@beforesemicolon/builder 1.8.1 → 1.8.13
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/README.md +839 -142
- package/dist/cjs/docs/markdown-layout/index.js +1 -0
- package/dist/cjs/docs/markdown-layout/marked-extension.js +1 -0
- package/dist/cjs/docs/markdown-layout/parser.js +7 -0
- package/dist/cjs/docs/markdown-layout/renderer.js +1 -0
- package/dist/cjs/docs/markdown-layout/types.js +1 -0
- package/dist/cjs/docs/run.js +76 -1
- package/dist/cjs/docs/templates/fading-citrus/README.md +770 -0
- package/dist/cjs/docs/templates/fading-citrus/assets/logo.svg +8 -0
- package/dist/cjs/docs/templates/fading-citrus/layouts/_code-snippet.js +53 -0
- package/dist/cjs/docs/templates/fading-citrus/layouts/_footer.js +7 -4
- package/dist/cjs/docs/templates/fading-citrus/layouts/_header.js +4 -4
- package/dist/cjs/docs/templates/fading-citrus/layouts/_layout-utils.js +89 -0
- package/dist/cjs/docs/templates/fading-citrus/layouts/document.js +7 -4
- package/dist/cjs/docs/templates/fading-citrus/layouts/landing-cta.js +30 -0
- package/dist/cjs/docs/templates/fading-citrus/layouts/landing-ecosystem.js +43 -0
- package/dist/cjs/docs/templates/fading-citrus/layouts/landing-features.js +25 -0
- package/dist/cjs/docs/templates/fading-citrus/layouts/landing-hero.js +81 -0
- package/dist/cjs/docs/templates/fading-citrus/layouts/landing-install.js +116 -0
- package/dist/cjs/docs/templates/fading-citrus/layouts/landing-showcase.js +125 -0
- package/dist/cjs/docs/templates/fading-citrus/layouts/landing.js +8 -618
- package/dist/cjs/docs/templates/fading-citrus/stylesheets/common.css +6 -4
- package/dist/cjs/docs/templates/fading-citrus/template.config.js +100 -1
- package/dist/cjs/docs/types.js +1 -1
- package/dist/esm/docs/markdown-layout/index.js +1 -0
- package/dist/esm/docs/markdown-layout/marked-extension.js +1 -0
- package/dist/esm/docs/markdown-layout/parser.js +7 -0
- package/dist/esm/docs/markdown-layout/renderer.js +1 -0
- package/dist/esm/docs/markdown-layout/types.js +0 -0
- package/dist/esm/docs/run.js +76 -1
- package/dist/esm/docs/templates/fading-citrus/README.md +770 -0
- package/dist/esm/docs/templates/fading-citrus/assets/logo.svg +8 -0
- package/dist/esm/docs/templates/fading-citrus/layouts/_code-snippet.js +53 -0
- package/dist/esm/docs/templates/fading-citrus/layouts/_footer.js +7 -4
- package/dist/esm/docs/templates/fading-citrus/layouts/_header.js +4 -4
- package/dist/esm/docs/templates/fading-citrus/layouts/_layout-utils.js +89 -0
- package/dist/esm/docs/templates/fading-citrus/layouts/document.js +7 -4
- package/dist/esm/docs/templates/fading-citrus/layouts/landing-cta.js +30 -0
- package/dist/esm/docs/templates/fading-citrus/layouts/landing-ecosystem.js +43 -0
- package/dist/esm/docs/templates/fading-citrus/layouts/landing-features.js +25 -0
- package/dist/esm/docs/templates/fading-citrus/layouts/landing-hero.js +81 -0
- package/dist/esm/docs/templates/fading-citrus/layouts/landing-install.js +116 -0
- package/dist/esm/docs/templates/fading-citrus/layouts/landing-showcase.js +125 -0
- package/dist/esm/docs/templates/fading-citrus/layouts/landing.js +8 -618
- package/dist/esm/docs/templates/fading-citrus/stylesheets/common.css +6 -4
- package/dist/esm/docs/templates/fading-citrus/template.config.js +100 -1
- package/dist/types/docs/markdown-layout/index.d.ts +4 -0
- package/dist/types/docs/markdown-layout/marked-extension.d.ts +8 -0
- package/dist/types/docs/markdown-layout/parser.d.ts +12 -0
- package/dist/types/docs/markdown-layout/renderer.d.ts +3 -0
- package/dist/types/docs/markdown-layout/types.d.ts +35 -0
- package/dist/types/docs/run.d.ts +12 -1
- package/dist/types/docs/types.d.ts +23 -0
- package/package.json +3 -2
- package/dist/cjs/docs/templates/fading-citrus/layouts/_copy-code.js +0 -50
- package/dist/cjs/docs/templates/fading-citrus/layouts/_logo.js +0 -31
- package/dist/esm/docs/templates/fading-citrus/layouts/_copy-code.js +0 -50
- package/dist/esm/docs/templates/fading-citrus/layouts/_logo.js +0 -31
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 220 60" role="img" aria-label="Fading Citrus">
|
|
2
|
+
<g fill="none" fill-rule="evenodd">
|
|
3
|
+
<circle cx="30" cy="30" r="23" fill="#f97316" />
|
|
4
|
+
<path d="M30 7a23 23 0 0 1 23 23H30z" fill="#22d3ee" opacity=".9" />
|
|
5
|
+
<path d="M30 7a23 23 0 0 0 0 46 23 23 0 0 1 0-46z" fill="#fff7ed" opacity=".35" />
|
|
6
|
+
<text x="68" y="37" fill="#f8fafc" font-family="Inter, Arial, sans-serif" font-size="22" font-weight="700">Docs</text>
|
|
7
|
+
</g>
|
|
8
|
+
</svg>
|
|
@@ -44,6 +44,59 @@ export const highlightLine = (line) => {
|
|
|
44
44
|
return tokens.join('')
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
+
export const renderCodeCopyScript = () => `
|
|
48
|
+
<script type="application/javascript">
|
|
49
|
+
(() => {
|
|
50
|
+
if (window.__fadingCitrusCodeCopyReady) return
|
|
51
|
+
window.__fadingCitrusCodeCopyReady = true
|
|
52
|
+
|
|
53
|
+
const showCopyButton = (snippet) => {
|
|
54
|
+
const copyBtn = snippet.querySelector('.code-copy-btn')
|
|
55
|
+
const hasCode = snippet.dataset.code || snippet.querySelector('code') || snippet.querySelector('.content')
|
|
56
|
+
if (copyBtn && hasCode) copyBtn.style.visibility = 'visible'
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
document.querySelectorAll('.code-snippet').forEach(showCopyButton)
|
|
60
|
+
|
|
61
|
+
document.addEventListener('click', (event) => {
|
|
62
|
+
const copyBtn = event.target.closest('.code-copy-btn')
|
|
63
|
+
if (!copyBtn) return
|
|
64
|
+
|
|
65
|
+
const snippet = copyBtn.closest('.code-snippet')
|
|
66
|
+
if (!snippet) return
|
|
67
|
+
|
|
68
|
+
let code = snippet.dataset.code ?? ''
|
|
69
|
+
|
|
70
|
+
if (!code) {
|
|
71
|
+
const codeEl = snippet.querySelector('code')
|
|
72
|
+
if (codeEl) {
|
|
73
|
+
const clone = codeEl.cloneNode(true)
|
|
74
|
+
clone.querySelectorAll('.line-number').forEach((el) => el.remove())
|
|
75
|
+
code = clone.textContent ?? ''
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (!code) {
|
|
80
|
+
code = snippet.querySelector('.content')?.textContent ?? ''
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (!code.trim().length) return
|
|
84
|
+
|
|
85
|
+
clearTimeout(copyBtn._timer)
|
|
86
|
+
navigator.clipboard.writeText(code.trim())
|
|
87
|
+
copyBtn.textContent = 'copied'
|
|
88
|
+
copyBtn.setAttribute('aria-label', 'Code copied')
|
|
89
|
+
copyBtn.classList.add('copied')
|
|
90
|
+
copyBtn._timer = setTimeout(() => {
|
|
91
|
+
copyBtn.textContent = 'copy'
|
|
92
|
+
copyBtn.setAttribute('aria-label', 'Copy code')
|
|
93
|
+
copyBtn.classList.remove('copied')
|
|
94
|
+
}, 1000)
|
|
95
|
+
})
|
|
96
|
+
})()
|
|
97
|
+
</script>
|
|
98
|
+
`
|
|
99
|
+
|
|
47
100
|
export const renderCodeBlock = (filename, code, lang = 'javascript') => {
|
|
48
101
|
const lines = code
|
|
49
102
|
.replace(/\n$/, '')
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import logo from './_logo.js'
|
|
2
|
-
|
|
3
1
|
const social = [
|
|
4
2
|
{
|
|
5
3
|
link: 'https://medium.com/before-semicolon',
|
|
@@ -33,11 +31,15 @@ const social = [
|
|
|
33
31
|
},
|
|
34
32
|
]
|
|
35
33
|
|
|
36
|
-
export default () =>
|
|
34
|
+
export default (props = {}) => {
|
|
35
|
+
const siteName =
|
|
36
|
+
props.projectMeta?.name || props.name || props.title || 'Documentation'
|
|
37
|
+
|
|
38
|
+
return `
|
|
37
39
|
<footer class="site-footer">
|
|
38
40
|
<div class="footer-inner wrapper">
|
|
39
41
|
<div class="footer-brand">
|
|
40
|
-
${logo
|
|
42
|
+
<img class="site-logo" src="/assets/logo.svg" alt="${siteName} logo" width="100" height="30" />
|
|
41
43
|
<p>A reactive HTML templating system for building dynamic UIs with pure JavaScript. No build step. No virtual DOM.</p>
|
|
42
44
|
</div>
|
|
43
45
|
<ul class="footer-links">
|
|
@@ -70,3 +72,4 @@ export default () => `
|
|
|
70
72
|
</div>
|
|
71
73
|
</footer>
|
|
72
74
|
`
|
|
75
|
+
}
|
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
import logo from './_logo.js'
|
|
2
|
-
|
|
3
1
|
export default (props = {}) => {
|
|
4
2
|
const isDocs = props.path?.startsWith('/documentation')
|
|
3
|
+
const siteName =
|
|
4
|
+
props.projectMeta?.name || props.name || props.title || 'Documentation'
|
|
5
5
|
|
|
6
6
|
return `
|
|
7
7
|
<header class="site-header">
|
|
8
8
|
<div class="header-inner wrapper">
|
|
9
9
|
<h1>
|
|
10
|
-
<a href="/" aria-label="
|
|
11
|
-
${logo
|
|
10
|
+
<a href="/" aria-label="${siteName} home">
|
|
11
|
+
<img class="site-logo" src="/assets/logo.svg" alt="${siteName} logo" width="90" height="27" />
|
|
12
12
|
</a>
|
|
13
13
|
</h1>
|
|
14
14
|
${
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
export const escapeHTML = (value = '') =>
|
|
2
|
+
String(value)
|
|
3
|
+
.replace(/&/g, '&')
|
|
4
|
+
.replace(/</g, '<')
|
|
5
|
+
.replace(/>/g, '>')
|
|
6
|
+
.replace(/"/g, '"')
|
|
7
|
+
.replace(/'/g, ''')
|
|
8
|
+
|
|
9
|
+
export const partByName = (parts, name) =>
|
|
10
|
+
parts.find((part) => part.name === name)
|
|
11
|
+
|
|
12
|
+
export const option = (value, fallback = '') =>
|
|
13
|
+
value === undefined || value === true ? fallback : String(value)
|
|
14
|
+
|
|
15
|
+
export const optionText = (value, fallback = '') =>
|
|
16
|
+
option(value, fallback).replace(/"/g, '"')
|
|
17
|
+
|
|
18
|
+
export const codeFromPart = (part = {}) => {
|
|
19
|
+
const match = String(part.body || '').match(
|
|
20
|
+
/(?:```|~~~)([^\n]*)\n([\s\S]*?)\n(?:```|~~~)/
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
if (!match) return ''
|
|
24
|
+
|
|
25
|
+
return match[2]
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export const withFirstParagraphClass = (html = '', className = '') =>
|
|
29
|
+
html.replace(/<p>/, `<p class="${className}">`)
|
|
30
|
+
|
|
31
|
+
export const statContent = (html = '') => {
|
|
32
|
+
const value = html.match(/<h2[^>]*>([\s\S]*?)<\/h2>/)?.[1] || ''
|
|
33
|
+
const label = html.match(/<p[^>]*>([\s\S]*?)<\/p>/)?.[1] || ''
|
|
34
|
+
|
|
35
|
+
return `
|
|
36
|
+
<span class="stat-value text-gradient-primary">${value}</span>
|
|
37
|
+
<span class="stat-label">${label}</span>
|
|
38
|
+
`
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export const stripHeadingLinks = (html = '') =>
|
|
42
|
+
html.replace(
|
|
43
|
+
/<h([1-6])([^>]*)><a href="[^"]*">([\s\S]*?)<\/a><\/h\1>/g,
|
|
44
|
+
'<h$1$2>$3</h$1>'
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
export const sectionHeader = (html = '') =>
|
|
48
|
+
stripHeadingLinks(html).replace(
|
|
49
|
+
/<p><code>([\s\S]*?)<\/code><\/p>/,
|
|
50
|
+
'<span class="section-tag font-mono">$1</span>'
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
export const groupedSectionHeader = (html = '') => {
|
|
54
|
+
const normalized = sectionHeader(html)
|
|
55
|
+
const match = normalized.match(
|
|
56
|
+
/^(\s*<span class="section-tag font-mono">[\s\S]*?<\/span>)(\s*<h2[^>]*>[\s\S]*?<\/h2>)([\s\S]*)$/
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
if (!match) return normalized
|
|
60
|
+
|
|
61
|
+
return `<div>${match[1]}${match[2]}</div>${match[3]}`
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export const icon = (name) => {
|
|
65
|
+
const icons = {
|
|
66
|
+
arrowRight:
|
|
67
|
+
'<svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12h14"/><path d="m12 5 7 7-7 7"/></svg>',
|
|
68
|
+
arrowUpRight:
|
|
69
|
+
'<svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M7 17 17 7"/><path d="M7 7h10v10"/></svg>',
|
|
70
|
+
book: '<svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M2 3h6a4 4 0 0 1 4 4v14a3 3 0 0 0-3-3H2z"/><path d="M22 3h-6a4 4 0 0 0-4 4v14a3 3 0 0 1 3-3h7z"/></svg>',
|
|
71
|
+
terminal:
|
|
72
|
+
'<svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="4 17 10 11 4 5"/><line x1="12" y1="19" x2="20" y2="19"/></svg>',
|
|
73
|
+
sparkles:
|
|
74
|
+
'<svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="sparkles-icon"><path d="m12 3-1.912 5.813a2 2 0 0 1-1.275 1.275L3 12l5.813 1.912a2 2 0 0 1 1.275 1.275L12 21l1.912-5.813a2 2 0 0 1 1.275-1.275L21 12l-5.813-1.912a2 2 0 0 1-1.275-1.275Z"/><path d="m5 3 1 2.5L8.5 6 6 7 5 9.5 4 7 1.5 6 4 5Z"/><path d="m19 17 1 2.5 2.5.5-2.5 1-1 2.5-1-2.5-2.5-1 2.5-1Z"/></svg>',
|
|
75
|
+
webComponents:
|
|
76
|
+
'<svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M5.5 8.5 9 12l-3.5 3.5L2 12l3.5-3.5Z"/><path d="m12 2 3.5 3.5L12 9 8.5 5.5 12 2Z"/><path d="M18.5 8.5 22 12l-3.5 3.5L15 12l3.5-3.5Z"/><path d="m12 15 3.5 3.5-3.5 3.5-3.5-3.5 3.5-3.5Z"/></svg>',
|
|
77
|
+
router: '<svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="6" cy="19" r="3"/><circle cx="18" cy="5" r="3"/><path d="M9 19h8.5a3.5 3.5 0 0 0 3.5-3.5V8.5a3.5 3.5 0 0 0-3.5-3.5H9"/></svg>',
|
|
78
|
+
reactive:
|
|
79
|
+
'<svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M13 2L3 14h9l-1 8 10-12h-9l1-8z"/></svg>',
|
|
80
|
+
tiny: '<svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20.24 12.24a6 6 0 0 0-8.49-8.49L5 10.5V19h8.5z"/><line x1="16" y1="8" x2="2" y2="22"/><line x1="17.5" y1="15" x2="9" y2="15"/></svg>',
|
|
81
|
+
standards:
|
|
82
|
+
'<svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M8.3 10a3.5 3.5 0 0 1 0-7h7.4a3.5 3.5 0 0 1 0 7H8.3Z"/><path d="M12 14a3 3 0 0 1 3-3h3.5a3 3 0 0 1 3 3v3.5a3 3 0 0 1-3 3H15a3 3 0 0 1-3-3V14Z"/><path d="M2 14.5a3.5 3.5 0 0 1 7 0v2.8a3.5 3.5 0 0 1-7 0v-2.8Z"/></svg>',
|
|
83
|
+
plug: '<svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 22v-5"/><path d="M9 8V2"/><path d="M15 8V2"/><path d="M18 8H6v5a4 4 0 0 0 4 4h4a4 4 0 0 0 4-4Z"/></svg>',
|
|
84
|
+
surgical:
|
|
85
|
+
'<svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m12 14 4-4"/><path d="M3.34 19a10 10 0 1 1 17.32 0"/></svg>',
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return icons[name] || icons.reactive
|
|
89
|
+
}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import meta from './_head-meta.js'
|
|
2
2
|
import header from './_header.js'
|
|
3
3
|
import footer from './_footer.js'
|
|
4
|
-
import copyCode from './_copy-code.js'
|
|
5
4
|
|
|
6
5
|
const githubDocsPath =
|
|
7
6
|
'https://github.com/beforesemicolon/markup/tree/main/docs'
|
|
@@ -47,6 +46,11 @@ export default (props) => {
|
|
|
47
46
|
<head>
|
|
48
47
|
${meta(props)}
|
|
49
48
|
<link rel="stylesheet" href="/stylesheets/documentation.css?v=20260621exact">
|
|
49
|
+
${
|
|
50
|
+
props.themeStylesheet
|
|
51
|
+
? `<link rel="stylesheet" href="${props.themeStylesheet}">`
|
|
52
|
+
: ''
|
|
53
|
+
}
|
|
50
54
|
</head>
|
|
51
55
|
<body>
|
|
52
56
|
${header(props)}
|
|
@@ -82,9 +86,8 @@ export default (props) => {
|
|
|
82
86
|
</aside>
|
|
83
87
|
</main>
|
|
84
88
|
|
|
85
|
-
${footer()}
|
|
86
|
-
|
|
87
|
-
${copyCode()}
|
|
89
|
+
${footer(props)}
|
|
90
|
+
${props.scripts?.join('') || ''}
|
|
88
91
|
|
|
89
92
|
</body>
|
|
90
93
|
</html>
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { icon, partByName } from './_layout-utils.js'
|
|
2
|
+
|
|
3
|
+
export default ({ parts, options }) => {
|
|
4
|
+
const copy = partByName(parts, 'copy') || parts[0]
|
|
5
|
+
const title = options.title || ''
|
|
6
|
+
const title2 = options.title2 || ''
|
|
7
|
+
|
|
8
|
+
return `
|
|
9
|
+
<section class="cta-section wrapper">
|
|
10
|
+
<div class="cta-card">
|
|
11
|
+
<div class="cta-glow"></div>
|
|
12
|
+
<h2>
|
|
13
|
+
<span class="text-gradient">${title}</span>
|
|
14
|
+
<span class="text-gradient-primary">${title2}</span>
|
|
15
|
+
</h2>
|
|
16
|
+
${copy?.html || ''}
|
|
17
|
+
<div class="cta-buttons">
|
|
18
|
+
<a href="/documentation/get-started" class="btn-primary">
|
|
19
|
+
<span>Get Started</span>
|
|
20
|
+
${icon('arrowRight')}
|
|
21
|
+
</a>
|
|
22
|
+
<a href="/documentation/index.html?v=20260621exact" class="btn-outline">
|
|
23
|
+
${icon('book')}
|
|
24
|
+
<span>Read the Docs</span>
|
|
25
|
+
</a>
|
|
26
|
+
</div>
|
|
27
|
+
</div>
|
|
28
|
+
</section>
|
|
29
|
+
`
|
|
30
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import {
|
|
2
|
+
groupedSectionHeader,
|
|
3
|
+
icon,
|
|
4
|
+
option,
|
|
5
|
+
partByName,
|
|
6
|
+
} from './_layout-utils.js'
|
|
7
|
+
|
|
8
|
+
export default ({ parts }) => {
|
|
9
|
+
const header = partByName(parts, 'header') || parts[0]
|
|
10
|
+
const cards = parts.filter((part) => part.name === 'product')
|
|
11
|
+
|
|
12
|
+
return `
|
|
13
|
+
<section class="products-section wrapper">
|
|
14
|
+
<div class="section-header">${groupedSectionHeader(header?.html || '')}</div>
|
|
15
|
+
<div class="products-grid">
|
|
16
|
+
${cards
|
|
17
|
+
.map(
|
|
18
|
+
(card) => `
|
|
19
|
+
<div class="product-card card-${option(card.options.color, 'orange')}">
|
|
20
|
+
<div class="product-glow"></div>
|
|
21
|
+
<a href="${option(card.options.href, '#')}" target="_blank" rel="noreferrer" class="product-head">
|
|
22
|
+
<div class="product-title-row">
|
|
23
|
+
<div class="icon-container">${icon(option(card.options.icon, 'webComponents'))}</div>
|
|
24
|
+
<div>
|
|
25
|
+
<h3>${option(card.options.title, card.name || '')}</h3>
|
|
26
|
+
<span class="package-name font-mono">${option(card.options.package, '')}</span>
|
|
27
|
+
</div>
|
|
28
|
+
</div>
|
|
29
|
+
<span class="product-arrow">${icon('arrowUpRight')}</span>
|
|
30
|
+
</a>
|
|
31
|
+
${card.html}
|
|
32
|
+
<a href="${option(card.options.href, '#')}" target="_blank" rel="noreferrer" class="product-link">
|
|
33
|
+
<span>Read the docs</span>
|
|
34
|
+
${icon('arrowUpRight')}
|
|
35
|
+
</a>
|
|
36
|
+
</div>
|
|
37
|
+
`
|
|
38
|
+
)
|
|
39
|
+
.join('')}
|
|
40
|
+
</div>
|
|
41
|
+
</section>
|
|
42
|
+
`
|
|
43
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { icon, option, partByName, sectionHeader } from './_layout-utils.js'
|
|
2
|
+
|
|
3
|
+
export default ({ parts }) => {
|
|
4
|
+
const header = partByName(parts, 'header') || parts[0]
|
|
5
|
+
const features = parts.filter((part) => part.name === 'feature')
|
|
6
|
+
|
|
7
|
+
return `
|
|
8
|
+
<section id="features" class="features-section wrapper">
|
|
9
|
+
<div class="section-header">${sectionHeader(header?.html || '')}</div>
|
|
10
|
+
<div class="features-grid">
|
|
11
|
+
${features
|
|
12
|
+
.map(
|
|
13
|
+
(feature) => `
|
|
14
|
+
<div class="feature-card">
|
|
15
|
+
<div class="feature-border"></div>
|
|
16
|
+
<div class="icon-box">${icon(option(feature.options.icon, 'reactive'))}</div>
|
|
17
|
+
${feature.html}
|
|
18
|
+
</div>
|
|
19
|
+
`
|
|
20
|
+
)
|
|
21
|
+
.join('')}
|
|
22
|
+
</div>
|
|
23
|
+
</section>
|
|
24
|
+
`
|
|
25
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { renderCodeBlock } from './_code-snippet.js'
|
|
2
|
+
import {
|
|
3
|
+
codeFromPart,
|
|
4
|
+
icon,
|
|
5
|
+
option,
|
|
6
|
+
partByName,
|
|
7
|
+
statContent,
|
|
8
|
+
withFirstParagraphClass,
|
|
9
|
+
} from './_layout-utils.js'
|
|
10
|
+
|
|
11
|
+
export default ({ parts, options }) => {
|
|
12
|
+
const copy = partByName(parts, 'copy') || parts[0]
|
|
13
|
+
const code = partByName(parts, 'code') || parts[1]
|
|
14
|
+
const stats = parts.filter((part) => part.name === 'stat')
|
|
15
|
+
const version = option(options.version)
|
|
16
|
+
const title = option(options.title, '')
|
|
17
|
+
const title2 = option(options.title2, '')
|
|
18
|
+
const primaryHref = option(
|
|
19
|
+
options.primaryHref,
|
|
20
|
+
'/documentation/get-started'
|
|
21
|
+
)
|
|
22
|
+
const primaryLabel = option(options.primaryLabel, 'Get Started')
|
|
23
|
+
const secondaryLabel = option(
|
|
24
|
+
options.secondaryLabel,
|
|
25
|
+
'npm i @beforesemicolon/markup'
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
return `
|
|
29
|
+
<section class="hero-section relative overflow-hidden">
|
|
30
|
+
<div class="absolute inset-0 bg-grid pointer-events-none"></div>
|
|
31
|
+
<div class="absolute inset-0 hero-glow pointer-events-none animate-pulse-glow"></div>
|
|
32
|
+
<div class="wrapper hero-container animate-float-up">
|
|
33
|
+
<div class="hero-content">
|
|
34
|
+
${
|
|
35
|
+
version
|
|
36
|
+
? `<a href="https://github.com/beforesemicolon/markup" target="_blank" rel="noopener" class="version-badge">
|
|
37
|
+
${icon('sparkles')}
|
|
38
|
+
<span>${version}</span>
|
|
39
|
+
${icon('arrowRight')}
|
|
40
|
+
</a>`
|
|
41
|
+
: ''
|
|
42
|
+
}
|
|
43
|
+
<h1 class="hero-title">
|
|
44
|
+
<span class="text-gradient">${title}</span><br/>
|
|
45
|
+
<span class="text-gradient-primary">${title2}</span>
|
|
46
|
+
</h1>
|
|
47
|
+
${withFirstParagraphClass(copy?.html || '', 'hero-subtitle')}
|
|
48
|
+
<div class="hero-actions">
|
|
49
|
+
<a href="${primaryHref}" class="btn-primary">
|
|
50
|
+
<span>${primaryLabel}</span>
|
|
51
|
+
${icon('arrowRight')}
|
|
52
|
+
</a>
|
|
53
|
+
<a href="#install" class="btn-secondary font-mono">${secondaryLabel}</a>
|
|
54
|
+
</div>
|
|
55
|
+
${
|
|
56
|
+
stats.length
|
|
57
|
+
? `<div class="hero-stats">${stats
|
|
58
|
+
.map(
|
|
59
|
+
(stat, index) => `
|
|
60
|
+
${index ? '<div class="stat-divider"></div>' : ''}
|
|
61
|
+
<div class="stat-item">${statContent(stat.html)}</div>
|
|
62
|
+
`
|
|
63
|
+
)
|
|
64
|
+
.join('')}</div>`
|
|
65
|
+
: ''
|
|
66
|
+
}
|
|
67
|
+
</div>
|
|
68
|
+
${
|
|
69
|
+
code
|
|
70
|
+
? `<div class="hero-code-panel">
|
|
71
|
+
<div class="glow-container">
|
|
72
|
+
<div class="absolute inset-0 bg-gradient-to-br from-primary/20 to-accent/20 blur-3xl rounded-full"></div>
|
|
73
|
+
</div>
|
|
74
|
+
${renderCodeBlock(option(code.options.filename, ''), codeFromPart(code), option(code.options.lang, 'javascript'))}
|
|
75
|
+
</div>`
|
|
76
|
+
: ''
|
|
77
|
+
}
|
|
78
|
+
</div>
|
|
79
|
+
</section>
|
|
80
|
+
`
|
|
81
|
+
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import {
|
|
2
|
+
escapeHTML,
|
|
3
|
+
icon,
|
|
4
|
+
option,
|
|
5
|
+
optionText,
|
|
6
|
+
partByName,
|
|
7
|
+
sectionHeader,
|
|
8
|
+
} from './_layout-utils.js'
|
|
9
|
+
|
|
10
|
+
export const renderInstallScript = () => `
|
|
11
|
+
<script type="application/javascript">
|
|
12
|
+
(() => {
|
|
13
|
+
if (window.__fadingCitrusInstallReady) return
|
|
14
|
+
window.__fadingCitrusInstallReady = true
|
|
15
|
+
|
|
16
|
+
document.querySelectorAll('[data-fading-citrus-install]').forEach((install) => {
|
|
17
|
+
const tabTriggers = install.querySelectorAll('.tab-trigger')
|
|
18
|
+
const tabContents = install.querySelectorAll('.tab-content')
|
|
19
|
+
const copyButtons = install.querySelectorAll('.install-copy-btn')
|
|
20
|
+
|
|
21
|
+
tabTriggers.forEach((trigger) => {
|
|
22
|
+
trigger.addEventListener('click', () => {
|
|
23
|
+
const targetTab = trigger.getAttribute('data-tab')
|
|
24
|
+
|
|
25
|
+
tabTriggers.forEach((tab) => {
|
|
26
|
+
tab.classList.remove('active')
|
|
27
|
+
tab.setAttribute('aria-selected', 'false')
|
|
28
|
+
})
|
|
29
|
+
tabContents.forEach((content) => content.classList.remove('active'))
|
|
30
|
+
|
|
31
|
+
trigger.classList.add('active')
|
|
32
|
+
trigger.setAttribute('aria-selected', 'true')
|
|
33
|
+
|
|
34
|
+
const activeContent = install.querySelector('#tab-' + targetTab)
|
|
35
|
+
if (activeContent) activeContent.classList.add('active')
|
|
36
|
+
})
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
copyButtons.forEach((button) => {
|
|
40
|
+
const originalLabel = button.getAttribute('aria-label') || ''
|
|
41
|
+
|
|
42
|
+
button.addEventListener('click', async () => {
|
|
43
|
+
const row = button.closest('.install-code-row')
|
|
44
|
+
const value = row ? row.getAttribute('data-code') : ''
|
|
45
|
+
|
|
46
|
+
try {
|
|
47
|
+
await navigator.clipboard.writeText(value)
|
|
48
|
+
button.classList.add('copied')
|
|
49
|
+
button.querySelector('span').textContent = 'Copied'
|
|
50
|
+
button.setAttribute('aria-label', 'Command copied')
|
|
51
|
+
setTimeout(() => {
|
|
52
|
+
button.classList.remove('copied')
|
|
53
|
+
button.querySelector('span').textContent = 'Copy'
|
|
54
|
+
button.setAttribute('aria-label', originalLabel)
|
|
55
|
+
}, 1500)
|
|
56
|
+
} catch {}
|
|
57
|
+
})
|
|
58
|
+
})
|
|
59
|
+
})
|
|
60
|
+
})()
|
|
61
|
+
</script>
|
|
62
|
+
`
|
|
63
|
+
|
|
64
|
+
export default ({ parts }) => {
|
|
65
|
+
const header = partByName(parts, 'header') || parts[0]
|
|
66
|
+
const tabs = parts.filter((part) => part.name === 'tab')
|
|
67
|
+
|
|
68
|
+
return `
|
|
69
|
+
<section id="install" class="install-section wrapper" data-fading-citrus-install>
|
|
70
|
+
<div class="install-container">
|
|
71
|
+
<div class="section-header center">${sectionHeader(header?.html || '')}</div>
|
|
72
|
+
<div class="tabs-container">
|
|
73
|
+
<div class="tabs-list" role="tablist" aria-label="Installation package manager">
|
|
74
|
+
${tabs
|
|
75
|
+
.map(
|
|
76
|
+
(tab, index) => `
|
|
77
|
+
<button class="tab-trigger${index === 1 ? ' active' : ''}" role="tab" aria-selected="${index === 1}" aria-controls="tab-${option(tab.options.key, tab.options.label)}" id="tab-trigger-${option(tab.options.key, tab.options.label)}" data-tab="${option(tab.options.key, tab.options.label)}">${option(tab.options.label, tab.options.key)}</button>
|
|
78
|
+
`
|
|
79
|
+
)
|
|
80
|
+
.join('')}
|
|
81
|
+
</div>
|
|
82
|
+
<div class="tabs-content-wrapper">
|
|
83
|
+
${tabs
|
|
84
|
+
.map((tab, index) => {
|
|
85
|
+
const key = option(
|
|
86
|
+
tab.options.key,
|
|
87
|
+
tab.options.label
|
|
88
|
+
)
|
|
89
|
+
const command = optionText(
|
|
90
|
+
tab.options.command,
|
|
91
|
+
tab.html.replace(/<[^>]+>/g, '').trim()
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
return `
|
|
95
|
+
<div class="tab-content${index === 1 ? ' active' : ''}" id="tab-${key}" role="tabpanel" tabindex="0" aria-labelledby="tab-trigger-${key}">
|
|
96
|
+
<div class="install-code-row" data-code="${escapeHTML(command)}">
|
|
97
|
+
<div class="install-command">
|
|
98
|
+
${icon('terminal')}
|
|
99
|
+
<code class="font-mono">${escapeHTML(command)}</code>
|
|
100
|
+
</div>
|
|
101
|
+
<button class="install-copy-btn" aria-label="Copy ${option(tab.options.label, key)} install command">
|
|
102
|
+
<svg aria-hidden="true" class="copy-icon" xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="14" height="14" x="8" y="8" rx="2" ry="2"/><path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2"/></svg>
|
|
103
|
+
<svg aria-hidden="true" class="check-icon" xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20 6 9 17l-5-5"/></svg>
|
|
104
|
+
<span>Copy</span>
|
|
105
|
+
</button>
|
|
106
|
+
</div>
|
|
107
|
+
</div>
|
|
108
|
+
`
|
|
109
|
+
})
|
|
110
|
+
.join('')}
|
|
111
|
+
</div>
|
|
112
|
+
</div>
|
|
113
|
+
</div>
|
|
114
|
+
</section>
|
|
115
|
+
`
|
|
116
|
+
}
|