first commit

This commit is contained in:
2026-10-28 09:17:40 -06:00
commit 49360140af
48 changed files with 2861 additions and 0 deletions
+10
View File
@@ -0,0 +1,10 @@
{{ define "main" }}
{{ partial "sections/hero.html" (dict
"compact" true
"visual" false
"eyebrow" "404"
"title" "That page isn't here."
"subhead" "It may have moved. Try the install guide, or head back home."
"ctas" (slice (dict "label" "Back home" "url" "/" "style" "primary") (dict "label" "Install guide" "url" "/install/" "style" "ghost"))
) }}
{{ end }}
+54
View File
@@ -0,0 +1,54 @@
<!DOCTYPE html>
<html lang="en">
<head>
{{ partial "head.html" . }}
</head>
<body id="top">
<a class="skip" href="#content">Skip to content</a>
{{ partial "header.html" . }}
<main id="content" tabindex="-1">
{{ block "main" . }}{{ end }}
</main>
{{ partial "footer.html" . }}
<script>
/* Progressive only: fade sections in as they scroll into view, and
darken the top bar once the page has scrolled. */
(function () {
var root = document.documentElement;
var head = document.querySelector('.site-head');
function onScroll() { if (head) head.classList.toggle('is-scrolled', scrollY > 8); }
addEventListener('scroll', onScroll, { passive: true });
onScroll();
if (!('IntersectionObserver' in window)) return;
root.classList.add('js');
var io = new IntersectionObserver(function (entries) {
entries.forEach(function (e) {
if (e.isIntersecting) { e.target.classList.add('is-visible'); io.unobserve(e.target); }
});
}, { rootMargin: '0px 0px -8% 0px' });
document.querySelectorAll('[data-reveal]').forEach(function (el) { io.observe(el); });
})();
/* A Copy button on every terminal block (needs the Clipboard API). */
(function () {
if (!navigator.clipboard) return;
document.querySelectorAll('.prose pre, .term-body, .sib-cmd').forEach(function (block) {
var btn = document.createElement('button');
btn.type = 'button';
btn.className = 'copy-btn';
btn.textContent = 'Copy';
btn.addEventListener('click', function () {
var code = block.querySelector('code') || block;
navigator.clipboard.writeText(code.textContent.replace(/\n$/, '')).then(function () {
btn.textContent = 'Copied';
setTimeout(function () { btn.textContent = 'Copy'; }, 1600);
});
});
(block.closest('.term') || block).appendChild(btn);
if (!block.closest('.term')) block.classList.add('has-copy');
});
})();
</script>
</body>
</html>
+3
View File
@@ -0,0 +1,3 @@
{{ define "main" }}
{{ partial "render-sections.html" . }}
{{ end }}
+3
View File
@@ -0,0 +1,3 @@
{{ define "main" }}
{{ partial "render-sections.html" . }}
{{ end }}
+32
View File
@@ -0,0 +1,32 @@
<footer class="foot">
<div class="wrap">
<div class="foot-top-row">
<div class="foot-brand">
<a href="{{ "/" | relURL }}" class="foot-mark brand" aria-label="CoreDesktop, home">
<img src="{{ "images/coredesktop-logo.svg" | relURL }}" alt="CoreDesktop" class="brand-logo" width="873" height="129">
</a>
<p class="foot-line">Scripts that turn a fresh Arch Linux and GNOME install into a tiling, keyboard-first desktop, ready for work.</p>
<span class="foot-place">Built on Arch Linux · MIT licensed</span>
</div>
<div class="foot-grid">
{{ range (slice (dict "title" "Guide" "menu" "footer_guide") (dict "title" "The Desktop" "menu" "footer_desktop") (dict "title" "Project" "menu" "footer_project")) }}
<nav class="foot-group" aria-label="{{ .title }}">
<span class="foot-head">{{ .title }}</span>
<ul class="foot-links">
{{ range index site.Menus .menu }}
{{ $external := hasPrefix .URL "http" }}
<li><a href="{{ if $external }}{{ .URL }}{{ else }}{{ .URL | relURL }}{{ end }}">{{ .Name }}{{ if $external }}<span class="arrow" aria-hidden="true">↗</span>{{ end }}</a></li>
{{ end }}
</ul>
</nav>
{{ end }}
</div>
</div>
<div class="foot-legal">
<span class="foot-copy">© {{ now.Format "2006" }} Federated Computer, Inc. CoreDesktop is not a distribution, yet — with gratitude to Arch Linux and to <a href="https://www.youtube.com/@typecraft_dev">Typecraft</a>.</span>
<a class="foot-top" href="#top">Back to top<span class="foot-up" aria-hidden="true">↑</span></a>
</div>
</div>
</footer>
+12
View File
@@ -0,0 +1,12 @@
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>{{ if .IsHome }}{{ .Site.Title }} — {{ .Site.Params.tagline }}{{ else }}{{ .Title }} · {{ .Site.Title }}{{ end }}</title>
<meta name="description" content="{{ if .Description }}{{ .Description }}{{ else }}{{ .Site.Params.tagline }}{{ end }}">
<meta name="theme-color" content="#001a2b">
<link rel="icon" href="{{ "favicon.ico" | relURL }}" sizes="32x32">
<link rel="icon" href="{{ "favicon.svg" | relURL }}" type="image/svg+xml">
<link rel="apple-touch-icon" href="{{ "apple-touch-icon.png" | relURL }}">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
<link rel="stylesheet" href="{{ "css/style.css" | relURL }}">
+37
View File
@@ -0,0 +1,37 @@
{{/*
The top bar: logo, the site's pages, and the two actions (source and
install). Sticky on every screen; below 1080px the pages fold into a Menu
toggle (a <details>, so it works without JS).
*/}}
{{ $page := . }}
<header class="site-head">
<div class="wrap head-bar">
<a href="{{ "/" | relURL }}" class="brand" aria-label="CoreDesktop, home">
<img src="{{ "images/coredesktop-logo.svg" | relURL }}" alt="CoreDesktop" class="brand-logo" width="873" height="129">
</a>
<nav class="head-nav" aria-label="Site">
{{ range .Site.Menus.main }}
<a href="{{ .URL | relURL }}" class="nav-link"{{ if hasPrefix $page.RelPermalink .URL }} aria-current="page"{{ end }}>{{ .Name }}</a>
{{ end }}
</nav>
<div class="head-actions">
<a href="{{ site.Params.repoUrl }}" class="head-signin">Source<span class="arrow" aria-hidden="true">↗</span></a>
<a href="{{ "/install/" | relURL }}" class="btn btn-primary btn-sm">Install</a>
</div>
<details class="menu">
<summary><span class="menu-word">Menu</span><span class="menu-glyph" aria-hidden="true"><i></i><i></i></span></summary>
<nav class="menu-panel" aria-label="Site">
<a href="{{ "/" | relURL }}" class="menu-top">Home</a>
{{ range .Site.Menus.main }}
<a href="{{ .URL | relURL }}" class="menu-top">{{ .Name }}</a>
{{ end }}
<div class="menu-actions">
{{ partial "sections/ctas.html" (slice (dict "label" "Install" "url" "/install/" "style" "primary") (dict "label" "Source on Gitea" "url" site.Params.repoUrl "style" "ghost")) }}
</div>
</nav>
</details>
</div>
</header>
+9
View File
@@ -0,0 +1,9 @@
{{ range .Params.sections }}
{{ if eq .type "hero" }}
{{ partial "sections/hero.html" . }}
{{ else if eq .type "final-cta" }}
{{ partial "sections/final-cta.html" . }}
{{ else }}
{{ partial "sections/wrapper.html" . }}
{{ end }}
{{ end }}
+12
View File
@@ -0,0 +1,12 @@
{{/*
Anchor id for a section: its own `id`, else one made from its `toc`,
`kicker`, or `headline`. Used by the section wrapper and by the pricing
switch.
*/}}
{{ $id := "" }}
{{ with .id }}
{{ $id = . }}
{{ else }}
{{ with (or .toc .kicker .headline) }}{{ $id = . | plainify | anchorize }}{{ end }}
{{ end }}
{{ return $id }}
+4
View File
@@ -0,0 +1,4 @@
{{ $s := . }}
{{ partial "sections/head.html" $s }}
{{ with $s.intro }}<div class="prose mb-2">{{ . | markdownify }}</div>{{ end }}
<div class="callout"><p>{{ $s.text | markdownify }}</p></div>
+22
View File
@@ -0,0 +1,22 @@
{{/*
A grid of cards: `items`, each with `title`, `body`, optional `label`
(a small tag above the title), `image`, and `link` (+ `linktext`).
Without an image, `visual: true` draws a gradient panel in its place.
*/}}
{{ $s := . }}
{{ partial "sections/head.html" $s }}
<ul class="card-grid{{ with $s.columns }} cols-{{ . }}{{ end }}">
{{ range $i, $c := $s.items }}
<li class="card{{ if or $c.image $s.visual }} has-visual{{ end }}">
{{ with $c.image }}<img class="card-visual" src="{{ . | relURL }}" alt="" loading="lazy">
{{ else }}{{ if $s.visual }}<span class="card-visual card-art" style="--i:{{ $i }}" aria-hidden="true"></span>{{ end }}{{ end }}
<div class="card-body">
{{ with $c.label }}<span class="card-label">{{ . }}</span>{{ end }}
<h3 class="card-title">{{ $c.title | markdownify }}</h3>
{{ with $c.body }}<p class="card-text">{{ . | markdownify }}</p>{{ end }}
{{ with $c.link }}<a class="more card-link" href="{{ . | relURL }}">{{ replaceRE `\s*[→↗]\s*$` "" ($c.linktext | default "Learn more") }}<span class="arrow" aria-hidden="true">→</span></a>{{ end }}
</div>
</li>
{{ end }}
</ul>
{{ if $s.ctas }}<div class="cta-row">{{ partial "sections/ctas.html" $s.ctas }}</div>{{ end }}
+5
View File
@@ -0,0 +1,5 @@
{{ $s := . }}
{{ partial "sections/head.html" $s }}
<ul class="check-list">
{{ range $s.items }}<li>{{ . | markdownify }}</li>{{ end }}
</ul>
+16
View File
@@ -0,0 +1,16 @@
{{/*
A single command in a terminal window, with a Copy button (added by the
script in baseof.html). `command` defaults to the site's install
command; `prompt` sets the title bar; `note` goes under the window.
*/}}
{{ $s := . }}
{{ partial "sections/head.html" $s }}
<figure class="term">
<div class="term-bar">
<span class="log-dots" aria-hidden="true"><i></i><i></i><i></i></span>
<span class="term-title">{{ $s.prompt | default "Terminal" }}</span>
</div>
<pre class="term-body"><code>{{ $s.command | default site.Params.installCommand }}</code></pre>
</figure>
{{ with $s.note }}<p class="term-note">{{ . | markdownify }}</p>{{ end }}
{{ if $s.ctas }}<div class="cta-row mt-2">{{ partial "sections/ctas.html" $s.ctas }}</div>{{ end }}
+17
View File
@@ -0,0 +1,17 @@
{{/*
Buttons. `primary` is the solid cyan button, `ghost` the outlined
secondary, `text` a quiet link with an arrow. Any trailing "→" in a
label is dropped — the arrow is drawn.
*/}}
{{ range . }}
{{ $label := replaceRE `\s*[→↗]\s*$` "" .label }}
{{ $external := or (hasPrefix .url "http") (hasPrefix .url "mailto:") }}
{{ $href := cond (or $external (hasPrefix .url "#")) .url (.url | relURL) }}
{{ if eq .style "ghost" }}
<a href="{{ $href }}" class="btn btn-secondary">{{ $label }}<span class="arrow" aria-hidden="true">{{ if $external }}↗{{ else }}→{{ end }}</span></a>
{{ else if eq .style "text" }}
<a href="{{ $href }}" class="more">{{ $label }}<span class="arrow" aria-hidden="true">{{ if $external }}↗{{ else }}→{{ end }}</span></a>
{{ else }}
<a href="{{ $href }}" class="btn btn-primary">{{ $label }}<span class="arrow" aria-hidden="true">{{ if $external }}↗{{ else }}→{{ end }}</span></a>
{{ end }}
{{ end }}
+11
View File
@@ -0,0 +1,11 @@
{{ $s := . }}
{{ partial "sections/head.html" $s }}
<div class="faq-list">
{{ range $s.items }}
<details class="faq-item">
<summary>{{ .q }}</summary>
<div class="faq-answer"><p>{{ .a | markdownify }}</p></div>
</details>
{{ end }}
</div>
{{ if $s.ctas }}<div class="cta-row mt-2">{{ partial "sections/ctas.html" $s.ctas }}</div>{{ end }}
+8
View File
@@ -0,0 +1,8 @@
{{ $s := . }}
{{ partial "sections/head.html" $s }}
{{ with $s.intro }}<div class="prose mb-2">{{ . | markdownify }}</div>{{ end }}
<ul class="feature-list">
{{ range $s.items }}
<li><strong>{{ .label }}</strong><span>{{ .body | markdownify }}</span></li>
{{ end }}
</ul>
+10
View File
@@ -0,0 +1,10 @@
{{ $s := . }}
<aside class="cta" data-reveal>
<div class="wrap cta-inner">
<div class="cta-copy">
<h2 class="cta-title">{{ $s.headline | markdownify }}</h2>
{{ with $s.body }}<p class="cta-text">{{ . | markdownify }}</p>{{ end }}
</div>
{{ if $s.ctas }}<div class="actions">{{ partial "sections/ctas.html" $s.ctas }}</div>{{ end }}
</div>
</aside>
+8
View File
@@ -0,0 +1,8 @@
{{/* Shared section heading: optional kicker (a mono eyebrow), headline, and lede. */}}
{{ if or .kicker .headline .lede }}
<div class="section-head">
{{ with .kicker }}<p class="eyebrow">{{ . }}</p>{{ end }}
{{ with .headline }}<h2>{{ . | markdownify }}</h2>{{ end }}
{{ with .lede }}<p class="lede">{{ . | markdownify }}</p>{{ end }}
</div>
{{ end }}
+139
View File
@@ -0,0 +1,139 @@
{{/*
The opening of every page: a full-bleed dark band. On the left, an
optional note (the eyebrow), the statement, the dek, the actions; on the
right, a terminal typing out an install (when `log:` is set), or the
tiles — a three-pane tiled desktop, CoreDesktop's default layout.
`visual: false` drops the tiles; `tiles:` relabels the three panes.
`video:` (a path without extension, with .webm, .mp4 and .jpg beside it)
instead runs a looping screen recording full width under the copy;
`videoTitle` sets its window bar and `videoLabel` its description.
*/}}
{{ $s := . }}
{{ $visual := and (not $s.video) (or $s.log (ne $s.visual false)) }}
<header class="hero{{ if $s.compact }} hero-compact{{ end }}{{ if $visual }} has-visual{{ end }}">
<div class="wrap hero-grid">
<div class="hero-copy">
{{ with $s.eyebrow }}<p class="note" data-reveal><span class="note-live" aria-hidden="true"></span><span class="note-text">{{ . }}</span></p>{{ end }}
<h1 class="statement" data-reveal style="--d:80ms">{{ $s.title | markdownify }}</h1>
{{ with $s.subhead }}<p class="dek" data-reveal style="--d:140ms">{{ . | markdownify }}</p>{{ end }}
{{ if $s.ctas }}<div class="actions" data-reveal style="--d:200ms">{{ partial "sections/ctas.html" $s.ctas }}</div>{{ end }}
</div>
{{ if not $s.video }}
{{ with $s.log }}
{{/*
A terminal, typed out line by line (see the script below). Lines take
`tag` + `text`; `think: true` marks a waiting step (greyed, with a
spinner); `done: true` closes the run. The window keeps a fixed height
and scrolls as lines arrive.
*/}}
<figure class="hero-log" data-reveal style="--d:260ms">
<div class="log-window">
<div class="log-bar">
<span class="log-dots" aria-hidden="true"><i></i><i></i><i></i></span>
<span class="log-title">{{ $s.logTitle | default "~ $ install.sh" }}</span>
<span class="log-live"><span class="note-live" aria-hidden="true"></span>Running</span>
</div>
<div class="log-view">
<ol class="log-lines" aria-label="CoreDesktop installing on a fresh Arch Linux system">
{{ range . }}
<li class="log-line{{ if .done }} is-done{{ end }}{{ if .think }} is-think{{ end }}">
<span class="log-tag">{{ .tag | default (cond .done "Done" (cond (.think | default false) "Wait" "")) }}</span>
<span class="log-text">{{ .text }}</span>
</li>
{{ end }}
</ol>
</div>
</div>
</figure>
<script>
(function () {
var fig = document.currentScript.previousElementSibling;
if (!fig || matchMedia('(prefers-reduced-motion: reduce)').matches) return;
var view = fig.querySelector('.log-view'), list = fig.querySelector('.log-lines');
var lines = [].slice.call(fig.querySelectorAll('.log-line'));
var onScreen = true, i = 0;
function visible() { return onScreen && !document.hidden; }
fig.classList.add('is-animated');
lines.forEach(function (li) {
li.style.setProperty('--n', li.querySelector('.log-text').textContent.trim().length);
});
// Pause while the log is off screen or the tab is hidden.
function schedule(fn, ms) { setTimeout(function () { if (visible()) fn(); else schedule(fn, 400); }, ms); }
function next() {
if (i === lines.length) { schedule(reset, 4200); return; }
var li = lines[i++], n = +li.style.getPropertyValue('--n');
// Finished lines drop their typing animation, so a repaint can't replay it.
lines.forEach(function (l) { if (l.classList.contains('is-current')) l.classList.add('is-typed'); l.classList.remove('is-current'); });
li.classList.add('is-on', 'is-current');
// Keep the newest line in view, like a terminal: slide the list up.
var shift = Math.max(0, li.offsetTop + li.offsetHeight + 18 - view.clientHeight);
list.style.transform = 'translateY(' + -shift + 'px)';
var pause = li.classList.contains('is-think') ? 1300 : li.classList.contains('is-done') ? 300 : 450;
schedule(next, n * 16 + pause);
}
function reset() {
fig.classList.add('is-clearing');
schedule(function () {
lines.forEach(function (l) { l.classList.remove('is-on', 'is-current', 'is-typed'); });
list.style.transition = 'none';
list.style.transform = '';
void list.offsetHeight;
list.style.transition = '';
fig.classList.remove('is-clearing');
i = 0;
schedule(next, 500);
}, 450);
}
if ('IntersectionObserver' in window) {
new IntersectionObserver(function (e) { onScreen = e[0].isIntersecting; }).observe(fig);
}
schedule(next, 700);
})();
</script>
{{ else }}
{{ if ne $s.visual false }}
{{ $tiles := $s.tiles | default (slice "Editor" "Terminal" "Browser") }}
<figure class="hero-tiles" aria-hidden="true" data-reveal style="--d:260ms">
<div class="tiles-bar"><span>Activities</span><span class="tiles-clock">09:41</span><span class="tiles-grid-icon"><i></i><i></i><i></i></span></div>
<div class="tiles-grid">
{{ range $i, $t := $tiles }}
<span class="tile{{ if eq $i 1 }} is-focus{{ end }}" style="--i:{{ $i }}">
<em>{{ printf "%02d" (add $i 1) }} · {{ $t }}</em>
<i></i><i></i><i></i>
</span>
{{ end }}
</div>
<div class="tiles-keys"><kbd>Super</kbd><span>+</span><kbd>←</kbd><kbd>→</kbd><span class="tiles-hint">move between tiles</span></div>
</figure>
{{ end }}
{{ end }}
{{ end }}
</div>
{{ with $s.video }}
{{/*
Muted, looping and inline, so it can autoplay. With reduced motion it
stays on the poster, with controls to play it (see the script below).
*/}}
<figure class="wrap hero-video" data-reveal style="--d:260ms">
<div class="video-window">
<div class="log-bar">
<span class="log-dots" aria-hidden="true"><i></i><i></i><i></i></span>
<span class="log-title">{{ $s.videoTitle | default "CoreDesktop" }}</span>
</div>
<video class="video-screen" autoplay muted loop playsinline preload="auto" width="1920" height="540"
poster="{{ printf "%s.jpg" . | relURL }}"{{ with $s.videoLabel }} aria-label="{{ . }}"{{ end }}>
<source src="{{ printf "%s.webm" . | relURL }}" type="video/webm">
<source src="{{ printf "%s.mp4" . | relURL }}" type="video/mp4">
</video>
</div>
</figure>
<script>
(function () {
var v = document.currentScript.previousElementSibling.querySelector('video');
if (!v || !matchMedia('(prefers-reduced-motion: reduce)').matches) return;
v.removeAttribute('autoplay'); v.pause(); v.currentTime = 0; v.controls = true;
})();
</script>
{{ end }}
</header>
+33
View File
@@ -0,0 +1,33 @@
{{/*
The sibling project directory, from data/siblings.yaml. Each card shows the
project, its author and tags, and the `sibling add` command to install it
(with a Copy button, from baseof.html). With no projects listed yet, an
empty card invites the first submission: `emptyText` and `emptyCtas`.
*/}}
{{ $s := . }}
{{ $projects := hugo.Data.siblings | default slice }}
{{ partial "sections/head.html" $s }}
{{ if $projects }}
<ul class="sib-grid">
{{ range sort $projects "name" }}
{{ $p := . }}
{{ $home := .homepage | default (replaceRE `\.git$` "" .url) }}
<li class="sib-card">
<div class="sib-head">
<h3 class="sib-name"><a href="{{ $home }}">{{ .name }}<span class="arrow" aria-hidden="true">↗</span></a></h3>
{{ with $p.author }}<span class="sib-author">by {{ with $p.authorUrl }}<a href="{{ . }}">{{ $p.author }}</a>{{ else }}{{ . }}{{ end }}</span>{{ end }}
</div>
<p class="sib-desc">{{ .description | markdownify }}</p>
{{ with .tags }}<ul class="sib-tags">{{ range . }}<li>{{ . }}</li>{{ end }}</ul>{{ end }}
<pre class="sib-cmd"><code>sibling add {{ .url }}</code></pre>
</li>
{{ end }}
</ul>
{{ else }}
<div class="sib-empty">
<span class="sib-empty-mark" aria-hidden="true"></span>
<p class="sib-empty-text">{{ $s.emptyText | default "No sibling projects are listed yet. Yours could be the first." | markdownify }}</p>
{{ with $s.emptyCtas }}<div class="actions">{{ partial "sections/ctas.html" . }}</div>{{ end }}
</div>
{{ end }}
{{ if $s.ctas }}<div class="cta-row">{{ partial "sections/ctas.html" $s.ctas }}</div>{{ end }}
+14
View File
@@ -0,0 +1,14 @@
{{/* Two columns, each a heading plus a short list. */}}
{{ $s := . }}
{{ partial "sections/head.html" $s }}
<div class="split">
{{ range $s.columns }}
<div class="split-col">
<h3>{{ .title }}</h3>
<ul class="split-list">
{{ range .items }}<li>{{ . | markdownify }}</li>{{ end }}
</ul>
</div>
{{ end }}
</div>
{{ if $s.ctas }}<div class="cta-row mt-2">{{ partial "sections/ctas.html" $s.ctas }}</div>{{ end }}
+45
View File
@@ -0,0 +1,45 @@
{{/*
Stat tiles. Values that start with a number ("80%+", "100+", "30X") count
up from zero when the row scrolls into view; others show as written.
Without JS or with reduced motion, every value simply shows.
*/}}
{{ $s := . }}
{{ partial "sections/head.html" $s }}
<div class="stat-row">
{{ range $s.items }}
<div class="stat">
<span class="stat-value">{{ .value }}</span>
<span class="stat-label">{{ .label | markdownify }}</span>
</div>
{{ end }}
</div>
<script>
(function () {
var row = document.currentScript.previousElementSibling;
if (!('IntersectionObserver' in window) || matchMedia('(prefers-reduced-motion: reduce)').matches) return;
var counters = [];
row.querySelectorAll('.stat-value').forEach(function (el) {
var m = el.textContent.trim().match(/^(\d+)(.*)$/);
if (!m) return;
var shown = document.createElement('span'), full = el.textContent.trim();
shown.setAttribute('aria-hidden', 'true');
shown.textContent = '0' + m[2];
el.innerHTML = '<span class="visually-hidden"></span>';
el.firstChild.textContent = full;
el.appendChild(shown);
counters.push({ node: shown, to: +m[1], suffix: m[2] });
});
if (!counters.length) return;
new IntersectionObserver(function (entries, io) {
if (!entries[0].isIntersecting) return;
io.disconnect();
var start = performance.now(), dur = 1200;
(function tick(now) {
var t = Math.min(1, (now - start) / dur), eased = 1 - Math.pow(1 - t, 3);
counters.forEach(function (c) { c.node.textContent = Math.round(c.to * eased) + c.suffix; });
if (t < 1) requestAnimationFrame(tick);
})(start);
}, { threshold: 0.6 }).observe(row);
})();
</script>
{{ if $s.ctas }}<div class="cta-row mt-2">{{ partial "sections/ctas.html" $s.ctas }}</div>{{ end }}
+14
View File
@@ -0,0 +1,14 @@
{{ $s := . }}
{{ partial "sections/head.html" $s }}
<ol class="step-row{{ if eq (len $s.items) 4 }} steps-4{{ end }}">
{{ range $i, $item := $s.items }}
<li class="step">
<div class="step-head">
<h3>{{ $item.title }}</h3>
<span class="step-num">{{ printf "%03d" (add $i 1) }}</span>
</div>
<p>{{ $item.body | markdownify }}</p>
</li>
{{ end }}
</ol>
{{ if $s.ctas }}<div class="cta-row">{{ partial "sections/ctas.html" $s.ctas }}</div>{{ end }}
+23
View File
@@ -0,0 +1,23 @@
{{/*
A plain table: `headers` (a list) and `rows` (a list of lists). The first
cell of each row is its label. Cells take Markdown.
*/}}
{{ $s := . }}
{{ partial "sections/head.html" $s }}
<div class="table-scroll">
<table class="compare-table">
<thead>
<tr>{{ range $s.headers }}<th scope="col">{{ . }}</th>{{ end }}</tr>
</thead>
<tbody>
{{ range $s.rows }}
<tr>
{{ range $i, $cell := . }}
{{ if eq $i 0 }}<th scope="row">{{ $cell | markdownify }}</th>{{ else }}<td>{{ $cell | markdownify }}</td>{{ end }}
{{ end }}
</tr>
{{ end }}
</tbody>
</table>
</div>
{{ if $s.ctas }}<div class="cta-row mt-2">{{ partial "sections/ctas.html" $s.ctas }}</div>{{ end }}
+20
View File
@@ -0,0 +1,20 @@
{{/*
Prose, with optional `ctas`. `offer` adds a highlighted card below:
`badge` (a small cyan tag), `text`, and `ctas`.
*/}}
{{ $s := . }}
{{ partial "sections/head.html" $s }}
{{/* markdownify drops the <p> around a lone paragraph; put it back so inline markup stays in the flow */}}
{{ $body := $s.body | markdownify }}
{{ if not (findRE `^\s*<(p|ul|ol|h[1-6]|div|blockquote|pre|table|figure|hr)[\s>]` $body) }}{{ $body = printf "<p>%s</p>" $body | safeHTML }}{{ end }}
<div class="prose{{ if $s.centered }} centered{{ end }}">
{{ $body }}
</div>
{{ if $s.ctas }}<div class="cta-row mt-2">{{ partial "sections/ctas.html" $s.ctas }}</div>{{ end }}
{{ with $s.offer }}
<div class="offer-card">
{{ with .badge }}<span class="offer-badge">{{ . }}</span>{{ end }}
<p class="offer-text">{{ .text | markdownify }}</p>
{{ with .ctas }}<div class="offer-actions">{{ partial "sections/ctas.html" . }}</div>{{ end }}
</div>
{{ end }}
+7
View File
@@ -0,0 +1,7 @@
{{ $s := . }}
{{ $id := partial "section-id.html" $s }}
<section class="section section-{{ $s.type }}"{{ with $id }} id="{{ . }}"{{ end }} data-reveal>
<div class="wrap">
{{ partial (printf "sections/%s.html" $s.type) $s }}
</div>
</section>