@repobit/dex-system-design 0.23.94 → 0.23.96
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/CHANGELOG.md +15 -0
- package/package.json +4 -4
- package/src/blog/assets/articles/fbi-crypto-scam-1.jpeg +0 -0
- package/src/blog/assets/articles/fbi-crypto-scam-cover.png +0 -0
- package/src/blog/assets/fonts/IBMPlexSans-Bold.woff2 +0 -0
- package/src/blog/assets/fonts/IBMPlexSans-BoldItalic.woff2 +0 -0
- package/src/blog/assets/fonts/IBMPlexSans-Italic.woff2 +0 -0
- package/src/blog/assets/fonts/IBMPlexSans-Light.woff2 +0 -0
- package/src/blog/assets/fonts/IBMPlexSans-LightItalic.woff2 +0 -0
- package/src/blog/assets/fonts/IBMPlexSans-Medium.woff2 +0 -0
- package/src/blog/assets/fonts/IBMPlexSans-MediumItalic.woff2 +0 -0
- package/src/blog/assets/fonts/IBMPlexSans-Regular.woff2 +0 -0
- package/src/blog/assets/fonts/IBMPlexSans-SemiBold.woff2 +0 -0
- package/src/blog/assets/fonts/IBMPlexSans-SemiBoldItalic.woff2 +0 -0
- package/src/blog/assets/fonts/IBMPlexSansCondensed-Bold.woff2 +0 -0
- package/src/blog/assets/fonts/IBMPlexSansCondensed-BoldItalic.woff2 +0 -0
- package/src/blog/assets/fonts/IBMPlexSansCondensed-Italic.woff2 +0 -0
- package/src/blog/assets/fonts/IBMPlexSansCondensed-Light.woff2 +0 -0
- package/src/blog/assets/fonts/IBMPlexSansCondensed-LightItalic.woff2 +0 -0
- package/src/blog/assets/fonts/IBMPlexSansCondensed-Medium.woff2 +0 -0
- package/src/blog/assets/fonts/IBMPlexSansCondensed-MediumItalic.woff2 +0 -0
- package/src/blog/assets/fonts/IBMPlexSansCondensed-Regular.woff2 +0 -0
- package/src/blog/assets/fonts/IBMPlexSansCondensed-SemiBold.woff2 +0 -0
- package/src/blog/assets/fonts/IBMPlexSansCondensed-SemiBoldItalic.woff2 +0 -0
- package/src/blog/assets/global-scam-intelligence-report-2026.png +0 -0
- package/src/blog/assets/psd-4.jpg +0 -0
- package/src/blog/src/articles/FBI warns of crypto investment scam that sends couriers to collect victims' cash-editedAB.docx +0 -0
- package/src/blog/src/articles/FBI-crypto-scam-courier-cash.png +0 -0
- package/src/blog/src/articles/fbi-crypto-scam.js +324 -0
- package/src/blog/src/articles/~$I warns of crypto investment scam that sends couriers to collect victims' cash-editedAB.docx +0 -0
- package/src/blog/src/atoms/blog-abstract.js +51 -0
- package/src/blog/src/atoms/blog-atoms.stories.js +519 -0
- package/src/blog/src/atoms/blog-authors.js +70 -0
- package/src/blog/src/atoms/blog-avatar.js +44 -0
- package/src/blog/src/atoms/blog-blockquote.js +92 -0
- package/src/blog/src/atoms/blog-caption.js +31 -0
- package/src/blog/src/atoms/blog-code-block.js +89 -0
- package/src/blog/src/atoms/blog-dek.js +30 -0
- package/src/blog/src/atoms/blog-featured-image.js +65 -0
- package/src/blog/src/atoms/blog-heading.js +63 -0
- package/src/blog/src/atoms/blog-image.js +50 -0
- package/src/blog/src/atoms/blog-key-takeaways.js +101 -0
- package/src/blog/src/atoms/blog-link.js +68 -0
- package/src/blog/src/atoms/blog-meta.js +87 -0
- package/src/blog/src/atoms/blog-most-popular.js +146 -0
- package/src/blog/src/atoms/blog-newsletter-signup.js +342 -0
- package/src/blog/src/atoms/blog-paragraph.js +61 -0
- package/src/blog/src/atoms/blog-product-upsell.js +168 -0
- package/src/blog/src/atoms/blog-related-articles.js +326 -0
- package/src/blog/src/atoms/blog-related-label.js +30 -0
- package/src/blog/src/atoms/blog-report-promo.js +250 -0
- package/src/blog/src/atoms/blog-standout.js +78 -0
- package/src/blog/src/atoms/blog-subhead.js +32 -0
- package/src/blog/src/atoms/blog-table-of-contents.js +447 -0
- package/src/blog/src/atoms/blog-tag.js +86 -0
- package/src/blog/src/atoms/blog-title.js +31 -0
- package/src/blog/src/base/blog-element.js +10 -0
- package/src/blog/src/breakpoints.js +23 -0
- package/src/blog/src/content/blog-article.js +736 -0
- package/src/blog/src/content/blog-atoms-showcase.js +401 -0
- package/src/blog/src/content/blog-page-layout.js +73 -0
- package/src/blog/src/content/blog-pages.stories.js +101 -0
- package/src/blog/src/content/render-inline-content.js +18 -0
- package/src/blog/src/data/popular-articles.js +43 -0
- package/src/blog/src/data/related-articles.js +58 -0
- package/src/blog/src/index.js +104 -0
- package/src/blog/src/lib/article-toc.js +206 -0
- package/src/blog/src/lib/load-prism.js +23 -0
- package/src/blog/src/shell/blog-shell.js +31 -0
- package/src/blog/src/styles/prism-theme.js +65 -0
- package/src/blog/src/tokens/blog.js +406 -0
- package/src/blog/src/tokens/core.js +811 -0
- package/src/blog/src/tokens/fonts.css +241 -0
- package/src/blog/src/tokens/index.js +27 -0
- package/src/blog/src/tokens/legacy.js +58 -0
- package/src/blog/tokens/blog.js +406 -0
- package/src/blog/tokens/core.js +811 -0
- package/src/blog/tokens/fonts.css +241 -0
- package/src/blog/tokens/index.js +27 -0
- package/src/blog/tokens/legacy.js +58 -0
- package/src/blog/tokens.css +1512 -0
- package/src/blog/tokens.js +1 -0
- package/src/blog/vendor/prism/prism.css +120 -0
- package/src/blog/vendor/prism/prism.js +300 -0
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { css, html } from 'lit';
|
|
2
|
+
import { BlogElement } from '../base/blog-element.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Body quote — soft blue panel with oversized opening mark.
|
|
6
|
+
* Hero abstract keeps the left accent bar; this is a distinct treatment.
|
|
7
|
+
*
|
|
8
|
+
* @attr {string} cite — Optional attribution (renders footer when set)
|
|
9
|
+
*/
|
|
10
|
+
export class BlogBlockquote extends BlogElement {
|
|
11
|
+
static properties = {
|
|
12
|
+
cite: { type: String }
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
constructor() {
|
|
16
|
+
super();
|
|
17
|
+
this.cite = '';
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
static styles = [
|
|
21
|
+
...BlogElement.styles,
|
|
22
|
+
css`
|
|
23
|
+
:host {
|
|
24
|
+
display: block;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
blockquote {
|
|
28
|
+
display: grid;
|
|
29
|
+
grid-template-columns: auto 1fr;
|
|
30
|
+
column-gap: var(--blog-quote-mark-gap);
|
|
31
|
+
align-items: start;
|
|
32
|
+
margin: 0;
|
|
33
|
+
padding: var(--blog-quote-padding-block)
|
|
34
|
+
var(--blog-quote-padding-inline);
|
|
35
|
+
padding-left: var(--blog-quote-padding-inline-start);
|
|
36
|
+
background: var(--blog-quote-background);
|
|
37
|
+
border-radius: var(--blog-quote-borderRadius);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
.quote__mark {
|
|
41
|
+
font-family: var(--blog-quote-mark-fontFamily);
|
|
42
|
+
font-size: var(--blog-quote-mark-fontSize);
|
|
43
|
+
font-weight: var(--blog-quote-mark-fontWeight);
|
|
44
|
+
font-style: normal;
|
|
45
|
+
line-height: var(--blog-quote-mark-lineHeight);
|
|
46
|
+
color: var(--blog-quote-mark-color);
|
|
47
|
+
margin-top: var(--blog-quote-mark-offset-top);
|
|
48
|
+
pointer-events: none;
|
|
49
|
+
user-select: none;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
.quote__body {
|
|
53
|
+
min-width: 0;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
.quote__text {
|
|
57
|
+
margin: 0;
|
|
58
|
+
font-family: var(--typography-fontFamily-sans);
|
|
59
|
+
font-size: var(--blog-quote-fontSize);
|
|
60
|
+
font-weight: var(--typography-fontWeight-normal);
|
|
61
|
+
font-style: var(--blog-quote-fontStyle);
|
|
62
|
+
line-height: var(--blog-quote-lineHeight);
|
|
63
|
+
color: var(--blog-quote-color);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
footer {
|
|
67
|
+
margin-top: var(--spacing-12);
|
|
68
|
+
font-size: var(--blog-quote-attribution-fontSize);
|
|
69
|
+
font-weight: var(--typography-fontWeight-normal);
|
|
70
|
+
font-style: normal;
|
|
71
|
+
line-height: var(--typography-lineHeight-normal);
|
|
72
|
+
color: var(--blog-quote-attribution-color);
|
|
73
|
+
}
|
|
74
|
+
`
|
|
75
|
+
];
|
|
76
|
+
|
|
77
|
+
render() {
|
|
78
|
+
return html`
|
|
79
|
+
<blockquote part="blockquote">
|
|
80
|
+
<span class="quote__mark" aria-hidden="true">“</span>
|
|
81
|
+
<div class="quote__body">
|
|
82
|
+
<p class="quote__text" part="quote-text"><slot></slot></p>
|
|
83
|
+
${this.cite
|
|
84
|
+
? html`<footer part="attribution">— ${this.cite}</footer>`
|
|
85
|
+
: null}
|
|
86
|
+
</div>
|
|
87
|
+
</blockquote>
|
|
88
|
+
`;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
customElements.define('blog-blockquote', BlogBlockquote);
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { css, html } from 'lit';
|
|
2
|
+
import { BlogElement } from '../base/blog-element.js';
|
|
3
|
+
|
|
4
|
+
/** Image / figure caption */
|
|
5
|
+
export class BlogCaption extends BlogElement {
|
|
6
|
+
static styles = [
|
|
7
|
+
...BlogElement.styles,
|
|
8
|
+
css`
|
|
9
|
+
:host {
|
|
10
|
+
display: block;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
figcaption {
|
|
14
|
+
margin: 0;
|
|
15
|
+
padding-top: var(--spacing-12);
|
|
16
|
+
font-family: var(--typography-fontFamily-sans);
|
|
17
|
+
font-size: var(--typography-caption-default-fontSize);
|
|
18
|
+
font-weight: var(--typography-caption-default-fontWeight);
|
|
19
|
+
line-height: var(--typography-caption-default-lineHeight);
|
|
20
|
+
color: var(--color-text-muted);
|
|
21
|
+
text-align: center;
|
|
22
|
+
}
|
|
23
|
+
`
|
|
24
|
+
];
|
|
25
|
+
|
|
26
|
+
render() {
|
|
27
|
+
return html`<figcaption part="caption"><slot></slot></figcaption>`;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
customElements.define('blog-caption', BlogCaption);
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { css, html } from 'lit';
|
|
2
|
+
import { BlogElement } from '../base/blog-element.js';
|
|
3
|
+
import { loadPrism } from '../lib/load-prism.js';
|
|
4
|
+
import { prismTheme } from '../styles/prism-theme.js';
|
|
5
|
+
import './blog-caption.js';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Syntax-highlighted code block — PrismJS, monochrome blog theme.
|
|
9
|
+
*
|
|
10
|
+
* @property {string} language — Prism language id (javascript, bash, json, …)
|
|
11
|
+
* @property {string} code — Source code text
|
|
12
|
+
* @property {string} caption — Optional label below the block
|
|
13
|
+
*/
|
|
14
|
+
export class BlogCodeBlock extends BlogElement {
|
|
15
|
+
static properties = {
|
|
16
|
+
language: { type: String },
|
|
17
|
+
code : { type: String },
|
|
18
|
+
caption : { type: String }
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
constructor() {
|
|
22
|
+
super();
|
|
23
|
+
this.language = 'javascript';
|
|
24
|
+
this.code = '';
|
|
25
|
+
this.caption = '';
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
static styles = [
|
|
29
|
+
...BlogElement.styles,
|
|
30
|
+
prismTheme,
|
|
31
|
+
css`
|
|
32
|
+
:host {
|
|
33
|
+
display: block;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
figure {
|
|
37
|
+
margin: 0;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
blog-caption {
|
|
41
|
+
display: block;
|
|
42
|
+
margin-top: var(--spacing-8);
|
|
43
|
+
}
|
|
44
|
+
`
|
|
45
|
+
];
|
|
46
|
+
|
|
47
|
+
async firstUpdated() {
|
|
48
|
+
await this.highlight();
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
async updated(changed) {
|
|
52
|
+
if (changed.has('code') || changed.has('language')) {
|
|
53
|
+
await this.highlight();
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async highlight() {
|
|
58
|
+
const code = this.renderRoot?.querySelector('code');
|
|
59
|
+
if (!code || !this.code) {
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
code.textContent = this.code;
|
|
64
|
+
code.className = `language-${this.language}`;
|
|
65
|
+
|
|
66
|
+
const pre = code.parentElement;
|
|
67
|
+
if (pre) {
|
|
68
|
+
pre.className = `language-${this.language}`;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const Prism = await loadPrism();
|
|
72
|
+
Prism.highlightElement(code);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
render() {
|
|
76
|
+
return html`
|
|
77
|
+
<figure part="figure">
|
|
78
|
+
<pre part="pre" class="language-${this.language}">
|
|
79
|
+
<code part="code" class="language-${this.language}">${this.code}</code>
|
|
80
|
+
</pre>
|
|
81
|
+
${this.caption
|
|
82
|
+
? html`<blog-caption part="caption">${this.caption}</blog-caption>`
|
|
83
|
+
: null}
|
|
84
|
+
</figure>
|
|
85
|
+
`;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
customElements.define('blog-code-block', BlogCodeBlock);
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { css, html } from 'lit';
|
|
2
|
+
import { BlogElement } from '../base/blog-element.js';
|
|
3
|
+
|
|
4
|
+
/** Article lead / dek paragraph */
|
|
5
|
+
export class BlogDek extends BlogElement {
|
|
6
|
+
static styles = [
|
|
7
|
+
...BlogElement.styles,
|
|
8
|
+
css`
|
|
9
|
+
:host {
|
|
10
|
+
display: block;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
p {
|
|
14
|
+
margin: 0;
|
|
15
|
+
font-family: var(--typography-fontFamily-sans);
|
|
16
|
+
font-size: var(--blog-hero-subtitle-fontSize);
|
|
17
|
+
font-weight: var(--typography-body-lead-fontWeight);
|
|
18
|
+
font-style: italic;
|
|
19
|
+
line-height: var(--typography-body-lead-lineHeight);
|
|
20
|
+
color: var(--blog-hero-subtitle-color);
|
|
21
|
+
}
|
|
22
|
+
`
|
|
23
|
+
];
|
|
24
|
+
|
|
25
|
+
render() {
|
|
26
|
+
return html`<p part="dek"><slot></slot></p>`;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
customElements.define('blog-dek', BlogDek);
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { css, html } from 'lit';
|
|
2
|
+
import { BlogElement } from '../base/blog-element.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Article hero featured image — dedicated cover for the entire article.
|
|
6
|
+
* Fixed 16:9 ratio, empty placeholder when src is missing.
|
|
7
|
+
*
|
|
8
|
+
* @attr {string} src
|
|
9
|
+
* @attr {string} alt
|
|
10
|
+
*/
|
|
11
|
+
export class BlogFeaturedImage extends BlogElement {
|
|
12
|
+
static properties = {
|
|
13
|
+
src: { type: String },
|
|
14
|
+
alt: { type: String }
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
constructor() {
|
|
18
|
+
super();
|
|
19
|
+
this.src = '';
|
|
20
|
+
this.alt = '';
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
static styles = [
|
|
24
|
+
...BlogElement.styles,
|
|
25
|
+
css`
|
|
26
|
+
:host {
|
|
27
|
+
display: block;
|
|
28
|
+
border-radius: var(--blog-hero-featured-borderRadius);
|
|
29
|
+
overflow: hidden;
|
|
30
|
+
aspect-ratio: var(--blog-hero-featured-aspect-ratio);
|
|
31
|
+
background: var(--blog-hero-featured-background);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
img,
|
|
35
|
+
.placeholder {
|
|
36
|
+
display: block;
|
|
37
|
+
width: 100%;
|
|
38
|
+
height: 100%;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
img {
|
|
42
|
+
object-fit: cover;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
.placeholder {
|
|
46
|
+
margin: 0;
|
|
47
|
+
}
|
|
48
|
+
`
|
|
49
|
+
];
|
|
50
|
+
|
|
51
|
+
render() {
|
|
52
|
+
if (!this.src) {
|
|
53
|
+
return html`<div
|
|
54
|
+
class="placeholder"
|
|
55
|
+
part="placeholder"
|
|
56
|
+
role="img"
|
|
57
|
+
aria-label=${this.alt || 'Article featured image'}
|
|
58
|
+
></div>`;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return html`<img src=${this.src} alt=${this.alt} part="image" />`;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
customElements.define('blog-featured-image', BlogFeaturedImage);
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { css, html } from 'lit';
|
|
2
|
+
import { BlogElement } from '../base/blog-element.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Section heading (h2–h4).
|
|
6
|
+
* @attr {2|3|4} level
|
|
7
|
+
*/
|
|
8
|
+
export class BlogHeading extends BlogElement {
|
|
9
|
+
static properties = {
|
|
10
|
+
level: { type: Number, reflect: true }
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
constructor() {
|
|
14
|
+
super();
|
|
15
|
+
this.level = 2;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
static styles = [
|
|
19
|
+
...BlogElement.styles,
|
|
20
|
+
css`
|
|
21
|
+
:host {
|
|
22
|
+
display: block;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
:is(h2, h3, h4) {
|
|
26
|
+
margin: 0;
|
|
27
|
+
font-style: normal;
|
|
28
|
+
color: var(--color-text-primary);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
h2 {
|
|
32
|
+
font-family: var(--typography-heading-h2-fontFamily);
|
|
33
|
+
font-size: var(--typography-heading-h2-fontSize);
|
|
34
|
+
font-weight: var(--typography-heading-h2-fontWeight);
|
|
35
|
+
line-height: var(--typography-heading-h2-lineHeight);
|
|
36
|
+
letter-spacing: var(--typography-heading-h2-letterSpacing);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
h3 {
|
|
40
|
+
font-family: var(--typography-heading-h3-fontFamily);
|
|
41
|
+
font-size: var(--typography-heading-h3-fontSize);
|
|
42
|
+
font-weight: var(--typography-heading-h3-fontWeight);
|
|
43
|
+
line-height: var(--typography-heading-h3-lineHeight);
|
|
44
|
+
letter-spacing: var(--typography-heading-h3-letterSpacing);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
h4 {
|
|
48
|
+
font-family: var(--typography-heading-h4-fontFamily);
|
|
49
|
+
font-size: var(--typography-heading-h4-fontSize);
|
|
50
|
+
font-weight: var(--typography-heading-h4-fontWeight);
|
|
51
|
+
line-height: var(--typography-heading-h4-lineHeight);
|
|
52
|
+
letter-spacing: var(--typography-heading-h4-letterSpacing);
|
|
53
|
+
}
|
|
54
|
+
`
|
|
55
|
+
];
|
|
56
|
+
|
|
57
|
+
render() {
|
|
58
|
+
const tag = `h${this.level}`;
|
|
59
|
+
return html`<${tag} part="heading"><slot></slot></${tag}>`;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
customElements.define('blog-heading', BlogHeading);
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { css, html } from 'lit';
|
|
2
|
+
import { BlogElement } from '../base/blog-element.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Inline article image — screenshots and supporting visuals in the body.
|
|
6
|
+
* Uses natural image dimensions (no fixed aspect ratio).
|
|
7
|
+
*
|
|
8
|
+
* @attr {string} src
|
|
9
|
+
* @attr {string} alt
|
|
10
|
+
*/
|
|
11
|
+
export class BlogImage extends BlogElement {
|
|
12
|
+
static properties = {
|
|
13
|
+
src: { type: String },
|
|
14
|
+
alt: { type: String }
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
constructor() {
|
|
18
|
+
super();
|
|
19
|
+
this.src = '';
|
|
20
|
+
this.alt = '';
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
static styles = [
|
|
24
|
+
...BlogElement.styles,
|
|
25
|
+
css`
|
|
26
|
+
:host {
|
|
27
|
+
display: block;
|
|
28
|
+
border-radius: var(--blog-content-image-borderRadius);
|
|
29
|
+
overflow: hidden;
|
|
30
|
+
background: var(--blog-content-image-background);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
img {
|
|
34
|
+
display: block;
|
|
35
|
+
width: 100%;
|
|
36
|
+
height: auto;
|
|
37
|
+
}
|
|
38
|
+
`
|
|
39
|
+
];
|
|
40
|
+
|
|
41
|
+
render() {
|
|
42
|
+
if (!this.src) {
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return html`<img src=${this.src} alt=${this.alt} part="image" />`;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
customElements.define('blog-image', BlogImage);
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { css, html } from 'lit';
|
|
2
|
+
import { BlogElement } from '../base/blog-element.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Key takeaways card — bordered summary block with title + bullet list.
|
|
6
|
+
*
|
|
7
|
+
* @property {string} title
|
|
8
|
+
* @property {string[]} items
|
|
9
|
+
*/
|
|
10
|
+
export class BlogKeyTakeaways extends BlogElement {
|
|
11
|
+
static properties = {
|
|
12
|
+
title: { type: String },
|
|
13
|
+
items: { type: Array }
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
constructor() {
|
|
17
|
+
super();
|
|
18
|
+
this.title = 'Key Takeaways';
|
|
19
|
+
this.items = [];
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
static styles = [
|
|
23
|
+
...BlogElement.styles,
|
|
24
|
+
css`
|
|
25
|
+
:host {
|
|
26
|
+
display: block;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
.takeaways {
|
|
30
|
+
border: var(--blog-takeaways-border-width) solid
|
|
31
|
+
var(--blog-takeaways-border-color);
|
|
32
|
+
border-radius: var(--blog-takeaways-borderRadius);
|
|
33
|
+
background: var(--blog-takeaways-background);
|
|
34
|
+
overflow: hidden;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
.takeaways__header {
|
|
38
|
+
padding: var(--blog-takeaways-header-padding-block)
|
|
39
|
+
var(--blog-takeaways-padding-inline);
|
|
40
|
+
padding-bottom: var(--blog-takeaways-header-padding-bottom);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
.takeaways__title {
|
|
44
|
+
margin: 0;
|
|
45
|
+
font-family: var(--blog-takeaways-title-fontFamily);
|
|
46
|
+
font-size: var(--blog-takeaways-title-fontSize);
|
|
47
|
+
font-weight: var(--blog-takeaways-title-fontWeight);
|
|
48
|
+
line-height: var(--blog-takeaways-title-lineHeight);
|
|
49
|
+
color: var(--blog-takeaways-title-color);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
.takeaways__divider {
|
|
53
|
+
margin: 0;
|
|
54
|
+
border: 0;
|
|
55
|
+
border-top: var(--blog-takeaways-divider-width) solid
|
|
56
|
+
var(--blog-takeaways-divider-color);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
.takeaways__list {
|
|
60
|
+
margin: 0;
|
|
61
|
+
padding: var(--blog-takeaways-body-padding-top)
|
|
62
|
+
var(--blog-takeaways-padding-inline)
|
|
63
|
+
var(--blog-takeaways-body-padding-bottom);
|
|
64
|
+
padding-left: calc(
|
|
65
|
+
var(--blog-takeaways-padding-inline) +
|
|
66
|
+
var(--blog-takeaways-list-indent)
|
|
67
|
+
);
|
|
68
|
+
list-style: disc;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
.takeaways__list li {
|
|
72
|
+
padding-left: var(--blog-takeaways-bullet-gap);
|
|
73
|
+
font-family: var(--typography-fontFamily-sans);
|
|
74
|
+
font-size: var(--blog-takeaways-item-fontSize);
|
|
75
|
+
font-weight: var(--blog-takeaways-item-fontWeight);
|
|
76
|
+
line-height: var(--blog-takeaways-item-lineHeight);
|
|
77
|
+
color: var(--blog-takeaways-item-color);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
.takeaways__list li + li {
|
|
81
|
+
margin-top: var(--blog-takeaways-item-gap);
|
|
82
|
+
}
|
|
83
|
+
`
|
|
84
|
+
];
|
|
85
|
+
|
|
86
|
+
render() {
|
|
87
|
+
return html`
|
|
88
|
+
<aside class="takeaways" part="takeaways">
|
|
89
|
+
<header class="takeaways__header">
|
|
90
|
+
<h2 class="takeaways__title" part="title">${this.title}</h2>
|
|
91
|
+
</header>
|
|
92
|
+
<hr class="takeaways__divider" />
|
|
93
|
+
<ul class="takeaways__list" part="list">
|
|
94
|
+
${this.items.map((item) => html`<li part="item">${item}</li>`)}
|
|
95
|
+
</ul>
|
|
96
|
+
</aside>
|
|
97
|
+
`;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
customElements.define('blog-key-takeaways', BlogKeyTakeaways);
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { css, html } from 'lit';
|
|
2
|
+
import { BlogElement } from '../base/blog-element.js';
|
|
3
|
+
|
|
4
|
+
/** Inline or standalone text link */
|
|
5
|
+
export class BlogLink extends BlogElement {
|
|
6
|
+
static properties = {
|
|
7
|
+
href : { type: String },
|
|
8
|
+
inline: { type: Boolean, reflect: true }
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
constructor() {
|
|
12
|
+
super();
|
|
13
|
+
this.href = '#';
|
|
14
|
+
this.inline = false;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
static styles = [
|
|
18
|
+
...BlogElement.styles,
|
|
19
|
+
css`
|
|
20
|
+
:host([inline]) {
|
|
21
|
+
display: inline;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
a {
|
|
25
|
+
font-family: var(--typography-fontFamily-sans);
|
|
26
|
+
font-size: var(--typography-link-standalone-fontSize);
|
|
27
|
+
font-weight: var(--typography-link-standalone-fontWeight);
|
|
28
|
+
line-height: var(--typography-link-standalone-lineHeight);
|
|
29
|
+
color: var(--color-text-link);
|
|
30
|
+
text-decoration: var(
|
|
31
|
+
--typography-link-standalone-textDecoration-default
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
:host([inline]) a {
|
|
36
|
+
font-family: inherit;
|
|
37
|
+
font-size: inherit;
|
|
38
|
+
font-weight: inherit;
|
|
39
|
+
line-height: inherit;
|
|
40
|
+
font-style: inherit;
|
|
41
|
+
color: var(--color-text-link);
|
|
42
|
+
text-decoration: var(--typography-link-default-textDecoration-default);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
a:hover {
|
|
46
|
+
color: var(--color-text-link-hover);
|
|
47
|
+
text-decoration: var(
|
|
48
|
+
--typography-link-standalone-textDecoration-hover
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
:host([inline]) a:hover {
|
|
53
|
+
text-decoration: var(--typography-link-default-textDecoration-hover);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
a:focus-visible {
|
|
57
|
+
outline: var(--focus-outline-traditional);
|
|
58
|
+
outline-offset: var(--focus-outline-offset);
|
|
59
|
+
}
|
|
60
|
+
`
|
|
61
|
+
];
|
|
62
|
+
|
|
63
|
+
render() {
|
|
64
|
+
return html`<a href=${this.href} part="link"><slot></slot></a>`;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
customElements.define('blog-link', BlogLink);
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { css, html } from 'lit';
|
|
2
|
+
import { BlogElement } from '../base/blog-element.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Meta line atom — author, date, reading time, updated, or separator.
|
|
6
|
+
* @attr {'author'|'date'|'reading-time'|'updated'|'separator'} type
|
|
7
|
+
* @attr {string} value — Text content (ignored for separator)
|
|
8
|
+
* @attr {string} datetime — ISO date when type="date" or type="updated"
|
|
9
|
+
*/
|
|
10
|
+
export class BlogMeta extends BlogElement {
|
|
11
|
+
static properties = {
|
|
12
|
+
type : { type: String, reflect: true },
|
|
13
|
+
value : { type: String },
|
|
14
|
+
datetime: { type: String }
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
constructor() {
|
|
18
|
+
super();
|
|
19
|
+
this.type = 'author';
|
|
20
|
+
this.value = '';
|
|
21
|
+
this.datetime = '';
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
static styles = [
|
|
25
|
+
...BlogElement.styles,
|
|
26
|
+
css`
|
|
27
|
+
:host {
|
|
28
|
+
display: inline;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
.meta {
|
|
32
|
+
font-family: var(--typography-fontFamily-sans);
|
|
33
|
+
font-size: var(--blog-byline-fontSize);
|
|
34
|
+
line-height: var(--typography-lineHeight-normal);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
:host([type='author']) .meta {
|
|
38
|
+
font-weight: var(--typography-fontWeight-medium);
|
|
39
|
+
color: var(--color-text-primary);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
:host([type='date']) .meta {
|
|
43
|
+
font-weight: var(--blog-meta-fontWeight);
|
|
44
|
+
color: var(--blog-date-color);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
:host([type='reading-time']) .meta {
|
|
48
|
+
font-weight: var(--blog-meta-fontWeight);
|
|
49
|
+
color: var(--blog-reading-time-color);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
:host([type='updated']) .meta {
|
|
53
|
+
font-weight: var(--blog-meta-fontWeight);
|
|
54
|
+
color: var(--blog-date-color);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
:host([type='separator']) .meta {
|
|
58
|
+
font-size: var(--blog-meta-separator-fontSize);
|
|
59
|
+
font-weight: var(--blog-meta-separator-fontWeight);
|
|
60
|
+
color: var(--blog-meta-separator-color);
|
|
61
|
+
user-select: none;
|
|
62
|
+
}
|
|
63
|
+
`
|
|
64
|
+
];
|
|
65
|
+
|
|
66
|
+
render() {
|
|
67
|
+
if (this.type === 'separator') {
|
|
68
|
+
return html`<span class="meta" aria-hidden="true">·</span>`;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (this.type === 'date') {
|
|
72
|
+
return html`<time class="meta" datetime=${this.datetime || undefined}
|
|
73
|
+
>${this.value}</time
|
|
74
|
+
>`;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (this.type === 'updated') {
|
|
78
|
+
return html`<time class="meta" datetime=${this.datetime || undefined}
|
|
79
|
+
>Updated ${this.value}</time
|
|
80
|
+
>`;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return html`<span class="meta">${this.value}</span>`;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
customElements.define('blog-meta', BlogMeta);
|