@repobit/dex-system-design 0.23.95 → 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.
Files changed (84) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/package.json +4 -4
  3. package/src/blog/assets/articles/fbi-crypto-scam-1.jpeg +0 -0
  4. package/src/blog/assets/articles/fbi-crypto-scam-cover.png +0 -0
  5. package/src/blog/assets/fonts/IBMPlexSans-Bold.woff2 +0 -0
  6. package/src/blog/assets/fonts/IBMPlexSans-BoldItalic.woff2 +0 -0
  7. package/src/blog/assets/fonts/IBMPlexSans-Italic.woff2 +0 -0
  8. package/src/blog/assets/fonts/IBMPlexSans-Light.woff2 +0 -0
  9. package/src/blog/assets/fonts/IBMPlexSans-LightItalic.woff2 +0 -0
  10. package/src/blog/assets/fonts/IBMPlexSans-Medium.woff2 +0 -0
  11. package/src/blog/assets/fonts/IBMPlexSans-MediumItalic.woff2 +0 -0
  12. package/src/blog/assets/fonts/IBMPlexSans-Regular.woff2 +0 -0
  13. package/src/blog/assets/fonts/IBMPlexSans-SemiBold.woff2 +0 -0
  14. package/src/blog/assets/fonts/IBMPlexSans-SemiBoldItalic.woff2 +0 -0
  15. package/src/blog/assets/fonts/IBMPlexSansCondensed-Bold.woff2 +0 -0
  16. package/src/blog/assets/fonts/IBMPlexSansCondensed-BoldItalic.woff2 +0 -0
  17. package/src/blog/assets/fonts/IBMPlexSansCondensed-Italic.woff2 +0 -0
  18. package/src/blog/assets/fonts/IBMPlexSansCondensed-Light.woff2 +0 -0
  19. package/src/blog/assets/fonts/IBMPlexSansCondensed-LightItalic.woff2 +0 -0
  20. package/src/blog/assets/fonts/IBMPlexSansCondensed-Medium.woff2 +0 -0
  21. package/src/blog/assets/fonts/IBMPlexSansCondensed-MediumItalic.woff2 +0 -0
  22. package/src/blog/assets/fonts/IBMPlexSansCondensed-Regular.woff2 +0 -0
  23. package/src/blog/assets/fonts/IBMPlexSansCondensed-SemiBold.woff2 +0 -0
  24. package/src/blog/assets/fonts/IBMPlexSansCondensed-SemiBoldItalic.woff2 +0 -0
  25. package/src/blog/assets/global-scam-intelligence-report-2026.png +0 -0
  26. package/src/blog/assets/psd-4.jpg +0 -0
  27. package/src/blog/src/articles/FBI warns of crypto investment scam that sends couriers to collect victims' cash-editedAB.docx +0 -0
  28. package/src/blog/src/articles/FBI-crypto-scam-courier-cash.png +0 -0
  29. package/src/blog/src/articles/fbi-crypto-scam.js +324 -0
  30. package/src/blog/src/articles/~$I warns of crypto investment scam that sends couriers to collect victims' cash-editedAB.docx +0 -0
  31. package/src/blog/src/atoms/blog-abstract.js +51 -0
  32. package/src/blog/src/atoms/blog-atoms.stories.js +519 -0
  33. package/src/blog/src/atoms/blog-authors.js +70 -0
  34. package/src/blog/src/atoms/blog-avatar.js +44 -0
  35. package/src/blog/src/atoms/blog-blockquote.js +92 -0
  36. package/src/blog/src/atoms/blog-caption.js +31 -0
  37. package/src/blog/src/atoms/blog-code-block.js +89 -0
  38. package/src/blog/src/atoms/blog-dek.js +30 -0
  39. package/src/blog/src/atoms/blog-featured-image.js +65 -0
  40. package/src/blog/src/atoms/blog-heading.js +63 -0
  41. package/src/blog/src/atoms/blog-image.js +50 -0
  42. package/src/blog/src/atoms/blog-key-takeaways.js +101 -0
  43. package/src/blog/src/atoms/blog-link.js +68 -0
  44. package/src/blog/src/atoms/blog-meta.js +87 -0
  45. package/src/blog/src/atoms/blog-most-popular.js +146 -0
  46. package/src/blog/src/atoms/blog-newsletter-signup.js +342 -0
  47. package/src/blog/src/atoms/blog-paragraph.js +61 -0
  48. package/src/blog/src/atoms/blog-product-upsell.js +168 -0
  49. package/src/blog/src/atoms/blog-related-articles.js +326 -0
  50. package/src/blog/src/atoms/blog-related-label.js +30 -0
  51. package/src/blog/src/atoms/blog-report-promo.js +250 -0
  52. package/src/blog/src/atoms/blog-standout.js +78 -0
  53. package/src/blog/src/atoms/blog-subhead.js +32 -0
  54. package/src/blog/src/atoms/blog-table-of-contents.js +447 -0
  55. package/src/blog/src/atoms/blog-tag.js +86 -0
  56. package/src/blog/src/atoms/blog-title.js +31 -0
  57. package/src/blog/src/base/blog-element.js +10 -0
  58. package/src/blog/src/breakpoints.js +23 -0
  59. package/src/blog/src/content/blog-article.js +736 -0
  60. package/src/blog/src/content/blog-atoms-showcase.js +401 -0
  61. package/src/blog/src/content/blog-page-layout.js +73 -0
  62. package/src/blog/src/content/blog-pages.stories.js +101 -0
  63. package/src/blog/src/content/render-inline-content.js +18 -0
  64. package/src/blog/src/data/popular-articles.js +43 -0
  65. package/src/blog/src/data/related-articles.js +58 -0
  66. package/src/blog/src/index.js +104 -0
  67. package/src/blog/src/lib/article-toc.js +206 -0
  68. package/src/blog/src/lib/load-prism.js +23 -0
  69. package/src/blog/src/shell/blog-shell.js +31 -0
  70. package/src/blog/src/styles/prism-theme.js +65 -0
  71. package/src/blog/src/tokens/blog.js +406 -0
  72. package/src/blog/src/tokens/core.js +811 -0
  73. package/src/blog/src/tokens/fonts.css +241 -0
  74. package/src/blog/src/tokens/index.js +27 -0
  75. package/src/blog/src/tokens/legacy.js +58 -0
  76. package/src/blog/tokens/blog.js +406 -0
  77. package/src/blog/tokens/core.js +811 -0
  78. package/src/blog/tokens/fonts.css +241 -0
  79. package/src/blog/tokens/index.js +27 -0
  80. package/src/blog/tokens/legacy.js +58 -0
  81. package/src/blog/tokens.css +1512 -0
  82. package/src/blog/tokens.js +1 -0
  83. package/src/blog/vendor/prism/prism.css +120 -0
  84. 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">&ldquo;</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">&mdash; ${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);