portable-agent-layer 0.29.1 → 0.30.1

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.
@@ -1,11 +1,12 @@
1
1
  /* presentation skill — theme-base/layouts.css
2
2
  * Per-layout styling via [data-layout="..."] attribute selectors.
3
- * 11 layouts. Brand-neutral — templates override colours via --brand-* vars. */
3
+ * 15 layouts. Brand-neutral — every color references --brand-* / --neutral-* tokens.
4
+ */
4
5
 
5
6
  /* Reveal default is centered text; we want left for content slides. */
6
7
  .reveal .slides > section { text-align: left; }
7
8
 
8
- /* ── 1. title ── Cover slide */
9
+ /* ── 1. title ── Cover slide (centered, brand mark + accent rule under title) */
9
10
  .reveal section[data-layout="title"] {
10
11
  text-align: center !important;
11
12
  display: flex !important;
@@ -14,29 +15,40 @@
14
15
  align-items: center;
15
16
  padding: var(--space-5) var(--space-4);
16
17
  }
18
+ .reveal section[data-layout="title"]::before {
19
+ content: '';
20
+ background: var(--brand-logo) no-repeat center / contain;
21
+ width: 320px;
22
+ height: 96px;
23
+ margin-bottom: var(--space-4);
24
+ display: block;
25
+ }
17
26
  .reveal section[data-layout="title"] h1 {
18
- font-size: 3.0em;
27
+ font-size: var(--text-4xl);
19
28
  margin-bottom: var(--space-2);
20
29
  color: var(--brand-primary);
30
+ letter-spacing: var(--tracking-tighter);
31
+ }
32
+ .reveal section[data-layout="title"] h1::after {
33
+ content: '';
34
+ display: block;
35
+ width: 64px;
36
+ height: 4px;
37
+ background: var(--brand-accent);
38
+ margin: var(--space-2) auto 0;
39
+ border-radius: var(--radius-full);
21
40
  }
22
41
  .reveal section[data-layout="title"] h2 {
23
- font-size: 1.4em;
42
+ font-size: var(--text-lg);
24
43
  font-weight: 400;
25
44
  color: var(--brand-muted);
26
45
  margin-bottom: var(--space-4);
27
- }
28
- .reveal section[data-layout="title"]::before {
29
- content: '';
30
- background: var(--brand-logo) no-repeat center / contain;
31
- width: 320px;
32
- height: 96px;
33
- margin-bottom: var(--space-4);
34
- display: block;
46
+ letter-spacing: var(--tracking-normal);
35
47
  }
36
48
 
37
- /* ── 2. section ── Section divider, full-bleed */
49
+ /* ── 2. section ── Section divider, gradient bg, accent rule */
38
50
  .reveal section[data-layout="section"] {
39
- background: var(--brand-primary);
51
+ background: linear-gradient(135deg, var(--brand-primary) 0%, var(--brand-primary-800) 100%);
40
52
  color: #fff;
41
53
  text-align: left !important;
42
54
  display: flex !important;
@@ -44,26 +56,35 @@
44
56
  justify-content: center;
45
57
  padding: var(--space-5);
46
58
  }
59
+ .reveal section[data-layout="section"]::before {
60
+ content: '';
61
+ display: block;
62
+ width: 64px;
63
+ height: 4px;
64
+ background: var(--brand-accent);
65
+ margin-bottom: var(--space-3);
66
+ border-radius: var(--radius-full);
67
+ }
47
68
  .reveal section[data-layout="section"] h1 {
48
69
  color: #fff;
49
- font-size: 2.8em;
70
+ font-size: var(--text-3xl);
50
71
  font-weight: 700;
51
72
  margin-bottom: var(--space-2);
73
+ letter-spacing: var(--tracking-tighter);
52
74
  }
53
75
  .reveal section[data-layout="section"] h2,
54
76
  .reveal section[data-layout="section"] h3 {
55
77
  color: rgba(255,255,255,0.7);
56
- font-size: 1.3em;
78
+ font-size: var(--text-lg);
57
79
  font-weight: 400;
58
80
  margin-bottom: var(--space-2);
59
81
  }
60
82
  .reveal section[data-layout="section"] p {
61
83
  color: rgba(255,255,255,0.85);
62
- font-size: 1.1em;
84
+ font-size: var(--text-lg);
63
85
  }
64
- .reveal section[data-layout="section"]::after { display: none !important; }
65
86
 
66
- /* ── 3. content ── default; no overrides needed */
87
+ /* ── 3. content ── default; styled in base.css */
67
88
 
68
89
  /* ── 4. two-column ── */
69
90
  .reveal section[data-layout="two-column"] .col-left,
@@ -90,13 +111,12 @@
90
111
  .reveal section[data-layout="image-text"] .image img,
91
112
  .reveal section[data-layout="image-text"] .image svg {
92
113
  width: 100%; height: auto; max-height: 60vh; object-fit: contain;
93
- border-radius: 4px;
94
- box-shadow: 0 8px 24px rgba(0,0,0,0.12);
114
+ border-radius: var(--radius-md);
95
115
  display: block;
96
116
  }
97
117
  .reveal section[data-layout="image-text"] .text { padding-left: var(--space-3); }
98
118
 
99
- /* ── 6. quote ── */
119
+ /* ── 6. quote ── Centered quote with oversized accent glyph */
100
120
  .reveal section[data-layout="quote"] {
101
121
  text-align: center !important;
102
122
  display: flex !important;
@@ -107,26 +127,30 @@
107
127
  .reveal section[data-layout="quote"] blockquote {
108
128
  border-left: none;
109
129
  background: transparent;
110
- font-size: 1.4em;
130
+ font-size: var(--text-xl);
111
131
  font-style: italic;
112
132
  color: var(--brand-fg);
113
133
  padding: 0;
114
134
  margin: 0 auto;
115
135
  max-width: 80%;
116
136
  position: relative;
137
+ line-height: var(--leading-snug);
138
+ border-radius: 0;
117
139
  }
118
140
  .reveal section[data-layout="quote"] blockquote::before {
119
141
  content: '"';
120
142
  font-family: Georgia, serif;
121
- font-size: 4em;
122
- color: var(--brand-accent);
143
+ font-size: 5em;
144
+ color: var(--brand-accent-300);
123
145
  position: absolute;
124
146
  top: -0.5em;
125
- left: -0.4em;
147
+ left: -0.5em;
126
148
  line-height: 1;
149
+ z-index: 0;
127
150
  }
151
+ .reveal section[data-layout="quote"] blockquote > * { position: relative; z-index: 1; }
128
152
 
129
- /* ── 7. closing ── */
153
+ /* ── 7. closing ── Thank-you / Q&A on gradient brand BG */
130
154
  .reveal section[data-layout="closing"] {
131
155
  text-align: center !important;
132
156
  display: flex !important;
@@ -134,23 +158,33 @@
134
158
  justify-content: center;
135
159
  align-items: center;
136
160
  padding: var(--space-5) var(--space-4);
137
- background: var(--brand-primary);
161
+ background: linear-gradient(135deg, var(--brand-primary) 0%, var(--brand-primary-800) 100%);
138
162
  color: #fff;
139
163
  }
140
164
  .reveal section[data-layout="closing"] h1 {
141
165
  color: #fff;
142
- font-size: 3.4em;
166
+ font-size: var(--text-4xl);
143
167
  margin-bottom: var(--space-2);
168
+ letter-spacing: var(--tracking-tighter);
169
+ }
170
+ .reveal section[data-layout="closing"] h1::after {
171
+ content: '';
172
+ display: block;
173
+ width: 64px;
174
+ height: 4px;
175
+ background: var(--brand-accent);
176
+ margin: var(--space-2) auto 0;
177
+ border-radius: var(--radius-full);
144
178
  }
145
179
  .reveal section[data-layout="closing"] h2 {
146
180
  color: rgba(255,255,255,0.85);
147
181
  font-weight: 400;
148
- font-size: 1.4em;
182
+ font-size: var(--text-xl);
149
183
  }
150
184
  .reveal section[data-layout="closing"] p { color: rgba(255,255,255,0.85); }
151
- .reveal section[data-layout="closing"]::after { display: none !important; }
152
185
 
153
- /* ── 8. agenda ── */
186
+ /* ── 8. agenda ── Numbered ol, generous whitespace.
187
+ * Sized so 10 items fit comfortably; if you have more, split into two slides. */
154
188
  .reveal section[data-layout="agenda"] ol {
155
189
  margin: var(--space-2) 0 0 0;
156
190
  padding-left: 0;
@@ -159,8 +193,9 @@
159
193
  }
160
194
  .reveal section[data-layout="agenda"] ol li {
161
195
  counter-increment: agenda;
162
- font-size: 0.85em;
163
- padding: 0.6rem 0;
196
+ font-size: var(--text-sm);
197
+ line-height: var(--leading-snug);
198
+ padding: 0.5rem 0;
164
199
  border-bottom: 1px solid var(--brand-divider);
165
200
  position: relative;
166
201
  padding-left: 2.6em;
@@ -170,12 +205,12 @@
170
205
  content: counter(agenda, decimal-leading-zero);
171
206
  position: absolute;
172
207
  left: 0;
173
- top: 0.6rem;
208
+ top: 0.5rem;
174
209
  color: var(--brand-accent);
175
210
  font-weight: 700;
176
- font-size: 1.0em;
211
+ font-size: var(--text-sm);
177
212
  font-family: var(--font-display);
178
- letter-spacing: 0.05em;
213
+ letter-spacing: var(--tracking-wide);
179
214
  }
180
215
 
181
216
  /* ── 9. table ── styling already in base; just ensure breathing room */
@@ -183,34 +218,181 @@
183
218
  margin-top: var(--space-3);
184
219
  }
185
220
 
186
- /* ── 10. comparison ── */
221
+ /* ── 10. comparison ── 2-3 option boxes with numbered badge + top stripe */
187
222
  .reveal section[data-layout="comparison"] .compare {
188
223
  display: flex;
189
224
  gap: var(--space-3);
190
225
  margin-top: var(--space-3);
191
226
  align-items: stretch;
227
+ counter-reset: cmp;
192
228
  }
193
229
  .reveal section[data-layout="comparison"] .option {
194
230
  flex: 1;
195
231
  background: var(--brand-surface);
196
232
  padding: var(--space-3);
197
- border-radius: 6px;
233
+ border-radius: var(--radius-md);
198
234
  border-top: 4px solid var(--brand-accent);
199
- font-size: 0.85em;
235
+ font-size: var(--text-sm);
236
+ position: relative;
237
+ counter-increment: cmp;
238
+ box-shadow: var(--shadow-sm);
239
+ }
240
+ .reveal section[data-layout="comparison"] .option::before {
241
+ content: counter(cmp, decimal-leading-zero);
242
+ position: absolute;
243
+ top: var(--space-1);
244
+ right: var(--space-2);
245
+ font-family: var(--font-display);
246
+ font-size: var(--text-sm);
247
+ font-weight: 700;
248
+ color: var(--brand-accent);
249
+ letter-spacing: var(--tracking-wide);
250
+ opacity: 0.6;
200
251
  }
201
252
  .reveal section[data-layout="comparison"] .option:nth-child(1) { border-top-color: var(--brand-primary); }
253
+ .reveal section[data-layout="comparison"] .option:nth-child(1)::before { color: var(--brand-primary); }
202
254
  .reveal section[data-layout="comparison"] .option p:first-child strong:first-child,
203
255
  .reveal section[data-layout="comparison"] .option > strong:first-child {
204
256
  display: block;
205
- font-size: 1.3em;
257
+ font-size: var(--text-lg);
206
258
  margin-bottom: var(--space-2);
207
259
  color: var(--brand-primary);
208
260
  }
209
261
 
210
- /* ── 11. code ── code-focused; pre fills more vertical space */
262
+ /* ── 11. code ── Code-focused; pre fills more vertical space */
211
263
  .reveal section[data-layout="code"] pre {
212
264
  width: 100%;
213
265
  max-height: 65vh;
214
266
  margin: var(--space-2) 0;
215
267
  }
216
268
  .reveal section[data-layout="code"] pre code { font-size: 0.9em; }
269
+
270
+ /* ── 12. big-stat ── One giant number + a muted caption beneath. Use sparingly.
271
+ * Author surface:
272
+ * <!-- .slide: data-layout="big-stat" -->
273
+ * # 87%
274
+ * ## of customers retained YoY
275
+ */
276
+ .reveal section[data-layout="big-stat"] {
277
+ text-align: center !important;
278
+ display: flex !important;
279
+ flex-direction: column;
280
+ justify-content: center;
281
+ align-items: center;
282
+ padding: var(--space-5);
283
+ }
284
+ .reveal section[data-layout="big-stat"] h1 {
285
+ font-family: var(--font-display);
286
+ font-size: var(--text-display);
287
+ font-weight: 800;
288
+ line-height: 0.9;
289
+ letter-spacing: var(--tracking-tighter);
290
+ color: var(--brand-primary);
291
+ margin: 0 0 var(--space-2) 0;
292
+ font-feature-settings: "tnum";
293
+ }
294
+ .reveal section[data-layout="big-stat"] h2,
295
+ .reveal section[data-layout="big-stat"] p {
296
+ font-size: var(--text-lg);
297
+ color: var(--brand-muted);
298
+ font-weight: 400;
299
+ max-width: 70%;
300
+ margin: 0 0 var(--space-1) 0;
301
+ line-height: var(--leading-snug);
302
+ text-align: center;
303
+ }
304
+ .reveal section[data-layout="big-stat"] em.unit {
305
+ font-size: 0.4em;
306
+ color: var(--brand-accent);
307
+ font-weight: 600;
308
+ font-style: normal;
309
+ margin-left: 0.1em;
310
+ letter-spacing: var(--tracking-normal);
311
+ vertical-align: top;
312
+ }
313
+
314
+ /* ── 13. metric-grid ── 3-up KPI cards. Wrap in <div class="metrics"> with .metric children.
315
+ * Author surface (HTML inside markdown):
316
+ * <div class="metrics">
317
+ * <div class="metric"><p class="label">MRR</p><p class="value">$48k</p><p class="delta up">+12%</p></div>
318
+ * ...
319
+ * </div>
320
+ */
321
+ .reveal section[data-layout="metric-grid"] .metrics {
322
+ display: grid;
323
+ grid-template-columns: repeat(3, 1fr);
324
+ gap: var(--space-3);
325
+ margin-top: var(--space-3);
326
+ }
327
+ .reveal section[data-layout="metric-grid"] .metric {
328
+ background: var(--brand-surface);
329
+ border-radius: var(--radius-md);
330
+ padding: var(--space-3);
331
+ box-shadow: var(--shadow-sm);
332
+ border-top: 3px solid var(--brand-accent);
333
+ }
334
+ .reveal section[data-layout="metric-grid"] .metric:nth-child(3n+1) { border-top-color: var(--brand-primary); }
335
+ .reveal section[data-layout="metric-grid"] .metric:nth-child(3n+2) { border-top-color: var(--brand-accent); }
336
+ .reveal section[data-layout="metric-grid"] .metric:nth-child(3n) { border-top-color: var(--brand-primary-700); }
337
+ .reveal section[data-layout="metric-grid"] .metric .value {
338
+ font-family: var(--font-display);
339
+ font-size: var(--text-3xl);
340
+ font-weight: 700;
341
+ line-height: 1;
342
+ color: var(--brand-primary);
343
+ margin: 0 0 var(--space-1) 0;
344
+ letter-spacing: var(--tracking-tight);
345
+ }
346
+ .reveal section[data-layout="metric-grid"] .metric .label {
347
+ font-size: var(--text-xs);
348
+ color: var(--brand-muted);
349
+ text-transform: uppercase;
350
+ letter-spacing: var(--tracking-wide);
351
+ margin: 0 0 var(--space-1) 0;
352
+ font-weight: 600;
353
+ }
354
+ .reveal section[data-layout="metric-grid"] .metric .delta {
355
+ font-size: var(--text-sm);
356
+ font-weight: 600;
357
+ display: inline-block;
358
+ margin: 0;
359
+ }
360
+ .reveal section[data-layout="metric-grid"] .metric .delta.up { color: #16A34A; }
361
+ .reveal section[data-layout="metric-grid"] .metric .delta.down { color: #DC2626; }
362
+
363
+ /* ── 14. pull-quote ── Oversized italic with accent rule + attribution
364
+ * Author surface:
365
+ * <!-- .slide: data-layout="pull-quote" -->
366
+ * > Big sentence.
367
+ *
368
+ * — Attribution name, role
369
+ */
370
+ .reveal section[data-layout="pull-quote"] {
371
+ display: flex !important;
372
+ flex-direction: column;
373
+ justify-content: center;
374
+ padding: var(--space-5);
375
+ }
376
+ .reveal section[data-layout="pull-quote"] blockquote {
377
+ font-size: var(--text-2xl);
378
+ font-style: italic;
379
+ font-weight: 300;
380
+ font-family: var(--font-display);
381
+ color: var(--brand-fg);
382
+ line-height: var(--leading-snug);
383
+ letter-spacing: var(--tracking-tight);
384
+ border-left: 4px solid var(--brand-accent);
385
+ background: transparent;
386
+ padding: 0 0 0 var(--space-3);
387
+ margin: 0;
388
+ border-radius: 0;
389
+ max-width: 90%;
390
+ }
391
+ .reveal section[data-layout="pull-quote"] blockquote + p {
392
+ margin-top: var(--space-3);
393
+ margin-left: calc(4px + var(--space-3));
394
+ font-size: var(--text-base);
395
+ font-style: normal;
396
+ color: var(--brand-muted);
397
+ font-weight: 500;
398
+ }
@@ -1,12 +1,20 @@
1
1
  #!/usr/bin/env bun
2
- // presentation skill — build a deck folder to a single self-contained HTML.
2
+ // presentation skill — build a deck folder to a self-contained HTML.
3
3
  //
4
4
  // Usage:
5
- // bun build.ts <deck-dir>
5
+ // bun build.ts <deck-dir> [--out <dir>] [--force]
6
+ //
7
+ // Output:
8
+ // <out>/<deck-name>/<deck-name>.md concatenated slides (written first)
9
+ // <out>/<deck-name>/<deck-name>.html self-contained presentation
10
+ //
11
+ // --out defaults to process.cwd(). The deck-name subdir is always created
12
+ // inside --out, even when --out is explicitly provided. Existing files in
13
+ // the subdir are preserved unless --force is passed.
6
14
 
7
15
  import { constants as fsConst } from "node:fs";
8
- import { access, mkdir, readdir, writeFile } from "node:fs/promises";
9
- import { join, resolve } from "node:path";
16
+ import { access, mkdir, readdir, readFile, writeFile } from "node:fs/promises";
17
+ import { basename, join, resolve } from "node:path";
10
18
  import { dataUri, escapeForTextarea, readText } from "./lib/inline";
11
19
  import { THEME_BASE, VENDOR_REVEAL } from "./lib/paths";
12
20
  import { getTemplate } from "./lib/registry";
@@ -50,14 +58,45 @@ async function exists(p: string): Promise<boolean> {
50
58
  }
51
59
  }
52
60
 
61
+ function deckSlug(deckDir: string): string {
62
+ // Filesystem-safe name. Falls back to "deck" if basename is empty (shouldn't happen).
63
+ const raw = basename(resolve(deckDir));
64
+ const slug = raw.replace(/[^a-zA-Z0-9._-]+/g, "-").replace(/^-+|-+$/g, "");
65
+ return slug || "deck";
66
+ }
67
+
68
+ async function buildConcat(deckDir: string): Promise<string> {
69
+ const slidesDir = join(deckDir, "slides");
70
+ if (await exists(slidesDir)) {
71
+ const files = (await readdir(slidesDir)).filter((f) => f.endsWith(".md")).sort();
72
+ if (files.length === 0) {
73
+ throw new Error(`slides/ is empty at ${slidesDir}`);
74
+ }
75
+ const parts = await Promise.all(files.map((f) => readText(join(slidesDir, f))));
76
+ return `${parts.map((p) => p.trim()).join("\n\n---\n\n")}\n`;
77
+ }
78
+ const legacy = join(deckDir, "content.md");
79
+ if (await exists(legacy)) {
80
+ return await readText(legacy);
81
+ }
82
+ throw new Error(`no slides/ directory or content.md found in ${deckDir}`);
83
+ }
84
+
53
85
  async function main() {
54
86
  const argv = process.argv.slice(2);
55
87
  if (argv.length === 0) {
56
- console.error("usage: build.ts <deck-dir>");
88
+ console.error("usage: build.ts <deck-dir> [--out <dir>] [--force]");
57
89
  process.exit(1);
58
90
  }
59
91
  const deckDir = resolve(argv[0]);
60
92
 
93
+ let outRoot = process.cwd();
94
+ let force = false;
95
+ for (let i = 1; i < argv.length; i++) {
96
+ if (argv[i] === "--out") outRoot = resolve(argv[++i]);
97
+ else if (argv[i] === "--force") force = true;
98
+ }
99
+
61
100
  const cfgPath = join(deckDir, "slides.config.yml");
62
101
  if (!(await exists(cfgPath))) {
63
102
  console.error(`slides.config.yml not found at ${cfgPath}`);
@@ -70,6 +109,28 @@ async function main() {
70
109
  process.exit(1);
71
110
  }
72
111
 
112
+ const slug = deckSlug(deckDir);
113
+ const outDir = join(outRoot, slug);
114
+ const concatPath = join(outDir, `${slug}.md`);
115
+ const htmlPath = join(outDir, `${slug}.html`);
116
+
117
+ if (!force) {
118
+ const collisions: string[] = [];
119
+ if (await exists(concatPath)) collisions.push(concatPath);
120
+ if (await exists(htmlPath)) collisions.push(htmlPath);
121
+ if (collisions.length > 0) {
122
+ console.error("refusing to overwrite existing output (pass --force to replace):");
123
+ for (const p of collisions) console.error(` - ${p}`);
124
+ process.exit(1);
125
+ }
126
+ }
127
+
128
+ // Step 1 — concatenate slides to a single on-disk markdown artifact.
129
+ const concatMd = await buildConcat(deckDir);
130
+ await mkdir(outDir, { recursive: true });
131
+ await writeFile(concatPath, concatMd, "utf8");
132
+
133
+ // Step 2 — build self-contained HTML, sourcing content from the on-disk concat.
73
134
  const template = await getTemplate(templateName);
74
135
  const tplYml = parseSimpleYaml(await readText(join(template.path, "template.yml")));
75
136
  const tplCss = await readText(join(template.path, "template.css"));
@@ -95,21 +156,7 @@ async function main() {
95
156
  );
96
157
  const notesJs = await readText(join(VENDOR_REVEAL, "plugin", "notes", "notes.js"));
97
158
 
98
- // Author surface: either a slides/ folder (preferred — many small files) or a single content.md.
99
- // slides/ wins if present. Files inside are sorted by filename, then joined with the slide separator.
100
- const slidesDir = join(deckDir, "slides");
101
- let contentMd: string;
102
- if (await exists(slidesDir)) {
103
- const files = (await readdir(slidesDir)).filter((f) => f.endsWith(".md")).sort();
104
- if (files.length === 0) {
105
- console.error(`slides/ is empty at ${slidesDir}`);
106
- process.exit(1);
107
- }
108
- const parts = await Promise.all(files.map((f) => readText(join(slidesDir, f))));
109
- contentMd = parts.map((p) => p.trim()).join("\n\n---\n\n") + "\n";
110
- } else {
111
- contentMd = await readText(join(deckDir, "content.md"));
112
- }
159
+ const contentMd = await readFile(concatPath, "utf8");
113
160
 
114
161
  let deckOverridesCss = "";
115
162
  const overridesPath = join(deckDir, "overrides.css");
@@ -144,14 +191,11 @@ async function main() {
144
191
  };
145
192
 
146
193
  const html = skeleton.replace(/\{\{(\w+)\}\}/g, (_, k) => subs[k] ?? "");
147
-
148
- const distDir = join(deckDir, "dist");
149
- await mkdir(distDir, { recursive: true });
150
- const outPath = join(distDir, "index.html");
151
- await writeFile(outPath, html, "utf8");
194
+ await writeFile(htmlPath, html, "utf8");
152
195
 
153
196
  const sizeMb = (Buffer.byteLength(html) / 1024 / 1024).toFixed(2);
154
- console.log(`✓ built ${outPath} (${sizeMb} MB self-contained)`);
197
+ console.log(`✓ concat ${concatPath}`);
198
+ console.log(`✓ html ${htmlPath} (${sizeMb} MB self-contained)`);
155
199
  }
156
200
 
157
201
  main().catch((e) => {