chore: import upstream snapshot with attribution
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"name": "kami",
|
||||
"interface": {
|
||||
"displayName": "Kami"
|
||||
},
|
||||
"plugins": [
|
||||
{
|
||||
"name": "kami",
|
||||
"source": {
|
||||
"source": "local",
|
||||
"path": "./plugins/kami"
|
||||
},
|
||||
"policy": {
|
||||
"installation": "AVAILABLE",
|
||||
"authentication": "ON_INSTALL"
|
||||
},
|
||||
"category": "Productivity"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"name": "kami",
|
||||
"description": "Document typesetting skill for Claude Code.",
|
||||
"owner": {
|
||||
"name": "Tw93",
|
||||
"email": "hitw93@gmail.com"
|
||||
},
|
||||
"plugins": [
|
||||
{
|
||||
"name": "kami",
|
||||
"version": "1.9.4",
|
||||
"description": "Typeset professional documents and landing pages with the Kami design system.",
|
||||
"category": "documents",
|
||||
"source": "./plugins/kami",
|
||||
"homepage": "https://github.com/tw93/kami"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"version": "0.0.1",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "kami-site",
|
||||
"runtimeExecutable": "python3",
|
||||
"runtimeArgs": ["-m", "http.server", "8321"],
|
||||
"port": 8321
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
github: ['tw93']
|
||||
custom: ['https://cats.tw93.fun?name=Kami']
|
||||
@@ -0,0 +1,79 @@
|
||||
name: check
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
pull_request:
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
lint-and-test:
|
||||
name: lint and test
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.12'
|
||||
|
||||
- name: Install test extras
|
||||
run: python3 -m pip install Pygments
|
||||
|
||||
- name: Lint templates and check token sync
|
||||
run: python3 scripts/build.py --check
|
||||
|
||||
- name: Check generated Codex plugin metadata
|
||||
run: python3 scripts/build_metadata.py --check
|
||||
|
||||
- name: Run test suite
|
||||
run: python3 scripts/tests/test_build.py
|
||||
|
||||
- name: Build and audit skill package
|
||||
run: bash scripts/package-skill.sh /tmp/kami-ci.zip
|
||||
|
||||
verify-render:
|
||||
name: render and verify
|
||||
runs-on: ubuntu-latest
|
||||
needs: lint-and-test
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.12'
|
||||
|
||||
- name: Install WeasyPrint system libs and CJK fallback fonts
|
||||
# libcairo2 / libpango / libharfbuzz are required by WeasyPrint at
|
||||
# runtime; fonts-noto-cjk provides a serif CJK fallback so the Chinese
|
||||
# templates render with embedded glyphs even though TsangerJinKai02 is
|
||||
# commercial and not shipped in CI. The verify step accepts Noto as a
|
||||
# recognized fallback (see scripts/build.py fallback_present).
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y --no-install-recommends \
|
||||
libcairo2 libpango-1.0-0 libpangoft2-1.0-0 libharfbuzz0b \
|
||||
fonts-noto-cjk fonts-noto-cjk-extra
|
||||
|
||||
- name: Install Python deps
|
||||
run: python3 -m pip install weasyprint pypdf Pygments
|
||||
|
||||
- name: Verify strict page-count targets
|
||||
# Only the six hard-invariant templates (resume == 2, one-pager == 1
|
||||
# across CN/EN/KO) run in CI. The KO pair also exercises the Korean
|
||||
# render path on Linux (fonts-noto-cjk supplies the glyphs). The
|
||||
# long-doc / portfolio / slides ceilings are soft and checked visually.
|
||||
# KAMI_ALLOW_FALLBACK_ONLY=1: CI does not ship commercial fonts
|
||||
# (TsangerJinKai02, Charter). Treat fallback-only embedding as a
|
||||
# warning instead of a failure so CI can still gate page counts.
|
||||
env:
|
||||
KAMI_ALLOW_FALLBACK_ONLY: '1'
|
||||
run: |
|
||||
python3 scripts/build.py --verify one-pager
|
||||
python3 scripts/build.py --verify one-pager-en
|
||||
python3 scripts/build.py --verify one-pager-ko
|
||||
python3 scripts/build.py --verify resume
|
||||
python3 scripts/build.py --verify resume-en
|
||||
python3 scripts/build.py --verify resume-ko
|
||||
@@ -0,0 +1,64 @@
|
||||
name: release
|
||||
|
||||
on:
|
||||
push:
|
||||
tags: ['V*']
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
tag:
|
||||
description: 'Tag to attach kami.zip to (e.g. V1.4.1)'
|
||||
required: true
|
||||
|
||||
jobs:
|
||||
attach-archive:
|
||||
name: build kami.zip and attach to release
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.event.inputs.tag || github.ref }}
|
||||
|
||||
- name: Resolve tag name
|
||||
id: tag
|
||||
run: |
|
||||
if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then
|
||||
TAG="${{ github.event.inputs.tag }}"
|
||||
else
|
||||
TAG="${GITHUB_REF#refs/tags/}"
|
||||
fi
|
||||
echo "name=$TAG" >> "$GITHUB_OUTPUT"
|
||||
echo "Resolved tag: $TAG"
|
||||
|
||||
- name: Build kami.zip
|
||||
run: |
|
||||
bash scripts/package-skill.sh dist/kami.zip
|
||||
ls -lah dist/kami.zip
|
||||
|
||||
- name: Ensure release exists, then attach archive
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
TAG: ${{ steps.tag.outputs.name }}
|
||||
run: |
|
||||
if ! gh release view "$TAG" >/dev/null 2>&1; then
|
||||
echo "Release $TAG does not exist, creating placeholder"
|
||||
gh release create "$TAG" --title "$TAG" --notes "Release notes pending. Edit on GitHub."
|
||||
fi
|
||||
gh release upload "$TAG" dist/kami.zip --clobber
|
||||
echo "OK: kami.zip attached to release $TAG"
|
||||
|
||||
- name: Add release reactions
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
TAG: ${{ steps.tag.outputs.name }}
|
||||
run: |
|
||||
# Match the house style every release carries: one of each positive
|
||||
# reaction. Idempotent (re-running adds nothing) and non-fatal so a
|
||||
# reaction hiccup never fails an otherwise-good release.
|
||||
rid="$(gh api "repos/${{ github.repository }}/releases/tags/$TAG" --jq .id)"
|
||||
for r in +1 eyes heart hooray laugh rocket; do
|
||||
gh api -X POST "repos/${{ github.repository }}/releases/$rid/reactions" \
|
||||
-f content="$r" --jq .content || echo "skip reaction $r"
|
||||
done
|
||||
echo "OK: reactions ensured on release $TAG"
|
||||
@@ -0,0 +1,25 @@
|
||||
# macOS
|
||||
.DS_Store
|
||||
|
||||
# Agent local settings
|
||||
.claude/settings.local.json
|
||||
|
||||
# Python
|
||||
__pycache__/
|
||||
*.pyc
|
||||
.pytest_cache/
|
||||
|
||||
# Font build artifacts
|
||||
assets/fonts/*.otf
|
||||
|
||||
# Template examples with placeholder content, regenerate via: python3 scripts/build.py
|
||||
assets/examples/
|
||||
|
||||
/tmp/
|
||||
/dist/*
|
||||
!/dist/kami.zip
|
||||
*.log
|
||||
.vercel
|
||||
|
||||
# Slides build artifact (moved into assets/examples/ by build.py)
|
||||
/output.pptx
|
||||
@@ -0,0 +1,220 @@
|
||||
# Kami Agent Guide
|
||||
|
||||
> Personal/global agent rules may live outside this repository. This file records Kami-specific repository maps, Working Rules, Current Risk Areas, Verification, Release Flow, and Fonts.
|
||||
|
||||
## Project
|
||||
|
||||
Kami is a document-generation skill and template system. It ships editorial HTML templates, reference guides, demo assets, and a packaged skill archive.
|
||||
|
||||
## Repository Map
|
||||
|
||||
- `SKILL.md` - skill routing and operating rules.
|
||||
- `CHEATSHEET.md` - quick design reference.
|
||||
- `CLAUDE.md` - Claude-specific notes pointing to AGENTS.md.
|
||||
- `references/` - design, writing, diagram, and production guidance.
|
||||
- `references/design.md`, `writing.md`, `production.md`, `diagrams.md` - full specs.
|
||||
- `references/resume-writing.md` - resume-specific bullet/project framing rules.
|
||||
- `references/anti-patterns.md` - six-category checklist for reviewing drafts.
|
||||
- `references/mermaid.md` - Mermaid diagram support: the two render paths (PDF vs browser) and the authoring pipeline.
|
||||
- `references/mermaid-theme.json` - canonical Kami↔beautiful-mermaid color/font theme (kept in sync with `tokens.json`).
|
||||
- `references/tokens.json` - canonical color tokens (drift-checked by `scripts/tokens.py`).
|
||||
- `references/checks_thresholds.json` - rhythm / density / orphan check thresholds (loaded by `scripts/checks.py`).
|
||||
- `references/brand-profile.md` and `references/brand.example.md` - optional brand profile behavior and public example.
|
||||
- `.claude-plugin/marketplace.json` - **generated** Claude Code plugin marketplace metadata. Points Claude Code at `plugins/kami`.
|
||||
- `.agents/plugins/marketplace.json` - **generated** Codex repo marketplace. Points Codex at `plugins/kami`; never hand-edit.
|
||||
- `plugins/kami/` - **generated** Claude Code / Codex plugin tree. Mirrors the lightweight skill package under `plugins/kami/skills/kami/`; edit source files and run `python3 scripts/build_metadata.py`.
|
||||
- `assets/templates/` - document templates including browser-only landing page variants.
|
||||
- `scripts/highlight.py` - Pygments-based syntax highlighting for code blocks at build time.
|
||||
- `assets/demos/` - README showcase demos.
|
||||
- `assets/showcase/` - README and public-site-only screenshots; excluded from `dist/kami.zip`.
|
||||
- `assets/diagrams/` - diagram prototypes and generated diagram assets; `src/*.mmd` records the Mermaid source of the `sequence` / `class` / `er` diagrams.
|
||||
- `scripts/mermaid_normalize.py` - re-themes any beautiful-mermaid SVG to the Kami palette and makes it WeasyPrint-safe (resolves `color-mix()`/`var()` to static hex, rewrites fonts). Pure Python, no Node; ships in the package.
|
||||
- `assets/fonts/` and `assets/illustrations/` - bundled visual assets.
|
||||
- `styles.css` - shared web-facing styles.
|
||||
- `index.html`, `index-zh.html`, `index-en.html`, `index-ja.html`, `index-ko.html`, `index-tw.html` - public site entrypoints.
|
||||
- `robots.txt`, `sitemap.xml`, and `vercel.json` - public crawler, deployment, and AI visibility files.
|
||||
- `llms.txt` - AI crawler and model-facing project summary.
|
||||
- `scripts/build.py` - CLI shell: build targets and dispatch to lint / verify / checks / tokens modules.
|
||||
- `scripts/verify.py` - end-to-end render verification (page count, embedded fonts, advisory density scan).
|
||||
- `scripts/lint.py` - template CSS lint rules and base/variant cross-template `:root` consistency check (CN↔EN and CN↔KO).
|
||||
- `scripts/tokens.py` - `tokens.json` drift check across HTML templates and PPTX slide scripts.
|
||||
- `scripts/checks.py` - PDF-side checks: placeholders, orphans, density, slide-deck rhythm.
|
||||
- `scripts/optional_deps.py` - centralized loader for weasyprint / pypdf / PyMuPDF with consistent install hints.
|
||||
- `scripts/shared.py` - shared constants and the canonical `HTML_TEMPLATES` registry used by the build scripts.
|
||||
- `scripts/ensure-fonts.sh` - verified font recovery helper (portable across bash 3.2+).
|
||||
- `scripts/package-skill.sh` - package builder for the release archive.
|
||||
- `scripts/site_facts.py` - public-site fact drift checks (install commands, version, template and diagram counts across `index*.html`, README, `llms.txt`); wired into `build.py --check`.
|
||||
- `scripts/check-update.sh` - quiet daily update check invoked from `SKILL.md`; read-only VERSION compare, silent on any failure.
|
||||
- `scripts/build_metadata.py` - codegen for Claude Code / Codex marketplace metadata and plugin mirror files. Run after changing `SKILL.md`, `CHEATSHEET.md`, `VERSION`, `references/`, `scripts/`, or shipped lightweight assets.
|
||||
- `scripts/draft-release-notes.py` - bilingual release notes scaffold from `git log`.
|
||||
- `scripts/tests/test_build.py` - zero-dependency test suite for build and shared helpers.
|
||||
- `.github/workflows/check.yml` - PR/push CI that runs `--check` and the test suite.
|
||||
- `.github/workflows/release.yml` - tag-triggered workflow that builds and attaches `dist/kami.zip` to the release.
|
||||
- `dist/kami.zip` - tracked release archive.
|
||||
|
||||
Reference docs are English-only. Language-specific output differences (CN/EN/KO) belong in templates, not duplicated reference files.
|
||||
|
||||
## Commands
|
||||
|
||||
```bash
|
||||
python3 scripts/build.py
|
||||
python3 scripts/build.py --check
|
||||
python3 scripts/build.py --verify
|
||||
python3 scripts/build.py --check-placeholders path/to/filled.html
|
||||
python3 scripts/build.py --check-markdown path/to/filled.pdf
|
||||
python3 scripts/build.py --check-orphans path/to/doc.pdf
|
||||
python3 scripts/build.py --check-density path/to/doc.pdf
|
||||
python3 scripts/build.py --check-rhythm slides slides-en
|
||||
python3 scripts/build_metadata.py
|
||||
python3 scripts/build_metadata.py --check
|
||||
python3 scripts/tests/test_build.py
|
||||
# Re-theme + WeasyPrint-safe a beautiful-mermaid SVG (no Node), then embed in a diagram shell:
|
||||
python3 scripts/mermaid_normalize.py raw.svg -o clean.svg
|
||||
python3 scripts/draft-release-notes.py V1.4.0..HEAD --version V1.4.1 --title "Steadier Hand"
|
||||
bash scripts/ensure-fonts.sh
|
||||
bash scripts/package-skill.sh
|
||||
```
|
||||
|
||||
## Working Rules
|
||||
|
||||
- Style changes must update `references/design.md` and the matching template tokens.
|
||||
- Landing or documentation-site work follows `references/design.md` Section 11: «Documentation site» for the doc shell (sidebar rail, on-this-page TOC, borderless prev/next pager, build-time zero-JS code highlighting) and «Responsive screenshot verification» (screenshot at 375px / 1280px per locale, objective line-widow scan) before shipping.
|
||||
- For hosted Kami site or public landing changes, first separate generic template work from Kami's own website. Generic behavior lives in `assets/templates/landing-page*` and `references/`; Kami site facts live across `index*.html`, `styles.css`, README, `llms.txt`, `robots.txt`, `sitemap.xml`, and `vercel.json`.
|
||||
- Public facts are wider than the hero. Pricing, install path, version, release, support, analytics, FAQ, and positioning claims must move together across pages, metadata, AI files, and download links. Do not leave site-only analytics or tracking changes contradicting "no analytics" or app/package privacy copy.
|
||||
- Content changes should avoid CSS churn unless layout behavior is part of the task.
|
||||
- For document or template tasks, lock the output contract before editing: language, template, output format, page or length target, visual acceptance check, and verification command.
|
||||
- Prefer the nearest existing template and deterministic verifier. Do not add a template, shared CSS layer, dependency, script flag, or optional mode unless the current request cannot be satisfied without it.
|
||||
- New templates should copy the nearest existing template, stay aligned with `references/design.md`, and add demo coverage.
|
||||
- Do not use graphic emoticons in docs, template comments, or script output.
|
||||
- Do not use em dashes (U+2014) in repository docs, generated documents, template comments, or site copy; use colons, commas, periods, or parentheses instead. Self-check: `grep -rn '—' README.md llms.txt index*.html`. Teaching counter-examples inside `references/anti-patterns.md` are exempt; its rule #27 covers the generated-document side.
|
||||
- Use `OK:` and `ERROR:` for status text in scripts.
|
||||
- Use `scripts/ensure-fonts.sh` to recover required fonts with retry and size validation when local font files are missing or truncated. It downloads to the XDG user font dir (`${XDG_DATA_HOME:-~/.local/share}/fonts/kami`), never into the skill's `assets/fonts`, so an installed Claude Desktop skill stays small; inside a repo checkout it is a no-op because the committed large fonts already satisfy the templates' relative path.
|
||||
- Do not bundle large CJK font files into `dist/kami.zip`; package scripts should exclude them while templates keep stable local-preview paths. The skill ZIP uploaded to Claude Desktop must be the `scripts/package-skill.sh` output under the 6MB package ceiling and must contain a top-level `kami/` skill folder; a hand-zipped checkout includes the tracked large fonts and Claude Desktop rejects it.
|
||||
- Do not bundle README/public-site-only showcase screenshots into `dist/kami.zip`; keep them under `assets/showcase/` and exclude that directory in `scripts/package-skill.sh`.
|
||||
- Keep multilingual public pages, `llms.txt`, `robots.txt`, sitemap, JSON-LD, and FAQ content aligned when changing public positioning or install instructions.
|
||||
- Brand profile support is optional context. Keep public examples in `references/`; do not hard-code a maintainer's private local profile content.
|
||||
- Slides default to WeasyPrint HTML-to-PDF templates unless the user explicitly needs editable PPTX output.
|
||||
- Templates intentionally inline their CSS rather than share a `_kami.css` partial: each template must remain a single self-contained HTML file so users can copy-paste it without a build step. When fixing CSS drift, apply the same change across affected templates rather than introducing a build-time include.
|
||||
- All template registries live in `scripts/shared.py`: `HTML_TEMPLATES` (PDF docs), `SCREEN_TEMPLATES` (browser-only), and `DIAGRAM_TEMPLATES` (assets/diagrams). `build.py` derives its target dicts from them via `build_targets()` / `screen_targets()` / `diagram_targets()`. Update the registry, not the per-script dicts, when adding or removing a template or diagram.
|
||||
- Mermaid diagrams: never embed raw beautiful-mermaid SVG into a PDF-bound template. WeasyPrint cannot resolve `color-mix()`, render `<foreignObject>`, or fetch a runtime web font, so always pipe through `scripts/mermaid_normalize.py` first (`--check` lint enforces this). It is pure Python, no Node bundled. `xychart-beta` is browser-only (it styles via `<style>` class selectors); use the hand-drawn chart diagrams for PDF. Full flow in `references/mermaid.md`.
|
||||
|
||||
## Refactor And Packaging Hard Stops
|
||||
|
||||
- When refactoring `scripts/build.py` or package helpers into new modules, confirm every new helper file is tracked by Git. `scripts/package-skill.sh` packages from `git ls-files`, so untracked modules pass local imports but disappear from `dist/kami.zip`.
|
||||
- Any source change that adds scripts, templates, reference JSON, workflows, or package inputs must refresh and inspect `dist/kami.zip`; package freshness is part of release readiness, not a later cleanup step.
|
||||
- Changes to `SKILL.md`, templates, scripts, references, or package inputs must decide explicitly whether `dist/kami.zip` needs refresh. If the behavior is shipped through the skill package, rebuild and inspect the ZIP before handoff.
|
||||
- Marketplace, plugin path, version, or generated mirror changes require runtime installation proof, not metadata proof only. For Claude Code changes, use an isolated `HOME=/tmp/...` smoke with `claude plugin marketplace add <path>`, `claude plugin install kami@kami`, `claude plugin details kami@kami`, and confirm the installed cache is the lightweight `plugins/kami` tree. For Codex changes, use an isolated `CODEX_HOME=/tmp/...` smoke with `codex plugin marketplace add <path>`, `codex plugin add kami@kami`, and `codex plugin list`; keep the generator, mirror tree, package audit, and install path aligned.
|
||||
- If `python3 scripts/build.py --verify` fails only because the host Python lacks PPTX fallback dependencies such as `python-pptx`, verify `slides` and `slides-en` from a temporary venv instead of treating the environment miss as a source regression.
|
||||
- Do not commit one-off review reports or diagnostic snapshots as durable docs. Extract stable rules into `AGENTS.md`, `CLAUDE.md`, `SKILL.md`, or `references/` and discard the stale report.
|
||||
|
||||
## CI And Verification Discipline
|
||||
|
||||
- Tests that need `weasyprint` / `pypdf` / `PyMuPDF` must run in a CI job that installs those deps (currently `verify-render`). The `lint-and-test` job ships only Pygments, so a `find_spec(...) is not None` skip-guard there silently skips the test while still printing `OK:`. A green `lint-and-test` does not mean the solver / render tests ran.
|
||||
- Edits to `.github/workflows/*.yml` should be validated on a feature branch (push, watch the run go green) before merging to `main`. Local font / dependency / runner assumptions diverge from CI more often than expected: this project has already burned commits on `pip` cache requiring a manifest, the `fallback_present` set missing Ubuntu defaults (DejaVu / Liberation), and CI never having commercial fonts (Charter / TsangerJinKai02).
|
||||
- Differences between CI and host behavior are expressed as a single explicit opt-in env var (`KAMI_ALLOW_FALLBACK_ONLY=1` for missing primary fonts). When a third such flag is needed, migrate to `references/verify_profile.json` or a `--ci-mode` CLI flag instead of letting `KAMI_*` env vars sprawl.
|
||||
|
||||
## Current Risk Areas
|
||||
|
||||
- WeasyPrint rendering is sensitive to font availability, solid hex tag backgrounds, page breaks, CJK fallback, and synthetic bold. Verify visually for template changes.
|
||||
- Slide output has three paths: `slides-weasy*.html` for default PDF decks, `slides*.py` for editable PPTX fallback, and `assets/templates/marp/slides-marp*.{md,css}` for Markdown-first Marp decks.
|
||||
- Marp theme CSS (`assets/templates/marp/slides-marp.css` and `-en`) inlines a full copy of the design tokens (`--parchment`, `--brand`, `--serif`, rhythm modules) because Marp themes must be self-contained. `build.py --sync` / `--check` token-sync the `marp/*.css` files and the CSS lint rules scan them too (both walk `shared.iter_template_files`), so token drift and rule violations in the Marp themes are caught, not silent. The remaining gap: the off-palette hex guard scans only editorial `TEMPLATES/*.html`, so off-palette colors in Marp CSS still need eyeball review.
|
||||
- AI/public visibility spans `index*.html`, `llms.txt`, `robots.txt`, `sitemap.xml`, FAQ JSON-LD, README install text, diagram counts, and release archive links.
|
||||
- `scripts/shared.py` centralizes constants used by the build scripts; keep paths and target names in sync before adding templates or diagrams.
|
||||
- `dist/kami.zip` is a tracked release archive. Packaging changes must update and inspect it deliberately.
|
||||
- Claude Code / Codex plugin files are generated artifacts. Do not edit `plugins/kami/`, `.claude-plugin/marketplace.json`, or `.agents/plugins/marketplace.json` directly; regenerate from the root source files and let `python3 scripts/build_metadata.py --check` catch drift.
|
||||
|
||||
## Hotspot Ownership
|
||||
|
||||
- `references/design.md` and `plugins/kami/skills/kami/references/design.md` own the Kami visual system, including large landing-page and documentation-site rules. Boundary: edit only the root source, and use the plugin path only as a generated mirror. Verification: `python3 scripts/build.py --check` plus screenshots for screen surfaces, then `python3 scripts/build_metadata.py --check`.
|
||||
- `styles.css` owns the hosted Kami public site shell, language switcher, gallery, and shared responsive behavior. Boundary: do not move generic template rules here. Verification: serve the site and screenshot 375px / 1280px per locale touched.
|
||||
- `assets/templates/landing-page.html`, `assets/templates/landing-page-en.html`, `assets/templates/landing-page-ko.html`, `plugins/kami/skills/kami/assets/templates/landing-page.html`, `plugins/kami/skills/kami/assets/templates/landing-page-en.html`, and `plugins/kami/skills/kami/assets/templates/landing-page-ko.html` own the generic screen-first template shipped to users. Boundary: edit root templates, keep real product-site facts in filled sites, and treat plugin paths as generated mirrors. Verification: `python3 scripts/build.py landing-page`, browser screenshots for changed breakpoints, and `python3 scripts/build_metadata.py --check`.
|
||||
- `references/production.md` and `plugins/kami/skills/kami/references/production.md` own production troubleshooting, pre-ship review, and known render pitfalls. Boundary: add stable invariants only, not dated review notes. Verification: `python3 scripts/build.py --check`, `python3 scripts/build_metadata.py --check`, and the relevant render command named by the rule.
|
||||
- `assets/templates/resume.html`, `assets/templates/resume-ko.html`, `plugins/kami/skills/kami/assets/templates/resume.html`, and `plugins/kami/skills/kami/assets/templates/resume-ko.html` own high-density resume layout. Boundary: preserve the two-page contract and do not fix overflow by generic shrinking first. Verification: `python3 scripts/build.py --verify resume` and `python3 scripts/build.py --verify resume-ko`.
|
||||
- `assets/demos/demo-resume-ko.html` owns the Korean resume demo content, not the template contract. Boundary: regenerate demo outputs when the demo changes, but put durable resume rules in templates or references. Verification: build the affected demo and confirm page count plus rendered screenshot.
|
||||
- `scripts/tests/test_build.py` and `plugins/kami/skills/kami/scripts/tests/test_build.py` own the zero-dependency test suite. Boundary: edit the root test file and regenerate the plugin mirror. Verification: `python3 scripts/tests/test_build.py` and `python3 scripts/build_metadata.py --check`.
|
||||
|
||||
## High-Risk Pitfalls
|
||||
|
||||
See `references/production.md` Part 4.
|
||||
|
||||
1. Tag rgba double rectangle: use solid hex backgrounds.
|
||||
2. Thin border plus border-radius double ring: border < 1pt with border-radius can trigger it.
|
||||
3. Resume 2-page overflow: tiny font, fallback, line-height, or margin changes can break it.
|
||||
4. `break-inside` fails inside flex: wrap content in a block wrapper.
|
||||
5. `height: 100vh` is unreliable under `@page`: use explicit mm values.
|
||||
6. SVG marker `orient="auto"` does not rotate in WeasyPrint: draw arrowheads manually.
|
||||
7. Section body text should not use `max-width`: `.manifesto`, `.section-lede`, and similar text should fill the `.page` container. Exceptions: `.type-sample` and `.footer .colophon`.
|
||||
8. Diagram template changes must sync to index showcase SVGs: any visual fix to `assets/diagrams/*.html` must also be applied to the matching mini SVG in `index.html`, `index-zh.html`, `index-ja.html`, `index-ko.html`, `index-tw.html`.
|
||||
|
||||
## Demo Screenshots
|
||||
|
||||
All demo PNG files use **1241x1754px** (first A4 portrait page at 150dpi).
|
||||
|
||||
For one-page and multi-page documents (one-pager / letter / resume / portfolio / long-doc / equity-report), capture page 1:
|
||||
|
||||
```bash
|
||||
pdftoppm -r 150 -f 1 -l 1 -png <pdf> /tmp/p && cp /tmp/p-1.png <target>.png
|
||||
```
|
||||
|
||||
For landscape slides, capture the first 2 pages, resize each to 867px high, add a 20px gap, then extend to 1241px wide:
|
||||
|
||||
```bash
|
||||
pdftoppm -r 150 -f 1 -l 2 -png <pdf> /tmp/sl
|
||||
magick /tmp/sl-1.png -resize x867 /tmp/sl1.png
|
||||
magick /tmp/sl-2.png -resize x867 /tmp/sl2.png
|
||||
magick -size $(identify -format '%w' /tmp/sl1.png)x20 xc:'#f5f4ed' /tmp/gap.png
|
||||
magick /tmp/sl1.png /tmp/gap.png /tmp/sl2.png -append /tmp/stacked.png
|
||||
magick /tmp/stacked.png -gravity Center -background '#f5f4ed' -extent 1241x1754 <target>.png
|
||||
```
|
||||
|
||||
## Verification Details
|
||||
|
||||
- Expected page counts: one-pager 1, letter 1, resume 2 strict, long-doc 7 plus or minus 2, portfolio 6 plus or minus 2, slides 7 plus or minus 3, equity-report 2 to 3, changelog 1 to 2. Landing pages are browser-only HTML with no PDF page count.
|
||||
- `scripts/build.py` sets PDF `/Author` from `git config user.name` or `KAMI_AUTHOR` only when the template still has an author placeholder. `/Producer` and `/Creator` should remain `Kami`.
|
||||
- Demo PNGs under `assets/demos/` are first-page previews at 1241x1754px. For slide demos, capture the first two landscape pages, stack them with a parchment gap, then extend to 1241x1754px.
|
||||
- Diagram count and names must stay aligned across `SKILL.md`, `CHEATSHEET.md`, `README.md`, `index*.html`, and `assets/diagrams/`.
|
||||
- Long-doc TOCs use WeasyPrint `target-counter()` and stable chapter ids for rendered page numbers. Do not reintroduce hand-written `.toc-page` spans in the templates. Running headers default to `h1`; if a filled document does not use `h1` for chapter titles, add `.running-title` to the element that should drive the header.
|
||||
|
||||
## Verification
|
||||
|
||||
- Template, CSS, or script changes: run `python3 scripts/build.py --check` (CSS lint + token sync + base/variant cross-template `:root` consistency, currently CN↔EN and CN↔KO) and `python3 scripts/build.py --verify`.
|
||||
- Demo changes: regenerate the affected demo outputs and confirm page counts stay in range.
|
||||
- Font issues: run `bash scripts/ensure-fonts.sh`, then rebuild the affected target.
|
||||
- Markdown-sourced filled documents: run `python3 scripts/build.py --check-markdown path/to/filled.pdf` after rendering, especially when the source had thematic breaks, bold markers, or inline-code backticks.
|
||||
- Slide rhythm or deck changes: run `python3 scripts/build.py --check-rhythm slides slides-en` plus the affected render command.
|
||||
- Public site or AI visibility changes: check `index*.html`, README, `llms.txt`, `robots.txt`, `sitemap.xml`, JSON-LD, FAQ, install links, and release/download links together. Serve the page and screenshot 375px / 1280px per locale, plus 320px when CTA width or mobile nav changes.
|
||||
- Packaging changes: run `bash scripts/package-skill.sh` and confirm `dist/kami.zip` stays small enough for release upload. Inspect `unzip -l dist/kami.zip` for accidental large fonts, showcase screenshots, cache files, or missing new helper files.
|
||||
- Claude Code / Codex marketplace changes: run `python3 scripts/build_metadata.py --check` and confirm `plugins/kami/.claude-plugin/plugin.json`, `plugins/kami/.codex-plugin/plugin.json`, `.claude-plugin/marketplace.json`, and `.agents/plugins/marketplace.json` stay generated. If install behavior, version selection, or source path changed, also run the matching isolated install smoke.
|
||||
- Documentation-only changes: check links and references.
|
||||
|
||||
## Critical Line-Break Scan
|
||||
|
||||
- Every user-visible typeset deliverable (rendered PDFs, README, public site pages) gets a page-by-page scan for three critical wrap states before handoff: a trailing line of only 1-2 words (orphan), a line one word away from wrapping, and a line that wraps early without filling its container.
|
||||
- Split the work between script and eye: `python3 scripts/build.py --check-orphans <pdf>` and `--check-density <pdf>` deterministically catch PDF orphan trailing lines and pages with too much trailing whitespace; the manual pass covers what they cannot see, near-wrap and premature-wrap states inside a page, plus non-PDF surfaces (README, `index*.html` at 375px / 1280px screenshots).
|
||||
- One hit means a whole-document sweep for the same class of issue, not a single-spot fix. Fix by adjusting content length (add or cut words) first; changing font size or spacing to dodge a wrap is the last resort and must re-pass `python3 scripts/build.py --check` and the page-count contract afterwards.
|
||||
|
||||
## Release Notes
|
||||
|
||||
- Before drafting notes, read the previous published release and treat it as the hard format template: `gh release view $(gh release list -R tw93/Kami --limit 1 --json tagName --jq '.[0].tagName') -R tw93/Kami`. Mirror its exact structure (centered logo block, `### Changelog`, `### 更新日志`, closing tagline blockquote); do not rebuild the shape from memory.
|
||||
- For public releases, keep notes concise and bilingual. Use one-to-one English and Chinese changelog items, 5 to 8 items, one sentence each.
|
||||
- Generate the scaffold with `python3 scripts/draft-release-notes.py V<prev>..V<new> --version V<new> --title "<Codename>"`, then regroup the raw commit list into 5 to 8 product-themed bullets and translate each to Chinese. Do not paste raw commit subjects.
|
||||
- Match the established shape: title is `V<x.y.z> <Two-Word Codename>` (e.g. `V1.7.2 Cleaner Resumes`), body is the centered logo block + `### Changelog` (English numbered list) + `### 更新日志` (Chinese numbered list) + the closing tagline line.
|
||||
|
||||
## Release Flow
|
||||
|
||||
- `bash scripts/package-skill.sh` writes the tracked `dist/kami.zip` release archive with a top-level `kami/` skill folder and excludes large TsangerJinKai / Source Han Serif K font files plus README/public-site-only showcase screenshots.
|
||||
- `dist/kami.zip` should be committed with release changes and uploaded to the latest GitHub release asset when refreshing the Claude Desktop package.
|
||||
- When refreshing a GitHub release asset, download the uploaded `kami.zip` and compare ZIP entry names plus per-entry SHA-256 digests against local `dist/kami.zip`; do not rely on release-page text, file size, or container SHA alone.
|
||||
- README and public site download links use `https://github.com/tw93/kami/releases/latest/download/kami.zip`; prefer refreshing that asset for small packaging or documentation fixes instead of creating a new tag.
|
||||
- Create a new version tag only when the maintainer explicitly wants a versioned release. Tag the commit that already contains the final refreshed `dist/kami.zip`; do not tag a source-only commit and refresh the archive afterward.
|
||||
- On tag push, `.github/workflows/release.yml` builds and attaches `dist/kami.zip`, creates the release if missing, and adds the house-style reactions (`+1 eyes heart hooray laugh rocket`, one each). Do not `gh release create` by hand; let CI create the placeholder, then set the real title and notes with `gh release edit V<x> --title "V<x> <Codename>" --notes-file <file>`.
|
||||
- If reactions are ever missing (older release, CI skipped), add them manually: `rid=$(gh api repos/tw93/Kami/releases/tags/V<x> --jq .id); for r in +1 eyes heart hooray laugh rocket; do gh api -X POST repos/tw93/Kami/releases/$rid/reactions -f content="$r"; done`.
|
||||
- Reactions are part of publish completion, not optional polish. After the release is live, read back `gh api repos/tw93/Kami/releases/$rid/reactions --jq '.[].content'` and confirm all six positive reactions (`+1`, `laugh`, `heart`, `hooray`, `rocket`, `eyes`) are present. Never add `-1` or `confused`; a negative reaction on our own release reads as self-deprecation.
|
||||
|
||||
## Fonts
|
||||
|
||||
- Chinese templates use TsangerJinKai02 W04/W05. Commercial use requires the appropriate font license.
|
||||
- If TsangerJinKai is unavailable, fall back through Source Han Serif SC, Noto Serif CJK SC, Songti SC, STSong, then Georgia.
|
||||
- English templates use Charter serif. Japanese output uses YuMincho first, then Hiragino Mincho ProN, Noto Serif CJK JP, Source Han Serif JP, TsangerJinKai02, and generic serif.
|
||||
- Korean templates use Source Han Serif K (Adobe, also distributed as Noto Serif KR by Google). Fallback chain: Source Han Serif K, Source Han Serif KR, Noto Serif KR, Apple SD Gothic Neo, AppleMyungjo, Charter, Georgia. `Source Han Serif KR` is the actual family name inside the bundled OTFs and must stay in the chain so fontconfig can resolve the `ensure-fonts.sh`-downloaded font by name on an offline Linux skill install.
|
||||
- Claude Desktop ZIPs do not bundle TsangerJinKai TTF or Source Han Serif K OTF files (the OTFs are OFL-licensed and git-tracked for the CDN `@font-face` fallback, but excluded from the package to keep it small). Run `bash scripts/ensure-fonts.sh` before building Chinese or Korean documents when fonts are missing; it drops them in the XDG user font dir (fontconfig-scanned, outside the skill), so the installed skill stays small and online renders still use the jsDelivr `@font-face` fallback.
|
||||
@@ -0,0 +1,378 @@
|
||||
# kami · Cheatsheet
|
||||
|
||||
One-page quick reference. Scan before filling a template or tweaking a detail. Full spec in `references/design.md`.
|
||||
|
||||
## Ten invariants
|
||||
|
||||
1. Page background `#f5f4ed` (parchment), never pure white
|
||||
2. Single accent: ink-blue `#1B365D`
|
||||
3. All grays **warm-toned** (yellow-brown undertone), no cool blue-gray
|
||||
4. One serif font per page (headings + body). `--sans` is a CSS alias for the same family; introduce a real sans only for genuinely UI-style chrome
|
||||
5. Serif weight locked at 500, no bold
|
||||
6. Line-height: headlines 1.1-1.3 / dense 1.4-1.45 / reading 1.5-1.55
|
||||
7. Letter-spacing: Chinese body with TsangerJinKai 0.1-0.2pt (dense layouts may push to 0.3pt); English body 0; small labels and all-caps overlines get +0.2-1pt
|
||||
8. Tag backgrounds solid hex, no rgba (WeasyPrint double-rectangle bug)
|
||||
9. Depth via ring / whisper shadow, no hard drop shadows
|
||||
10. No italic in templates or demos
|
||||
|
||||
## Sources and Materials
|
||||
|
||||
|
||||
| Trigger | Do first |
|
||||
| --------------------------------------------------------- | ------------------------------------------------------------------- |
|
||||
| Latest product / version / launch / funding / market data | Check reliable sources first |
|
||||
| Company / product / project branded doc | Confirm logo, product image, or UI screenshot |
|
||||
| Key number or result | Record the source; if unverifiable, write magnitude or mark missing |
|
||||
| Missing material | Mark the gap or ask the user; do not use unrelated imagery |
|
||||
|
||||
|
||||
## Color
|
||||
|
||||
|
||||
| Role | Hex | Use |
|
||||
| ------------ | ------------- | --------------------------------------------------- |
|
||||
| Parchment | `#f5f4ed` | Page background |
|
||||
| Ivory | `#faf9f5` | Card / lifted container |
|
||||
| Warm Sand | `#e8e6dc` | Button / interactive surface |
|
||||
| Dark Surface | `#30302e` | Dark container |
|
||||
| Deep Dark | `#141413` | Dark page background |
|
||||
| **Brand** | **`#1B365D`** | **Accent · CTA · title left bar (≤ 5% of surface)** |
|
||||
| Ink Light | `#2D5A8A` | Links on dark surfaces |
|
||||
| Near Black | `#141413` | Primary text |
|
||||
| Dark Warm | `#3d3d3a` | Secondary text · table headers · links |
|
||||
| Olive | `#504e49` | Subtext · descriptions |
|
||||
| Stone | `#6b6a64` | Tertiary · metadata |
|
||||
| Border | `#e8e6dc` | Primary border · section divider |
|
||||
| Border Soft | `#e5e3d8` | Secondary border · row separator |
|
||||
|
||||
|
||||
**rgba -> solid** (parchment base + ink-blue):
|
||||
|
||||
|
||||
| Alpha | Solid |
|
||||
| -------- | --------------------------- |
|
||||
| 0.08 | `#EEF2F7` |
|
||||
| 0.14 | `#E4ECF5` |
|
||||
| **0.18** | **`#E4ECF5`** ← default tag |
|
||||
| 0.22 | `#D0DCE9` |
|
||||
| 0.30 | `#D6E1EE` |
|
||||
|
||||
|
||||
## Type (print pt)
|
||||
|
||||
|
||||
| Role | Size | Weight | Line-height |
|
||||
| ---------- | ---- | ------ | ----------- |
|
||||
| Display | 36 | 500 | 1.10 |
|
||||
| H1 | 22 | 500 | 1.20 |
|
||||
| H2 | 16 | 500 | 1.25 |
|
||||
| H3 | 13 | 500 | 1.30 |
|
||||
| Body Lead | 11 | 400 | 1.55 |
|
||||
| Body | 10 | 400 | 1.55 |
|
||||
| Body Dense | 9.2 | 400 | 1.42 |
|
||||
| Caption | 9 | 400 | 1.45 |
|
||||
| Label | 9 | 600 | 1.35 |
|
||||
| Tiny | 9 | 400 | 1.40 |
|
||||
|
||||
|
||||
Screen (px) ≈ pt × 1.33.
|
||||
Minimum floor: web text >= 12px, PDF text >= 9pt.
|
||||
|
||||
## Font stacks
|
||||
|
||||
Each language uses a single serif for the entire page. `--sans` always equals `var(--serif)`.
|
||||
|
||||
English:
|
||||
|
||||
```css
|
||||
--serif: Charter, Georgia, Palatino,
|
||||
"Times New Roman", serif;
|
||||
--sans: var(--serif);
|
||||
--mono: "JetBrains Mono", "SF Mono", "Fira Code",
|
||||
Consolas, Monaco, monospace;
|
||||
```
|
||||
|
||||
Chinese:
|
||||
|
||||
```css
|
||||
--serif: "TsangerJinKai02", "Source Han Serif SC",
|
||||
"Noto Serif CJK SC", "Songti SC", "STSong",
|
||||
Georgia, serif;
|
||||
--sans: var(--serif);
|
||||
--mono: "JetBrains Mono", "SF Mono", Consolas,
|
||||
"TsangerJinKai02", "Source Han Serif SC",
|
||||
monospace;
|
||||
```
|
||||
|
||||
Japanese:
|
||||
|
||||
```css
|
||||
--serif: "YuMincho", "Yu Mincho", "Hiragino Mincho ProN",
|
||||
"Noto Serif CJK JP", "Source Han Serif JP",
|
||||
"TsangerJinKai02", Georgia, serif;
|
||||
--sans: var(--serif);
|
||||
```
|
||||
|
||||
Any font-family that may render Chinese or Japanese must include a CJK fallback, including `@page` footer text, `pre`, `code`, and SVG labels. A pure mono stack can render missing glyph boxes in WeasyPrint.
|
||||
|
||||
## Spacing (4pt base)
|
||||
|
||||
|
||||
| Tier | Value | Use |
|
||||
| ---- | -------- | ---------------------- |
|
||||
| xs | 2-3pt | Inline |
|
||||
| sm | 4-5pt | Tag padding |
|
||||
| md | 8-10pt | Component interior |
|
||||
| lg | 16-20pt | Between components |
|
||||
| xl | 24-32pt | Section-title margin |
|
||||
| 2xl | 40-60pt | Between major sections |
|
||||
| 3xl | 80-120pt | Between chapters |
|
||||
|
||||
|
||||
**Page margins (A4)**
|
||||
|
||||
|
||||
| Document | T · R · B · L |
|
||||
| ------------- | -------------------- |
|
||||
| Resume | 11 · 13 · 11 · 13 mm |
|
||||
| One-Pager | 15 · 18 · 15 · 18 mm |
|
||||
| Long Doc | 20 · 22 · 22 · 22 mm |
|
||||
| Letter | 25 mm all sides |
|
||||
| Portfolio | 12 · 15 · 12 · 15 mm |
|
||||
| Equity Report | 16 · 18 · 18 · 18 mm |
|
||||
| Changelog | 20 · 22 · 22 · 22 mm |
|
||||
| Landing Page | N/A (screen-first, max-width: 1120px, padding: 88px 64px) |
|
||||
|
||||
|
||||
## Radius scale
|
||||
|
||||
`4pt -> 6pt -> 8pt (default) -> 12pt -> 16pt -> 24pt -> 32pt (hero)`
|
||||
|
||||
## Common CSS snippets
|
||||
|
||||
### Card
|
||||
|
||||
```css
|
||||
.card {
|
||||
background: var(--ivory);
|
||||
border: 0.5pt solid var(--border-cream);
|
||||
border-radius: 8pt;
|
||||
padding: 16pt 20pt;
|
||||
transition: box-shadow 0.2s;
|
||||
}
|
||||
.card:hover {
|
||||
box-shadow: 0 4pt 24pt rgba(0, 0, 0, 0.05); /* whisper shadow */
|
||||
}
|
||||
```
|
||||
|
||||
### Tag (default lightest solid)
|
||||
|
||||
```css
|
||||
.tag {
|
||||
background: #EEF2F7; /* 0.08 equivalent */
|
||||
color: var(--brand);
|
||||
font-size: 9pt; font-weight: 600;
|
||||
padding: 1pt 5pt;
|
||||
border-radius: 2pt;
|
||||
letter-spacing: 0.4pt;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
```
|
||||
|
||||
### Section title (brand left bar is the signature move)
|
||||
|
||||
```css
|
||||
.section-title {
|
||||
font-family: var(--serif);
|
||||
font-size: 14pt; font-weight: 500;
|
||||
color: var(--near-black);
|
||||
margin: 24pt 0 10pt 0;
|
||||
border-left: 2.5pt solid var(--brand);
|
||||
border-radius: 1.5pt;
|
||||
padding-left: 8pt;
|
||||
}
|
||||
```
|
||||
|
||||
Resume exception: `resume*.html` uses a quiet bottom rule instead of the brand left bar. Keep project rows borderless so section titles do not create double rules or lonely page-top lines.
|
||||
|
||||
### Table (kami-table)
|
||||
|
||||
Base class works on bare `<table>` or `.kami-table`. Add variant classes for density/alignment:
|
||||
|
||||
```css
|
||||
/* Base */
|
||||
table, .kami-table {
|
||||
width: 100%; border-collapse: collapse;
|
||||
font-size: 9.5pt; margin: 12pt 0; break-inside: avoid;
|
||||
}
|
||||
table th { text-align: left; font-weight: 500; color: var(--dark-warm);
|
||||
padding: 6pt 8pt; border-bottom: 1pt solid var(--border); }
|
||||
table td { padding: 5pt 8pt; border-bottom: 0.3pt solid var(--border-soft);
|
||||
vertical-align: top; }
|
||||
```
|
||||
|
||||
|
||||
| Variant | Class | Effect |
|
||||
| --------- | ------------------ | ---------------------------------------------------- |
|
||||
| Compact | `.compact` | 8pt font, tight padding (data-dense tables) |
|
||||
| Financial | `.financial` | Right-align all columns except first, `tabular-nums` |
|
||||
| Striped | `.striped` | Alternating `var(--ivory)` row background |
|
||||
| Total row | `.total` on `<tr>` | Bold, brand top border, no bottom border |
|
||||
|
||||
|
||||
Combine freely: `<table class="kami-table financial striped">`.
|
||||
|
||||
### Metric (data card)
|
||||
|
||||
```css
|
||||
.metric { display: flex; align-items: baseline; gap: 6pt; }
|
||||
.metric-value {
|
||||
font-family: var(--serif); font-size: 16pt; font-weight: 500;
|
||||
color: var(--brand);
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
.metric-label { font-size: 9pt; color: var(--olive); }
|
||||
```
|
||||
|
||||
### Quote
|
||||
|
||||
```css
|
||||
.quote {
|
||||
border-left: 2pt solid var(--brand);
|
||||
padding: 4pt 0 4pt 14pt;
|
||||
color: var(--olive);
|
||||
line-height: 1.55;
|
||||
}
|
||||
```
|
||||
|
||||
## Diagram components
|
||||
|
||||
Eighteen built-in diagram types (incl. Mermaid-sourced sequence / class / ER; see `references/mermaid.md`). Extract the `<svg>` block and embed in a `<figure>` in long-doc / portfolio:
|
||||
|
||||
|
||||
| Type | File | Use |
|
||||
| ------------- | ------------------------------------ | ----------------------------------------------- |
|
||||
| Architecture | `assets/diagrams/architecture.html` | System components and connections |
|
||||
| Architecture Board | `assets/diagrams/architecture-board.html` | Report-scale five-layer system board (standalone page) |
|
||||
| Flowchart | `assets/diagrams/flowchart.html` | Decision branches and flows |
|
||||
| Quadrant | `assets/diagrams/quadrant.html` | 2×2 positioning |
|
||||
| Bar Chart | `assets/diagrams/bar-chart.html` | Category comparison (up to 8 groups × 3 series) |
|
||||
| Line Chart | `assets/diagrams/line-chart.html` | Trends over time (up to 12 points × 3 lines) |
|
||||
| Donut Chart | `assets/diagrams/donut-chart.html` | Proportional breakdown (up to 6 segments) |
|
||||
| State Machine | `assets/diagrams/state-machine.html` | Finite states + directed transitions |
|
||||
| Timeline | `assets/diagrams/timeline.html` | Time axis + milestone events |
|
||||
| Swimlane | `assets/diagrams/swimlane.html` | Cross-responsibility process flow |
|
||||
| Tree | `assets/diagrams/tree.html` | Hierarchical relationships |
|
||||
| Layer Stack | `assets/diagrams/layer-stack.html` | Vertically stacked system layers |
|
||||
| Venn | `assets/diagrams/venn.html` | Set intersections and overlaps |
|
||||
| Candlestick | `assets/diagrams/candlestick.html` | OHLC price history (up to 30 days) |
|
||||
| Waterfall | `assets/diagrams/waterfall.html` | Revenue bridge / decomposition |
|
||||
|
||||
|
||||
Usage: extract the `<svg>` block from the HTML file and paste into the template's `<figure>` container.
|
||||
|
||||
**Repo-maintained diagram** (README / docs-site figure living in the user's repository): keep the trio consistent, `index.html` source + same-name PNG re-exported after every change + `prompt.md` (must preserve / suggested additions / visual direction / sister boundaries). Evidence pass before drawing; maturity encoding for shipped / in-build / future. See `references/diagrams.md` «Maintained diagram assets».
|
||||
|
||||
**Data chart colors**: primary series `#1B365D` · secondary `#504e49` → `#6b6a64` → `#b8b7b0` → `#d4d3cd` → `#EEF2F7`.
|
||||
|
||||
**Editing data**: only modify elements between `<!-- DATA START -->` / `<!-- DATA END -->`, leave CSS untouched. All coordinates must be divisible by 4.
|
||||
|
||||
## Dark section
|
||||
|
||||
Alternate light/dark rhythm: add `.sd-alt` to any section container.
|
||||
|
||||
- Background switches to `--deep-dark` (`#141413`)
|
||||
- Body text switches to `--warm-silver` (`#b0aea5`)
|
||||
- Headings switch to `--ivory`
|
||||
- Appropriate for: section-level light/dark alternation in long-doc / portfolio
|
||||
- Restriction: showcase pages only, never in print templates
|
||||
|
||||
## Verification checks
|
||||
|
||||
`python3 scripts/build.py --verify [target]` checks source templates and slides in sequence:
|
||||
|
||||
1. Source file exists
|
||||
2. WeasyPrint render to PDF for HTML / diagram targets
|
||||
3. Page count check for strict targets
|
||||
4. Font embedding check
|
||||
5. PPTX generation for `slides` / `slides-en`
|
||||
|
||||
Source templates intentionally keep `{{...}}` fields. Run `python3 scripts/build.py --check-placeholders path/to/filled.html` on completed documents. Run `python3 scripts/build.py --check-density` to warn on pages with >25% trailing whitespace (skips cover).
|
||||
|
||||
Marp variant deck (opt-in): `assets/templates/marp/`. Render with local `marp-cli`. See design.md §8 + production.md Part 2.5.
|
||||
|
||||
## Content quality (one rule per type)
|
||||
|
||||
Full quality bars in `references/writing.md`. The single most important rule for each document type:
|
||||
|
||||
|
||||
| Document | Core quality rule |
|
||||
| ------------- | ------------------------------------------------------------------------------------ |
|
||||
| Resume | Every bullet: Action + Scope + Measurable Result + Business Outcome |
|
||||
| Portfolio | Open with the problem and stakes, not the project name |
|
||||
| Slides | Slide titles are full sentences (assertions), not topic labels |
|
||||
| Equity Report | Lead with variant perception: what you see that the market doesn't |
|
||||
| Long Document | Each chapter claim paragraph must survive the "so what?" test |
|
||||
| One-Pager | Metrics are the headline; if the 4 cards don't tell the story, the metrics are wrong |
|
||||
| Letter | First paragraph states purpose in one sentence |
|
||||
| Changelog | One sentence per change, verb-led, user-facing language |
|
||||
|
||||
|
||||
## Per-page font size strategy (Resume two-page)
|
||||
|
||||
Page 1 carries the projects section, which is the densest content. Page 2 carries open source, convictions, impact, skills, and education, which has more breathing room.
|
||||
|
||||
| Location | Class | Default | Dense (5 projects) |
|
||||
|---|---|---|---|
|
||||
| Project body | `.proj-text` | 9pt / lh 1.40 | 9pt / lh 1.38 |
|
||||
| Timeline body | `.tl-body` | 9pt / lh 1.40 | 8.5pt (CN) |
|
||||
| Summary | `.summary` | 9.2pt | 9pt via body |
|
||||
| Section titles | `.section-title` | margin-top 5mm | 3.5mm |
|
||||
| OS intro | `.os-intro` | 9.2pt | unchanged |
|
||||
| Conviction body | `.conv-body` | 9pt | unchanged |
|
||||
| Skills body | `.skill-body` | 9pt | unchanged |
|
||||
|
||||
**Reference config (5 projects + full page 2)**:
|
||||
|
||||
```html
|
||||
<body class="resume--dense">
|
||||
<!-- page 1: 5 projects, timeline, summary -->
|
||||
<!-- page 2: OS grid (6), convictions, impact, skills, edu -->
|
||||
</body>
|
||||
```
|
||||
|
||||
Filled resume PDFs should be exactly 2 pages with both pages visually used. Check the rendered result:
|
||||
|
||||
```bash
|
||||
python3 scripts/build.py --check-resume-balance path/to/resume.pdf
|
||||
```
|
||||
|
||||
Page 2 font sizes stay at template defaults. The density variant only tightens page 1 elements. If page 2 has unusually long content, reduce `.os-intro`, `.conv-body`, or `.skill-body` individually, never globally.
|
||||
|
||||
Resume visual rule: header and section titles carry the only structural rules. Top metrics stack value over label so labels stay single-line; project rows separate by padding, not borders.
|
||||
|
||||
## Quick decisions
|
||||
|
||||
|
||||
| Need | Use |
|
||||
| ------------------- | -------------------------------------------------------------- |
|
||||
| Headline | serif 500, line-height 1.10-1.30 |
|
||||
| Reading body (EN) | serif 400, 9.5-10pt, 1.55 |
|
||||
| Reading body (CN) | sans 400, 9.5-10pt, 1.55 |
|
||||
| Emphasize a number | `color: var(--brand)`, no bold |
|
||||
| Divide two sections | 2.5pt brand left bar, or 0.5pt warm dotted |
|
||||
| Quote | 2pt brand left border + olive color |
|
||||
| Code | ivory bg + 0.5pt border + 6pt radius + mono |
|
||||
| Primary button | brand fill + ivory text |
|
||||
| Secondary button | warm-sand + dark-warm |
|
||||
| Chapter start | serif heading + 2.5pt brand left bar |
|
||||
| Cover | Display heading + right-aligned author/date + heavy whitespace |
|
||||
| Figure SVG | `width: 100%; height: auto; max-height: <safe>`. Never `max-height` alone (starves width on wide viewBoxes; production.md #17). |
|
||||
| Metric labels (4-col) | Soft cap 14-18 chars at 9pt Charter; trim context, don't wrap (production.md #18). |
|
||||
| Multi-column body | Hold lengths within ±10 chars across parallel columns (production.md #19). |
|
||||
| Image references | Always inside `assets/demos/images/` or `assets/illustrations/`; never `../../sibling-project/...` (production.md #20). |
|
||||
| Metric row layout | Vertical stack (`flex-direction: column`); horizontal baseline-align breaks when any label wraps (production.md #21). |
|
||||
| Slide bullets | Numerals `1. 2. 3.` or `•`; en-dash `–` reads informal at slide scale (production.md #22). Print docs keep en-dash. |
|
||||
|
||||
|
||||
Not on the table -> first principles: **serif carries authority, sans carries utility, warm gray carries rhythm, ink-blue carries focus**.
|
||||
@@ -0,0 +1,44 @@
|
||||
# Kami
|
||||
|
||||
Document-generation skill and template system. Editorial HTML templates + PDF/PPTX/PNG build pipeline.
|
||||
|
||||
## 启动前
|
||||
|
||||
- 个人/全局规则可放在仓库外;本文件只记录 Kami 项目内的 Claude Code 入口和维护规则。
|
||||
- 仓库地图、Working Rules、Current Risk Areas、Verification Details、Release Flow、Fonts 全在 `AGENTS.md`。
|
||||
- 模板设计规范看 `references/design.md`,写作规范看 `references/writing.md`,反模式 checklist 看 `references/anti-patterns.md`。
|
||||
|
||||
## 常用命令
|
||||
|
||||
```bash
|
||||
python3 scripts/build.py # 构建所有目标
|
||||
python3 scripts/build.py --check # 快速校验
|
||||
python3 scripts/build.py --verify # 完整验证
|
||||
python3 scripts/build.py --check-markdown path/to/filled.pdf # 检查 Markdown 标记残留
|
||||
python3 scripts/build_metadata.py --check # Claude/Codex 插件镜像和 marketplace 漂移检查
|
||||
python3 scripts/tests/test_build.py # 测试套件
|
||||
bash scripts/ensure-fonts.sh # 字体恢复(缺字体或字体被截断时)
|
||||
bash scripts/package-skill.sh # 构建 release 压缩包
|
||||
python3 scripts/mermaid_normalize.py raw.svg -o clean.svg # beautiful-mermaid SVG 重上 Kami 色 + WeasyPrint 安全化(无 Node)
|
||||
```
|
||||
|
||||
## 项目独有硬规则
|
||||
|
||||
与 `AGENTS.md` Working Rules 重叠或冲突时,以 `AGENTS.md` 为准(它是跨 runtime 的单一 source of truth);本节只是高频速查。
|
||||
|
||||
- 改 style 时同步更新 `references/design.md` 和模板 tokens,不要只改单点。
|
||||
- 加新模板:从最近的模板复制,对齐 `references/design.md`,加 demo 覆盖。
|
||||
- 不要在 docs / template 注释 / 脚本输出里用图形 emoji。脚本状态用 `OK:` / `ERROR:`。
|
||||
- 仓库 docs(`README.md`、`index*.html`、`llms.txt`、`AGENTS.md`、`CLAUDE.md` 等)和模板生成产物禁止 em dash(`—`,U+2014),用冒号 / 逗号 / 句号 / 括号代替。生成文档侧另见 `references/anti-patterns.md` #27。自检:`grep -rn '—' README.md llms.txt index*.html`(`references/anti-patterns.md` 内是教学反例,豁免)。
|
||||
- 模板**内联** CSS,不抽公共 partial。修 CSS 漂移时跨模板同步改,不要引入 build-time include。
|
||||
- 所有模板注册表都在 `scripts/shared.py`(`HTML_TEMPLATES` 文档 / `SCREEN_TEMPLATES` 浏览器 / `DIAGRAM_TEMPLATES` 图),build.py 从中派生。加删模板或图改这一处,不要分别改 build.py。
|
||||
- 不打包大体积商业字体到 `dist/kami.zip`,但模板要保留稳定的本机预览路径;ZIP 内必须是顶层 `kami/` skill 文件夹。
|
||||
- `dist/kami.zip` 是 tracked release 制品。小修通常刷 latest release 资源即可,不必新 tag。
|
||||
- 改 build / packaging 相关代码后,刷新并检查 `dist/kami.zip`;新增 helper/module/reference JSON 后,确认文件已被 Git 跟踪并进入 package。
|
||||
- 官网 / AI 可见性改动不只看首页:同步 `index*.html`、README、`llms.txt`、`robots.txt`、`sitemap.xml`、JSON-LD、FAQ、安装 / 版本 / 支持链接,并看 375px / 1280px 真实截图。
|
||||
- 改 Codex / Claude 插件 marketplace、版本或安装路径后,不只看 metadata;跑 `python3 scripts/build_metadata.py --check`,必要时用隔离 `CODEX_HOME=/tmp/...` 或 `HOME=/tmp/...` 的 Claude Code 安装路径做真实冒烟。
|
||||
- 刷新 release 包或 latest 资源前后,不只看页面大小;下载 `kami.zip`,对比 ZIP entry 列表和每个 entry 的 SHA-256。
|
||||
- 不提交一次性的 review 报告或诊断快照;只把稳定规则沉淀到 `AGENTS.md`、`SKILL.md` 或 `references/`。
|
||||
- 写 release notes 前先 `gh release view` 上一个正式 release,把它当格式硬模板,不凭记忆重建结构;发布后回读 reaction 确认六个正向表情(+1 / laugh / heart / hooray / rocket / eyes)都在,绝不加 -1 / confused。
|
||||
- 排版产物(PDF / README / 官网页)交付前扫临界换行:`--check-orphans` / `--check-density` 管 PDF 孤行和稀疏页,人工扫"差一个词就换行 / 未撑满就提前换行"和非 PDF 面;发现一处即全文档同类全修,优先改内容长度,不动字号间距。
|
||||
- Mermaid 图:raw beautiful-mermaid SVG 不能直接进 PDF 模板,先过 `scripts/mermaid_normalize.py`(纯 Python,会重上 Kami 色 + 解析 `color-mix()`;`--check` 会拦未归一化的 `color-mix(` / `foreignObject` / web font)。Kami 不打包 Node:要从 Mermaid 文本出新图,在任意 beautiful-mermaid(如 agents.craft.do/mermaid)里生成 SVG 再跑 normalizer。`xychart` 只走浏览器,PDF 用现有手绘图。细节见 `references/mermaid.md`。
|
||||
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2026 Tw93
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -0,0 +1,205 @@
|
||||
<div align="center">
|
||||
<img src="assets/images/logo.svg" width="120" />
|
||||
<h1>Kami</h1>
|
||||
<p><b>Good content deserves good paper.</b></p>
|
||||
<a href="https://github.com/tw93/kami/stargazers"><img src="https://img.shields.io/github/stars/tw93/kami?style=flat-square" alt="Stars"></a>
|
||||
<a href="https://github.com/tw93/kami/releases"><img src="https://img.shields.io/github/v/tag/tw93/kami?label=version&style=flat-square" alt="Version"></a>
|
||||
<a href="LICENSE"><img src="https://img.shields.io/badge/license-MIT-blue.svg?style=flat-square" alt="License"></a>
|
||||
<a href="https://twitter.com/HiTw93"><img src="https://img.shields.io/badge/follow-Tw93-red?style=flat-square&logo=Twitter" alt="Twitter"></a>
|
||||
</div>
|
||||
|
||||
## Why
|
||||
|
||||
Kami (紙, かみ) means paper in Japanese: the surface where a finished idea lands. AI can produce documents better than most humans do manually. The missing piece is not capability but constraint: without a design system, every session drifts into generic gray and inconsistent layouts.
|
||||
|
||||
Kami fills that gap: one constraint language, eight document templates plus a landing-page system, simple enough for agents to run reliably, strict enough that every output is something you actually want to ship.
|
||||
|
||||
Part of a trilogy: [Kaku](https://github.com/tw93/Kaku) (書く) writes code, [Waza](https://github.com/tw93/Waza) (技) drills habits, [Kami](https://github.com/tw93/Kami) (紙) delivers documents.
|
||||
|
||||
## Showcase
|
||||
|
||||
Real PDFs from one constraint set, across templates and languages. Click any preview to open it.
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<td align="center" width="25%">
|
||||
<a href="assets/demos/demo-musk-resume.pdf"><img src="assets/demos/demo-musk-resume.png" alt="Founder resume"></a>
|
||||
<br><b>Resume</b> · English
|
||||
<br><sub>Founder resume, 2 pages</sub>
|
||||
</td>
|
||||
<td align="center" width="25%">
|
||||
<a href="assets/demos/demo-kami-print.pdf"><img src="assets/demos/demo-kami-print.png" alt="Kami print one-pager"></a>
|
||||
<br><b>One-Pager</b> · 中文
|
||||
<br><sub>Kami 介绍 · 白底打印版</sub>
|
||||
</td>
|
||||
<td align="center" width="25%">
|
||||
<a href="assets/demos/demo-tesla.pdf"><img src="assets/demos/demo-tesla.png" alt="Tesla equity report"></a>
|
||||
<br><b>Equity Report</b> · 中文
|
||||
<br><sub>Tesla Q1 2026 财报点评</sub>
|
||||
</td>
|
||||
<td align="center" width="25%">
|
||||
<a href="assets/demos/demo-agent-slides.pdf"><img src="assets/demos/demo-agent-slides.png" alt="Agent keynote slides" /></a>
|
||||
<br><b>Slides</b> · English
|
||||
<br><sub>Agent keynote, 8 slides</sub>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center" width="25%">
|
||||
<a href="assets/demos/demo-mole.pdf"><img src="assets/demos/demo-mole.png" alt="Mole product brief"></a>
|
||||
<br><b>One-Pager</b> · English
|
||||
<br><sub>Mole product brief, 1 page</sub>
|
||||
</td>
|
||||
<td align="center" width="25%">
|
||||
<a href="assets/demos/demo-letter.pdf"><img src="assets/demos/demo-letter.png" alt="Recommendation letter"></a>
|
||||
<br><b>Letter</b> · 中文
|
||||
<br><sub>推荐信, 1 页</sub>
|
||||
</td>
|
||||
<td align="center" width="25%">
|
||||
<a href="assets/demos/demo-changelog.pdf"><img src="assets/demos/demo-changelog.png" alt="Mole release notes"></a>
|
||||
<br><b>Changelog</b> · English
|
||||
<br><sub>Mole v1.7.1 release notes</sub>
|
||||
</td>
|
||||
<td align="center" width="25%">
|
||||
<a href="assets/demos/demo-kaku.pdf"><img src="assets/demos/demo-kaku.png" alt="Kaku portfolio"></a>
|
||||
<br><b>Portfolio</b> · 日本語
|
||||
<br><sub>Kaku ターミナル作品集 · 7 ページ</sub>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
## Install
|
||||
|
||||
**Claude Code**, v2.1.142 or newer
|
||||
|
||||
```bash
|
||||
/plugin marketplace add tw93/kami
|
||||
/plugin install kami@kami
|
||||
```
|
||||
|
||||
The marketplace points Claude Code at the generated lightweight plugin bundle, not the whole website and release archive tree.
|
||||
|
||||
**Codex plugin marketplace**
|
||||
|
||||
```bash
|
||||
codex plugin marketplace add tw93/kami
|
||||
codex plugin add kami@kami
|
||||
```
|
||||
|
||||
This installs Kami as a Codex plugin from the repo marketplace, so future updates can use `codex plugin marketplace upgrade kami` followed by `codex plugin add kami@kami`.
|
||||
|
||||
**Generic agents** for tools that read from `~/.agents/`
|
||||
|
||||
```bash
|
||||
npx skills add tw93/kami/plugins/kami -a universal -g -y
|
||||
```
|
||||
|
||||
The plugin path exposes the generated lightweight skill bundle. A bare `tw93/kami` would install only `SKILL.md`, because the repo root doubles as the website source and the `skills` CLI treats a root-level skill as a single file.
|
||||
|
||||
**Claude Desktop**
|
||||
|
||||
Download the release asset [kami.zip](https://github.com/tw93/kami/releases/latest/download/kami.zip), not GitHub's source-code ZIP. Open Customize > Skills > "+" > Create skill, and upload the ZIP directly, no need to unzip.
|
||||
|
||||
The ZIP is lightweight and contains a `kami/` skill folder. Large CJK fonts are excluded from the package: in a repo checkout they load from local font files first, then jsDelivr CDN; in an installed skill, `scripts/ensure-fonts.sh` recovers missing Chinese or Korean fonts into the user font directory.
|
||||
|
||||
**Update**
|
||||
|
||||
- Claude Code: `claude plugin update kami`
|
||||
- Codex: `codex plugin marketplace upgrade kami`, then `codex plugin add kami@kami` to refresh the installed snapshot
|
||||
- Claude Desktop: download the latest [kami.zip](https://github.com/tw93/kami/releases/latest/download/kami.zip), click "..." on the skill card, choose Replace, upload
|
||||
- Generic agents: re-run the `npx skills add tw93/kami/plugins/kami -a universal -g -y` command, which overwrites the existing copy. Avoid `npx skills update` for now: it can mis-detect repo subpath installs while the repo root also has `SKILL.md` (vercel-labs/skills#1517).
|
||||
|
||||
Kami also runs a quiet version check at most once a day and tells you in chat when a newer version is out; it only reads a public version file, sends no data, and is skipped when offline.
|
||||
|
||||
## Use
|
||||
|
||||
The skill auto-triggers from natural requests, no slash command needed. Optimized for English and Chinese; Japanese and Korean are supported via best-effort CJK paths with visual QA before delivery.
|
||||
|
||||
Example prompts by language:
|
||||
|
||||
- English: `make a one-pager for my startup` / `turn this research into a long doc` / `write a formal letter` / `make a portfolio of my projects` / `build me a resume` / `design a slide deck for my talk` / `make this talk as a Marp deck` / `build a landing page for my app`
|
||||
- 中文: `帮我做一份一页纸` / `帮我排版一份长文档` / `帮我写一封正式信件` / `帮我做一份作品集` / `帮我做一份简历` / `帮我做一套演讲幻灯片` / `帮我做一份 Markdown 风格的演示稿` / `帮我做一个产品落地页`
|
||||
- 日本語: `スタートアップ向けの一枚資料を作って` / `この調査を長文レポートに整えて` / `正式な依頼文を作って` / `プロジェクト作品集を作って` / `履歴書を作って` / `登壇用スライドを作って` / `Marp で登壇スライドを作って` / `アプリのランディングページを作って`
|
||||
- 한국어: `스타트업 원페이저를 만들어줘` / `이 리서치를 장문 문서로 정리해줘` / `정식 레터를 작성해줘` / `프로젝트 포트폴리오를 만들어줘` / `이력서를 만들어줘` / `발표용 슬라이드를 만들어줘` / `Marp 슬라이드로 만들어줘` / `앱 랜딩 페이지를 만들어줘`
|
||||
|
||||
**Brand profile** (optional)
|
||||
|
||||
Create `~/.config/kami/brand.md` to persist identity, brand, defaults, and writing habits. See [brand.example.md](references/brand.example.md) for a full template.
|
||||
|
||||
The file has YAML frontmatter for structured fields like name, role, email, brand color, language, page size, and tone, plus a Markdown body for freeform notes. Kami treats it as the lowest-resolution context: applied only when the current request is ambiguous, and always overridable by what the specific document needs. The goal is to feel familiar across your work without making every output look the same.
|
||||
|
||||
## Design
|
||||
|
||||
Warm parchment canvas `#f5f4ed`, ink blue `#1B365D` as the sole accent, serif carries hierarchy, no hard shadows or flashy palettes. Not a UI framework; a constraint system for printed matter. Documents should read as composed pages, not dashboards.
|
||||
|
||||
- **Templates.** Eight document templates: One-Pager, Long Doc, Letter, Portfolio, Resume, Slides, Equity Report, and Changelog, plus a Landing Page system, in EN, CN, and KO.
|
||||
- **Diagrams.** Eighteen inline SVG types, including a report-scale architecture board. Sequence, class, and ER can be authored from Mermaid text: [beautiful-mermaid](https://github.com/lukilabs/beautiful-mermaid) renders the SVG and `scripts/mermaid_normalize.py` re-themes it to the Kami palette and makes it WeasyPrint-safe, no Node bundled.
|
||||
- **Slides.** Three rendering paths: WeasyPrint HTML to PDF by default, python-pptx for editable PPTX on request, and a Marp variant in `assets/templates/marp/` for Markdown-first decks.
|
||||
- **Code.** Pygments-based syntax highlighting when `Pygments` is installed; without it, PDFs still render and code stays monochrome.
|
||||
- **Print.** Parchment is the default canvas; an opt-in white-paper variant flips any document to a white background for home or office printers, sinking the warmth into cards and tables so the hierarchy still reads. The [one-page Kami intro](assets/demos/demo-kami-print.pdf) (Chinese) is rendered with this variant; recipe in [production.md](references/production.md).
|
||||
|
||||
Kami picks the right variant based on the language you write in.
|
||||
|
||||
**Fonts**: Each language uses a single serif font for the entire page. Chinese: TsangerJinKai02. Japanese: YuMincho. Korean: Source Han Serif K. English: Charter. See [License](#license) for font terms.
|
||||
|
||||
Full spec: [design.md](references/design.md). Cheatsheet: [CHEATSHEET.md](CHEATSHEET.md).
|
||||
|
||||
## Beyond Documents
|
||||
|
||||
One constraint set, applied past the page: it lays out deployable websites and briefs AI image renderers, so both come back in the Kami look.
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<td align="center" width="25%" valign="top">
|
||||
<a href="https://kami.tw93.fun"><img src="assets/showcase/kami-landing.png" alt="Kami landing page" height="150"></a>
|
||||
<br><b>Kami</b> · landing page
|
||||
<br><sub>Design system homepage</sub>
|
||||
</td>
|
||||
<td align="center" width="25%" valign="top">
|
||||
<a href="https://mole.fit"><img src="assets/showcase/mole-landing.png" alt="Mole landing page" height="150"></a>
|
||||
<br><b>Mole</b> · landing page
|
||||
<br><sub>macOS system utility</sub>
|
||||
</td>
|
||||
<td align="center" width="25%" valign="top">
|
||||
<img src="assets/illustrations/travel-spatialvla.png" alt="SpatialVLA architecture redraw" height="150">
|
||||
<br><b>Architecture redraw</b> · English
|
||||
<br><sub>SpatialVLA Figure 1, schematic</sub>
|
||||
</td>
|
||||
<td align="center" width="25%" valign="top">
|
||||
<img src="assets/illustrations/travel-tesla-optimus.png" alt="Tesla Optimus patent overview" height="150">
|
||||
<br><b>Evidence layout</b> · 中文
|
||||
<br><sub>Tesla Optimus 专利图一览</sub>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
Landing pages ship as deployable multilingual sites. Illustrations are drawn by the host's own image model: where the host can generate images, like ChatGPT, it renders directly; where it cannot, like Claude or Codex, it outputs the brief for you to paste into any image model.
|
||||
|
||||
```text
|
||||
Redraw this as a clean editorial diagram. Background: warm parchment (#f5f4ed), never pure white. One accent only, ink blue (#1B365D); everything else in warm gray with a yellow-brown undertone, no other colors. Thin single-line geometric strokes and simple flat icons. No gradients, no drop shadows, no 3D. Labels in a serif typeface. Generous whitespace, calm and composed, like a figure in a well-typeset report.
|
||||
```
|
||||
|
||||
<sub>Rendered by ChatGPT Images in a single pass with no manual touch-up. Kami specifies, the renderer draws.</sub>
|
||||
|
||||
## Background
|
||||
|
||||
I like investing in US equities and ask Claude to write research reports all the time. Every output landed in the same default-doc look: gray, flat, a different layout each session. The structure was hard to scan, the formatting felt dated, and nothing about the page made me want to keep reading. So I started fixing the typography, the palette, the spacing, one rule at a time, until the report became a page I actually enjoyed.
|
||||
|
||||
Later I needed to present "The Agent You Don't Know: Principles, Architecture and Engineering Practice." I already had the document and didn't want to build slides from scratch, so I used Claude Design to lay it out in my own style, tweaked it round after round, and eventually got it to a place I was happy with. That process added inline SVG charts, a unified warm palette, and a tighter editorial rhythm. It kept growing until it covered every document I regularly ship, so I kept abstracting the process, and it became kami: one quiet design system I can hand to any agent and trust the output.
|
||||
|
||||
## Support
|
||||
|
||||
- The most direct way to support me is getting [Mole for Mac](https://mole.fit), my paid Mac cleanup app.
|
||||
- If Kami helped you, give it a star, [share it](https://twitter.com/intent/tweet?url=https://github.com/tw93/kami&text=Kami%20-%20A%20quiet%20design%20system%20for%20professional%20documents.), or open an issue or PR.
|
||||
- I have two cats, TangYuan and Coke. If you think kami delights your life, you can feed them <a href="https://cats.tw93.fun?name=Kami" target="_blank">canned food 🥩</a>.
|
||||
|
||||
<details>
|
||||
<summary>These lovely people already did 🐱</summary>
|
||||
<br/>
|
||||
<a href="https://cats.tw93.fun?name=Kami"><img src="https://cdn.jsdelivr.net/gh/tw93/sponsors@main/assets/sponsors.svg" width="1000" loading="lazy" /></a>
|
||||
</details>
|
||||
|
||||
## License
|
||||
|
||||
MIT License for kami code and templates. Feel free to use and contribute.
|
||||
|
||||
**Fonts**: TsangerJinKai02 is free for personal use only; commercial use requires a license from [tsanger.cn](https://tsanger.cn). Charter, YuMincho, Source Han Serif K under OFL, and CJK fallbacks are system-bundled or open-licensed.
|
||||
@@ -0,0 +1,7 @@
|
||||
# WeHub 来源说明
|
||||
|
||||
- 原始项目:`tw93/Kami`
|
||||
- 原始仓库:https://github.com/tw93/Kami
|
||||
- 导入方式:上游默认分支的最新快照
|
||||
- 原作者、版权和许可证信息以原始仓库及本仓库 LICENSE 为准
|
||||
- 本文件仅用于记录来源,不代表 WeHub 是原项目作者
|
||||
@@ -0,0 +1,569 @@
|
||||
---
|
||||
name: kami
|
||||
description: 'Typeset professional documents and product landing pages: resumes, one-pagers, white papers, letters, portfolios, slide decks, landing pages. Warm parchment, ink-blue accent, serif-led hierarchy. CN uses TsangerJinKai02, EN uses Charter, JA uses YuMincho (best-effort). Triggers on "做 PDF / 排版 / 一页纸 / 白皮书 / 作品集 / 简历 / PPT / slides / Marp / markdown slides / マークダウンのスライド / 落地页 / 官网 / landing page / product page", or "build me a resume / make a one-pager / design a slide deck / turn this into a PDF / make this presentable / create a landing page".'
|
||||
---
|
||||
|
||||
# kami · 紙
|
||||
|
||||
**紙 · かみ** - the paper your deliverables land on.
|
||||
|
||||
Good content deserves good paper. One design language across documents and landing pages: warm parchment canvas, ink-blue accent, serif-led hierarchy, tight editorial rhythm.
|
||||
|
||||
Part of `Kaku · Waza · Kami` - Kaku writes code, Waza drills habits, **Kami delivers documents**.
|
||||
|
||||
**Update check (non-blocking).** At the start of a task, run `bash scripts/check-update.sh`. It does a read-only version check at most once per day and prints one line when a newer kami is available; relay that line to the user, then continue. It sends no data, and fails silently when offline, sandboxed, or without `curl`. Never let it block the work.
|
||||
|
||||
## Step 0 · Load brand profile (if exists)
|
||||
|
||||
Check `~/.config/kami/brand.md` (preferred) or `~/.kami/brand.md` (legacy fallback). If found, read `references/brand-profile.md` for the full four-layer application spec (placeholder substitution, session defaults, visual customization, habit notes) and its six guardrails. If no profile exists, continue without interruption.
|
||||
|
||||
Key rule: explicit prompt > editorial judgment > habit notes > frontmatter defaults > built-in defaults. Profile fills gaps silently; it never overrides the current conversation.
|
||||
|
||||
## Step 0.5 · User project style scan (opt-in)
|
||||
|
||||
Run this only when the user explicitly references a sibling project as a visual reference: "like my <project> site", "match the style of <repo>", "use the look from <directory>". Skip silently when no such reference exists.
|
||||
|
||||
When triggered, before generating:
|
||||
|
||||
1. Locate the referenced project's style files:
|
||||
```bash
|
||||
find <referenced-path> -maxdepth 4 \( -name "*.css" -o -name "tailwind.config.*" -o -name "theme.*" -o -name "tokens.*" \) | head -20
|
||||
```
|
||||
2. Extract: dominant color values (hex / hsl), font stack, spacing scale, border-radius scale. Prefer values declared in CSS variables or design tokens over inline literals.
|
||||
3. Merge into the in-session brand profile as Layer C (visual customization), not Layer B (session defaults). Do not override an explicit `--brand` flag or values that the user typed in this turn.
|
||||
4. Report back in one line before continuing: "scanned <project>, extracted N colors / M fonts; using as visual reference."
|
||||
|
||||
Skip and fall back to the brand profile defaults if the referenced path does not exist, no CSS-like files are found, or the extraction would conflict with the user's explicit values in the current message.
|
||||
|
||||
---
|
||||
|
||||
## Step 1 · Decide the language
|
||||
|
||||
**Match the user's language.** Chinese -> `*.html` / `slides-weasy.html`. English -> `*-en.html` / `slides-weasy-en.html`. Japanese -> CJK path (`.html` / `slides-weasy.html`) as best-effort, JP Mincho first, visual QA before shipping. Korean -> dedicated `*-ko.html` / `slides-weasy-ko.html` family as best-effort, visual QA before shipping. Reference docs are shared English specs.
|
||||
|
||||
When ambiguous (e.g. a one-word command like "resume"), ask a one-liner rather than guess.
|
||||
|
||||
| User language | HTML templates | Slides (PDF default) | Slides (PPTX fallback) |
|
||||
|---|---|---|---|
|
||||
| Chinese (primary) | `*.html` | `slides-weasy.html` | `slides.py` |
|
||||
| English | `*-en.html` | `slides-weasy-en.html` | `slides-en.py` |
|
||||
| Japanese (best-effort) | `*.html` | `slides-weasy.html` | `slides.py` |
|
||||
| Korean (best-effort) | `*-ko.html` | `slides-weasy-ko.html` | n/a (use `slides-en.py` only if PPTX is required) |
|
||||
| Other languages (best-effort) | choose CJK or EN path by script coverage, then verify manually | choose `slides-weasy.html` or `slides-weasy-en.html`, then verify manually | use `slides.py` / `slides-en.py` only if PPTX is required |
|
||||
|
||||
> Default to the WeasyPrint HTML path; fall back to PPTX (`slides*.py`) only when the user explicitly needs an editable deck.
|
||||
|
||||
Always use `CHEATSHEET.md` and `references/*.md` for design, writing, production, and diagram guidance.
|
||||
|
||||
Code blocks with `class="language-*"` are highlighted only when optional `Pygments` is installed in the build environment. Without it, PDFs still render and code blocks stay monochrome.
|
||||
|
||||
## Step 1.5 · Intent extraction (silent checklist)
|
||||
|
||||
Before choosing a template, verify these four dimensions are clear. Do not ask unless 2+ are missing and cannot be inferred from context.
|
||||
|
||||
| Dimension | What to extract | Example |
|
||||
|---|---|---|
|
||||
| **Purpose** | Why this document exists | Persuade investor vs. align internal team vs. close a candidate |
|
||||
| **Audience** | Who reads it, what they already know | Technical CTO (skip basics) vs. non-technical board (explain terms) |
|
||||
| **Constraint** | Hard limits on length, format, tone, or delivery | "One page max", "formal English", "print-ready A4" |
|
||||
| **Success** | What outcome counts as success | They schedule a meeting / they approve the budget / they understand the architecture |
|
||||
|
||||
Rules:
|
||||
- If the conversation already answered a dimension, skip it silently.
|
||||
- If a dimension can be inferred from the document type (e.g. resume purpose is always "get an interview"), skip it.
|
||||
- If 2+ dimensions are genuinely unclear, ask in a single compact question (max 2 sub-questions).
|
||||
- Never ask all four as a checklist. This is a background verification, not a form.
|
||||
|
||||
## Execution contract
|
||||
|
||||
Before creating or modifying an output, lock the contract: language, template, output format, page or length target, visual acceptance check, and verification command. Infer from the user's request when clear; ask only when missing fields materially change the deliverable.
|
||||
|
||||
Use the nearest existing template and verification path. Do not add a new template, shared CSS layer, dependency, script flag, or optional mode unless the current request cannot be satisfied without it.
|
||||
|
||||
If a change touches `SKILL.md`, templates, scripts, references, or package inputs, decide whether `dist/kami.zip` must be refreshed before handoff. Shipped behavior is not ready until the package contains the changed files.
|
||||
|
||||
---
|
||||
|
||||
## Step 2 · Pick the document type
|
||||
|
||||
| User says | Document | CN template | EN template | KO template |
|
||||
|---|---|---|---|---|
|
||||
| "one-pager / 方案 / 执行摘要 / exec summary" | One-Pager | `one-pager.html` | `one-pager-en.html` | `one-pager-ko.html` |
|
||||
| "white paper / 白皮书 / 长文 / 年度总结 / technical report" | Long Doc | `long-doc.html` | `long-doc-en.html` | `long-doc-ko.html` |
|
||||
| "formal letter / 信件 / 辞职信 / 推荐信 / memo" | Letter | `letter.html` | `letter-en.html` | `letter-ko.html` |
|
||||
| "portfolio / 作品集 / case studies" | Portfolio | `portfolio.html` | `portfolio-en.html` | `portfolio-ko.html` |
|
||||
| "resume / CV / 简历 / 履歴書" | Resume | `resume.html` | `resume-en.html` | `resume-ko.html` |
|
||||
| "slides / PPT / deck / 演示" | Slides | `slides-weasy.html` | `slides-weasy-en.html` | `slides-weasy-ko.html` |
|
||||
| "个股研报 / equity report / 估值分析 / investment memo / 股票分析" | Equity Report | `equity-report.html` | `equity-report-en.html` | `equity-report-ko.html` |
|
||||
| "更新日志 / changelog / release notes / 版本记录" | Changelog | `changelog.html` | `changelog-en.html` | `changelog-ko.html` |
|
||||
| "landing page / 落地页 / 官网 / product page / 产品页" | Landing Page | `landing-page.html` | `landing-page-en.html` | `landing-page-ko.html` |
|
||||
|
||||
> **Changelog vs. release notes**: The changelog template above is for styled document output. GitHub release notes are a separate deliverable; use `/write` with Release Note Template Mode.
|
||||
|
||||
> **Landing Page**: Screen-first interactive template. No PDF output. Includes gallery carousel with auto-rotate, hero entrance animation, responsive breakpoints (880px / 480px), and prefers-reduced-motion support. Deploy as static HTML to Vercel / Netlify / any host. The agent fills {{PLACEHOLDER}} values and HTML comment blocks, then saves as a ready-to-serve `.html` file.
|
||||
|
||||
> **Landing Page companion files**: For a production multilingual deploy, copy the five `landing-page-*.example` files alongside the main HTML, remove the `.example` suffix, and fill the placeholders. They cover Vercel rewrites and headers, sitemap hreflang, robots AI allowlist, and llms.txt + llms-full.txt for AI assistants. The main HTML already ships matching hreflang and og:locale in `<head>`; an Accept-Language redirect at the end of `landing-page-en.html` is commented out for opt-in. `{{SITE_ORIGIN}}` is the scheme + host of your `{{CANONICAL_URL}}` (e.g. `https://example.com`). See `references/design.md` Section 11 «Companion assets».
|
||||
|
||||
> **Production product site mode**: If the user needs docs, help, releases, changelog, roadmap, legal pages, or more than two locales, treat it as a site system. Lock product category, real screenshot slots, locale list, companion files, long-content pages, and generator/check needs before filling templates. Keep project-specific release artifacts, payment providers, appcast rules, and private local paths out of Kami. See `references/design.md` Section 11 «Product site system».
|
||||
|
||||
> **Documentation pages**: When a landing page grows into a docs or help site, use the doc shell in `references/design.md` Section 11 «Documentation site»: a sticky sidebar nav with a 2px brand rail (not a dark underline), an on-this-page TOC hidden below the tablet breakpoint, a constrained prose measure, and a quiet borderless prev/next pager (text links, not bordered cards). Highlight code at build time with zero runtime JS on a dark code surface; plain code stays the source of truth.
|
||||
|
||||
> Slides: default to `slides-weasy.html` / `slides-weasy-en.html` / `slides-weasy-ko.html` (WeasyPrint HTML → PDF). Use `slides.py` / `slides-en.py` only when the user explicitly requires an editable PPTX file. Use `assets/templates/marp/slides-marp(.md|.css)` only when the user explicitly asks for Marp / markdown slides / a deck that lives in a `.md` file.
|
||||
|
||||
> Deck recipe: read design.md Section 8 before drafting slides. Sketch title sequence, evidence shape, and image slot before generating or cropping visuals. Keep audience copy separate from visual briefs. Marp-specific constraints live in design.md §8 «Marp variant».
|
||||
|
||||
### Decision tree (use before asking)
|
||||
|
||||
Walk this tree before reaching for a one-liner question. Ask only when two cells genuinely both fit.
|
||||
|
||||
| Signal | Document |
|
||||
|---|---|
|
||||
| Length target unknown | Ask "how many pages" before classifying |
|
||||
| ≤ 1 page + investor / recruiter / exec summary audience | one-pager |
|
||||
| ≤ 1 page + formal correspondence (sales, hiring, resignation, memo) | letter |
|
||||
| 1.5-2 pages + career narrative + project bullets | resume |
|
||||
| 3-6 pages + project showcase + visual heavy | portfolio |
|
||||
| 6-15 pages + sustained argument + low visual density | long-doc |
|
||||
| Presentation flow + speaker support + per-slide assertion | slides |
|
||||
| Financial / metrics dashboard + thesis + price or risk view | equity-report |
|
||||
| Version-by-version log + release facts | changelog |
|
||||
| Product showcase + pricing + screenshots + FAQ for browser | landing-page |
|
||||
|
||||
Ambiguity examples that justify a one-liner:
|
||||
- "1.5 page career story with heavy visuals" -> ask "resume or portfolio?"
|
||||
- "2 page exec summary with metric tiles" -> ask "one-pager or equity-report?"
|
||||
- "5 page argument with several charts" -> ask "long-doc or portfolio?"
|
||||
|
||||
Pick from the tree first. Ask only when the tree is genuinely silent.
|
||||
|
||||
### Diagrams (primitives, not a separate template type)
|
||||
|
||||
When the user asks for **a diagram inside** a long-doc / portfolio / slide (not a standalone document), route to `assets/diagrams/` rather than a template:
|
||||
|
||||
| User says | Diagram | Template |
|
||||
|---|---|---|
|
||||
| "架构图 / architecture / 系统图 / components diagram" | Architecture | `assets/diagrams/architecture.html` |
|
||||
| "架构全景 / architecture board / 平台全景 / 系统大图 / five-layer panorama" | Architecture Board | `assets/diagrams/architecture-board.html` |
|
||||
| "流程图 / flowchart / 决策流 / branching logic" | Flowchart | `assets/diagrams/flowchart.html` |
|
||||
| "象限图 / quadrant / 优先级矩阵 / 2×2 matrix" | Quadrant | `assets/diagrams/quadrant.html` |
|
||||
| "柱状图 / bar chart / 分类对比 / grouped bars" | Bar Chart | `assets/diagrams/bar-chart.html` |
|
||||
| "折线图 / line chart / 趋势 / 股价 / time series" | Line Chart | `assets/diagrams/line-chart.html` |
|
||||
| "环形图 / donut / pie / 占比 / 分布结构" | Donut Chart | `assets/diagrams/donut-chart.html` |
|
||||
| "状态机 / state machine / 状态图 / lifecycle" | State Machine | `assets/diagrams/state-machine.html` |
|
||||
| "时间线 / timeline / 里程碑 / milestones / roadmap" | Timeline | `assets/diagrams/timeline.html` |
|
||||
| "泳道图 / swimlane / 跨角色流程 / cross-team flow" | Swimlane | `assets/diagrams/swimlane.html` |
|
||||
| "树状图 / tree / hierarchy / 层级 / 组织架构" | Tree | `assets/diagrams/tree.html` |
|
||||
| "分层图 / layer stack / 分层架构 / OSI / stack" | Layer Stack | `assets/diagrams/layer-stack.html` |
|
||||
| "维恩图 / venn / 交集 / overlap / 集合关系" | Venn | `assets/diagrams/venn.html` |
|
||||
| "K 线 / candlestick / OHLC / 股价走势 / price history" | Candlestick | `assets/diagrams/candlestick.html` |
|
||||
| "瀑布图 / waterfall / 收入桥 / revenue bridge / decomposition" | Waterfall | `assets/diagrams/waterfall.html` |
|
||||
|
||||
Read `references/diagrams.md` before drawing - it has the selection guide, kami token map, and the AI-slop anti-pattern table. Extract the `<svg>` block from the template and drop it into a `<figure>` inside long-doc / portfolio.
|
||||
|
||||
For a **full-system architecture board** (platform panorama, control plane, roadmap, or owner map in one artifact), do not inflate the single architecture figure past its node budget. Start from `assets/diagrams/architecture-board.html` and follow the «Architecture boards» section in `references/diagrams.md`: five fixed information layers, bands over cards, lines never on module edges, and a structure outline before any rendering.
|
||||
|
||||
For a **repo-maintained diagram** (README or docs-site architecture figure, "给项目画张架构图", or updating a diagram that already lives in the user's repository), follow «Maintained diagram assets» in `references/diagrams.md`: run the evidence pass first (existing `prompt.md`, `index.html`, current PNG, then the facts that define objects and boundaries), keep the trio (`index.html` + same-name PNG + `prompt.md`) consistent, encode shipped / in-build / future maturity, and re-export the PNG after every HTML change. Never redraw an existing diagram from memory, and never hand-edit the PNG.
|
||||
|
||||
Before drawing, always ask: **would a well-written paragraph teach the reader less than this diagram?** If no, don't draw.
|
||||
|
||||
**Auto-select charts from data.** When content contains numerical data, choose the chart type and embed it without waiting for the user to specify. Decision tree (first match wins):
|
||||
|
||||
| Data shape | Chart |
|
||||
|---|---|
|
||||
| Has open/high/low/close fields, or per-day price | Candlestick |
|
||||
| Has + and - contributions that sum to a total (bridge, waterfall, P&L) | Waterfall |
|
||||
| One series, values sum to ~100%, items ≤ 6 | Donut |
|
||||
| One series, values sum to ~100%, items ≥ 7 | Horizontal bar |
|
||||
| Two or more series across time (months, quarters, years) | Line |
|
||||
| One series across time, large count changes dominate (not rate) | Bar |
|
||||
| Multiple categories, same time snapshot, 2+ series | Grouped bar |
|
||||
| 2×2 strategic or priority positioning | Quadrant |
|
||||
| Hierarchical data with depth ≥ 2 | Tree |
|
||||
| Process with decision branches | Flowchart |
|
||||
| Cross-team or cross-role process with ≥ 3 actors | Swimlane |
|
||||
| Set overlaps or shared attributes between 2-3 groups | Venn |
|
||||
| Category comparison, single series, no time axis | Bar |
|
||||
|
||||
When data fits multiple types, prefer the one that shows variance most clearly. Always embed inside a `<figure>` with a caption that states the insight, not just the data range.
|
||||
|
||||
### Illustrations (host image model, not inline SVG)
|
||||
|
||||
Inline diagrams above are vector SVGs you assemble by hand. For a standalone raster illustration, or a redraw of a figure, photo, or screenshot in the Kami look, delegate the drawing to the host's own image generation. Never call an external image API or require a key; rendering is the host's job.
|
||||
|
||||
- If the running host can generate images (for example ChatGPT), apply the brief below and render the image directly.
|
||||
- If it cannot (Claude, Codex, most coding agents), output the brief as text so the user can paste it into any image model.
|
||||
|
||||
Brief: warm parchment (`#f5f4ed`) background, never pure white; one accent only, ink blue (`#1B365D`); all else warm gray with a yellow-brown undertone, no other colors; thin single-line geometric strokes and simple flat icons; no gradients, drop shadows, or 3D; serif labels; generous whitespace, composed like a figure in a well-typeset report.
|
||||
|
||||
## Step 2.1 · Source and material pass
|
||||
|
||||
Run this before distilling or filling content when the document depends on facts or materials outside the user's draft. Skip it only for personal drafts where the user already supplied everything needed.
|
||||
|
||||
### Source check
|
||||
|
||||
Trigger when the document mentions a specific company, product, person, release date, version, funding round, metric, market fact, technical spec, or any current fact likely to change.
|
||||
|
||||
- Use primary sources before writing: user-provided material, official site, docs, filings, press release, app store page, or repo release
|
||||
- Keep a short note of source names and dates for facts that drive the document
|
||||
- If sources conflict or a fact cannot be checked quickly, ask the user instead of choosing silently
|
||||
- Avoid current-sounding claims such as "latest", "recent", "new", version numbers, launch dates, or financial figures unless they are checked
|
||||
|
||||
### Material check
|
||||
|
||||
Trigger when the document is about a company, product, project, venue, or personal brand.
|
||||
|
||||
Confirm the materials that make the subject recognizable before layout:
|
||||
|
||||
| Need | Required when | Accept |
|
||||
|---|---|---|
|
||||
| Logo | Any branded document | User file or official SVG/PNG |
|
||||
| Product image | Physical product / venue / object | Official image, user image, or marked gap |
|
||||
| UI screenshot | App / SaaS / website / tool | Current screenshot, official product image, or user capture |
|
||||
| Brand colors | Branded one-pager / portfolio / deck | Official value, extracted asset value, or keep kami ink-blue |
|
||||
| Fonts | Only if brand typography matters | Official font, close system fallback, or kami default |
|
||||
|
||||
If a required item is missing, use a compact gap table and ask once. Do not replace missing material with generic imagery, approximate logo drawings, or invented values.
|
||||
|
||||
Logo fallback: when the request names no logo but the brand profile has a `logo` path, fill the commented `.brand-logo` slot in `one-pager` / `portfolio` / `slides-weasy` per `references/brand-profile.md` Layer C. Expand `~` to an absolute path, and if the file is missing or the template has no slot, leave it commented and render without a logo (never insert a broken image). An explicit logo in the current request always wins.
|
||||
|
||||
### Materials status block
|
||||
|
||||
After the material check, output a structured status block before continuing. This is a one-shot transparency display, not a question:
|
||||
|
||||
```
|
||||
Materials status:
|
||||
- Logo: OK assets/client-logo.svg
|
||||
- Brand colors: OK #1B365D mapped to --brand
|
||||
- Product screenshot: MISSING (proceeding with kami default placeholder)
|
||||
- UI screenshot: not required for this doc type
|
||||
```
|
||||
|
||||
Use `OK`, `MISSING`, or `not required`. If a required item is missing and no user input arrived, ask once with the gap table; otherwise continue silently.
|
||||
|
||||
## Step 2.5 · Distill raw content (if applicable)
|
||||
|
||||
**Auto-detect whether to distill.** Do not ask the user; judge from the input:
|
||||
|
||||
| Skip distill (fill directly) | Run distill |
|
||||
|---|---|
|
||||
| Content has explicit section labels matching template structure | Raw prose without section structure |
|
||||
| Metrics already quantified with units in place | Numbers scattered or implied, not extracted |
|
||||
| User wrote "use this as-is" / "直接用这个" / "原封不动" | User pasted multi-source dump (chat / email thread / multiple docs) |
|
||||
| Content count matches template (e.g. 4 metrics for 4 metric cards) | Content count mismatches template (too many or too few items) |
|
||||
| One coherent voice with consistent claims | Conflicting claims or duplicate facts across sources |
|
||||
|
||||
When in doubt, run distill. Distill is cheap; rebuilding a misaligned doc is not.
|
||||
|
||||
When the user hands over **raw material** (meeting notes, brain dump, existing doc in different format, chat transcript, scattered points):
|
||||
|
||||
1. **Extract**: pull out every factual claim, number, date, name, source, material reference, and action item
|
||||
2. **Classify**: map each extract to the target template's sections (see `references/writing.md` for section structure per doc type)
|
||||
3. **Gap-check**: list what the template needs but the raw content doesn't have - include missing facts, missing proof, and missing materials
|
||||
4. **Ask once**: share the gap table with the user. Do not guess to fill gaps.
|
||||
|
||||
Example gap-check:
|
||||
|
||||
| Template needs | Found | Missing |
|
||||
|---|---|---|
|
||||
| 4 metric cards | "8 years", "50-person team" | 2 more quantifiable results |
|
||||
| 3-5 core projects | 2 mentioned | at least 1 more with outcome |
|
||||
| Materials | logo file provided | product screenshot source |
|
||||
|
||||
Then proceed to Step 2.6 (slides) or the layout note (all other doc types) with structured, distilled content.
|
||||
|
||||
## Step 2.6 · Deck pre-flight (slides only)
|
||||
|
||||
Skip this step for every doc type except slides.
|
||||
|
||||
### Path selection
|
||||
|
||||
Default to the WeasyPrint HTML path. Switch to pptx only if the user explicitly requires an editable PPTX file. Switch to Marp only when the user explicitly asks for Marp / markdown slides.
|
||||
|
||||
| Path | Template | When |
|
||||
|---|---|---|
|
||||
| WeasyPrint HTML → PDF (default) | `slides-weasy.html` / `slides-weasy-en.html` / `slides-weasy-ko.html` | All cases unless PPTX or Marp is required |
|
||||
| python-pptx → PPTX (fallback) | `slides.py` / `slides-en.py` | User explicitly requires editable PPTX |
|
||||
| Marp Markdown (variant) | `assets/templates/marp/slides-marp.md` (+ `slides-marp.css`) / `slides-marp-en.md` (+ `slides-marp-en.css`) | User explicitly asks for Marp, "markdown slides", or a `.md` deck. Shipped `.md` is a working demo of Kami Marp itself; copy it, swap content, keep the structure. Renders via local `marp` CLI; not bundled. |
|
||||
|
||||
### Page size
|
||||
|
||||
Default is `280mm 158mm`. Ask only if the user has mentioned length or density constraints.
|
||||
|
||||
| Size | When |
|
||||
|---|---|
|
||||
| `280mm 158mm` | Default; fits most decks |
|
||||
| `297mm 167mm` | User wants a bit more room |
|
||||
| `338mm 190mm` | Heavy content slide or many data points per page |
|
||||
|
||||
### Content pre-flight
|
||||
|
||||
Before drafting any slide, confirm these points with the user. Ask all at once, skip any already answered:
|
||||
|
||||
| # | Question |
|
||||
|---|---|
|
||||
| 1 | **Audience + venue** - who is in the room, and is it live keynote, investor 1:1, or async share link? |
|
||||
| 2 | **Length target** - presentation time or slide count? (15 min: ~10 slides / 30 min: ~20 slides / 45 min: ~25-30 slides) |
|
||||
| 3 | **Source material** - what content is already ready: outline, doc, notes, data? |
|
||||
| 4 | **Images** - are screenshots, charts, logos, or product images available; which slides need real evidence slots; and is a separate visual brief needed? |
|
||||
| 5 | **Hard constraints** - brand colors, required logo, PPTX required, any slides that must exist? |
|
||||
| 6 | **Format confirmation** - slides deck, or a one-pager that looks like a deck? |
|
||||
|
||||
Before drafting any landing page or product site, lock these points from the source material. Ask once only when a missing item would change the deliverable:
|
||||
|
||||
| # | Lock |
|
||||
|---|---|
|
||||
| 1 | **Product category** - first-viewport category: app, CLI, terminal, utility, skill, template system, or another user-provided label. |
|
||||
| 2 | **Real assets** - available product screenshots, logo, icon, or UI captures, mapped to hero/gallery/feature/social slots. Missing assets must stay marked, not replaced with stock imagery. |
|
||||
| 3 | **Site shape** - single page, or home plus docs/help/releases/changelog/roadmap/legal pages? |
|
||||
| 4 | **Locales** - exact locale list, canonical paths, and whether a generator/check mode is needed. |
|
||||
| 5 | **Truth surfaces** - install path, price, version, support route, FAQ, `llms.txt`, and `llms-full.txt` that must stay synchronized. |
|
||||
|
||||
### Content rules for slides
|
||||
|
||||
- Ghost deck test: read only the slide titles in order. They must tell the argument; if not, fix titles or structure before styling
|
||||
- One evidence shape per slide: chart, table, screenshot, code, quote, or conclusion. Split mixed evidence instead of crowding one slide
|
||||
- Audience copy stays clean: titles, body, and captions never contain image prompts, crop instructions, or generation notes
|
||||
- No section divider slides: use `.eyebrow` for section numbering, not a dedicated blue-background page
|
||||
- No CJK parentheses: replace `(...)` with `·` or `,`
|
||||
- Each bullet fits one line: trim until it does
|
||||
- 2×2 layouts: use `table.t2x2`, not CSS Grid
|
||||
- Pinned conclusions: use `.co` at `position: absolute; bottom: 12mm`
|
||||
|
||||
These rules apply identically to Marp decks. Marp-specific syntax: see `references/design.md` §8 «Marp variant».
|
||||
|
||||
## Step 2.7 · Layout note (transparent, non-blocking)
|
||||
|
||||
Before loading specs and filling the template, write a short editor-style note stating the layout intent: template choice, length target, narrative arc, embedded diagrams, material status, and output formats. Match the document's language. Keep it under 80 words, written as prose, not a status panel. Continue immediately after; do not wait.
|
||||
|
||||
Example (CN):
|
||||
|
||||
> 排版意图:Equity Report 中文版,2 页 A4。先立论与目标价,进入估值 (DCF 与可比公司),落于催化剂与风险。中段嵌一张营收趋势折线和 FY26 收入桥瀑布。Logo 已就位,产品图暂缺,header 改走纯文字。输出 HTML 与 PDF。
|
||||
|
||||
Example (EN):
|
||||
|
||||
> Layout intent: Equity Report (EN), two pages A4. Open with thesis and price target, run through valuation (DCF and comparables), close on catalysts and risks. A revenue line chart and an FY26 waterfall sit mid-doc. Logo is in hand; product image is absent, so the header stays text-only. Output: HTML and PDF.
|
||||
|
||||
The note is for transparency, not approval. If the user pushes back, adjust; otherwise proceed to Step 3.
|
||||
|
||||
---
|
||||
|
||||
## Step 3 · Load the right amount of spec
|
||||
|
||||
Pick the tier that matches the task. Default to the lowest tier that covers the work.
|
||||
|
||||
| Tier | When | Read |
|
||||
|---|---|---|
|
||||
| **Content-only** | Updating text, swapping bullets, translating an existing doc. CSS stays untouched. | `CHEATSHEET.md` only |
|
||||
| **Layout tweak** | Adjusting spacing, moving sections, changing font size within spec. CSS touched. | `CHEATSHEET.md` + template (tokens already inline) |
|
||||
| **New document** | Building from scratch or from raw content. | Full design spec + writing spec + template |
|
||||
| **Resume content** | Resume-specific bullet structure, project framing, scope-result-outcome rules. | `resume-writing.md` + template |
|
||||
| **Sources / materials** | Company, product, market, launch, funding, specs, or branded subject. | `writing.md` source rules + user/source material |
|
||||
| **Deck (>20 slides)** | Long presentation needing Part Divider, Code Cards, section headers. | Full design spec + Deck Recipe (design.md section 8) |
|
||||
| **Troubleshoot** | Rendering bug, font issue, page overflow. | `production.md` (+ design spec if CSS is the cause) |
|
||||
| **Anti-patterns** | Reviewing AI-generated drafts before shipping. | `anti-patterns.md` (six-category checklist) |
|
||||
| **Diagram** | Embedding SVG in a doc, or maintaining a repo-owned diagram (trio: HTML + PNG + prompt.md). | `diagrams.md` only (has its own token map) |
|
||||
|
||||
You can always escalate mid-task if the work turns out to need more than the initial tier.
|
||||
|
||||
The full spec files for reference:
|
||||
- Design: `references/design.md`
|
||||
- Writing (general): `references/writing.md`
|
||||
- Writing (resume-specific): `references/resume-writing.md`
|
||||
- Production: `references/production.md`
|
||||
- Diagrams: `references/diagrams.md`
|
||||
- Anti-patterns: `references/anti-patterns.md`
|
||||
|
||||
## Step 4 · Fill content into the template
|
||||
|
||||
- Copy the template into your working directory; don't write HTML from scratch
|
||||
- **CSS stays untouched**, only edit the body
|
||||
- Content follows `writing.md`: data over adjectives, distinctive phrasing over industry clichés
|
||||
- Avoid patterns listed in `references/anti-patterns.md`: emptiness, fabrication, mimicry, excess, source gaps, tone contamination
|
||||
- **Before filling, read the quality bar for your document type** in `writing.md` section "Quality bars by document type". Structure is necessary but not sufficient: a resume bullet needs Action + Scope + Result + Business Outcome; an equity report needs variant perception + quantified catalysts; slides need assertion-evidence titles. Meeting the quality bar is as important as filling every placeholder.
|
||||
|
||||
### Do not generate
|
||||
|
||||
These are the most common AI document failures. Cross-reference `references/anti-patterns.md` for the full list.
|
||||
|
||||
- Do not leave placeholder text in the final document ("Lorem ipsum", "[Insert here]", "TBD")
|
||||
- Do not invent metrics, financial data, or statistics; mark gaps with `[DATA NEEDED: description]`
|
||||
- Do not use stock-image descriptions as image placeholders ("A diverse team collaborating in a modern office")
|
||||
- Do not pad content to fill template slots (a resume with 3 real projects does not need 5 fabricated ones)
|
||||
- Do not write a paragraph that merely restates its own heading in sentence form
|
||||
|
||||
### Fill PDF metadata (WeasyPrint reads these into the PDF)
|
||||
|
||||
Every template has meta placeholders in `<head>`. Fill all four before building:
|
||||
|
||||
| Placeholder (CN) | Placeholder (EN) | Rule |
|
||||
|---|---|---|
|
||||
| `{{作者}}` | `{{AUTHOR}}` | Resume/letter/portfolio: use the person's name from the doc. All others: leave as-is (build script infers from git config or env) |
|
||||
| `{{摘要}}` | `{{DESCRIPTION}}` | Extract one sentence (≤150 chars) from the first 2 paragraphs |
|
||||
| `{{关键词}}` | `{{KEYWORDS}}` | 3-5 keywords from the title + section headings, comma-separated |
|
||||
| `{{文档标题}}` / `{{信件主题}}` etc. | `{{DOC_TITLE}}` / `{{LETTER_SUBJECT}}` etc. | Infer from the H1 or `.header .title` text |
|
||||
|
||||
`<meta name="generator" content="Kami">` is already fixed in the template; do not change it.
|
||||
|
||||
**Author inference**: `build.py` automatically sets PDF `/Author` metadata from:
|
||||
1. `git config user.name` (primary)
|
||||
2. `KAMI_AUTHOR` environment variable (fallback)
|
||||
3. `"Kami"` (final fallback)
|
||||
|
||||
For personal documents (resume/letter/portfolio), the HTML `<meta name="author">` should match the person's name in the content. For non-personal documents (one-pager/long-doc), leave the placeholder as-is and let the build script infer it.
|
||||
|
||||
## Step 4.1 · Per-page density target (multi-page templates only)
|
||||
|
||||
适用:slides-weasy / long-doc / portfolio / equity-report / changelog。不适用 resume / one-pager / letter(这些有独立的长度合约)。
|
||||
|
||||
正文页填充率目标 60-80%。封面 / 目录 / 末尾署名页豁免。这条规则解决的是 AI 生成多页文档时最常见的 draft 缺陷:把内容拆得太散,结果几页都填不满。
|
||||
|
||||
### Items-per-page contract
|
||||
|
||||
| Template | Typical body page | Hard floor (merge if below) |
|
||||
|---|---|---|
|
||||
| slides-weasy | 1 assertion title + 3-5 supporting items, or 1 chart + 2-3 callouts | <3 items and no chart → merge into adjacent slide |
|
||||
| long-doc | 1 chapter heading + 2-4 paragraphs + at most 1 figure | Chapter renders to <40% page → merge into neighbor chapter |
|
||||
| portfolio | 1 project header + 1 hero image + 3-5 outcome bullets | No image and <3 outcomes → merge with adjacent project |
|
||||
| equity-report | 1 section + 1 table/chart + supporting prose | Only a 2-row table on the page → combine sections |
|
||||
| changelog | 1 version block + 4-8 entries | Version has <4 entries → place on the same page as the prior version |
|
||||
|
||||
### Sparse-page merge rule
|
||||
|
||||
Before finalizing, scan the draft. Any body page that would render under 50% full → apply one of, in order:
|
||||
|
||||
1. Merge upward into the previous section.
|
||||
2. Merge downward into the next section.
|
||||
3. Promote a list to a small diagram or table that earns the space.
|
||||
4. Pin a `.co` callout to bottom (slides-weasy only). Whitespace above a pinned callout is intentional, not sparse.
|
||||
|
||||
Forbidden ways to "fill" a sparse page: padding with filler prose, repeating the heading as a sentence, inventing statistics, restating the prior page in different words. If the merge options don't apply, the page itself shouldn't exist.
|
||||
|
||||
### Last-page exemption
|
||||
|
||||
The last body page is allowed to run 40-60% fill. Forcing balance on the last page usually means padding. The colophon / closing slide may have any fill level.
|
||||
|
||||
### Verify after build
|
||||
|
||||
```bash
|
||||
python3 scripts/build.py --check-density # flags >25% (WARN) / >50% (SPARSE) trailing whitespace
|
||||
```
|
||||
|
||||
If a body page (not cover, not last page) gets a SPARSE warning, treat it as a draft defect and re-author with the merge rule.
|
||||
|
||||
## Step 4.2 · Resume recruiter pass (resume only)
|
||||
|
||||
Mechanical checks (`--check-placeholders`, `--check-resume-balance`, `--check-density`) validate structure and layout, not prose. A resume can pass all of them and still read broken. After filling and before building, reread every project card the way a recruiter would, against the row definitions in `references/resume-writing.md` ("What goes in each row"): Role carries your position in the project, not background alone; Actions are verb-led, one concrete approach per sentence; Impact reads as an outcome, not a restatement of the process. One cross-row check on top: no row repeats another row's information.
|
||||
|
||||
Fix a failing row by rewriting from the source material. If the source cannot support a row (for example, no outcome fact exists), ask the user for the missing fact. Do not pad, and do not fall back to generic claims ("保障稳定运行", "improved efficiency").
|
||||
|
||||
This pass is internal: run it silently; surface it only when a row cannot be fixed without new information from the user.
|
||||
|
||||
## Step 4.5 · Auto-select output format
|
||||
|
||||
Do not ask the user which format to export. Decide from context:
|
||||
|
||||
| Signal | Output | Why |
|
||||
|---|---|---|
|
||||
| Any document request | HTML + PDF | PDF is the default deliverable, HTML is the source |
|
||||
| Slides / PPT / deck | HTML + PDF + PPTX | Presentations need a projectable format |
|
||||
| "分享" / "发朋友圈" / "share" / "post" / "preview" | + PNG | Social platforms and messaging need images |
|
||||
| "嵌入" / "插图" / "embed in another doc" | PNG only | Used as material inside other documents |
|
||||
| User explicitly says a format | Follow the user | Explicit request overrides auto-selection |
|
||||
|
||||
PDF always ships for document templates. Landing pages ship as a ready-to-serve static HTML file. PPTX follows slides. PNG follows sharing context. The user should never need to think about formats.
|
||||
|
||||
## Step 5 · Build & verify
|
||||
|
||||
```bash
|
||||
python3 scripts/build.py --verify # build all templates + page count + font check + slides
|
||||
python3 scripts/build.py --verify resume-en # single target full verification
|
||||
python3 scripts/build.py landing-page # screen-first static HTML template check
|
||||
python3 scripts/build.py --verify slides # single slide deck verification
|
||||
python3 scripts/build.py --check-placeholders path/to/filled.html
|
||||
python3 scripts/build.py --check-markdown path/to/filled.pdf
|
||||
python3 scripts/build.py --check-resume-balance path/to/resume.pdf
|
||||
python3 scripts/build.py --check-density # page whitespace scanner (skips cover)
|
||||
python3 scripts/build.py --check # lint + token/theme + public-site fact checks
|
||||
python3 scripts/build_metadata.py --check # Claude/Codex plugin mirror + marketplace drift check
|
||||
```
|
||||
|
||||
> **Screen verify**: `--check-density` is a print gate. For screen output (landing or docs pages) instead screenshot the rendered page at 375px and 1280px in every locale and scan for line widows before shipping. See `references/design.md` Section 11 «Responsive screenshot verification».
|
||||
|
||||
Source templates intentionally keep `{{...}}` fields. Run placeholder checks on completed documents, not on the template library.
|
||||
|
||||
For Markdown-sourced long documents, also run `--check-markdown` on the rendered PDF. It catches visible raw `---`, `**bold**`, and inline-code backticks that should have been converted or removed before delivery.
|
||||
|
||||
Visual anomalies (tag double rectangle, font fallback, page break issues) -> `production.md` Part 4.
|
||||
|
||||
### Maintainer-mode checks
|
||||
|
||||
Use these only when maintaining this repository or release package, not for ordinary document generation.
|
||||
|
||||
- If marketplace metadata, generated plugin mirrors, version selection, or install paths change, run `python3 scripts/build_metadata.py --check`; for Claude Code install behavior, smoke with an isolated `HOME=/tmp/...` using `claude plugin marketplace add <path>`, `claude plugin install kami@kami`, and `claude plugin details kami@kami`; for Codex install behavior, smoke with an isolated `CODEX_HOME=/tmp/...` using `codex plugin marketplace add <path>`, `codex plugin add kami@kami`, and `codex plugin list`.
|
||||
- If `SKILL.md`, templates, scripts, references, or other package inputs change and the behavior ships through the skill package, run `bash scripts/package-skill.sh` and inspect `dist/kami.zip` before handoff.
|
||||
- If a GitHub release asset is refreshed, download the uploaded `kami.zip` and compare ZIP entry names plus per-entry SHA-256 digests against local `dist/kami.zip`; page text, file size, and the container hash are not enough.
|
||||
|
||||
## Fonts
|
||||
|
||||
**Chinese**
|
||||
- Main serif: TsangerJinKai02-W04.ttf (400 weight) + TsangerJinKai02-W05.ttf (500 weight, real bold)
|
||||
- Templates use dual @font-face declarations: W04 for body text, W05 for headings
|
||||
- Both files are commercial fonts. Keep them available in the repository for local preview and CDN fallback, but do not bundle them inside Claude Desktop skill ZIPs
|
||||
- Fallback chain baked into templates: Source Han Serif SC -> Noto Serif CJK SC -> Songti SC -> STSong -> Georgia
|
||||
|
||||
**Japanese (best-effort)**
|
||||
- Uses CJK template path, no dedicated `-ja` templates yet
|
||||
- JP Mincho-first stack: YuMincho -> Hiragino Mincho ProN -> Noto Serif CJK JP -> Source Han Serif JP -> TsangerJinKai02 -> serif
|
||||
- Visually verify line breaks, punctuation rhythm, and emphasis weight before shipping
|
||||
|
||||
**Korean (best-effort)**
|
||||
- Dedicated `-ko` templates use Source Han Serif K Regular / Medium, with the real OTF family name `Source Han Serif KR` kept in every fallback stack
|
||||
- Fallback: Noto Serif KR / Apple SD Gothic Neo / AppleMyungjo / Charter / Georgia
|
||||
- The OTFs are OFL-licensed and tracked for local preview / CDN fallback, but excluded from Claude Desktop skill ZIPs to keep the package small
|
||||
|
||||
**English**
|
||||
- Single serif: Charter (system-bundled, macOS/iOS), used for both headlines and body
|
||||
- No separate sans: `--sans: var(--serif)`, one font per page
|
||||
- Fallback: Georgia (cross-platform) / Palatino / Times New Roman
|
||||
|
||||
Font files next to HTML with relative `@font-face` paths is the most stable setup. `scripts/package-skill.sh` excludes large CJK font files from the Claude Desktop ZIP, so the uploaded package stays under the 6MB package ceiling and contains a top-level `kami/` skill folder. Always upload that `package-skill.sh` output, never a hand-zipped checkout (the tracked CJK fonts make it too large and Claude Desktop rejects the upload).
|
||||
|
||||
**Font auto-recovery (Claude Desktop)**
|
||||
|
||||
Before building Chinese or Korean documents, ensure fonts are present. The script tries multiple CDN sources with retry and size validation:
|
||||
|
||||
```bash
|
||||
bash scripts/ensure-fonts.sh
|
||||
```
|
||||
|
||||
It downloads to the XDG user font dir (`${XDG_DATA_HOME:-~/.local/share}/fonts/kami`, override with `KAMI_FONT_DIR`), **not** into the skill's `assets/fonts` -- that keeps the installed skill small so Claude Desktop never trips its size limit. fontconfig scans that dir by default, so WeasyPrint finds `TsangerJinKai02` and `Source Han Serif K` there; online renders fall back to the jsDelivr `@font-face` URL. Run once before building. If all sources fail, the script prints per-language alternatives.
|
||||
|
||||
## Feedback protocol
|
||||
|
||||
When the user gives **vague visual feedback** ("looks off", "太挤了", "not elegant"), do not guess. Ask back with current values:
|
||||
|
||||
| User says | Ask about |
|
||||
|---|---|
|
||||
| "太挤了" / "too cramped" | Which element? Line-height (current: X)? Padding (current: Y)? Page margin? |
|
||||
| "太松了" / "too loose" | Same direction, reversed |
|
||||
| "颜色不对" / "color feels wrong" | Which element? Brand blue overused? A gray reading too cool? |
|
||||
| "不够好看" / "not polished" | Font rendering? Alignment? Whitespace distribution? Hierarchy unclear? |
|
||||
| "看着不专业" / "unprofessional" | Content wording? Or layout (alignment, consistency)? |
|
||||
|
||||
Template response: "X is currently set to Y. Would you like (a) [specific alternative within spec] or (b) [another option]?"
|
||||
|
||||
Never say "I'll adjust the spacing" without naming the exact property and its new value.
|
||||
|
||||
---
|
||||
|
||||
## When not to use this skill
|
||||
|
||||
- User explicitly wants Material / Fluent / Tailwind default - different design language
|
||||
- Need dark / cyberpunk / futurist aesthetic (this is deliberately anti-future)
|
||||
- Need saturated multi-color (this has one accent)
|
||||
- Need cartoon / animation / illustration style (this is editorial)
|
||||
- Web dynamic app UI (this is for print / static documents)
|
||||
|
||||
---
|
||||
|
||||
Next: **apply Step 3's tier table to decide what to read**, then copy the matching template and start filling.
|
||||
@@ -0,0 +1,632 @@
|
||||
<!DOCTYPE html>
|
||||
<!-- ==================================================================
|
||||
SLIDES DEMO · Agent development · English
|
||||
Landscape A4, one slide per page, kami palette.
|
||||
Content distilled from the article "Things You Don't Know About Agents"
|
||||
(tw93.fun, 2026-03-21).
|
||||
================================================================== -->
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Things You Don't Know About Agents · Slides</title>
|
||||
<style>
|
||||
/* Palatino is a system font; JetBrains Mono kept for code blocks */
|
||||
@font-face { font-family: "JetBrains Mono"; src: url("../fonts/JetBrainsMono.woff2") format("woff2"); font-weight: 400; font-style: normal; }
|
||||
@font-face { font-family: "JetBrains Mono"; src: url("../fonts/JetBrainsMono.woff2") format("woff2"); font-weight: 500; font-style: normal; }
|
||||
|
||||
@page {
|
||||
size: A4 landscape;
|
||||
margin: 0;
|
||||
background: #f5f4ed;
|
||||
}
|
||||
|
||||
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
|
||||
:root {
|
||||
--parchment: #f5f4ed;
|
||||
--ivory: #faf9f5;
|
||||
--near-black: #141413;
|
||||
--dark-warm: #3d3d3a;
|
||||
--olive: #504e49;
|
||||
--stone: #6b6a64;
|
||||
--brand: #1B365D;
|
||||
--brand-tint: #EEF2F7;
|
||||
--border: #e8e6dc;
|
||||
|
||||
--serif: Charter, Georgia, Palatino, serif;
|
||||
--sans: var(--serif);
|
||||
--mono: "JetBrains Mono", "SF Mono", Consolas, monospace;
|
||||
}
|
||||
|
||||
body {
|
||||
color: var(--near-black);
|
||||
font-family: var(--serif);
|
||||
}
|
||||
|
||||
.slide {
|
||||
width: 297mm;
|
||||
height: 210mm;
|
||||
padding: 18mm 22mm;
|
||||
background: var(--parchment);
|
||||
break-after: page;
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.slide:last-child { break-after: auto; }
|
||||
|
||||
.eyebrow {
|
||||
font-family: var(--mono);
|
||||
font-size: 9pt;
|
||||
letter-spacing: 2.5pt;
|
||||
text-transform: uppercase;
|
||||
color: var(--stone);
|
||||
}
|
||||
|
||||
.page-num {
|
||||
position: absolute;
|
||||
bottom: 10mm;
|
||||
right: 22mm;
|
||||
font-family: var(--mono);
|
||||
font-size: 9pt;
|
||||
color: var(--stone);
|
||||
letter-spacing: 0.5pt;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
.footer-mark {
|
||||
position: absolute;
|
||||
bottom: 10mm;
|
||||
left: 22mm;
|
||||
font-family: var(--mono);
|
||||
font-size: 9pt;
|
||||
color: var(--stone);
|
||||
letter-spacing: 1.5pt;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
/* --- Cover --- */
|
||||
.cover {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 32pt;
|
||||
align-items: center;
|
||||
}
|
||||
.cover-left { display: flex; flex-direction: column; }
|
||||
.cover .mark {
|
||||
font-family: var(--mono);
|
||||
font-size: 10pt;
|
||||
color: var(--brand);
|
||||
letter-spacing: 3pt;
|
||||
text-transform: uppercase;
|
||||
margin-bottom: 22pt;
|
||||
}
|
||||
.cover h1 {
|
||||
font-family: var(--serif);
|
||||
font-size: 42pt;
|
||||
font-weight: 500;
|
||||
line-height: 1.05;
|
||||
letter-spacing: -1.5pt;
|
||||
color: var(--near-black);
|
||||
margin-bottom: 16pt;
|
||||
}
|
||||
.cover .sub {
|
||||
font-family: var(--serif);
|
||||
font-size: 15pt;
|
||||
color: var(--olive);
|
||||
line-height: 1.45;
|
||||
max-width: 34ch;
|
||||
}
|
||||
.cover .line {
|
||||
width: 60pt;
|
||||
height: 2pt;
|
||||
background: var(--brand);
|
||||
margin: 20pt 0 16pt 0;
|
||||
}
|
||||
.cover .meta {
|
||||
font-family: var(--sans);
|
||||
font-size: 10pt;
|
||||
color: var(--stone);
|
||||
letter-spacing: 0.4pt;
|
||||
}
|
||||
.cover-visual { display: flex; align-items: center; justify-content: center; }
|
||||
|
||||
/* --- Content slide --- */
|
||||
.slide-head {
|
||||
margin-bottom: 10pt;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: baseline;
|
||||
}
|
||||
.slide-title {
|
||||
font-family: var(--serif);
|
||||
font-size: 38pt;
|
||||
font-weight: 500;
|
||||
line-height: 1.1;
|
||||
letter-spacing: -0.6pt;
|
||||
color: var(--near-black);
|
||||
max-width: 24ch;
|
||||
margin-top: 6pt;
|
||||
margin-bottom: 28pt;
|
||||
}
|
||||
|
||||
.body-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 28pt;
|
||||
}
|
||||
.body-single {
|
||||
max-width: 70ch;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.claim {
|
||||
font-family: var(--serif);
|
||||
font-size: 17pt;
|
||||
line-height: 1.45;
|
||||
color: var(--dark-warm);
|
||||
margin-bottom: 14pt;
|
||||
}
|
||||
.claim .hl { color: var(--brand); }
|
||||
|
||||
ul.pts {
|
||||
list-style: none;
|
||||
counter-reset: pts;
|
||||
}
|
||||
ul.pts li {
|
||||
counter-increment: pts;
|
||||
font-family: var(--sans);
|
||||
font-size: 12.5pt;
|
||||
line-height: 1.5;
|
||||
color: var(--dark-warm);
|
||||
padding-left: 18pt;
|
||||
position: relative;
|
||||
margin-bottom: 9pt;
|
||||
}
|
||||
ul.pts li::before {
|
||||
content: counter(pts) ".";
|
||||
position: absolute;
|
||||
left: 0;
|
||||
color: var(--brand);
|
||||
font-weight: 500;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
ul.pts li .hl { color: var(--brand); font-weight: 500; }
|
||||
|
||||
/* kami-native callout: transparent + single brand left bar */
|
||||
.callout {
|
||||
background: transparent;
|
||||
border-left: 2pt solid var(--brand);
|
||||
padding: 8pt 0 8pt 16pt;
|
||||
font-family: var(--serif);
|
||||
font-size: 14pt;
|
||||
line-height: 1.5;
|
||||
color: var(--olive);
|
||||
margin-top: 4pt;
|
||||
}
|
||||
.callout .hl { color: var(--brand); }
|
||||
|
||||
/* kami-native metrics: vertical stack, number above label.
|
||||
Stacked layout keeps all 3 numbers on one baseline regardless of label
|
||||
length; horizontal baseline-align breaks visually when labels wrap. */
|
||||
.metrics {
|
||||
display: flex;
|
||||
gap: 20pt;
|
||||
margin-top: 20pt;
|
||||
padding-top: 12pt;
|
||||
border-top: 0.3pt dotted var(--border);
|
||||
}
|
||||
.metric {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6pt;
|
||||
}
|
||||
.metric .v {
|
||||
font-family: var(--serif);
|
||||
font-size: 32pt;
|
||||
font-weight: 500;
|
||||
color: var(--brand);
|
||||
line-height: 1;
|
||||
font-variant-numeric: tabular-nums;
|
||||
letter-spacing: -0.3pt;
|
||||
}
|
||||
.metric .l {
|
||||
font-family: var(--sans);
|
||||
font-size: 10pt;
|
||||
color: var(--olive);
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
/* --- Diagram column --- */
|
||||
.diagram-col {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: center;
|
||||
margin-top: -20pt;
|
||||
}
|
||||
.diagram-col svg { width: 96%; }
|
||||
|
||||
/* --- Ending slide --- */
|
||||
.ending { justify-content: center; align-items: center; text-align: center; }
|
||||
.ending h1 {
|
||||
font-family: var(--serif);
|
||||
font-size: 52pt;
|
||||
font-weight: 500;
|
||||
color: var(--near-black);
|
||||
letter-spacing: -0.8pt;
|
||||
margin-bottom: 12pt;
|
||||
}
|
||||
.ending .sub {
|
||||
font-family: var(--serif);
|
||||
font-size: 17pt;
|
||||
color: var(--olive);
|
||||
max-width: 44ch;
|
||||
line-height: 1.45;
|
||||
}
|
||||
.ending .line {
|
||||
width: 60pt;
|
||||
height: 1.5pt;
|
||||
background: var(--brand);
|
||||
margin: 22pt auto;
|
||||
}
|
||||
|
||||
/* --- Code Card --- */
|
||||
.code-card {
|
||||
background: var(--ivory);
|
||||
border: 1px solid #e8e5da;
|
||||
border-radius: 6pt;
|
||||
padding: 14pt 18pt;
|
||||
font-family: var(--mono);
|
||||
font-size: 10pt;
|
||||
line-height: 1.55;
|
||||
color: var(--near-black);
|
||||
white-space: pre;
|
||||
overflow: hidden;
|
||||
}
|
||||
.code-card .k { color: var(--brand); }
|
||||
.code-card .c { color: #6b6a64; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<!-- ═══════════ 01 · Cover ═══════════ -->
|
||||
<section class="slide cover">
|
||||
<div class="cover-left">
|
||||
<div class="mark">Keynote · 2026</div>
|
||||
<h1>Things you don't know about Agents</h1>
|
||||
<div class="sub">Loops, harness, context, memory: what actually moves the needle in production.</div>
|
||||
<div class="line"></div>
|
||||
<div class="meta">kami slides demo · A4 landscape · 8 slides</div>
|
||||
</div>
|
||||
<div class="cover-visual">
|
||||
<!-- Decorative loop mark: concentric arcs in brand + stone -->
|
||||
<svg viewBox="0 0 260 260" xmlns="http://www.w3.org/2000/svg" style="width: 220pt;">
|
||||
<circle cx="130" cy="130" r="100" fill="none" stroke="#e8e6dc" stroke-width="1"/>
|
||||
<circle cx="130" cy="130" r="72" fill="none" stroke="#e8e6dc" stroke-width="1"/>
|
||||
<circle cx="130" cy="130" r="44" fill="#EEF2F7" stroke="#1B365D" stroke-width="1.2"/>
|
||||
<!-- arc segments on outer ring to suggest cycle -->
|
||||
<path d="M 130 30 A 100 100 0 0 1 230 130" fill="none" stroke="#1B365D" stroke-width="2.2" stroke-linecap="round"/>
|
||||
<path d="M 230 130 A 100 100 0 0 1 130 230" fill="none" stroke="#6b6a64" stroke-width="1.2" stroke-linecap="round"/>
|
||||
<path d="M 130 230 A 100 100 0 0 1 30 130" fill="none" stroke="#6b6a64" stroke-width="1.2" stroke-linecap="round"/>
|
||||
<path d="M 30 130 A 100 100 0 0 1 130 30" fill="none" stroke="#6b6a64" stroke-width="1.2" stroke-linecap="round"/>
|
||||
<!-- center label -->
|
||||
<text x="130" y="125" text-anchor="middle" font-size="10" font-weight="500" fill="#141413" font-family="Charter, Georgia, serif">Agent</text>
|
||||
<text x="130" y="141" text-anchor="middle" font-size="8" fill="#1B365D" font-family="'JetBrains Mono', monospace" letter-spacing="2">LOOP</text>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="page-num">01</div>
|
||||
<div class="footer-mark">Agent Engineering</div>
|
||||
</section>
|
||||
|
||||
<!-- ═══════════ 02 · Agent loop ═══════════ -->
|
||||
<section class="slide">
|
||||
<div class="slide-head">
|
||||
<span class="eyebrow">01 · Agent Loop</span>
|
||||
</div>
|
||||
<div class="slide-title">Simple core,<br>complex surroundings</div>
|
||||
|
||||
<div class="body-grid">
|
||||
<div>
|
||||
<p class="claim">The loop is small. The <span class="hl">infrastructure around it</span> is what keeps it stable as features grow.</p>
|
||||
<ul class="pts">
|
||||
<li>A working Agent loop fits in about <span class="hl">20 lines</span> of code.</li>
|
||||
<li>Control flow lives in the tools, not in branchy internal state.</li>
|
||||
<li>Workflow vs Agent: predefined paths in code vs model picks paths at runtime.</li>
|
||||
</ul>
|
||||
<div class="callout">
|
||||
If your loop keeps growing every sprint, you are fixing the wrong layer. The tax is paid <span class="hl">outside</span> the loop.
|
||||
</div>
|
||||
</div>
|
||||
<div class="diagram-col">
|
||||
<!-- Agent loop cycle diagram. viewBox 680×560 (same ratio as reference).
|
||||
4 nodes 200×72 rx=8, centered at NESW with 12-14px inset from viewBox edges.
|
||||
Ring r=192 grazes top/bottom nodes at their inner edges, passes through left/right.
|
||||
Q-curve arcs: control at tangent-axis outer corner, giving true quarter-circle sweep.
|
||||
Chevron marker (open path, not filled polygon). Core is the only focal element. -->
|
||||
<svg viewBox="0 0 680 560" xmlns="http://www.w3.org/2000/svg">
|
||||
<!-- surroundings label -->
|
||||
<text x="340" y="12" text-anchor="middle" font-size="9" fill="#6b6a64" font-family="'JetBrains Mono', monospace" letter-spacing="1.5">TOOLS · HARNESS · EVALS · MEMORY</text>
|
||||
|
||||
<!-- dashed ink-blue ring -->
|
||||
<circle cx="340" cy="280" r="192" fill="none" stroke="#1B365D" stroke-width="1" stroke-dasharray="4 3"/>
|
||||
|
||||
<!-- PLAN: x=240 y=16 w=200 h=72 rx=8, center (340, 52) -->
|
||||
<rect x="240" y="16" width="200" height="72" rx="8" fill="#ffffff" stroke="#141413" stroke-width="0.8"/>
|
||||
<text x="340" y="52" text-anchor="middle" dominant-baseline="central" font-size="14" font-weight="500" fill="#141413" font-family="Charter, Georgia, serif">PLAN</text>
|
||||
|
||||
<!-- ACT: x=468 y=244 w=200 h=72 rx=8, center (568, 280) -->
|
||||
<rect x="468" y="244" width="200" height="72" rx="8" fill="#ffffff" stroke="#141413" stroke-width="0.8"/>
|
||||
<text x="568" y="280" text-anchor="middle" dominant-baseline="central" font-size="14" font-weight="500" fill="#141413" font-family="Charter, Georgia, serif">ACT</text>
|
||||
|
||||
<!-- OBSERVE: x=240 y=472 w=200 h=72 rx=8, center (340, 508) -->
|
||||
<rect x="240" y="472" width="200" height="72" rx="8" fill="#ffffff" stroke="#141413" stroke-width="0.8"/>
|
||||
<text x="340" y="508" text-anchor="middle" dominant-baseline="central" font-size="14" font-weight="500" fill="#141413" font-family="Charter, Georgia, serif">OBSERVE</text>
|
||||
|
||||
<!-- REFLECT: x=12 y=244 w=200 h=72 rx=8, center (112, 280) -->
|
||||
<rect x="12" y="244" width="200" height="72" rx="8" fill="#ffffff" stroke="#141413" stroke-width="0.8"/>
|
||||
<text x="112" y="280" text-anchor="middle" dominant-baseline="central" font-size="14" font-weight="500" fill="#141413" font-family="Charter, Georgia, serif">REFLECT</text>
|
||||
|
||||
<!-- center: focal CORE text -->
|
||||
<text x="340" y="268" text-anchor="middle" dominant-baseline="central" font-size="16" font-weight="500" fill="#141413" font-family="Charter, Georgia, serif">20 LOC</text>
|
||||
<text x="340" y="292" text-anchor="middle" dominant-baseline="central" font-size="10" fill="#1B365D" font-family="'JetBrains Mono', monospace" letter-spacing="1.5">CORE</text>
|
||||
|
||||
<!-- clockwise arcs (no marker-end; WeasyPrint ignores orient="auto").
|
||||
Arrowheads drawn manually as chevron paths below each arc. -->
|
||||
<!-- PLAN right-mid (440,52) → ACT top-mid (568,244) via (568,52) -->
|
||||
<path d="M 440 52 Q 568 52 568 244" fill="none" stroke="#504e49" stroke-width="1.5"/>
|
||||
<!-- chevron ↓ at (568, 244) -->
|
||||
<path d="M 560 236 L 568 244 L 576 236" fill="none" stroke="#504e49" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
|
||||
<!-- ACT bottom-mid (568,316) → OBSERVE right-mid (440,508) via (568,508) -->
|
||||
<path d="M 568 316 Q 568 508 440 508" fill="none" stroke="#504e49" stroke-width="1.5"/>
|
||||
<!-- chevron ← at (440, 508) -->
|
||||
<path d="M 448 500 L 440 508 L 448 516" fill="none" stroke="#504e49" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
|
||||
<!-- OBSERVE left-mid (240,508) → REFLECT bottom-mid (112,316) via (112,508) -->
|
||||
<path d="M 240 508 Q 112 508 112 316" fill="none" stroke="#504e49" stroke-width="1.5"/>
|
||||
<!-- chevron ↑ at (112, 316) -->
|
||||
<path d="M 104 324 L 112 316 L 120 324" fill="none" stroke="#504e49" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
|
||||
<!-- REFLECT top-mid (112,244) → PLAN left-mid (240,52) via (112,52) -->
|
||||
<path d="M 112 244 Q 112 52 240 52" fill="none" stroke="#504e49" stroke-width="1.5"/>
|
||||
<!-- chevron → at (240, 52) -->
|
||||
<path d="M 232 44 L 240 52 L 232 60" fill="none" stroke="#504e49" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
<div class="page-num">02</div>
|
||||
<div class="footer-mark">Agent Engineering</div>
|
||||
</section>
|
||||
|
||||
<!-- ═══════════ 01 · Agent Loop (traced) ═══════════ -->
|
||||
<section class="slide">
|
||||
<div class="slide-head">
|
||||
<span class="eyebrow">01 · Agent Loop</span>
|
||||
</div>
|
||||
<div class="slide-title">One turn, traced</div>
|
||||
|
||||
<p class="claim" style="max-width: 60ch;">The same loop, made concrete: a single user turn flows through the agent to a tool and back.</p>
|
||||
|
||||
<div style="flex:1; display:flex; align-items:center; justify-content:center;">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 420 280" width="640" height="427" style="background:#f5f4ed">
|
||||
<style>
|
||||
text { font-family: Charter, Georgia, "TsangerJinKai02", "Source Han Serif SC", "Noto Serif CJK SC", serif; }
|
||||
</style>
|
||||
<defs>
|
||||
<marker id="seq-arrow" markerWidth="8" markerHeight="5" refX="8" refY="2.5" orient="auto-start-reverse">
|
||||
<polygon points="0 0, 8 2.5, 0 5" fill="#1b365d" />
|
||||
</marker>
|
||||
<marker id="seq-arrow-open" markerWidth="8" markerHeight="5" refX="8" refY="2.5" orient="auto-start-reverse">
|
||||
<polyline points="0 0, 8 2.5, 0 5" fill="none" stroke="#1b365d" stroke-width="1" />
|
||||
</marker>
|
||||
</defs>
|
||||
<line class="lifeline" data-actor="U" x1="70" y1="70" x2="70" y2="250" stroke="#504e49" stroke-width="0.75" stroke-dasharray="6 4" />
|
||||
<line class="lifeline" data-actor="A" x1="210" y1="70" x2="210" y2="250" stroke="#504e49" stroke-width="0.75" stroke-dasharray="6 4" />
|
||||
<line class="lifeline" data-actor="T" x1="350" y1="70" x2="350" y2="250" stroke="#504e49" stroke-width="0.75" stroke-dasharray="6 4" />
|
||||
<g class="message" data-from="U" data-to="A" data-label="ask" data-line-style="solid" data-arrow-head="filled" data-self="false">
|
||||
<line x1="70" y1="90" x2="210" y2="90" stroke="#504e49" stroke-width="1" marker-end="url(#seq-arrow)" />
|
||||
<text x="140" y="80" font-size="11" text-anchor="middle" font-weight="400" fill="#6b6a64" dy="3.8499999999999996">ask</text>
|
||||
</g>
|
||||
<g class="message" data-from="A" data-to="T" data-label="invoke(query)" data-line-style="solid" data-arrow-head="filled" data-self="false">
|
||||
<line x1="210" y1="130" x2="350" y2="130" stroke="#504e49" stroke-width="1" marker-end="url(#seq-arrow)" />
|
||||
<text x="280" y="120" font-size="11" text-anchor="middle" font-weight="400" fill="#6b6a64" dy="3.8499999999999996">invoke(query)</text>
|
||||
</g>
|
||||
<g class="message" data-from="T" data-to="A" data-label="result" data-line-style="dashed" data-arrow-head="filled" data-self="false">
|
||||
<line x1="350" y1="170" x2="210" y2="170" stroke="#504e49" stroke-width="1" stroke-dasharray="6 4" marker-end="url(#seq-arrow)" />
|
||||
<text x="280" y="160" font-size="11" text-anchor="middle" font-weight="400" fill="#6b6a64" dy="3.8499999999999996">result</text>
|
||||
</g>
|
||||
<g class="message" data-from="A" data-to="U" data-label="answer" data-line-style="dashed" data-arrow-head="filled" data-self="false">
|
||||
<line x1="210" y1="210" x2="70" y2="210" stroke="#504e49" stroke-width="1" stroke-dasharray="6 4" marker-end="url(#seq-arrow)" />
|
||||
<text x="140" y="200" font-size="11" text-anchor="middle" font-weight="400" fill="#6b6a64" dy="3.8499999999999996">answer</text>
|
||||
</g>
|
||||
<g class="actor" data-id="U" data-label="User" data-type="participant">
|
||||
<rect x="30" y="30" width="80" height="40" rx="4" ry="4" fill="#faf9f5" stroke="#e8e6dc" stroke-width="1" />
|
||||
<text x="70" y="50" font-size="13" text-anchor="middle" font-weight="500" fill="#141413" dy="4.55">User</text>
|
||||
</g>
|
||||
<g class="actor" data-id="A" data-label="Agent" data-type="participant">
|
||||
<rect x="170" y="30" width="80" height="40" rx="4" ry="4" fill="#faf9f5" stroke="#e8e6dc" stroke-width="1" />
|
||||
<text x="210" y="50" font-size="13" text-anchor="middle" font-weight="500" fill="#141413" dy="4.55">Agent</text>
|
||||
</g>
|
||||
<g class="actor" data-id="T" data-label="Tool" data-type="participant">
|
||||
<rect x="310" y="30" width="80" height="40" rx="4" ry="4" fill="#faf9f5" stroke="#e8e6dc" stroke-width="1" />
|
||||
<text x="350" y="50" font-size="13" text-anchor="middle" font-weight="500" fill="#141413" dy="4.55">Tool</text>
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<div class="page-num">03</div>
|
||||
<div class="footer-mark">Agent Engineering</div>
|
||||
</section>
|
||||
|
||||
<!-- ═══════════ 03 · Harness over models ═══════════ -->
|
||||
<section class="slide">
|
||||
<div class="slide-head">
|
||||
<span class="eyebrow">02 · Harness</span>
|
||||
</div>
|
||||
<div class="slide-title">Harness wins<br>over hardware</div>
|
||||
|
||||
<div class="body-single">
|
||||
<p class="claim">More expensive models bring gains <span class="hl">far smaller than expected</span>. Verification, boundaries, feedback, and fallbacks matter more than model capability.</p>
|
||||
<ul class="pts">
|
||||
<li>Upgrade the harness first. If accuracy does not move, then try the model.</li>
|
||||
<li>Evaluation is the only honest signal. Test harness before test model.</li>
|
||||
<li>Smaller model + strong harness routinely beats larger model + weak harness in production.</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="metrics">
|
||||
<div class="metric">
|
||||
<div class="v">20</div>
|
||||
<div class="l">lines of code in a working Agent core loop</div>
|
||||
</div>
|
||||
<div class="metric">
|
||||
<div class="v">4</div>
|
||||
<div class="l">harness layers that matter: verify, bound, feedback, fallback</div>
|
||||
</div>
|
||||
<div class="metric">
|
||||
<div class="v">10×</div>
|
||||
<div class="l">velocity gains trace to execution discipline, not model swaps</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="page-num">04</div>
|
||||
<div class="footer-mark">Agent Engineering</div>
|
||||
</section>
|
||||
|
||||
<!-- ═══════════ 04 · Context ═══════════ -->
|
||||
<section class="slide">
|
||||
<div class="slide-head">
|
||||
<span class="eyebrow">03 · Context</span>
|
||||
</div>
|
||||
<div class="slide-title">Density beats length</div>
|
||||
|
||||
<div class="body-grid">
|
||||
<div>
|
||||
<p class="claim">Long context windows do not fix weak context design. <span class="hl">Context Rot</span> sets in around 300–400K tokens regardless of the model.</p>
|
||||
<ul class="pts">
|
||||
<li>Layer the load: constant, on-demand, runtime, memory, system.</li>
|
||||
<li>Index first, full content on demand. Beats dumping everything up front.</li>
|
||||
<li>Stable prompt prefixes let caching actually pay off.</li>
|
||||
<li>Every token that is not load-bearing is diluting signal.</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="diagram-col">
|
||||
<!-- Context: flat vs layered accuracy comparison -->
|
||||
<svg viewBox="0 0 280 230" xmlns="http://www.w3.org/2000/svg">
|
||||
<!-- Background -->
|
||||
<rect width="280" height="230" fill="#f5f4ed"/>
|
||||
<!-- Title -->
|
||||
<text x="140" y="18" text-anchor="middle" font-size="8.5" fill="#6b6a64" font-family="'JetBrains Mono', monospace" letter-spacing="1.5">LOADING STRATEGY vs ACCURACY</text>
|
||||
<!-- Y axis label -->
|
||||
<text x="14" y="130" text-anchor="middle" font-size="8" fill="#504e49" font-family="Charter, Georgia, serif" transform="rotate(-90 14 130)">Accuracy</text>
|
||||
<!-- Y axis line -->
|
||||
<line x1="36" y1="30" x2="36" y2="180" stroke="#504e49" stroke-width="0.8"/>
|
||||
<!-- Y axis ticks -->
|
||||
<line x1="30" y1="56" x2="36" y2="56" stroke="#504e49" stroke-width="0.8"/>
|
||||
<text x="26" y="59" text-anchor="end" font-size="8" fill="#6b6a64" font-family="Charter, Georgia, serif">85%</text>
|
||||
<line x1="30" y1="120" x2="36" y2="120" stroke="#504e49" stroke-width="0.8"/>
|
||||
<text x="26" y="123" text-anchor="end" font-size="8" fill="#6b6a64" font-family="Charter, Georgia, serif">53%</text>
|
||||
<!-- X axis -->
|
||||
<line x1="36" y1="180" x2="256" y2="180" stroke="#504e49" stroke-width="0.8"/>
|
||||
<!-- bar 1: flat loading -->
|
||||
<rect x="56" y="120" width="68" height="60" fill="#e8e6dc" stroke="none"/>
|
||||
<text x="90" y="112" text-anchor="middle" font-size="8" fill="#6b6a64" font-family="Charter, Georgia, serif">53%</text>
|
||||
<text x="90" y="198" text-anchor="middle" font-size="8.5" fill="#504e49" font-family="Charter, Georgia, serif">Flat</text>
|
||||
<text x="90" y="209" text-anchor="middle" font-size="7.5" fill="#6b6a64" font-family="'JetBrains Mono', monospace">loading</text>
|
||||
<!-- bar 2: layered loading (brand) -->
|
||||
<rect x="156" y="56" width="68" height="124" fill="#EEF2F7" stroke="#1B365D" stroke-width="1"/>
|
||||
<text x="190" y="48" text-anchor="middle" font-size="8" fill="#1B365D" font-family="Charter, Georgia, serif" font-weight="500">85%</text>
|
||||
<text x="190" y="198" text-anchor="middle" font-size="8.5" fill="#1B365D" font-family="Charter, Georgia, serif" font-weight="500">Layered</text>
|
||||
<text x="190" y="209" text-anchor="middle" font-size="7.5" fill="#1B365D" font-family="'JetBrains Mono', monospace">loading</text>
|
||||
<!-- dotted line at 85 -->
|
||||
<line x1="36" y1="56" x2="256" y2="56" stroke="#1B365D" stroke-width="0.6" stroke-dasharray="3 3"/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="page-num">05</div>
|
||||
<div class="footer-mark">Agent Engineering</div>
|
||||
</section>
|
||||
|
||||
<!-- ═══════════ 05 · Tools + Memory ═══════════ -->
|
||||
<section class="slide">
|
||||
<div class="slide-head">
|
||||
<span class="eyebrow">04 · Tools & Memory</span>
|
||||
</div>
|
||||
<div class="slide-title">Put state outside<br>the context</div>
|
||||
|
||||
<div class="body-grid">
|
||||
<div>
|
||||
<p class="claim">Tools should match Agent goals, not underlying API shapes. Memory lives on disk, not in the window.</p>
|
||||
<ul class="pts">
|
||||
<li><span class="hl">ACI principle</span>: Agent-Computer Interface - design for what the Agent wants to do, not for the HTTP verb.</li>
|
||||
<li>A bad tool description looks like a model failure until you re-read the description.</li>
|
||||
<li>File-based state survives restarts. In-context state does not.</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div>
|
||||
<p class="claim" style="font-size: 14pt;"><span class="hl">Four kinds of memory</span></p>
|
||||
<ul class="pts">
|
||||
<li><span class="hl">Working</span>: the context window - fast, expensive, temporary.</li>
|
||||
<li><span class="hl">Procedural</span>: SKILL.md files - how to behave, loaded lazily.</li>
|
||||
<li><span class="hl">Episodic</span>: JSONL logs - what happened, appendable.</li>
|
||||
<li><span class="hl">Semantic</span>: MEMORY.md - what to remember across sessions, consolidated at thresholds.</li>
|
||||
</ul>
|
||||
<div class="callout">
|
||||
Cross-session consistency needs explicit consolidation, not hope.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="page-num">06</div>
|
||||
<div class="footer-mark">Agent Engineering</div>
|
||||
</section>
|
||||
|
||||
<!-- ═══════════ 06 · Code Card demo ═══════════ -->
|
||||
<section class="slide">
|
||||
<div class="slide-head">
|
||||
<span class="eyebrow">05 · Code Style</span>
|
||||
</div>
|
||||
<div class="slide-title">Pseudocode over syntax</div>
|
||||
|
||||
<div class="body-grid">
|
||||
<div>
|
||||
<p class="claim">Comments should outnumber code lines. The reader sees <span class="hl">logic</span>, not a language tutorial.</p>
|
||||
<ul class="pts">
|
||||
<li>Write the intent first, then the implementation detail below it.</li>
|
||||
<li>Variable names describe what a thing <span class="hl">is</span>, not what type it has.</li>
|
||||
<li>One concept per block. Split ruthlessly.</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div style="display:flex;flex-direction:column;gap:14pt;">
|
||||
<div class="code-card"><span class="c"># resolve tool call or decide to stop</span>
|
||||
<span class="k">function</span> agent_step(context, tools):
|
||||
<span class="c"># model picks the next action</span>
|
||||
action = <span class="k">model.think</span>(context)
|
||||
|
||||
<span class="c"># terminal condition: model says done</span>
|
||||
<span class="k">if</span> action.type == <span class="k">"stop"</span>:
|
||||
<span class="k">return</span> action.result
|
||||
|
||||
<span class="c"># delegate to the right tool</span>
|
||||
result = tools[action.name](action.args)
|
||||
<span class="k">return</span> agent_step(context + result, tools)</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="page-num">07</div>
|
||||
<div class="footer-mark">Agent Engineering</div>
|
||||
</section>
|
||||
|
||||
<!-- ═══════════ 07 · Closing ═══════════ -->
|
||||
<section class="slide ending">
|
||||
<h1>Protocol first.<br>Then parallelism.</h1>
|
||||
<div class="line"></div>
|
||||
<div class="sub">Fix your evals before you tweak the Agent. Most of what looks like model failure is infrastructure noise in disguise.</div>
|
||||
<div class="page-num">08</div>
|
||||
<div class="footer-mark">End of Deck</div>
|
||||
</section>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
After Width: | Height: | Size: 183 KiB |
@@ -0,0 +1,224 @@
|
||||
<!DOCTYPE html>
|
||||
<!-- ==================================================================
|
||||
CHANGELOG · English · parchment design system
|
||||
1-2 page A4 release notes / version changelog
|
||||
================================================================== -->
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Mole · v1.7.1 Release Notes</title>
|
||||
<meta name="author" content="Tw93">
|
||||
<meta name="description" content="Mole v1.7.1 release notes: accessibility, a faster status board, and deeper cleanup, uninstall, and updates.">
|
||||
<meta name="keywords" content="Mole, macOS, release notes, changelog, Mac utility">
|
||||
<meta name="generator" content="Kami">
|
||||
<style>
|
||||
@font-face { font-family: "JetBrains Mono"; src: url("../fonts/JetBrainsMono.woff2") format("woff2"); font-weight: 400; font-style: normal; }
|
||||
@font-face { font-family: "JetBrains Mono"; src: url("../fonts/JetBrainsMono.woff2") format("woff2"); font-weight: 500; font-style: normal; }
|
||||
|
||||
@page {
|
||||
size: A4;
|
||||
margin: 20mm 22mm 22mm 22mm;
|
||||
background: #f5f4ed;
|
||||
}
|
||||
|
||||
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
|
||||
:root {
|
||||
--parchment: #f5f4ed;
|
||||
--ivory: #faf9f5;
|
||||
--near-black:#141413;
|
||||
--dark-warm: #3d3d3a;
|
||||
--olive: #504e49;
|
||||
--stone: #6b6a64;
|
||||
--brand: #1B365D;
|
||||
--border: #e8e6dc;
|
||||
--border-soft:#e5e3d8;
|
||||
--tag-bg: #E4ECF5;
|
||||
--breaking-bg:#f0e0d8;
|
||||
--breaking-fg:#8b4513;
|
||||
|
||||
--serif: Charter, Georgia,
|
||||
Palatino, "Times New Roman", serif;
|
||||
--sans: var(--serif);
|
||||
--mono: "JetBrains Mono", "SF Mono", "Fira Code",
|
||||
Consolas, Monaco, monospace;
|
||||
}
|
||||
|
||||
html, body { background: var(--parchment); }
|
||||
|
||||
@media screen {
|
||||
body { max-width: 210mm; margin: 0 auto; padding: 20mm 22mm; }
|
||||
}
|
||||
|
||||
body {
|
||||
color: var(--near-black);
|
||||
font-family: var(--serif);
|
||||
font-size: 10.5pt;
|
||||
line-height: 1.55;
|
||||
widows: 3;
|
||||
orphans: 3;
|
||||
}
|
||||
|
||||
strong { font-weight: 500; }
|
||||
.hl { color: var(--brand); font-weight: 500; }
|
||||
|
||||
/* HEADER - left-aligned editorial opener, matches the one-pager. */
|
||||
.header {
|
||||
margin-bottom: 20pt;
|
||||
padding-bottom: 13pt;
|
||||
border-bottom: 0.5pt solid var(--border);
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
justify-content: space-between;
|
||||
gap: 20pt;
|
||||
}
|
||||
.title-block { flex: 1; }
|
||||
.eyebrow {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 7pt;
|
||||
font-family: var(--sans);
|
||||
font-size: 9pt;
|
||||
color: var(--brand);
|
||||
letter-spacing: 1.5pt;
|
||||
text-transform: uppercase;
|
||||
font-weight: 500;
|
||||
margin-bottom: 8pt;
|
||||
}
|
||||
.eyebrow::before {
|
||||
content: "";
|
||||
width: 8pt;
|
||||
height: 1.5pt;
|
||||
border-radius: 0.75pt;
|
||||
background: var(--brand);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
h1 {
|
||||
font-family: var(--serif);
|
||||
font-size: 29pt;
|
||||
font-weight: 500;
|
||||
color: var(--near-black);
|
||||
line-height: 1.1;
|
||||
letter-spacing: -0.5pt;
|
||||
margin-bottom: 7pt;
|
||||
}
|
||||
.subtitle {
|
||||
font-size: 11.5pt;
|
||||
color: var(--olive);
|
||||
line-height: 1.45;
|
||||
max-width: 150mm;
|
||||
}
|
||||
.meta {
|
||||
font-size: 10pt;
|
||||
color: var(--stone);
|
||||
white-space: nowrap;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
/* SECTION HEAD - brand left bar */
|
||||
h2 {
|
||||
font-family: var(--serif);
|
||||
font-size: 14pt;
|
||||
font-weight: 500;
|
||||
color: var(--near-black);
|
||||
margin: 20pt 0 10pt 0;
|
||||
border-left: 2.5pt solid var(--brand);
|
||||
border-radius: 1.5pt;
|
||||
padding-left: 9pt;
|
||||
break-after: avoid;
|
||||
}
|
||||
h2:first-of-type { margin-top: 0; }
|
||||
|
||||
code { font-family: var(--mono); font-size: 0.92em; }
|
||||
|
||||
/* CHANGE LIST - numbered with brand-colored markers and a bold lead-in.
|
||||
Numbers carry sequence without a repeated decorative glyph; the section
|
||||
bars stay the only brand marks. Each section restarts at 1. */
|
||||
ol.changes { margin: 0; padding-left: 18pt; }
|
||||
ol.changes li {
|
||||
margin-bottom: 7pt;
|
||||
padding-left: 3pt;
|
||||
line-height: 1.55;
|
||||
break-inside: avoid;
|
||||
}
|
||||
ol.changes li:last-child { margin-bottom: 0; }
|
||||
ol.changes li::marker { color: var(--brand); font-weight: 500; }
|
||||
ol.changes li strong { font-weight: 500; color: var(--near-black); }
|
||||
ol.changes li .hl { color: var(--brand); font-weight: 500; }
|
||||
|
||||
/* ACKNOWLEDGEMENTS - a quiet labelled note, not a filled card, so the page
|
||||
stays flat. The small brand label is the only marker it needs. */
|
||||
.thanks {
|
||||
margin-top: 18pt;
|
||||
font-size: 9.5pt;
|
||||
line-height: 1.55;
|
||||
color: var(--olive);
|
||||
break-inside: avoid;
|
||||
}
|
||||
.thanks-label {
|
||||
font-family: var(--sans);
|
||||
font-size: 9pt;
|
||||
color: var(--brand);
|
||||
letter-spacing: 1pt;
|
||||
text-transform: uppercase;
|
||||
font-weight: 500;
|
||||
margin-bottom: 4pt;
|
||||
}
|
||||
.thanks .hl { color: var(--brand); font-weight: 500; }
|
||||
|
||||
.changelog-footer {
|
||||
margin-top: 13pt;
|
||||
padding-top: 8pt;
|
||||
border-top: 0.3pt dotted var(--border);
|
||||
font-size: 9pt;
|
||||
color: var(--stone);
|
||||
line-height: 1.5;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
font-variant-numeric: tabular-nums;
|
||||
letter-spacing: 0.3pt;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="header">
|
||||
<div class="title-block">
|
||||
<div class="eyebrow">Release Notes</div>
|
||||
<h1>Mole v1.7.1</h1>
|
||||
<div class="subtitle">Accessibility throughout, a faster status board, and deeper cleanup, uninstall, and updates.</div>
|
||||
</div>
|
||||
<div class="meta">June 13, 2026</div>
|
||||
</div>
|
||||
|
||||
<h2>Highlights</h2>
|
||||
<ol class="changes">
|
||||
<li><strong>Accessibility throughout</strong>: VoiceOver labels, smoother keyboard paths, and Reduce Motion across the app and the website, guided by feedback from <span class="hl">users with visual impairments</span>.</li>
|
||||
<li><strong>A faster status board</strong>: the dashboard opens from your last snapshot while fresh data loads, process rows appear sooner, and power and temperature readings line up better.</li>
|
||||
<li><strong>Deeper software and uninstall</strong>: Electron apps update inside Mole alongside Sparkle, Homebrew, and the App Store, and uninstall now connects startup items and package receipts.</li>
|
||||
<li><strong>Clean covers more, still review-first</strong>: WeChat caches, oversized live logs, Xcode artifacts, and project <code>build</code> and <code>dist</code> folders are easy to review, group, or exclude.</li>
|
||||
<li><strong>Fan control on Apple Silicon</strong>: presets now apply reliably, including M4-class Macs where the thermal controller had kept fan writes locked.</li>
|
||||
<li><strong>License and devices</strong>: device lists load faster, you can release every Mac at once, and moving a license to a new machine is handled inside the app.</li>
|
||||
</ol>
|
||||
|
||||
<h2>Fixes & polish</h2>
|
||||
<ol class="changes">
|
||||
<li><strong>Menu bar</strong>: the icon is restored when Control Center drops it, and the popover stays compact on shorter screens.</li>
|
||||
<li><strong>Clean</strong>: AI tool caches such as ChatGPT, Codex, and Antigravity can be cleaned again.</li>
|
||||
<li><strong>Privacy</strong>: camera and microphone use is detected as it starts, with fewer false alerts.</li>
|
||||
<li><strong>Planets</strong>: rendered larger and more natural, with a remastered NASA Blue Marble Earth and a steadier spin.</li>
|
||||
<li><strong>Analyze and Doctor</strong>: disk analysis uses less CPU on many-core Macs, and Doctor reports carry more system state for clearer issue reports.</li>
|
||||
</ol>
|
||||
|
||||
<div class="thanks">
|
||||
<div class="thanks-label">Acknowledgements</div>
|
||||
Thanks to the Mole users with visual impairments whose feedback shaped this release's accessibility work, and to <span class="hl">@_huawuque</span> and <span class="hl">@liujiayi1111</span> for surfacing real-world issues before the stable build.
|
||||
</div>
|
||||
|
||||
<div class="changelog-footer">
|
||||
<span>Mole (鼴): a quiet keeper for your Mac.</span>
|
||||
<span>mole.fit</span>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
After Width: | Height: | Size: 276 KiB |
@@ -0,0 +1,795 @@
|
||||
<!DOCTYPE html>
|
||||
<!-- ==================================================================
|
||||
PORTFOLIO TEMPLATE · kami design system
|
||||
Kaku ターミナルエミュレータ ポートフォリオ · 日本語版
|
||||
7 ページ: 表紙 + About + 主要機能 + AI アシスタント + 性能比較 + 導入ガイド
|
||||
================================================================== -->
|
||||
<html lang="ja">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Kaku · ターミナルエミュレータ</title>
|
||||
<style>
|
||||
@font-face {
|
||||
font-family: "TsangerJinKai02";
|
||||
src: url("../fonts/TsangerJinKai02-W04.ttf") format("truetype"),
|
||||
url("https://cdn.jsdelivr.net/gh/tw93/Kami@main/assets/fonts/TsangerJinKai02-W04.ttf") format("truetype");
|
||||
font-weight: 400;
|
||||
font-style: normal;
|
||||
}
|
||||
@font-face {
|
||||
font-family: "TsangerJinKai02";
|
||||
src: url("../fonts/TsangerJinKai02-W05.ttf") format("truetype"),
|
||||
url("https://cdn.jsdelivr.net/gh/tw93/Kami@main/assets/fonts/TsangerJinKai02-W05.ttf") format("truetype");
|
||||
font-weight: 500;
|
||||
font-style: normal;
|
||||
}
|
||||
@page {
|
||||
size: A4;
|
||||
margin: 12mm 15mm 12mm 15mm;
|
||||
background: #f5f4ed;
|
||||
}
|
||||
|
||||
@page:first {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
|
||||
:root {
|
||||
--parchment: #f5f4ed;
|
||||
--ivory: #faf9f5;
|
||||
--near-black:#141413;
|
||||
--dark-warm: #3d3d3a;
|
||||
--charcoal: #4d4c48;
|
||||
--olive: #504e49;
|
||||
--stone: #6b6a64;
|
||||
--brand: #1B365D;
|
||||
--border: #e8e6dc;
|
||||
--border-soft:#e5e3d8;
|
||||
--tag-bg: #E4ECF5;
|
||||
--jp-serif: "YuMincho", "Yu Mincho", "Hiragino Mincho ProN", "Noto Serif CJK JP", "Source Han Serif JP", "TsangerJinKai02", Georgia, serif;
|
||||
}
|
||||
|
||||
html, body { background: var(--parchment); }
|
||||
|
||||
body {
|
||||
color: var(--near-black);
|
||||
font-family: var(--jp-serif);
|
||||
font-size: 10pt;
|
||||
line-height: 1.6;
|
||||
letter-spacing: 0.3pt;
|
||||
}
|
||||
|
||||
.serif {
|
||||
font-family: var(--jp-serif);
|
||||
}
|
||||
|
||||
code {
|
||||
font-family: var(--jp-serif);
|
||||
font-size: 9pt;
|
||||
background: var(--tag-bg);
|
||||
color: var(--brand);
|
||||
padding: 1pt 4pt;
|
||||
border-radius: 2pt;
|
||||
}
|
||||
|
||||
/* ════════════ COVER ════════════ */
|
||||
.cover {
|
||||
width: 100%;
|
||||
height: 297mm;
|
||||
padding: 25mm 20mm;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
break-after: page;
|
||||
}
|
||||
.cover-top {
|
||||
font-family: var(--jp-serif);
|
||||
font-size: 10pt;
|
||||
color: var(--brand);
|
||||
letter-spacing: 1pt;
|
||||
}
|
||||
.cover-center {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 14pt;
|
||||
}
|
||||
.cover-name {
|
||||
font-family: var(--jp-serif);
|
||||
font-size: 64pt;
|
||||
font-weight: 500;
|
||||
line-height: 1.05;
|
||||
color: var(--near-black);
|
||||
letter-spacing: 1pt;
|
||||
}
|
||||
.cover-tagline {
|
||||
font-size: 16pt;
|
||||
color: var(--olive);
|
||||
line-height: 1.5;
|
||||
max-width: 80%;
|
||||
}
|
||||
.cover-line {
|
||||
width: 80pt;
|
||||
height: 2pt;
|
||||
background: var(--brand);
|
||||
margin: 18pt 0 4pt 0;
|
||||
}
|
||||
.cover-meta {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-end;
|
||||
font-size: 10pt;
|
||||
color: var(--stone);
|
||||
}
|
||||
.cover-meta strong {
|
||||
color: var(--dark-warm);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* ════════════ SECTION ════════════ */
|
||||
section {
|
||||
break-after: page;
|
||||
padding: 10mm 0;
|
||||
}
|
||||
.section-eyebrow {
|
||||
font-size: 9pt;
|
||||
color: var(--brand);
|
||||
letter-spacing: 1pt;
|
||||
margin-bottom: 8pt;
|
||||
}
|
||||
.section-title {
|
||||
font-family: var(--jp-serif);
|
||||
font-size: 28pt;
|
||||
font-weight: 500;
|
||||
line-height: 1.2;
|
||||
margin-bottom: 20pt;
|
||||
}
|
||||
.section-lead {
|
||||
font-size: 12pt;
|
||||
line-height: 1.55;
|
||||
color: var(--dark-warm);
|
||||
margin-bottom: 24pt;
|
||||
}
|
||||
|
||||
/* ════════════ ABOUT ════════════ */
|
||||
.about-body {
|
||||
line-height: 1.5;
|
||||
font-size: 10pt;
|
||||
}
|
||||
.about-body p {
|
||||
margin-bottom: 6pt;
|
||||
color: var(--dark-warm);
|
||||
}
|
||||
.about-body p:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
/* ════════════ FEATURE CARDS ════════════ */
|
||||
.feature-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 14pt;
|
||||
margin-bottom: 20pt;
|
||||
}
|
||||
.feature-card {
|
||||
background: var(--ivory);
|
||||
border: 0.5pt solid var(--border);
|
||||
border-radius: 8pt;
|
||||
padding: 14pt 16pt;
|
||||
break-inside: avoid;
|
||||
}
|
||||
.feature-card h3 {
|
||||
font-family: var(--jp-serif);
|
||||
font-size: 13pt;
|
||||
font-weight: 500;
|
||||
color: var(--near-black);
|
||||
margin-bottom: 6pt;
|
||||
}
|
||||
.feature-card p {
|
||||
font-size: 9.5pt;
|
||||
line-height: 1.5;
|
||||
color: var(--charcoal);
|
||||
}
|
||||
|
||||
/* ════════════ AI SECTION ════════════ */
|
||||
.ai-modes {
|
||||
margin-bottom: 20pt;
|
||||
}
|
||||
.ai-mode {
|
||||
background: var(--ivory);
|
||||
border-left: 3pt solid var(--brand);
|
||||
border-radius: 2pt;
|
||||
padding: 12pt 14pt;
|
||||
margin-bottom: 12pt;
|
||||
break-inside: avoid;
|
||||
}
|
||||
.ai-mode h3 {
|
||||
font-family: var(--jp-serif);
|
||||
font-size: 13pt;
|
||||
font-weight: 500;
|
||||
color: var(--near-black);
|
||||
margin-bottom: 6pt;
|
||||
}
|
||||
.ai-mode p {
|
||||
font-size: 9.5pt;
|
||||
line-height: 1.5;
|
||||
color: var(--charcoal);
|
||||
margin-bottom: 6pt;
|
||||
}
|
||||
.ai-mode code {
|
||||
font-family: var(--jp-serif);
|
||||
font-size: 9pt;
|
||||
background: var(--tag-bg);
|
||||
color: var(--brand);
|
||||
padding: 1pt 4pt;
|
||||
border-radius: 2pt;
|
||||
}
|
||||
|
||||
/* ════════════ PERFORMANCE TABLE ════════════ */
|
||||
.perf-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin-bottom: 20pt;
|
||||
}
|
||||
.perf-table th,
|
||||
.perf-table td {
|
||||
text-align: left;
|
||||
padding: 10pt 12pt;
|
||||
border-bottom: 0.5pt solid var(--border-soft);
|
||||
font-size: 9.5pt;
|
||||
}
|
||||
.perf-table th {
|
||||
font-weight: 500;
|
||||
color: var(--stone);
|
||||
font-size: 9pt;
|
||||
}
|
||||
.perf-table td:first-child {
|
||||
font-weight: 500;
|
||||
color: var(--near-black);
|
||||
}
|
||||
.perf-table td:nth-child(2) {
|
||||
color: var(--olive);
|
||||
}
|
||||
.perf-table td:nth-child(3) {
|
||||
color: var(--brand);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* ════════════ INSTALL SECTION ════════════ */
|
||||
.install-steps {
|
||||
margin-bottom: 10pt;
|
||||
}
|
||||
.install-step {
|
||||
display: flex;
|
||||
gap: 12pt;
|
||||
margin-bottom: 10pt;
|
||||
break-inside: avoid;
|
||||
}
|
||||
.install-num {
|
||||
font-family: var(--jp-serif);
|
||||
font-size: 24pt;
|
||||
font-weight: 500;
|
||||
color: var(--brand);
|
||||
line-height: 1;
|
||||
min-width: 32pt;
|
||||
}
|
||||
.install-content h3 {
|
||||
font-family: var(--jp-serif);
|
||||
font-size: 12pt;
|
||||
font-weight: 500;
|
||||
color: var(--near-black);
|
||||
margin-bottom: 4pt;
|
||||
}
|
||||
.install-content p {
|
||||
font-size: 9.5pt;
|
||||
line-height: 1.5;
|
||||
color: var(--charcoal);
|
||||
}
|
||||
.install-content code {
|
||||
font-family: var(--jp-serif);
|
||||
font-size: 9pt;
|
||||
background: var(--ivory);
|
||||
border: 0.5pt solid var(--border);
|
||||
padding: 8pt 10pt;
|
||||
border-radius: 4pt;
|
||||
display: block;
|
||||
margin-top: 6pt;
|
||||
color: var(--near-black);
|
||||
}
|
||||
|
||||
/* ════════════ SHORTCUTS ════════════ */
|
||||
.shortcuts {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 5pt 20pt;
|
||||
margin-bottom: 12pt;
|
||||
}
|
||||
.shortcut-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: 4pt 0;
|
||||
border-bottom: 0.5pt solid var(--border-soft);
|
||||
font-size: 9.5pt;
|
||||
}
|
||||
.shortcut-item span:first-child {
|
||||
color: var(--charcoal);
|
||||
}
|
||||
.shortcut-item span:last-child {
|
||||
font-family: var(--jp-serif);
|
||||
font-size: 9pt;
|
||||
color: var(--brand);
|
||||
}
|
||||
|
||||
/* ════════════ CONTACT ════════════ */
|
||||
.contact {
|
||||
padding: 20mm 0 10mm 0;
|
||||
}
|
||||
.contact-title {
|
||||
font-family: var(--jp-serif);
|
||||
font-size: 32pt;
|
||||
font-weight: 500;
|
||||
color: var(--near-black);
|
||||
margin-bottom: 12pt;
|
||||
}
|
||||
.contact-line {
|
||||
width: 60pt;
|
||||
height: 2pt;
|
||||
background: var(--brand);
|
||||
margin-bottom: 20pt;
|
||||
}
|
||||
.contact-list {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 12pt;
|
||||
font-size: 10pt;
|
||||
}
|
||||
.contact-list div {
|
||||
display: flex;
|
||||
gap: 8pt;
|
||||
}
|
||||
.contact-list strong {
|
||||
color: var(--stone);
|
||||
font-weight: 500;
|
||||
min-width: 60pt;
|
||||
}
|
||||
.contact-list a {
|
||||
color: var(--brand);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
/* ════════════ ABOUT METRICS ════════════ */
|
||||
.about-metrics {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
gap: 10pt;
|
||||
margin-bottom: 14pt;
|
||||
}
|
||||
.about-metric {
|
||||
background: var(--ivory);
|
||||
border: 0.5pt solid var(--border);
|
||||
border-radius: 8pt;
|
||||
padding: 10pt 14pt;
|
||||
text-align: center;
|
||||
}
|
||||
.about-metric-value {
|
||||
font-family: var(--jp-serif);
|
||||
font-size: 18pt;
|
||||
font-weight: 500;
|
||||
color: var(--brand);
|
||||
line-height: 1.2;
|
||||
}
|
||||
.about-metric-label {
|
||||
font-size: 9pt;
|
||||
color: var(--stone);
|
||||
margin-top: 3pt;
|
||||
}
|
||||
|
||||
/* ════════════ TOOL CARDS ════════════ */
|
||||
.tools-row {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 12pt;
|
||||
margin-top: 20pt;
|
||||
}
|
||||
.tool-card {
|
||||
background: var(--ivory);
|
||||
border: 0.5pt solid var(--border);
|
||||
border-radius: 8pt;
|
||||
padding: 13pt 15pt;
|
||||
break-inside: avoid;
|
||||
}
|
||||
.tool-card h4 {
|
||||
font-family: var(--jp-serif);
|
||||
font-size: 11pt;
|
||||
font-weight: 500;
|
||||
color: var(--near-black);
|
||||
margin-bottom: 5pt;
|
||||
}
|
||||
.tool-card p { font-size: 9pt; line-height: 1.45; color: var(--charcoal); margin-bottom: 7pt; }
|
||||
.tool-shortcut {
|
||||
display: inline-block;
|
||||
font-family: var(--jp-serif);
|
||||
font-size: 9pt;
|
||||
color: var(--brand);
|
||||
background: var(--tag-bg);
|
||||
padding: 1pt 5pt;
|
||||
border-radius: 2pt;
|
||||
}
|
||||
|
||||
/* ════════════ CONFIG TABLE ════════════ */
|
||||
.config-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin-bottom: 16pt;
|
||||
font-size: 9pt;
|
||||
}
|
||||
.config-table th, .config-table td {
|
||||
text-align: left;
|
||||
padding: 7pt 10pt;
|
||||
border-bottom: 0.5pt solid var(--border-soft);
|
||||
}
|
||||
.config-table th {
|
||||
font-weight: 500;
|
||||
color: var(--stone);
|
||||
font-size: 9pt;
|
||||
}
|
||||
.config-table td:first-child {
|
||||
font-family: var(--jp-serif);
|
||||
font-size: 9pt;
|
||||
color: var(--brand);
|
||||
white-space: nowrap;
|
||||
}
|
||||
.config-table td { color: var(--charcoal); }
|
||||
|
||||
/* ════════════ OPT GRID ════════════ */
|
||||
.opt-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 12pt;
|
||||
margin-bottom: 16pt;
|
||||
}
|
||||
.opt-card {
|
||||
background: var(--ivory);
|
||||
border: 0.5pt solid var(--border);
|
||||
border-radius: 8pt;
|
||||
padding: 13pt 15pt;
|
||||
break-inside: avoid;
|
||||
}
|
||||
.opt-card h4 {
|
||||
font-family: var(--jp-serif);
|
||||
font-size: 11pt;
|
||||
font-weight: 500;
|
||||
color: var(--near-black);
|
||||
margin-bottom: 5pt;
|
||||
}
|
||||
.opt-card p { font-size: 9pt; line-height: 1.45; color: var(--charcoal); }
|
||||
|
||||
.contact-screenshot {
|
||||
margin-top: 16pt;
|
||||
border-radius: 8pt;
|
||||
overflow: hidden;
|
||||
border: 0.5pt solid var(--border);
|
||||
}
|
||||
.contact-screenshot img {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
/* ════════════ CLI TABLE ════════════ */
|
||||
.cli-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin-top: 6pt;
|
||||
font-size: 9pt;
|
||||
}
|
||||
.cli-table th, .cli-table td {
|
||||
text-align: left;
|
||||
padding: 5pt 10pt;
|
||||
border-bottom: 0.5pt solid var(--border-soft);
|
||||
}
|
||||
.cli-table th { font-weight: 500; color: var(--stone); font-size: 9pt; }
|
||||
.cli-table td:first-child {
|
||||
font-family: var(--jp-serif);
|
||||
font-size: 9pt;
|
||||
color: var(--brand);
|
||||
white-space: nowrap;
|
||||
width: 120pt;
|
||||
}
|
||||
.cli-table td { color: var(--charcoal); }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<!-- ════════════ COVER ════════════ -->
|
||||
<div class="cover">
|
||||
<div class="cover-top">TERMINAL EMULATOR · 2025</div>
|
||||
<div class="cover-center">
|
||||
<div class="cover-name">Kaku</div>
|
||||
<div class="cover-tagline">AI コーディング向けに設計した高速ターミナルエミュレータ。初期設定のまま使え、Lua で深く拡張できる、軽量で高性能な実装です。</div>
|
||||
<div class="cover-line"></div>
|
||||
</div>
|
||||
|
||||
<div class="cover-meta">
|
||||
<div><strong>カテゴリ</strong> ターミナルエミュレータ</div>
|
||||
<div><strong>技術</strong> Rust · WezTerm · Lua</div>
|
||||
<div><strong>状態</strong> OSS · 4800+ Stars</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ════════════ ABOUT ════════════ -->
|
||||
<section>
|
||||
<div class="section-eyebrow">01 · ABOUT</div>
|
||||
<div class="section-title">Kaku とは</div>
|
||||
<div class="section-lead">Kaku(書く)は、思考をかたちにする行為を指す言葉です。WezTerm をベースに深く最適化し、実用的なデフォルトと完全な Lua カスタマイズ性、軽快な操作感を両立したターミナルです。</div>
|
||||
|
||||
<div class="about-metrics">
|
||||
<div class="about-metric">
|
||||
<div class="about-metric-value">4800+</div>
|
||||
<div class="about-metric-label">GitHub Stars</div>
|
||||
</div>
|
||||
<div class="about-metric">
|
||||
<div class="about-metric-value">Rust</div>
|
||||
<div class="about-metric-label">主要言語</div>
|
||||
</div>
|
||||
<div class="about-metric">
|
||||
<div class="about-metric-value">40 MB</div>
|
||||
<div class="about-metric-label">実行ファイル</div>
|
||||
</div>
|
||||
<div class="about-metric">
|
||||
<div class="about-metric-value">macOS</div>
|
||||
<div class="about-metric-label">重点プラットフォーム</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="about-body">
|
||||
<p>Kaku は三部作の一角です。Kaku(書く)がコードを書き、Waza(技)が習慣を鍛え、Kami(紙)が文書を仕上げます。私は長く CLI 中心で仕事をしてきました。Alacritty を長年使ったあと、AI 補助コーディングへ比重が移るにつれ、より強いタブ管理と内蔵 AI 連携が必要になりました。Kitty、Ghostty、Warp、iTerm2 も試しましたが、性能優先、デフォルトで使いやすい、Lua で全面的に拡張できる、というバランスに最も近かったのが Kaku です。</p>
|
||||
<p>WezTerm は強力なエンジンです。Kaku ではその上で 2 点に集中しました。不要な重量の削減と、AI ワークフローの直接統合です。設定は WezTerm Lua API と互換で、既存設定を移行せず再利用できます。Starship、Delta、Lazygit、Yazi も最初から連携済みです。macOS では、Traffic Light のタブバー統合、システム外観への追従、ネイティブフォント描画、Apple notarization まで含めて、導入直後から実用状態になります。</p>
|
||||
</div>
|
||||
<div class="contact-screenshot">
|
||||
<img src="images/kaku-hero.jpg" alt="Kaku terminal interface">
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ════════════ FEATURES ════════════ -->
|
||||
<section>
|
||||
<div class="section-eyebrow">02 · FEATURES</div>
|
||||
<div class="section-title">主要機能</div>
|
||||
<div class="section-lead">Kaku は複雑な初期設定なしで使い始められます。同時に WezTerm Lua API 互換を維持し、深いカスタマイズにも対応します。</div>
|
||||
|
||||
<div class="feature-grid">
|
||||
<div class="feature-card">
|
||||
<h3>ゼロ設定で起動</h3>
|
||||
<p>標準フォントは JetBrains Mono。macOS の描画特性に合わせて調整済みです。ディスプレイ解像度を自動判定し、低解像度は 15px、高解像度は 17px、行高 1.28 を自動適用します。</p>
|
||||
</div>
|
||||
|
||||
<div class="feature-card">
|
||||
<h3>テーマ自動切替</h3>
|
||||
<p>macOS の外観設定に連動して Kaku Dark / Kaku Light を自動切替します。選択色、字重、色上書きテーブルも同時更新され、<code>color_scheme</code> で固定運用もできます。</p>
|
||||
</div>
|
||||
|
||||
<div class="feature-card">
|
||||
<h3>厳選シェルツール</h3>
|
||||
<p>zsh プラグインを自動読み込みします。<code>z</code> ディレクトリ移動、<code>zsh-completions</code>、構文ハイライト、補完候補、Smart Tab に対応し、fish 連携も可能です。</p>
|
||||
</div>
|
||||
|
||||
<div class="feature-card">
|
||||
<h3>高速で軽量</h3>
|
||||
<p>シンボル削減と機能整理でバイナリを 40% 圧縮しました(67MB -> 40MB)。リソースは 100MB から 80MB へ。JIT 初期化で Shell 初期化時間も半減します(200ms -> 100ms)。</p>
|
||||
</div>
|
||||
|
||||
<div class="feature-card">
|
||||
<h3>WezTerm 互換設定</h3>
|
||||
<p>WezTerm の Lua 設定をそのまま利用でき、API 互換を保ちます。移行作業は不要です。<code>kaku.lua</code> は defaults を先に読み込み、その後 overrides を適用します。</p>
|
||||
</div>
|
||||
|
||||
<div class="feature-card">
|
||||
<h3>洗練されたデフォルト</h3>
|
||||
<p>選択コピー、クリック可能なパス、履歴表示、ペイン入力ブロードキャスト、バックグラウンド完了表示、Traffic Light のタブバー統合を標準搭載します。</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="tools-row">
|
||||
<div class="tool-card">
|
||||
<h4>Lazygit 連携</h4>
|
||||
<p>PATH または Homebrew のパスから lazygit バイナリを自動検出します。未コミット変更がある場合は 1 回だけ通知します。</p>
|
||||
<span class="tool-shortcut">Cmd + Shift + G</span>
|
||||
</div>
|
||||
<div class="tool-card">
|
||||
<h4>Yazi 連携</h4>
|
||||
<p>Shell ラッパー <code>y</code> で起動し、終了時に作業ディレクトリを同期します。配色は <code>yazi/theme.toml</code> に自動反映されます。</p>
|
||||
<span class="tool-shortcut">Cmd + Shift + Y</span>
|
||||
</div>
|
||||
<div class="tool-card">
|
||||
<h4>Remote Files(SSHFS)</h4>
|
||||
<p>アクティブな SSH ペインから接続先を自動判定し、sshfs でリモートファイルシステムをマウントして Yazi で閲覧できます。</p>
|
||||
<span class="tool-shortcut">Cmd + Shift + R</span>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ════════════ AI ASSISTANT ════════════ -->
|
||||
<section>
|
||||
<div class="section-eyebrow">03 · AI ASSISTANT</div>
|
||||
<div class="section-title">Kaku AI アシスタント</div>
|
||||
<div class="section-lead">Kaku には AI アシスタントを内蔵しています。2 つのモードで、ターミナル作業の主要な AI ユースケースをカバーします。<code style="font-size: 9pt;background:var(--tag-bg);color:var(--brand);padding:1pt 4pt;border-radius:2pt;">kaku ai</code> で設定パネルを開き、外部コーディングツールと Kaku Assistant を設定できます。</div>
|
||||
|
||||
<div class="ai-modes">
|
||||
<div class="ai-mode">
|
||||
<h3>エラー自動リカバリ</h3>
|
||||
<p>コマンド失敗時に、失敗コマンド + 終了コード + 作業ディレクトリ + git ブランチを LLM へ送信し、修正案をターミナル内に表示します。<code>Cmd + Shift + E</code> でそのまま適用できます。</p>
|
||||
<p>未発火条件: <code>Ctrl+C</code> 中断、help フラグ、素のパッケージマネージャー実行、git pull 競合、非 shell 前景プロセス。危険コマンド(<code>rm -rf</code>、<code>git reset --hard</code>)は貼り付け可能ですが自動実行しません。</p>
|
||||
</div>
|
||||
|
||||
<div class="ai-mode">
|
||||
<h3>自然言語からコマンド</h3>
|
||||
<p>プロンプトで <code># <説明></code> を入力して Enter すると、Kaku が shell に渡す前に行を取得し、現在のディレクトリと git ブランチを添えて LLM に送信します。生成されたコマンドはプロンプトへ戻り、確認してから実行できます。</p>
|
||||
<p style="font-size: 9pt;color:var(--brand);line-height:1.7;margin-top:4pt;">
|
||||
# list all files modified in the last 7 days<br>
|
||||
# find and kill the process on port 3000<br>
|
||||
# compress the src folder excluding node_modules
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="display:grid;grid-template-columns:1fr 1fr;gap:16pt;margin-top:4pt;">
|
||||
<div>
|
||||
<div style="font-family:var(--jp-serif);font-size:11pt;font-weight:500;color:var(--near-black);margin-bottom:8pt;">assistant.toml 設定項目</div>
|
||||
<table class="config-table">
|
||||
<tr><th>項目</th><th>説明</th></tr>
|
||||
<tr><td>enabled</td><td>true で有効 / false で無効</td></tr>
|
||||
<tr><td>api_key</td><td>プロバイダー API Key</td></tr>
|
||||
<tr><td>model</td><td>モデル識別子 (例: DeepSeek-V3.2)</td></tr>
|
||||
<tr><td>base_url</td><td>OpenAI 互換 API のベース URL</td></tr>
|
||||
<tr><td>custom_headers</td><td>プロキシ追加 HTTP ヘッダー</td></tr>
|
||||
</table>
|
||||
</div>
|
||||
<div>
|
||||
<div style="font-family:var(--jp-serif);font-size:11pt;font-weight:500;color:var(--near-black);margin-bottom:8pt;">対応 AI コーディングツール</div>
|
||||
<table class="config-table">
|
||||
<tr><th>ツール</th><th>説明</th></tr>
|
||||
<tr><td>claude</td><td>Claude Code · Anthropic</td></tr>
|
||||
<tr><td>codex</td><td>OpenAI Codex CLI</td></tr>
|
||||
<tr><td>gemini</td><td>Gemini CLI · Google</td></tr>
|
||||
<tr><td>copilot</td><td>GitHub Copilot CLI</td></tr>
|
||||
<tr><td>kimi</td><td>Kimi Code · Moonshot</td></tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ════════════ PERFORMANCE ════════════ -->
|
||||
<section>
|
||||
<div class="section-eyebrow">04 · PERFORMANCE</div>
|
||||
<div class="section-title">性能比較</div>
|
||||
<div class="section-lead">シンボル削減、機能整理、リソース最適化により、Kaku は機能を維持したままサイズと起動時間を大幅に改善しました。macOS 専用設計でプラットフォーム統合も深めています。</div>
|
||||
|
||||
<table class="perf-table">
|
||||
<tr>
|
||||
<th>指標</th>
|
||||
<th>上流 WezTerm</th>
|
||||
<th>Kaku</th>
|
||||
<th>最適化手法</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>実行ファイルサイズ</td>
|
||||
<td>~67 MB</td>
|
||||
<td>~40 MB</td>
|
||||
<td>シンボル削減 + 機能整理</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>リソース容量</td>
|
||||
<td>~100 MB</td>
|
||||
<td>~80 MB</td>
|
||||
<td>リソース最適化 + 遅延ロード</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>起動遅延</td>
|
||||
<td>標準</td>
|
||||
<td>即時</td>
|
||||
<td>JIT 初期化</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Shell 初期化時間</td>
|
||||
<td>~200ms</td>
|
||||
<td>~100ms</td>
|
||||
<td>環境設定フロー最適化</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<div class="opt-grid">
|
||||
<div class="opt-card">
|
||||
<h4>バイナリ最適化</h4>
|
||||
<p>コンパイル時に未使用の feature flags を削除し、symbol stripping とデバッグ情報削減を実施。Rust release profile は LTO + codegen-units=1 で最適化を最大化します。</p>
|
||||
</div>
|
||||
<div class="opt-card">
|
||||
<h4>遅延ロード戦略</h4>
|
||||
<p>Shell ツール群(Starship、Delta、Yazi テーマ同期)は just-in-time 初期化を採用。最初の Kaku セッションでのみ起動し、システム全体の shell 起動時間を悪化させません。</p>
|
||||
</div>
|
||||
<div class="opt-card">
|
||||
<h4>macOS 統合</h4>
|
||||
<p>Traffic Light のタブバー統合(INTEGRATED_BUTTONS|RESIZE)、システム外観連動、Apple notarization(警告なし)、macOS ネイティブフォント描画スタックを提供します。</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ════════════ INSTALLATION ════════════ -->
|
||||
<section>
|
||||
<div class="section-eyebrow">05 · INSTALLATION</div>
|
||||
<div class="section-title">インストールと使い方</div>
|
||||
<div class="section-lead">Kaku はシンプルな導入手順と直感的なショートカット体系を提供します。初回起動時に shell 環境も自動設定されます。</div>
|
||||
|
||||
<div class="install-steps">
|
||||
<div class="install-step">
|
||||
<div class="install-num">1</div>
|
||||
<div class="install-content">
|
||||
<h3>インストール</h3>
|
||||
<p>GitHub Releases から最新の DMG を取得してアプリケーションフォルダへ移動するか、Homebrew で導入します。</p>
|
||||
<code>brew install tw93/tap/kaku</code>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="install-step">
|
||||
<div class="install-num">2</div>
|
||||
<div class="install-content">
|
||||
<h3>初回起動</h3>
|
||||
<p>Kaku は Apple notarization 済みです。セキュリティ警告なしで起動でき、初回起動時に shell 環境を自動設定します。</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="install-step">
|
||||
<div class="install-num">3</div>
|
||||
<div class="install-content">
|
||||
<h3>設定カスタマイズ</h3>
|
||||
<p><span style="font-size: 9pt;color:var(--brand);">Cmd + ,</span> で設定パネルを開くか、<span style="font-size: 9pt;color:var(--brand);">kaku config</span> で Lua 設定ファイルを編集します。</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="section-lead" style="margin-bottom: 10pt;">主要ショートカット:</div>
|
||||
|
||||
<div class="shortcuts">
|
||||
<div class="shortcut-item"><span>新規タブ</span><span>Cmd + T</span></div>
|
||||
<div class="shortcut-item"><span>新規ウィンドウ</span><span>Cmd + N</span></div>
|
||||
<div class="shortcut-item"><span>タブ/ペインを閉じる</span><span>Cmd + W</span></div>
|
||||
<div class="shortcut-item"><span>タブ切替</span><span>Cmd + Shift + [ / ]</span></div>
|
||||
<div class="shortcut-item"><span>ペイン縦分割</span><span>Cmd + D</span></div>
|
||||
<div class="shortcut-item"><span>ペイン横分割</span><span>Cmd + Shift + D</span></div>
|
||||
<div class="shortcut-item"><span>ペイン移動</span><span>Cmd + Opt + 矢印</span></div>
|
||||
<div class="shortcut-item"><span>Lazygit を開く</span><span>Cmd + Shift + G</span></div>
|
||||
<div class="shortcut-item"><span>Yazi を開く</span><span>Cmd + Shift + Y</span></div>
|
||||
<div class="shortcut-item"><span>AI パネル</span><span>Cmd + Shift + A</span></div>
|
||||
<div class="shortcut-item"><span>AI 提案を適用</span><span>Cmd + Shift + E</span></div>
|
||||
<div class="shortcut-item"><span>画面クリア</span><span>Cmd + K</span></div>
|
||||
</div>
|
||||
|
||||
<div style="font-family:var(--jp-serif);font-size:11pt;font-weight:500;color:var(--near-black);margin:10pt 0 6pt;">CLI コマンド一覧</div>
|
||||
<table class="cli-table">
|
||||
<tr><th>コマンド</th><th>説明</th></tr>
|
||||
<tr><td>kaku ai</td><td>AI 設定パネルを開き、Kaku Assistant と外部コーディングツールを設定</td></tr>
|
||||
<tr><td>kaku config</td><td>既定エディタで <code style="font-size: 9pt;color:var(--brand);">~/.config/kaku/kaku.lua</code> を開く</td></tr>
|
||||
<tr><td>kaku doctor</td><td>診断を実行し、shell 連携、PATH、任意ツールの導入状態を確認</td></tr>
|
||||
<tr><td>kaku update</td><td>最新バージョンの確認と更新を実行</td></tr>
|
||||
<tr><td>kaku init</td><td>shell 連携を設定し、Starship / Delta / Lazygit / Yazi を任意で導入</td></tr>
|
||||
</table>
|
||||
</section>
|
||||
|
||||
<!-- ════════════ CONTACT ════════════ -->
|
||||
<section class="contact">
|
||||
<div class="section-eyebrow">06 · GET KAKU</div>
|
||||
<div class="contact-title">Kaku はオープンソースです</div>
|
||||
<div class="contact-line"></div>
|
||||
<div class="contact-list">
|
||||
<div><strong>GitHub</strong><a href="https://github.com/tw93/Kaku">github.com/tw93/Kaku</a></div>
|
||||
<div><strong>公式サイト</strong><a href="https://kaku.app">kaku.app</a></div>
|
||||
<div><strong>インストール</strong>brew install tw93/tap/kaku</div>
|
||||
<div><strong>Stars</strong>4800+ 増加中</div>
|
||||
<div><strong>ドキュメント</strong><a href="https://github.com/tw93/Kaku/tree/main/docs">完全ドキュメントと FAQ</a></div>
|
||||
<div><strong>作者</strong><a href="https://twitter.com/HiTw93">@HiTw93</a></div>
|
||||
</div>
|
||||
|
||||
<div class="contact-screenshot">
|
||||
<img src="images/kaku-action.jpg" alt="Kaku terminal in action">
|
||||
</div>
|
||||
</section>
|
||||
</html>
|
||||
|
After Width: | Height: | Size: 86 KiB |
@@ -0,0 +1,381 @@
|
||||
<!DOCTYPE html>
|
||||
<!-- ==================================================================
|
||||
KAMI 介绍 · 一页纸 · 白底打印版
|
||||
基于 one-pager.html,@page / body 背景改为纯白以贴合打印纸张。
|
||||
渲染:base_url 指向 assets/templates,字体相对路径 ../fonts 生效。
|
||||
目标:页数必须 == 1
|
||||
================================================================== -->
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Kami · 为 AI 时代设计的文档排版系统</title>
|
||||
<meta name="author" content="Tw93">
|
||||
<meta name="description" content="Kami 是为 AI 时代设计的文档排版系统:给大模型一段简报,得到衬线主导、墨蓝点缀、可直接打印的专业 PDF,覆盖简历到白皮书 8 类文档与 18 种图表。">
|
||||
<meta name="keywords" content="Kami, 文档排版, 设计系统, AI 文档, 一页纸, PDF">
|
||||
<meta name="generator" content="Kami">
|
||||
<style>
|
||||
/* Regular weight */
|
||||
@font-face {
|
||||
font-family: "TsangerJinKai02";
|
||||
src: url("../fonts/TsangerJinKai02-W04.ttf") format("truetype"),
|
||||
url("https://cdn.jsdelivr.net/gh/tw93/Kami@main/assets/fonts/TsangerJinKai02-W04.ttf") format("truetype");
|
||||
font-weight: 400;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
/* Bold weight - W05 for all bold variants */
|
||||
@font-face {
|
||||
font-family: "TsangerJinKai02";
|
||||
src: url("../fonts/TsangerJinKai02-W05.ttf") format("truetype"),
|
||||
url("https://cdn.jsdelivr.net/gh/tw93/Kami@main/assets/fonts/TsangerJinKai02-W05.ttf") format("truetype");
|
||||
font-weight: 500;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
/* 打印优化:白底。屏幕默认是 #f5f4ed 羊皮纸底,此版本改白底是为了
|
||||
直接打印到白纸时不在背景上铺一层墨,并让边缘干净。 */
|
||||
@page {
|
||||
size: A4;
|
||||
margin: 15mm 18mm 15mm 18mm;
|
||||
background: #ffffff;
|
||||
}
|
||||
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
|
||||
:root {
|
||||
--parchment: #f5f4ed;
|
||||
--ivory: #faf9f5;
|
||||
--near-black:#141413;
|
||||
--dark-warm: #3d3d3a;
|
||||
--olive: #504e49;
|
||||
--stone: #6b6a64;
|
||||
--brand: #1B365D;
|
||||
--border: #e8e6dc;
|
||||
--border-soft:#e5e3d8;
|
||||
--tag-bg: #E4ECF5;
|
||||
--serif: "TsangerJinKai02", "Source Han Serif SC", "Noto Serif CJK SC", "Songti SC", "STSong", Georgia, serif;
|
||||
--sans: var(--serif);
|
||||
--mono: "JetBrains Mono", "SF Mono", "Fira Code", Consolas, Monaco, "TsangerJinKai02", monospace;
|
||||
}
|
||||
|
||||
html, body { background: #ffffff; }
|
||||
|
||||
@media screen {
|
||||
body { max-width: 210mm; margin: 0 auto; padding: 15mm 18mm; }
|
||||
}
|
||||
|
||||
body {
|
||||
color: var(--near-black);
|
||||
font-family: var(--serif);
|
||||
font-size: 10pt;
|
||||
line-height: 1.45;
|
||||
letter-spacing: 0.3pt;
|
||||
}
|
||||
|
||||
.serif { font-family: var(--serif); }
|
||||
|
||||
/* ========== HEADER ========== */
|
||||
.header {
|
||||
margin-bottom: 16pt;
|
||||
padding-bottom: 12pt;
|
||||
border-bottom: 0.5pt solid var(--border);
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
justify-content: space-between;
|
||||
gap: 20pt;
|
||||
}
|
||||
.title-block { flex: 1; }
|
||||
.eyebrow {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 7pt;
|
||||
font-size: 9pt;
|
||||
color: var(--brand);
|
||||
letter-spacing: 1.5pt;
|
||||
text-transform: uppercase;
|
||||
margin-bottom: 7pt;
|
||||
font-weight: 500;
|
||||
}
|
||||
.eyebrow::before {
|
||||
content: "";
|
||||
width: 8pt;
|
||||
height: 1.5pt;
|
||||
border-radius: 0.75pt;
|
||||
background: var(--brand);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
h1 {
|
||||
font-family: var(--serif);
|
||||
font-size: 25pt;
|
||||
font-weight: 500;
|
||||
color: var(--near-black);
|
||||
line-height: 1.15;
|
||||
margin-bottom: 5pt;
|
||||
}
|
||||
.subtitle {
|
||||
font-size: 11pt;
|
||||
color: var(--olive);
|
||||
line-height: 1.4;
|
||||
}
|
||||
.meta {
|
||||
font-size: 9pt;
|
||||
color: var(--stone);
|
||||
text-align: right;
|
||||
line-height: 1.5;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* ========== METRICS ========== */
|
||||
.metrics {
|
||||
display: flex;
|
||||
gap: 14pt;
|
||||
margin-bottom: 22pt;
|
||||
padding: 5pt 0 8pt 0;
|
||||
border-bottom: 0.3pt dotted var(--border);
|
||||
}
|
||||
.metric {
|
||||
flex: 1;
|
||||
background: transparent;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
gap: 5pt;
|
||||
break-inside: avoid;
|
||||
}
|
||||
.metric-value {
|
||||
font-family: var(--serif);
|
||||
font-size: 24pt;
|
||||
font-weight: 500;
|
||||
color: var(--brand);
|
||||
line-height: 1;
|
||||
font-variant-numeric: tabular-nums;
|
||||
white-space: nowrap;
|
||||
letter-spacing: -0.2pt;
|
||||
}
|
||||
.metric-label {
|
||||
font-size: 9pt;
|
||||
color: var(--olive);
|
||||
line-height: 1.3;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* ========== SECTIONS ========== */
|
||||
section { margin-bottom: 20pt; break-inside: avoid; }
|
||||
section:last-of-type { margin-bottom: 0; }
|
||||
|
||||
h2 {
|
||||
font-family: var(--serif);
|
||||
font-size: 16pt;
|
||||
font-weight: 500;
|
||||
color: var(--near-black);
|
||||
margin-bottom: 6pt;
|
||||
}
|
||||
h2 .sub {
|
||||
font-size: 9pt;
|
||||
color: var(--stone);
|
||||
font-weight: 400;
|
||||
margin-left: 6pt;
|
||||
}
|
||||
|
||||
p { margin-bottom: 6pt; line-height: 1.45; }
|
||||
p:last-child { margin-bottom: 0; }
|
||||
|
||||
.lead {
|
||||
font-size: 12pt;
|
||||
line-height: 1.6;
|
||||
color: var(--dark-warm);
|
||||
margin-bottom: 20pt;
|
||||
letter-spacing: 0.04em;
|
||||
}
|
||||
|
||||
.hl { color: var(--brand); font-weight: 500; }
|
||||
|
||||
/* ========== 2-column grid ========== */
|
||||
.two-col {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 24pt;
|
||||
}
|
||||
|
||||
/* ========== dash list ========== */
|
||||
ul.dash { margin: 0; padding-left: 16pt; }
|
||||
ul.dash li { margin-bottom: 6pt; line-height: 1.45; }
|
||||
ul.dash li:last-child { margin-bottom: 0; }
|
||||
ul.dash li::marker { color: var(--brand); }
|
||||
|
||||
/* ========== Timeline ========== */
|
||||
.timeline { display: flex; gap: 12pt; margin-top: 6pt; }
|
||||
.tl-step { flex: 1; break-inside: avoid; }
|
||||
.tl-year {
|
||||
font-family: var(--serif);
|
||||
font-size: 11pt;
|
||||
font-weight: 500;
|
||||
color: var(--brand);
|
||||
margin-bottom: 2pt;
|
||||
}
|
||||
.tl-head {
|
||||
font-size: 10pt;
|
||||
font-weight: 500;
|
||||
color: var(--near-black);
|
||||
margin-bottom: 2pt;
|
||||
}
|
||||
.tl-body { font-size: 9pt; color: var(--olive); line-height: 1.4; }
|
||||
|
||||
/* ========== Install command block ========== */
|
||||
.cmd {
|
||||
display: block;
|
||||
background: var(--ivory);
|
||||
border: 0.5pt solid var(--border);
|
||||
border-radius: 5pt;
|
||||
padding: 9pt 12pt;
|
||||
font-family: var(--mono);
|
||||
font-size: 9pt;
|
||||
color: var(--dark-warm);
|
||||
line-height: 1.65;
|
||||
letter-spacing: 0;
|
||||
white-space: pre;
|
||||
margin-top: 4pt;
|
||||
}
|
||||
.cmd .p { color: var(--brand); }
|
||||
.cmd .c { color: var(--stone); }
|
||||
|
||||
/* ========== Closing callout ========== */
|
||||
.callout {
|
||||
background: transparent;
|
||||
border-left: 1.8pt solid var(--brand);
|
||||
padding: 4pt 0 4pt 14pt;
|
||||
margin-top: 18pt;
|
||||
font-size: 10pt;
|
||||
line-height: 1.55;
|
||||
color: var(--olive);
|
||||
break-inside: avoid;
|
||||
}
|
||||
.callout .hl { color: var(--brand); font-weight: 500; }
|
||||
|
||||
/* ========== FOOTER ========== */
|
||||
.footer {
|
||||
margin-top: 18pt;
|
||||
padding-top: 6pt;
|
||||
border-top: 0.3pt dotted var(--border);
|
||||
font-size: 9pt;
|
||||
color: var(--stone);
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
letter-spacing: 0.3pt;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<!-- ========== HEADER ========== -->
|
||||
<div class="header">
|
||||
<div class="title-block">
|
||||
<div class="eyebrow">AI 文档排版系统</div>
|
||||
<h1>Kami · 紙</h1>
|
||||
<div class="subtitle">好内容值得好纸。给大模型一段简报,得到可直接打印的专业文档。</div>
|
||||
</div>
|
||||
<div class="meta">
|
||||
Tw93<br>
|
||||
2026.06.29<br>
|
||||
v1.9.1
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ========== METRICS ========== -->
|
||||
<div class="metrics">
|
||||
<div class="metric">
|
||||
<div class="metric-value">8</div>
|
||||
<div class="metric-label">文档模板</div>
|
||||
</div>
|
||||
<div class="metric">
|
||||
<div class="metric-value">18</div>
|
||||
<div class="metric-label">图表类型</div>
|
||||
</div>
|
||||
<div class="metric">
|
||||
<div class="metric-value">5</div>
|
||||
<div class="metric-label">语言支持</div>
|
||||
</div>
|
||||
<div class="metric">
|
||||
<div class="metric-value">1</div>
|
||||
<div class="metric-label">强调色</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ========== LEAD ========== -->
|
||||
<p class="lead">
|
||||
Kami 把编辑级的排版规范沉淀成一套可复用的设计语言:<span class="hl">单一墨蓝强调</span>、衬线承担层次、暖灰中性色、克制的留白。你只描述需求,AI 负责选模板、填内容、导出 A4 PDF,<span class="hl">无需手写一行 CSS</span>。
|
||||
</p>
|
||||
|
||||
<!-- ========== 核心内容(两栏) ========== -->
|
||||
<div class="two-col">
|
||||
<section>
|
||||
<h2>一种语言,多类文档</h2>
|
||||
<p>同一套色彩 token 与排印规则,从一页纸贯通到长文白皮书,风格始终一致。</p>
|
||||
<ul class="dash">
|
||||
<li>简历、一页纸方案、正式信件</li>
|
||||
<li>作品集、白皮书、长文报告</li>
|
||||
<li>演示 slides、个股研报、更新日志</li>
|
||||
<li>内嵌 <span class="hl">18 种</span>矢量图表与流程图</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>克制的设计原则</h2>
|
||||
<p>规则比选项多,刻意收窄表达空间,换来跨文档的稳定与可读。</p>
|
||||
<ul class="dash">
|
||||
<li>墨蓝强调覆盖面积不超过 <span class="hl">5%</span></li>
|
||||
<li>衬线承担层次,字重只用 400 与 500</li>
|
||||
<li>暖灰中性色,杜绝冷调蓝灰</li>
|
||||
<li>为打印而生,分页与字体内嵌稳定</li>
|
||||
</ul>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<!-- ========== 工作流 Timeline ========== -->
|
||||
<section>
|
||||
<h2>四步交付<span class="sub">从一句话简报到可打印 PDF</span></h2>
|
||||
<div class="timeline">
|
||||
<div class="tl-step">
|
||||
<div class="tl-year">第 1 步</div>
|
||||
<div class="tl-head">描述需求</div>
|
||||
<div class="tl-body">一句话简报交给 AI,说清用途与受众。</div>
|
||||
</div>
|
||||
<div class="tl-step">
|
||||
<div class="tl-year">第 2 步</div>
|
||||
<div class="tl-head">自动选型</div>
|
||||
<div class="tl-body">按用途匹配模板,数据自动配上合适图表。</div>
|
||||
</div>
|
||||
<div class="tl-step">
|
||||
<div class="tl-year">第 3 步</div>
|
||||
<div class="tl-head">填充排版</div>
|
||||
<div class="tl-body">内容落入衬线网格,层次与节奏自动对齐。</div>
|
||||
</div>
|
||||
<div class="tl-step">
|
||||
<div class="tl-year">第 4 步</div>
|
||||
<div class="tl-head">导出交付</div>
|
||||
<div class="tl-body">输出 A4 PDF 与 HTML,可直接打印或分享。</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ========== 安装 ========== -->
|
||||
<section>
|
||||
<h2>在你的 AI 工具里启用<span class="sub">Claude Code / Codex / Claude Desktop</span></h2>
|
||||
<code class="cmd"><span class="c"># Claude Code</span>
|
||||
<span class="p">/plugin</span> marketplace add tw93/kami <span class="p">&&</span> <span class="p">/plugin</span> install kami@kami
|
||||
<span class="c"># Codex</span>
|
||||
codex plugin marketplace add tw93/kami <span class="p">&&</span> codex plugin add kami@kami</code>
|
||||
</section>
|
||||
|
||||
<!-- ========== 收尾旁注 ========== -->
|
||||
<div class="callout">
|
||||
Kami 是 <span class="hl">Kaku · Waza · Kami</span> 工具集的一员:Kaku 写代码,Waza 练习惯,Kami 交付文档。把排版交给系统,把注意力留给内容。
|
||||
</div>
|
||||
|
||||
<!-- ========== FOOTER ========== -->
|
||||
<div class="footer">
|
||||
<span>本页为白底打印版,屏幕默认采用 #f5f4ed 羊皮纸底</span>
|
||||
<span>kami.tw93.fun · github.com/tw93/kami</span>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
After Width: | Height: | Size: 360 KiB |
@@ -0,0 +1,248 @@
|
||||
<!DOCTYPE html>
|
||||
<!-- ==================================================================
|
||||
LETTER · 中文 · parchment design system
|
||||
A4 正式信件 / 推荐信
|
||||
================================================================== -->
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>推荐信 · 陈知远</title>
|
||||
<meta name="author" content="林川">
|
||||
<meta name="description" content="一封推荐工程师陈知远的正式推荐信。">
|
||||
<meta name="keywords" content="推荐信, 工程师, 正式信件">
|
||||
<meta name="generator" content="Kami">
|
||||
<style>
|
||||
@font-face {
|
||||
font-family: "TsangerJinKai02";
|
||||
src: url("../fonts/TsangerJinKai02-W04.ttf") format("truetype"),
|
||||
url("https://cdn.jsdelivr.net/gh/tw93/Kami@main/assets/fonts/TsangerJinKai02-W04.ttf") format("truetype");
|
||||
font-weight: 400;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "TsangerJinKai02";
|
||||
src: url("../fonts/TsangerJinKai02-W05.ttf") format("truetype"),
|
||||
url("https://cdn.jsdelivr.net/gh/tw93/Kami@main/assets/fonts/TsangerJinKai02-W05.ttf") format("truetype");
|
||||
font-weight: 500;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
@page {
|
||||
size: A4;
|
||||
margin: 25mm 25mm 25mm 25mm;
|
||||
background: #f5f4ed;
|
||||
}
|
||||
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
|
||||
:root {
|
||||
--parchment: #f5f4ed;
|
||||
--near-black:#141413;
|
||||
--dark-warm: #3d3d3a;
|
||||
--olive: #504e49;
|
||||
--stone: #6b6a64;
|
||||
--brand: #1B365D;
|
||||
--ivory: #faf9f5;
|
||||
--border: #e8e6dc;
|
||||
--border-soft:#e5e3d8;
|
||||
--serif: "TsangerJinKai02", "Source Han Serif SC", "Noto Serif CJK SC", "Songti SC", "STSong", Georgia, serif;
|
||||
--sans: var(--serif);
|
||||
}
|
||||
|
||||
html, body { background: var(--parchment); }
|
||||
|
||||
@media screen {
|
||||
body { max-width: 210mm; margin: 0 auto; padding: 25mm; }
|
||||
}
|
||||
|
||||
body {
|
||||
color: var(--near-black);
|
||||
font-family: var(--serif);
|
||||
font-size: 11.5pt;
|
||||
line-height: 1.55;
|
||||
letter-spacing: 0.3pt;
|
||||
}
|
||||
|
||||
.letterhead {
|
||||
text-align: right;
|
||||
margin-bottom: 30pt;
|
||||
font-size: 10pt;
|
||||
line-height: 1.55;
|
||||
color: var(--olive);
|
||||
}
|
||||
.letterhead .sender-name {
|
||||
font-size: 13pt;
|
||||
font-weight: 500;
|
||||
color: var(--near-black);
|
||||
margin-bottom: 4pt;
|
||||
}
|
||||
.letterhead a {
|
||||
color: var(--dark-warm);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.date {
|
||||
text-align: right;
|
||||
font-size: 10.5pt;
|
||||
color: var(--olive);
|
||||
margin-bottom: 24pt;
|
||||
font-variant-numeric: tabular-nums;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.recipient {
|
||||
margin-bottom: 24pt;
|
||||
line-height: 1.5;
|
||||
}
|
||||
.recipient .to {
|
||||
font-size: 10pt;
|
||||
color: var(--stone);
|
||||
letter-spacing: 0.5pt;
|
||||
text-transform: uppercase;
|
||||
margin-bottom: 3pt;
|
||||
}
|
||||
.recipient .name {
|
||||
font-size: 12pt;
|
||||
font-weight: 500;
|
||||
color: var(--near-black);
|
||||
}
|
||||
.recipient .org {
|
||||
font-size: 10.5pt;
|
||||
color: var(--olive);
|
||||
}
|
||||
|
||||
.subject {
|
||||
margin-bottom: 24pt;
|
||||
border-left: 2.5pt solid var(--brand);
|
||||
border-radius: 1.5pt;
|
||||
padding-left: 8pt;
|
||||
}
|
||||
.subject .label {
|
||||
font-family: var(--sans);
|
||||
font-size: 9pt;
|
||||
color: var(--brand);
|
||||
letter-spacing: 1pt;
|
||||
text-transform: uppercase;
|
||||
margin-bottom: 3pt;
|
||||
}
|
||||
.subject .title {
|
||||
font-size: 15pt;
|
||||
font-weight: 500;
|
||||
color: var(--near-black);
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
.salutation {
|
||||
margin-bottom: 18pt;
|
||||
font-size: 12pt;
|
||||
color: var(--near-black);
|
||||
}
|
||||
|
||||
.body-content p {
|
||||
margin-bottom: 14pt;
|
||||
text-align: justify;
|
||||
text-justify: inter-ideograph;
|
||||
line-height: 1.55;
|
||||
text-indent: 2em;
|
||||
}
|
||||
.body-content p:first-child { text-indent: 2em; }
|
||||
.body-content p.no-indent { text-indent: 0; }
|
||||
|
||||
.body-content .hl {
|
||||
color: var(--brand);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.closing {
|
||||
margin-top: 30pt;
|
||||
line-height: 1.55;
|
||||
}
|
||||
.closing .regards {
|
||||
margin-bottom: 30pt;
|
||||
color: var(--near-black);
|
||||
}
|
||||
.closing .signature {
|
||||
font-size: 14pt;
|
||||
font-weight: 500;
|
||||
color: var(--near-black);
|
||||
margin-bottom: 3pt;
|
||||
}
|
||||
.closing .signoff-meta {
|
||||
font-size: 10pt;
|
||||
color: var(--olive);
|
||||
}
|
||||
|
||||
.attachments {
|
||||
margin-top: 36pt;
|
||||
padding-top: 10pt;
|
||||
border-top: 0.5pt dotted var(--border);
|
||||
font-size: 9.5pt;
|
||||
color: var(--stone);
|
||||
font-family: var(--sans);
|
||||
line-height: 1.55;
|
||||
}
|
||||
.attachments .label {
|
||||
color: var(--brand);
|
||||
margin-right: 6pt;
|
||||
font-weight: 500;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="letterhead">
|
||||
<div class="sender-name">林川</div>
|
||||
<div>云栖科技 · 平台工程部 技术总监</div>
|
||||
<div>010-8000 1234 · <a href="mailto:linchuan@example.com">linchuan@example.com</a></div>
|
||||
</div>
|
||||
|
||||
<div class="date">2026 年 6 月 14 日</div>
|
||||
|
||||
<div class="recipient">
|
||||
<div class="to">致</div>
|
||||
<div class="name">招聘委员会</div>
|
||||
<div class="org">星核实验室 · 研发中心</div>
|
||||
</div>
|
||||
|
||||
<div class="subject">
|
||||
<div class="label">关于</div>
|
||||
<div class="title">推荐陈知远担任高级后端工程师</div>
|
||||
</div>
|
||||
|
||||
<div class="salutation">尊敬的招聘委员会:</div>
|
||||
|
||||
<div class="body-content">
|
||||
|
||||
<p>
|
||||
我谨以诚挚的态度,推荐陈知远先生加入贵实验室研发团队。过去四年他在我负责的平台工程部任后端工程师,是少有的既能把架构想清楚、又能把代码写扎实的人。
|
||||
</p>
|
||||
|
||||
<p>
|
||||
他最让我看重的,是在复杂系统中定位根因的能力。去年核心结算服务间歇性超时,多位工程师排查两周无果,是他坚持加日志、抓运行时证据,最终定位到一处连接池配置的边界问题,<span class="hl">将接口 P99 延迟从 1.8 秒降到 120 毫秒</span>。
|
||||
</p>
|
||||
|
||||
<p>
|
||||
他也是一位可靠的协作者:主导的服务拆分横跨三个团队,既能写清楚设计文档说服评审,也愿在上线当晚守到最后一台机器迁移完成。我相信他的工程判断力与责任心,会让他在更具挑战的课题中迅速成长。如需了解更多细节,欢迎随时与我联系。
|
||||
</p>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="closing">
|
||||
<div class="regards">
|
||||
此致<br>敬礼!
|
||||
</div>
|
||||
|
||||
<div class="signature">林 川</div>
|
||||
<div class="signoff-meta">
|
||||
云栖科技 · 平台工程部 技术总监<br>
|
||||
2026 年 6 月 14 日
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="attachments">
|
||||
<span class="label">附件</span>
|
||||
① 陈知远个人简历 · ② 核心项目技术总结
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
After Width: | Height: | Size: 285 KiB |
|
After Width: | Height: | Size: 149 KiB |
@@ -0,0 +1,311 @@
|
||||
<!DOCTYPE html>
|
||||
<!-- ==================================================================
|
||||
ONE-PAGER · English · parchment design system
|
||||
Product brief with a hero product shot
|
||||
================================================================== -->
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Mole · Product Brief</title>
|
||||
<meta name="author" content="Tw93">
|
||||
<meta name="description" content="Mole folds five Mac maintenance tools into one quiet, buy-once binary.">
|
||||
<meta name="keywords" content="Mole, macOS, cleanup, uninstall, disk analyzer, Mac utility">
|
||||
<meta name="generator" content="Kami">
|
||||
<style>
|
||||
@font-face { font-family: "JetBrains Mono"; src: url("../fonts/JetBrainsMono.woff2") format("woff2"); font-weight: 400; font-style: normal; }
|
||||
|
||||
@page {
|
||||
size: A4;
|
||||
margin: 15mm 18mm 15mm 18mm;
|
||||
background: #f5f4ed;
|
||||
}
|
||||
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
|
||||
:root {
|
||||
--parchment: #f5f4ed;
|
||||
--ivory: #faf9f5;
|
||||
--near-black:#141413;
|
||||
--dark-warm: #3d3d3a;
|
||||
--olive: #504e49;
|
||||
--stone: #6b6a64;
|
||||
--brand: #1B365D;
|
||||
--border: #e8e6dc;
|
||||
--border-soft:#e5e3d8;
|
||||
|
||||
--serif: Charter, Georgia,
|
||||
Palatino, "Times New Roman", serif;
|
||||
--sans: var(--serif);
|
||||
}
|
||||
|
||||
html, body { background: var(--parchment); }
|
||||
|
||||
@media screen {
|
||||
body { max-width: 210mm; margin: 0 auto; padding: 15mm 18mm; }
|
||||
}
|
||||
|
||||
body {
|
||||
color: var(--near-black);
|
||||
font-family: var(--serif);
|
||||
font-size: 10pt;
|
||||
line-height: 1.45;
|
||||
}
|
||||
|
||||
/* HEADER - quiet editorial opener: brand tick eyebrow + clean title,
|
||||
anchored by one hairline rule. No full-height left bar. */
|
||||
.header {
|
||||
margin-bottom: 14pt;
|
||||
padding-bottom: 11pt;
|
||||
border-bottom: 0.5pt solid var(--border);
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
justify-content: space-between;
|
||||
gap: 20pt;
|
||||
}
|
||||
.title-block { flex: 1; }
|
||||
.eyebrow {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 7pt;
|
||||
font-size: 9pt;
|
||||
color: var(--brand);
|
||||
letter-spacing: 1.5pt;
|
||||
text-transform: uppercase;
|
||||
margin-bottom: 7pt;
|
||||
font-weight: 500;
|
||||
}
|
||||
.eyebrow::before {
|
||||
content: "";
|
||||
width: 8pt;
|
||||
height: 1.5pt;
|
||||
border-radius: 0.75pt;
|
||||
background: var(--brand);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
h1 {
|
||||
font-family: var(--serif);
|
||||
font-size: 26pt;
|
||||
font-weight: 500;
|
||||
color: var(--near-black);
|
||||
line-height: 1.12;
|
||||
letter-spacing: -0.3pt;
|
||||
margin-bottom: 5pt;
|
||||
}
|
||||
.subtitle {
|
||||
font-size: 11.5pt;
|
||||
color: var(--olive);
|
||||
line-height: 1.4;
|
||||
}
|
||||
.meta {
|
||||
font-size: 9pt;
|
||||
color: var(--stone);
|
||||
text-align: right;
|
||||
line-height: 1.5;
|
||||
white-space: nowrap;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
.lead {
|
||||
font-size: 12pt;
|
||||
line-height: 1.5;
|
||||
color: var(--dark-warm);
|
||||
margin-bottom: 13pt;
|
||||
}
|
||||
.lead .hl { color: var(--brand); font-weight: 500; }
|
||||
|
||||
.block-intro {
|
||||
font-size: 10.5pt;
|
||||
line-height: 1.5;
|
||||
color: var(--olive);
|
||||
margin-bottom: 12pt;
|
||||
}
|
||||
.block-intro .hl { color: var(--brand); font-weight: 500; }
|
||||
|
||||
/* PRODUCT SHOT - the single visual anchor. */
|
||||
.product-shot {
|
||||
margin-bottom: 14pt;
|
||||
break-inside: avoid;
|
||||
}
|
||||
.product-shot .frame {
|
||||
height: 116mm;
|
||||
overflow: hidden;
|
||||
border-radius: 5pt;
|
||||
box-shadow: 0 1.5pt 6pt rgba(20, 20, 19, 0.16);
|
||||
}
|
||||
.product-shot .frame img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
object-position: center;
|
||||
display: block;
|
||||
}
|
||||
.product-shot figcaption {
|
||||
margin-top: 7pt;
|
||||
font-size: 8.5pt;
|
||||
color: var(--stone);
|
||||
line-height: 1.45;
|
||||
text-align: center;
|
||||
}
|
||||
.product-shot figcaption .hl { color: var(--brand); font-weight: 500; }
|
||||
|
||||
.metrics {
|
||||
display: flex;
|
||||
gap: 16pt;
|
||||
margin-bottom: 14pt;
|
||||
padding: 0 0 11pt 0;
|
||||
border-bottom: 0.3pt dotted var(--border);
|
||||
}
|
||||
.metric {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
gap: 5pt;
|
||||
break-inside: avoid;
|
||||
}
|
||||
.metric-value {
|
||||
font-family: var(--serif);
|
||||
font-size: 21pt;
|
||||
font-weight: 500;
|
||||
color: var(--brand);
|
||||
line-height: 1;
|
||||
font-variant-numeric: tabular-nums;
|
||||
white-space: nowrap;
|
||||
letter-spacing: -0.3pt;
|
||||
}
|
||||
.metric-label {
|
||||
font-size: 9pt;
|
||||
color: var(--olive);
|
||||
line-height: 1.3;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.two-col {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 18pt;
|
||||
}
|
||||
section { break-inside: avoid; }
|
||||
|
||||
h2 {
|
||||
font-family: var(--serif);
|
||||
font-size: 14pt;
|
||||
font-weight: 500;
|
||||
color: var(--near-black);
|
||||
margin-bottom: 5pt;
|
||||
}
|
||||
p { line-height: 1.45; }
|
||||
|
||||
ul.dash {
|
||||
list-style: none;
|
||||
margin: 4pt 0 0 0;
|
||||
padding: 0;
|
||||
}
|
||||
ul.dash li {
|
||||
position: relative;
|
||||
padding-left: 14pt;
|
||||
margin-bottom: 3.5pt;
|
||||
line-height: 1.4;
|
||||
}
|
||||
ul.dash li:last-child { margin-bottom: 0; }
|
||||
ul.dash li::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 5pt;
|
||||
width: 5pt;
|
||||
height: 1.5pt;
|
||||
border-radius: 0.75pt;
|
||||
background: var(--brand);
|
||||
}
|
||||
ul.dash li strong { color: var(--near-black); font-weight: 500; }
|
||||
|
||||
.footer {
|
||||
margin-top: 14pt;
|
||||
padding-top: 6pt;
|
||||
border-top: 0.3pt dotted var(--border);
|
||||
font-size: 9pt;
|
||||
color: var(--stone);
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
font-variant-numeric: tabular-nums;
|
||||
letter-spacing: 0.3pt;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="header">
|
||||
<div class="title-block">
|
||||
<div class="eyebrow">Product Brief</div>
|
||||
<h1>Five Mac tools, one quiet binary</h1>
|
||||
<div class="subtitle">Mole cleans, uninstalls, optimizes, analyzes, and monitors, then gets out of your way.</div>
|
||||
</div>
|
||||
<div class="meta">
|
||||
Tw93<br>
|
||||
2026.06.14<br>
|
||||
v1.7.1
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p class="lead">
|
||||
Mac maintenance scattered into five subscription apps, each wanting a menu-bar slot and a yearly fee. Mole folds them back into <span class="hl">one quiet binary you buy once</span>.
|
||||
</p>
|
||||
|
||||
<figure class="product-shot">
|
||||
<div class="frame"><img src="demo-mole-clean.jpg" alt="Mole clean view showing an earth and a scan button"></div>
|
||||
<figcaption>One window, five tools. Mole <span class="hl">replaces CleanMyMac, App Cleaner, Sensei, DaisyDisk, and iStat Menus</span>.</figcaption>
|
||||
</figure>
|
||||
|
||||
<div class="metrics">
|
||||
<div class="metric">
|
||||
<div class="metric-value">5</div>
|
||||
<div class="metric-label">tools, one binary</div>
|
||||
</div>
|
||||
<div class="metric">
|
||||
<div class="metric-value">$19</div>
|
||||
<div class="metric-label">no subscription</div>
|
||||
</div>
|
||||
<div class="metric">
|
||||
<div class="metric-value">2</div>
|
||||
<div class="metric-label">Macs per license</div>
|
||||
</div>
|
||||
<div class="metric">
|
||||
<div class="metric-value">0</div>
|
||||
<div class="metric-label">menu-bar clutter</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p class="block-intro">
|
||||
Each tool gets its own page and a single job, with no browser tab and no menu-bar widget in sight. The result is a utility you <span class="hl">set up once and barely notice</span> afterward.
|
||||
</p>
|
||||
|
||||
<div class="two-col">
|
||||
<section>
|
||||
<h2>Why Mole</h2>
|
||||
<ul class="dash">
|
||||
<li>One-time <strong>$19</strong>, no yearly fee.</li>
|
||||
<li>No menu-bar widget or upsell.</li>
|
||||
<li>No telemetry, runs fully offline.</li>
|
||||
<li>Native on Apple Silicon and Intel.</li>
|
||||
<li>Supports macOS 14 and later.</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>What's inside</h2>
|
||||
<ul class="dash">
|
||||
<li><strong>Clean</strong> clears caches, logs, and leftovers.</li>
|
||||
<li><strong>Uninstall</strong> removes apps and every trailing file.</li>
|
||||
<li><strong>Optimize</strong> tunes startup and memory load.</li>
|
||||
<li><strong>Analyze</strong> shows where disk space really goes.</li>
|
||||
<li><strong>Status</strong> tracks CPU, disk, and network live.</li>
|
||||
</ul>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<div class="footer">
|
||||
<span>Mac utilities · macOS 14+ · Apple Silicon & Intel</span>
|
||||
<span>mole.fit</span>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
After Width: | Height: | Size: 441 KiB |
@@ -0,0 +1,682 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Elon Musk · Resume</title>
|
||||
<style>
|
||||
/* Palatino is a system font, no @font-face needed */
|
||||
|
||||
@page {
|
||||
size: A4;
|
||||
margin: 11mm 13mm 11mm 13mm;
|
||||
background: #f5f4ed;
|
||||
}
|
||||
|
||||
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
|
||||
:root {
|
||||
--parchment: #f5f4ed;
|
||||
--ivory: #faf9f5;
|
||||
--border: #e8e6dc;
|
||||
--border-soft: #e5e3d8;
|
||||
--near-black: #141413;
|
||||
--dark-warm: #3d3d3a;
|
||||
--olive: #504e49;
|
||||
--stone: #6b6a64;
|
||||
--brand: #1B365D;
|
||||
--brand-tint: #EEF2F7;
|
||||
|
||||
--serif: Charter, Georgia,
|
||||
Palatino, "Times New Roman", serif;
|
||||
--sans: var(--serif);
|
||||
--mono: "JetBrains Mono", "SF Mono", "Fira Code",
|
||||
Consolas, Monaco, monospace;
|
||||
}
|
||||
|
||||
html, body { background: var(--parchment); }
|
||||
|
||||
body {
|
||||
color: var(--near-black);
|
||||
font-family: var(--serif);
|
||||
font-size: 9.4pt;
|
||||
line-height: 1.48;
|
||||
letter-spacing: 0;
|
||||
}
|
||||
|
||||
.serif { font-family: var(--serif); }
|
||||
.mono { font-family: var(--mono); }
|
||||
|
||||
a { color: var(--brand); text-decoration: none; }
|
||||
|
||||
/* HEADER */
|
||||
.header {
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 4.2mm;
|
||||
padding-bottom: 2.3mm;
|
||||
border-bottom: 0.6pt solid var(--border);
|
||||
}
|
||||
.header > div:first-child { flex-shrink: 0; }
|
||||
.name {
|
||||
font-family: var(--serif);
|
||||
font-size: 26pt;
|
||||
font-weight: 500;
|
||||
letter-spacing: -0.2pt;
|
||||
color: var(--near-black);
|
||||
line-height: 1;
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
gap: 3mm;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.alias {
|
||||
font-size: 12.5pt;
|
||||
font-weight: 400;
|
||||
color: var(--stone);
|
||||
}
|
||||
.contact {
|
||||
text-align: right;
|
||||
font-size: 9pt;
|
||||
color: var(--stone);
|
||||
line-height: 1.5;
|
||||
}
|
||||
.contact .role {
|
||||
color: var(--brand);
|
||||
font-size: 10.5pt;
|
||||
font-weight: 500;
|
||||
}
|
||||
.contact .sep { color: var(--border); margin: 0 0.4em; }
|
||||
.contact a { color: var(--dark-warm); }
|
||||
.contact .loc { color: var(--olive); font-weight: 500; }
|
||||
|
||||
/* METRICS */
|
||||
.metrics {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||
column-gap: 5mm;
|
||||
margin-bottom: 3mm;
|
||||
}
|
||||
.metric {
|
||||
padding: 1mm 0 1.2mm;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 0.7mm;
|
||||
min-width: 0;
|
||||
break-inside: avoid;
|
||||
}
|
||||
.metric-value {
|
||||
font-family: var(--serif);
|
||||
font-size: 13pt;
|
||||
font-weight: 500;
|
||||
color: var(--brand);
|
||||
line-height: 1;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
.metric-value .unit {
|
||||
font-size: 10.5pt;
|
||||
margin-left: 0.5mm;
|
||||
font-weight: 500;
|
||||
color: var(--brand);
|
||||
}
|
||||
.metric-label {
|
||||
font-size: 9pt;
|
||||
color: var(--olive);
|
||||
line-height: 1.2;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* SECTION TITLE */
|
||||
.section-title {
|
||||
font-family: var(--serif);
|
||||
font-size: 13pt;
|
||||
font-weight: 500;
|
||||
color: var(--near-black);
|
||||
border-bottom: 0.5pt solid var(--border);
|
||||
padding: 0 0 3pt 0;
|
||||
margin: 5.8mm 0 2mm 0;
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
gap: 2.5mm;
|
||||
}
|
||||
.section-title .sub {
|
||||
margin-left: auto;
|
||||
font-family: var(--sans);
|
||||
font-weight: 400;
|
||||
font-size: 9pt;
|
||||
color: var(--stone);
|
||||
letter-spacing: 0.2pt;
|
||||
}
|
||||
section:first-of-type .section-title { margin-top: 0; }
|
||||
.page-break > .section-title { margin-top: 0; }
|
||||
|
||||
/* SUMMARY */
|
||||
.summary { font-size: 9.4pt; line-height: 1.50; color: var(--near-black); }
|
||||
.summary .hl { color: var(--brand); }
|
||||
|
||||
/* TIMELINE */
|
||||
.timeline { display: flex; gap: 7mm; margin-bottom: 6.5mm; }
|
||||
.tl-step { flex: 1; padding: 0.8mm 0; break-inside: avoid; }
|
||||
.tl-top {
|
||||
display: flex; align-items: baseline; gap: 2mm;
|
||||
margin-bottom: 0.6mm;
|
||||
}
|
||||
.tl-year {
|
||||
font-family: var(--serif);
|
||||
font-size: 11pt; font-weight: 500;
|
||||
color: var(--brand); line-height: 1;
|
||||
}
|
||||
.tl-head {
|
||||
font-size: 11pt; font-weight: 500;
|
||||
color: var(--near-black);
|
||||
}
|
||||
.tl-body { font-size: 9pt; color: var(--olive); line-height: 1.45; }
|
||||
|
||||
/* PROJECTS */
|
||||
.project {
|
||||
padding: 2.6mm 0;
|
||||
break-inside: avoid;
|
||||
}
|
||||
.project:first-of-type {
|
||||
padding-top: 1.6mm;
|
||||
}
|
||||
.proj-head {
|
||||
display: flex; align-items: baseline;
|
||||
gap: 2mm; margin-bottom: 0.9mm;
|
||||
}
|
||||
.proj-name {
|
||||
font-family: var(--serif);
|
||||
font-size: 11pt; font-weight: 500; color: var(--near-black);
|
||||
}
|
||||
.proj-kind { font-size: 11pt; color: var(--olive); }
|
||||
.proj-role {
|
||||
font-size: 9pt; color: var(--brand); font-weight: 500;
|
||||
margin-left: auto; background: #EEF2F7;
|
||||
padding: 0.4mm 1.6mm; border-radius: 2pt; letter-spacing: 0.3pt;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
.proj-lines { display: table; width: 100%; }
|
||||
.proj-row { display: table-row; }
|
||||
.proj-label {
|
||||
display: table-cell; width: 16mm;
|
||||
font-size: 9pt; color: var(--brand);
|
||||
font-weight: 500; letter-spacing: 0.4pt;
|
||||
text-transform: uppercase;
|
||||
padding: 0.4mm 0 0.4mm 0; vertical-align: top;
|
||||
}
|
||||
.proj-text {
|
||||
display: table-cell; font-size: 9.2pt;
|
||||
color: var(--near-black); line-height: 1.45;
|
||||
padding: 0 0 0.4mm 1mm;
|
||||
}
|
||||
.proj-text .hl { color: var(--brand); }
|
||||
|
||||
/* PAGE 2 FORCE BREAK */
|
||||
.page-break { break-before: page; }
|
||||
|
||||
/* OPEN SOURCE */
|
||||
.os-intro {
|
||||
font-size: 9.4pt; line-height: 1.45;
|
||||
color: var(--near-black);
|
||||
margin: 1.6mm 0 2.4mm 0;
|
||||
break-inside: avoid;
|
||||
}
|
||||
.os-intro .hl { color: var(--brand); }
|
||||
.os-intro .strong { color: var(--near-black); font-weight: 500; }
|
||||
|
||||
.os-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
column-gap: 9mm; row-gap: 1.2mm;
|
||||
}
|
||||
.os-item {
|
||||
display: flex; align-items: baseline;
|
||||
gap: 2.5mm; padding: 1.2mm 0;
|
||||
border-bottom: 0.3pt dotted var(--border);
|
||||
break-inside: avoid;
|
||||
}
|
||||
.os-name {
|
||||
font-family: var(--serif);
|
||||
font-size: 10.5pt; font-weight: 500;
|
||||
flex-shrink: 0; min-width: 18mm;
|
||||
}
|
||||
.os-name a { color: var(--brand); }
|
||||
.os-desc { font-size: 9pt; color: var(--olive); line-height: 1.4; flex: 1; }
|
||||
.os-star {
|
||||
font-size: 9pt; color: var(--dark-warm);
|
||||
font-weight: 500; white-space: nowrap;
|
||||
flex-shrink: 0; min-width: 10mm; text-align: right;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
.os-star.big { color: var(--brand); font-size: 9.2pt; }
|
||||
|
||||
.os-highlight {
|
||||
margin-top: 2.5mm;
|
||||
padding: 1.8mm 3mm;
|
||||
background: var(--brand-tint);
|
||||
border-radius: 2pt;
|
||||
font-size: 9pt; line-height: 1.45;
|
||||
color: var(--near-black);
|
||||
break-inside: avoid;
|
||||
}
|
||||
.os-highlight .hl { color: var(--brand); }
|
||||
.os-highlight .tag {
|
||||
display: inline-block;
|
||||
background: var(--brand); color: var(--ivory);
|
||||
font-size: 9pt; font-weight: 500;
|
||||
padding: 0.2mm 1.6mm; border-radius: 2pt;
|
||||
margin-right: 1.5mm; letter-spacing: 0.5pt;
|
||||
text-transform: uppercase;
|
||||
transform: translateY(-0.2mm);
|
||||
}
|
||||
|
||||
/* CONVICTIONS */
|
||||
.convictions {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr 1fr;
|
||||
gap: 3mm;
|
||||
margin-top: 1.4mm;
|
||||
}
|
||||
.conv-card { padding: 0 2mm 0 0; break-inside: avoid; }
|
||||
.conv-head {
|
||||
font-family: var(--serif);
|
||||
font-size: 10pt; font-weight: 500;
|
||||
color: var(--near-black); margin-bottom: 1mm;
|
||||
display: flex; align-items: baseline; gap: 2mm;
|
||||
}
|
||||
.conv-head .year {
|
||||
color: var(--brand); font-weight: 500;
|
||||
font-size: 9pt;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
.conv-body {
|
||||
font-size: 9pt; color: var(--olive);
|
||||
line-height: 1.45;
|
||||
}
|
||||
.conv-body .hl { color: var(--brand); }
|
||||
|
||||
/* IMPACT */
|
||||
.handle-strip {
|
||||
padding: 0; margin: 1.4mm 0 2mm 0;
|
||||
display: flex; align-items: baseline; gap: 3.5mm;
|
||||
break-inside: avoid;
|
||||
}
|
||||
.handle-strip .handle {
|
||||
font-family: var(--serif);
|
||||
font-size: 10pt; font-weight: 500; color: var(--brand);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.handle-strip .follower { font-size: 9pt; color: var(--stone); flex-shrink: 0; font-variant-numeric: tabular-nums; }
|
||||
.handle-strip .desc { font-size: 9pt; color: var(--olive); line-height: 1.45; flex: 1; }
|
||||
|
||||
.impact-grid { display: grid; grid-template-columns: 1.15fr 1fr; gap: 6mm; }
|
||||
.inf-block-title {
|
||||
margin-top: 2.5mm;
|
||||
font-family: var(--serif);
|
||||
font-size: 10pt; font-weight: 500;
|
||||
color: var(--near-black);
|
||||
margin-bottom: 1.5mm; padding-bottom: 0.5mm;
|
||||
border-bottom: 0.3pt dotted var(--border);
|
||||
display: flex; align-items: baseline; gap: 2mm;
|
||||
}
|
||||
.inf-block-title .small {
|
||||
font-family: var(--sans);
|
||||
font-size: 9pt; color: var(--stone);
|
||||
font-weight: 400;
|
||||
}
|
||||
.art-row {
|
||||
padding: 1.8mm 0;
|
||||
border-bottom: 0.3pt dotted var(--border-soft);
|
||||
}
|
||||
.talk-row {
|
||||
display: flex; justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
gap: 2mm; padding: 1.8mm 0;
|
||||
border-bottom: 0.3pt dotted var(--border-soft);
|
||||
}
|
||||
.art-row:last-of-type, .talk-row:last-of-type { border-bottom: none; }
|
||||
.art-header {
|
||||
display: flex; align-items: baseline;
|
||||
justify-content: space-between;
|
||||
gap: 2mm;
|
||||
}
|
||||
.art-title, .talk-title {
|
||||
font-size: 9.2pt; color: var(--near-black);
|
||||
font-weight: 500; line-height: 1.4;
|
||||
flex: 1 1 auto; min-width: 0;
|
||||
}
|
||||
.art-stats, .talk-venue {
|
||||
font-size: 9pt; color: var(--stone);
|
||||
line-height: 1.4; margin-top: 0.6mm;
|
||||
}
|
||||
.art-stats .em-brand { color: var(--brand); }
|
||||
.art-date, .talk-date {
|
||||
font-size: 9pt; color: var(--stone);
|
||||
font-variant-numeric: tabular-nums;
|
||||
font-weight: 400;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* SKILLS */
|
||||
.skill-row {
|
||||
display: flex; gap: 3mm; padding: 1.8mm 0;
|
||||
border-bottom: 0.3pt dotted var(--border);
|
||||
align-items: baseline;
|
||||
}
|
||||
.skill-row:last-of-type { border-bottom: none; }
|
||||
.skill-label {
|
||||
font-size: 9pt; font-weight: 500;
|
||||
color: var(--brand); letter-spacing: 0.5pt;
|
||||
text-transform: uppercase;
|
||||
width: 32mm; flex-shrink: 0;
|
||||
line-height: 1.35;
|
||||
}
|
||||
.skill-body { font-size: 9.2pt; color: var(--near-black); line-height: 1.45; flex: 1; }
|
||||
.skill-body .em-brand { color: var(--brand); }
|
||||
|
||||
/* EDUCATION */
|
||||
.edu-row {
|
||||
display: flex; justify-content: space-between;
|
||||
align-items: baseline; padding: 1.8mm 0;
|
||||
}
|
||||
.edu-row .school {
|
||||
font-family: var(--serif);
|
||||
font-weight: 500; font-size: 11pt;
|
||||
}
|
||||
.edu-row .major { color: var(--olive); font-size: 9.4pt; }
|
||||
.edu-row .date { color: var(--stone); font-variant-numeric: tabular-nums; font-size: 9pt; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<!-- =====================================================================
|
||||
Usage:
|
||||
- Replace all {{VAR}} placeholders with real content
|
||||
- Drop sections that don't apply (e.g. no open source -> remove that block)
|
||||
- Each project strictly three-part: ROLE / ACTIONS / IMPACT
|
||||
- Must verify: 2 pages exactly (strict)
|
||||
python3 scripts/build.py resume-en
|
||||
- Content strategy: references/writing.md
|
||||
===================================================================== -->
|
||||
|
||||
|
||||
<!-- ═══════════ PAGE 1 ═══════════ -->
|
||||
|
||||
<div class="header">
|
||||
<div>
|
||||
<div class="name serif">Elon Musk</div>
|
||||
</div>
|
||||
<div class="contact">
|
||||
<span class="role">Founder · Chief Engineer · CEO</span>
|
||||
<br>
|
||||
<a href="https://x.com/elonmusk">x.com/elonmusk</a>
|
||||
<span class="sep">·</span>
|
||||
<a href="mailto:em@example.com">em@example.com</a>
|
||||
<span class="sep">·</span>
|
||||
<span class="loc">Austin, TX</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Four strongest numeric badges -->
|
||||
<div class="metrics">
|
||||
<div class="metric"><span class="metric-value serif">30<span class="unit">yrs</span></span><span class="metric-label">building hard tech</span></div>
|
||||
<div class="metric"><span class="metric-value serif">6<span class="unit">cos</span></span><span class="metric-label">founded / co-founded</span></div>
|
||||
<div class="metric"><span class="metric-value serif">400<span class="unit">+</span></span><span class="metric-label">Falcon launches</span></div>
|
||||
<div class="metric"><span class="metric-value serif">7M<span class="unit">+</span></span><span class="metric-label">Tesla vehicles</span></div>
|
||||
</div>
|
||||
|
||||
<section>
|
||||
<div class="section-title">Summary</div>
|
||||
<div class="summary">
|
||||
Founder and engineer across SpaceX, Tesla, xAI, Neuralink, The Boring Company, and X. 30 years running companies where the product is hard and the supply chain is physical. Long arc: move civilization to multi-planetary, sustainable energy, beneficial AI. Core depth in rocket engineering, mass manufacturing, capital markets, and first-principles problem decomposition.
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<div class="section-title">Experience<span class="sub">1995 - Present · six companies, one reusable rocket, one mass-market EV</span></div>
|
||||
|
||||
<!-- Timeline: three-step long-range arc -->
|
||||
<div class="timeline">
|
||||
<div class="tl-step">
|
||||
<div class="tl-top"><div class="tl-year serif">1995</div><div class="tl-head">Internet era</div></div>
|
||||
<div class="tl-body">Zip2 -> X.com -> PayPal. Sold two internet companies before age 32.</div>
|
||||
</div>
|
||||
<div class="tl-step">
|
||||
<div class="tl-top"><div class="tl-year serif">2002</div><div class="tl-head">Space + autos</div></div>
|
||||
<div class="tl-body">SpaceX, Tesla, SolarCity. Built physical infrastructure others called impossible.</div>
|
||||
</div>
|
||||
<div class="tl-step">
|
||||
<div class="tl-top"><div class="tl-year serif">2023</div><div class="tl-head">AI era</div></div>
|
||||
<div class="tl-body">xAI, Neuralink scaling, X platform ownership. Bet on AGI and BCI.</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Project block - repeat 3–5 times -->
|
||||
<div class="project">
|
||||
<div class="proj-head">
|
||||
<span class="proj-name serif">SpaceX</span>
|
||||
<span class="proj-kind">· Space launch · reusable rockets</span>
|
||||
<span class="proj-role">founder</span>
|
||||
</div>
|
||||
<div class="proj-lines">
|
||||
<div class="proj-row">
|
||||
<div class="proj-label">Role</div>
|
||||
<div class="proj-text">Founded 2002. CEO and Chief Engineer from day one. First private company to orbit (2008), dock ISS (2012), land a booster (2015), and fly crew (2020).</div>
|
||||
</div>
|
||||
<div class="proj-row">
|
||||
<div class="proj-label">Actions</div>
|
||||
<div class="proj-text">Full vertical integration from propellant to avionics. Merlin and Raptor engines in-house. Reusable-booster thesis pursued through 12 failed landings. Launch cadence: 1/year (2008) to every 2-3 days (2024).</div>
|
||||
</div>
|
||||
<div class="proj-row">
|
||||
<div class="proj-label">Impact</div>
|
||||
<div class="proj-text">Falcon 9: <span class="hl">400+ launches</span>, 95%+ reuse, 10x cost reduction. Starlink: <span class="hl">7,000+ satellites</span>, largest constellation ever. Valued at $350B (2024).</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="project">
|
||||
<div class="proj-head">
|
||||
<span class="proj-name serif">Tesla</span>
|
||||
<span class="proj-kind">· EVs · energy · autonomy</span>
|
||||
<span class="proj-role">ceo</span>
|
||||
</div>
|
||||
<div class="proj-lines">
|
||||
<div class="proj-row">
|
||||
<div class="proj-label">Role</div>
|
||||
<div class="proj-text">Lead investor 2004, CEO from 2008 during near-bankruptcy. Built from a two-car project into the world's most valuable automaker.</div>
|
||||
</div>
|
||||
<div class="proj-row">
|
||||
<div class="proj-label">Actions</div>
|
||||
<div class="proj-text">Master Plan framework: each model funds the next. Four Gigafactories on three continents. FSD shipped as continuous software, not fixed release. Vertically integrated batteries, motors, silicon, charging.</div>
|
||||
</div>
|
||||
<div class="proj-row">
|
||||
<div class="proj-label">Impact</div>
|
||||
<div class="proj-text"><span class="hl">7M+ vehicles delivered</span> (1.8M in 2023), first EV to cross 20% gross margin, first automaker past $1T. Every major OEM announced EV transition within 5 years of Model 3.</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="project">
|
||||
<div class="proj-head">
|
||||
<span class="proj-name serif">xAI</span>
|
||||
<span class="proj-kind">· Frontier AI · foundation models</span>
|
||||
<span class="proj-role">founder</span>
|
||||
</div>
|
||||
<div class="proj-lines">
|
||||
<div class="proj-row">
|
||||
<div class="proj-label">Role</div>
|
||||
<div class="proj-text">Founded 2023 to understand the true nature of the universe. Shipped Grok within 6 months, one of the fastest foundation-model companies to production.</div>
|
||||
</div>
|
||||
<div class="proj-row">
|
||||
<div class="proj-label">Actions</div>
|
||||
<div class="proj-text">Built Colossus (100K H100 GPUs) in 122 days from concept to training start. Integrated Grok directly into X for real-time data access and instant distribution to 500M+ users.</div>
|
||||
</div>
|
||||
<div class="proj-row">
|
||||
<div class="proj-label">Impact</div>
|
||||
<div class="proj-text">Colossus in <span class="hl">122 days</span>, a pace the industry called implausible. Grok reached ChatGPT-level benchmarks within 18 months. xAI valued at $50B (2024).</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="project">
|
||||
<div class="proj-head">
|
||||
<span class="proj-name serif">Neuralink</span>
|
||||
<span class="proj-kind">· Brain-computer interfaces · BCI implants</span>
|
||||
<span class="proj-role">founder</span>
|
||||
</div>
|
||||
<div class="proj-lines">
|
||||
<div class="proj-row">
|
||||
<div class="proj-label">Role</div>
|
||||
<div class="proj-text">Founded 2016 to build high-bandwidth brain-computer interfaces. Co-leads engineering on the N1 implant, surgical robot, and electrode array design.</div>
|
||||
</div>
|
||||
<div class="proj-row">
|
||||
<div class="proj-label">Actions</div>
|
||||
<div class="proj-text">Shipped N1 chip with <span class="hl">1,024 electrodes</span> and precision surgical robot. FDA Breakthrough Device designation and IDE approval (2023) after five years of primate trials.</div>
|
||||
</div>
|
||||
<div class="proj-row">
|
||||
<div class="proj-label">Impact</div>
|
||||
<div class="proj-text">First human implanted January 2024; full cursor control in 30 days. <span class="hl">Second patient mid-2024</span>, expanding to speech restoration. Valued at $5B (2023). Long-term goal: high-bandwidth symbiosis between human cognition and AI.</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
||||
<!-- ═══════════ PAGE 2 ═══════════ -->
|
||||
|
||||
<section class="page-break">
|
||||
<div class="section-title">Open Source & Indie Work<span class="sub">1995 - present · Founder-engineer across six hard-tech companies</span></div>
|
||||
|
||||
<div class="os-intro">
|
||||
<span class="hl">First-principles founder across hard tech.</span> Approach: start from the physics, question every received cost assumption, prototype in public. Public reach: <span class="strong">214M followers on X · 125 followers on GitHub</span>.
|
||||
</div>
|
||||
|
||||
<div class="os-grid">
|
||||
<div class="os-item">
|
||||
<span class="os-name serif"><a href="https://spacex.com">SpaceX</a></span>
|
||||
<span class="os-desc">Launch · Starlink · Starship</span>
|
||||
<span class="os-star big">* $350B</span>
|
||||
</div>
|
||||
<div class="os-item">
|
||||
<span class="os-name serif"><a href="https://tesla.com">Tesla</a></span>
|
||||
<span class="os-desc">EVs · energy · FSD</span>
|
||||
<span class="os-star big">* $900B</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="os-highlight">
|
||||
<span class="tag">Cadence</span>The Falcon 9 booster that landed on 22 December 2015 was the first orbital-class rocket to return intact and fly again. The industry assumption that rockets were expendable was gone within 24 months.
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<div class="section-title">Judgment & Conviction</div>
|
||||
<div class="convictions">
|
||||
<div class="conv-card">
|
||||
<div class="conv-head"><span class="year">1999</span>Bet on online banking</div>
|
||||
<div class="conv-body">
|
||||
Founded X.com while consumer online banking was niche; merger with Confinity became PayPal. Sold to eBay for $1.5B, with proceeds recycled into SpaceX and Tesla within two years.
|
||||
</div>
|
||||
</div>
|
||||
<div class="conv-card">
|
||||
<div class="conv-head"><span class="year">2008</span>CEO during near-bankruptcy</div>
|
||||
<div class="conv-body">
|
||||
Took Tesla CEO during the 2008 crisis, three weeks from insolvency. Closed a Mercedes partnership that funded Model S, enabling every subsequent Tesla product.
|
||||
</div>
|
||||
</div>
|
||||
<div class="conv-card">
|
||||
<div class="conv-head"><span class="year">2015</span>Committed to reusable boosters</div>
|
||||
<div class="conv-body">
|
||||
Pushed Falcon 9 reusability after <span class="hl">12 consecutive landing failures</span> when board and industry said stop. Succeeded on attempt 13; reusability is now the industry baseline.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<div class="section-title">Public Impact</div>
|
||||
|
||||
<div class="handle-strip">
|
||||
<span class="handle serif">X · <a href="https://tesla.com/blog" style="color: var(--brand); border-bottom: none;">@elonmusk</a></span>
|
||||
<span class="follower">214M followers</span>
|
||||
<span class="desc">Direct channel to 214M followers. Each engineering post, launch video, or product hint moves markets within hours.</span>
|
||||
</div>
|
||||
|
||||
<div class="impact-grid">
|
||||
<div>
|
||||
<div class="inf-block-title">Selected writing</div>
|
||||
<div class="art-row">
|
||||
<div class="art-header">
|
||||
<a class="art-title" href="https://youtube.com/ted" style="text-decoration: none;">Master Plan Part Deux</a>
|
||||
<span class="art-date">2016.07</span>
|
||||
</div>
|
||||
<div class="art-stats">Tesla blog, <span class="em-brand">40M+</span> reads; reframed EV strategy across the industry</div>
|
||||
</div>
|
||||
<div class="art-row">
|
||||
<div class="art-header">
|
||||
<a class="art-title" href="https://spacex.com" style="text-decoration: none;">First Principles Thinking in Engineering</a>
|
||||
<span class="art-date">2013.03</span>
|
||||
</div>
|
||||
<div class="art-stats">TED Talk · 5.6M views on youtube.com</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div class="inf-block-title">Invited talks</div>
|
||||
<div class="talk-row">
|
||||
<div>
|
||||
<div class="talk-title">Making Humans Multi-Planetary</div>
|
||||
<div class="talk-venue">IAC Guadalajara · 2016</div>
|
||||
</div>
|
||||
<div class="talk-date">2016.09</div>
|
||||
</div>
|
||||
<div class="talk-row">
|
||||
<div>
|
||||
<div class="talk-title">Starship and Mars</div>
|
||||
<div class="talk-venue">Starbase Texas · 2024</div>
|
||||
</div>
|
||||
<div class="talk-date">2024.04</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<div class="section-title">Core Skills</div>
|
||||
<!-- Five fixed rows, each with at least one brand-color emphasis -->
|
||||
<div class="skill-row">
|
||||
<div class="skill-label">Rocket · Engine Design</div>
|
||||
<div class="skill-body">Led Merlin, Raptor, Starship architecture; <span class="em-brand">first reusable orbital booster</span> shipped by any org.</div>
|
||||
</div>
|
||||
<div class="skill-row">
|
||||
<div class="skill-label">Manufacturing · Scale</div>
|
||||
<div class="skill-body">Built Tesla Fremont, Shanghai, Berlin, Austin; SpaceX assembly scaled from 0 to <span class="em-brand">100+ launches/year</span>.</div>
|
||||
</div>
|
||||
<div class="skill-row">
|
||||
<div class="skill-label">Capital · Crisis Mgmt</div>
|
||||
<div class="skill-body">Closed Daimler investment when Tesla was <span class="em-brand">weeks from insolvency</span>; survived five near-bankruptcies across companies.</div>
|
||||
</div>
|
||||
<div class="skill-row">
|
||||
<div class="skill-label">Public Communication</div>
|
||||
<div class="skill-body">214M followers on X; announcements routinely move the <span class="em-brand">S&P 500 component</span> price of his companies.</div>
|
||||
</div>
|
||||
<div class="skill-row">
|
||||
<div class="skill-label">First Principles · Cost</div>
|
||||
<div class="skill-body">Questioning every accepted cost assumption (rockets, batteries, tunneling) has driven <span class="em-brand">10× cost reductions</span> across four industries.</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section style="break-inside: avoid;">
|
||||
<div class="section-title">Education</div>
|
||||
<div class="edu-row">
|
||||
<div>
|
||||
<span class="school serif">University of Pennsylvania</span>
|
||||
<span class="major"> · Wharton, B.S. Economics · B.A. Physics</span>
|
||||
</div>
|
||||
<div class="date">1992 - 1997</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
After Width: | Height: | Size: 418 KiB |
@@ -0,0 +1,827 @@
|
||||
<!DOCTYPE html>
|
||||
<!-- ==================================================================
|
||||
이력서 데모 (KO) · resume-ko.html 템플릿 기반
|
||||
가상의 인디 개발자 / AI 에이전트 엔지니어 샘플 (Kami showcase)
|
||||
================================================================== -->
|
||||
<html lang="ko">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>김도현 · 이력서</title>
|
||||
<meta name="author" content="김도현">
|
||||
<meta name="description" content="AI 에이전트 엔지니어 겸 인디 개발자 이력서 샘플 · Kami resume-ko 데모">
|
||||
<meta name="keywords" content="이력서, AI 에이전트, 오픈소스, 인디 개발자, 한국어">
|
||||
<meta name="generator" content="Kami">
|
||||
<style>
|
||||
/* Regular weight */
|
||||
@font-face {
|
||||
font-family: "Source Han Serif K";
|
||||
src: url("../fonts/SourceHanSerifKR-Regular.otf") format("opentype"),
|
||||
url("https://cdn.jsdelivr.net/gh/tw93/Kami@main/assets/fonts/SourceHanSerifKR-Regular.otf") format("opentype");
|
||||
font-weight: 400;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
/* Medium weight */
|
||||
@font-face {
|
||||
font-family: "Source Han Serif K";
|
||||
src: url("../fonts/SourceHanSerifKR-Medium.otf") format("opentype"),
|
||||
url("https://cdn.jsdelivr.net/gh/tw93/Kami@main/assets/fonts/SourceHanSerifKR-Medium.otf") format("opentype");
|
||||
font-weight: 500;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
@page {
|
||||
size: A4;
|
||||
margin: 11mm 13mm 11mm 13mm;
|
||||
background: #f5f4ed;
|
||||
}
|
||||
|
||||
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
|
||||
:root {
|
||||
--parchment: #f5f4ed;
|
||||
--ivory: #faf9f5;
|
||||
--border: #e8e6dc;
|
||||
--border-soft: #e5e3d8;
|
||||
--near-black: #141413;
|
||||
--dark-warm: #3d3d3a;
|
||||
--olive: #504e49;
|
||||
--stone: #6b6a64;
|
||||
--brand: #1B365D;
|
||||
--brand-tint: #EEF2F7;
|
||||
--serif: "Source Han Serif K", "Source Han Serif KR", "Noto Serif KR", "Apple SD Gothic Neo", "AppleMyungjo", Charter, Georgia, serif;
|
||||
--sans: var(--serif);
|
||||
}
|
||||
|
||||
html, body { background: var(--parchment); }
|
||||
|
||||
@media screen {
|
||||
body { max-width: 210mm; margin: 0 auto; padding: 11mm 13mm; }
|
||||
}
|
||||
|
||||
body {
|
||||
color: var(--near-black);
|
||||
font-family: var(--serif);
|
||||
font-size: 9.2pt;
|
||||
line-height: 1.42;
|
||||
letter-spacing: 0.3pt;
|
||||
widows: 3;
|
||||
orphans: 3;
|
||||
font-synthesis: none;
|
||||
}
|
||||
|
||||
.serif {
|
||||
font-family: var(--serif);
|
||||
}
|
||||
|
||||
a { color: var(--brand); text-decoration: none; }
|
||||
|
||||
/* HEADER */
|
||||
.header {
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 4.2mm;
|
||||
padding-bottom: 2.3mm;
|
||||
border-bottom: 0.6pt solid var(--border);
|
||||
}
|
||||
.header > div:first-child { flex-shrink: 0; }
|
||||
.name {
|
||||
font-family: var(--serif);
|
||||
font-size: 26.5pt;
|
||||
font-weight: 500;
|
||||
letter-spacing: 0.5pt;
|
||||
color: var(--near-black);
|
||||
line-height: 1;
|
||||
margin-bottom: 0;
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
gap: 2.2mm;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.alias {
|
||||
font-size: 12.5pt;
|
||||
font-weight: 400;
|
||||
color: var(--stone);
|
||||
letter-spacing: 0.2pt;
|
||||
}
|
||||
.alias::before {
|
||||
content: "";
|
||||
}
|
||||
.tagline { font-size: 9.5pt; color: var(--olive); line-height: 1.3; }
|
||||
.tagline .accent { color: var(--brand); font-weight: 500; }
|
||||
|
||||
.contact {
|
||||
text-align: right;
|
||||
font-size: 9pt;
|
||||
color: var(--stone);
|
||||
line-height: 1.5;
|
||||
margin-bottom: -0.5mm;
|
||||
}
|
||||
.contact a { color: var(--dark-warm); }
|
||||
.contact .loc { color: var(--olive); font-weight: 500; }
|
||||
.contact .role { color: var(--brand); font-size: 10.5pt; font-weight: 500; }
|
||||
.contact .sep { color: var(--border); margin: 0 0.4em; }
|
||||
|
||||
/* METRICS */
|
||||
.metrics {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||
column-gap: 6mm;
|
||||
margin-bottom: 3mm;
|
||||
margin-left: 0;
|
||||
margin-right: 0;
|
||||
}
|
||||
.metric {
|
||||
background: transparent;
|
||||
padding: 1mm 0 1.2mm;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 0.7mm;
|
||||
min-width: 0;
|
||||
break-inside: avoid;
|
||||
}
|
||||
.metric-value {
|
||||
font-family: var(--serif);
|
||||
font-size: 13pt;
|
||||
font-weight: 500;
|
||||
color: var(--brand);
|
||||
line-height: 1;
|
||||
font-variant-numeric: tabular-nums;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.metric-label {
|
||||
font-size: 9pt;
|
||||
color: var(--olive);
|
||||
line-height: 1.2;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.metric-value .unit {
|
||||
font-size: 10.5pt;
|
||||
margin-left: 0.3mm;
|
||||
font-weight: 500;
|
||||
color: var(--brand);
|
||||
}
|
||||
|
||||
.metrics--stack .metric {
|
||||
gap: 0.5mm;
|
||||
padding-bottom: 2mm;
|
||||
border-bottom: 0.3pt dotted var(--border);
|
||||
}
|
||||
|
||||
/* SECTION */
|
||||
.section-title {
|
||||
font-family: var(--serif);
|
||||
font-size: 12.5pt;
|
||||
font-weight: 500;
|
||||
color: var(--near-black);
|
||||
border-bottom: 0.5pt solid var(--border);
|
||||
padding: 0 0 1mm 0;
|
||||
margin: 5.8mm 0 1.7mm 0;
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
gap: 2mm;
|
||||
}
|
||||
.section-title .sub {
|
||||
margin-left: auto;
|
||||
font-family: var(--serif);
|
||||
font-weight: 400;
|
||||
font-size: 9pt;
|
||||
color: var(--stone);
|
||||
letter-spacing: 0.3pt;
|
||||
}
|
||||
|
||||
section:first-of-type .section-title { margin-top: 0; }
|
||||
.page-break > .section-title { margin-top: 0; }
|
||||
|
||||
.section-title + .skill-row { padding-top: 1.2mm; }
|
||||
.section-title + .edu-row { padding-top: 1.2mm; }
|
||||
|
||||
/* SUMMARY */
|
||||
.summary { font-size: 9.2pt; line-height: 1.55; color: var(--near-black); }
|
||||
.summary .em-brand { color: var(--brand); }
|
||||
.summary .hl { color: var(--brand); }
|
||||
|
||||
/* TIMELINE */
|
||||
.timeline { display: flex; gap: 6mm; margin-bottom: 4mm; margin-left: 0; margin-right: 0; }
|
||||
.tl-step {
|
||||
flex: 1; background: transparent;
|
||||
padding: 1.4mm 0;
|
||||
break-inside: avoid;
|
||||
}
|
||||
.tl-top {
|
||||
display: flex; align-items: baseline; gap: 1.5mm;
|
||||
margin-bottom: 0.5mm;
|
||||
}
|
||||
.tl-year {
|
||||
font-family: var(--serif);
|
||||
font-size: 11pt; font-weight: 500;
|
||||
color: var(--brand); line-height: 1;
|
||||
}
|
||||
.tl-head {
|
||||
font-size: 11pt; font-weight: 500;
|
||||
color: var(--near-black);
|
||||
}
|
||||
.tl-body { font-size: 9pt; color: var(--olive); line-height: 1.40; letter-spacing: 0.3pt; }
|
||||
|
||||
/* PROJECTS */
|
||||
.project {
|
||||
padding: 2.6mm 0;
|
||||
break-inside: avoid;
|
||||
}
|
||||
.project:first-of-type { padding-top: 1.2mm; }
|
||||
.proj-head {
|
||||
display: flex; align-items: baseline;
|
||||
gap: 2mm; margin-bottom: 0.75mm;
|
||||
}
|
||||
.proj-name {
|
||||
font-family: var(--serif);
|
||||
font-size: 11pt; font-weight: 500; color: var(--near-black);
|
||||
}
|
||||
.proj-kind { font-size: 11pt; color: var(--olive); }
|
||||
.proj-role {
|
||||
font-size: 9pt; color: var(--brand); font-weight: 500;
|
||||
margin-left: auto; background: #EEF2F7;
|
||||
padding: 0.4mm 1.2mm; border-radius: 2pt; letter-spacing: 0.05pt;
|
||||
}
|
||||
.proj-lines { display: table; width: 100%; }
|
||||
.proj-row { display: table-row; }
|
||||
.proj-label {
|
||||
display: table-cell; width: 11mm;
|
||||
font-size: 9pt; color: var(--brand);
|
||||
font-weight: 500; letter-spacing: 0.2pt;
|
||||
padding: 0.2mm 0 0.4mm 0; vertical-align: top;
|
||||
}
|
||||
.proj-text {
|
||||
display: table-cell; font-size: 9pt;
|
||||
color: var(--near-black); line-height: 1.5; letter-spacing: 0.3pt;
|
||||
padding: 0 0 0.9mm 0.5mm;
|
||||
}
|
||||
.proj-text .hl { color: var(--brand); }
|
||||
|
||||
.team-culture {
|
||||
margin-top: 2mm; padding: 1.5mm 2.5mm;
|
||||
background: var(--brand-tint);
|
||||
border-radius: 2pt;
|
||||
font-size: 9pt; color: var(--olive); line-height: 1.40;
|
||||
break-inside: avoid;
|
||||
}
|
||||
.team-culture .hl { color: var(--brand); }
|
||||
|
||||
/* PAGE 2 FORCE BREAK */
|
||||
.page-break { break-before: page; }
|
||||
|
||||
/* OPEN SOURCE */
|
||||
.os-intro {
|
||||
font-size: 9.2pt; line-height: 1.40; letter-spacing: 0.3pt;
|
||||
color: var(--near-black);
|
||||
padding: 0; margin: 1.2mm 0 2mm 0;
|
||||
background: transparent;
|
||||
break-inside: avoid;
|
||||
}
|
||||
.os-intro .hl { color: var(--brand); }
|
||||
.os-intro .strong { color: var(--near-black); }
|
||||
|
||||
.os-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
column-gap: 8mm; row-gap: 1mm;
|
||||
}
|
||||
.os-item {
|
||||
display: flex; align-items: baseline;
|
||||
gap: 2mm; padding: 1mm 0 1mm 0;
|
||||
border-bottom: 0.3pt dotted var(--border);
|
||||
break-inside: avoid;
|
||||
}
|
||||
.os-item:last-child,
|
||||
.os-item:nth-last-child(2):nth-child(odd) { border-bottom: none; }
|
||||
.os-name {
|
||||
font-family: var(--serif);
|
||||
font-size: 10pt; font-weight: 500;
|
||||
flex-shrink: 0; min-width: 14mm;
|
||||
}
|
||||
.os-name a { color: var(--brand); }
|
||||
.os-desc { font-size: 9pt; color: var(--olive); line-height: 1.40; flex: 1; }
|
||||
.os-star {
|
||||
font-size: 9pt; color: var(--dark-warm);
|
||||
font-weight: 500; white-space: nowrap; flex-shrink: 0; min-width: 7.5mm; text-align: right;
|
||||
}
|
||||
.os-star.big { color: var(--brand); font-size: 9.2pt; }
|
||||
|
||||
.os-highlight {
|
||||
margin-top: 2.5mm;
|
||||
padding: 1.8mm 2.8mm;
|
||||
background: var(--brand-tint);
|
||||
border-radius: 2pt;
|
||||
font-size: 9pt; line-height: 1.40;
|
||||
color: var(--near-black);
|
||||
letter-spacing: 0.3pt;
|
||||
break-inside: avoid;
|
||||
}
|
||||
.os-highlight .hl { color: var(--brand); }
|
||||
.os-highlight .tag {
|
||||
display: inline-block;
|
||||
background: var(--brand); color: var(--ivory);
|
||||
font-size: 9pt; font-weight: 500;
|
||||
padding: 0.2mm 1.5mm; border-radius: 2pt;
|
||||
margin-right: 1.2mm; letter-spacing: 0.2pt;
|
||||
transform: translateY(-0.2mm);
|
||||
}
|
||||
|
||||
/* AI CONVICTION */
|
||||
.convictions {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr 1fr;
|
||||
gap: 2.5mm;
|
||||
margin-top: 1.2mm;
|
||||
}
|
||||
.conv-card {
|
||||
background: transparent;
|
||||
border: none;
|
||||
border-radius: 0;
|
||||
padding: 0 2mm 0 0;
|
||||
break-inside: avoid;
|
||||
}
|
||||
.conv-head, .ai-card-head {
|
||||
font-family: var(--serif);
|
||||
font-size: 9.8pt; font-weight: 500;
|
||||
color: var(--near-black); margin-bottom: 0.9mm;
|
||||
display: flex; align-items: baseline; gap: 1.5mm;
|
||||
}
|
||||
.conv-head .year, .ai-card-head .year {
|
||||
color: var(--brand); font-weight: 500;
|
||||
font-size: 9pt;
|
||||
}
|
||||
.conv-body, .ai-card-body {
|
||||
font-size: 9pt; color: var(--olive);
|
||||
line-height: 1.40; letter-spacing: 0.3pt;
|
||||
}
|
||||
.conv-body .hl, .ai-card-body .hl { color: var(--brand); }
|
||||
.conv-body .em-brand, .ai-card-body .em-brand { color: var(--brand); }
|
||||
|
||||
/* INFLUENCE */
|
||||
.handle-strip {
|
||||
background: transparent;
|
||||
padding: 0; margin: 1.2mm 0 2mm 0;
|
||||
display: flex; align-items: baseline; gap: 3mm;
|
||||
break-inside: avoid;
|
||||
}
|
||||
.handle-strip .handle {
|
||||
font-family: var(--serif);
|
||||
font-size: 9.8pt; font-weight: 500; color: var(--brand);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.handle-strip .follower { font-size: 9pt; color: var(--stone); flex-shrink: 0; }
|
||||
.handle-strip .desc { font-size: 9pt; color: var(--olive); line-height: 1.40; letter-spacing: 0.3pt; flex: 1; }
|
||||
|
||||
.impact-grid { display: grid; grid-template-columns: 1.15fr 1fr; gap: 5.5mm; }
|
||||
.inf-block-title {
|
||||
margin-top: 2.5mm;
|
||||
font-family: var(--serif);
|
||||
font-size: 9.8pt; font-weight: 500;
|
||||
color: var(--near-black);
|
||||
margin-bottom: 1.5mm; padding-bottom: 0.5mm;
|
||||
border-bottom: 0.3pt dotted var(--border);
|
||||
display: flex; align-items: baseline; gap: 2mm;
|
||||
}
|
||||
.inf-block-title .small {
|
||||
font-family: var(--serif);
|
||||
font-size: 9pt; color: var(--stone);
|
||||
font-weight: 400; margin-left: 0; letter-spacing: 0.2pt;
|
||||
}
|
||||
.art-row {
|
||||
padding: 1.6mm 0;
|
||||
border-bottom: 0.3pt dotted var(--border-soft);
|
||||
}
|
||||
.talk-row {
|
||||
display: flex; justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
gap: 2mm; padding: 1.6mm 0;
|
||||
border-bottom: 0.3pt dotted var(--border-soft);
|
||||
}
|
||||
.art-row:last-of-type, .talk-row:last-of-type { border-bottom: none; }
|
||||
.art-header {
|
||||
display: flex; align-items: baseline;
|
||||
justify-content: space-between;
|
||||
gap: 2mm; padding-right: 3mm;
|
||||
}
|
||||
.art-title, .talk-title {
|
||||
font-size: 9pt; color: var(--near-black);
|
||||
font-weight: 500; line-height: 1.40;
|
||||
flex: 1 1 auto; min-width: 0;
|
||||
}
|
||||
.art-stats, .talk-venue {
|
||||
font-size: 9pt; color: var(--stone);
|
||||
line-height: 1.40; margin-top: 0.5mm; padding-left: 0; letter-spacing: 0.3pt;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.art-stats .em-brand {
|
||||
color: var(--brand);
|
||||
}
|
||||
.art-date {
|
||||
font-size: 9pt; color: var(--stone);
|
||||
font-variant-numeric: tabular-nums;
|
||||
font-weight: 400;
|
||||
}
|
||||
.talk-date {
|
||||
font-size: 9pt; color: var(--stone); font-weight: 500;
|
||||
white-space: nowrap; font-variant-numeric: tabular-nums;
|
||||
padding-top: 0.3mm;
|
||||
}
|
||||
|
||||
/* SKILLS */
|
||||
.skill-row {
|
||||
display: flex; gap: 2.5mm; padding: 1.7mm 0;
|
||||
border-bottom: 0.3pt dotted var(--border);
|
||||
align-items: baseline;
|
||||
}
|
||||
.skill-row:last-of-type { border-bottom: none; }
|
||||
.skill-label {
|
||||
font-size: 9pt; font-weight: 500;
|
||||
color: var(--brand); letter-spacing: 0.3pt;
|
||||
width: 22mm; flex-shrink: 0;
|
||||
line-height: 1.36;
|
||||
}
|
||||
.skill-body { font-size: 9pt; color: var(--near-black); line-height: 1.40; letter-spacing: 0.3pt; flex: 1; }
|
||||
.skill-body .em-brand { color: var(--brand); }
|
||||
|
||||
.no-break { break-inside: avoid; }
|
||||
|
||||
/* EDUCATION */
|
||||
.edu-row {
|
||||
display: flex; justify-content: space-between;
|
||||
align-items: baseline; font-size: 9.5pt; padding: 1.7mm 0;
|
||||
}
|
||||
.edu-row .school {
|
||||
font-family: var(--serif);
|
||||
font-weight: 500; font-size: 10.7pt;
|
||||
}
|
||||
.edu-row .major { color: var(--olive); font-size: 9.5pt; }
|
||||
.edu-row .date { color: var(--stone); font-variant-numeric: tabular-nums; font-size: 9pt; }
|
||||
|
||||
/* TABLE (kami-table) */
|
||||
table, .kami-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
font-size: 9pt;
|
||||
margin: 6pt 0;
|
||||
break-inside: avoid;
|
||||
}
|
||||
table th, .kami-table th {
|
||||
text-align: left;
|
||||
font-weight: 500;
|
||||
color: var(--dark-warm);
|
||||
padding: 3pt 6pt;
|
||||
border-bottom: 1pt solid var(--border);
|
||||
background: transparent;
|
||||
}
|
||||
table td, .kami-table td {
|
||||
padding: 2pt 6pt;
|
||||
border-bottom: 0.3pt solid var(--border-soft);
|
||||
vertical-align: top;
|
||||
}
|
||||
table.compact th, .kami-table.compact th { padding: 2pt 4pt; font-size: 8pt; }
|
||||
table.compact td, .kami-table.compact td { padding: 1pt 4pt; font-size: 8pt; line-height: 1.4; }
|
||||
table.financial td:not(:first-child), .kami-table.financial td:not(:first-child) {
|
||||
text-align: right; font-variant-numeric: tabular-nums;
|
||||
}
|
||||
table.financial th:not(:first-child), .kami-table.financial th:not(:first-child) { text-align: right; }
|
||||
table.striped tbody tr:nth-child(even) td, .kami-table.striped tbody tr:nth-child(even) td {
|
||||
background: var(--ivory);
|
||||
}
|
||||
table .total td, .kami-table .total td {
|
||||
font-weight: 500; border-top: 1pt solid var(--brand); border-bottom: none; color: var(--near-black);
|
||||
}
|
||||
|
||||
/* DENSE VARIANT: add class="resume--dense" to <body> when 5+ projects overflow */
|
||||
body.resume--dense { font-size: 9pt; }
|
||||
body.resume--dense .proj-text { line-height: 1.38; }
|
||||
body.resume--dense .tl-body { font-size: 8.5pt; }
|
||||
body.resume--dense .project { padding: 1.6mm 0; }
|
||||
body.resume--dense .section-title { margin-top: 4.2mm; margin-bottom: 1.2mm; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<!-- ========== 1페이지 ========== -->
|
||||
|
||||
<div class="header">
|
||||
<div>
|
||||
<div class="name serif">김도현<span class="alias">Dohyun Kim</span></div>
|
||||
</div>
|
||||
<div class="contact">
|
||||
<span class="role">AI 에이전트 엔지니어 · 창업자</span>
|
||||
<br>
|
||||
<a href="https://github.com/dohyun">GitHub @dohyun</a>
|
||||
<span class="sep">·</span>
|
||||
<a href="mailto:dohyun@example.com">dohyun@example.com</a>
|
||||
<span class="sep">·</span>
|
||||
<span class="loc">31세, 서울</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 핵심 수치 4개 -->
|
||||
<div class="metrics">
|
||||
<div class="metric"><span class="metric-value serif">9<span class="unit">년</span></span><span class="metric-label">엔지니어링 경력</span></div>
|
||||
<div class="metric"><span class="metric-value serif">28K</span><span class="metric-label">GitHub stars</span></div>
|
||||
<div class="metric"><span class="metric-value serif">5</span><span class="metric-label">출시 제품</span></div>
|
||||
<div class="metric"><span class="metric-value serif">12<span class="unit">개국</span></span><span class="metric-label">사용자 분포</span></div>
|
||||
</div>
|
||||
|
||||
<section>
|
||||
<div class="section-title">자기 소개</div>
|
||||
<div class="summary">
|
||||
현재 AI 에이전트 스타트업 <span class="em-brand">Aru</span>의 공동창업자 겸 엔지니어링 리드로, 6인 팀을 이끌며 LLM 기반 업무 자동화 런타임을 설계한다. 백엔드 엔지니어로 출발해 기획부터 배포·운영까지 제품을 혼자 끝까지 만드는 사이클을 여러 번 돌렸고, 그 과정에서 <span class="em-brand">에이전트 런타임</span>, <span class="em-brand">디자인 시스템</span>, <span class="em-brand">한국어 조판</span>, 다국어 제품화에 강점을 쌓았다. 오픈소스로 누적 28K stars를 모으며, 작은 도구를 사용자 손에 닿는 완성품까지 끌고 가는 일을 가장 즐긴다.
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<div class="section-title">경력<span class="sub">2016 - 현재 (백엔드에서 AI 에이전트까지)</span></div>
|
||||
|
||||
<!-- 타임라인 3단계: 장기 성장 신호 -->
|
||||
<div class="timeline">
|
||||
<div class="tl-step">
|
||||
<div class="tl-top"><div class="tl-year serif">2016</div><div class="tl-head">백엔드 엔지니어</div></div>
|
||||
<div class="tl-body">대형 커머스의 서버 인프라와 결제 시스템을 맡아 트래픽과 데이터 정합성 문제를 다뤘다.</div>
|
||||
</div>
|
||||
<div class="tl-step">
|
||||
<div class="tl-top"><div class="tl-year serif">2020</div><div class="tl-head">풀스택 · 제품</div></div>
|
||||
<div class="tl-body">기획·디자인·배포까지 혼자 책임지는 사이클을 익히고 첫 오픈소스 도구를 출시했다.</div>
|
||||
</div>
|
||||
<div class="tl-step">
|
||||
<div class="tl-top"><div class="tl-year serif">2023</div><div class="tl-head">AI 에이전트 창업</div></div>
|
||||
<div class="tl-body">LLM을 실무 워크플로에 연결하는 데 집중해 에이전트 런타임 스타트업을 시작했다.</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 프로젝트 항목 -->
|
||||
<div class="project">
|
||||
<div class="proj-head">
|
||||
<span class="proj-name serif">Aru</span>
|
||||
<span class="proj-kind">· AI 에이전트 플랫폼</span>
|
||||
<span class="proj-role">방향 주도</span>
|
||||
</div>
|
||||
<div class="proj-lines">
|
||||
<div class="proj-row">
|
||||
<div class="proj-label">역할</div>
|
||||
<div class="proj-text">2023년 공동창업. 반복 업무를 자동화하는 에이전트 런타임을 0에서 설계하고, 6인 엔지니어링 팀을 꾸려 제품 방향을 직접 이끌었다.</div>
|
||||
</div>
|
||||
<div class="proj-row">
|
||||
<div class="proj-label">행동</div>
|
||||
<div class="proj-text">도구 호출·장기 메모리·자동 평가 루프를 하나의 런타임으로 묶고, 12개 언어 UI와 온프레미스 배포를 지원해 보안에 민감한 팀까지 끌어들였다.</div>
|
||||
</div>
|
||||
<div class="proj-row">
|
||||
<div class="proj-label">결과</div>
|
||||
<div class="proj-text">출시 8개월 만에 유료 팀 <span class="hl">320곳</span>, 월 처리 작업 <span class="hl">140만 건</span>을 넘겼고 재계약률 <span class="hl">92%</span>를 기록했다.</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="project">
|
||||
<div class="proj-head">
|
||||
<span class="proj-name serif">Nori</span>
|
||||
<span class="proj-kind">· 오픈소스 문서 생성기</span>
|
||||
<span class="proj-role">단독 개발</span>
|
||||
</div>
|
||||
<div class="proj-lines">
|
||||
<div class="proj-row">
|
||||
<div class="proj-label">역할</div>
|
||||
<div class="proj-text">마크다운을 편집 품질의 PDF로 바꾸는 오픈소스 도구. 디자인 제약을 코드로 고정한다는 단순한 아이디어에서 시작했다.</div>
|
||||
</div>
|
||||
<div class="proj-row">
|
||||
<div class="proj-label">행동</div>
|
||||
<div class="proj-text">타이포그래피와 여백 규칙을 토큰으로 정의하고, 한국어 본문용 세리프 조판과 다국어 폰트 폴백을 직접 튜닝해 어떤 언어에서도 깨지지 않게 만들었다.</div>
|
||||
</div>
|
||||
<div class="proj-row">
|
||||
<div class="proj-label">결과</div>
|
||||
<div class="proj-text">GitHub <span class="hl">14K stars</span>, 누적 다운로드 <span class="hl">21만</span>. 콘퍼런스 3곳에서 소개됐고 6개 언어 현지화 PR을 커뮤니티가 자발적으로 기여했다.</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="project">
|
||||
<div class="proj-head">
|
||||
<span class="proj-name serif">Mokpo</span>
|
||||
<span class="proj-kind">· 셀프호스팅 메모 앱</span>
|
||||
<span class="proj-role">단독 개발</span>
|
||||
</div>
|
||||
<div class="proj-lines">
|
||||
<div class="proj-row">
|
||||
<div class="proj-label">역할</div>
|
||||
<div class="proj-text">로컬 우선 셀프호스팅 메모 앱. 데이터 주권과 즉각적인 입력 속도를 동시에 만족시키는 것을 목표로 삼았다.</div>
|
||||
</div>
|
||||
<div class="proj-row">
|
||||
<div class="proj-label">행동</div>
|
||||
<div class="proj-text">오프라인 우선 동기화와 충돌 해결(CRDT)을 직접 구현하고, 1년간 매주 릴리스를 거르지 않으며 사용자 요청을 빠르게 반영했다.</div>
|
||||
</div>
|
||||
<div class="proj-row">
|
||||
<div class="proj-label">결과</div>
|
||||
<div class="proj-text">월 활성 사용자 <span class="hl">4만 명</span>, 12개국에서 사용 중. 유료 전환율 <span class="hl">6%</span>로 1인 운영만으로 흑자를 유지한다.</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="project">
|
||||
<div class="proj-head">
|
||||
<span class="proj-name serif">Gose</span>
|
||||
<span class="proj-kind">· 정적 사이트 빌더</span>
|
||||
<span class="proj-role">단독 개발</span>
|
||||
</div>
|
||||
<div class="proj-lines">
|
||||
<div class="proj-row">
|
||||
<div class="proj-label">역할</div>
|
||||
<div class="proj-text">Go로 만든 정적 사이트 빌더. 제작자가 설정 없이 바로 글쓰기에 집중하도록 돕는 것이 목표였다.</div>
|
||||
</div>
|
||||
<div class="proj-row">
|
||||
<div class="proj-label">행동</div>
|
||||
<div class="proj-text">증분 빌드와 핫 리로드를 직접 구현해 수천 페이지도 1초 내 갱신되게 했고, 별도 설정 없이 바로 쓸 수 있는 기본 테마를 함께 제공했다.</div>
|
||||
</div>
|
||||
<div class="proj-row">
|
||||
<div class="proj-label">결과</div>
|
||||
<div class="proj-text">GitHub <span class="hl">1.1K stars</span>, 개인 블로그와 문서 사이트 다수가 채택. 빌드 속도 벤치마크에서 동급 도구 대비 <span class="hl">3배</span> 빨랐다.</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="project">
|
||||
<div class="proj-head">
|
||||
<span class="proj-name serif">Baram</span>
|
||||
<span class="proj-kind">· macOS 메뉴바 유틸리티</span>
|
||||
<span class="proj-role">단독 개발</span>
|
||||
</div>
|
||||
<div class="proj-lines">
|
||||
<div class="proj-row">
|
||||
<div class="proj-label">역할</div>
|
||||
<div class="proj-text">메뉴바에서 시스템 상태와 자주 쓰는 작업을 한눈에 다루는 macOS 유틸리티. 작은 화면에서의 정보 밀도를 고민했다.</div>
|
||||
</div>
|
||||
<div class="proj-row">
|
||||
<div class="proj-label">행동</div>
|
||||
<div class="proj-text">SwiftUI로 가벼운 메뉴바 앱을 만들고, 배터리·네트워크·단축키를 한 패널에 묶어 클릭 한 번으로 끝나도록 설계했다.</div>
|
||||
</div>
|
||||
<div class="proj-row">
|
||||
<div class="proj-label">결과</div>
|
||||
<div class="proj-text">GitHub <span class="hl">480 stars</span>, 사용자 요청으로 위젯을 12종까지 확장. 디자인 중심 개발자 사이에서 입소문으로 퍼졌다.</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
||||
<!-- ========== 2페이지 ========== -->
|
||||
|
||||
<section class="page-break">
|
||||
<div class="section-title">오픈소스 & 독립 개발<span class="sub">2018 - 현재 · 작게 시작해 끝까지 혼자 출시</span></div>
|
||||
|
||||
<div class="os-intro">
|
||||
<span class="hl">디자인 감각을 갖춘 풀스택 개발자.</span> 작은 도구를 공개로 만들고, 사용자 피드백으로 다듬어 완성까지 끌고 간다. GitHub 누적 <span class="strong">28K stars · 3.1K forks · 1.9K followers</span>.
|
||||
</div>
|
||||
|
||||
<div class="os-grid">
|
||||
<div class="os-item">
|
||||
<span class="os-name serif"><a href="https://github.com/dohyun/aru">Aru</a></span>
|
||||
<span class="os-desc">Python · 에이전트 런타임 · 다국어 지원</span>
|
||||
<span class="os-star big">★ 9.8K</span>
|
||||
</div>
|
||||
<div class="os-item">
|
||||
<span class="os-name serif"><a href="https://github.com/dohyun/nori">Nori</a></span>
|
||||
<span class="os-desc">Rust · 문서 생성기 · PDF 출력</span>
|
||||
<span class="os-star big">★ 14K</span>
|
||||
</div>
|
||||
<div class="os-item">
|
||||
<span class="os-name serif"><a href="https://github.com/dohyun/mokpo">Mokpo</a></span>
|
||||
<span class="os-desc">TypeScript · 로컬 우선 메모 앱</span>
|
||||
<span class="os-star">★ 2.4K</span>
|
||||
</div>
|
||||
<div class="os-item">
|
||||
<span class="os-name serif"><a href="https://github.com/dohyun/gose">Gose</a></span>
|
||||
<span class="os-desc">Go · 정적 사이트 빌더</span>
|
||||
<span class="os-star">★ 1.1K</span>
|
||||
</div>
|
||||
<div class="os-item">
|
||||
<span class="os-name serif"><a href="https://github.com/dohyun/baram">Baram</a></span>
|
||||
<span class="os-desc">Swift · macOS 메뉴바 유틸리티</span>
|
||||
<span class="os-star">★ 480</span>
|
||||
</div>
|
||||
<div class="os-item">
|
||||
<span class="os-name serif"><a href="https://github.com/dohyun/saebyeok">Saebyeok</a></span>
|
||||
<span class="os-desc">CSS · 따뜻한 다크 테마 모음</span>
|
||||
<span class="os-star">★ 320</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="os-highlight">
|
||||
<span class="tag">전파</span>Nori는 출시 첫 주에 Hacker News 1면에 올랐고, 일본과 독일 개발자들이 자발적으로 현지화 PR을 보내며 6개 언어로 확장됐다.
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<div class="section-title">AI 판단과 행동</div>
|
||||
<div class="convictions">
|
||||
<div class="conv-card">
|
||||
<div class="conv-head"><span class="year">2019</span>회사를 떠나 단독 개발</div>
|
||||
<div class="conv-body">
|
||||
안정적인 시니어 자리를 정리하고 1년간 직접 만든 도구의 사용자 반응만으로 다음을 결정했다. 그해 Nori가 손익분기를 넘겼다.
|
||||
</div>
|
||||
</div>
|
||||
<div class="conv-card">
|
||||
<div class="conv-head"><span class="year">2021</span>한국어 조판에 집중</div>
|
||||
<div class="conv-body">
|
||||
영어 우선 도구들이 한국어를 후순위로 둘 때 한국어 본문 가독성을 1순위로 잡았다. 국내 제작자 커뮤니티의 표준 도구가 됐다.
|
||||
</div>
|
||||
</div>
|
||||
<div class="conv-card">
|
||||
<div class="conv-head"><span class="year">2023</span>모델보다 워크플로</div>
|
||||
<div class="conv-body">
|
||||
모델 경쟁이 과열될 때 자체 모델 대신 워크플로 연결에 베팅했다. 18개월 만에 유료 팀 <span class="hl">300곳</span>을 확보했다.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<div class="section-title">외부 영향력</div>
|
||||
|
||||
<div class="handle-strip">
|
||||
<span class="handle serif">블로그 · <a href="https://dohyun.dev">@dohyun.dev</a></span>
|
||||
<span class="follower">2.1만 구독</span>
|
||||
<span class="desc">에이전트 설계와 한국어 조판을 다루는 주간 뉴스레터.</span>
|
||||
</div>
|
||||
|
||||
<div class="impact-grid">
|
||||
<div>
|
||||
<div class="inf-block-title">대표 기술 장문<span class="small">에이전트 · 조판</span></div>
|
||||
<div class="art-row">
|
||||
<div class="art-header">
|
||||
<a class="art-title" href="https://dohyun.dev/agent-runtime">《에이전트 런타임을 0에서 만들기》</a>
|
||||
<span class="art-date">2024.03</span>
|
||||
</div>
|
||||
<div class="art-stats">조회 <span class="em-brand">38만</span>, 국내 기술 블로그 연간 인기글</div>
|
||||
</div>
|
||||
<div class="art-row">
|
||||
<div class="art-header">
|
||||
<a class="art-title" href="https://dohyun.dev/ko-typography">《한국어 본문 조판의 디테일》</a>
|
||||
<span class="art-date">2022.09</span>
|
||||
</div>
|
||||
<div class="art-stats">조회 12만, 디자인 커뮤니티 추천</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div class="inf-block-title">초청 강연<span class="small">콘퍼런스</span></div>
|
||||
<div class="talk-row">
|
||||
<div>
|
||||
<div class="talk-title">《작은 팀을 위한 에이전트 설계》</div>
|
||||
<div class="talk-venue">PyCon Korea</div>
|
||||
</div>
|
||||
<div class="talk-date">2024.10</div>
|
||||
</div>
|
||||
<div class="talk-row">
|
||||
<div>
|
||||
<div class="talk-title">《제약이 디자인을 만든다》</div>
|
||||
<div class="talk-venue">FEConf</div>
|
||||
</div>
|
||||
<div class="talk-date">2023.11</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<div class="section-title">핵심 역량</div>
|
||||
<div class="skill-row">
|
||||
<div class="skill-label">에이전트 설계</div>
|
||||
<div class="skill-body">LLM 도구 호출, 메모리, 평가 루프 설계. <span class="em-brand">프로덕션 에이전트 런타임</span>을 직접 구축.</div>
|
||||
</div>
|
||||
<div class="skill-row">
|
||||
<div class="skill-label">백엔드</div>
|
||||
<div class="skill-body">Python · Go · Rust. 대규모 작업 큐와 <span class="em-brand">분산 처리</span> 경험.</div>
|
||||
</div>
|
||||
<div class="skill-row">
|
||||
<div class="skill-label">프론트 ·<br>디자인 시스템</div>
|
||||
<div class="skill-body">TypeScript · React. <span class="em-brand">토큰 기반 디자인 시스템</span>으로 일관된 제품 UI.</div>
|
||||
</div>
|
||||
<div class="skill-row">
|
||||
<div class="skill-label">한국어 조판</div>
|
||||
<div class="skill-body">세리프 본문 가독성, 자간 · 행간 튜닝. <span class="em-brand">다국어 폰트 폴백</span> 설계.</div>
|
||||
</div>
|
||||
<div class="skill-row">
|
||||
<div class="skill-label">제품 · 출시</div>
|
||||
<div class="skill-body">기획부터 배포 · 마케팅까지 단독 사이클. <span class="em-brand">3개 제품</span> 흑자 전환.</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="no-break">
|
||||
<div class="section-title">학력</div>
|
||||
<div class="edu-row">
|
||||
<div>
|
||||
<span class="school serif">한국과학기술원 (KAIST)</span>
|
||||
<span class="major"> · 전산학부 · 컴퓨터공학 · 대학원 대신 바로 창업 전선으로</span>
|
||||
</div>
|
||||
<div class="date">2012 - 2016</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
After Width: | Height: | Size: 472 KiB |
@@ -0,0 +1,577 @@
|
||||
<!DOCTYPE html>
|
||||
<!-- ==================================================================
|
||||
特斯拉 Q1 2026 投资分析 · 基于 equity-report.html 模板
|
||||
数据来源:Tesla Q1 2026 财报,2026.04.22 发布
|
||||
================================================================== -->
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>特斯拉 · Q1 2026 投资分析</title>
|
||||
<meta name="author" content="Tw93">
|
||||
<meta name="description" content="特斯拉 Q1 2026 财报分析:营收 $22.4B 超预期,毛利率恢复至 21.1%,Robotaxi 落地 Dallas/Houston,$25B 资本开支指引引发市场分歧">
|
||||
<meta name="keywords" content="特斯拉, TSLA, Q1 2026, 财报, 投资分析, Robotaxi, 电动车">
|
||||
<meta name="generator" content="Kami">
|
||||
<style>
|
||||
@font-face {
|
||||
font-family: "TsangerJinKai02";
|
||||
src: url("../fonts/TsangerJinKai02-W04.ttf") format("truetype"),
|
||||
url("https://cdn.jsdelivr.net/gh/tw93/Kami@main/assets/fonts/TsangerJinKai02-W04.ttf") format("truetype");
|
||||
font-weight: 400;
|
||||
font-style: normal;
|
||||
}
|
||||
@font-face {
|
||||
font-family: "TsangerJinKai02";
|
||||
src: url("../fonts/TsangerJinKai02-W05.ttf") format("truetype"),
|
||||
url("https://cdn.jsdelivr.net/gh/tw93/Kami@main/assets/fonts/TsangerJinKai02-W05.ttf") format("truetype");
|
||||
font-weight: 500;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
@page {
|
||||
size: A4;
|
||||
margin: 12mm 16mm 12mm 16mm;
|
||||
background: #f5f4ed;
|
||||
|
||||
@bottom-center {
|
||||
content: counter(page) " · 特斯拉 Q1 2026 投资分析";
|
||||
font-family: "TsangerJinKai02", "Source Han Serif SC",
|
||||
"Noto Serif CJK SC", "Songti SC", Georgia, serif;
|
||||
font-size: 9pt;
|
||||
color: #6b6a64;
|
||||
}
|
||||
}
|
||||
|
||||
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
|
||||
:root {
|
||||
--parchment: #f5f4ed;
|
||||
--ivory: #faf9f5;
|
||||
--near-black:#141413;
|
||||
--dark-warm: #3d3d3a;
|
||||
--olive: #504e49;
|
||||
--stone: #6b6a64;
|
||||
--brand: #1B365D;
|
||||
--border: #e8e6dc;
|
||||
--border-soft:#e5e3d8;
|
||||
--tag-bg: #E4ECF5;
|
||||
--serif: "TsangerJinKai02", "Source Han Serif SC", "Noto Serif CJK SC", "Songti SC", "STSong", Georgia, serif;
|
||||
--sans: var(--serif);
|
||||
}
|
||||
|
||||
html, body { background: var(--parchment); }
|
||||
|
||||
@media screen {
|
||||
body { max-width: 210mm; margin: 0 auto; padding: 12mm 16mm; }
|
||||
}
|
||||
|
||||
body {
|
||||
color: var(--near-black);
|
||||
font-family: var(--serif);
|
||||
font-size: 9.5pt;
|
||||
line-height: 1.42;
|
||||
letter-spacing: 0.3pt;
|
||||
}
|
||||
|
||||
strong { font-weight: 500; }
|
||||
.hl { color: var(--brand); font-weight: 500; }
|
||||
|
||||
/* ========== HEADER ========== */
|
||||
.report-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
margin-bottom: 12pt;
|
||||
padding-bottom: 10pt;
|
||||
border-bottom: 0.5pt solid var(--border);
|
||||
}
|
||||
.ticker-block { flex: 1; }
|
||||
.ticker-eyebrow {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 7pt;
|
||||
font-size: 9pt;
|
||||
color: var(--brand);
|
||||
letter-spacing: 1.5pt;
|
||||
text-transform: uppercase;
|
||||
font-weight: 500;
|
||||
margin-bottom: 4pt;
|
||||
}
|
||||
.ticker-eyebrow::before {
|
||||
content: "";
|
||||
width: 8pt;
|
||||
height: 1.5pt;
|
||||
border-radius: 0.75pt;
|
||||
background: var(--brand);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.ticker-name {
|
||||
font-family: var(--serif);
|
||||
font-size: 24pt;
|
||||
font-weight: 500;
|
||||
color: var(--near-black);
|
||||
line-height: 1.15;
|
||||
margin-bottom: 4pt;
|
||||
}
|
||||
.ticker-sub {
|
||||
font-size: 10pt;
|
||||
color: var(--olive);
|
||||
line-height: 1.4;
|
||||
}
|
||||
.price-block {
|
||||
text-align: right;
|
||||
padding-top: 4pt;
|
||||
}
|
||||
.price-current {
|
||||
font-family: var(--serif);
|
||||
font-size: 28pt;
|
||||
font-weight: 500;
|
||||
color: var(--near-black);
|
||||
line-height: 1;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
.price-change {
|
||||
font-size: 10pt;
|
||||
font-weight: 500;
|
||||
margin-top: 2pt;
|
||||
font-variant-numeric: tabular-nums;
|
||||
color: var(--stone);
|
||||
}
|
||||
.price-date {
|
||||
font-size: 9pt;
|
||||
color: var(--stone);
|
||||
margin-top: 2pt;
|
||||
}
|
||||
|
||||
/* ========== METRICS ========== */
|
||||
.metrics {
|
||||
display: flex;
|
||||
gap: 14pt;
|
||||
margin-bottom: 14pt;
|
||||
padding: 4pt 0;
|
||||
border-top: 0.3pt dotted var(--border);
|
||||
border-bottom: 0.3pt dotted var(--border);
|
||||
}
|
||||
.metric {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
gap: 4pt;
|
||||
}
|
||||
.metric-value {
|
||||
font-family: var(--serif);
|
||||
font-size: 16pt;
|
||||
font-weight: 500;
|
||||
color: var(--brand);
|
||||
line-height: 1;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
.metric-label {
|
||||
font-size: 9pt;
|
||||
color: var(--olive);
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
/* ========== SECTIONS ========== */
|
||||
h2 {
|
||||
font-family: var(--serif);
|
||||
font-size: 15pt;
|
||||
font-weight: 500;
|
||||
color: var(--near-black);
|
||||
margin: 9pt 0 3pt 0;
|
||||
break-after: avoid;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: 12pt;
|
||||
font-weight: 500;
|
||||
color: var(--dark-warm);
|
||||
margin: 6pt 0 2pt 0;
|
||||
break-after: avoid;
|
||||
}
|
||||
|
||||
p { margin: 0 0 4pt 0; line-height: 1.42; }
|
||||
|
||||
ul, ol {
|
||||
margin: 4pt 0 8pt 0;
|
||||
padding-left: 18pt;
|
||||
line-height: 1.45;
|
||||
}
|
||||
ul li { margin-bottom: 1pt; }
|
||||
ul li::marker { color: var(--brand); }
|
||||
|
||||
/* ========== TABLE ========== */
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
font-size: 9.5pt;
|
||||
margin: 8pt 0;
|
||||
break-inside: avoid;
|
||||
}
|
||||
table th {
|
||||
text-align: left;
|
||||
font-weight: 500;
|
||||
color: var(--dark-warm);
|
||||
padding: 5pt 8pt;
|
||||
border-bottom: 1pt solid var(--border);
|
||||
background: transparent;
|
||||
}
|
||||
table td {
|
||||
padding: 5pt 8pt;
|
||||
border-bottom: 0.3pt solid var(--border-soft);
|
||||
vertical-align: top;
|
||||
}
|
||||
table.financial td:not(:first-child) { text-align: right; font-variant-numeric: tabular-nums; }
|
||||
table.financial th:not(:first-child) { text-align: right; }
|
||||
table.striped tbody tr:nth-child(even) td { background: var(--ivory); }
|
||||
table.compact th { padding: 4pt 6pt; font-size: 8.5pt; }
|
||||
table.compact td { padding: 3pt 6pt; font-size: 8.5pt; line-height: 1.4; }
|
||||
|
||||
/* ========== CALLOUT ========== */
|
||||
.callout {
|
||||
background: var(--ivory);
|
||||
border-left: 2pt solid var(--brand);
|
||||
padding: 8pt 12pt;
|
||||
border-radius: 3pt;
|
||||
margin: 8pt 0;
|
||||
line-height: 1.42;
|
||||
break-inside: avoid;
|
||||
}
|
||||
|
||||
/* ========== FIGURE ========== */
|
||||
figure { margin: 8pt 0; break-inside: avoid; }
|
||||
figure svg { width: 100%; height: auto; max-height: 70mm; }
|
||||
figcaption {
|
||||
font-size: 9pt;
|
||||
color: var(--stone);
|
||||
margin-top: 4pt;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* ========== RISK MATRIX ========== */
|
||||
.risk-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 8pt;
|
||||
margin: 6pt 0;
|
||||
}
|
||||
.risk-item {
|
||||
padding: 5pt 9pt;
|
||||
background: var(--ivory);
|
||||
border-radius: 3pt;
|
||||
break-inside: avoid;
|
||||
}
|
||||
.risk-label {
|
||||
font-size: 9pt;
|
||||
font-weight: 500;
|
||||
color: var(--brand);
|
||||
margin-bottom: 2pt;
|
||||
}
|
||||
.risk-desc {
|
||||
font-size: 9pt;
|
||||
color: var(--olive);
|
||||
line-height: 1.42;
|
||||
}
|
||||
|
||||
/* ========== ANALYST BOX ========== */
|
||||
.analyst-box {
|
||||
background: var(--ivory);
|
||||
border: 0.5pt solid var(--border);
|
||||
border-radius: 4pt;
|
||||
padding: 8pt 12pt;
|
||||
margin: 6pt 0;
|
||||
break-inside: avoid;
|
||||
}
|
||||
.analyst-label {
|
||||
font-size: 9pt;
|
||||
color: var(--brand);
|
||||
letter-spacing: 0.5pt;
|
||||
text-transform: uppercase;
|
||||
margin-bottom: 4pt;
|
||||
}
|
||||
|
||||
/* ========== FOOTER ========== */
|
||||
.report-footer {
|
||||
margin-top: 6pt;
|
||||
padding-top: 4pt;
|
||||
border-top: 0.3pt dotted var(--border);
|
||||
font-size: 9pt;
|
||||
color: var(--stone);
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<!-- ========== HEADER ========== -->
|
||||
<div class="report-header">
|
||||
<div class="ticker-block">
|
||||
<div class="ticker-eyebrow">Q1 2026 财报点评</div>
|
||||
<div class="ticker-name">特斯拉 <span style="font-size: 14pt; color: var(--stone);">TSLA</span></div>
|
||||
<div class="ticker-sub">汽车与能源 · NASDAQ · 利润率在恢复,但资本开支的账单到了</div>
|
||||
</div>
|
||||
<div class="price-block">
|
||||
<div class="price-current">$373.72</div>
|
||||
<div class="price-change">财报后 -3.56%</div>
|
||||
<div class="price-date">2026.04.23 收盘</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ========== METRICS ========== -->
|
||||
<div class="metrics">
|
||||
<div class="metric">
|
||||
<div class="metric-value">$1.2T</div>
|
||||
<div class="metric-label">市值</div>
|
||||
</div>
|
||||
<div class="metric">
|
||||
<div class="metric-value">~220x</div>
|
||||
<div class="metric-label">前瞻 P/E</div>
|
||||
</div>
|
||||
<div class="metric">
|
||||
<div class="metric-value">$22.4B</div>
|
||||
<div class="metric-label">Q1 营收</div>
|
||||
</div>
|
||||
<div class="metric">
|
||||
<div class="metric-value">21.1%</div>
|
||||
<div class="metric-label">毛利率</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ========== 投资逻辑 ========== -->
|
||||
<h2>投资逻辑</h2>
|
||||
<p>特斯拉 Q1 2026 营收 <span class="hl">$22.39B(同比 +16%)</span>,超市场预期的 $21.92B;Non-GAAP EPS <span class="hl">$0.41,高于预期的 $0.37</span>。毛利率从去年同期的 16.3% 恢复到 21.1%(+478 bps),能源业务毛利率创历史新高 39.5%。汽车业务释放了矛盾信号:交付 358,023 辆,低于市场预期约 7,600 辆,但利润率扩张的叙事依然成立。</p>
|
||||
|
||||
<p>核心矛盾:短期利润率恢复 vs 2026 全年 $25B+ 资本开支承诺。管理层预期全年自由现金流转负。财报后股价下跌 3.56%,市场定价的是资本开支冲击而非盈利超预期。</p>
|
||||
|
||||
<div class="callout">
|
||||
<strong>持有。</strong>Q1 利润率恢复验证了降本能力,但 $25B capex + 负 FCF 指引意味着当前股价定价了 AI/Robotaxi 的期权价值,而这部分尚无规模化收入贡献。建议等 Q2 交付数据和 Robotaxi 单位经济学落地后再做加仓决策。
|
||||
</div>
|
||||
|
||||
<!-- ========== K 线图 ========== -->
|
||||
<h2>近期价格走势</h2>
|
||||
<figure>
|
||||
<svg viewBox="0 0 760 270" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect width="100%" height="100%" fill="#f5f4ed"/>
|
||||
|
||||
<!-- 网格:价格区间 335-410,图表区 x=56 y=20 w=660 h=220,基线 y=240 -->
|
||||
<!-- scale: 1 dollar = 2.93px (220/75) -->
|
||||
<line x1="56" y1="240" x2="716" y2="240" stroke="#e8e7e1" stroke-width="0.8"/>
|
||||
<line x1="56" y1="211" x2="716" y2="211" stroke="#e8e7e1" stroke-width="0.8"/>
|
||||
<line x1="56" y1="181" x2="716" y2="181" stroke="#e8e7e1" stroke-width="0.8"/>
|
||||
<line x1="56" y1="152" x2="716" y2="152" stroke="#e8e7e1" stroke-width="0.8"/>
|
||||
<line x1="56" y1="123" x2="716" y2="123" stroke="#e8e7e1" stroke-width="0.8"/>
|
||||
<line x1="56" y1="93" x2="716" y2="93" stroke="#e8e7e1" stroke-width="0.8"/>
|
||||
<line x1="56" y1="64" x2="716" y2="64" stroke="#e8e7e1" stroke-width="0.8"/>
|
||||
<line x1="56" y1="35" x2="716" y2="35" stroke="#e8e7e1" stroke-width="0.8"/>
|
||||
|
||||
<!-- Y 轴标签 -->
|
||||
<text x="48" y="244" fill="#6b6a64" font-size="9" text-anchor="end" font-family="Charter,Georgia,serif">335</text>
|
||||
<text x="48" y="215" fill="#6b6a64" font-size="9" text-anchor="end" font-family="Charter,Georgia,serif">345</text>
|
||||
<text x="48" y="185" fill="#6b6a64" font-size="9" text-anchor="end" font-family="Charter,Georgia,serif">355</text>
|
||||
<text x="48" y="156" fill="#6b6a64" font-size="9" text-anchor="end" font-family="Charter,Georgia,serif">365</text>
|
||||
<text x="48" y="127" fill="#6b6a64" font-size="9" text-anchor="end" font-family="Charter,Georgia,serif">375</text>
|
||||
<text x="48" y="97" fill="#6b6a64" font-size="9" text-anchor="end" font-family="Charter,Georgia,serif">385</text>
|
||||
<text x="48" y="68" fill="#6b6a64" font-size="9" text-anchor="end" font-family="Charter,Georgia,serif">395</text>
|
||||
<text x="48" y="39" fill="#6b6a64" font-size="9" text-anchor="end" font-family="Charter,Georgia,serif">405</text>
|
||||
|
||||
<!-- 20 K 线, y = 240 - (price-335)*2.93 -->
|
||||
|
||||
<!-- Day 1 Mar 26: O=381.60 H=384.44 L=371.87 C=372.11 DOWN -->
|
||||
<line x1="72" y1="95" x2="72" y2="132" stroke="#6b6a64" stroke-width="1.2"/>
|
||||
<rect x="64" y="103" width="16" height="27" fill="#6b6a64" rx="1"/>
|
||||
|
||||
<!-- Day 2 Mar 27: O=369.69 H=369.86 L=359.47 C=361.83 DOWN -->
|
||||
<line x1="105" y1="138" x2="105" y2="168" stroke="#6b6a64" stroke-width="1.2"/>
|
||||
<rect x="97" y="138" width="16" height="23" fill="#6b6a64" rx="1"/>
|
||||
|
||||
<!-- Day 3 Mar 30: O=365.86 H=367.29 L=352.14 C=355.28 DOWN -->
|
||||
<line x1="138" y1="146" x2="138" y2="190" stroke="#6b6a64" stroke-width="1.2"/>
|
||||
<rect x="130" y="149" width="16" height="31" fill="#6b6a64" rx="1"/>
|
||||
|
||||
<!-- Day 4 Mar 31: O=361.51 H=373.33 L=361.00 C=371.75 UP -->
|
||||
<line x1="171" y1="128" x2="171" y2="164" stroke="#1B365D" stroke-width="1.2"/>
|
||||
<rect x="163" y="132" width="16" height="30" fill="#1B365D" rx="1"/>
|
||||
|
||||
<!-- Day 5 Apr 1: O=378.63 H=383.14 L=374.08 C=381.26 UP -->
|
||||
<line x1="204" y1="99" x2="204" y2="126" stroke="#1B365D" stroke-width="1.2"/>
|
||||
<rect x="196" y="104" width="16" height="15" fill="#1B365D" rx="1"/>
|
||||
|
||||
<!-- Day 6 Apr 2: O=364.20 H=370.28 L=359.03 C=360.59 DOWN -->
|
||||
<line x1="237" y1="137" x2="237" y2="169" stroke="#6b6a64" stroke-width="1.2"/>
|
||||
<rect x="229" y="143" width="16" height="18" fill="#6b6a64" rx="1"/>
|
||||
|
||||
<!-- Day 7 Apr 6: O=362.59 H=367.72 L=346.64 C=352.82 DOWN -->
|
||||
<line x1="270" y1="144" x2="270" y2="206" stroke="#6b6a64" stroke-width="1.2"/>
|
||||
<rect x="262" y="149" width="16" height="40" fill="#6b6a64" rx="1"/>
|
||||
|
||||
<!-- Day 8 Apr 7: O=346.44 H=348.02 L=337.24 C=346.65 UP -->
|
||||
<line x1="303" y1="202" x2="303" y2="233" stroke="#1B365D" stroke-width="1.2"/>
|
||||
<rect x="295" y="202" width="16" height="15" fill="#1B365D" rx="1"/>
|
||||
|
||||
<!-- Day 9 Apr 8: O=363.79 H=364.50 L=339.67 C=343.25 DOWN -->
|
||||
<line x1="336" y1="154" x2="336" y2="226" stroke="#6b6a64" stroke-width="1.2"/>
|
||||
<rect x="328" y="154" width="16" height="60" fill="#6b6a64" rx="1"/>
|
||||
|
||||
<!-- Day 10 Apr 9: O=343.15 H=348.88 L=337.25 C=345.62 UP -->
|
||||
<line x1="369" y1="199" x2="369" y2="233" stroke="#1B365D" stroke-width="1.2"/>
|
||||
<rect x="361" y="209" width="16" height="17" fill="#1B365D" rx="1"/>
|
||||
|
||||
<!-- Day 11 Apr 10: O=346.29 H=350.36 L=342.74 C=348.95 UP -->
|
||||
<line x1="402" y1="195" x2="402" y2="217" stroke="#1B365D" stroke-width="1.2"/>
|
||||
<rect x="394" y="199" width="16" height="13" fill="#1B365D" rx="1"/>
|
||||
|
||||
<!-- Day 12 Apr 13: O=350.07 H=356.35 L=348.57 C=352.42 UP -->
|
||||
<line x1="435" y1="177" x2="435" y2="200" stroke="#1B365D" stroke-width="1.2"/>
|
||||
<rect x="427" y="189" width="16" height="7" fill="#1B365D" rx="1"/>
|
||||
|
||||
<!-- Day 13 Apr 14: O=357.67 H=367.63 L=354.77 C=364.20 UP -->
|
||||
<line x1="468" y1="144" x2="468" y2="182" stroke="#1B365D" stroke-width="1.2"/>
|
||||
<rect x="460" y="154" width="16" height="19" fill="#1B365D" rx="1"/>
|
||||
|
||||
<!-- Day 14 Apr 15: O=366.83 H=394.65 L=362.50 C=391.95 UP -->
|
||||
<line x1="501" y1="65" x2="501" y2="159" stroke="#1B365D" stroke-width="1.2"/>
|
||||
<rect x="493" y="73" width="16" height="74" fill="#1B365D" rx="1"/>
|
||||
|
||||
<!-- Day 15 Apr 16: O=393.81 H=394.06 L=381.80 C=388.90 DOWN -->
|
||||
<line x1="534" y1="67" x2="534" y2="103" stroke="#6b6a64" stroke-width="1.2"/>
|
||||
<rect x="526" y="68" width="16" height="14" fill="#6b6a64" rx="1"/>
|
||||
|
||||
<!-- Day 16 Apr 17: O=395.92 H=409.28 L=391.65 C=400.62 UP -->
|
||||
<line x1="567" y1="23" x2="567" y2="74" stroke="#1B365D" stroke-width="1.2"/>
|
||||
<rect x="559" y="48" width="16" height="14" fill="#1B365D" rx="1"/>
|
||||
|
||||
<!-- Day 17 Apr 20: O=402.58 H=406.80 L=388.33 C=392.50 DOWN -->
|
||||
<line x1="600" y1="30" x2="600" y2="84" stroke="#6b6a64" stroke-width="1.2"/>
|
||||
<rect x="592" y="42" width="16" height="30" fill="#6b6a64" rx="1"/>
|
||||
|
||||
<!-- Day 18 Apr 21: O=393.00 H=393.95 L=385.22 C=386.42 DOWN -->
|
||||
<line x1="633" y1="67" x2="633" y2="94" stroke="#6b6a64" stroke-width="1.2"/>
|
||||
<rect x="625" y="70" width="16" height="19" fill="#6b6a64" rx="1"/>
|
||||
|
||||
<!-- Day 19 Apr 22: O=387.57 H=393.01 L=385.30 C=387.51 DOWN -->
|
||||
<line x1="666" y1="70" x2="666" y2="94" stroke="#6b6a64" stroke-width="1.2"/>
|
||||
<rect x="658" y="86" width="16" height="4" fill="#6b6a64" rx="1"/>
|
||||
|
||||
<!-- Day 20 Apr 23: O=375.28 H=386.00 L=368.39 C=373.72 DOWN -->
|
||||
<line x1="699" y1="91" x2="699" y2="143" stroke="#6b6a64" stroke-width="1.2"/>
|
||||
<rect x="691" y="101" width="16" height="22" fill="#6b6a64" rx="1"/>
|
||||
|
||||
<!-- X 轴 -->
|
||||
<text x="72" y="260" fill="#6b6a64" font-size="8" text-anchor="middle" font-family="Charter,Georgia,serif">3/26</text>
|
||||
<text x="237" y="260" fill="#6b6a64" font-size="8" text-anchor="middle" font-family="Charter,Georgia,serif">4/2</text>
|
||||
<text x="402" y="260" fill="#6b6a64" font-size="8" text-anchor="middle" font-family="Charter,Georgia,serif">4/10</text>
|
||||
<text x="567" y="260" fill="#6b6a64" font-size="8" text-anchor="middle" font-family="Charter,Georgia,serif">4/17</text>
|
||||
<text x="699" y="260" fill="#6b6a64" font-size="8" text-anchor="middle" font-family="Charter,Georgia,serif">4/23</text>
|
||||
|
||||
<!-- 财报标注 -->
|
||||
<line x1="682" y1="24" x2="682" y2="82" stroke="#1B365D" stroke-width="0.8" stroke-dasharray="3,2"/>
|
||||
<text x="680" y="20" fill="#1B365D" font-size="8" text-anchor="end" font-weight="500" font-family="Charter,Georgia,serif">Earnings</text>
|
||||
|
||||
<!-- 图例 -->
|
||||
<line x1="56" y1="280" x2="716" y2="280" stroke="#e8e7e1" stroke-width="0.8"/>
|
||||
<rect x="56" y="290" width="10" height="10" fill="#1B365D" rx="2"/>
|
||||
<text x="72" y="299" fill="#504e49" font-size="9" font-family="Charter,Georgia,serif">Up</text>
|
||||
<rect x="120" y="290" width="10" height="10" fill="#6b6a64" rx="2"/>
|
||||
<text x="136" y="299" fill="#504e49" font-size="9" font-family="Charter,Georgia,serif">Down</text>
|
||||
<text x="240" y="299" fill="#504e49" font-size="9" font-family="Charter,Georgia,serif">20 trading days | $337 - $409</text>
|
||||
</svg>
|
||||
<figcaption>图 1. TSLA 近 20 个交易日走势(3/26 - 4/23)。4/17 冲高 $409 后回落,财报后因 capex 指引下跌。</figcaption>
|
||||
</figure>
|
||||
|
||||
<!-- ========== 财务概览 ========== -->
|
||||
<h2>财务概览</h2>
|
||||
<table class="financial striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>指标</th>
|
||||
<th>FY2024</th>
|
||||
<th>FY2025</th>
|
||||
<th>Q1 2026</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr><td>营收</td><td>$97.7B</td><td>$97.0B</td><td>$22.39B</td></tr>
|
||||
<tr><td>GAAP 净利润</td><td>$7.1B</td><td>~$3.0B</td><td>$477M</td></tr>
|
||||
<tr><td>GAAP EPS</td><td>$2.04</td><td>~$0.86</td><td>$0.13</td></tr>
|
||||
<tr><td>Non-GAAP EPS</td><td>--</td><td>--</td><td>$0.41</td></tr>
|
||||
<tr><td>毛利率</td><td>17.9%</td><td>~17%</td><td>21.1%</td></tr>
|
||||
<tr><td>经营利润率</td><td>7.2%</td><td>~4%</td><td>4.2%</td></tr>
|
||||
<tr><td>自由现金流</td><td>--</td><td>--</td><td>$1.4B</td></tr>
|
||||
<tr><td>资本开支</td><td>--</td><td>--</td><td>$2.49B</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<p>毛利率恢复到 <span class="hl">21.1%</span> 是本季最重要的数字,COGS 优化和能源业务混合效应驱动了扩张。但 Electrek 指出,Q1 经营利润增长有一部分来自保修准备金调整和关税退税的一次性收益,意味着底层利润率改善可能更接近 19-20%。</p>
|
||||
|
||||
<!-- ========== 分部表现 ========== -->
|
||||
<h2>分部表现</h2>
|
||||
|
||||
<h3>汽车业务</h3>
|
||||
<p>交付 <span class="hl">358,023</span> 辆,低于市场预期 365,645 辆。产量 408,386 辆超过交付约 5 万辆,库存在积累。Model 3/Y 贡献 341,893 辆,Cybertruck 及其他车型 16,130 辆。监管积分收入 $380M(占汽车收入 1.9%),占比继续下降(去年同期 3.7%),说明核心业务的利润质量在提升。</p>
|
||||
|
||||
<h3>能源业务</h3>
|
||||
<p>能源收入 <span class="hl">$2.41B</span>,同比下降 12%(去年同期 $2.73B 为高基数),但毛利率创历史新高 <span class="hl">39.5%</span>。储能部署 8.8 GWh。这个业务正在成为利润发动机:39.5% 毛利率 vs 汽车业务的 ~17%。Megapack 3 量产预计 2026 年下半年启动。</p>
|
||||
|
||||
<!-- ========== 催化剂 ========== -->
|
||||
<h2>近期催化剂</h2>
|
||||
<p><strong>Robotaxi(Q2-Q3):</strong>4 月在 Dallas/Houston 上线无人驾驶付费服务,零事故。付费里程环比翻倍,Q2 首次披露单位经济学。</p>
|
||||
<p><strong>FSD 国际化(Q3):</strong>荷兰和中国已获批,欧洲更大范围推广预计 Q3。FSD 转纯订阅,Q1 新增订阅创纪录。</p>
|
||||
<p><strong>Cybercab + Q2 交付:</strong>Cybercab H2 量产,预期取代 Model Y 成为自动驾驶网络主力。Q2 交付受益于 Model Y 产线爬坡。</p>
|
||||
|
||||
<!-- ========== 竞争格局 ========== -->
|
||||
<h2>竞争格局</h2>
|
||||
|
||||
<table class="financial compact striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>公司</th>
|
||||
<th>市值</th>
|
||||
<th>前瞻 P/E</th>
|
||||
<th>营收增速</th>
|
||||
<th>毛利率</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr><td><strong>特斯拉</strong></td><td>$1.2T</td><td>~220x</td><td>+16%</td><td>21.1%</td></tr>
|
||||
<tr><td>比亚迪</td><td>~$120B</td><td>~22x</td><td>+33%</td><td>~22%</td></tr>
|
||||
<tr><td>Rivian</td><td>~$18B</td><td>N/A</td><td>+8%</td><td>-10%</td></tr>
|
||||
<tr><td>GM(EV)</td><td>$48B</td><td>~5x</td><td>+12%</td><td>~8%</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<p>特斯拉前瞻 P/E 是比亚迪的 <span class="hl">10 倍</span>,但营收增速更低、毛利率接近。溢价完全建立在 AI/Robotaxi 期权价值上。向自动驾驶和储能的战略转型是要跳出单位销量竞争,但如果 Robotaxi 单位经济学在 Q2-Q3 未能验证,估值有明显压缩空间。</p>
|
||||
|
||||
<!-- ========== 风险 + 总结 ========== -->
|
||||
<h2>风险与总结</h2>
|
||||
<div class="risk-grid">
|
||||
<div class="risk-item">
|
||||
<div class="risk-label">资本开支</div>
|
||||
<div class="risk-desc">$25B+ capex 指引,全年 FCF 预计转负。AI/Robotaxi 变现延迟将损害资本配置信任度。</div>
|
||||
</div>
|
||||
<div class="risk-item">
|
||||
<div class="risk-label">交付疲软</div>
|
||||
<div class="risk-desc">年度交付连续两年下滑(峰值 181 万 -> 164 万)。Q1 再次 miss 共识预期。</div>
|
||||
</div>
|
||||
<div class="risk-item">
|
||||
<div class="risk-label">HW3 淘汰</div>
|
||||
<div class="risk-desc">HW3 车辆无法支持无监督 FSD。2024 年前车主面临硬件升级壁垒。</div>
|
||||
</div>
|
||||
<div class="risk-item">
|
||||
<div class="risk-label">竞争与关税</div>
|
||||
<div class="risk-desc">比亚迪 $20-30K 价位段成本优势 35%。美国关税政策增加供应链不确定性。</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="analyst-box">
|
||||
<div class="analyst-label">总结</div>
|
||||
利润率恢复了,营收超预期了,但股价跌了,因为 $25B capex + 负 FCF 在 Robotaxi 尚无规模收入时是硬伤。多头核心:Dallas/Houston Robotaxi 数据会在 Q2 验证单位经济学并触发重估。空头核心:$25B/年烧在 3-5 年才能变现的项目上,而核心汽车业务在萎缩。已持有者:继续持有到 Q2,等 Robotaxi 数据再评估。新建仓:建议等 7 月交付和 Robotaxi 数据再决定。
|
||||
</div>
|
||||
|
||||
<!-- ========== FOOTER ========== -->
|
||||
<div class="report-footer">
|
||||
<span>仅供个人投资参考,不构成投资建议</span>
|
||||
<span>Tw93 · 2026.04.24</span>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
After Width: | Height: | Size: 317 KiB |
@@ -0,0 +1,449 @@
|
||||
<!DOCTYPE html>
|
||||
<!-- ==================================================================
|
||||
ONE-PAGER · English · parchment design system
|
||||
Single A4 proposal / report / executive summary
|
||||
Build: python3 scripts/build.py one-pager-en
|
||||
Target: exactly 1 page
|
||||
================================================================== -->
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Waza · Engineering Skills for AI Agents</title>
|
||||
<meta name="author" content="Tw93">
|
||||
<meta name="description" content="Waza turns proven engineering habits into eight skills that run on any AI coding agent, so your workflow never depends on a single model.">
|
||||
<meta name="keywords" content="Waza, AI agents, engineering skills, Claude Code, Codex, coding workflow">
|
||||
<meta name="generator" content="Kami">
|
||||
<style>
|
||||
@font-face { font-family: "JetBrains Mono"; src: url("../fonts/JetBrainsMono.woff2") format("woff2"); font-weight: 400; font-style: normal; }
|
||||
@font-face { font-family: "JetBrains Mono"; src: url("../fonts/JetBrainsMono.woff2") format("woff2"); font-weight: 500; font-style: normal; }
|
||||
|
||||
@page {
|
||||
size: A4;
|
||||
margin: 15mm 18mm 15mm 18mm;
|
||||
background: #f5f4ed;
|
||||
}
|
||||
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
|
||||
:root {
|
||||
--parchment: #f5f4ed;
|
||||
--ivory: #faf9f5;
|
||||
--near-black:#141413;
|
||||
--dark-warm: #3d3d3a;
|
||||
--olive: #504e49;
|
||||
--stone: #6b6a64;
|
||||
--brand: #1B365D;
|
||||
--border: #e8e6dc;
|
||||
--border-soft:#e5e3d8;
|
||||
--tag-bg: #E4ECF5;
|
||||
|
||||
--serif: Charter, Georgia,
|
||||
Palatino, "Times New Roman", serif;
|
||||
--sans: var(--serif);
|
||||
}
|
||||
|
||||
html, body { background: var(--parchment); }
|
||||
|
||||
@media screen {
|
||||
body { max-width: 210mm; margin: 0 auto; padding: 15mm 18mm; }
|
||||
}
|
||||
|
||||
body {
|
||||
color: var(--near-black);
|
||||
font-family: var(--serif);
|
||||
font-size: 10pt;
|
||||
line-height: 1.45;
|
||||
}
|
||||
|
||||
.serif { font-family: var(--serif); }
|
||||
|
||||
.header {
|
||||
margin-bottom: 16pt;
|
||||
padding-bottom: 12pt;
|
||||
border-bottom: 0.5pt solid var(--border);
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
justify-content: space-between;
|
||||
gap: 20pt;
|
||||
}
|
||||
.title-block { flex: 1; }
|
||||
.eyebrow {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 7pt;
|
||||
font-size: 9pt;
|
||||
color: var(--brand);
|
||||
letter-spacing: 1.5pt;
|
||||
text-transform: uppercase;
|
||||
margin-bottom: 7pt;
|
||||
font-weight: 500;
|
||||
}
|
||||
.eyebrow::before {
|
||||
content: "";
|
||||
width: 8pt;
|
||||
height: 1.5pt;
|
||||
border-radius: 0.75pt;
|
||||
background: var(--brand);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
h1 {
|
||||
font-family: var(--serif);
|
||||
font-size: 26pt;
|
||||
font-weight: 500;
|
||||
color: var(--near-black);
|
||||
line-height: 1.12;
|
||||
letter-spacing: -0.3pt;
|
||||
margin-bottom: 5pt;
|
||||
}
|
||||
.subtitle {
|
||||
font-size: 11.5pt;
|
||||
color: var(--olive);
|
||||
line-height: 1.4;
|
||||
}
|
||||
.meta {
|
||||
font-size: 9pt;
|
||||
color: var(--stone);
|
||||
text-align: right;
|
||||
line-height: 1.5;
|
||||
white-space: nowrap;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
/* Brand logo slot (optional, from brand profile; styles apply only once uncommented). */
|
||||
.brand-logo {
|
||||
height: 44px;
|
||||
width: auto;
|
||||
max-width: 160px;
|
||||
object-fit: contain;
|
||||
align-self: flex-start;
|
||||
}
|
||||
|
||||
/* METRICS - horizontal baseline, no containers (matches resume aesthetic). */
|
||||
.metrics {
|
||||
display: flex;
|
||||
gap: 16pt;
|
||||
margin-bottom: 22pt;
|
||||
padding: 4pt 0 6pt 0;
|
||||
border-bottom: 0.3pt dotted var(--border);
|
||||
}
|
||||
.metric {
|
||||
flex: 1;
|
||||
background: transparent;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
gap: 5pt;
|
||||
break-inside: avoid;
|
||||
}
|
||||
.metric-value {
|
||||
font-family: var(--serif);
|
||||
font-size: 22pt;
|
||||
font-weight: 500;
|
||||
color: var(--brand);
|
||||
line-height: 1;
|
||||
font-variant-numeric: tabular-nums;
|
||||
white-space: nowrap;
|
||||
letter-spacing: -0.3pt;
|
||||
}
|
||||
.metric-label {
|
||||
font-size: 9pt;
|
||||
color: var(--olive);
|
||||
line-height: 1.3;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* PRODUCT SHOT (optional) - one real screenshot as the page's visual anchor.
|
||||
Uncomment the <figure> in the body, point src at a local image, and tune
|
||||
.frame height to fill the page. object-fit trims dead margin, not the window. */
|
||||
.product-shot { margin-bottom: 16pt; break-inside: avoid; }
|
||||
.product-shot .frame {
|
||||
height: 110mm;
|
||||
overflow: hidden;
|
||||
border-radius: 5pt;
|
||||
box-shadow: 0 1.5pt 6pt rgba(20, 20, 19, 0.16);
|
||||
}
|
||||
.product-shot .frame img {
|
||||
width: 100%; height: 100%;
|
||||
object-fit: cover; object-position: center; display: block;
|
||||
}
|
||||
.product-shot figcaption {
|
||||
margin-top: 7pt; font-size: 8.5pt;
|
||||
color: var(--stone); line-height: 1.45; text-align: center;
|
||||
}
|
||||
.product-shot figcaption .hl { color: var(--brand); font-weight: 500; }
|
||||
|
||||
section { margin-bottom: 26pt; break-inside: avoid; }
|
||||
section:last-of-type { margin-bottom: 0; }
|
||||
|
||||
h2 {
|
||||
font-family: var(--serif);
|
||||
font-size: 16pt;
|
||||
font-weight: 500;
|
||||
color: var(--near-black);
|
||||
margin-bottom: 6pt;
|
||||
}
|
||||
h2 .sub {
|
||||
font-size: 9pt;
|
||||
color: var(--stone);
|
||||
font-weight: 400;
|
||||
margin-left: 6pt;
|
||||
}
|
||||
|
||||
p { margin-bottom: 6pt; line-height: 1.45; }
|
||||
p:last-child { margin-bottom: 0; }
|
||||
|
||||
.lead {
|
||||
font-size: 12.5pt;
|
||||
line-height: 1.5;
|
||||
color: var(--dark-warm);
|
||||
margin-bottom: 22pt;
|
||||
}
|
||||
|
||||
.hl { color: var(--brand); font-weight: 500; }
|
||||
|
||||
.two-col {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 18pt;
|
||||
}
|
||||
|
||||
ul.dash {
|
||||
margin: 0;
|
||||
padding-left: 16pt;
|
||||
}
|
||||
ul.dash li {
|
||||
margin-bottom: 5pt;
|
||||
line-height: 1.45;
|
||||
}
|
||||
ul.dash li:last-child { margin-bottom: 0; }
|
||||
ul.dash li::marker { color: var(--brand); }
|
||||
|
||||
/* SKILLS - aligned command / description, so the descriptions share one
|
||||
left edge instead of starting ragged after each command. */
|
||||
.skills {
|
||||
display: grid;
|
||||
grid-template-columns: max-content 1fr;
|
||||
column-gap: 12pt;
|
||||
row-gap: 7pt;
|
||||
margin-top: 4pt;
|
||||
}
|
||||
.skills dt { font-weight: 700; color: var(--near-black); }
|
||||
.skills dd { margin: 0; color: var(--olive); }
|
||||
|
||||
/* FLOW DIAGRAM - kami-skinned SVG: parchment, ink-blue focal, warm grays. */
|
||||
.flow { margin: 12pt 0 0 0; break-inside: avoid; }
|
||||
.flow svg { width: 100%; height: auto; display: block; }
|
||||
.flow figcaption {
|
||||
margin-top: 9pt; font-size: 8.5pt;
|
||||
color: var(--stone); line-height: 1.45; text-align: center;
|
||||
}
|
||||
.flow figcaption .hl { color: var(--brand); font-weight: 500; }
|
||||
|
||||
.timeline {
|
||||
display: flex;
|
||||
gap: 12pt;
|
||||
margin-top: 6pt;
|
||||
}
|
||||
.tl-step { flex: 1; break-inside: avoid; }
|
||||
.tl-year {
|
||||
font-family: var(--serif);
|
||||
font-size: 11pt;
|
||||
font-weight: 500;
|
||||
color: var(--brand);
|
||||
margin-bottom: 2pt;
|
||||
letter-spacing: 0.3pt;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
.tl-head {
|
||||
font-size: 10pt;
|
||||
font-weight: 500;
|
||||
color: var(--near-black);
|
||||
margin-bottom: 2pt;
|
||||
}
|
||||
.tl-body {
|
||||
font-size: 9pt;
|
||||
color: var(--olive);
|
||||
line-height: 1.45;
|
||||
}
|
||||
|
||||
.tag {
|
||||
display: inline-block;
|
||||
background: var(--tag-bg);
|
||||
color: var(--brand);
|
||||
font-size: 9pt;
|
||||
font-weight: 500;
|
||||
padding: 1pt 6pt;
|
||||
border-radius: 3pt;
|
||||
letter-spacing: 0.4pt;
|
||||
text-transform: uppercase;
|
||||
margin-right: 3pt;
|
||||
}
|
||||
|
||||
/* TABLE (kami-table) */
|
||||
table, .kami-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
font-size: 9pt;
|
||||
margin: 8pt 0;
|
||||
break-inside: avoid;
|
||||
}
|
||||
table th, .kami-table th {
|
||||
text-align: left;
|
||||
font-weight: 500;
|
||||
color: var(--dark-warm);
|
||||
padding: 4pt 6pt;
|
||||
border-bottom: 1pt solid var(--border);
|
||||
background: transparent;
|
||||
}
|
||||
table td, .kami-table td {
|
||||
padding: 3pt 6pt;
|
||||
border-bottom: 0.3pt solid var(--border-soft);
|
||||
vertical-align: top;
|
||||
}
|
||||
table.compact th, .kami-table.compact th { padding: 2pt 5pt; font-size: 8pt; }
|
||||
table.compact td, .kami-table.compact td { padding: 1pt 5pt; font-size: 8pt; line-height: 1.4; }
|
||||
table.financial td:not(:first-child), .kami-table.financial td:not(:first-child) {
|
||||
text-align: right; font-variant-numeric: tabular-nums;
|
||||
}
|
||||
table.financial th:not(:first-child), .kami-table.financial th:not(:first-child) { text-align: right; }
|
||||
table.striped tbody tr:nth-child(even) td, .kami-table.striped tbody tr:nth-child(even) td {
|
||||
background: var(--ivory);
|
||||
}
|
||||
table .total td, .kami-table .total td {
|
||||
font-weight: 500; border-top: 1pt solid var(--brand); border-bottom: none; color: var(--near-black);
|
||||
}
|
||||
|
||||
/* CALLOUT - transparent, single brand bar. No filled container. */
|
||||
.callout {
|
||||
background: transparent;
|
||||
border-left: 1.8pt solid var(--brand);
|
||||
padding: 4pt 0 4pt 14pt;
|
||||
margin: 22pt 0 0 0;
|
||||
font-size: 10.5pt;
|
||||
line-height: 1.55;
|
||||
color: var(--olive);
|
||||
break-inside: avoid;
|
||||
}
|
||||
.callout .hl, .callout .em-brand { color: var(--brand); }
|
||||
|
||||
.footer {
|
||||
margin-top: 18pt;
|
||||
padding-top: 6pt;
|
||||
border-top: 0.3pt dotted var(--border);
|
||||
font-size: 9pt;
|
||||
color: var(--stone);
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
font-variant-numeric: tabular-nums;
|
||||
letter-spacing: 0.3pt;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="header">
|
||||
<div class="title-block">
|
||||
<div class="eyebrow">Engineering Skills · 技</div>
|
||||
<h1>Waza</h1>
|
||||
<div class="subtitle">Engineering habits you already know, turned into skills any AI agent can run.</div>
|
||||
</div>
|
||||
<div class="meta">
|
||||
Tw93<br>
|
||||
v3.28<br>
|
||||
MIT
|
||||
</div>
|
||||
<!-- Brand logo slot (optional, from the brand profile logo field). Leave commented when there is no logo; do not insert a broken image.
|
||||
To use, uncomment and set src to an expanded local absolute path:
|
||||
<img class="brand-logo" src="/absolute/path/to/logo.svg" alt=""> -->
|
||||
</div>
|
||||
|
||||
<div class="metrics">
|
||||
<div class="metric">
|
||||
<div class="metric-value">8</div>
|
||||
<div class="metric-label">engineering skills</div>
|
||||
</div>
|
||||
<div class="metric">
|
||||
<div class="metric-value">6</div>
|
||||
<div class="metric-label">agents supported</div>
|
||||
</div>
|
||||
<div class="metric">
|
||||
<div class="metric-value">1</div>
|
||||
<div class="metric-label">line to install</div>
|
||||
</div>
|
||||
<div class="metric">
|
||||
<div class="metric-value">0</div>
|
||||
<div class="metric-label">model lock-in</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p class="lead">
|
||||
Models and tools come and go; the way good engineers actually work, planning, reviewing, debugging, reading the source, does not. Waza turns those habits into eight skills that run on whatever agent you have, so your workflow <span class="hl">never depends on a single model staying available</span>.
|
||||
</p>
|
||||
|
||||
<div class="two-col">
|
||||
<section>
|
||||
<h2>Plan & build</h2>
|
||||
<dl class="skills">
|
||||
<dt>/think</dt><dd>pressure-test a task into a plan</dd>
|
||||
<dt>/design</dt><dd>distinctive UI, never defaults</dd>
|
||||
<dt>/learn</dt><dd>map a new domain fast</dd>
|
||||
<dt>/read</dt><dd>distill any URL or PDF</dd>
|
||||
</dl>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>Review & ship</h2>
|
||||
<dl class="skills">
|
||||
<dt>/check</dt><dd>review the diff with evidence</dd>
|
||||
<dt>/hunt</dt><dd>find a bug's true root cause</dd>
|
||||
<dt>/write</dt><dd>make prose sound human</dd>
|
||||
<dt>/health</dt><dd>audit your agent setup</dd>
|
||||
</dl>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<section>
|
||||
<h2>Habits, not superpowers</h2>
|
||||
<p>The fashionable move is to bolt on a pack of "superpowers", dozens of skills promising an agent can suddenly do anything. But unconstrained capability is the problem, not the cure: it drifts into confident, generic, subtly wrong work. Waza adds no new powers. It encodes the habits a good engineer already has, so the model spends its capability on precision instead of breadth. Eight skills, not eighty, the few that actually decide quality, each carrying reference docs and real-failure gotchas rather than a one-line promise that rots the moment a task gets specific.</p>
|
||||
<figure class="flow">
|
||||
<svg viewBox="0 0 720 150" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<marker id="aw-arr" markerWidth="8" markerHeight="6" refX="7" refY="3" orient="auto">
|
||||
<polygon points="0 0, 8 3, 0 6" fill="#504e49"/>
|
||||
</marker>
|
||||
<marker id="aw-arr-b" markerWidth="8" markerHeight="6" refX="7" refY="3" orient="auto">
|
||||
<polygon points="0 0, 8 3, 0 6" fill="#1B365D"/>
|
||||
</marker>
|
||||
</defs>
|
||||
|
||||
<line x1="212" y1="75" x2="250" y2="75" stroke="#504e49" stroke-width="1.3" marker-end="url(#aw-arr)"/>
|
||||
<line x1="465" y1="75" x2="503" y2="75" stroke="#1B365D" stroke-width="1.3" marker-end="url(#aw-arr-b)"/>
|
||||
|
||||
<rect x="4" y="23" width="206" height="104" rx="6" fill="#faf9f5" stroke="#141413" stroke-width="1"/>
|
||||
<text x="107" y="52" fill="#504e49" font-size="7.5" font-family="'JetBrains Mono', monospace" text-anchor="middle" letter-spacing="0.14em">INPUT</text>
|
||||
<text x="107" y="78" fill="#141413" font-size="13" font-weight="500" font-family="inherit" text-anchor="middle">Engineering habits</text>
|
||||
<text x="107" y="97" fill="#504e49" font-size="9" font-family="inherit" text-anchor="middle">plan · review · debug · read</text>
|
||||
|
||||
<rect x="257" y="23" width="206" height="104" rx="6" fill="#EEF2F7" stroke="#1B365D" stroke-width="1.4"/>
|
||||
<text x="360" y="52" fill="#1B365D" font-size="7.5" font-family="'JetBrains Mono', monospace" text-anchor="middle" letter-spacing="0.14em">WAZA · 8 SKILLS</text>
|
||||
<text x="360" y="78" fill="#141413" font-size="13" font-weight="500" font-family="inherit" text-anchor="middle">Goal + guardrails</text>
|
||||
<text x="360" y="97" fill="#504e49" font-size="9" font-family="inherit" text-anchor="middle">then let the model run</text>
|
||||
|
||||
<rect x="510" y="23" width="206" height="104" rx="6" fill="#faf9f5" stroke="#141413" stroke-width="1"/>
|
||||
<text x="613" y="52" fill="#504e49" font-size="7.5" font-family="'JetBrains Mono', monospace" text-anchor="middle" letter-spacing="0.14em">OUTPUT</text>
|
||||
<text x="613" y="78" fill="#141413" font-size="13" font-weight="500" font-family="inherit" text-anchor="middle">Precise, reviewed work</text>
|
||||
<text x="613" y="97" fill="#504e49" font-size="9" font-family="inherit" text-anchor="middle">on any agent, not generic</text>
|
||||
</svg>
|
||||
<figcaption>Capability is the input, not the product. <span class="hl">Constraints are what make it land.</span></figcaption>
|
||||
</figure>
|
||||
</section>
|
||||
|
||||
<div class="callout">
|
||||
Runs on <span class="hl">Claude Code, Codex, Antigravity, OpenCode, Claude Desktop, and Pi</span>. One workflow on every agent, installed once with <span class="hl">npx skills add tw93/Waza</span>.
|
||||
</div>
|
||||
|
||||
<div class="footer">
|
||||
<span>Open source · MIT · part of Kaku · Waza · Kami</span>
|
||||
<span>github.com/tw93/Waza</span>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
After Width: | Height: | Size: 271 KiB |
|
After Width: | Height: | Size: 949 KiB |
|
After Width: | Height: | Size: 460 KiB |
@@ -0,0 +1,271 @@
|
||||
<!DOCTYPE html>
|
||||
<!-- ==================================================================
|
||||
DIAGRAM · Architecture Board (kami palette)
|
||||
Report-scale board: five fixed information layers, bands with
|
||||
dividers instead of card piles, one focal node, a table shell for
|
||||
governance, and a single brand-colored main axis. Demo content maps
|
||||
the Kaku terminal (a WezTerm fork); replace it with your system.
|
||||
Author the structure outline below BEFORE moving any rectangle.
|
||||
================================================================== -->
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Architecture Board · kami diagram</title>
|
||||
<style>
|
||||
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
|
||||
:root {
|
||||
--parchment: #f5f4ed;
|
||||
--ivory: #faf9f5;
|
||||
--near-black: #141413;
|
||||
--olive: #504e49;
|
||||
--stone: #6b6a64;
|
||||
--brand: #1B365D;
|
||||
--brand-tint: #EEF2F7;
|
||||
--border: #e8e6dc;
|
||||
|
||||
--serif: Charter, Georgia, Palatino, serif;
|
||||
--sans: var(--serif);
|
||||
--mono: "JetBrains Mono", "SF Mono", Consolas, "TsangerJinKai02", "Source Han Serif SC", "Noto Serif CJK SC", "Songti SC", monospace;
|
||||
}
|
||||
|
||||
/* Poster-size single page so WeasyPrint exports the whole board on one
|
||||
sheet; browsers ignore @page. No 100vh / vertical flex centering here:
|
||||
content is taller than a viewport and would paginate blank (see
|
||||
production.md #5). */
|
||||
@page { size: 1320px 1280px; margin: 0; }
|
||||
|
||||
body {
|
||||
font-family: var(--sans);
|
||||
background: var(--parchment);
|
||||
color: var(--near-black);
|
||||
padding: 3rem 2rem;
|
||||
}
|
||||
|
||||
.frame { max-width: 1240px; width: 100%; margin: 0 auto; }
|
||||
|
||||
.eyebrow {
|
||||
font-family: var(--mono);
|
||||
font-size: 0.7rem;
|
||||
letter-spacing: 0.22em;
|
||||
text-transform: uppercase;
|
||||
color: var(--stone);
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-family: var(--serif);
|
||||
font-size: 1.9rem;
|
||||
font-weight: 500;
|
||||
letter-spacing: -0.3pt;
|
||||
line-height: 1.15;
|
||||
margin-bottom: 1.6rem;
|
||||
}
|
||||
|
||||
svg { width: 100%; min-width: 1080px; display: block; }
|
||||
|
||||
.caption {
|
||||
font-family: var(--serif);
|
||||
font-size: 0.95rem;
|
||||
color: var(--olive);
|
||||
margin-top: 1rem;
|
||||
max-width: 62ch;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="frame">
|
||||
<p class="eyebrow">Architecture Board · kami diagram</p>
|
||||
<h1>{{System name}} architecture board</h1>
|
||||
|
||||
<!-- Structure outline (write this first, then render):
|
||||
Section: Kaku architecture, one judgment in the title layer
|
||||
Band: Platform surface -> AI coding sessions | Daily terminal work | macOS desktop | Shell suite
|
||||
Band: GUI shell -> Window frontend | GPU glyph pipeline | Input map | [FOCAL] AI chat engine
|
||||
Band: Terminal core -> Multiplexer | PTY layer | Escape parsing | Cell model
|
||||
Flow: Keystroke -> Input map -> PTY / shell -> Escape parse -> Cell grid -> GPU frame
|
||||
Table: Config | Theme | Distribution | Upstream
|
||||
Note: AI request path is not the main axis, demoted to a caption
|
||||
-->
|
||||
<svg viewBox="0 0 1200 1016" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<pattern id="dots" width="22" height="22" patternUnits="userSpaceOnUse">
|
||||
<circle cx="1" cy="1" r="0.9" fill="#E3E2DC"/>
|
||||
</pattern>
|
||||
</defs>
|
||||
|
||||
<rect width="100%" height="100%" fill="#f5f4ed"/>
|
||||
<rect width="100%" height="100%" fill="url(#dots)" opacity="0.55"/>
|
||||
|
||||
<!-- Layer 1 · Title: subject + judgment, basis caption top right -->
|
||||
<text x="48" y="44" fill="#141413" font-size="28" font-weight="600" font-family="inherit">Kaku: a WezTerm core, rewired for AI coding</text>
|
||||
<text x="1152" y="40" fill="#6b6a64" font-size="10" font-family="'JetBrains Mono', monospace" text-anchor="end" letter-spacing="0.18em">WORKSPACE VIEW · 2026-07</text>
|
||||
<text x="48" y="72" fill="#504e49" font-size="13" font-family="inherit">Defaults ship on day one, Lua customization stays, and the AI engine is the fork's real addition.</text>
|
||||
<line x1="48" y1="92" x2="1152" y2="92" stroke="#1B365D" stroke-width="0.8"/>
|
||||
|
||||
<!-- Layer 2 · Platform surface (band, peers separated by dividers) -->
|
||||
<text x="48" y="128" fill="#6b6a64" font-size="10" font-family="'JetBrains Mono', monospace" letter-spacing="0.22em">01 · PLATFORM SURFACE</text>
|
||||
<text x="1152" y="128" fill="#6b6a64" font-size="10" font-family="'JetBrains Mono', monospace" text-anchor="end" letter-spacing="0.18em">WHO KAKU SERVES</text>
|
||||
<rect x="48" y="144" width="1104" height="100" rx="6" fill="#faf9f5" stroke="#e8e6dc" stroke-width="1"/>
|
||||
<line x1="324" y1="160" x2="324" y2="228" stroke="#e8e6dc" stroke-width="1"/>
|
||||
<line x1="600" y1="160" x2="600" y2="228" stroke="#e8e6dc" stroke-width="1"/>
|
||||
<line x1="876" y1="160" x2="876" y2="228" stroke="#e8e6dc" stroke-width="1"/>
|
||||
|
||||
<text x="72" y="180" fill="#141413" font-size="14" font-weight="600" font-family="inherit">AI coding sessions</text>
|
||||
<text x="72" y="204" fill="#504e49" font-size="10" font-family="'JetBrains Mono', monospace">in-terminal assistant</text>
|
||||
<text x="72" y="224" fill="#6b6a64" font-size="10" font-family="'JetBrains Mono', monospace">ai_chat_engine · ai_tools</text>
|
||||
|
||||
<text x="348" y="180" fill="#141413" font-size="14" font-weight="600" font-family="inherit">Daily terminal work</text>
|
||||
<text x="348" y="204" fill="#504e49" font-size="10" font-family="'JetBrains Mono', monospace">panes · tabs · workspaces</text>
|
||||
<text x="348" y="224" fill="#6b6a64" font-size="10" font-family="'JetBrains Mono', monospace">local, ssh, mux domains</text>
|
||||
|
||||
<text x="624" y="180" fill="#141413" font-size="14" font-weight="600" font-family="inherit">macOS desktop</text>
|
||||
<text x="624" y="204" fill="#504e49" font-size="10" font-family="'JetBrains Mono', monospace">theme follows the system</text>
|
||||
<text x="624" y="224" fill="#6b6a64" font-size="10" font-family="'JetBrains Mono', monospace">notarized DMG app</text>
|
||||
|
||||
<text x="900" y="180" fill="#141413" font-size="14" font-weight="600" font-family="inherit">Shell suite</text>
|
||||
<text x="900" y="204" fill="#504e49" font-size="10" font-family="'JetBrains Mono', monospace">curated zsh plugins</text>
|
||||
<text x="900" y="224" fill="#6b6a64" font-size="10" font-family="'JetBrains Mono', monospace">prompt · diff · navigation</text>
|
||||
|
||||
<!-- Layer 3a · GUI shell (band; focal cell carries the only accent) -->
|
||||
<text x="48" y="288" fill="#6b6a64" font-size="10" font-family="'JetBrains Mono', monospace" letter-spacing="0.22em">02 · GUI SHELL · KAKU-GUI</text>
|
||||
<text x="1152" y="288" fill="#6b6a64" font-size="10" font-family="'JetBrains Mono', monospace" text-anchor="end" letter-spacing="0.18em">GPU FRONT END</text>
|
||||
<rect x="48" y="304" width="1104" height="100" rx="6" fill="#faf9f5" stroke="#e8e6dc" stroke-width="1"/>
|
||||
<line x1="324" y1="320" x2="324" y2="388" stroke="#e8e6dc" stroke-width="1"/>
|
||||
<line x1="600" y1="320" x2="600" y2="388" stroke="#e8e6dc" stroke-width="1"/>
|
||||
|
||||
<text x="72" y="340" fill="#141413" font-size="14" font-weight="600" font-family="inherit">Window frontend</text>
|
||||
<text x="72" y="364" fill="#504e49" font-size="10" font-family="'JetBrains Mono', monospace">frontend.rs · macos</text>
|
||||
<text x="72" y="384" fill="#6b6a64" font-size="10" font-family="'JetBrains Mono', monospace">events · commands</text>
|
||||
|
||||
<text x="348" y="340" fill="#141413" font-size="14" font-weight="600" font-family="inherit">GPU glyph pipeline</text>
|
||||
<text x="348" y="364" fill="#504e49" font-size="10" font-family="'JetBrains Mono', monospace">glyphcache · shaders</text>
|
||||
<text x="348" y="384" fill="#6b6a64" font-size="10" font-family="'JetBrains Mono', monospace">custom glyphs</text>
|
||||
|
||||
<text x="624" y="340" fill="#141413" font-size="14" font-weight="600" font-family="inherit">Input map</text>
|
||||
<text x="624" y="364" fill="#504e49" font-size="10" font-family="'JetBrains Mono', monospace">inputmap.rs · commands.rs</text>
|
||||
<text x="624" y="384" fill="#6b6a64" font-size="10" font-family="'JetBrains Mono', monospace">keys to actions</text>
|
||||
|
||||
<rect x="880" y="308" width="268" height="92" rx="6" fill="#EEF2F7" stroke="#1B365D" stroke-width="1"/>
|
||||
<text x="904" y="340" fill="#141413" font-size="14" font-weight="600" font-family="inherit">AI chat engine</text>
|
||||
<text x="904" y="364" fill="#1B365D" font-size="10" font-family="'JetBrains Mono', monospace">ai_client · conversations</text>
|
||||
<text x="904" y="384" fill="#504e49" font-size="10" font-family="'JetBrains Mono', monospace">auth · state · remote</text>
|
||||
|
||||
<!-- Layer 3b · Terminal core (band) -->
|
||||
<text x="48" y="448" fill="#6b6a64" font-size="10" font-family="'JetBrains Mono', monospace" letter-spacing="0.22em">03 · TERMINAL CORE</text>
|
||||
<text x="1152" y="448" fill="#6b6a64" font-size="10" font-family="'JetBrains Mono', monospace" text-anchor="end" letter-spacing="0.18em">INHERITED FROM WEZTERM · TRIMMED</text>
|
||||
<rect x="48" y="464" width="1104" height="100" rx="6" fill="#faf9f5" stroke="#e8e6dc" stroke-width="1"/>
|
||||
<line x1="324" y1="480" x2="324" y2="548" stroke="#e8e6dc" stroke-width="1"/>
|
||||
<line x1="600" y1="480" x2="600" y2="548" stroke="#e8e6dc" stroke-width="1"/>
|
||||
<line x1="876" y1="480" x2="876" y2="548" stroke="#e8e6dc" stroke-width="1"/>
|
||||
|
||||
<text x="72" y="500" fill="#141413" font-size="14" font-weight="600" font-family="inherit">Multiplexer</text>
|
||||
<text x="72" y="524" fill="#504e49" font-size="10" font-family="'JetBrains Mono', monospace">pane · tab · domain</text>
|
||||
<text x="72" y="544" fill="#6b6a64" font-size="10" font-family="'JetBrains Mono', monospace">mux crate</text>
|
||||
|
||||
<text x="348" y="500" fill="#141413" font-size="14" font-weight="600" font-family="inherit">PTY layer</text>
|
||||
<text x="348" y="524" fill="#504e49" font-size="10" font-family="'JetBrains Mono', monospace">child shell processes</text>
|
||||
<text x="348" y="544" fill="#6b6a64" font-size="10" font-family="'JetBrains Mono', monospace">pty crate</text>
|
||||
|
||||
<text x="624" y="500" fill="#141413" font-size="14" font-weight="600" font-family="inherit">Escape parsing</text>
|
||||
<text x="624" y="524" fill="#504e49" font-size="10" font-family="'JetBrains Mono', monospace">vtparse · escape-parser</text>
|
||||
<text x="624" y="544" fill="#6b6a64" font-size="10" font-family="'JetBrains Mono', monospace">bytes to actions</text>
|
||||
|
||||
<text x="900" y="500" fill="#141413" font-size="14" font-weight="600" font-family="inherit">Cell model</text>
|
||||
<text x="900" y="524" fill="#504e49" font-size="10" font-family="'JetBrains Mono', monospace">surface · cell · termwiz</text>
|
||||
<text x="900" y="544" fill="#6b6a64" font-size="10" font-family="'JetBrains Mono', monospace">grid state · attributes</text>
|
||||
|
||||
<!-- Layer 4 · Main axis: the only brand-colored line on the board -->
|
||||
<text x="48" y="608" fill="#6b6a64" font-size="10" font-family="'JetBrains Mono', monospace" letter-spacing="0.22em">04 · MAIN AXIS · INPUT TO PIXELS</text>
|
||||
<text x="1152" y="608" fill="#6b6a64" font-size="10" font-family="'JetBrains Mono', monospace" text-anchor="end" letter-spacing="0.18em">THE PATH EVERY KEYSTROKE TAKES</text>
|
||||
|
||||
<!-- Connectors keep a 4px standoff from both node edges -->
|
||||
<line x1="196" y1="648" x2="236" y2="648" stroke="#1B365D" stroke-width="1.4"/>
|
||||
<path d="M 228 644 L 236 648 L 228 652" fill="none" stroke="#1B365D" stroke-width="1.4" stroke-linecap="round"/>
|
||||
<line x1="388" y1="648" x2="428" y2="648" stroke="#1B365D" stroke-width="1.4"/>
|
||||
<path d="M 420 644 L 428 648 L 420 652" fill="none" stroke="#1B365D" stroke-width="1.4" stroke-linecap="round"/>
|
||||
<line x1="580" y1="648" x2="620" y2="648" stroke="#1B365D" stroke-width="1.4"/>
|
||||
<path d="M 612 644 L 620 648 L 612 652" fill="none" stroke="#1B365D" stroke-width="1.4" stroke-linecap="round"/>
|
||||
<line x1="772" y1="648" x2="812" y2="648" stroke="#1B365D" stroke-width="1.4"/>
|
||||
<path d="M 804 644 L 812 648 L 804 652" fill="none" stroke="#1B365D" stroke-width="1.4" stroke-linecap="round"/>
|
||||
<line x1="964" y1="648" x2="1004" y2="648" stroke="#1B365D" stroke-width="1.4"/>
|
||||
<path d="M 996 644 L 1004 648 L 996 652" fill="none" stroke="#1B365D" stroke-width="1.4" stroke-linecap="round"/>
|
||||
|
||||
<rect x="48" y="624" width="144" height="48" rx="6" fill="#faf9f5" stroke="#141413" stroke-width="1"/>
|
||||
<text x="120" y="644" fill="#141413" font-size="12" font-weight="600" font-family="inherit" text-anchor="middle">Keystroke</text>
|
||||
<text x="120" y="660" fill="#6b6a64" font-size="8.5" font-family="'JetBrains Mono', monospace" text-anchor="middle">user intent</text>
|
||||
|
||||
<rect x="240" y="624" width="144" height="48" rx="6" fill="#faf9f5" stroke="#141413" stroke-width="1"/>
|
||||
<text x="312" y="644" fill="#141413" font-size="12" font-weight="600" font-family="inherit" text-anchor="middle">Input map</text>
|
||||
<text x="312" y="660" fill="#6b6a64" font-size="8.5" font-family="'JetBrains Mono', monospace" text-anchor="middle">kaku-gui</text>
|
||||
|
||||
<rect x="432" y="624" width="144" height="48" rx="6" fill="#faf9f5" stroke="#141413" stroke-width="1"/>
|
||||
<text x="504" y="644" fill="#141413" font-size="12" font-weight="600" font-family="inherit" text-anchor="middle">PTY · shell</text>
|
||||
<text x="504" y="660" fill="#6b6a64" font-size="8.5" font-family="'JetBrains Mono', monospace" text-anchor="middle">pty crate</text>
|
||||
|
||||
<rect x="624" y="624" width="144" height="48" rx="6" fill="#faf9f5" stroke="#141413" stroke-width="1"/>
|
||||
<text x="696" y="644" fill="#141413" font-size="12" font-weight="600" font-family="inherit" text-anchor="middle">Escape parse</text>
|
||||
<text x="696" y="660" fill="#6b6a64" font-size="8.5" font-family="'JetBrains Mono', monospace" text-anchor="middle">vtparse</text>
|
||||
|
||||
<rect x="816" y="624" width="144" height="48" rx="6" fill="#faf9f5" stroke="#141413" stroke-width="1"/>
|
||||
<text x="888" y="644" fill="#141413" font-size="12" font-weight="600" font-family="inherit" text-anchor="middle">Cell grid</text>
|
||||
<text x="888" y="660" fill="#6b6a64" font-size="8.5" font-family="'JetBrains Mono', monospace" text-anchor="middle">surface</text>
|
||||
|
||||
<rect x="1008" y="624" width="144" height="48" rx="6" fill="#faf9f5" stroke="#141413" stroke-width="1"/>
|
||||
<text x="1080" y="644" fill="#141413" font-size="12" font-weight="600" font-family="inherit" text-anchor="middle">GPU frame</text>
|
||||
<text x="1080" y="660" fill="#6b6a64" font-size="8.5" font-family="'JetBrains Mono', monospace" text-anchor="middle">glyphcache</text>
|
||||
|
||||
<!-- Non-core relation demoted to a caption, not another line -->
|
||||
<text x="48" y="700" fill="#6b6a64" font-size="10" font-family="'JetBrains Mono', monospace" letter-spacing="0.08em">AUX · AI PATH: PROMPT → AI_CHAT_ENGINE → AI_CLIENT → PROVIDER API</text>
|
||||
|
||||
<!-- Layer 5 · Governance (table shell, not five cards) -->
|
||||
<text x="48" y="744" fill="#6b6a64" font-size="10" font-family="'JetBrains Mono', monospace" letter-spacing="0.22em">05 · CONTROL PLANE & GOVERNANCE</text>
|
||||
<text x="1152" y="744" fill="#6b6a64" font-size="10" font-family="'JetBrains Mono', monospace" text-anchor="end" letter-spacing="0.18em">HOW THE FORK STAYS MAINTAINABLE</text>
|
||||
|
||||
<text x="48" y="784" fill="#141413" font-size="10" font-weight="600" font-family="'JetBrains Mono', monospace" letter-spacing="0.18em">CONFIG</text>
|
||||
<text x="348" y="784" fill="#141413" font-size="10" font-weight="600" font-family="'JetBrains Mono', monospace" letter-spacing="0.18em">THEME</text>
|
||||
<text x="624" y="784" fill="#141413" font-size="10" font-weight="600" font-family="'JetBrains Mono', monospace" letter-spacing="0.18em">DISTRIBUTION</text>
|
||||
<text x="900" y="784" fill="#141413" font-size="10" font-weight="600" font-family="'JetBrains Mono', monospace" letter-spacing="0.18em">UPSTREAM</text>
|
||||
<line x1="48" y1="796" x2="1152" y2="796" stroke="#141413" stroke-width="1"/>
|
||||
<line x1="324" y1="760" x2="324" y2="900" stroke="#e8e6dc" stroke-width="1"/>
|
||||
<line x1="600" y1="760" x2="600" y2="900" stroke="#e8e6dc" stroke-width="1"/>
|
||||
<line x1="876" y1="760" x2="876" y2="900" stroke="#e8e6dc" stroke-width="1"/>
|
||||
|
||||
<text x="48" y="828" fill="#141413" font-size="14" font-weight="600" font-family="inherit">Lua end to end</text>
|
||||
<text x="48" y="852" fill="#504e49" font-size="10" font-family="'JetBrains Mono', monospace">config · lua-api-crates</text>
|
||||
<text x="48" y="876" fill="#6b6a64" font-size="10" font-family="'JetBrains Mono', monospace">config_tui · doctor</text>
|
||||
|
||||
<text x="348" y="828" fill="#141413" font-size="14" font-weight="600" font-family="inherit">Follows macOS</text>
|
||||
<text x="348" y="852" fill="#504e49" font-size="10" font-family="'JetBrains Mono', monospace">kaku_theme · color-funcs</text>
|
||||
<text x="348" y="876" fill="#6b6a64" font-size="10" font-family="'JetBrains Mono', monospace">dark and light tuned</text>
|
||||
|
||||
<text x="624" y="828" fill="#141413" font-size="14" font-weight="600" font-family="inherit">Notarized DMG</text>
|
||||
<text x="624" y="852" fill="#504e49" font-size="10" font-family="'JetBrains Mono', monospace">GitHub releases</text>
|
||||
<text x="624" y="876" fill="#6b6a64" font-size="10" font-family="'JetBrains Mono', monospace">brew tw93/tap/kakuku</text>
|
||||
|
||||
<text x="900" y="828" fill="#141413" font-size="14" font-weight="600" font-family="inherit">WezTerm fork</text>
|
||||
<text x="900" y="852" fill="#504e49" font-size="10" font-family="'JetBrains Mono', monospace">core crates tracked</text>
|
||||
<text x="900" y="876" fill="#6b6a64" font-size="10" font-family="'JetBrains Mono', monospace">40% smaller binary</text>
|
||||
<line x1="48" y1="900" x2="1152" y2="900" stroke="#DEDED7" stroke-width="0.8"/>
|
||||
|
||||
<!-- Legend + closing note: one text size on one baseline -->
|
||||
<text x="48" y="948" fill="#504e49" font-size="8.5" font-family="'JetBrains Mono', monospace" letter-spacing="0.22em">LEGEND</text>
|
||||
|
||||
<rect x="48" y="968" width="14" height="10" rx="2" fill="#EEF2F7" stroke="#1B365D" stroke-width="1"/>
|
||||
<text x="68" y="976" fill="#504e49" font-size="10" font-family="inherit">Focal · the fork's addition</text>
|
||||
|
||||
<rect x="288" y="968" width="28" height="10" rx="2" fill="#faf9f5" stroke="#B2B1AC" stroke-width="1"/>
|
||||
<line x1="302" y1="970" x2="302" y2="976" stroke="#B2B1AC" stroke-width="1"/>
|
||||
<text x="324" y="976" fill="#504e49" font-size="10" font-family="inherit">Band · peers share one shell</text>
|
||||
|
||||
<line x1="544" y1="972" x2="574" y2="972" stroke="#1B365D" stroke-width="1.4"/>
|
||||
<path d="M 566 968 L 574 972 L 566 976" fill="none" stroke="#1B365D" stroke-width="1.4" stroke-linecap="round"/>
|
||||
<text x="584" y="976" fill="#504e49" font-size="10" font-family="inherit">Main axis</text>
|
||||
|
||||
<text x="1152" y="976" fill="#504e49" font-size="10" font-family="inherit" text-anchor="end">Crate-level detail lives in the workspace docs; this board carries the spine.</text>
|
||||
</svg>
|
||||
|
||||
<p class="caption">One focal per board: the AI chat engine, the piece this fork actually adds. Bands group peers behind thin dividers, governance sits in a table shell, and the only brand-colored line on the page is the main axis.</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,180 @@
|
||||
<!DOCTYPE html>
|
||||
<!-- ==================================================================
|
||||
DIAGRAM · Architecture (kami palette)
|
||||
Components + connections in a system. One focal node, everything else
|
||||
in warm neutrals. Drop the <svg> block into a long-doc or portfolio
|
||||
figure element to embed.
|
||||
================================================================== -->
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Architecture · kami diagram</title>
|
||||
<style>
|
||||
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
|
||||
:root {
|
||||
--parchment: #f5f4ed;
|
||||
--ivory: #faf9f5;
|
||||
--near-black: #141413;
|
||||
--olive: #504e49;
|
||||
--stone: #6b6a64;
|
||||
--brand: #1B365D;
|
||||
--brand-tint: #EEF2F7;
|
||||
--border: #e8e6dc;
|
||||
|
||||
--serif: Charter, Georgia, Palatino, serif;
|
||||
--sans: var(--serif);
|
||||
--mono: "JetBrains Mono", "SF Mono", Consolas, "TsangerJinKai02", "Source Han Serif SC", "Noto Serif CJK SC", "Songti SC", monospace;
|
||||
}
|
||||
|
||||
/* Poster-size page so WeasyPrint exports the full diagram without
|
||||
A4 clipping; browsers ignore @page. */
|
||||
@page { size: 1080px 880px; margin: 0; }
|
||||
|
||||
body {
|
||||
font-family: var(--sans);
|
||||
background: var(--parchment);
|
||||
color: var(--near-black);
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 3rem 2rem;
|
||||
}
|
||||
|
||||
.frame { max-width: 1000px; width: 100%; }
|
||||
|
||||
.eyebrow {
|
||||
font-family: var(--mono);
|
||||
font-size: 0.7rem;
|
||||
letter-spacing: 0.22em;
|
||||
text-transform: uppercase;
|
||||
color: var(--stone);
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-family: var(--serif);
|
||||
font-size: 1.9rem;
|
||||
font-weight: 500;
|
||||
letter-spacing: -0.3pt;
|
||||
line-height: 1.15;
|
||||
margin-bottom: 1.6rem;
|
||||
}
|
||||
|
||||
svg { width: 100%; min-width: 860px; display: block; }
|
||||
|
||||
.caption {
|
||||
font-family: var(--serif);
|
||||
font-size: 0.95rem;
|
||||
color: var(--olive);
|
||||
margin-top: 1rem;
|
||||
max-width: 52ch;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="frame">
|
||||
<p class="eyebrow">Architecture · kami diagram</p>
|
||||
<h1>{{System name}} in production</h1>
|
||||
|
||||
<svg viewBox="0 0 960 460" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<pattern id="dots" width="22" height="22" patternUnits="userSpaceOnUse">
|
||||
<circle cx="1" cy="1" r="0.9" fill="#E3E2DC"/>
|
||||
</pattern>
|
||||
<marker id="arrow" markerWidth="8" markerHeight="6" refX="7" refY="3" orient="auto">
|
||||
<polygon points="0 0, 8 3, 0 6" fill="#504e49"/>
|
||||
</marker>
|
||||
<marker id="arrow-brand" markerWidth="8" markerHeight="6" refX="7" refY="3" orient="auto">
|
||||
<polygon points="0 0, 8 3, 0 6" fill="#1B365D"/>
|
||||
</marker>
|
||||
</defs>
|
||||
|
||||
<rect width="100%" height="100%" fill="#f5f4ed"/>
|
||||
<rect width="100%" height="100%" fill="url(#dots)" opacity="0.55"/>
|
||||
|
||||
<!-- Arrows (behind nodes) -->
|
||||
<line x1="168" y1="240" x2="220" y2="240" stroke="#504e49" stroke-width="1.2" marker-end="url(#arrow)"/>
|
||||
<line x1="364" y1="240" x2="416" y2="240" stroke="#1B365D" stroke-width="1.4" marker-end="url(#arrow-brand)"/>
|
||||
<line x1="576" y1="224" x2="628" y2="180" stroke="#504e49" stroke-width="1.2" marker-end="url(#arrow)"/>
|
||||
<line x1="576" y1="256" x2="628" y2="300" stroke="#504e49" stroke-width="1.2" marker-end="url(#arrow)"/>
|
||||
|
||||
<!-- Arrow labels (masked rect prevents line bleed-through) -->
|
||||
<rect x="172" y="220" width="48" height="12" rx="2" fill="#f5f4ed"/>
|
||||
<text x="196" y="230" fill="#504e49" font-size="8" font-family="'JetBrains Mono', monospace" text-anchor="middle" letter-spacing="0.1em">HTTPS</text>
|
||||
|
||||
<rect x="368" y="220" width="48" height="12" rx="2" fill="#f5f4ed"/>
|
||||
<text x="392" y="230" fill="#1B365D" font-size="8" font-family="'JetBrains Mono', monospace" text-anchor="middle" letter-spacing="0.1em">SSR</text>
|
||||
|
||||
<rect x="560" y="180" width="56" height="12" rx="2" fill="#f5f4ed"/>
|
||||
<text x="588" y="190" fill="#504e49" font-size="8" font-family="'JetBrains Mono', monospace" text-anchor="middle" letter-spacing="0.1em">READ</text>
|
||||
|
||||
<rect x="560" y="288" width="56" height="12" rx="2" fill="#f5f4ed"/>
|
||||
<text x="588" y="298" fill="#504e49" font-size="8" font-family="'JetBrains Mono', monospace" text-anchor="middle" letter-spacing="0.1em">QUERY</text>
|
||||
|
||||
<!-- Node 1: Reader (external) -->
|
||||
<rect x="40" y="208" width="128" height="64" rx="6" fill="#f5f4ed"/>
|
||||
<rect x="40" y="208" width="128" height="64" rx="6" fill="#E9E8E1" stroke="#6b6a64" stroke-width="1"/>
|
||||
<text x="62" y="226" fill="#6b6a64" font-size="7" font-family="'JetBrains Mono', monospace" letter-spacing="0.15em">USER</text>
|
||||
<text x="104" y="246" fill="#141413" font-size="12" font-weight="500" font-family="inherit" text-anchor="middle">Reader</text>
|
||||
<text x="104" y="262" fill="#504e49" font-size="9" font-family="'JetBrains Mono', monospace" text-anchor="middle">Browser</text>
|
||||
|
||||
<!-- Node 2: Edge / CDN (cloud) -->
|
||||
<rect x="220" y="208" width="144" height="64" rx="6" fill="#f5f4ed"/>
|
||||
<rect x="220" y="208" width="144" height="64" rx="6" fill="#EEEDE6" stroke="#B2B1AC" stroke-width="1"/>
|
||||
<text x="242" y="226" fill="#6b6a64" font-size="7" font-family="'JetBrains Mono', monospace" letter-spacing="0.15em">EDGE</text>
|
||||
<text x="292" y="246" fill="#141413" font-size="12" font-weight="500" font-family="inherit" text-anchor="middle">CDN</text>
|
||||
<text x="292" y="262" fill="#504e49" font-size="9" font-family="'JetBrains Mono', monospace" text-anchor="middle">cache · SSL</text>
|
||||
|
||||
<!-- Node 3: Origin (FOCAL - brand accent) -->
|
||||
<rect x="416" y="208" width="160" height="64" rx="6" fill="#f5f4ed"/>
|
||||
<rect x="416" y="208" width="160" height="64" rx="6" fill="#EEF2F7" stroke="#1B365D" stroke-width="1"/>
|
||||
<text x="438" y="226" fill="#1B365D" font-size="7" font-family="'JetBrains Mono', monospace" letter-spacing="0.15em">ORIGIN</text>
|
||||
<text x="496" y="246" fill="#141413" font-size="12" font-weight="500" font-family="inherit" text-anchor="middle">App Server</text>
|
||||
<text x="496" y="262" fill="#504e49" font-size="9" font-family="'JetBrains Mono', monospace" text-anchor="middle">render · route</text>
|
||||
|
||||
<!-- Node 4: Store (content) -->
|
||||
<rect x="628" y="128" width="144" height="64" rx="6" fill="#f5f4ed"/>
|
||||
<rect x="628" y="128" width="144" height="64" rx="6" fill="#faf9f5" stroke="#141413" stroke-width="1"/>
|
||||
<text x="650" y="146" fill="#141413" font-size="7" font-family="'JetBrains Mono', monospace" letter-spacing="0.15em">BUNDLE</text>
|
||||
<text x="700" y="166" fill="#141413" font-size="12" font-weight="500" font-family="inherit" text-anchor="middle">Content</text>
|
||||
<text x="700" y="182" fill="#504e49" font-size="9" font-family="'JetBrains Mono', monospace" text-anchor="middle">*.mdx · assets</text>
|
||||
|
||||
<!-- Node 5: Database (store) -->
|
||||
<rect x="628" y="288" width="144" height="64" rx="6" fill="#f5f4ed"/>
|
||||
<rect x="628" y="288" width="144" height="64" rx="6" fill="#EAE9E2" stroke="#504e49" stroke-width="1"/>
|
||||
<text x="650" y="306" fill="#504e49" font-size="7" font-family="'JetBrains Mono', monospace" letter-spacing="0.15em">STORE</text>
|
||||
<text x="700" y="326" fill="#141413" font-size="12" font-weight="500" font-family="inherit" text-anchor="middle">Database</text>
|
||||
<text x="700" y="342" fill="#504e49" font-size="9" font-family="'JetBrains Mono', monospace" text-anchor="middle">postgres · vectors</text>
|
||||
|
||||
<!-- Legend strip -->
|
||||
<line x1="40" y1="390" x2="920" y2="390" stroke="#DEDED7" stroke-width="0.8"/>
|
||||
<text x="40" y="406" fill="#504e49" font-size="8" font-family="'JetBrains Mono', monospace" letter-spacing="0.22em">LEGEND</text>
|
||||
|
||||
<rect x="40" y="422" width="14" height="10" rx="2" fill="#EEF2F7" stroke="#1B365D" stroke-width="1"/>
|
||||
<text x="60" y="430" fill="#504e49" font-size="8.5" font-family="inherit">Focal · origin</text>
|
||||
|
||||
<rect x="170" y="422" width="14" height="10" rx="2" fill="#faf9f5" stroke="#141413" stroke-width="1"/>
|
||||
<text x="190" y="430" fill="#504e49" font-size="8.5" font-family="inherit">Backend · bundle</text>
|
||||
|
||||
<rect x="320" y="422" width="14" height="10" rx="2" fill="#EAE9E2" stroke="#504e49" stroke-width="1"/>
|
||||
<text x="340" y="430" fill="#504e49" font-size="8.5" font-family="inherit">Store</text>
|
||||
|
||||
<rect x="420" y="422" width="14" height="10" rx="2" fill="#EEEDE6" stroke="#B2B1AC" stroke-width="1"/>
|
||||
<text x="440" y="430" fill="#504e49" font-size="8.5" font-family="inherit">Cloud</text>
|
||||
|
||||
<rect x="510" y="422" width="14" height="10" rx="2" fill="#E9E8E1" stroke="#6b6a64" stroke-width="1"/>
|
||||
<text x="530" y="430" fill="#504e49" font-size="8.5" font-family="inherit">External</text>
|
||||
|
||||
<line x1="620" y1="428" x2="650" y2="428" stroke="#504e49" stroke-width="1.2" marker-end="url(#arrow)"/>
|
||||
<text x="658" y="430" fill="#504e49" font-size="8.5" font-family="inherit">Standard flow</text>
|
||||
|
||||
<line x1="780" y1="428" x2="810" y2="428" stroke="#1B365D" stroke-width="1.4" marker-end="url(#arrow-brand)"/>
|
||||
<text x="818" y="430" fill="#504e49" font-size="8.5" font-family="inherit">Primary path</text>
|
||||
</svg>
|
||||
|
||||
<p class="caption">Focal rule: one ink-blue node per diagram, marking the component the reader should look at first. Every other box stays in warm neutrals so the accent actually means something.</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,187 @@
|
||||
<!DOCTYPE html>
|
||||
<!-- ==================================================================
|
||||
DIAGRAM · Bar Chart (kami palette)
|
||||
Grouped bar chart for categorical comparison (revenue, market share, etc.)
|
||||
Up to 8 categories × 3 series. Demo: 4 quarters × 2 series.
|
||||
DATA START / DATA END marks the region Claude replaces when filling.
|
||||
================================================================== -->
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Bar Chart · kami diagram</title>
|
||||
<style>
|
||||
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
|
||||
:root {
|
||||
--parchment: #f5f4ed;
|
||||
--ivory: #faf9f5;
|
||||
--near-black: #141413;
|
||||
--olive: #504e49;
|
||||
--stone: #6b6a64;
|
||||
--brand: #1B365D;
|
||||
--brand-tint: #EEF2F7;
|
||||
--border: #e8e6dc;
|
||||
|
||||
--serif: Charter, Georgia, Palatino, serif;
|
||||
--sans: var(--serif);
|
||||
--mono: "JetBrains Mono", "SF Mono", Consolas, "TsangerJinKai02", "Source Han Serif SC", "Noto Serif CJK SC", "Songti SC", monospace;
|
||||
}
|
||||
|
||||
/* Poster-size page so WeasyPrint exports the full diagram without
|
||||
A4 clipping; browsers ignore @page. */
|
||||
@page { size: 1000px 960px; margin: 0; }
|
||||
|
||||
body {
|
||||
font-family: var(--sans);
|
||||
background: var(--parchment);
|
||||
color: var(--near-black);
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 3rem 2rem;
|
||||
}
|
||||
|
||||
.frame { max-width: 920px; width: 100%; }
|
||||
|
||||
.eyebrow {
|
||||
font-family: var(--mono);
|
||||
font-size: 0.7rem;
|
||||
letter-spacing: 0.22em;
|
||||
text-transform: uppercase;
|
||||
color: var(--stone);
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-family: var(--serif);
|
||||
font-size: 1.9rem;
|
||||
font-weight: 500;
|
||||
letter-spacing: -0.3pt;
|
||||
line-height: 1.15;
|
||||
margin-bottom: 1.6rem;
|
||||
}
|
||||
|
||||
svg { width: 100%; min-width: 640px; display: block; }
|
||||
|
||||
.caption {
|
||||
font-family: var(--serif);
|
||||
font-size: 0.95rem;
|
||||
color: var(--olive);
|
||||
margin-top: 1rem;
|
||||
max-width: 52ch;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="frame">
|
||||
<p class="eyebrow">Bar Chart · kami diagram</p>
|
||||
<h1>{{Chart Title}}</h1>
|
||||
|
||||
<!-- ============================================================
|
||||
DATA START
|
||||
Chart area: x=60 y=40 w=560 h=280 · base y=320
|
||||
viewBox: 0 0 680 420
|
||||
Groups: 4 · group-width=140 · bar-width=32 · gap=8
|
||||
Scale: max=140 · 1 unit = 2px
|
||||
Y-axis labels: 0/20/40/60/80/100/120/140
|
||||
Series colors: brand=#1B365D olive=#504e49
|
||||
============================================================ -->
|
||||
<svg viewBox="0 0 680 420" xmlns="http://www.w3.org/2000/svg">
|
||||
|
||||
<!-- Canvas -->
|
||||
<rect width="100%" height="100%" fill="#f5f4ed"/>
|
||||
|
||||
<!-- Horizontal grid lines (y = 320 down to 40, step 40 = 20 units × 2px) -->
|
||||
<line x1="60" y1="320" x2="620" y2="320" stroke="#e8e7e1" stroke-width="0.8"/>
|
||||
<line x1="60" y1="280" x2="620" y2="280" stroke="#e8e7e1" stroke-width="0.8"/>
|
||||
<line x1="60" y1="240" x2="620" y2="240" stroke="#e8e7e1" stroke-width="0.8"/>
|
||||
<line x1="60" y1="200" x2="620" y2="200" stroke="#e8e7e1" stroke-width="0.8"/>
|
||||
<line x1="60" y1="160" x2="620" y2="160" stroke="#e8e7e1" stroke-width="0.8"/>
|
||||
<line x1="60" y1="120" x2="620" y2="120" stroke="#e8e7e1" stroke-width="0.8"/>
|
||||
<line x1="60" y1="80" x2="620" y2="80" stroke="#e8e7e1" stroke-width="0.8"/>
|
||||
<line x1="60" y1="40" x2="620" y2="40" stroke="#e8e7e1" stroke-width="0.8"/>
|
||||
|
||||
<!-- Y-axis labels -->
|
||||
<text x="52" y="324" fill="#6b6a64" font-size="10" font-family="inherit" text-anchor="end">0</text>
|
||||
<text x="52" y="284" fill="#6b6a64" font-size="10" font-family="inherit" text-anchor="end">20</text>
|
||||
<text x="52" y="244" fill="#6b6a64" font-size="10" font-family="inherit" text-anchor="end">40</text>
|
||||
<text x="52" y="204" fill="#6b6a64" font-size="10" font-family="inherit" text-anchor="end">60</text>
|
||||
<text x="52" y="164" fill="#6b6a64" font-size="10" font-family="inherit" text-anchor="end">80</text>
|
||||
<text x="52" y="124" fill="#6b6a64" font-size="10" font-family="inherit" text-anchor="end">100</text>
|
||||
<text x="52" y="84" fill="#6b6a64" font-size="10" font-family="inherit" text-anchor="end">120</text>
|
||||
<text x="52" y="44" fill="#6b6a64" font-size="10" font-family="inherit" text-anchor="end">140</text>
|
||||
|
||||
<!-- Y-axis unit label -->
|
||||
<text x="16" y="184" fill="#6b6a64" font-size="9" font-family="'JetBrains Mono', monospace" letter-spacing="0.12em" transform="rotate(-90 16 184)" text-anchor="middle">UNIT</text>
|
||||
|
||||
<!-- X-axis baseline -->
|
||||
<line x1="60" y1="320" x2="620" y2="320" stroke="#141413" stroke-width="0.8"/>
|
||||
|
||||
<!-- ==================================================
|
||||
BARS · replace values between DATA START / DATA END
|
||||
Group centers: 130, 270, 410, 550
|
||||
Series A (brand) bars at: 94/234/374/514 · h from base 320
|
||||
Series B (olive) bars at: 134/274/414/554 · h from base 320
|
||||
================================================== -->
|
||||
|
||||
<!-- Group 1: x-label "2021" -->
|
||||
<!-- Series A: value 44 → height 88, top y=232 -->
|
||||
<rect x="94" y="232" width="32" height="88" fill="#1B365D" rx="2"/>
|
||||
<!-- Series B: value 32 → height 64, top y=256 -->
|
||||
<rect x="134" y="256" width="32" height="64" fill="#504e49" rx="2"/>
|
||||
|
||||
<!-- Group 2: x-label "2022" -->
|
||||
<!-- Series A: value 60 → height 120, top y=200 -->
|
||||
<rect x="234" y="200" width="32" height="120" fill="#1B365D" rx="2"/>
|
||||
<!-- Series B: value 48 → height 96, top y=224 -->
|
||||
<rect x="274" y="224" width="32" height="96" fill="#504e49" rx="2"/>
|
||||
|
||||
<!-- Group 3: x-label "2023" (focal) -->
|
||||
<!-- Series A: value 72 → height 144, top y=176 -->
|
||||
<rect x="374" y="176" width="32" height="144" fill="#1B365D" rx="2"/>
|
||||
<!-- Series B: value 64 → height 128, top y=192 -->
|
||||
<rect x="414" y="192" width="32" height="128" fill="#504e49" rx="2"/>
|
||||
|
||||
<!-- Group 4: x-label "2024" -->
|
||||
<!-- Series A: value 88 → height 176, top y=144 -->
|
||||
<rect x="514" y="144" width="32" height="176" fill="#1B365D" rx="2"/>
|
||||
<!-- Series B: value 76 → height 152, top y=168 -->
|
||||
<rect x="554" y="168" width="32" height="152" fill="#504e49" rx="2"/>
|
||||
|
||||
<!-- Data labels (top of each bar) -->
|
||||
<text x="110" y="228" fill="#141413" font-size="10" font-family="inherit" font-weight="500" text-anchor="middle">44</text>
|
||||
<text x="150" y="252" fill="#141413" font-size="10" font-family="inherit" text-anchor="middle">32</text>
|
||||
|
||||
<text x="250" y="196" fill="#141413" font-size="10" font-family="inherit" font-weight="500" text-anchor="middle">60</text>
|
||||
<text x="290" y="220" fill="#141413" font-size="10" font-family="inherit" text-anchor="middle">48</text>
|
||||
|
||||
<text x="390" y="172" fill="#141413" font-size="10" font-family="inherit" font-weight="500" text-anchor="middle">72</text>
|
||||
<text x="430" y="188" fill="#141413" font-size="10" font-family="inherit" text-anchor="middle">64</text>
|
||||
|
||||
<text x="530" y="140" fill="#141413" font-size="10" font-family="inherit" font-weight="500" text-anchor="middle">88</text>
|
||||
<text x="570" y="164" fill="#141413" font-size="10" font-family="inherit" text-anchor="middle">76</text>
|
||||
|
||||
<!-- X-axis category labels (group centers) -->
|
||||
<text x="130" y="340" fill="#141413" font-size="11" font-family="inherit" text-anchor="middle">2021</text>
|
||||
<text x="270" y="340" fill="#141413" font-size="11" font-family="inherit" text-anchor="middle">2022</text>
|
||||
<text x="410" y="340" fill="#141413" font-size="11" font-family="inherit" text-anchor="middle">2023</text>
|
||||
<text x="550" y="340" fill="#141413" font-size="11" font-family="inherit" text-anchor="middle">2024</text>
|
||||
|
||||
<!-- ================================================== DATA END -->
|
||||
|
||||
<!-- Legend -->
|
||||
<line x1="60" y1="368" x2="620" y2="368" stroke="#e8e7e1" stroke-width="0.8"/>
|
||||
|
||||
<rect x="60" y="380" width="12" height="12" fill="#1B365D" rx="2"/>
|
||||
<text x="78" y="391" fill="#504e49" font-size="10" font-family="inherit">{{Series A label}}</text>
|
||||
|
||||
<rect x="200" y="380" width="12" height="12" fill="#504e49" rx="2"/>
|
||||
<text x="218" y="391" fill="#504e49" font-size="10" font-family="inherit">{{Series B label}}</text>
|
||||
|
||||
</svg>
|
||||
|
||||
<p class="caption">{{Caption text. The focal series in ink-blue carries the primary argument. State the takeaway here, not a description of what is plotted.}}</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,223 @@
|
||||
<!DOCTYPE html>
|
||||
<!-- ==================================================================
|
||||
DIAGRAM · Candlestick Chart (kami palette)
|
||||
OHLC price history for equity analysis.
|
||||
Up to 30 data points. Demo: 20 trading days.
|
||||
DATA START / DATA END marks the region Claude replaces when filling.
|
||||
================================================================== -->
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Candlestick Chart · kami diagram</title>
|
||||
<style>
|
||||
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
|
||||
:root {
|
||||
--parchment: #f5f4ed;
|
||||
--ivory: #faf9f5;
|
||||
--near-black: #141413;
|
||||
--olive: #504e49;
|
||||
--stone: #6b6a64;
|
||||
--brand: #1B365D;
|
||||
--brand-tint: #EEF2F7;
|
||||
--border: #e8e6dc;
|
||||
--up: #1B365D;
|
||||
--down: #6b6a64;
|
||||
|
||||
--serif: Charter, Georgia, Palatino, serif;
|
||||
--sans: var(--serif);
|
||||
--mono: "JetBrains Mono", "SF Mono", Consolas, "TsangerJinKai02", "Source Han Serif SC", "Noto Serif CJK SC", "Songti SC", monospace;
|
||||
}
|
||||
|
||||
/* Poster-size page so WeasyPrint exports the full diagram without
|
||||
A4 clipping; browsers ignore @page. */
|
||||
@page { size: 1000px 900px; margin: 0; }
|
||||
|
||||
body {
|
||||
font-family: var(--sans);
|
||||
background: var(--parchment);
|
||||
color: var(--near-black);
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 3rem 2rem;
|
||||
}
|
||||
|
||||
.frame { max-width: 920px; width: 100%; }
|
||||
|
||||
.eyebrow {
|
||||
font-family: var(--mono);
|
||||
font-size: 0.7rem;
|
||||
letter-spacing: 0.22em;
|
||||
text-transform: uppercase;
|
||||
color: var(--stone);
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-family: var(--serif);
|
||||
font-size: 1.9rem;
|
||||
font-weight: 500;
|
||||
letter-spacing: -0.3pt;
|
||||
line-height: 1.15;
|
||||
margin-bottom: 1.6rem;
|
||||
}
|
||||
|
||||
svg { width: 100%; min-width: 640px; display: block; }
|
||||
|
||||
.caption {
|
||||
font-family: var(--serif);
|
||||
font-size: 0.95rem;
|
||||
color: var(--olive);
|
||||
margin-top: 1rem;
|
||||
max-width: 52ch;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="frame">
|
||||
<p class="eyebrow">Candlestick · kami diagram</p>
|
||||
<h1>{{Chart Title}}</h1>
|
||||
|
||||
<!-- ============================================================
|
||||
DATA START
|
||||
Chart area: x=60 y=40 w=640 h=280 · base y=320
|
||||
viewBox: 0 0 760 420
|
||||
Candles: 20 · candle-spacing=32 · body-width=16
|
||||
Price range: 100-160 · scale: 1 unit = 4.67px (280/60)
|
||||
Y-axis: 100 / 110 / 120 / 130 / 140 / 150 / 160
|
||||
Up color: #1B365D (brand) · Down color: #6b6a64 (stone)
|
||||
Wick: 1.2px stroke centered on candle
|
||||
============================================================ -->
|
||||
<svg viewBox="0 0 760 420" xmlns="http://www.w3.org/2000/svg">
|
||||
|
||||
<!-- Canvas -->
|
||||
<rect width="100%" height="100%" fill="#f5f4ed"/>
|
||||
|
||||
<!-- Horizontal grid lines (y: 320=100, 273=110, 227=120, 180=130, 133=140, 87=150, 40=160) -->
|
||||
<line x1="60" y1="320" x2="700" y2="320" stroke="#e8e7e1" stroke-width="0.8"/>
|
||||
<line x1="60" y1="273" x2="700" y2="273" stroke="#e8e7e1" stroke-width="0.8"/>
|
||||
<line x1="60" y1="227" x2="700" y2="227" stroke="#e8e7e1" stroke-width="0.8"/>
|
||||
<line x1="60" y1="180" x2="700" y2="180" stroke="#e8e7e1" stroke-width="0.8"/>
|
||||
<line x1="60" y1="133" x2="700" y2="133" stroke="#e8e7e1" stroke-width="0.8"/>
|
||||
<line x1="60" y1="87" x2="700" y2="87" stroke="#e8e7e1" stroke-width="0.8"/>
|
||||
<line x1="60" y1="40" x2="700" y2="40" stroke="#e8e7e1" stroke-width="0.8"/>
|
||||
|
||||
<!-- Y-axis labels -->
|
||||
<text x="52" y="324" fill="#6b6a64" font-size="10" text-anchor="end" font-family="Charter, Georgia, serif">100</text>
|
||||
<text x="52" y="277" fill="#6b6a64" font-size="10" text-anchor="end" font-family="Charter, Georgia, serif">110</text>
|
||||
<text x="52" y="231" fill="#6b6a64" font-size="10" text-anchor="end" font-family="Charter, Georgia, serif">120</text>
|
||||
<text x="52" y="184" fill="#6b6a64" font-size="10" text-anchor="end" font-family="Charter, Georgia, serif">130</text>
|
||||
<text x="52" y="137" fill="#6b6a64" font-size="10" text-anchor="end" font-family="Charter, Georgia, serif">140</text>
|
||||
<text x="52" y="91" fill="#6b6a64" font-size="10" text-anchor="end" font-family="Charter, Georgia, serif">150</text>
|
||||
<text x="52" y="44" fill="#6b6a64" font-size="10" text-anchor="end" font-family="Charter, Georgia, serif">160</text>
|
||||
|
||||
<!-- Candlesticks: 20 days, x starts at 76, spacing=32
|
||||
price_to_y(p) = 320 - (p - 100) * 4.67
|
||||
Up candle: fill=#1B365D (close > open, body from open to close)
|
||||
Down candle: fill=#6b6a64 (close < open, body from close to open) -->
|
||||
|
||||
<!-- Day 1: O=120 H=128 L=116 C=126 (UP) -->
|
||||
<line x1="76" y1="187" x2="76" y2="245" stroke="#1B365D" stroke-width="1.2"/>
|
||||
<rect x="68" y="194" width="16" height="28" fill="#1B365D" rx="1"/>
|
||||
|
||||
<!-- Day 2: O=126 H=132 L=122 C=130 (UP) -->
|
||||
<line x1="108" y1="171" x2="108" y2="217" stroke="#1B365D" stroke-width="1.2"/>
|
||||
<rect x="100" y="180" width="16" height="19" fill="#1B365D" rx="1"/>
|
||||
|
||||
<!-- Day 3: O=130 H=136 L=126 C=124 (DOWN) -->
|
||||
<line x1="140" y1="152" x2="140" y2="208" stroke="#6b6a64" stroke-width="1.2"/>
|
||||
<rect x="132" y="180" width="16" height="28" fill="#6b6a64" rx="1"/>
|
||||
|
||||
<!-- Day 4: O=124 H=128 L=118 C=122 (DOWN) -->
|
||||
<line x1="172" y1="189" x2="172" y2="236" stroke="#6b6a64" stroke-width="1.2"/>
|
||||
<rect x="164" y="208" width="16" height="9" fill="#6b6a64" rx="1"/>
|
||||
|
||||
<!-- Day 5: O=122 H=130 L=120 C=128 (UP) -->
|
||||
<line x1="204" y1="180" x2="204" y2="227" stroke="#1B365D" stroke-width="1.2"/>
|
||||
<rect x="196" y="189" width="16" height="28" fill="#1B365D" rx="1"/>
|
||||
|
||||
<!-- Day 6: O=128 H=134 L=126 C=132 (UP) -->
|
||||
<line x1="236" y1="161" x2="236" y2="199" stroke="#1B365D" stroke-width="1.2"/>
|
||||
<rect x="228" y="171" width="16" height="19" fill="#1B365D" rx="1"/>
|
||||
|
||||
<!-- Day 7: O=132 H=138 L=128 C=136 (UP) -->
|
||||
<line x1="268" y1="143" x2="268" y2="189" stroke="#1B365D" stroke-width="1.2"/>
|
||||
<rect x="260" y="152" width="16" height="19" fill="#1B365D" rx="1"/>
|
||||
|
||||
<!-- Day 8: O=136 H=142 L=132 C=134 (DOWN) -->
|
||||
<line x1="300" y1="124" x2="300" y2="171" stroke="#6b6a64" stroke-width="1.2"/>
|
||||
<rect x="292" y="143" width="16" height="9" fill="#6b6a64" rx="1"/>
|
||||
|
||||
<!-- Day 9: O=134 H=140 L=130 C=138 (UP) -->
|
||||
<line x1="332" y1="133" x2="332" y2="180" stroke="#1B365D" stroke-width="1.2"/>
|
||||
<rect x="324" y="143" width="16" height="19" fill="#1B365D" rx="1"/>
|
||||
|
||||
<!-- Day 10: O=138 H=144 L=134 C=136 (DOWN) -->
|
||||
<line x1="364" y1="115" x2="364" y2="161" stroke="#6b6a64" stroke-width="1.2"/>
|
||||
<rect x="356" y="133" width="16" height="9" fill="#6b6a64" rx="1"/>
|
||||
|
||||
<!-- Day 11: O=136 H=140 L=132 C=140 (UP) -->
|
||||
<line x1="396" y1="133" x2="396" y2="171" stroke="#1B365D" stroke-width="1.2"/>
|
||||
<rect x="388" y="133" width="16" height="19" fill="#1B365D" rx="1"/>
|
||||
|
||||
<!-- Day 12: O=140 H=146 L=136 C=144 (UP) -->
|
||||
<line x1="428" y1="105" x2="428" y2="152" stroke="#1B365D" stroke-width="1.2"/>
|
||||
<rect x="420" y="115" width="16" height="19" fill="#1B365D" rx="1"/>
|
||||
|
||||
<!-- Day 13: O=144 H=148 L=140 C=142 (DOWN) -->
|
||||
<line x1="460" y1="96" x2="460" y2="133" stroke="#6b6a64" stroke-width="1.2"/>
|
||||
<rect x="452" y="115" width="16" height="9" fill="#6b6a64" rx="1"/>
|
||||
|
||||
<!-- Day 14: O=142 H=148 L=138 C=146 (UP) -->
|
||||
<line x1="492" y1="96" x2="492" y2="143" stroke="#1B365D" stroke-width="1.2"/>
|
||||
<rect x="484" y="105" width="16" height="19" fill="#1B365D" rx="1"/>
|
||||
|
||||
<!-- Day 15: O=146 H=152 L=142 C=148 (UP) -->
|
||||
<line x1="524" y1="77" x2="524" y2="124" stroke="#1B365D" stroke-width="1.2"/>
|
||||
<rect x="516" y="96" width="16" height="9" fill="#1B365D" rx="1"/>
|
||||
|
||||
<!-- Day 16: O=148 H=152 L=144 C=146 (DOWN) -->
|
||||
<line x1="556" y1="77" x2="556" y2="115" stroke="#6b6a64" stroke-width="1.2"/>
|
||||
<rect x="548" y="96" width="16" height="9" fill="#6b6a64" rx="1"/>
|
||||
|
||||
<!-- Day 17: O=146 H=150 L=142 C=150 (UP) -->
|
||||
<line x1="588" y1="87" x2="588" y2="124" stroke="#1B365D" stroke-width="1.2"/>
|
||||
<rect x="580" y="87" width="16" height="19" fill="#1B365D" rx="1"/>
|
||||
|
||||
<!-- Day 18: O=150 H=154 L=146 C=148 (DOWN) -->
|
||||
<line x1="620" y1="68" x2="620" y2="96" stroke="#6b6a64" stroke-width="1.2"/>
|
||||
<rect x="612" y="77" width="16" height="19" fill="#6b6a64" rx="1"/>
|
||||
|
||||
<!-- Day 19: O=148 H=156 L=146 C=154 (UP) -->
|
||||
<line x1="652" y1="59" x2="652" y2="96" stroke="#1B365D" stroke-width="1.2"/>
|
||||
<rect x="644" y="68" width="16" height="19" fill="#1B365D" rx="1"/>
|
||||
|
||||
<!-- Day 20: O=154 H=158 L=150 C=152 (DOWN) -->
|
||||
<line x1="684" y1="49" x2="684" y2="87" stroke="#6b6a64" stroke-width="1.2"/>
|
||||
<rect x="676" y="59" width="16" height="9" fill="#6b6a64" rx="1"/>
|
||||
<!-- DATA END -->
|
||||
|
||||
<!-- X-axis date labels (every 5th day) -->
|
||||
<text x="76" y="340" fill="#6b6a64" font-size="10" text-anchor="middle" font-family="Charter, Georgia, serif">{{D1}}</text>
|
||||
<text x="236" y="340" fill="#6b6a64" font-size="10" text-anchor="middle" font-family="Charter, Georgia, serif">{{D6}}</text>
|
||||
<text x="396" y="340" fill="#6b6a64" font-size="10" text-anchor="middle" font-family="Charter, Georgia, serif">{{D11}}</text>
|
||||
<text x="556" y="340" fill="#6b6a64" font-size="10" text-anchor="middle" font-family="Charter, Georgia, serif">{{D16}}</text>
|
||||
<text x="684" y="340" fill="#6b6a64" font-size="10" text-anchor="middle" font-family="Charter, Georgia, serif">{{D20}}</text>
|
||||
|
||||
<!-- Legend -->
|
||||
<line x1="60" y1="368" x2="700" y2="368" stroke="#e8e7e1" stroke-width="0.8"/>
|
||||
|
||||
<rect x="60" y="380" width="12" height="12" fill="#1B365D" rx="2"/>
|
||||
<text x="78" y="391" fill="#504e49" font-size="10" font-family="Charter, Georgia, serif">Up (close > open)</text>
|
||||
|
||||
<rect x="240" y="380" width="12" height="12" fill="#6b6a64" rx="2"/>
|
||||
<text x="258" y="391" fill="#504e49" font-size="10" font-family="Charter, Georgia, serif">Down (close < open)</text>
|
||||
|
||||
</svg>
|
||||
|
||||
<p class="caption">{{Caption: e.g. "20-day price action showing accumulation phase."}}</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,158 @@
|
||||
<!DOCTYPE html>
|
||||
<!-- ==================================================================
|
||||
DIAGRAM · Class (kami palette, Mermaid-sourced)
|
||||
Source: assets/diagrams/src/class.mmd. Generated with beautiful-mermaid +
|
||||
scripts/mermaid_normalize.py (Kami re-theme + WeasyPrint-safe static SVG). To
|
||||
change it, regenerate the SVG from any beautiful-mermaid renderer and re-run
|
||||
the normalizer; do not hand-tune coordinates. See references/mermaid.md.
|
||||
================================================================== -->
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Class · kami diagram</title>
|
||||
<style>
|
||||
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
|
||||
@font-face {
|
||||
font-family: "TsangerJinKai02";
|
||||
src: url("../fonts/TsangerJinKai02-W04.ttf") format("truetype"),
|
||||
url("https://cdn.jsdelivr.net/gh/tw93/Kami@main/assets/fonts/TsangerJinKai02-W04.ttf") format("truetype");
|
||||
font-weight: 400;
|
||||
font-style: normal;
|
||||
}
|
||||
@font-face {
|
||||
font-family: "TsangerJinKai02";
|
||||
src: url("../fonts/TsangerJinKai02-W05.ttf") format("truetype"),
|
||||
url("https://cdn.jsdelivr.net/gh/tw93/Kami@main/assets/fonts/TsangerJinKai02-W05.ttf") format("truetype");
|
||||
font-weight: 500;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
:root {
|
||||
--parchment: #f5f4ed;
|
||||
--ivory: #faf9f5;
|
||||
--near-black: #141413;
|
||||
--olive: #504e49;
|
||||
--stone: #6b6a64;
|
||||
--brand: #1B365D;
|
||||
--border: #e8e6dc;
|
||||
|
||||
--serif: Charter, Georgia, "TsangerJinKai02", "Source Han Serif SC", "Noto Serif CJK SC", serif;
|
||||
--sans: var(--serif);
|
||||
--mono: "JetBrains Mono", "SF Mono", Consolas, "TsangerJinKai02", "Source Han Serif SC", monospace;
|
||||
}
|
||||
|
||||
/* Poster-size page so WeasyPrint exports the full diagram without
|
||||
A4 clipping; browsers ignore @page. */
|
||||
@page { size: 800px 1180px; margin: 0; }
|
||||
|
||||
body {
|
||||
font-family: var(--sans);
|
||||
background: var(--parchment);
|
||||
color: var(--near-black);
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 3rem 2rem;
|
||||
}
|
||||
|
||||
.frame { max-width: 720px; width: 100%; text-align: center; }
|
||||
.frame > .eyebrow, .frame > h1, .frame > .caption { text-align: left; }
|
||||
|
||||
.eyebrow {
|
||||
font-family: var(--mono);
|
||||
font-size: 0.7rem;
|
||||
letter-spacing: 0.22em;
|
||||
text-transform: uppercase;
|
||||
color: var(--stone);
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-family: var(--serif);
|
||||
font-size: 1.9rem;
|
||||
font-weight: 500;
|
||||
letter-spacing: -0.3pt;
|
||||
line-height: 1.15;
|
||||
margin-bottom: 1.6rem;
|
||||
}
|
||||
|
||||
/* Size to the diagram's intrinsic dimensions, capped so wide diagrams fit
|
||||
the frame and tall ones stay on a single page. Never upscale past natural
|
||||
size (avoids blowing a 200px-wide class diagram up to full width). */
|
||||
svg { display: block; margin: 0 auto; max-width: 100%; max-height: 780px; height: auto; width: auto; }
|
||||
|
||||
.caption {
|
||||
font-family: var(--serif);
|
||||
font-size: 0.95rem;
|
||||
color: var(--olive);
|
||||
margin-top: 1rem;
|
||||
max-width: 52ch;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="frame">
|
||||
<p class="eyebrow">Class · kami diagram</p>
|
||||
<h1>{{What structure does this model}}</h1>
|
||||
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 208.2 604.6" width="208.2" height="604.6" style="background:#f5f4ed">
|
||||
<style>
|
||||
text { font-family: Charter, Georgia, "TsangerJinKai02", "Source Han Serif SC", "Noto Serif CJK SC", serif; }
|
||||
.mono { font-family: Charter, Georgia, "TsangerJinKai02", "Source Han Serif SC", "Noto Serif CJK SC", serif; }
|
||||
</style>
|
||||
<defs>
|
||||
<marker id="cls-inherit" markerWidth="12" markerHeight="10" refX="12" refY="5" orient="auto-start-reverse">
|
||||
<polygon points="0 0, 12 5, 0 10" fill="#f5f4ed" stroke="#1b365d" stroke-width="1.5" />
|
||||
</marker>
|
||||
<marker id="cls-composition" markerWidth="12" markerHeight="10" refX="0" refY="5" orient="auto-start-reverse">
|
||||
<polygon points="6 0, 12 5, 6 10, 0 5" fill="#1b365d" stroke="#1b365d" stroke-width="1" />
|
||||
</marker>
|
||||
<marker id="cls-aggregation" markerWidth="12" markerHeight="10" refX="0" refY="5" orient="auto-start-reverse">
|
||||
<polygon points="6 0, 12 5, 6 10, 0 5" fill="#f5f4ed" stroke="#1b365d" stroke-width="1.5" />
|
||||
</marker>
|
||||
<marker id="cls-arrow" markerWidth="8" markerHeight="6" refX="8" refY="3" orient="auto-start-reverse">
|
||||
<polyline points="0 0, 8 3, 0 6" fill="none" stroke="#1b365d" stroke-width="1.5" />
|
||||
</marker>
|
||||
</defs>
|
||||
<polyline class="class-relationship" data-from="Customer" data-to="Order" data-type="association" data-marker-at="to" data-label="places" data-from-cardinality="1" data-to-cardinality="*" points="104.1,108 104.1,248.3" fill="none" stroke="#504e49" stroke-width="1" marker-end="url(#cls-arrow)" />
|
||||
<polyline class="class-relationship" data-from="Order" data-to="LineItem" data-type="composition" data-marker-at="from" data-label="contains" data-from-cardinality="1" data-to-cardinality="*" points="104.1,356.3 104.1,496.6" fill="none" stroke="#504e49" stroke-width="1" marker-start="url(#cls-composition)" />
|
||||
<g class="class-node" data-id="Order" data-label="Order">
|
||||
<rect x="40" y="248.3" width="128.2" height="108" rx="0" ry="0" fill="#faf9f5" stroke="#e8e6dc" stroke-width="1" />
|
||||
<rect x="40" y="248.3" width="128.2" height="32" rx="0" ry="0" fill="#eae9e2" stroke="#e8e6dc" stroke-width="1" />
|
||||
<text x="104.1" y="264.3" text-anchor="middle" font-size="13" font-weight="700" fill="#141413" dy="4.55">Order</text>
|
||||
<line x1="40" y1="280.3" x2="168.2" y2="280.3" stroke="#e8e6dc" stroke-width="0.75" />
|
||||
<text x="48" y="294.3" class="mono" dy="0.35em" font-size="11" font-weight="400"><tspan fill="#bdbcb6">+ </tspan><tspan fill="#6b6a64">id</tspan><tspan fill="#bdbcb6">: </tspan><tspan fill="#6b6a64">String</tspan></text>
|
||||
<text x="48" y="314.3" class="mono" dy="0.35em" font-size="11" font-weight="400"><tspan fill="#bdbcb6">+ </tspan><tspan fill="#6b6a64">createdAt</tspan><tspan fill="#bdbcb6">: </tspan><tspan fill="#6b6a64">Date</tspan></text>
|
||||
<line x1="40" y1="328.3" x2="168.2" y2="328.3" stroke="#e8e6dc" stroke-width="0.75" />
|
||||
<text x="48" y="342.3" class="mono" dy="0.35em" font-size="11" font-weight="400"><tspan fill="#bdbcb6">+ </tspan><tspan fill="#6b6a64">total()</tspan><tspan fill="#bdbcb6">: </tspan><tspan fill="#6b6a64">Money</tspan></text>
|
||||
</g>
|
||||
<g class="class-node" data-id="Customer" data-label="Customer">
|
||||
<rect x="44.099999999999994" y="40" width="120" height="68" rx="0" ry="0" fill="#faf9f5" stroke="#e8e6dc" stroke-width="1" />
|
||||
<rect x="44.099999999999994" y="40" width="120" height="32" rx="0" ry="0" fill="#eae9e2" stroke="#e8e6dc" stroke-width="1" />
|
||||
<text x="104.1" y="56" text-anchor="middle" font-size="13" font-weight="700" fill="#141413" dy="4.55">Customer</text>
|
||||
<line x1="44.099999999999994" y1="72" x2="164.1" y2="72" stroke="#e8e6dc" stroke-width="0.75" />
|
||||
<text x="52.099999999999994" y="86" class="mono" dy="0.35em" font-size="11" font-weight="400"><tspan fill="#bdbcb6">+ </tspan><tspan fill="#6b6a64">name</tspan><tspan fill="#bdbcb6">: </tspan><tspan fill="#6b6a64">String</tspan></text>
|
||||
<line x1="44.099999999999994" y1="100" x2="164.1" y2="100" stroke="#e8e6dc" stroke-width="0.75" />
|
||||
</g>
|
||||
<g class="class-node" data-id="LineItem" data-label="LineItem">
|
||||
<rect x="44.099999999999994" y="496.6" width="120" height="68" rx="0" ry="0" fill="#faf9f5" stroke="#e8e6dc" stroke-width="1" />
|
||||
<rect x="44.099999999999994" y="496.6" width="120" height="32" rx="0" ry="0" fill="#eae9e2" stroke="#e8e6dc" stroke-width="1" />
|
||||
<text x="104.1" y="512.6" text-anchor="middle" font-size="13" font-weight="700" fill="#141413" dy="4.55">LineItem</text>
|
||||
<line x1="44.099999999999994" y1="528.6" x2="164.1" y2="528.6" stroke="#e8e6dc" stroke-width="0.75" />
|
||||
<text x="52.099999999999994" y="542.6" class="mono" dy="0.35em" font-size="11" font-weight="400"><tspan fill="#bdbcb6">+ </tspan><tspan fill="#6b6a64">qty</tspan><tspan fill="#bdbcb6">: </tspan><tspan fill="#6b6a64">int</tspan></text>
|
||||
<line x1="44.099999999999994" y1="556.6" x2="164.1" y2="556.6" stroke="#e8e6dc" stroke-width="0.75" />
|
||||
</g>
|
||||
<text x="81.237" y="170.15" font-size="11" text-anchor="middle" font-weight="400" fill="#6b6a64" dy="3.8499999999999996">places</text>
|
||||
<text x="90.1" y="122" font-size="11" text-anchor="middle" font-weight="400" fill="#6b6a64" dy="3.8499999999999996">1</text>
|
||||
<text x="90.1" y="234.3" font-size="11" text-anchor="middle" font-weight="400" fill="#6b6a64" dy="3.8499999999999996">*</text>
|
||||
<text x="77.079" y="418.45" font-size="11" text-anchor="middle" font-weight="400" fill="#6b6a64" dy="3.8499999999999996">contains</text>
|
||||
<text x="90.1" y="370.3" font-size="11" text-anchor="middle" font-weight="400" fill="#6b6a64" dy="3.8499999999999996">1</text>
|
||||
<text x="90.1" y="482.6" font-size="11" text-anchor="middle" font-weight="400" fill="#6b6a64" dy="3.8499999999999996">*</text>
|
||||
</svg>
|
||||
|
||||
<p class="caption">Boxes are types; compartments list fields and methods. Arrows carry association and composition.</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,194 @@
|
||||
<!DOCTYPE html>
|
||||
<!-- ==================================================================
|
||||
DIAGRAM · Donut Chart (kami palette)
|
||||
Proportional distribution (spend breakdown, user segments, etc.)
|
||||
Up to 6 segments. Demo: 6-segment revenue mix.
|
||||
DATA START / DATA END marks the region Claude replaces when filling.
|
||||
|
||||
Arc path formula (clockwise, starting from top -90°):
|
||||
M outer_start_x outer_start_y
|
||||
A R R 0 large-arc 1 outer_end_x outer_end_y
|
||||
L inner_end_x inner_end_y
|
||||
A r r 0 large-arc 0 inner_start_x inner_start_y
|
||||
Z
|
||||
cx=300 cy=200 R=136 (outer) r=76 (inner)
|
||||
large-arc flag = 1 if segment > 180°, else 0
|
||||
================================================================== -->
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Donut Chart · kami diagram</title>
|
||||
<style>
|
||||
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
|
||||
:root {
|
||||
--parchment: #f5f4ed;
|
||||
--ivory: #faf9f5;
|
||||
--near-black: #141413;
|
||||
--olive: #504e49;
|
||||
--stone: #6b6a64;
|
||||
--brand: #1B365D;
|
||||
--brand-tint: #EEF2F7;
|
||||
--border: #e8e6dc;
|
||||
|
||||
--serif: Charter, Georgia, Palatino, serif;
|
||||
--sans: var(--serif);
|
||||
--mono: "JetBrains Mono", "SF Mono", Consolas, "TsangerJinKai02", "Source Han Serif SC", "Noto Serif CJK SC", "Songti SC", monospace;
|
||||
}
|
||||
|
||||
/* Poster-size page so WeasyPrint exports the full diagram without
|
||||
A4 clipping; browsers ignore @page. */
|
||||
@page { size: 1000px 960px; margin: 0; }
|
||||
|
||||
body {
|
||||
font-family: var(--sans);
|
||||
background: var(--parchment);
|
||||
color: var(--near-black);
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 3rem 2rem;
|
||||
}
|
||||
|
||||
.frame { max-width: 920px; width: 100%; }
|
||||
|
||||
.eyebrow {
|
||||
font-family: var(--mono);
|
||||
font-size: 0.7rem;
|
||||
letter-spacing: 0.22em;
|
||||
text-transform: uppercase;
|
||||
color: var(--stone);
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-family: var(--serif);
|
||||
font-size: 1.9rem;
|
||||
font-weight: 500;
|
||||
letter-spacing: -0.3pt;
|
||||
line-height: 1.15;
|
||||
margin-bottom: 1.6rem;
|
||||
}
|
||||
|
||||
svg { width: 100%; min-width: 640px; display: block; }
|
||||
|
||||
.caption {
|
||||
font-family: var(--serif);
|
||||
font-size: 0.95rem;
|
||||
color: var(--olive);
|
||||
margin-top: 1rem;
|
||||
max-width: 52ch;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="frame">
|
||||
<p class="eyebrow">Donut Chart · kami diagram</p>
|
||||
<h1>{{Chart Title}}</h1>
|
||||
|
||||
<!-- ============================================================
|
||||
DATA START
|
||||
viewBox: 0 0 680 420
|
||||
Donut center: cx=300 cy=200 R=136 (outer) r=76 (inner)
|
||||
6 segments, clockwise from top (-90°):
|
||||
S1: 32% -90° to 25.2° fill=#1B365D (focal/largest)
|
||||
S2: 24% 25.2° to 111.6° fill=#504e49
|
||||
S3: 18% 111.6° to 176.4° fill=#6b6a64
|
||||
S4: 12% 176.4° to 219.6° fill=#b8b7b0
|
||||
S5: 8% 219.6° to 248.4° fill=#d4d3cd
|
||||
S6: 6% 248.4° to 270° fill=#EEF2F7 stroke=#e8e7e1
|
||||
Center text: primary label + secondary label at (300,200)
|
||||
Legend: x=476 from y=144 to y=296 step=24
|
||||
============================================================ -->
|
||||
<svg viewBox="0 0 680 420" xmlns="http://www.w3.org/2000/svg">
|
||||
|
||||
<!-- Canvas -->
|
||||
<rect width="100%" height="100%" fill="#f5f4ed"/>
|
||||
|
||||
<!-- ==================================================
|
||||
DONUT SEGMENTS · replace between DATA START / DATA END
|
||||
Arc coordinates pre-computed for cx=300 cy=200 R=136 r=76
|
||||
================================================== -->
|
||||
|
||||
<!-- S1: 32% · -90° to 25.2° · span 115.2° · large-arc=0 -->
|
||||
<path d="M 300 64 A 136 136 0 0 1 423.1 257.9 L 368.8 232.4 A 76 76 0 0 0 300 124 Z"
|
||||
fill="#1B365D"/>
|
||||
|
||||
<!-- S2: 24% · 25.2° to 111.6° · span 86.4° · large-arc=0 -->
|
||||
<path d="M 423.1 257.9 A 136 136 0 0 1 249.1 326.1 L 271.5 270.5 A 76 76 0 0 0 368.8 232.4 Z"
|
||||
fill="#504e49"/>
|
||||
|
||||
<!-- S3: 18% · 111.6° to 176.4° · span 64.8° · large-arc=0 -->
|
||||
<path d="M 249.1 326.1 A 136 136 0 0 1 164.3 208.6 L 224.2 204.8 A 76 76 0 0 0 271.5 270.5 Z"
|
||||
fill="#6b6a64"/>
|
||||
|
||||
<!-- S4: 12% · 176.4° to 219.6° · span 43.2° · large-arc=0 -->
|
||||
<path d="M 164.3 208.6 A 136 136 0 0 1 195.2 113.3 L 241.4 151.6 A 76 76 0 0 0 224.2 204.8 Z"
|
||||
fill="#b8b7b0"/>
|
||||
|
||||
<!-- S5: 8% · 219.6° to 248.4° · span 28.8° · large-arc=0 -->
|
||||
<path d="M 195.2 113.3 A 136 136 0 0 1 249.8 73.6 L 271.9 129.4 A 76 76 0 0 0 241.4 151.6 Z"
|
||||
fill="#d4d3cd"/>
|
||||
|
||||
<!-- S6: 6% · 248.4° to 270° · span 21.6° · large-arc=0 -->
|
||||
<path d="M 249.8 73.6 A 136 136 0 0 1 300 64 L 300 124 A 76 76 0 0 0 271.9 129.4 Z"
|
||||
fill="#EEF2F7" stroke="#e8e7e1" stroke-width="0.8"/>
|
||||
|
||||
<!-- Thin separator ring over segment joins (visual polish) -->
|
||||
<circle cx="300" cy="200" r="136" fill="none" stroke="#f5f4ed" stroke-width="1.5"/>
|
||||
<circle cx="300" cy="200" r="76" fill="none" stroke="#f5f4ed" stroke-width="1.5"/>
|
||||
|
||||
<!-- Donut hole background -->
|
||||
<circle cx="300" cy="200" r="75" fill="#f5f4ed"/>
|
||||
|
||||
<!-- Center label (primary value + secondary text) -->
|
||||
<text x="300" y="193" fill="#1B365D" font-size="26" font-family="inherit" font-weight="500" text-anchor="middle">32%</text>
|
||||
<text x="300" y="214" fill="#6b6a64" font-size="10" font-family="inherit" text-anchor="middle" letter-spacing="0.1em">{{CENTER LABEL}}</text>
|
||||
|
||||
<!-- ================================================== DATA END -->
|
||||
|
||||
<!-- Legend (right of donut) -->
|
||||
<line x1="460" y1="120" x2="460" y2="300" stroke="#e8e7e1" stroke-width="0.8"/>
|
||||
|
||||
<!-- S1 -->
|
||||
<rect x="472" y="136" width="12" height="12" fill="#1B365D" rx="2"/>
|
||||
<text x="492" y="147" fill="#141413" font-size="9.5" font-family="inherit" font-weight="500">32%</text>
|
||||
<text x="532" y="147" fill="#504e49" font-size="9" font-family="inherit">{{Category A}}</text>
|
||||
|
||||
<!-- S2 -->
|
||||
<rect x="472" y="160" width="12" height="12" fill="#504e49" rx="2"/>
|
||||
<text x="492" y="171" fill="#141413" font-size="9.5" font-family="inherit">24%</text>
|
||||
<text x="532" y="171" fill="#504e49" font-size="9" font-family="inherit">{{Category B}}</text>
|
||||
|
||||
<!-- S3 -->
|
||||
<rect x="472" y="184" width="12" height="12" fill="#6b6a64" rx="2"/>
|
||||
<text x="492" y="195" fill="#141413" font-size="9.5" font-family="inherit">18%</text>
|
||||
<text x="532" y="195" fill="#504e49" font-size="9" font-family="inherit">{{Category C}}</text>
|
||||
|
||||
<!-- S4 -->
|
||||
<rect x="472" y="208" width="12" height="12" fill="#b8b7b0" rx="2"/>
|
||||
<text x="492" y="219" fill="#141413" font-size="9.5" font-family="inherit">12%</text>
|
||||
<text x="532" y="219" fill="#504e49" font-size="9" font-family="inherit">{{Category D}}</text>
|
||||
|
||||
<!-- S5 -->
|
||||
<rect x="472" y="232" width="12" height="12" fill="#d4d3cd" rx="2"/>
|
||||
<text x="492" y="243" fill="#141413" font-size="9.5" font-family="inherit">8%</text>
|
||||
<text x="532" y="243" fill="#504e49" font-size="9" font-family="inherit">{{Category E}}</text>
|
||||
|
||||
<!-- S6 -->
|
||||
<rect x="472" y="256" width="12" height="12" fill="#EEF2F7" stroke="#e8e7e1" stroke-width="0.8" rx="2"/>
|
||||
<text x="492" y="267" fill="#141413" font-size="9.5" font-family="inherit">6%</text>
|
||||
<text x="532" y="267" fill="#504e49" font-size="9" font-family="inherit">{{Category F}}</text>
|
||||
|
||||
<!-- Bottom rule -->
|
||||
<line x1="60" y1="380" x2="620" y2="380" stroke="#e8e7e1" stroke-width="0.8"/>
|
||||
<text x="60" y="397" fill="#6b6a64" font-size="9" font-family="'JetBrains Mono', monospace" letter-spacing="0.15em">TOTAL · 100%</text>
|
||||
<text x="460" y="397" fill="#6b6a64" font-size="9" font-family="inherit">{{Source / period}}</text>
|
||||
|
||||
</svg>
|
||||
|
||||
<p class="caption">{{Caption text. The ink-blue segment is the focal category. Lead with the insight, not the breakdown.}}</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,166 @@
|
||||
<!DOCTYPE html>
|
||||
<!-- ==================================================================
|
||||
DIAGRAM · ER (kami palette, Mermaid-sourced)
|
||||
Source: assets/diagrams/src/er.mmd. Generated with beautiful-mermaid +
|
||||
scripts/mermaid_normalize.py (Kami re-theme + WeasyPrint-safe static SVG). To
|
||||
change it, regenerate the SVG from any beautiful-mermaid renderer and re-run
|
||||
the normalizer; do not hand-tune coordinates. See references/mermaid.md.
|
||||
================================================================== -->
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>ER · kami diagram</title>
|
||||
<style>
|
||||
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
|
||||
@font-face {
|
||||
font-family: "TsangerJinKai02";
|
||||
src: url("../fonts/TsangerJinKai02-W04.ttf") format("truetype"),
|
||||
url("https://cdn.jsdelivr.net/gh/tw93/Kami@main/assets/fonts/TsangerJinKai02-W04.ttf") format("truetype");
|
||||
font-weight: 400;
|
||||
font-style: normal;
|
||||
}
|
||||
@font-face {
|
||||
font-family: "TsangerJinKai02";
|
||||
src: url("../fonts/TsangerJinKai02-W05.ttf") format("truetype"),
|
||||
url("https://cdn.jsdelivr.net/gh/tw93/Kami@main/assets/fonts/TsangerJinKai02-W05.ttf") format("truetype");
|
||||
font-weight: 500;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
:root {
|
||||
--parchment: #f5f4ed;
|
||||
--ivory: #faf9f5;
|
||||
--near-black: #141413;
|
||||
--olive: #504e49;
|
||||
--stone: #6b6a64;
|
||||
--brand: #1B365D;
|
||||
--border: #e8e6dc;
|
||||
|
||||
--serif: Charter, Georgia, "TsangerJinKai02", "Source Han Serif SC", "Noto Serif CJK SC", serif;
|
||||
--sans: var(--serif);
|
||||
--mono: "JetBrains Mono", "SF Mono", Consolas, "TsangerJinKai02", "Source Han Serif SC", monospace;
|
||||
}
|
||||
|
||||
/* Poster-size page so WeasyPrint exports the full diagram without
|
||||
A4 clipping; browsers ignore @page. */
|
||||
@page { size: 800px 600px; margin: 0; }
|
||||
|
||||
body {
|
||||
font-family: var(--sans);
|
||||
background: var(--parchment);
|
||||
color: var(--near-black);
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 3rem 2rem;
|
||||
}
|
||||
|
||||
.frame { max-width: 720px; width: 100%; text-align: center; }
|
||||
.frame > .eyebrow, .frame > h1, .frame > .caption { text-align: left; }
|
||||
|
||||
.eyebrow {
|
||||
font-family: var(--mono);
|
||||
font-size: 0.7rem;
|
||||
letter-spacing: 0.22em;
|
||||
text-transform: uppercase;
|
||||
color: var(--stone);
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-family: var(--serif);
|
||||
font-size: 1.9rem;
|
||||
font-weight: 500;
|
||||
letter-spacing: -0.3pt;
|
||||
line-height: 1.15;
|
||||
margin-bottom: 1.6rem;
|
||||
}
|
||||
|
||||
/* Size to the diagram's intrinsic dimensions, capped so wide diagrams fit
|
||||
the frame and tall ones stay on a single page. Never upscale past natural
|
||||
size (avoids blowing a 200px-wide class diagram up to full width). */
|
||||
svg { display: block; margin: 0 auto; max-width: 100%; max-height: 780px; height: auto; width: auto; }
|
||||
|
||||
.caption {
|
||||
font-family: var(--serif);
|
||||
font-size: 0.95rem;
|
||||
color: var(--olive);
|
||||
margin-top: 1rem;
|
||||
max-width: 52ch;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="frame">
|
||||
<p class="eyebrow">Entity-Relationship · kami diagram</p>
|
||||
<h1>{{What schema does this map}}</h1>
|
||||
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 961.866 262" width="961.866" height="262" style="background:#f5f4ed">
|
||||
<style>
|
||||
text { font-family: Charter, Georgia, "TsangerJinKai02", "Source Han Serif SC", "Noto Serif CJK SC", serif; }
|
||||
.mono { font-family: Charter, Georgia, "TsangerJinKai02", "Source Han Serif SC", "Noto Serif CJK SC", serif; }
|
||||
</style>
|
||||
<defs>
|
||||
</defs>
|
||||
<polyline class="er-relationship" data-entity1="CUSTOMER" data-entity2="ORDER" data-cardinality1="one" data-cardinality2="zero-many" data-identifying="true" data-label="places" points="180,194 401.726,194" fill="none" stroke="#504e49" stroke-width="1" />
|
||||
<polyline class="er-relationship" data-entity1="ORDER" data-entity2="LINE_ITEM" data-cardinality1="one" data-cardinality2="many" data-identifying="true" data-label="contains" points="541.726,194 701.866,194 701.866,86.66666666666666 781.866,86.66666666666666" fill="none" stroke="#504e49" stroke-width="1" />
|
||||
<polyline class="er-relationship" data-entity1="PRODUCT" data-entity2="LINE_ITEM" data-cardinality1="one" data-cardinality2="zero-many" data-identifying="true" data-label="appears in" points="541.726,68 781.866,68" fill="none" stroke="#504e49" stroke-width="1" />
|
||||
<g class="entity" data-id="CUSTOMER" data-label="CUSTOMER">
|
||||
<rect x="40" y="166" width="140" height="56" rx="0" ry="0" fill="#faf9f5" stroke="#e8e6dc" stroke-width="1" />
|
||||
<rect x="40" y="166" width="140" height="34" rx="0" ry="0" fill="#eae9e2" stroke="#e8e6dc" stroke-width="1" />
|
||||
<text x="110" y="183" text-anchor="middle" font-size="13" font-weight="700" fill="#141413" dy="4.55">CUSTOMER</text>
|
||||
<line x1="40" y1="200" x2="180" y2="200" stroke="#e8e6dc" stroke-width="0.75" />
|
||||
<text x="110" y="211" text-anchor="middle" dy="0.35em" font-size="11" fill="#bdbcb6" font-style="italic">(no attributes)</text>
|
||||
</g>
|
||||
<g class="entity" data-id="ORDER" data-label="ORDER">
|
||||
<rect x="401.726" y="166" width="140" height="56" rx="0" ry="0" fill="#faf9f5" stroke="#e8e6dc" stroke-width="1" />
|
||||
<rect x="401.726" y="166" width="140" height="34" rx="0" ry="0" fill="#eae9e2" stroke="#e8e6dc" stroke-width="1" />
|
||||
<text x="471.726" y="183" text-anchor="middle" font-size="13" font-weight="700" fill="#141413" dy="4.55">ORDER</text>
|
||||
<line x1="401.726" y1="200" x2="541.726" y2="200" stroke="#e8e6dc" stroke-width="0.75" />
|
||||
<text x="471.726" y="211" text-anchor="middle" dy="0.35em" font-size="11" fill="#bdbcb6" font-style="italic">(no attributes)</text>
|
||||
</g>
|
||||
<g class="entity" data-id="LINE_ITEM" data-label="LINE_ITEM">
|
||||
<rect x="781.866" y="49.333333333333336" width="140" height="56" rx="0" ry="0" fill="#faf9f5" stroke="#e8e6dc" stroke-width="1" />
|
||||
<rect x="781.866" y="49.333333333333336" width="140" height="34" rx="0" ry="0" fill="#eae9e2" stroke="#e8e6dc" stroke-width="1" />
|
||||
<text x="851.866" y="66.33333333333334" text-anchor="middle" font-size="13" font-weight="700" fill="#141413" dy="4.55">LINE_ITEM</text>
|
||||
<line x1="781.866" y1="83.33333333333334" x2="921.866" y2="83.33333333333334" stroke="#e8e6dc" stroke-width="0.75" />
|
||||
<text x="851.866" y="94.33333333333334" text-anchor="middle" dy="0.35em" font-size="11" fill="#bdbcb6" font-style="italic">(no attributes)</text>
|
||||
</g>
|
||||
<g class="entity" data-id="PRODUCT" data-label="PRODUCT">
|
||||
<rect x="401.726" y="40" width="140" height="56" rx="0" ry="0" fill="#faf9f5" stroke="#e8e6dc" stroke-width="1" />
|
||||
<rect x="401.726" y="40" width="140" height="34" rx="0" ry="0" fill="#eae9e2" stroke="#e8e6dc" stroke-width="1" />
|
||||
<text x="471.726" y="57" text-anchor="middle" font-size="13" font-weight="700" fill="#141413" dy="4.55">PRODUCT</text>
|
||||
<line x1="401.726" y1="74" x2="541.726" y2="74" stroke="#e8e6dc" stroke-width="0.75" />
|
||||
<text x="471.726" y="85" text-anchor="middle" dy="0.35em" font-size="11" fill="#bdbcb6" font-style="italic">(no attributes)</text>
|
||||
</g>
|
||||
<line x1="184" y1="188" x2="184" y2="200" stroke="#504e49" stroke-width="1.25" />
|
||||
<line x1="188" y1="188" x2="188" y2="200" stroke="#504e49" stroke-width="1.25" />
|
||||
<line x1="397.726" y1="201" x2="385.726" y2="194" stroke="#504e49" stroke-width="1.25" />
|
||||
<line x1="397.726" y1="194" x2="385.726" y2="194" stroke="#504e49" stroke-width="1.25" />
|
||||
<line x1="397.726" y1="187" x2="385.726" y2="194" stroke="#504e49" stroke-width="1.25" />
|
||||
<circle cx="381.726" cy="194" r="4" fill="#f5f4ed" stroke="#504e49" stroke-width="1.25" />
|
||||
<line x1="545.726" y1="188" x2="545.726" y2="200" stroke="#504e49" stroke-width="1.25" />
|
||||
<line x1="549.726" y1="188" x2="549.726" y2="200" stroke="#504e49" stroke-width="1.25" />
|
||||
<line x1="777.866" y1="93.66666666666666" x2="765.866" y2="86.66666666666666" stroke="#504e49" stroke-width="1.25" />
|
||||
<line x1="777.866" y1="86.66666666666666" x2="765.866" y2="86.66666666666666" stroke="#504e49" stroke-width="1.25" />
|
||||
<line x1="777.866" y1="79.66666666666666" x2="765.866" y2="86.66666666666666" stroke="#504e49" stroke-width="1.25" />
|
||||
<line x1="545.726" y1="62" x2="545.726" y2="74" stroke="#504e49" stroke-width="1.25" />
|
||||
<line x1="549.726" y1="62" x2="549.726" y2="74" stroke="#504e49" stroke-width="1.25" />
|
||||
<line x1="777.866" y1="75" x2="765.866" y2="68" stroke="#504e49" stroke-width="1.25" />
|
||||
<line x1="777.866" y1="68" x2="765.866" y2="68" stroke="#504e49" stroke-width="1.25" />
|
||||
<line x1="777.866" y1="61" x2="765.866" y2="68" stroke="#504e49" stroke-width="1.25" />
|
||||
<circle cx="761.866" cy="68" r="4" fill="#f5f4ed" stroke="#504e49" stroke-width="1.25" />
|
||||
<rect x="270" y="183.85" width="41.726000000000006" height="20.3" rx="2" ry="2" fill="#f5f4ed" stroke="#dad9d3" stroke-width="0.5" />
|
||||
<text x="290.863" y="194" text-anchor="middle" font-size="11" font-weight="400" fill="#6b6a64" dy="3.8499999999999996">places</text>
|
||||
<rect x="676.845" y="170.2533333333333" width="50.04200000000001" height="20.3" rx="2" ry="2" fill="#f5f4ed" stroke="#dad9d3" stroke-width="0.5" />
|
||||
<text x="701.866" y="180.4033333333333" text-anchor="middle" font-size="11" font-weight="400" fill="#6b6a64" dy="3.8499999999999996">contains</text>
|
||||
<rect x="631.726" y="57.85" width="60.14" height="20.3" rx="2" ry="2" fill="#f5f4ed" stroke="#dad9d3" stroke-width="0.5" />
|
||||
<text x="661.796" y="68" text-anchor="middle" font-size="11" font-weight="400" fill="#6b6a64" dy="3.8499999999999996">appears in</text>
|
||||
</svg>
|
||||
|
||||
<p class="caption">Crow's-foot notation: each connector's ends mark cardinality between entities.</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,170 @@
|
||||
<!DOCTYPE html>
|
||||
<!-- ==================================================================
|
||||
DIAGRAM · Flowchart (kami palette)
|
||||
Decision logic with branches. One focal decision, two to four outcomes.
|
||||
Use when the structure IS the point. If a table communicates the
|
||||
same thing, use a table instead.
|
||||
================================================================== -->
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Flowchart · kami diagram</title>
|
||||
<style>
|
||||
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
|
||||
:root {
|
||||
--parchment: #f5f4ed;
|
||||
--ivory: #faf9f5;
|
||||
--near-black: #141413;
|
||||
--olive: #504e49;
|
||||
--stone: #6b6a64;
|
||||
--brand: #1B365D;
|
||||
--brand-tint: #EEF2F7;
|
||||
--border: #e8e6dc;
|
||||
|
||||
--serif: Charter, Georgia, Palatino, serif;
|
||||
--sans: var(--serif);
|
||||
--mono: "JetBrains Mono", "SF Mono", Consolas, "TsangerJinKai02", "Source Han Serif SC", "Noto Serif CJK SC", "Songti SC", monospace;
|
||||
}
|
||||
|
||||
/* Poster-size page so WeasyPrint exports the full diagram without
|
||||
A4 clipping; browsers ignore @page. */
|
||||
@page { size: 1040px 980px; margin: 0; }
|
||||
|
||||
body {
|
||||
font-family: var(--sans);
|
||||
background: var(--parchment);
|
||||
color: var(--near-black);
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 3rem 2rem;
|
||||
}
|
||||
|
||||
.frame { max-width: 960px; width: 100%; }
|
||||
|
||||
.eyebrow {
|
||||
font-family: var(--mono);
|
||||
font-size: 0.7rem;
|
||||
letter-spacing: 0.22em;
|
||||
text-transform: uppercase;
|
||||
color: var(--stone);
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-family: var(--serif);
|
||||
font-size: 1.9rem;
|
||||
font-weight: 500;
|
||||
letter-spacing: -0.3pt;
|
||||
line-height: 1.15;
|
||||
margin-bottom: 1.6rem;
|
||||
}
|
||||
|
||||
svg { width: 100%; min-width: 820px; display: block; }
|
||||
|
||||
.caption {
|
||||
font-family: var(--serif);
|
||||
font-size: 0.95rem;
|
||||
color: var(--olive);
|
||||
margin-top: 1rem;
|
||||
max-width: 52ch;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="frame">
|
||||
<p class="eyebrow">Flowchart · kami diagram</p>
|
||||
<h1>{{Question the flow answers}}</h1>
|
||||
|
||||
<svg viewBox="0 0 860 520" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<marker id="arrow" markerWidth="8" markerHeight="6" refX="7" refY="3" orient="auto">
|
||||
<polygon points="0 0, 8 3, 0 6" fill="#504e49"/>
|
||||
</marker>
|
||||
<marker id="arrow-brand" markerWidth="8" markerHeight="6" refX="7" refY="3" orient="auto">
|
||||
<polygon points="0 0, 8 3, 0 6" fill="#1B365D"/>
|
||||
</marker>
|
||||
</defs>
|
||||
|
||||
<rect width="100%" height="100%" fill="#f5f4ed"/>
|
||||
|
||||
<!-- ========== Flow ========== -->
|
||||
<!-- Start → Check -->
|
||||
<line x1="430" y1="72" x2="430" y2="112" stroke="#504e49" stroke-width="1.2" marker-end="url(#arrow)"/>
|
||||
|
||||
<!-- Check → Decision (focal) -->
|
||||
<line x1="430" y1="176" x2="430" y2="216" stroke="#504e49" stroke-width="1.2" marker-end="url(#arrow)"/>
|
||||
|
||||
<!-- Decision → Yes branch (brand) -->
|
||||
<path d="M 360 268 L 200 268 L 200 332" fill="none" stroke="#1B365D" stroke-width="1.4" marker-end="url(#arrow-brand)"/>
|
||||
<rect x="170" y="284" width="34" height="14" rx="2" fill="#f5f4ed"/>
|
||||
<text x="187" y="294" fill="#1B365D" font-size="8" font-family="'JetBrains Mono', monospace" text-anchor="middle" letter-spacing="0.12em">YES</text>
|
||||
|
||||
<!-- Decision → No branch -->
|
||||
<path d="M 500 268 L 660 268 L 660 332" fill="none" stroke="#504e49" stroke-width="1.2" marker-end="url(#arrow)"/>
|
||||
<rect x="636" y="284" width="30" height="14" rx="2" fill="#f5f4ed"/>
|
||||
<text x="651" y="294" fill="#504e49" font-size="8" font-family="'JetBrains Mono', monospace" text-anchor="middle" letter-spacing="0.12em">NO</text>
|
||||
|
||||
<!-- Yes outcome → End -->
|
||||
<path d="M 200 396 L 200 440 L 396 440" fill="none" stroke="#504e49" stroke-width="1.2" marker-end="url(#arrow)"/>
|
||||
|
||||
<!-- No outcome → End -->
|
||||
<path d="M 660 396 L 660 440 L 464 440" fill="none" stroke="#504e49" stroke-width="1.2" marker-end="url(#arrow)"/>
|
||||
|
||||
<!-- ========== Nodes ========== -->
|
||||
|
||||
<!-- Start (pill) -->
|
||||
<rect x="360" y="40" width="140" height="32" rx="16" fill="#E9E8E1" stroke="#6b6a64" stroke-width="1"/>
|
||||
<text x="430" y="58" fill="#141413" font-size="11" font-family="inherit" text-anchor="middle">Start</text>
|
||||
|
||||
<!-- Check step -->
|
||||
<rect x="350" y="112" width="160" height="64" rx="6" fill="#faf9f5" stroke="#141413" stroke-width="1"/>
|
||||
<text x="364" y="130" fill="#504e49" font-size="7" font-family="'JetBrains Mono', monospace" letter-spacing="0.15em">STEP 01</text>
|
||||
<text x="430" y="150" fill="#141413" font-size="12" font-weight="500" font-family="inherit" text-anchor="middle">{{Describe input}}</text>
|
||||
<text x="430" y="166" fill="#504e49" font-size="9" font-family="inherit" text-anchor="middle">one short line</text>
|
||||
|
||||
<!-- Decision diamond (FOCAL) -->
|
||||
<polygon points="430,216 500,242 500,268 430,320 360,268 360,242"
|
||||
fill="#EEF2F7" stroke="#1B365D" stroke-width="1.2"/>
|
||||
<text x="430" y="266" fill="#141413" font-size="11" font-weight="500" font-family="inherit" text-anchor="middle">{{Decision}}</text>
|
||||
<text x="430" y="282" fill="#1B365D" font-size="8" font-family="'JetBrains Mono', monospace" text-anchor="middle" letter-spacing="0.15em">BRANCH</text>
|
||||
|
||||
<!-- Yes outcome -->
|
||||
<rect x="120" y="332" width="160" height="64" rx="6" fill="#faf9f5" stroke="#141413" stroke-width="1"/>
|
||||
<text x="134" y="350" fill="#504e49" font-size="7" font-family="'JetBrains Mono', monospace" letter-spacing="0.15em">OUTCOME A</text>
|
||||
<text x="200" y="370" fill="#141413" font-size="12" font-weight="500" font-family="inherit" text-anchor="middle">{{Path taken}}</text>
|
||||
<text x="200" y="386" fill="#504e49" font-size="9" font-family="inherit" text-anchor="middle">what happens next</text>
|
||||
|
||||
<!-- No outcome -->
|
||||
<rect x="580" y="332" width="160" height="64" rx="6" fill="#EEEDE6" stroke="#B2B1AC" stroke-width="1"/>
|
||||
<text x="594" y="350" fill="#6b6a64" font-size="7" font-family="'JetBrains Mono', monospace" letter-spacing="0.15em">OUTCOME B</text>
|
||||
<text x="660" y="370" fill="#141413" font-size="12" font-weight="500" font-family="inherit" text-anchor="middle">{{Alt path}}</text>
|
||||
<text x="660" y="386" fill="#504e49" font-size="9" font-family="inherit" text-anchor="middle">skipped / deferred</text>
|
||||
|
||||
<!-- End (pill) -->
|
||||
<rect x="396" y="424" width="68" height="32" rx="16" fill="#E9E8E1" stroke="#6b6a64" stroke-width="1"/>
|
||||
<text x="430" y="442" fill="#141413" font-size="11" font-family="inherit" text-anchor="middle">End</text>
|
||||
|
||||
<!-- Legend -->
|
||||
<line x1="40" y1="484" x2="820" y2="484" stroke="#DEDED7" stroke-width="0.8"/>
|
||||
<text x="40" y="500" fill="#504e49" font-size="8" font-family="'JetBrains Mono', monospace" letter-spacing="0.22em">LEGEND</text>
|
||||
|
||||
<polygon points="180,492 192,498 180,504 168,498" fill="#EEF2F7" stroke="#1B365D" stroke-width="1"/>
|
||||
<text x="202" y="502" fill="#504e49" font-size="8.5" font-family="inherit">Focal decision</text>
|
||||
|
||||
<rect x="310" y="494" width="14" height="10" rx="2" fill="#faf9f5" stroke="#141413" stroke-width="1"/>
|
||||
<text x="330" y="502" fill="#504e49" font-size="8.5" font-family="inherit">Step · outcome</text>
|
||||
|
||||
<rect x="440" y="494" width="14" height="10" rx="16" fill="#E9E8E1" stroke="#6b6a64" stroke-width="1"/>
|
||||
<text x="460" y="502" fill="#504e49" font-size="8.5" font-family="inherit">Start · end</text>
|
||||
|
||||
<rect x="560" y="494" width="14" height="10" rx="2" fill="#EEEDE6" stroke="#B2B1AC" stroke-width="1"/>
|
||||
<text x="580" y="502" fill="#504e49" font-size="8.5" font-family="inherit">Deferred branch</text>
|
||||
</svg>
|
||||
|
||||
<p class="caption">The decision diamond carries the accent. Its two branches diverge into equal weights so the reader sees the question, not an implied answer.</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,184 @@
|
||||
<!DOCTYPE html>
|
||||
<!-- ==================================================================
|
||||
DIAGRAM · Layer Stack (kami palette)
|
||||
Horizontal bands stacked vertically, showing system layers.
|
||||
One focal layer in ink-blue; request/response flow on the sides.
|
||||
================================================================== -->
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Layer Stack · kami diagram</title>
|
||||
<style>
|
||||
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
|
||||
:root {
|
||||
--parchment: #f5f4ed;
|
||||
--ivory: #faf9f5;
|
||||
--near-black: #141413;
|
||||
--olive: #504e49;
|
||||
--stone: #6b6a64;
|
||||
--brand: #1B365D;
|
||||
--brand-tint: #EEF2F7;
|
||||
--border: #e8e6dc;
|
||||
|
||||
--serif: Charter, Georgia, Palatino, serif;
|
||||
--sans: var(--serif);
|
||||
--mono: "JetBrains Mono", "SF Mono", Consolas, monospace;
|
||||
}
|
||||
|
||||
/* Poster-size page so WeasyPrint exports the full diagram without
|
||||
A4 clipping; browsers ignore @page. */
|
||||
@page { size: 1080px 880px; margin: 0; }
|
||||
|
||||
body {
|
||||
font-family: var(--sans);
|
||||
background: var(--parchment);
|
||||
color: var(--near-black);
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 3rem 2rem;
|
||||
}
|
||||
|
||||
.frame { max-width: 1000px; width: 100%; }
|
||||
|
||||
.eyebrow {
|
||||
font-family: var(--mono);
|
||||
font-size: 0.7rem;
|
||||
letter-spacing: 0.22em;
|
||||
text-transform: uppercase;
|
||||
color: var(--stone);
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-family: var(--serif);
|
||||
font-size: 1.9rem;
|
||||
font-weight: 500;
|
||||
letter-spacing: -0.3pt;
|
||||
line-height: 1.15;
|
||||
margin-bottom: 1.6rem;
|
||||
}
|
||||
|
||||
svg { width: 100%; min-width: 860px; display: block; }
|
||||
|
||||
.caption {
|
||||
font-family: var(--serif);
|
||||
font-size: 0.95rem;
|
||||
color: var(--olive);
|
||||
margin-top: 1rem;
|
||||
max-width: 52ch;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="frame">
|
||||
<p class="eyebrow">Layer Stack · kami diagram</p>
|
||||
<h1>{{System name}} architecture layers</h1>
|
||||
|
||||
<svg viewBox="0 0 960 460" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<pattern id="dots" width="22" height="22" patternUnits="userSpaceOnUse">
|
||||
<circle cx="1" cy="1" r="0.9" fill="#E3E2DC"/>
|
||||
</pattern>
|
||||
</defs>
|
||||
|
||||
<rect width="100%" height="100%" fill="#f5f4ed"/>
|
||||
<rect width="100%" height="100%" fill="url(#dots)" opacity="0.55"/>
|
||||
|
||||
<!-- ── Layer bands (x=96 w=724) ── -->
|
||||
|
||||
<!-- L1: Presentation, y=52, h=56 -->
|
||||
<rect x="96" y="52" width="724" height="56" rx="4" fill="#faf9f5" stroke="#141413" stroke-width="1"/>
|
||||
<text x="128" y="74" fill="#141413" font-size="7" font-family="'JetBrains Mono', monospace"
|
||||
letter-spacing="0.15em">01</text>
|
||||
<text x="152" y="74" fill="#141413" font-size="12" font-weight="500"
|
||||
font-family="inherit">Presentation</text>
|
||||
<text x="152" y="94" fill="#504e49" font-size="9" font-family="'JetBrains Mono', monospace">
|
||||
UI · views · routing</text>
|
||||
|
||||
<!-- L2: Business Logic, y=116, h=56, FOCAL -->
|
||||
<rect x="96" y="116" width="724" height="56" rx="4" fill="#EEF2F7" stroke="#1B365D" stroke-width="1"/>
|
||||
<text x="128" y="138" fill="#1B365D" font-size="7" font-family="'JetBrains Mono', monospace"
|
||||
letter-spacing="0.15em">02 · FOCAL</text>
|
||||
<text x="224" y="138" fill="#141413" font-size="12" font-weight="500"
|
||||
font-family="inherit">Business Logic</text>
|
||||
<text x="224" y="158" fill="#1B365D" font-size="9" font-family="'JetBrains Mono', monospace">
|
||||
rules · validation · orchestration</text>
|
||||
|
||||
<!-- L3: Data Access, y=180, h=56 -->
|
||||
<rect x="96" y="180" width="724" height="56" rx="4"
|
||||
fill="#f5f4ed" stroke="#504e49" stroke-width="1"/>
|
||||
<text x="128" y="202" fill="#504e49" font-size="7" font-family="'JetBrains Mono', monospace"
|
||||
letter-spacing="0.15em">03</text>
|
||||
<text x="152" y="202" fill="#141413" font-size="12" font-weight="500"
|
||||
font-family="inherit">Data Access</text>
|
||||
<text x="152" y="222" fill="#504e49" font-size="9" font-family="'JetBrains Mono', monospace">
|
||||
repositories · queries · cache</text>
|
||||
|
||||
<!-- L4: Storage, y=244, h=56 -->
|
||||
<rect x="96" y="244" width="724" height="56" rx="4"
|
||||
fill="#f0efe8" stroke="#6b6a64" stroke-width="1"/>
|
||||
<text x="128" y="266" fill="#6b6a64" font-size="7" font-family="'JetBrains Mono', monospace"
|
||||
letter-spacing="0.15em">04</text>
|
||||
<text x="152" y="266" fill="#141413" font-size="12" font-weight="500"
|
||||
font-family="inherit">Storage</text>
|
||||
<text x="152" y="286" fill="#504e49" font-size="9" font-family="'JetBrains Mono', monospace">
|
||||
database · object store · search index</text>
|
||||
|
||||
<!-- L5: Infrastructure, y=308, h=56 -->
|
||||
<rect x="96" y="308" width="724" height="56" rx="4"
|
||||
fill="#eae9e1" stroke="#b8b7b0" stroke-width="1"/>
|
||||
<text x="128" y="330" fill="#b8b7b0" font-size="7" font-family="'JetBrains Mono', monospace"
|
||||
letter-spacing="0.15em">05</text>
|
||||
<text x="152" y="330" fill="#141413" font-size="12" font-weight="500"
|
||||
font-family="inherit">Infrastructure</text>
|
||||
<text x="152" y="350" fill="#504e49" font-size="9" font-family="'JetBrains Mono', monospace">
|
||||
cloud · network · runtime</text>
|
||||
|
||||
<!-- ── Left: REQUEST down-arrow ── -->
|
||||
<line x1="52" y1="64" x2="52" y2="352" stroke="#504e49" stroke-width="1.2"/>
|
||||
<path d="M 47,346 L 52,352 L 57,346"
|
||||
fill="none" stroke="#504e49" stroke-width="1.5" stroke-linecap="round"/>
|
||||
<rect x="28" y="192" width="48" height="12" rx="2" fill="#f5f4ed"/>
|
||||
<text x="52" y="202" fill="#504e49" font-size="8" font-family="'JetBrains Mono', monospace"
|
||||
text-anchor="middle" letter-spacing="0.1em">REQUEST</text>
|
||||
|
||||
<!-- ── Right: RESPONSE up-arrow ── -->
|
||||
<line x1="872" y1="352" x2="872" y2="64" stroke="#1B365D" stroke-width="1.4"/>
|
||||
<path d="M 867,70 L 872,64 L 877,70"
|
||||
fill="none" stroke="#1B365D" stroke-width="1.5" stroke-linecap="round"/>
|
||||
<rect x="848" y="192" width="52" height="12" rx="2" fill="#f5f4ed"/>
|
||||
<text x="872" y="202" fill="#1B365D" font-size="8" font-family="'JetBrains Mono', monospace"
|
||||
text-anchor="middle" letter-spacing="0.1em">RESPONSE</text>
|
||||
|
||||
<!-- ── Legend ── -->
|
||||
<line x1="40" y1="388" x2="920" y2="388" stroke="#DEDED7" stroke-width="0.8"/>
|
||||
<text x="40" y="404" fill="#504e49" font-size="8" font-family="'JetBrains Mono', monospace"
|
||||
letter-spacing="0.22em">LEGEND</text>
|
||||
|
||||
<rect x="40" y="418" width="14" height="10" rx="2" fill="#EEF2F7" stroke="#1B365D" stroke-width="1"/>
|
||||
<text x="60" y="426" fill="#504e49" font-size="8.5" font-family="inherit">Focal layer</text>
|
||||
|
||||
<rect x="170" y="418" width="14" height="10" rx="2" fill="#faf9f5" stroke="#141413" stroke-width="1"/>
|
||||
<text x="190" y="426" fill="#504e49" font-size="8.5" font-family="inherit">Top layer</text>
|
||||
|
||||
<rect x="290" y="418" width="14" height="10" rx="2" fill="#eae9e1" stroke="#b8b7b0" stroke-width="1"/>
|
||||
<text x="310" y="426" fill="#504e49" font-size="8.5" font-family="inherit">Foundation</text>
|
||||
|
||||
<line x1="420" y1="424" x2="452" y2="424" stroke="#504e49" stroke-width="1.2"/>
|
||||
<path d="M 446,420 L 452,424 L 446,428"
|
||||
fill="none" stroke="#504e49" stroke-width="1.5" stroke-linecap="round"/>
|
||||
<text x="460" y="427" fill="#504e49" font-size="8.5" font-family="inherit">Request direction</text>
|
||||
|
||||
<line x1="620" y1="424" x2="652" y2="424" stroke="#1B365D" stroke-width="1.4"/>
|
||||
<path d="M 646,420 L 652,424 L 646,428"
|
||||
fill="none" stroke="#1B365D" stroke-width="1.5" stroke-linecap="round"/>
|
||||
<text x="660" y="427" fill="#504e49" font-size="8.5" font-family="inherit">Response direction</text>
|
||||
</svg>
|
||||
|
||||
<p class="caption">Focal rule: one layer in ink-blue marks where the most domain-specific logic lives. Layers above carry user-facing concerns; layers below carry infrastructure. Side arrows orient the reader to flow direction without cluttering the bands.</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,207 @@
|
||||
<!DOCTYPE html>
|
||||
<!-- ==================================================================
|
||||
DIAGRAM · Line Chart (kami palette)
|
||||
Trend lines for time-series or ordered data (stock price, growth rate, etc.)
|
||||
Up to 12 points × 3 lines. Demo: 8 months × 2 lines.
|
||||
DATA START / DATA END marks the region Claude replaces when filling.
|
||||
================================================================== -->
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Line Chart · kami diagram</title>
|
||||
<style>
|
||||
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
|
||||
:root {
|
||||
--parchment: #f5f4ed;
|
||||
--ivory: #faf9f5;
|
||||
--near-black: #141413;
|
||||
--olive: #504e49;
|
||||
--stone: #6b6a64;
|
||||
--brand: #1B365D;
|
||||
--brand-tint: #EEF2F7;
|
||||
--border: #e8e6dc;
|
||||
|
||||
--serif: Charter, Georgia, Palatino, serif;
|
||||
--sans: var(--serif);
|
||||
--mono: "JetBrains Mono", "SF Mono", Consolas, "TsangerJinKai02", "Source Han Serif SC", "Noto Serif CJK SC", "Songti SC", monospace;
|
||||
}
|
||||
|
||||
/* Poster-size page so WeasyPrint exports the full diagram without
|
||||
A4 clipping; browsers ignore @page. */
|
||||
@page { size: 1000px 960px; margin: 0; }
|
||||
|
||||
body {
|
||||
font-family: var(--sans);
|
||||
background: var(--parchment);
|
||||
color: var(--near-black);
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 3rem 2rem;
|
||||
}
|
||||
|
||||
.frame { max-width: 920px; width: 100%; }
|
||||
|
||||
.eyebrow {
|
||||
font-family: var(--mono);
|
||||
font-size: 0.7rem;
|
||||
letter-spacing: 0.22em;
|
||||
text-transform: uppercase;
|
||||
color: var(--stone);
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-family: var(--serif);
|
||||
font-size: 1.9rem;
|
||||
font-weight: 500;
|
||||
letter-spacing: -0.3pt;
|
||||
line-height: 1.15;
|
||||
margin-bottom: 1.6rem;
|
||||
}
|
||||
|
||||
svg { width: 100%; min-width: 640px; display: block; }
|
||||
|
||||
.caption {
|
||||
font-family: var(--serif);
|
||||
font-size: 0.95rem;
|
||||
color: var(--olive);
|
||||
margin-top: 1rem;
|
||||
max-width: 52ch;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="frame">
|
||||
<p class="eyebrow">Line Chart · kami diagram</p>
|
||||
<h1>{{Chart Title}}</h1>
|
||||
|
||||
<!-- ============================================================
|
||||
DATA START
|
||||
Chart area: x=60 y=40 w=560 h=280 · base y=320
|
||||
viewBox: 0 0 680 420
|
||||
Points: 8 · x-spacing=80 · x positions: 60,140,220,300,380,460,540,620
|
||||
Scale: max=140 · 1 unit = 2px · y = 320 - value×2
|
||||
Y-axis labels: 0/20/40/60/80/100/120/140
|
||||
Line 1 (brand): stroke=#1B365D · dot fill=#1B365D
|
||||
Line 2 (olive): stroke=#504e49 · dot fill=white stroke=#504e49
|
||||
============================================================ -->
|
||||
<svg viewBox="0 0 680 420" xmlns="http://www.w3.org/2000/svg">
|
||||
|
||||
<!-- Canvas -->
|
||||
<rect width="100%" height="100%" fill="#f5f4ed"/>
|
||||
|
||||
<!-- Horizontal grid lines -->
|
||||
<line x1="60" y1="320" x2="620" y2="320" stroke="#e8e7e1" stroke-width="0.8"/>
|
||||
<line x1="60" y1="280" x2="620" y2="280" stroke="#e8e7e1" stroke-width="0.8"/>
|
||||
<line x1="60" y1="240" x2="620" y2="240" stroke="#e8e7e1" stroke-width="0.8"/>
|
||||
<line x1="60" y1="200" x2="620" y2="200" stroke="#e8e7e1" stroke-width="0.8"/>
|
||||
<line x1="60" y1="160" x2="620" y2="160" stroke="#e8e7e1" stroke-width="0.8"/>
|
||||
<line x1="60" y1="120" x2="620" y2="120" stroke="#e8e7e1" stroke-width="0.8"/>
|
||||
<line x1="60" y1="80" x2="620" y2="80" stroke="#e8e7e1" stroke-width="0.8"/>
|
||||
<line x1="60" y1="40" x2="620" y2="40" stroke="#e8e7e1" stroke-width="0.8"/>
|
||||
|
||||
<!-- Vertical grid lines (at each data point) -->
|
||||
<line x1="140" y1="40" x2="140" y2="320" stroke="#e8e7e1" stroke-width="0.5" stroke-dasharray="3 4"/>
|
||||
<line x1="220" y1="40" x2="220" y2="320" stroke="#e8e7e1" stroke-width="0.5" stroke-dasharray="3 4"/>
|
||||
<line x1="300" y1="40" x2="300" y2="320" stroke="#e8e7e1" stroke-width="0.5" stroke-dasharray="3 4"/>
|
||||
<line x1="380" y1="40" x2="380" y2="320" stroke="#e8e7e1" stroke-width="0.5" stroke-dasharray="3 4"/>
|
||||
<line x1="460" y1="40" x2="460" y2="320" stroke="#e8e7e1" stroke-width="0.5" stroke-dasharray="3 4"/>
|
||||
<line x1="540" y1="40" x2="540" y2="320" stroke="#e8e7e1" stroke-width="0.5" stroke-dasharray="3 4"/>
|
||||
|
||||
<!-- Y-axis labels -->
|
||||
<text x="52" y="324" fill="#6b6a64" font-size="10" font-family="inherit" text-anchor="end">0</text>
|
||||
<text x="52" y="284" fill="#6b6a64" font-size="10" font-family="inherit" text-anchor="end">20</text>
|
||||
<text x="52" y="244" fill="#6b6a64" font-size="10" font-family="inherit" text-anchor="end">40</text>
|
||||
<text x="52" y="204" fill="#6b6a64" font-size="10" font-family="inherit" text-anchor="end">60</text>
|
||||
<text x="52" y="164" fill="#6b6a64" font-size="10" font-family="inherit" text-anchor="end">80</text>
|
||||
<text x="52" y="124" fill="#6b6a64" font-size="10" font-family="inherit" text-anchor="end">100</text>
|
||||
<text x="52" y="84" fill="#6b6a64" font-size="10" font-family="inherit" text-anchor="end">120</text>
|
||||
<text x="52" y="44" fill="#6b6a64" font-size="10" font-family="inherit" text-anchor="end">140</text>
|
||||
|
||||
<!-- Y-axis unit label -->
|
||||
<text x="16" y="184" fill="#6b6a64" font-size="9" font-family="'JetBrains Mono', monospace" letter-spacing="0.12em" transform="rotate(-90 16 184)" text-anchor="middle">UNIT</text>
|
||||
|
||||
<!-- X-axis baseline -->
|
||||
<line x1="60" y1="320" x2="620" y2="320" stroke="#141413" stroke-width="0.8"/>
|
||||
|
||||
<!-- ==================================================
|
||||
LINES AND DOTS · replace values between DATA START / DATA END
|
||||
x positions: 60 140 220 300 380 460 540 620
|
||||
y = 320 - value × 2 (max 140 fills chart height 280)
|
||||
|
||||
Demo data:
|
||||
Line 1 (brand): 52, 60, 56, 72, 80, 76, 88, 96
|
||||
y: 216,200,208,176,160,168,144,128
|
||||
Line 2 (olive): 36, 44, 48, 52, 60, 64, 68, 76
|
||||
y: 248,232,224,216,200,192,184,168
|
||||
================================================== -->
|
||||
|
||||
<!-- Line 1 (brand #1B365D) -->
|
||||
<polyline
|
||||
points="60,216 140,200 220,208 300,176 380,160 460,168 540,144 620,128"
|
||||
fill="none" stroke="#1B365D" stroke-width="2" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
|
||||
<!-- Line 2 (olive #504e49) -->
|
||||
<polyline
|
||||
points="60,248 140,232 220,224 300,216 380,200 460,192 540,184 620,168"
|
||||
fill="none" stroke="#504e49" stroke-width="1.5" stroke-linejoin="round" stroke-linecap="round" stroke-dasharray="5 3"/>
|
||||
|
||||
<!-- Dots · Line 1 (brand, filled) -->
|
||||
<circle cx="60" cy="216" r="4" fill="#1B365D" stroke="#f5f4ed" stroke-width="1.5"/>
|
||||
<circle cx="140" cy="200" r="4" fill="#1B365D" stroke="#f5f4ed" stroke-width="1.5"/>
|
||||
<circle cx="220" cy="208" r="4" fill="#1B365D" stroke="#f5f4ed" stroke-width="1.5"/>
|
||||
<circle cx="300" cy="176" r="4" fill="#1B365D" stroke="#f5f4ed" stroke-width="1.5"/>
|
||||
<circle cx="380" cy="160" r="4" fill="#1B365D" stroke="#f5f4ed" stroke-width="1.5"/>
|
||||
<circle cx="460" cy="168" r="4" fill="#1B365D" stroke="#f5f4ed" stroke-width="1.5"/>
|
||||
<circle cx="540" cy="144" r="4" fill="#1B365D" stroke="#f5f4ed" stroke-width="1.5"/>
|
||||
<!-- Focal dot (last point) slightly larger -->
|
||||
<circle cx="620" cy="128" r="5.5" fill="#1B365D" stroke="#f5f4ed" stroke-width="1.5"/>
|
||||
|
||||
<!-- Dots · Line 2 (olive, open) -->
|
||||
<circle cx="60" cy="248" r="3.5" fill="#f5f4ed" stroke="#504e49" stroke-width="1.5"/>
|
||||
<circle cx="140" cy="232" r="3.5" fill="#f5f4ed" stroke="#504e49" stroke-width="1.5"/>
|
||||
<circle cx="220" cy="224" r="3.5" fill="#f5f4ed" stroke="#504e49" stroke-width="1.5"/>
|
||||
<circle cx="300" cy="216" r="3.5" fill="#f5f4ed" stroke="#504e49" stroke-width="1.5"/>
|
||||
<circle cx="380" cy="200" r="3.5" fill="#f5f4ed" stroke="#504e49" stroke-width="1.5"/>
|
||||
<circle cx="460" cy="192" r="3.5" fill="#f5f4ed" stroke="#504e49" stroke-width="1.5"/>
|
||||
<circle cx="540" cy="184" r="3.5" fill="#f5f4ed" stroke="#504e49" stroke-width="1.5"/>
|
||||
<circle cx="620" cy="168" r="3.5" fill="#f5f4ed" stroke="#504e49" stroke-width="1.5"/>
|
||||
|
||||
<!-- End-of-line value labels -->
|
||||
<text x="628" y="132" fill="#1B365D" font-size="11" font-family="inherit" font-weight="500">96</text>
|
||||
<text x="628" y="172" fill="#504e49" font-size="10" font-family="inherit">76</text>
|
||||
|
||||
<!-- X-axis labels -->
|
||||
<text x="60" y="340" fill="#141413" font-size="11" font-family="inherit" text-anchor="middle">Jan</text>
|
||||
<text x="140" y="340" fill="#141413" font-size="11" font-family="inherit" text-anchor="middle">Feb</text>
|
||||
<text x="220" y="340" fill="#141413" font-size="11" font-family="inherit" text-anchor="middle">Mar</text>
|
||||
<text x="300" y="340" fill="#141413" font-size="11" font-family="inherit" text-anchor="middle">Apr</text>
|
||||
<text x="380" y="340" fill="#141413" font-size="11" font-family="inherit" text-anchor="middle">May</text>
|
||||
<text x="460" y="340" fill="#141413" font-size="11" font-family="inherit" text-anchor="middle">Jun</text>
|
||||
<text x="540" y="340" fill="#141413" font-size="11" font-family="inherit" text-anchor="middle">Jul</text>
|
||||
<text x="620" y="340" fill="#141413" font-size="11" font-family="inherit" text-anchor="middle">Aug</text>
|
||||
|
||||
<!-- ================================================== DATA END -->
|
||||
|
||||
<!-- Legend -->
|
||||
<line x1="60" y1="368" x2="620" y2="368" stroke="#e8e7e1" stroke-width="0.8"/>
|
||||
|
||||
<!-- Line 1 legend: solid segment + dot -->
|
||||
<line x1="60" y1="386" x2="84" y2="386" stroke="#1B365D" stroke-width="2" stroke-linecap="round"/>
|
||||
<circle cx="72" cy="386" r="3.5" fill="#1B365D"/>
|
||||
<text x="92" y="390" fill="#504e49" font-size="10" font-family="inherit">{{Line 1 label}}</text>
|
||||
|
||||
<!-- Line 2 legend: dashed segment + open dot -->
|
||||
<line x1="220" y1="386" x2="244" y2="386" stroke="#504e49" stroke-width="1.5" stroke-dasharray="5 3" stroke-linecap="round"/>
|
||||
<circle cx="232" cy="386" r="3" fill="#f5f4ed" stroke="#504e49" stroke-width="1.5"/>
|
||||
<text x="252" y="390" fill="#504e49" font-size="10" font-family="inherit">{{Line 2 label}}</text>
|
||||
|
||||
</svg>
|
||||
|
||||
<p class="caption">{{Caption text. The ink-blue line carries the primary trend argument. State what the trend means, not what was plotted.}}</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,173 @@
|
||||
<!DOCTYPE html>
|
||||
<!-- ==================================================================
|
||||
DIAGRAM · Quadrant (kami palette)
|
||||
Two-axis positioning. 4 quadrants, 6-10 plotted points, 1-2 focal.
|
||||
Classic use: Impact × Effort, Reach × Confidence, Urgency × Importance.
|
||||
================================================================== -->
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Quadrant · kami diagram</title>
|
||||
<style>
|
||||
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
|
||||
:root {
|
||||
--parchment: #f5f4ed;
|
||||
--ivory: #faf9f5;
|
||||
--near-black: #141413;
|
||||
--olive: #504e49;
|
||||
--stone: #6b6a64;
|
||||
--brand: #1B365D;
|
||||
--brand-tint: #EEF2F7;
|
||||
--border: #e8e6dc;
|
||||
|
||||
--serif: Charter, Georgia, Palatino, serif;
|
||||
--sans: var(--serif);
|
||||
--mono: "JetBrains Mono", "SF Mono", Consolas, "TsangerJinKai02", "Source Han Serif SC", "Noto Serif CJK SC", "Songti SC", monospace;
|
||||
}
|
||||
|
||||
/* Poster-size page so WeasyPrint exports the full diagram without
|
||||
A4 clipping; browsers ignore @page. */
|
||||
@page { size: 1000px 1000px; margin: 0; }
|
||||
|
||||
body {
|
||||
font-family: var(--sans);
|
||||
background: var(--parchment);
|
||||
color: var(--near-black);
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 3rem 2rem;
|
||||
}
|
||||
|
||||
.frame { max-width: 920px; width: 100%; }
|
||||
|
||||
.eyebrow {
|
||||
font-family: var(--mono);
|
||||
font-size: 0.7rem;
|
||||
letter-spacing: 0.22em;
|
||||
text-transform: uppercase;
|
||||
color: var(--stone);
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-family: var(--serif);
|
||||
font-size: 1.9rem;
|
||||
font-weight: 500;
|
||||
letter-spacing: -0.3pt;
|
||||
line-height: 1.15;
|
||||
margin-bottom: 1.6rem;
|
||||
}
|
||||
|
||||
svg { width: 100%; min-width: 780px; display: block; }
|
||||
|
||||
.caption {
|
||||
font-family: var(--serif);
|
||||
font-size: 0.95rem;
|
||||
color: var(--olive);
|
||||
margin-top: 1rem;
|
||||
max-width: 52ch;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="frame">
|
||||
<p class="eyebrow">Quadrant · kami diagram</p>
|
||||
<h1>{{X axis}} × {{Y axis}}</h1>
|
||||
|
||||
<svg viewBox="0 0 820 540" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<marker id="arrow" markerWidth="8" markerHeight="6" refX="7" refY="3" orient="auto">
|
||||
<polygon points="0 0, 8 3, 0 6" fill="#504e49"/>
|
||||
</marker>
|
||||
</defs>
|
||||
|
||||
<rect width="100%" height="100%" fill="#f5f4ed"/>
|
||||
|
||||
<!-- Quadrant zones (subtle background tint on the "preferred" quadrant) -->
|
||||
<rect x="80" y="60" width="660" height="380" fill="none" stroke="#DEDED7" stroke-width="0.8"/>
|
||||
<rect x="410" y="60" width="330" height="190" fill="#F3EEE6"/>
|
||||
|
||||
<!-- Cross axes (through midpoint) -->
|
||||
<line x1="80" y1="250" x2="740" y2="250" stroke="#CCCCC6" stroke-width="0.8"/>
|
||||
<line x1="410" y1="60" x2="410" y2="440" stroke="#CCCCC6" stroke-width="0.8"/>
|
||||
|
||||
<!-- Axis arrows + labels -->
|
||||
<line x1="80" y1="460" x2="740" y2="460" stroke="#504e49" stroke-width="1.2" marker-end="url(#arrow)"/>
|
||||
<line x1="60" y1="440" x2="60" y2="60" stroke="#504e49" stroke-width="1.2" marker-end="url(#arrow)"/>
|
||||
|
||||
<text x="410" y="480" fill="#141413" font-size="11" font-weight="500" font-family="inherit" text-anchor="middle" letter-spacing="0.08em">{{X axis · low to high}}</text>
|
||||
<text x="30" y="250" fill="#141413" font-size="11" font-weight="500" font-family="inherit" text-anchor="middle" letter-spacing="0.08em" transform="rotate(-90 30 250)">{{Y axis · low to high}}</text>
|
||||
|
||||
<!-- Quadrant labels -->
|
||||
<text x="244" y="88" fill="#6b6a64" font-size="8.5" font-family="'JetBrains Mono', monospace" text-anchor="middle" letter-spacing="0.22em">HIGH-Y · LOW-X</text>
|
||||
<text x="244" y="102" fill="#504e49" font-size="10" font-family="inherit" text-anchor="middle">incubate</text>
|
||||
|
||||
<text x="576" y="88" fill="#1B365D" font-size="8.5" font-family="'JetBrains Mono', monospace" text-anchor="middle" letter-spacing="0.22em">HIGH-Y · HIGH-X</text>
|
||||
<text x="576" y="102" fill="#1B365D" font-size="10" font-family="inherit" text-anchor="middle" font-weight="500">do first</text>
|
||||
|
||||
<text x="244" y="428" fill="#6b6a64" font-size="8.5" font-family="'JetBrains Mono', monospace" text-anchor="middle" letter-spacing="0.22em">LOW-Y · LOW-X</text>
|
||||
<text x="244" y="420" fill="#6b6a64" font-size="10" font-family="inherit" text-anchor="middle">drop</text>
|
||||
|
||||
<text x="576" y="428" fill="#6b6a64" font-size="8.5" font-family="'JetBrains Mono', monospace" text-anchor="middle" letter-spacing="0.22em">LOW-Y · HIGH-X</text>
|
||||
<text x="576" y="420" fill="#504e49" font-size="10" font-family="inherit" text-anchor="middle">delegate</text>
|
||||
|
||||
<!-- Plotted points -->
|
||||
<!-- 1: Top-left (incubate) -->
|
||||
<circle cx="190" cy="180" r="5" fill="#faf9f5" stroke="#141413" stroke-width="1"/>
|
||||
<text x="200" y="184" fill="#141413" font-size="10" font-family="inherit" font-weight="500">{{Item A}}</text>
|
||||
|
||||
<!-- 2: Top-right FOCAL (do first) -->
|
||||
<circle cx="550" cy="140" r="6.5" fill="#1B365D" stroke="#1B365D" stroke-width="1"/>
|
||||
<text x="562" y="145" fill="#1B365D" font-size="11" font-family="inherit" font-weight="500">{{Item B · focal}}</text>
|
||||
|
||||
<!-- 3: Top-right secondary -->
|
||||
<circle cx="640" cy="200" r="5" fill="#EEF2F7" stroke="#1B365D" stroke-width="1"/>
|
||||
<text x="650" y="204" fill="#141413" font-size="10" font-family="inherit" font-weight="500">{{Item C}}</text>
|
||||
|
||||
<!-- 4: Top-right -->
|
||||
<circle cx="490" cy="210" r="5" fill="#faf9f5" stroke="#141413" stroke-width="1"/>
|
||||
<text x="500" y="214" fill="#141413" font-size="10" font-family="inherit">{{Item D}}</text>
|
||||
|
||||
<!-- 5: Bottom-right (delegate) -->
|
||||
<circle cx="580" cy="350" r="4.5" fill="#EAE9E2" stroke="#504e49" stroke-width="1"/>
|
||||
<text x="590" y="354" fill="#504e49" font-size="10" font-family="inherit">{{Item E}}</text>
|
||||
|
||||
<!-- 6: Bottom-left (drop) -->
|
||||
<circle cx="180" cy="380" r="4" fill="#EEEDE6" stroke="#B2B1AC" stroke-width="1"/>
|
||||
<text x="190" y="384" fill="#6b6a64" font-size="10" font-family="inherit">{{Item F}}</text>
|
||||
|
||||
<!-- 7: Bottom-left -->
|
||||
<circle cx="280" cy="340" r="4" fill="#EEEDE6" stroke="#B2B1AC" stroke-width="1"/>
|
||||
<text x="290" y="344" fill="#6b6a64" font-size="10" font-family="inherit">{{Item G}}</text>
|
||||
|
||||
<!-- 8: Near center -->
|
||||
<circle cx="380" cy="270" r="4" fill="#faf9f5" stroke="#141413" stroke-width="1"/>
|
||||
<text x="390" y="274" fill="#141413" font-size="10" font-family="inherit">{{Item H}}</text>
|
||||
|
||||
<!-- Legend -->
|
||||
<line x1="40" y1="502" x2="780" y2="502" stroke="#DEDED7" stroke-width="0.8"/>
|
||||
<text x="40" y="518" fill="#504e49" font-size="8" font-family="'JetBrains Mono', monospace" letter-spacing="0.22em">LEGEND</text>
|
||||
|
||||
<circle cx="180" cy="514" r="5" fill="#1B365D" stroke="#1B365D" stroke-width="1"/>
|
||||
<text x="192" y="518" fill="#504e49" font-size="8.5" font-family="inherit">Focal · do first</text>
|
||||
|
||||
<circle cx="328" cy="514" r="5" fill="#EEF2F7" stroke="#1B365D" stroke-width="1"/>
|
||||
<text x="340" y="518" fill="#504e49" font-size="8.5" font-family="inherit">Near-focal</text>
|
||||
|
||||
<circle cx="450" cy="514" r="5" fill="#faf9f5" stroke="#141413" stroke-width="1"/>
|
||||
<text x="462" y="518" fill="#504e49" font-size="8.5" font-family="inherit">Standard</text>
|
||||
|
||||
<circle cx="555" cy="514" r="5" fill="#EAE9E2" stroke="#504e49" stroke-width="1"/>
|
||||
<text x="567" y="518" fill="#504e49" font-size="8.5" font-family="inherit">Secondary</text>
|
||||
|
||||
<circle cx="660" cy="514" r="5" fill="#EEEDE6" stroke="#B2B1AC" stroke-width="1"/>
|
||||
<text x="672" y="518" fill="#504e49" font-size="8.5" font-family="inherit">Drop</text>
|
||||
</svg>
|
||||
|
||||
<p class="caption">The ink-blue point is where the reader's eye lands. The tinted upper-right quadrant is the preferred region. Everything else recedes so the judgment reads instantly.</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,148 @@
|
||||
<!DOCTYPE html>
|
||||
<!-- ==================================================================
|
||||
DIAGRAM · Sequence (kami palette, Mermaid-sourced)
|
||||
Source: assets/diagrams/src/sequence.mmd. Generated with beautiful-mermaid +
|
||||
scripts/mermaid_normalize.py (Kami re-theme + WeasyPrint-safe static SVG). To
|
||||
change it, regenerate the SVG from any beautiful-mermaid renderer and re-run
|
||||
the normalizer; do not hand-tune coordinates. See references/mermaid.md.
|
||||
================================================================== -->
|
||||
<html lang="zh">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Sequence · kami diagram</title>
|
||||
<style>
|
||||
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
|
||||
@font-face {
|
||||
font-family: "TsangerJinKai02";
|
||||
src: url("../fonts/TsangerJinKai02-W04.ttf") format("truetype"),
|
||||
url("https://cdn.jsdelivr.net/gh/tw93/Kami@main/assets/fonts/TsangerJinKai02-W04.ttf") format("truetype");
|
||||
font-weight: 400;
|
||||
font-style: normal;
|
||||
}
|
||||
@font-face {
|
||||
font-family: "TsangerJinKai02";
|
||||
src: url("../fonts/TsangerJinKai02-W05.ttf") format("truetype"),
|
||||
url("https://cdn.jsdelivr.net/gh/tw93/Kami@main/assets/fonts/TsangerJinKai02-W05.ttf") format("truetype");
|
||||
font-weight: 500;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
:root {
|
||||
--parchment: #f5f4ed;
|
||||
--ivory: #faf9f5;
|
||||
--near-black: #141413;
|
||||
--olive: #504e49;
|
||||
--stone: #6b6a64;
|
||||
--brand: #1B365D;
|
||||
--border: #e8e6dc;
|
||||
|
||||
--serif: Charter, Georgia, "TsangerJinKai02", "Source Han Serif SC", "Noto Serif CJK SC", serif;
|
||||
--sans: var(--serif);
|
||||
--mono: "JetBrains Mono", "SF Mono", Consolas, "TsangerJinKai02", "Source Han Serif SC", monospace;
|
||||
}
|
||||
|
||||
/* Poster-size page so WeasyPrint exports the full diagram without
|
||||
A4 clipping; browsers ignore @page. */
|
||||
@page { size: 800px 880px; margin: 0; }
|
||||
|
||||
body {
|
||||
font-family: var(--sans);
|
||||
background: var(--parchment);
|
||||
color: var(--near-black);
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 3rem 2rem;
|
||||
}
|
||||
|
||||
.frame { max-width: 720px; width: 100%; text-align: center; }
|
||||
.frame > .eyebrow, .frame > h1, .frame > .caption { text-align: left; }
|
||||
|
||||
.eyebrow {
|
||||
font-family: var(--mono);
|
||||
font-size: 0.7rem;
|
||||
letter-spacing: 0.22em;
|
||||
text-transform: uppercase;
|
||||
color: var(--stone);
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-family: var(--serif);
|
||||
font-size: 1.9rem;
|
||||
font-weight: 500;
|
||||
letter-spacing: -0.3pt;
|
||||
line-height: 1.15;
|
||||
margin-bottom: 1.6rem;
|
||||
}
|
||||
|
||||
/* Size to the diagram's intrinsic dimensions, capped so wide diagrams fit
|
||||
the frame and tall ones stay on a single page. Never upscale past natural
|
||||
size (avoids blowing a 200px-wide class diagram up to full width). */
|
||||
svg { display: block; margin: 0 auto; max-width: 100%; max-height: 780px; height: auto; width: auto; }
|
||||
|
||||
.caption {
|
||||
font-family: var(--serif);
|
||||
font-size: 0.95rem;
|
||||
color: var(--olive);
|
||||
margin-top: 1rem;
|
||||
max-width: 52ch;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="frame">
|
||||
<p class="eyebrow">Sequence · kami diagram</p>
|
||||
<h1>{{What interaction does this trace}}</h1>
|
||||
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 420 280" width="420" height="280" style="background:#f5f4ed">
|
||||
<style>
|
||||
text { font-family: Charter, Georgia, "TsangerJinKai02", "Source Han Serif SC", "Noto Serif CJK SC", serif; }
|
||||
</style>
|
||||
<defs>
|
||||
<marker id="seq-arrow" markerWidth="8" markerHeight="5" refX="8" refY="2.5" orient="auto-start-reverse">
|
||||
<polygon points="0 0, 8 2.5, 0 5" fill="#1b365d" />
|
||||
</marker>
|
||||
<marker id="seq-arrow-open" markerWidth="8" markerHeight="5" refX="8" refY="2.5" orient="auto-start-reverse">
|
||||
<polyline points="0 0, 8 2.5, 0 5" fill="none" stroke="#1b365d" stroke-width="1" />
|
||||
</marker>
|
||||
</defs>
|
||||
<line class="lifeline" data-actor="U" x1="70" y1="70" x2="70" y2="250" stroke="#504e49" stroke-width="0.75" stroke-dasharray="6 4" />
|
||||
<line class="lifeline" data-actor="A" x1="210" y1="70" x2="210" y2="250" stroke="#504e49" stroke-width="0.75" stroke-dasharray="6 4" />
|
||||
<line class="lifeline" data-actor="D" x1="350" y1="70" x2="350" y2="250" stroke="#504e49" stroke-width="0.75" stroke-dasharray="6 4" />
|
||||
<g class="message" data-from="U" data-to="A" data-label="提交请求" data-line-style="solid" data-arrow-head="filled" data-self="false">
|
||||
<line x1="70" y1="90" x2="210" y2="90" stroke="#504e49" stroke-width="1" marker-end="url(#seq-arrow)" />
|
||||
<text x="140" y="80" font-size="11" text-anchor="middle" font-weight="400" fill="#6b6a64" dy="3.8499999999999996">提交请求</text>
|
||||
</g>
|
||||
<g class="message" data-from="A" data-to="D" data-label="查询记录" data-line-style="solid" data-arrow-head="filled" data-self="false">
|
||||
<line x1="210" y1="130" x2="350" y2="130" stroke="#504e49" stroke-width="1" marker-end="url(#seq-arrow)" />
|
||||
<text x="280" y="120" font-size="11" text-anchor="middle" font-weight="400" fill="#6b6a64" dy="3.8499999999999996">查询记录</text>
|
||||
</g>
|
||||
<g class="message" data-from="D" data-to="A" data-label="返回结果" data-line-style="dashed" data-arrow-head="filled" data-self="false">
|
||||
<line x1="350" y1="170" x2="210" y2="170" stroke="#504e49" stroke-width="1" stroke-dasharray="6 4" marker-end="url(#seq-arrow)" />
|
||||
<text x="280" y="160" font-size="11" text-anchor="middle" font-weight="400" fill="#6b6a64" dy="3.8499999999999996">返回结果</text>
|
||||
</g>
|
||||
<g class="message" data-from="A" data-to="U" data-label="响应数据" data-line-style="dashed" data-arrow-head="filled" data-self="false">
|
||||
<line x1="210" y1="210" x2="70" y2="210" stroke="#504e49" stroke-width="1" stroke-dasharray="6 4" marker-end="url(#seq-arrow)" />
|
||||
<text x="140" y="200" font-size="11" text-anchor="middle" font-weight="400" fill="#6b6a64" dy="3.8499999999999996">响应数据</text>
|
||||
</g>
|
||||
<g class="actor" data-id="U" data-label="用户" data-type="participant">
|
||||
<rect x="30" y="30" width="80" height="40" rx="4" ry="4" fill="#faf9f5" stroke="#e8e6dc" stroke-width="1" />
|
||||
<text x="70" y="50" font-size="13" text-anchor="middle" font-weight="500" fill="#141413" dy="4.55">用户</text>
|
||||
</g>
|
||||
<g class="actor" data-id="A" data-label="API" data-type="participant">
|
||||
<rect x="170" y="30" width="80" height="40" rx="4" ry="4" fill="#faf9f5" stroke="#e8e6dc" stroke-width="1" />
|
||||
<text x="210" y="50" font-size="13" text-anchor="middle" font-weight="500" fill="#141413" dy="4.55">API</text>
|
||||
</g>
|
||||
<g class="actor" data-id="D" data-label="数据库" data-type="participant">
|
||||
<rect x="310" y="30" width="80" height="40" rx="4" ry="4" fill="#faf9f5" stroke="#e8e6dc" stroke-width="1" />
|
||||
<text x="350" y="50" font-size="13" text-anchor="middle" font-weight="500" fill="#141413" dy="4.55">数据库</text>
|
||||
</g>
|
||||
</svg>
|
||||
|
||||
<p class="caption">Lifelines drop from each participant; messages read top to bottom. Dashed returns distinguish responses from calls.</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,14 @@
|
||||
classDiagram
|
||||
class Order {
|
||||
+String id
|
||||
+Date createdAt
|
||||
+total() Money
|
||||
}
|
||||
class Customer {
|
||||
+String name
|
||||
}
|
||||
class LineItem {
|
||||
+int qty
|
||||
}
|
||||
Customer "1" --> "*" Order : places
|
||||
Order "1" *-- "*" LineItem : contains
|
||||
@@ -0,0 +1,4 @@
|
||||
erDiagram
|
||||
CUSTOMER ||--o{ ORDER : places
|
||||
ORDER ||--|{ LINE_ITEM : contains
|
||||
PRODUCT ||--o{ LINE_ITEM : "appears in"
|
||||
@@ -0,0 +1,8 @@
|
||||
sequenceDiagram
|
||||
participant U as 用户
|
||||
participant A as API
|
||||
participant D as 数据库
|
||||
U->>A: 提交请求
|
||||
A->>D: 查询记录
|
||||
D-->>A: 返回结果
|
||||
A-->>U: 响应数据
|
||||
@@ -0,0 +1,217 @@
|
||||
<!DOCTYPE html>
|
||||
<!-- ==================================================================
|
||||
DIAGRAM · State Machine (kami palette)
|
||||
Finite states + directed transitions. One focal state, everything
|
||||
else in warm neutrals. Drop the <svg> block into a long-doc figure.
|
||||
================================================================== -->
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>State Machine · kami diagram</title>
|
||||
<style>
|
||||
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
|
||||
:root {
|
||||
--parchment: #f5f4ed;
|
||||
--ivory: #faf9f5;
|
||||
--near-black: #141413;
|
||||
--olive: #504e49;
|
||||
--stone: #6b6a64;
|
||||
--brand: #1B365D;
|
||||
--brand-tint: #EEF2F7;
|
||||
--border: #e8e6dc;
|
||||
|
||||
--serif: Charter, Georgia, Palatino, serif;
|
||||
--sans: var(--serif);
|
||||
--mono: "JetBrains Mono", "SF Mono", Consolas, monospace;
|
||||
}
|
||||
|
||||
/* Poster-size page so WeasyPrint exports the full diagram without
|
||||
A4 clipping; browsers ignore @page. */
|
||||
@page { size: 1080px 880px; margin: 0; }
|
||||
|
||||
body {
|
||||
font-family: var(--sans);
|
||||
background: var(--parchment);
|
||||
color: var(--near-black);
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 3rem 2rem;
|
||||
}
|
||||
|
||||
.frame { max-width: 1000px; width: 100%; }
|
||||
|
||||
.eyebrow {
|
||||
font-family: var(--mono);
|
||||
font-size: 0.7rem;
|
||||
letter-spacing: 0.22em;
|
||||
text-transform: uppercase;
|
||||
color: var(--stone);
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-family: var(--serif);
|
||||
font-size: 1.9rem;
|
||||
font-weight: 500;
|
||||
letter-spacing: -0.3pt;
|
||||
line-height: 1.15;
|
||||
margin-bottom: 1.6rem;
|
||||
}
|
||||
|
||||
svg { width: 100%; min-width: 860px; display: block; }
|
||||
|
||||
.caption {
|
||||
font-family: var(--serif);
|
||||
font-size: 0.95rem;
|
||||
color: var(--olive);
|
||||
margin-top: 1rem;
|
||||
max-width: 52ch;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="frame">
|
||||
<p class="eyebrow">State Machine · kami diagram</p>
|
||||
<h1>{{Component name}} lifecycle</h1>
|
||||
|
||||
<svg viewBox="0 0 960 460" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<pattern id="dots" width="22" height="22" patternUnits="userSpaceOnUse">
|
||||
<circle cx="1" cy="1" r="0.9" fill="#E3E2DC"/>
|
||||
</pattern>
|
||||
</defs>
|
||||
|
||||
<rect width="100%" height="100%" fill="#f5f4ed"/>
|
||||
<rect width="100%" height="100%" fill="url(#dots)" opacity="0.55"/>
|
||||
|
||||
<!-- ── Back arc: Processing → Idle (reset / timeout) ── -->
|
||||
<path d="M 560,196 Q 356,120 152,196"
|
||||
fill="none" stroke="#504e49" stroke-width="1.2" stroke-dasharray="4,3"/>
|
||||
<rect x="328" y="120" width="56" height="12" rx="2" fill="#f5f4ed"/>
|
||||
<text x="356" y="130" fill="#504e49" font-size="8" font-family="'JetBrains Mono', monospace"
|
||||
text-anchor="middle" letter-spacing="0.1em">RESET</text>
|
||||
<!-- Left-pointing chevron at Idle top -->
|
||||
<path d="M 158,191 L 152,196 L 158,201"
|
||||
fill="none" stroke="#504e49" stroke-width="1.5" stroke-linecap="round"/>
|
||||
|
||||
<!-- ── Forward arrows (behind nodes) ── -->
|
||||
<!-- Start dot → Idle -->
|
||||
<line x1="46" y1="228" x2="80" y2="228" stroke="#6b6a64" stroke-width="1.2"/>
|
||||
<path d="M 74,224 L 80,228 L 74,232"
|
||||
fill="none" stroke="#6b6a64" stroke-width="1.5" stroke-linecap="round"/>
|
||||
|
||||
<!-- Idle → Active -->
|
||||
<line x1="224" y1="228" x2="284" y2="228" stroke="#1B365D" stroke-width="1.4"/>
|
||||
<rect x="226" y="218" width="56" height="12" rx="2" fill="#f5f4ed"/>
|
||||
<text x="254" y="228" fill="#1B365D" font-size="8" font-family="'JetBrains Mono', monospace"
|
||||
text-anchor="middle" letter-spacing="0.1em">TRIGGER</text>
|
||||
<path d="M 278,224 L 284,228 L 278,232"
|
||||
fill="none" stroke="#1B365D" stroke-width="1.5" stroke-linecap="round"/>
|
||||
|
||||
<!-- Active → Processing -->
|
||||
<line x1="428" y1="228" x2="488" y2="228" stroke="#504e49" stroke-width="1.2"/>
|
||||
<rect x="430" y="218" width="56" height="12" rx="2" fill="#f5f4ed"/>
|
||||
<text x="458" y="228" fill="#504e49" font-size="8" font-family="'JetBrains Mono', monospace"
|
||||
text-anchor="middle" letter-spacing="0.1em">SUBMIT</text>
|
||||
<path d="M 482,224 L 488,228 L 482,232"
|
||||
fill="none" stroke="#504e49" stroke-width="1.5" stroke-linecap="round"/>
|
||||
|
||||
<!-- Processing → Done -->
|
||||
<line x1="632" y1="228" x2="720" y2="228" stroke="#504e49" stroke-width="1.2"/>
|
||||
<rect x="634" y="218" width="84" height="12" rx="2" fill="#f5f4ed"/>
|
||||
<text x="676" y="228" fill="#504e49" font-size="8" font-family="'JetBrains Mono', monospace"
|
||||
text-anchor="middle" letter-spacing="0.1em">COMPLETE</text>
|
||||
<path d="M 714,224 L 720,228 L 714,232"
|
||||
fill="none" stroke="#504e49" stroke-width="1.5" stroke-linecap="round"/>
|
||||
|
||||
<!-- Done → End marker -->
|
||||
<line x1="848" y1="228" x2="896" y2="228" stroke="#504e49" stroke-width="1.2"/>
|
||||
<path d="M 890,224 L 896,228 L 890,232"
|
||||
fill="none" stroke="#504e49" stroke-width="1.5" stroke-linecap="round"/>
|
||||
|
||||
<!-- ── Start marker (solid circle) ── -->
|
||||
<circle cx="40" cy="228" r="6" fill="#141413"/>
|
||||
|
||||
<!-- ── State nodes ── -->
|
||||
|
||||
<!-- S1: Idle -->
|
||||
<rect x="80" y="196" width="144" height="64" rx="6" fill="#f5f4ed"/>
|
||||
<rect x="80" y="196" width="144" height="64" rx="6"
|
||||
fill="#E9E8E1" stroke="#6b6a64" stroke-width="1"/>
|
||||
<text x="102" y="214" fill="#6b6a64" font-size="7" font-family="'JetBrains Mono', monospace"
|
||||
letter-spacing="0.15em">STATE</text>
|
||||
<text x="152" y="234" fill="#141413" font-size="12" font-weight="500"
|
||||
font-family="inherit" text-anchor="middle">Idle</text>
|
||||
<text x="152" y="250" fill="#504e49" font-size="9" font-family="'JetBrains Mono', monospace"
|
||||
text-anchor="middle">waiting</text>
|
||||
|
||||
<!-- S2: Active (FOCAL) -->
|
||||
<rect x="284" y="196" width="144" height="64" rx="6" fill="#f5f4ed"/>
|
||||
<rect x="284" y="196" width="144" height="64" rx="6"
|
||||
fill="#EEF2F7" stroke="#1B365D" stroke-width="1"/>
|
||||
<text x="306" y="214" fill="#1B365D" font-size="7" font-family="'JetBrains Mono', monospace"
|
||||
letter-spacing="0.15em">FOCAL</text>
|
||||
<text x="356" y="234" fill="#141413" font-size="12" font-weight="500"
|
||||
font-family="inherit" text-anchor="middle">Active</text>
|
||||
<text x="356" y="250" fill="#504e49" font-size="9" font-family="'JetBrains Mono', monospace"
|
||||
text-anchor="middle">editing</text>
|
||||
|
||||
<!-- S3: Processing -->
|
||||
<rect x="488" y="196" width="144" height="64" rx="6" fill="#f5f4ed"/>
|
||||
<rect x="488" y="196" width="144" height="64" rx="6"
|
||||
fill="#EEEDE6" stroke="#B2B1AC" stroke-width="1"/>
|
||||
<text x="510" y="214" fill="#6b6a64" font-size="7" font-family="'JetBrains Mono', monospace"
|
||||
letter-spacing="0.15em">STATE</text>
|
||||
<text x="560" y="234" fill="#141413" font-size="12" font-weight="500"
|
||||
font-family="inherit" text-anchor="middle">Processing</text>
|
||||
<text x="560" y="250" fill="#504e49" font-size="9" font-family="'JetBrains Mono', monospace"
|
||||
text-anchor="middle">running</text>
|
||||
|
||||
<!-- S4: Done -->
|
||||
<rect x="720" y="196" width="128" height="64" rx="6" fill="#f5f4ed"/>
|
||||
<rect x="720" y="196" width="128" height="64" rx="6"
|
||||
fill="#faf9f5" stroke="#141413" stroke-width="1"/>
|
||||
<text x="742" y="214" fill="#141413" font-size="7" font-family="'JetBrains Mono', monospace"
|
||||
letter-spacing="0.15em">TERMINAL</text>
|
||||
<text x="784" y="234" fill="#141413" font-size="12" font-weight="500"
|
||||
font-family="inherit" text-anchor="middle">Done</text>
|
||||
<text x="784" y="250" fill="#504e49" font-size="9" font-family="'JetBrains Mono', monospace"
|
||||
text-anchor="middle">resolved</text>
|
||||
|
||||
<!-- ── End marker (double circle) ── -->
|
||||
<circle cx="916" cy="228" r="8" fill="none" stroke="#141413" stroke-width="1.4"/>
|
||||
<circle cx="916" cy="228" r="4" fill="#141413"/>
|
||||
|
||||
<!-- ── Legend ── -->
|
||||
<line x1="40" y1="386" x2="920" y2="386" stroke="#DEDED7" stroke-width="0.8"/>
|
||||
<text x="40" y="402" fill="#504e49" font-size="8" font-family="'JetBrains Mono', monospace"
|
||||
letter-spacing="0.22em">LEGEND</text>
|
||||
|
||||
<circle cx="47" cy="420" r="6" fill="#141413"/>
|
||||
<text x="60" y="424" fill="#504e49" font-size="8.5" font-family="inherit">Start</text>
|
||||
|
||||
<rect x="100" y="413" width="14" height="10" rx="2" fill="#EEF2F7" stroke="#1B365D" stroke-width="1"/>
|
||||
<text x="120" y="421" fill="#504e49" font-size="8.5" font-family="inherit">Focal state</text>
|
||||
|
||||
<rect x="230" y="413" width="14" height="10" rx="2" fill="#faf9f5" stroke="#141413" stroke-width="1"/>
|
||||
<text x="250" y="421" fill="#504e49" font-size="8.5" font-family="inherit">Terminal</text>
|
||||
|
||||
<line x1="360" y1="418" x2="392" y2="418" stroke="#1B365D" stroke-width="1.4"/>
|
||||
<path d="M 386,414 L 392,418 L 386,422"
|
||||
fill="none" stroke="#1B365D" stroke-width="1.5" stroke-linecap="round"/>
|
||||
<text x="400" y="421" fill="#504e49" font-size="8.5" font-family="inherit">Forward transition</text>
|
||||
|
||||
<line x1="560" y1="418" x2="592" y2="418" stroke="#504e49" stroke-width="1.2"
|
||||
stroke-dasharray="4,3"/>
|
||||
<path d="M 586,414 L 592,418 L 586,422"
|
||||
fill="none" stroke="#504e49" stroke-width="1.5" stroke-linecap="round"/>
|
||||
<text x="600" y="421" fill="#504e49" font-size="8.5" font-family="inherit">Reset / timeout</text>
|
||||
</svg>
|
||||
|
||||
<p class="caption">Focal rule: one ink-blue state per diagram, marking where the user spends most time. Start and terminal markers follow UML convention; the dashed arc shows the exceptional path.</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,202 @@
|
||||
<!DOCTYPE html>
|
||||
<!-- ==================================================================
|
||||
DIAGRAM · Swimlane (kami palette)
|
||||
Process flow across responsibility lanes. Focal lane in ink-blue;
|
||||
other lanes in warm neutrals. Orthogonal elbow connectors only.
|
||||
================================================================== -->
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Swimlane · kami diagram</title>
|
||||
<style>
|
||||
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
|
||||
:root {
|
||||
--parchment: #f5f4ed;
|
||||
--ivory: #faf9f5;
|
||||
--near-black: #141413;
|
||||
--olive: #504e49;
|
||||
--stone: #6b6a64;
|
||||
--brand: #1B365D;
|
||||
--brand-tint: #EEF2F7;
|
||||
--border: #e8e6dc;
|
||||
|
||||
--serif: Charter, Georgia, Palatino, serif;
|
||||
--sans: var(--serif);
|
||||
--mono: "JetBrains Mono", "SF Mono", Consolas, monospace;
|
||||
}
|
||||
|
||||
/* Poster-size page so WeasyPrint exports the full diagram without
|
||||
A4 clipping; browsers ignore @page. */
|
||||
@page { size: 1080px 880px; margin: 0; }
|
||||
|
||||
body {
|
||||
font-family: var(--sans);
|
||||
background: var(--parchment);
|
||||
color: var(--near-black);
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 3rem 2rem;
|
||||
}
|
||||
|
||||
.frame { max-width: 1000px; width: 100%; }
|
||||
|
||||
.eyebrow {
|
||||
font-family: var(--mono);
|
||||
font-size: 0.7rem;
|
||||
letter-spacing: 0.22em;
|
||||
text-transform: uppercase;
|
||||
color: var(--stone);
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-family: var(--serif);
|
||||
font-size: 1.9rem;
|
||||
font-weight: 500;
|
||||
letter-spacing: -0.3pt;
|
||||
line-height: 1.15;
|
||||
margin-bottom: 1.6rem;
|
||||
}
|
||||
|
||||
svg { width: 100%; min-width: 860px; display: block; }
|
||||
|
||||
.caption {
|
||||
font-family: var(--serif);
|
||||
font-size: 0.95rem;
|
||||
color: var(--olive);
|
||||
margin-top: 1rem;
|
||||
max-width: 52ch;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="frame">
|
||||
<p class="eyebrow">Swimlane · kami diagram</p>
|
||||
<h1>{{System name}} request flow</h1>
|
||||
|
||||
<svg viewBox="0 0 960 460" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<pattern id="dots" width="22" height="22" patternUnits="userSpaceOnUse">
|
||||
<circle cx="1" cy="1" r="0.9" fill="#E3E2DC"/>
|
||||
</pattern>
|
||||
</defs>
|
||||
|
||||
<rect width="100%" height="100%" fill="#f5f4ed"/>
|
||||
<rect width="100%" height="100%" fill="url(#dots)" opacity="0.55"/>
|
||||
|
||||
<!-- ── Lane backgrounds ── -->
|
||||
<!-- Lane 1: Client, y=56..176 -->
|
||||
<rect x="64" y="56" width="880" height="120" rx="0" fill="#F0F0E9"/>
|
||||
<!-- Lane 2: Server, y=176..296 (focal) -->
|
||||
<rect x="64" y="176" width="880" height="120" fill="#EEF2F7" opacity="0.40"/>
|
||||
<!-- Lane 3: Database, y=296..416 -->
|
||||
<rect x="64" y="296" width="880" height="120" rx="0" fill="#F0F0E9"/>
|
||||
|
||||
<!-- Lane dividers -->
|
||||
<line x1="64" y1="56" x2="944" y2="56" stroke="#e8e6dc" stroke-width="1"/>
|
||||
<line x1="64" y1="176" x2="944" y2="176" stroke="#1B365D" stroke-width="0.8" stroke-dasharray="4,3"/>
|
||||
<line x1="64" y1="296" x2="944" y2="296" stroke="#e8e6dc" stroke-width="0.8" stroke-dasharray="4,3"/>
|
||||
<line x1="64" y1="416" x2="944" y2="416" stroke="#e8e6dc" stroke-width="1"/>
|
||||
|
||||
<!-- Lane label column -->
|
||||
<rect x="0" y="56" width="64" height="360" fill="#f5f4ed" stroke="#e8e6dc" stroke-width="0.8"/>
|
||||
<text x="32" y="124" fill="#6b6a64" font-size="8" font-family="'JetBrains Mono', monospace"
|
||||
text-anchor="middle" letter-spacing="0.15em"
|
||||
transform="rotate(-90, 32, 116)">CLIENT</text>
|
||||
<text x="32" y="244" fill="#1B365D" font-size="8" font-family="'JetBrains Mono', monospace"
|
||||
text-anchor="middle" letter-spacing="0.15em"
|
||||
transform="rotate(-90, 32, 236)">SERVER</text>
|
||||
<text x="32" y="364" fill="#6b6a64" font-size="8" font-family="'JetBrains Mono', monospace"
|
||||
text-anchor="middle" letter-spacing="0.15em"
|
||||
transform="rotate(-90, 32, 356)">DATABASE</text>
|
||||
|
||||
<!-- ── Connectors (drawn behind nodes) ── -->
|
||||
|
||||
<!-- N1 → N2: (152,140) → (152,212) vertical -->
|
||||
<line x1="152" y1="140" x2="152" y2="212" stroke="#504e49" stroke-width="1.2"/>
|
||||
<path d="M 147,206 L 152,212 L 157,206"
|
||||
fill="none" stroke="#504e49" stroke-width="1.5" stroke-linecap="round"/>
|
||||
|
||||
<!-- N2 → N3: (224,236) → (288,236) horizontal -->
|
||||
<line x1="224" y1="236" x2="288" y2="236" stroke="#1B365D" stroke-width="1.4"/>
|
||||
<path d="M 282,231 L 288,236 L 282,241"
|
||||
fill="none" stroke="#1B365D" stroke-width="1.5" stroke-linecap="round"/>
|
||||
|
||||
<!-- N3 → N4: elbow (360,260) → (360,296) → (568,296) → (568,332) -->
|
||||
<polyline points="360,260 360,296 568,296 568,332"
|
||||
fill="none" stroke="#504e49" stroke-width="1.2"/>
|
||||
<path d="M 563,326 L 568,332 L 573,326"
|
||||
fill="none" stroke="#504e49" stroke-width="1.5" stroke-linecap="round"/>
|
||||
|
||||
<!-- N4 → N5: (640,356) → (776,356) → (776,140) -->
|
||||
<polyline points="640,356 812,356 812,140"
|
||||
fill="none" stroke="#504e49" stroke-width="1.2"/>
|
||||
<path d="M 807,146 L 812,140 L 817,146"
|
||||
fill="none" stroke="#504e49" stroke-width="1.5" stroke-linecap="round"/>
|
||||
|
||||
<!-- ── Nodes ── -->
|
||||
|
||||
<!-- N1: Request, Lane 1, x=80 y=92 w=144 h=48 -->
|
||||
<rect x="80" y="92" width="144" height="48" rx="6" fill="#f5f4ed"/>
|
||||
<rect x="80" y="92" width="144" height="48" rx="6"
|
||||
fill="#E9E8E1" stroke="#6b6a64" stroke-width="1"/>
|
||||
<text x="102" y="108" fill="#6b6a64" font-size="7" font-family="'JetBrains Mono', monospace"
|
||||
letter-spacing="0.15em">USER</text>
|
||||
<text x="152" y="124" fill="#141413" font-size="12" font-weight="500"
|
||||
font-family="inherit" text-anchor="middle">Request</text>
|
||||
|
||||
<!-- N2: Validate, Lane 2, x=80 y=212 w=144 h=48 FOCAL -->
|
||||
<rect x="80" y="212" width="144" height="48" rx="6" fill="#f5f4ed"/>
|
||||
<rect x="80" y="212" width="144" height="48" rx="6"
|
||||
fill="#EEF2F7" stroke="#1B365D" stroke-width="1"/>
|
||||
<text x="102" y="228" fill="#1B365D" font-size="7" font-family="'JetBrains Mono', monospace"
|
||||
letter-spacing="0.15em">FOCAL</text>
|
||||
<text x="152" y="244" fill="#141413" font-size="12" font-weight="500"
|
||||
font-family="inherit" text-anchor="middle">Validate</text>
|
||||
|
||||
<!-- N3: Route, Lane 2, x=288 y=212 w=144 h=48 -->
|
||||
<rect x="288" y="212" width="144" height="48" rx="6" fill="#f5f4ed"/>
|
||||
<rect x="288" y="212" width="144" height="48" rx="6"
|
||||
fill="#EEEDE6" stroke="#B2B1AC" stroke-width="1"/>
|
||||
<text x="310" y="228" fill="#6b6a64" font-size="7" font-family="'JetBrains Mono', monospace"
|
||||
letter-spacing="0.15em">HANDLER</text>
|
||||
<text x="360" y="244" fill="#141413" font-size="12" font-weight="500"
|
||||
font-family="inherit" text-anchor="middle">Route</text>
|
||||
|
||||
<!-- N4: Query, Lane 3, x=496 y=332 w=144 h=48 -->
|
||||
<rect x="496" y="332" width="144" height="48" rx="6" fill="#f5f4ed"/>
|
||||
<rect x="496" y="332" width="144" height="48" rx="6"
|
||||
fill="#EAE9E2" stroke="#504e49" stroke-width="1"/>
|
||||
<text x="518" y="348" fill="#504e49" font-size="7" font-family="'JetBrains Mono', monospace"
|
||||
letter-spacing="0.15em">STORE</text>
|
||||
<text x="568" y="364" fill="#141413" font-size="12" font-weight="500"
|
||||
font-family="inherit" text-anchor="middle">Query</text>
|
||||
|
||||
<!-- N5: Respond, Lane 1, x=740 y=92 w=144 h=48 -->
|
||||
<rect x="740" y="92" width="144" height="48" rx="6" fill="#f5f4ed"/>
|
||||
<rect x="740" y="92" width="144" height="48" rx="6"
|
||||
fill="#E9E8E1" stroke="#6b6a64" stroke-width="1"/>
|
||||
<text x="762" y="108" fill="#6b6a64" font-size="7" font-family="'JetBrains Mono', monospace"
|
||||
letter-spacing="0.15em">USER</text>
|
||||
<text x="812" y="124" fill="#141413" font-size="12" font-weight="500"
|
||||
font-family="inherit" text-anchor="middle">Respond</text>
|
||||
|
||||
<!-- ── Legend ── -->
|
||||
<rect x="64" y="416" width="880" height="44" fill="#f5f4ed"/>
|
||||
<text x="80" y="434" fill="#504e49" font-size="8" font-family="'JetBrains Mono', monospace"
|
||||
letter-spacing="0.22em">LEGEND</text>
|
||||
<rect x="160" y="426" width="14" height="10" rx="2" fill="#EEF2F7" stroke="#1B365D" stroke-width="1"/>
|
||||
<text x="180" y="434" fill="#504e49" font-size="8.5" font-family="inherit">Focal lane / node</text>
|
||||
<rect x="320" y="426" width="14" height="10" rx="2" fill="#EAE9E2" stroke="#504e49" stroke-width="1"/>
|
||||
<text x="340" y="434" fill="#504e49" font-size="8.5" font-family="inherit">Store</text>
|
||||
<rect x="420" y="426" width="14" height="10" rx="2" fill="#E9E8E1" stroke="#6b6a64" stroke-width="1"/>
|
||||
<text x="440" y="434" fill="#504e49" font-size="8.5" font-family="inherit">Client / external</text>
|
||||
</svg>
|
||||
|
||||
<p class="caption">Focal rule: the Server lane carries the business logic, so it gets the ink-blue tint. Client and Database lanes stay in warm neutral to keep the eye on what actually decides the outcome.</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,170 @@
|
||||
<!DOCTYPE html>
|
||||
<!-- ==================================================================
|
||||
DIAGRAM · Timeline (kami palette)
|
||||
Horizontal time axis with alternating above/below event labels.
|
||||
One focal milestone in ink-blue; all others in warm neutrals.
|
||||
================================================================== -->
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Timeline · kami diagram</title>
|
||||
<style>
|
||||
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
|
||||
:root {
|
||||
--parchment: #f5f4ed;
|
||||
--ivory: #faf9f5;
|
||||
--near-black: #141413;
|
||||
--olive: #504e49;
|
||||
--stone: #6b6a64;
|
||||
--brand: #1B365D;
|
||||
--brand-tint: #EEF2F7;
|
||||
--border: #e8e6dc;
|
||||
|
||||
--serif: Charter, Georgia, Palatino, serif;
|
||||
--sans: var(--serif);
|
||||
--mono: "JetBrains Mono", "SF Mono", Consolas, monospace;
|
||||
}
|
||||
|
||||
/* Poster-size page so WeasyPrint exports the full diagram without
|
||||
A4 clipping; browsers ignore @page. */
|
||||
@page { size: 1080px 880px; margin: 0; }
|
||||
|
||||
body {
|
||||
font-family: var(--sans);
|
||||
background: var(--parchment);
|
||||
color: var(--near-black);
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 3rem 2rem;
|
||||
}
|
||||
|
||||
.frame { max-width: 1000px; width: 100%; }
|
||||
|
||||
.eyebrow {
|
||||
font-family: var(--mono);
|
||||
font-size: 0.7rem;
|
||||
letter-spacing: 0.22em;
|
||||
text-transform: uppercase;
|
||||
color: var(--stone);
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-family: var(--serif);
|
||||
font-size: 1.9rem;
|
||||
font-weight: 500;
|
||||
letter-spacing: -0.3pt;
|
||||
line-height: 1.15;
|
||||
margin-bottom: 1.6rem;
|
||||
}
|
||||
|
||||
svg { width: 100%; min-width: 860px; display: block; }
|
||||
|
||||
.caption {
|
||||
font-family: var(--serif);
|
||||
font-size: 0.95rem;
|
||||
color: var(--olive);
|
||||
margin-top: 1rem;
|
||||
max-width: 52ch;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="frame">
|
||||
<p class="eyebrow">Timeline · kami diagram</p>
|
||||
<h1>{{Project name}} milestones</h1>
|
||||
|
||||
<svg viewBox="0 0 960 460" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<pattern id="dots" width="22" height="22" patternUnits="userSpaceOnUse">
|
||||
<circle cx="1" cy="1" r="0.9" fill="#E3E2DC"/>
|
||||
</pattern>
|
||||
</defs>
|
||||
|
||||
<rect width="100%" height="100%" fill="#f5f4ed"/>
|
||||
<rect width="100%" height="100%" fill="url(#dots)" opacity="0.55"/>
|
||||
|
||||
<!-- ── Horizontal axis ── -->
|
||||
<line x1="60" y1="228" x2="900" y2="228" stroke="#b8b7b0" stroke-width="1.4"/>
|
||||
<!-- Axis end arrow (right) -->
|
||||
<path d="M 894,224 L 900,228 L 894,232"
|
||||
fill="none" stroke="#b8b7b0" stroke-width="1.5" stroke-linecap="round"/>
|
||||
|
||||
<!-- ================================================================
|
||||
Events: x = 140, 292, 444, 596, 748
|
||||
Alternating: above (0,2,4), below (1,3)
|
||||
Event 2 (x=444) is focal
|
||||
================================================================ -->
|
||||
|
||||
<!-- Event 0: x=140, ABOVE, "Concept" 2019 -->
|
||||
<line x1="140" y1="220" x2="140" y2="168" stroke="#6b6a64" stroke-width="1" stroke-dasharray="3,3"/>
|
||||
<circle cx="140" cy="228" r="5" fill="#6b6a64" stroke="#f5f4ed" stroke-width="1.5"/>
|
||||
<rect x="88" y="128" width="104" height="40" rx="4"
|
||||
fill="#f5f4ed" stroke="#e8e6dc" stroke-width="0.8"/>
|
||||
<text x="140" y="143" fill="#6b6a64" font-size="8" font-family="'JetBrains Mono', monospace"
|
||||
text-anchor="middle" letter-spacing="0.15em">2019</text>
|
||||
<text x="140" y="159" fill="#141413" font-size="11" font-weight="500"
|
||||
font-family="inherit" text-anchor="middle">Concept</text>
|
||||
|
||||
<!-- Event 1: x=292, BELOW, "Prototype" 2020 -->
|
||||
<line x1="292" y1="236" x2="292" y2="288" stroke="#6b6a64" stroke-width="1" stroke-dasharray="3,3"/>
|
||||
<circle cx="292" cy="228" r="5" fill="#6b6a64" stroke="#f5f4ed" stroke-width="1.5"/>
|
||||
<rect x="236" y="288" width="112" height="40" rx="4"
|
||||
fill="#f5f4ed" stroke="#e8e6dc" stroke-width="0.8"/>
|
||||
<text x="292" y="303" fill="#6b6a64" font-size="8" font-family="'JetBrains Mono', monospace"
|
||||
text-anchor="middle" letter-spacing="0.15em">2020</text>
|
||||
<text x="292" y="319" fill="#141413" font-size="11" font-weight="500"
|
||||
font-family="inherit" text-anchor="middle">Prototype</text>
|
||||
|
||||
<!-- Event 2: x=444, ABOVE, "Launch" 2021 FOCAL -->
|
||||
<line x1="444" y1="220" x2="444" y2="148" stroke="#1B365D" stroke-width="1.2"/>
|
||||
<circle cx="444" cy="228" r="6" fill="#1B365D" stroke="#f5f4ed" stroke-width="1.5"/>
|
||||
<rect x="380" y="100" width="128" height="48" rx="4"
|
||||
fill="#EEF2F7" stroke="#1B365D" stroke-width="1"/>
|
||||
<text x="444" y="117" fill="#1B365D" font-size="8" font-family="'JetBrains Mono', monospace"
|
||||
text-anchor="middle" letter-spacing="0.15em">2021 · FOCAL</text>
|
||||
<text x="444" y="138" fill="#141413" font-size="13" font-weight="500"
|
||||
font-family="inherit" text-anchor="middle">Launch</text>
|
||||
|
||||
<!-- Event 3: x=596, BELOW, "Growth" 2022 -->
|
||||
<line x1="596" y1="236" x2="596" y2="288" stroke="#6b6a64" stroke-width="1" stroke-dasharray="3,3"/>
|
||||
<circle cx="596" cy="228" r="5" fill="#504e49" stroke="#f5f4ed" stroke-width="1.5"/>
|
||||
<rect x="540" y="288" width="112" height="40" rx="4"
|
||||
fill="#f5f4ed" stroke="#e8e6dc" stroke-width="0.8"/>
|
||||
<text x="596" y="303" fill="#6b6a64" font-size="8" font-family="'JetBrains Mono', monospace"
|
||||
text-anchor="middle" letter-spacing="0.15em">2022</text>
|
||||
<text x="596" y="319" fill="#141413" font-size="11" font-weight="500"
|
||||
font-family="inherit" text-anchor="middle">Growth</text>
|
||||
|
||||
<!-- Event 4: x=748, ABOVE, "Scale" 2023 -->
|
||||
<line x1="748" y1="220" x2="748" y2="168" stroke="#6b6a64" stroke-width="1" stroke-dasharray="3,3"/>
|
||||
<circle cx="748" cy="228" r="5" fill="#504e49" stroke="#f5f4ed" stroke-width="1.5"/>
|
||||
<rect x="692" y="128" width="112" height="40" rx="4"
|
||||
fill="#f5f4ed" stroke="#e8e6dc" stroke-width="0.8"/>
|
||||
<text x="748" y="143" fill="#6b6a64" font-size="8" font-family="'JetBrains Mono', monospace"
|
||||
text-anchor="middle" letter-spacing="0.15em">2023</text>
|
||||
<text x="748" y="159" fill="#141413" font-size="11" font-weight="500"
|
||||
font-family="inherit" text-anchor="middle">Scale</text>
|
||||
|
||||
<!-- ── Legend ── -->
|
||||
<line x1="40" y1="390" x2="920" y2="390" stroke="#DEDED7" stroke-width="0.8"/>
|
||||
<text x="40" y="406" fill="#504e49" font-size="8" font-family="'JetBrains Mono', monospace"
|
||||
letter-spacing="0.22em">LEGEND</text>
|
||||
|
||||
<circle cx="47" cy="424" r="6" fill="#1B365D" stroke="#f5f4ed" stroke-width="1.5"/>
|
||||
<text x="60" y="428" fill="#504e49" font-size="8.5" font-family="inherit">Focal milestone</text>
|
||||
|
||||
<circle cx="183" cy="424" r="5" fill="#504e49" stroke="#f5f4ed" stroke-width="1.5"/>
|
||||
<text x="196" y="428" fill="#504e49" font-size="8.5" font-family="inherit">Standard milestone</text>
|
||||
|
||||
<line x1="360" y1="424" x2="392" y2="424" stroke="#6b6a64" stroke-width="1" stroke-dasharray="3,3"/>
|
||||
<text x="400" y="428" fill="#504e49" font-size="8.5" font-family="inherit">Connector</text>
|
||||
</svg>
|
||||
|
||||
<p class="caption">Focal rule: ink-blue marks the single most important milestone, the moment the reader should remember. Every other event stays in warm stone so the contrast does the work.</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,227 @@
|
||||
<!DOCTYPE html>
|
||||
<!-- ==================================================================
|
||||
DIAGRAM · Tree (kami palette)
|
||||
Hierarchy with orthogonal elbow connectors. One focal sub-tree
|
||||
in ink-blue; root and other branches in warm neutrals.
|
||||
================================================================== -->
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Tree · kami diagram</title>
|
||||
<style>
|
||||
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
|
||||
:root {
|
||||
--parchment: #f5f4ed;
|
||||
--ivory: #faf9f5;
|
||||
--near-black: #141413;
|
||||
--olive: #504e49;
|
||||
--stone: #6b6a64;
|
||||
--brand: #1B365D;
|
||||
--brand-tint: #EEF2F7;
|
||||
--border: #e8e6dc;
|
||||
|
||||
--serif: Charter, Georgia, Palatino, serif;
|
||||
--sans: var(--serif);
|
||||
--mono: "JetBrains Mono", "SF Mono", Consolas, monospace;
|
||||
}
|
||||
|
||||
/* Poster-size page so WeasyPrint exports the full diagram without
|
||||
A4 clipping; browsers ignore @page. */
|
||||
@page { size: 1080px 880px; margin: 0; }
|
||||
|
||||
body {
|
||||
font-family: var(--sans);
|
||||
background: var(--parchment);
|
||||
color: var(--near-black);
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 3rem 2rem;
|
||||
}
|
||||
|
||||
.frame { max-width: 1000px; width: 100%; }
|
||||
|
||||
.eyebrow {
|
||||
font-family: var(--mono);
|
||||
font-size: 0.7rem;
|
||||
letter-spacing: 0.22em;
|
||||
text-transform: uppercase;
|
||||
color: var(--stone);
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-family: var(--serif);
|
||||
font-size: 1.9rem;
|
||||
font-weight: 500;
|
||||
letter-spacing: -0.3pt;
|
||||
line-height: 1.15;
|
||||
margin-bottom: 1.6rem;
|
||||
}
|
||||
|
||||
svg { width: 100%; min-width: 860px; display: block; }
|
||||
|
||||
.caption {
|
||||
font-family: var(--serif);
|
||||
font-size: 0.95rem;
|
||||
color: var(--olive);
|
||||
margin-top: 1rem;
|
||||
max-width: 52ch;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="frame">
|
||||
<p class="eyebrow">Tree · kami diagram</p>
|
||||
<h1>{{System name}} hierarchy</h1>
|
||||
|
||||
<svg viewBox="0 0 960 460" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<pattern id="dots" width="22" height="22" patternUnits="userSpaceOnUse">
|
||||
<circle cx="1" cy="1" r="0.9" fill="#E3E2DC"/>
|
||||
</pattern>
|
||||
</defs>
|
||||
|
||||
<rect width="100%" height="100%" fill="#f5f4ed"/>
|
||||
<rect width="100%" height="100%" fill="url(#dots)" opacity="0.55"/>
|
||||
|
||||
<!-- ── Connector lines (drawn behind nodes) ── -->
|
||||
|
||||
<!-- Root → junction at y=116, then branch to C1(x=248) and C2(x=712) -->
|
||||
<line x1="480" y1="88" x2="480" y2="116" stroke="#504e49" stroke-width="1.2"/>
|
||||
<line x1="248" y1="116" x2="712" y2="116" stroke="#504e49" stroke-width="1.2"/>
|
||||
<line x1="248" y1="116" x2="248" y2="152" stroke="#504e49" stroke-width="1.2"/>
|
||||
<line x1="712" y1="116" x2="712" y2="152" stroke="#1B365D" stroke-width="1.4"/>
|
||||
|
||||
<!-- Chevrons at C1 and C2 tops -->
|
||||
<path d="M 243,148 L 248,152 L 253,148"
|
||||
fill="none" stroke="#504e49" stroke-width="1.5" stroke-linecap="round"/>
|
||||
<path d="M 707,148 L 712,152 L 717,148"
|
||||
fill="none" stroke="#1B365D" stroke-width="1.5" stroke-linecap="round"/>
|
||||
|
||||
<!-- C1 branch → junction at y=244, then to L1(x=112) and L2(x=268) -->
|
||||
<line x1="248" y1="200" x2="248" y2="244" stroke="#504e49" stroke-width="1.2"/>
|
||||
<line x1="112" y1="244" x2="268" y2="244" stroke="#504e49" stroke-width="1.2"/>
|
||||
<line x1="112" y1="244" x2="112" y2="280" stroke="#504e49" stroke-width="1.2"/>
|
||||
<line x1="268" y1="244" x2="268" y2="280" stroke="#504e49" stroke-width="1.2"/>
|
||||
|
||||
<!-- Chevrons at L1 and L2 tops -->
|
||||
<path d="M 107,276 L 112,280 L 117,276"
|
||||
fill="none" stroke="#504e49" stroke-width="1.5" stroke-linecap="round"/>
|
||||
<path d="M 263,276 L 268,280 L 273,276"
|
||||
fill="none" stroke="#504e49" stroke-width="1.5" stroke-linecap="round"/>
|
||||
|
||||
<!-- C2 branch → junction at y=244, then to L3(x=552), L4(x=712), L5(x=872) -->
|
||||
<line x1="712" y1="200" x2="712" y2="244" stroke="#1B365D" stroke-width="1.4"/>
|
||||
<line x1="552" y1="244" x2="872" y2="244" stroke="#1B365D" stroke-width="1.4"/>
|
||||
<line x1="552" y1="244" x2="552" y2="280" stroke="#1B365D" stroke-width="1.4"/>
|
||||
<line x1="712" y1="244" x2="712" y2="280" stroke="#1B365D" stroke-width="1.4"/>
|
||||
<line x1="872" y1="244" x2="872" y2="280" stroke="#1B365D" stroke-width="1.4"/>
|
||||
|
||||
<!-- Chevrons at L3, L4, L5 tops -->
|
||||
<path d="M 547,276 L 552,280 L 557,276"
|
||||
fill="none" stroke="#1B365D" stroke-width="1.5" stroke-linecap="round"/>
|
||||
<path d="M 707,276 L 712,280 L 717,276"
|
||||
fill="none" stroke="#1B365D" stroke-width="1.5" stroke-linecap="round"/>
|
||||
<path d="M 867,276 L 872,280 L 877,276"
|
||||
fill="none" stroke="#1B365D" stroke-width="1.5" stroke-linecap="round"/>
|
||||
|
||||
<!-- ── Root node ── -->
|
||||
<rect x="408" y="40" width="144" height="48" rx="6" fill="#f5f4ed"/>
|
||||
<rect x="408" y="40" width="144" height="48" rx="6"
|
||||
fill="#faf9f5" stroke="#141413" stroke-width="1"/>
|
||||
<text x="430" y="56" fill="#141413" font-size="7" font-family="'JetBrains Mono', monospace"
|
||||
letter-spacing="0.15em">ROOT</text>
|
||||
<text x="480" y="72" fill="#141413" font-size="12" font-weight="500"
|
||||
font-family="inherit" text-anchor="middle">{{System}}</text>
|
||||
|
||||
<!-- ── Child nodes ── -->
|
||||
|
||||
<!-- C1: left branch -->
|
||||
<rect x="176" y="152" width="144" height="48" rx="6" fill="#f5f4ed"/>
|
||||
<rect x="176" y="152" width="144" height="48" rx="6"
|
||||
fill="#E9E8E1" stroke="#6b6a64" stroke-width="1"/>
|
||||
<text x="198" y="168" fill="#6b6a64" font-size="7" font-family="'JetBrains Mono', monospace"
|
||||
letter-spacing="0.15em">MODULE</text>
|
||||
<text x="248" y="184" fill="#141413" font-size="12" font-weight="500"
|
||||
font-family="inherit" text-anchor="middle">{{Module A}}</text>
|
||||
|
||||
<!-- C2: right branch (FOCAL) -->
|
||||
<rect x="640" y="152" width="144" height="48" rx="6" fill="#f5f4ed"/>
|
||||
<rect x="640" y="152" width="144" height="48" rx="6"
|
||||
fill="#EEF2F7" stroke="#1B365D" stroke-width="1"/>
|
||||
<text x="662" y="168" fill="#1B365D" font-size="7" font-family="'JetBrains Mono', monospace"
|
||||
letter-spacing="0.15em">FOCAL</text>
|
||||
<text x="712" y="184" fill="#141413" font-size="12" font-weight="500"
|
||||
font-family="inherit" text-anchor="middle">{{Module B}}</text>
|
||||
|
||||
<!-- ── Leaf nodes (C1 branch) ── -->
|
||||
|
||||
<!-- L1 -->
|
||||
<rect x="48" y="280" width="128" height="48" rx="6" fill="#f5f4ed"/>
|
||||
<rect x="48" y="280" width="128" height="48" rx="6"
|
||||
fill="#E9E8E1" stroke="#6b6a64" stroke-width="1"/>
|
||||
<text x="112" y="300" fill="#141413" font-size="11" font-weight="500"
|
||||
font-family="inherit" text-anchor="middle">{{Leaf 1}}</text>
|
||||
<text x="112" y="316" fill="#504e49" font-size="9" font-family="'JetBrains Mono', monospace"
|
||||
text-anchor="middle">service</text>
|
||||
|
||||
<!-- L2 -->
|
||||
<rect x="204" y="280" width="128" height="48" rx="6" fill="#f5f4ed"/>
|
||||
<rect x="204" y="280" width="128" height="48" rx="6"
|
||||
fill="#E9E8E1" stroke="#6b6a64" stroke-width="1"/>
|
||||
<text x="268" y="300" fill="#141413" font-size="11" font-weight="500"
|
||||
font-family="inherit" text-anchor="middle">{{Leaf 2}}</text>
|
||||
<text x="268" y="316" fill="#504e49" font-size="9" font-family="'JetBrains Mono', monospace"
|
||||
text-anchor="middle">service</text>
|
||||
|
||||
<!-- ── Leaf nodes (C2 branch, focal) ── -->
|
||||
|
||||
<!-- L3 -->
|
||||
<rect x="488" y="280" width="128" height="48" rx="6" fill="#f5f4ed"/>
|
||||
<rect x="488" y="280" width="128" height="48" rx="6"
|
||||
fill="#EEF2F7" stroke="#1B365D" stroke-width="0.8"/>
|
||||
<text x="552" y="300" fill="#141413" font-size="11" font-weight="500"
|
||||
font-family="inherit" text-anchor="middle">{{Leaf 3}}</text>
|
||||
<text x="552" y="316" fill="#1B365D" font-size="9" font-family="'JetBrains Mono', monospace"
|
||||
text-anchor="middle">component</text>
|
||||
|
||||
<!-- L4 -->
|
||||
<rect x="648" y="280" width="128" height="48" rx="6" fill="#f5f4ed"/>
|
||||
<rect x="648" y="280" width="128" height="48" rx="6"
|
||||
fill="#EEF2F7" stroke="#1B365D" stroke-width="0.8"/>
|
||||
<text x="712" y="300" fill="#141413" font-size="11" font-weight="500"
|
||||
font-family="inherit" text-anchor="middle">{{Leaf 4}}</text>
|
||||
<text x="712" y="316" fill="#1B365D" font-size="9" font-family="'JetBrains Mono', monospace"
|
||||
text-anchor="middle">component</text>
|
||||
|
||||
<!-- L5 -->
|
||||
<rect x="808" y="280" width="128" height="48" rx="6" fill="#f5f4ed"/>
|
||||
<rect x="808" y="280" width="128" height="48" rx="6"
|
||||
fill="#EEF2F7" stroke="#1B365D" stroke-width="0.8"/>
|
||||
<text x="872" y="300" fill="#141413" font-size="11" font-weight="500"
|
||||
font-family="inherit" text-anchor="middle">{{Leaf 5}}</text>
|
||||
<text x="872" y="316" fill="#1B365D" font-size="9" font-family="'JetBrains Mono', monospace"
|
||||
text-anchor="middle">component</text>
|
||||
|
||||
<!-- ── Legend ── -->
|
||||
<line x1="40" y1="376" x2="920" y2="376" stroke="#DEDED7" stroke-width="0.8"/>
|
||||
<text x="40" y="392" fill="#504e49" font-size="8" font-family="'JetBrains Mono', monospace"
|
||||
letter-spacing="0.22em">LEGEND</text>
|
||||
|
||||
<rect x="40" y="406" width="14" height="10" rx="2" fill="#EEF2F7" stroke="#1B365D" stroke-width="1"/>
|
||||
<text x="60" y="414" fill="#504e49" font-size="8.5" font-family="inherit">Focal sub-tree</text>
|
||||
|
||||
<rect x="190" y="406" width="14" height="10" rx="2" fill="#faf9f5" stroke="#141413" stroke-width="1"/>
|
||||
<text x="210" y="414" fill="#504e49" font-size="8.5" font-family="inherit">Root</text>
|
||||
|
||||
<rect x="280" y="406" width="14" height="10" rx="2" fill="#E9E8E1" stroke="#6b6a64" stroke-width="1"/>
|
||||
<text x="300" y="414" fill="#504e49" font-size="8.5" font-family="inherit">Secondary branch</text>
|
||||
</svg>
|
||||
|
||||
<p class="caption">Focal rule: one sub-tree in ink-blue marks the branch where complexity lives, or the path the reader needs to understand first. Orthogonal elbows make parent-child relationships unambiguous.</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,154 @@
|
||||
<!DOCTYPE html>
|
||||
<!-- ==================================================================
|
||||
DIAGRAM · Venn (kami palette)
|
||||
Two or three overlapping circles showing set relationships.
|
||||
Focal circle in ink-blue tint; intersection label in serif callout.
|
||||
================================================================== -->
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Venn · kami diagram</title>
|
||||
<style>
|
||||
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
|
||||
:root {
|
||||
--parchment: #f5f4ed;
|
||||
--ivory: #faf9f5;
|
||||
--near-black: #141413;
|
||||
--olive: #504e49;
|
||||
--stone: #6b6a64;
|
||||
--brand: #1B365D;
|
||||
--brand-tint: #EEF2F7;
|
||||
--border: #e8e6dc;
|
||||
|
||||
--serif: Charter, Georgia, Palatino, serif;
|
||||
--sans: var(--serif);
|
||||
--mono: "JetBrains Mono", "SF Mono", Consolas, monospace;
|
||||
}
|
||||
|
||||
/* Poster-size page so WeasyPrint exports the full diagram without
|
||||
A4 clipping; browsers ignore @page. */
|
||||
@page { size: 1080px 880px; margin: 0; }
|
||||
|
||||
body {
|
||||
font-family: var(--sans);
|
||||
background: var(--parchment);
|
||||
color: var(--near-black);
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 3rem 2rem;
|
||||
}
|
||||
|
||||
.frame { max-width: 1000px; width: 100%; }
|
||||
|
||||
.eyebrow {
|
||||
font-family: var(--mono);
|
||||
font-size: 0.7rem;
|
||||
letter-spacing: 0.22em;
|
||||
text-transform: uppercase;
|
||||
color: var(--stone);
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-family: var(--serif);
|
||||
font-size: 1.9rem;
|
||||
font-weight: 500;
|
||||
letter-spacing: -0.3pt;
|
||||
line-height: 1.15;
|
||||
margin-bottom: 1.6rem;
|
||||
}
|
||||
|
||||
svg { width: 100%; min-width: 860px; display: block; }
|
||||
|
||||
.caption {
|
||||
font-family: var(--serif);
|
||||
font-size: 0.95rem;
|
||||
color: var(--olive);
|
||||
margin-top: 1rem;
|
||||
max-width: 52ch;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="frame">
|
||||
<p class="eyebrow">Venn · kami diagram</p>
|
||||
<h1>{{Topic A}} and {{Topic B}} intersection</h1>
|
||||
|
||||
<svg viewBox="0 0 960 460" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<pattern id="dots" width="22" height="22" patternUnits="userSpaceOnUse">
|
||||
<circle cx="1" cy="1" r="0.9" fill="#E3E2DC"/>
|
||||
</pattern>
|
||||
</defs>
|
||||
|
||||
<rect width="100%" height="100%" fill="#f5f4ed"/>
|
||||
<rect width="100%" height="100%" fill="url(#dots)" opacity="0.55"/>
|
||||
|
||||
<!-- ── Venn circles ── -->
|
||||
<!-- Circle A (FOCAL): cx=376 cy=208 r=152 -->
|
||||
<circle cx="376" cy="208" r="152"
|
||||
fill="#EEF2F7" stroke="#1B365D" stroke-width="1.2"/>
|
||||
|
||||
<!-- Circle B: cx=584 cy=208 r=152 -->
|
||||
<circle cx="584" cy="208" r="152"
|
||||
fill="#EAE9E3" stroke="#504e49" stroke-width="1.2"/>
|
||||
|
||||
<!-- ── Zone labels ── -->
|
||||
|
||||
<!-- A-only zone (cx≈264) -->
|
||||
<text x="264" y="196" fill="#141413" font-size="12" font-weight="500"
|
||||
font-family="inherit" text-anchor="middle">{{Set A only}}</text>
|
||||
<text x="264" y="214" fill="#504e49" font-size="9" font-family="'JetBrains Mono', monospace"
|
||||
text-anchor="middle">unique to A</text>
|
||||
|
||||
<!-- Intersection zone (cx=480), focal callout -->
|
||||
<text x="480" y="198" fill="#1B365D" font-size="13" font-weight="500"
|
||||
font-family="inherit"
|
||||
text-anchor="middle">{{Shared}}</text>
|
||||
<text x="480" y="218" fill="#1B365D" font-size="9" font-family="'JetBrains Mono', monospace"
|
||||
text-anchor="middle">overlap</text>
|
||||
|
||||
<!-- B-only zone (cx≈696) -->
|
||||
<text x="696" y="196" fill="#141413" font-size="12" font-weight="500"
|
||||
font-family="inherit" text-anchor="middle">{{Set B only}}</text>
|
||||
<text x="696" y="214" fill="#504e49" font-size="9" font-family="'JetBrains Mono', monospace"
|
||||
text-anchor="middle">unique to B</text>
|
||||
|
||||
<!-- ── Circle labels (top of each circle) ── -->
|
||||
<text x="296" y="88" fill="#1B365D" font-size="10" font-weight="500"
|
||||
font-family="'JetBrains Mono', monospace" text-anchor="middle"
|
||||
letter-spacing="0.1em">SET A</text>
|
||||
<text x="664" y="88" fill="#504e49" font-size="10" font-weight="500"
|
||||
font-family="'JetBrains Mono', monospace" text-anchor="middle"
|
||||
letter-spacing="0.1em">SET B</text>
|
||||
|
||||
<!-- ── Example label rows (bottom of circles) ── -->
|
||||
<text x="264" y="292" fill="#6b6a64" font-size="9" font-family="inherit"
|
||||
text-anchor="middle">item · item · item</text>
|
||||
<text x="480" y="292" fill="#6b6a64" font-size="9" font-family="inherit"
|
||||
text-anchor="middle">item · item</text>
|
||||
<text x="696" y="292" fill="#6b6a64" font-size="9" font-family="inherit"
|
||||
text-anchor="middle">item · item · item</text>
|
||||
|
||||
<!-- ── Legend ── -->
|
||||
<line x1="40" y1="372" x2="920" y2="372" stroke="#DEDED7" stroke-width="0.8"/>
|
||||
<text x="40" y="388" fill="#504e49" font-size="8" font-family="'JetBrains Mono', monospace"
|
||||
letter-spacing="0.22em">LEGEND</text>
|
||||
|
||||
<circle cx="47" cy="406" r="12" fill="#EEF2F7" stroke="#1B365D" stroke-width="1"/>
|
||||
<text x="66" y="410" fill="#504e49" font-size="8.5" font-family="inherit">Focal set (A)</text>
|
||||
|
||||
<circle cx="199" cy="406" r="12" fill="#EAE9E3" stroke="#504e49" stroke-width="1"/>
|
||||
<text x="218" y="410" fill="#504e49" font-size="8.5" font-family="inherit">Secondary set (B)</text>
|
||||
|
||||
<text x="375" y="402" fill="#1B365D" font-size="11" font-family="inherit">Overlap</text>
|
||||
<text x="435" y="410" fill="#504e49" font-size="8.5" font-family="inherit">Intersection label (serif callout)</text>
|
||||
</svg>
|
||||
|
||||
<p class="caption">Focal rule: the circle whose contents matter most to the argument gets the ink-blue stroke. The intersection label uses a serif italic callout to draw the eye to the overlap, which is usually the point of the diagram.</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,192 @@
|
||||
<!DOCTYPE html>
|
||||
<!-- ==================================================================
|
||||
DIAGRAM · Waterfall Chart (kami palette)
|
||||
Revenue bridge / valuation decomposition / cash flow breakdown.
|
||||
Start value, positive/negative segments, end value.
|
||||
DATA START / DATA END marks the region Claude replaces when filling.
|
||||
================================================================== -->
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Waterfall Chart · kami diagram</title>
|
||||
<style>
|
||||
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
|
||||
:root {
|
||||
--parchment: #f5f4ed;
|
||||
--ivory: #faf9f5;
|
||||
--near-black: #141413;
|
||||
--olive: #504e49;
|
||||
--stone: #6b6a64;
|
||||
--brand: #1B365D;
|
||||
--brand-tint: #EEF2F7;
|
||||
--border: #e8e6dc;
|
||||
--positive: #1B365D;
|
||||
--negative: #6b6a64;
|
||||
--total: #4d4c48;
|
||||
|
||||
--serif: Charter, Georgia, Palatino, serif;
|
||||
--sans: var(--serif);
|
||||
--mono: "JetBrains Mono", "SF Mono", Consolas, "TsangerJinKai02", "Source Han Serif SC", "Noto Serif CJK SC", "Songti SC", monospace;
|
||||
}
|
||||
|
||||
/* Poster-size page so WeasyPrint exports the full diagram without
|
||||
A4 clipping; browsers ignore @page. */
|
||||
@page { size: 1000px 940px; margin: 0; }
|
||||
|
||||
body {
|
||||
font-family: var(--sans);
|
||||
background: var(--parchment);
|
||||
color: var(--near-black);
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 3rem 2rem;
|
||||
}
|
||||
|
||||
.frame { max-width: 920px; width: 100%; }
|
||||
|
||||
.eyebrow {
|
||||
font-family: var(--mono);
|
||||
font-size: 0.7rem;
|
||||
letter-spacing: 0.22em;
|
||||
text-transform: uppercase;
|
||||
color: var(--stone);
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-family: var(--serif);
|
||||
font-size: 1.9rem;
|
||||
font-weight: 500;
|
||||
letter-spacing: -0.3pt;
|
||||
line-height: 1.15;
|
||||
margin-bottom: 1.6rem;
|
||||
}
|
||||
|
||||
svg { width: 100%; min-width: 640px; display: block; }
|
||||
|
||||
.caption {
|
||||
font-family: var(--serif);
|
||||
font-size: 0.95rem;
|
||||
color: var(--olive);
|
||||
margin-top: 1rem;
|
||||
max-width: 52ch;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="frame">
|
||||
<p class="eyebrow">Waterfall · kami diagram</p>
|
||||
<h1>{{Chart Title}}</h1>
|
||||
|
||||
<!-- ============================================================
|
||||
DATA START
|
||||
Chart area: x=60 y=40 w=600 h=280 · base y=320
|
||||
viewBox: 0 0 720 420
|
||||
Bars: 7 (start + 4 changes + subtotal + end)
|
||||
bar-width=64 · gap=20 · bar-spacing=84
|
||||
Scale: max=200 · 1 unit = 1.4px (280/200)
|
||||
Y-axis: 0 / 40 / 80 / 120 / 160 / 200
|
||||
Positive: #1B365D · Negative: #6b6a64 · Total: #4d4c48
|
||||
Connector lines: dashed 0.8px #b8b7b0
|
||||
============================================================ -->
|
||||
<svg viewBox="0 0 720 420" xmlns="http://www.w3.org/2000/svg">
|
||||
|
||||
<!-- Canvas -->
|
||||
<rect width="100%" height="100%" fill="#f5f4ed"/>
|
||||
|
||||
<!-- Horizontal grid lines (y: 320=0, 264=40, 208=80, 152=120, 96=160, 40=200) -->
|
||||
<line x1="60" y1="320" x2="660" y2="320" stroke="#e8e7e1" stroke-width="0.8"/>
|
||||
<line x1="60" y1="264" x2="660" y2="264" stroke="#e8e7e1" stroke-width="0.8"/>
|
||||
<line x1="60" y1="208" x2="660" y2="208" stroke="#e8e7e1" stroke-width="0.8"/>
|
||||
<line x1="60" y1="152" x2="660" y2="152" stroke="#e8e7e1" stroke-width="0.8"/>
|
||||
<line x1="60" y1="96" x2="660" y2="96" stroke="#e8e7e1" stroke-width="0.8"/>
|
||||
<line x1="60" y1="40" x2="660" y2="40" stroke="#e8e7e1" stroke-width="0.8"/>
|
||||
|
||||
<!-- Y-axis labels -->
|
||||
<text x="52" y="324" fill="#6b6a64" font-size="10" text-anchor="end" font-family="Charter, Georgia, serif">0</text>
|
||||
<text x="52" y="268" fill="#6b6a64" font-size="10" text-anchor="end" font-family="Charter, Georgia, serif">40</text>
|
||||
<text x="52" y="212" fill="#6b6a64" font-size="10" text-anchor="end" font-family="Charter, Georgia, serif">80</text>
|
||||
<text x="52" y="156" fill="#6b6a64" font-size="10" text-anchor="end" font-family="Charter, Georgia, serif">120</text>
|
||||
<text x="52" y="100" fill="#6b6a64" font-size="10" text-anchor="end" font-family="Charter, Georgia, serif">160</text>
|
||||
<text x="52" y="44" fill="#6b6a64" font-size="10" text-anchor="end" font-family="Charter, Georgia, serif">200</text>
|
||||
|
||||
<!-- Bar 1: Start value = 120 (total bar, from baseline to 120) -->
|
||||
<!-- y = 320 - 120*1.4 = 152, h = 168 -->
|
||||
<rect x="68" y="152" width="64" height="168" fill="#4d4c48" rx="2"/>
|
||||
<text x="100" y="144" fill="#141413" font-size="11" font-weight="500" text-anchor="middle" font-family="Charter, Georgia, serif">120</text>
|
||||
|
||||
<!-- Connector: bar1 top to bar2 -->
|
||||
<line x1="132" y1="152" x2="152" y2="152" stroke="#b8b7b0" stroke-width="0.8" stroke-dasharray="3,2"/>
|
||||
|
||||
<!-- Bar 2: +30 (positive, floats from 120 to 150) -->
|
||||
<!-- bottom y = 152, top y = 152 - 30*1.4 = 110, h = 42 -->
|
||||
<rect x="152" y="110" width="64" height="42" fill="#1B365D" rx="2"/>
|
||||
<text x="184" y="102" fill="#141413" font-size="11" font-weight="500" text-anchor="middle" font-family="Charter, Georgia, serif">+30</text>
|
||||
|
||||
<!-- Connector: bar2 top to bar3 -->
|
||||
<line x1="216" y1="110" x2="236" y2="110" stroke="#b8b7b0" stroke-width="0.8" stroke-dasharray="3,2"/>
|
||||
|
||||
<!-- Bar 3: +20 (positive, floats from 150 to 170) -->
|
||||
<!-- bottom y = 110, top y = 110 - 20*1.4 = 82, h = 28 -->
|
||||
<rect x="236" y="82" width="64" height="28" fill="#1B365D" rx="2"/>
|
||||
<text x="268" y="74" fill="#141413" font-size="11" font-weight="500" text-anchor="middle" font-family="Charter, Georgia, serif">+20</text>
|
||||
|
||||
<!-- Connector: bar3 top to bar4 -->
|
||||
<line x1="300" y1="82" x2="320" y2="82" stroke="#b8b7b0" stroke-width="0.8" stroke-dasharray="3,2"/>
|
||||
|
||||
<!-- Bar 4: -40 (negative, floats from 170 down to 130) -->
|
||||
<!-- top y = 82, bottom y = 82 + 40*1.4 = 138, h = 56 -->
|
||||
<rect x="320" y="82" width="64" height="56" fill="#6b6a64" rx="2"/>
|
||||
<text x="352" y="148" fill="#141413" font-size="11" font-weight="500" text-anchor="middle" font-family="Charter, Georgia, serif">-40</text>
|
||||
|
||||
<!-- Connector: bar4 bottom to bar5 -->
|
||||
<line x1="384" y1="138" x2="404" y2="138" stroke="#b8b7b0" stroke-width="0.8" stroke-dasharray="3,2"/>
|
||||
|
||||
<!-- Bar 5: +10 (positive, floats from 130 to 140) -->
|
||||
<!-- bottom y = 138, top y = 138 - 10*1.4 = 124, h = 14 -->
|
||||
<rect x="404" y="124" width="64" height="14" fill="#1B365D" rx="2"/>
|
||||
<text x="436" y="116" fill="#141413" font-size="11" font-weight="500" text-anchor="middle" font-family="Charter, Georgia, serif">+10</text>
|
||||
|
||||
<!-- Connector: bar5 top to bar6 -->
|
||||
<line x1="468" y1="124" x2="488" y2="124" stroke="#b8b7b0" stroke-width="0.8" stroke-dasharray="3,2"/>
|
||||
|
||||
<!-- Bar 6: -20 (negative, floats from 140 down to 120) -->
|
||||
<!-- top y = 124, bottom y = 124 + 20*1.4 = 152, h = 28 -->
|
||||
<rect x="488" y="124" width="64" height="28" fill="#6b6a64" rx="2"/>
|
||||
<text x="520" y="162" fill="#141413" font-size="11" font-weight="500" text-anchor="middle" font-family="Charter, Georgia, serif">-20</text>
|
||||
|
||||
<!-- Bar 7: End value = 120 (total bar, from baseline to 120) -->
|
||||
<rect x="588" y="152" width="64" height="168" fill="#4d4c48" rx="2"/>
|
||||
<text x="620" y="144" fill="#141413" font-size="11" font-weight="500" text-anchor="middle" font-family="Charter, Georgia, serif">120</text>
|
||||
<!-- DATA END -->
|
||||
|
||||
<!-- X-axis category labels -->
|
||||
<text x="100" y="340" fill="#141413" font-size="10" text-anchor="middle" font-family="Charter, Georgia, serif">{{Start}}</text>
|
||||
<text x="184" y="340" fill="#141413" font-size="10" text-anchor="middle" font-family="Charter, Georgia, serif">{{Cat A}}</text>
|
||||
<text x="268" y="340" fill="#141413" font-size="10" text-anchor="middle" font-family="Charter, Georgia, serif">{{Cat B}}</text>
|
||||
<text x="352" y="340" fill="#141413" font-size="10" text-anchor="middle" font-family="Charter, Georgia, serif">{{Cat C}}</text>
|
||||
<text x="436" y="340" fill="#141413" font-size="10" text-anchor="middle" font-family="Charter, Georgia, serif">{{Cat D}}</text>
|
||||
<text x="520" y="340" fill="#141413" font-size="10" text-anchor="middle" font-family="Charter, Georgia, serif">{{Cat E}}</text>
|
||||
<text x="620" y="340" fill="#141413" font-size="10" text-anchor="middle" font-family="Charter, Georgia, serif">{{End}}</text>
|
||||
|
||||
<!-- Legend -->
|
||||
<line x1="60" y1="368" x2="660" y2="368" stroke="#e8e7e1" stroke-width="0.8"/>
|
||||
|
||||
<rect x="60" y="380" width="12" height="12" fill="#1B365D" rx="2"/>
|
||||
<text x="78" y="391" fill="#504e49" font-size="10" font-family="Charter, Georgia, serif">Increase</text>
|
||||
|
||||
<rect x="180" y="380" width="12" height="12" fill="#6b6a64" rx="2"/>
|
||||
<text x="198" y="391" fill="#504e49" font-size="10" font-family="Charter, Georgia, serif">Decrease</text>
|
||||
|
||||
<rect x="300" y="380" width="12" height="12" fill="#4d4c48" rx="2"/>
|
||||
<text x="318" y="391" fill="#504e49" font-size="10" font-family="Charter, Georgia, serif">Total</text>
|
||||
|
||||
</svg>
|
||||
|
||||
<p class="caption">{{Caption: e.g. "Revenue bridge from FY2024 to FY2025, showing growth drivers and headwinds."}}</p>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,107 @@
|
||||
Source Han Serif K (also distributed as Noto Serif KR)
|
||||
Copyright 2017-2022 Adobe (http://www.adobe.com/), with Reserved Font
|
||||
Name 'Source'. Source is a trademark of Adobe in the United States
|
||||
and/or other countries.
|
||||
|
||||
This Font Software is licensed under the SIL Open Font License,
|
||||
Version 1.1.
|
||||
|
||||
Source:
|
||||
https://github.com/adobe-fonts/source-han-serif (Adobe official)
|
||||
https://fonts.google.com/noto/specimen/Noto+Serif+KR (Google Fonts mirror)
|
||||
|
||||
The Adobe and Google distributions are the same font under two names.
|
||||
Kami's templates reference three names in the `--serif` chain
|
||||
("Source Han Serif K", "Source Han Serif KR", "Noto Serif KR"):
|
||||
"Source Han Serif K" is the `@font-face` declared alias (loads via the
|
||||
bundled file or CDN), "Source Han Serif KR" is the actual family name
|
||||
inside the OTFs (so fontconfig resolves the ensure-fonts.sh download by
|
||||
name on an offline Linux box), and "Noto Serif KR" covers the Google
|
||||
Fonts install. The font therefore resolves regardless of source.
|
||||
|
||||
-----------------------------------------------------------
|
||||
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||
-----------------------------------------------------------
|
||||
|
||||
PREAMBLE
|
||||
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||
development of collaborative font projects, to support the font
|
||||
creation efforts of academic and linguistic communities, and to
|
||||
provide a free and open framework in which fonts may be shared and
|
||||
improved in partnership with others.
|
||||
|
||||
The OFL allows the licensed fonts to be used, studied, modified and
|
||||
redistributed freely as long as they are not sold by themselves. The
|
||||
fonts, including any derivative works, can be bundled, embedded,
|
||||
redistributed and/or sold with any software provided that any reserved
|
||||
names are not used by derivative works. The fonts and derivatives,
|
||||
however, cannot be released under any other type of license. The
|
||||
requirement for fonts to remain under this license does not apply to
|
||||
any document created using the fonts or their derivatives.
|
||||
|
||||
DEFINITIONS
|
||||
"Font Software" refers to the set of files released by the Copyright
|
||||
Holder(s) under this license and clearly marked as such. This may
|
||||
include source files, build scripts and documentation.
|
||||
|
||||
"Reserved Font Name" refers to any names specified as such after the
|
||||
copyright statement(s).
|
||||
|
||||
"Original Version" refers to the collection of Font Software
|
||||
components as distributed by the Copyright Holder(s).
|
||||
|
||||
"Modified Version" refers to any derivative made by adding to,
|
||||
deleting, or substituting -- in part or in whole -- any of the
|
||||
components of the Original Version, by changing formats or by porting
|
||||
the Font Software to a new environment.
|
||||
|
||||
"Author" refers to any designer, engineer, programmer, technical
|
||||
writer or other person who contributed to the Font Software.
|
||||
|
||||
PERMISSION & CONDITIONS
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of the Font Software, to use, study, copy, merge, embed,
|
||||
modify, redistribute, and sell modified and unmodified copies of the
|
||||
Font Software, subject to the following conditions:
|
||||
|
||||
1) Neither the Font Software nor any of its individual components, in
|
||||
Original or Modified Versions, may be sold by itself.
|
||||
|
||||
2) Original or Modified Versions of the Font Software may be bundled,
|
||||
redistributed and/or sold with any software, provided that each copy
|
||||
contains the above copyright notice and this license. These can be
|
||||
included either as stand-alone text files, human-readable headers or
|
||||
in the appropriate machine-readable metadata fields within text or
|
||||
binary files as long as those fields can be easily viewed by the user.
|
||||
|
||||
3) No Modified Version of the Font Software may use the Reserved Font
|
||||
Name(s) unless explicit written permission is granted by the
|
||||
corresponding Copyright Holder. This restriction only applies to the
|
||||
primary font name as presented to the users.
|
||||
|
||||
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
||||
Software shall not be used to promote, endorse or advertise any
|
||||
Modified Version, except to acknowledge the contribution(s) of the
|
||||
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||
permission.
|
||||
|
||||
5) The Font Software, modified or unmodified, in part or in whole,
|
||||
must be distributed entirely under this license, and must not be
|
||||
distributed under any other license. The requirement for fonts to
|
||||
remain under this license does not apply to any document created using
|
||||
the Font Software.
|
||||
|
||||
TERMINATION
|
||||
This license becomes null and void if any of the above conditions are
|
||||
not met.
|
||||
|
||||
DISCLAIMER
|
||||
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||
OTHER DEALINGS IN THE FONT SOFTWARE.
|
||||
|
After Width: | Height: | Size: 2.0 MiB |
|
After Width: | Height: | Size: 1.8 MiB |
|
After Width: | Height: | Size: 1.8 MiB |
|
After Width: | Height: | Size: 8.7 KiB |
|
After Width: | Height: | Size: 2.5 MiB |
|
After Width: | Height: | Size: 2.3 MiB |
|
After Width: | Height: | Size: 2.9 MiB |
@@ -0,0 +1,249 @@
|
||||
<!DOCTYPE html>
|
||||
<!-- ==================================================================
|
||||
CHANGELOG · English · parchment design system
|
||||
1-2 page A4 release notes / version changelog
|
||||
Structure: version header -> Breaking -> Features -> Fixes -> Thanks
|
||||
================================================================== -->
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>{{PROJECT_NAME}} · {{VERSION}} Release Notes</title>
|
||||
<meta name="author" content="{{AUTHOR}}">
|
||||
<meta name="description" content="{{DESCRIPTION}}">
|
||||
<meta name="keywords" content="{{KEYWORDS}}">
|
||||
<meta name="generator" content="Kami">
|
||||
<style>
|
||||
@font-face { font-family: "JetBrains Mono"; src: url("../fonts/JetBrainsMono.woff2") format("woff2"); font-weight: 400; font-style: normal; }
|
||||
@font-face { font-family: "JetBrains Mono"; src: url("../fonts/JetBrainsMono.woff2") format("woff2"); font-weight: 500; font-style: normal; }
|
||||
|
||||
@page {
|
||||
size: A4;
|
||||
margin: 20mm 22mm 22mm 22mm;
|
||||
background: #f5f4ed;
|
||||
}
|
||||
|
||||
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
|
||||
:root {
|
||||
--parchment: #f5f4ed;
|
||||
--ivory: #faf9f5;
|
||||
--near-black:#141413;
|
||||
--dark-warm: #3d3d3a;
|
||||
--olive: #504e49;
|
||||
--stone: #6b6a64;
|
||||
--brand: #1B365D;
|
||||
--border: #e8e6dc;
|
||||
--border-soft:#e5e3d8;
|
||||
--tag-bg: #E4ECF5;
|
||||
--breaking-bg:#f0e0d8;
|
||||
--breaking-fg:#8b4513;
|
||||
|
||||
--serif: Charter, Georgia,
|
||||
Palatino, "Times New Roman", serif;
|
||||
--sans: var(--serif);
|
||||
--mono: "JetBrains Mono", "SF Mono", "Fira Code",
|
||||
Consolas, Monaco, monospace;
|
||||
}
|
||||
|
||||
html, body { background: var(--parchment); }
|
||||
|
||||
@media screen {
|
||||
body { max-width: 210mm; margin: 0 auto; padding: 20mm 22mm; }
|
||||
}
|
||||
|
||||
body {
|
||||
color: var(--near-black);
|
||||
font-family: var(--serif);
|
||||
font-size: 10.5pt;
|
||||
line-height: 1.55;
|
||||
widows: 3;
|
||||
orphans: 3;
|
||||
}
|
||||
|
||||
strong { font-weight: 500; }
|
||||
.hl { color: var(--brand); font-weight: 500; }
|
||||
|
||||
/* HEADER - left-aligned editorial opener (matches the one-pager) */
|
||||
.header {
|
||||
margin-bottom: 20pt;
|
||||
padding-bottom: 13pt;
|
||||
border-bottom: 0.5pt solid var(--border);
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
justify-content: space-between;
|
||||
gap: 20pt;
|
||||
}
|
||||
.title-block { flex: 1; }
|
||||
.eyebrow {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 7pt;
|
||||
font-family: var(--sans);
|
||||
font-size: 9pt;
|
||||
color: var(--brand);
|
||||
letter-spacing: 1.5pt;
|
||||
text-transform: uppercase;
|
||||
font-weight: 500;
|
||||
margin-bottom: 8pt;
|
||||
}
|
||||
.eyebrow::before {
|
||||
content: "";
|
||||
width: 8pt;
|
||||
height: 1.5pt;
|
||||
border-radius: 0.75pt;
|
||||
background: var(--brand);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
h1 {
|
||||
font-family: var(--serif);
|
||||
font-size: 29pt;
|
||||
font-weight: 500;
|
||||
color: var(--near-black);
|
||||
line-height: 1.1;
|
||||
letter-spacing: -0.5pt;
|
||||
margin-bottom: 7pt;
|
||||
}
|
||||
.subtitle {
|
||||
font-size: 11.5pt;
|
||||
color: var(--olive);
|
||||
line-height: 1.45;
|
||||
max-width: 150mm;
|
||||
}
|
||||
.meta {
|
||||
font-size: 10pt;
|
||||
color: var(--stone);
|
||||
white-space: nowrap;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
/* SECTION HEAD - brand left bar */
|
||||
h2 {
|
||||
font-family: var(--serif);
|
||||
font-size: 14pt;
|
||||
font-weight: 500;
|
||||
color: var(--near-black);
|
||||
margin: 20pt 0 10pt 0;
|
||||
border-left: 2.5pt solid var(--brand);
|
||||
border-radius: 1.5pt;
|
||||
padding-left: 9pt;
|
||||
break-after: avoid;
|
||||
}
|
||||
h2:first-of-type { margin-top: 0; }
|
||||
|
||||
p { margin: 0 0 8pt 0; line-height: 1.55; widows: 2; orphans: 2; }
|
||||
code { font-family: var(--mono); font-size: 0.92em; }
|
||||
|
||||
/* CHANGE LIST - numbered, brand-colored markers, bold lead-in then detail */
|
||||
ol.changes { margin: 0; padding-left: 18pt; }
|
||||
ol.changes li {
|
||||
margin-bottom: 7pt;
|
||||
padding-left: 3pt;
|
||||
line-height: 1.55;
|
||||
break-inside: avoid;
|
||||
}
|
||||
ol.changes li:last-child { margin-bottom: 0; }
|
||||
ol.changes li::marker { color: var(--brand); font-weight: 500; }
|
||||
ol.changes li strong { font-weight: 500; color: var(--near-black); }
|
||||
ol.changes li .hl { color: var(--brand); font-weight: 500; }
|
||||
|
||||
/* TAG */
|
||||
.tag {
|
||||
display: inline-block;
|
||||
background: var(--tag-bg);
|
||||
color: var(--brand);
|
||||
font-family: var(--sans);
|
||||
font-size: 9pt;
|
||||
font-weight: 500;
|
||||
padding: 1pt 6pt;
|
||||
border-radius: 3pt;
|
||||
letter-spacing: 0.4pt;
|
||||
text-transform: uppercase;
|
||||
margin-right: 3pt;
|
||||
}
|
||||
.tag.breaking {
|
||||
background: var(--breaking-bg);
|
||||
color: var(--breaking-fg);
|
||||
}
|
||||
|
||||
/* ACKNOWLEDGEMENTS - a quiet labelled note, not a filled card, so the page
|
||||
stays flat. The small brand label is the only marker it needs. */
|
||||
.thanks {
|
||||
margin-top: 18pt;
|
||||
font-size: 9.5pt;
|
||||
line-height: 1.55;
|
||||
color: var(--olive);
|
||||
break-inside: avoid;
|
||||
}
|
||||
.thanks .hl { color: var(--brand); font-weight: 500; }
|
||||
.thanks-label {
|
||||
font-family: var(--sans);
|
||||
font-size: 9pt;
|
||||
color: var(--brand);
|
||||
letter-spacing: 1pt;
|
||||
text-transform: uppercase;
|
||||
font-weight: 500;
|
||||
margin-bottom: 4pt;
|
||||
}
|
||||
|
||||
/* FOOTER */
|
||||
.changelog-footer {
|
||||
margin-top: 20pt;
|
||||
padding-top: 8pt;
|
||||
border-top: 0.3pt dotted var(--border);
|
||||
font-size: 9pt;
|
||||
color: var(--stone);
|
||||
line-height: 1.5;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
font-variant-numeric: tabular-nums;
|
||||
letter-spacing: 0.3pt;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<!-- HEADER -->
|
||||
<div class="header">
|
||||
<div class="title-block">
|
||||
<div class="eyebrow">Release Notes</div>
|
||||
<h1>{{PROJECT_NAME}} {{VERSION}}</h1>
|
||||
<div class="subtitle">{{One-line release highlight.}}</div>
|
||||
</div>
|
||||
<div class="meta">{{RELEASE_DATE}}</div>
|
||||
</div>
|
||||
|
||||
<!-- Each entry: a bold lead-in, then a colon and the detail. Numbers carry
|
||||
sequence and restart per section. Drop any section that does not apply. -->
|
||||
<h2>Breaking Changes</h2>
|
||||
<ol class="changes">
|
||||
<li><span class="tag breaking">Breaking</span> <strong>{{What changed}}</strong>: {{what readers must do about it.}}</li>
|
||||
</ol>
|
||||
|
||||
<h2>Features</h2>
|
||||
<ol class="changes">
|
||||
<li><strong>{{Feature}}</strong>: {{one-line description.}}</li>
|
||||
<li><strong>{{Feature}}</strong>: {{one-line description.}}</li>
|
||||
<li><strong>{{Feature}}</strong>: {{one-line description.}}</li>
|
||||
</ol>
|
||||
|
||||
<h2>Fixes</h2>
|
||||
<ol class="changes">
|
||||
<li><strong>{{Area}}</strong>: {{what was fixed.}}</li>
|
||||
<li><strong>{{Area}}</strong>: {{what was fixed.}}</li>
|
||||
<li><strong>{{Area}}</strong>: {{what was fixed.}}</li>
|
||||
</ol>
|
||||
|
||||
<!-- ACKNOWLEDGEMENTS (optional) -->
|
||||
<div class="thanks">
|
||||
<div class="thanks-label">Acknowledgements</div>
|
||||
{{Thank contributors, e.g. "Thanks to @user1 and @user2 for their help."}}
|
||||
</div>
|
||||
|
||||
<!-- FOOTER -->
|
||||
<div class="changelog-footer">
|
||||
<span>{{One-line project description.}}</span>
|
||||
<span>{{PROJECT_URL}}</span>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,259 @@
|
||||
<!DOCTYPE html>
|
||||
<!-- ==================================================================
|
||||
CHANGELOG TEMPLATE (KO) · parchment design system
|
||||
1-2 페이지 A4 버전 업데이트 로그 / Release Notes
|
||||
구조: 버전 제목 -> Breaking -> Features -> Fixes -> 감사의 말
|
||||
변경 후 실행: python3 -c "from weasyprint import HTML; HTML('changelog-ko.html').write_pdf('out.pdf')"
|
||||
목표: 최대 2페이지
|
||||
================================================================== -->
|
||||
<html lang="ko">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>{{프로젝트명}} · {{버전}} 업데이트 로그</title>
|
||||
<meta name="author" content="{{작성자}}">
|
||||
<meta name="description" content="{{요약}}">
|
||||
<meta name="keywords" content="{{키워드}}">
|
||||
<meta name="generator" content="Kami">
|
||||
<style>
|
||||
/* Regular weight */
|
||||
@font-face {
|
||||
font-family: "Source Han Serif K";
|
||||
src: url("../fonts/SourceHanSerifKR-Regular.otf") format("opentype"),
|
||||
url("https://cdn.jsdelivr.net/gh/tw93/Kami@main/assets/fonts/SourceHanSerifKR-Regular.otf") format("opentype");
|
||||
font-weight: 400;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
/* Medium weight */
|
||||
@font-face {
|
||||
font-family: "Source Han Serif K";
|
||||
src: url("../fonts/SourceHanSerifKR-Medium.otf") format("opentype"),
|
||||
url("https://cdn.jsdelivr.net/gh/tw93/Kami@main/assets/fonts/SourceHanSerifKR-Medium.otf") format("opentype");
|
||||
font-weight: 500;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
@page {
|
||||
size: A4;
|
||||
margin: 20mm 22mm 22mm 22mm;
|
||||
background: #f5f4ed;
|
||||
}
|
||||
|
||||
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
|
||||
:root {
|
||||
--parchment: #f5f4ed;
|
||||
--ivory: #faf9f5;
|
||||
--near-black:#141413;
|
||||
--dark-warm: #3d3d3a;
|
||||
--olive: #504e49;
|
||||
--stone: #6b6a64;
|
||||
--brand: #1B365D;
|
||||
--border: #e8e6dc;
|
||||
--border-soft:#e5e3d8;
|
||||
--tag-bg: #E4ECF5;
|
||||
--breaking-bg:#f0e0d8;
|
||||
--breaking-fg:#8b4513;
|
||||
--serif: "Source Han Serif K", "Source Han Serif KR", "Noto Serif KR", "Apple SD Gothic Neo", "AppleMyungjo", Charter, Georgia, serif;
|
||||
--sans: var(--serif);
|
||||
--mono: "JetBrains Mono", "D2Coding", "SF Mono", "Fira Code", Consolas, Monaco, "Source Han Serif K", "Source Han Serif KR", monospace;
|
||||
}
|
||||
|
||||
html, body { background: var(--parchment); }
|
||||
|
||||
@media screen {
|
||||
body { max-width: 210mm; margin: 0 auto; padding: 20mm 22mm; }
|
||||
}
|
||||
|
||||
body {
|
||||
color: var(--near-black);
|
||||
font-family: var(--serif);
|
||||
font-size: 10.5pt;
|
||||
line-height: 1.55;
|
||||
letter-spacing: 0.3pt;
|
||||
widows: 3;
|
||||
orphans: 3;
|
||||
font-synthesis: none;
|
||||
}
|
||||
|
||||
strong { font-weight: 500; }
|
||||
.hl { color: var(--brand); font-weight: 500; }
|
||||
|
||||
/* ========== HEADER (left-aligned editorial, matches one-pager) ========== */
|
||||
.header {
|
||||
margin-bottom: 20pt;
|
||||
padding-bottom: 13pt;
|
||||
border-bottom: 0.5pt solid var(--border);
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
justify-content: space-between;
|
||||
gap: 20pt;
|
||||
}
|
||||
.title-block { flex: 1; }
|
||||
.eyebrow {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 7pt;
|
||||
font-family: var(--sans);
|
||||
font-size: 9pt;
|
||||
color: var(--brand);
|
||||
letter-spacing: 1.5pt;
|
||||
font-weight: 500;
|
||||
margin-bottom: 8pt;
|
||||
}
|
||||
.eyebrow::before {
|
||||
content: "";
|
||||
width: 8pt;
|
||||
height: 1.5pt;
|
||||
border-radius: 0.75pt;
|
||||
background: var(--brand);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
/* KO locale tuning: Source Han Serif K hangul reads optically smaller, so the
|
||||
title sits at the CN baseline (26pt / 1.15). See references/design.md. */
|
||||
h1 {
|
||||
font-family: var(--serif);
|
||||
font-size: 26pt;
|
||||
font-weight: 500;
|
||||
color: var(--near-black);
|
||||
line-height: 1.15;
|
||||
margin-bottom: 7pt;
|
||||
}
|
||||
.subtitle {
|
||||
font-size: 11.5pt;
|
||||
color: var(--olive);
|
||||
line-height: 1.5;
|
||||
max-width: 150mm;
|
||||
}
|
||||
.meta {
|
||||
font-size: 10pt;
|
||||
color: var(--stone);
|
||||
white-space: nowrap;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
/* ========== SECTION HEAD - brand left bar ========== */
|
||||
h2 {
|
||||
font-family: var(--serif);
|
||||
font-size: 14pt;
|
||||
font-weight: 500;
|
||||
color: var(--near-black);
|
||||
margin: 20pt 0 10pt 0;
|
||||
border-left: 2.5pt solid var(--brand);
|
||||
border-radius: 1.5pt;
|
||||
padding-left: 9pt;
|
||||
break-after: avoid;
|
||||
}
|
||||
h2:first-of-type { margin-top: 0; }
|
||||
|
||||
p { margin: 0 0 8pt 0; line-height: 1.55; widows: 2; orphans: 2; }
|
||||
code { font-family: var(--mono); font-size: 0.92em; }
|
||||
|
||||
/* ========== CHANGE LIST - numbered, brand markers, bold lead-in ========== */
|
||||
ol.changes { margin: 0; padding-left: 18pt; }
|
||||
ol.changes li {
|
||||
margin-bottom: 7pt;
|
||||
padding-left: 3pt;
|
||||
line-height: 1.55;
|
||||
break-inside: avoid;
|
||||
}
|
||||
ol.changes li:last-child { margin-bottom: 0; }
|
||||
ol.changes li::marker { color: var(--brand); font-weight: 500; }
|
||||
ol.changes li strong { font-weight: 500; color: var(--near-black); }
|
||||
ol.changes li .hl { color: var(--brand); font-weight: 500; }
|
||||
|
||||
/* ========== TAG ========== */
|
||||
.tag {
|
||||
display: inline-block;
|
||||
background: var(--tag-bg);
|
||||
color: var(--brand);
|
||||
font-size: 9pt;
|
||||
font-weight: 500;
|
||||
padding: 1pt 6pt;
|
||||
border-radius: 3pt;
|
||||
margin-right: 3pt;
|
||||
}
|
||||
.tag.breaking {
|
||||
background: var(--breaking-bg);
|
||||
color: var(--breaking-fg);
|
||||
}
|
||||
|
||||
/* ========== ACKNOWLEDGEMENTS - quiet labelled note, not a filled card ========== */
|
||||
.thanks {
|
||||
margin-top: 18pt;
|
||||
font-size: 9.5pt;
|
||||
line-height: 1.55;
|
||||
color: var(--olive);
|
||||
break-inside: avoid;
|
||||
}
|
||||
.thanks .hl { color: var(--brand); font-weight: 500; }
|
||||
.thanks-label {
|
||||
font-size: 9pt;
|
||||
color: var(--brand);
|
||||
letter-spacing: 0.5pt;
|
||||
text-transform: uppercase;
|
||||
margin-bottom: 4pt;
|
||||
}
|
||||
|
||||
/* ========== FOOTER ========== */
|
||||
.changelog-footer {
|
||||
margin-top: 20pt;
|
||||
padding-top: 8pt;
|
||||
border-top: 0.3pt dotted var(--border);
|
||||
font-size: 9pt;
|
||||
color: var(--stone);
|
||||
line-height: 1.5;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
font-variant-numeric: tabular-nums;
|
||||
letter-spacing: 0.3pt;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<!-- ========== 버전 헤더 ========== -->
|
||||
<!-- HEADER -->
|
||||
<div class="header">
|
||||
<div class="title-block">
|
||||
<div class="eyebrow">업데이트 로그</div>
|
||||
<h1>{{프로젝트명}} {{버전}}</h1>
|
||||
<div class="subtitle">{{이번 버전의 핵심 한 문장}}</div>
|
||||
</div>
|
||||
<div class="meta">{{출시 날짜}}</div>
|
||||
</div>
|
||||
|
||||
<!-- 각 항목: 굵은 도입어 + 콜론 + 설명. 번호가 순서를 맡고 섹션마다 1부터 다시 시작합니다. 해당 없는 섹션은 삭제하세요. -->
|
||||
<h2>호환성 변경 사항</h2>
|
||||
<ol class="changes">
|
||||
<li><span class="tag breaking">Breaking</span> <strong>{{변경된 점}}</strong>: {{사용자가 해야 할 조치.}}</li>
|
||||
</ol>
|
||||
|
||||
<h2>신기능</h2>
|
||||
<ol class="changes">
|
||||
<li><strong>{{기능}}</strong>: {{한 문장 설명.}}</li>
|
||||
<li><strong>{{기능}}</strong>: {{한 문장 설명.}}</li>
|
||||
<li><strong>{{기능}}</strong>: {{한 문장 설명.}}</li>
|
||||
</ol>
|
||||
|
||||
<h2>수정 사항</h2>
|
||||
<ol class="changes">
|
||||
<li><strong>{{영역}}</strong>: {{무엇을 고쳤는지.}}</li>
|
||||
<li><strong>{{영역}}</strong>: {{무엇을 고쳤는지.}}</li>
|
||||
<li><strong>{{영역}}</strong>: {{무엇을 고쳤는지.}}</li>
|
||||
</ol>
|
||||
|
||||
<!-- 감사의 말 (선택) -->
|
||||
<div class="thanks">
|
||||
<div class="thanks-label">감사의 말</div>
|
||||
{{기여자에게 감사. 예: "기여해 주신 @user1, @user2 님께 감사드립니다."}}
|
||||
</div>
|
||||
|
||||
<!-- FOOTER -->
|
||||
<div class="changelog-footer">
|
||||
<span>{{프로젝트 한 줄 설명}}</span>
|
||||
<span>{{프로젝트 URL}}</span>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,254 @@
|
||||
<!DOCTYPE html>
|
||||
<!-- ==================================================================
|
||||
CHANGELOG TEMPLATE · parchment design system
|
||||
1-2 页 A4 版本更新日志 / Release Notes
|
||||
结构:版本标题 -> Breaking -> Features -> Fixes -> 致谢
|
||||
遵循 tw93/Mole release note 格式
|
||||
================================================================== -->
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>{{项目名}} · {{版本号}} 更新日志</title>
|
||||
<meta name="author" content="{{作者}}">
|
||||
<meta name="description" content="{{摘要}}">
|
||||
<meta name="keywords" content="{{关键词}}">
|
||||
<meta name="generator" content="Kami">
|
||||
<style>
|
||||
/* Regular weight */
|
||||
@font-face {
|
||||
font-family: "TsangerJinKai02";
|
||||
src: url("../fonts/TsangerJinKai02-W04.ttf") format("truetype"),
|
||||
url("https://cdn.jsdelivr.net/gh/tw93/Kami@main/assets/fonts/TsangerJinKai02-W04.ttf") format("truetype");
|
||||
font-weight: 400;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
/* Bold weight - W05 for all bold variants */
|
||||
@font-face {
|
||||
font-family: "TsangerJinKai02";
|
||||
src: url("../fonts/TsangerJinKai02-W05.ttf") format("truetype"),
|
||||
url("https://cdn.jsdelivr.net/gh/tw93/Kami@main/assets/fonts/TsangerJinKai02-W05.ttf") format("truetype");
|
||||
font-weight: 500;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
@page {
|
||||
size: A4;
|
||||
margin: 20mm 22mm 22mm 22mm;
|
||||
background: #f5f4ed;
|
||||
}
|
||||
|
||||
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
|
||||
:root {
|
||||
--parchment: #f5f4ed;
|
||||
--ivory: #faf9f5;
|
||||
--near-black:#141413;
|
||||
--dark-warm: #3d3d3a;
|
||||
--olive: #504e49;
|
||||
--stone: #6b6a64;
|
||||
--brand: #1B365D;
|
||||
--border: #e8e6dc;
|
||||
--border-soft:#e5e3d8;
|
||||
--tag-bg: #E4ECF5;
|
||||
--breaking-bg:#f0e0d8;
|
||||
--breaking-fg:#8b4513;
|
||||
--serif: "TsangerJinKai02", "Source Han Serif SC", "Noto Serif CJK SC", "Songti SC", "STSong", Georgia, serif;
|
||||
--sans: var(--serif);
|
||||
--mono: "JetBrains Mono", "SF Mono", "Fira Code", Consolas, Monaco, "TsangerJinKai02", "Source Han Serif SC", monospace;
|
||||
}
|
||||
|
||||
html, body { background: var(--parchment); }
|
||||
|
||||
@media screen {
|
||||
body { max-width: 210mm; margin: 0 auto; padding: 20mm 22mm; }
|
||||
}
|
||||
|
||||
body {
|
||||
color: var(--near-black);
|
||||
font-family: var(--serif);
|
||||
font-size: 10.5pt;
|
||||
line-height: 1.55;
|
||||
letter-spacing: 0.3pt;
|
||||
widows: 3;
|
||||
orphans: 3;
|
||||
}
|
||||
|
||||
strong { font-weight: 500; }
|
||||
.hl { color: var(--brand); font-weight: 500; }
|
||||
|
||||
/* ========== HEADER (left-aligned editorial, matches one-pager) ========== */
|
||||
.header {
|
||||
margin-bottom: 20pt;
|
||||
padding-bottom: 13pt;
|
||||
border-bottom: 0.5pt solid var(--border);
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
justify-content: space-between;
|
||||
gap: 20pt;
|
||||
}
|
||||
.title-block { flex: 1; }
|
||||
.eyebrow {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 7pt;
|
||||
font-family: var(--sans);
|
||||
font-size: 9pt;
|
||||
color: var(--brand);
|
||||
letter-spacing: 1.5pt;
|
||||
font-weight: 500;
|
||||
margin-bottom: 8pt;
|
||||
}
|
||||
.eyebrow::before {
|
||||
content: "";
|
||||
width: 8pt;
|
||||
height: 1.5pt;
|
||||
border-radius: 0.75pt;
|
||||
background: var(--brand);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
h1 {
|
||||
font-family: var(--serif);
|
||||
font-size: 26pt;
|
||||
font-weight: 500;
|
||||
color: var(--near-black);
|
||||
line-height: 1.15;
|
||||
margin-bottom: 7pt;
|
||||
}
|
||||
.subtitle {
|
||||
font-size: 11.5pt;
|
||||
color: var(--olive);
|
||||
line-height: 1.5;
|
||||
max-width: 150mm;
|
||||
}
|
||||
.meta {
|
||||
font-size: 10pt;
|
||||
color: var(--stone);
|
||||
white-space: nowrap;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
/* ========== SECTION HEAD - brand left bar ========== */
|
||||
h2 {
|
||||
font-family: var(--serif);
|
||||
font-size: 14pt;
|
||||
font-weight: 500;
|
||||
color: var(--near-black);
|
||||
margin: 20pt 0 10pt 0;
|
||||
border-left: 2.5pt solid var(--brand);
|
||||
border-radius: 1.5pt;
|
||||
padding-left: 9pt;
|
||||
break-after: avoid;
|
||||
}
|
||||
h2:first-of-type { margin-top: 0; }
|
||||
|
||||
p { margin: 0 0 8pt 0; line-height: 1.55; widows: 2; orphans: 2; }
|
||||
code { font-family: var(--mono); font-size: 0.92em; }
|
||||
|
||||
/* ========== CHANGE LIST - numbered, brand markers, bold lead-in ========== */
|
||||
ol.changes { margin: 0; padding-left: 18pt; }
|
||||
ol.changes li {
|
||||
margin-bottom: 7pt;
|
||||
padding-left: 3pt;
|
||||
line-height: 1.55;
|
||||
break-inside: avoid;
|
||||
}
|
||||
ol.changes li:last-child { margin-bottom: 0; }
|
||||
ol.changes li::marker { color: var(--brand); font-weight: 500; }
|
||||
ol.changes li strong { font-weight: 500; color: var(--near-black); }
|
||||
ol.changes li .hl { color: var(--brand); font-weight: 500; }
|
||||
|
||||
/* ========== TAG ========== */
|
||||
.tag {
|
||||
display: inline-block;
|
||||
background: var(--tag-bg);
|
||||
color: var(--brand);
|
||||
font-size: 9pt;
|
||||
font-weight: 500;
|
||||
padding: 1pt 6pt;
|
||||
border-radius: 3pt;
|
||||
margin-right: 3pt;
|
||||
}
|
||||
.tag.breaking {
|
||||
background: var(--breaking-bg);
|
||||
color: var(--breaking-fg);
|
||||
}
|
||||
|
||||
/* ========== ACKNOWLEDGEMENTS - quiet labelled note, not a filled card ========== */
|
||||
.thanks {
|
||||
margin-top: 18pt;
|
||||
font-size: 9.5pt;
|
||||
line-height: 1.55;
|
||||
color: var(--olive);
|
||||
break-inside: avoid;
|
||||
}
|
||||
.thanks .hl { color: var(--brand); font-weight: 500; }
|
||||
.thanks-label {
|
||||
font-size: 9pt;
|
||||
color: var(--brand);
|
||||
letter-spacing: 0.5pt;
|
||||
text-transform: uppercase;
|
||||
margin-bottom: 4pt;
|
||||
}
|
||||
|
||||
/* ========== FOOTER ========== */
|
||||
.changelog-footer {
|
||||
margin-top: 20pt;
|
||||
padding-top: 8pt;
|
||||
border-top: 0.3pt dotted var(--border);
|
||||
font-size: 9pt;
|
||||
color: var(--stone);
|
||||
line-height: 1.5;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
font-variant-numeric: tabular-nums;
|
||||
letter-spacing: 0.3pt;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<!-- HEADER -->
|
||||
<div class="header">
|
||||
<div class="title-block">
|
||||
<div class="eyebrow">更新日志</div>
|
||||
<h1>{{项目名}} {{版本号}}</h1>
|
||||
<div class="subtitle">{{一句话版本亮点}}</div>
|
||||
</div>
|
||||
<div class="meta">{{发布日期}}</div>
|
||||
</div>
|
||||
|
||||
<!-- 每条:加粗引导词 + 全角冒号 + 描述。编号承担顺序,每节从 1 重新计数;不适用的节直接删掉。 -->
|
||||
<h2>破坏性变更</h2>
|
||||
<ol class="changes">
|
||||
<li><span class="tag breaking">Breaking</span> <strong>{{变更点}}</strong>:{{读者需要做的处理}}</li>
|
||||
</ol>
|
||||
|
||||
<h2>新功能</h2>
|
||||
<ol class="changes">
|
||||
<li><strong>{{功能}}</strong>:{{一句话描述}}</li>
|
||||
<li><strong>{{功能}}</strong>:{{一句话描述}}</li>
|
||||
<li><strong>{{功能}}</strong>:{{一句话描述}}</li>
|
||||
</ol>
|
||||
|
||||
<h2>修复</h2>
|
||||
<ol class="changes">
|
||||
<li><strong>{{范围}}</strong>:{{修复了什么}}</li>
|
||||
<li><strong>{{范围}}</strong>:{{修复了什么}}</li>
|
||||
<li><strong>{{范围}}</strong>:{{修复了什么}}</li>
|
||||
</ol>
|
||||
|
||||
<!-- 致谢(可选) -->
|
||||
<div class="thanks">
|
||||
<div class="thanks-label">致谢</div>
|
||||
{{感谢贡献者,如 "感谢 @user1、@user2 的贡献。"}}
|
||||
</div>
|
||||
|
||||
<!-- FOOTER -->
|
||||
<div class="changelog-footer">
|
||||
<span>{{项目一句话描述}}</span>
|
||||
<span>{{项目 URL}}</span>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,498 @@
|
||||
<!DOCTYPE html>
|
||||
<!-- ==================================================================
|
||||
EQUITY REPORT · English · parchment design system
|
||||
2-3 page A4 equity research / valuation / investment memo
|
||||
Structure: header -> metrics -> charts -> financials -> comps -> risks -> summary
|
||||
================================================================== -->
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>{{COMPANY_NAME}} · Equity Report</title>
|
||||
<meta name="author" content="{{AUTHOR}}">
|
||||
<meta name="description" content="{{DESCRIPTION}}">
|
||||
<meta name="keywords" content="{{KEYWORDS}}">
|
||||
<meta name="generator" content="Kami">
|
||||
<style>
|
||||
@font-face { font-family: "JetBrains Mono"; src: url("../fonts/JetBrainsMono.woff2") format("woff2"); font-weight: 400; font-style: normal; }
|
||||
@font-face { font-family: "JetBrains Mono"; src: url("../fonts/JetBrainsMono.woff2") format("woff2"); font-weight: 500; font-style: normal; }
|
||||
|
||||
@page {
|
||||
size: A4;
|
||||
margin: 16mm 18mm 18mm 18mm;
|
||||
background: #f5f4ed;
|
||||
|
||||
@bottom-center {
|
||||
content: counter(page) " · {{COMPANY_NAME}} Report";
|
||||
font-family: Charter, Georgia, Palatino, serif;
|
||||
font-size: 9pt;
|
||||
color: #6b6a64;
|
||||
}
|
||||
}
|
||||
|
||||
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
|
||||
:root {
|
||||
--parchment: #f5f4ed;
|
||||
--ivory: #faf9f5;
|
||||
--near-black:#141413;
|
||||
--dark-warm: #3d3d3a;
|
||||
--olive: #504e49;
|
||||
--stone: #6b6a64;
|
||||
--brand: #1B365D;
|
||||
--border: #e8e6dc;
|
||||
--border-soft:#e5e3d8;
|
||||
--tag-bg: #E4ECF5;
|
||||
--positive: #1B365D;
|
||||
--negative: #6b6a64;
|
||||
|
||||
--serif: Charter, Georgia,
|
||||
Palatino, "Times New Roman", serif;
|
||||
--sans: var(--serif);
|
||||
--mono: "JetBrains Mono", "SF Mono", "Fira Code",
|
||||
Consolas, Monaco, monospace;
|
||||
|
||||
--rhythm-module: 14pt;
|
||||
--rhythm-section: 18pt;
|
||||
}
|
||||
|
||||
html, body { background: var(--parchment); }
|
||||
|
||||
@media screen {
|
||||
body { max-width: 210mm; margin: 0 auto; padding: 16mm 18mm 18mm 18mm; }
|
||||
}
|
||||
|
||||
body {
|
||||
color: var(--near-black);
|
||||
font-family: var(--serif);
|
||||
font-size: 10pt;
|
||||
line-height: 1.5;
|
||||
widows: 3;
|
||||
orphans: 3;
|
||||
}
|
||||
|
||||
strong { font-weight: 500; }
|
||||
.hl { color: var(--brand); font-weight: 500; }
|
||||
|
||||
/* HEADER */
|
||||
.report-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
margin-bottom: var(--rhythm-module);
|
||||
padding-bottom: 10pt;
|
||||
border-bottom: 0.5pt solid var(--border);
|
||||
}
|
||||
.ticker-block { flex: 1; }
|
||||
.ticker-eyebrow {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 7pt;
|
||||
font-family: var(--sans);
|
||||
font-size: 9pt;
|
||||
color: var(--brand);
|
||||
letter-spacing: 1.5pt;
|
||||
text-transform: uppercase;
|
||||
font-weight: 500;
|
||||
margin-bottom: 4pt;
|
||||
}
|
||||
.ticker-eyebrow::before {
|
||||
content: "";
|
||||
width: 8pt;
|
||||
height: 1.5pt;
|
||||
border-radius: 0.75pt;
|
||||
background: var(--brand);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.ticker-name {
|
||||
font-family: var(--serif);
|
||||
font-size: 26pt;
|
||||
font-weight: 500;
|
||||
color: var(--near-black);
|
||||
line-height: 1.12;
|
||||
letter-spacing: -0.3pt;
|
||||
margin-bottom: 4pt;
|
||||
}
|
||||
.ticker-sub {
|
||||
font-size: 10pt;
|
||||
color: var(--olive);
|
||||
line-height: 1.4;
|
||||
}
|
||||
.price-block {
|
||||
text-align: right;
|
||||
padding-top: 4pt;
|
||||
}
|
||||
.price-current {
|
||||
font-family: var(--serif);
|
||||
font-size: 30pt;
|
||||
font-weight: 500;
|
||||
color: var(--near-black);
|
||||
line-height: 1;
|
||||
font-variant-numeric: tabular-nums;
|
||||
letter-spacing: -0.5pt;
|
||||
}
|
||||
.price-change {
|
||||
font-size: 10pt;
|
||||
font-weight: 500;
|
||||
margin-top: 2pt;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
.price-change.up { color: var(--positive); }
|
||||
.price-change.down { color: var(--negative); }
|
||||
.price-date {
|
||||
font-size: 9pt;
|
||||
color: var(--stone);
|
||||
margin-top: 2pt;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
/* METRICS */
|
||||
.metrics {
|
||||
display: flex;
|
||||
gap: 16pt;
|
||||
margin-bottom: 16pt;
|
||||
padding: 6pt 0;
|
||||
border-top: 0.3pt dotted var(--border);
|
||||
border-bottom: 0.3pt dotted var(--border);
|
||||
}
|
||||
.metric {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
gap: 5pt;
|
||||
}
|
||||
.metric-value {
|
||||
font-family: var(--serif);
|
||||
font-size: 18pt;
|
||||
font-weight: 500;
|
||||
color: var(--brand);
|
||||
line-height: 1;
|
||||
font-variant-numeric: tabular-nums;
|
||||
white-space: nowrap;
|
||||
letter-spacing: -0.3pt;
|
||||
}
|
||||
.metric-label {
|
||||
font-size: 9pt;
|
||||
color: var(--olive);
|
||||
line-height: 1.3;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* SECTIONS */
|
||||
h2 {
|
||||
font-family: var(--serif);
|
||||
font-size: 16pt;
|
||||
font-weight: 500;
|
||||
color: var(--near-black);
|
||||
margin: 18pt 0 6pt 0;
|
||||
break-after: avoid;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: 13pt;
|
||||
font-weight: 500;
|
||||
color: var(--dark-warm);
|
||||
margin: 12pt 0 4pt 0;
|
||||
break-after: avoid;
|
||||
}
|
||||
|
||||
p { margin: 0 0 8pt 0; line-height: 1.5; widows: 2; orphans: 2; }
|
||||
|
||||
ul, ol {
|
||||
margin: 4pt 0 8pt 0;
|
||||
padding-left: 18pt;
|
||||
line-height: 1.5;
|
||||
}
|
||||
ul li::marker { color: var(--brand); }
|
||||
ol li::marker { color: var(--brand); font-weight: 500; }
|
||||
|
||||
/* TAG */
|
||||
.tag {
|
||||
display: inline-block;
|
||||
background: var(--tag-bg);
|
||||
color: var(--brand);
|
||||
font-family: var(--sans);
|
||||
font-size: 9pt;
|
||||
font-weight: 500;
|
||||
padding: 1pt 6pt;
|
||||
border-radius: 3pt;
|
||||
letter-spacing: 0.4pt;
|
||||
text-transform: uppercase;
|
||||
margin-right: 3pt;
|
||||
}
|
||||
|
||||
/* TABLE (kami-table) */
|
||||
table, .kami-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
font-size: 9.5pt;
|
||||
margin: 8pt 0;
|
||||
break-inside: avoid;
|
||||
}
|
||||
table th, .kami-table th {
|
||||
text-align: left;
|
||||
font-family: var(--sans);
|
||||
font-weight: 500;
|
||||
color: var(--dark-warm);
|
||||
padding: 5pt 8pt;
|
||||
border-bottom: 1pt solid var(--border);
|
||||
background: transparent;
|
||||
}
|
||||
table td, .kami-table td {
|
||||
padding: 4pt 8pt;
|
||||
border-bottom: 0.3pt solid var(--border-soft);
|
||||
vertical-align: top;
|
||||
}
|
||||
table.compact th, .kami-table.compact th { padding: 3pt 6pt; font-size: 8pt; }
|
||||
table.compact td, .kami-table.compact td { padding: 2pt 6pt; font-size: 8pt; line-height: 1.4; }
|
||||
table.financial td:not(:first-child), .kami-table.financial td:not(:first-child) {
|
||||
text-align: right; font-variant-numeric: tabular-nums;
|
||||
}
|
||||
table.financial th:not(:first-child), .kami-table.financial th:not(:first-child) { text-align: right; }
|
||||
table.striped tbody tr:nth-child(even) td, .kami-table.striped tbody tr:nth-child(even) td {
|
||||
background: var(--ivory);
|
||||
}
|
||||
table .total td, .kami-table .total td {
|
||||
font-weight: 500; border-top: 1pt solid var(--brand); border-bottom: none; color: var(--near-black);
|
||||
}
|
||||
|
||||
/* CALLOUT */
|
||||
.callout {
|
||||
background: var(--ivory);
|
||||
border-left: 2pt solid var(--brand);
|
||||
padding: 8pt 12pt;
|
||||
border-radius: 3pt;
|
||||
margin: 10pt 0;
|
||||
line-height: 1.5;
|
||||
break-inside: avoid;
|
||||
}
|
||||
|
||||
/* FIGURE */
|
||||
figure { margin: 12pt 0; break-inside: avoid; }
|
||||
figcaption {
|
||||
font-family: var(--sans);
|
||||
font-size: 9pt;
|
||||
color: var(--stone);
|
||||
margin-top: 4pt;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* RISK MATRIX */
|
||||
.risk-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 10pt;
|
||||
margin: 8pt 0;
|
||||
}
|
||||
.risk-item {
|
||||
padding: 6pt 10pt;
|
||||
background: var(--ivory);
|
||||
border-radius: 3pt;
|
||||
break-inside: avoid;
|
||||
}
|
||||
.risk-label {
|
||||
font-family: var(--sans);
|
||||
font-size: 9pt;
|
||||
font-weight: 500;
|
||||
color: var(--brand);
|
||||
letter-spacing: 0.3pt;
|
||||
text-transform: uppercase;
|
||||
margin-bottom: 2pt;
|
||||
}
|
||||
.risk-desc {
|
||||
font-size: 9pt;
|
||||
color: var(--olive);
|
||||
line-height: 1.45;
|
||||
}
|
||||
|
||||
/* TWO-COLUMN */
|
||||
.two-col {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 16pt;
|
||||
}
|
||||
|
||||
/* ANALYST SUMMARY */
|
||||
.analyst-box {
|
||||
background: var(--ivory);
|
||||
border-left: 1.4pt solid var(--brand);
|
||||
border-radius: 4pt;
|
||||
padding: 10pt 14pt;
|
||||
margin: 12pt 0;
|
||||
break-inside: avoid;
|
||||
}
|
||||
|
||||
/* CHART PLACEHOLDER */
|
||||
.chart-placeholder {
|
||||
background: var(--ivory);
|
||||
border-radius: 4pt;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: var(--stone);
|
||||
font-size: 9pt;
|
||||
}
|
||||
.analyst-label {
|
||||
font-family: var(--sans);
|
||||
font-size: 9pt;
|
||||
color: var(--brand);
|
||||
letter-spacing: 1pt;
|
||||
text-transform: uppercase;
|
||||
font-weight: 500;
|
||||
margin-bottom: 4pt;
|
||||
}
|
||||
|
||||
/* FOOTER */
|
||||
.report-footer {
|
||||
margin-top: 16pt;
|
||||
padding-top: 6pt;
|
||||
border-top: 0.3pt dotted var(--border);
|
||||
font-size: 9pt;
|
||||
color: var(--stone);
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
.page-break { break-before: page; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<!-- HEADER -->
|
||||
<div class="report-header">
|
||||
<div class="ticker-block">
|
||||
<div class="ticker-eyebrow">{{EYEBROW - e.g. Equity Research / Valuation / Investment Memo}}</div>
|
||||
<div class="ticker-name">{{COMPANY_NAME}} <span style="font-size: 14pt; color: var(--stone);">{{TICKER}}</span></div>
|
||||
<div class="ticker-sub">{{SECTOR}} · {{EXCHANGE}} · {{One-line thesis.}}</div>
|
||||
</div>
|
||||
<div class="price-block">
|
||||
<div class="price-current">{{CURRENT_PRICE}}</div>
|
||||
<div class="price-change up">{{CHANGE - e.g. "+12.3%"}}</div>
|
||||
<div class="price-date">{{DATE}}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- METRICS -->
|
||||
<div class="metrics">
|
||||
<div class="metric">
|
||||
<div class="metric-value">{{NUMBER}}</div>
|
||||
<div class="metric-label">Market Cap</div>
|
||||
</div>
|
||||
<div class="metric">
|
||||
<div class="metric-value">{{NUMBER}}</div>
|
||||
<div class="metric-label">P/E Ratio</div>
|
||||
</div>
|
||||
<div class="metric">
|
||||
<div class="metric-value">{{NUMBER}}</div>
|
||||
<div class="metric-label">Revenue</div>
|
||||
</div>
|
||||
<div class="metric">
|
||||
<div class="metric-value">{{NUMBER}}</div>
|
||||
<div class="metric-label">Margin</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- INVESTMENT THESIS -->
|
||||
<h2>Investment Thesis</h2>
|
||||
<p>{{Two or three sentences laying out the core thesis. Use <span class="hl">key figures</span> to support the argument. This paragraph is the soul of the report.}}</p>
|
||||
|
||||
<div class="callout">
|
||||
{{One-line verdict: buy / hold / watch, and the single strongest reason.}}
|
||||
</div>
|
||||
|
||||
<!-- CHART -->
|
||||
<h2>Price Action</h2>
|
||||
<figure>
|
||||
<div class="chart-placeholder" style="height: 60mm;">
|
||||
[Candlestick / price chart placeholder - extract SVG from diagrams/]
|
||||
</div>
|
||||
<figcaption>{{Chart title, e.g. "20-day price action showing accumulation phase."}}</figcaption>
|
||||
</figure>
|
||||
|
||||
<!-- FINANCIALS -->
|
||||
<h2>Financial Overview</h2>
|
||||
<table class="financial striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Metric</th>
|
||||
<th>FY2023</th>
|
||||
<th>FY2024</th>
|
||||
<th>FY2025E</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr><td>Revenue</td><td>{{VAL}}</td><td>{{VAL}}</td><td>{{VAL}}</td></tr>
|
||||
<tr><td>Net Income</td><td>{{VAL}}</td><td>{{VAL}}</td><td>{{VAL}}</td></tr>
|
||||
<tr><td>EPS</td><td>{{VAL}}</td><td>{{VAL}}</td><td>{{VAL}}</td></tr>
|
||||
<tr><td>Gross Margin</td><td>{{VAL}}</td><td>{{VAL}}</td><td>{{VAL}}</td></tr>
|
||||
<tr><td>ROE</td><td>{{VAL}}</td><td>{{VAL}}</td><td>{{VAL}}</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<!-- REVENUE BREAKDOWN -->
|
||||
<h2>Revenue Breakdown</h2>
|
||||
<figure>
|
||||
<div class="chart-placeholder" style="height: 50mm;">
|
||||
[Revenue breakdown chart placeholder - donut or waterfall]
|
||||
</div>
|
||||
<figcaption>{{Chart title.}}</figcaption>
|
||||
</figure>
|
||||
|
||||
<!-- COMPETITIVE POSITIONING -->
|
||||
<div class="page-break"></div>
|
||||
<h2>Competitive Landscape</h2>
|
||||
<p>{{Industry landscape overview, one or two paragraphs.}}</p>
|
||||
|
||||
<table class="financial compact striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Company</th>
|
||||
<th>Mkt Cap</th>
|
||||
<th>P/E</th>
|
||||
<th>Rev Growth</th>
|
||||
<th>Margin</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr><td><strong>{{TARGET}}</strong></td><td>{{VAL}}</td><td>{{VAL}}</td><td>{{VAL}}</td><td>{{VAL}}</td></tr>
|
||||
<tr><td>{{COMP_A}}</td><td>{{VAL}}</td><td>{{VAL}}</td><td>{{VAL}}</td><td>{{VAL}}</td></tr>
|
||||
<tr><td>{{COMP_B}}</td><td>{{VAL}}</td><td>{{VAL}}</td><td>{{VAL}}</td><td>{{VAL}}</td></tr>
|
||||
<tr><td>{{COMP_C}}</td><td>{{VAL}}</td><td>{{VAL}}</td><td>{{VAL}}</td><td>{{VAL}}</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<!-- RISKS -->
|
||||
<h2>Risk Factors</h2>
|
||||
<div class="risk-grid">
|
||||
<div class="risk-item">
|
||||
<div class="risk-label">{{Risk Type 1}}</div>
|
||||
<div class="risk-desc">{{Risk description.}}</div>
|
||||
</div>
|
||||
<div class="risk-item">
|
||||
<div class="risk-label">{{Risk Type 2}}</div>
|
||||
<div class="risk-desc">{{Risk description.}}</div>
|
||||
</div>
|
||||
<div class="risk-item">
|
||||
<div class="risk-label">{{Risk Type 3}}</div>
|
||||
<div class="risk-desc">{{Risk description.}}</div>
|
||||
</div>
|
||||
<div class="risk-item">
|
||||
<div class="risk-label">{{Risk Type 4}}</div>
|
||||
<div class="risk-desc">{{Risk description.}}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ANALYST SUMMARY -->
|
||||
<div class="analyst-box">
|
||||
<div class="analyst-label">Analyst Summary</div>
|
||||
{{Three to five sentences summarizing the position. Include target price (if applicable), rating, key catalysts, and core assumptions.}}
|
||||
</div>
|
||||
|
||||
<!-- FOOTER -->
|
||||
<div class="report-footer">
|
||||
<span>{{FIRM / DISCLAIMER}}</span>
|
||||
<span>{{AUTHOR}} · {{DATE}}</span>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,565 @@
|
||||
<!DOCTYPE html>
|
||||
<!-- ==================================================================
|
||||
EQUITY REPORT TEMPLATE (KO) · parchment design system
|
||||
2-3 페이지 A4 개별 종목 리서치 / 밸류에이션 분석 / 투자 메모
|
||||
구조: 제목 영역 -> 핵심 지표 -> 차트 분석 -> 경쟁 구도 -> 리스크 -> 총평
|
||||
변경 후 실행: python3 -c "from weasyprint import HTML; HTML('equity-report-ko.html').write_pdf('out.pdf')"
|
||||
목표: 최대 3페이지
|
||||
================================================================== -->
|
||||
<html lang="ko">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>{{종목명}} · 종목 리서치</title>
|
||||
<meta name="author" content="{{작성자}}">
|
||||
<meta name="description" content="{{요약}}">
|
||||
<meta name="keywords" content="{{키워드}}">
|
||||
<meta name="generator" content="Kami">
|
||||
<style>
|
||||
/* Regular weight */
|
||||
@font-face {
|
||||
font-family: "Source Han Serif K";
|
||||
src: url("../fonts/SourceHanSerifKR-Regular.otf") format("opentype"),
|
||||
url("https://cdn.jsdelivr.net/gh/tw93/Kami@main/assets/fonts/SourceHanSerifKR-Regular.otf") format("opentype");
|
||||
font-weight: 400;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
/* Medium weight */
|
||||
@font-face {
|
||||
font-family: "Source Han Serif K";
|
||||
src: url("../fonts/SourceHanSerifKR-Medium.otf") format("opentype"),
|
||||
url("https://cdn.jsdelivr.net/gh/tw93/Kami@main/assets/fonts/SourceHanSerifKR-Medium.otf") format("opentype");
|
||||
font-weight: 500;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
@page {
|
||||
size: A4;
|
||||
margin: 16mm 18mm 18mm 18mm;
|
||||
background: #f5f4ed;
|
||||
|
||||
@bottom-center {
|
||||
content: counter(page) " · {{종목명}} 리서치";
|
||||
font-family: "Source Han Serif K", "Source Han Serif KR", "Apple SD Gothic Neo",
|
||||
"Noto Serif KR", "AppleMyungjo", Georgia, serif;
|
||||
font-size: 9pt;
|
||||
color: #6b6a64;
|
||||
}
|
||||
}
|
||||
|
||||
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
|
||||
:root {
|
||||
--parchment: #f5f4ed;
|
||||
--ivory: #faf9f5;
|
||||
--near-black:#141413;
|
||||
--dark-warm: #3d3d3a;
|
||||
--olive: #504e49;
|
||||
--stone: #6b6a64;
|
||||
--brand: #1B365D;
|
||||
--border: #e8e6dc;
|
||||
--border-soft:#e5e3d8;
|
||||
--tag-bg: #E4ECF5;
|
||||
--positive: #1B365D;
|
||||
--negative: #6b6a64;
|
||||
--serif: "Source Han Serif K", "Source Han Serif KR", "Noto Serif KR", "Apple SD Gothic Neo", "AppleMyungjo", Charter, Georgia, serif;
|
||||
--sans: var(--serif);
|
||||
|
||||
--rhythm-module: 14pt;
|
||||
--rhythm-section: 18pt;
|
||||
}
|
||||
|
||||
html, body { background: var(--parchment); }
|
||||
|
||||
@media screen {
|
||||
body { max-width: 210mm; margin: 0 auto; padding: 16mm 18mm 18mm 18mm; }
|
||||
}
|
||||
|
||||
body {
|
||||
color: var(--near-black);
|
||||
font-family: var(--serif);
|
||||
font-size: 10pt;
|
||||
line-height: 1.5;
|
||||
letter-spacing: 0.3pt;
|
||||
widows: 3;
|
||||
orphans: 3;
|
||||
font-synthesis: none;
|
||||
}
|
||||
|
||||
strong { font-weight: 500; }
|
||||
.hl { color: var(--brand); font-weight: 500; }
|
||||
|
||||
/* ========== HEADER ========== */
|
||||
.report-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
margin-bottom: var(--rhythm-module);
|
||||
padding-bottom: 10pt;
|
||||
border-bottom: 0.5pt solid var(--border);
|
||||
}
|
||||
.ticker-block { flex: 1; }
|
||||
.ticker-eyebrow {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 7pt;
|
||||
font-size: 9pt;
|
||||
color: var(--brand);
|
||||
letter-spacing: 1.5pt;
|
||||
text-transform: uppercase;
|
||||
font-weight: 500;
|
||||
margin-bottom: 4pt;
|
||||
}
|
||||
.ticker-eyebrow::before {
|
||||
content: "";
|
||||
width: 8pt;
|
||||
height: 1.5pt;
|
||||
border-radius: 0.75pt;
|
||||
background: var(--brand);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
/* KO locale tuning: Source Han Serif K hangul glyphs have a smaller optical body
|
||||
than TsangerJinKai. Numerics inherit the CN baseline (verified during the
|
||||
one-pager-ko pilot, see references/design.md). */
|
||||
.ticker-name {
|
||||
font-family: var(--serif);
|
||||
font-size: 24pt;
|
||||
font-weight: 500;
|
||||
color: var(--near-black);
|
||||
line-height: 1.15;
|
||||
margin-bottom: 4pt;
|
||||
}
|
||||
.ticker-sub {
|
||||
font-size: 10pt;
|
||||
color: var(--olive);
|
||||
line-height: 1.4;
|
||||
}
|
||||
.price-block {
|
||||
text-align: right;
|
||||
padding-top: 4pt;
|
||||
}
|
||||
.price-current {
|
||||
font-family: var(--serif);
|
||||
font-size: 28pt;
|
||||
font-weight: 500;
|
||||
color: var(--near-black);
|
||||
line-height: 1;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
.price-change {
|
||||
font-size: 10pt;
|
||||
font-weight: 500;
|
||||
margin-top: 2pt;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
.price-change.up { color: var(--positive); }
|
||||
.price-change.down { color: var(--negative); }
|
||||
.price-date {
|
||||
font-size: 9pt;
|
||||
color: var(--stone);
|
||||
margin-top: 2pt;
|
||||
}
|
||||
|
||||
/* ========== METRICS ========== */
|
||||
.metrics {
|
||||
display: flex;
|
||||
gap: 14pt;
|
||||
margin-bottom: 16pt;
|
||||
padding: 6pt 0;
|
||||
border-top: 0.3pt dotted var(--border);
|
||||
border-bottom: 0.3pt dotted var(--border);
|
||||
}
|
||||
.metric {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
gap: 4pt;
|
||||
}
|
||||
.metric-value {
|
||||
font-family: var(--serif);
|
||||
font-size: 16pt;
|
||||
font-weight: 500;
|
||||
color: var(--brand);
|
||||
line-height: 1;
|
||||
font-variant-numeric: tabular-nums;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.metric-label {
|
||||
font-size: 9pt;
|
||||
color: var(--olive);
|
||||
line-height: 1.3;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* ========== SECTIONS ========== */
|
||||
h2 {
|
||||
font-family: var(--serif);
|
||||
font-size: 16pt;
|
||||
font-weight: 500;
|
||||
color: var(--near-black);
|
||||
margin: 18pt 0 6pt 0;
|
||||
break-after: avoid;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: 13pt;
|
||||
font-weight: 500;
|
||||
color: var(--dark-warm);
|
||||
margin: 12pt 0 4pt 0;
|
||||
break-after: avoid;
|
||||
}
|
||||
|
||||
p { margin: 0 0 8pt 0; line-height: 1.5; widows: 2; orphans: 2; }
|
||||
|
||||
ul, ol {
|
||||
margin: 4pt 0 8pt 0;
|
||||
padding-left: 18pt;
|
||||
line-height: 1.5;
|
||||
}
|
||||
ul li::marker { color: var(--brand); }
|
||||
ol li::marker { color: var(--brand); font-weight: 500; }
|
||||
|
||||
/* ========== TAG ========== */
|
||||
.tag {
|
||||
display: inline-block;
|
||||
background: var(--tag-bg);
|
||||
color: var(--brand);
|
||||
font-size: 9pt;
|
||||
font-weight: 500;
|
||||
padding: 1pt 6pt;
|
||||
border-radius: 3pt;
|
||||
margin-right: 3pt;
|
||||
}
|
||||
|
||||
/* ========== TABLE (kami-table) ========== */
|
||||
table, .kami-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
font-size: 9.5pt;
|
||||
margin: 8pt 0;
|
||||
break-inside: avoid;
|
||||
}
|
||||
table th, .kami-table th {
|
||||
text-align: left;
|
||||
font-weight: 500;
|
||||
color: var(--dark-warm);
|
||||
padding: 5pt 8pt;
|
||||
border-bottom: 1pt solid var(--border);
|
||||
background: transparent;
|
||||
}
|
||||
table td, .kami-table td {
|
||||
padding: 4pt 8pt;
|
||||
border-bottom: 0.3pt solid var(--border-soft);
|
||||
vertical-align: top;
|
||||
}
|
||||
table.compact th, .kami-table.compact th { padding: 3pt 6pt; font-size: 8pt; }
|
||||
table.compact td, .kami-table.compact td { padding: 2pt 6pt; font-size: 8pt; line-height: 1.4; }
|
||||
table.financial td:not(:first-child), .kami-table.financial td:not(:first-child) {
|
||||
text-align: right; font-variant-numeric: tabular-nums;
|
||||
}
|
||||
table.financial th:not(:first-child), .kami-table.financial th:not(:first-child) { text-align: right; }
|
||||
table.striped tbody tr:nth-child(even) td, .kami-table.striped tbody tr:nth-child(even) td {
|
||||
background: var(--ivory);
|
||||
}
|
||||
table .total td, .kami-table .total td {
|
||||
font-weight: 500; border-top: 1pt solid var(--brand); border-bottom: none; color: var(--near-black);
|
||||
}
|
||||
|
||||
/* ========== CALLOUT ========== */
|
||||
.callout {
|
||||
background: var(--ivory);
|
||||
border-left: 2pt solid var(--brand);
|
||||
padding: 8pt 12pt;
|
||||
border-radius: 3pt;
|
||||
margin: 10pt 0;
|
||||
line-height: 1.5;
|
||||
break-inside: avoid;
|
||||
}
|
||||
.callout blockquote,
|
||||
.callout .quote {
|
||||
border-left: none;
|
||||
padding-left: 0;
|
||||
margin: 6pt 0;
|
||||
color: var(--olive);
|
||||
}
|
||||
.callout blockquote:first-child,
|
||||
.callout .quote:first-child { margin-top: 0; }
|
||||
.callout blockquote:last-child,
|
||||
.callout .quote:last-child { margin-bottom: 0; }
|
||||
|
||||
/* ========== FIGURE ========== */
|
||||
figure {
|
||||
margin: 12pt 0;
|
||||
break-inside: avoid;
|
||||
}
|
||||
figcaption {
|
||||
font-size: 9pt;
|
||||
color: var(--stone);
|
||||
margin-top: 4pt;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* ========== RISK MATRIX ========== */
|
||||
.risk-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 10pt;
|
||||
margin: 8pt 0;
|
||||
}
|
||||
.risk-item {
|
||||
padding: 6pt 10pt;
|
||||
background: var(--ivory);
|
||||
border-radius: 3pt;
|
||||
break-inside: avoid;
|
||||
}
|
||||
.risk-label {
|
||||
font-size: 9pt;
|
||||
font-weight: 500;
|
||||
color: var(--brand);
|
||||
margin-bottom: 2pt;
|
||||
}
|
||||
.risk-desc {
|
||||
font-size: 9pt;
|
||||
color: var(--olive);
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
/* ========== TWO-COLUMN ========== */
|
||||
.two-col {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 16pt;
|
||||
}
|
||||
|
||||
/* ========== ANALYST SUMMARY ========== */
|
||||
.analyst-box {
|
||||
background: var(--ivory);
|
||||
border-left: 1.4pt solid var(--brand);
|
||||
border-radius: 4pt;
|
||||
padding: 10pt 14pt;
|
||||
margin: 12pt 0;
|
||||
break-inside: avoid;
|
||||
}
|
||||
|
||||
/* ========== CHART PLACEHOLDER ========== */
|
||||
.chart-placeholder {
|
||||
background: var(--ivory);
|
||||
border-radius: 4pt;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: var(--stone);
|
||||
font-size: 9pt;
|
||||
}
|
||||
.analyst-label {
|
||||
font-size: 9pt;
|
||||
color: var(--brand);
|
||||
letter-spacing: 0.5pt;
|
||||
text-transform: uppercase;
|
||||
margin-bottom: 4pt;
|
||||
}
|
||||
|
||||
/* ========== FOOTER ========== */
|
||||
.report-footer {
|
||||
margin-top: 16pt;
|
||||
padding-top: 6pt;
|
||||
border-top: 0.3pt dotted var(--border);
|
||||
font-size: 9pt;
|
||||
color: var(--stone);
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.page-break { break-before: page; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<!-- ========== 헤더 ========== -->
|
||||
<div class="report-header">
|
||||
<div class="ticker-block">
|
||||
<div class="ticker-eyebrow">{{EYEBROW · 예: "종목 리서치" / "밸류에이션 분석" / "투자 메모"}}</div>
|
||||
<div class="ticker-name">{{회사명}} <span style="font-size: 14pt; color: var(--stone);">{{종목 코드}}</span></div>
|
||||
<div class="ticker-sub">{{섹터}} · {{거래소}} · {{핵심 투자 판단 한 문장}}</div>
|
||||
</div>
|
||||
<div class="price-block">
|
||||
<div class="price-current">{{현재가}}</div>
|
||||
<div class="price-change up">{{등락률 예: "+12.3%"}}</div>
|
||||
<div class="price-date">{{날짜}}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ========== 핵심 지표 ========== -->
|
||||
<div class="metrics">
|
||||
<div class="metric">
|
||||
<div class="metric-value">{{수치}}</div>
|
||||
<div class="metric-label">시가총액</div>
|
||||
</div>
|
||||
<div class="metric">
|
||||
<div class="metric-value">{{수치}}</div>
|
||||
<div class="metric-label">P/E</div>
|
||||
</div>
|
||||
<div class="metric">
|
||||
<div class="metric-value">{{수치}}</div>
|
||||
<div class="metric-label">매출</div>
|
||||
</div>
|
||||
<div class="metric">
|
||||
<div class="metric-value">{{수치}}</div>
|
||||
<div class="metric-label">이익률</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ========== 투자 논거 ========== -->
|
||||
<h2>투자 논거</h2>
|
||||
<p>{{2-3문장의 핵심 투자 포인트. <span class="hl">핵심 데이터</span>로 판단을 뒷받침한다. 이 단락이 리서치 전체의 핵심이다.}}</p>
|
||||
|
||||
<div class="callout">
|
||||
{{한 문장 요약: 매수/보유/관망의 핵심 이유.}}
|
||||
</div>
|
||||
|
||||
<!-- ========== 차트 (SVG 삽입) ========== -->
|
||||
<h2>가격 추이</h2>
|
||||
<figure>
|
||||
<!-- diagrams/에서 <svg>를 추출해 여기에 삽입 -->
|
||||
<div class="chart-placeholder" style="height: 60mm;">
|
||||
[캔들스틱 / 가격 추이 차트 자리 · diagrams/에서 SVG를 추출해 삽입]
|
||||
</div>
|
||||
<figcaption>{{차트 제목, 예: "최근 20 거래일 가격 추이"}}</figcaption>
|
||||
</figure>
|
||||
|
||||
<!-- ========== 재무 개요 ========== -->
|
||||
<h2>재무 개요</h2>
|
||||
<table class="financial striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>지표</th>
|
||||
<th>FY2023</th>
|
||||
<th>FY2024</th>
|
||||
<th>FY2025E</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>매출</td>
|
||||
<td>{{데이터}}</td>
|
||||
<td>{{데이터}}</td>
|
||||
<td>{{데이터}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>순이익</td>
|
||||
<td>{{데이터}}</td>
|
||||
<td>{{데이터}}</td>
|
||||
<td>{{데이터}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>EPS</td>
|
||||
<td>{{데이터}}</td>
|
||||
<td>{{데이터}}</td>
|
||||
<td>{{데이터}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>매출총이익률</td>
|
||||
<td>{{데이터}}</td>
|
||||
<td>{{데이터}}</td>
|
||||
<td>{{데이터}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>ROE</td>
|
||||
<td>{{데이터}}</td>
|
||||
<td>{{데이터}}</td>
|
||||
<td>{{데이터}}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<!-- ========== 매출 구조 (선택 차트) ========== -->
|
||||
<h2>매출 구조</h2>
|
||||
<figure>
|
||||
<div class="chart-placeholder" style="height: 50mm;">
|
||||
[매출 분해 차트 자리 · 도넛 차트 또는 폭포 차트]
|
||||
</div>
|
||||
<figcaption>{{차트 제목}}</figcaption>
|
||||
</figure>
|
||||
|
||||
<!-- ========== 경쟁 구도 ========== -->
|
||||
<div class="page-break"></div>
|
||||
<h2>경쟁 구도</h2>
|
||||
<p>{{업계 구도 개요, 1-2 단락.}}</p>
|
||||
|
||||
<table class="financial compact striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>회사</th>
|
||||
<th>시가총액</th>
|
||||
<th>P/E</th>
|
||||
<th>매출 성장률</th>
|
||||
<th>이익률</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><strong>{{대상 회사}}</strong></td>
|
||||
<td>{{데이터}}</td>
|
||||
<td>{{데이터}}</td>
|
||||
<td>{{데이터}}</td>
|
||||
<td>{{데이터}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{경쟁사 A}}</td>
|
||||
<td>{{데이터}}</td>
|
||||
<td>{{데이터}}</td>
|
||||
<td>{{데이터}}</td>
|
||||
<td>{{데이터}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{경쟁사 B}}</td>
|
||||
<td>{{데이터}}</td>
|
||||
<td>{{데이터}}</td>
|
||||
<td>{{데이터}}</td>
|
||||
<td>{{데이터}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{경쟁사 C}}</td>
|
||||
<td>{{데이터}}</td>
|
||||
<td>{{데이터}}</td>
|
||||
<td>{{데이터}}</td>
|
||||
<td>{{데이터}}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<!-- ========== 리스크 ========== -->
|
||||
<h2>리스크 고지</h2>
|
||||
<div class="risk-grid">
|
||||
<div class="risk-item">
|
||||
<div class="risk-label">{{리스크 유형 1}}</div>
|
||||
<div class="risk-desc">{{리스크 설명}}</div>
|
||||
</div>
|
||||
<div class="risk-item">
|
||||
<div class="risk-label">{{리스크 유형 2}}</div>
|
||||
<div class="risk-desc">{{리스크 설명}}</div>
|
||||
</div>
|
||||
<div class="risk-item">
|
||||
<div class="risk-label">{{리스크 유형 3}}</div>
|
||||
<div class="risk-desc">{{리스크 설명}}</div>
|
||||
</div>
|
||||
<div class="risk-item">
|
||||
<div class="risk-label">{{리스크 유형 4}}</div>
|
||||
<div class="risk-desc">{{리스크 설명}}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ========== 애널리스트 총평 ========== -->
|
||||
<div class="analyst-box">
|
||||
<div class="analyst-label">애널리스트 총평</div>
|
||||
{{3-5문장 총평. 목표 주가(해당 시), 투자 의견, 핵심 촉매제, 주요 가정을 포함한다.}}
|
||||
</div>
|
||||
|
||||
<!-- ========== 푸터 ========== -->
|
||||
<div class="report-footer">
|
||||
<span>{{기관명 / 면책 조항}}</span>
|
||||
<span>{{작성자}} · {{날짜}}</span>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,559 @@
|
||||
<!DOCTYPE html>
|
||||
<!-- ==================================================================
|
||||
EQUITY REPORT TEMPLATE · parchment design system
|
||||
2-3 页 A4 个股研报 / 估值分析 / 投资备忘
|
||||
结构:标题区 -> 核心指标 -> 图表分析 -> 竞争格局 -> 风险 -> 总结
|
||||
================================================================== -->
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>{{股票名称}} · 个股研报</title>
|
||||
<meta name="author" content="{{作者}}">
|
||||
<meta name="description" content="{{摘要}}">
|
||||
<meta name="keywords" content="{{关键词}}">
|
||||
<meta name="generator" content="Kami">
|
||||
<style>
|
||||
/* Regular weight */
|
||||
@font-face {
|
||||
font-family: "TsangerJinKai02";
|
||||
src: url("../fonts/TsangerJinKai02-W04.ttf") format("truetype"),
|
||||
url("https://cdn.jsdelivr.net/gh/tw93/Kami@main/assets/fonts/TsangerJinKai02-W04.ttf") format("truetype");
|
||||
font-weight: 400;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
/* Bold weight - W05 for all bold variants */
|
||||
@font-face {
|
||||
font-family: "TsangerJinKai02";
|
||||
src: url("../fonts/TsangerJinKai02-W05.ttf") format("truetype"),
|
||||
url("https://cdn.jsdelivr.net/gh/tw93/Kami@main/assets/fonts/TsangerJinKai02-W05.ttf") format("truetype");
|
||||
font-weight: 500;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
@page {
|
||||
size: A4;
|
||||
margin: 16mm 18mm 18mm 18mm;
|
||||
background: #f5f4ed;
|
||||
|
||||
@bottom-center {
|
||||
content: counter(page) " · {{股票名称}} 研报";
|
||||
font-family: "TsangerJinKai02", "Source Han Serif SC",
|
||||
"Noto Serif CJK SC", "Songti SC", Georgia, serif;
|
||||
font-size: 9pt;
|
||||
color: #6b6a64;
|
||||
}
|
||||
}
|
||||
|
||||
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
|
||||
:root {
|
||||
--parchment: #f5f4ed;
|
||||
--ivory: #faf9f5;
|
||||
--near-black:#141413;
|
||||
--dark-warm: #3d3d3a;
|
||||
--olive: #504e49;
|
||||
--stone: #6b6a64;
|
||||
--brand: #1B365D;
|
||||
--border: #e8e6dc;
|
||||
--border-soft:#e5e3d8;
|
||||
--tag-bg: #E4ECF5;
|
||||
--positive: #1B365D;
|
||||
--negative: #6b6a64;
|
||||
--serif: "TsangerJinKai02", "Source Han Serif SC", "Noto Serif CJK SC", "Songti SC", "STSong", Georgia, serif;
|
||||
--sans: var(--serif);
|
||||
|
||||
--rhythm-module: 14pt;
|
||||
--rhythm-section: 18pt;
|
||||
}
|
||||
|
||||
html, body { background: var(--parchment); }
|
||||
|
||||
@media screen {
|
||||
body { max-width: 210mm; margin: 0 auto; padding: 16mm 18mm 18mm 18mm; }
|
||||
}
|
||||
|
||||
body {
|
||||
color: var(--near-black);
|
||||
font-family: var(--serif);
|
||||
font-size: 10pt;
|
||||
line-height: 1.5;
|
||||
letter-spacing: 0.3pt;
|
||||
widows: 3;
|
||||
orphans: 3;
|
||||
}
|
||||
|
||||
strong { font-weight: 500; }
|
||||
.hl { color: var(--brand); font-weight: 500; }
|
||||
|
||||
/* ========== HEADER ========== */
|
||||
.report-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
margin-bottom: var(--rhythm-module);
|
||||
padding-bottom: 10pt;
|
||||
border-bottom: 0.5pt solid var(--border);
|
||||
}
|
||||
.ticker-block { flex: 1; }
|
||||
.ticker-eyebrow {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 7pt;
|
||||
font-size: 9pt;
|
||||
color: var(--brand);
|
||||
letter-spacing: 1.5pt;
|
||||
text-transform: uppercase;
|
||||
font-weight: 500;
|
||||
margin-bottom: 4pt;
|
||||
}
|
||||
.ticker-eyebrow::before {
|
||||
content: "";
|
||||
width: 8pt;
|
||||
height: 1.5pt;
|
||||
border-radius: 0.75pt;
|
||||
background: var(--brand);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.ticker-name {
|
||||
font-family: var(--serif);
|
||||
font-size: 24pt;
|
||||
font-weight: 500;
|
||||
color: var(--near-black);
|
||||
line-height: 1.15;
|
||||
margin-bottom: 4pt;
|
||||
}
|
||||
.ticker-sub {
|
||||
font-size: 10pt;
|
||||
color: var(--olive);
|
||||
line-height: 1.4;
|
||||
}
|
||||
.price-block {
|
||||
text-align: right;
|
||||
padding-top: 4pt;
|
||||
}
|
||||
.price-current {
|
||||
font-family: var(--serif);
|
||||
font-size: 28pt;
|
||||
font-weight: 500;
|
||||
color: var(--near-black);
|
||||
line-height: 1;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
.price-change {
|
||||
font-size: 10pt;
|
||||
font-weight: 500;
|
||||
margin-top: 2pt;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
.price-change.up { color: var(--positive); }
|
||||
.price-change.down { color: var(--negative); }
|
||||
.price-date {
|
||||
font-size: 9pt;
|
||||
color: var(--stone);
|
||||
margin-top: 2pt;
|
||||
}
|
||||
|
||||
/* ========== METRICS ========== */
|
||||
.metrics {
|
||||
display: flex;
|
||||
gap: 14pt;
|
||||
margin-bottom: 16pt;
|
||||
padding: 6pt 0;
|
||||
border-top: 0.3pt dotted var(--border);
|
||||
border-bottom: 0.3pt dotted var(--border);
|
||||
}
|
||||
.metric {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
gap: 4pt;
|
||||
}
|
||||
.metric-value {
|
||||
font-family: var(--serif);
|
||||
font-size: 16pt;
|
||||
font-weight: 500;
|
||||
color: var(--brand);
|
||||
line-height: 1;
|
||||
font-variant-numeric: tabular-nums;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.metric-label {
|
||||
font-size: 9pt;
|
||||
color: var(--olive);
|
||||
line-height: 1.3;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* ========== SECTIONS ========== */
|
||||
h2 {
|
||||
font-family: var(--serif);
|
||||
font-size: 16pt;
|
||||
font-weight: 500;
|
||||
color: var(--near-black);
|
||||
margin: 18pt 0 6pt 0;
|
||||
break-after: avoid;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: 13pt;
|
||||
font-weight: 500;
|
||||
color: var(--dark-warm);
|
||||
margin: 12pt 0 4pt 0;
|
||||
break-after: avoid;
|
||||
}
|
||||
|
||||
p { margin: 0 0 8pt 0; line-height: 1.5; widows: 2; orphans: 2; }
|
||||
|
||||
ul, ol {
|
||||
margin: 4pt 0 8pt 0;
|
||||
padding-left: 18pt;
|
||||
line-height: 1.5;
|
||||
}
|
||||
ul li::marker { color: var(--brand); }
|
||||
ol li::marker { color: var(--brand); font-weight: 500; }
|
||||
|
||||
/* ========== TAG ========== */
|
||||
.tag {
|
||||
display: inline-block;
|
||||
background: var(--tag-bg);
|
||||
color: var(--brand);
|
||||
font-size: 9pt;
|
||||
font-weight: 500;
|
||||
padding: 1pt 6pt;
|
||||
border-radius: 3pt;
|
||||
margin-right: 3pt;
|
||||
}
|
||||
|
||||
/* ========== TABLE (kami-table) ========== */
|
||||
table, .kami-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
font-size: 9.5pt;
|
||||
margin: 8pt 0;
|
||||
break-inside: avoid;
|
||||
}
|
||||
table th, .kami-table th {
|
||||
text-align: left;
|
||||
font-weight: 500;
|
||||
color: var(--dark-warm);
|
||||
padding: 5pt 8pt;
|
||||
border-bottom: 1pt solid var(--border);
|
||||
background: transparent;
|
||||
}
|
||||
table td, .kami-table td {
|
||||
padding: 4pt 8pt;
|
||||
border-bottom: 0.3pt solid var(--border-soft);
|
||||
vertical-align: top;
|
||||
}
|
||||
table.compact th, .kami-table.compact th { padding: 3pt 6pt; font-size: 8pt; }
|
||||
table.compact td, .kami-table.compact td { padding: 2pt 6pt; font-size: 8pt; line-height: 1.4; }
|
||||
table.financial td:not(:first-child), .kami-table.financial td:not(:first-child) {
|
||||
text-align: right; font-variant-numeric: tabular-nums;
|
||||
}
|
||||
table.financial th:not(:first-child), .kami-table.financial th:not(:first-child) { text-align: right; }
|
||||
table.striped tbody tr:nth-child(even) td, .kami-table.striped tbody tr:nth-child(even) td {
|
||||
background: var(--ivory);
|
||||
}
|
||||
table .total td, .kami-table .total td {
|
||||
font-weight: 500; border-top: 1pt solid var(--brand); border-bottom: none; color: var(--near-black);
|
||||
}
|
||||
|
||||
/* ========== CALLOUT ========== */
|
||||
.callout {
|
||||
background: var(--ivory);
|
||||
border-left: 2pt solid var(--brand);
|
||||
padding: 8pt 12pt;
|
||||
border-radius: 3pt;
|
||||
margin: 10pt 0;
|
||||
line-height: 1.5;
|
||||
break-inside: avoid;
|
||||
}
|
||||
.callout blockquote,
|
||||
.callout .quote {
|
||||
border-left: none;
|
||||
padding-left: 0;
|
||||
margin: 6pt 0;
|
||||
color: var(--olive);
|
||||
}
|
||||
.callout blockquote:first-child,
|
||||
.callout .quote:first-child { margin-top: 0; }
|
||||
.callout blockquote:last-child,
|
||||
.callout .quote:last-child { margin-bottom: 0; }
|
||||
|
||||
/* ========== FIGURE ========== */
|
||||
figure {
|
||||
margin: 12pt 0;
|
||||
break-inside: avoid;
|
||||
}
|
||||
figcaption {
|
||||
font-size: 9pt;
|
||||
color: var(--stone);
|
||||
margin-top: 4pt;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* ========== RISK MATRIX ========== */
|
||||
.risk-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 10pt;
|
||||
margin: 8pt 0;
|
||||
}
|
||||
.risk-item {
|
||||
padding: 6pt 10pt;
|
||||
background: var(--ivory);
|
||||
border-radius: 3pt;
|
||||
break-inside: avoid;
|
||||
}
|
||||
.risk-label {
|
||||
font-size: 9pt;
|
||||
font-weight: 500;
|
||||
color: var(--brand);
|
||||
margin-bottom: 2pt;
|
||||
}
|
||||
.risk-desc {
|
||||
font-size: 9pt;
|
||||
color: var(--olive);
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
/* ========== TWO-COLUMN ========== */
|
||||
.two-col {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 16pt;
|
||||
}
|
||||
|
||||
/* ========== ANALYST SUMMARY ========== */
|
||||
.analyst-box {
|
||||
background: var(--ivory);
|
||||
border-left: 1.4pt solid var(--brand);
|
||||
border-radius: 4pt;
|
||||
padding: 10pt 14pt;
|
||||
margin: 12pt 0;
|
||||
break-inside: avoid;
|
||||
}
|
||||
|
||||
/* ========== CHART PLACEHOLDER ========== */
|
||||
.chart-placeholder {
|
||||
background: var(--ivory);
|
||||
border-radius: 4pt;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: var(--stone);
|
||||
font-size: 9pt;
|
||||
}
|
||||
.analyst-label {
|
||||
font-size: 9pt;
|
||||
color: var(--brand);
|
||||
letter-spacing: 0.5pt;
|
||||
text-transform: uppercase;
|
||||
margin-bottom: 4pt;
|
||||
}
|
||||
|
||||
/* ========== FOOTER ========== */
|
||||
.report-footer {
|
||||
margin-top: 16pt;
|
||||
padding-top: 6pt;
|
||||
border-top: 0.3pt dotted var(--border);
|
||||
font-size: 9pt;
|
||||
color: var(--stone);
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.page-break { break-before: page; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<!-- ========== HEADER ========== -->
|
||||
<div class="report-header">
|
||||
<div class="ticker-block">
|
||||
<div class="ticker-eyebrow">{{EYEBROW · 如 "个股研报" / "估值分析" / "投资备忘"}}</div>
|
||||
<div class="ticker-name">{{公司名称}} <span style="font-size: 14pt; color: var(--stone);">{{股票代码}}</span></div>
|
||||
<div class="ticker-sub">{{行业}} · {{交易所}} · {{一句核心判断}}</div>
|
||||
</div>
|
||||
<div class="price-block">
|
||||
<div class="price-current">{{当前价格}}</div>
|
||||
<div class="price-change up">{{涨跌幅 如 "+12.3%"}}</div>
|
||||
<div class="price-date">{{日期}}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ========== METRICS ========== -->
|
||||
<div class="metrics">
|
||||
<div class="metric">
|
||||
<div class="metric-value">{{数字}}</div>
|
||||
<div class="metric-label">市值</div>
|
||||
</div>
|
||||
<div class="metric">
|
||||
<div class="metric-value">{{数字}}</div>
|
||||
<div class="metric-label">P/E</div>
|
||||
</div>
|
||||
<div class="metric">
|
||||
<div class="metric-value">{{数字}}</div>
|
||||
<div class="metric-label">营收</div>
|
||||
</div>
|
||||
<div class="metric">
|
||||
<div class="metric-value">{{数字}}</div>
|
||||
<div class="metric-label">利润率</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ========== INVESTMENT THESIS ========== -->
|
||||
<h2>投资逻辑</h2>
|
||||
<p>{{2-3 句核心投资论点。用 <span class="hl">关键数据</span> 支撑判断。这段是整份研报的灵魂。}}</p>
|
||||
|
||||
<div class="callout">
|
||||
{{一句话总结:买入/持有/观望的核心理由。}}
|
||||
</div>
|
||||
|
||||
<!-- ========== CHART (嵌入 SVG 图表) ========== -->
|
||||
<h2>价格走势</h2>
|
||||
<figure>
|
||||
<!-- 从 candlestick.html 或 line-chart.html 提取 <svg> 嵌入此处 -->
|
||||
<div class="chart-placeholder" style="height: 60mm;">
|
||||
[K 线图 / 价格走势图占位 · 从 diagrams/ 提取 SVG 嵌入]
|
||||
</div>
|
||||
<figcaption>{{图表标题,如 "近 20 个交易日价格走势"}}</figcaption>
|
||||
</figure>
|
||||
|
||||
<!-- ========== FINANCIALS ========== -->
|
||||
<h2>财务概览</h2>
|
||||
<table class="financial striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>指标</th>
|
||||
<th>FY2023</th>
|
||||
<th>FY2024</th>
|
||||
<th>FY2025E</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>营收</td>
|
||||
<td>{{数据}}</td>
|
||||
<td>{{数据}}</td>
|
||||
<td>{{数据}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>净利润</td>
|
||||
<td>{{数据}}</td>
|
||||
<td>{{数据}}</td>
|
||||
<td>{{数据}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>EPS</td>
|
||||
<td>{{数据}}</td>
|
||||
<td>{{数据}}</td>
|
||||
<td>{{数据}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>毛利率</td>
|
||||
<td>{{数据}}</td>
|
||||
<td>{{数据}}</td>
|
||||
<td>{{数据}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>ROE</td>
|
||||
<td>{{数据}}</td>
|
||||
<td>{{数据}}</td>
|
||||
<td>{{数据}}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<!-- ========== REVENUE BREAKDOWN (可选图表) ========== -->
|
||||
<h2>收入结构</h2>
|
||||
<figure>
|
||||
<div class="chart-placeholder" style="height: 50mm;">
|
||||
[营收分解图占位 · 环形图或瀑布图]
|
||||
</div>
|
||||
<figcaption>{{图表标题}}</figcaption>
|
||||
</figure>
|
||||
|
||||
<!-- ========== COMPETITIVE POSITIONING ========== -->
|
||||
<div class="page-break"></div>
|
||||
<h2>竞争格局</h2>
|
||||
<p>{{行业格局概述,1-2 段。}}</p>
|
||||
|
||||
<table class="financial compact striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>公司</th>
|
||||
<th>市值</th>
|
||||
<th>P/E</th>
|
||||
<th>营收增速</th>
|
||||
<th>利润率</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><strong>{{目标公司}}</strong></td>
|
||||
<td>{{数据}}</td>
|
||||
<td>{{数据}}</td>
|
||||
<td>{{数据}}</td>
|
||||
<td>{{数据}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{竞品 A}}</td>
|
||||
<td>{{数据}}</td>
|
||||
<td>{{数据}}</td>
|
||||
<td>{{数据}}</td>
|
||||
<td>{{数据}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{竞品 B}}</td>
|
||||
<td>{{数据}}</td>
|
||||
<td>{{数据}}</td>
|
||||
<td>{{数据}}</td>
|
||||
<td>{{数据}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{竞品 C}}</td>
|
||||
<td>{{数据}}</td>
|
||||
<td>{{数据}}</td>
|
||||
<td>{{数据}}</td>
|
||||
<td>{{数据}}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<!-- ========== RISKS ========== -->
|
||||
<h2>风险提示</h2>
|
||||
<div class="risk-grid">
|
||||
<div class="risk-item">
|
||||
<div class="risk-label">{{风险类型 1}}</div>
|
||||
<div class="risk-desc">{{风险描述}}</div>
|
||||
</div>
|
||||
<div class="risk-item">
|
||||
<div class="risk-label">{{风险类型 2}}</div>
|
||||
<div class="risk-desc">{{风险描述}}</div>
|
||||
</div>
|
||||
<div class="risk-item">
|
||||
<div class="risk-label">{{风险类型 3}}</div>
|
||||
<div class="risk-desc">{{风险描述}}</div>
|
||||
</div>
|
||||
<div class="risk-item">
|
||||
<div class="risk-label">{{风险类型 4}}</div>
|
||||
<div class="risk-desc">{{风险描述}}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ========== ANALYST SUMMARY ========== -->
|
||||
<div class="analyst-box">
|
||||
<div class="analyst-label">分析师总结</div>
|
||||
{{3-5 句总结。包含目标价(如有)、评级建议、核心催化剂、关键假设。}}
|
||||
</div>
|
||||
|
||||
<!-- ========== FOOTER ========== -->
|
||||
<div class="report-footer">
|
||||
<span>{{机构名 / 免责声明}}</span>
|
||||
<span>{{作者}} · {{日期}}</span>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,76 @@
|
||||
# {{PRODUCT_NAME}} - Full Knowledge Base
|
||||
|
||||
<!-- Placeholders to fill before publishing:
|
||||
{{PRODUCT_NAME}}, {{TAGLINE_LONG_ONE_SENTENCE}}, {{AUTHOR_NAME}}, {{AUTHOR_URL}},
|
||||
{{SOURCE_REPO_URL}}, {{OS_REQUIREMENT}}, {{ONE_PARAGRAPH_POSITIONING}},
|
||||
{{ONE_PARAGRAPH_PRICE_TERMS_REFUND_LICENSE_SEATS}}, {{FEATURE_*_NAME}}, {{ONE_PARAGRAPH_FEATURE_*}},
|
||||
{{COMPETITOR_A}}, {{COMPETITOR_B}}, {{GENERIC_TOOL}}, {{TWO_OR_THREE_SENTENCES}},
|
||||
{{ANSWER_*_SENTENCES}}, {{COMMON_TOOL_USERS_ALREADY_USE}}, {{PRICE_AMOUNT}}, {{SITE_ORIGIN}} -->
|
||||
|
||||
> {{TAGLINE_LONG_ONE_SENTENCE}}
|
||||
|
||||
This is the long-form companion to llms.txt. Keep llms.txt as the short summary; put feature details, FAQ, and comparison here so AI assistants get accurate answers without having to scrape the site.
|
||||
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
{{PRODUCT_NAME}} is {{ONE_PARAGRAPH_POSITIONING}}.
|
||||
|
||||
Author: {{AUTHOR_NAME}} ({{AUTHOR_URL}}).
|
||||
Source: {{SOURCE_REPO_URL}}.
|
||||
Platform: {{OS_REQUIREMENT}}.
|
||||
|
||||
## Pricing
|
||||
|
||||
{{ONE_PARAGRAPH_PRICE_TERMS_REFUND_LICENSE_SEATS}}
|
||||
|
||||
## Features
|
||||
|
||||
### {{FEATURE_1_NAME}}
|
||||
{{ONE_PARAGRAPH_FEATURE_1}}. Cover: what it does, when to use it, the most common output it produces, and any limit the user should know.
|
||||
|
||||
### {{FEATURE_2_NAME}}
|
||||
{{ONE_PARAGRAPH_FEATURE_2}}
|
||||
|
||||
### {{FEATURE_3_NAME}}
|
||||
{{ONE_PARAGRAPH_FEATURE_3}}
|
||||
|
||||
Repeat one subsection per feature. Aim for 3-7 features. Skip marketing adjectives - LLMs index facts, not enthusiasm.
|
||||
|
||||
## How {{PRODUCT_NAME}} differs
|
||||
|
||||
### vs {{COMPETITOR_A}}
|
||||
{{TWO_OR_THREE_SENTENCES}}. State the concrete differentiator (workflow, output, distribution model, price). Avoid "we are better" framing.
|
||||
|
||||
### vs {{COMPETITOR_B}}
|
||||
{{TWO_OR_THREE_SENTENCES}}.
|
||||
|
||||
### vs general-purpose tools (e.g. {{GENERIC_TOOL}})
|
||||
{{TWO_OR_THREE_SENTENCES}}.
|
||||
|
||||
## FAQ
|
||||
|
||||
### What is {{PRODUCT_NAME}} for?
|
||||
{{ANSWER_3_SENTENCES}}.
|
||||
|
||||
### Who is it not for?
|
||||
{{ANSWER_2_SENTENCES}}. Be specific about workflows that are out of scope, so AI assistants do not over-recommend.
|
||||
|
||||
### How does it integrate with {{COMMON_TOOL_USERS_ALREADY_USE}}?
|
||||
{{ANSWER_2_SENTENCES}}.
|
||||
|
||||
### What does the {{PRICE_AMOUNT}} buy?
|
||||
{{ANSWER_2_SENTENCES}} including seat count, updates, and refund window.
|
||||
|
||||
### How do I get support?
|
||||
{{ANSWER_1_SENTENCE}} with a real contact channel.
|
||||
|
||||
## Links
|
||||
|
||||
- Website: {{SITE_ORIGIN}}
|
||||
- Website (Chinese): {{SITE_ORIGIN}}/zh/
|
||||
- Documentation: {{SITE_ORIGIN}}/docs
|
||||
- Help: {{SITE_ORIGIN}}/help
|
||||
- Source: {{SOURCE_REPO_URL}}
|
||||
- Releases: {{SITE_ORIGIN}}/releases
|
||||
@@ -0,0 +1,34 @@
|
||||
# {{PRODUCT_NAME}}
|
||||
|
||||
<!-- Placeholders to fill before publishing:
|
||||
{{PRODUCT_NAME}}, {{TAGLINE_LONG_ONE_SENTENCE}}, {{AUTHOR_NAME}}, {{AUTHOR_URL}},
|
||||
{{ONE_PARAGRAPH_POSITIONING}}, {{COMPETITOR_A}}, {{COMPETITOR_B}}, {{ONE_LINE_CONTRAST}},
|
||||
{{ONE_LINE_PRICE_AND_TERMS}}, {{LICENSE}}, {{SITE_ORIGIN}}, {{SOURCE_REPO_URL}}, {{ONE_LINE}} -->
|
||||
|
||||
> {{TAGLINE_LONG_ONE_SENTENCE}}
|
||||
|
||||
## Author
|
||||
- Name: {{AUTHOR_NAME}}
|
||||
- Site: {{AUTHOR_URL}}
|
||||
|
||||
## What is {{PRODUCT_NAME}}
|
||||
{{PRODUCT_NAME}} is {{ONE_PARAGRAPH_POSITIONING}}. Include who it is for, the core job it does, and the one differentiator that separates it from common alternatives (name the alternatives by their real names; do not write "competitors"). Mention platform requirements at the end of this paragraph.
|
||||
|
||||
## How it differs
|
||||
- vs {{COMPETITOR_A}}: {{ONE_LINE_CONTRAST}}
|
||||
- vs {{COMPETITOR_B}}: {{ONE_LINE_CONTRAST}}
|
||||
- vs general-purpose tools: {{ONE_LINE_CONTRAST}}
|
||||
|
||||
## Pricing
|
||||
{{ONE_LINE_PRICE_AND_TERMS}}. If free, write "Free. Open source under the {{LICENSE}} license." Mention any trial scope explicitly.
|
||||
|
||||
## Key pages
|
||||
- [Documentation]({{SITE_ORIGIN}}/docs): {{ONE_LINE}}
|
||||
- [Help]({{SITE_ORIGIN}}/help): {{ONE_LINE}}
|
||||
- [Full knowledge base]({{SITE_ORIGIN}}/llms-full.txt): comprehensive feature details for AI assistants
|
||||
- [Releases]({{SITE_ORIGIN}}/releases): version history and changelogs
|
||||
|
||||
## Links
|
||||
- Website: {{SITE_ORIGIN}}
|
||||
- Website (Chinese): {{SITE_ORIGIN}}/zh/
|
||||
- Source: {{SOURCE_REPO_URL}}
|
||||
@@ -0,0 +1,63 @@
|
||||
# Companion to landing-page.html / landing-page-en.html.
|
||||
# Drop into your repo root as robots.txt (without the .example suffix) and replace example.com with your domain.
|
||||
# AI-crawler allowlist mirrors what Kami's own site already ships - keep these open so Claude, ChatGPT, Perplexity, and Apple Intelligence can index your product.
|
||||
|
||||
User-agent: *
|
||||
Allow: /
|
||||
|
||||
# Add Disallow lines for paths that should not be indexed, e.g.
|
||||
# Disallow: /api/
|
||||
# Disallow: /admin/
|
||||
|
||||
Sitemap: https://example.com/sitemap.xml
|
||||
|
||||
# Search engines
|
||||
User-agent: Googlebot
|
||||
Allow: /
|
||||
|
||||
User-agent: Bingbot
|
||||
Allow: /
|
||||
|
||||
User-agent: Baiduspider
|
||||
Allow: /
|
||||
|
||||
User-agent: Applebot
|
||||
Allow: /
|
||||
|
||||
# AI training crawlers
|
||||
User-agent: GPTBot
|
||||
Allow: /
|
||||
|
||||
User-agent: ChatGPT-User
|
||||
Allow: /
|
||||
|
||||
User-agent: ClaudeBot
|
||||
Allow: /
|
||||
|
||||
User-agent: anthropic-ai
|
||||
Allow: /
|
||||
|
||||
User-agent: PerplexityBot
|
||||
Allow: /
|
||||
|
||||
User-agent: Google-Extended
|
||||
Allow: /
|
||||
|
||||
User-agent: CCBot
|
||||
Allow: /
|
||||
|
||||
# AI search bots (retrieval, not training)
|
||||
User-agent: OAI-SearchBot
|
||||
Allow: /
|
||||
|
||||
User-agent: Claude-SearchBot
|
||||
Allow: /
|
||||
|
||||
User-agent: Claude-User
|
||||
Allow: /
|
||||
|
||||
User-agent: Perplexity-User
|
||||
Allow: /
|
||||
|
||||
User-agent: DuckAssistBot
|
||||
Allow: /
|
||||
@@ -0,0 +1,53 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
Companion to landing-page.html / landing-page-en.html.
|
||||
Drop into your repo root as sitemap.xml (without the .example suffix) and replace example.com with your domain.
|
||||
Drop any <xhtml:link> line whose locale is not shipped. Update <lastmod> on each public release.
|
||||
-->
|
||||
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"
|
||||
xmlns:xhtml="http://www.w3.org/1999/xhtml">
|
||||
<url>
|
||||
<loc>https://example.com/</loc>
|
||||
<xhtml:link rel="alternate" hreflang="en" href="https://example.com/"/>
|
||||
<xhtml:link rel="alternate" hreflang="zh-CN" href="https://example.com/zh/"/>
|
||||
<xhtml:link rel="alternate" hreflang="zh-Hant" href="https://example.com/tw/"/>
|
||||
<xhtml:link rel="alternate" hreflang="ja" href="https://example.com/ja/"/>
|
||||
<xhtml:link rel="alternate" hreflang="ko" href="https://example.com/ko/"/>
|
||||
<xhtml:link rel="alternate" hreflang="x-default" href="https://example.com/"/>
|
||||
<lastmod>2026-01-01</lastmod>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>1.0</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://example.com/zh/</loc>
|
||||
<xhtml:link rel="alternate" hreflang="en" href="https://example.com/"/>
|
||||
<xhtml:link rel="alternate" hreflang="zh-CN" href="https://example.com/zh/"/>
|
||||
<lastmod>2026-01-01</lastmod>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>0.9</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://example.com/tw/</loc>
|
||||
<xhtml:link rel="alternate" hreflang="en" href="https://example.com/"/>
|
||||
<xhtml:link rel="alternate" hreflang="zh-Hant" href="https://example.com/tw/"/>
|
||||
<lastmod>2026-01-01</lastmod>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>0.8</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://example.com/ja/</loc>
|
||||
<xhtml:link rel="alternate" hreflang="en" href="https://example.com/"/>
|
||||
<xhtml:link rel="alternate" hreflang="ja" href="https://example.com/ja/"/>
|
||||
<lastmod>2026-01-01</lastmod>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>0.8</priority>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://example.com/ko/</loc>
|
||||
<xhtml:link rel="alternate" hreflang="en" href="https://example.com/"/>
|
||||
<xhtml:link rel="alternate" hreflang="ko" href="https://example.com/ko/"/>
|
||||
<lastmod>2026-01-01</lastmod>
|
||||
<changefreq>monthly</changefreq>
|
||||
<priority>0.8</priority>
|
||||
</url>
|
||||
</urlset>
|
||||
@@ -0,0 +1,37 @@
|
||||
{
|
||||
"rewrites": [
|
||||
{ "source": "/zh", "destination": "/index-zh.html" },
|
||||
{ "source": "/zh/", "destination": "/index-zh.html" },
|
||||
{ "source": "/tw", "destination": "/index-tw.html" },
|
||||
{ "source": "/tw/", "destination": "/index-tw.html" },
|
||||
{ "source": "/ja", "destination": "/index-ja.html" },
|
||||
{ "source": "/ja/", "destination": "/index-ja.html" },
|
||||
{ "source": "/ko", "destination": "/index-ko.html" },
|
||||
{ "source": "/ko/", "destination": "/index-ko.html" }
|
||||
],
|
||||
|
||||
"redirects": [
|
||||
{
|
||||
"source": "/(.*)",
|
||||
"has": [{ "type": "host", "value": "www.example.com" }],
|
||||
"destination": "https://example.com/$1",
|
||||
"permanent": true
|
||||
}
|
||||
],
|
||||
|
||||
"headers": [
|
||||
{
|
||||
"source": "/(.*)",
|
||||
"headers": [
|
||||
{ "key": "X-Content-Type-Options", "value": "nosniff" },
|
||||
{ "key": "X-Frame-Options", "value": "DENY" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"source": "/:path*.(png|jpg|gif|ico|svg|webp)",
|
||||
"headers": [
|
||||
{ "key": "Cache-Control", "value": "public, max-age=31536000, immutable" }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,289 @@
|
||||
<!DOCTYPE html>
|
||||
<!-- ==================================================================
|
||||
LETTER · English · parchment design system
|
||||
A4 formal letter / memo / recommendation / resignation / statement
|
||||
Characteristics: minimal, serif-led, slightly larger body type
|
||||
================================================================== -->
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>{{LETTER_SUBJECT}}</title>
|
||||
<meta name="author" content="{{AUTHOR}}">
|
||||
<meta name="description" content="{{DESCRIPTION}}">
|
||||
<meta name="keywords" content="{{KEYWORDS}}">
|
||||
<meta name="generator" content="Kami">
|
||||
<style>
|
||||
@font-face { font-family: "JetBrains Mono"; src: url("../fonts/JetBrainsMono.woff2") format("woff2"); font-weight: 400; font-style: normal; }
|
||||
@font-face { font-family: "JetBrains Mono"; src: url("../fonts/JetBrainsMono.woff2") format("woff2"); font-weight: 500; font-style: normal; }
|
||||
|
||||
@page {
|
||||
size: A4;
|
||||
margin: 25mm 25mm 25mm 25mm;
|
||||
background: #f5f4ed;
|
||||
}
|
||||
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
|
||||
:root {
|
||||
--parchment: #f5f4ed;
|
||||
--near-black:#141413;
|
||||
--dark-warm: #3d3d3a;
|
||||
--olive: #504e49;
|
||||
--stone: #6b6a64;
|
||||
--brand: #1B365D;
|
||||
--ivory: #faf9f5;
|
||||
--border: #e8e6dc;
|
||||
--border-soft:#e5e3d8;
|
||||
|
||||
--serif: Charter, Georgia,
|
||||
Palatino, "Times New Roman", serif;
|
||||
--sans: var(--serif);
|
||||
}
|
||||
|
||||
html, body { background: var(--parchment); }
|
||||
|
||||
@media screen {
|
||||
body { max-width: 210mm; margin: 0 auto; padding: 25mm; }
|
||||
}
|
||||
|
||||
body {
|
||||
color: var(--near-black);
|
||||
font-family: var(--serif);
|
||||
font-size: 11.5pt;
|
||||
line-height: 1.55;
|
||||
}
|
||||
|
||||
.letterhead {
|
||||
text-align: right;
|
||||
margin-bottom: 30pt;
|
||||
font-size: 10pt;
|
||||
line-height: 1.55;
|
||||
color: var(--olive);
|
||||
}
|
||||
.letterhead .sender-name {
|
||||
font-size: 13pt;
|
||||
font-weight: 500;
|
||||
color: var(--near-black);
|
||||
margin-bottom: 4pt;
|
||||
}
|
||||
.letterhead a { color: var(--dark-warm); text-decoration: none; }
|
||||
|
||||
.date {
|
||||
text-align: right;
|
||||
font-size: 10.5pt;
|
||||
color: var(--olive);
|
||||
margin-bottom: 24pt;
|
||||
font-variant-numeric: tabular-nums;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.recipient {
|
||||
margin-bottom: 24pt;
|
||||
line-height: 1.5;
|
||||
}
|
||||
.recipient .to {
|
||||
font-family: var(--sans);
|
||||
font-size: 9pt;
|
||||
color: var(--stone);
|
||||
letter-spacing: 0.5pt;
|
||||
text-transform: uppercase;
|
||||
margin-bottom: 3pt;
|
||||
font-weight: 500;
|
||||
}
|
||||
.recipient .name {
|
||||
font-size: 12pt;
|
||||
font-weight: 500;
|
||||
color: var(--near-black);
|
||||
}
|
||||
.recipient .org {
|
||||
font-size: 10.5pt;
|
||||
color: var(--olive);
|
||||
}
|
||||
|
||||
.subject {
|
||||
margin-bottom: 24pt;
|
||||
border-left: 2.5pt solid var(--brand);
|
||||
border-radius: 1.5pt;
|
||||
padding-left: 8pt;
|
||||
}
|
||||
.subject .label {
|
||||
font-family: var(--sans);
|
||||
font-size: 9pt;
|
||||
color: var(--brand);
|
||||
letter-spacing: 1pt;
|
||||
text-transform: uppercase;
|
||||
margin-bottom: 3pt;
|
||||
font-weight: 500;
|
||||
}
|
||||
.subject .title {
|
||||
font-size: 16pt;
|
||||
font-weight: 500;
|
||||
color: var(--near-black);
|
||||
line-height: 1.25;
|
||||
letter-spacing: -0.2pt;
|
||||
}
|
||||
|
||||
.salutation {
|
||||
margin-bottom: 18pt;
|
||||
font-size: 12pt;
|
||||
color: var(--near-black);
|
||||
}
|
||||
|
||||
.body-content p {
|
||||
margin-bottom: 12pt;
|
||||
text-align: left;
|
||||
line-height: 1.55;
|
||||
}
|
||||
.body-content p:first-child { text-indent: 0; }
|
||||
.body-content p.indent { text-indent: 1.5em; }
|
||||
|
||||
.body-content .hl {
|
||||
color: var(--brand);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.closing {
|
||||
margin-top: 28pt;
|
||||
line-height: 1.55;
|
||||
}
|
||||
.closing .regards {
|
||||
margin-bottom: 30pt;
|
||||
color: var(--near-black);
|
||||
}
|
||||
.closing .signature {
|
||||
font-size: 14pt;
|
||||
font-weight: 500;
|
||||
color: var(--near-black);
|
||||
margin-bottom: 3pt;
|
||||
}
|
||||
.closing .signoff-meta {
|
||||
font-family: var(--sans);
|
||||
font-size: 10pt;
|
||||
color: var(--olive);
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.attachments {
|
||||
margin-top: 36pt;
|
||||
padding-top: 10pt;
|
||||
border-top: 0.5pt dotted var(--border);
|
||||
font-family: var(--sans);
|
||||
font-size: 9.5pt;
|
||||
color: var(--stone);
|
||||
line-height: 1.55;
|
||||
}
|
||||
.attachments .label {
|
||||
color: var(--brand);
|
||||
margin-right: 6pt;
|
||||
font-weight: 500;
|
||||
letter-spacing: 0.5pt;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
/* Memo variant: sans-led, no indent */
|
||||
body.memo {
|
||||
font-family: var(--sans);
|
||||
font-size: 10.5pt;
|
||||
line-height: 1.55;
|
||||
}
|
||||
body.memo .body-content p { text-indent: 0; margin-bottom: 10pt; }
|
||||
|
||||
/* TABLE (kami-table) */
|
||||
table, .kami-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
font-size: 10pt;
|
||||
margin: 10pt 0;
|
||||
break-inside: avoid;
|
||||
}
|
||||
table th, .kami-table th {
|
||||
text-align: left;
|
||||
font-weight: 500;
|
||||
color: var(--dark-warm);
|
||||
padding: 5pt 8pt;
|
||||
border-bottom: 1pt solid var(--border);
|
||||
background: transparent;
|
||||
}
|
||||
table td, .kami-table td {
|
||||
padding: 4pt 8pt;
|
||||
border-bottom: 0.3pt solid var(--border-soft);
|
||||
vertical-align: top;
|
||||
}
|
||||
table.compact th, .kami-table.compact th { padding: 3pt 6pt; font-size: 8pt; }
|
||||
table.compact td, .kami-table.compact td { padding: 2pt 6pt; font-size: 8pt; line-height: 1.4; }
|
||||
table.financial td:not(:first-child), .kami-table.financial td:not(:first-child) {
|
||||
text-align: right; font-variant-numeric: tabular-nums;
|
||||
}
|
||||
table.financial th:not(:first-child), .kami-table.financial th:not(:first-child) { text-align: right; }
|
||||
table.striped tbody tr:nth-child(even) td, .kami-table.striped tbody tr:nth-child(even) td {
|
||||
background: var(--ivory);
|
||||
}
|
||||
table .total td, .kami-table .total td {
|
||||
font-weight: 500; border-top: 1pt solid var(--brand); border-bottom: none; color: var(--near-black);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="letterhead">
|
||||
<div class="sender-name">{{SENDER_NAME}}</div>
|
||||
<div>{{SENDER_ADDRESS / DEPARTMENT / ORGANIZATION}}</div>
|
||||
<div>{{PHONE}} · <a href="mailto:{{EMAIL}}">{{EMAIL}}</a></div>
|
||||
</div>
|
||||
|
||||
<div class="date">{{DATE, e.g. April 18, 2026}}</div>
|
||||
|
||||
<div class="recipient">
|
||||
<div class="to">To</div>
|
||||
<div class="name">{{RECIPIENT_NAME_OR_TITLE}}</div>
|
||||
<div class="org">{{RECIPIENT_ORG / DEPARTMENT}}</div>
|
||||
</div>
|
||||
|
||||
<div class="subject">
|
||||
<div class="label">Re</div>
|
||||
<div class="title">{{One-sentence subject line: what this letter is about.}}</div>
|
||||
</div>
|
||||
|
||||
<div class="salutation">{{Dear {{NAME}},}}</div>
|
||||
|
||||
<div class="body-content">
|
||||
|
||||
<p>
|
||||
{{Paragraph 1: state the purpose. Why you are writing and the core intent.
|
||||
No preamble. Two sentences at most so the reader knows what you want from this letter.}}
|
||||
</p>
|
||||
|
||||
<p>
|
||||
{{Paragraph 2: elaborate. Background, reasoning, evidence. Use
|
||||
<span class="hl">brand-color emphasis</span> on the single most important point.}}
|
||||
</p>
|
||||
|
||||
<p>
|
||||
{{Paragraph 3: be specific. What you want the reader to do, by when,
|
||||
and how to reach you. Give the action a clear exit.}}
|
||||
</p>
|
||||
|
||||
<p>
|
||||
{{Paragraph 4 (optional): close. Express anticipation, gratitude, or a courteous sign-off line.}}
|
||||
</p>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="closing">
|
||||
<div class="regards">
|
||||
{{Closing - "Best regards," / "Sincerely," / "Warm regards,"}}
|
||||
</div>
|
||||
|
||||
<div class="signature">{{HANDWRITTEN_NAME_OR_SIGNATURE}}</div>
|
||||
<div class="signoff-meta">
|
||||
{{TITLE · DEPARTMENT}}<br>
|
||||
{{DATE (optional restatement)}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="attachments">
|
||||
<span class="label">Enclosures</span>
|
||||
{{List - ① Attachment 1 · ② Attachment 2}}
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,317 @@
|
||||
<!DOCTYPE html>
|
||||
<!-- ==================================================================
|
||||
LETTER TEMPLATE (KO) · parchment design system
|
||||
A4 공식 서한 / 메모 / 추천서 / 사직서 / 개인 성명서
|
||||
특징: 극도로 심플, serif 주도, 자간 여유, 단락 간격 넉넉
|
||||
변경 후 실행: python3 -c "from weasyprint import HTML; HTML('letter-ko.html').write_pdf('out.pdf')"
|
||||
목표: 페이지 수 == 1
|
||||
================================================================== -->
|
||||
<html lang="ko">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>{{서한 주제}}</title>
|
||||
<meta name="author" content="{{작성자}}">
|
||||
<meta name="description" content="{{요약}}">
|
||||
<meta name="keywords" content="{{키워드}}">
|
||||
<meta name="generator" content="Kami">
|
||||
<style>
|
||||
/* Regular weight */
|
||||
@font-face {
|
||||
font-family: "Source Han Serif K";
|
||||
src: url("../fonts/SourceHanSerifKR-Regular.otf") format("opentype"),
|
||||
url("https://cdn.jsdelivr.net/gh/tw93/Kami@main/assets/fonts/SourceHanSerifKR-Regular.otf") format("opentype");
|
||||
font-weight: 400;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
/* Medium weight */
|
||||
@font-face {
|
||||
font-family: "Source Han Serif K";
|
||||
src: url("../fonts/SourceHanSerifKR-Medium.otf") format("opentype"),
|
||||
url("https://cdn.jsdelivr.net/gh/tw93/Kami@main/assets/fonts/SourceHanSerifKR-Medium.otf") format("opentype");
|
||||
font-weight: 500;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
@page {
|
||||
size: A4;
|
||||
margin: 25mm 25mm 25mm 25mm;
|
||||
background: #f5f4ed;
|
||||
}
|
||||
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
|
||||
:root {
|
||||
--parchment: #f5f4ed;
|
||||
--near-black:#141413;
|
||||
--dark-warm: #3d3d3a;
|
||||
--olive: #504e49;
|
||||
--stone: #6b6a64;
|
||||
--brand: #1B365D;
|
||||
--ivory: #faf9f5;
|
||||
--border: #e8e6dc;
|
||||
--border-soft:#e5e3d8;
|
||||
--serif: "Source Han Serif K", "Source Han Serif KR", "Noto Serif KR", "Apple SD Gothic Neo", "AppleMyungjo", Charter, Georgia, serif;
|
||||
--sans: var(--serif);
|
||||
}
|
||||
|
||||
html, body { background: var(--parchment); }
|
||||
|
||||
@media screen {
|
||||
body { max-width: 210mm; margin: 0 auto; padding: 25mm; }
|
||||
}
|
||||
|
||||
body {
|
||||
color: var(--near-black);
|
||||
font-family: var(--serif);
|
||||
font-size: 11.5pt;
|
||||
line-height: 1.55;
|
||||
letter-spacing: 0.3pt;
|
||||
font-synthesis: none;
|
||||
}
|
||||
|
||||
/* ========== LETTERHEAD ========== */
|
||||
.letterhead {
|
||||
text-align: right;
|
||||
margin-bottom: 30pt;
|
||||
font-size: 10pt;
|
||||
line-height: 1.55;
|
||||
color: var(--olive);
|
||||
}
|
||||
.letterhead .sender-name {
|
||||
font-size: 13pt;
|
||||
font-weight: 500;
|
||||
color: var(--near-black);
|
||||
margin-bottom: 4pt;
|
||||
}
|
||||
.letterhead a {
|
||||
color: var(--dark-warm);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
/* ========== META ========== */
|
||||
.date {
|
||||
text-align: right;
|
||||
font-size: 10.5pt;
|
||||
color: var(--olive);
|
||||
margin-bottom: 24pt;
|
||||
font-variant-numeric: tabular-nums;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.recipient {
|
||||
margin-bottom: 24pt;
|
||||
line-height: 1.5;
|
||||
}
|
||||
.recipient .to {
|
||||
font-size: 10pt;
|
||||
color: var(--stone);
|
||||
letter-spacing: 0.5pt;
|
||||
text-transform: uppercase;
|
||||
margin-bottom: 3pt;
|
||||
}
|
||||
.recipient .name {
|
||||
font-size: 12pt;
|
||||
font-weight: 500;
|
||||
color: var(--near-black);
|
||||
}
|
||||
.recipient .org {
|
||||
font-size: 10.5pt;
|
||||
color: var(--olive);
|
||||
}
|
||||
|
||||
/* ========== SUBJECT ========== */
|
||||
.subject {
|
||||
margin-bottom: 24pt;
|
||||
border-left: 2.5pt solid var(--brand);
|
||||
border-radius: 1.5pt;
|
||||
padding-left: 8pt;
|
||||
}
|
||||
.subject .label {
|
||||
font-family: var(--sans);
|
||||
font-size: 9pt;
|
||||
color: var(--brand);
|
||||
letter-spacing: 1pt;
|
||||
text-transform: uppercase;
|
||||
margin-bottom: 3pt;
|
||||
}
|
||||
/* KO locale tuning: Source Han Serif K hangul glyphs have a smaller optical body
|
||||
than TsangerJinKai. Numerics inherit the CN baseline (verified during the
|
||||
one-pager-ko pilot, see references/design.md). */
|
||||
.subject .title {
|
||||
font-size: 15pt;
|
||||
font-weight: 500;
|
||||
color: var(--near-black);
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
/* ========== SALUTATION ========== */
|
||||
.salutation {
|
||||
margin-bottom: 18pt;
|
||||
font-size: 12pt;
|
||||
color: var(--near-black);
|
||||
}
|
||||
|
||||
/* ========== BODY ========== */
|
||||
.body-content p {
|
||||
margin-bottom: 14pt;
|
||||
line-height: 1.55;
|
||||
}
|
||||
.body-content p:first-child { }
|
||||
.body-content p.no-indent { text-indent: 0; }
|
||||
|
||||
.body-content .hl {
|
||||
color: var(--brand);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* ========== CLOSING ========== */
|
||||
.closing {
|
||||
margin-top: 30pt;
|
||||
line-height: 1.55;
|
||||
}
|
||||
.closing .regards {
|
||||
margin-bottom: 30pt;
|
||||
color: var(--near-black);
|
||||
}
|
||||
.closing .signature {
|
||||
font-size: 14pt;
|
||||
font-weight: 500;
|
||||
color: var(--near-black);
|
||||
margin-bottom: 3pt;
|
||||
}
|
||||
.closing .signoff-meta {
|
||||
font-size: 10pt;
|
||||
color: var(--olive);
|
||||
}
|
||||
|
||||
/* ========== ATTACHMENT ========== */
|
||||
.attachments {
|
||||
margin-top: 36pt;
|
||||
padding-top: 10pt;
|
||||
border-top: 0.5pt dotted var(--border);
|
||||
font-size: 9.5pt;
|
||||
color: var(--stone);
|
||||
font-family: var(--sans);
|
||||
line-height: 1.55;
|
||||
}
|
||||
.attachments .label {
|
||||
color: var(--brand);
|
||||
margin-right: 6pt;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* Memo variant: removes indent, uses sans chain */
|
||||
body.memo {
|
||||
font-family: var(--sans);
|
||||
line-height: 1.55;
|
||||
}
|
||||
body.memo .body-content p { text-indent: 0; margin-bottom: 10pt; }
|
||||
|
||||
/* ========== TABLE (kami-table) ========== */
|
||||
table, .kami-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
font-size: 10pt;
|
||||
margin: 10pt 0;
|
||||
break-inside: avoid;
|
||||
}
|
||||
table th, .kami-table th {
|
||||
text-align: left;
|
||||
font-weight: 500;
|
||||
color: var(--dark-warm);
|
||||
padding: 5pt 8pt;
|
||||
border-bottom: 1pt solid var(--border);
|
||||
background: transparent;
|
||||
}
|
||||
table td, .kami-table td {
|
||||
padding: 4pt 8pt;
|
||||
border-bottom: 0.3pt solid var(--border-soft);
|
||||
vertical-align: top;
|
||||
}
|
||||
table.compact th, .kami-table.compact th { padding: 3pt 6pt; font-size: 8pt; }
|
||||
table.compact td, .kami-table.compact td { padding: 2pt 6pt; font-size: 8pt; line-height: 1.4; }
|
||||
table.financial td:not(:first-child), .kami-table.financial td:not(:first-child) {
|
||||
text-align: right; font-variant-numeric: tabular-nums;
|
||||
}
|
||||
table.financial th:not(:first-child), .kami-table.financial th:not(:first-child) { text-align: right; }
|
||||
table.striped tbody tr:nth-child(even) td, .kami-table.striped tbody tr:nth-child(even) td {
|
||||
background: var(--ivory);
|
||||
}
|
||||
table .total td, .kami-table .total td {
|
||||
font-weight: 500; border-top: 1pt solid var(--brand); border-bottom: none; color: var(--near-black);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<!-- ========== LETTERHEAD (발신인) ========== -->
|
||||
<div class="letterhead">
|
||||
<div class="sender-name">{{발신인 성명}}</div>
|
||||
<div>{{발신인 주소 / 부서 / 기관}}</div>
|
||||
<div>{{전화번호}} · <a href="mailto:{{EMAIL}}">{{EMAIL}}</a></div>
|
||||
</div>
|
||||
|
||||
<!-- ========== 날짜 ========== -->
|
||||
<div class="date">{{날짜 예: 2026년 4월 18일}}</div>
|
||||
|
||||
<!-- ========== 수신인 ========== -->
|
||||
<div class="recipient">
|
||||
<div class="to">수신</div>
|
||||
<div class="name">{{수신인 성명 / 직함}}</div>
|
||||
<div class="org">{{수신인 기관 / 부서}}</div>
|
||||
</div>
|
||||
|
||||
<!-- ========== 제목 ========== -->
|
||||
<div class="subject">
|
||||
<div class="label">제목</div>
|
||||
<div class="title">{{서한 주제를 한 문장으로 명확하게}}</div>
|
||||
</div>
|
||||
|
||||
<!-- ========== 인사말 ========== -->
|
||||
<div class="salutation">{{인사말 예: "안녕하세요, XX 님," / "존경하는 XX 귀중,"}}</div>
|
||||
|
||||
<!-- ========== 본문 ========== -->
|
||||
<div class="body-content">
|
||||
|
||||
<p>
|
||||
{{첫 번째 단락: 서두. 이 서한을 쓰게 된 배경과 핵심 의도를 밝힌다.
|
||||
돌려 말하지 않고 1-2문장으로 독자가 무엇에 관한 편지인지 파악하게 한다.}}
|
||||
</p>
|
||||
|
||||
<p>
|
||||
{{두 번째 단락: 전개. 배경, 이유, 근거를 제시한다.
|
||||
<span class="hl">핵심 정보</span>는 하이라이트로 강조할 수 있다.}}
|
||||
</p>
|
||||
|
||||
<p>
|
||||
{{세 번째 단락: 구체적 요청. 상대방이 무엇을, 언제, 어떻게 해야 하는지 명확히 기술한다.
|
||||
행동으로 이어질 수 있는 출구를 열어 둔다.}}
|
||||
</p>
|
||||
|
||||
<p>
|
||||
{{네 번째 단락 (선택): 마무리. 기대감 표명, 감사 인사, 또는 정중한 맺음말.}}
|
||||
</p>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- ========== 맺음말 ========== -->
|
||||
<div class="closing">
|
||||
<div class="regards">
|
||||
{{맺음말. 예: "감사합니다." / "부탁드립니다." / "Best regards,"}}
|
||||
</div>
|
||||
|
||||
<div class="signature">{{서명 / 성명}}</div>
|
||||
<div class="signoff-meta">
|
||||
{{직함 · 부서}}<br>
|
||||
{{날짜 재기입 · 선택}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ========== 첨부 (선택) ========== -->
|
||||
<div class="attachments">
|
||||
<span class="label">첨부</span>
|
||||
{{첨부 목록: ① 첨부 파일명 1 · ② 첨부 파일명 2}}
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,317 @@
|
||||
<!DOCTYPE html>
|
||||
<!-- ==================================================================
|
||||
LETTER TEMPLATE · parchment design system
|
||||
A4 正式信件 / 备忘录 / 推荐信 / 辞职信 / 个人声明
|
||||
特点:极简,serif 主导,字号稍大,段距宽松
|
||||
================================================================== -->
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>{{信件主题}}</title>
|
||||
<meta name="author" content="{{作者}}">
|
||||
<meta name="description" content="{{摘要}}">
|
||||
<meta name="keywords" content="{{关键词}}">
|
||||
<meta name="generator" content="Kami">
|
||||
<style>
|
||||
/* Regular weight */
|
||||
@font-face {
|
||||
font-family: "TsangerJinKai02";
|
||||
src: url("../fonts/TsangerJinKai02-W04.ttf") format("truetype"),
|
||||
url("https://cdn.jsdelivr.net/gh/tw93/Kami@main/assets/fonts/TsangerJinKai02-W04.ttf") format("truetype");
|
||||
font-weight: 400;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
/* Bold weight - W05 for all bold variants */
|
||||
@font-face {
|
||||
font-family: "TsangerJinKai02";
|
||||
src: url("../fonts/TsangerJinKai02-W05.ttf") format("truetype"),
|
||||
url("https://cdn.jsdelivr.net/gh/tw93/Kami@main/assets/fonts/TsangerJinKai02-W05.ttf") format("truetype");
|
||||
font-weight: 500;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
@page {
|
||||
size: A4;
|
||||
margin: 25mm 25mm 25mm 25mm;
|
||||
background: #f5f4ed;
|
||||
}
|
||||
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
|
||||
:root {
|
||||
--parchment: #f5f4ed;
|
||||
--near-black:#141413;
|
||||
--dark-warm: #3d3d3a;
|
||||
--olive: #504e49;
|
||||
--stone: #6b6a64;
|
||||
--brand: #1B365D;
|
||||
--ivory: #faf9f5;
|
||||
--border: #e8e6dc;
|
||||
--border-soft:#e5e3d8;
|
||||
--serif: "TsangerJinKai02", "Source Han Serif SC", "Noto Serif CJK SC", "Songti SC", "STSong", Georgia, serif;
|
||||
--sans: var(--serif);
|
||||
}
|
||||
|
||||
html, body { background: var(--parchment); }
|
||||
|
||||
@media screen {
|
||||
body { max-width: 210mm; margin: 0 auto; padding: 25mm; }
|
||||
}
|
||||
|
||||
body {
|
||||
color: var(--near-black);
|
||||
font-family: var(--serif);
|
||||
font-size: 11.5pt;
|
||||
line-height: 1.55;
|
||||
letter-spacing: 0.3pt;
|
||||
}
|
||||
|
||||
/* ========== LETTERHEAD 信头 ========== */
|
||||
.letterhead {
|
||||
text-align: right;
|
||||
margin-bottom: 30pt;
|
||||
font-size: 10pt;
|
||||
line-height: 1.55;
|
||||
color: var(--olive);
|
||||
}
|
||||
.letterhead .sender-name {
|
||||
font-size: 13pt;
|
||||
font-weight: 500;
|
||||
color: var(--near-black);
|
||||
margin-bottom: 4pt;
|
||||
}
|
||||
.letterhead a {
|
||||
color: var(--dark-warm);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
/* ========== META ========== */
|
||||
.date {
|
||||
text-align: right;
|
||||
font-size: 10.5pt;
|
||||
color: var(--olive);
|
||||
margin-bottom: 24pt;
|
||||
font-variant-numeric: tabular-nums;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.recipient {
|
||||
margin-bottom: 24pt;
|
||||
line-height: 1.5;
|
||||
}
|
||||
.recipient .to {
|
||||
font-size: 10pt;
|
||||
color: var(--stone);
|
||||
letter-spacing: 0.5pt;
|
||||
text-transform: uppercase;
|
||||
margin-bottom: 3pt;
|
||||
}
|
||||
.recipient .name {
|
||||
font-size: 12pt;
|
||||
font-weight: 500;
|
||||
color: var(--near-black);
|
||||
}
|
||||
.recipient .org {
|
||||
font-size: 10.5pt;
|
||||
color: var(--olive);
|
||||
}
|
||||
|
||||
/* ========== SUBJECT ========== */
|
||||
.subject {
|
||||
margin-bottom: 24pt;
|
||||
border-left: 2.5pt solid var(--brand);
|
||||
border-radius: 1.5pt;
|
||||
padding-left: 8pt;
|
||||
}
|
||||
.subject .label {
|
||||
font-family: var(--sans);
|
||||
font-size: 9pt;
|
||||
color: var(--brand);
|
||||
letter-spacing: 1pt;
|
||||
text-transform: uppercase;
|
||||
margin-bottom: 3pt;
|
||||
}
|
||||
/* CN locale tuning: TsangerJinKai glyphs read denser, so the subject title
|
||||
sits one step smaller than EN (15pt vs 16pt) with looser leading. */
|
||||
.subject .title {
|
||||
font-size: 15pt;
|
||||
font-weight: 500;
|
||||
color: var(--near-black);
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
/* ========== SALUTATION ========== */
|
||||
.salutation {
|
||||
margin-bottom: 18pt;
|
||||
font-size: 12pt;
|
||||
color: var(--near-black);
|
||||
}
|
||||
|
||||
/* ========== BODY ========== */
|
||||
.body-content p {
|
||||
margin-bottom: 14pt;
|
||||
text-align: justify;
|
||||
text-justify: inter-ideograph;
|
||||
line-height: 1.55;
|
||||
text-indent: 2em; /* 中文段落首行缩进 */
|
||||
}
|
||||
.body-content p:first-child { text-indent: 2em; }
|
||||
.body-content p.no-indent { text-indent: 0; }
|
||||
|
||||
.body-content .hl {
|
||||
color: var(--brand);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* ========== CLOSING ========== */
|
||||
.closing {
|
||||
margin-top: 30pt;
|
||||
line-height: 1.55;
|
||||
}
|
||||
.closing .regards {
|
||||
margin-bottom: 30pt;
|
||||
color: var(--near-black);
|
||||
}
|
||||
.closing .signature {
|
||||
font-size: 14pt;
|
||||
font-weight: 500;
|
||||
color: var(--near-black);
|
||||
margin-bottom: 3pt;
|
||||
}
|
||||
.closing .signoff-meta {
|
||||
font-size: 10pt;
|
||||
color: var(--olive);
|
||||
}
|
||||
|
||||
/* ========== ATTACHMENT ========== */
|
||||
.attachments {
|
||||
margin-top: 36pt;
|
||||
padding-top: 10pt;
|
||||
border-top: 0.5pt dotted var(--border);
|
||||
font-size: 9.5pt;
|
||||
color: var(--stone);
|
||||
font-family: var(--sans);
|
||||
line-height: 1.55;
|
||||
}
|
||||
.attachments .label {
|
||||
color: var(--brand);
|
||||
margin-right: 6pt;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* ========== 一些特殊变体 ========== */
|
||||
/* 如果是 memo(备忘录),用 sans 字体,去掉首行缩进 */
|
||||
body.memo {
|
||||
font-family: var(--sans);
|
||||
line-height: 1.55;
|
||||
}
|
||||
body.memo .body-content p { text-indent: 0; margin-bottom: 10pt; }
|
||||
|
||||
/* ========== TABLE (kami-table) ========== */
|
||||
table, .kami-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
font-size: 10pt;
|
||||
margin: 10pt 0;
|
||||
break-inside: avoid;
|
||||
}
|
||||
table th, .kami-table th {
|
||||
text-align: left;
|
||||
font-weight: 500;
|
||||
color: var(--dark-warm);
|
||||
padding: 5pt 8pt;
|
||||
border-bottom: 1pt solid var(--border);
|
||||
background: transparent;
|
||||
}
|
||||
table td, .kami-table td {
|
||||
padding: 4pt 8pt;
|
||||
border-bottom: 0.3pt solid var(--border-soft);
|
||||
vertical-align: top;
|
||||
}
|
||||
table.compact th, .kami-table.compact th { padding: 3pt 6pt; font-size: 8pt; }
|
||||
table.compact td, .kami-table.compact td { padding: 2pt 6pt; font-size: 8pt; line-height: 1.4; }
|
||||
table.financial td:not(:first-child), .kami-table.financial td:not(:first-child) {
|
||||
text-align: right; font-variant-numeric: tabular-nums;
|
||||
}
|
||||
table.financial th:not(:first-child), .kami-table.financial th:not(:first-child) { text-align: right; }
|
||||
table.striped tbody tr:nth-child(even) td, .kami-table.striped tbody tr:nth-child(even) td {
|
||||
background: var(--ivory);
|
||||
}
|
||||
table .total td, .kami-table .total td {
|
||||
font-weight: 500; border-top: 1pt solid var(--brand); border-bottom: none; color: var(--near-black);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<!-- ========== LETTERHEAD(寄件人) ========== -->
|
||||
<div class="letterhead">
|
||||
<div class="sender-name">{{寄件人姓名}}</div>
|
||||
<div>{{寄件人地址 / 部门 / 机构}}</div>
|
||||
<div>{{电话}} · <a href="mailto:{{EMAIL}}">{{EMAIL}}</a></div>
|
||||
</div>
|
||||
|
||||
<!-- ========== 日期 ========== -->
|
||||
<div class="date">{{日期 如 2026 年 4 月 18 日}}</div>
|
||||
|
||||
<!-- ========== 收件人 ========== -->
|
||||
<div class="recipient">
|
||||
<div class="to">致</div>
|
||||
<div class="name">{{收件人姓名 / 称谓}}</div>
|
||||
<div class="org">{{收件人机构 / 部门}}</div>
|
||||
</div>
|
||||
|
||||
<!-- ========== 主题 ========== -->
|
||||
<div class="subject">
|
||||
<div class="label">关于</div>
|
||||
<div class="title">{{信件主题,一句话说清}}</div>
|
||||
</div>
|
||||
|
||||
<!-- ========== 称呼 ========== -->
|
||||
<div class="salutation">{{称呼 如 "尊敬的 XX 先生:" / "Dear XX,"}}</div>
|
||||
|
||||
<!-- ========== 正文 ========== -->
|
||||
<div class="body-content">
|
||||
|
||||
<p>
|
||||
{{第一段:破题。说明写这封信的缘由和核心意图。
|
||||
不要绕弯,1-2 句话让读者知道你要说什么。}}
|
||||
</p>
|
||||
|
||||
<p>
|
||||
{{第二段:展开。给出背景、理由、论据。
|
||||
如有 <span class="hl">关键信息</span> 可用高亮突出。}}
|
||||
</p>
|
||||
|
||||
<p>
|
||||
{{第三段:具体。说清楚你希望对方做什么、
|
||||
何时做、怎么联系。让行动有明确出口。}}
|
||||
</p>
|
||||
|
||||
<p>
|
||||
{{第四段(可选):收尾。表达期待、致谢或礼貌性结束语。}}
|
||||
</p>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- ========== 敬语 ========== -->
|
||||
<div class="closing">
|
||||
<div class="regards">
|
||||
{{敬语。中文常用 "此致 / 敬礼!"、"顺颂商祺"。英文 "Best regards," / "Sincerely,"}}
|
||||
</div>
|
||||
|
||||
<div class="signature">{{签名 / 亲笔姓名}}</div>
|
||||
<div class="signoff-meta">
|
||||
{{职位 · 部门}}<br>
|
||||
{{日期复写 · 可选}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ========== 附件(可选) ========== -->
|
||||
<div class="attachments">
|
||||
<span class="label">附件</span>
|
||||
{{附件清单:① 附件名 1 · ② 附件名 2}}
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,542 @@
|
||||
<!DOCTYPE html>
|
||||
<!-- ==================================================================
|
||||
LONG DOCUMENT · English · parchment design system
|
||||
Multi-page A4 white paper / tech report / annual review
|
||||
Flow: cover -> toc -> exec summary -> chapters -> appendix
|
||||
================================================================== -->
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>{{DOC_TITLE}}</title>
|
||||
<meta name="author" content="{{AUTHOR}}">
|
||||
<meta name="description" content="{{DESCRIPTION}}">
|
||||
<meta name="keywords" content="{{KEYWORDS}}">
|
||||
<meta name="generator" content="Kami">
|
||||
<style>
|
||||
/* Palatino is a system font, no @font-face needed */
|
||||
@font-face { font-family: "JetBrains Mono"; src: url("../fonts/JetBrainsMono.woff2") format("woff2"); font-weight: 400; font-style: normal; }
|
||||
@font-face { font-family: "JetBrains Mono"; src: url("../fonts/JetBrainsMono.woff2") format("woff2"); font-weight: 500; font-style: normal; }
|
||||
|
||||
@page {
|
||||
size: A4;
|
||||
margin: 20mm 22mm 22mm 22mm;
|
||||
background: #f5f4ed;
|
||||
|
||||
@top-right {
|
||||
content: string(section-title);
|
||||
font-family: Charter, Georgia, Palatino, serif;
|
||||
font-size: 8pt;
|
||||
color: #6b6a64;
|
||||
}
|
||||
|
||||
@bottom-center {
|
||||
content: counter(page) " · {{DOC_TITLE}}";
|
||||
font-family: Charter, Georgia, Palatino, serif;
|
||||
font-size: 9pt;
|
||||
color: #6b6a64;
|
||||
}
|
||||
}
|
||||
@page:first {
|
||||
@top-right { content: ""; }
|
||||
@bottom-center { content: ""; }
|
||||
}
|
||||
|
||||
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
|
||||
:root {
|
||||
--parchment: #f5f4ed;
|
||||
--ivory: #faf9f5;
|
||||
--near-black:#141413;
|
||||
--dark-warm: #3d3d3a;
|
||||
--olive: #504e49;
|
||||
--stone: #6b6a64;
|
||||
--brand: #1B365D;
|
||||
--border: #e8e6dc;
|
||||
--border-soft:#e5e3d8;
|
||||
--tag-bg: #E4ECF5;
|
||||
|
||||
--serif: Charter, Georgia,
|
||||
Palatino, "Times New Roman", serif;
|
||||
--sans: var(--serif);
|
||||
--mono: "JetBrains Mono", "SF Mono", "Fira Code",
|
||||
Consolas, Monaco, monospace;
|
||||
|
||||
--rhythm-module: 14pt;
|
||||
--rhythm-section: 18pt;
|
||||
}
|
||||
|
||||
html, body { background: var(--parchment); }
|
||||
|
||||
@media screen {
|
||||
body { max-width: 210mm; margin: 0 auto; padding: 20mm 22mm 22mm 22mm; }
|
||||
}
|
||||
|
||||
body {
|
||||
color: var(--near-black);
|
||||
font-family: var(--serif);
|
||||
font-size: 10.5pt;
|
||||
line-height: 1.55;
|
||||
widows: 3;
|
||||
orphans: 3;
|
||||
}
|
||||
|
||||
.sans { font-family: var(--sans); }
|
||||
|
||||
/* COVER */
|
||||
.cover {
|
||||
min-height: 240mm;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
padding: 40mm 0 0 0;
|
||||
break-after: page;
|
||||
}
|
||||
.cover-eyebrow {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8pt;
|
||||
font-family: var(--sans);
|
||||
font-size: 10pt;
|
||||
color: var(--brand);
|
||||
letter-spacing: 2pt;
|
||||
text-transform: uppercase;
|
||||
font-weight: 500;
|
||||
margin-bottom: 18pt;
|
||||
}
|
||||
.cover-eyebrow::before {
|
||||
content: "";
|
||||
width: 9pt;
|
||||
height: 1.5pt;
|
||||
border-radius: 0.75pt;
|
||||
background: var(--brand);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.cover-title {
|
||||
font-size: 40pt;
|
||||
font-weight: 500;
|
||||
color: var(--near-black);
|
||||
line-height: 1.12;
|
||||
letter-spacing: -0.5pt;
|
||||
margin-bottom: 16pt;
|
||||
}
|
||||
.cover-sub {
|
||||
font-family: var(--sans);
|
||||
font-size: 14pt;
|
||||
color: var(--olive);
|
||||
line-height: 1.45;
|
||||
max-width: 85%;
|
||||
margin-bottom: 30pt;
|
||||
}
|
||||
.cover-meta {
|
||||
font-family: var(--sans);
|
||||
font-size: 10pt;
|
||||
color: var(--stone);
|
||||
line-height: 1.55;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
.cover-meta strong { color: var(--dark-warm); font-weight: 500; }
|
||||
|
||||
/* TOC */
|
||||
.toc { break-after: page; }
|
||||
.toc h2 {
|
||||
font-size: 22pt;
|
||||
font-weight: 500;
|
||||
margin-bottom: var(--rhythm-module);
|
||||
border-left: 2.5pt solid var(--brand);
|
||||
border-radius: 1.5pt;
|
||||
padding-left: 8pt;
|
||||
}
|
||||
.toc-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: baseline;
|
||||
padding: 6pt 0;
|
||||
border-bottom: 0.3pt dotted var(--border);
|
||||
font-size: 11pt;
|
||||
}
|
||||
.toc-item:last-of-type { border-bottom: none; }
|
||||
.toc-num {
|
||||
color: var(--brand);
|
||||
font-weight: 500;
|
||||
min-width: 30pt;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
.toc-title {
|
||||
flex: 1;
|
||||
color: var(--near-black);
|
||||
padding-left: 6pt;
|
||||
text-decoration: none;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 12pt;
|
||||
}
|
||||
.toc-title[href]::after {
|
||||
content: target-counter(attr(href), page);
|
||||
color: var(--stone);
|
||||
font-variant-numeric: tabular-nums;
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
/* HEADINGS */
|
||||
h1 {
|
||||
font-size: 24pt;
|
||||
font-weight: 500;
|
||||
line-height: 1.15;
|
||||
letter-spacing: -0.3pt;
|
||||
margin: 0 0 10pt 0;
|
||||
border-left: 2.5pt solid var(--brand);
|
||||
border-radius: 1.5pt;
|
||||
padding-left: 8pt;
|
||||
color: var(--near-black);
|
||||
break-after: avoid;
|
||||
string-set: section-title content();
|
||||
}
|
||||
.running-title { string-set: section-title content(); }
|
||||
h2 {
|
||||
font-size: 16pt;
|
||||
font-weight: 500;
|
||||
line-height: 1.25;
|
||||
margin: 24pt 0 8pt 0;
|
||||
color: var(--near-black);
|
||||
break-after: avoid;
|
||||
}
|
||||
h3 {
|
||||
font-size: 13pt;
|
||||
font-weight: 500;
|
||||
line-height: 1.3;
|
||||
margin: 18pt 0 6pt 0;
|
||||
color: var(--dark-warm);
|
||||
break-after: avoid;
|
||||
}
|
||||
|
||||
.chapter-num {
|
||||
font-family: var(--sans);
|
||||
font-size: 10pt;
|
||||
color: var(--brand);
|
||||
letter-spacing: 1pt;
|
||||
text-transform: uppercase;
|
||||
font-weight: 500;
|
||||
margin-bottom: 6pt;
|
||||
}
|
||||
|
||||
/* PARAGRAPHS */
|
||||
p { margin: 0 0 10pt 0; line-height: 1.55; color: var(--near-black); widows: 2; orphans: 2; }
|
||||
|
||||
.lead {
|
||||
font-size: 12pt;
|
||||
line-height: 1.55;
|
||||
color: var(--dark-warm);
|
||||
margin-bottom: var(--rhythm-module);
|
||||
}
|
||||
|
||||
.hl { color: var(--brand); font-weight: 500; }
|
||||
strong { font-weight: 500; }
|
||||
|
||||
/* LISTS */
|
||||
ul, ol {
|
||||
margin: 6pt 0 10pt 0;
|
||||
padding-left: 20pt;
|
||||
line-height: 1.55;
|
||||
}
|
||||
ul li::marker { color: var(--brand); }
|
||||
ol li::marker { color: var(--brand); font-weight: 500; }
|
||||
|
||||
/* QUOTE */
|
||||
blockquote, .quote {
|
||||
border-left: 2pt solid var(--brand);
|
||||
margin: 12pt 0;
|
||||
padding: 4pt 0 4pt 16pt;
|
||||
color: var(--olive);
|
||||
line-height: 1.55;
|
||||
break-inside: avoid;
|
||||
}
|
||||
blockquote .cite, .quote .cite {
|
||||
display: block;
|
||||
font-family: var(--sans);
|
||||
font-size: 9pt;
|
||||
color: var(--stone);
|
||||
margin-top: 4pt;
|
||||
}
|
||||
|
||||
/* CODE */
|
||||
code {
|
||||
font-family: var(--mono);
|
||||
font-size: 9pt;
|
||||
background: var(--ivory);
|
||||
padding: 1pt 4pt;
|
||||
border-radius: 2pt;
|
||||
color: var(--dark-warm);
|
||||
}
|
||||
pre {
|
||||
font-family: var(--mono);
|
||||
font-size: 9pt;
|
||||
line-height: 1.5;
|
||||
background: var(--ivory);
|
||||
border-radius: 4pt;
|
||||
padding: 10pt 14pt;
|
||||
margin: 10pt 0;
|
||||
white-space: pre-wrap;
|
||||
color: var(--near-black);
|
||||
break-inside: avoid;
|
||||
}
|
||||
pre code { background: transparent; padding: 0; font-size: inherit; }
|
||||
|
||||
/* TABLE (kami-table) */
|
||||
table, .kami-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
font-size: 9.5pt;
|
||||
margin: 12pt 0;
|
||||
break-inside: avoid;
|
||||
}
|
||||
table th, .kami-table th {
|
||||
text-align: left;
|
||||
font-family: var(--sans);
|
||||
font-weight: 500;
|
||||
color: var(--dark-warm);
|
||||
padding: 6pt 8pt;
|
||||
border-bottom: 1pt solid var(--border);
|
||||
background: var(--ivory);
|
||||
}
|
||||
table td, .kami-table td {
|
||||
padding: 5pt 8pt;
|
||||
border-bottom: 0.3pt solid var(--border-soft);
|
||||
vertical-align: top;
|
||||
}
|
||||
table.compact th, .kami-table.compact th { padding: 3pt 6pt; font-size: 8pt; }
|
||||
table.compact td, .kami-table.compact td { padding: 2pt 6pt; font-size: 8pt; line-height: 1.4; }
|
||||
table.financial td:not(:first-child), .kami-table.financial td:not(:first-child) {
|
||||
text-align: right; font-variant-numeric: tabular-nums;
|
||||
}
|
||||
table.financial th:not(:first-child), .kami-table.financial th:not(:first-child) { text-align: right; }
|
||||
table.striped tbody tr:nth-child(even) td, .kami-table.striped tbody tr:nth-child(even) td {
|
||||
background: var(--ivory);
|
||||
}
|
||||
table .total td, .kami-table .total td {
|
||||
font-weight: 500; border-top: 1pt solid var(--brand); border-bottom: none; color: var(--near-black);
|
||||
}
|
||||
|
||||
/* CALLOUT / TAKEAWAY */
|
||||
.callout {
|
||||
background: var(--ivory);
|
||||
border-left: 2pt solid var(--brand);
|
||||
padding: 10pt 14pt;
|
||||
border-radius: 3pt;
|
||||
margin: 12pt 0;
|
||||
line-height: 1.55;
|
||||
break-inside: avoid;
|
||||
}
|
||||
.callout blockquote,
|
||||
.callout .quote {
|
||||
border-left: none;
|
||||
padding-left: 0;
|
||||
margin: 6pt 0;
|
||||
color: var(--olive);
|
||||
}
|
||||
.callout blockquote:first-child,
|
||||
.callout .quote:first-child { margin-top: 0; }
|
||||
.callout blockquote:last-child,
|
||||
.callout .quote:last-child { margin-bottom: 0; }
|
||||
.takeaway {
|
||||
background: var(--ivory);
|
||||
border-radius: 4pt;
|
||||
padding: 10pt 14pt;
|
||||
margin: 14pt 0;
|
||||
break-inside: avoid;
|
||||
}
|
||||
.takeaway-label {
|
||||
font-family: var(--sans);
|
||||
font-size: 9pt;
|
||||
color: var(--brand);
|
||||
letter-spacing: 1pt;
|
||||
text-transform: uppercase;
|
||||
font-weight: 500;
|
||||
margin-bottom: 4pt;
|
||||
}
|
||||
|
||||
/* TAG */
|
||||
.tag {
|
||||
display: inline-block;
|
||||
background: var(--tag-bg);
|
||||
color: var(--brand);
|
||||
font-family: var(--sans);
|
||||
font-size: 9pt;
|
||||
font-weight: 500;
|
||||
padding: 1pt 6pt;
|
||||
border-radius: 3pt;
|
||||
letter-spacing: 0.4pt;
|
||||
text-transform: uppercase;
|
||||
margin-right: 4pt;
|
||||
}
|
||||
|
||||
/* FIGURE */
|
||||
figure { margin: 14pt 0; break-inside: avoid; }
|
||||
figure img { max-width: 100%; border-radius: 4pt; }
|
||||
figcaption {
|
||||
font-family: var(--sans);
|
||||
font-size: 9pt;
|
||||
color: var(--stone);
|
||||
margin-top: 6pt;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* CHAPTER BREAK */
|
||||
.chapter { break-before: page; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<!-- COVER -->
|
||||
<section class="cover">
|
||||
<div>
|
||||
<div class="cover-eyebrow">{{EYEBROW - e.g. Technical Report / Annual Review / White Paper}}</div>
|
||||
<div class="cover-title">{{Document title<br>can span two lines}}</div>
|
||||
<div class="cover-sub">{{One-line subtitle - what this is and who it's for.}}</div>
|
||||
</div>
|
||||
<div class="cover-meta">
|
||||
<strong>{{AUTHOR / TEAM}}</strong><br>
|
||||
{{Version 1.0}} · {{YYYY.MM}}<br>
|
||||
{{PUBLISHER / ORGANIZATION}}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- TOC -->
|
||||
<section class="toc">
|
||||
<h2>Contents</h2>
|
||||
<div class="toc-item"><span class="toc-num">01</span><a class="toc-title" href="#ch-executive-summary">Executive Summary</a></div>
|
||||
<div class="toc-item"><span class="toc-num">02</span><a class="toc-title" href="#ch-background">Background & Problem Statement</a></div>
|
||||
<div class="toc-item"><span class="toc-num">03</span><a class="toc-title" href="#ch-methodology">Methodology & Findings</a></div>
|
||||
<div class="toc-item"><span class="toc-num">04</span><a class="toc-title" href="#ch-conclusions">Conclusions & Recommendations</a></div>
|
||||
<div class="toc-item"><span class="toc-num">05</span><a class="toc-title" href="#ch-appendix">Appendix</a></div>
|
||||
</section>
|
||||
|
||||
<!-- Chapter 01 -->
|
||||
<section class="chapter" id="ch-executive-summary">
|
||||
<div class="chapter-num">01 · Executive Summary</div>
|
||||
<h1>Executive Summary</h1>
|
||||
|
||||
<p class="lead">
|
||||
{{Two or three sentences opening the whole thesis. Use <span class="hl">brand-color emphasis</span> to grab attention on the sharpest claim. A reader of only this paragraph should understand what the document argues.}}
|
||||
</p>
|
||||
|
||||
<h2>Key Takeaways</h2>
|
||||
<ul>
|
||||
<li>{{Takeaway 1 - a quantified conclusion in one line.}}</li>
|
||||
<li>{{Takeaway 2 - an insight backed by data.}}</li>
|
||||
<li>{{Takeaway 3 - a forward-looking judgment.}}</li>
|
||||
</ul>
|
||||
|
||||
<div class="takeaway">
|
||||
<div class="takeaway-label">Questions this document answers</div>
|
||||
{{List the three core questions as actual questions - so the reader can decide in ten seconds whether to read on.}}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Chapter 02 -->
|
||||
<section class="chapter" id="ch-background">
|
||||
<div class="chapter-num">02 · Background</div>
|
||||
<h1>Background & Problem Statement</h1>
|
||||
|
||||
<p class="lead">
|
||||
{{Chapter intro - what this chapter is solving, why it matters. One or two sentences.}}
|
||||
</p>
|
||||
|
||||
<h2>Current State</h2>
|
||||
<p>{{Three to five lines describing the status quo. Use <span class="hl">specific figures</span> rather than adjectives.}}</p>
|
||||
|
||||
<h2>The Core Problem</h2>
|
||||
<p>{{State the problem specifically. Use a callout to emphasize a key observation:}}</p>
|
||||
|
||||
<div class="callout">
|
||||
{{A short quoted line or key observation. Different in tone from the body so the reader gets a breath.}}
|
||||
</div>
|
||||
|
||||
<h2>Metrics of Success</h2>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Dimension</th>
|
||||
<th>Current</th>
|
||||
<th>Target</th>
|
||||
<th>Gap</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr><td>{{DIMENSION_1}}</td><td>{{VAL}}</td><td>{{VAL}}</td><td>{{GAP}}</td></tr>
|
||||
<tr><td>{{DIMENSION_2}}</td><td>{{VAL}}</td><td>{{VAL}}</td><td>{{GAP}}</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</section>
|
||||
|
||||
<!-- Chapter 03 -->
|
||||
<section class="chapter" id="ch-methodology">
|
||||
<div class="chapter-num">03 · Methodology</div>
|
||||
<h1>Methodology & Findings</h1>
|
||||
|
||||
<p class="lead">{{Chapter intro.}}</p>
|
||||
|
||||
<h2>Approach</h2>
|
||||
<p>{{Describe the methodology. Code or formula examples welcome:}}</p>
|
||||
|
||||
<pre><code class="language-python">def analyze(data):
|
||||
"""Transform raw data."""
|
||||
return transform(data)</code></pre>
|
||||
|
||||
<h2>Key Findings</h2>
|
||||
|
||||
<h3>Finding 1 - {{TITLE}}</h3>
|
||||
<p>{{A paragraph with data: <span class="hl">specific numbers or ratios</span>.}}</p>
|
||||
|
||||
<h3>Finding 2 - {{TITLE}}</h3>
|
||||
<p>{{A paragraph with data: <span class="hl">specific numbers or ratios</span>.}}</p>
|
||||
|
||||
<blockquote>
|
||||
{{A quoted passage - user interview, expert perspective, or cited source.}}
|
||||
<span class="cite"> - {{SOURCE / PERSON}}, {{DATE}}</span>
|
||||
</blockquote>
|
||||
</section>
|
||||
|
||||
<!-- Chapter 04 -->
|
||||
<section class="chapter" id="ch-conclusions">
|
||||
<div class="chapter-num">04 · Conclusions</div>
|
||||
<h1>Conclusions & Recommendations</h1>
|
||||
|
||||
<p class="lead">{{Chapter intro - a one-line summary of the conclusion, then the recommendations below.}}</p>
|
||||
|
||||
<h2>Core Conclusions</h2>
|
||||
<ol>
|
||||
<li>{{Conclusion 1.}}</li>
|
||||
<li>{{Conclusion 2.}}</li>
|
||||
<li>{{Conclusion 3.}}</li>
|
||||
</ol>
|
||||
|
||||
<h2>Recommended Next Steps</h2>
|
||||
<p>{{Concrete, executable recommendations tied to the conclusions.}}</p>
|
||||
|
||||
<div class="takeaway">
|
||||
<div class="takeaway-label">Call to Action</div>
|
||||
{{If the reader does one thing, what is it? Specific enough to start Monday morning.}}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Chapter 05 -->
|
||||
<section class="chapter" id="ch-appendix">
|
||||
<div class="chapter-num">05 · Appendix</div>
|
||||
<h1>Appendix</h1>
|
||||
|
||||
<h2>A. References</h2>
|
||||
<ul>
|
||||
<li>{{Reference 1}}</li>
|
||||
<li>{{Reference 2}}</li>
|
||||
</ul>
|
||||
|
||||
<h2>B. Glossary</h2>
|
||||
<p><strong>{{TERM}}</strong> - {{definition}}</p>
|
||||
<p><strong>{{TERM}}</strong> - {{definition}}</p>
|
||||
|
||||
<h2>C. Acknowledgements</h2>
|
||||
<p>{{Acknowledgement paragraph.}}</p>
|
||||
</section>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,615 @@
|
||||
<!DOCTYPE html>
|
||||
<!-- ==================================================================
|
||||
LONG DOCUMENT TEMPLATE (KO) · parchment design system
|
||||
다중 A4 백서 / 기술 보고서 / 연간 결산 보고서
|
||||
구조: 표지 -> 목차 -> 요약 -> 본문 챕터 -> 부록
|
||||
변경 후 실행: python3 -c "from weasyprint import HTML; HTML('long-doc-ko.html').write_pdf('out.pdf')"
|
||||
목표: 최대 9페이지
|
||||
================================================================== -->
|
||||
<html lang="ko">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>{{문서 제목}}</title>
|
||||
<meta name="author" content="{{작성자}}">
|
||||
<meta name="description" content="{{요약}}">
|
||||
<meta name="keywords" content="{{키워드}}">
|
||||
<meta name="generator" content="Kami">
|
||||
<style>
|
||||
/* Regular weight */
|
||||
@font-face {
|
||||
font-family: "Source Han Serif K";
|
||||
src: url("../fonts/SourceHanSerifKR-Regular.otf") format("opentype"),
|
||||
url("https://cdn.jsdelivr.net/gh/tw93/Kami@main/assets/fonts/SourceHanSerifKR-Regular.otf") format("opentype");
|
||||
font-weight: 400;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
/* Medium weight */
|
||||
@font-face {
|
||||
font-family: "Source Han Serif K";
|
||||
src: url("../fonts/SourceHanSerifKR-Medium.otf") format("opentype"),
|
||||
url("https://cdn.jsdelivr.net/gh/tw93/Kami@main/assets/fonts/SourceHanSerifKR-Medium.otf") format("opentype");
|
||||
font-weight: 500;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
@page {
|
||||
size: A4;
|
||||
margin: 20mm 22mm 22mm 22mm;
|
||||
background: #f5f4ed;
|
||||
|
||||
@top-right {
|
||||
content: string(section-title);
|
||||
font-family: "Source Han Serif K", "Source Han Serif KR", "Apple SD Gothic Neo",
|
||||
"Noto Serif KR", "AppleMyungjo", Georgia, serif;
|
||||
font-size: 8pt;
|
||||
color: #6b6a64;
|
||||
}
|
||||
|
||||
@bottom-center {
|
||||
content: counter(page) " · {{문서 제목}}";
|
||||
font-family: "Source Han Serif K", "Source Han Serif KR", "Apple SD Gothic Neo",
|
||||
"Noto Serif KR", "AppleMyungjo", Georgia, serif;
|
||||
font-size: 9pt;
|
||||
color: #6b6a64;
|
||||
}
|
||||
}
|
||||
|
||||
@page:first {
|
||||
@top-right { content: ""; }
|
||||
@bottom-center { content: ""; }
|
||||
}
|
||||
|
||||
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
|
||||
:root {
|
||||
--parchment: #f5f4ed;
|
||||
--ivory: #faf9f5;
|
||||
--near-black:#141413;
|
||||
--dark-warm: #3d3d3a;
|
||||
--olive: #504e49;
|
||||
--stone: #6b6a64;
|
||||
--brand: #1B365D;
|
||||
--border: #e8e6dc;
|
||||
--border-soft:#e5e3d8;
|
||||
--tag-bg: #E4ECF5;
|
||||
--serif: "Source Han Serif K", "Source Han Serif KR", "Noto Serif KR", "Apple SD Gothic Neo", "AppleMyungjo", Charter, Georgia, serif;
|
||||
--sans: var(--serif);
|
||||
|
||||
--rhythm-module: 14pt;
|
||||
--rhythm-section: 18pt;
|
||||
}
|
||||
|
||||
html, body { background: var(--parchment); }
|
||||
|
||||
@media screen {
|
||||
body { max-width: 210mm; margin: 0 auto; padding: 20mm 22mm 22mm 22mm; }
|
||||
}
|
||||
|
||||
body {
|
||||
color: var(--near-black);
|
||||
font-family: var(--serif);
|
||||
font-size: 10.5pt;
|
||||
line-height: 1.55;
|
||||
letter-spacing: 0.3pt;
|
||||
widows: 3;
|
||||
orphans: 3;
|
||||
font-synthesis: none;
|
||||
}
|
||||
|
||||
.sans {
|
||||
font-family: var(--sans);
|
||||
}
|
||||
|
||||
/* ========== COVER ========== */
|
||||
.cover {
|
||||
min-height: 240mm;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
padding: 40mm 0 0 0;
|
||||
break-after: page;
|
||||
}
|
||||
.cover-eyebrow {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8pt;
|
||||
font-family: var(--sans);
|
||||
font-size: 10pt;
|
||||
color: var(--brand);
|
||||
letter-spacing: 1.5pt;
|
||||
text-transform: uppercase;
|
||||
font-weight: 500;
|
||||
margin-bottom: 18pt;
|
||||
}
|
||||
.cover-eyebrow::before {
|
||||
content: "";
|
||||
width: 9pt;
|
||||
height: 1.5pt;
|
||||
border-radius: 0.75pt;
|
||||
background: var(--brand);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.cover-title {
|
||||
font-size: 36pt;
|
||||
font-weight: 500;
|
||||
color: var(--near-black);
|
||||
line-height: 1.15;
|
||||
letter-spacing: 0.3pt;
|
||||
margin-bottom: 16pt;
|
||||
}
|
||||
.cover-sub {
|
||||
font-family: var(--sans);
|
||||
font-size: 14pt;
|
||||
color: var(--olive);
|
||||
line-height: 1.5;
|
||||
max-width: 85%;
|
||||
margin-bottom: 30pt;
|
||||
}
|
||||
.cover-meta {
|
||||
font-family: var(--sans);
|
||||
font-size: 10pt;
|
||||
color: var(--stone);
|
||||
line-height: 1.5;
|
||||
}
|
||||
.cover-meta strong {
|
||||
color: var(--dark-warm);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* ========== TOC (목차) ========== */
|
||||
.toc { break-after: page; }
|
||||
.toc h2 {
|
||||
font-size: 22pt;
|
||||
font-weight: 500;
|
||||
margin-bottom: var(--rhythm-module);
|
||||
border-left: 2.5pt solid var(--brand);
|
||||
border-radius: 1.5pt;
|
||||
padding-left: 8pt;
|
||||
}
|
||||
.toc-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: baseline;
|
||||
padding: 6pt 0;
|
||||
border-bottom: 0.3pt dotted var(--border);
|
||||
font-size: 11pt;
|
||||
}
|
||||
.toc-item:last-of-type { border-bottom: none; }
|
||||
.toc-num {
|
||||
color: var(--brand);
|
||||
font-weight: 500;
|
||||
min-width: 30pt;
|
||||
}
|
||||
.toc-title {
|
||||
flex: 1;
|
||||
color: var(--near-black);
|
||||
padding-left: 6pt;
|
||||
text-decoration: none;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 12pt;
|
||||
}
|
||||
.toc-title[href]::after {
|
||||
content: target-counter(attr(href), page);
|
||||
color: var(--stone);
|
||||
font-variant-numeric: tabular-nums;
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
/* ========== HEADINGS ========== */
|
||||
h1 {
|
||||
font-size: 22pt;
|
||||
font-weight: 500;
|
||||
line-height: 1.2;
|
||||
margin: 0 0 10pt 0;
|
||||
border-left: 2.5pt solid var(--brand);
|
||||
border-radius: 1.5pt;
|
||||
padding-left: 8pt;
|
||||
color: var(--near-black);
|
||||
break-after: avoid;
|
||||
string-set: section-title content();
|
||||
}
|
||||
|
||||
.running-title { string-set: section-title content(); }
|
||||
|
||||
h2 {
|
||||
font-size: 16pt;
|
||||
font-weight: 500;
|
||||
line-height: 1.25;
|
||||
margin: 24pt 0 8pt 0;
|
||||
color: var(--near-black);
|
||||
break-after: avoid;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: 13pt;
|
||||
font-weight: 500;
|
||||
line-height: 1.3;
|
||||
margin: 18pt 0 6pt 0;
|
||||
color: var(--dark-warm);
|
||||
break-after: avoid;
|
||||
}
|
||||
|
||||
.chapter-num {
|
||||
font-family: var(--sans);
|
||||
font-size: 10pt;
|
||||
color: var(--brand);
|
||||
letter-spacing: 1pt;
|
||||
text-transform: uppercase;
|
||||
margin-bottom: 6pt;
|
||||
}
|
||||
|
||||
/* ========== PARAGRAPHS ========== */
|
||||
p {
|
||||
margin: 0 0 10pt 0;
|
||||
line-height: 1.55;
|
||||
color: var(--near-black);
|
||||
widows: 2;
|
||||
orphans: 2;
|
||||
}
|
||||
|
||||
.lead {
|
||||
font-size: 12pt;
|
||||
line-height: 1.55;
|
||||
color: var(--dark-warm);
|
||||
margin-bottom: var(--rhythm-module);
|
||||
}
|
||||
|
||||
.hl { color: var(--brand); font-weight: 500; }
|
||||
strong { font-weight: 500; }
|
||||
|
||||
/* ========== LISTS ========== */
|
||||
ul, ol {
|
||||
margin: 6pt 0 10pt 0;
|
||||
padding-left: 20pt;
|
||||
line-height: 1.55;
|
||||
}
|
||||
ul li::marker { color: var(--brand); }
|
||||
ol li::marker { color: var(--brand); font-weight: 500; }
|
||||
|
||||
/* ========== QUOTE ========== */
|
||||
blockquote, .quote {
|
||||
border-left: 2pt solid var(--brand);
|
||||
margin: 12pt 0;
|
||||
padding: 4pt 0 4pt 16pt;
|
||||
color: var(--olive);
|
||||
line-height: 1.55;
|
||||
break-inside: avoid;
|
||||
}
|
||||
blockquote .cite, .quote .cite {
|
||||
display: block;
|
||||
font-size: 9pt;
|
||||
color: var(--stone);
|
||||
margin-top: 4pt;
|
||||
}
|
||||
|
||||
/* ========== CODE ========== */
|
||||
/* Mono-first for Latin glyphs; Korean fallback via SourceHanSerifK so
|
||||
Korean comments inside code blocks render without missing-glyph boxes. */
|
||||
code {
|
||||
font-family: "JetBrains Mono", "D2Coding", "SF Mono", Consolas,
|
||||
"Source Han Serif K", "Source Han Serif KR", "Apple SD Gothic Neo", monospace;
|
||||
font-size: 9pt;
|
||||
background: var(--ivory);
|
||||
padding: 1pt 4pt;
|
||||
border-radius: 2pt;
|
||||
color: var(--dark-warm);
|
||||
}
|
||||
pre {
|
||||
font-family: "JetBrains Mono", "D2Coding", "SF Mono", Consolas,
|
||||
"Source Han Serif K", "Source Han Serif KR", "Apple SD Gothic Neo", monospace;
|
||||
font-size: 9pt;
|
||||
line-height: 1.5;
|
||||
background: var(--ivory);
|
||||
border-radius: 4pt;
|
||||
padding: 10pt 14pt;
|
||||
margin: 10pt 0;
|
||||
white-space: pre-wrap;
|
||||
color: var(--near-black);
|
||||
break-inside: avoid;
|
||||
}
|
||||
pre code { background: transparent; padding: 0; font-size: inherit; }
|
||||
|
||||
/* ========== TABLE (kami-table) ========== */
|
||||
table, .kami-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
font-size: 9.5pt;
|
||||
margin: 12pt 0;
|
||||
break-inside: avoid;
|
||||
}
|
||||
table th, .kami-table th {
|
||||
text-align: left;
|
||||
font-weight: 500;
|
||||
color: var(--dark-warm);
|
||||
padding: 6pt 8pt;
|
||||
border-bottom: 1pt solid var(--border);
|
||||
background: transparent;
|
||||
}
|
||||
table td, .kami-table td {
|
||||
padding: 5pt 8pt;
|
||||
border-bottom: 0.3pt solid var(--border-soft);
|
||||
vertical-align: top;
|
||||
}
|
||||
table.compact th, .kami-table.compact th { padding: 3pt 6pt; font-size: 8pt; }
|
||||
table.compact td, .kami-table.compact td { padding: 2pt 6pt; font-size: 8pt; line-height: 1.4; }
|
||||
table.financial td:not(:first-child), .kami-table.financial td:not(:first-child) {
|
||||
text-align: right; font-variant-numeric: tabular-nums;
|
||||
}
|
||||
table.financial th:not(:first-child), .kami-table.financial th:not(:first-child) { text-align: right; }
|
||||
table.striped tbody tr:nth-child(even) td, .kami-table.striped tbody tr:nth-child(even) td {
|
||||
background: var(--ivory);
|
||||
}
|
||||
table .total td, .kami-table .total td {
|
||||
font-weight: 500; border-top: 1pt solid var(--brand); border-bottom: none; color: var(--near-black);
|
||||
}
|
||||
|
||||
/* ========== CALLOUT ========== */
|
||||
.callout {
|
||||
background: var(--ivory);
|
||||
border-left: 2pt solid var(--brand);
|
||||
padding: 10pt 14pt;
|
||||
border-radius: 3pt;
|
||||
margin: 12pt 0;
|
||||
line-height: 1.55;
|
||||
break-inside: avoid;
|
||||
}
|
||||
.callout blockquote,
|
||||
.callout .quote {
|
||||
border-left: none;
|
||||
padding-left: 0;
|
||||
margin: 6pt 0;
|
||||
color: var(--olive);
|
||||
}
|
||||
.callout blockquote:first-child,
|
||||
.callout .quote:first-child { margin-top: 0; }
|
||||
.callout blockquote:last-child,
|
||||
.callout .quote:last-child { margin-bottom: 0; }
|
||||
|
||||
.takeaway {
|
||||
background: var(--ivory);
|
||||
border-radius: 4pt;
|
||||
padding: 10pt 14pt;
|
||||
margin: 14pt 0;
|
||||
break-inside: avoid;
|
||||
}
|
||||
.takeaway-label {
|
||||
font-family: var(--sans);
|
||||
font-size: 9pt;
|
||||
color: var(--brand);
|
||||
letter-spacing: 0.5pt;
|
||||
text-transform: uppercase;
|
||||
margin-bottom: 4pt;
|
||||
}
|
||||
|
||||
/* ========== TAG ========== */
|
||||
.tag {
|
||||
display: inline-block;
|
||||
background: var(--tag-bg);
|
||||
color: var(--brand);
|
||||
font-family: var(--sans);
|
||||
font-size: 9pt;
|
||||
font-weight: 500;
|
||||
padding: 1pt 6pt;
|
||||
border-radius: 3pt;
|
||||
margin-right: 4pt;
|
||||
}
|
||||
|
||||
/* ========== FIGURE ========== */
|
||||
figure {
|
||||
margin: 14pt 0;
|
||||
break-inside: avoid;
|
||||
}
|
||||
figure img {
|
||||
max-width: 100%;
|
||||
border-radius: 4pt;
|
||||
}
|
||||
figcaption {
|
||||
font-family: var(--sans);
|
||||
font-size: 9pt;
|
||||
color: var(--stone);
|
||||
margin-top: 6pt;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* ========== EXECUTIVE SUMMARY ========== */
|
||||
.exec-summary {
|
||||
background: var(--ivory);
|
||||
border-left: 1.4pt solid var(--brand);
|
||||
border-radius: 6pt;
|
||||
padding: 16pt 20pt;
|
||||
margin: 14pt 0;
|
||||
break-inside: avoid;
|
||||
}
|
||||
.exec-summary h2 {
|
||||
margin-top: 0;
|
||||
font-size: 13pt;
|
||||
}
|
||||
|
||||
/* ========== CHAPTER BREAK ========== */
|
||||
.chapter { break-before: page; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<!-- ========== 표지 ========== -->
|
||||
<section class="cover">
|
||||
<div>
|
||||
<div class="cover-eyebrow">{{EYEBROW · 예: "기술 보고서" / "연간 결산" / "백서"}}</div>
|
||||
<div class="cover-title">{{문서 주제목<br>두 줄도 가능}}</div>
|
||||
<div class="cover-sub">{{부제목: 이 문서가 무엇인지, 누구를 위한 것인지 한 문장으로}}</div>
|
||||
</div>
|
||||
<div class="cover-meta">
|
||||
<strong>{{작성자 / 팀}}</strong><br>
|
||||
{{버전 V1.0}} · {{날짜 2026.04}}<br>
|
||||
{{발행 기관}}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ========== 목차 ========== -->
|
||||
<section class="toc">
|
||||
<h2>목차</h2>
|
||||
<div class="toc-item">
|
||||
<span class="toc-num">01</span>
|
||||
<a class="toc-title" href="#ch-executive-summary">요약</a>
|
||||
</div>
|
||||
<div class="toc-item">
|
||||
<span class="toc-num">02</span>
|
||||
<a class="toc-title" href="#ch-background">배경 및 문제 정의</a>
|
||||
</div>
|
||||
<div class="toc-item">
|
||||
<span class="toc-num">03</span>
|
||||
<a class="toc-title" href="#ch-methodology">방법론 및 주요 발견</a>
|
||||
</div>
|
||||
<div class="toc-item">
|
||||
<span class="toc-num">04</span>
|
||||
<a class="toc-title" href="#ch-conclusions">결론 및 제언</a>
|
||||
</div>
|
||||
<div class="toc-item">
|
||||
<span class="toc-num">05</span>
|
||||
<a class="toc-title" href="#ch-appendix">부록</a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ========== Chapter 01: 요약 ========== -->
|
||||
<section class="chapter" id="ch-executive-summary">
|
||||
<div class="chapter-num">01 · Executive Summary</div>
|
||||
<h1>요약</h1>
|
||||
|
||||
<p class="lead">
|
||||
{{2-3문장의 핵심 논지 도입부. <span class="hl">키워드 하이라이트</span>로 독자의 주의를 끈다.
|
||||
이 단락만 읽어도 문서 전체가 무엇을 말하는지 파악할 수 있어야 한다.}}
|
||||
</p>
|
||||
|
||||
<h2>핵심 요점</h2>
|
||||
<ul>
|
||||
<li>{{요점 1: 한 문장, 수치화 가능한 결론}}</li>
|
||||
<li>{{요점 2: 데이터 기반의 인사이트}}</li>
|
||||
<li>{{요점 3: 향후 방향에 대한 판단}}</li>
|
||||
</ul>
|
||||
|
||||
<div class="takeaway">
|
||||
<div class="takeaway-label">이 문서가 답하는 질문</div>
|
||||
{{이 문서의 핵심 질문 3가지를 의문문으로 나열. 독자가 계속 읽을 필요가 있는지 바로 판단하게 한다.}}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ========== Chapter 02: 배경 ========== -->
|
||||
<section class="chapter" id="ch-background">
|
||||
<div class="chapter-num">02 · Background</div>
|
||||
<h1>배경 및 문제 정의</h1>
|
||||
|
||||
<p class="lead">
|
||||
{{챕터 도입부: 이 챕터가 다루는 문제와 그 중요성. 1-2문장.}}
|
||||
</p>
|
||||
|
||||
<h2>현황</h2>
|
||||
<p>{{3-5줄 단락, 현재 상황을 서술한다. <span class="hl">구체적인 수치</span>를 형용사 대신 사용한다.}}</p>
|
||||
|
||||
<h2>핵심 문제</h2>
|
||||
<p>{{구체적인 핵심 문제를 기술한다. 아래 callout으로 핵심 관찰을 강조할 수 있다:}}</p>
|
||||
|
||||
<div class="callout">
|
||||
{{중요한 인용 또는 핵심 관찰. 본문과 약간 다른 어조로, 독자에게 호흡 공간을 준다.}}
|
||||
</div>
|
||||
|
||||
<h2>측정 기준</h2>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>항목</th>
|
||||
<th>현재 수준</th>
|
||||
<th>목표 수준</th>
|
||||
<th>격차</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>{{항목 1}}</td>
|
||||
<td>{{데이터}}</td>
|
||||
<td>{{데이터}}</td>
|
||||
<td>{{격차}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{항목 2}}</td>
|
||||
<td>{{데이터}}</td>
|
||||
<td>{{데이터}}</td>
|
||||
<td>{{격차}}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</section>
|
||||
|
||||
<!-- ========== Chapter 03: 방법론 ========== -->
|
||||
<section class="chapter" id="ch-methodology">
|
||||
<div class="chapter-num">03 · Methodology</div>
|
||||
<h1>방법론 및 주요 발견</h1>
|
||||
|
||||
<p class="lead">{{챕터 도입부}}</p>
|
||||
|
||||
<h2>연구 방법</h2>
|
||||
<p>{{방법론을 기술한다. 코드 / 수식 예시를 포함할 수 있다:}}</p>
|
||||
|
||||
<pre><code class="language-python">def analyze(data):
|
||||
"""Transform raw data."""
|
||||
return transform(data)</code></pre>
|
||||
|
||||
<h2>주요 발견</h2>
|
||||
|
||||
<h3>발견 1: {{제목}}</h3>
|
||||
<p>{{한 단락 논술. <span class="hl">구체적인 수치 / 비율</span>을 포함한다.}}</p>
|
||||
|
||||
<h3>발견 2: {{제목}}</h3>
|
||||
<p>{{한 단락 논술. <span class="hl">구체적인 수치 / 비율</span>을 포함한다.}}</p>
|
||||
|
||||
<blockquote>
|
||||
{{인용: 사용자 인터뷰, 전문가 의견, 또는 문헌 인용}}
|
||||
<span class="cite"> - {{출처 / 인물}}, {{날짜}}</span>
|
||||
</blockquote>
|
||||
</section>
|
||||
|
||||
<!-- ========== Chapter 04: 결론 ========== -->
|
||||
<section class="chapter" id="ch-conclusions">
|
||||
<div class="chapter-num">04 · Conclusions</div>
|
||||
<h1>결론 및 제언</h1>
|
||||
|
||||
<p class="lead">{{챕터 도입부: 결론을 한 문장으로 요약하고, 아래에서 제언을 전개한다.}}</p>
|
||||
|
||||
<h2>핵심 결론</h2>
|
||||
<ol>
|
||||
<li>{{결론 1}}</li>
|
||||
<li>{{결론 2}}</li>
|
||||
<li>{{결론 3}}</li>
|
||||
</ol>
|
||||
|
||||
<h2>다음 단계 제언</h2>
|
||||
<p>{{결론에 기반한 구체적이고 실행 가능한 제언.}}</p>
|
||||
|
||||
<div class="takeaway">
|
||||
<div class="takeaway-label">Call to Action</div>
|
||||
{{독자가 한 가지만 실행한다면 무엇인가? 월요일 아침에 바로 시작할 수 있을 만큼 구체적으로.}}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ========== Chapter 05: 부록 ========== -->
|
||||
<section class="chapter" id="ch-appendix">
|
||||
<div class="chapter-num">05 · Appendix</div>
|
||||
<h1>부록</h1>
|
||||
|
||||
<h2>A. 참고 자료</h2>
|
||||
<ul>
|
||||
<li>{{참고 문헌 1}}</li>
|
||||
<li>{{참고 문헌 2}}</li>
|
||||
</ul>
|
||||
|
||||
<h2>B. 용어 설명</h2>
|
||||
<p><strong>{{용어}}</strong>: {{정의}}</p>
|
||||
<p><strong>{{용어}}</strong>: {{정의}}</p>
|
||||
|
||||
<h2>C. 감사의 말</h2>
|
||||
<p>{{감사 단락}}</p>
|
||||
</section>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,672 @@
|
||||
<!DOCTYPE html>
|
||||
<!-- ==================================================================
|
||||
LONG DOCUMENT TEMPLATE · parchment design system
|
||||
多页 A4 白皮书/技术报告/年度总结
|
||||
结构:封面 -> 目录 -> 执行摘要 -> 正文章节 -> 附录
|
||||
================================================================== -->
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>{{文档标题}}</title>
|
||||
<meta name="author" content="{{作者}}">
|
||||
<meta name="description" content="{{摘要}}">
|
||||
<meta name="keywords" content="{{关键词}}">
|
||||
<meta name="generator" content="Kami">
|
||||
<style>
|
||||
/* Regular weight */
|
||||
@font-face {
|
||||
font-family: "TsangerJinKai02";
|
||||
src: url("../fonts/TsangerJinKai02-W04.ttf") format("truetype"),
|
||||
url("https://cdn.jsdelivr.net/gh/tw93/Kami@main/assets/fonts/TsangerJinKai02-W04.ttf") format("truetype");
|
||||
font-weight: 400;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
/* Bold weight - W05 for all bold variants */
|
||||
@font-face {
|
||||
font-family: "TsangerJinKai02";
|
||||
src: url("../fonts/TsangerJinKai02-W05.ttf") format("truetype"),
|
||||
url("https://cdn.jsdelivr.net/gh/tw93/Kami@main/assets/fonts/TsangerJinKai02-W05.ttf") format("truetype");
|
||||
font-weight: 500;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
@page {
|
||||
size: A4;
|
||||
margin: 20mm 22mm 22mm 22mm;
|
||||
background: #f5f4ed;
|
||||
|
||||
@top-right {
|
||||
content: string(section-title);
|
||||
font-family: "TsangerJinKai02", "Source Han Serif SC",
|
||||
"Noto Serif CJK SC", "Songti SC", Georgia, serif;
|
||||
font-size: 8pt;
|
||||
color: #6b6a64;
|
||||
}
|
||||
|
||||
@bottom-center {
|
||||
content: counter(page) " · {{文档标题}}";
|
||||
font-family: "TsangerJinKai02", "Source Han Serif SC",
|
||||
"Noto Serif CJK SC", "Songti SC", Georgia, serif;
|
||||
font-size: 9pt;
|
||||
color: #6b6a64;
|
||||
}
|
||||
}
|
||||
|
||||
@page:first {
|
||||
@top-right { content: ""; }
|
||||
@bottom-center { content: ""; }
|
||||
}
|
||||
|
||||
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
|
||||
:root {
|
||||
--parchment: #f5f4ed;
|
||||
--ivory: #faf9f5;
|
||||
--near-black:#141413;
|
||||
--dark-warm: #3d3d3a;
|
||||
--olive: #504e49;
|
||||
--stone: #6b6a64;
|
||||
--brand: #1B365D;
|
||||
--border: #e8e6dc;
|
||||
--border-soft:#e5e3d8;
|
||||
--tag-bg: #E4ECF5;
|
||||
--serif: "TsangerJinKai02", "Source Han Serif SC", "Noto Serif CJK SC", "Songti SC", "STSong", Georgia, serif;
|
||||
--sans: var(--serif);
|
||||
|
||||
--rhythm-module: 14pt;
|
||||
--rhythm-section: 18pt;
|
||||
}
|
||||
|
||||
html, body { background: var(--parchment); }
|
||||
|
||||
@media screen {
|
||||
body { max-width: 210mm; margin: 0 auto; padding: 20mm 22mm 22mm 22mm; }
|
||||
}
|
||||
|
||||
body {
|
||||
color: var(--near-black);
|
||||
font-family: var(--serif);
|
||||
font-size: 10.5pt;
|
||||
line-height: 1.55;
|
||||
letter-spacing: 0.3pt;
|
||||
widows: 3;
|
||||
orphans: 3;
|
||||
}
|
||||
|
||||
.sans {
|
||||
font-family: var(--sans);
|
||||
}
|
||||
|
||||
/* ========== COVER ========== */
|
||||
.cover {
|
||||
min-height: 240mm;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
padding: 40mm 0 0 0;
|
||||
break-after: page;
|
||||
}
|
||||
.cover-eyebrow {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8pt;
|
||||
font-family: var(--sans);
|
||||
font-size: 10pt;
|
||||
color: var(--brand);
|
||||
letter-spacing: 1.5pt;
|
||||
text-transform: uppercase;
|
||||
font-weight: 500;
|
||||
margin-bottom: 18pt;
|
||||
}
|
||||
.cover-eyebrow::before {
|
||||
content: "";
|
||||
width: 9pt;
|
||||
height: 1.5pt;
|
||||
border-radius: 0.75pt;
|
||||
background: var(--brand);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.cover-title {
|
||||
font-size: 36pt;
|
||||
font-weight: 500;
|
||||
color: var(--near-black);
|
||||
line-height: 1.15;
|
||||
letter-spacing: 0.3pt;
|
||||
margin-bottom: 16pt;
|
||||
}
|
||||
.cover-sub {
|
||||
font-family: var(--sans);
|
||||
font-size: 14pt;
|
||||
color: var(--olive);
|
||||
line-height: 1.5;
|
||||
max-width: 85%;
|
||||
margin-bottom: 30pt;
|
||||
}
|
||||
.cover-meta {
|
||||
font-family: var(--sans);
|
||||
font-size: 10pt;
|
||||
color: var(--stone);
|
||||
line-height: 1.5;
|
||||
}
|
||||
.cover-meta strong {
|
||||
color: var(--dark-warm);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* ========== TOC(目录)========== */
|
||||
.toc { break-after: page; }
|
||||
.toc h2 {
|
||||
font-size: 22pt;
|
||||
font-weight: 500;
|
||||
margin-bottom: var(--rhythm-module);
|
||||
border-left: 2.5pt solid var(--brand);
|
||||
border-radius: 1.5pt;
|
||||
padding-left: 8pt;
|
||||
}
|
||||
.toc-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: baseline;
|
||||
padding: 6pt 0;
|
||||
border-bottom: 0.3pt dotted var(--border);
|
||||
font-size: 11pt;
|
||||
}
|
||||
.toc-item:last-of-type { border-bottom: none; }
|
||||
.toc-num {
|
||||
color: var(--brand);
|
||||
font-weight: 500;
|
||||
min-width: 30pt;
|
||||
}
|
||||
.toc-title {
|
||||
flex: 1;
|
||||
color: var(--near-black);
|
||||
padding-left: 6pt;
|
||||
text-decoration: none;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 12pt;
|
||||
}
|
||||
.toc-title[href]::after {
|
||||
content: target-counter(attr(href), page);
|
||||
color: var(--stone);
|
||||
font-variant-numeric: tabular-nums;
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
/* ========== HEADINGS ========== */
|
||||
h1 {
|
||||
font-size: 22pt;
|
||||
font-weight: 500;
|
||||
line-height: 1.2;
|
||||
margin: 0 0 10pt 0;
|
||||
border-left: 2.5pt solid var(--brand);
|
||||
border-radius: 1.5pt;
|
||||
padding-left: 8pt;
|
||||
color: var(--near-black);
|
||||
break-after: avoid;
|
||||
string-set: section-title content();
|
||||
}
|
||||
|
||||
.running-title { string-set: section-title content(); }
|
||||
|
||||
h2 {
|
||||
font-size: 16pt;
|
||||
font-weight: 500;
|
||||
line-height: 1.25;
|
||||
margin: 24pt 0 8pt 0;
|
||||
color: var(--near-black);
|
||||
break-after: avoid;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: 13pt;
|
||||
font-weight: 500;
|
||||
line-height: 1.3;
|
||||
margin: 18pt 0 6pt 0;
|
||||
color: var(--dark-warm);
|
||||
break-after: avoid;
|
||||
}
|
||||
|
||||
.chapter-num {
|
||||
font-family: var(--sans);
|
||||
font-size: 10pt;
|
||||
color: var(--brand);
|
||||
letter-spacing: 1pt;
|
||||
text-transform: uppercase;
|
||||
margin-bottom: 6pt;
|
||||
}
|
||||
|
||||
/* ========== PARAGRAPHS ========== */
|
||||
p {
|
||||
margin: 0 0 10pt 0;
|
||||
line-height: 1.55;
|
||||
color: var(--near-black);
|
||||
widows: 2;
|
||||
orphans: 2;
|
||||
}
|
||||
|
||||
.lead {
|
||||
font-size: 12pt;
|
||||
line-height: 1.55;
|
||||
color: var(--dark-warm);
|
||||
margin-bottom: var(--rhythm-module);
|
||||
}
|
||||
|
||||
.hl { color: var(--brand); font-weight: 500; }
|
||||
strong { font-weight: 500; }
|
||||
|
||||
/* ========== LISTS ========== */
|
||||
ul, ol {
|
||||
margin: 6pt 0 10pt 0;
|
||||
padding-left: 20pt;
|
||||
line-height: 1.55;
|
||||
}
|
||||
ul li::marker { color: var(--brand); }
|
||||
ol li::marker { color: var(--brand); font-weight: 500; }
|
||||
|
||||
/* ========== QUOTE ========== */
|
||||
blockquote, .quote {
|
||||
border-left: 2pt solid var(--brand);
|
||||
margin: 12pt 0;
|
||||
padding: 4pt 0 4pt 16pt;
|
||||
color: var(--olive);
|
||||
line-height: 1.55;
|
||||
break-inside: avoid;
|
||||
}
|
||||
blockquote .cite, .quote .cite {
|
||||
display: block;
|
||||
font-size: 9pt;
|
||||
color: var(--stone);
|
||||
margin-top: 4pt;
|
||||
}
|
||||
|
||||
/* ========== CODE ========== */
|
||||
/* Mono first for English glyphs, then CJK fallback so Chinese comments
|
||||
inside code/pre render correctly. A mono-only chain causes missing
|
||||
glyphs rendered as "?" or missing-glyph boxes in WeasyPrint when the system lacks
|
||||
a mono font with CJK coverage. */
|
||||
code {
|
||||
font-family: "JetBrains Mono", "SF Mono", Consolas,
|
||||
"TsangerJinKai02", "Source Han Serif SC",
|
||||
"Noto Serif CJK SC", "Songti SC", monospace;
|
||||
font-size: 9pt;
|
||||
background: var(--ivory);
|
||||
padding: 1pt 4pt;
|
||||
border-radius: 2pt;
|
||||
color: var(--dark-warm);
|
||||
}
|
||||
pre {
|
||||
font-family: "JetBrains Mono", "SF Mono", Consolas,
|
||||
"TsangerJinKai02", "Source Han Serif SC",
|
||||
"Noto Serif CJK SC", "Songti SC", monospace;
|
||||
font-size: 9pt;
|
||||
line-height: 1.5;
|
||||
background: var(--ivory);
|
||||
border-radius: 4pt;
|
||||
padding: 10pt 14pt;
|
||||
margin: 10pt 0;
|
||||
white-space: pre-wrap;
|
||||
color: var(--near-black);
|
||||
break-inside: avoid;
|
||||
}
|
||||
pre code { background: transparent; padding: 0; font-size: inherit; }
|
||||
|
||||
/* ========== TABLE (kami-table) ========== */
|
||||
table, .kami-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
font-size: 9.5pt;
|
||||
margin: 12pt 0;
|
||||
break-inside: avoid;
|
||||
}
|
||||
table th, .kami-table th {
|
||||
text-align: left;
|
||||
font-weight: 500;
|
||||
color: var(--dark-warm);
|
||||
padding: 6pt 8pt;
|
||||
border-bottom: 1pt solid var(--border);
|
||||
background: transparent;
|
||||
}
|
||||
table td, .kami-table td {
|
||||
padding: 5pt 8pt;
|
||||
border-bottom: 0.3pt solid var(--border-soft);
|
||||
vertical-align: top;
|
||||
}
|
||||
table.compact th, .kami-table.compact th { padding: 3pt 6pt; font-size: 8pt; }
|
||||
table.compact td, .kami-table.compact td { padding: 2pt 6pt; font-size: 8pt; line-height: 1.4; }
|
||||
table.financial td:not(:first-child), .kami-table.financial td:not(:first-child) {
|
||||
text-align: right; font-variant-numeric: tabular-nums;
|
||||
}
|
||||
table.financial th:not(:first-child), .kami-table.financial th:not(:first-child) { text-align: right; }
|
||||
table.striped tbody tr:nth-child(even) td, .kami-table.striped tbody tr:nth-child(even) td {
|
||||
background: var(--ivory);
|
||||
}
|
||||
table .total td, .kami-table .total td {
|
||||
font-weight: 500; border-top: 1pt solid var(--brand); border-bottom: none; color: var(--near-black);
|
||||
}
|
||||
|
||||
/* ========== CALLOUT ========== */
|
||||
.callout {
|
||||
background: var(--ivory);
|
||||
border-left: 2pt solid var(--brand);
|
||||
padding: 10pt 14pt;
|
||||
border-radius: 3pt;
|
||||
margin: 12pt 0;
|
||||
line-height: 1.55;
|
||||
break-inside: avoid;
|
||||
}
|
||||
.callout blockquote,
|
||||
.callout .quote {
|
||||
border-left: none;
|
||||
padding-left: 0;
|
||||
margin: 6pt 0;
|
||||
color: var(--olive);
|
||||
}
|
||||
.callout blockquote:first-child,
|
||||
.callout .quote:first-child { margin-top: 0; }
|
||||
.callout blockquote:last-child,
|
||||
.callout .quote:last-child { margin-bottom: 0; }
|
||||
|
||||
.takeaway {
|
||||
background: var(--ivory);
|
||||
border-radius: 4pt;
|
||||
padding: 10pt 14pt;
|
||||
margin: 14pt 0;
|
||||
break-inside: avoid;
|
||||
}
|
||||
.takeaway-label {
|
||||
font-family: var(--sans);
|
||||
font-size: 9pt;
|
||||
color: var(--brand);
|
||||
letter-spacing: 0.5pt;
|
||||
text-transform: uppercase;
|
||||
margin-bottom: 4pt;
|
||||
}
|
||||
|
||||
/* ========== TAG ========== */
|
||||
.tag {
|
||||
display: inline-block;
|
||||
background: var(--tag-bg);
|
||||
color: var(--brand);
|
||||
font-family: var(--sans);
|
||||
font-size: 9pt;
|
||||
font-weight: 500;
|
||||
padding: 1pt 6pt;
|
||||
border-radius: 3pt;
|
||||
margin-right: 4pt;
|
||||
}
|
||||
|
||||
/* ========== FIGURE ========== */
|
||||
figure {
|
||||
margin: 14pt 0;
|
||||
break-inside: avoid;
|
||||
}
|
||||
figure img {
|
||||
max-width: 100%;
|
||||
border-radius: 4pt;
|
||||
}
|
||||
/* Inline diagrams (e.g. a Mermaid-sourced SVG via scripts/mermaid_normalize.py):
|
||||
center and cap width so the figure never bleeds past the text column. */
|
||||
figure svg {
|
||||
display: block;
|
||||
margin: 0 auto;
|
||||
max-width: 78%;
|
||||
height: auto;
|
||||
}
|
||||
figcaption {
|
||||
font-family: var(--sans);
|
||||
font-size: 9pt;
|
||||
color: var(--stone);
|
||||
margin-top: 6pt;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* ========== EXECUTIVE SUMMARY ========== */
|
||||
.exec-summary {
|
||||
background: var(--ivory);
|
||||
border-left: 1.4pt solid var(--brand);
|
||||
border-radius: 6pt;
|
||||
padding: 16pt 20pt;
|
||||
margin: 14pt 0;
|
||||
break-inside: avoid;
|
||||
}
|
||||
.exec-summary h2 {
|
||||
margin-top: 0;
|
||||
font-size: 13pt;
|
||||
}
|
||||
|
||||
/* ========== CHAPTER BREAK ========== */
|
||||
.chapter { break-before: page; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<!-- ═════════════ COVER ═════════════ -->
|
||||
<section class="cover">
|
||||
<div>
|
||||
<div class="cover-eyebrow">{{EYEBROW · 如 "技术报告" / "年度总结" / "白皮书"}}</div>
|
||||
<div class="cover-title">{{文档主标题<br>可以两行}}</div>
|
||||
<div class="cover-sub">{{副标题,一句话说清这份文档是什么 / 为谁而写}}</div>
|
||||
</div>
|
||||
<div class="cover-meta">
|
||||
<strong>{{作者 / 团队}}</strong><br>
|
||||
{{版本 V1.0}} · {{日期 2026.04}}<br>
|
||||
{{发布方 / 机构}}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ═════════════ 目录 ═════════════ -->
|
||||
<section class="toc">
|
||||
<h2>目录</h2>
|
||||
<div class="toc-item">
|
||||
<span class="toc-num">01</span>
|
||||
<a class="toc-title" href="#ch-executive-summary">执行摘要</a>
|
||||
</div>
|
||||
<div class="toc-item">
|
||||
<span class="toc-num">02</span>
|
||||
<a class="toc-title" href="#ch-background">背景与问题定义</a>
|
||||
</div>
|
||||
<div class="toc-item">
|
||||
<span class="toc-num">03</span>
|
||||
<a class="toc-title" href="#ch-methodology">方法与发现</a>
|
||||
</div>
|
||||
<div class="toc-item">
|
||||
<span class="toc-num">04</span>
|
||||
<a class="toc-title" href="#ch-conclusions">结论与建议</a>
|
||||
</div>
|
||||
<div class="toc-item">
|
||||
<span class="toc-num">05</span>
|
||||
<a class="toc-title" href="#ch-appendix">附录</a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ═════════════ Chapter 01:执行摘要 ═════════════ -->
|
||||
<section class="chapter" id="ch-executive-summary">
|
||||
<div class="chapter-num">01 · Executive Summary</div>
|
||||
<h1>执行摘要</h1>
|
||||
|
||||
<p class="lead">
|
||||
{{一段 2-3 句话的大论点开场。用 <span class="hl">关键词高亮</span> 抓住读者注意力。
|
||||
让读者读这段就能理解整份文档想表达什么。}}
|
||||
</p>
|
||||
|
||||
<h2>核心 Takeaways</h2>
|
||||
<ul>
|
||||
<li>{{Takeaway 1:一句话,可量化的结论}}</li>
|
||||
<li>{{Takeaway 2:有数据的洞察}}</li>
|
||||
<li>{{Takeaway 3:对未来的判断}}</li>
|
||||
</ul>
|
||||
|
||||
<div class="takeaway">
|
||||
<div class="takeaway-label">本文档回答的问题</div>
|
||||
{{用疑问句列出 3 个本文档的核心问题。让读者立刻 get 到是否需要读完。}}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ═════════════ Chapter 02:背景 ═════════════ -->
|
||||
<section class="chapter" id="ch-background">
|
||||
<div class="chapter-num">02 · Background</div>
|
||||
<h1>背景与问题定义</h1>
|
||||
|
||||
<p class="lead">
|
||||
{{章节导语:这一章要解决什么问题,为什么重要。1-2 句。}}
|
||||
</p>
|
||||
|
||||
<h2>当前现状</h2>
|
||||
<p>{{3-5 行段落,铺陈当前状况。用 <span class="hl">具体数据</span> 而不是形容词。}}</p>
|
||||
|
||||
<h2>核心问题</h2>
|
||||
<p>{{陈述具体的核心问题。可以用 callout 突出关键观察:}}</p>
|
||||
|
||||
<div class="callout">
|
||||
{{一段重要引用或核心观察。和正文语气略有不同,给读者呼吸节奏。}}
|
||||
</div>
|
||||
|
||||
<h2>衡量标准</h2>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>维度</th>
|
||||
<th>当前水平</th>
|
||||
<th>目标水平</th>
|
||||
<th>差距</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>{{维度 1}}</td>
|
||||
<td>{{数据}}</td>
|
||||
<td>{{数据}}</td>
|
||||
<td>{{差距}}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{{维度 2}}</td>
|
||||
<td>{{数据}}</td>
|
||||
<td>{{数据}}</td>
|
||||
<td>{{差距}}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</section>
|
||||
|
||||
<!-- ═════════════ Chapter 03:方法 ═════════════ -->
|
||||
<section class="chapter" id="ch-methodology">
|
||||
<div class="chapter-num">03 · Methodology</div>
|
||||
<h1>方法与发现</h1>
|
||||
|
||||
<p class="lead">{{章节导语}}</p>
|
||||
|
||||
<h2>研究方法</h2>
|
||||
<p>{{描述方法论。可以用代码 / 公式示例:}}</p>
|
||||
|
||||
<pre><code class="language-python">def analyze(data):
|
||||
"""Transform raw data."""
|
||||
return transform(data)</code></pre>
|
||||
|
||||
<figure>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 420 280" width="420" height="280" style="background:#f5f4ed">
|
||||
<style>
|
||||
text { font-family: Charter, Georgia, "TsangerJinKai02", "Source Han Serif SC", "Noto Serif CJK SC", serif; }
|
||||
</style>
|
||||
<defs>
|
||||
<marker id="seq-arrow" markerWidth="8" markerHeight="5" refX="8" refY="2.5" orient="auto-start-reverse">
|
||||
<polygon points="0 0, 8 2.5, 0 5" fill="#1b365d" />
|
||||
</marker>
|
||||
<marker id="seq-arrow-open" markerWidth="8" markerHeight="5" refX="8" refY="2.5" orient="auto-start-reverse">
|
||||
<polyline points="0 0, 8 2.5, 0 5" fill="none" stroke="#1b365d" stroke-width="1" />
|
||||
</marker>
|
||||
</defs>
|
||||
<line class="lifeline" data-actor="U" x1="70" y1="70" x2="70" y2="250" stroke="#504e49" stroke-width="0.75" stroke-dasharray="6 4" />
|
||||
<line class="lifeline" data-actor="A" x1="210" y1="70" x2="210" y2="250" stroke="#504e49" stroke-width="0.75" stroke-dasharray="6 4" />
|
||||
<line class="lifeline" data-actor="D" x1="350" y1="70" x2="350" y2="250" stroke="#504e49" stroke-width="0.75" stroke-dasharray="6 4" />
|
||||
<g class="message" data-from="U" data-to="A" data-label="提交请求" data-line-style="solid" data-arrow-head="filled" data-self="false">
|
||||
<line x1="70" y1="90" x2="210" y2="90" stroke="#504e49" stroke-width="1" marker-end="url(#seq-arrow)" />
|
||||
<text x="140" y="80" font-size="11" text-anchor="middle" font-weight="400" fill="#6b6a64" dy="3.8499999999999996">提交请求</text>
|
||||
</g>
|
||||
<g class="message" data-from="A" data-to="D" data-label="查询记录" data-line-style="solid" data-arrow-head="filled" data-self="false">
|
||||
<line x1="210" y1="130" x2="350" y2="130" stroke="#504e49" stroke-width="1" marker-end="url(#seq-arrow)" />
|
||||
<text x="280" y="120" font-size="11" text-anchor="middle" font-weight="400" fill="#6b6a64" dy="3.8499999999999996">查询记录</text>
|
||||
</g>
|
||||
<g class="message" data-from="D" data-to="A" data-label="返回结果" data-line-style="dashed" data-arrow-head="filled" data-self="false">
|
||||
<line x1="350" y1="170" x2="210" y2="170" stroke="#504e49" stroke-width="1" stroke-dasharray="6 4" marker-end="url(#seq-arrow)" />
|
||||
<text x="280" y="160" font-size="11" text-anchor="middle" font-weight="400" fill="#6b6a64" dy="3.8499999999999996">返回结果</text>
|
||||
</g>
|
||||
<g class="message" data-from="A" data-to="U" data-label="响应数据" data-line-style="dashed" data-arrow-head="filled" data-self="false">
|
||||
<line x1="210" y1="210" x2="70" y2="210" stroke="#504e49" stroke-width="1" stroke-dasharray="6 4" marker-end="url(#seq-arrow)" />
|
||||
<text x="140" y="200" font-size="11" text-anchor="middle" font-weight="400" fill="#6b6a64" dy="3.8499999999999996">响应数据</text>
|
||||
</g>
|
||||
<g class="actor" data-id="U" data-label="用户" data-type="participant">
|
||||
<rect x="30" y="30" width="80" height="40" rx="4" ry="4" fill="#faf9f5" stroke="#e8e6dc" stroke-width="1" />
|
||||
<text x="70" y="50" font-size="13" text-anchor="middle" font-weight="500" fill="#141413" dy="4.55">用户</text>
|
||||
</g>
|
||||
<g class="actor" data-id="A" data-label="API" data-type="participant">
|
||||
<rect x="170" y="30" width="80" height="40" rx="4" ry="4" fill="#faf9f5" stroke="#e8e6dc" stroke-width="1" />
|
||||
<text x="210" y="50" font-size="13" text-anchor="middle" font-weight="500" fill="#141413" dy="4.55">API</text>
|
||||
</g>
|
||||
<g class="actor" data-id="D" data-label="数据库" data-type="participant">
|
||||
<rect x="310" y="30" width="80" height="40" rx="4" ry="4" fill="#faf9f5" stroke="#e8e6dc" stroke-width="1" />
|
||||
<text x="350" y="50" font-size="13" text-anchor="middle" font-weight="500" fill="#141413" dy="4.55">数据库</text>
|
||||
</g>
|
||||
</svg>
|
||||
<figcaption>图 1:用户请求的调用时序</figcaption>
|
||||
</figure>
|
||||
|
||||
<h2>关键发现</h2>
|
||||
|
||||
<h3>发现 1:{{标题}}</h3>
|
||||
<p>{{一段论述。包含数据:<span class="hl">具体数字 / 具体比例</span>。}}</p>
|
||||
|
||||
<h3>发现 2:{{标题}}</h3>
|
||||
<p>{{一段论述。包含数据:<span class="hl">具体数字 / 具体比例</span>。}}</p>
|
||||
|
||||
<blockquote>
|
||||
{{一段引用,可以是用户访谈、专家观点、文献引用}}
|
||||
<span class="cite"> - {{来源 / 人物}},{{日期}}</span>
|
||||
</blockquote>
|
||||
</section>
|
||||
|
||||
<!-- ═════════════ Chapter 04:结论 ═════════════ -->
|
||||
<section class="chapter" id="ch-conclusions">
|
||||
<div class="chapter-num">04 · Conclusions</div>
|
||||
<h1>结论与建议</h1>
|
||||
|
||||
<p class="lead">{{章节导语:一句话总结结论,下面展开建议。}}</p>
|
||||
|
||||
<h2>核心结论</h2>
|
||||
<ol>
|
||||
<li>{{结论 1}}</li>
|
||||
<li>{{结论 2}}</li>
|
||||
<li>{{结论 3}}</li>
|
||||
</ol>
|
||||
|
||||
<h2>下一步建议</h2>
|
||||
<p>{{基于结论的具体可执行建议。}}</p>
|
||||
|
||||
<div class="takeaway">
|
||||
<div class="takeaway-label">Call to Action</div>
|
||||
{{如果读者要做一件事,是什么?具体到可以周一早上就开始行动。}}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ═════════════ Chapter 05:附录 ═════════════ -->
|
||||
<section class="chapter" id="ch-appendix">
|
||||
<div class="chapter-num">05 · Appendix</div>
|
||||
<h1>附录</h1>
|
||||
|
||||
<h2>A. 参考资料</h2>
|
||||
<ul>
|
||||
<li>{{参考文献 1}}</li>
|
||||
<li>{{参考文献 2}}</li>
|
||||
</ul>
|
||||
|
||||
<h2>B. 术语表</h2>
|
||||
<p><strong>{{术语}}</strong>:{{定义}}</p>
|
||||
<p><strong>{{术语}}</strong>:{{定义}}</p>
|
||||
|
||||
<h2>C. 致谢</h2>
|
||||
<p>{{致谢段落}}</p>
|
||||
</section>
|
||||
|
||||
</body>
|
||||
</html>
|
||||