OpenPress

<Press theme> + press/<slug>/theme/

佈景主題 (Themes)

OpenPress theme 有兩層:typed theme object 負責 Press metadata 與 CSS variables;Press-local theme 目錄負責實際 CSS 與字型載入。

OpenPress 的 theme 有兩層。

Typed theme object 寫在 press/<slug>/press.tsx,並傳給 <Press theme={...}>。它告訴引擎這份 Press 使用 documentslidebare profile,並把 --op-theme-* CSS variables 注入 measurement 與 runtime。

Folder-local theme 目錄 press/<slug>/theme/ 仍然是實際 CSS source。字型載入、prose rules、slide template classes、專案自己的 selectors 都放在這裡。Generic page shell、measurement CSS、print reset 與預設 MDX prose surface 則由 framework 維護。

為什麼 theme 要跟著 Press 走?

同一個 workspace 可能同時有 A4 報告與 16:9 slide deck。這些格式不應該共享意外的全域視覺狀態。Theme object 與 theme 目錄都跟著所屬 Press 走,才能讓每個輸出有清楚的 styling boundary。

Theme object 用來放:

  • profile 選擇:page-based document 用 defineDocumentTheme(),slide deck 用 defineSlideTheme()
  • 可攜式 color、font、typography tokens。
  • Workbench 與 reader 會讀到的 theme metadata。
  • pagination measurement 必須看得到的 CSS variables。

Theme 檔案用來放:

  • tokens.css:顏色、字型、間距與 artifact-level design decisions 的 CSS variables。
  • fonts.css:可攜式 @font-face 載入。
  • 消費 --op-theme-* variables 的 prose 或 slide-template selectors。

不要用 theme 檔案重做 page shell。新工作不應該再帶 base/page-contract.cssbase/print.cssbase/typography.css;這些契約已經由 framework 維護。

基本流程

Page-based document:

import { Press } from "@open-press/core";
import { defineDocumentTheme } from "@open-press/core/theme";

const documentTheme = defineDocumentTheme({
  name: "Report Theme",
  colors: {
    paper: "#ffffff",
    ink: "#16161d",
    accent: "#0d5f89",
  },
  typography: {
    heading: { font: "serif", size: "28pt", lineHeight: 1.18, color: "ink" },
    body: { font: "body", size: "10.5pt", lineHeight: 1.7, color: "ink" },
  },
});

export default function ReportPress() {
  return <Press slug="report" title="Report" page="a4" theme={documentTheme}>{/* pages */}</Press>;
}

Slide deck:

import { Press, Slide } from "@open-press/core";
import { defineSlideTheme } from "@open-press/core/theme";

const slideTheme = defineSlideTheme({
  name: "Deck Theme",
  colors: {
    bg: "#fcf7e9",
    ink: "#16161d",
    accent: "#d42a20",
  },
  typography: {
    display: { font: "serif", size: 138, lineHeight: 1.05, color: "ink" },
    body: { font: "sans", size: 36, lineHeight: 1.48, color: "muted" },
  },
});

export default function DeckPress() {
  return (
    <Press slug="deck" title="Deck" type="slides" page="slide-16-9" theme={slideTheme}>
      <Slide id="cover" />
    </Press>
  );
}

Profile 要和 Press type 對齊。Slide Press 使用 defineSlideTheme();page-based Press 使用 defineDocumentTheme()

Page geometry

頁面尺寸寫在 <Press page>,不是 CSS。通用格式可用內建 preset:

<Press page="a4" />
<Press page="slide-16-9" />

專案特定格式請用 custom object:

<Press
  page={{ id: "field-guide-card", label: "Field Guide Card", width: "148mm", height: "210mm" }}
/>

Exporter 會把 resolved geometry 寫入 document theme metadata,並注入 runtime CSS variables,例如 --openpress-page-width--openpress-page-height。Theme object 可以定義 typography 與 color,但不取代 <Press page>

Tailwind v4 與 prose

新 UI 採 Tailwind-first。Page chrome、cover、TOC、slide layout 與 prose variants 應該放在 React components,用 Tailwind classes 表達。MDX 內文使用 framework 的 prose surface,再透過 component classes 或 prose preset 客製化,不要回到 shared typography CSS。

多 Press 共用 Tailwind token name 時,Tailwind v4 @theme 應維持 generic 或 variable-backed。Artifact-specific values 要放在該 Press wrapper 或 component 裡,避免某個 Press 污染另一個 Press。