@nukipa/post-content 0.1.0 → 0.2.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/package.json +4 -3
- package/src/components.js +185 -15
- package/styles.css +432 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nukipa/post-content",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Framework-agnostic processor for Nukipa CMS post bodies — markdown + component markers + citations → HTML + interactive island descriptors.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Nukipa Labs",
|
|
@@ -21,9 +21,10 @@
|
|
|
21
21
|
".": {
|
|
22
22
|
"types": "./src/index.d.ts",
|
|
23
23
|
"import": "./src/index.js"
|
|
24
|
-
}
|
|
24
|
+
},
|
|
25
|
+
"./styles.css": "./styles.css"
|
|
25
26
|
},
|
|
26
|
-
"files": ["src", "README.md"],
|
|
27
|
+
"files": ["src", "styles.css", "README.md"],
|
|
27
28
|
"peerDependencies": {
|
|
28
29
|
"marked": "^17.0.0"
|
|
29
30
|
},
|
package/src/components.js
CHANGED
|
@@ -358,22 +358,192 @@ function renderQuote(content) {
|
|
|
358
358
|
};
|
|
359
359
|
}
|
|
360
360
|
|
|
361
|
+
/**
|
|
362
|
+
* `referenced_blog_post` — an embeddable card pointing at another blog
|
|
363
|
+
* post. Same render output whether it's embedded inside a CMS blog
|
|
364
|
+
* post (via cms.post_components) or inside a newsletter issue (via
|
|
365
|
+
* the `{{post:UUID}}` shorthand the newsletters worker resolves).
|
|
366
|
+
*
|
|
367
|
+
* Content shape:
|
|
368
|
+
* { post_id, title?, url?, excerpt?, cover_url?, brand? }
|
|
369
|
+
*
|
|
370
|
+
* The host resolves `post_id` to the canonical metadata before render;
|
|
371
|
+
* we accept `title` / `url` / etc. on the content row so a pre-resolved
|
|
372
|
+
* snapshot can be passed in (newsletters does this — the worker has
|
|
373
|
+
* already fetched cms.posts when expanding markers, and passes the
|
|
374
|
+
* fields straight through).
|
|
375
|
+
*
|
|
376
|
+
* The HTML is email-safe (table + inline CSS) so the same renderer
|
|
377
|
+
* works for both web blog posts and email newsletters without
|
|
378
|
+
* branching.
|
|
379
|
+
*/
|
|
380
|
+
/**
|
|
381
|
+
* `referenced_blog_post` renderer with two styles:
|
|
382
|
+
*
|
|
383
|
+
* - 'card' (default) — full-width card: hero cover on top, title,
|
|
384
|
+
* excerpt, prominent CTA button. The hero card.
|
|
385
|
+
* - 'compact' — row layout: small 96-px square cover on the left,
|
|
386
|
+
* title + truncated excerpt on the right,
|
|
387
|
+
* inline "Read more →" link (no CTA button).
|
|
388
|
+
* Half the vertical real estate of 'card', so
|
|
389
|
+
* it works as a list-style "see also" rail.
|
|
390
|
+
*
|
|
391
|
+
* Both shapes use the same brand colors and the same table-based
|
|
392
|
+
* email-safe markup; the only difference is the row layout. We render
|
|
393
|
+
* inline CSS only (Gmail strips <style> at the inbox).
|
|
394
|
+
*/
|
|
395
|
+
function renderReferencedPost(content = {}) {
|
|
396
|
+
const style = content.style === 'compact' ? 'compact' : 'card';
|
|
397
|
+
return style === 'compact'
|
|
398
|
+
? renderReferencedPostCompact(content)
|
|
399
|
+
: renderReferencedPostCard(content);
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
function commonRefPostInputs(content) {
|
|
403
|
+
const safe = (s) => String(s || '').replace(/[&<>"]/g, (c) => ({
|
|
404
|
+
'&': '&', '<': '<', '>': '>', '"': '"'
|
|
405
|
+
}[c]));
|
|
406
|
+
const url = content.url || '';
|
|
407
|
+
const title = content.title || 'Untitled post';
|
|
408
|
+
const excerpt = content.excerpt || '';
|
|
409
|
+
const cover = content.cover_url || '';
|
|
410
|
+
const brand = content.brand || {};
|
|
411
|
+
return {
|
|
412
|
+
safe,
|
|
413
|
+
url, title, excerpt, cover,
|
|
414
|
+
primary: brand.primary_color || '#0054C9',
|
|
415
|
+
text: brand.text_color || '#001D21',
|
|
416
|
+
muted: '#5a5a5a',
|
|
417
|
+
card: '#ffffff',
|
|
418
|
+
border: '#e5e7eb'
|
|
419
|
+
};
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
function renderReferencedPostCard(content) {
|
|
423
|
+
const { safe, url, title, excerpt, cover, primary, text, muted, card, border } =
|
|
424
|
+
commonRefPostInputs(content);
|
|
425
|
+
|
|
426
|
+
const coverEl = cover
|
|
427
|
+
? `<a href="${safe(url)}" target="_blank" style="text-decoration:none;display:block">
|
|
428
|
+
<img src="${safe(cover)}" alt="${safe(title)}" width="600"
|
|
429
|
+
style="display:block;width:100%;max-width:600px;height:auto;border:0;outline:none;text-decoration:none" />
|
|
430
|
+
</a>`
|
|
431
|
+
: `<a href="${safe(url)}" target="_blank" style="text-decoration:none;display:block">
|
|
432
|
+
<div style="width:100%;height:180px;background:${primary};
|
|
433
|
+
display:flex;align-items:center;justify-content:center;
|
|
434
|
+
padding:0 24px;color:#fff;font-weight:700;font-size:22px;
|
|
435
|
+
line-height:1.2;text-align:center;font-family:inherit">
|
|
436
|
+
${safe(title)}
|
|
437
|
+
</div>
|
|
438
|
+
</a>`;
|
|
439
|
+
|
|
440
|
+
const html = `
|
|
441
|
+
<table role="presentation" width="100%" cellpadding="0" cellspacing="0" border="0"
|
|
442
|
+
class="bp-component bp-referenced-post bp-referenced-post--card"
|
|
443
|
+
data-style="card"
|
|
444
|
+
style="border-collapse:collapse;margin:24px 0;background:${card};border:1px solid ${border};border-radius:10px;overflow:hidden">
|
|
445
|
+
<tr><td style="padding:0">${coverEl}</td></tr>
|
|
446
|
+
<tr><td style="padding:18px 22px 6px">
|
|
447
|
+
<a href="${safe(url)}" target="_blank"
|
|
448
|
+
style="font-size:19px;font-weight:700;line-height:1.25;color:${text};text-decoration:none">
|
|
449
|
+
${safe(title)}
|
|
450
|
+
</a>
|
|
451
|
+
</td></tr>
|
|
452
|
+
${excerpt ? `
|
|
453
|
+
<tr><td style="padding:6px 22px 14px;color:${muted};font-size:14px;line-height:1.5">
|
|
454
|
+
${safe(excerpt)}
|
|
455
|
+
</td></tr>` : ''}
|
|
456
|
+
<tr><td style="padding:0 22px 22px">
|
|
457
|
+
<a href="${safe(url)}" target="_blank"
|
|
458
|
+
style="display:inline-block;padding:10px 16px;background:${primary};color:#ffffff;
|
|
459
|
+
font-weight:600;font-size:14px;text-decoration:none;border-radius:8px">
|
|
460
|
+
Read the article →
|
|
461
|
+
</a>
|
|
462
|
+
</td></tr>
|
|
463
|
+
</table>`.replace(/\s*\n\s*/g, ' ').trim();
|
|
464
|
+
return { html };
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
function renderReferencedPostCompact(content) {
|
|
468
|
+
const { safe, url, title, excerpt, cover, primary, text, muted, card, border } =
|
|
469
|
+
commonRefPostInputs(content);
|
|
470
|
+
|
|
471
|
+
// 96-px square cover keeps the row Outlook-safe (a width attribute on
|
|
472
|
+
// the <td> with fixed pixel values is the most reliable). When no
|
|
473
|
+
// cover exists, fall back to a brand-coloured tile with a tiny
|
|
474
|
+
// initial letter so the row still has visual presence.
|
|
475
|
+
const coverCell = cover
|
|
476
|
+
? `<td width="96" style="width:96px;padding:0;vertical-align:top">
|
|
477
|
+
<a href="${safe(url)}" target="_blank" style="display:block">
|
|
478
|
+
<img src="${safe(cover)}" alt="${safe(title)}" width="96" height="96"
|
|
479
|
+
style="display:block;width:96px;height:96px;object-fit:cover;border:0" />
|
|
480
|
+
</a>
|
|
481
|
+
</td>`
|
|
482
|
+
: `<td width="96" style="width:96px;padding:0;vertical-align:top">
|
|
483
|
+
<a href="${safe(url)}" target="_blank" style="display:block">
|
|
484
|
+
<div style="width:96px;height:96px;background:${primary};
|
|
485
|
+
display:flex;align-items:center;justify-content:center;
|
|
486
|
+
color:#fff;font-weight:700;font-size:24px;font-family:inherit;
|
|
487
|
+
line-height:1;text-align:center">
|
|
488
|
+
${safe(title.charAt(0).toUpperCase())}
|
|
489
|
+
</div>
|
|
490
|
+
</a>
|
|
491
|
+
</td>`;
|
|
492
|
+
|
|
493
|
+
// Excerpt is truncated to ~140 chars for the compact row. The card
|
|
494
|
+
// style keeps the full excerpt; this one trades fidelity for density.
|
|
495
|
+
const trimmedExcerpt = excerpt && excerpt.length > 140
|
|
496
|
+
? excerpt.slice(0, 137).replace(/\s+\S*$/, '') + '…'
|
|
497
|
+
: excerpt;
|
|
498
|
+
|
|
499
|
+
const html = `
|
|
500
|
+
<table role="presentation" width="100%" cellpadding="0" cellspacing="0" border="0"
|
|
501
|
+
class="bp-component bp-referenced-post bp-referenced-post--compact"
|
|
502
|
+
data-style="compact"
|
|
503
|
+
style="border-collapse:collapse;margin:14px 0;background:${card};border:1px solid ${border};border-radius:10px;overflow:hidden">
|
|
504
|
+
<tr>
|
|
505
|
+
${coverCell}
|
|
506
|
+
<td style="padding:12px 14px;vertical-align:top">
|
|
507
|
+
<a href="${safe(url)}" target="_blank"
|
|
508
|
+
style="font-size:15px;font-weight:700;line-height:1.3;color:${text};text-decoration:none;display:block">
|
|
509
|
+
${safe(title)}
|
|
510
|
+
</a>
|
|
511
|
+
${trimmedExcerpt ? `
|
|
512
|
+
<p style="margin:4px 0 6px;color:${muted};font-size:13px;line-height:1.45">
|
|
513
|
+
${safe(trimmedExcerpt)}
|
|
514
|
+
</p>` : ''}
|
|
515
|
+
<a href="${safe(url)}" target="_blank"
|
|
516
|
+
style="display:inline-block;color:${primary};font-weight:600;font-size:13px;text-decoration:none">
|
|
517
|
+
Read more →
|
|
518
|
+
</a>
|
|
519
|
+
</td>
|
|
520
|
+
</tr>
|
|
521
|
+
</table>`.replace(/\s*\n\s*/g, ' ').trim();
|
|
522
|
+
return { html };
|
|
523
|
+
}
|
|
524
|
+
|
|
361
525
|
const RENDERERS = {
|
|
362
|
-
cta:
|
|
363
|
-
form:
|
|
364
|
-
contact_form:
|
|
365
|
-
callout:
|
|
366
|
-
faq:
|
|
367
|
-
steps:
|
|
368
|
-
card:
|
|
369
|
-
data_table:
|
|
370
|
-
comparison:
|
|
371
|
-
process:
|
|
372
|
-
image:
|
|
373
|
-
image_carousel:
|
|
374
|
-
chart:
|
|
375
|
-
widget:
|
|
376
|
-
quote:
|
|
526
|
+
cta: (c) => renderCta(c),
|
|
527
|
+
form: (c) => renderForm(c),
|
|
528
|
+
contact_form: (c) => renderContactForm(c),
|
|
529
|
+
callout: (c) => renderCallout(c.content || {}),
|
|
530
|
+
faq: (c) => renderFaq(c.content || {}),
|
|
531
|
+
steps: (c) => renderSteps(c.content || {}),
|
|
532
|
+
card: (c) => renderCard(c.content || {}),
|
|
533
|
+
data_table: (c) => renderDataTable(c.content || {}),
|
|
534
|
+
comparison: (c) => renderComparison(c.content || {}),
|
|
535
|
+
process: (c) => renderProcess(c.content || {}),
|
|
536
|
+
image: (c) => renderImage(c),
|
|
537
|
+
image_carousel: (c) => renderImageCarousel(c.content || {}),
|
|
538
|
+
chart: (c) => renderChart(c.content || {}),
|
|
539
|
+
widget: (c, opts) => renderWidget(c, opts),
|
|
540
|
+
quote: (c) => renderQuote(c.content || {}),
|
|
541
|
+
// Referenced blog-post card. Same renderer used by both the CMS
|
|
542
|
+
// post-renderer pipeline (when authors embed another post via the
|
|
543
|
+
// editor) and the newsletters send/preview pipeline (which converts
|
|
544
|
+
// `{{post:UUID}}` markers into a pre-resolved component-shaped object
|
|
545
|
+
// on the fly — see services/newsletters/src/lib/postEmbeds.js).
|
|
546
|
+
referenced_blog_post: (c) => renderReferencedPost(c.content || {})
|
|
377
547
|
};
|
|
378
548
|
|
|
379
549
|
/**
|
package/styles.css
ADDED
|
@@ -0,0 +1,432 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Nukipa post-content default stylesheet.
|
|
3
|
+
*
|
|
4
|
+
* Ship-ready CSS for every component this package's renderPostBody()
|
|
5
|
+
* emits — callouts, FAQs, carousels, the inline CTA block, etc.
|
|
6
|
+
* Drop this into a Next.js layout (or any HTML host) and the
|
|
7
|
+
* rendered post is styled out of the box, no copying of ~800 lines
|
|
8
|
+
* of selectors.
|
|
9
|
+
*
|
|
10
|
+
* import '@nukipa/post-content/styles.css';
|
|
11
|
+
*
|
|
12
|
+
* THEME TOKENS — every color/spacing value falls back to a sensible
|
|
13
|
+
* default. To re-theme, set the matching CSS variable on `:root` (or
|
|
14
|
+
* any ancestor of the post body):
|
|
15
|
+
*
|
|
16
|
+
* :root {
|
|
17
|
+
* --color-primary: #0054C9;
|
|
18
|
+
* --color-primary-contrast:#FFFFFF;
|
|
19
|
+
* --color-bg: #FFFFFF;
|
|
20
|
+
* --color-text: #001D21;
|
|
21
|
+
* --color-text-muted: #51646A;
|
|
22
|
+
* --color-border: #E5DFDC;
|
|
23
|
+
* --color-surface: #F0EBEB;
|
|
24
|
+
* }
|
|
25
|
+
*
|
|
26
|
+
* Selectors target the classes the package emits directly — no host
|
|
27
|
+
* scope prefix (e.g. `.article-content` in apps/public). If you need
|
|
28
|
+
* to scope these rules to a single subtree, nest the @import in a
|
|
29
|
+
* cascade layer or wrap the body in a class and re-author the rules
|
|
30
|
+
* — this is the canonical stylesheet.
|
|
31
|
+
*/
|
|
32
|
+
|
|
33
|
+
/* ───── Container + prose typography ──────────────────────────────── */
|
|
34
|
+
.prose-body {
|
|
35
|
+
max-width: 70ch;
|
|
36
|
+
line-height: 1.75;
|
|
37
|
+
font-size: 1.0625rem;
|
|
38
|
+
color: var(--color-text, #001D21);
|
|
39
|
+
}
|
|
40
|
+
.prose-body h1, .prose-body h2, .prose-body h3 {
|
|
41
|
+
font-weight: 600;
|
|
42
|
+
letter-spacing: -0.02em;
|
|
43
|
+
margin-top: 1.6em;
|
|
44
|
+
margin-bottom: 0.5em;
|
|
45
|
+
}
|
|
46
|
+
.prose-body h1 { font-size: 2rem; }
|
|
47
|
+
.prose-body h2 { font-size: 1.5rem; }
|
|
48
|
+
.prose-body h3 { font-size: 1.25rem; }
|
|
49
|
+
.prose-body p { margin: 0 0 1.1em; }
|
|
50
|
+
.prose-body a {
|
|
51
|
+
color: var(--color-primary, #0054C9);
|
|
52
|
+
text-decoration: underline;
|
|
53
|
+
text-underline-offset: 2px;
|
|
54
|
+
}
|
|
55
|
+
.prose-body code {
|
|
56
|
+
background: var(--color-surface, #F0EBEB);
|
|
57
|
+
padding: 0.1em 0.35em;
|
|
58
|
+
border-radius: 6px;
|
|
59
|
+
font-size: 0.9em;
|
|
60
|
+
font-family: var(--font-mono, ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace);
|
|
61
|
+
}
|
|
62
|
+
.prose-body pre {
|
|
63
|
+
background: var(--color-dark, #1F2732);
|
|
64
|
+
color: var(--color-on-dark, #EAF1F8);
|
|
65
|
+
padding: 1.1em;
|
|
66
|
+
border-radius: 16px;
|
|
67
|
+
overflow-x: auto;
|
|
68
|
+
}
|
|
69
|
+
.prose-body pre code { background: transparent; padding: 0; }
|
|
70
|
+
.prose-body img { border-radius: 16px; margin: 1.6em 0; max-width: 100%; height: auto; }
|
|
71
|
+
.prose-body blockquote {
|
|
72
|
+
border-left: 2px solid var(--color-primary, #0054C9);
|
|
73
|
+
padding-left: 1em;
|
|
74
|
+
color: var(--color-text-muted, #51646A);
|
|
75
|
+
margin: 1.2em 0;
|
|
76
|
+
}
|
|
77
|
+
.prose-body ul, .prose-body ol { padding-left: 1.4em; margin: 0 0 1.1em; }
|
|
78
|
+
.prose-body li { margin: 0.3em 0; }
|
|
79
|
+
|
|
80
|
+
/* ───── Component wrapper (every bp-* gets one) ────────────────────── */
|
|
81
|
+
.bp-component { margin: 1.5rem 0; }
|
|
82
|
+
|
|
83
|
+
/* ───── Callouts ───────────────────────────────────────────────────── */
|
|
84
|
+
.bp-callout {
|
|
85
|
+
padding: 0.75rem 1rem;
|
|
86
|
+
border-radius: 0 0.5rem 0.5rem 0;
|
|
87
|
+
border-left: 4px solid var(--color-border, #E5DFDC);
|
|
88
|
+
background: var(--color-surface, #F0EBEB);
|
|
89
|
+
}
|
|
90
|
+
.bp-callout__header {
|
|
91
|
+
font-weight: 600;
|
|
92
|
+
font-size: 0.8125rem;
|
|
93
|
+
text-transform: uppercase;
|
|
94
|
+
letter-spacing: 0.03em;
|
|
95
|
+
margin-bottom: 0.375rem;
|
|
96
|
+
}
|
|
97
|
+
.bp-callout__body p:last-child { margin-bottom: 0; }
|
|
98
|
+
.bp-callout--note { background: color-mix(in srgb, #3b82f6 8%, var(--color-bg, #FFFFFF)); border-left-color: #3b82f6; }
|
|
99
|
+
.bp-callout--note .bp-callout__header { color: #3b82f6; }
|
|
100
|
+
.bp-callout--tip { background: color-mix(in srgb, #10b981 8%, var(--color-bg, #FFFFFF)); border-left-color: #10b981; }
|
|
101
|
+
.bp-callout--tip .bp-callout__header { color: #10b981; }
|
|
102
|
+
.bp-callout--important { background: color-mix(in srgb, var(--color-primary, #0054C9) 8%, var(--color-bg, #FFFFFF)); border-left-color: var(--color-primary, #0054C9); }
|
|
103
|
+
.bp-callout--important .bp-callout__header { color: var(--color-primary, #0054C9); }
|
|
104
|
+
.bp-callout--warning { background: color-mix(in srgb, #f59e0b 8%, var(--color-bg, #FFFFFF)); border-left-color: #f59e0b; }
|
|
105
|
+
.bp-callout--warning .bp-callout__header { color: #f59e0b; }
|
|
106
|
+
.bp-callout--caution { background: color-mix(in srgb, #ef4444 8%, var(--color-bg, #FFFFFF)); border-left-color: #ef4444; }
|
|
107
|
+
.bp-callout--caution .bp-callout__header { color: #ef4444; }
|
|
108
|
+
|
|
109
|
+
/* ───── FAQ ────────────────────────────────────────────────────────── */
|
|
110
|
+
.bp-faq {
|
|
111
|
+
border: 1px solid var(--color-border, #E5DFDC);
|
|
112
|
+
border-radius: 0.75rem;
|
|
113
|
+
overflow: hidden;
|
|
114
|
+
}
|
|
115
|
+
.bp-faq-item { border-bottom: 1px solid var(--color-border, #E5DFDC); }
|
|
116
|
+
.bp-faq-item:last-child { border-bottom: none; }
|
|
117
|
+
.bp-faq-item summary {
|
|
118
|
+
padding: 0.875rem 1.25rem;
|
|
119
|
+
font-weight: 600;
|
|
120
|
+
font-size: 1rem;
|
|
121
|
+
cursor: pointer;
|
|
122
|
+
list-style: none;
|
|
123
|
+
display: flex;
|
|
124
|
+
align-items: center;
|
|
125
|
+
gap: 0.625rem;
|
|
126
|
+
color: var(--color-text, #001D21);
|
|
127
|
+
transition: background 0.15s;
|
|
128
|
+
}
|
|
129
|
+
.bp-faq-item summary:hover { background: var(--color-surface, #F0EBEB); }
|
|
130
|
+
.bp-faq-item summary::-webkit-details-marker { display: none; }
|
|
131
|
+
.bp-faq-icon { font-size: 1.25rem; color: var(--color-primary, #0054C9); flex-shrink: 0; }
|
|
132
|
+
.bp-faq-question { flex: 1; }
|
|
133
|
+
.bp-faq-chevron { font-size: 1.375rem; color: var(--color-text-muted, #51646A); transition: transform 0.2s; flex-shrink: 0; }
|
|
134
|
+
.bp-faq-item[open] .bp-faq-chevron { transform: rotate(180deg); }
|
|
135
|
+
.bp-faq-answer { padding: 0 1.25rem 1rem 3rem; color: var(--color-text-muted, #51646A); }
|
|
136
|
+
.bp-faq-answer p:last-child { margin-bottom: 0; }
|
|
137
|
+
|
|
138
|
+
/* ───── Steps — vertical timeline ──────────────────────────────────── */
|
|
139
|
+
.bp-steps { padding-left: 0; }
|
|
140
|
+
.bp-step { display: flex; gap: 1.25rem; }
|
|
141
|
+
.bp-step__marker {
|
|
142
|
+
display: flex; flex-direction: column; align-items: center; flex-shrink: 0;
|
|
143
|
+
}
|
|
144
|
+
.bp-step__num {
|
|
145
|
+
width: 2.25rem; height: 2.25rem; border-radius: 50%;
|
|
146
|
+
background: var(--color-primary, #0054C9);
|
|
147
|
+
color: var(--color-primary-contrast, #FFFFFF);
|
|
148
|
+
display: flex; align-items: center; justify-content: center;
|
|
149
|
+
font-weight: 700; font-size: 0.9rem; flex-shrink: 0;
|
|
150
|
+
box-shadow: 0 0 0 4px color-mix(in srgb, var(--color-primary, #0054C9) 15%, transparent);
|
|
151
|
+
}
|
|
152
|
+
.bp-step__line {
|
|
153
|
+
width: 2px; flex: 1; min-height: 1.5rem;
|
|
154
|
+
background: color-mix(in srgb, var(--color-primary, #0054C9) 25%, transparent);
|
|
155
|
+
}
|
|
156
|
+
.bp-step__content { padding-bottom: 2rem; flex: 1; }
|
|
157
|
+
.bp-step--last .bp-step__content { padding-bottom: 0; }
|
|
158
|
+
.bp-step__content strong { font-size: 1.0625rem; display: block; margin-bottom: 0.25rem; }
|
|
159
|
+
.bp-step__content p:last-child { margin-bottom: 0; }
|
|
160
|
+
|
|
161
|
+
/* ───── Card ───────────────────────────────────────────────────────── */
|
|
162
|
+
.bp-card {
|
|
163
|
+
border: 1px solid var(--color-border, #E5DFDC);
|
|
164
|
+
border-radius: 0.75rem;
|
|
165
|
+
overflow: hidden;
|
|
166
|
+
background: var(--color-surface, #F0EBEB);
|
|
167
|
+
box-shadow: 0 2px 12px rgba(0,0,0,0.1);
|
|
168
|
+
border-left: 4px solid var(--color-primary, #0054C9);
|
|
169
|
+
}
|
|
170
|
+
.bp-card__img { width: 100%; height: auto; display: block; }
|
|
171
|
+
.bp-card__body { padding: 1.25rem 1.5rem; }
|
|
172
|
+
.bp-card__title { font-weight: 700; font-size: 1.125rem; margin-bottom: 0.5rem; color: var(--color-primary, #0054C9); }
|
|
173
|
+
.bp-card__body p { color: var(--color-text-muted, #51646A); }
|
|
174
|
+
.bp-card__body p:last-child { margin-bottom: 0; }
|
|
175
|
+
|
|
176
|
+
/* ───── Chart (canvas placeholder, host hydrates) ──────────────────── */
|
|
177
|
+
.bp-chart { border: 1px solid var(--color-border, #E5DFDC); border-radius: 0.5rem; padding: 1rem; }
|
|
178
|
+
.bp-chart__title { font-weight: 600; margin-bottom: 0.5rem; }
|
|
179
|
+
.bp-chart__canvas { width: 100%; max-height: 400px; }
|
|
180
|
+
|
|
181
|
+
/* ───── Data Table ─────────────────────────────────────────────────── */
|
|
182
|
+
.bp-data-table {
|
|
183
|
+
border: 1px solid var(--color-border, #E5DFDC);
|
|
184
|
+
border-radius: 0.5rem;
|
|
185
|
+
overflow-x: auto;
|
|
186
|
+
-webkit-overflow-scrolling: touch;
|
|
187
|
+
}
|
|
188
|
+
.bp-data-table__title {
|
|
189
|
+
font-weight: 600; padding: 0.75rem 1rem;
|
|
190
|
+
background: var(--color-surface, #F0EBEB);
|
|
191
|
+
border-bottom: 1px solid var(--color-border, #E5DFDC);
|
|
192
|
+
}
|
|
193
|
+
.bp-data-table table { margin: 0; width: max-content; min-width: 100%; }
|
|
194
|
+
.bp-data-table th, .bp-data-table td { min-width: 100px; }
|
|
195
|
+
|
|
196
|
+
/* ───── Image Carousel ─────────────────────────────────────────────── */
|
|
197
|
+
.bp-carousel {
|
|
198
|
+
position: relative;
|
|
199
|
+
border: 1px solid var(--color-border, #E5DFDC);
|
|
200
|
+
border-radius: 0.75rem;
|
|
201
|
+
overflow: hidden;
|
|
202
|
+
}
|
|
203
|
+
.bp-carousel__track { display: flex; overflow: hidden; }
|
|
204
|
+
.bp-carousel__slide { min-width: 100%; display: none; }
|
|
205
|
+
.bp-carousel__slide.active { display: block; }
|
|
206
|
+
.bp-carousel__slide img { width: 100%; height: auto; display: block; }
|
|
207
|
+
.bp-carousel__caption {
|
|
208
|
+
padding: 0.625rem 1rem;
|
|
209
|
+
font-size: 0.875rem;
|
|
210
|
+
color: var(--color-text-muted, #51646A);
|
|
211
|
+
text-align: center;
|
|
212
|
+
background: var(--color-surface, #F0EBEB);
|
|
213
|
+
}
|
|
214
|
+
.bp-carousel__prev, .bp-carousel__next {
|
|
215
|
+
position: absolute; top: 50%; transform: translateY(-50%);
|
|
216
|
+
background: rgba(0,0,0,0.5); color: #fff; border: none; border-radius: 50%;
|
|
217
|
+
width: 2.5rem; height: 2.5rem; padding: 0; cursor: pointer;
|
|
218
|
+
display: flex; align-items: center; justify-content: center;
|
|
219
|
+
backdrop-filter: blur(4px); transition: background 0.15s;
|
|
220
|
+
}
|
|
221
|
+
.bp-carousel__prev .material-icons, .bp-carousel__next .material-icons { font-size: 1.5rem; }
|
|
222
|
+
.bp-carousel__prev:hover, .bp-carousel__next:hover { background: rgba(0,0,0,0.7); }
|
|
223
|
+
.bp-carousel__prev { left: 0.75rem; }
|
|
224
|
+
.bp-carousel__next { right: 0.75rem; }
|
|
225
|
+
|
|
226
|
+
/* ───── Inline image (single figure) ───────────────────────────────── */
|
|
227
|
+
.bp-image {
|
|
228
|
+
margin: 1.75rem 0;
|
|
229
|
+
display: flex; flex-direction: column; align-items: center; gap: 0.5rem;
|
|
230
|
+
}
|
|
231
|
+
.bp-image img {
|
|
232
|
+
max-width: 100%; height: auto; display: block;
|
|
233
|
+
border-radius: 0.75rem;
|
|
234
|
+
border: 1px solid var(--color-border, #E5DFDC);
|
|
235
|
+
}
|
|
236
|
+
.bp-image__caption {
|
|
237
|
+
font-size: 0.9375rem;
|
|
238
|
+
color: var(--color-text-muted, #51646A);
|
|
239
|
+
text-align: center;
|
|
240
|
+
line-height: 1.5;
|
|
241
|
+
}
|
|
242
|
+
.bp-image__caption p { margin: 0; }
|
|
243
|
+
.bp-image__credit {
|
|
244
|
+
font-size: 0.75rem;
|
|
245
|
+
color: var(--color-text-muted, #51646A);
|
|
246
|
+
opacity: 0.85;
|
|
247
|
+
}
|
|
248
|
+
.bp-image__credit a { color: inherit; text-decoration: underline; }
|
|
249
|
+
.bp-image-placeholder {
|
|
250
|
+
margin: 1.75rem 0; padding: 2rem 1rem;
|
|
251
|
+
border: 1px dashed var(--color-border, #E5DFDC);
|
|
252
|
+
border-radius: 0.75rem;
|
|
253
|
+
text-align: center;
|
|
254
|
+
color: var(--color-text-muted, #51646A);
|
|
255
|
+
font-style: italic;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/* ───── Process — horizontal stages with connectors ───────────────── */
|
|
259
|
+
.bp-process { display: flex; align-items: flex-start; gap: 0; flex-wrap: wrap; justify-content: center; }
|
|
260
|
+
.bp-process__stage {
|
|
261
|
+
display: flex; flex-direction: column; align-items: center; text-align: center;
|
|
262
|
+
flex: 1; min-width: 0; padding: 1rem 0.75rem;
|
|
263
|
+
}
|
|
264
|
+
.bp-process__icon {
|
|
265
|
+
width: 3rem; height: 3rem; border-radius: 50%;
|
|
266
|
+
background: var(--color-primary, #0054C9);
|
|
267
|
+
color: var(--color-primary-contrast, #FFFFFF);
|
|
268
|
+
display: flex; align-items: center; justify-content: center;
|
|
269
|
+
font-weight: 700; font-size: 1rem; flex-shrink: 0; margin-bottom: 0.75rem;
|
|
270
|
+
box-shadow: 0 0 0 4px color-mix(in srgb, var(--color-primary, #0054C9) 15%, transparent);
|
|
271
|
+
}
|
|
272
|
+
.bp-process__icon .material-icons { font-size: 1.375rem; }
|
|
273
|
+
.bp-process__num { font-weight: 700; font-size: 1rem; }
|
|
274
|
+
.bp-process__title { font-weight: 600; font-size: 1rem; margin-bottom: 0.25rem; }
|
|
275
|
+
.bp-process__desc { font-size: 0.875rem; color: var(--color-text-muted, #51646A); line-height: 1.5; }
|
|
276
|
+
.bp-process__connector {
|
|
277
|
+
display: flex; align-items: center; padding-top: 1.25rem;
|
|
278
|
+
color: var(--color-text-muted, #51646A);
|
|
279
|
+
}
|
|
280
|
+
.bp-process__connector .material-icons { font-size: 1.5rem; }
|
|
281
|
+
|
|
282
|
+
/* ───── Comparison ─────────────────────────────────────────────────── */
|
|
283
|
+
.bp-comparison {
|
|
284
|
+
border: 1px solid var(--color-border, #E5DFDC);
|
|
285
|
+
border-radius: 0.5rem;
|
|
286
|
+
overflow-x: auto;
|
|
287
|
+
-webkit-overflow-scrolling: touch;
|
|
288
|
+
}
|
|
289
|
+
.bp-comparison table { margin: 0; width: max-content; min-width: 100%; }
|
|
290
|
+
.bp-comparison th, .bp-comparison td { min-width: 100px; }
|
|
291
|
+
.bp-comparison__cell { text-align: center; }
|
|
292
|
+
|
|
293
|
+
/* ───── Cite chip — superscript footnote link ──────────────────────── */
|
|
294
|
+
.bp-cite {
|
|
295
|
+
display: inline-block;
|
|
296
|
+
font-size: 0.7em;
|
|
297
|
+
vertical-align: super;
|
|
298
|
+
line-height: 1;
|
|
299
|
+
padding: 1px 5px;
|
|
300
|
+
margin: 0 1px;
|
|
301
|
+
border-radius: 999px;
|
|
302
|
+
background: color-mix(in srgb, var(--color-primary, #0054C9) 12%, transparent);
|
|
303
|
+
color: var(--color-primary, #0054C9);
|
|
304
|
+
font-weight: 600;
|
|
305
|
+
}
|
|
306
|
+
.bp-cite a { color: inherit; text-decoration: none; }
|
|
307
|
+
.bp-cite a:hover { text-decoration: underline; }
|
|
308
|
+
.bp-cite--orphan {
|
|
309
|
+
background: var(--color-surface, #F0EBEB);
|
|
310
|
+
color: var(--color-text-muted, #51646A);
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
/* ───── Inline widget iframe ───────────────────────────────────────── */
|
|
314
|
+
.inline-widget-container {
|
|
315
|
+
margin: 2rem 0;
|
|
316
|
+
border-radius: 0.75rem;
|
|
317
|
+
overflow: visible;
|
|
318
|
+
}
|
|
319
|
+
.inline-widget-container iframe {
|
|
320
|
+
width: 100%;
|
|
321
|
+
border: none;
|
|
322
|
+
min-height: 200px;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/* ───── Inline CTA ─────────────────────────────────────────────────── */
|
|
326
|
+
.inline-cta-block {
|
|
327
|
+
display: flex;
|
|
328
|
+
flex-direction: column;
|
|
329
|
+
align-items: center;
|
|
330
|
+
gap: 0.75rem;
|
|
331
|
+
padding: 2.5rem 2rem;
|
|
332
|
+
margin: 2.5rem 0;
|
|
333
|
+
background: var(--color-surface, #F0EBEB);
|
|
334
|
+
border: 1px solid var(--color-border, #E5DFDC);
|
|
335
|
+
border-radius: 1rem;
|
|
336
|
+
text-align: center;
|
|
337
|
+
}
|
|
338
|
+
.inline-cta-description {
|
|
339
|
+
color: var(--color-text-muted, #51646A);
|
|
340
|
+
font-size: 0.9375rem;
|
|
341
|
+
line-height: 1.5;
|
|
342
|
+
margin: 0;
|
|
343
|
+
}
|
|
344
|
+
.inline-cta-btn {
|
|
345
|
+
display: inline-block;
|
|
346
|
+
padding: 0.75rem 2rem;
|
|
347
|
+
border-radius: 0.5rem;
|
|
348
|
+
font-size: 1rem;
|
|
349
|
+
font-weight: 600;
|
|
350
|
+
text-decoration: none;
|
|
351
|
+
cursor: pointer;
|
|
352
|
+
transition: all 0.2s ease;
|
|
353
|
+
border: none;
|
|
354
|
+
font-family: inherit;
|
|
355
|
+
}
|
|
356
|
+
.inline-cta-btn.cta-primary {
|
|
357
|
+
background: var(--color-primary, #0054C9);
|
|
358
|
+
color: var(--color-primary-contrast, #FFFFFF);
|
|
359
|
+
}
|
|
360
|
+
.inline-cta-btn.cta-primary:hover {
|
|
361
|
+
opacity: 0.9;
|
|
362
|
+
transform: translateY(-1px);
|
|
363
|
+
}
|
|
364
|
+
.inline-cta-btn.cta-outline {
|
|
365
|
+
background: transparent;
|
|
366
|
+
border: 2px solid var(--color-primary, #0054C9);
|
|
367
|
+
color: var(--color-primary, #0054C9);
|
|
368
|
+
}
|
|
369
|
+
.inline-cta-btn.cta-outline:hover {
|
|
370
|
+
background: color-mix(in srgb, var(--color-primary, #0054C9) 10%, transparent);
|
|
371
|
+
}
|
|
372
|
+
.inline-cta-btn.cta-subtle {
|
|
373
|
+
background: transparent;
|
|
374
|
+
color: var(--color-primary, #0054C9);
|
|
375
|
+
text-decoration: underline;
|
|
376
|
+
padding: 0.5rem 0;
|
|
377
|
+
}
|
|
378
|
+
.inline-cta-btn.cta-subtle:hover { opacity: 0.8; }
|
|
379
|
+
|
|
380
|
+
/* ───── CTA Form ───────────────────────────────────────────────────── */
|
|
381
|
+
.inline-cta-form-block { padding: 2rem; }
|
|
382
|
+
.inline-cta-form-title {
|
|
383
|
+
font-size: 1.375rem;
|
|
384
|
+
font-weight: 700;
|
|
385
|
+
color: var(--color-text, #001D21);
|
|
386
|
+
margin: 0 0 1.25rem;
|
|
387
|
+
}
|
|
388
|
+
.inline-cta-form {
|
|
389
|
+
display: flex;
|
|
390
|
+
flex-direction: column;
|
|
391
|
+
gap: 0.75rem;
|
|
392
|
+
max-width: 480px;
|
|
393
|
+
width: 100%;
|
|
394
|
+
margin: 0 auto;
|
|
395
|
+
}
|
|
396
|
+
.inline-cta-input,
|
|
397
|
+
.inline-cta-textarea {
|
|
398
|
+
width: 100%;
|
|
399
|
+
padding: 0.75rem 1rem;
|
|
400
|
+
border: 1px solid var(--color-border, #E5DFDC);
|
|
401
|
+
border-radius: 0.5rem;
|
|
402
|
+
font-size: 0.9375rem;
|
|
403
|
+
font-family: inherit;
|
|
404
|
+
background: var(--color-bg, #FFFFFF);
|
|
405
|
+
color: var(--color-text, #001D21);
|
|
406
|
+
transition: border-color 0.2s ease;
|
|
407
|
+
box-sizing: border-box;
|
|
408
|
+
}
|
|
409
|
+
.inline-cta-input:focus,
|
|
410
|
+
.inline-cta-textarea:focus {
|
|
411
|
+
outline: none;
|
|
412
|
+
border-color: var(--color-primary, #0054C9);
|
|
413
|
+
}
|
|
414
|
+
.inline-cta-textarea {
|
|
415
|
+
resize: vertical;
|
|
416
|
+
min-height: 80px;
|
|
417
|
+
}
|
|
418
|
+
.inline-cta-submit { align-self: center; margin-top: 0.5rem; }
|
|
419
|
+
.inline-cta-success {
|
|
420
|
+
color: var(--color-primary, #0054C9);
|
|
421
|
+
font-weight: 600;
|
|
422
|
+
font-size: 1rem;
|
|
423
|
+
margin: 1rem 0;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
/* ───── Mobile tweaks ──────────────────────────────────────────────── */
|
|
427
|
+
@media (max-width: 768px) {
|
|
428
|
+
.prose-body { font-size: 1rem; }
|
|
429
|
+
.prose-body h1 { font-size: 1.75rem; }
|
|
430
|
+
.prose-body h2 { font-size: 1.5rem; }
|
|
431
|
+
.prose-body h3 { font-size: 1.25rem; }
|
|
432
|
+
}
|