# cti.li の本のテンプレ — AI への指示（チャットで作るとき）

あなた（Claude や ChatGPT のチャットの AI）は、利用者が添付したこのテンプレ（EPUB）と原稿から、本（EPUB）を作って返します。利用者は技術に詳しくない前提です。**技術的なことは聞き返さず、自分で決めて最後まで進めてください。** 聞くのは、利用者でないと決められない本の中身（著者名・何を書くか・誰に向けた本か など）だけにします。

**この指示書は、要約せずに全部読む。** EPUB は ZIP なので、Python の `zipfile` で作業用のフォルダに広げて読む。コードを動かせないとき・ファイルを渡せないときは、利用者に「ファイルを作れる設定にしてください（Claude なら設定（Settings）の Capabilities で Code execution and file creation をオンにする）」と伝える。

できあがるのは、版面（頁の作り）まで決めてある本です。cti.li の読み器（https://cti.li/contents/reader.html）で、テンプレの CSS のとおりに組まれます。CSS は組版エンジン Copper で組む前提で作ってあるので、**`style.css` と `css/cover.css` は、判型を変えるとき（4 の「判型」）のほかは直さない。** ブラウザや他の読書アプリでの見え方に合わせて直さない。

CSS の中の説明には、別の作り方（フォルダを cti.li に送って組む作り方）の言葉が出てくる。読み替える: `book.json` の `size` は content.opf の `jgs:format`、`book.json` の `writing` は変えない、`pages/00-cover.html` は `00-cover.xhtml`、`<!-- css: … -->` は頁の頭の `<link>`。「見本（`mode=screen` など）で確かめる」は、チャットではできないので気にしない。

## 1. テンプレの中身

```
mimetype                 （そのまま。ZIP の先頭に、圧縮せずに入れる）
AGENTS.md                この指示書（返す EPUB からは外す）
META-INF/container.xml   （そのまま）
EPUB/content.opf         題・著者・紹介文・判型と、ファイルの一覧（manifest）と並び（spine）
EPUB/nav.xhtml           読み器の目次
EPUB/style.css           全頁に当たる CSS（版面・書体・見出し・本文）
EPUB/css/cover.css       表紙の頁だけの CSS
EPUB/00-cover.xhtml      表紙
EPUB/01-contents.xhtml   目次の頁
EPUB/10-chapter1.xhtml … 章（1章を1ファイルに）
EPUB/99-colophon.xhtml   奥付
EPUB/images/sample.svg   見本の絵（使わなければ消す）
EPUB/print-cover/cover.xhtml  紙の本（ペーパーバック）の表紙。5 のコードが content.opf の題・著者・紹介文で書き直すので、手で直さない・消さない
```

テンプレは 2 つある。縦書き（小説・随筆など。新書判）と横書き（実用書・教科書・レシピなど。A5 判。囲み・番号付きの手順・表の見本入り）。添付されたテンプレの組み方向が頼みと合わないときは、作り始める前に、もう一方のテンプレ（https://cti.li/contents/book/ の作り方の頁の「チャットで作る」にある）を添付してもらう。組み方向をこちらで変えようとしない。

**テンプレの見本の文（「本の題」「著者の名前」「ここに本文を書きます」「節の見出し」「この本の紹介を一、二文で。」「副題（要らなければ消す）」など）は全部、本の中身に置き換えるか消す。** 見本の頁の書き方（ルビ・縦中横・傍点・脚注・絵・囲み・手順・表）は、原稿で使う所だけまねる。

## 2. 決めること

利用者の頼みと原稿を読み、次を決める。決まっていないものは妥当に選び、最後に報告する。

- 題・著者。**著者名（利用者の名前や筆名）が原稿にも頼みにも無ければ、始める前に聞く。**
- 章立て: 1章を1つのファイルにする（章は新しい頁から始まる）。**本文が 5,000 字くらいまでの短い本**（新書判で 10 頁ほど）は1つのファイルにまとめ、h1 は本の題にして、原稿の見出しを 1 段ずつ下げる（章は h2、節は h3。例: 原稿の「一　朝の坂道」は `<h2 id="s1">一　朝の坂道</h2>`）。目次の頁（01-contents）は短い本なら外してよい。
- 表紙: 文字だけの表紙（題・副題・著者）。利用者が表紙用の絵を渡したときだけ絵を使う（4 の「表紙」）。本文に入れる写真を勝手に表紙にしない。
- 紹介文: 原稿から一、二文で自分で書く。
- 奥付: 題・著者・発行日（本を作った日。縦書きは「二〇二六年十一月十日　初版」のように漢数字、横書きは「2026年11月10日　初版」）と、要るときだけ発行者・連絡先・出典。**原稿のファイル名、作業のメモ、利用者に確かめてほしいことは本に書かない**（利用者はそのまま公開することがある）。気になる所は最後に利用者に伝える。

## 3. 原稿の読み方

- **Word（.docx）**: `python-docx` で段落を順に読む（無ければ `word/document.xml` を ZIP として読む）。「表題」「見出し 1」「見出し 2」の段落は題・章・節。ルビ（`w:ruby`）は `<ruby>` に。表は `<table>` に。貼ってある写真は `word/media/` から取り出して `EPUB/images/` に写し、原稿の位置に `figure` で置く（すぐ次の段落が写真の説明なら `figcaption` にする）。番号付きの箇条書きは、横書きなら `<ol class="steps">`、ほかの箇条書きは `<ul>`。古い形式（.doc）は読めないので、利用者に Word で開いて「名前を付けて保存」で .docx にしてもらう。
- **テキスト**: 空行か改行で段落を分ける。行頭の全角の空白は字下げのしるしなので、段落にするときは消す（字下げは CSS が付ける）。全角の空白で下げた短い行や「一　…」のような行は見出し。「[写真1　説明]」のような印は、そこに絵を置き、説明を `figcaption` にする（「写真1」の番号は残さない）。
- 原稿の字は変えない（数字を漢数字にするなど、組むために直したところは報告する）。原稿の誤りらしい所（材料の表に無い材料が作り方に出てくる など）も直さず、最後に利用者に伝える。

## 4. 書き方

### 頁のファイル（XHTML）

テンプレの頁のファイルと同じ形にする。頭（`<?xml …?>` から `</head>` まで）はテンプレのまま写し、`<title>` だけ直す（章のファイルは章の題、ほかは本の題）。新しい章は `10-chapter1.xhtml` を写して作り、名前は `10-…`・`20-…`・`30-…` と番号の順にする（ファイル名は半角の英数字と `- _ .` だけ）。

- **XML として正しく書く。** タグは必ず閉じる（`<br/>`・`<img …/>`）、`&` は `&amp;`、`<` は `&lt;`。属性は `"` で囲む。
- 章の題は `<h1 id="c1">`（id はファイルごとに変える。目次からのリンク先になる）、節は `<h2>`。
- 段落は `<p>`。段落の初めは CSS が一字下げる（見出しの直後も下げる）。字下げしない段落は `<p class="noindent">`、一行あけるのは `<p class="blank">`。
- ルビ `<ruby>漢字<rt>かんじ</rt></ruby>`、傍点・圏点 `<span class="em">…</span>`、脚注 `<span class="fn">注の文</span>`（その頁の下に送られる）。
- 縦書きの縦中横 `<span class="tcy">12</span>`。
- **縦書きの数字**: 漢数字にするのが普通。量や数・時刻は位取りで（3つ→三つ、10年→十年、100本→百本、25人→二十五人、7時半→七時半）、元号の年も位取り（昭和62年→昭和六十二年）、西暦の年と番号は一字ずつ（1962年→一九六二年）。数字のまま残したいもの（型番・番地など）は、2桁までは縦中横にする。半角のままだと横倒しになる。英字の名前・URL は横倒しのままでよい。横書きの本の数字は、原稿のまま（算用数字）でよい。
- 絵: `<figure><img src="images/x.jpg" alt="…"/><figcaption>…</figcaption></figure>`。`alt` は短い説明（`figcaption` と同じでよい）。大きさは CSS が版面の半分までに抑える。1枚で1頁に大きく載せるなら `<figure class="page">`。JPEG・PNG・SVG が使える。長辺が 2000 ピクセルを超える写真は、Pillow（PIL）があれば縮める（無ければそのまま）。全体で 60MB を超えない。
- 横書きのテンプレには、囲み `<div class="note"><p class="label">こつ</p><p>…</p></div>`、番号付きの手順 `<ol class="steps">`、表 `<table class="list">`（数の列は `class="num"`）の見本がある。「こつ：」「注意：」のような短い言葉で始まる段落は、横書きなら囲みにしてよい。
- 割りたくないまとまりは `<div class="keep">`。
- 使えないもの: `<script>`・フォーム・外のサイトの絵や CSS（絵は `EPUB/images/` に入れる）。`style="…"` で位置や大きさを細かく決めない。

### 表紙・目次・奥付

- 表紙（`00-cover.xhtml`）: `cover-title`・`cover-sub`・`cover-author` の字を直す。副題が無ければ `cover-sub` の行を消す。位置と大きさは `css/cover.css` のまま。
- 表紙に絵を使うとき: **題が描いてある絵**なら `EPUB/images/cover.jpg` に置き、`css/cover.css` の先頭の説明のとおり頁の背景にして、`cover-text` の div を `<p class="cover-alt">本の題</p>`（題は本の題に）に替える。content.opf の、その絵の item に `properties="cover-image"` を付ける（本棚の表紙になる）。題の無い絵に題を重ねるときは `images/cover-art.jpg` にして背景にし、`cover-text` を残して絵の上で読める色にする（このときは `cover-image` を付けない）。
- 目次の頁（`01-contents.xhtml`）と `nav.xhtml`: 章の h1 の並びを、どちらにも同じ順で書く（`<li><a href="10-chapter1.xhtml#c1">第一章　…</a></li>`）。h1 が本に1つだけなら、h2 にも id を付けて載せる（h3 は載せない）。`nav.xhtml` の `<h1>目次</h1>` はそのまま。
- 奥付（`99-colophon.xhtml`）: 2 のとおり。出典など長い行は `<p class="hang">`。

### content.opf

- `dc:title`・`dc:creator`（著者）・`dc:description`（紹介文）を直す。`dc:language` は日本語の本なら `ja` のまま。
- `dc:identifier` は新しく作る（`urn:uuid:` に Python の `uuid.uuid4()`）。
- `dcterms:modified` は今の時刻（UTC、`2026-11-10T03:00:00Z` の形）。
- `manifest` に、`EPUB/` の中のファイルを全部載せる（`nav.xhtml` 以外の id は重ならない名前に）。`media-type` は xhtml `application/xhtml+xml`、css `text/css`、jpg `image/jpeg`、png `image/png`、svg `image/svg+xml`。ファイルを消したら（目次の頁を外すときなど）、その item と、spine の itemref も消す。
- `spine` に頁のファイルを本の順に並べる（表紙・目次・章・奥付）。
- 紙の本の表紙の item（`properties="jgs:print-cover"`）は消さない。spine には入れない。
- `jgs:layout`・`jgs:writing-modes`・`page-progression-direction` は変えない。

### 判型（本の大きさ）

テンプレの余白と字の大きさは、縦書きは新書判（113×176mm）、横書きは A5 判（148×210mm）に合わせてある。頼まれたときだけ変える。`jgs:format` を `shinsho`・`a6`（文庫）・`b6`・`a5`・`b5`・`a4` に直し、`style.css` の `@page` の `margin` と、`html` の `font-size`・`line-height` を合わせる。縦書きの目安（行送り 1.7）: `a6` 9pt・余白 上下 14mm 左右 11.5mm／`shinsho` 9.5pt・上 18 下 16 左右 13mm／`b6` 9.5pt・上 18 下 16 左右 15mm／`a5` 10pt・行送り 1.75・上 20 下 18 左右 17mm。

## 5. 確かめて、固める

チャットでは組んだ頁を見られないので、次を Python で確かめてから固める。このコードは、はじめに紙の本の表紙（`print-cover/cover.xhtml`）を content.opf の題・著者・紹介文で書き直す。`work` は広げたフォルダ、`out` は返すファイルの名前（本の題に `.epub` を付ける。ファイル名に使えない字 `\ / : * ? " < > |` は除く）。

```python
import os, re, zipfile, xml.etree.ElementTree as ET
from xml.sax.saxutils import escape

work, out = "work", "本の題.epub"
ep = os.path.join(work, "EPUB")
opf = open(os.path.join(ep, "content.opf"), encoding="utf-8").read()
problems = []
# 紙の本の表紙を、content.opf の題・著者・紹介文で書き直す（背の字の大きさも題の字数で決め直す）
pc = os.path.join(ep, "print-cover", "cover.xhtml")
try:
    meta = ET.parse(os.path.join(ep, "content.opf")).getroot()
except ET.ParseError:
    meta = None  # content.opf の誤りは下で出る。直してからもう一度流す
def dc(name):
    el = meta.find(f".//{{http://purl.org/dc/elements/1.1/}}{name}")
    return (el.text or "").strip() if el is not None else ""
if meta is None:
    pass
elif os.path.isfile(pc):
    title, author, desc = dc("title"), dc("creator"), dc("description")
    s = open(pc, encoding="utf-8").read()
    ems = lambda t: "%g" % round(max(1, sum(0.6 if ord(c) < 0x2E80 else 1 for c in t)), 1)
    put = lambda pat, new: re.sub(pat, lambda m: m.group(1) + new + m.group(2), s, flags=re.S)
    s = put(r"(<title>).*?(</title>)", escape(title))
    for c, t in (("b-title", title), ("s-title", title), ("f-title", title),
                 ("b-author", author), ("s-author", author), ("f-author", author)):
        s = put(rf'(<p class="{c}">).*?(</p>)', escape(t))
    s = put(r'(<div class="b-text">).*?(</div>)', "".join(f"<p>{escape(p.strip())}</p>" for p in desc.split("\n") if p.strip()))
    s = put(r"(\* 0\.58 / )[\d.]+(\))", ems(title))
    s = put(r"(\* 0\.24 / )[\d.]+(\))", ems(author))
    open(pc, "w", encoding="utf-8", newline="\n").write(s)
else:
    problems.append("紙の本の表紙が無い: print-cover/cover.xhtml（テンプレから写し直す）")
# テンプレの見本の文。part は一部に含まれたら、whole は段落・見出しの字がそれだけなら残っている
part = ["ここに本文を書きます", "副題（要らなければ消す）", '番号付きの手順は ol class="steps" で書きます',
        '囲みは div class="note" で書きます', "figure で包む", 'alt="見本の絵"', 'class="hang" にすると']
whole = ["本の題", "著者の名前", "著者　著者の名前", "節の見出し", "この本の紹介を一、二文で。"]
for root, _, files in os.walk(ep):
    for f in files:
        p = os.path.join(root, f)
        if not f.endswith((".xhtml", ".opf", ".svg")):
            continue
        try:
            tree = ET.parse(p)
        except ET.ParseError as e:
            problems.append(f"XML の誤り {f}: {e}")
            continue
        text = open(p, encoding="utf-8").read()
        problems += [f"見本の文が残っている {f}: {s}" for s in part if s in text]
        for el in tree.iter():
            t = "".join(el.itertext()).strip()
            if t in whole and len(el) == 0:
                problems.append(f"見本の文が残っている {f}: {t}")
        # 絵とリンクの先があるか(外のサイトへのリンクは見ない)
        for el in tree.iter():
            for attr in ("src", "href"):
                v = el.get(attr)
                if not v or re.match(r"^[a-z]+:", v) or v.startswith("#"):
                    continue
                target, _, frag = v.partition("#")
                tp = os.path.normpath(os.path.join(os.path.dirname(p), target))
                if not os.path.isfile(tp):
                    problems.append(f"先が無い {f}: {v}")
                elif frag and f'id="{frag}"' not in open(tp, encoding="utf-8").read():
                    problems.append(f"リンク先の id が無い {f}: {v}")
hrefs = re.findall(r'<item [^>]*href="([^"]+)"', opf)
for h in hrefs:
    if not os.path.isfile(os.path.join(ep, h)):
        problems.append(f"manifest にあるのにファイルが無い: {h}")
for root, _, files in os.walk(ep):
    for f in files:
        rel = os.path.relpath(os.path.join(root, f), ep).replace(os.sep, "/")
        if rel != "content.opf" and rel not in hrefs:
            problems.append(f"manifest に無いファイル: {rel}")
ids = set(re.findall(r'<item [^>]*id="([^"]+)"', opf))
for i in re.findall(r'<itemref idref="([^"]+)"', opf):
    if i not in ids:
        problems.append(f"spine の idref が manifest に無い: {i}")
print("\n".join(problems) or "OK")

if not problems:
    with zipfile.ZipFile(out, "w") as z:
        z.writestr(zipfile.ZipInfo("mimetype"), "application/epub+zip", compress_type=zipfile.ZIP_STORED)
        for root, _, files in os.walk(work):
            for f in sorted(files):
                p = os.path.join(root, f)
                rel = os.path.relpath(p, work).replace(os.sep, "/")
                if rel in ("mimetype", "AGENTS.md"):
                    continue
                z.write(p, rel, compress_type=zipfile.ZIP_DEFLATED)
```

`OK` と出るまで直す。`AGENTS.md` は固めるときに外れる。

組版エンジン（Copper）を呼べる道具（cti.li の MCP の Preview など）があっても、**写真や絵を base64 の字にして送って確かめない。** 長い字を写すうちに崩れて失敗し、時間ばかりかかる。頁の見え方を確かめるなら、絵を外した頁で確かめる。絵があるかは上のコードの「先が無い」で足りる。

## 6. 利用者に渡して、伝える

できた EPUB をダウンロードできる形で渡す。**利用者が使っている言葉で、短く、専門の言葉やファイルの名前を並べずに**書く。伝えるのは次のこと。

- 読み方: パソコンかスマホのブラウザで https://cti.li/contents/reader.html を開き、「EPUB を開く」を押して、ダウンロードした EPUB を選ぶ。
  - 判型を新書判以外にしたときは、読み器の「設定」の「本の大きさ」で同じ大きさ（例: 文庫・A6）を選ぶ。
- 直したいときは、この会話で「表紙の字を大きくして」のように頼めばよいこと。直すときは、前に渡した EPUB をもとに直して、新しい EPUB を渡す。
- 自分で決めたこと（組み方・判型・表紙・書き足した紹介文 など）と、気になる所が残っていればそれ。
- JINKEN.TV の会員は、示現舎の電子書籍販売サイト「示現舎書店」にこの EPUB をアップロードすると公開できること（頼まれたとき、公開したいと言われたときだけ）。

## 7. 守ること

- 他人の文章や絵を勝手に使わない。使ってよいのは、利用者が権利を持つもの・許しを得たもの・著作権の切れたもの（青空文庫など）。
- 原稿に無いことを書き足さない（紹介文・奥付のほかは）。
