---
import Base from '../../layouts/Base.astro';
import { ymd } from '../../lib/format';
import { mediaHtml } from '../../lib/media';
import { fixBody, textOf, abs, AUTHOR, PUBLISHER, SITE, SITE_NAME } from '../../lib/seo';
import { SERIES_PAGES, allCards, withDeepDiveThumb } from '../../lib/records';
import RecordCard from '../../components/RecordCard.astro';
import { getCollection, render } from 'astro:content';
import { existsSync, readFileSync } from 'node:fs';
import aiConsult from '../../data/aiConsult.json';
import { linkifyHtml, emphasizeHtml, linkLogsHtml } from '../../lib/links.mjs';
import { roomyTables } from '../../lib/tables.mjs';
import { fullTextPath } from '../../lib/fulltext';
import emphasis from '../../data/emphasis.json';

// 記事ページは2種類から作る
// - Studio から移した記事：CMS の本文 HTML（src/data/records/。tools/import_all.py で作る）
// - これから Claude が書く記事：Markdown（src/content/records/）
export async function getStaticPaths() {
  const studio = Object.values(import.meta.glob('../../data/records/*.json', { eager: true, import: 'default' })) as any[];
  const md = await getCollection('records');
  return [
    ...studio.map((r) => ({ params: { slug: r.slug }, props: { kind: 'studio', rec: r } })),
    ...md.map((e) => ({ params: { slug: e.data.slug }, props: { kind: 'md', entry: e } })),
  ];
}
const { kind, entry, rec } = Astro.props;
const esc = (s) => s.replace(/&/g, '&amp;').replace(/</g, '&lt;');
const tocOf = (items) => items.length ? `<nav class="toc"><ul>${items.map((t) => `<li><a href="#${t.id}">${esc(t.text)}</a></li>`).join('')}</ul></nav>` : '';

let r, body = '', Content = null, toc = '';
if (kind === 'studio') {
  r = rec;
  body = fixBody(mediaHtml(rec.body.replace('<!--TOC-->', tocOf(rec.toc))), rec.title);
} else {
  const d = entry.data;
  r = { title: d.title, description: d.meta_description || '', slug: d.slug, day: `LOG${d.day}`, series: d.series,
        published: d.date.toISOString(), cover: d.cover ?? null, thumb: d.cover ? null : `/thumbs/LOG${d.day}.html`, tags: d.tags || [] };
  const rendered = await render(entry);
  Content = rendered.Content;
  toc = tocOf(rendered.headings.filter((h) => h.depth === 2).map((h) => ({ id: h.slug, text: h.text })));
  // 目次は最初の見出しの直前に置く（冒頭の書き出しを先に読んでもらうため。article_rules.md「冒頭の書き出し」）
  const html = entry.rendered?.html;
  if (html) {
    const at = toc ? html.search(/<h2[\s>]/) : -1;
    body = !toc ? html : at >= 0 ? html.slice(0, at) + toc + html.slice(at) : toc + html;
  }
}
// 本文の URL・サイトの住所をリンクにする（中の住所は同じタブ、外のサイトは新しいタブ。src/lib/links.mjs）
// 記事のいちばん大事な一文を太字にする（src/data/emphasis.json）
// 本文の記事番号（LOG334 など）を、その記事へのリンクにする（同じ記事へは最初の1か所だけ・自分へはつながない）。
// 古い呼び方の DAY200 は LOG200 に直す。LOG343（DAY から LOG への呼び方の変更を書いた記事）は DAY を例として書いているので直さない
const LOGS = Object.fromEntries((await allCards()).map((c) => [c.day, c.slug]));
const isEnglish = /-en$/.test(r.slug);
body = linkifyHtml(linkLogsHtml(emphasizeHtml(body, (emphasis as Record<string, string[]>)[r.slug] || []), LOGS, r.slug, isEnglish, r.slug !== 'studio-shutdown-cloudflare-redirect', r.slug === 'day39'));
// 表を窮屈にしない（本文より広い表は横スクロール・短いセルは折り返さない・長いセルは最小の幅を持たせる。src/lib/tables.mjs）
body = roomyTables(body);

// 検索エンジン向けの情報
// 考察記事は、問いを文字で組んだサムネイルを記事の上と SNS の画像にも使う（Gemini の絵より優先）
r = withDeepDiveThumb(r);
// SNS・検索結果に出す画像：表紙の画像 → サムネイルを書き出した画像（public/og/。tools/render_og.py）→ サイト共通の画像
const ogFromThumb = r.thumb ? r.thumb.replace(/^\/thumbs\//, '/og/').replace(/\.html$/, '.jpg') : null;
const ogImage = r.cover || (ogFromThumb && existsSync(`public${ogFromThumb}`) ? ogFromThumb : undefined);
const isEn = rec?.lang === 'EN' || /-en$/.test(r.slug);
const description = r.description || textOf(kind === 'studio' ? rec.body : entry.body, 120);
const allSlugs = new Set([
  ...Object.values(import.meta.glob('../../data/records/*.json', { eager: true, import: 'default' })).map((x: any) => x.slug),
  ...(await getCollection('records')).map((e) => e.data.slug),
]);
const pair = isEn ? r.slug.replace(/-en$/, '') : `${r.slug}-en`;
const alternates = allSlugs.has(pair) ? [{ lang: isEn ? 'ja' : 'en', path: `/record/${pair}` }] : [];
// 英語の考察記事（Consideration）は、考察記事のシリーズページ（series5）につなぐ
const seriesKey = r.series === 'Consideration' ? '考察記事' : r.series;
const seriesPage = Object.entries(SERIES_PAGES).find(([, c]) => c.series === seriesKey);
const seriesName = seriesPage ? seriesPage[1].title.split(' | ').pop() : r.series;

// 同じシリーズの記事（一覧と同じく LOG 番号の大きい順）。前後の記事と、この記事の近くの6本
const sameSeries = (await allCards()).filter((c) => c.series === r.series);
const pos = sameSeries.findIndex((c) => c.slug === r.slug);
const newer = pos > 0 ? sameSeries[pos - 1] : null;
const older = pos >= 0 && pos < sameSeries.length - 1 ? sameSeries[pos + 1] : null;
const others = sameSeries.filter((c) => c.slug !== r.slug);
const start = Math.max(0, Math.min((pos < 0 ? 0 : pos) - 3, others.length - 6));
const nearby = others.slice(start, start + 6);
// AI活用の記事（研究を回すために作った仕組みの記録）の末尾に、相談の案内を出す。
// Markdown の記事は先頭の項目 ai_consult: true、Studio から移した記事は src/data/aiConsult.json の slug で決める
const showConsult = kind === 'md' ? entry.data.ai_consult === true : (aiConsult as string[]).includes(r.slug);
const url = `${SITE}/record/${r.slug}`;
// 研究の記録の末尾から、相談のページ（/consult）へ（MASTER v2.1・2026-10-28）。AI活用の相談の案内が出る記事には出さない
const WORK_NOTE = {
  'フィジカルAI研究室': 'この記録の学習環境を、自社でも立ち上げたい方へ。借りて測るところから、一緒に手を動かして進めます。',
  '導入シミュレーション': '自店や提案先の店で、ロボットを入れる前に確かめたい方へ。店の形と人数から、一緒に手を動かして試算します。',
};
const workNote = !showConsult && !isEnglish ? WORK_NOTE[r.series] : null;
// 長いコード・ログ・データを外に出した記事（LOG364 から。tools/split_files.py）は、
// 全文（Markdown。Studio から移した記事は HTML）を head に、ファイルの一覧を構造化データ（hasPart）に出して、AI がたどれるようにする
const filesIndexPath = `public/files/${r.day}/index.json`;
const filesIndex = existsSync(filesIndexPath) ? JSON.parse(readFileSync(filesIndexPath, 'utf-8')) : null;
// 記事の素材（分けたファイル）への入口（2026-10-28）。本文の「ファイル一式」の枠に id="files" を付け、タイトルの下から飛べるようにする
if (filesIndex) body = body.replace('<div class="filebox fb-all">', '<div class="filebox fb-all" id="files">');
const filesCount = filesIndex && body.includes('id="files"') ? filesIndex.files.length : 0;
// 古い記録から今の話への道（2026-10-28）。検索から古い記録に着いた人が、そこで止まらないように。
// 公開から60日より前の記録に出す。同じシリーズに60日以内の記録があればその最新へ（「{シリーズ名}の最新」）、なければサイト全体の最新の記録へ（「いま進めている記録」）
const DAY_MS = 864e5, OLD_DAYS = 60;
const fresh = (c) => Date.now() - new Date(c.published).getTime() < OLD_DAYS * DAY_MS;
const isOld = !isEnglish && Date.now() - new Date(r.published).getTime() >= OLD_DAYS * DAY_MS;
const seriesNow = isOld ? (await allCards()).find((c) => c.series === r.series && c.slug !== r.slug && !/-en$/.test(c.slug) && fresh(c)) : null;
const siteNow = isOld && !seriesNow ? (await allCards()).find((c) => !/-en$/.test(c.slug) && c.slug !== r.slug) : null;
const nowCard = seriesNow || siteNow;
// AIで読む（2026-11-01）：読み手が使っている AI を開き、この記事の全文（/record/{slug}.md。src/lib/fulltext.ts）を読ませる質問を入れた状態にする。
// ChatGPT・Claude・Perplexity は住所の後ろの q= で入力欄に入る。Gemini は入れる方法がないので、質問文をコピーしてから開く。
// 前回選んだ AI を見た人のブラウザに覚え（p2m-site-ai）、次から一番上に出す。押した数は /api/ev-stats（場所 ai）で見る
const mdUrl = `${SITE}${fullTextPath(r.slug)}`;
// 全文ページの住所に ?via=（どの AI から来たか）を付ける。AI が読みに来たとき、ボタン経由だと worker/index.js が分けて数える（/api/cr-stats の「AIで読む経由」）
const aiPromptFor = (via) => {
  const md = `${mdUrl}?via=${via}`;
  return isEnglish
    ? `Please read this article and answer my questions.\nArticle: ${url}\nFull text (including code and logs): ${md}\n\nFirst, briefly summarize what was done and what was found. Then tell me what I should check to reproduce it in my own environment.`
    : `この記事を読んで、質問に答えてください。\n記事: ${url}\n全文（コード・実行ログを含む）: ${md}\n\nまず、何をして何が分かったのかを短くまとめてください。そのあと、私が同じことを自分の環境で試すときに、確かめることを教えてください。`;
};
const aiQ = (via) => encodeURIComponent(aiPromptFor(via));
const AI_TARGETS = [
  { id: 'chatgpt', name: 'ChatGPT', href: `https://chatgpt.com/?q=${aiQ('chatgpt')}` },
  { id: 'claude', name: 'Claude', href: `https://claude.ai/new?q=${aiQ('claude')}` },
  { id: 'perplexity', name: 'Perplexity', href: `https://www.perplexity.ai/search?q=${aiQ('perplexity')}` },
  { id: 'gemini', name: 'Gemini', href: 'https://gemini.google.com/app', copy: aiPromptFor('gemini') },
];
const aiCopyPrompt = aiPromptFor('copy');
const ym = (iso) => { const d = new Date(new Date(iso).getTime() + 9 * 3600e3); return `${d.getUTCFullYear()}年${d.getUTCMonth() + 1}月`; };
const codeLang = { csharp: 'C#', json: 'JSON', yaml: 'YAML', markdown: 'Markdown', python: 'Python', text: 'Text' };
const jsonLd = [
  {
    '@context': 'https://schema.org', '@type': 'BlogPosting',
    headline: r.title, description, url, mainEntityOfPage: url,
    inLanguage: isEn ? 'en' : 'ja',
    datePublished: r.published, dateModified: rec?.updated || r.published,
    image: ogImage ? [abs(ogImage)] : undefined,
    articleSection: r.series, keywords: r.tags?.length ? r.tags.join(', ') : undefined,
    author: AUTHOR, publisher: PUBLISHER,
    isPartOf: { '@type': 'Blog', name: SITE_NAME, url: SITE + '/' },
    hasPart: filesIndex ? [
      { '@type': 'DigitalDocument', name: `${r.day} 記事の全文（${filesIndex.article_full_type === 'text/html' ? 'HTML' : 'Markdown'}）`, encodingFormat: filesIndex.article_full_type || 'text/markdown', url: abs(filesIndex.article_full || filesIndex.article_full_markdown) },
      ...filesIndex.files.map((f) => f.lang === 'csharp' || f.lang === 'yaml' || /\.(cs|prefab|asset|mat)\.txt$/.test(f.file)
        ? { '@type': 'SoftwareSourceCode', name: f.subheading || f.heading || f.file, programmingLanguage: codeLang[f.lang] || f.lang, url: abs(f.url) }
        : { '@type': 'Dataset', name: [f.heading, f.subheading].filter(Boolean).join(' / ') || f.file, url: abs(f.url) }),
    ] : undefined,
  },
  {
    '@context': 'https://schema.org', '@type': 'BreadcrumbList',
    itemListElement: [
      { '@type': 'ListItem', position: 1, name: SITE_NAME, item: SITE + '/' },
      ...(seriesPage ? [{ '@type': 'ListItem', position: 2, name: seriesPage[1].title, item: `${SITE}/${seriesPage[0]}` }] : []),
      { '@type': 'ListItem', position: seriesPage ? 3 : 2, name: r.title, item: url },
    ],
  },
];
---
<Base title={`${r.title} | ${SITE_NAME}`} description={description} ogImage={ogImage} path={`/record/${r.slug}`}
  lang={isEn ? 'en' : 'ja'} ogType="article" published={r.published} modified={rec?.updated || r.published}
  alternates={alternates} jsonLd={jsonLd}>
  <link slot="head" rel="alternate" type="text/markdown" title={isEnglish ? 'Full text (including code and logs)' : '記事の全文（コードとログを含む）'} href={fullTextPath(r.slug)} />
  <article class="rec">
    <nav class="crumbs" aria-label="現在地">
      <a href="/">{SITE_NAME}</a><span aria-hidden="true">›</span>{seriesPage ? <a href={`/${seriesPage[0]}`}>{seriesName}</a> : <span>{seriesName}</span>}
    </nav>
    {r.thumb ? <div class="hero"><iframe src={r.thumb} sandbox="allow-scripts" title={r.title} scrolling="no" tabindex="-1"></iframe></div> : r.cover && <img class="hero-img" src={r.cover} alt={r.title} width="1920" height="1080" />}
    <div class="meta"><span class="day">{r.day}</span>{seriesPage ? <a class="tag" href={`/${seriesPage[0]}`}>{r.series}</a> : <span class="tag">{r.series}</span>}<span class="date">{ymd(r.published)}</span></div>
    <h1 class="title">{r.title}</h1>
    {nowCard && (
      <a class="now-bar" href={`/record/${nowCard.slug}`} data-ev="今の続き" data-ev-area="now">
        <span class="nb-k">{ym(r.published)}の記録です。{seriesNow ? `${seriesName}の最新` : 'いま進めている記録'}</span>
        <span class="nb-t">{nowCard.day}　{nowCard.title}</span><i class="ms" aria-hidden="true">arrow_forward</i>
      </a>
    )}
    {filesCount > 0 && (
      <a class="files-jump" href="#files" data-ev="素材" data-ev-area="files">この記事の素材（コード・ログ・データ {filesCount}ファイル・まとめてダウンロードあり）<i class="ms" aria-hidden="true">arrow_downward</i></a>
    )}
    <details class="ai-read" data-ev-area="ai">
      <summary data-ev="AIで読む:開く">
        <span class="ar-t">{isEnglish ? 'Read with AI' : 'AIで読む'}</span>
        <span class="ar-s">{isEnglish ? 'Ask ChatGPT, Claude and others about this article' : 'ChatGPT・Claude などで、この記事に質問できます'}</span>
        <i class="ms" aria-hidden="true">expand_more</i>
      </summary>
      <ul class="ar-list">
        {AI_TARGETS.map((a) => (
          <li data-ai={a.id}>
            <a href={a.href} target="_blank" rel="noopener" data-ev="AIで読む" data-copy={a.copy || undefined}>
              {isEnglish ? `Open in ${a.name}` : `${a.name}で開く`}{a.copy && <span class="ar-n">{isEnglish ? '(copies the question; paste it)' : '（質問文をコピーして開きます。貼り付けてください）'}</span>}<i class="ms" aria-hidden="true">open_in_new</i>
            </a>
          </li>
        ))}
        <li data-ai="copy">
          <button type="button" data-ev="AIで読む:コピー" data-copy={aiCopyPrompt}>{isEnglish ? 'Copy the question (for other AI)' : '質問文をコピー（ほかの AI で使う）'}<i class="ms" aria-hidden="true">content_copy</i></button>
        </li>
      </ul>
      <p class="ar-msg" role="status" aria-live="polite"></p>
      <textarea class="ar-prompt" hidden readonly></textarea>
    </details>
    {kind === 'studio'
      ? <div class="richText" set:html={body} />
      : body ? <div class="richText" set:html={body} /> : <div class="richText"><Fragment set:html={toc} /><Content /></div>}
    {showConsult && (
      <aside class="consult" aria-label="ご相談について">
        <p>この記事の仕組みは、フィジカルAIの研究をひとりで回すために作ったものです。同じような仕組みを仕事に取り入れたい方は、メッセージからご相談ください。</p>
        <a class="btn-dark" href="/message?topic=ai">メッセージで相談する<i class="ms">arrow_forward</i></a>
      </aside>
    )}
    {workNote && (
      <aside class="consult" aria-label="仕事でのご相談について" data-ev-area="consult">
        <p>{workNote}</p>
        <a class="btn-dark" href="/consult" data-ev="記録の末尾:相談について">一緒に進め方を見る<i class="ms">arrow_forward</i></a>
      </aside>
    )}
    {(newer || older) && (
      <nav class="pager" aria-label="同じシリーズの前後の記事">
        {older ? <a class="pg pg-prev" href={`/record/${older.slug}`}><span class="pg-k">← 前の記事　{older.day}</span><span class="pg-t">{older.title}</span></a> : <span />}
        {newer ? <a class="pg pg-next" href={`/record/${newer.slug}`}><span class="pg-k">次の記事　{newer.day} →</span><span class="pg-t">{newer.title}</span></a> : <span />}
      </nav>
    )}
  </article>
  {nearby.length > 0 && (
    <section class="related">
      <div class="panel">
        <h2 class="sec-h">{seriesName}の記録 | Series</h2>
        <div class="grid3">{nearby.map((c) => <RecordCard item={c} />)}</div>
        {seriesPage && <div class="rel-more"><a class="btn-dark" href={`/${seriesPage[0]}`}>{seriesName}の記録をすべて見る（{sameSeries.length}本）<i class="ms">arrow_forward</i></a></div>}
      </div>
    </section>
  )}
  <script is:inline>
    // AIで読む：前回選んだ AI を一番上に出す・Gemini とコピーは質問文をコピーする（覚えられない環境でも動く）
    (() => {
      const box = document.querySelector('.ai-read');
      if (!box) return;
      const KEY = 'p2m-site-ai';
      const list = box.querySelector('.ar-list'), msg = box.querySelector('.ar-msg');
      const en = document.documentElement.lang === 'en';
      try {
        const last = localStorage.getItem(KEY);
        const li = last && list.querySelector(`li[data-ai="${last}"]`);
        if (li && li !== list.firstElementChild) list.prepend(li);
        if (li) li.classList.add('ar-last');
      } catch { /* 覚えられない環境ではいつもの並び */ }
      const copy = async (prompt) => {
        try { await navigator.clipboard.writeText(prompt); return true; } catch { return false; }
      };
      box.addEventListener('click', async (e) => {
        const li = e.target.closest && e.target.closest('li[data-ai]');
        if (!li) return;
        try { localStorage.setItem(KEY, li.dataset.ai); } catch { /* 覚えられなくても開く */ }
        const a = e.target.closest('[data-copy]');
        if (!a) return;
        const ok = await copy(a.dataset.copy);
        msg.textContent = ok
          ? (en ? 'Copied the question. Paste it into the AI.' : '質問文をコピーしました。AI の入力欄に貼り付けてください。')
          : (en ? 'Could not copy. Please copy the question below.' : 'コピーできませんでした。下の質問文を選んでコピーしてください。');
        if (!ok) { const t = box.querySelector('.ar-prompt'); t.value = a.dataset.copy; t.hidden = false; t.select(); }
      });
    })();
  </script>
  {filesIndex && (
    <script is:inline>
      // 長いコード・ログの枠：押したときだけ全文を読み込んで、先頭の数行の所に出す（LOG364 から）
      document.addEventListener('click', async (e) => {
        const b = e.target.closest && e.target.closest('.filebox .fb-open');
        if (!b) return;
        const box = b.closest('.filebox'), code = box.querySelector('.fb-pre code');
        if (box.dataset.full === '1') { box.classList.toggle('fb-closed'); b.textContent = box.classList.contains('fb-closed') ? '全文をここに表示' : 'たたむ'; return; }
        b.disabled = true; b.textContent = '読み込み中…';
        try {
          const res = await fetch(box.dataset.src);
          code.textContent = await res.text();
          box.dataset.full = '1'; box.classList.add('fb-full'); b.textContent = 'たたむ';
        } catch (err) { b.textContent = '読み込めませんでした（別のタブで開いてください）'; }
        b.disabled = false;
      });
    </script>
  )}
</Base>

<style is:global>
  .rec { width: 620px; max-width: calc(100% - 48px); margin: 0 auto; padding: 104px 0 64px; }
  .rec .hero { aspect-ratio: 16 / 9; position: relative; border-radius: 4px; overflow: hidden; background: #0d0d0d; }
  .rec .hero iframe { position: absolute; inset: 0; width: 100%; height: 100%; border: 0; pointer-events: none; }
  .rec .hero-img { width: 100%; height: auto; aspect-ratio: 16 / 9; object-fit: cover; border-radius: 4px; display: block; }
  .rec .crumbs { display: flex; flex-wrap: wrap; align-items: center; gap: 4px 8px; margin: 0 0 16px; font: 400 13px/20px var(--sans); color: var(--ink-3); }
  .rec .crumbs a { color: var(--ink-2); text-decoration: underline; text-underline-offset: 2px; }
  .rec .crumbs a:hover { color: var(--ink); }
  .rec a.tag:hover { background: #e4e4e4; color: var(--ink-2); }
  .pager { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; margin: 64px 0 0; padding-top: 32px; border-top: 1px solid #ddd; }
  .pg { display: flex; flex-direction: column; gap: 8px; padding: 16px; border-radius: 8px; background: #fff; box-shadow: 0 8px 16px rgba(0,0,0,.08); }
  .pg:hover .pg-t { text-decoration: underline; }
  .pg-next .pg-k { text-align: right; }
  .pg-k { font: 400 12px/18px var(--sans); color: var(--ink-3); }
  .pg-t { font: 600 14px/1.6 var(--sans); color: var(--ink); overflow-wrap: anywhere; }
  .rel-more { display: flex; justify-content: center; margin-top: 40px; }
  .rel-more .btn-dark { height: auto; min-height: 40px; padding: 8px 20px; white-space: normal; text-align: center; }
  @media (max-width: 599px) { .pager { grid-template-columns: 1fr; } .pg-next .pg-k { text-align: left; } }
  .consult { margin: 56px 0 0; padding: 24px; border-radius: 8px; background: #f0f0f0; }
  .consult p { margin: 0 0 16px; font: 400 16px/28px var(--sans); color: var(--ink); }
  .consult .btn-dark { height: auto; min-height: 40px; padding: 8px 20px; white-space: normal; }
  .rec .meta { display: flex; align-items: center; gap: 12px; margin-top: 16px; }
  .rec .day { font: 400 28px/28px var(--sans); }
  .rec .title { margin: 12px 0 40px; font: 600 28px/42px var(--sans); overflow-wrap: anywhere; }
  .rec .title:has(+ .now-bar), .rec .title:has(+ .files-jump) { margin-bottom: 24px; }
  .now-bar { display: grid; grid-template-columns: minmax(0, 1fr) auto; align-items: center; gap: 2px 12px; margin: 0 0 24px; padding: 12px 16px; border-radius: 4px; background: var(--ink); color: #fff; text-decoration: none; }
  .now-bar .nb-k { grid-column: 1; font: 400 13px/20px var(--sans); color: rgba(255,255,255,.72); }
  .now-bar .nb-t { grid-column: 1; font: 600 15px/24px var(--sans); overflow-wrap: anywhere; }
  .now-bar .ms { grid-column: 2; grid-row: 1 / span 2; font-size: 20px; transition: transform .2s ease; }
  .now-bar:hover .ms { transform: translateX(3px); }
  .files-jump { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin: 0 0 24px; padding: 12px 16px; border-radius: 4px; background: #f0f0f0; color: var(--ink); font: 600 14px/22px var(--sans); text-decoration: none; }
  .files-jump .ms { font-size: 20px; flex: none; }
  .files-jump:hover { background: #e6e6e6; }
  #files { scroll-margin-top: 88px; }
  /* AIで読む（2026-11-01）。形は「この記事の素材」と同じ灰色の帯。押すと AI の一覧が開く */
  .rec .title:has(+ .ai-read) { margin-bottom: 24px; }
  .ai-read { margin: 0 0 32px; border-radius: 4px; background: #f0f0f0; color: var(--ink); }
  .ai-read summary { display: grid; grid-template-columns: minmax(0, 1fr) auto; align-items: center; gap: 0 12px; padding: 12px 16px; cursor: pointer; list-style: none; border-radius: 4px; }
  .ai-read summary::-webkit-details-marker { display: none; }
  .ai-read summary:hover { background: #e6e6e6; }
  .ai-read .ar-t { grid-column: 1; font: 600 14px/22px var(--sans); }
  .ai-read .ar-s { grid-column: 1; font: 400 13px/20px var(--sans); color: var(--ink-2); }
  .ai-read summary .ms { grid-column: 2; grid-row: 1 / span 2; font-size: 20px; transition: transform .2s ease; }
  .ai-read[open] summary .ms { transform: rotate(180deg); }
  .ai-read .ar-list { list-style: none; margin: 0; padding: 4px 8px 8px; display: grid; gap: 4px; }
  .ai-read .ar-list a, .ai-read .ar-list button { display: flex; align-items: center; gap: 8px; width: 100%; padding: 10px 12px; border: 0; border-radius: 4px; background: #fff; color: var(--ink); font: 600 14px/22px var(--sans); text-align: left; text-decoration: none; cursor: pointer; }
  .ai-read .ar-list a:hover, .ai-read .ar-list button:hover { background: var(--ink); color: #fff; }
  .ai-read .ar-list .ms { font-size: 18px; flex: none; margin-left: auto; }
  .ai-read .ar-n { font: 400 12px/18px var(--sans); color: inherit; opacity: .7; }
  .ai-read .ar-last a::before { content: "前回"; flex: none; padding: 0 6px; border: 1px solid currentColor; border-radius: 4px; font: 400 11px/18px var(--sans); }
  html[lang="en"] .ai-read .ar-last a::before { content: "Last used"; }
  .ai-read .ar-msg { margin: 0; padding: 0 16px; font: 400 13px/20px var(--sans); color: var(--ink-2); }
  .ai-read .ar-msg:not(:empty) { padding-bottom: 12px; }
  .ai-read .ar-prompt { display: block; width: calc(100% - 32px); margin: 0 16px 12px; min-height: 120px; font: 400 13px/20px var(--sans); }
  .ai-read .ar-prompt[hidden] { display: none; }

  .richText { color: var(--ink); font-family: var(--sans); }
  .richText p { font-size: 18px; line-height: 36px; margin: 8px 0 12px; overflow-wrap: anywhere; }
  .richText h2 { font-size: 24px; line-height: 36px; font-weight: 600; margin: 40px 0 16px; }
  .richText h3 { font-size: 20px; line-height: 30px; font-weight: 600; margin: 36px 0 16px; padding-left: 12px; border-left: 2px solid var(--ink); }
  .richText h4 { font-size: 18px; line-height: 30px; font-weight: 600; margin: 24px 0 8px; }
  .richText h5 { font-size: 14px; line-height: 1.8; font-weight: 400; color: #555; margin: 48px 0 0; }
  .richText hr { border: 0; border-top: 1px solid #ddd; margin: 10px 0; }
  .richText strong { font-weight: 600; }
  .richText a { color: inherit; text-decoration: underline; }
  .richText pre { background: #eee; border-radius: 4px; padding: 25px 35px; margin: 4px 0; overflow-x: auto;
    font: 400 16px/24px var(--mono); white-space: pre-wrap; overflow-wrap: anywhere; }
  .richText pre code { font: inherit; }
  .richText :not(pre) > code { background: #eee; padding: 0 5px; margin: 2px; border: 1px solid rgba(0,0,0,.1); border-radius: 6px; font: 400 18px/36px var(--sans); overflow-wrap: anywhere; }
  .richText blockquote { margin: 10px 0; padding: 10px 15px; border-left: 3px solid rgba(0,0,0,.15); color: #333; }
  .richText blockquote p { font: 400 16px/28.8px 'Noto Sans JP', sans-serif; margin: 10px 0; }
  .richText figure { margin: 16px 0; }
  .richText figure img { width: 100%; height: auto; }
  .richText figcaption:empty { display: none; }
  .richText ul, .richText ol { padding-left: 1.5em; }
  .richText li { font-size: 18px; line-height: 36px; margin: 10px 0; }
  /* 表は本文の幅に押し込まず、はみ出す分は横にスクロールする（縦に1文字ずつ折り返して読めなくなっていた。LOG355） */
  .richText .table { margin: 10px 0; overflow-x: auto; -webkit-overflow-scrolling: touch; }
  .richText table { border-collapse: collapse; width: auto; min-width: 100%; font-size: 14px; }
  /* 表は見出しの行を少し濃く、線をはっきり、本文の行は白に（ページの地色と見分けられるように） */
  .richText th, .richText td { border: 1px solid #d2d2d2; vertical-align: top; text-align: left; }
  .richText th { background: #e9e9e9; padding: 24px; font: 600 16px/24px var(--sans); }
  .richText td { background: #fff; padding: 16px; font: 400 18px/27px var(--sans); }
  .richText th p, .richText td p { margin: 0; font: inherit; }
  .richText .embed { margin: 16px 0; }
  .richText .embed iframe { display: block; width: 100%; height: 150px; border: 0; }
  .richText .video { margin: 16px 0; }
  .richText .video video { display: block; width: 100%; height: auto; }
  .richText .yt { aspect-ratio: 16 / 9; margin: 16px 0; }
  /* 長いコード・ログの枠（LOG364 から。tools/split_files.py） */
  /* 記事の中のバナー（【バナー】gpu-buy-or-rent。src/lib/remark-markers.mjs）。色と背景の写真はトップのカード（GuideCards）と同じ、大きさは記事用に小さく */
  .richText a.art-bn { position: relative; isolation: isolate; overflow: hidden; display: grid; grid-template-columns: auto minmax(0, 1fr) auto; align-items: center; gap: 12px 20px;
    margin: 20px 0 28px; padding: 18px 20px; border-radius: 8px; box-shadow: 0 8px 16px rgba(0,0,0,.08); background: #0b0c0e; color: #fff; text-decoration: none; overflow-wrap: normal; word-break: normal; }
  .richText a.art-bn::before { content: ""; position: absolute; inset: 0; z-index: -2; background: url(/site/gbr/reverse.webp) right center / cover no-repeat; transition: transform .6s ease; }
  .richText a.art-bn::after { content: ""; position: absolute; inset: 0; z-index: -1; background: linear-gradient(90deg, rgba(11,12,14,.95) 0%, rgba(11,12,14,.86) 55%, rgba(11,12,14,.55) 100%); }
  .richText a.art-bn:hover::before { transform: scale(1.03); }
  .richText .art-bn .ab-figs { display: grid; gap: 2px; font-family: 'Manrope', 'Inter', 'Noto Sans JP', sans-serif; font-variant-numeric: tabular-nums; line-height: 1.1; text-align: right; }
  .richText .art-bn .ab-buy { font-size: 13px; font-weight: 800; color: #8d939d; text-decoration: line-through; text-decoration-thickness: 2px; }
  .richText .art-bn .ab-rent { font-size: 22px; font-weight: 800; color: #f2600c; letter-spacing: -.01em; }
  .richText .art-bn .ab-text { display: grid; gap: 2px; min-width: 0; word-break: auto-phrase; }
  .richText .art-bn .ab-label { font: 700 11px/16px var(--sans); color: #b9bec7; letter-spacing: .08em; }
  .richText .art-bn .ab-title { font: 900 15px/1.5 var(--sans); color: #fff; }
  .richText .art-bn .ab-sub { font: 400 12px/1.6 var(--sans); color: #c8ccd4; }
  .richText .art-bn .ab-go { display: inline-grid; place-items: center; width: 36px; height: 36px; border-radius: 4px; background: #f2600c; color: #fff; font-size: 18px; transition: background .15s, color .15s; }
  .richText a.art-bn:hover .ab-go { background: #fff; color: #121418; }
  /* 配膳ロボットの導入前シミュレーション：トップのカードと同じ明るい面と店の立体の絵（左に小さく） */
  .richText a.art-bn-light { background: #eceef1; color: #121418; box-shadow: 0 8px 16px rgba(0,0,0,.08); }
  .richText a.art-bn-light::before, .richText a.art-bn-light::after { content: none; }
  .richText .art-bn-light .ab-img { display: block; width: 112px; overflow: hidden; }
  .richText .art-bn-light .ab-img img { display: block; width: 100%; height: auto; transition: transform .6s ease; }
  .richText a.art-bn-light:hover .ab-img img { transform: scale(1.04); }
  .richText .art-bn-light .ab-label { color: #5c616b; }
  .richText .art-bn-light .ab-title { color: #121418; }
  .richText .art-bn-light .ab-sub { color: #4a4f58; }
  .richText a.art-bn-light:hover .ab-go { background: #121418; color: #fff; }
  @media (max-width: 479px) {
    .richText a.art-bn { grid-template-columns: minmax(0, 1fr) auto; }
    .richText a.art-bn::after { background: rgba(11,12,14,.84); }
    .richText .art-bn .ab-figs { grid-column: 1 / -1; grid-auto-flow: column; justify-content: start; align-items: baseline; gap: 10px; text-align: left; }
    .richText .art-bn-light .ab-img { grid-column: 1 / -1; width: 140px; }
  }
  .richText .filebox { margin: 8px 0 16px; border: 1px solid #d2d2d2; border-radius: 4px; background: #fff; }
  .richText .fb-head { display: flex; flex-wrap: wrap; align-items: baseline; gap: 4px 12px; padding: 12px 16px; border-bottom: 1px solid #e4e4e4; }
  .richText .fb-name { font: 600 14px/20px var(--mono); overflow-wrap: anywhere; text-decoration: none; border-bottom: 1px solid currentColor; }
  .richText .fb-meta { font: 400 13px/20px var(--sans); color: #666; }
  .richText .filebox .fb-pre { margin: 0; border-radius: 0; padding: 12px 16px; font-size: 13px; line-height: 20px; max-height: 180px; overflow: hidden; position: relative; }
  .richText .filebox.fb-full .fb-pre { max-height: 70vh; overflow: auto; }
  .richText .filebox.fb-full.fb-closed .fb-pre { max-height: 180px; overflow: hidden; }
  .richText .fb-act { display: flex; flex-wrap: wrap; gap: 8px 16px; align-items: center; padding: 10px 16px; border-top: 1px solid #e4e4e4; font: 400 14px/20px var(--sans); }
  .richText .fb-open { font: 600 14px/20px var(--sans); padding: 6px 12px; border: 1px solid var(--ink); border-radius: 4px; background: var(--ink); color: #fff; cursor: pointer; }
  .richText .fb-open:disabled { opacity: .6; cursor: default; }
  .richText .fb-all { padding: 8px 16px; background: #f6f6f6; }
  .richText .fb-all p { font-size: 16px; line-height: 28px; margin: 8px 0; }
  .richText .fb-all li { font-size: 16px; line-height: 28px; margin: 4px 0; }
  .richText .fb-all .fb-lh { font: 600 16px/28px var(--sans); margin: 16px 0 4px; }
  .richText .fb-list .fb-li { font-size: 14px; line-height: 22px; margin: 2px 0; overflow-wrap: anywhere; }
  .richText .yt iframe { width: 100%; height: 100%; border: 0; }

  .toc { background: #f0f0f0; border-radius: 4px; padding: 20px 20px 8px 8px; margin: 0 0 24px; }
  .toc ul { list-style: none; margin: 0; padding: 0; }
  .toc li { position: relative; padding-left: 32px; margin: 0 0 16px; font: 400 16px/32px var(--sans); color: var(--ink-2); }
  .toc a { text-decoration: none; color: inherit; }

  /* 本文より広いもの（表・画像）が画面の幅を押し広げないようにする */
  .richText { max-width: 100%; min-width: 0; }
  .richText a, .richText code, .richText li, .richText td, .richText th, .richText blockquote, .richText h2, .richText h3 { overflow-wrap: anywhere; word-break: break-word; }
  /* 表の中は、英数字の語（ファイル名・数値）を途中で折らない。見出しの行は日本語も途中で折らない */
  .richText td, .richText td code, .richText td a, .richText th, .richText th code { overflow-wrap: normal; word-break: normal; }
  .richText .nw, .richText .nw * { white-space: nowrap; }
  .richText .cw { min-width: 9em; }
  .richText pre code { white-space: pre-wrap; }
  .richText img { max-width: 100%; height: auto; }
  @media (max-width: 767px) {
    .rec { max-width: calc(100% - 48px); padding-top: 72px; }
    .rec .title { font-size: 24px; line-height: 36px; }
  }
</style>
