- {/* equivalent of ui/badge.html color="primary" light=true */}
Added in {addedIn}
)
@@ -243,8 +243,7 @@ const docsLinks = docs.links as DocsLink[];
Hello World Lorem ipsum[1] dolor sit amet, consectetur adipiscing elit. Nulla accumsan, metus ultrices eleifend gravida, nulla nunc varius lectus, nec rutrum justo nibh eu lectus. Ut vulputate semper dui. Fusce erat odio, sollicitudin vel erat vel, interdum mattis neque. Subscript works as well!
Second level Curabitur accumsan turpis pharetra augue tincidunt blandit. Quisque condimentum maximus mi, sit amet commodo arcu rutrum id. Proin pretium urna vel cursus venenatis. Suspendisse potenti. Etiam mattis sem rhoncus lacus dapibus facilisis. Donec at dignissim dui. Ut et neque nisl.
In fermentum leo eu lectus mollis, quis dictum mi aliquet. Morbi eu nulla lobortis, lobortis est in, fringilla felis. Aliquam nec felis in sapien venenatis viverra fermentum nec lectus. Ut non enim metus.
diff --git a/preview/astro.config.mjs b/preview/astro.config.mjs
index cff5a5141..be8fbdb74 100644
--- a/preview/astro.config.mjs
+++ b/preview/astro.config.mjs
@@ -1,120 +1,112 @@
// @ts-check
-import { defineConfig } from 'astro/config'
-import mdx from '@astrojs/mdx'
-import { satteri } from '@astrojs/markdown-satteri'
-import beautify from 'js-beautify'
-import { execFileSync } from 'node:child_process'
-import { devNull } from 'node:os'
-import { fileURLToPath } from 'node:url'
+import { defineConfig } from 'astro/config';
+import mdx from '@astrojs/mdx';
+import { satteri } from '@astrojs/markdown-satteri';
+import beautify from 'js-beautify';
+import { execFileSync } from 'node:child_process';
+import { devNull } from 'node:os';
+import { fileURLToPath } from 'node:url';
/**
- * Equivalent of the Eleventy "html-prettify" step (@tabler/preview): the
- * generated HTML is the product (users copy it 1:1), so after the build we
- * format it with prettier per .prettierrc.
+ * After build, format page HTML with prettier (users copy it 1:1).
* @returns {import('astro').AstroIntegration}
*/
function prettifyHtml() {
- return {
- name: 'prettify-html',
- hooks: {
- 'astro:build:done': async ({ dir, logger }) => {
- const outDir = fileURLToPath(dir)
- // dist/preview/ and dist/dist/ are copy-assets.mjs's copies of public/{preview,dist}
- // (demo css/js and @tabler/core's dist, including vendored libs) — not pages, and
- // some vendored libs ship their own malformed docs/*.html that trips the parser below.
- /** @param {string} file */
- const isVendorCopy = (file) => file.includes(`${outDir}preview/`) || file.includes(`${outDir}dist/`)
- // Astro appends "overflow-x: auto" to the shiki style — the
- // Eleventy pipeline doesn't have it and HTML is the product: restore 1:1.
- const { globSync } = await import('node:fs')
- const { readFileSync, writeFileSync } = await import('node:fs')
- for (const file of globSync(`${outDir}**/*.html`, { exclude: isVendorCopy })) {
- const content = readFileSync(file, 'utf8')
- const cleaned = content.replaceAll('; overflow-x: auto;', '')
- if (cleaned !== content) writeFileSync(file, cleaned)
- }
- execFileSync(
- 'npx',
- [
- 'prettier',
- '--write',
- '--parser',
- 'html',
- // Prettier's default ignore-path is [.gitignore, .prettierignore], and both
- // list "dist" (needed elsewhere so normal lint/format passes skip build
- // output) — that silently no-ops this pass on the very directory it targets.
- // Point at devNull to opt this one deliberate pass out of those ignores.
- '--ignore-path',
- devNull,
- `${outDir}**/*.html`,
- `!${outDir}preview/**`,
- `!${outDir}dist/**`,
- ],
- { stdio: 'inherit' },
- )
- logger.info('HTML formatted with prettier')
- },
- },
- }
+ return {
+ name: 'prettify-html',
+ hooks: {
+ 'astro:build:done': async ({ dir, logger }) => {
+ const outDir = fileURLToPath(dir);
+ // dist/preview/ and dist/dist/ are copy-assets.mjs's copies of public/{preview,dist}
+ // (demo css/js and @tabler/core's dist, including vendored libs) — not pages, and
+ // some vendored libs ship their own malformed docs/*.html that trips the parser below.
+ /** @param {string} file */
+ const isVendorCopy = (file) => file.includes(`${outDir}preview/`) || file.includes(`${outDir}dist/`);
+ // Strip Astro's extra "overflow-x: auto" on shiki styles.
+ const { globSync } = await import('node:fs');
+ const { readFileSync, writeFileSync } = await import('node:fs');
+ for (const file of globSync(`${outDir}**/*.html`, { exclude: isVendorCopy })) {
+ const content = readFileSync(file, 'utf8');
+ const cleaned = content.replaceAll('; overflow-x: auto;', '');
+ if (cleaned !== content) writeFileSync(file, cleaned);
+ }
+ execFileSync(
+ 'npx',
+ [
+ 'prettier',
+ '--write',
+ '--parser',
+ 'html',
+ // Prettier's default ignore-path is [.gitignore, .prettierignore], and both
+ // list "dist" (needed elsewhere so normal lint/format passes skip build
+ // output) — that silently no-ops this pass on the very directory it targets.
+ // Point at devNull to opt this one deliberate pass out of those ignores.
+ '--ignore-path',
+ devNull,
+ `${outDir}**/*.html`,
+ `!${outDir}preview/**`,
+ `!${outDir}dist/**`,
+ ],
+ { stdio: 'inherit' },
+ );
+ logger.info('HTML formatted with prettier');
+ },
+ },
+ };
}
// https://astro.build/config
export default defineConfig({
- // pages live at the package root (./pages) — all components/lib/data are
- // shared (see the @shared alias)
- srcDir: '.',
- server: {
- port: 3000,
- // bind on all interfaces so the dev server is reachable from Docker
- // port mappings and other devices on the local network
- host: true,
- },
- vite: {
- resolve: {
- alias: {
- // demo data lives in the monorepo's shared/data — single source of
- // truth shared with the Eleventy packages (no copies in src/data)
- '@data': fileURLToPath(new URL('../shared/data', import.meta.url)),
- // Astro components/lib shared with docs (single source of truth)
- '@shared': fileURLToPath(new URL('../shared', import.meta.url)),
- '@ui': fileURLToPath(new URL('../shared/ui', import.meta.url)),
- '@components': fileURLToPath(new URL('../shared/components', import.meta.url)),
- // this package's pages dir — used by @shared/lib/docs-children's glob
- '@pages': fileURLToPath(new URL('./pages', import.meta.url)),
- },
- },
- },
- build: {
- // emit sign-in.html instead of sign-in/index.html — matches the Eleventy
- // preview package layout, where the HTML files are the distributed product
- format: 'file',
- },
- // Do not collapse whitespace in the output — the HTML must stay readable
- // (like the Eleventy build); prettier finalizes formatting after the build.
- compressHTML: false,
- integrations: [mdx(), prettifyHtml()],
- markdown: {
- // markdown-it in Eleventy does not produce typographic quotes — neither do we
- processor: satteri({ features: { smartPunctuation: false } }),
- shikiConfig: {
- theme: 'github-dark',
- transformers: [
- {
- // The Eleventy docs pipeline beautifies html fences before highlighting
- preprocess(code) {
- if (this.options.lang === 'html') {
- return beautify.html(code, { indent_size: 2, wrap_line_length: 80 })
- }
- },
- // Eleventy docs emits raw shiki output: .
- // Astro adds its own astro-code class and data-language — restore the
- // exact markdown-it + shiki pipeline markup.
- pre(node) {
- node.properties.class = 'shiki github-dark'
- delete node.properties.dataLanguage
- },
- },
- ],
- },
- },
-})
+ // pages live at the package root (./pages) — all components/lib/data are
+ // shared (see the @shared alias)
+ srcDir: '.',
+ server: {
+ port: 3000,
+ // bind on all interfaces so the dev server is reachable from Docker
+ // port mappings and other devices on the local network
+ host: true,
+ },
+ vite: {
+ resolve: {
+ alias: {
+ // Demo data in shared/data (single source of truth).
+ '@data': fileURLToPath(new URL('../shared/data', import.meta.url)),
+ // Components/lib shared with docs.
+ '@shared': fileURLToPath(new URL('../shared', import.meta.url)),
+ '@ui': fileURLToPath(new URL('../shared/ui', import.meta.url)),
+ '@components': fileURLToPath(new URL('../shared/components', import.meta.url)),
+ // Used by @shared/lib/docs-children's glob.
+ '@pages': fileURLToPath(new URL('./pages', import.meta.url)),
+ },
+ },
+ },
+ build: {
+ // Emit sign-in.html instead of sign-in/index.html (HTML is the product).
+ format: 'file',
+ },
+ // Keep readable HTML; prettier formats after the build.
+ compressHTML: false,
+ integrations: [mdx(), prettifyHtml()],
+ markdown: {
+ // No typographic quote rewriting.
+ processor: satteri({ features: { smartPunctuation: false } }),
+ shikiConfig: {
+ theme: 'github-dark',
+ transformers: [
+ {
+ // Beautify html fences before highlighting.
+ preprocess(code) {
+ if (this.options.lang === 'html') {
+ return beautify.html(code, { indent_size: 2, wrap_line_length: 80 });
+ }
+ },
+ // Keep shiki classes only (drop Astro's astro-code / data-language).
+ pre(node) {
+ node.properties.class = 'shiki github-dark';
+ delete node.properties.dataLanguage;
+ },
+ },
+ ],
+ },
+ },
+});
diff --git a/preview/pages/2-step-verification-code.astro b/preview/pages/2-step-verification-code.astro
index 81d10d78b..735484d8a 100644
--- a/preview/pages/2-step-verification-code.astro
+++ b/preview/pages/2-step-verification-code.astro
@@ -1,79 +1,93 @@
---
-import FormFooter from '@ui/FormFooter.astro'
-import SingleLayout from '@shared/layouts/SingleLayout.astro'
-import Button from '@ui/Button.astro'
-import ButtonList from '@ui/ButtonList.astro'
+import FormFooter from '@ui/FormFooter.astro';
+import SingleLayout from '@shared/layouts/SingleLayout.astro';
+import Button from '@ui/Button.astro';
+import ButtonList from '@ui/ButtonList.astro';
+import CaptureScript from '@shared/components/CaptureScript.astro';
---
-
-
-
-
+ input.addEventListener('keydown', (e) => {
+ const target = e.target;
+ // If the input field is empty and Backspace is pressed, and there is a previous input field, focus it
+ if (target.value.length === 0 && e.key === 'Backspace' && i > 0) {
+ inputs[i - 1].focus();
+ }
+ });
+ });
+
+
+
-
- It may take a minute to receive your code. Haven't received it?
Resend a new code.
-
+
+ It may take a minute to receive your code. Haven't received it?
Resend a new code.
+
diff --git a/preview/pages/2-step-verification.astro b/preview/pages/2-step-verification.astro
index 7f572f886..238268ae7 100644
--- a/preview/pages/2-step-verification.astro
+++ b/preview/pages/2-step-verification.astro
@@ -1,7 +1,5 @@
---
-// Liquid: {% if country.code == 'US' %} selected{% endif %} — flags.json entries
-// have no `code` property, so the reference renders value="" and never `selected`.
-// We mirror that output exactly.
+// flags.json entries have no `code` property — value="" and never `selected`.
import FormFooter from '@ui/FormFooter.astro'
import SingleLayout from '@shared/layouts/SingleLayout.astro'
diff --git a/preview/pages/all-elements.astro b/preview/pages/all-elements.astro
index dd637f64f..ecbb8349e 100644
--- a/preview/pages/all-elements.astro
+++ b/preview/pages/all-elements.astro
@@ -1,835 +1,870 @@
---
+
// Code-block content lives in the frontmatter so Astro does not try to evaluate
// its `${name}` / `{ }` as expressions; injected verbatim via set:html.
-import ButtonGroup from '@ui/ButtonGroup.astro'
-import DefaultLayout from '@shared/layouts/DefaultLayout.astro'
-import Button from '@ui/Button.astro'
-import ButtonList from '@ui/ButtonList.astro'
-import Card from '@ui/Card.astro'
-import CardHeader from '@ui/CardHeader.astro'
-import CardBody from '@ui/CardBody.astro'
-import CardFooter from '@ui/CardFooter.astro'
-import Alert from '@ui/Alert.astro'
-import Badge from '@ui/Badge.astro'
-import Progress from '@ui/Progress.astro'
-import Select from '@ui/Select.astro'
-import Check from '@ui/form/Check.astro'
-import FormGroup from '@ui/FormGroup.astro'
-import Nav from '@ui/Nav.astro'
-import Breadcrumb from '@ui/Breadcrumb.astro'
-import Pagination from '@ui/Pagination.astro'
-import Avatar from '@ui/Avatar.astro'
-import Icon from '@ui/Icon.astro'
-import Dropdown from '@ui/Dropdown.astro'
-import Accordion from '@ui/Accordion.astro'
-import Spinner from '@ui/Spinner.astro'
-import Rating from '@ui/Rating.astro'
-import Steps from '@ui/Steps.astro'
-import StatusDot from '@ui/StatusDot.astro'
-import Toast from '@ui/Toast.astro'
-import InputIcon from '@ui/form/InputIcon.astro'
-import InputGroup from '@ui/InputGroup.astro'
-import Range from '@ui/Range.astro'
-import Tag from '@ui/Tag.astro'
-import Ribbon from '@ui/Ribbon.astro'
-import Flag from '@ui/Flag.astro'
-import Payment from '@ui/Payment.astro'
-import Timeline from '@ui/Timeline.astro'
-import Empty from '@ui/Empty.astro'
-import NavSegmented from '@ui/NavSegmented.astro'
-import ListGroup from '@ui/ListGroup.astro'
-import ListGroupItem from '@ui/ListGroupItem.astro'
+import ButtonGroup from '@ui/ButtonGroup.astro';
+import DefaultLayout from '@shared/layouts/DefaultLayout.astro';
+import Button from '@ui/Button.astro';
+import ButtonList from '@ui/ButtonList.astro';
+import Card from '@ui/Card.astro';
+import CardHeader from '@ui/CardHeader.astro';
+import CardBody from '@ui/CardBody.astro';
+import CardFooter from '@ui/CardFooter.astro';
+import Alert from '@ui/Alert.astro';
+import Badge from '@ui/Badge.astro';
+import Progress from '@ui/Progress.astro';
+import Select from '@ui/Select.astro';
+import Check from '@ui/form/Check.astro';
+import FormGroup from '@ui/FormGroup.astro';
+import Nav from '@ui/Nav.astro';
+import Breadcrumb from '@ui/Breadcrumb.astro';
+import Pagination from '@ui/Pagination.astro';
+import Avatar from '@ui/Avatar.astro';
+import Icon from '@ui/Icon.astro';
+import Dropdown from '@ui/Dropdown.astro';
+import Accordion from '@ui/Accordion.astro';
+import Spinner from '@ui/Spinner.astro';
+import Rating from '@ui/Rating.astro';
+import Steps from '@ui/Steps.astro';
+import StatusDot from '@ui/StatusDot.astro';
+import Toast from '@ui/Toast.astro';
+import InputIcon from '@ui/form/InputIcon.astro';
+import InputGroup from '@ui/InputGroup.astro';
+import Range from '@ui/Range.astro';
+import Tag from '@ui/Tag.astro';
+import Ribbon from '@ui/Ribbon.astro';
+import Flag from '@ui/Flag.astro';
+import Payment from '@ui/Payment.astro';
+import Timeline from '@ui/Timeline.astro';
+import Empty from '@ui/Empty.astro';
+import NavSegmented from '@ui/NavSegmented.astro';
+import ListGroup from '@ui/ListGroup.astro';
+import ListGroupItem from '@ui/ListGroupItem.astro';
const codeExample = `// JavaScript example
function greetUser(name) {
console.log(\`Hello, \${name}!\`);
return true;
-}`
+}`;
---
-
-
-
-
-
-
-
-
-
-
Heading 1
- Heading 2
- Heading 3
- Heading 4
- Heading 5
- Heading 6
-
-
-
This is a lead paragraph with larger text.
-
- This is a regular paragraph with bold text , italic text , and
- underlined text .
-
-
This is small muted text.
-
Primary text color
-
Success text color
-
Danger text color
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
Heading 1
+ Heading 2
+ Heading 3
+ Heading 4
+ Heading 5
+ Heading 6
+
+
+
This is a lead paragraph with larger text.
+
This is a regular paragraph with bold text , italic text , and
+ underlined text .
+
This is small muted text.
+
Primary text color
+
Success text color
+
Danger text color
+
+
+
+
+
-
-
-
-
-
-
-
-
Standard Buttons
-
-
-
-
-
-
-
-
-
-
-
-
Button Sizes
-
-
-
-
-
+
+
+
+
+
+
+
+
Standard Buttons
+
+
+
+
+
+
+
+
+
+
+
+
Button Sizes
+
+
+
+
+
- Icon Buttons
-
-
-
-
-
-
-
-
-
-
+
Icon Buttons
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
- This is a simple card with header and body content.
-
-
-
+
+
+
+
+
+ This is a simple card with header and body content.
+
+
+
-
-
-
-
- This card includes a footer section.
-
-
-
-
-
-
+
+
+
+
+ This card includes a footer section.
+
+
+
+
+
+
-
-
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
+
+
-
-
+
+
-
-
-
-
-
-
-
-
Tabs
-
-
-
+
+
+
+
+
+
+
+
Tabs
+
+
+
-
Pills Navigation
-
-
-
+
Pills Navigation
+
+
+
-
Breadcrumb
-
-
-
-
Pagination
- {
- /* active-item="2" is a STRING in Liquid; `i == "2"` never matches the
- integer index, so no page renders active — reproduced by passing a string. */
- }
-
+
Breadcrumb
+
+
+
+
Pagination
+ {/* activeItem="2" is a string — strict equality never matches integer index, so no page is active */}
+
-
Pagination with Text
-
-
-
-
-
-
+
Pagination with Text
+
+
+
+
+
+
-
-
-
-
-
- Unordered List
-
- First item
- Second item
- Third item
-
+
+
+
+
+
+ Unordered List
+
+ First item
+ Second item
+ Third item
+
- Ordered List
-
- First item
- Second item
- Third item
-
-
-
-
+ Ordered List
+
+ First item
+ Second item
+ Third item
+
+
+
+
-
-
-
-
-
-
-
-
-
- Name
- Email
- Status
-
-
-
-
- John Doe
- john@example.com
-
-
-
- Jane Smith
- jane@example.com
-
-
-
- Bob Johnson
- bob@example.com
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+ Name
+ Email
+ Status
+
+
+
+
+ John Doe
+ john@example.com
+
+
+
+ Jane Smith
+ jane@example.com
+
+
+
+ Bob Johnson
+ bob@example.com
+
+
+
+
+
+
+
+
-
-
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
Spinners
-
+
+
+
+
+
+
+
+
+
Spinners
+
-
Rating
-
-
-
+
Rating
+
+
+
-
Steps
-
-
-
-
-
-
-
-
-
-
-
+
Steps
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
- Status Dots
-
-
-
-
-
-
+
+
+
+
+
+ Status Dots
+
+
+
+
+
+
- Toast Notifications
-
-
-
-
-
-
+ Toast Notifications
+
+
+
+
+
+
-
-
-
-
-
- Input with Icons
-
-
-
-
-
-
+
+
+
+
+
+ Input with Icons
+
+
+
+
+
+
- Input Groups
-
-
-
-
-
-
+ Input Groups
+
+
+
+
+
+
- Range Slider
-
-
-
-
-
-
+ Range Slider
+
+
+
+
+
+
-
-
-
-
-
- Tags
-
-
-
-
-
+
+
+
+
+
+ Tags
+
+
+
+
+
- Ribbons
-
-
-
-
-
-
-
-
-
+ Ribbons
+
+
+
+
+
+
+
+
+
-
-
-
-
-
- Flags
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+ Flags
+
+
+
+
+
+
+
+
+
+
+
+
+
+
- Payment Icons
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+ Payment Icons
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
- Open Modal
- Success Modal
-
+
+
+
+
+
+
+
+ Open Modal
+
+
+ Success Modal
+
+
-
-
-
-
-
-
-
This is a sample modal dialog. You can put any content here.
-
-
-
-
-
+
+
+
+
+
+
+
This is a sample modal dialog. You can put any content here.
+
+
+
+
+
-
-
-
-
-
-
-
Success!
-
Your action was completed successfully.
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
Success!
+
Your action was completed successfully.
+
+
+
+
+
+
+
+
-
-
-
-
-
- Basic Button Group
-
- Left
- Middle
- Right
-
+
+
+
+
+
+ Basic Button Group
+
+ Left
+ Middle
+ Right
+
- Button Toolbar
-
-
- 1
- 2
- 3
-
-
- 4
- 5
-
-
+ Button Toolbar
+
+
+ 1
+ 2
+ 3
+
+
+ 4
+ 5
+
+
- Vertical Button Group
-
- Top
- Middle
- Bottom
-
-
-
-
+ Vertical Button Group
+
+ Top
+ Middle
+ Bottom
+
+
+
+
-
-
-
-
-
- Basic Segmented
-
-
-
+
+
+
+
+
+ Basic Segmented
+
+
+
- With Icons
-
-
-
+ With Icons
+
+
+
- With Emojis
-
-
-
+ With Emojis
+
+
+
- With Icons and Text
-
-
-
-
-
-
+ With Icons and Text
+
+
+
+
+
+
-
-
-
-
-
-
-
-
Basic Collapse
-
Toggle Collapse
-
-
This is collapsed content that can be toggled. It's hidden by default and shown when the button is clicked.
-
-
-
-
Multiple Targets
-
- Toggle First
- Toggle Second
-
-
-
First collapsible content.
-
-
-
Second collapsible content.
-
-
-
-
-
-
+
+
+
+
+
+
+
+
Basic Collapse
+
+ Toggle Collapse
+
+
+
+ This is collapsed content that can be toggled. It's hidden by default and shown when the button is clicked.
+
+
+
+
+
Multiple Targets
+
+
+ Toggle First
+
+
+ Toggle Second
+
+
+
+
+ First collapsible content.
+
+
+
+
+ Second collapsible content.
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
Tooltips
-
- Top
- Right
- Bottom
- Left
-
-
-
-
Popovers
-
- Top Popover
- Right Popover
-
-
-
-
-
-
+
+
+
+
+
+
+
+
Tooltips
+
+
+ Top
+
+
+ Right
+
+
+ Bottom
+
+
+ Left
+
+
+
+
+
Popovers
+
+
+ Top Popover
+
+
+ Right Popover
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
- Home
-
-
-
- Active item
-
-
-
- Settings
-
-
-
- Profile
-
-
-
- Disabled item
-
-
-
-
-
+
+
+
+
+
+
+
+
+ Home
+
+
+
+ Active item
+
+
+
+ Settings
+
+
+
+ Profile
+
+
+
+ Disabled item
+
+
+
+
+
-
-
-
-
-
- Blockquote
-
- This is a blockquote example with some sample text to demonstrate the styling.
-
-
+
+
+
+
+
+ Blockquote
+
+ This is a blockquote example with some sample text to demonstrate the styling.
+
+
- Code Block
-
+ Code Block
+
- Inline Elements
- This paragraph contains inline code, Ctrl + S keyboard shortcut, and highlighted text .
-
-
-
-
+
Inline Elements
+
This paragraph contains inline code, Ctrl + S keyboard shortcut, and highlighted text .
+
+
+
+
diff --git a/preview/pages/auth-lock.astro b/preview/pages/auth-lock.astro
index 0228efddd..c7b0d71db 100644
--- a/preview/pages/auth-lock.astro
+++ b/preview/pages/auth-lock.astro
@@ -1,9 +1,9 @@
---
-// title really is "Forgot password" in the Liquid source)
-import SingleLayout from '@shared/layouts/SingleLayout.astro'
-import AuthLockCard from '@shared/components/cards/AuthLockCard.astro'
+// Title is "Forgot password" (matches auth-lock card, not page name).
+import SingleLayout from '@shared/layouts/SingleLayout.astro';
+import AuthLockCard from '@shared/components/cards/AuthLockCard.astro';
---
-
+
diff --git a/preview/pages/avatars.astro b/preview/pages/avatars.astro
index 75ceb46b8..345e2c6e1 100644
--- a/preview/pages/avatars.astro
+++ b/preview/pages/avatars.astro
@@ -1,169 +1,166 @@
---
-// Note: the `description` front matter key is inert in the dev build (no
-// in the reference output), so it is not ported.
-import DefaultLayout from '@shared/layouts/DefaultLayout.astro'
-import Avatar from '@ui/Avatar.astro'
-import AvatarUpload from '@ui/AvatarUpload.astro'
-import AvatarList from '@ui/AvatarList.astro'
-import Card from '@ui/Card.astro'
-import CardBody from '@ui/CardBody.astro'
-import CardTitle from '@ui/CardTitle.astro'
-import people from '@data/people.json'
-import { site } from '@shared/lib/site'
-import { firstLetters } from '@shared/lib/string-format'
+import DefaultLayout from '@shared/layouts/DefaultLayout.astro';
+import Avatar from '@ui/Avatar.astro';
+import AvatarUpload from '@ui/AvatarUpload.astro';
+import AvatarList from '@ui/AvatarList.astro';
+import Card from '@ui/Card.astro';
+import CardBody from '@ui/CardBody.astro';
+import CardTitle from '@ui/CardTitle.astro';
+import people from '@data/people.json';
+import { site } from '@shared/lib/site';
+import { firstLetters } from '@shared/lib/string-format';
-const iconIcons = ['user', 'settings', 'car', 'balloon', 'users', 'users-group', 'apps', 'ghost']
+const iconIcons = ['user', 'settings', 'car', 'balloon', 'users', 'users-group', 'apps', 'ghost'];
-// Liquid: {% for color in site.colors %} — the iterated keys match site.themeColors
-// (blue..cyan), confirmed against the reference output.
-const colors = site.themeColors
+// themeColors keys (blue..cyan).
+const colors = site.themeColors;
-const people8 = people.slice(0, 8)
-const people5 = people.slice(0, 5)
-const sizes = ['xxs', 'xs', 'sm', 'md', 'lg', 'xl']
-const listSizes = ['xxs', 'xs', 'sm', 'md', 'lg']
-const uploadSizes = ['xxs', 'xs', 'sm', 'md', 'lg', 'xl', '2xl']
-const statusColors = ['red', 'green', 'blue', 'yellow', 'secondary']
-const brands = ['netflix', 'amazon', 'messenger', 'figma', 'twitch']
+const people8 = people.slice(0, 8);
+const people5 = people.slice(0, 5);
+const sizes = ['xxs', 'xs', 'sm', 'md', 'lg', 'xl'];
+const listSizes = ['xxs', 'xs', 'sm', 'md', 'lg'];
+const uploadSizes = ['xxs', 'xs', 'sm', 'md', 'lg', 'xl', '2xl'];
+const statusColors = ['red', 'green', 'blue', 'yellow', 'secondary'];
+const brands = ['netflix', 'amazon', 'messenger', 'figma', 'twitch'];
---
-
-
-
-
- Default avatar
-
-
-
-
-
-
-
- Avatar with icon
+
+
+
+
+ Default avatar
+
+
+
+
+
+
+
+ Avatar with icon
-
- {iconIcons.map((icon) =>
)}
-
-
-
-
-
-
-
- Avatar with icon
+
+ {iconIcons.map((icon) =>
)}
+
+
+
+
+
+
+
+ Avatar with icon
-
- {colors.map((color) =>
)}
-
-
-
-
-
-
-
- Simple avatar
-
- {people8.map((person) =>
)}
-
-
-
-
-
-
-
- Avatar placeholder
-
- {people8.map((person) =>
)}
-
-
-
-
-
-
-
- Avatar shapes
-
-
-
-
-
-
-
- Avatar sizes
-
-
-
- {sizes.map((size) =>
)}
-
-
-
-
- {sizes.map((size) =>
)}
-
-
-
-
-
-
-
-
-
- Avatar lists
-
- {
- listSizes.map((size) => (
- <>
-
-
- {people5.map((person) => (
-
- ))}
-
-
-
-
-
- {people5.map((person) => (
-
- ))}
-
-
-
- >
- ))
- }
-
-
-
-
-
-
-
- Avatar placeholder
- {uploadSizes.map((size) => )}
-
-
-
-
-
-
- Avatar statuses
- {statusColors.map((color, i) => )}
-
-
-
-
-
-
- Avatar brands
- {brands.map((brand, i) => )}
-
-
-
-
+
+ {colors.map((color) =>
)}
+
+
+
+
+
+
+
+ Simple avatar
+
+ {people8.map((person) =>
)}
+
+
+
+
+
+
+
+ Avatar placeholder
+
+ {people8.map((person) =>
)}
+
+
+
+
+
+
+
+ Avatar shapes
+
+
+
+
+
+
+
+ Avatar sizes
+
+
+
+ {sizes.map((size) =>
)}
+
+
+
+
+ {sizes.map((size) =>
)}
+
+
+
+
+
+
+
+
+
+ Avatar lists
+
+ {
+ listSizes.map((size) => (
+ <>
+
+
+ {people5.map((person) => (
+
+ ))}
+
+
+
+
+
+ {people5.map((person) => (
+
+ ))}
+
+
+
+ >
+ ))
+ }
+
+
+
+
+
+
+
+ Avatar placeholder
+ {uploadSizes.map((size) => )}
+
+
+
+
+
+
+ Avatar statuses
+ {statusColors.map((color, i) => )}
+
+
+
+
+
+
+ Avatar brands
+ {brands.map((brand, i) => )}
+
+
+
+
diff --git a/preview/pages/badges.astro b/preview/pages/badges.astro
index 639d44e84..7f41d180b 100644
--- a/preview/pages/badges.astro
+++ b/preview/pages/badges.astro
@@ -1,205 +1,206 @@
---
-// Liquid: colors = ['default'] + site.colors keys + ['dark', 'light']
-import BadgesList from '@ui/BadgesList.astro'
-import DefaultLayout from '@shared/layouts/DefaultLayout.astro'
-import Icon from '@ui/Icon.astro'
-import ButtonList from '@ui/ButtonList.astro'
-import Card from '@ui/Card.astro'
-import CardBody from '@ui/CardBody.astro'
-import CardTitle from '@ui/CardTitle.astro'
-import DropdownMenu from '@ui/DropdownMenu.astro'
-import site from '@data/site.json'
-import { ucFirst } from '@shared/lib/string-format'
+// colors = ['default'] + site.colors keys + ['dark', 'light']
-const colors = ['default', ...Object.keys(site.colors), 'dark', 'light']
-const sizes = ['sm', 'md', 'lg']
+import BadgesList from '@ui/BadgesList.astro';
+import DefaultLayout from '@shared/layouts/DefaultLayout.astro';
+import Icon from '@ui/Icon.astro';
+import ButtonList from '@ui/ButtonList.astro';
+import Card from '@ui/Card.astro';
+import CardBody from '@ui/CardBody.astro';
+import CardTitle from '@ui/CardTitle.astro';
+import DropdownMenu from '@ui/DropdownMenu.astro';
+import site from '@data/site.json';
+import { ucFirst } from '@shared/lib/string-format';
+
+const colors = ['default', ...Object.keys(site.colors), 'dark', 'light'];
+const sizes = ['sm', 'md', 'lg'];
---
-
-
-
-
-
-
- Example heading New
- Example heading New
- Example heading New
- Example heading New
- Example heading New
- Example heading New
-
-
-
-
-
-
- Badge sizes
+
+
+
+
+
+
+ Example heading New
+ Example heading New
+ Example heading New
+ Example heading New
+ Example heading New
+ Example heading New
+
+
+
+
+
+
+ Badge sizes
-
- {
- sizes.map((size) => (
-
- Default
-
- Left icon
-
-
- Right icon
-
-
-
-
-
-
- ))
- }
-
-
-
-
-
-
-
- Positioned badges
+
+ {
+ sizes.map((size) => (
+
+ Default
+
+ Left icon
+
+
+ Right icon
+
+
+
+
+
+ ))
+ }
+
+
+
+
+
+
+
+ Positioned badges
-
- Notifications 4
+
+ Notifications 4
-
- Inbox
-
- 9+
- unread messages
-
-
+
+ Inbox
+
+ 9+
+ unread messages
+
+
-
- Profile
-
-
+
+ Profile
+
+
-
- Settings
-
-
+
+ Settings
+
+
-
-
-
-
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Basic badges
-
- {colors.map((color) => {ucFirst(color)} )}
-
-
-
-
-
-
-
- Light badges
-
- {colors.map((color) => {ucFirst(color)} )}
-
-
-
-
-
-
-
- Outline badges
-
- {colors.map((color) => {ucFirst(color)} )}
-
-
-
-
-
-
-
- Badges with icons
-
- {
- colors.map((color) => (
-
- {' '}
- {ucFirst(color)}{' '}
-
- ))
- }
-
-
-
-
-
-
-
-
-
-
-
-
-
- {
- colors.map((color, index) => (
-
- {ucFirst(color)} badge {index + 1}
-
- ))
- }
-
-
-
-
-
-
-
-
- {
- colors.map((color, index) => (
-
- {ucFirst(color)} badge {index + 1}
-
- ))
- }
-
-
-
-
-
-
-
-
- {
- colors.map((color) => (
-
- {ucFirst(color)} badge
-
- ))
- }
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Basic badges
+
+ {colors.map((color) => {ucFirst(color)} )}
+
+
+
+
+
+
+
+ Light badges
+
+ {colors.map((color) => {ucFirst(color)} )}
+
+
+
+
+
+
+
+ Outline badges
+
+ {colors.map((color) => {ucFirst(color)} )}
+
+
+
+
+
+
+
+ Badges with icons
+
+ {
+ colors.map((color) => (
+
+ {' '}
+ {ucFirst(color)}{' '}
+
+ ))
+ }
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {
+ colors.map((color, index) => (
+
+ {ucFirst(color)} badge {index + 1}
+
+ ))
+ }
+
+
+
+
+
+
+
+
+ {
+ colors.map((color, index) => (
+
+ {ucFirst(color)} badge{' '}
+ {index + 1}
+
+ ))
+ }
+
+
+
+
+
+
+
+
+ {
+ colors.map((color) => (
+
+ {ucFirst(color)} badge
+
+ ))
+ }
+
+
+
+
+
+
+
diff --git a/preview/pages/blank.astro b/preview/pages/blank.astro
index 4fd2dae0d..0324fb35b 100644
--- a/preview/pages/blank.astro
+++ b/preview/pages/blank.astro
@@ -1,10 +1,9 @@
---
-// front matter is inert in the Liquid templates (never referenced) — the
-// empty page header comes from the missing `page-header` key.
-import DefaultLayout from '@shared/layouts/DefaultLayout.astro'
-import Empty from '@ui/Empty.astro'
+// front matter keys unused — empty page header comes from missing `page-header` key.
+import DefaultLayout from '@shared/layouts/DefaultLayout.astro';
+import Empty from '@ui/Empty.astro';
---
-
+
diff --git a/preview/pages/buttons.astro b/preview/pages/buttons.astro
index f293f8014..530db5243 100644
--- a/preview/pages/buttons.astro
+++ b/preview/pages/buttons.astro
@@ -1,201 +1,191 @@
---
-import DefaultLayout from '@shared/layouts/DefaultLayout.astro'
-import Icon from '@ui/Icon.astro'
-import Button from '@ui/Button.astro'
-import ButtonList from '@ui/ButtonList.astro'
-import Card from '@ui/Card.astro'
-import CardHeader from '@ui/CardHeader.astro'
-import CardBody from '@ui/CardBody.astro'
-import site from '@data/site.json'
+import DefaultLayout from '@shared/layouts/DefaultLayout.astro';
+import Icon from '@ui/Icon.astro';
+import Button from '@ui/Button.astro';
+import ButtonList from '@ui/ButtonList.astro';
+import Card from '@ui/Card.astro';
+import CardHeader from '@ui/CardHeader.astro';
+import CardBody from '@ui/CardBody.astro';
+import site from '@data/site.json';
-type ColorEntry = [string, { title: string; icon?: string }]
+type ColorEntry = [string, { title: string; icon?: string }];
-// Liquid iterates the site.json objects as [key, value] pairs.
-// themeColors/colors entries have no icon — ui/icon.html renders nothing there.
-const themeColors = Object.entries(site.themeColors) as ColorEntry[]
-const colors = Object.entries(site.colors) as ColorEntry[]
-const socialColors = Object.entries(site.socialColors) as ColorEntry[]
+// site.json objects iterated as [key, value] pairs.
+// themeColors/colors entries have no icon — Icon renders nothing there.
+const themeColors = Object.entries(site.themeColors) as ColorEntry[];
+const colors = Object.entries(site.colors) as ColorEntry[];
+const socialColors = Object.entries(site.socialColors) as ColorEntry[];
-const actions = ['edit', 'copy', 'settings', 'clipboard', 'x']
-const sizes = ['sm', 'md', 'lg', 'xl']
+const actions = ['edit', 'copy', 'settings', 'clipboard', 'x'];
+const sizes = ['sm', 'md', 'lg', 'xl'];
---
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {
- actions.map((action) => (
-
-
-
- ))
- }
-
-
-
-
-
-
-
-
-
-
- {
- sizes.map((size) => (
-
-
-
-
-
-
- ))
- }
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+ {
+ socialColors.map(([name, app]) => (
+ {app.icon && }
+ ))
+ }
+
+
+
+
+
+
+
+
+
+
+ {
+ actions.map((action) => (
+
+
+
+ ))
+ }
+
+
+
+
+
+
+
+
+
+
+ {
+ sizes.map((size) => (
+
+
+
+
+
+
+ ))
+ }
+
+
+
+
+
diff --git a/preview/pages/changelog.astro b/preview/pages/changelog.astro
index c87bf938f..ab2d22452 100644
--- a/preview/pages/changelog.astro
+++ b/preview/pages/changelog.astro
@@ -1,18 +1,11 @@
---
-import ProseLayout from '@shared/layouts/ProseLayout.astro'
-import { renderMarkdown } from '@shared/lib/render-markdown'
-// Liquid: {{ changelog | renderContent: "md" }} — `changelog` is the raw
-// content of core/CHANGELOG.md (shared/e11ty/data.mjs), rendered by
-// Eleventy's markdown-it. renderMarkdown() mirrors that engine exactly
-// (markdown-it 14.3.0, { html: true }, indented code blocks disabled) —
-// output verified byte-identical to the reference build. Injected via
-// set:html: the fragment contains entities and a literal `{$prefix}` token
-// that must not be re-escaped or parsed as JSX.
-import changelog from '../../core/CHANGELOG.md?raw'
+import ProseLayout from '@shared/layouts/ProseLayout.astro';
+import { renderMarkdown } from '@shared/lib/render-markdown';
+import changelog from '../../core/CHANGELOG.md?raw';
-const changelogHtml = renderMarkdown(changelog)
+const changelogHtml = renderMarkdown(changelog);
---
-
+
diff --git a/preview/pages/colorpicker.astro b/preview/pages/colorpicker.astro
index 00482cb2c..dbc9e0e17 100644
--- a/preview/pages/colorpicker.astro
+++ b/preview/pages/colorpicker.astro
@@ -1,31 +1,34 @@
---
-// Liquid: {% for color in site.colors %} — forloop.index (1-based) → id,
-// color[1].hex → value.
-import DefaultLayout from '@shared/layouts/DefaultLayout.astro'
-import Card from '@ui/Card.astro'
-import CardBody from '@ui/CardBody.astro'
-import CardTitle from '@ui/CardTitle.astro'
-import Colorpicker from '@ui/Colorpicker.astro'
-import site from '@data/site.json'
+import DefaultLayout from '@shared/layouts/DefaultLayout.astro';
+import Card from '@ui/Card.astro';
+import CardBody from '@ui/CardBody.astro';
+import CardTitle from '@ui/CardTitle.astro';
+import Colorpicker from '@ui/Colorpicker.astro';
+import site from '@data/site.json';
-const colors = Object.values(site.colors) as { hex: string }[]
+const colors = Object.values(site.colors) as { hex: string }[];
---
-
-
-
- Basic
-
- {
- colors.map((color, index) => (
-
- ))
- }
-
-
-
+
+
+
+ Basic
+
+ {
+ colors.map((color, index) => (
+
+ ))
+ }
+
+
+
diff --git a/preview/pages/colors.astro b/preview/pages/colors.astro
index 9cfe865af..5ee77374d 100644
--- a/preview/pages/colors.astro
+++ b/preview/pages/colors.astro
@@ -1,235 +1,247 @@
---
-import DefaultLayout from '@shared/layouts/DefaultLayout.astro'
-import FormGroup from '@ui/FormGroup.astro'
-import Card from '@ui/Card.astro'
-import CardBody from '@ui/CardBody.astro'
-import Avatar from '@ui/Avatar.astro'
-import CardTitle from '@ui/CardTitle.astro'
-import site from '@data/site.json'
+import DefaultLayout from '@shared/layouts/DefaultLayout.astro';
+import CaptureScript from '@shared/components/CaptureScript.astro';
+import FormGroup from '@ui/FormGroup.astro';
+import Card from '@ui/Card.astro';
+import CardBody from '@ui/CardBody.astro';
+import Avatar from '@ui/Avatar.astro';
+import CardTitle from '@ui/CardTitle.astro';
+import site from '@data/site.json';
-type ColorEntry = [string, { title: string; hex: string; abbr?: string; icon?: string }]
+type ColorEntry = [string, { title: string; hex: string; abbr?: string; icon?: string }];
-// Liquid iterates the site.json objects as [key, value] pairs.
-const colors = Object.entries(site.colors) as ColorEntry[]
-const lightColors = Object.entries(site.lightColors) as ColorEntry[]
-const grayColors = Object.entries(site.grayColors) as ColorEntry[]
-const socialColors = Object.entries(site.socialColors) as ColorEntry[]
+const colors = Object.entries(site.colors) as ColorEntry[];
+const lightColors = Object.entries(site.lightColors) as ColorEntry[];
+const grayColors = Object.entries(site.grayColors) as ColorEntry[];
+const socialColors = Object.entries(site.socialColors) as ColorEntry[];
-// Liquid: colors keys + inverted, white, transparent (pushed before the gradient loops).
-const gradientColors = [...Object.keys(site.colors), 'inverted', 'white', 'transparent']
+const gradientColors = [...Object.keys(site.colors), 'inverted', 'white', 'transparent'];
---
-
-
-
-
-
- {
- colors.map(([name, color]) => (
-
-
-
-
- {color.title}
-
- {color.hex}
-
-
-
- ))
- }
-
-
-
-
-
-
-
-
- {
- lightColors.map(([name, color]) => (
-
-
-
-
- {color.title}
-
- {color.hex}
-
-
-
- ))
- }
-
-
-
-
-
-
-
-
- {
- grayColors.map(([name, color]) => (
-
-
-
-
- {color.title}
-
- {color.hex}
-
-
-
- ))
- }
-
-
-
-
-
-
-
-
- {
- socialColors.map(([name, color]) => (
-
-
-
-
- {color.title}
-
- {color.hex}
-
-
-
- ))
- }
-
-
-
-
-
-
-
-
-
- Gradient
-
-
-
-
-
- {gradientColors.map((color) => {color} )}
-
-
-
-
- {
- gradientColors.map((color) => (
-
- {color}
-
- ))
- }
-
-
-
-
-
-
-
- {gradientColors.map((color) => {color} )}
-
-
-
-
- to top
- to top right
- to right
- to bottom right
- to bottom
- to bottom left
- to left
- to top left
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {gradientColors.map((color) =>
)}
-
-
-
-
- {gradientColors.map((color) =>
)}
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+ {
+ colors.map(([name, color]) => (
+
+
+
+
+ {color.title}
+
+ {color.hex}
+
+
+
+ ))
+ }
+
+
+
+
+
+
+
+
+ {
+ lightColors.map(([name, color]) => (
+
+
+
+
+ {color.title}
+
+ {color.hex}
+
+
+
+ ))
+ }
+
+
+
+
+
+
+
+
+ {
+ grayColors.map(([name, color]) => (
+
+
+
+
+ {color.title}
+
+ {color.hex}
+
+
+
+ ))
+ }
+
+
+
+
+
+
+
+
+ {
+ socialColors.map(([name, color]) => (
+
+
+
+
+ {color.title}
+
+ {color.hex}
+
+
+
+ ))
+ }
+
+
+
+
+
+
+
+
+
+ Gradient
+
+
+
+
+
+ {gradientColors.map((color) => {color} )}
+
+
+
+
+ {
+ gradientColors.map((color) => (
+
+ {color}
+
+ ))
+ }
+
+
+
+
+
+
+
+ {gradientColors.map((color) => {color} )}
+
+
+
+
+ to top
+ to top right
+ to right
+ to bottom right
+ to bottom
+ to bottom left
+ to left
+ to top left
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {
+ gradientColors.map((color) => (
+
+ ))
+ }
+
+
+
+
+ {
+ gradientColors.map((color) => (
+
+ ))
+ }
+
+
+
+
+
+
+
+
+
-
-
-
+ updateGradient();
+ });
+
+
+
diff --git a/preview/pages/dashboard-crypto.astro b/preview/pages/dashboard-crypto.astro
index 2fef984c0..25ea481c7 100644
--- a/preview/pages/dashboard-crypto.astro
+++ b/preview/pages/dashboard-crypto.astro
@@ -1,332 +1,337 @@
---
-import CardActions from '@ui/CardActions.astro'
-import DefaultLayout from '@shared/layouts/DefaultLayout.astro'
-import Avatar from '@ui/Avatar.astro'
-import Trending from '@ui/Trending.astro'
-import CardDropdown from '@ui/CardDropdown.astro'
-import Card from '@ui/Card.astro'
-import CardHeader from '@ui/CardHeader.astro'
-import CardBody from '@ui/CardBody.astro'
-import NavSegmented from '@ui/NavSegmented.astro'
-import Chart from '@ui/Chart.astro'
-import SwitchIcon from '@ui/SwitchIcon.astro'
-import cryptoCurrencies from '@data/crypto-currencies.json'
-import cryptoMarkets from '@data/crypto-markets.json'
-import cryptoOrders from '@data/crypto-orders.json'
-import { parseCurrency, roundTo } from '@shared/lib/string-format'
+import CardActions from '@ui/CardActions.astro';
+import DefaultLayout from '@shared/layouts/DefaultLayout.astro';
+import Avatar from '@ui/Avatar.astro';
+import Trending from '@ui/Trending.astro';
+import CardDropdown from '@ui/CardDropdown.astro';
+import Card from '@ui/Card.astro';
+import CardHeader from '@ui/CardHeader.astro';
+import CardBody from '@ui/CardBody.astro';
+import NavSegmented from '@ui/NavSegmented.astro';
+import Chart from '@ui/Chart.astro';
+import SwitchIcon from '@ui/SwitchIcon.astro';
+import cryptoCurrencies from '@data/crypto-currencies.json';
+import cryptoMarkets from '@data/crypto-markets.json';
+import cryptoOrders from '@data/crypto-orders.json';
+import { parseCurrency, roundTo } from '@shared/lib/string-format';
interface Currency {
- 'symbol': string
- 'price': string
- 'p24h': number
- 'volume-24h': string
+ symbol: string;
+ price: string;
+ p24h: number;
+ 'volume-24h': string;
}
-const currencies = cryptoCurrencies as Currency[]
-const find = (symbol: string) => currencies.find((c) => c.symbol === symbol)!
-const btc = find('BTC')
-const ltc = find('LTC')
-const eth = find('ETH')
-const xmr = find('XMR')
+const currencies = cryptoCurrencies as Currency[];
+const find = (symbol: string) => currencies.find((c) => c.symbol === symbol)!;
+const btc = find('BTC');
+const ltc = find('LTC');
+const eth = find('ETH');
+const xmr = find('XMR');
-const btcBalance = 2.3
-const btcPriceNum = parseCurrency(btc.price)
-const totalUsd = Math.round(btcPriceNum * btcBalance).toLocaleString('en-US')
-// Liquid `divided_by` then `round: 8` (trailing zeros dropped by number output)
-const ltcBtc = roundTo(parseCurrency(ltc.price) / btcPriceNum)
-const ethBtc = roundTo(parseCurrency(eth.price) / btcPriceNum)
-const xmrBtc = roundTo(parseCurrency(xmr.price) / btcPriceNum)
+const btcBalance = 2.3;
+const btcPriceNum = parseCurrency(btc.price);
+const totalUsd = Math.round(btcPriceNum * btcBalance).toLocaleString('en-US');
+// divided_by then round to 8 decimals (trailing zeros dropped in output)
+const ltcBtc = roundTo(parseCurrency(ltc.price) / btcPriceNum);
+const ethBtc = roundTo(parseCurrency(eth.price) / btcPriceNum);
+const xmrBtc = roundTo(parseCurrency(xmr.price) / btcPriceNum);
-const markets = (cryptoMarkets as { coin: string; price: string; volume: string; change: string }[]).slice(0, 10)
-const orders = cryptoOrders as { sell_orders: { price: string; btc: string; sum: string }[]; buy_orders: { price: string; btc: string; sum: string }[] }
-const operationCurrencies = currencies.slice(0, 20)
+const markets = (cryptoMarkets as { coin: string; price: string; volume: string; change: string }[]).slice(0, 10);
+const orders = cryptoOrders as { sell_orders: { price: string; btc: string; sum: string }[]; buy_orders: { price: string; btc: string; sum: string }[] };
+const operationCurrencies = currencies.slice(0, 20);
---
-
-
-
-
-
-
-
-
-
- ${totalUsd}
- {btcBalance} {btc.symbol}
-
-
-
- Since last week
-
-
-
-
+
+
+
+
+
+
+
+
+
+ ${totalUsd}
+ {btcBalance} {btc.symbol}
+
+
+
+ Since last week
+
+
+
+
-
-
-
-
-
- {btc.price}
- {btc.price}
-
-
- Volume: {btc['volume-24h']}
-
-
-
-
+
+
+
+
+
+ {btc.price}
+ {btc.price}
+
+
+ Volume: {btc['volume-24h']}
+
+
+
+
-
-
-
-
-
- {ltcBtc}
- {ltc.price}
-
-
- Volume: {ltc['volume-24h'].replace('$', '')}
-
-
-
-
+
+
+
+
+
+ {ltcBtc}
+ {ltc.price}
+
+
+ Volume: {ltc['volume-24h'].replace('$', '')}
+
+
+
+
-
-
-
-
-
- {ethBtc}
- {eth.price}
-
-
- Volume: {eth['volume-24h'].replace('$', '')}
-
-
-
-
+
+
+
+
+
+ {ethBtc}
+ {eth.price}
+
+
+ Volume: {eth['volume-24h'].replace('$', '')}
+
+
+
+
-
-
-
-
-
- {xmrBtc}
- {xmr.price}
-
-
- Volume: {xmr['volume-24h'].replace('$', '')}
-
-
-
-
-
-
-
-
-
-
-
- Markets
-
-
-
-
-
-
-
-
-
-
- Coin
- Price
- Volume
- Change
-
-
-
- {
- markets.map((market) => (
-
-
-
-
- {market.coin}
- {market.price}
- {market.volume}
-
-
-
-
- ))
- }
-
-
-
-
+
+
+
+
+
+ {xmrBtc}
+ {xmr.price}
+
+
+ Volume: {xmr['volume-24h'].replace('$', '')}
+
+
+
+
+
+
+
+
+
+
+
+ Markets
+
+
+
+
+
+
+
+
+
+
+ Coin
+ Price
+ Volume
+ Change
+
+
+
+ {
+ markets.map((market) => (
+
+
+
+
+ {market.coin}
+ {market.price}
+ {market.volume}
+
+
+
+
+ ))
+ }
+
+
+
+
-
-
-
- LTC/BTC
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Operations
-
-
-
-
+
+
+
+ LTC/BTC
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
The final amount could change depending on current market conditions.
+
+
+
-
-
-
- Sell Orders
-
- View all
-
-
-
-
-
- Price
- BTC
- Sum(BTC)
-
-
-
- {
- orders.sell_orders.map((order) => (
-
- {order.price}
- {order.btc}
- {order.sum}
-
- ))
- }
-
-
-
-
+
+
+
+ Sell Orders
+
+ View all
+
+
+
+
+
+ Price
+ BTC
+ Sum(BTC)
+
+
+
+ {
+ orders.sell_orders.map((order) => (
+
+ {order.price}
+ {order.btc}
+ {order.sum}
+
+ ))
+ }
+
+
+
+
-
-
-
- Buy Orders
-
- View all
-
-
-
-
-
- Price
- BTC
- Sum(BTC)
-
-
-
- {
- orders.buy_orders.map((order) => (
-
- {order.price}
- {order.btc}
- {order.sum}
-
- ))
- }
-
-
-
-
-
-
-
+
+
+
+ Buy Orders
+
+ View all
+
+
+
+
+
+ Price
+ BTC
+ Sum(BTC)
+
+
+
+ {
+ orders.buy_orders.map((order) => (
+
+ {order.price}
+ {order.btc}
+ {order.sum}
+
+ ))
+ }
+
+
+
+
+
+
+
diff --git a/preview/pages/email-inbox.astro b/preview/pages/email-inbox.astro
index b6bdb38f0..59b02a1ec 100644
--- a/preview/pages/email-inbox.astro
+++ b/preview/pages/email-inbox.astro
@@ -1,212 +1,208 @@
---
-import Subheader from '@ui/Subheader.astro'
-import ButtonGroup from '@ui/ButtonGroup.astro'
-import DefaultLayout from '@shared/layouts/DefaultLayout.astro'
-import Button from '@ui/Button.astro'
-import Icon from '@ui/Icon.astro'
-import Card from '@ui/Card.astro'
-import Offcanvas from '@ui/Offcanvas.astro'
-import Progress from '@ui/Progress.astro'
-import Modal from '@shared/components/modals/Modal.astro'
-import NewEmailModalContent from '@shared/components/modals/NewEmailModalContent.astro'
-import mails from '@data/mails.json'
----
-
-
-
+
+
+
+
+
+
+
diff --git a/preview/pages/emails.astro b/preview/pages/emails.astro
index 0d2b176b3..d71f66e10 100644
--- a/preview/pages/emails.astro
+++ b/preview/pages/emails.astro
@@ -1,95 +1,107 @@
---
// Masonry auto-inits from the data-masonry attribute (page-lib, no init script,
// like cards-masonry.astro); fslightbox is a pure page-lib (no init script).
-import DefaultLayout from '@shared/layouts/DefaultLayout.astro'
-import CardStamp from '@ui/CardStamp.astro'
-import Prose from '@ui/Prose.astro'
-import { site } from '@shared/lib/site'
-import emails from '@data/emails.json'
+import DefaultLayout from '@shared/layouts/DefaultLayout.astro';
+import CardStamp from '@ui/CardStamp.astro';
+import Prose from '@ui/Prose.astro';
+import CaptureScript from '@shared/components/CaptureScript.astro';
+import { site } from '@shared/lib/site';
+import emails from '@data/emails.json';
-const emailEntries = Object.entries(emails)
+const emailEntries = Object.entries(emails);
---
-
-
-
-
-
-
-
-
-
Tabler Emails
-
- {emailEntries.length} eye-catching, customizable and responsive email templates to improve your email communication. No coding skills needed.
-
-
-
-
-
-
-
-
-
- {
- emailEntries.map(([key, email]) => (
-
- ))
- }
-
-
-
+
+
+
+
+
+
+
+
+
Tabler Emails
+
+ {emailEntries.length} eye-catching, customizable and responsive email templates to improve your email communication. No coding skills needed.
+
+
+
+
+
+
+
+
+
+ {emailEntries.map(([key, email]) => (
+
+ ))}
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
+ emailModal.querySelector('[data-email-title]').textContent = title;
+ emailModal.querySelector('[data-email-description]').textContent = description;
+ emailModal.querySelector('[data-email-image]').src = image;
+ });
+ }
+
+
+
diff --git a/preview/pages/flags.astro b/preview/pages/flags.astro
index 288d1e5e1..d9ff9c47a 100644
--- a/preview/pages/flags.astro
+++ b/preview/pages/flags.astro
@@ -1,32 +1,37 @@
---
-import DefaultLayout from '@shared/layouts/DefaultLayout.astro'
-import Card from '@ui/Card.astro'
-import CardHeader from '@ui/CardHeader.astro'
-import CardBody from '@ui/CardBody.astro'
-import flags from '@data/flags.json'
+import DefaultLayout from '@shared/layouts/DefaultLayout.astro';
+import Card from '@ui/Card.astro';
+import CardHeader from '@ui/CardHeader.astro';
+import CardBody from '@ui/CardBody.astro';
+import flags from '@data/flags.json';
-// Liquid: {% for icon in (0..20) %}
{% endfor %} — 21 filler divs
-const fillers = Array.from({ length: 21 })
+// 21 filler divs for flexbox layout.
+const fillers = Array.from({ length: 21 });
---
-
-
- List of all flags
-
-
-
-
- {
- flags.map((country) => (
-
-
-
- ))
- }
- {fillers.map(() =>
)}
-
-
-
-
+
+
+ List of all flags
+
+
+
+
+ {
+ flags.map((country) => (
+
+
+
+ ))
+ }
+ {fillers.map(() =>
)}
+
+
+
+
diff --git a/preview/pages/gallery.astro b/preview/pages/gallery.astro
index 1f6aeb7da..6ca0db9a1 100644
--- a/preview/pages/gallery.astro
+++ b/preview/pages/gallery.astro
@@ -1,26 +1,32 @@
---
-import DefaultLayout from '@shared/layouts/DefaultLayout.astro'
-import GalleryPhoto from '@shared/components/cards/GalleryPhoto.astro'
-import Pagination from '@ui/Pagination.astro'
-import photos from '@data/photos.json'
-import people from '@data/people.json'
+import DefaultLayout from '@shared/layouts/DefaultLayout.astro';
+import GalleryPhoto from '@shared/components/cards/GalleryPhoto.astro';
+import Pagination from '@ui/Pagination.astro';
+import photos from '@data/photos.json';
+import people from '@data/people.json';
-// Liquid: photos | where: "horizontal", true — limit: 15; person = people[forloop.index0]
-const galleryPhotos = photos.filter((photo) => photo.horizontal).slice(0, 15)
+// Horizontal photos, limit 15; person = people[loop index].
+const galleryPhotos = photos.filter((photo) => photo.horizontal).slice(0, 15);
---
-
-
- {
- galleryPhotos.map((photo, index) => (
-
-
-
- ))
- }
-
+
+
+ {
+ galleryPhotos.map((photo, index) => (
+
+
+
+ ))
+ }
+
-
+
diff --git a/preview/pages/illustrations.astro b/preview/pages/illustrations.astro
index 679a26827..183bf84cd 100644
--- a/preview/pages/illustrations.astro
+++ b/preview/pages/illustrations.astro
@@ -1,195 +1,207 @@
---
-import DefaultLayout from '@shared/layouts/DefaultLayout.astro'
-import Icon from '@ui/Icon.astro'
-import CardStamp from '@ui/CardStamp.astro'
-import Card from '@ui/Card.astro'
-import CardBody from '@ui/CardBody.astro'
-import Prose from '@ui/Prose.astro'
-import freeIllustrations from '@data/free-illustrations.json'
-import illustrationsList from '@data/illustrations.json'
-import siteData from '@data/site.json'
+import DefaultLayout from '@shared/layouts/DefaultLayout.astro';
+import Icon from '@ui/Icon.astro';
+import CardStamp from '@ui/CardStamp.astro';
+import Card from '@ui/Card.astro';
+import CardBody from '@ui/CardBody.astro';
+import Prose from '@ui/Prose.astro';
+import CaptureScript from '@shared/components/CaptureScript.astro';
+import freeIllustrations from '@data/free-illustrations.json';
+import illustrationsList from '@data/illustrations.json';
+import siteData from '@data/site.json';
-const autodark = (freeIllustrations as { autodark: Record }).autodark
-const autodarkEntries = Object.entries(autodark)
+const autodark = (freeIllustrations as { autodark: Record }).autodark;
+const autodarkEntries = Object.entries(autodark);
-// first-illustration: the Liquid loop overwrites each pass → last value wins.
-const firstIllustration = autodarkEntries.length ? autodarkEntries[autodarkEntries.length - 1][1] : ''
+// Last autodark entry (loop overwrites each pass).
+const firstIllustration = autodarkEntries.length
+ ? autodarkEntries[autodarkEntries.length - 1][1]
+ : '';
-// Page-local transform: replace: ' svg.replaceAll(' svg.replaceAll('
-const skinColors = siteData.skinColors as Record
-const colorEntries = Object.values(colors)
-const skinEntries = Object.values(skinColors)
+const colors = siteData.colors as Record;
+const skinColors = siteData.skinColors as Record;
+const colorEntries = Object.values(colors);
+const skinEntries = Object.values(skinColors);
-// skinColor = site.skinColors | first → the first value object (Rose).
-const skinFirst = skinEntries[0]
-const buyLink = (siteData.illustrations as { buy_link: string }).buy_link
+const skinFirst = skinEntries[0];
+const buyLink = (siteData.illustrations as { buy_link: string }).buy_link;
-// {{ illustrations | size | minus: 4 }}
-const moreCount = illustrationsList.length - 4
+const moreCount = illustrationsList.length - 4;
-// {% capture_script %} — build the illustrations JS map from the same data.
-// skin_color / color[1].prop resolve to empty in Liquid → literal "var()".
-const illustrationsData = Object.fromEntries(autodarkEntries.map(([key, svg]) => [key, { svg: withClass(svg) }]))
+const illustrationsData = Object.fromEntries(
+ autodarkEntries.map(([key, svg]) => [key, { svg: withClass(svg) }]),
+);
---
-
-
-
-
-
-
-
-
-
-
-
-
-
Primary color
-
+
+
+
+
+
+
+
+
+
+
+
+
+
Primary color
+
-
Skin color
-
- {
- skinEntries.map((color, i) => (
-
-
-
-
-
-
- ))
- }
-
+
Skin color
+
+ {
+ skinEntries.map((color, i) => (
+
+
+
+
+
+
+ ))
+ }
+
-
Select SVG illustration
-
- {
- autodarkEntries.map(([key, svg], i) => (
-
-
-
-
-
-
- ))
- }
-
-
-
-
-
-
-
-
-
-
-
+ Select SVG illustration
+
+ {
+ autodarkEntries.map(([key, svg], i) => (
+
+
+
+
+
+
+ ))
+ }
+
+
+
+
+
+
+
+
+
+
+
-
- {moreCount} more SVG Illustrations
-
+
+ {moreCount} more SVG Illustrations
+
-
-
-
-
-
-
-
-
Tabler Illustrations
-
Access a wide range of SVG illustrations for various projects. Effortlessly customize any illustration to align perfectly with your chosen color scheme!
-
-
-
-
-
-
-
-
- {
- illustrationsList.map((illustration) => (
-
- ))
- }
-
-
-
-
-
-
+ document.querySelectorAll('.js-select-skin-color').forEach((elem) => {
+ elem.addEventListener('change', (e) => {
+ skinColor = e.target.value;
+ document.getElementById('current-illustration-style').style.setProperty('--tblr-illustrations-skin', skinColor);
+ });
+ });
+
+
+
diff --git a/preview/pages/inline-player.astro b/preview/pages/inline-player.astro
index cb0d44b37..e105ce6ba 100644
--- a/preview/pages/inline-player.astro
+++ b/preview/pages/inline-player.astro
@@ -1,29 +1,34 @@
---
-// Liquid: {% for provider in inline-players %} → one card per provider.
-import DefaultLayout from '@shared/layouts/DefaultLayout.astro'
-import Card from '@ui/Card.astro'
-import CardBody from '@ui/CardBody.astro'
-import CardTitle from '@ui/CardTitle.astro'
-import InlinePlayer from '@ui/InlinePlayer.astro'
-import players from '@data/inline-players.json'
+// One card per inline-player provider.
+import DefaultLayout from '@shared/layouts/DefaultLayout.astro';
+import Card from '@ui/Card.astro';
+import CardBody from '@ui/CardBody.astro';
+import CardTitle from '@ui/CardTitle.astro';
+import InlinePlayer from '@ui/InlinePlayer.astro';
+import players from '@data/inline-players.json';
-type Provider = { 'title': string; 'type': string; 'id': string; 'embed-id': string | number }
+type Provider = { title: string; type: string; id: string; 'embed-id': string | number };
---
-
-
- {
- (players as Provider[]).map((provider) => (
-
-
-
- {provider.title}
+
+
+ {
+ (players as Provider[]).map((provider) => (
+
+
+
+ {provider.title}
-
-
-
-
- ))
- }
-
+
+
+
+
+ ))
+ }
+
diff --git a/preview/pages/job-listing.astro b/preview/pages/job-listing.astro
index 8b7a8ee9d..2b8d2d6f1 100644
--- a/preview/pages/job-listing.astro
+++ b/preview/pages/job-listing.astro
@@ -1,152 +1,151 @@
---
-// NOTE: page-header-actions "add-job" requires PageHeader.astro to dispatch it
-// to HeaderActionsAddJob — see the migration report (PageHeader.astro is a
-// shared component and was not modified here).
-import DefaultLayout from '@shared/layouts/DefaultLayout.astro'
-import Icon from '@ui/Icon.astro'
-import Card from '@ui/Card.astro'
-import Avatar from '@ui/Avatar.astro'
-import Check from '@ui/form/Check.astro'
-import jobsData from '@data/jobs.json'
+import DefaultLayout from '@shared/layouts/DefaultLayout.astro';
+import Icon from '@ui/Icon.astro';
+import Card from '@ui/Card.astro';
+import Avatar from '@ui/Avatar.astro';
+import Check from '@ui/form/Check.astro';
+import jobsData from '@data/jobs.json';
interface Job {
- company: string
- location: string
- title: string
- type: string
- image: string
- salary?: string
- tags: string[]
+ company: string;
+ location: string;
+ title: string;
+ type: string;
+ image: string;
+ salary?: string;
+ tags: string[];
}
-const jobs = jobsData as Job[]
+const jobs = jobsData as Job[];
-const types = ['Programming', 'Design', 'Management / Finance', 'Customer Support', 'Sales / Marketing']
-const salaries = ['$20K - $50K', '$50K - $100K', '> $100K', 'Drawing / Painting']
+const types = ['Programming', 'Design', 'Management / Finance', 'Customer Support', 'Sales / Marketing'];
+const salaries = ['$20K - $50K', '$50K - $100K', '> $100K', 'Drawing / Painting'];
---
-
-
-
- Job Types
-
- {
- types.map((type, i) => (
-
-
- {type}
-
- ))
- }
-
+
+
+
+ Job Types
+
+ {
+ types.map((type, i) => (
+
+
+ {type}
+
+ ))
+ }
+
- Remote
-
-
-
+ Remote
+
+
+
- Salary Range
-
- {
- salaries.map((salary, i) => (
-
-
- {salary}
-
- ))
- }
-
+ Salary Range
+
+ {
+ salaries.map((salary, i) => (
+
+
+ {salary}
+
+ ))
+ }
+
- Immigration
-
-
+
Immigration
+
+
-
Only show companies that can sponsor a visa
-
+
Only show companies that can sponsor a visa
+
- Location
-
-
- Anywhere
- London
- San Francisco
- New York
- Berlin
-
-
+ Location
+
+
+ Anywhere
+ London
+ San Francisco
+ New York
+ Berlin
+
+
-
-
-
-
-
-
- {
- jobs.map((job) => (
-
-
-
-
-
-
-
- {job.salary &&
{job.salary}
}
-
-
-
-
-
- {job.company}
-
-
- {job.type}
-
-
- {job.location}
-
-
-
-
- {job.company}
-
-
- {job.type}
-
-
- {job.location}
-
-
-
-
-
- {job.tags.map((tag) => (
-
- {tag}
-
- ))}
-
-
-
-
-
-
-
- ))
- }
-
-
-
-
+
+
+
+
+
+
+ {
+ jobs.map((job) => (
+
+
+
+
+
+
+
+ {job.salary &&
{job.salary}
}
+
+
+
+
+
+ {job.company}
+
+
+ {job.type}
+
+
+ {job.location}
+
+
+
+
+ {job.company}
+
+
+ {job.type}
+
+
+ {job.location}
+
+
+
+
+
+ {job.tags.map((tag) => (
+
{tag}
+ ))}
+
+
+
+
+
+
+
+ ))
+ }
+
+
+
+
diff --git a/preview/pages/license.astro b/preview/pages/license.astro
index 186c6d9eb..668f71a12 100644
--- a/preview/pages/license.astro
+++ b/preview/pages/license.astro
@@ -1,16 +1,10 @@
---
-// The prose is shared/includes/license.md rendered via `renderContent: "md"`
-// (markdown-it). The rendered HTML is inlined verbatim from the reference build
-// to guarantee a 1:1 DOM match — Astro's markdown pipeline (remark + smartypants)
-// would produce different quote characters and structure.
-// TODO: source of truth remains shared/includes/license.md; regenerate this
-// block if the markdown changes.
-import DefaultLayout from '@shared/layouts/DefaultLayout.astro'
-import Icon from '@ui/Icon.astro'
-import Card from '@ui/Card.astro'
-import CardBody from '@ui/CardBody.astro'
-import CardFooter from '@ui/CardFooter.astro'
-import Prose from '@ui/Prose.astro'
+import DefaultLayout from '@shared/layouts/DefaultLayout.astro';
+import Icon from '@ui/Icon.astro';
+import Card from '@ui/Card.astro';
+import CardBody from '@ui/CardBody.astro';
+import CardFooter from '@ui/CardFooter.astro';
+import Prose from '@ui/Prose.astro';
const licenseHtml = `
This is a legal agreement between you, the Purchaser, and Tabler. Purchasing or downloading of any Tabler product (Tabler Free, Tabler PRO, Tabler Email), constitutes your acceptance of the terms of this license, Tabler terms of service and Tabler private policy .
@@ -37,60 +31,66 @@ const licenseHtml = `
You cannot add our source code to any open source repository.
The source code may not be placed on any website in a complete or archived downloadable format.
-`
+`;
---
-
-
-
-
-
-
-
-
-
-
- tabler/tabler is licensed under the
-
MIT License
-
-
+
+
+
+
+
+
+
+
+
+
+ tabler/tabler is licensed under the
+
MIT License
+
+
- A short and simple permissive license with conditions only requiring preservation of copyright and license notices. Licensed works, modifications, and larger works may be distributed under different terms and without source code.
+
+ A short and simple permissive license with conditions only requiring preservation of copyright and
+ license notices. Licensed works, modifications, and larger works may be distributed under different terms
+ and without source code.
+
- Permissions
-
- Commercial use
- Modification
- Distribution
- Private use
-
+ Permissions
- Limitations
-
+
+ Commercial use
+ Modification
+ Distribution
+ Private use
+
- Conditions
-
- License and copyright notice
-
-
-
- This is not legal advice.
- Learn more about repository licenses.
-
-
-
-
+
+ Limitations
+
+
+ Conditions
+
+ License and copyright notice
+
+
+
+ This is not legal advice.
+ Learn more about repository licenses.
+
+
+
+
diff --git a/preview/pages/lightbox.astro b/preview/pages/lightbox.astro
index 6f9b7eaa7..eb99bac4e 100644
--- a/preview/pages/lightbox.astro
+++ b/preview/pages/lightbox.astro
@@ -1,23 +1,27 @@
---
-// Liquid: photos | where: "horizontal", true — the gallery iterates the filtered
-// list for both the fslightbox href and the Photo include.
-import DefaultLayout from '@shared/layouts/DefaultLayout.astro'
-import Photo from '@ui/Photo.astro'
-import photos from '@data/photos.json'
+// Gallery uses horizontal photos only.
+import DefaultLayout from '@shared/layouts/DefaultLayout.astro';
+import Photo from '@ui/Photo.astro';
+import photos from '@data/photos.json';
-const filteredPhotos = photos.filter((photo) => photo.horizontal)
+const filteredPhotos = photos.filter((photo) => photo.horizontal);
---
-
-
- {
- filteredPhotos.map((photo) => (
-
- ))
- }
-
+
+
+ {
+ filteredPhotos.map((photo) => (
+
+ ))
+ }
+
diff --git a/preview/pages/map-fullsize.astro b/preview/pages/map-fullsize.astro
index 220119181..1d0148be9 100644
--- a/preview/pages/map-fullsize.astro
+++ b/preview/pages/map-fullsize.astro
@@ -1,40 +1,37 @@
---
-// Front matter: layout-wrapper-full + layout-sidebar + layout-hide-topbar,
-// page-libs: [google-maps], page-menu: plugins.map-fullsize. No title / no
-// page-header (the page-header block renders empty in the reference).
-//
-// {% assign map-id = 'google' %} → id="map-google".
-// The {% capture_script %} block is registered synchronously (before any await).
-// We mirror the development build (environment == 'development'), so the
-// `window.tabler_map` bookkeeping lines are emitted (as in the reference).
-//
-// REQUIRES DefaultLayout `wrapperFull` support (see report): page-wrapper-full
-// class + slot rendered WITHOUT the .container-xl wrapper. Not implemented in
-// the shared DefaultLayout yet — flagged rather than modified here.
-import DefaultLayout from '@shared/layouts/DefaultLayout.astro'
+import DefaultLayout from '@shared/layouts/DefaultLayout.astro';
+import CaptureScript from '@shared/components/CaptureScript.astro';
---
-
-
-
-
-
-
+ document.readyState !== 'loading' ? initMap() : document.addEventListener('DOMContentLoaded', initMap, { once: true });
+
+
+
diff --git a/preview/pages/maps.astro b/preview/pages/maps.astro
index cae1704dd..e6500c1ed 100644
--- a/preview/pages/maps.astro
+++ b/preview/pages/maps.astro
@@ -1,37 +1,36 @@
---
-// Liquid: {% for map in maps %} — card maps span a full-width column with no
-// card-body; non-card maps get a card-body with a title.
-import DefaultLayout from '@shared/layouts/DefaultLayout.astro'
-import Map from '@ui/Map.astro'
-import Card from '@ui/Card.astro'
-import CardBody from '@ui/CardBody.astro'
-import maps from '@data/maps.json'
+// Card maps span full-width column with no card-body; non-card maps get card-body with title.
+import DefaultLayout from '@shared/layouts/DefaultLayout.astro';
+import Map from '@ui/Map.astro';
+import Card from '@ui/Card.astro';
+import CardBody from '@ui/CardBody.astro';
+import maps from '@data/maps.json';
-type MapData = { title?: string; card?: boolean }
-const mapEntries = Object.entries(maps as Record)
+type MapData = { title?: string; card?: boolean };
+const mapEntries = Object.entries(maps as Record);
---
-
- {
- mapEntries.map(([mapId, data]) =>
- data.card ? (
-
-
-
-
-
- ) : (
-
-
-
- {data.title}
-
-
-
-
- ),
- )
- }
-
+
+ {
+ mapEntries.map(([mapId, data]) =>
+ data.card ? (
+
+
+
+
+
+ ) : (
+
+
+
+ {data.title}
+
+
+
+
+ ),
+ )
+ }
+
diff --git a/preview/pages/markdown.astro b/preview/pages/markdown.astro
index fcbf832ef..a24c3cb24 100644
--- a/preview/pages/markdown.astro
+++ b/preview/pages/markdown.astro
@@ -1,11 +1,5 @@
---
-import RedirectLayout from '@shared/layouts/RedirectLayout.astro'
-
-// Front matter in preview/pages/markdown.html sets `redirect.to: prose.html`,
-// but shared/includes/redirect.html reads `page.redirect.to` — and in Eleventy
-// `page` is the built-in page object (no front matter), so the include gets an
-// empty url. The reference build (markdown.html) therefore redirects to the
-// relative root ".". We reproduce that exactly by passing no url.
+import RedirectLayout from '@shared/layouts/RedirectLayout.astro';
---
diff --git a/preview/pages/marketing/pricing.astro b/preview/pages/marketing/pricing.astro
index 20f622d29..8fd6fec84 100644
--- a/preview/pages/marketing/pricing.astro
+++ b/preview/pages/marketing/pricing.astro
@@ -1,21 +1,19 @@
---
-// The `description` front matter is not emitted by the base layout (no
-// page-specific in the reference build) — no prop.
-import MarketingLayout from '@shared/layouts/MarketingLayout.astro'
-import Pricing from '@shared/components/marketing/sections/Pricing.astro'
-import PricingBanner from '@shared/components/marketing/sections/PricingBanner.astro'
-import Faq from '@shared/components/marketing/sections/Faq.astro'
+import MarketingLayout from '@shared/layouts/MarketingLayout.astro';
+import Pricing from '@shared/components/marketing/sections/Pricing.astro';
+import PricingBanner from '@shared/components/marketing/sections/PricingBanner.astro';
+import Faq from '@shared/components/marketing/sections/Faq.astro';
---
-
+
-
-
-
+
+
+
diff --git a/preview/pages/modals.astro b/preview/pages/modals.astro
index 643524843..85c374a2c 100644
--- a/preview/pages/modals.astro
+++ b/preview/pages/modals.astro
@@ -1,164 +1,169 @@
---
-// Every modal is rendered inline (`{% include "ui/modal.html" ... inline show %}`)
-// via ModalInline (renders in place, unlike Modal.astro which drains to ).
-import DefaultLayout from '@shared/layouts/DefaultLayout.astro'
-import Card from '@ui/Card.astro'
-import CardBody from '@ui/CardBody.astro'
-import ModalInline from '@shared/components/modals/ModalInline.astro'
-import SimpleModalContent from '@shared/components/modals/SimpleModalContent.astro'
-import LargeModalContent from '@shared/components/modals/LargeModalContent.astro'
-import SmallModalContent from '@shared/components/modals/SmallModalContent.astro'
-import FullWidthModalContent from '@shared/components/modals/FullWidthModalContent.astro'
-import ScrollableModalContent from '@shared/components/modals/ScrollableModalContent.astro'
-import ReportModalContent from '@shared/components/modals/ReportModalContent.astro'
-import SuccessModalContent from '@shared/components/modals/SuccessModalContent.astro'
-import DangerModalContent from '@shared/components/modals/DangerModalContent.astro'
-import TeamModalContent from '@shared/components/modals/TeamModalContent.astro'
-import SignatureModalContent from '@shared/components/modals/SignatureModalContent.astro'
-import NewEmailModalContent from '@shared/components/modals/NewEmailModalContent.astro'
-import NewEventModalContent from '@shared/components/modals/NewEventModalContent.astro'
-import NewTaskModalContent from '@shared/components/modals/NewTaskModalContent.astro'
-import EditProfileModalContent from '@shared/components/modals/EditProfileModalContent.astro'
-import ConfirmDeleteModalContent from '@shared/components/modals/ConfirmDeleteModalContent.astro'
-import ChangePasswordModalContent from '@shared/components/modals/ChangePasswordModalContent.astro'
-import AddTaskModalContent from '@shared/components/modals/AddTaskModalContent.astro'
+// Every modal is rendered inline via ModalInline (in place, unlike Modal.astro
+// which drains to ).
+import DefaultLayout from '@shared/layouts/DefaultLayout.astro';
+import Card from '@ui/Card.astro';
+import CardBody from '@ui/CardBody.astro';
+import ModalInline from '@shared/components/modals/ModalInline.astro';
+import SimpleModalContent from '@shared/components/modals/SimpleModalContent.astro';
+import LargeModalContent from '@shared/components/modals/LargeModalContent.astro';
+import SmallModalContent from '@shared/components/modals/SmallModalContent.astro';
+import FullWidthModalContent from '@shared/components/modals/FullWidthModalContent.astro';
+import ScrollableModalContent from '@shared/components/modals/ScrollableModalContent.astro';
+import ReportModalContent from '@shared/components/modals/ReportModalContent.astro';
+import SuccessModalContent from '@shared/components/modals/SuccessModalContent.astro';
+import DangerModalContent from '@shared/components/modals/DangerModalContent.astro';
+import TeamModalContent from '@shared/components/modals/TeamModalContent.astro';
+import SignatureModalContent from '@shared/components/modals/SignatureModalContent.astro';
+import NewEmailModalContent from '@shared/components/modals/NewEmailModalContent.astro';
+import NewEventModalContent from '@shared/components/modals/NewEventModalContent.astro';
+import NewTaskModalContent from '@shared/components/modals/NewTaskModalContent.astro';
+import EditProfileModalContent from '@shared/components/modals/EditProfileModalContent.astro';
+import ConfirmDeleteModalContent from '@shared/components/modals/ConfirmDeleteModalContent.astro';
+import ChangePasswordModalContent from '@shared/components/modals/ChangePasswordModalContent.astro';
+import AddTaskModalContent from '@shared/components/modals/AddTaskModalContent.astro';
-const cardClass = 'position-relative rounded d-block bg-surface-backdrop py-6 w-auto h-auto z-0'
+const cardClass = 'position-relative rounded d-block bg-surface-backdrop py-6 w-auto h-auto z-0';
// add-task bakes `show` into the class string (no `show` flag) → aria-hidden="true".
-const addTaskClass = 'position-relative rounded d-block show bg-surface-backdrop py-6 w-auto h-auto z-0'
+const addTaskClass = 'position-relative rounded d-block show bg-surface-backdrop py-6 w-auto h-auto z-0';
---
-
-
-
-
-
-
-
-
-
Simple modal
-
-
-
-
-
-
Large modal
-
-
-
-
-
-
Small modal
-
-
-
-
-
-
Full width modal
-
-
-
-
-
-
Scrollable modal
-
-
-
-
-
-
Modal with form
-
-
-
-
-
-
Success modal
-
-
-
-
-
-
Danger modal
-
-
-
-
-
-
Modal with simple form
-
-
-
-
-
-
Modal with signature form
-
-
-
-
-
-
New email modal
-
-
-
-
-
-
New event modal
-
-
-
-
-
-
New task modal
-
-
-
-
-
-
Edit profile modal
-
-
-
-
-
-
Confirm delete modal
-
-
-
-
-
-
Change password modal
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
Simple modal
+
+
+
+
+
+
Large modal
+
+
+
+
+
+
Small modal
+
+
+
+
+
+
Full width modal
+
+
+
+
+
+
Scrollable modal
+
+
+
+
+
+
Modal with form
+
+
+
+
+
+
Success modal
+
+
+
+
+
+
Danger modal
+
+
+
+
+
+
Modal with simple form
+
+
+
+
+
+
Modal with signature form
+
+
+
+
+
+
New email modal
+
+
+
+
+
+
New event modal
+
+
+
+
+
+
New task modal
+
+
+
+
+
+
Edit profile modal
+
+
+
+
+
+
Confirm delete modal
+
+
+
+
+
+
Change password modal
+
+
+
+
+
+
+
+
+
+
diff --git a/preview/pages/music.astro b/preview/pages/music.astro
index 75b1ec7d9..39178f2a7 100644
--- a/preview/pages/music.astro
+++ b/preview/pages/music.astro
@@ -1,29 +1,28 @@
---
-import DefaultLayout from '@shared/layouts/DefaultLayout.astro'
-import TracksList from '@shared/components/cards/music/TracksList.astro'
-import TrackInfo from '@shared/components/cards/music/TrackInfo.astro'
+import DefaultLayout from '@shared/layouts/DefaultLayout.astro';
+import TracksList from '@shared/components/cards/music/TracksList.astro';
+import TrackInfo from '@shared/components/cards/music/TrackInfo.astro';
-// Liquid: {% for i in (8..13) %}
-const topTrackIds = [8, 9, 10, 11, 12, 13]
+const topTrackIds = [8, 9, 10, 11, 12, 13];
---
-
-
-
-
-
-
Top tracks
+
+
+
+
+
+
Top tracks
-
- {
- topTrackIds.map((trackId) => (
-
-
-
- ))
- }
-
-
-
+
+ {
+ topTrackIds.map((trackId) => (
+
+
+
+ ))
+ }
+
+
+
diff --git a/preview/pages/navigation.astro b/preview/pages/navigation.astro
index 2a38386f3..daf214fa6 100644
--- a/preview/pages/navigation.astro
+++ b/preview/pages/navigation.astro
@@ -1,41 +1,49 @@
---
// with variant params, rendered with the shared Navbar component.
//
-// Note: fluid-search (variant 5) is a no-op — the search block in the Liquid
-// condensed branch is dead code (`unless condensed` inside `if condensed`);
-// the prop exists on Navbar only to mirror the include signature.
-import DefaultLayout from '@shared/layouts/DefaultLayout.astro'
-import Navbar from '@shared/components/navbar/Navbar.astro'
+// fluid-search (variant 5) is a no-op — search block in condensed branch is dead code.
+import DefaultLayout from '@shared/layouts/DefaultLayout.astro';
+import Navbar from '@shared/components/navbar/Navbar.astro';
---
-
-
-
-
+
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
-
+
+
+
+
diff --git a/preview/pages/onboarding.astro b/preview/pages/onboarding.astro
index 86f5625c2..cf25b1d80 100644
--- a/preview/pages/onboarding.astro
+++ b/preview/pages/onboarding.astro
@@ -1,128 +1,126 @@
---
-// NOTE: the Liquid source passes `current=2` to ui/progress-steps.html, but that
-// include reads `active` (not `current`) — so the arg is a no-op and only the
-// first step is active. The reference reflects this (Step 1 = bg-primary).
-import BaseLayout from '@shared/layouts/BaseLayout.astro'
-import NavbarLogo from '@shared/components/navbar/NavbarLogo.astro'
-import ProgressSteps from '@ui/ProgressSteps.astro'
-import Button from '@ui/Button.astro'
-import ButtonList from '@ui/ButtonList.astro'
-import FormGroup from '@ui/FormGroup.astro'
+// NOTE: `current=2` passed to ProgressSteps is a no-op (reads `active`, not `current`) — only step 1 is active.
+import BaseLayout from '@shared/layouts/BaseLayout.astro';
+import NavbarLogo from '@shared/components/navbar/NavbarLogo.astro';
+import ProgressSteps from '@ui/ProgressSteps.astro';
+import Button from '@ui/Button.astro';
+import ButtonList from '@ui/ButtonList.astro';
+import FormGroup from '@ui/FormGroup.astro';
---
-
-
-
+
+
+
-
-
+
diff --git a/preview/pages/pay.astro b/preview/pages/pay.astro
index 72b5a9750..ec1312394 100644
--- a/preview/pages/pay.astro
+++ b/preview/pages/pay.astro
@@ -1,92 +1,133 @@
---
-// page-libs [tabler-payments, imask]: `tabler-payments` is not a key in
-// core/libs.json, so it resolves to nothing (only imask emits a
-
-
-
-
-
-
-
- Steps Progress
-
-
-
-
-
-
-
- Progress Background
-
-
-
-
-
-
-
- Progress Background Colors
-
-
-
-
-
-
-
- Progress Description
-
-
-
-
-
-
-
- Progress Description Sizes
-
-
-
-
-
+ document.getElementById('progress-animated-0')!.addEventListener('click', () => setWidth(0));
+ document.getElementById('progress-animated-add-10')!.addEventListener('click', () => setWidth(width + 10));
+ document.getElementById('progress-animated-minus-10')!.addEventListener('click', () => setWidth(width - 10));
+ document.getElementById('progress-animated-100')!.addEventListener('click', () => setWidth(100));
+
+
+
+
+
+
+
+
+
+
+ Steps Progress
+
+
+
+
+
+
+
+
+
+ Progress Background
+
+
+
+
+
+
+
+
+
+ Progress Background Colors
+
+
+
+
+
+
+
+
+
+ Progress Description
+
+
+
+
+
+
+
+
+
+ Progress Description Sizes
+
+
+
+
+
+
diff --git a/preview/pages/screenshot.astro b/preview/pages/screenshot.astro
index d7afdcf4a..74fc6391a 100644
--- a/preview/pages/screenshot.astro
+++ b/preview/pages/screenshot.astro
@@ -1,78 +1,76 @@
---
-// The Liquid `{% for color in site.colors %}` swatches map to site.themeColors
-// (blue…cyan), matching the reference bg-gradient-from-{color} output.
-// The large `{% comment %}…{% endcomment %}` prose/nav-segmented block in the
-// source is a Liquid comment (not rendered) — intentionally omitted.
-import BaseLayout from '@shared/layouts/BaseLayout.astro'
-import NavbarLogo from '@shared/components/navbar/NavbarLogo.astro'
-import { site } from '@shared/lib/site'
+// Color swatches use site.themeColors (blue…cyan).
+// Large prose/nav-segmented block from source template omitted (was commented out).
+import BaseLayout from '@shared/layouts/BaseLayout.astro';
+import NavbarLogo from '@shared/components/navbar/NavbarLogo.astro';
+import { site } from '@shared/lib/site';
---
-
+ .avatar-2 {
+ background-image: url(/static/avatars/032f.jpg);
+ }
+
-
-
-
-
-
-
-
- {
- site.themeColors.map((color) => (
-
- ))
- }
-
-
+
+
+
+
+
+
+
+ {
+ site.themeColors.map((color) => (
+
+ ))
+ }
+
+
-
-
+
+
-
- Light
- Light
- Dark
- Dark
-
-
+
+ Light
+ Light
+ Dark
+ Dark
+
+
diff --git a/preview/pages/sign-in-cover.astro b/preview/pages/sign-in-cover.astro
index 607e08188..34b80de76 100644
--- a/preview/pages/sign-in-cover.astro
+++ b/preview/pages/sign-in-cover.astro
@@ -1,32 +1,31 @@
---
-// Photo id=11 → the 12th entry of the unfiltered photos list (Liquid
-// filtered-photos[11]); no `horizontal` filter is applied here.
-import BaseLayout from '@shared/layouts/BaseLayout.astro'
-import NavbarLogo from '@shared/components/navbar/NavbarLogo.astro'
-import SignInForm from '@shared/components/cards/SignInForm.astro'
-import Photo from '@ui/Photo.astro'
-import photos from '@data/photos.json'
+// Photo id=11 → 12th entry of the unfiltered photos list; no horizontal filter.
+import BaseLayout from '@shared/layouts/BaseLayout.astro';
+import NavbarLogo from '@shared/components/navbar/NavbarLogo.astro';
+import SignInForm from '@shared/components/cards/SignInForm.astro';
+import Photo from '@ui/Photo.astro';
+import photos from '@data/photos.json';
---
-
-
-
-
-
-
+
+
+
+
+
+
-
Login to your account
+
Login to your account
-
+
-
- Don't have account yet?
Sign up
-
-
-
-
-
+
+ Don't have account yet?
Sign up
+
+
+
+
+
diff --git a/preview/pages/sign-in-illustration.astro b/preview/pages/sign-in-illustration.astro
index 2171d90bd..f2b46acb9 100644
--- a/preview/pages/sign-in-illustration.astro
+++ b/preview/pages/sign-in-illustration.astro
@@ -4,8 +4,7 @@ import NavbarLogo from '@shared/components/navbar/NavbarLogo.astro'
import SignInCard from '@shared/components/cards/SignInCard.astro'
import Illustration from '@ui/Illustration.astro'
-// Liquid passes show-header="1" to cards/sign-in.html, but the include renders
-// the header unconditionally — SignInCard.astro mirrors that (no prop needed).
+// show-header prop ignored — SignInCard renders header unconditionally.
---
diff --git a/preview/pages/sign-in-link.astro b/preview/pages/sign-in-link.astro
index 90d8a45c0..c2d3f92f0 100644
--- a/preview/pages/sign-in-link.astro
+++ b/preview/pages/sign-in-link.astro
@@ -1,25 +1,24 @@
---
-import SingleLayout from '@shared/layouts/SingleLayout.astro'
+import SingleLayout from '@shared/layouts/SingleLayout.astro';
-// TODO: Liquid uses {{ site.email }} — add `email` to src/lib/site.ts once it is
-// safe to touch shared files (kept local here to avoid cross-agent conflicts).
-const siteEmail = 'support@tabler.io'
+// TODO: add `email` to src/lib/site.ts — kept local here to avoid cross-agent conflicts.
+const siteEmail = 'support@tabler.io';
---
-
-
-
Check your inbox
+
+
+
Check your inbox
-
- We've sent you a magic link to {siteEmail} .
- Please click the link to confirm your address.
-
-
+
+ We've sent you a magic link to {siteEmail} .
+ Please click the link to confirm your address.
+
+
-
- Can't see the email? Please check the spam folder.
- Wrong email? Please
re-enter your address .
-
-
+
+ Can't see the email? Please check the spam folder.
+ Wrong email? Please
re-enter your address .
+
+
diff --git a/preview/pages/signatures.astro b/preview/pages/signatures.astro
index 6e4e8dd50..ab6353721 100644
--- a/preview/pages/signatures.astro
+++ b/preview/pages/signatures.astro
@@ -1,19 +1,17 @@
---
-// {% capture simple-js %} / {% capture advanced-js %} — raw JS passed as the
-// signature include's extra-js param (appended inside the SignaturePad init).
+// Raw JS passed as Signature `extraJs` (appended inside the SignaturePad init).
+import CardSubtitle from '@ui/CardSubtitle.astro';
+import DefaultLayout from '@shared/layouts/DefaultLayout.astro';
+import Signature from '@ui/Signature.astro';
+import Button from '@ui/Button.astro';
+import ButtonList from '@ui/ButtonList.astro';
+import Card from '@ui/Card.astro';
+import CardBody from '@ui/CardBody.astro';
+import CardTitle from '@ui/CardTitle.astro';
+import FormGroup from '@ui/FormGroup.astro';
+import CaptureModal from '@shared/components/CaptureModal.astro';
-import CardSubtitle from '@ui/CardSubtitle.astro'
-import DefaultLayout from '@shared/layouts/DefaultLayout.astro'
-import Signature from '@ui/Signature.astro'
-import Button from '@ui/Button.astro'
-import ButtonList from '@ui/ButtonList.astro'
-import Card from '@ui/Card.astro'
-import CardBody from '@ui/CardBody.astro'
-import CardTitle from '@ui/CardTitle.astro'
-import FormGroup from '@ui/FormGroup.astro'
-import CaptureModal from '@shared/components/modals/CaptureModal.astro'
-
-const simpleJs = `signaturePad.fromDataURL("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGZpbGw9Im5vbmUiIHZpZXdCb3g9IjAgMCA1OTIgMjAwIj4KICA8ZyBjbGlwLXBhdGg9InVybCgjYSkiPgogICAgPHBhdGggZmlsbD0iIzAwMCIgZD0iTTEzMi4wOTIgNzkuMDY1YzAgLjMwNC0uMzU1IDEuNDAzLTEuMDYzIDMuMjk4LS42OTQgMS44OTQtMS4xNDIgMy4wMjItMS4zNDUgMy4zODQtMi4zNDIgMy44NDctNi43NiA3LjE4LTEzLjI1MiAxMC4wMDEtNS4zNjUgMi4yMjctMTMuNDU1IDUuNTktMjQuMjcgMTAuMDg4YTE0OC4xOSAxNDguMTkgMCAwIDAtMTAuMzQ3IDMuMTI0Yy0yLjY5Ljg4Mi02LjU4NiAyLjk1LTExLjY5IDYuMjA0YTE4Ny42NiAxODcuNjYgMCAwIDAtMS4wNjMgMTAuMTk2IDQwNy40OSA0MDcuNDkgMCAwIDEtMS40MzIgMTUuMzM4Yy0uODgyIDEuMTg2LTEuOTg4IDEuNzc5LTMuMzE4IDEuNzc5LjExNSAwIC4xNzMtLjAxNS4xNzMtLjA0NC0uMjAyLS4xMDEtLjg5Ni0uMjgyLTIuMDgyLS41NDJ2LTIuODg1YzAtMi43MDUuMjMxLTUuNDYuNjk0LTguMjY2IDAtLjA1Ny4yNTMtLjc3My43Ni0yLjE0Ny41MDUtMS4zMzEuNzU4LTIuMTQxLjc1OC0yLjQzIDAtMS42MDUtLjI5Ni0zLjI0LS44ODktNC45MDMtLjM5IDEuMTI4LTEuMTEzIDEuODU5LTIuMTY5IDIuMTkxLTEuMjQ0LS43MjMtMS44NjUtMS45ODgtMS44NjUtMy43OTYgMC0uMjAzIDEuMDQ4LTEuNzI5IDMuMTQ1LTQuNTc4IDIuMTQtMi45NjUgMy4zODMtNS4wMDQgMy43My02LjExNy4yNzUtLjgxLjc2Ny0zLjQ1IDEuNDc1LTcuOTE5IDEuMDQxLTYuNjEgMS45Ni0xMS40NCAyLjc1NS0xNC40OTEgMS42OTItNi42MzkgMy42OTQtMTAuODYyIDYuMDA4LTEyLjY3bDIuNDMgMS44MjN2MS44NjVjLTEuNTA1IDEuMTU3LTIuNTY3IDMuMjYyLTMuMTkgNi4zMTMtLjE4Ny45NC0uNjM2IDMuMzQxLTEuMzQ0IDcuMjAzLS4xNDUuNzA4LS43MjMgMi41NDUtMS43MzUgNS41MS0uOTExIDIuNjAzLTEuMzY3IDQuMTg3LTEuMzY3IDQuNzUxdjUuNDI0Yy4yNi4yNDUuNS4zNjguNzE2LjM2OCAxLjIxNSAwIDMuMTA5LS41MDYgNS42ODMtMS41MTggMy4wOC0xLjE1NyA1LjAxNy0xLjg1MSA1LjgxMy0yLjA4MyAxMy4wNzEtMy4zODQgMjMuMTUtNy4yMzggMzAuMjM1LTExLjU2MyA2LjIxOC0zLjc0NiA5Ljk5Mi02LjI5OCAxMS4zMjItNy42NTggMS41NzYtMS41NjIgMi4zNjQtMy42NjYgMi4zNjQtNi4zMTMgMC00LjY3MS0yLjEwNC04LjU2OS02LjMxMi0xMS42OTMtMy44Ni0yLjkwNy04LjE5MS00LjM2LTEyLjk5Mi00LjM2LTMuNzg4IDAtOC4yNzEuNDYyLTEzLjQ0NyAxLjM4OC02LjY2NiAxLjIxNS0xMi45MiAyLjkwNy0xOC43NjIgNS4wNzYtOC44NjQgMy4yODQtMTMuNzM2IDYuODM0LTE0LjYxOCAxMC42NTJsLTEuMDg1LjgwM0M1OC44MzkgNzguNDIyIDU4IDc3LjA2MiA1OCA3NS43OWMwLTEuODA4IDEuODgtNC4wMTQgNS42NC02LjYxNyAzLjA4LTIuMTQgNS44Mi0zLjU2NSA4LjIyLTQuMjc0IDE1Ljk0OS00LjkxNyAyNy44NjQtNy4zNzYgMzUuNzQ0LTcuMzc2IDcuNDYxIDAgMTMuNDEyIDEuOTUzIDE3Ljg1MSA1Ljg1NyA0LjQyNCAzLjg2MiA2LjYzNyA5LjA5IDYuNjM3IDE1LjY4NVptMzIuMjk1IDU1LjQwN3YxLjkwOWMtLjU5Mi44MjQtMS4yNDMgMS4yMzYtMS45NTIgMS4yMzYtLjQ0OCAwLTEuOTczLTEuMzU5LTQuNTc2LTQuMDc4LTIuNjAzLTIuNzMzLTQuMzM4LTQuMS01LjIwNi00LjEgMCAuMDU4LTMuMDggMS4yNDQtOS4yMzkgMy41NTgtNi4zMDUgMi4zNDMtMTAuMzE3IDMuNTE0LTEyLjAzOCAzLjUxNC0xLjQxNyAwLTIuOTg2LS42OTQtNC43MDctMi4wODMtMS43Mi0xLjM3NC0yLjU4MS0yLjY2OC0yLjU4MS0zLjg4MyAwLTMuMTM4IDMuNDM0LTYuNzk3IDEwLjMwMy0xMC45NzcgNi40MzQtMy45NjMgMTEuMzQzLTUuOTQ0IDE0LjcyNy01Ljk0NCAxLjQxNyAwIDIuNjkuODEgMy44MTcgMi40M3YxLjgyMmMtLjU2NC44MS0xLjIxNCAxLjIxNS0xLjk1MiAxLjIxNS0uMTMgMC0uNjE0LS4xODEtMS40NTMtLjU0My0uODM5LS4zNzYtMS4zMzctLjU2NC0xLjQ5Ni0uNTY0LTEuMTI4IDAtMy43MTcgMS4xNjUtNy43NjUgMy40OTMtMy44MzIgMi4xNjktNi4zOTkgMy43ODktNy43IDQuODYtMS4zMDIgMS4xMjgtMi4xMzMgMi40MTUtMi40OTQgMy44NjEuOTgzLjkyNiAyLjE1NCAxLjM4OCAzLjUxMyAxLjM4OC43MDkgMCA0LjEwNy0xLjE3MSAxMC4xOTQtMy41MTQgNi4wNzMtMi4zNzIgOS43NDYtMy41NTggMTEuMDE5LTMuNTU4IDEuODY1IDAgMy4yMS40NyA0LjAzNCAxLjQxLjE4OC4yMTcuOTA0IDEuNDkgMi4xNDcgMy44MThhMjEuOTAyIDIxLjkwMiAwIDAgMCAzLjQwNSA0LjczWm02OS44ODQtMTkuNzQydjEuOTUzYy0yLjk5MyAyLjQ1OC01Ljg5OSA1LjIxMy04LjcxOSA4LjI2NS04Ljc2MiA5LjQ1OS0xNC42NjIgMTQuMTg4LTE3LjY5OSAxNC4xODgtMi40NTggMC01LjE5OC0yLjIyLTguMjItNi42NmwtNi41MjgtOS42NTRjLTEuMjQ0LjIwMy0zLjQ5MiAyLjQ2Ni02Ljc0NiA2Ljc5LTMuMDUxIDQuMTIyLTUuODQyIDYuMTgzLTguMzcyIDYuMTgzbC0xLjE5My0uNjI5Yy0uODgyLTEuNjItMi41MzgtNS4xNy00Ljk2Ny0xMC42NTItMi42NDYtNS45MjktMy45NjktOS4zMDYtMy45NjktMTAuMTMxIDAtMS40MzIuODQ2LTIuNjE4IDIuNTM4LTMuNTU4IDEuNjkxIDIuMDQgMy4zNTQgNC44NjcgNC45ODggOC40ODMgMi40NTggNS4zMzYgMy44NzUgOC4zMyA0LjI1MSA4Ljk4MWgyLjA2MWMuNTY0LTEuMDQxIDEuODk0LTIuNjU0IDMuOTkxLTQuODM4LjczNy0xLjEyOCAxLjgyMi0yLjY5IDMuMjUzLTQuNjg2IDEuNjc3LTIuMzcyIDMuMDk1LTMuNTU3IDQuMjUxLTMuNTU3IDEuMDcgMCAxLjk3NC41OTIgMi43MTEgMS43NzggMS4wNzEgMS42OTMgMi45ODYgNC43MyA1Ljc0OCA5LjExMiAzLjA1MSA0Ljc0NCA1LjMgNy4xMTYgNi43NDYgNy4xMTYgMS4wNyAwIDUuMDgyLTMuMDgxIDEyLjAzNy05LjI0MiA2Ljk3LTYuMTYxIDEwLjc5NS05LjI0MiAxMS40NzQtOS4yNDJoMi4zNjRabTQ0LjU3MyAxOS41MjVjLTEuMDQyIDEuMjQ0LTIuNDAxIDEuODY2LTQuMDc4IDEuODY2aC0xNC45ODhjLTMuNDcgMC03LjMzOC0uNTIxLTExLjYwNC0xLjU2Mi01LjU5NS0xLjM2LTguNzYyLTMuMDY3LTkuNS01LjEyLS44MDktMi4zMTQtMS4yMTQtNC41MzQtMS4yMTQtNi42NiAwLTQuMjY3IDEuODM2LTcuNTQzIDUuNTA5LTkuODI4IDMuMDk0LTEuOTgxIDYuODc2LTIuOTcyIDExLjM0NC0yLjk3MiAzLjg0NiAwIDYuODk3LjgzMiA5LjE1MyAyLjQ5NSAyLjI1NSAxLjY0OSAzLjM4MyAzLjg5OCAzLjM4MyA2Ljc0NyAwIDEuNTkxLTIuNzExIDMuMzI2LTguMTMzIDUuMjA2LTQuNDgzIDEuNjItNy43NDQgMi40My05Ljc4MiAyLjQzYTQuNDkgNC40OSAwIDAgMS0uNjczLS4wNDNjMCAuMzktLjMxMS44NjgtLjkzMiAxLjQzMi4zMzIuNTY0IDEuOTgxIDEuMjE0IDQuOTQ1IDEuOTUyIDIuNjYuNjggNC41NTUgMS4wMiA1LjY4MiAxLjAyaDEzLjQ3YzAgLjAyOSAxLjY5OS4yODIgNS4wOTcuNzU5IDEuMDcuNjggMS44NDMgMS40MzkgMi4zMjEgMi4yNzhabS0xNy40NjEtMTYuOWMtMS41NzYtMS42MDUtNC4xMTMtMi40MDgtNy42MTMtMi40MDgtMi40NTggMC00LjczNS40NzctNi44MzIgMS40MzItMi43MDQgMS4yNDQtNC4wNTYgMi45MjktNC4wNTYgNS4wNTUgMCAuNzUyLjI1MyAxLjI4Ny43NTkgMS42MDVoMi43MTFjLjk1NSAwIDMuNTUtLjgwMyA3Ljc4Ny0yLjQwOCA0LjIzNy0xLjYyIDYuNjUyLTIuNzEyIDcuMjQ0LTMuMjc2Wm0zMi40OTEtNTMuMzAyLTIuNDA3IDUzLjU2M2MtLjE0NS4xMTUtLjIxNy40NTUtLjIxNyAxLjAxOSAwIC41NjQuMDcyIDEuMzM4LjIxNyAyLjMyMS4zMDQgMi42NjIgMS4wNDEgNy42OTUgMi4yMTIgMTUuMDk5LS4wNTguMjQ2LS41NDIuNTI4LTEuNDUzLjg0N2EyLjkyIDIuOTIgMCAwIDEtLjQ1NS4wNDNjLTIuOTY1IDAtNC44ODgtMy40MjEtNS43Ny0xMC4yNjEtLjAyOS0uMTQ1LS4wNDMtLjQyNy0uMDQzLS44NDYgMC0xLjMzMS4wNzItNC4wNTcuMjE3LTguMTc5LjIwMi01LjMwOC40NDEtMTAuODYyLjcxNS0xNi42NjEuMDg3LTIuMTk4LjQxMi02LjI5MS45NzYtMTIuMjc5LjUzNS01LjQ4MS44MDMtOS41NTIuODAzLTEyLjIxNCAwLTguMDk5LjUzNS0xMy43NiAxLjYwNS0xNi45ODZsLjY1MS0uMDQzaC4xOTVjLjk2OSAwIDEuNjE5LjUzNSAxLjk1MiAxLjYwNS4yNi43MDkuNTI4IDEuNy44MDIgMi45NzJabTExNC4yMTggNjguMTg0Yy0uNjggMS4zMDItMS41MTEgMS45NTMtMi40OTQgMS45NTMtLjQ5MiAwLTEuNjQyLS4zNjktMy40NDktMS4xMDctMi4xNjktLjg4Mi0zLjQzNC0xLjM4MS0zLjc5Ni0xLjQ5Ny0xLjU5LS4zOS0zLjk3Ni0uNzM3LTcuMTU3LTEuMDQxLTMuODQ3LS4zNzYtNi4yMzItLjY4Ny03LjE1OC0uOTMzLTQuNDk3LTEuMTEzLTEwLjAyLTIuNC0xNi41NzEtMy44NjEtMy4wOC0uNjgtNy4wMi0yLjMyMS0xMS44MjEtNC45MjUtNS44Ny0zLjE4Mi04LjgwNi01LjgwNy04LjgwNi03Ljg3NSAwLS40MTkgMy42OC0zLjgxMSAxMS4wNC0xMC4xNzQgOC4wMjYtNi45MTMgMTIuMjc3LTEwLjYwOSAxMi43NTQtMTEuMDg2LjQ2My0uNzA5IDEuMi0xLjcxNCAyLjIxMi0zLjAxNS4yNzUtLjI2IDIuNTc0LTIuMzY1IDYuODk4LTYuMzEzIDMuNDg0LTMuMTk3IDYuMzY5LTUuNzEzIDguNjU0LTcuNTUgMy41NTctMi44NzggNi43MTYtNC45NjggOS40NzgtNi4yNy40MDUuMzc3Ljg0Ni41ODYgMS4zMjMuNjN2My4xMDJsLS4zOS4yMTdhNTkuMDEgNTkuMDEgMCAwIDAtNi4zMTIgNC40NDdjLTYuOTEyIDUuNjI2LTE5LjczIDE3LjgwNC0zOC40NTYgMzYuNTMzIDUuNTM4IDMuOTQ4IDEzLjE3MyA2Ljg4NCAyMi45MDUgOC44MDggMS4wMjYuMTg4IDcuMTg2IDEuNzI4IDE4LjQ3OSA0LjYyMSAxLjM4OC4zMzIgNC4wNDIuOTc2IDcuOTYgMS45My4yODkuMDg3IDEuODU4IDEuMjIyIDQuNzA3IDMuNDA2Wm0tNTguNTQtNTMuMzQ1LTYuNjU5IDU3LjIyOWMtLjQ0OC40NjItMS4yNjUuNjk0LTIuNDUxLjY5NC0yLjAxIDAtMy4wMTUtLjcyMy0zLjAxNS0yLjE3IDAtLjU2NC40NDEtNC4yODEgMS4zMjMtMTEuMTUuMzYyLTIuNTYgMS40NDYtOS45NzIgMy4yNTQtMjIuMjM3LjMxOC0yLjUxNi45MTEtNy4wMjEgMS43NzgtMTMuNTE1LjczOC01LjEyIDEuOC05LjUxIDMuMTg5LTEzLjE2OCAxLjc0OS4zNjEgMi42MjQgMS41MTggMi42MjQgMy40NyAwIC4zMDQtLjAxNC41ODYtLjA0My44NDdabTk5LjQyNSAzMy43MTJjMCAxLjE4Ni0yLjE0NyA0LjE1OC02LjQ0MiA4LjkxNi0zLjc4OCA0LjE4LTYuNjAxIDYuOTQzLTguNDM3IDguMjg4LS45MjYuNjc5LTIuOTE0IDIuMTU1LTUuOTY1IDQuNDI1LTEuMjcyLjg5Ny0zLjIyNCAxLjc4Ni01Ljg1NiAyLjY2OS0yLjY3NS44NjctNC43NzkgMS4zMDEtNi4zMTIgMS4zMDEtMi44NzcgMC01LjI2My0xLjg1MS03LjE1Ny01LjU1NC0xLjU0OC0zLjAyMi0yLjMyMS02LjE2OC0yLjMyMS05LjQzNiAwLTIuNzE5LjU2NC01LjUwMyAxLjY5Mi04LjM1My40MTkgMCAuODUzLS4xOTUgMS4zMDEtLjU4NS41MzUuMzAzIDEuMjcyLjg5NiAyLjIxMiAxLjc3OS0uMjg5IDIuMjI3LS40MzMgNC4xNzItLjQzMyA1LjgzNSAwIDYuODQxIDIuMjE5IDEwLjI2MiA2LjY1OCAxMC4yNjIgNC4yOTUgMCA5LjQ1LTIuNjY5IDE1LjQ2NS04LjAwNi45NTQtLjg1MyA1LjQ3My01LjA3NiAxMy41NTYtMTIuNjY5aC42NzJjLjkxMSAwIDEuMzY3LjM3NiAxLjM2NyAxLjEyOFptMzguNDEyIDEuMDJjMCAuNTkzLS4yOTYgMy45Ny0uODg5IDEwLjEzMWE3MjIuMzI4IDcyMi4zMjggMCAwIDEtMS44NjUgMTYuNzA0Yy0uMDU4LjQ0OS0uNTA3LjY3My0xLjM0NS42NzMtMS4wOTkgMC0yLjAwMy0xLjM4OS0yLjcxMS00LjE2NS0uNTM1LTIuMTEyLS44MDMtMy45ODUtLjgwMy01LjYxOSAwLS42MjIuMzU0LTIuODU3IDEuMDYzLTYuNzA0LjcyMy0zLjgzMiAxLjA4NC02LjA1MiAxLjA4NC02LjY2IDAtMS42NjMtLjQ0OC0yLjQ5NC0xLjM0NC0yLjQ5NC0xLjE1NyAwLTMuNjMgMS4wMDUtNy40MTggMy4wMTUtMy4yODMgMS43NS01LjY2OSAzLjE3NS03LjE1OCA0LjI3NC0yLjg0OCAyLjA5Ny02LjA4IDUuMjUtOS42OTUgOS40NTgtLjU5My42OC0xLjc1NyAxLjk3NC0zLjQ5MiAzLjg4NC0uNjY1LjcwOC0yLjAwMyAxLjA3Ny00LjAxMyAxLjEwNnYtMi44NDJjMC0xLjMxNiAyLjU5Ni00LjIzOCA3Ljc4Ny04Ljc2NGExMTMuMjg2IDExMy4yODYgMCAwIDEgMTMuMDU3LTkuODI4YzYuMzc3LTQuMTUxIDExLjEyLTYuMjI2IDE0LjIyOC02LjIyNi44MjUgMCAxLjYxMy40NDggMi4zNjUgMS4zNDUuNzY2LjkxMSAxLjE0OSAxLjgxNSAxLjE0OSAyLjcxMlpNNTM0IDEzNC40NzJ2MS45MDljLS41OTMuODI0LTEuMjQzIDEuMjM2LTEuOTUyIDEuMjM2LS40NDggMC0xLjk3NC0xLjM1OS00LjU3Ny00LjA3OC0yLjYwMi0yLjczMy00LjMzNy00LjEtNS4yMDUtNC4xIDAgLjA1OC0zLjA4IDEuMjQ0LTkuMjQgMy41NTgtNi4zMDQgMi4zNDMtMTAuMzE3IDMuNTE0LTEyLjAzOCAzLjUxNC0xLjQxNyAwLTIuOTg1LS42OTQtNC43MDYtMi4wODMtMS43MjEtMS4zNzQtMi41ODEtMi42NjgtMi41ODEtMy44ODMgMC0zLjEzOCAzLjQzNC02Ljc5NyAxMC4zMDItMTAuOTc3IDYuNDM1LTMuOTYzIDExLjM0NC01Ljk0NCAxNC43MjgtNS45NDQgMS40MTcgMCAyLjY4OS44MSAzLjgxNyAyLjQzdjEuODIyYy0uNTY0LjgxLTEuMjE1IDEuMjE1LTEuOTUyIDEuMjE1LS4xMyAwLS42MTUtLjE4MS0xLjQ1My0uNTQzLS44MzktLjM3Ni0xLjMzOC0uNTY0LTEuNDk3LS41NjQtMS4xMjggMC0zLjcxNiAxLjE2NS03Ljc2NSAzLjQ5My0zLjgzMiAyLjE2OS02LjM5OCAzLjc4OS03LjcgNC44Ni0xLjMwMSAxLjEyOC0yLjEzMiAyLjQxNS0yLjQ5NCAzLjg2MS45ODMuOTI2IDIuMTU1IDEuMzg4IDMuNTE0IDEuMzg4LjcwOCAwIDQuMTA2LTEuMTcxIDEwLjE5NC0zLjUxNCA2LjA3My0yLjM3MiA5Ljc0Ni0zLjU1OCAxMS4wMTgtMy41NTggMS44NjYgMCAzLjIxLjQ3IDQuMDM1IDEuNDEuMTg3LjIxNy45MDMgMS40OSAyLjE0NyAzLjgxOGEyMS45MDIgMjEuOTAyIDAgMCAwIDMuNDA1IDQuNzNaIi8+CiAgPC9nPgogIDxkZWZzPgogICAgPGNsaXBQYXRoIGlkPSJhIj4KICAgICAgPHBhdGggZmlsbD0iI2ZmZiIgZD0iTTU4IDQ5aDQ3NnYxMDJINTh6Ii8+CiAgICA8L2NsaXBQYXRoPgogIDwvZGVmcz4KPC9zdmc+");`
+const simpleJs = `signaturePad.fromDataURL("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGZpbGw9Im5vbmUiIHZpZXdCb3g9IjAgMCA1OTIgMjAwIj4KICA8ZyBjbGlwLXBhdGg9InVybCgjYSkiPgogICAgPHBhdGggZmlsbD0iIzAwMCIgZD0iTTEzMi4wOTIgNzkuMDY1YzAgLjMwNC0uMzU1IDEuNDAzLTEuMDYzIDMuMjk4LS42OTQgMS44OTQtMS4xNDIgMy4wMjItMS4zNDUgMy4zODQtMi4zNDIgMy44NDctNi43NiA3LjE4LTEzLjI1MiAxMC4wMDEtNS4zNjUgMi4yMjctMTMuNDU1IDUuNTktMjQuMjcgMTAuMDg4YTE0OC4xOSAxNDguMTkgMCAwIDAtMTAuMzQ3IDMuMTI0Yy0yLjY5Ljg4Mi02LjU4NiAyLjk1LTExLjY5IDYuMjA0YTE4Ny42NiAxODcuNjYgMCAwIDAtMS4wNjMgMTAuMTk2IDQwNy40OSA0MDcuNDkgMCAwIDEtMS40MzIgMTUuMzM4Yy0uODgyIDEuMTg2LTEuOTg4IDEuNzc5LTMuMzE4IDEuNzc5LjExNSAwIC4xNzMtLjAxNS4xNzMtLjA0NC0uMjAyLS4xMDEtLjg5Ni0uMjgyLTIuMDgyLS41NDJ2LTIuODg1YzAtMi43MDUuMjMxLTUuNDYuNjk0LTguMjY2IDAtLjA1Ny4yNTMtLjc3My43Ni0yLjE0Ny41MDUtMS4zMzEuNzU4LTIuMTQxLjc1OC0yLjQzIDAtMS42MDUtLjI5Ni0zLjI0LS44ODktNC45MDMtLjM5IDEuMTI4LTEuMTEzIDEuODU5LTIuMTY5IDIuMTkxLTEuMjQ0LS43MjMtMS44NjUtMS45ODgtMS44NjUtMy43OTYgMC0uMjAzIDEuMDQ4LTEuNzI5IDMuMTQ1LTQuNTc4IDIuMTQtMi45NjUgMy4zODMtNS4wMDQgMy43My02LjExNy4yNzUtLjgxLjc2Ny0zLjQ1IDEuNDc1LTcuOTE5IDEuMDQxLTYuNjEgMS45Ni0xMS40NCAyLjc1NS0xNC40OTEgMS42OTItNi42MzkgMy42OTQtMTAuODYyIDYuMDA4LTEyLjY3bDIuNDMgMS44MjN2MS44NjVjLTEuNTA1IDEuMTU3LTIuNTY3IDMuMjYyLTMuMTkgNi4zMTMtLjE4Ny45NC0uNjM2IDMuMzQxLTEuMzQ0IDcuMjAzLS4xNDUuNzA4LS43MjMgMi41NDUtMS43MzUgNS41MS0uOTExIDIuNjAzLTEuMzY3IDQuMTg3LTEuMzY3IDQuNzUxdjUuNDI0Yy4yNi4yNDUuNS4zNjguNzE2LjM2OCAxLjIxNSAwIDMuMTA5LS41MDYgNS42ODMtMS41MTggMy4wOC0xLjE1NyA1LjAxNy0xLjg1MSA1LjgxMy0yLjA4MyAxMy4wNzEtMy4zODQgMjMuMTUtNy4yMzggMzAuMjM1LTExLjU2MyA2LjIxOC0zLjc0NiA5Ljk5Mi02LjI5OCAxMS4zMjItNy42NTggMS41NzYtMS41NjIgMi4zNjQtMy42NjYgMi4zNjQtNi4zMTMgMC00LjY3MS0yLjEwNC04LjU2OS02LjMxMi0xMS42OTMtMy44Ni0yLjkwNy04LjE5MS00LjM2LTEyLjk5Mi00LjM2LTMuNzg4IDAtOC4yNzEuNDYyLTEzLjQ0NyAxLjM4OC02LjY2NiAxLjIxNS0xMi45MiAyLjkwNy0xOC43NjIgNS4wNzYtOC44NjQgMy4yODQtMTMuNzM2IDYuODM0LTE0LjYxOCAxMC42NTJsLTEuMDg1LjgwM0M1OC44MzkgNzguNDIyIDU4IDc3LjA2MiA1OCA3NS43OWMwLTEuODA4IDEuODgtNC4wMTQgNS42NC02LjYxNyAzLjA4LTIuMTQgNS44Mi0zLjU2NSA4LjIyLTQuMjc0IDE1Ljk0OS00LjkxNyAyNy44NjQtNy4zNzYgMzUuNzQ0LTcuMzc2IDcuNDYxIDAgMTMuNDEyIDEuOTUzIDE3Ljg1MSA1Ljg1NyA0LjQyNCAzLjg2MiA2LjYzNyA5LjA5IDYuNjM3IDE1LjY4NVptMzIuMjk1IDU1LjQwN3YxLjkwOWMtLjU5Mi44MjQtMS4yNDMgMS4yMzYtMS45NTIgMS4yMzYtLjQ0OCAwLTEuOTczLTEuMzU5LTQuNTc2LTQuMDc4LTIuNjAzLTIuNzMzLTQuMzM4LTQuMS01LjIwNi00LjEgMCAuMDU4LTMuMDggMS4yNDQtOS4yMzkgMy41NTgtNi4zMDUgMi4zNDMtMTAuMzE3IDMuNTE0LTEyLjAzOCAzLjUxNC0xLjQxNyAwLTIuOTg2LS42OTQtNC43MDctMi4wODMtMS43Mi0xLjM3NC0yLjU4MS0yLjY2OC0yLjU4MS0zLjg4MyAwLTMuMTM4IDMuNDM0LTYuNzk3IDEwLjMwMy0xMC45NzcgNi40MzQtMy45NjMgMTEuMzQzLTUuOTQ0IDE0LjcyNy01Ljk0NCAxLjQxNyAwIDIuNjkuODEgMy44MTcgMi40M3YxLjgyMmMtLjU2NC44MS0xLjIxNCAxLjIxNS0xLjk1MiAxLjIxNS0uMTMgMC0uNjE0LS4xODEtMS40NTMtLjU0My0uODM5LS4zNzYtMS4zMzctLjU2NC0xLjQ5Ni0uNTY0LTEuMTI4IDAtMy43MTcgMS4xNjUtNy43NjUgMy40OTMtMy44MzIgMi4xNjktNi4zOTkgMy43ODktNy43IDQuODYtMS4zMDIgMS4xMjgtMi4xMzMgMi40MTUtMi40OTQgMy44NjEuOTgzLjkyNiAyLjE1NCAxLjM4OCAzLjUxMyAxLjM4OC43MDkgMCA0LjEwNy0xLjE3MSAxMC4xOTQtMy41MTQgNi4wNzMtMi4zNzIgOS43NDYtMy41NTggMTEuMDE5LTMuNTU4IDEuODY1IDAgMy4yMS40NyA0LjAzNCAxLjQxLjE4OC4yMTcuOTA0IDEuNDkgMi4xNDcgMy44MThhMjEuOTAyIDIxLjkwMiAwIDAgMCAzLjQwNSA0LjczWm02OS44ODQtMTkuNzQydjEuOTUzYy0yLjk5MyAyLjQ1OC01Ljg5OSA1LjIxMy04LjcxOSA4LjI2NS04Ljc2MiA5LjQ1OS0xNC42NjIgMTQuMTg4LTE3LjY5OSAxNC4xODgtMi40NTggMC01LjE5OC0yLjIyLTguMjItNi42NmwtNi41MjgtOS42NTRjLTEuMjQ0LjIwMy0zLjQ5MiAyLjQ2Ni02Ljc0NiA2Ljc5LTMuMDUxIDQuMTIyLTUuODQyIDYuMTgzLTguMzcyIDYuMTgzbC0xLjE5My0uNjI5Yy0uODgyLTEuNjItMi41MzgtNS4xNy00Ljk2Ny0xMC42NTItMi42NDYtNS45MjktMy45NjktOS4zMDYtMy45NjktMTAuMTMxIDAtMS40MzIuODQ2LTIuNjE4IDIuNTM4LTMuNTU4IDEuNjkxIDIuMDQgMy4zNTQgNC44NjcgNC45ODggOC40ODMgMi40NTggNS4zMzYgMy44NzUgOC4zMyA0LjI1MSA4Ljk4MWgyLjA2MWMuNTY0LTEuMDQxIDEuODk0LTIuNjU0IDMuOTkxLTQuODM4LjczNy0xLjEyOCAxLjgyMi0yLjY5IDMuMjUzLTQuNjg2IDEuNjc3LTIuMzcyIDMuMDk1LTMuNTU3IDQuMjUxLTMuNTU3IDEuMDcgMCAxLjk3NC41OTIgMi43MTEgMS43NzggMS4wNzEgMS42OTMgMi45ODYgNC43MyA1Ljc0OCA5LjExMiAzLjA1MSA0Ljc0NCA1LjMgNy4xMTYgNi43NDYgNy4xMTYgMS4wNyAwIDUuMDgyLTMuMDgxIDEyLjAzNy05LjI0MiA2Ljk3LTYuMTYxIDEwLjc5NS05LjI0MiAxMS40NzQtOS4yNDJoMi4zNjRabTQ0LjU3MyAxOS41MjVjLTEuMDQyIDEuMjQ0LTIuNDAxIDEuODY2LTQuMDc4IDEuODY2aC0xNC45ODhjLTMuNDcgMC03LjMzOC0uNTIxLTExLjYwNC0xLjU2Mi01LjU5NS0xLjM2LTguNzYyLTMuMDY3LTkuNS01LjEyLS44MDktMi4zMTQtMS4yMTQtNC41MzQtMS4yMTQtNi42NiAwLTQuMjY3IDEuODM2LTcuNTQzIDUuNTA5LTkuODI4IDMuMDk0LTEuOTgxIDYuODc2LTIuOTcyIDExLjM0NC0yLjk3MiAzLjg0NiAwIDYuODk3LjgzMiA5LjE1MyAyLjQ5NSAyLjI1NSAxLjY0OSAzLjM4MyAzLjg5OCAzLjM4MyA2Ljc0NyAwIDEuNTkxLTIuNzExIDMuMzI2LTguMTMzIDUuMjA2LTQuNDgzIDEuNjItNy43NDQgMi40My05Ljc4MiAyLjQzYTQuNDkgNC40OSAwIDAgMS0uNjczLS4wNDNjMCAuMzktLjMxMS44NjgtLjkzMiAxLjQzMi4zMzIuNTY0IDEuOTgxIDEuMjE0IDQuOTQ1IDEuOTUyIDIuNjYuNjggNC41NTUgMS4wMiA1LjY4MiAxLjAyaDEzLjQ3YzAgLjAyOSAxLjY5OS4yODIgNS4wOTcuNzU5IDEuMDcuNjggMS44NDMgMS40MzkgMi4zMjEgMi4yNzhabS0xNy40NjEtMTYuOWMtMS41NzYtMS42MDUtNC4xMTMtMi40MDgtNy42MTMtMi40MDgtMi40NTggMC00LjczNS40NzctNi44MzIgMS40MzItMi43MDQgMS4yNDQtNC4wNTYgMi45MjktNC4wNTYgNS4wNTUgMCAuNzUyLjI1MyAxLjI4Ny43NTkgMS42MDVoMi43MTFjLjk1NSAwIDMuNTUtLjgwMyA3Ljc4Ny0yLjQwOCA0LjIzNy0xLjYyIDYuNjUyLTIuNzEyIDcuMjQ0LTMuMjc2Wm0zMi40OTEtNTMuMzAyLTIuNDA3IDUzLjU2M2MtLjE0NS4xMTUtLjIxNy40NTUtLjIxNyAxLjAxOSAwIC41NjQuMDcyIDEuMzM4LjIxNyAyLjMyMS4zMDQgMi42NjIgMS4wNDEgNy42OTUgMi4yMTIgMTUuMDk5LS4wNTguMjQ2LS41NDIuNTI4LTEuNDUzLjg0N2EyLjkyIDIuOTIgMCAwIDEtLjQ1NS4wNDNjLTIuOTY1IDAtNC44ODgtMy40MjEtNS43Ny0xMC4yNjEtLjAyOS0uMTQ1LS4wNDMtLjQyNy0uMDQzLS44NDYgMC0xLjMzMS4wNzItNC4wNTcuMjE3LTguMTc5LjIwMi01LjMwOC40NDEtMTAuODYyLjcxNS0xNi42NjEuMDg3LTIuMTk4LjQxMi02LjI5MS45NzYtMTIuMjc5LjUzNS01LjQ4MS44MDMtOS41NTIuODAzLTEyLjIxNCAwLTguMDk5LjUzNS0xMy43NiAxLjYwNS0xNi45ODZsLjY1MS0uMDQzaC4xOTVjLjk2OSAwIDEuNjE5LjUzNSAxLjk1MiAxLjYwNS4yNi43MDkuNTI4IDEuNy44MDIgMi45NzJabTExNC4yMTggNjguMTg0Yy0uNjggMS4zMDItMS41MTEgMS45NTMtMi40OTQgMS45NTMtLjQ5MiAwLTEuNjQyLS4zNjktMy40NDktMS4xMDctMi4xNjktLjg4Mi0zLjQzNC0xLjM4MS0zLjc5Ni0xLjQ5Ny0xLjU5LS4zOS0zLjk3Ni0uNzM3LTcuMTU3LTEuMDQxLTMuODQ3LS4zNzYtNi4yMzItLjY4Ny03LjE1OC0uOTMzLTQuNDk3LTEuMTEzLTEwLjAyLTIuNC0xNi41NzEtMy44NjEtMy4wOC0uNjgtNy4wMi0yLjMyMS0xMS44MjEtNC45MjUtNS44Ny0zLjE4Mi04LjgwNi01LjgwNy04LjgwNi03Ljg3NSAwLS40MTkgMy42OC0zLjgxMSAxMS4wNC0xMC4xNzQgOC4wMjYtNi45MTMgMTIuMjc3LTEwLjYwOSAxMi43NTQtMTEuMDg2LjQ2My0uNzA5IDEuMi0xLjcxNCAyLjIxMi0zLjAxNS4yNzUtLjI2IDIuNTc0LTIuMzY1IDYuODk4LTYuMzEzIDMuNDg0LTMuMTk3IDYuMzY5LTUuNzEzIDguNjU0LTcuNTUgMy41NTctMi44NzggNi43MTYtNC45NjggOS40NzgtNi4yNy40MDUuMzc3Ljg0Ni41ODYgMS4zMjMuNjN2My4xMDJsLS4zOS4yMTdhNTkuMDEgNTkuMDEgMCAwIDAtNi4zMTIgNC40NDdjLTYuOTEyIDUuNjI2LTE5LjczIDE3LjgwNC0zOC40NTYgMzYuNTMzIDUuNTM4IDMuOTQ4IDEzLjE3MyA2Ljg4NCAyMi45MDUgOC44MDggMS4wMjYuMTg4IDcuMTg2IDEuNzI4IDE4LjQ3OSA0LjYyMSAxLjM4OC4zMzIgNC4wNDIuOTc2IDcuOTYgMS45My4yODkuMDg3IDEuODU4IDEuMjIyIDQuNzA3IDMuNDA2Wm0tNTguNTQtNTMuMzQ1LTYuNjU5IDU3LjIyOWMtLjQ0OC40NjItMS4yNjUuNjk0LTIuNDUxLjY5NC0yLjAxIDAtMy4wMTUtLjcyMy0zLjAxNS0yLjE3IDAtLjU2NC40NDEtNC4yODEgMS4zMjMtMTEuMTUuMzYyLTIuNTYgMS40NDYtOS45NzIgMy4yNTQtMjIuMjM3LjMxOC0yLjUxNi45MTEtNy4wMjEgMS43NzgtMTMuNTE1LjczOC01LjEyIDEuOC05LjUxIDMuMTg5LTEzLjE2OCAxLjc0OS4zNjEgMi42MjQgMS41MTggMi42MjQgMy40NyAwIC4zMDQtLjAxNC41ODYtLjA0My44NDdabTk5LjQyNSAzMy43MTJjMCAxLjE4Ni0yLjE0NyA0LjE1OC02LjQ0MiA4LjkxNi0zLjc4OCA0LjE4LTYuNjAxIDYuOTQzLTguNDM3IDguMjg4LS45MjYuNjc5LTIuOTE0IDIuMTU1LTUuOTY1IDQuNDI1LTEuMjcyLjg5Ny0zLjIyNCAxLjc4Ni01Ljg1NiAyLjY2OS0yLjY3NS44NjctNC43NzkgMS4zMDEtNi4zMTIgMS4zMDEtMi44NzcgMC01LjI2My0xLjg1MS03LjE1Ny01LjU1NC0xLjU0OC0zLjAyMi0yLjMyMS02LjE2OC0yLjMyMS05LjQzNiAwLTIuNzE5LjU2NC01LjUwMyAxLjY5Mi04LjM1My40MTkgMCAuODUzLS4xOTUgMS4zMDEtLjU4NS41MzUuMzAzIDEuMjcyLjg5NiAyLjIxMiAxLjc3OS0uMjg5IDIuMjI3LS40MzMgNC4xNzItLjQzMyA1LjgzNSAwIDYuODQxIDIuMjE5IDEwLjI2MiA2LjY1OCAxMC4yNjIgNC4yOTUgMCA5LjQ1LTIuNjY5IDE1LjQ2NS04LjAwNi45NTQtLjg1MyA1LjQ3My01LjA3NiAxMy41NTYtMTIuNjY5aC42NzJjLjkxMSAwIDEuMzY3LjM3NiAxLjM2NyAxLjEyOFptMzguNDEyIDEuMDJjMCAuNTkzLS4yOTYgMy45Ny0uODg5IDEwLjEzMWE3MjIuMzI4IDcyMi4zMjggMCAwIDEtMS44NjUgMTYuNzA0Yy0uMDU4LjQ0OS0uNTA3LjY3My0xLjM0NS42NzMtMS4wOTkgMC0yLjAwMy0xLjM4OS0yLjcxMS00LjE2NS0uNTM1LTIuMTEyLS44MDMtMy45ODUtLjgwMy01LjYxOSAwLS42MjIuMzU0LTIuODU3IDEuMDYzLTYuNzA0LjcyMy0zLjgzMiAxLjA4NC02LjA1MiAxLjA4NC02LjY2IDAtMS42NjMtLjQ0OC0yLjQ5NC0xLjM0NC0yLjQ5NC0xLjE1NyAwLTMuNjMgMS4wMDUtNy40MTggMy4wMTUtMy4yODMgMS43NS01LjY2OSAzLjE3NS03LjE1OCA0LjI3NC0yLjg0OCAyLjA5Ny02LjA4IDUuMjUtOS42OTUgOS40NTgtLjU5My42OC0xLjc1NyAxLjk3NC0zLjQ5MiAzLjg4NC0uNjY1LjcwOC0yLjAwMyAxLjA3Ny00LjAxMyAxLjEwNnYtMi44NDJjMC0xLjMxNiAyLjU5Ni00LjIzOCA3Ljc4Ny04Ljc2NGExMTMuMjg2IDExMy4yODYgMCAwIDEgMTMuMDU3LTkuODI4YzYuMzc3LTQuMTUxIDExLjEyLTYuMjI2IDE0LjIyOC02LjIyNi44MjUgMCAxLjYxMy40NDggMi4zNjUgMS4zNDUuNzY2LjkxMSAxLjE0OSAxLjgxNSAxLjE0OSAyLjcxMlpNNTM0IDEzNC40NzJ2MS45MDljLS41OTMuODI0LTEuMjQzIDEuMjM2LTEuOTUyIDEuMjM2LS40NDggMC0xLjk3NC0xLjM1OS00LjU3Ny00LjA3OC0yLjYwMi0yLjczMy00LjMzNy00LjEtNS4yMDUtNC4xIDAgLjA1OC0zLjA4IDEuMjQ0LTkuMjQgMy41NTgtNi4zMDQgMi4zNDMtMTAuMzE3IDMuNTE0LTEyLjAzOCAzLjUxNC0xLjQxNyAwLTIuOTg1LS42OTQtNC43MDYtMi4wODMtMS43MjEtMS4zNzQtMi41ODEtMi42NjgtMi41ODEtMy44ODMgMC0zLjEzOCAzLjQzNC02Ljc5NyAxMC4zMDItMTAuOTc3IDYuNDM1LTMuOTYzIDExLjM0NC01Ljk0NCAxNC43MjgtNS45NDQgMS40MTcgMCAyLjY4OS44MSAzLjgxNyAyLjQzdjEuODIyYy0uNTY0LjgxLTEuMjE1IDEuMjE1LTEuOTUyIDEuMjE1LS4xMyAwLS42MTUtLjE4MS0xLjQ1My0uNTQzLS44MzktLjM3Ni0xLjMzOC0uNTY0LTEuNDk3LS41NjQtMS4xMjggMC0zLjcxNiAxLjE2NS03Ljc2NSAzLjQ5My0zLjgzMiAyLjE2OS02LjM5OCAzLjc4OS03LjcgNC44Ni0xLjMwMSAxLjEyOC0yLjEzMiAyLjQxNS0yLjQ5NCAzLjg2MS45ODMuOTI2IDIuMTU1IDEuMzg4IDMuNTE0IDEuMzg4LjcwOCAwIDQuMTA2LTEuMTcxIDEwLjE5NC0zLjUxNCA2LjA3My0yLjM3MiA5Ljc0Ni0zLjU1OCAxMS4wMTgtMy41NTggMS44NjYgMCAzLjIxLjQ3IDQuMDM1IDEuNDEuMTg3LjIxNy45MDMgMS40OSAyLjE0NyAzLjgxOGEyMS45MDIgMjEuOTAyIDAgMCAwIDMuNDA1IDQuNzNaIi8+CiAgPC9nPgogIDxkZWZzPgogICAgPGNsaXBQYXRoIGlkPSJhIj4KICAgICAgPHBhdGggZmlsbD0iI2ZmZiIgZD0iTTU4IDQ5aDQ3NnYxMDJINTh6Ii8+CiAgICA8L2NsaXBQYXRoPgogIDwvZGVmcz4KPC9zdmc+");`;
const advancedJs = `function download(dataURL, filename) {
const blob = dataURLToBlob(dataURL);
@@ -56,92 +54,104 @@ document.querySelector("#signature-advanced-svg").addEventListener("click", func
document.querySelector("#signature-advanced-png").addEventListener("click", function () {
const dataURL = signaturePad.toDataURL();
download(dataURL, "signature.png");
-});`
+});`;
---
-
-
-
-
-
- Confirm transfer
- Please confirm the transfer of funds by signing below.
-
-
-
-
-
-
-
-
-
-
-
- I agree that the signature and initials will be the electronic representation of my signature and initials for all purposes when I (or my agent) use them on documents, including legally binding contracts - just the same as a pen-and-paper signature or initial.
+
+
+
+
+
+ Cancel
+ Confirm transfer
+
+
+
+
+
-
-
-
-
-
- Advanced demo
+
+
+
+
+
+ Advanced demo
-
+
-
-
-
-
-
-
- Download SVG
-
-
- Download PNG
-
-
-
-
-
-
+
+
+
+
+
+
+ Download SVG
+
+
+ Download PNG
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
Save your signature
-
+
+
+
+
+
+
Save your signature
+
-
I agree that the signature and initials will be the electronic representation of my signature and initials for all purposes when I (or my agent) use them on documents, including legally binding contracts - just the same as a pen-and-paper signature or initial.
-
-
-
-
-
-
+
+ I agree that the signature and initials will be the electronic representation of my signature and initials for all
+ purposes when I (or my agent) use
+ them on documents, including legally binding contracts - just the same as a pen-and-paper signature or initial.
+
+
+
+
+
+
+
diff --git a/preview/pages/sitemap.xml.ts b/preview/pages/sitemap.xml.ts
index 87a3e419b..58514fe95 100644
--- a/preview/pages/sitemap.xml.ts
+++ b/preview/pages/sitemap.xml.ts
@@ -3,9 +3,7 @@ import { site } from '@shared/lib/site';
export const prerender = true;
-// (build.format 'file'), index pages collapse to their directory URL.
-// Entry order mirrors the Eleventy `pages` collection: top-level pages first,
-// then nested ones, each group in reverse-alphabetical order.
+// Index pages collapse to directory URLs (build.format 'file').
const pages = import.meta.glob('./**/*.astro');
const urls = Object.keys(pages)
@@ -14,7 +12,6 @@ const urls = Object.keys(pages)
const depthA = a.split('/').length;
const depthB = b.split('/').length;
if (depthA !== depthB) return depthA - depthB;
- // byte-wise, descending — matches the Eleventy `pages` collection order
return a < b ? 1 : -1;
})
.map((path) => {
@@ -34,7 +31,7 @@ const escapeXml = (value: string) =>
export const GET: APIRoute = () => {
const environment = process.env.NODE_ENV || 'production';
const baseUrl = environment !== 'development' ? site.previewUrl : '';
- // same shape as Liquid's `'now' | date_to_xmlschema` (UTC, +00:00 suffix)
+ // ISO 8601 UTC timestamp (+00:00 suffix)
const lastModified = new Date().toISOString().replace(/\.\d{3}Z$/, '+00:00');
const entries = urls
.map(
diff --git a/preview/pages/social-icons.astro b/preview/pages/social-icons.astro
index c68f0a4ac..5e321db80 100644
--- a/preview/pages/social-icons.astro
+++ b/preview/pages/social-icons.astro
@@ -1,101 +1,104 @@
---
-// Note: the Liquid front matter `plugins: ['social']` is inert for the output —
-// the `.social` styles ship in tabler-socials.css which is loaded globally on
-// every page; no extra page lib is emitted. So no pageLibs is passed.
-import DefaultLayout from '@shared/layouts/DefaultLayout.astro'
-import ButtonList from '@ui/ButtonList.astro'
-import Trending from '@ui/Trending.astro'
-import Card from '@ui/Card.astro'
-import CardHeader from '@ui/CardHeader.astro'
-import CardBody from '@ui/CardBody.astro'
-import socialTiles from '@data/social-tiles.json'
-import socials from '@data/socials.json'
+// plugins: ['social'] front matter is inert — .social styles ship globally in tabler-socials.css.
+import DefaultLayout from '@shared/layouts/DefaultLayout.astro';
+import ButtonList from '@ui/ButtonList.astro';
+import Trending from '@ui/Trending.astro';
+import Card from '@ui/Card.astro';
+import CardHeader from '@ui/CardHeader.astro';
+import CardBody from '@ui/CardBody.astro';
+import socialTiles from '@data/social-tiles.json';
+import socials from '@data/socials.json';
interface SocialTile {
- icon: string
- title: string
- description: string
- trending: number
+ icon: string;
+ title: string;
+ description: string;
+ trending: number;
}
interface Social {
- name: string
- file: string
+ name: string;
+ file: string;
}
-const tiles = socialTiles as SocialTile[]
-const socialList = socials as Social[]
-const fillers = Array.from({ length: 21 })
+const tiles = socialTiles as SocialTile[];
+const socialList = socials as Social[];
+const fillers = Array.from({ length: 21 });
---
-
-
-
- {
- tiles.map((tile) => (
-
-
-
-
-
-
-
-
-
{tile.title}
-
{tile.description}
-
-
-
-
-
-
-
-
- ))
- }
-
-
-
-
-
- Sign in with social media
-
-
-
- {
- socialList.map((social) => (
-
-
- Sign in with {social.name}
-
- ))
- }
-
-
-
-
+
+
+
+ {
+ tiles.map((tile) => (
+
+
+
+
+
+
+
+
+
{tile.title}
+
{tile.description}
+
+
+
+
+
+
+
+
+ ))
+ }
+
+
+
+
+
+ Sign in with social media
+
+
+
+ {
+ socialList.map((social) => (
+
+
+ Sign in with {social.name}
+
+ ))
+ }
+
+
+
+
-
-
-
- List of all social media icons
-
-
-
-
- {
- socialList.map((social) => (
-
-
-
- ))
- }
- {fillers.map(() =>
)}
-
-
-
-
-
-
+
+
+
+ List of all social media icons
+
+
+
+
+ {
+ socialList.map((social) => (
+
+
+
+ ))
+ }
+ {fillers.map(() =>
)}
+
+
+
+
+
+
diff --git a/preview/pages/tags.astro b/preview/pages/tags.astro
index 8fd2e62a0..3a127f183 100644
--- a/preview/pages/tags.astro
+++ b/preview/pages/tags.astro
@@ -1,115 +1,114 @@
---
-import DefaultLayout from '@shared/layouts/DefaultLayout.astro'
-import Tag from '@ui/Tag.astro'
-import TagsList from '@ui/TagsList.astro'
-import Card from '@ui/Card.astro'
-import CardBody from '@ui/CardBody.astro'
-import CardTitle from '@ui/CardTitle.astro'
-import people from '@data/people.json'
-import flags from '@data/flags.json'
-import siteData from '@data/site.json'
+import DefaultLayout from '@shared/layouts/DefaultLayout.astro';
+import Tag from '@ui/Tag.astro';
+import TagsList from '@ui/TagsList.astro';
+import Card from '@ui/Card.astro';
+import CardBody from '@ui/CardBody.astro';
+import CardTitle from '@ui/CardTitle.astro';
+import people from '@data/people.json';
+import flags from '@data/flags.json';
+import siteData from '@data/site.json';
-const tagIcons = ['bold', 'italic', 'underline', 'copy', 'scissors', 'file-plus', 'file-minus', 'ghost', 'star', 'script', 'photo', 'dog', 'piano']
+const tagIcons = ['bold', 'italic', 'underline', 'copy', 'scissors', 'file-plus', 'file-minus', 'ghost', 'star', 'script', 'photo', 'dog', 'piano'];
-const range = (a: number, b: number) => Array.from({ length: b - a + 1 }, (_, i) => a + i)
+const range = (a: number, b: number) => Array.from({ length: b - a + 1 }, (_, i) => a + i);
-const flags9 = flags.slice(0, 9)
-const people8 = people.slice(0, 8)
+const flags9 = flags.slice(0, 9);
+const people8 = people.slice(0, 8);
-// site.colors is an object keyed by colour name; the Liquid loop yields the
-// value objects with .class / .title.
-const colors = Object.values(siteData.colors) as { class: string; title: string }[]
+// site.colors yields value objects with .class / .title.
+const colors = Object.values(siteData.colors) as { class: string; title: string }[];
---
-
-
-
-
- Default tags
-
- {range(1, 14).map((i) => )}
-
-
-
-
+
+
+
+
+ Default tags
+
+ {range(1, 14).map((i) => )}
+
+
+
+
-
-
-
- Tags with flag
-
- {flags9.map((country) => )}
-
-
-
-
+
+
+
+ Tags with flag
+
+ {flags9.map((country) => )}
+
+
+
+
-
-
-
- Tags with icon
-
- {tagIcons.map((icon) => )}
-
-
-
-
+
+
+
+ Tags with icon
+
+ {tagIcons.map((icon) => )}
+
+
+
+
-
-
-
- Tags with avatar
-
- {people8.map((person) => )}
-
-
-
-
+
+
+
+ Tags with avatar
+
+ {people8.map((person) => )}
+
+
+
+
-
-
-
- Tags with status
-
- {colors.map((color) => )}
-
-
-
-
+
+
+
+ Tags with status
+
+ {colors.map((color) => )}
+
+
+
+
-
-
-
- Tags with legend
-
- {colors.map((color) => )}
-
-
-
-
+
+
+
+ Tags with legend
+
+ {colors.map((color) => )}
+
+
+
+
-
-
-
- Default tags
-
- {range(1, 6).map((i) => )}
- {range(7, 12).map((i) => )}
-
-
-
-
+
+
+
+ Default tags
+
+ {range(1, 6).map((i) => )}
+ {range(7, 12).map((i) => )}
+
+
+
+
-
-
-
- Default tags
-
- {range(1, 12).map((i) => )}
-
-
-
-
-
+
+
+
+ Default tags
+
+ {range(1, 12).map((i) => )}
+
+
+
+
+
diff --git a/preview/pages/tasks-list.astro b/preview/pages/tasks-list.astro
index 6fab6fc4a..91843e636 100644
--- a/preview/pages/tasks-list.astro
+++ b/preview/pages/tasks-list.astro
@@ -1,152 +1,182 @@
---
-import CardActions from '@ui/CardActions.astro'
-import DefaultLayout from '@shared/layouts/DefaultLayout.astro'
-import Button from '@ui/Button.astro'
-import Icon from '@ui/Icon.astro'
-import Avatar from '@ui/Avatar.astro'
-import Badge from '@ui/Badge.astro'
-import Modal from '@shared/components/modals/Modal.astro'
-import FormGroup from '@ui/FormGroup.astro'
-import tasks from '@data/tasks.json'
-import people from '@data/people.json'
+
+import CardActions from '@ui/CardActions.astro';
+import DefaultLayout from '@shared/layouts/DefaultLayout.astro';
+import Button from '@ui/Button.astro';
+import Icon from '@ui/Icon.astro';
+import Avatar from '@ui/Avatar.astro';
+import Badge from '@ui/Badge.astro';
+import Modal from '@shared/components/modals/Modal.astro';
+import CaptureModal from '@shared/components/CaptureModal.astro';
+import FormGroup from '@ui/FormGroup.astro';
+import tasks from '@data/tasks.json';
+import people from '@data/people.json';
interface Person {
- id?: string
- full_name?: string
- photo?: string
- [key: string]: unknown
+ id?: string;
+ full_name?: string;
+ photo?: string;
+ [key: string]: unknown;
}
interface Task {
- 'name'?: string
- 'assigned_to'?: number
- 'due_date'?: string
- 'due-date'?: string
- 'priority'?: string
- [key: string]: unknown
+ name?: string;
+ assigned_to?: number;
+ due_date?: string;
+ 'due-date'?: string;
+ priority?: string;
+ [key: string]: unknown;
}
interface Column {
- name: string
- tasks: Task[]
+ name: string;
+ tasks: Task[];
}
-const columns = (tasks as { columns: Column[] }).columns
-const peopleList = people as Person[]
+const columns = (tasks as { columns: Column[] }).columns;
+const peopleList = people as Person[];
-// add-task modal: assignable people (parts/modals/add-task.html — "5,6,2,3")
-const selectedPeople = [5, 6, 2, 3].map((id) => peopleList[id - 1])
+// Assignable people for the add-task modal.
+const selectedPeople = [5, 6, 2, 3].map((id) => peopleList[id - 1]);
---
-
-
- {
- columns.map((section) => (
-
- ))
- }
-
-
+
+
+ {
+ columns.map((section) => (
+
+ ))
+ }
+
+
-
-
+
+
+
-
-
-
-
-
-
-
- Select person
- {selectedPeople.map((person) => {person.full_name} )}
-
-
-
-
- Low
- Medium
- High
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+ Select person
+ {
+ selectedPeople.map((person) => (
+ {person.full_name}
+ ))
+ }
+
+
+
+
+ Low
+ Medium
+ High
+
+
+
+
+
+
+
-
-
+
+
+
diff --git a/preview/pages/terms-of-service.astro b/preview/pages/terms-of-service.astro
index 738a8d389..10f9a32cc 100644
--- a/preview/pages/terms-of-service.astro
+++ b/preview/pages/terms-of-service.astro
@@ -3,16 +3,7 @@ import SingleLayout from '@shared/layouts/SingleLayout.astro'
import CardTitle from '@ui/CardTitle.astro'
import Prose from '@ui/Prose.astro'
-// Liquid renders shared/includes/terms-of-service.md via `renderContent: "md"`
-// (markdown-it). The rendered HTML below is inlined verbatim from the reference
-// build to guarantee a 1:1 DOM match — Astro's markdown pipeline (remark +
-// smartypants) would produce different quote characters and list structure.
-// TODO: source of truth remains shared/includes/terms-of-service.md; regenerate
-// this block if the markdown changes.
-//
-// The card title is empty on purpose: the Liquid template prints {{ page.title }},
-// which is Eleventy's `page` object (no `title` property) — the reference output
-// is an empty .
+// Card title is empty on purpose.
---
diff --git a/preview/pages/text-features.astro b/preview/pages/text-features.astro
index 54c52cf80..daaf40131 100644
--- a/preview/pages/text-features.astro
+++ b/preview/pages/text-features.astro
@@ -1,98 +1,83 @@
---
-// The right column renders headings h1..h6 via a Liquid {% for i in (1..6) %}
-// loop → dynamic tag name here. {{ site.homepage }} → @data/site.json homepage.
-import DefaultLayout from '@shared/layouts/DefaultLayout.astro'
-import Prose from '@ui/Prose.astro'
-import siteData from '@data/site.json'
+// Right column renders headings h1..h6 with dynamic tag names. Homepage URL from site.json.
+import DefaultLayout from '@shared/layouts/DefaultLayout.astro';
+import Prose from '@ui/Prose.astro';
+import siteData from '@data/site.json';
-const homepage = siteData.homepage
+const homepage = siteData.homepage;
---
-
-
-
-
-
- Text features
+
+
+
+
+
+ Text features
- HTML provides various tags to format text and add meaning. For example, important words can be highlighted, and emphasized text can be italicized.
+ HTML provides various tags to format text and add meaning. For example, important words can be highlighted, and emphasized text can be italicized.
- If you want to visit an interesting website, check out this page .
+ If you want to visit an interesting website, check out this page .
- The term HTML is widely used in web development.
+ The term HTML is widely used in web development.
- Previously, the instruction said "Do not include images." However, "You may now add images."
+ Previously, the instruction said "Do not include images." However, "You may now add images."
- "The best way to predict the future is to create it." – Peter Drucker
+ "The best way to predict the future is to create it." – Peter Drucker
- Sometimes, highlighting important text can improve readability.
+ Sometimes, highlighting important text can improve readability.
- In JavaScript, you can log messages using the following code: console.log('Hello, world!');
+ In JavaScript, you can log messages using the following code: console.log('Hello, world!');
- To copy text on Windows, use Ctrl + C . On macOS, use Cmd + C .
+ To copy text on Windows, use Ctrl + C . On macOS, use Cmd + C .
- Water is written chemically as H2 O, while Einstein’s famous equation is E = mc2 .
+ Water is written chemically as H2 O, while Einstein’s famous equation is E = mc2 .
- Many people mistakenly spell "recieve" instead of "receive" .
+ Many people mistakenly spell "recieve" instead of "receive" .
- The correct way to write the date format is "February 12, 2026" , not "12th February, 2026" in American English.
+ The correct way to write the date format is "February 12, 2026" , not "12th February, 2026" in American English.
-
- If you need select text, you can use your mouse or keyboard. To select text using your mouse, click and drag the cursor over the text you want to highlight .
-
+
+ If you need select text, you can use your mouse or keyboard. To select text using your mouse, click and drag the cursor over the text you want to highlight .
+
- Disclaimer: This text is for demonstration purposes only.
-
-
-
-
-
-
-
-
- {
- [1, 2, 3, 4, 5, 6].map((i) => {
- const Heading = `h${i}`
- return (
-
- Heading {i} by{' '}
-
- <>
-
- @
- >
- JohnDoe
-
-
- )
- })
- }
+ Disclaimer: This text is for demonstration purposes only.
+
+
+
+
+
+
+
+
+ {
+ [1, 2, 3, 4, 5, 6].map((i) => {
+ const Heading = `h${i}`;
+ return (
+
+ Heading {i} by @ JohnDoe
+
+ );
+ })
+ }
-
- Tabler is a modern UI framework which provide developers with a lot of pre-build components and customizable options. It is
- build on Bootstrap, making it easy to integrate into existing projects. The design is clean, responsive, and accessible, ensuring that user can navigate through interface easily. Tabler also support all modern browsers, but some features may not work properly on Internet Explorer. With
- it's lightweight structure and optimized performance, Tabler helps developers create stunning web applications faster.
-
+
+ Tabler is a modern UI framework which provide developers with a lot of pre-build components and customizable options. It is
+ build on Bootstrap, making it easy to integrate into existing projects. The design is clean, responsive, and accessible, ensuring that user can navigate
+ through interface easily. Tabler also support all modern browsers, but some features may not work properly on Internet Explorer. With
+ it's lightweight structure and optimized performance, Tabler helps developers create stunning web applications faster.
+
-
- Hey @ JohnDoe , have you seen the latest updates on #WebDevelopment16 ? @ JaneSmith just shared an interesting article about Messenger and Netflix !
-
+ Hey @ JohnDoe , have you seen the latest updates on #WebDevelopment16 ? @ JaneSmith just shared an interesting article about Messenger and Netflix !
-
- The sky is #066fd1 , the grass is rgb(47, 179, 68) , fire trucks are often red , oranges are hsl(24deg, 94.49%, 49.8%) . Some flowers are hwb(288.35deg, 24.31%, 21.18%) .
-
+
+ The sky is #066fd1 , the grass is rgb(47, 179, 68) , fire trucks are often red , oranges are hsl(24deg, 94.49%, 49.8%) . Some flowers are hwb(288.35deg, 24.31%, 21.18%) .
+
-
-
-
-
-
-
+
+
+
+
+
+
diff --git a/preview/pages/tour.astro b/preview/pages/tour.astro
index 26a45b6dd..308c9a4bd 100644
--- a/preview/pages/tour.astro
+++ b/preview/pages/tour.astro
@@ -1,219 +1,231 @@
---
-import CardActions from '@ui/CardActions.astro'
-import DefaultLayout from '@shared/layouts/DefaultLayout.astro'
-import Button from '@ui/Button.astro'
-import ButtonList from '@ui/ButtonList.astro'
-import Card from '@ui/Card.astro'
-import CardHeader from '@ui/CardHeader.astro'
-import CardBody from '@ui/CardBody.astro'
-import CardTitle from '@ui/CardTitle.astro'
-import Badge from '@ui/Badge.astro'
-import Icon from '@ui/Icon.astro'
+import CardActions from '@ui/CardActions.astro';
+import DefaultLayout from '@shared/layouts/DefaultLayout.astro';
+import CaptureScript from '@shared/components/CaptureScript.astro';
+import Button from '@ui/Button.astro';
+import ButtonList from '@ui/ButtonList.astro';
+import Card from '@ui/Card.astro';
+import CardHeader from '@ui/CardHeader.astro';
+import CardBody from '@ui/CardBody.astro';
+import CardTitle from '@ui/CardTitle.astro';
+import Badge from '@ui/Badge.astro';
+import Icon from '@ui/Icon.astro';
---
-
-
-
-
-
-
-
-
-
-
- Click the "Start Tour" button to begin an interactive tour of this page. The tour will guide you through different elements and features.
-
-
-
-
-
-
-
- This is the first card in our tour. It demonstrates how Driver.js highlights elements on the page.
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+ Click the "Start Tour" button to begin an interactive tour of this page.
+ The tour will guide you through different elements and features.
+
+
+
+
+
+
+
+
+ This is the first card in our tour. It demonstrates how Driver.js highlights elements on the page.
+
+
+
+
+
+
+
-
-
-
-
-
- This is a full-width card that demonstrates how the tour works with larger elements.
-
-
-
-
- Name
- Status
- Role
-
-
-
-
-
- John Doe
-
- Developer
-
-
-
-
-
- Jane Smith
-
- Designer
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+ This is a full-width card that demonstrates how the tour works with larger elements.
+
+
+
+
+ Name
+ Status
+ Role
+
+
+
+
+
+ John Doe
+
+ Developer
+
+
+
+
+
+ Jane Smith
+
+ Designer
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
- Settings
- Configure your application settings here.
-
-
-
+
+
+
+
+
+
+ Settings
+ Configure your application settings here.
+
+
+
-
-
-
-
-
-
- Users
- Manage your team members and permissions.
-
-
-
+
+
+
+
+
+
+ Users
+ Manage your team members and permissions.
+
+
+
-
-
-
-
-
-
- Analytics
- View your application statistics and reports.
-
-
-
-
+
+
+
+
+
+
+ Analytics
+ View your application statistics and reports.
+
+
+
+
-
-
-
+ const startButton = document.getElementById('start-tour');
+ if (startButton) {
+ startButton.addEventListener('click', function () {
+ driverObj.drive();
+ });
+ }
+ });
+
+
+
diff --git a/preview/pages/users.astro b/preview/pages/users.astro
index 7ce42058e..3ac549846 100644
--- a/preview/pages/users.astro
+++ b/preview/pages/users.astro
@@ -1,59 +1,69 @@
---
-// Note: the Liquid template assigns `progress` and `online_counter` per person
-// but never renders them — omitted here.
-// Note: the avatar include passes `rounded=true`, which ui/avatar.html ignores
-// (no such param) — omitted; Avatar.astro has no `rounded` prop.
-import DefaultLayout from '@shared/layouts/DefaultLayout.astro'
-import Avatar from '@ui/Avatar.astro'
-import Card from '@ui/Card.astro'
-import CardBody from '@ui/CardBody.astro'
-import Icon from '@ui/Icon.astro'
-import Pagination from '@ui/Pagination.astro'
-import people from '@data/people.json'
+// progress and online_counter assigned per person but never rendered — omitted.
+// rounded=true on avatar has no effect — Avatar has no `rounded` prop.
+import DefaultLayout from '@shared/layouts/DefaultLayout.astro';
+import Avatar from '@ui/Avatar.astro';
+import Card from '@ui/Card.astro';
+import CardBody from '@ui/CardBody.astro';
+import Icon from '@ui/Icon.astro';
+import Pagination from '@ui/Pagination.astro';
+import people from '@data/people.json';
interface Person {
- full_name?: string
- job_title?: string
- photo?: string
- [key: string]: unknown
+ full_name?: string;
+ job_title?: string;
+ photo?: string;
+ [key: string]: unknown;
}
-const users = (people as Person[]).slice(0, 18)
+const users = (people as Person[]).slice(0, 18);
---
-
-
- {
- users.map((person, idx) => {
- const index = idx + 1
- return (
-
-
-
-
-
- {person.job_title}
+
+
+ {
+ users.map((person, idx) => {
+ const index = idx + 1;
+ return (
+
+
+
+
+
+ {person.job_title}
- {index === 1 ? Owner : index < 5 ? Admin : null}
-
-
-
-
- )
- })
- }
-
+
+ {index === 1 ? (
+ Owner
+ ) : index < 5 ? (
+ Admin
+ ) : null}
+
+
+
+
+
+ );
+ })
+ }
+
-
+
diff --git a/preview/pages/wizard.astro b/preview/pages/wizard.astro
index b2a716aa3..84b338229 100644
--- a/preview/pages/wizard.astro
+++ b/preview/pages/wizard.astro
@@ -10,8 +10,7 @@ import Progress from '@ui/Progress.astro'
import { site } from '@shared/lib/site'
import timezones from '@data/timezones.json'
-// TODO: front matter `page-menu: extra.wizard` is not ported — it is only read
-// by layout/navbar-menu.html, which the `single` layout does not include.
+// TODO: page-menu (extra.wizard) is unused — SingleLayout has no navbar menu.
---
diff --git a/shared/components/CaptureModal.astro b/shared/components/CaptureModal.astro
new file mode 100644
index 000000000..7cc988de9
--- /dev/null
+++ b/shared/components/CaptureModal.astro
@@ -0,0 +1,8 @@
+---
+// Captures slot markup (typically a modal) into page-modals; BaseLayout emits
+// it at the end of via .
+// Registration is synchronous (a promise) — see page-modals.ts.
+import { addPageModal } from '@shared/lib/page-modals';
+
+addPageModal(Astro.slots.render('default'));
+---
diff --git a/shared/components/CaptureScript.astro b/shared/components/CaptureScript.astro
new file mode 100644
index 000000000..ba3e364af
--- /dev/null
+++ b/shared/components/CaptureScript.astro
@@ -0,0 +1,8 @@
+---
+// Captures slot markup (typically
+
diff --git a/shared/components/js/TablerList.astro b/shared/components/js/TablerList.astro
index 38c77bfd2..f4b0101ae 100644
--- a/shared/components/js/TablerList.astro
+++ b/shared/components/js/TablerList.astro
@@ -1,6 +1,6 @@
---
-// so it renders inline where it is placed (mirrors the Eleventy dev build,
-// which keeps the `window.tabler_list` registry assignment).
+import CaptureScript from '../CaptureScript.astro';
+
interface Props {
id?: string
/** List.js `valueNames` entries — plain strings or `{ attr, name }` sort descriptors. */
@@ -11,6 +11,7 @@ const { id = 'default', valueNames } = Astro.props
const listId = `table-${id}`
---
+
+
diff --git a/shared/components/layout/Footer.astro b/shared/components/layout/Footer.astro
index 134ac4713..9ed9232b8 100644
--- a/shared/components/layout/Footer.astro
+++ b/shared/components/layout/Footer.astro
@@ -1,64 +1,65 @@
---
-import Icon from '@ui/Icon.astro'
-import { site } from '@shared/lib/site'
-import { formatUtcTimestamp } from '@shared/lib/date-format'
+import Icon from '@ui/Icon.astro';
+import { site } from '@shared/lib/site';
+import { formatUtcTimestamp } from '@shared/lib/date-format';
-// The PoC mirrors the development build (as in BaseLayout).
-const environment: string = 'development'
+// Development build (unminified assets), same as BaseLayout.
+const environment: string = 'development';
-const now = new Date()
-const generatedAt = formatUtcTimestamp(now)
+const now = new Date();
+const generatedAt = formatUtcTimestamp(now);
---
diff --git a/shared/components/layout/HeaderActionsBreadcrumb.astro b/shared/components/layout/HeaderActionsBreadcrumb.astro
index d4b5bc3b8..cf2553c54 100644
--- a/shared/components/layout/HeaderActionsBreadcrumb.astro
+++ b/shared/components/layout/HeaderActionsBreadcrumb.astro
@@ -1,13 +1,10 @@
---
-// Liquid appends `page.page-header` to "Tabler,Pages,", but inside the include
-// context `page` is the Eleventy page object (url, inputPath, …), which has no
-// `page-header` key — the appended value is empty, so the rendered breadcrumb
-// is always "Tabler > Pages" (confirmed against the faq.html reference).
-import Breadcrumb from '@ui/Breadcrumb.astro'
+// Breadcrumb is always "Tabler > Pages" — page-header append is empty in reference output.
+import Breadcrumb from '@ui/Breadcrumb.astro';
---
-
+
diff --git a/shared/components/layout/HeaderActionsButtons.astro b/shared/components/layout/HeaderActionsButtons.astro
index 8307ed1d3..1c2dc4c47 100644
--- a/shared/components/layout/HeaderActionsButtons.astro
+++ b/shared/components/layout/HeaderActionsButtons.astro
@@ -1,39 +1,48 @@
---
-import Button from '@ui/Button.astro'
-import ButtonList from '@ui/ButtonList.astro'
-import Icon from '@ui/Icon.astro'
-import Modal from '../modals/Modal.astro'
-import ReportModalContent from '../modals/ReportModalContent.astro'
+import Button from '@ui/Button.astro';
+import ButtonList from '@ui/ButtonList.astro';
+import Icon from '@ui/Icon.astro';
+import Modal from '../modals/Modal.astro';
+import CaptureModal from '../CaptureModal.astro';
+import ReportModalContent from '../modals/ReportModalContent.astro';
interface Props {
- /** layout-navbar-overlap && layout-navbar-dark → the "New view" button is color="dark" */
- dark?: boolean
+ /** layout-navbar-overlap && layout-navbar-dark → the "New view" button is color="dark" */
+ dark?: boolean;
}
-const { dark } = Astro.props
+const { dark } = Astro.props;
---
-
-
-
- {
- /* Button.astro doesn't support modal-id (data-bs-toggle/target), so these two
- buttons are reproduced directly from the ui/button.html output. */
- }
-
-
- Create new report
-
-
-
-
+
+
+
+ {/* Button.astro doesn't support modal-id (data-bs-toggle/target). */}
+
+
+ Create new report
+
+
+
+
-
-
-
-
-
+
+
+
+
+
diff --git a/shared/components/layout/HeaderActionsPhotos.astro b/shared/components/layout/HeaderActionsPhotos.astro
index ec53c461d..918480416 100644
--- a/shared/components/layout/HeaderActionsPhotos.astro
+++ b/shared/components/layout/HeaderActionsPhotos.astro
@@ -1,25 +1,19 @@
---
-// ui/form/input-icon.html is inlined below — the include is called with no
-// parameters, so only the default branch renders (icon "search", addon after
-// the input, type "text", placeholder "Search…", empty value).
-// TODO: extract to src/components/ui/form/InputIcon.astro (loader, prepend,
-// icon, icon-class, class, light, rounded, input-class, type, value,
-// placeholder, aria-label, readonly params) when another page needs it.
-import Icon from '@ui/Icon.astro'
-import Button from '@ui/Button.astro'
+import Icon from '@ui/Icon.astro';
+import Button from '@ui/Button.astro';
---
diff --git a/shared/components/layout/HeaderUptime.astro b/shared/components/layout/HeaderUptime.astro
index 9b61f36b4..49a3f6d2a 100644
--- a/shared/components/layout/HeaderUptime.astro
+++ b/shared/components/layout/HeaderUptime.astro
@@ -1,39 +1,40 @@
---
-// ui/status-indicator.html is inlined here (animated green) — it is only used
-// on this page. TODO: no standalone StatusIndicator component yet.
-import Button from '@ui/Button.astro'
-import ButtonList from '@ui/ButtonList.astro'
+// TODO: no standalone StatusIndicator component yet.
+import Button from '@ui/Button.astro';
+import ButtonList from '@ui/ButtonList.astro';
---
diff --git a/shared/components/layout/PageHeader.astro b/shared/components/layout/PageHeader.astro
index 2f5be55c1..eb155e9be 100644
--- a/shared/components/layout/PageHeader.astro
+++ b/shared/components/layout/PageHeader.astro
@@ -1,5 +1,5 @@
---
-// variables in Liquid) are plain props here.
+// Layout front-matter flags are plain props here.
// TODO: layout-navbar-overlap && layout-navbar-dark → extra text-white class
// on .page-header — both unset on the homepage.
import Icon from '@ui/Icon.astro';
diff --git a/shared/components/marketing/MarketingNavbar.astro b/shared/components/marketing/MarketingNavbar.astro
index e1a3bd0a9..5a42d338a 100644
--- a/shared/components/marketing/MarketingNavbar.astro
+++ b/shared/components/marketing/MarketingNavbar.astro
@@ -1,49 +1,56 @@
---
-// The "active" class is hardcoded on the Home link in the Liquid source — it is
-// NOT derived from the current page, so every marketing page marks Home active.
-// The relative base is "../" (page|relative for the one-level-deep marketing pages).
-import NavbarLogo from '../navbar/NavbarLogo.astro'
+// "active" is hardcoded on Home — not derived from the current page.
+// Relative base is "../" for one-level-deep marketing pages.
+import NavbarLogo from '../navbar/NavbarLogo.astro';
-const base = '..'
+const base = '..';
---
diff --git a/shared/components/marketing/hero/Browser.astro b/shared/components/marketing/hero/Browser.astro
index 7b8f5d99b..057a434f3 100644
--- a/shared/components/marketing/hero/Browser.astro
+++ b/shared/components/marketing/hero/Browser.astro
@@ -1,50 +1,51 @@
---
-// Inlines ui/marketing/browser.html (the browser chrome) and
-// ui/responsive-image.html (the preview image). site.previewUrl is the browser
-// input URL; the "../" asset base matches the marketing pages' depth (page|relative = "..").
-import Icon from '@ui/Icon.astro'
-import { site } from '@shared/lib/site'
+import Icon from '@ui/Icon.astro';
+import { site } from '@shared/lib/site';
---
diff --git a/shared/components/marketing/hero/Side.astro b/shared/components/marketing/hero/Side.astro
index 5b7a174ba..048a9b740 100644
--- a/shared/components/marketing/hero/Side.astro
+++ b/shared/components/marketing/hero/Side.astro
@@ -1,78 +1,77 @@
---
-// Port of marketing/hero/side.html.
-// Inlines ui/typed.html: renders the seed span (first string) and the Typed.js init
-// (page-libs: [typed.js] loads the library), rendered directly here.
-import Icon from '@ui/Icon.astro'
-import Illustration from '@ui/Illustration.astro'
+import Icon from '@ui/Icon.astro';
+import Illustration from '@ui/Illustration.astro';
+import CaptureScript from '../../CaptureScript.astro';
-// ui/typed.html: strings = include.strings | split: '|'; id defaults to "typed".
-const strings = ['more effective', 'more efficient', 'more productive']
-const typedId = 'typed'
+const strings = ['more effective', 'more efficient', 'more productive'];
+const typedId = 'typed';
---
-
-
-
-
-
- Better email communication,
- {strings[0]}
-
-
54 eye-catching, customizable and responsive email templates to improve your email communication. No coding skills needed.
-
-
-
-
-
+
+
+
+
+
+ Better email communication,
+ {strings[0]}
+
+
54 eye-catching, customizable and responsive email templates to improve your email communication. No coding skills needed.
+
+
+
+
+
+
+
diff --git a/shared/components/marketing/sections/Features.astro b/shared/components/marketing/sections/Features.astro
index 41d8fa325..f2e0bfa7d 100644
--- a/shared/components/marketing/sections/Features.astro
+++ b/shared/components/marketing/sections/Features.astro
@@ -1,39 +1,37 @@
---
-// The ui/shape.html include is inlined:
-// (shape class order: shape, shape-{size}, shape-{color}, {class}, rounded-circle).
-import SectionDivider from '../SectionDivider.astro'
-import Icon from '@ui/Icon.astro'
+import SectionDivider from '../SectionDivider.astro';
+import Icon from '@ui/Icon.astro';
interface Props {
- background?: string
- class?: string
- divider?: string
+ background?: string;
+ class?: string;
+ divider?: string;
}
-const { background, class: className, divider } = Astro.props
+const { background, class: className, divider } = Astro.props;
const sectionClass = ['section', background && `section-${background}`, className]
---
-
-
-
-
-
-
Mobile-optimized
-
Our email templates are fully responsive, so you can be sure they will look great on any device and screen size.
-
-
-
-
Compatible with 90+ email clients
-
Tested across 90+ email clients and devices, Tabler emails will help you make your email communication professional and reliable.
-
-
-
-
Unique, minimal design
-
Draw recipients’ attention with beautiful, minimal email designs based on Bootstrap and Material Design principles.
-
-
-
+
+
+
+
+
+
Mobile-optimized
+
Our email templates are fully responsive, so you can be sure they will look great on any device and screen size.
+
+
+
+
Compatible with 90+ email clients
+
Tested across 90+ email clients and devices, Tabler emails will help you make your email communication professional and reliable.
+
+
+
+
Unique, minimal design
+
Draw recipients’ attention with beautiful, minimal email designs based on Bootstrap and Material Design principles.
+
+
+
diff --git a/shared/components/marketing/sections/Features2.astro b/shared/components/marketing/sections/Features2.astro
index c78495444..54c506367 100644
--- a/shared/components/marketing/sections/Features2.astro
+++ b/shared/components/marketing/sections/Features2.astro
@@ -1,73 +1,77 @@
---
-// The ui/svg.html placeholder (width=500 height=400 border=true) is inlined,
-// and ui/shape.html (size="md") as
.
-import SectionDivider from '../SectionDivider.astro'
-import Icon from '@ui/Icon.astro'
+import SectionDivider from '../SectionDivider.astro';
+import Icon from '@ui/Icon.astro';
interface Props {
- background?: string
- class?: string
- divider?: string
+ background?: string;
+ class?: string;
+ divider?: string;
}
-const { background, class: className, divider } = Astro.props
+const { background, class: className, divider } = Astro.props;
const sectionClass = ['section', background && `section-${background}`, className]
---
-
-
-
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
Designed with users in mind
-
Tabler is fully responsive and compatible with all modern browsers. Thanks to its modern, user-friendly design you can create a fully functional interface that users will love. Every UI element has been created with attention to detail to make your interface beautiful!
-
-
-
-
-
-
-
-
Built for developers
-
Having in mind what it takes to write high-quality code, we want to help you speed up the development process and keep your code clean. Based on Bootstrap 5, Tabler is a cutting-edge solution, compatible with all modern browsers and fully responsive.
-
-
-
-
-
-
-
-
Fully customizable
-
You can easily customize the UI elements to make them fit the needs of your project. And don’t worry if you don’t have much experience - Tabler is easy to get started!
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Designed with users in mind
+
+ Tabler is fully responsive and compatible with all modern browsers. Thanks to its modern, user-friendly design you can create a fully functional interface that users will love. Every UI element has been created with
+ attention to detail to make your interface beautiful!
+
+
+
+
+
+
+
+
+
Built for developers
+
+ Having in mind what it takes to write high-quality code, we want to help you speed up the development process and keep your code clean. Based on Bootstrap 5, Tabler is a cutting-edge solution, compatible with all modern
+ browsers and fully responsive.
+
+
+
+
+
+
+
+
+
Fully customizable
+
You can easily customize the UI elements to make them fit the needs of your project. And don’t worry if you don’t have much experience - Tabler is easy to get started!
+
+
+
+
+
+
+
diff --git a/shared/components/marketing/sections/Features3.astro b/shared/components/marketing/sections/Features3.astro
index 81c19945f..6708c2716 100644
--- a/shared/components/marketing/sections/Features3.astro
+++ b/shared/components/marketing/sections/Features3.astro
@@ -1,72 +1,77 @@
---
-// placeholder svg on the right). ui/svg.html and ui/shape.html inlined.
-import SectionDivider from '../SectionDivider.astro'
-import Icon from '@ui/Icon.astro'
+import SectionDivider from '../SectionDivider.astro';
+import Icon from '@ui/Icon.astro';
interface Props {
- background?: string
- class?: string
- divider?: string
+ background?: string;
+ class?: string;
+ divider?: string;
}
-const { background, class: className, divider } = Astro.props
+const { background, class: className, divider } = Astro.props;
const sectionClass = ['section', background && `section-${background}`, className]
---
-
-
-
+
+
+
-
-
-
-
-
-
-
-
Designed with users in mind
-
Tabler is fully responsive and compatible with all modern browsers. Thanks to its modern, user-friendly design you can create a fully functional interface that users will love. Every UI element has been created with attention to detail to make your interface beautiful!
-
-
-
-
-
-
-
-
Built for developers
-
Having in mind what it takes to write high-quality code, we want to help you speed up the development process and keep your code clean. Based on Bootstrap 5, Tabler is a cutting-edge solution, compatible with all modern browsers and fully responsive.
-
-
-
-
-
-
-
-
Fully customizable
-
You can easily customize the UI elements to make them fit the needs of your project. And don’t worry if you don’t have much experience - Tabler is easy to get started!
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
Designed with users in mind
+
+ Tabler is fully responsive and compatible with all modern browsers. Thanks to its modern, user-friendly design you can create a fully functional interface that users will love. Every UI element has been created with
+ attention to detail to make your interface beautiful!
+
+
+
+
+
+
+
+
+
Built for developers
+
+ Having in mind what it takes to write high-quality code, we want to help you speed up the development process and keep your code clean. Based on Bootstrap 5, Tabler is a cutting-edge solution, compatible with all modern
+ browsers and fully responsive.
+
+
+
+
+
+
+
+
+
Fully customizable
+
You can easily customize the UI elements to make them fit the needs of your project. And don’t worry if you don’t have much experience - Tabler is easy to get started!
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/shared/components/marketing/sections/Testimonials.astro b/shared/components/marketing/sections/Testimonials.astro
index 5a1ad7fa4..4a3a8dbe0 100644
--- a/shared/components/marketing/sections/Testimonials.astro
+++ b/shared/components/marketing/sections/Testimonials.astro
@@ -1,88 +1,89 @@
---
// Marketing pages live one level deep, so the avatar asset base is ".."
// (reference uses "../static/avatars/...") — passed to Avatar via `base`.
-import testimonials from '@data/testimonials.json'
-import people from '@data/people.json'
-import Avatar from '@ui/Avatar.astro'
+import testimonials from '@data/testimonials.json';
+import people from '@data/people.json';
+import Avatar from '@ui/Avatar.astro';
interface Person {
- full_name?: string
- photo?: string
- job_title?: string
- [key: string]: unknown
+ full_name?: string;
+ photo?: string;
+ job_title?: string;
+ [key: string]: unknown;
}
interface Props {
- background?: string
- class?: string
- limit?: number
- hideHeader?: boolean
+ background?: string;
+ class?: string;
+ limit?: number;
+ hideHeader?: boolean;
}
-const { background, class: className, limit = 99, hideHeader } = Astro.props
+const { background, class: className, limit = 99, hideHeader } = Astro.props;
const sectionClass = ['section', background && `section-${background}`, className]
-// Liquid: testimonials | slice: 0, limit | split_to_n: 3
-const list = (testimonials as string[]).slice(0, limit)
-const n = 3
-const chunkSize = Math.round(list.length / n)
-const groups: string[][] = []
+// Slice testimonials and split into 3 columns.
+const list = (testimonials as string[]).slice(0, limit);
+const n = 3;
+const chunkSize = Math.round(list.length / n);
+const groups: string[][] = [];
for (let i = 0; i < list.length; i += chunkSize) {
- groups.push(list.slice(i, i + chunkSize))
+ groups.push(list.slice(i, i + chunkSize));
}
-// Liquid: {% assign i = 1 %} — a global counter incremented per testimonial,
-// used to index people[i] (people[0] is intentionally skipped).
-let i = 1
+// Global counter starting at 1 — indexes people[i] (people[0] is intentionally skipped).
+let i = 1;
const cards = groups.map((group) =>
- group.map((testimonial) => {
- const person = (people as Person[])[i] ?? {}
- i += 1
- return { testimonial, person }
- }),
-)
+ group.map((testimonial) => {
+ const person = (people as Person[])[i] ?? {};
+ i += 1;
+ return { testimonial, person };
+ }),
+);
---
-
- {
- !hideHeader && (
-
- )
- }
+
+ {
+ !hideHeader && (
+
+ )
+ }
-
- {
- cards.map((group) => (
-
+
+ ))
+ }
+
+
diff --git a/shared/components/modals/AddTaskModalContent.astro b/shared/components/modals/AddTaskModalContent.astro
index a4e6fe4b6..345f83fa6 100644
--- a/shared/components/modals/AddTaskModalContent.astro
+++ b/shared/components/modals/AddTaskModalContent.astro
@@ -1,46 +1,52 @@
---
-// selected ids "5,6,2,3" → people[id-1] (Liquid: id | plus: 0 | minus: 1).
-import people from '@data/people.json'
-import FormGroup from '@ui/FormGroup.astro'
+// selected ids "5,6,2,3" → people[id-1].
+import people from '@data/people.json';
+import FormGroup from '@ui/FormGroup.astro';
interface Person {
- id: string
- full_name: string
+ id: string;
+ full_name: string;
}
-const selectedPeople = ['5', '6', '2', '3'].map((idStr) => (people as Person[])[Number(idStr) - 1])
+const selectedPeople = ['5', '6', '2', '3'].map((idStr) => (people as Person[])[Number(idStr) - 1]);
---
-
-
-
-
-
-
- Select person
- {selectedPeople.map((person) => {person.full_name} )}
-
-
-
-
- Low
- Medium
- High
-
-
-
-
-
-
+
+
+
+
+
+
+ Select person
+ {
+ selectedPeople.map((person) => (
+ {person.full_name}
+ ))
+ }
+
+
+
+
+ Low
+ Medium
+ High
+
+
+
+
+
+
diff --git a/shared/components/modals/CaptureModal.astro b/shared/components/modals/CaptureModal.astro
deleted file mode 100644
index deeae3c4d..000000000
--- a/shared/components/modals/CaptureModal.astro
+++ /dev/null
@@ -1,8 +0,0 @@
----
-// ui/modal.html — see Modal.astro for that). The captured markup is emitted
-// verbatim at the {% modals %} slot (end of ) by PageModals.
-// Registration is synchronous (a promise) — see src/lib/page-modals.ts.
-import { addPageModal } from '@shared/lib/page-modals'
-
-addPageModal(Astro.slots.render('default'))
----
diff --git a/shared/components/modals/ChangePasswordModalContent.astro b/shared/components/modals/ChangePasswordModalContent.astro
index f6109b6ef..d8a2d8a61 100644
--- a/shared/components/modals/ChangePasswordModalContent.astro
+++ b/shared/components/modals/ChangePasswordModalContent.astro
@@ -1,118 +1,122 @@
---
-// Port of parts/modals/change-password.html.
-import FormHint from '@ui/FormHint.astro'
-import Button from '@ui/Button.astro'
-import InputGroup from '@ui/InputGroup.astro'
-import FormGroup from '@ui/FormGroup.astro'
+import FormHint from '@ui/FormHint.astro';
+import Button from '@ui/Button.astro';
+import InputGroup from '@ui/InputGroup.astro';
+import FormGroup from '@ui/FormGroup.astro';
---
-
-
-
-
+
+
+
+
-
-
- Your password must be 8-20 characters long, contain letters and numbers, and must not contain spaces, special characters, or emoji.
-
-
+
+
+
+ Your password must be 8-20 characters long, contain letters and numbers, and must not contain
+ spaces, special characters, or emoji.
+
+
+
-
-
- Passwords do not match.
-
+
+
+
+ Passwords do not match.
+
+
-
-
+
+
diff --git a/shared/components/modals/ConfirmDeleteModalContent.astro b/shared/components/modals/ConfirmDeleteModalContent.astro
index 43d33a12b..13e200198 100644
--- a/shared/components/modals/ConfirmDeleteModalContent.astro
+++ b/shared/components/modals/ConfirmDeleteModalContent.astro
@@ -1,57 +1,58 @@
---
-// Port of parts/modals/confirm-delete.html.
-import Icon from '@ui/Icon.astro'
-import Button from '@ui/Button.astro'
-import ModalClose from './ModalClose.astro'
+import Icon from '@ui/Icon.astro';
+import Button from '@ui/Button.astro';
+import ModalClose from './ModalClose.astro';
---
-
+
-
Are you sure?
-
-
Do you really want to delete this item? This action cannot be undone.
-
-
-
-
Items to be deleted:
-
- • Item 1
- • Item 2
- • Item 3
-
-
-
-
-
+
Are you sure?
+
+
Do you really want to delete this item? This action cannot be undone.
+
+
+
+
Items to be deleted:
+
+ • Item 1
+ • Item 2
+ • Item 3
+
+
+
+
+
-
-
- I understand this action cannot be undone
-
+
+
+ I understand this action cannot be undone
+
diff --git a/shared/components/modals/Modal.astro b/shared/components/modals/Modal.astro
index 8e8b72fdb..f259851d0 100644
--- a/shared/components/modals/Modal.astro
+++ b/shared/components/modals/Modal.astro
@@ -1,37 +1,50 @@
---
-// registers the modal in page-modals, and BaseLayout emits it at the end of
-// (like {% modals %}).
-// NOTE: registration must be synchronous (a promise, not an awaited string) —
-// see the comment in src/lib/page-modals.ts.
-import { addPageModal } from '@shared/lib/page-modals'
-
+// Modal shell markup. Wrap with at the call site to emit at the
+// end of (see PageModals). For inline gallery demos use ModalInline.
interface Props {
- modalId?: string
- size?: string
- top?: boolean
- scrollable?: boolean
- class?: string
- show?: boolean
- style?: string
+ modalId?: string;
+ size?: string;
+ top?: boolean;
+ scrollable?: boolean;
+ class?: string;
+ show?: boolean;
+ style?: string;
}
-const { modalId = 'simple', size, top, scrollable, class: className, show, style } = Astro.props
+const {
+ modalId = 'simple',
+ size,
+ top,
+ scrollable,
+ class: className,
+ show,
+ style,
+} = Astro.props;
-const modalClass = ['modal modal-blur fade', className, show && 'show'].filter(Boolean).join(' ')
-const dialogClass = ['modal-dialog', size && `modal-${size}`, !top && 'modal-dialog-centered', scrollable && 'modal-dialog-scrollable'].filter(Boolean).join(' ')
+const modalClass = ['modal modal-blur fade', className, show && 'show'].filter(Boolean).join(' ');
+const dialogClass = [
+ 'modal-dialog',
+ size && `modal-${size}`,
+ !top && 'modal-dialog-centered',
+ scrollable && 'modal-dialog-scrollable',
+]
+ .filter(Boolean)
+ .join(' ');
+---
-addPageModal(
- Astro.slots.render('default').then(
- (content) =>
- `
-
-
+
+
-`,
- ),
-)
----
+
diff --git a/shared/components/modals/SignatureModalContent.astro b/shared/components/modals/SignatureModalContent.astro
index 84a4e3d7b..46469f5fe 100644
--- a/shared/components/modals/SignatureModalContent.astro
+++ b/shared/components/modals/SignatureModalContent.astro
@@ -1,5 +1,5 @@
---
-// The `sample` flag on the Liquid include is inert (ui/signature.html never reads it).
+// The `sample` flag is unused.
import CardSubtitle from '@ui/CardSubtitle.astro'
import ModalClose from './ModalClose.astro'
diff --git a/shared/components/navbar/Navbar.astro b/shared/components/navbar/Navbar.astro
index 3a0e3ad7f..dc20d8de6 100644
--- a/shared/components/navbar/Navbar.astro
+++ b/shared/components/navbar/Navbar.astro
@@ -1,21 +1,11 @@
---
-// Full port of layout/navbar.html (both condensed and non-condensed branches).
-// Called from DefaultLayout with the params default.html forwards (condensed,
-// overlap, dark, hideBrand, sticky, transparent, class, hideSearch — always
-// true there) and standalone from navigation.astro with the showcase params
-// (sample, personId, hideLogo, smallLogo, showTitle, hideIcons, hideUsername,
-// hideSearch, hideBrand, background, backgroundColor, fluidSearch).
+// Condensed and non-condensed navbar branches. DefaultLayout passes condensed,
+// overlap, dark, hideBrand, sticky, transparent, class, hideSearch; navigation
+// showcase pages also use sample, personId, hideLogo, smallLogo, showTitle,
+// hideIcons, hideUsername, background, backgroundColor, fluidSearch.
//
-// TODO: unported Liquid params (unused by any ported page):
-// - hide-menu (the `elsif include.hide-menu` branch in the condensed collapse:
-// search only, rounded=include.transparent),
-// - dark-secondary (data-bs-theme="dark" on the non-condensed inner
-// `
`).
-//
-// Note on fluid-search: in the Liquid source the search block inside the
-// condensed branch sits in `{% unless condensed %}` nested in `{% if condensed %}`
-// — dead code, it can never render. fluid-search therefore has no effect on the
-// output; the prop is accepted for parity with the include signature only.
+// fluidSearch has no effect on output (search in the condensed branch is dead
+// code); the prop is kept for call-site compatibility.
import Icon from '@ui/Icon.astro';
import NavbarLogo from './NavbarLogo.astro';
import NavbarToggler from './NavbarToggler.astro';
@@ -110,7 +100,6 @@ const headerClass = [
)
}
- {/* navbar.html passes the show-* flags with default: true */}
- {/* the Liquid search block here is inside `unless condensed` — dead code, nothing to render */}
+ {/* Search block omitted when condensed — nothing to render */}
)}
diff --git a/shared/components/navbar/NavbarLogo.astro b/shared/components/navbar/NavbarLogo.astro
index 009442ff5..86d786212 100644
--- a/shared/components/navbar/NavbarLogo.astro
+++ b/shared/components/navbar/NavbarLogo.astro
@@ -1,54 +1,57 @@
---
-// TODO: unported Liquid params: prefix (the "sidebar-brand" variant, used only
-// in layouts with a sidebar), breakpoint (assigned in Liquid but unused in the
-// markup), href ("/{href}" appended to page|relative — no ported page passes
-// it).
-import { site } from '@shared/lib/site'
+import { site } from '@shared/lib/site';
interface Props {
- href?: string
- class?: string
- /** wraps the logo in
(header=true param) */
- header?: boolean
- smallLogo?: boolean
- hideLogo?: boolean
- showTitle?: boolean
+ href?: string;
+ class?: string;
+ /** wraps the logo in
(header=true param) */
+ header?: boolean;
+ smallLogo?: boolean;
+ hideLogo?: boolean;
+ showTitle?: boolean;
}
-const { href = '.', class: className, header = false, smallLogo = false, hideLogo = false, showTitle = false } = Astro.props
+const {
+ href = '.',
+ class: className,
+ header = false,
+ smallLogo = false,
+ hideLogo = false,
+ showTitle = false,
+} = Astro.props;
-const brandClasses = ['navbar-brand navbar-brand-autodark', className]
-const logoClass = `navbar-brand-image${showTitle ? ' me-3' : ''}`
+const brandClasses = ['navbar-brand navbar-brand-autodark', className];
+const logoClass = `navbar-brand-image${showTitle ? ' me-3' : ''}`;
const logoMark =
- '
'
+ '
';
const logoText =
- '
'
+ '
';
-const logoSvg = smallLogo ? `
${logoMark} ` : `
${logoMark}${logoText} `
+const logoSvg = smallLogo
+ ? `
${logoMark} `
+ : `
${logoMark}${logoText} `;
---
{
- header ? (
-
- '} />
-
- '} />
-
- ) : (
-
- '} />
-
- {!hideLogo && }
- {showTitle && 'Dashboard'}
-
- '} />
-
- )
+ header ? (
+
+ '} />
+
+ '} />
+
+ ) : (
+
+ '} />
+ {!hideLogo && }{showTitle && 'Dashboard'}
+ '} />
+
+ )
}
diff --git a/shared/components/navbar/NavbarMenu.astro b/shared/components/navbar/NavbarMenu.astro
index eba139b66..64633be17 100644
--- a/shared/components/navbar/NavbarMenu.astro
+++ b/shared/components/navbar/NavbarMenu.astro
@@ -1,102 +1,132 @@
---
-// TODO: %ICONS_COUNT% substitution in titles (Liquid: icons-info.count) —
-// menu.json doesn't contain that placeholder and src/data has no icons-info.json,
-// so we skip it for now.
-import Icon from '@ui/Icon.astro'
-import NavbarMenuItem from './NavbarMenuItem.astro'
-import menu from '@data/menu.json'
-import menuSample from '@data/menu-sample.json'
+// TODO: %ICONS_COUNT% substitution in titles — menu.json has no placeholder, skipped for now.
+import Icon from '@ui/Icon.astro';
+import NavbarMenuItem from './NavbarMenuItem.astro';
+import menu from '@data/menu.json';
+import menuSample from '@data/menu-sample.json';
interface Level1 {
- 'title': string
- 'title-long'?: string
- 'icon'?: string
- 'url'?: string
- 'badge'?: string
- 'active'?: boolean
- 'disabled'?: boolean
- 'right'?: boolean
- 'columns'?: number
- 'children'?: Record>
+ title: string;
+ 'title-long'?: string;
+ icon?: string;
+ url?: string;
+ badge?: string;
+ active?: boolean;
+ disabled?: boolean;
+ right?: boolean;
+ columns?: number;
+ children?: Record>;
}
interface Props {
- /** equivalent of the page-menu front matter, e.g. "dashboards.default" */
- pageMenu?: string
- sample?: boolean
- hideIcons?: boolean
- longTitles?: boolean
- keepOpen?: boolean
- autoOpen?: boolean
- class?: string
+ /** equivalent of the page-menu front matter, e.g. "dashboards.default" */
+ pageMenu?: string;
+ sample?: boolean;
+ hideIcons?: boolean;
+ longTitles?: boolean;
+ keepOpen?: boolean;
+ autoOpen?: boolean;
+ class?: string;
}
-const { pageMenu = '', sample = false, hideIcons = false, longTitles = false, keepOpen = false, autoOpen = false, class: className } = Astro.props
+const {
+ pageMenu = '',
+ sample = false,
+ hideIcons = false,
+ longTitles = false,
+ keepOpen = false,
+ autoOpen = false,
+ class: className,
+} = Astro.props;
-const currentPage = pageMenu.split('.')
-const items = Object.entries((sample ? menuSample : menu) as Record)
+const currentPage = pageMenu.split('.');
+const items = Object.entries((sample ? menuSample : menu) as Record);
-// Columns as in Liquid: per-column = ceil(size / columns), break every per-column.
+// per-column = ceil(size / columns), break every per-column.
const chunk = (arr: T[], size: number): T[][] => {
- const out: T[][] = []
- for (let i = 0; i < arr.length; i += size) out.push(arr.slice(i, i + size))
- return out
-}
+ const out: T[][] = [];
+ for (let i = 0; i < arr.length; i += size) out.push(arr.slice(i, i + size));
+ return out;
+};
---
diff --git a/shared/components/navbar/NavbarMenuItem.astro b/shared/components/navbar/NavbarMenuItem.astro
index 022972bf6..2fb726cc6 100644
--- a/shared/components/navbar/NavbarMenuItem.astro
+++ b/shared/components/navbar/NavbarMenuItem.astro
@@ -1,68 +1,88 @@
---
-// Level 2 (and 3) of the menu from layout/navbar-menu.html — a dropdown-item
-// entry, optionally with a nested dropend.
-import Icon from '@ui/Icon.astro'
+// Dropdown menu item, optionally with a nested dropend.
+import Icon from '@ui/Icon.astro';
interface Level3 {
- title: string
- url?: string
- badge?: string
+ title: string;
+ url?: string;
+ badge?: string;
}
interface Level2 extends Level3 {
- icon?: string
- color?: string
- children?: Record
+ icon?: string;
+ color?: string;
+ children?: Record;
}
interface Props {
- level1Key: string
- itemKey: string
- item: Level2
- currentPage: string[]
- keepOpen?: boolean
+ level1Key: string;
+ itemKey: string;
+ item: Level2;
+ currentPage: string[];
+ keepOpen?: boolean;
}
-const { level1Key, itemKey, item, currentPage, keepOpen = false } = Astro.props
+const { level1Key, itemKey, item, currentPage, keepOpen = false } = Astro.props;
-const hasChildren = !!item.children
-const isExternal = (url?: string) => !!url && (url.includes('http://') || url.includes('https://'))
-const relative = (url?: string) => (isExternal(url) ? url : `./${url}`)
+const hasChildren = !!item.children;
+const isExternal = (url?: string) => !!url && (url.includes('http://') || url.includes('https://'));
+const relative = (url?: string) => (isExternal(url) ? url : `./${url}`);
-const active = level1Key === currentPage[0] && itemKey === currentPage[1] && currentPage.length === 2
+const active = level1Key === currentPage[0] && itemKey === currentPage[1] && currentPage.length === 2;
-const itemClasses = ['dropdown-item', hasChildren && 'dropdown-toggle', active && 'active', item.color && `text-${item.color}`]
+const itemClasses = [
+ 'dropdown-item',
+ hasChildren && 'dropdown-toggle',
+ active && 'active',
+ item.color && `text-${item.color}`,
+]
---
-{
- hasChildren ? (
-
+ ) : (
+
+ {item.icon && }
- {item.title}
+ {item.title}
- {item.badge && {item.badge} }
-
- )
+ {item.badge && {item.badge} }
+
+ )
}
diff --git a/shared/components/navbar/NavbarSearch.astro b/shared/components/navbar/NavbarSearch.astro
index b605b158f..7f2cd0096 100644
--- a/shared/components/navbar/NavbarSearch.astro
+++ b/shared/components/navbar/NavbarSearch.astro
@@ -1,22 +1,20 @@
---
-// The Liquid include assigns `breakpoint` (default 'lg') but never uses it in
-// the markup, so the param is intentionally not ported.
-// Form action: Liquid `{{ page | relative }}/` → "./" (all ported pages are
-// root-level, see MIGRATION.md).
-import InputIcon from '@ui/form/InputIcon.astro'
+// breakpoint param (default 'lg') is unused.
+// Form action is "./" for root-level pages.
+import InputIcon from '@ui/form/InputIcon.astro';
interface Props {
- class?: string
- rounded?: boolean
+ class?: string;
+ rounded?: boolean;
}
-const { class: className, rounded = false } = Astro.props
+const { class: className, rounded = false } = Astro.props;
---
-
-
-
+
+
+
diff --git a/shared/components/navbar/NavbarSide.astro b/shared/components/navbar/NavbarSide.astro
index bc5851343..497741af2 100644
--- a/shared/components/navbar/NavbarSide.astro
+++ b/shared/components/navbar/NavbarSide.astro
@@ -1,71 +1,69 @@
---
-import Button from '@ui/Button.astro'
-import ButtonList from '@ui/ButtonList.astro'
-import NavbarSideTheme from './NavbarSideTheme.astro'
-import NavbarSideNotifications from './NavbarSideNotifications.astro'
-import NavbarSideApps from './NavbarSideApps.astro'
-import NavbarSideLanguage from './NavbarSideLanguage.astro'
-import NavbarSideUser from './NavbarSideUser.astro'
+import Button from '@ui/Button.astro';
+import ButtonList from '@ui/ButtonList.astro';
+import NavbarSideTheme from './NavbarSideTheme.astro';
+import NavbarSideNotifications from './NavbarSideNotifications.astro';
+import NavbarSideApps from './NavbarSideApps.astro';
+import NavbarSideLanguage from './NavbarSideLanguage.astro';
+import NavbarSideUser from './NavbarSideUser.astro';
interface Props {
- class?: string
- breakpoint?: string
- condensed?: boolean
- personId?: number
- hideUsername?: boolean
- dark?: boolean
- showThemeToggle?: boolean
- showNotifications?: boolean
- showApps?: boolean
- showLanguageSelector?: boolean
- showUser?: boolean
+ class?: string;
+ breakpoint?: string;
+ condensed?: boolean;
+ personId?: number;
+ hideUsername?: boolean;
+ dark?: boolean;
+ showThemeToggle?: boolean;
+ showNotifications?: boolean;
+ showApps?: boolean;
+ showLanguageSelector?: boolean;
+ showUser?: boolean;
}
const {
- class: className,
- breakpoint = 'md',
- condensed = false,
- personId = 1,
- hideUsername = false,
- dark = false,
- // As in layout/navbar-side.html: show-* flags not passed in are falsy
- // (navbar.html explicitly passes true, sidebar.html passes nothing —
- // in the sidebar only the Sponsor button renders).
- showThemeToggle = false,
- showNotifications = false,
- showApps = false,
- showLanguageSelector = false,
- showUser = false,
-} = Astro.props
+ class: className,
+ breakpoint = 'md',
+ condensed = false,
+ personId = 1,
+ hideUsername = false,
+ dark = false,
+ // show-* flags default false — sidebar only shows the Sponsor button unless set.
+ showThemeToggle = false,
+ showNotifications = false,
+ showApps = false,
+ showLanguageSelector = false,
+ showUser = false,
+} = Astro.props;
// TODO: site.githubSponsorsUrl — add to src/lib/site.ts (out of scope for this
// task; value from /Users/chomik/htdocs/tabler/shared/data/site.json).
-const githubSponsorsUrl = 'https://github.com/sponsors/codecalm'
+const githubSponsorsUrl = 'https://github.com/sponsors/codecalm';
---
- {
- !condensed && (
-
-
-
-
-
- )
- }
+ {
+ !condensed && (
+
+
+
+
+
+ )
+ }
- {
- (showThemeToggle || showNotifications || showApps || showLanguageSelector) && (
-
- {showThemeToggle && }
- {showNotifications && }
- {showApps && }
- {showLanguageSelector && }
-
- )
- }
+ {
+ (showThemeToggle || showNotifications || showApps || showLanguageSelector) && (
+
+ {showThemeToggle && }
+ {showNotifications && }
+ {showApps && }
+ {showLanguageSelector && }
+
+ )
+ }
- {showUser &&
}
+ {showUser &&
}
diff --git a/shared/components/navbar/NavbarSideApps.astro b/shared/components/navbar/NavbarSideApps.astro
index a0e0d42b6..323bbdae8 100644
--- a/shared/components/navbar/NavbarSideApps.astro
+++ b/shared/components/navbar/NavbarSideApps.astro
@@ -1,44 +1,50 @@
---
-// (the cards/navbar-apps.html card inlined — only the navbar uses it).
-
-import CardActions from '@ui/CardActions.astro'
-import Icon from '@ui/Icon.astro'
-import brands from '@data/brands.json'
+import CardActions from '@ui/CardActions.astro';
+import Icon from '@ui/Icon.astro';
+import brands from '@data/brands.json';
---
diff --git a/shared/components/navbar/NavbarSideNotifications.astro b/shared/components/navbar/NavbarSideNotifications.astro
index 95eadb4fc..df7dea329 100644
--- a/shared/components/navbar/NavbarSideNotifications.astro
+++ b/shared/components/navbar/NavbarSideNotifications.astro
@@ -1,91 +1,106 @@
---
-// (the cards/navbar-notifications.html card inlined — only the navbar uses it).
-import Icon from '@ui/Icon.astro'
-import Button from '@ui/Button.astro'
-import ListGroup from '@ui/ListGroup.astro'
-import ListGroupItem from '@ui/ListGroupItem.astro'
-import CardTitle from '@ui/CardTitle.astro'
+import Icon from '@ui/Icon.astro';
+import Button from '@ui/Button.astro';
+import ListGroup from '@ui/ListGroup.astro';
+import ListGroupItem from '@ui/ListGroupItem.astro';
+import CardTitle from '@ui/CardTitle.astro';
---
diff --git a/shared/components/navbar/NavbarSideUser.astro b/shared/components/navbar/NavbarSideUser.astro
index 53726014f..e1d7045c7 100644
--- a/shared/components/navbar/NavbarSideUser.astro
+++ b/shared/components/navbar/NavbarSideUser.astro
@@ -1,43 +1,40 @@
---
-// Avatar (ui/avatar.html) inlined — the ui/* components are handled by another agent.
-// Note: in Liquid, person-id=1 yields person people[0] in the Eleventy output
-// (Paweł Kuna, photo 000m.jpg) — so we index 1-based, like ui/avatar.html
-// (person-id | minus: 1).
-import Icon from '@ui/Icon.astro'
-import Avatar from '@ui/Avatar.astro'
-import people from '@data/people.json'
+// person-id=1 → people[0] (1-based index).
+import Icon from '@ui/Icon.astro';
+import Avatar from '@ui/Avatar.astro';
+import people from '@data/people.json';
interface Props {
- personId?: number
- hideUsername?: boolean
- dark?: boolean
+ personId?: number;
+ hideUsername?: boolean;
+ dark?: boolean;
}
-const { personId = 1, hideUsername = false, dark = false } = Astro.props
+const { personId = 1, hideUsername = false, dark = false } = Astro.props;
-const person = people[personId - 1]
+const person = people[personId - 1];
---
diff --git a/shared/components/navbar/Sidebar.astro b/shared/components/navbar/Sidebar.astro
index fb13e0974..a42fa1486 100644
--- a/shared/components/navbar/Sidebar.astro
+++ b/shared/components/navbar/Sidebar.astro
@@ -1,44 +1,45 @@
---
-// dark=layout-sidebar-dark, end=layout-sidebar-end,
-// transparent=layout-navbar-transparent, breakpoint="lg").
-//
-// TODO: unported Liquid params (unused by the ported pages):
-// - background / background-color, class, hide-brand,
-// hide-username / person-id (passed down to navbar-side).
-import NavbarToggler from './NavbarToggler.astro'
-import NavbarLogo from './NavbarLogo.astro'
-import NavbarSide from './NavbarSide.astro'
-import NavbarMenu from './NavbarMenu.astro'
+import NavbarToggler from './NavbarToggler.astro';
+import NavbarLogo from './NavbarLogo.astro';
+import NavbarSide from './NavbarSide.astro';
+import NavbarMenu from './NavbarMenu.astro';
interface Props {
- dark?: boolean
- breakpoint?: string
- /** layout-sidebar-end — navbar-end class */
- end?: boolean
- /** layout-navbar-transparent — navbar-transparent class */
- transparent?: boolean
- /** equivalent of the page-menu front matter (active menu entry), e.g. "layout.vertical" */
- pageMenu?: string
+ dark?: boolean;
+ breakpoint?: string;
+ /** layout-sidebar-end — navbar-end class */
+ end?: boolean;
+ /** layout-navbar-transparent — navbar-transparent class */
+ transparent?: boolean;
+ /** equivalent of the page-menu front matter (active menu entry), e.g. "layout.vertical" */
+ pageMenu?: string;
}
-const { dark = false, breakpoint = 'lg', end = false, transparent = false, pageMenu } = Astro.props
+const { dark = false, breakpoint = 'lg', end = false, transparent = false, pageMenu } = Astro.props;
-const asideClass = ['navbar navbar-vertical', end && 'navbar-end', `navbar-expand-${breakpoint}`, transparent && 'navbar-transparent'].filter(Boolean).join(' ')
+const asideClass = [
+ 'navbar navbar-vertical',
+ end && 'navbar-end',
+ `navbar-expand-${breakpoint}`,
+ transparent && 'navbar-transparent',
+]
+ .filter(Boolean)
+ .join(' ');
---
diff --git a/shared/components/parts/Datagrid.astro b/shared/components/parts/Datagrid.astro
index ea2c3f06d..046a28bd8 100644
--- a/shared/components/parts/Datagrid.astro
+++ b/shared/components/parts/Datagrid.astro
@@ -1,92 +1,85 @@
---
-// Equivalent of parts/datagrid.html (static demo datagrid, no parameters).
-//
-// Nested includes used only here are inlined:
-// - ui/status.html (call: text="Active" color="green") — inlined; the dot/animated/lite
-// branches of the Liquid include are not ported (unused here).
-// - ui/avatar-list.html (call: stacked=true text="+3" size="xs")
-// TODO: the Liquid avatar includes pass rounded=true, which ui/avatar.html ignores
-// (no output) — intentionally not ported.
-import Avatar from '@ui/Avatar.astro'
-import AvatarList from '@ui/AvatarList.astro'
-import Icon from '@ui/Icon.astro'
-import people from '@data/people.json'
+// rounded=true on avatar includes has no effect.
+import Avatar from '@ui/Avatar.astro';
+import AvatarList from '@ui/AvatarList.astro';
+import Icon from '@ui/Icon.astro';
+import people from '@data/people.json';
interface Person {
- full_name?: string
- photo?: string
- [key: string]: unknown
+ full_name?: string;
+ photo?: string;
+ [key: string]: unknown;
}
-const creator = (people as Person[])[0]
+const creator = (people as Person[])[0];
---
-
-
Registrar
-
Third Party
-
-
-
Nameservers
-
Third Party
-
-
-
-
-
Creator
-
-
-
- {creator.full_name}
-
-
-
-
-
-
Edge network
-
- Active
-
-
-
-
-
Checkbox
-
-
-
- Click me
-
-
-
-
-
-
-
Longer description
-
Lorem ipsum dolor sit amet, consectetur adipisicing elit.
-
+
+
Registrar
+
Third Party
+
+
+
Nameservers
+
Third Party
+
+
+
+
+
Creator
+
+
+
+ {creator.full_name}
+
+
+
+
+
+
Edge network
+
+ Active
+
+
+
+
+
Checkbox
+
+
+
+ Click me
+
+
+
+
+
+
+
Longer description
+
Lorem ipsum dolor sit amet, consectetur adipisicing elit.
+
diff --git a/shared/components/parts/NavAside.astro b/shared/components/parts/NavAside.astro
index 923d6ed86..c7619cdd1 100644
--- a/shared/components/parts/NavAside.astro
+++ b/shared/components/parts/NavAside.astro
@@ -1,79 +1,89 @@
---
-// Equivalent of parts/nav/nav-aside.html (search filter aside, no parameters).
-// `{{ page | relative }}/` resolves to "./" for root-level pages.
-import Subheader from '@ui/Subheader.astro'
-import InputGroup from '@ui/InputGroup.astro'
-import Button from '@ui/Button.astro'
-import ListGroup from '@ui/ListGroup.astro'
-import ListGroupItem from '@ui/ListGroupItem.astro'
-import { randomNumber } from '@shared/lib/pseudo-random'
+import Subheader from '@ui/Subheader.astro';
+import InputGroup from '@ui/InputGroup.astro';
+import Button from '@ui/Button.astro';
+import ListGroup from '@ui/ListGroup.astro';
+import ListGroupItem from '@ui/ListGroupItem.astro';
+import { randomNumber } from '@shared/lib/pseudo-random';
-const categories = ['Games', 'Clothing', 'Jewelery', 'Toys']
-const ratings = ['5 stars', '4 stars', '3 stars', '2 and less stars']
-const tags = ['business', 'evening', 'leisure', 'party']
+const categories = ['Games', 'Clothing', 'Jewelery', 'Toys'];
+const ratings = ['5 stars', '4 stars', '3 stars', '2 and less stars'];
+const tags = ['business', 'evening', 'leisure', 'party'];
---
- Category
-
- {
- categories.map((item, i) => (
-
- {item}
- {randomNumber(i + 1, 11, 200)}
-
- ))
- }
-
+ Category
+
+ {
+ categories.map((item, i) => (
+
+ {item}
+ {randomNumber(i + 1, 11, 200)}
+
+ ))
+ }
+
- Rating
-
- {
- ratings.map((item, i) => (
-
-
- {item}
-
- ))
- }
-
+ Rating
+
+ {
+ ratings.map((item, i) => (
+
+
+ {item}
+
+ ))
+ }
+
- Tags
-
- {
- tags.map((item, i) => (
-
-
- {item}
-
- ))
- }
-
+ Tags
+
+ {
+ tags.map((item, i) => (
+
+
+ {item}
+
+ ))
+ }
+
- Price
-
+ Price
+
- Shipping
-
-
- United Kingdom
- USA
- Germany
- Poland
- Other…
-
-
+ Shipping
+
+
+ United Kingdom
+ USA
+ Germany
+ Poland
+ Other…
+
+
-
-
-
-
+
+
+
+
diff --git a/shared/components/parts/Tasks.astro b/shared/components/parts/Tasks.astro
index deeec56c3..94894d713 100644
--- a/shared/components/parts/Tasks.astro
+++ b/shared/components/parts/Tasks.astro
@@ -1,134 +1,141 @@
---
-// Inlines ui/avatar-list.html (stacked, size xs) and ui/switch-icon.html
-// (heart, scale variant) — the exact variants this part uses.
-// NOTE: the Liquid `task.due-date` reads the hyphenated key which does not
-// exist in tasks.json (the field is `due_date`), so the due-date branch never
-// renders — reproduced faithfully by reading task['due-date'] (undefined).
-// The avatar-list offset uses task['users-offset'] (the hyphenated key that
-// DOES exist), matching the Eleventy output.
-import Icon from '@ui/Icon.astro'
-import AvatarList from '@ui/AvatarList.astro'
-import CardTitle from '@ui/CardTitle.astro'
-import tasksData from '@data/tasks.json'
+// task['due-date'] is undefined (field is due_date) — due-date branch never renders.
+// Avatar offset uses task['users-offset'] (hyphenated key).
+import Icon from '@ui/Icon.astro';
+import AvatarList from '@ui/AvatarList.astro';
+import CardTitle from '@ui/CardTitle.astro';
+import tasksData from '@data/tasks.json';
interface Column {
- name: string
- tasks: Record[]
+ name: string;
+ tasks: Record[];
}
interface Props {
- data?: { columns: Column[] }
- class?: string
+ data?: { columns: Column[] };
+ class?: string;
}
-const { data = tasksData as unknown as { columns: Column[] }, class: className } = Astro.props
+const { data = tasksData as unknown as { columns: Column[] }, class: className } = Astro.props;
---
- {
- data.columns.map((column) => (
-
-
{column.name}
+ {
+ data.columns.map((column) => (
+
+
{column.name}
-
-
- {column.tasks.map((task) => (
-
-
- {task.color &&
}
+
+
+ {column.tasks.map((task) => (
+
+
+ {task.color &&
}
- {task.starred && (
-
-
-
- )}
+ {task.starred && (
+
+
+
+ )}
-
-
{task.name}
+
+
{task.name}
- {/* Liquid outputs the description unescaped; it may contain markup (e.g.
#tag ) */}
- {task.description &&
}
+ {/* Description may contain markup (e.g.
#tag ) — rendered with set:html */}
+ {task.description &&
}
- {task.image && (
-
-
-
- )}
+ {task.image && (
+
+
+
+ )}
-
-
-
+
+
+
+ {task.users && (
+
+ )}
+
- {task['due-date'] && (
-
- )}
+ {task['due-date'] && (
+
+ )}
-
-
-
-
-
-
-
-
-
- {task.likes ? task.likes : ''}
-
+
+
+
+
+
+
+
+
+
+ {task.likes ? task.likes : ''}
+
- {task.subtasks && (
-
- )}
- {task.comments && (
-
- )}
-
-
-
+ {task.subtasks && (
+
+ )}
+ {task.comments && (
+
+ )}
+
+
+
- {task.subtasks && (
-
- {task.subtasks.map((subtask: { name: string; done?: boolean }) => (
-
- {subtask.done ? (
-
-
- {subtask.name}
-
- ) : (
-
-
- {subtask.name}
-
- )}
-
- ))}
-
- )}
-
-
-
- ))}
-
-
-
- ))
- }
+ {task.subtasks && (
+
+ {task.subtasks.map((subtask: { name: string; done?: boolean }) => (
+
+ {subtask.done ? (
+
+
+ {subtask.name}
+
+ ) : (
+
+
+ {subtask.name}
+
+ )}
+
+ ))}
+
+ )}
+
+
+
+ ))}
+
+
+
+ ))
+ }
diff --git a/shared/layouts/BaseLayout.astro b/shared/layouts/BaseLayout.astro
index d4c09a2ff..57c570f8a 100644
--- a/shared/layouts/BaseLayout.astro
+++ b/shared/layouts/BaseLayout.astro
@@ -1,134 +1,146 @@
---
-import ThemeSettings from '@ui/ThemeSettings.astro'
-import { site } from '@shared/lib/site'
-import PageModals from '@shared/components/PageModals.astro'
-import libs from '@tabler/core/libs.json'
+import ThemeSettings from '@ui/ThemeSettings.astro';
+import { site } from '@shared/lib/site';
+import PageModals from '@shared/components/PageModals.astro';
+import PageScripts from '@shared/components/PageScripts.astro';
+import libs from '@tabler/core/libs.json';
interface Props {
- title?: string
- /** page-libs from the Eleventy front matter, e.g. ['apexcharts', 'jsvectormap'] */
- pageLibs?: string[]
- /** body-class front matter, e.g. "layout-boxed" / "layout-fluid" */
- bodyClass?: string
- /** layout-rtl — dir="rtl" + RTL variants of the tabler/plugin stylesheets */
- rtl?: boolean
- /** relative asset base (Liquid `page | relative`): '.' for root pages, '..' one level deep */
- base?: string
+ title?: string;
+ /** Third-party libs from libs.json, e.g. ['apexcharts', 'jsvectormap'] */
+ pageLibs?: string[];
+ /** Extra body classes, e.g. "layout-boxed" / "layout-fluid" */
+ bodyClass?: string;
+ /** dir="rtl" + RTL variants of the tabler/plugin stylesheets */
+ rtl?: boolean;
+ /** Relative asset base: '.' for root pages, '..' one level deep */
+ base?: string;
}
-const { title, pageLibs = [], bodyClass, rtl = false, base = '.' } = Astro.props
+const { title, pageLibs = [], bodyClass, rtl = false, base = '.' } = Astro.props;
-// PoC mirrors the Eleventy development build: unminified assets, dev favicon.
-const environment = 'development'
-const min = environment === 'development' ? '' : '.min'
+// Development build: unminified assets, dev favicon.
+const environment = 'development';
+const min = environment === 'development' ? '' : '.min';
// RTL swaps tabler/plugin stylesheets for their .rtl.css variants (demo stays LTR)
-const rtlSuffix = rtl ? '.rtl' : ''
+const rtlSuffix = rtl ? '.rtl' : '';
-type Lib = { npm?: string; js?: string[]; css?: string[]; head?: boolean }
-const pageLibEntries = Object.entries(libs as Record).filter(([name]) => pageLibs.includes(name))
-// dev build uses the dev Google Maps key (Liquid: google-maps-key)
-const googleMapsKey = site.googleMapsDevKey
-// Liquid: external URLs (http/https) are emitted verbatim (with the maps-key
-// placeholder replaced), local files as ./dist/libs/{npm}/{file}
-const libHref = (lib: Lib, file: string) => (/^https?:\/\//.test(file) ? file.replace('GOOGLE_MAPS_KEY', googleMapsKey) : `${base}/dist/libs/${lib.npm}/${file}`)
-const libCssFiles = pageLibEntries.flatMap(([, lib]) => (lib.css ?? []).map((file) => libHref(lib, file)))
-const libJsFiles = (head: boolean) => pageLibEntries.filter(([, lib]) => Boolean(lib.head) === head).flatMap(([, lib]) => (lib.js ?? []).map((file) => libHref(lib, file)))
+type Lib = { npm?: string; js?: string[]; css?: string[]; head?: boolean };
+const pageLibEntries = Object.entries(libs as Record).filter(([name]) =>
+ pageLibs.includes(name),
+);
+const googleMapsKey = site.googleMapsDevKey;
+// External URLs (http/https) are emitted verbatim (with the maps-key placeholder
+// replaced); local files as ./dist/libs/{npm}/{file}.
+const libHref = (lib: Lib, file: string) =>
+ /^https?:\/\//.test(file)
+ ? file.replace('GOOGLE_MAPS_KEY', googleMapsKey)
+ : `${base}/dist/libs/${lib.npm}/${file}`;
+const libCssFiles = pageLibEntries.flatMap(([, lib]) =>
+ (lib.css ?? []).map((file) => libHref(lib, file)),
+);
+const libJsFiles = (head: boolean) =>
+ pageLibEntries
+ .filter(([, lib]) => Boolean(lib.head) === head)
+ .flatMap(([, lib]) => (lib.js ?? []).map((file) => libHref(lib, file)));
---
-
-
-
-
-
+
+
+
+
+
- {title ? `${title} - ` : ''}{site.title} - {site.descriptionShort}
+ {title ? `${title} - ` : ''}{site.title} - {site.descriptionShort}
-
-
-
+
+
+
- {
- libCssFiles.length > 0 && (
-
- '} />
- {libCssFiles.map((href) => (
-
- ))}
- '} />
-
- )
- }
+ {
+ libCssFiles.length > 0 && (
+
+ '} />
+ {libCssFiles.map((href) => (
+
+ ))}
+ '} />
+
+ )
+ }
-
-
-
+
+
+
-
- {site.cssPlugins.map((plugin) => )}
-
+
+ {site.cssPlugins.map((plugin) => )}
+
-
-
-
+
+
+
- {
- libJsFiles(true).length > 0 && (
-
- '} />
- {libJsFiles(true).map((src) => (
-
- ))}
- '} />
-
- )
- }
+ {
+ libJsFiles(true).length > 0 && (
+
+ '} />
+ {libJsFiles(true).map((src) => (
+
+ ))}
+ '} />
+
+ )
+ }
-
- {/* is:inline: keep the tag verbatim — Astro's scoped-style pipeline would add data-astro-cid-* attributes across the page */}
-
-
-
+
+ {/* is:inline: keep the tag verbatim — Astro's scoped-style pipeline would add data-astro-cid-* attributes across the page */}
+
+
+
-
- Skip to main content
+
+ Skip to main content
-
-
-
+
+
+
-
+
- {/* equivalent of {% modals %}: modals registered by the page components */}
-
+
-
+
- {
- libJsFiles(false).length > 0 && (
-
- '} />
- {libJsFiles(false).map((src) => (
-
- ))}
- '} />
-
- )
- }
+ {
+ libJsFiles(false).length > 0 && (
+
+ '} />
+ {libJsFiles(false).map((src) => (
+
+ ))}
+ '} />
+
+ )
+ }
-
-
-
+
+
+
-
-
-
-
-
+
+
+
+
+
+
-
-
+ checkItems()
+ })
+
+
+
diff --git a/shared/layouts/DefaultLayout.astro b/shared/layouts/DefaultLayout.astro
index 73f06c66a..6ddc48d82 100644
--- a/shared/layouts/DefaultLayout.astro
+++ b/shared/layouts/DefaultLayout.astro
@@ -1,112 +1,148 @@
---
// TODO: no-container (-less body) — unused so far.
-// Note: the `blank: true` front matter (blank.html) is inert — it is not
-// referenced by any template or Eleventy config; the empty page header on that
-// page comes purely from `page-header` being unset. No prop for it.
-import BaseLayout from './BaseLayout.astro'
-import Navbar from '@shared/components/navbar/Navbar.astro'
-import Sidebar from '@shared/components/navbar/Sidebar.astro'
-import PageHeader from '@shared/components/layout/PageHeader.astro'
-import Footer from '@shared/components/layout/Footer.astro'
+import BaseLayout from './BaseLayout.astro';
+import Navbar from '@shared/components/navbar/Navbar.astro';
+import Sidebar from '@shared/components/navbar/Sidebar.astro';
+import PageHeader from '@shared/components/layout/PageHeader.astro';
+import Footer from '@shared/components/layout/Footer.astro';
interface Props {
- title?: string
- /** page-libs from the Eleventy front matter — passed down to BaseLayout */
- pageLibs?: string[]
- /** page-header — the title in the page header */
- pageHeader?: string
- /** page-header-file — renders a layout/headers/{file} component instead of the title block */
- pageHeaderFile?: string
- /** page-header-pretitle */
- pretitle?: string
- /** page-header-description */
- description?: string
- /** page-header-actions, e.g. "buttons" */
- actions?: string
- /** page-menu from the front matter, e.g. "layout.vertical" — active menu entry */
- pageMenu?: string
- /** layout-sidebar */
- sidebar?: boolean
- /** layout-sidebar-dark */
- sidebarDark?: boolean
- /** layout-hide-topbar */
- hideTopbar?: boolean
- /** page-container-centered — adds my-auto to the .container-xl */
- containerCentered?: boolean
- /** layout-wrapper-full — page-wrapper-full + no .container-xl around the body */
- wrapperFull?: boolean
- /** page-container-class — extra classes appended to the .container-xl */
- containerClass?: string
- /** body-class front matter (e.g. "layout-boxed"/"layout-fluid") → passed to BaseLayout */
- bodyClass?: string
- /** layout-rtl → passed to BaseLayout (dir="rtl" + RTL stylesheets) */
- rtl?: boolean
- /** layout-sidebar-end */
- sidebarEnd?: boolean
- /** layout-navbar-transparent (sidebar variant) */
- navbarTransparent?: boolean
- /** layout-navbar-condensed */
- navbarCondensed?: boolean
- /** layout-navbar-dark */
- navbarDark?: boolean
- /** layout-navbar-overlap */
- navbarOverlap?: boolean
- /** layout-navbar-sticky */
- navbarSticky?: boolean
- /** layout-navbar-hide-brand */
- navbarHideBrand?: boolean
- /** layout-navbar-class */
- navbarClass?: string
+ title?: string;
+ /** Script/CSS library keys passed to BaseLayout */
+ pageLibs?: string[];
+ /** page-header — the title in the page header */
+ pageHeader?: string;
+ /** page-header-file — renders a layout/headers/{file} component instead of the title block */
+ pageHeaderFile?: string;
+ /** page-header-pretitle */
+ pretitle?: string;
+ /** page-header-description */
+ description?: string;
+ /** page-header-actions, e.g. "buttons" */
+ actions?: string;
+ /** page-menu from the front matter, e.g. "layout.vertical" — active menu entry */
+ pageMenu?: string;
+ /** layout-sidebar */
+ sidebar?: boolean;
+ /** layout-sidebar-dark */
+ sidebarDark?: boolean;
+ /** layout-hide-topbar */
+ hideTopbar?: boolean;
+ /** page-container-centered — adds my-auto to the .container-xl */
+ containerCentered?: boolean;
+ /** layout-wrapper-full — page-wrapper-full + no .container-xl around the body */
+ wrapperFull?: boolean;
+ /** page-container-class — extra classes appended to the .container-xl */
+ containerClass?: string;
+ /** body-class front matter (e.g. "layout-boxed"/"layout-fluid") → passed to BaseLayout */
+ bodyClass?: string;
+ /** layout-rtl → passed to BaseLayout (dir="rtl" + RTL stylesheets) */
+ rtl?: boolean;
+ /** layout-sidebar-end */
+ sidebarEnd?: boolean;
+ /** layout-navbar-transparent (sidebar variant) */
+ navbarTransparent?: boolean;
+ /** layout-navbar-condensed */
+ navbarCondensed?: boolean;
+ /** layout-navbar-dark */
+ navbarDark?: boolean;
+ /** layout-navbar-overlap */
+ navbarOverlap?: boolean;
+ /** layout-navbar-sticky */
+ navbarSticky?: boolean;
+ /** layout-navbar-hide-brand */
+ navbarHideBrand?: boolean;
+ /** layout-navbar-class */
+ navbarClass?: string;
}
-const { title, pageLibs, pageHeader, pageHeaderFile, pretitle, description, actions, pageMenu, sidebar, sidebarDark, hideTopbar, containerCentered, wrapperFull, containerClass, bodyClass, rtl, sidebarEnd, navbarTransparent, navbarCondensed, navbarDark, navbarOverlap, navbarSticky, navbarHideBrand, navbarClass } =
- Astro.props
+const {
+ title,
+ pageLibs,
+ pageHeader,
+ pageHeaderFile,
+ pretitle,
+ description,
+ actions,
+ pageMenu,
+ sidebar,
+ sidebarDark,
+ hideTopbar,
+ containerCentered,
+ wrapperFull,
+ containerClass,
+ bodyClass,
+ rtl,
+ sidebarEnd,
+ navbarTransparent,
+ navbarCondensed,
+ navbarDark,
+ navbarOverlap,
+ navbarSticky,
+ navbarHideBrand,
+ navbarClass,
+} = Astro.props;
---
-
- {
- sidebar && (
-
- '} />
-
- '} />
-
- )
- }
+
+ {
+ sidebar && (
+
+ '} />
+
+ '} />
+
+ )
+ }
- {
- !hideTopbar && (
-
- '} />
-
- '} />
-
- )
- }
+ {
+ !hideTopbar && (
+
+ '} />
+
+ '} />
+
+ )
+ }
-
-
-
-
+
+
+
+
-
-
- {
- wrapperFull ? (
-
- ) : (
-
-
-
- )
- }
-
-
+
+
+ {
+ wrapperFull ? (
+
+ ) : (
+
+
+
+ )
+ }
+
+
-
-
-
-
-
+
+
+
+
+
diff --git a/shared/layouts/ErrorLayout.astro b/shared/layouts/ErrorLayout.astro
index 356316c59..047443b66 100644
--- a/shared/layouts/ErrorLayout.astro
+++ b/shared/layouts/ErrorLayout.astro
@@ -1,37 +1,42 @@
---
-// "border-top-wide border-primary", a .page-center container and ui/empty.html
-// fed from the errors.json entry keyed by the `page-error` front matter).
-import BaseLayout from './BaseLayout.astro'
-import Empty from '@ui/Empty.astro'
-import errors from '@data/errors.json'
+// Error page: border-top banner, centered Empty from errors.json (`pageError`).
+import BaseLayout from './BaseLayout.astro';
+import Empty from '@ui/Empty.astro';
+import errors from '@data/errors.json';
interface Props {
- title?: string
- /** page-error front matter — key into errors.json ("404" | "500" | "maintenance" | …) */
- pageError: string
+ title?: string;
+ /** page-error front matter — key into errors.json ("404" | "500" | "maintenance" | …) */
+ pageError: string;
}
-const { title, pageError } = Astro.props
+const { title, pageError } = Astro.props;
type ErrorEntry = {
- illustration?: string
- title?: string | number
- header?: string
- description?: string
-}
+ illustration?: string;
+ title?: string | number;
+ header?: string;
+ description?: string;
+};
-// {% assign error = errors[page-error] %}
-const error = (errors as Record
)[pageError] ?? {}
-// {% assign header = error.header | default: 'Oops… You just found an error page' %}
-const header = error.header ?? 'Oops… You just found an error page'
+const error = (errors as Record)[pageError] ?? {};
+const header = error.header ?? 'Oops… You just found an error page';
---
-
-
-
+
+
+
diff --git a/shared/layouts/MarketingLayout.astro b/shared/layouts/MarketingLayout.astro
index 89c5d71fa..6e2bad2c8 100644
--- a/shared/layouts/MarketingLayout.astro
+++ b/shared/layouts/MarketingLayout.astro
@@ -1,160 +1,178 @@
---
-// "body-marketing body-gradient", plugins: marketing).
-// The `plugins: marketing` front matter is already covered by site.cssPlugins
-// (which includes 'marketing'), so no extra handling is needed here.
-// The footer (products/support/tabler columns, payment icons, socials, and the
-// copyright bar) lives inline in this layout, exactly as in the Liquid source.
-
-import Subheader from '@ui/Subheader.astro'
-import BaseLayout from './BaseLayout.astro'
-import MarketingNavbar from '@shared/components/marketing/MarketingNavbar.astro'
-import Payment from '@ui/Payment.astro'
-import Icon from '@ui/Icon.astro'
-import { site } from '@shared/lib/site'
+import Subheader from '@ui/Subheader.astro';
+import BaseLayout from './BaseLayout.astro';
+import MarketingNavbar from '@shared/components/marketing/MarketingNavbar.astro';
+import Payment from '@ui/Payment.astro';
+import Icon from '@ui/Icon.astro';
+import { site } from '@shared/lib/site';
interface Props {
- title?: string
- /** page-libs from the Eleventy front matter, e.g. ['typed.js'] */
- pageLibs?: string[]
+ title?: string;
+ /** Script/CSS library keys, e.g. ['typed.js'] */
+ pageLibs?: string[];
}
-const { title, pageLibs } = Astro.props
+const { title, pageLibs } = Astro.props;
-// site.base is undefined in the Eleventy build → the product links resolve to
-// an empty href (href="").
-const base = ''
+const base = '';
---
-
-
-
+
+
+
-
-
-
-
-
+
+
+
+
+
-
-
-
-
+
+
diff --git a/shared/layouts/PayLayout.astro b/shared/layouts/PayLayout.astro
index 9bd3fbfc9..9ed7b11fc 100644
--- a/shared/layouts/PayLayout.astro
+++ b/shared/layouts/PayLayout.astro
@@ -1,35 +1,35 @@
---
// Minimal navbar (small logo + close button) then a .page with the content.
-import BaseLayout from './BaseLayout.astro'
-import NavbarLogo from '@shared/components/navbar/NavbarLogo.astro'
+import BaseLayout from './BaseLayout.astro';
+import NavbarLogo from '@shared/components/navbar/NavbarLogo.astro';
interface Props {
- title?: string
- /** page-libs from the Eleventy front matter — passed down to BaseLayout */
- pageLibs?: string[]
- /** body-class front matter — passed down to BaseLayout */
- bodyClass?: string
+ title?: string;
+ /** Script/CSS library keys passed to BaseLayout */
+ pageLibs?: string[];
+ /** body-class front matter — passed down to BaseLayout */
+ bodyClass?: string;
}
-const { title, pageLibs, bodyClass } = Astro.props
+const { title, pageLibs, bodyClass } = Astro.props;
---
-
-
+
-
-
-
-
-
+
+
+
+
+
diff --git a/shared/layouts/RedirectLayout.astro b/shared/layouts/RedirectLayout.astro
index 0a64aa7ad..02c9f3362 100644
--- a/shared/layouts/RedirectLayout.astro
+++ b/shared/layouts/RedirectLayout.astro
@@ -1,47 +1,31 @@
---
-// (a bare meta-refresh document).
-//
-// The include computes:
-// {% if url contains 'http://' or 'https://' %}{{ url }}
-// {% else %}{{ page | relative }}{{ url }}{% endif %}
-// For root-level pages `{{ page | relative }}` resolves to ".".
+// Bare meta-refresh redirect document.
interface Props {
- /**
- * include.url — the raw redirect target. NOTE: shared/layouts/redirect.html
- * passes `page.redirect.to`, but in Eleventy `page` is the built-in page
- * object (no front matter), so `page.redirect` is undefined and the include
- * receives an empty url → the target collapses to the relative root ".".
- * The reference build (markdown.html) redirects to "." for this reason.
- */
- url?: string
- /**
- * `{{ page | relative }}` — the relative path to the site root from this
- * page. Defaults to "." (root-level pages); nested pages pass e.g. "../../..".
- */
- base?: string
+ /** Redirect target. Absolute http(s) URLs are used as-is; relative ones are prefixed with `base`. */
+ url?: string;
+ /** Relative path to the site root from this page. Defaults to ".". */
+ base?: string;
}
-const { url = '', base = '.' } = Astro.props
-const isAbsolute = url.includes('http://') || url.includes('https://')
-const target = isAbsolute ? url : `${base}${url}`
+const { url = '', base = '.' } = Astro.props;
+const isAbsolute = url.includes('http://') || url.includes('https://');
+const target = isAbsolute ? url : `${base}${url}`;
---
-
-
- Redirecting…
-
-
-
-
- Click here if you are not redirected.
-
-
-
-
-
+
+
+ Redirecting…
+
+
+
+
+ Click here if you are not redirected.
+
+
+
diff --git a/shared/layouts/SettingsLayout.astro b/shared/layouts/SettingsLayout.astro
index 38f9665d4..5b2dd6d5f 100644
--- a/shared/layouts/SettingsLayout.astro
+++ b/shared/layouts/SettingsLayout.astro
@@ -1,6 +1,6 @@
---
// Wraps the page body in a card with a left settings-nav column; the active
-// nav item is driven by the `active` prop (Liquid used page.fileSlug).
+// nav item is driven by the `active` prop.
import Subheader from '@ui/Subheader.astro'
import DefaultLayout from './DefaultLayout.astro'
diff --git a/shared/lib/chart-script.ts b/shared/lib/chart-script.ts
index 08c51ab44..381fd6ebe 100644
--- a/shared/lib/chart-script.ts
+++ b/shared/lib/chart-script.ts
@@ -1,8 +1,6 @@
-// Port of ui/chart.html (Liquid) — generator of the ApexCharts