Kiluth Document Spec

Department

Technology

Summary

Defines what “Kiluth-styled document” means — the HTML/CSS spec, component vocabulary, and editorial house rules for producing PDFs that match Kiluth’s brand. Any tool (Claude Code skill, AI tool, manual HTML, server-side renderer) that conforms to this spec produces visually consistent Kiluth documents.

Table of Contents


Purpose

Kiluth documents (proposals, quotations, briefs, agreements, reports) share a single visual language. This spec is the canonical reference for that language. Read it if you want to:

  • Produce a Kiluth-styled PDF from any source (markdown, HTML, AI prompt)
  • Verify a third-party tool conforms to the brand
  • Train or prompt an AI to generate Kiluth-styled content
  • Extend the system with new components
Outcome
Any output that obeys this spec looks like a Kiluth document, regardless of who or what produced it.

Visual Rules (Automatic)

These rules are baked into the template’s CSS. You don’t author them — they apply to any conforming HTML automatically.

#RuleDetail
1Page size522 × 756 pt (custom, ~7.25” × 10.5”)
2Page margins90pt top, 72pt sides, 102pt bottom (so the footer has breathing room)
3Body fontTheinhardt 10pt (Latin) + IBM Plex Sans Thai 10pt (Thai), line-height 1.5
4Heading weightsRegular (400) + small -webkit-text-stroke to approximate the unavailable Theinhardt Medium
5Vertical railsBetween every column except after the last, color #c8c6bf
6Horizontal rulesBetween every row except after the last, color #c8c6bf
7LetterheadLogo top-left, “Confidential” (or other label) top-center, contact info top-right
8FooterAddress bottom-left, page number bottom-right
9Every <h1> starts a new pageAutomatic via page-break-before: always
10Headings stick to their contentpage-break-after: avoid on h1–h6 prevents widow titles
11Last row of a multi-row table doesn’t get orphaned on a new pagetr:last-child:not(:first-child) { page-break-before: avoid }

Component Vocabulary

Cover Page

Default cover is auto-generated from a title + a single label/value pair (typically “Department”). For docs that need additional cover fields (Customer, Date, Project Code, Parties), write a custom cover as the first element of the body content:

<section class="cover">
  <h1>Document Title</h1>
  <div class="cover-dept-label">Client</div>
  <div class="cover-dept-value">Customer Co., Ltd.</div>
  <div class="cover-dept-label">Date</div>
  <div class="cover-dept-value">2026-05-24</div>
</section>

A custom cover suppresses the default. Multiple label/value pairs auto-space with 18pt between groups.

Two-line cover (title + descriptor). Add a .cover-subtitle element directly under the <h1>:

<section class="cover">
  <h1>Document Title</h1>
  <div class="cover-subtitle">Short descriptor line</div>
  <div class="cover-dept-label">Client</div>
  <div class="cover-dept-value">Customer Co., Ltd.</div>
</section>

.cover-subtitle renders at 13pt (smaller than h1, larger than body) with normal weight.

NEVER use a <table> for cover fields. The template styles .cover-dept-label / .cover-dept-value as a clean label-above-value stack. Wrapping the same fields in a <table> draws cell borders + vertical rails (universal table rules) and the cover renders as a data grid instead of a field list.

Pick subtitle OR cover field for the same content, not both. If “E-commerce Excluded” goes in a .cover-subtitle under the <h1>, don’t ALSO emit it as a Scope: E-commerce Excluded field. Either is fine; both is redundant duplication on the cover.

“Department” is owning-team metadata, not a customer-facing cover field. It’s passed via the --department flag to the renderer. Don’t add a <div class="cover-dept-label">Department</div> block unless the source document explicitly shows Department on its cover — for customer-facing docs (quotations, agreements), use Customer / Scope / Date instead.

Headings

<h1>Section Title</h1>      <!-- 21pt, starts new page -->
<h2>Subsection</h2>         <!-- 12pt, inline -->
<h3>Sub-subsection</h3>     <!-- 11pt, inline -->

Always use semantic <h1><h6> for titles. Never fake a heading with <p><strong>Title</strong></p> — the page-break-avoidance rules only fire on real heading tags.

Paragraph + inline

<p>Standard prose. <strong>Bold</strong>, <em>italic</em>,
   <a href="https://www.kiluth.com">link</a>, <code>inline code</code>.</p>

Table of Contents

Use a div-based stack, not a <table>:

<h1>Table of Contents</h1>
<div class="toc-list">
  <div class="toc-item">Summary<span class="toc-page">2</span></div>
  <div class="toc-item">Approach<span class="toc-page">5</span></div>
</div>

Page numbers are static (estimated by section length). Include a TOC when the document is multi-section and > ~5 pages; skip for short docs.

Strip leading numbering from TOC entries. If a section heading is <h1>1. Project Summary</h1> or <h1>1. บทสรุปโครงการ</h1>, the matching .toc-item text is Project Summary / บทสรุปโครงการnot 1. Project Summary / 1. บทสรุปโครงการ. The number stays on the heading itself; in the TOC it’s visual noise. Applies to 1., 1), (1), 01., and any other leading enumerator.

Multi-column tables (4+ cols) — Layout A vs Layout B

Total page content width is 378pt (522pt page − 72pt × 2 side margins). For tables with 4+ columns, pick a layout BEFORE composing.

Layout (A) — ALL SHORT COLUMNS. Every column is a label, number, or short phrase. Set explicit widths on the first row’s <td> cells summing to ≤378pt:

<table>
  <thead>
    <tr>
      <th style="width: 32pt">#</th>
      <th>Description</th>
      <th style="width: 42pt">Days</th>
      <th style="width: 70pt">Price (THB)</th>
    </tr>
  </thead>
  ...
</table>

Layout (B) — ONE PARAGRAPH COLUMN. Use a 2-row-per-record pattern: top row carries the structured fields, bottom row spans full width with the paragraph:

<table>
  <thead>
    <tr><th>Category</th><th>Component</th><th>Provider</th><th>Monthly</th><th>Notes</th></tr>
  </thead>
  <tbody>
    <tr><td>Frontend</td><td>Static Hosting</td><td>Vercel</td><td>1,500</td><td>Edge cache</td></tr>
    <tr><td colspan="5" style="padding-left: 12pt; color: #6a6a6a; font-size: 8.5pt;">Where the website itself lives — Vercel stores and serves the web pages quickly around the world.</td></tr>
    ...
  </tbody>
</table>

Layout B is MANDATORY (no judgment call) when ANY trigger fires:

  • A column header is “Explanation”, “Description”, “Justification”, “Rationale”, “Reason”, “Comment”, “Detail/Details”, or “Notes” (when Notes carries sentences vs. short phrases) — anything prose-implying
  • Any cell in a column contains a complete sentence (capital letter + ends with period)
  • Any cell in a column has >10 words
  • The doc is a “Technical Expenses” / “Cost Breakdown” / “Pricing Detail” table with 5+ columns

Anti-pattern: Don’t squeeze a paragraph into a narrow 6th column — it wraps one-word-per-line and the doc looks broken. That’s Layout A misapplied.

Multi-row scope tables (sections + subsections + bullets)

For tables like a Website Development scope (sections numbered 1, 2, 4, … with optional subsections 1.1, 1.2, … and bullet lists describing each item):

ONE row per leaf section. If a section has NO subsections (e.g. “Project Management — 10 days — 50,000”), emit ONE row that carries the section number + name + days + price together. Do NOT emit a separate “section header row” PLUS a “data row” for the same section.

  • Section WITH subsections (1.1, 1.2 etc.): one section-header row spanning Description (no Days/Price), then subsection rows below with Days/Price filled.
  • Section WITHOUT subsections: one row carrying everything.

Bullets and sub-content stay INSIDE the table. Emit them as the NEXT row in the same table: <tr><td></td><td colspan="N-1"><ul><li>...</li></ul></td></tr> (leave the # cell empty). DO NOT close the table, drop a standalone <ul>, then start a new table fragment — that breaks the column grid and the table header repeats at the top of every new page (visible signal you got this wrong). One <table> element per logical table.

<table>
  <thead><tr><th>#</th><th>Description</th><th>Days</th><th>Price (THB)</th></tr></thead>
  <tbody>
    <!-- Section WITH subsections -->
    <tr><td colspan="4"><strong>1. UX Design</strong></td></tr>
    <tr><td>1.1</td><td>Strategy Handoff &amp; UX Kickoff</td><td>1</td><td>5,000</td></tr>
    <tr><td></td><td colspan="3"><ul><li>Internal review</li><li>Handoff meeting</li></ul></td></tr>
    <tr><td>1.2</td><td>Initiate UX Design</td><td>11</td><td>45,000</td></tr>
    <tr><td></td><td colspan="3"><ul><li>Refine sitemap</li><li>Translate journeys</li></ul></td></tr>
 
    <!-- Section WITHOUT subsections — ONE row, no separate header -->
    <tr><td>5</td><td><strong>Project Management — Bonsoir Website</strong></td><td>10</td><td>50,000</td></tr>
    <tr><td></td><td colspan="3"><ul><li>Project planning</li><li>Weekly check-ins</li></ul></td></tr>
  </tbody>
</table>

Plain 2-column table

<table>
  <thead><tr><th>Column A</th><th>Column B</th></tr></thead>
  <tbody>
    <tr><td>row 1 a</td><td>row 1 b</td></tr>
    <tr><td>row 2 a</td><td>row 2 b</td></tr>
  </tbody>
</table>

Vertical rail between cols + horizontal rules between rows appear automatically. Last row has no closing rule.

Multi-column grid table

For 3+ columns. Optional <th colspan="N"> row for a section title above the column headers.

<table>
  <thead>
    <tr><th colspan="4">Section title</th></tr>
    <tr><th>#</th><th>Description</th><th>Day</th><th>Price</th></tr>
  </thead>
  <tbody>
    <tr><td>1</td><td>Project initiation</td><td>1</td><td>8,000</td></tr>
  </tbody>
</table>

Numbered table — class="numbered"

Narrow right-aligned first column (67pt). Use for numbered enumerations, checklists, system-setup steps.

<table class="numbered">
  <thead><tr><th></th><th>Section title</th></tr></thead>
  <tbody>
    <tr><td>1</td><td>First item</td></tr>
    <tr><td>2</td><td>Second item</td></tr>
  </tbody>
</table>

Checkbox glyphs (☐) work in the first column for checklist patterns.

Single-row callout

For one label paired with one piece of content (Link, Outcome, Reference). Set the first td width inline so the label column doesn’t take half the page:

<table>
  <tbody>
    <tr>
      <td style="width: 67pt;">Outcome</td>
      <td>The shipped feature reduces support tickets by 40%.</td>
    </tr>
  </tbody>
</table>

✓ / ✕ correctness table

For do/don’t pairings. Unicode glyphs go in the label cell. Lock first-column width to 67pt for visual alignment with other label tables:

<table>
  <thead><tr><th style="width: 67pt;"></th><th>Example</th></tr></thead>
  <tbody>
    <tr><td>✓ Correct</td><td>Lead with the answer, then explain.</td></tr>
    <tr><td>✕ Incorrect</td><td>Bury the conclusion in a long preamble.</td></tr>
  </tbody>
</table>

Form table — class="form"

For Email Templates, Asana Cards, anything with a label column + a multi-paragraph body that needs to flow across pages cleanly. Use <tr class="title"> for the title row and <tr class="end"> for the LAST row of each logical block (Subject, Content, Title, Assignee, Description).

<table class="form">
  <tbody>
    <tr class="title"><th></th><th>Example Email Template</th></tr>
    <tr class="end"><td>Subject</td><td>New Employee Information</td></tr>
    <tr><td>Content</td><td>Hello [Name],</td></tr>
    <tr><td></td><td>Welcome to Kiluth...</td></tr>
    <tr><td></td><td>Best regards,<br>[HR Name]</td></tr>
  </tbody>
</table>

Calendar / grid table

For multi-column timeline grids (week-by-task layouts). Each cell carries <strong>Name</strong><br>Description:

<table>
  <thead>
    <tr><th>Month</th><th colspan="4" style="text-align: right;">1</th></tr>
    <tr><th>Weeks</th><th>1</th><th>2</th><th>3</th><th>4</th></tr>
  </thead>
  <tbody>
    <tr>
      <td></td>
      <td><strong>Draft UX Design</strong><br>Wireframe, User Flow</td>
      <td><strong>Finalize UX Design</strong><br>Final UX</td>
      <td>...</td><td>...</td>
    </tr>
  </tbody>
</table>

Card stack

For repeating label/value blocks (Project Duration, Expected Deliverables, Persona profiles). Just stack multiple plain 2-col <table> blocks — the table margin gives the spacing between cards. The first row of each card is the title (Month, Category, Phase):

<table>
  <tbody>
    <tr><td style="width: 96pt;">Month</td><td>1</td></tr>
    <tr><td>Main Objective</td><td>Prepare Backend Structure + Design UX/UI</td></tr>
    <tr><td>Details</td><td>...</td></tr>
  </tbody>
</table>
 
<table>
  <tbody>
    <tr><td style="width: 96pt;">Month</td><td>2</td></tr>
    ...
  </tbody>
</table>

Signatures — class="signatures"

Two signing parties side-by-side with no vertical divider. Flex layout, not a table:

<div class="signatures">
  <div class="signature">
    <div class="signature-label">Signed ……………………………………..  Contractor</div>
    <div>Mr Phuttiphan Samranruen</div>
    <div>Account Executive</div>
    <div>Kiluth Co., Ltd.</div>
  </div>
  <div class="signature">
    <div class="signature-label">Signed ……………………………………..  Client</div>
    <div>………………………………….</div>
    <div>Client Co., Ltd.</div>
  </div>
</div>

Page-break-inside is avoided so the whole block stays on one page.

Blockquote / code / image / horizontal rule

Standard semantic HTML — all styled to match Kiluth’s body conventions.

<blockquote>A pulled-out quote in muted gray with a left rail.</blockquote>
 
<pre><code>$ npm install
$ npm run build</code></pre>
 
<img src="path/to/image.png" alt="description">
 
<hr>

Editorial House Rules

These are not enforced by CSS — they’re authoring discipline. Follow them when composing content.

#RuleWhy
1No auto-numbering of section headings. Render heading text verbatim. If a heading text doesn’t start with a number, don’t add one.Avoids the TOC and body both numbering, which reads like double-counting.
2No invented prefixes (Section 1:, Chapter 1:, Part A:).Same reason.
3Don’t re-organize content into a hierarchy the user didn’t supply. Keep flat content flat.Source fidelity.
4TOC is optional. Include for multi-section docs > ~5 pages; skip for short docs (briefs, single-page memos). User preference wins.Short docs don’t need navigation.
5Prefer class="numbered" over <ul> / <ol> for top-level LISTS (sequences of short items: checklist, enumeration, ordered steps). Reserve bullet/numbered lists for items inside other components (form-table cells, table cells).Kiluth house style — numbered tables read cleaner and more deliberately than browser default list styling.
6Always use h1-h6 for titles. Never fake a heading with bolded paragraphs.Page-break-avoid rules only fire on semantic heading tags.
7Match source structure when one is referenced. Don’t invent stylistic flourishes the source doesn’t have.Preserves intent.
8Heading-vs-list distinction. Rule 5 applies to LISTS (item sequences). It does NOT apply to numbered section HEADINGS. If source has **1. Payment Milestones** / **2. Change Requests** as bold subheadings each followed by prose body, render as <h2>1. Payment Milestones</h2> + prose, NOT as a 3-col numbered table. Each numbered item with its own prose body = section (h2). Each numbered item as a one-liner = list (numbered table).Loses heading semantics and makes prose feel like data.
9Convert markdown bullets to real <ul><li>. When source has - item bullets inside a table cell, emit <ul><li>item</li></ul>. Don’t paste raw - prefixes as text — Chrome’s headless renderer doesn’t post-process them; literal ”- item” stays as ”- item” with no bullet character, no hanging indent.Bullets only look like bullets when they ARE bullets.
10Never add <div style="page-break-after: always;"> after an h1. Every <h1> already carries page-break-before: always. An extra break pushes the following content to a THIRD page and orphans the h1 alone on a wasted page.Double break = wasted page.
11Preserve bilingual content as-is. If source has 22 พฤษภาคม 2026 (22 May 2026), keep both Thai + English. If signature labels are bilingual (ลงชื่อ ... ผู้รับจ้าง (Contractor)), keep them bilingual — never collapse to English-only.Honors source intent across both audiences.
12Preserve source section numbering even when it has gaps. If source jumps 1, 2, 4, 5, 9 — keep the gaps. Don’t silently renumber to 1–5; if the gap looks unintentional, flag it during iteration (Stage A) before rendering.Source fidelity.
13Flag source defects before rendering. Empty TOTAL/Subtotal cells (compute and offer the sum), section numbering gaps (confirm intentional), column-header typos (fix and note). Mirror source 1:1 — but call out obvious problems instead of shipping them.Faithful-mirroring trumps no-invention; flag-defects trumps faithful-mirroring.

Page Layout & Print Behavior

When to break a page

TriggerHow
New top-level section (h1)Automatic — every <h1> carries page-break-before: always
New subsection on a fresh page (h2 without parent h1 above it)Explicit: <div style="page-break-after: always;"></div> before the h2
Two adjacent form tables (Email → Asana, etc.)Inline on the second table: <table class="form" style="page-break-before: always;">
Before a signature block at the end of a docUse h1 if it’s a “Signatures” section; otherwise the explicit break div

When NOT to break

  • Within a section (h2/h3 flow with their parent h1)
  • Mid-paragraph or mid-table
  • Just because there’s extra whitespace at the bottom — better than a forced fresh page that’s mostly empty

Title-content continuity

A title must never be separated from its content. If the title would land at the bottom of a page with the content on the next, the title gets pushed forward to where its content is. The template enforces this automatically via page-break-after: avoid on h1–h6 — but only if the title is a semantic heading tag.


Rendering Pipeline

The spec is the visual contract. How HTML becomes PDF is a separate concern, and there are multiple valid paths:

PathUse when
Claude Code with the kiluth-document skill (internal)Authoring + rendering inside the Kiluth workspace. AI-mediated content composition, dept-routed artifact storage, lessons feedback loop.
Manual chrome --headless --print-to-pdfOne-off renders of hand-authored HTML. No tooling beyond a local Chrome install.
Server-side Puppeteer / @sparticuz/chromium (planned)Programmatic rendering from a backend service (e.g. Kiluth Chat, scheduled jobs).

All three paths consume HTML that conforms to this spec and produce visually identical output.

The CSS template files live in the workspace at skills/kiluth-document/template-en.html and template-th.html. They are the source of truth for the visual rules.

Template wrapper (download the canonical HTML)

The spec describes the BODY content patterns (cover, tables, signatures, etc.). The full HTML wrapper — @page rules, @font-face for Theinhardt + IBM Plex Sans Thai, letterhead with embedded Kiluth logo SVG, footer with address/tax-ID/page-counter — lives in the template file. Authoring tools (Claude Code skill, kiluth-api-2 service) include it automatically; external/manual rendering paths must fetch it.

The template uses two placeholders: {{LABEL}} (letterhead center label, default Confidential) and {{CONTENT}} (where your body HTML gets inserted). Substitute these strings, render with Chrome --headless --print-to-pdf, and you get a brand-conformant PDF.

External-AI authoring workflow:

  1. Fetch the template HTML for your language (en or th)
  2. Compose the body content per this spec (cover, sections, tables, signatures)
  3. Replace {{LABEL}} with the letterhead label and {{CONTENT}} with your body
  4. Save as a single HTML file and render with chrome --headless --print-to-pdf=out.pdf in.html

The fonts (Theinhardt + IBM Plex Sans Thai) and the logo SVG load over the network during the print — make sure the rendering environment has network access.


Authoring Checklist

Before rendering any document, verify:

#Check
1Every section title uses <h1><h6>, not bolded paragraphs
2Cover is appropriate for doc type (default for simple docs, custom <section class="cover"> for docs with extra fields like Client/Date)
3Top-level lists are <table class="numbered">, not <ul> / <ol>
4Multi-paragraph email/asana/agreement-style content uses <table class="form"> with tr.title + tr.end
5Two-party signature block uses <div class="signatures">, not a 2-cell <table>
6TOC uses .toc-list / .toc-item, not a <table>
7Section headings don’t carry invented numbering (Section 1:, Chapter A:)
8Page breaks only via <h1> (automatic) or explicit style="page-break-*" directives — but NEVER page-break-after immediately after an <h1>
9Content faithfully mirrors source structure — no extra hierarchy, no auto-renames
10Cover uses .cover-dept-label / .cover-dept-value divs — NEVER a <table> (would draw cell borders)
11Cover doesn’t duplicate the same value as both .cover-subtitle and a .cover-dept-value field
124+ col tables with any prose-bearing column use Layout B (2-row-per-record with colspan paragraph row), not Layout A
13Markdown - item bullets inside cells are converted to <ul><li>item</li></ul> (not pasted as literal text)
14Numbered subheadings followed by their own prose body are <h2> + prose, NOT a <table class="numbered">
15TOC entries strip leading numbering (1. , (1), etc.) even when the source heading carries it
16Bilingual content (Thai+English dates, signature labels) preserved as-is from source
17Source defects flagged before rendering — empty TOTAL/Subtotal computed, gaps confirmed, typos noted