portable-agent-layer 0.31.0 → 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.
- package/assets/skills/consulting-report/SKILL.md +8 -2
- package/assets/skills/consulting-report/demo/app/globals.css +115 -0
- package/assets/skills/consulting-report/demo/app/page.tsx +75 -28
- package/assets/skills/consulting-report/demo/components/comparison-table.tsx +40 -0
- package/assets/skills/consulting-report/demo/components/section.tsx +3 -2
- package/assets/skills/consulting-report/demo/components/stat-grid.tsx +26 -0
- package/assets/skills/consulting-report/demo/components/table-of-contents.tsx +27 -0
- package/assets/skills/consulting-report/template/app/globals.css +115 -0
- package/assets/skills/consulting-report/template/app/page.tsx +55 -28
- package/assets/skills/consulting-report/template/components/comparison-table.tsx +40 -0
- package/assets/skills/consulting-report/template/components/section.tsx +3 -2
- package/assets/skills/consulting-report/template/components/stat-grid.tsx +26 -0
- package/assets/skills/consulting-report/template/components/table-of-contents.tsx +27 -0
- package/package.json +1 -1
|
@@ -60,10 +60,13 @@ Run with **Node**, not Bun — Playwright's `chromium.launch()` hangs under Bun
|
|
|
60
60
|
│ ├── layout.tsx # font wiring (Inter + Source Serif 4)
|
|
61
61
|
│ ├── page.tsx # the report's layout — edit freely
|
|
62
62
|
│ └── globals.css # design tokens via @theme + custom CSS
|
|
63
|
-
├── components/ #
|
|
63
|
+
├── components/ # report primitives — edit if you need new shapes
|
|
64
64
|
│ ├── cover-page.tsx
|
|
65
|
+
│ ├── table-of-contents.tsx
|
|
65
66
|
│ ├── section.tsx
|
|
66
67
|
│ ├── exhibit.tsx
|
|
68
|
+
│ ├── stat-grid.tsx
|
|
69
|
+
│ ├── comparison-table.tsx
|
|
67
70
|
│ ├── finding-card.tsx
|
|
68
71
|
│ ├── recommendation-card.tsx
|
|
69
72
|
│ ├── severity-badge.tsx
|
|
@@ -83,8 +86,11 @@ Run with **Node**, not Bun — Playwright's `chromium.launch()` hangs under Bun
|
|
|
83
86
|
## Component Cheatsheet
|
|
84
87
|
|
|
85
88
|
- `<CoverPage clientName reportTitle reportDate classification consultancyName preTitle />` — full-bleed cover
|
|
86
|
-
- `<
|
|
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
|
|
87
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
|
|
88
94
|
- `<FindingCard finding={f} index={i} />` — driven by `Finding` type, includes severity badge
|
|
89
95
|
- `<RecommendationCard recommendation={r} index={i} />` — driven by `Recommendation` type, includes priority badge
|
|
90
96
|
- `<Callout label?>` — left-rule emphasis block (default label "Key Takeaway")
|
|
@@ -266,6 +266,121 @@ body {
|
|
|
266
266
|
margin-top: 0.25rem;
|
|
267
267
|
}
|
|
268
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
|
+
|
|
269
384
|
/* Cover page */
|
|
270
385
|
.cover-page {
|
|
271
386
|
min-height: 100vh;
|
|
@@ -1,13 +1,28 @@
|
|
|
1
1
|
import { AlertTriangle, CheckCircle2, Lightbulb, Target } from "lucide-react";
|
|
2
2
|
import { Callout } from "@/components/callout";
|
|
3
|
+
import { ComparisonTable } from "@/components/comparison-table";
|
|
3
4
|
import { CoverPage } from "@/components/cover-page";
|
|
4
5
|
import { Exhibit } from "@/components/exhibit";
|
|
5
6
|
import { FindingCard } from "@/components/finding-card";
|
|
6
7
|
import { RecommendationCard } from "@/components/recommendation-card";
|
|
7
8
|
import { Section } from "@/components/section";
|
|
9
|
+
import { StatGrid } from "@/components/stat-grid";
|
|
10
|
+
import { TableOfContents } from "@/components/table-of-contents";
|
|
8
11
|
import { Timeline } from "@/components/timeline";
|
|
9
12
|
import { reportData } from "@/lib/report-data";
|
|
10
13
|
|
|
14
|
+
const sections = [
|
|
15
|
+
{ id: "executive-summary", title: "Executive Summary" },
|
|
16
|
+
{ id: "situation-assessment", title: "Situation Assessment" },
|
|
17
|
+
{ id: "key-findings", title: "Key Findings" },
|
|
18
|
+
{ id: "risk-analysis", title: "Risk Analysis" },
|
|
19
|
+
{ id: "strategic-opportunity", title: "Strategic Opportunity" },
|
|
20
|
+
{ id: "recommendations", title: "Strategic Recommendations" },
|
|
21
|
+
{ id: "target-state", title: "Target State Vision" },
|
|
22
|
+
{ id: "roadmap", title: "Implementation Roadmap" },
|
|
23
|
+
{ id: "call-to-action", title: "Call to Action" },
|
|
24
|
+
];
|
|
25
|
+
|
|
11
26
|
export default function ReportPage() {
|
|
12
27
|
const data = reportData;
|
|
13
28
|
|
|
@@ -23,29 +38,34 @@ export default function ReportPage() {
|
|
|
23
38
|
/>
|
|
24
39
|
|
|
25
40
|
<div className="report-container">
|
|
26
|
-
<
|
|
41
|
+
<TableOfContents items={sections} />
|
|
42
|
+
|
|
43
|
+
<Section id="executive-summary" title="Executive Summary">
|
|
27
44
|
<p className="text-lg mb-6">{data.executiveSummary.context}</p>
|
|
28
45
|
|
|
29
|
-
<
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
46
|
+
<StatGrid
|
|
47
|
+
stats={[
|
|
48
|
+
{
|
|
49
|
+
value: String(data.executiveSummary.methodology.interviewCount),
|
|
50
|
+
label: "Interviews",
|
|
51
|
+
caption: "across leadership and engineering",
|
|
52
|
+
},
|
|
53
|
+
{ value: "3×", label: "MTTR", caption: "vs. peer benchmark" },
|
|
54
|
+
{
|
|
55
|
+
value: "71%",
|
|
56
|
+
label: "Concentration",
|
|
57
|
+
caption: "on-call held by 3 engineers",
|
|
58
|
+
},
|
|
59
|
+
]}
|
|
60
|
+
/>
|
|
61
|
+
|
|
62
|
+
<Exhibit number={1} title="Roles Interviewed">
|
|
63
|
+
<div className="grid grid-cols-2 gap-1">
|
|
64
|
+
{data.executiveSummary.methodology.roles.map((role) => (
|
|
65
|
+
<p key={role} className="text-sm text-muted">
|
|
66
|
+
{role}
|
|
40
67
|
</p>
|
|
41
|
-
|
|
42
|
-
{data.executiveSummary.methodology.roles.map((role) => (
|
|
43
|
-
<p key={role} className="text-sm text-muted">
|
|
44
|
-
{role}
|
|
45
|
-
</p>
|
|
46
|
-
))}
|
|
47
|
-
</div>
|
|
48
|
-
</div>
|
|
68
|
+
))}
|
|
49
69
|
</div>
|
|
50
70
|
</Exhibit>
|
|
51
71
|
|
|
@@ -75,7 +95,7 @@ export default function ReportPage() {
|
|
|
75
95
|
</ul>
|
|
76
96
|
</Section>
|
|
77
97
|
|
|
78
|
-
<Section title="Situation Assessment">
|
|
98
|
+
<Section id="situation-assessment" title="Situation Assessment">
|
|
79
99
|
<div className="grid gap-6">
|
|
80
100
|
<div>
|
|
81
101
|
<h3 className="text-lg font-semibold mb-2 flex items-center gap-2">
|
|
@@ -95,7 +115,7 @@ export default function ReportPage() {
|
|
|
95
115
|
</div>
|
|
96
116
|
</Section>
|
|
97
117
|
|
|
98
|
-
<Section title="Key Findings">
|
|
118
|
+
<Section id="key-findings" title="Key Findings">
|
|
99
119
|
<p className="text-muted mb-6">
|
|
100
120
|
Our analysis identified {data.findings.length} significant findings that
|
|
101
121
|
require attention. Each finding is supported by evidence gathered during our
|
|
@@ -108,7 +128,7 @@ export default function ReportPage() {
|
|
|
108
128
|
</div>
|
|
109
129
|
</Section>
|
|
110
130
|
|
|
111
|
-
<Section title="Risk Analysis">
|
|
131
|
+
<Section id="risk-analysis" title="Risk Analysis">
|
|
112
132
|
<div className="grid gap-6">
|
|
113
133
|
<Exhibit number={3} title="Existential Risks">
|
|
114
134
|
<div className="space-y-3">
|
|
@@ -142,7 +162,7 @@ export default function ReportPage() {
|
|
|
142
162
|
</div>
|
|
143
163
|
</Section>
|
|
144
164
|
|
|
145
|
-
<Section title="Strategic Opportunity">
|
|
165
|
+
<Section id="strategic-opportunity" title="Strategic Opportunity">
|
|
146
166
|
<Callout label="The Path Forward">{data.strategicOpportunity.goodNews}</Callout>
|
|
147
167
|
|
|
148
168
|
<h3 className="text-lg font-semibold mt-6 mb-3 flex items-center gap-2">
|
|
@@ -162,7 +182,7 @@ export default function ReportPage() {
|
|
|
162
182
|
</ul>
|
|
163
183
|
</Section>
|
|
164
184
|
|
|
165
|
-
<Section title="Strategic Recommendations">
|
|
185
|
+
<Section id="recommendations" title="Strategic Recommendations">
|
|
166
186
|
<p className="text-muted mb-6">
|
|
167
187
|
Recommendations are prioritized by urgency and impact.
|
|
168
188
|
</p>
|
|
@@ -173,9 +193,36 @@ export default function ReportPage() {
|
|
|
173
193
|
</div>
|
|
174
194
|
</Section>
|
|
175
195
|
|
|
176
|
-
<Section title="Target State Vision">
|
|
196
|
+
<Section id="target-state" title="Target State Vision">
|
|
177
197
|
<p className="text-lg mb-6">{data.targetState.description}</p>
|
|
178
198
|
|
|
199
|
+
<ComparisonTable
|
|
200
|
+
leftLabel="Today"
|
|
201
|
+
rightLabel="Target"
|
|
202
|
+
rows={[
|
|
203
|
+
{
|
|
204
|
+
metric: "MTTR",
|
|
205
|
+
left: "3× peer benchmark",
|
|
206
|
+
right: "Within 1.5× peer benchmark",
|
|
207
|
+
},
|
|
208
|
+
{
|
|
209
|
+
metric: "Rollback path",
|
|
210
|
+
left: "Manual, untested",
|
|
211
|
+
right: "Single-command, validated monthly",
|
|
212
|
+
},
|
|
213
|
+
{
|
|
214
|
+
metric: "On-call coverage",
|
|
215
|
+
left: "3 engineers carry 71%",
|
|
216
|
+
right: "8 engineers, max 15% each",
|
|
217
|
+
},
|
|
218
|
+
{
|
|
219
|
+
metric: "Runbook freshness",
|
|
220
|
+
left: "7 of 14 stale (>18 months)",
|
|
221
|
+
right: "Tracked as service-owner KPI",
|
|
222
|
+
},
|
|
223
|
+
]}
|
|
224
|
+
/>
|
|
225
|
+
|
|
179
226
|
<Exhibit number={5} title="Key Capabilities Enabled">
|
|
180
227
|
<div className="grid md:grid-cols-3 gap-4">
|
|
181
228
|
{data.targetState.keyCapabilities.map((capability) => (
|
|
@@ -201,7 +248,7 @@ export default function ReportPage() {
|
|
|
201
248
|
</ul>
|
|
202
249
|
</Section>
|
|
203
250
|
|
|
204
|
-
<Section title="Implementation Roadmap">
|
|
251
|
+
<Section id="roadmap" title="Implementation Roadmap">
|
|
205
252
|
<p className="text-muted mb-6">
|
|
206
253
|
The transformation should be executed in phases, with clear milestones and
|
|
207
254
|
decision points.
|
|
@@ -211,7 +258,7 @@ export default function ReportPage() {
|
|
|
211
258
|
</Exhibit>
|
|
212
259
|
</Section>
|
|
213
260
|
|
|
214
|
-
<Section title="Call to Action">
|
|
261
|
+
<Section id="call-to-action" title="Call to Action">
|
|
215
262
|
<div className="bg-primary/5 rounded-2xl p-8 border border-primary/20">
|
|
216
263
|
<h3 className="text-xl font-semibold mb-4">Immediate Next Steps</h3>
|
|
217
264
|
<ol className="space-y-3 mb-6">
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
interface ComparisonRow {
|
|
2
|
+
metric: string;
|
|
3
|
+
left: string;
|
|
4
|
+
right: string;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
interface ComparisonTableProps {
|
|
8
|
+
leftLabel: string;
|
|
9
|
+
rightLabel: string;
|
|
10
|
+
rows: ComparisonRow[];
|
|
11
|
+
metricLabel?: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function ComparisonTable({
|
|
15
|
+
leftLabel,
|
|
16
|
+
rightLabel,
|
|
17
|
+
rows,
|
|
18
|
+
metricLabel = "Metric",
|
|
19
|
+
}: ComparisonTableProps) {
|
|
20
|
+
return (
|
|
21
|
+
<table className="comparison-table">
|
|
22
|
+
<thead>
|
|
23
|
+
<tr>
|
|
24
|
+
<th>{metricLabel}</th>
|
|
25
|
+
<th>{leftLabel}</th>
|
|
26
|
+
<th>{rightLabel}</th>
|
|
27
|
+
</tr>
|
|
28
|
+
</thead>
|
|
29
|
+
<tbody>
|
|
30
|
+
{rows.map((row) => (
|
|
31
|
+
<tr key={row.metric}>
|
|
32
|
+
<td className="metric">{row.metric}</td>
|
|
33
|
+
<td>{row.left}</td>
|
|
34
|
+
<td>{row.right}</td>
|
|
35
|
+
</tr>
|
|
36
|
+
))}
|
|
37
|
+
</tbody>
|
|
38
|
+
</table>
|
|
39
|
+
);
|
|
40
|
+
}
|
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
import { cn } from "@/lib/utils";
|
|
2
2
|
|
|
3
3
|
interface SectionProps {
|
|
4
|
+
id?: string;
|
|
4
5
|
title: string;
|
|
5
6
|
children: React.ReactNode;
|
|
6
7
|
className?: string;
|
|
7
8
|
}
|
|
8
9
|
|
|
9
|
-
export function Section({ title, children, className }: SectionProps) {
|
|
10
|
+
export function Section({ id, title, children, className }: SectionProps) {
|
|
10
11
|
return (
|
|
11
|
-
<section className={cn("report-section", className)}>
|
|
12
|
+
<section id={id} className={cn("report-section", className)}>
|
|
12
13
|
<h2>{title}</h2>
|
|
13
14
|
{children}
|
|
14
15
|
</section>
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
interface Stat {
|
|
2
|
+
value: string;
|
|
3
|
+
label: string;
|
|
4
|
+
caption?: string;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
interface StatGridProps {
|
|
8
|
+
stats: Stat[];
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function StatGrid({ stats }: StatGridProps) {
|
|
12
|
+
return (
|
|
13
|
+
<div
|
|
14
|
+
className="stat-grid"
|
|
15
|
+
style={{ gridTemplateColumns: `repeat(${stats.length}, minmax(0, 1fr))` }}
|
|
16
|
+
>
|
|
17
|
+
{stats.map((s) => (
|
|
18
|
+
<div key={s.label} className="stat">
|
|
19
|
+
<div className="stat-value">{s.value}</div>
|
|
20
|
+
<div className="stat-label">{s.label}</div>
|
|
21
|
+
{s.caption && <div className="stat-caption">{s.caption}</div>}
|
|
22
|
+
</div>
|
|
23
|
+
))}
|
|
24
|
+
</div>
|
|
25
|
+
);
|
|
26
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
interface TocItem {
|
|
2
|
+
id: string;
|
|
3
|
+
title: string;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
interface TableOfContentsProps {
|
|
7
|
+
items: TocItem[];
|
|
8
|
+
title?: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function TableOfContents({ items, title = "Contents" }: TableOfContentsProps) {
|
|
12
|
+
return (
|
|
13
|
+
<nav className="toc">
|
|
14
|
+
<h2>{title}</h2>
|
|
15
|
+
<ol>
|
|
16
|
+
{items.map((item, i) => (
|
|
17
|
+
<li key={item.id}>
|
|
18
|
+
<a href={`#${item.id}`}>
|
|
19
|
+
<span className="toc-number">{(i + 1).toString().padStart(2, "0")}</span>
|
|
20
|
+
<span className="toc-title">{item.title}</span>
|
|
21
|
+
</a>
|
|
22
|
+
</li>
|
|
23
|
+
))}
|
|
24
|
+
</ol>
|
|
25
|
+
</nav>
|
|
26
|
+
);
|
|
27
|
+
}
|
|
@@ -266,6 +266,121 @@ body {
|
|
|
266
266
|
margin-top: 0.25rem;
|
|
267
267
|
}
|
|
268
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
|
+
|
|
269
384
|
/* Cover page */
|
|
270
385
|
.cover-page {
|
|
271
386
|
min-height: 100vh;
|
|
@@ -1,13 +1,28 @@
|
|
|
1
1
|
import { AlertTriangle, CheckCircle2, Lightbulb, Target } from "lucide-react";
|
|
2
2
|
import { Callout } from "@/components/callout";
|
|
3
|
+
import { ComparisonTable } from "@/components/comparison-table";
|
|
3
4
|
import { CoverPage } from "@/components/cover-page";
|
|
4
5
|
import { Exhibit } from "@/components/exhibit";
|
|
5
6
|
import { FindingCard } from "@/components/finding-card";
|
|
6
7
|
import { RecommendationCard } from "@/components/recommendation-card";
|
|
7
8
|
import { Section } from "@/components/section";
|
|
9
|
+
import { StatGrid } from "@/components/stat-grid";
|
|
10
|
+
import { TableOfContents } from "@/components/table-of-contents";
|
|
8
11
|
import { Timeline } from "@/components/timeline";
|
|
9
12
|
import { reportData } from "@/lib/report-data";
|
|
10
13
|
|
|
14
|
+
const sections = [
|
|
15
|
+
{ id: "executive-summary", title: "Executive Summary" },
|
|
16
|
+
{ id: "situation-assessment", title: "Situation Assessment" },
|
|
17
|
+
{ id: "key-findings", title: "Key Findings" },
|
|
18
|
+
{ id: "risk-analysis", title: "Risk Analysis" },
|
|
19
|
+
{ id: "strategic-opportunity", title: "Strategic Opportunity" },
|
|
20
|
+
{ id: "recommendations", title: "Strategic Recommendations" },
|
|
21
|
+
{ id: "target-state", title: "Target State Vision" },
|
|
22
|
+
{ id: "roadmap", title: "Implementation Roadmap" },
|
|
23
|
+
{ id: "call-to-action", title: "Call to Action" },
|
|
24
|
+
];
|
|
25
|
+
|
|
11
26
|
export default function ReportPage() {
|
|
12
27
|
const data = reportData;
|
|
13
28
|
|
|
@@ -23,29 +38,29 @@ export default function ReportPage() {
|
|
|
23
38
|
/>
|
|
24
39
|
|
|
25
40
|
<div className="report-container">
|
|
26
|
-
<
|
|
41
|
+
<TableOfContents items={sections} />
|
|
42
|
+
|
|
43
|
+
<Section id="executive-summary" title="Executive Summary">
|
|
27
44
|
<p className="text-lg mb-6">{data.executiveSummary.context}</p>
|
|
28
45
|
|
|
29
|
-
<
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
46
|
+
<StatGrid
|
|
47
|
+
stats={[
|
|
48
|
+
{
|
|
49
|
+
value: String(data.executiveSummary.methodology.interviewCount),
|
|
50
|
+
label: "Interviews",
|
|
51
|
+
},
|
|
52
|
+
{ value: "—", label: "Headline metric", caption: "replace with your own" },
|
|
53
|
+
{ value: "—", label: "Headline metric", caption: "replace with your own" },
|
|
54
|
+
]}
|
|
55
|
+
/>
|
|
56
|
+
|
|
57
|
+
<Exhibit number={1} title="Roles Interviewed">
|
|
58
|
+
<div className="grid grid-cols-2 gap-1">
|
|
59
|
+
{data.executiveSummary.methodology.roles.map((role) => (
|
|
60
|
+
<p key={role} className="text-sm text-muted">
|
|
61
|
+
{role}
|
|
40
62
|
</p>
|
|
41
|
-
|
|
42
|
-
{data.executiveSummary.methodology.roles.map((role) => (
|
|
43
|
-
<p key={role} className="text-sm text-muted">
|
|
44
|
-
{role}
|
|
45
|
-
</p>
|
|
46
|
-
))}
|
|
47
|
-
</div>
|
|
48
|
-
</div>
|
|
63
|
+
))}
|
|
49
64
|
</div>
|
|
50
65
|
</Exhibit>
|
|
51
66
|
|
|
@@ -75,7 +90,7 @@ export default function ReportPage() {
|
|
|
75
90
|
</ul>
|
|
76
91
|
</Section>
|
|
77
92
|
|
|
78
|
-
<Section title="Situation Assessment">
|
|
93
|
+
<Section id="situation-assessment" title="Situation Assessment">
|
|
79
94
|
<div className="grid gap-6">
|
|
80
95
|
<div>
|
|
81
96
|
<h3 className="text-lg font-semibold mb-2 flex items-center gap-2">
|
|
@@ -95,7 +110,7 @@ export default function ReportPage() {
|
|
|
95
110
|
</div>
|
|
96
111
|
</Section>
|
|
97
112
|
|
|
98
|
-
<Section title="Key Findings">
|
|
113
|
+
<Section id="key-findings" title="Key Findings">
|
|
99
114
|
<p className="text-muted mb-6">
|
|
100
115
|
Our analysis identified {data.findings.length} significant findings that
|
|
101
116
|
require attention. Each finding is supported by evidence gathered during our
|
|
@@ -108,7 +123,7 @@ export default function ReportPage() {
|
|
|
108
123
|
</div>
|
|
109
124
|
</Section>
|
|
110
125
|
|
|
111
|
-
<Section title="Risk Analysis">
|
|
126
|
+
<Section id="risk-analysis" title="Risk Analysis">
|
|
112
127
|
<div className="grid gap-6">
|
|
113
128
|
<Exhibit number={3} title="Existential Risks">
|
|
114
129
|
<div className="space-y-3">
|
|
@@ -142,7 +157,7 @@ export default function ReportPage() {
|
|
|
142
157
|
</div>
|
|
143
158
|
</Section>
|
|
144
159
|
|
|
145
|
-
<Section title="Strategic Opportunity">
|
|
160
|
+
<Section id="strategic-opportunity" title="Strategic Opportunity">
|
|
146
161
|
<Callout label="The Path Forward">{data.strategicOpportunity.goodNews}</Callout>
|
|
147
162
|
|
|
148
163
|
<h3 className="text-lg font-semibold mt-6 mb-3 flex items-center gap-2">
|
|
@@ -162,7 +177,7 @@ export default function ReportPage() {
|
|
|
162
177
|
</ul>
|
|
163
178
|
</Section>
|
|
164
179
|
|
|
165
|
-
<Section title="Strategic Recommendations">
|
|
180
|
+
<Section id="recommendations" title="Strategic Recommendations">
|
|
166
181
|
<p className="text-muted mb-6">
|
|
167
182
|
Recommendations are prioritized by urgency and impact.
|
|
168
183
|
</p>
|
|
@@ -173,9 +188,21 @@ export default function ReportPage() {
|
|
|
173
188
|
</div>
|
|
174
189
|
</Section>
|
|
175
190
|
|
|
176
|
-
<Section title="Target State Vision">
|
|
191
|
+
<Section id="target-state" title="Target State Vision">
|
|
177
192
|
<p className="text-lg mb-6">{data.targetState.description}</p>
|
|
178
193
|
|
|
194
|
+
<ComparisonTable
|
|
195
|
+
leftLabel="Today"
|
|
196
|
+
rightLabel="Target"
|
|
197
|
+
rows={[
|
|
198
|
+
{
|
|
199
|
+
metric: "Replace with metric",
|
|
200
|
+
left: "Current state value",
|
|
201
|
+
right: "Target state value",
|
|
202
|
+
},
|
|
203
|
+
]}
|
|
204
|
+
/>
|
|
205
|
+
|
|
179
206
|
<Exhibit number={5} title="Key Capabilities Enabled">
|
|
180
207
|
<div className="grid md:grid-cols-3 gap-4">
|
|
181
208
|
{data.targetState.keyCapabilities.map((capability) => (
|
|
@@ -201,7 +228,7 @@ export default function ReportPage() {
|
|
|
201
228
|
</ul>
|
|
202
229
|
</Section>
|
|
203
230
|
|
|
204
|
-
<Section title="Implementation Roadmap">
|
|
231
|
+
<Section id="roadmap" title="Implementation Roadmap">
|
|
205
232
|
<p className="text-muted mb-6">
|
|
206
233
|
The transformation should be executed in phases, with clear milestones and
|
|
207
234
|
decision points.
|
|
@@ -211,7 +238,7 @@ export default function ReportPage() {
|
|
|
211
238
|
</Exhibit>
|
|
212
239
|
</Section>
|
|
213
240
|
|
|
214
|
-
<Section title="Call to Action">
|
|
241
|
+
<Section id="call-to-action" title="Call to Action">
|
|
215
242
|
<div className="bg-primary/5 rounded-2xl p-8 border border-primary/20">
|
|
216
243
|
<h3 className="text-xl font-semibold mb-4">Immediate Next Steps</h3>
|
|
217
244
|
<ol className="space-y-3 mb-6">
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
interface ComparisonRow {
|
|
2
|
+
metric: string;
|
|
3
|
+
left: string;
|
|
4
|
+
right: string;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
interface ComparisonTableProps {
|
|
8
|
+
leftLabel: string;
|
|
9
|
+
rightLabel: string;
|
|
10
|
+
rows: ComparisonRow[];
|
|
11
|
+
metricLabel?: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function ComparisonTable({
|
|
15
|
+
leftLabel,
|
|
16
|
+
rightLabel,
|
|
17
|
+
rows,
|
|
18
|
+
metricLabel = "Metric",
|
|
19
|
+
}: ComparisonTableProps) {
|
|
20
|
+
return (
|
|
21
|
+
<table className="comparison-table">
|
|
22
|
+
<thead>
|
|
23
|
+
<tr>
|
|
24
|
+
<th>{metricLabel}</th>
|
|
25
|
+
<th>{leftLabel}</th>
|
|
26
|
+
<th>{rightLabel}</th>
|
|
27
|
+
</tr>
|
|
28
|
+
</thead>
|
|
29
|
+
<tbody>
|
|
30
|
+
{rows.map((row) => (
|
|
31
|
+
<tr key={row.metric}>
|
|
32
|
+
<td className="metric">{row.metric}</td>
|
|
33
|
+
<td>{row.left}</td>
|
|
34
|
+
<td>{row.right}</td>
|
|
35
|
+
</tr>
|
|
36
|
+
))}
|
|
37
|
+
</tbody>
|
|
38
|
+
</table>
|
|
39
|
+
);
|
|
40
|
+
}
|
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
import { cn } from "@/lib/utils";
|
|
2
2
|
|
|
3
3
|
interface SectionProps {
|
|
4
|
+
id?: string;
|
|
4
5
|
title: string;
|
|
5
6
|
children: React.ReactNode;
|
|
6
7
|
className?: string;
|
|
7
8
|
}
|
|
8
9
|
|
|
9
|
-
export function Section({ title, children, className }: SectionProps) {
|
|
10
|
+
export function Section({ id, title, children, className }: SectionProps) {
|
|
10
11
|
return (
|
|
11
|
-
<section className={cn("report-section", className)}>
|
|
12
|
+
<section id={id} className={cn("report-section", className)}>
|
|
12
13
|
<h2>{title}</h2>
|
|
13
14
|
{children}
|
|
14
15
|
</section>
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
interface Stat {
|
|
2
|
+
value: string;
|
|
3
|
+
label: string;
|
|
4
|
+
caption?: string;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
interface StatGridProps {
|
|
8
|
+
stats: Stat[];
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function StatGrid({ stats }: StatGridProps) {
|
|
12
|
+
return (
|
|
13
|
+
<div
|
|
14
|
+
className="stat-grid"
|
|
15
|
+
style={{ gridTemplateColumns: `repeat(${stats.length}, minmax(0, 1fr))` }}
|
|
16
|
+
>
|
|
17
|
+
{stats.map((s) => (
|
|
18
|
+
<div key={s.label} className="stat">
|
|
19
|
+
<div className="stat-value">{s.value}</div>
|
|
20
|
+
<div className="stat-label">{s.label}</div>
|
|
21
|
+
{s.caption && <div className="stat-caption">{s.caption}</div>}
|
|
22
|
+
</div>
|
|
23
|
+
))}
|
|
24
|
+
</div>
|
|
25
|
+
);
|
|
26
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
interface TocItem {
|
|
2
|
+
id: string;
|
|
3
|
+
title: string;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
interface TableOfContentsProps {
|
|
7
|
+
items: TocItem[];
|
|
8
|
+
title?: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function TableOfContents({ items, title = "Contents" }: TableOfContentsProps) {
|
|
12
|
+
return (
|
|
13
|
+
<nav className="toc">
|
|
14
|
+
<h2>{title}</h2>
|
|
15
|
+
<ol>
|
|
16
|
+
{items.map((item, i) => (
|
|
17
|
+
<li key={item.id}>
|
|
18
|
+
<a href={`#${item.id}`}>
|
|
19
|
+
<span className="toc-number">{(i + 1).toString().padStart(2, "0")}</span>
|
|
20
|
+
<span className="toc-title">{item.title}</span>
|
|
21
|
+
</a>
|
|
22
|
+
</li>
|
|
23
|
+
))}
|
|
24
|
+
</ol>
|
|
25
|
+
</nav>
|
|
26
|
+
);
|
|
27
|
+
}
|