portable-agent-layer 0.30.1 → 0.32.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/assets/skills/consulting-report/SKILL.md +74 -74
  2. package/assets/skills/consulting-report/demo/app/globals.css +459 -0
  3. package/assets/skills/consulting-report/demo/app/layout.tsx +32 -0
  4. package/assets/skills/consulting-report/demo/app/page.tsx +302 -0
  5. package/assets/skills/consulting-report/demo/bun.lock +240 -0
  6. package/assets/skills/consulting-report/demo/components/callout.tsx +13 -0
  7. package/assets/skills/consulting-report/demo/components/comparison-table.tsx +40 -0
  8. package/assets/skills/consulting-report/demo/components/cover-page.tsx +34 -0
  9. package/assets/skills/consulting-report/demo/components/exhibit.tsx +21 -0
  10. package/assets/skills/consulting-report/demo/components/finding-card.tsx +28 -0
  11. package/assets/skills/consulting-report/demo/components/quote-block.tsx +17 -0
  12. package/assets/skills/consulting-report/demo/components/recommendation-card.tsx +52 -0
  13. package/assets/skills/consulting-report/demo/components/section.tsx +17 -0
  14. package/assets/skills/consulting-report/demo/components/severity-badge.tsx +19 -0
  15. package/assets/skills/consulting-report/demo/components/stat-grid.tsx +26 -0
  16. package/assets/skills/consulting-report/demo/components/table-of-contents.tsx +27 -0
  17. package/assets/skills/consulting-report/demo/components/timeline.tsx +20 -0
  18. package/assets/skills/consulting-report/demo/lib/report-data.ts +247 -0
  19. package/assets/skills/consulting-report/demo/lib/utils.ts +6 -0
  20. package/assets/skills/consulting-report/demo/next.config.js +9 -0
  21. package/assets/skills/consulting-report/demo/package.json +27 -0
  22. package/assets/skills/consulting-report/demo/postcss.config.mjs +5 -0
  23. package/assets/skills/consulting-report/demo/tsconfig.json +41 -0
  24. package/assets/skills/consulting-report/template/app/globals.css +459 -0
  25. package/assets/skills/consulting-report/template/app/layout.tsx +32 -0
  26. package/assets/skills/consulting-report/template/app/page.tsx +282 -0
  27. package/assets/skills/consulting-report/template/bun.lock +240 -0
  28. package/assets/skills/consulting-report/template/components/callout.tsx +13 -0
  29. package/assets/skills/consulting-report/template/components/comparison-table.tsx +40 -0
  30. package/assets/skills/consulting-report/template/components/cover-page.tsx +34 -0
  31. package/assets/skills/consulting-report/template/components/exhibit.tsx +21 -0
  32. package/assets/skills/consulting-report/template/components/finding-card.tsx +28 -0
  33. package/assets/skills/consulting-report/template/components/quote-block.tsx +17 -0
  34. package/assets/skills/consulting-report/template/components/recommendation-card.tsx +52 -0
  35. package/assets/skills/consulting-report/template/components/section.tsx +17 -0
  36. package/assets/skills/consulting-report/template/components/severity-badge.tsx +19 -0
  37. package/assets/skills/consulting-report/template/components/stat-grid.tsx +26 -0
  38. package/assets/skills/consulting-report/template/components/table-of-contents.tsx +27 -0
  39. package/assets/skills/consulting-report/template/components/timeline.tsx +20 -0
  40. package/assets/skills/consulting-report/template/lib/report-data.ts +176 -0
  41. package/assets/skills/consulting-report/template/lib/utils.ts +6 -0
  42. package/assets/skills/consulting-report/template/next.config.js +9 -0
  43. package/assets/skills/consulting-report/template/package.json +27 -0
  44. package/assets/skills/consulting-report/template/postcss.config.mjs +5 -0
  45. package/assets/skills/consulting-report/template/tsconfig.json +27 -0
  46. package/assets/skills/consulting-report/tools/dev.ts +47 -0
  47. package/assets/skills/consulting-report/tools/generate-pdf.ts +140 -408
  48. package/assets/skills/consulting-report/tools/scaffold.ts +83 -48
  49. package/assets/skills/presentation/SKILL.md +1 -1
  50. package/package.json +9 -9
  51. package/assets/skills/consulting-report/demo/content/current-state.md +0 -33
  52. package/assets/skills/consulting-report/demo/content/executive-summary.md +0 -19
  53. package/assets/skills/consulting-report/demo/content/report-data.ts +0 -101
  54. package/assets/skills/consulting-report/demo/diagrams/.gitkeep +0 -0
  55. package/assets/skills/consulting-report/template/README.md +0 -28
  56. package/assets/skills/consulting-report/template/content/executive-summary.md +0 -19
  57. package/assets/skills/consulting-report/template/content/report-data.ts +0 -59
  58. package/assets/skills/consulting-report/template/diagrams/.gitkeep +0 -0
@@ -1,115 +1,115 @@
1
1
  ---
2
2
  name: consulting-report
3
- description: Produce branded consulting-report PDFs from a structured report directory (cover page, linked TOC, headers/footers with page numbers, typography system, callout boxes, findings + recommendations). Use when generating an assessment report, strategic review, or consulting deliverable PDF.
4
- argument-hint: <report-dir> OR `scaffold <target-dir>` to start a new report
3
+ description: Build a beautifully-typeset consulting-report PDF from a typed data file and a React layout. Use when generating an assessment, strategic review, operational readiness check, or any McKinsey-style consulting deliverable as a PDF.
4
+ argument-hint: scaffold <target-dir> | dev <report-dir> | <report-dir> (render PDF)
5
5
  ---
6
6
 
7
7
  ## Overview
8
8
 
9
- Renders a structured consulting-report directory to a branded PDF: cover page, linked table of contents, page-numbered headers/footers, typography system (Georgia body + Inter headings), colored callout boxes for findings and recommendations, tables with zebra striping.
10
-
11
- Each report lives in its own directory with data (TypeScript) + narrative (Markdown) + diagrams (images). The skill provides a scaffolder to spin up new reports from a template and a generator to render them.
12
-
13
- **Default brand:** Konvert7. Override per report via the `brand` block in `report-data.ts`.
14
-
15
- ## Report Directory Layout
16
-
17
- ```
18
- <report-dir>/
19
- ├── content/
20
- │ ├── report-data.ts # report structure (schema: ConsultingReport)
21
- │ ├── executive-summary.md # narrative sections
22
- │ └── …
23
- ├── diagrams/ # source images (PNG/JPG)
24
- ├── diagrams-compressed/ # generated — ignore
25
- └── <client>-<title>-<date>.{pdf,html} # output
26
- ```
9
+ Every report is a self-contained Next.js app: typed report data in `lib/report-data.ts`, layout composed from React components in `app/page.tsx`, fonts via `next/font/google` (Source Serif 4 + Inter), Tailwind v4 for styling. `bun run dev` gives a live preview while authoring; the PDF is rendered by Playwright against a static export.
27
10
 
28
11
  ## Workflow
29
12
 
30
- ### Step 1: Scaffold a new report (skip if the report directory already exists)
13
+ ### 1. Scaffold a new report
31
14
 
32
15
  ```bash
33
16
  bun ~/.pal/skills/consulting-report/tools/scaffold.ts <target-dir> \
34
- --client "Client Name" \
35
- --title "Report Title"
17
+ [--client "Client Name"] [--title "Report Title"] [--no-install]
36
18
  ```
37
19
 
38
- Creates the directory, stamps today's date + client + title into `report-data.ts`, and writes a starter `executive-summary.md`. If the target directory already exists, the command errors — move or remove first.
20
+ Creates `<target-dir>` from the template and runs `bun install` inside. If `--client` / `--title` are passed, they're stamped into `lib/report-data.ts`. The scaffolder refuses to overwrite an existing directory.
21
+
22
+ ### 2. Fill in the content
39
23
 
40
- ### Step 2: Fill in content
24
+ Two files do the work:
41
25
 
42
- Edit:
26
+ - **`lib/report-data.ts`** — typed metadata: client, title, date, classification, consultancy name, executive summary, situation assessment, findings, risk analysis, strategic opportunity, recommendations, target state, roadmap, call to action. The `ReportData` interface guides what's required.
27
+ - **`app/page.tsx`** — the layout. Composes `<CoverPage/>`, `<Section/>`, `<Exhibit/>`, `<FindingCard/>`, `<RecommendationCard/>`, `<Callout/>`, `<Timeline/>`, etc. against the data. Edit freely — section titles, intros, ordering, custom JSX, anything.
43
28
 
44
- - `<dir>/content/report-data.ts` cover metadata, sections list, optional findings / recommendations / conclusion / appendix. Each `section.content` is either an inline markdown string OR a `.md` filename relative to `content/`.
45
- - `<dir>/content/*.md` — the narrative sections referenced from `report-data.ts`.
46
- - `<dir>/diagrams/` — drop PNG/JPG images. Reference them from markdown with relative paths, e.g. `![alt](../diagrams-compressed/architecture.jpg)`.
29
+ Static images go in `public/`; reference them from JSX as `<img src="/your-image.png">`.
47
30
 
48
- ### Step 3: Render
31
+ ### 3. Live preview while authoring
49
32
 
50
33
  ```bash
51
- node --experimental-strip-types ~/.pal/skills/consulting-report/tools/generate-pdf.ts <report-dir>
34
+ bun ~/.pal/skills/consulting-report/tools/dev.ts <report-dir>
52
35
  ```
53
36
 
54
- Output: `<dir>/<client-slug>-<title-slug>-<date>.pdf` and matching `.html`. Override with `--pdf <path>` / `--html <path>`.
55
-
56
- The generator:
57
- - Loads `content/report-data.ts` dynamically
58
- - Compresses `diagrams/*` to JPEG 70% / 1200px via `sips` (macOS); silently skips if `sips` is absent
59
- - Renders cover, auto-generated linked TOC, sections, findings, recommendations, conclusion, appendix
60
- - Prints via Playwright with page-numbered header/footer templates
37
+ Wraps `bun run dev` in the report directory. Open the URL printed by Next, edit `app/page.tsx` or `lib/report-data.ts`, browser hot-reloads.
61
38
 
62
- ### Step 4: Verify
39
+ ### 4. Render the PDF
63
40
 
64
41
  ```bash
65
- ls -lh <dir>/*.pdf
42
+ node --experimental-strip-types ~/.pal/skills/consulting-report/tools/generate-pdf.ts <report-dir>
66
43
  ```
67
44
 
68
- Open the PDF. Check: cover centered and branded; TOC links jump; findings render in red/amber boxes by severity; recommendations in blue boxes with priority badges; every page has the CONFIDENTIAL footer and page number.
69
-
70
- ## Report Schema
71
-
72
- ```ts
73
- interface ConsultingReport {
74
- clientName: string;
75
- reportTitle: string;
76
- reportDate: string;
77
- classification: string; // e.g., "CONFIDENTIAL"
78
- version: string;
79
- brand?: { businessName: string; brandLabel?: string; logoPath?: string; };
80
- sections: Section[];
81
- findings?: Finding[]; // renders as red/amber/blue boxes by severity
82
- recommendations?: Recommendation[]; // blue boxes with priority badges
83
- conclusion?: Conclusion;
84
- supportingEvidence?: Record<string, string[]>; // appendix
85
- }
86
-
87
- interface Section { id: string; title: string; content: string; subsections?: Section[]; }
88
- interface Finding { id: string; title: string; severity: "critical"|"high"|"medium"|"low"; evidence: string; impact?: string; }
89
- interface Recommendation { id: string; title: string; priority: "immediate"|"short-term"|"long-term"; detail: string; owner?: string; }
90
- interface Conclusion { assessorNote?: string; contextNote?: string; closingRemarks?: string; }
45
+ Runs `next build` (which produces a static export at `out/`), then Playwright loads it via a tiny in-process HTTP server and prints the PDF with page-numbered header/footer. Output:
46
+
47
+ ```
48
+ <report-dir>/<client-slug>-<title-slug>-<date>.pdf
91
49
  ```
92
50
 
93
- ## Styling
51
+ Override with `--pdf <path>`. Pass `--skip-build` to re-render the PDF from the existing `out/` without re-building.
94
52
 
95
- The typography system (Georgia body 10.5pt / Inter headings), color palette (navy #1B2A4A, blue #2E5090, red #DC2626, amber #D97706, green #059669), callout boxes, badges, table styling, cover layout, and header/footer templates are baked into `tools/generate-pdf.ts`. To customize: edit the `css()` function and the `renderPdf()` header/footer strings in one place.
53
+ Run with **Node**, not Bun Playwright's `chromium.launch()` hangs under Bun on Windows.
96
54
 
97
- Do NOT combine CSS `@page` margin-box rules with the Playwright `displayHeaderFooter` templates — they duplicate.
55
+ ## Directory Layout
98
56
 
99
- ## Demo
57
+ ```
58
+ <report-dir>/
59
+ ├── app/
60
+ │ ├── layout.tsx # font wiring (Inter + Source Serif 4)
61
+ │ ├── page.tsx # the report's layout — edit freely
62
+ │ └── globals.css # design tokens via @theme + custom CSS
63
+ ├── components/ # report primitives — edit if you need new shapes
64
+ │ ├── cover-page.tsx
65
+ │ ├── table-of-contents.tsx
66
+ │ ├── section.tsx
67
+ │ ├── exhibit.tsx
68
+ │ ├── stat-grid.tsx
69
+ │ ├── comparison-table.tsx
70
+ │ ├── finding-card.tsx
71
+ │ ├── recommendation-card.tsx
72
+ │ ├── severity-badge.tsx
73
+ │ ├── callout.tsx
74
+ │ ├── quote-block.tsx
75
+ │ └── timeline.tsx
76
+ ├── lib/
77
+ │ ├── report-data.ts # all your content
78
+ │ └── utils.ts
79
+ ├── public/ # static images, optional
80
+ ├── package.json # exact-pinned: next, react, tailwindcss, etc.
81
+ ├── tsconfig.json
82
+ ├── next.config.js
83
+ └── postcss.config.mjs
84
+ ```
100
85
 
101
- A runnable demo lives at `~/.pal/skills/consulting-report/demo/`:
86
+ ## Component Cheatsheet
87
+
88
+ - `<CoverPage clientName reportTitle reportDate classification consultancyName preTitle />` — full-bleed cover
89
+ - `<TableOfContents items={[{id, title}, …]} title?>` — linked TOC, anchors to section IDs
90
+ - `<Section id title>` — top-level section with bottom-rule heading; `id` enables TOC links
91
+ - `<Exhibit number title source?>` — bordered card for figures, tables, side data
92
+ - `<StatGrid stats={[{value, label, caption?}, …]} />` — large-number grid for executive summary
93
+ - `<ComparisonTable leftLabel rightLabel rows={[{metric, left, right}, …]} metricLabel?>` — current vs. target side-by-side
94
+ - `<FindingCard finding={f} index={i} />` — driven by `Finding` type, includes severity badge
95
+ - `<RecommendationCard recommendation={r} index={i} />` — driven by `Recommendation` type, includes priority badge
96
+ - `<Callout label?>` — left-rule emphasis block (default label "Key Takeaway")
97
+ - `<QuoteBlock quote attribution role? />` — pull-quote with serif quote mark
98
+ - `<Timeline phases={r.roadmap} />` — vertical timeline with dotted line
99
+ - `<SeverityBadge severity />` — pill badge: critical / high / medium / low
100
+
101
+ ## Demo
102
102
 
103
103
  ```bash
104
104
  node --experimental-strip-types ~/.pal/skills/consulting-report/tools/generate-pdf.ts ~/.pal/skills/consulting-report/demo
105
105
  ```
106
106
 
107
- Inspect the produced PDF to see the full layout (cover, TOC, sections, findings, recommendations, conclusion, appendix) before writing your own report.
107
+ Renders the bundled Acme Industries example end-to-end. Inspect the resulting PDF to see the full layout before authoring your own.
108
108
 
109
109
  ## Important
110
110
 
111
- - Reports live wherever you want; the skill only needs the `<report-dir>` path
112
- - The scaffolder refuses to overwrite an existing directory
113
- - Images go in `diagrams/`; reference them from markdown via `diagrams-compressed/<name>.jpg` so the compressed output is used
114
- - Heading anchor IDs come from `section.id` keep them unique and slug-safe
115
- - Every report re-renders deterministically from source; the PDF and HTML are disposable artifacts
111
+ - Run on Node 22.6 (Playwright + `--experimental-strip-types`)
112
+ - Bundled fonts come from Google Fonts via `next/font/google` — no licensing surface, no CDN at runtime, glyphs embedded at build time
113
+ - Reports are disposable artifacts of `lib/report-data.ts` + `app/page.tsx`; commit the source, not the PDF
114
+ - The scaffolder runs `bun install` inside the target by default — pass `--no-install` to skip
115
+ - Header/footer templates render with `displayHeaderFooter: true` in Playwright; don't combine with CSS `@page` margin-box rules
@@ -0,0 +1,459 @@
1
+ @import "tailwindcss";
2
+
3
+ @theme {
4
+ --color-background: #ffffff;
5
+ --color-background-secondary: #f8fafc;
6
+ --color-background-tertiary: #f1f5f9;
7
+ --color-background-elevated: #e2e8f0;
8
+
9
+ --color-foreground: #0f172a;
10
+ --color-muted: #475569;
11
+ --color-muted-dark: #94a3b8;
12
+
13
+ --color-border: #e2e8f0;
14
+ --color-border-subtle: #f1f5f9;
15
+ --color-border-emphasis: #cbd5e1;
16
+
17
+ --color-primary: #1d4ed8;
18
+ --color-accent: #7c3aed;
19
+ --color-success: #16a34a;
20
+ --color-warning: #d97706;
21
+ --color-destructive: #dc2626;
22
+
23
+ --color-section: #f8fafc;
24
+ --color-callout: #eff6ff;
25
+
26
+ --font-sans: var(--font-inter), system-ui, sans-serif;
27
+ --font-serif: var(--font-source-serif), Georgia, serif;
28
+ --font-heading: var(--font-inter), system-ui, sans-serif;
29
+ --font-body: var(--font-source-serif), Georgia, serif;
30
+ }
31
+
32
+ @source "./**/*.{ts,tsx}";
33
+ @source "../components/**/*.{ts,tsx}";
34
+
35
+ body {
36
+ font-family: var(--font-body);
37
+ background: var(--color-background);
38
+ color: var(--color-foreground);
39
+ line-height: 1.7;
40
+ -webkit-font-smoothing: antialiased;
41
+ -moz-osx-font-smoothing: grayscale;
42
+ }
43
+
44
+ /* McKinsey-style report layout */
45
+ .report-container {
46
+ max-width: 850px;
47
+ margin: 0 auto;
48
+ padding: 2rem;
49
+ }
50
+
51
+ .report-section {
52
+ margin-bottom: 3rem;
53
+ page-break-inside: avoid;
54
+ }
55
+
56
+ .report-section h2 {
57
+ font-family: var(--font-heading);
58
+ font-size: 1.75rem;
59
+ font-weight: 600;
60
+ color: var(--color-foreground);
61
+ margin-bottom: 1.5rem;
62
+ padding-bottom: 0.5rem;
63
+ border-bottom: 2px solid var(--color-primary);
64
+ }
65
+
66
+ .report-section h3 {
67
+ font-family: var(--font-heading);
68
+ font-size: 1.25rem;
69
+ font-weight: 600;
70
+ color: var(--color-foreground);
71
+ margin-bottom: 1rem;
72
+ }
73
+
74
+ /* Exhibit */
75
+ .exhibit {
76
+ background: var(--color-background-secondary);
77
+ border: 1px solid var(--color-border);
78
+ border-radius: 0.5rem;
79
+ padding: 1.5rem;
80
+ margin: 1.5rem 0;
81
+ }
82
+ .exhibit-header {
83
+ display: flex;
84
+ justify-content: space-between;
85
+ align-items: baseline;
86
+ margin-bottom: 1rem;
87
+ padding-bottom: 0.5rem;
88
+ border-bottom: 1px solid var(--color-border-subtle);
89
+ }
90
+ .exhibit-number {
91
+ font-family: var(--font-sans);
92
+ font-weight: 600;
93
+ color: var(--color-primary);
94
+ font-size: 0.875rem;
95
+ text-transform: uppercase;
96
+ letter-spacing: 0.1em;
97
+ }
98
+ .exhibit-title {
99
+ font-family: var(--font-heading);
100
+ font-weight: 600;
101
+ color: var(--color-foreground);
102
+ }
103
+
104
+ /* Callout */
105
+ .callout {
106
+ background: var(--color-callout);
107
+ border-left: 4px solid var(--color-primary);
108
+ padding: 1.25rem 1.5rem;
109
+ margin: 1.5rem 0;
110
+ border-radius: 0 0.5rem 0.5rem 0;
111
+ }
112
+ .callout-label {
113
+ font-family: var(--font-sans);
114
+ font-weight: 600;
115
+ color: var(--color-primary);
116
+ font-size: 0.75rem;
117
+ text-transform: uppercase;
118
+ letter-spacing: 0.1em;
119
+ margin-bottom: 0.5rem;
120
+ }
121
+ .callout-content {
122
+ font-size: 1.125rem;
123
+ font-weight: 500;
124
+ color: var(--color-foreground);
125
+ }
126
+
127
+ /* Quote block */
128
+ .quote-block {
129
+ position: relative;
130
+ padding: 1.5rem 2rem;
131
+ margin: 1.5rem 0;
132
+ background: var(--color-background-secondary);
133
+ border-radius: 0.5rem;
134
+ border: 1px solid var(--color-border-subtle);
135
+ }
136
+ .quote-block::before {
137
+ content: "\201C";
138
+ position: absolute;
139
+ top: 0.5rem;
140
+ left: 0.75rem;
141
+ font-size: 3rem;
142
+ color: var(--color-primary);
143
+ opacity: 0.5;
144
+ font-family: Georgia, serif;
145
+ line-height: 1;
146
+ }
147
+ .quote-text {
148
+ font-style: italic;
149
+ color: var(--color-foreground);
150
+ font-size: 1.0625rem;
151
+ line-height: 1.7;
152
+ }
153
+ .quote-attribution {
154
+ margin-top: 0.75rem;
155
+ font-size: 0.875rem;
156
+ color: var(--color-muted);
157
+ }
158
+
159
+ /* Severity badges */
160
+ .severity-badge {
161
+ display: inline-flex;
162
+ align-items: center;
163
+ padding: 0.25rem 0.75rem;
164
+ border-radius: 9999px;
165
+ font-family: var(--font-sans);
166
+ font-size: 0.75rem;
167
+ font-weight: 600;
168
+ text-transform: uppercase;
169
+ letter-spacing: 0.05em;
170
+ }
171
+ .severity-critical {
172
+ background: rgba(220, 38, 38, 0.1);
173
+ color: var(--color-destructive);
174
+ border: 1px solid rgba(220, 38, 38, 0.3);
175
+ }
176
+ .severity-high {
177
+ background: rgba(234, 88, 12, 0.1);
178
+ color: #ea580c;
179
+ border: 1px solid rgba(234, 88, 12, 0.3);
180
+ }
181
+ .severity-medium {
182
+ background: rgba(217, 119, 6, 0.1);
183
+ color: var(--color-warning);
184
+ border: 1px solid rgba(217, 119, 6, 0.3);
185
+ }
186
+ .severity-low {
187
+ background: rgba(22, 163, 74, 0.1);
188
+ color: var(--color-success);
189
+ border: 1px solid rgba(22, 163, 74, 0.3);
190
+ }
191
+
192
+ /* Finding card */
193
+ .finding-card {
194
+ background: var(--color-background-secondary);
195
+ border: 1px solid var(--color-border);
196
+ border-radius: 0.5rem;
197
+ padding: 1.5rem;
198
+ margin-bottom: 1rem;
199
+ }
200
+ .finding-header {
201
+ display: flex;
202
+ justify-content: space-between;
203
+ align-items: flex-start;
204
+ margin-bottom: 0.75rem;
205
+ }
206
+ .finding-title {
207
+ font-family: var(--font-heading);
208
+ font-weight: 600;
209
+ color: var(--color-foreground);
210
+ font-size: 1.0625rem;
211
+ }
212
+ .finding-evidence {
213
+ font-size: 0.9375rem;
214
+ color: var(--color-muted);
215
+ margin-top: 0.5rem;
216
+ }
217
+
218
+ /* Timeline */
219
+ .timeline {
220
+ position: relative;
221
+ padding-left: 2rem;
222
+ }
223
+ .timeline::before {
224
+ content: "";
225
+ position: absolute;
226
+ left: 0.5rem;
227
+ top: 0;
228
+ bottom: 0;
229
+ width: 2px;
230
+ background: linear-gradient(180deg, var(--color-primary) 0%, var(--color-accent) 100%);
231
+ }
232
+ .timeline-item {
233
+ position: relative;
234
+ padding-bottom: 1.5rem;
235
+ }
236
+ .timeline-item::before {
237
+ content: "";
238
+ position: absolute;
239
+ /* Center dot on the vertical line: line is at left:0.5rem (8px) +1px,
240
+ timeline-item starts at padding 2rem (32px). Dot half-width 6px → place
241
+ left at -1.8125rem so dot center lands at 9px from .timeline left. */
242
+ left: -1.8125rem;
243
+ top: 0.4rem;
244
+ width: 0.75rem;
245
+ height: 0.75rem;
246
+ border-radius: 50%;
247
+ background: var(--color-primary);
248
+ }
249
+ .timeline-phase {
250
+ font-family: var(--font-sans);
251
+ font-weight: 600;
252
+ color: var(--color-primary);
253
+ font-size: 0.875rem;
254
+ text-transform: uppercase;
255
+ letter-spacing: 0.1em;
256
+ }
257
+ .timeline-title {
258
+ font-family: var(--font-heading);
259
+ font-weight: 600;
260
+ color: var(--color-foreground);
261
+ margin-top: 0.25rem;
262
+ }
263
+ .timeline-description {
264
+ color: var(--color-muted);
265
+ font-size: 0.9375rem;
266
+ margin-top: 0.25rem;
267
+ }
268
+
269
+ /* Table of Contents */
270
+ .toc {
271
+ page-break-after: always;
272
+ margin-bottom: 3rem;
273
+ }
274
+ .toc h2 {
275
+ font-family: var(--font-heading);
276
+ font-size: 1.75rem;
277
+ font-weight: 600;
278
+ color: var(--color-foreground);
279
+ margin-bottom: 1.5rem;
280
+ padding-bottom: 0.5rem;
281
+ border-bottom: 2px solid var(--color-primary);
282
+ }
283
+ .toc ol {
284
+ list-style: none;
285
+ padding-left: 0;
286
+ margin: 0;
287
+ }
288
+ .toc li {
289
+ margin: 0.75rem 0;
290
+ border-bottom: 1px dotted var(--color-border-emphasis);
291
+ padding-bottom: 0.5rem;
292
+ }
293
+ .toc a {
294
+ display: flex;
295
+ gap: 1rem;
296
+ align-items: baseline;
297
+ color: var(--color-foreground);
298
+ text-decoration: none;
299
+ }
300
+ .toc-number {
301
+ font-family: var(--font-sans);
302
+ font-weight: 600;
303
+ font-size: 0.875rem;
304
+ color: var(--color-primary);
305
+ width: 2rem;
306
+ flex-shrink: 0;
307
+ }
308
+ .toc-title {
309
+ font-family: var(--font-heading);
310
+ font-weight: 500;
311
+ font-size: 1rem;
312
+ }
313
+
314
+ /* Stat grid */
315
+ .stat-grid {
316
+ display: grid;
317
+ gap: 1.5rem;
318
+ margin: 1.5rem 0;
319
+ padding: 1.5rem;
320
+ background: var(--color-background-secondary);
321
+ border-radius: 0.5rem;
322
+ border: 1px solid var(--color-border);
323
+ page-break-inside: avoid;
324
+ }
325
+ .stat {
326
+ text-align: left;
327
+ }
328
+ .stat-value {
329
+ font-family: var(--font-sans);
330
+ font-size: 2.5rem;
331
+ font-weight: 700;
332
+ color: var(--color-primary);
333
+ letter-spacing: -0.02em;
334
+ line-height: 1;
335
+ }
336
+ .stat-label {
337
+ margin-top: 0.5rem;
338
+ font-family: var(--font-sans);
339
+ font-size: 0.8125rem;
340
+ font-weight: 600;
341
+ color: var(--color-foreground);
342
+ }
343
+ .stat-caption {
344
+ margin-top: 0.25rem;
345
+ font-family: var(--font-body);
346
+ font-size: 0.8125rem;
347
+ color: var(--color-muted);
348
+ }
349
+
350
+ /* Comparison table */
351
+ .comparison-table {
352
+ width: 100%;
353
+ border-collapse: collapse;
354
+ margin: 1.5rem 0;
355
+ font-family: var(--font-body);
356
+ font-size: 0.9375rem;
357
+ page-break-inside: avoid;
358
+ }
359
+ .comparison-table th {
360
+ font-family: var(--font-sans);
361
+ font-size: 0.7rem;
362
+ font-weight: 600;
363
+ text-transform: uppercase;
364
+ letter-spacing: 0.08em;
365
+ color: var(--color-primary);
366
+ padding: 0.75rem 1rem;
367
+ border-bottom: 2px solid var(--color-primary);
368
+ text-align: left;
369
+ }
370
+ .comparison-table td {
371
+ padding: 0.75rem 1rem;
372
+ border-bottom: 1px solid var(--color-border-subtle);
373
+ vertical-align: top;
374
+ }
375
+ .comparison-table .metric {
376
+ font-family: var(--font-sans);
377
+ font-weight: 600;
378
+ color: var(--color-foreground);
379
+ }
380
+ .comparison-table tbody tr:last-child td {
381
+ border-bottom: none;
382
+ }
383
+
384
+ /* Cover page */
385
+ .cover-page {
386
+ min-height: 100vh;
387
+ display: flex;
388
+ flex-direction: column;
389
+ justify-content: center;
390
+ padding: 4rem;
391
+ page-break-after: always;
392
+ background: linear-gradient(180deg, var(--color-background) 0%, var(--color-background-secondary) 100%);
393
+ }
394
+ .cover-classification {
395
+ font-family: var(--font-sans);
396
+ font-size: 0.875rem;
397
+ font-weight: 600;
398
+ color: var(--color-destructive);
399
+ text-transform: uppercase;
400
+ letter-spacing: 0.15em;
401
+ margin-bottom: 4rem;
402
+ }
403
+ .cover-title {
404
+ font-family: var(--font-heading);
405
+ font-size: 3rem;
406
+ font-weight: 600;
407
+ color: var(--color-foreground);
408
+ line-height: 1.2;
409
+ margin-bottom: 1rem;
410
+ letter-spacing: -0.02em;
411
+ }
412
+ .cover-subtitle {
413
+ font-family: var(--font-heading);
414
+ font-size: 1.5rem;
415
+ color: var(--color-muted);
416
+ margin-bottom: 4rem;
417
+ font-weight: 400;
418
+ }
419
+ .cover-meta {
420
+ margin-top: auto;
421
+ padding-top: 1.5rem;
422
+ border-top: 1px solid var(--color-border);
423
+ }
424
+ .cover-date {
425
+ font-family: var(--font-sans);
426
+ font-size: 1rem;
427
+ color: var(--color-muted);
428
+ }
429
+
430
+ /* Print styles — what Playwright sees */
431
+ @media print {
432
+ body {
433
+ font-size: 11pt;
434
+ }
435
+ .report-container {
436
+ max-width: none;
437
+ padding: 0;
438
+ }
439
+ .report-section,
440
+ .callout,
441
+ .exhibit,
442
+ .finding-card,
443
+ .quote-block {
444
+ break-inside: avoid;
445
+ }
446
+ .cover-page {
447
+ page-break-after: always;
448
+ }
449
+ a {
450
+ text-decoration: none;
451
+ color: var(--color-foreground);
452
+ }
453
+ }
454
+
455
+ /* Page setup for PDF */
456
+ @page {
457
+ size: A4;
458
+ margin: 18mm 16mm;
459
+ }
@@ -0,0 +1,32 @@
1
+ import type { Metadata } from "next";
2
+ import { Inter, Source_Serif_4 } from "next/font/google";
3
+ import "./globals.css";
4
+
5
+ const inter = Inter({
6
+ subsets: ["latin"],
7
+ variable: "--font-inter",
8
+ display: "swap",
9
+ });
10
+
11
+ const sourceSerif = Source_Serif_4({
12
+ subsets: ["latin"],
13
+ variable: "--font-source-serif",
14
+ display: "swap",
15
+ });
16
+
17
+ export const metadata: Metadata = {
18
+ title: "Consulting Report",
19
+ description: "Strategic assessment and recommendations.",
20
+ };
21
+
22
+ export default function RootLayout({
23
+ children,
24
+ }: Readonly<{ children: React.ReactNode }>) {
25
+ return (
26
+ <html lang="en" className={`${inter.variable} ${sourceSerif.variable}`}>
27
+ <body className="bg-background font-body text-foreground antialiased">
28
+ {children}
29
+ </body>
30
+ </html>
31
+ );
32
+ }