first commit
This commit is contained in:
@@ -0,0 +1,60 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
{{ partial "head.html" . }}
|
||||
</head>
|
||||
<body id="top">
|
||||
<a class="skip" href="#content">Skip to content</a>
|
||||
<div class="page">
|
||||
{{ partial "header.html" . }}
|
||||
<main class="col" id="content" tabindex="-1">
|
||||
{{ block "main" . }}{{ end }}
|
||||
{{ partial "footer.html" . }}
|
||||
</main>
|
||||
</div>
|
||||
<script>
|
||||
/* Progressive only: fade sections in as they scroll into view, and mark
|
||||
the rail's "On this page" link for the section being read. */
|
||||
(function () {
|
||||
var root = document.documentElement;
|
||||
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); });
|
||||
|
||||
var links = [].slice.call(document.querySelectorAll('.nav-here a'));
|
||||
if (!links.length) return;
|
||||
var targets = links.map(function (a) { return document.getElementById(decodeURIComponent(a.hash.slice(1))); });
|
||||
var ticking = false;
|
||||
function update() {
|
||||
ticking = false;
|
||||
var current = -1;
|
||||
targets.forEach(function (t, i) { if (t && t.getBoundingClientRect().top < innerHeight * 0.3) current = i; });
|
||||
links.forEach(function (a, i) {
|
||||
if (i === current) a.setAttribute('aria-current', 'location'); else a.removeAttribute('aria-current');
|
||||
});
|
||||
}
|
||||
addEventListener('scroll', function () { if (!ticking) { ticking = true; requestAnimationFrame(update); } }, { passive: true });
|
||||
update();
|
||||
})();
|
||||
|
||||
/* Pricing: a link to a plan panel's id (e.g. /pricing/#hosted) selects its tab. */
|
||||
(function () {
|
||||
function openPanel() {
|
||||
var panel = location.hash && document.getElementById(decodeURIComponent(location.hash.slice(1)));
|
||||
if (!panel || !panel.classList.contains('plan-panel')) return;
|
||||
var i = [].indexOf.call(panel.parentNode.children, panel);
|
||||
var radio = panel.closest('.plan-switch').querySelectorAll('.plan-radio')[i];
|
||||
if (radio) { radio.checked = true; (panel.closest('.section') || panel).scrollIntoView(); }
|
||||
}
|
||||
addEventListener('hashchange', openPanel);
|
||||
openPanel();
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,10 @@
|
||||
{{/* Long-form legal text (Privacy Policy, Terms): a plain heading, then the Markdown body. */}}
|
||||
{{ define "main" }}
|
||||
<header class="hero hero-compact">
|
||||
<h1 class="statement">{{ .Title }}</h1>
|
||||
{{ with .Params.effective }}<p class="dek">Effective date: {{ . }}</p>{{ end }}
|
||||
</header>
|
||||
<section class="section">
|
||||
<div class="prose doc">{{ .Content }}</div>
|
||||
</section>
|
||||
{{ end }}
|
||||
@@ -0,0 +1,3 @@
|
||||
{{ define "main" }}
|
||||
{{ partial "render-sections.html" . }}
|
||||
{{ end }}
|
||||
@@ -0,0 +1,31 @@
|
||||
{{ define "main" }}
|
||||
<div class="form-page" data-reveal>
|
||||
<p class="eyebrow">Sign In</p>
|
||||
<h1 class="statement">{{ .Params.heading | default "Welcome back" }}</h1>
|
||||
|
||||
<form onsubmit="return false;">
|
||||
<div class="field">
|
||||
<label for="email">Email</label>
|
||||
<input id="email" type="email" name="email" autocomplete="email" required>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="password">Password</label>
|
||||
<input id="password" type="password" name="password" autocomplete="current-password" required>
|
||||
</div>
|
||||
<div class="field-row">
|
||||
<label><input type="checkbox" name="remember"> Remember me</label>
|
||||
<a href="#">Forgot password?</a>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary"><span class="btn-label">Sign In</span></button>
|
||||
</form>
|
||||
|
||||
<p class="form-foot">
|
||||
Don't have an account yet?
|
||||
<a class="more" href="{{ "/start-now/" | relURL }}">Start Your 7-Day Free Trial<span class="arrow" aria-hidden="true">→</span></a>
|
||||
</p>
|
||||
|
||||
<p class="form-note">
|
||||
Having trouble signing in? <a href="mailto:{{ .Site.Params.supportEmail }}">Contact Support</a>
|
||||
</p>
|
||||
</div>
|
||||
{{ end }}
|
||||
@@ -0,0 +1,3 @@
|
||||
{{ define "main" }}
|
||||
{{ partial "render-sections.html" . }}
|
||||
{{ end }}
|
||||
@@ -0,0 +1,34 @@
|
||||
{{/*
|
||||
One page per app in the Software Library. Front matter carries the facts
|
||||
(eyebrow, headline, dek, replaces, plans, price, projectUrl); the Markdown
|
||||
body is the long-form copy, followed by the closing call to action.
|
||||
*/}}
|
||||
{{ define "main" }}
|
||||
<header class="hero hero-compact">
|
||||
<p class="note" data-reveal><span class="note-live" aria-hidden="true"></span><span class="note-text">{{ .Params.eyebrow | default "Software Library" }}</span></p>
|
||||
<h1 class="statement" data-reveal style="--d:80ms">{{ .Params.headline | default .Title | markdownify }}</h1>
|
||||
{{ with .Params.dek }}<p class="dek" data-reveal style="--d:140ms">{{ . | markdownify }}</p>{{ end }}
|
||||
<div class="actions" data-reveal style="--d:200ms">
|
||||
{{ partial "sections/ctas.html" (slice (dict "label" "Start 7-Day Free Trial" "url" "/start-now/" "style" "primary") (dict "label" "Book a Demo" "url" site.Params.demoUrl "style" "ghost")) }}
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<section class="section" data-reveal>
|
||||
<dl class="app-facts">
|
||||
{{ with .Params.replaces }}<div><dt>Replaces</dt><dd>{{ . }}</dd></div>{{ end }}
|
||||
<div><dt>Hosted plans</dt><dd>{{ with .Params.plans }}Included in {{ . }}{{ else }}Add to any plan{{ end }}{{ with $.Params.price }} · {{ . }} on its own{{ end }}</dd></div>
|
||||
<div><dt>Self-hosted</dt><dd>Any tier (Free includes up to 3 apps)</dd></div>
|
||||
{{ with .Params.projectUrl }}<div><dt>Project</dt><dd><a href="{{ . }}" target="_blank" rel="noopener">{{ replaceRE `^https?://(www\.)?([^/]+).*$` "$2" . }}<span class="arrow" aria-hidden="true">↗</span></a></dd></div>{{ end }}
|
||||
</dl>
|
||||
<p class="app-facts-more"><a class="more" href="{{ "/pricing/" | relURL }}">See pricing<span class="arrow" aria-hidden="true">→</span></a> <a class="more" href="{{ "/product/software-library/" | relURL }}">Browse the Software Library<span class="arrow" aria-hidden="true">→</span></a></p>
|
||||
</section>
|
||||
|
||||
<section class="section" data-reveal>
|
||||
<div class="prose doc">{{ .Content }}</div>
|
||||
</section>
|
||||
|
||||
{{ partial "sections/final-cta.html" (dict
|
||||
"headline" (printf "Run %s on Federated" .Title)
|
||||
"body" "Private, single-tenant, and integrated with the rest of your apps. Talk to us, or start your free trial."
|
||||
"ctas" (slice (dict "label" "Book a Demo" "url" site.Params.demoUrl "style" "primary") (dict "label" "Start 7-Day Free Trial" "url" "/start-now/" "style" "ghost"))) }}
|
||||
{{ end }}
|
||||
@@ -0,0 +1,3 @@
|
||||
{{ define "main" }}
|
||||
{{ partial "render-sections.html" . }}
|
||||
{{ end }}
|
||||
@@ -0,0 +1,17 @@
|
||||
{{/*
|
||||
Layer fit — which of the four platform layers a customer path uses.
|
||||
Input: dict with "fit" (map of layer key -> "yes" | "own" | "no")
|
||||
and optional "compact" (bool). State is always spelled out in text,
|
||||
so color is never the only signal.
|
||||
*/}}
|
||||
{{ $fit := .fit }}
|
||||
<ul class="fit{{ if .compact }} fit-compact{{ end }}">
|
||||
{{ range hugo.Data.layers }}
|
||||
{{ $state := index $fit .key | default "no" }}
|
||||
<li class="fit-row fit-{{ .key }} is-{{ $state }}">
|
||||
<span class="fit-bar" aria-hidden="true"><span></span></span>
|
||||
<span class="fit-name">{{ .name }}</span>
|
||||
<span class="fit-state">{{ if eq $state "yes" }}Included{{ else if eq $state "own" }}Your own{{ else }}Add anytime{{ end }}</span>
|
||||
</li>
|
||||
{{ end }}
|
||||
</ul>
|
||||
@@ -0,0 +1,31 @@
|
||||
<footer class="foot" data-reveal>
|
||||
<div class="foot-brand">
|
||||
<a href="{{ "/" | relURL }}" class="foot-mark" aria-label="Federated Computer, home">
|
||||
<img src="{{ "images/logo.png" | relURL }}" alt="Federated Computer" class="brand-logo" width="775" height="129">
|
||||
</a>
|
||||
<p class="foot-line">The open-source operating system for your business — and the private AI that runs on it.</p>
|
||||
<span class="foot-place">Colorado Springs, Colorado</span>
|
||||
<ul class="foot-social">
|
||||
{{ range site.Menus.footer_social }}<li><a href="{{ .URL }}" target="_blank" rel="noopener">{{ .Name }}<span class="arrow" aria-hidden="true">↗</span></a></li>{{ end }}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="foot-grid">
|
||||
{{ range (slice (dict "title" "Solutions" "menu" "footer_solutions") (dict "title" "Product" "menu" "footer_product") (dict "title" "Company" "menu" "footer_company") (dict "title" "Get Started" "menu" "footer_start")) }}
|
||||
<nav class="foot-group" aria-label="{{ .title }}">
|
||||
<span class="foot-head">{{ .title }}</span>
|
||||
<ul class="foot-links">
|
||||
{{ range index site.Menus .menu }}
|
||||
<li><a href="{{ if or (hasPrefix .URL "mailto:") (hasPrefix .URL "http") }}{{ .URL }}{{ else }}{{ .URL | relURL }}{{ end }}">{{ .Name }}</a></li>
|
||||
{{ end }}
|
||||
</ul>
|
||||
</nav>
|
||||
{{ end }}
|
||||
</div>
|
||||
|
||||
<div class="foot-legal">
|
||||
<span class="foot-copy">© {{ now.Format "2006" }} Federated Computer, Inc. FedQ'd in Colorado Springs — built on Federated Computer by a team around the world.</span>
|
||||
{{ range site.Menus.footer_legal }}<a class="foot-policy" href="{{ .URL | relURL }}">{{ .Name }}</a>{{ end }}
|
||||
<a class="foot-top" href="#top">Back to top<span class="foot-up" aria-hidden="true">↑</span></a>
|
||||
</div>
|
||||
</footer>
|
||||
@@ -0,0 +1,9 @@
|
||||
<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="#ffffff">
|
||||
<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=Geist:wght@400;500;600&family=Geist+Mono:wght@400;500&display=swap" rel="stylesheet">
|
||||
<link rel="stylesheet" href="{{ "css/style.css" | relURL }}">
|
||||
@@ -0,0 +1,98 @@
|
||||
{{/*
|
||||
The rail: logo, the site's pages ("doors"), the two account actions, and
|
||||
an index of the sections on this page. Sticky beside the column on wide
|
||||
screens; a one-line bar with a Menu toggle below 1000px.
|
||||
*/}}
|
||||
{{ $page := . }}
|
||||
<aside class="rail">
|
||||
<div class="rail-bar">
|
||||
<a href="{{ "/" | relURL }}" class="brand" aria-label="Federated Computer, home">
|
||||
<img src="{{ "images/logo.png" | relURL }}" alt="Federated Computer" class="brand-logo" width="775" height="129">
|
||||
</a>
|
||||
|
||||
<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">
|
||||
{{ range .Site.Menus.main }}
|
||||
<a href="{{ .URL | relURL }}" class="menu-top">{{ .Name }}</a>
|
||||
{{ with .Params.dropdown }}
|
||||
<div class="menu-sub">
|
||||
{{ range index $page.Site.Menus . }}<a href="{{ .URL | relURL }}">{{ .Name }}</a>{{ end }}
|
||||
</div>
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
<div class="menu-actions">
|
||||
{{ partial "sections/ctas.html" (slice (dict "label" "Start Now" "url" "/start-now/" "style" "primary") (dict "label" "Sign In" "url" "/sign-in/" "style" "ghost")) }}
|
||||
</div>
|
||||
</nav>
|
||||
</details>
|
||||
</div>
|
||||
|
||||
<nav class="nav nav-doors" aria-label="Site">
|
||||
{{ range .Site.Menus.main }}
|
||||
{{ $active := or (eq $page.RelPermalink .URL) (and (ne .URL "/") (hasPrefix $page.RelPermalink .URL)) }}
|
||||
<a href="{{ .URL | relURL }}"{{ if $active }} aria-current="page"{{ end }}>{{ .Name }}</a>
|
||||
{{ if $active }}
|
||||
{{ with .Params.dropdown }}
|
||||
<div class="nav-sub">
|
||||
{{ range index $page.Site.Menus . }}
|
||||
<a href="{{ .URL | relURL }}"{{ if eq $page.RelPermalink .URL }} aria-current="page"{{ end }}>{{ .Name }}</a>
|
||||
{{ end }}
|
||||
</div>
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
</nav>
|
||||
|
||||
<div class="rail-actions">
|
||||
<a href="{{ "/start-now/" | relURL }}" class="btn btn-primary btn-sm"><span class="btn-label">Start Now</span></a>
|
||||
<a href="{{ "/sign-in/" | relURL }}" class="rail-signin"{{ if eq $page.RelPermalink "/sign-in/" }} aria-current="page"{{ end }}>Sign In</a>
|
||||
</div>
|
||||
|
||||
{{ $toc := slice }}
|
||||
{{ range .Params.sections }}
|
||||
{{ $label := or .toc .kicker .headline }}
|
||||
{{ if and $label (not (in (slice "hero" "divider" "final-cta") .type)) }}
|
||||
{{ $toc = $toc | append (dict "label" ($label | plainify) "id" (partial "section-id.html" .)) }}
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
{{ if $toc }}
|
||||
<hr class="rail-rule">
|
||||
<nav class="nav nav-here" aria-label="On this page">
|
||||
{{ range $toc }}<a href="#{{ .id }}">{{ .label }}</a>{{ end }}
|
||||
</nav>
|
||||
{{ end }}
|
||||
|
||||
{{/*
|
||||
"Apps + AI": a slowly scrolling wall of app tiles pinned to the bottom of
|
||||
the rail (wide screens only). The list is repeated once, hidden from
|
||||
assistive tech and focus, so the scroll can loop seamlessly. Tiles come
|
||||
from data/app_wall.yaml; icons from the sprite at images/app-icons.svg.
|
||||
*/}}
|
||||
{{ with hugo.Data.app_wall }}
|
||||
{{ $sprite := "images/app-icons.svg" | relURL }}
|
||||
<nav class="app-wall" aria-label="Apps + AI">
|
||||
<a class="app-wall-head" href="{{ "/product/software-library/" | relURL }}">
|
||||
<span class="app-wall-title">Apps + AI</span>
|
||||
<span class="app-wall-count">Catalog<span class="arrow" aria-hidden="true">→</span></span>
|
||||
</a>
|
||||
<div class="app-wall-view">
|
||||
<div class="app-wall-scroll">
|
||||
{{ range $copy := slice 0 1 }}
|
||||
<ul class="app-wall-grid"{{ if $copy }} aria-hidden="true"{{ end }}>
|
||||
{{ range hugo.Data.app_wall }}
|
||||
<li>
|
||||
<a class="app-tile" href="{{ .url | relURL }}"{{ if $copy }} tabindex="-1"{{ end }}>
|
||||
{{ with .icon }}<svg class="app-tile-icon" aria-hidden="true"><use href="{{ $sprite }}#{{ . }}"></use></svg>
|
||||
{{ else }}<span class="app-tile-icon app-tile-letter" aria-hidden="true">{{ substr .name 0 1 }}</span>{{ end }}
|
||||
<span class="app-tile-name">{{ .name }}</span>
|
||||
</a>
|
||||
</li>
|
||||
{{ end }}
|
||||
</ul>
|
||||
{{ end }}
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
{{ end }}
|
||||
</aside>
|
||||
@@ -0,0 +1,11 @@
|
||||
{{ range .Params.sections }}
|
||||
{{ if eq .type "hero" }}
|
||||
{{ partial "sections/hero.html" . }}
|
||||
{{ else if eq .type "divider" }}
|
||||
{{ partial "sections/divider.html" . }}
|
||||
{{ else if eq .type "final-cta" }}
|
||||
{{ partial "sections/final-cta.html" . }}
|
||||
{{ else }}
|
||||
{{ partial "sections/wrapper.html" . }}
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
@@ -0,0 +1,73 @@
|
||||
{{/*
|
||||
The savings calculator: pick the apps you pay for today and your team
|
||||
size, and compare their yearly retail cost with Federated's annual price.
|
||||
Input: dict with "key" (unique id), "apps" (name, cost, perSeat, selected),
|
||||
"annual", optional "note", "compact" (one result, no cost breakdown) and
|
||||
"more" (a link to the full calculator). The totals need JS; without it,
|
||||
the app list still shows each app's retail price.
|
||||
*/}}
|
||||
{{ $key := .key }}
|
||||
<form class="savings-calc{{ if .compact }} is-compact{{ end }}" id="{{ $key }}-calc" data-annual="{{ .annual }}" onsubmit="return false">
|
||||
<fieldset class="calc-apps">
|
||||
<legend class="plan-subhead">{{ if .compact }}Which of these do you pay for?{{ else }}Which apps do you pay for today?{{ end }}</legend>
|
||||
<div class="calc-app-grid">
|
||||
{{ range .apps }}
|
||||
<label class="calc-app">
|
||||
<input type="checkbox" value="{{ .cost }}" data-per-seat="{{ .perSeat }}"{{ if .selected }} checked{{ end }}>
|
||||
<span class="calc-app-name">{{ .name }}</span>
|
||||
<span class="calc-app-cost">${{ .cost }}{{ if .perSeat }}/person{{ else }} flat{{ end }}</span>
|
||||
</label>
|
||||
{{ end }}
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
<div class="calc-users">
|
||||
<label class="plan-subhead" for="{{ $key }}-users">How many people work at your company?</label>
|
||||
<div class="calc-users-row">
|
||||
<input type="range" id="{{ $key }}-users" min="1" max="28" step="1" value="2">
|
||||
<output class="calc-users-value" for="{{ $key }}-users">10</output>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="calc-result" aria-live="polite">
|
||||
{{ if not .compact }}
|
||||
<div class="calc-stat"><span class="calc-stat-label">Apps replaced</span><span class="calc-stat-value" data-out="count">0</span></div>
|
||||
<div class="calc-stat"><span class="calc-stat-label">Their retail cost</span><span class="calc-stat-value" data-out="cost">$0</span><span class="calc-stat-unit">/year</span></div>
|
||||
{{ end }}
|
||||
<div class="calc-stat is-highlight"><span class="calc-stat-label">You'd save</span><span class="calc-stat-value" data-out="savings">$0</span><span class="calc-stat-unit">/year</span></div>
|
||||
{{ with .more }}<a class="more calc-more" href="{{ .url | relURL }}">{{ .label }}<span class="arrow" aria-hidden="true">→</span></a>{{ end }}
|
||||
</div>
|
||||
{{ with .note }}<p class="plan-note">{{ . | markdownify }}</p>{{ end }}
|
||||
</form>
|
||||
<script>
|
||||
(function () {
|
||||
var form = document.getElementById('{{ $key }}-calc');
|
||||
if (!form) return;
|
||||
var annual = +form.dataset.annual || 0;
|
||||
var range = form.querySelector('input[type=range]');
|
||||
var shown = form.querySelector('.calc-users-value');
|
||||
var fmt = function (n) { return '$' + Math.round(n).toLocaleString('en-US'); };
|
||||
function out(name, text) { var el = form.querySelector('[data-out=' + name + ']'); if (el) el.textContent = text; }
|
||||
// The slider's 28 steps: 1, then 10–100 by 10, 200–1,000 by 100, 1,500–5,000 by 500.
|
||||
function users(step) {
|
||||
if (step <= 1) return 1;
|
||||
if (step <= 11) return (step - 1) * 10;
|
||||
if (step <= 20) return (step - 10) * 100;
|
||||
return 1000 + (step - 20) * 500;
|
||||
}
|
||||
function update() {
|
||||
var n = users(+range.value), count = 0, cost = 0;
|
||||
shown.textContent = n.toLocaleString('en-US') + (+range.value === +range.max ? '+' : '');
|
||||
range.setAttribute('aria-valuetext', shown.textContent + ' people');
|
||||
form.querySelectorAll('.calc-app input:checked').forEach(function (box) {
|
||||
count++;
|
||||
cost += +box.value * (box.dataset.perSeat === 'true' ? n : 1) * 12;
|
||||
});
|
||||
out('count', count);
|
||||
out('cost', fmt(cost));
|
||||
out('savings', fmt(Math.max(0, cost - annual)));
|
||||
}
|
||||
form.addEventListener('input', update);
|
||||
update();
|
||||
})();
|
||||
</script>
|
||||
@@ -0,0 +1,12 @@
|
||||
{{/*
|
||||
Anchor id for a section: its own `id`, else one made from its `toc`,
|
||||
`kicker`, or `headline`. Shared by the section wrapper and the rail's
|
||||
"On this page" index so the two always agree.
|
||||
*/}}
|
||||
{{ $id := "" }}
|
||||
{{ with .id }}
|
||||
{{ $id = . }}
|
||||
{{ else }}
|
||||
{{ with (or .toc .kicker .headline) }}{{ $id = . | plainify | anchorize }}{{ end }}
|
||||
{{ end }}
|
||||
{{ return $id }}
|
||||
@@ -0,0 +1,26 @@
|
||||
{{/*
|
||||
Browsable app catalog. Each app may link to its Federated page (`page`,
|
||||
shown as "Learn more") and to the project's own site (`url`).
|
||||
*/}}
|
||||
{{ $s := . }}
|
||||
{{ partial "sections/head.html" $s }}
|
||||
<div class="app-directory">
|
||||
{{ range $s.categories }}
|
||||
<details class="app-category">
|
||||
<summary>
|
||||
<span class="app-category-name">{{ .name }}</span>
|
||||
<span class="app-count">{{ len .apps }}</span>
|
||||
</summary>
|
||||
<ul class="app-list">
|
||||
{{ range $app := .apps }}
|
||||
<li class="app-item">
|
||||
<strong class="app-name">{{ with $app.page }}<a href="{{ . | relURL }}">{{ $app.name }}</a>{{ else }}{{ $app.name }}{{ end }}</strong>
|
||||
{{ with $app.description }}<span class="app-desc">{{ . }}</span>{{ end }}
|
||||
{{ with $app.page }}<a class="app-link" href="{{ . | relURL }}">Learn more.</a>{{ end }}
|
||||
{{ with $app.url }}<a class="app-link" href="{{ . }}" target="_blank" rel="noopener">{{ if $app.page }}Project site ↗{{ else }}Learn more.{{ end }}</a>{{ end }}
|
||||
</li>
|
||||
{{ end }}
|
||||
</ul>
|
||||
</details>
|
||||
{{ end }}
|
||||
</div>
|
||||
@@ -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>
|
||||
@@ -0,0 +1,5 @@
|
||||
{{ $s := . }}
|
||||
{{ partial "sections/head.html" $s }}
|
||||
<ul class="check-list">
|
||||
{{ range $s.items }}<li>{{ . | markdownify }}</li>{{ end }}
|
||||
</ul>
|
||||
@@ -0,0 +1,20 @@
|
||||
{{/*
|
||||
Buttons. `primary` is the solid blue button with a leading arrow tile,
|
||||
`ghost` the pale blue secondary with corner ticks, `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">
|
||||
<span class="btn-tile" aria-hidden="true"><svg viewBox="0 0 16 16" width="14" height="14" fill="none"><path d="M3 8h9M8.5 4.5 12 8l-3.5 3.5" stroke="currentColor" stroke-width="1.5" stroke-linecap="square"/></svg></span>
|
||||
<span class="btn-label">{{ $label }}</span>
|
||||
</a>
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
@@ -0,0 +1,11 @@
|
||||
{{ $s := . }}
|
||||
<div class="stack-diagram">
|
||||
{{ range $i, $l := $s.layers }}
|
||||
<div class="layer">
|
||||
<span class="layer-num">{{ printf "%02d" (add $i 1) }}</span>
|
||||
<h4>{{ $l.title }}</h4>
|
||||
<p>{{ $l.body }}</p>
|
||||
</div>
|
||||
{{ end }}
|
||||
</div>
|
||||
{{ with $s.spineLabel }}<p class="mcp-spine">{{ . }}</p>{{ end }}
|
||||
@@ -0,0 +1 @@
|
||||
<hr class="rule-dashed" aria-hidden="true">
|
||||
@@ -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 }}
|
||||
@@ -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>{{ .body | markdownify }}</li>
|
||||
{{ end }}
|
||||
</ul>
|
||||
@@ -0,0 +1,6 @@
|
||||
{{ $s := . }}
|
||||
<aside class="cta" data-reveal>
|
||||
<h2 class="cta-title">{{ $s.headline | markdownify }}</h2>
|
||||
{{ with $s.body }}<p class="cta-text">{{ . | markdownify }}</p>{{ end }}
|
||||
{{ if $s.ctas }}<div class="actions">{{ partial "sections/ctas.html" $s.ctas }}</div>{{ end }}
|
||||
</aside>
|
||||
@@ -0,0 +1,11 @@
|
||||
{{ $s := . }}
|
||||
{{ partial "sections/head.html" $s }}
|
||||
<div class="founder-block">
|
||||
<div class="founder-photo">{{ if $s.photoLabel }}{{ $s.photoLabel }}{{ else }}Photo{{ end }}</div>
|
||||
<div class="founder-body">
|
||||
<h3>{{ $s.name }}</h3>
|
||||
<span class="founder-role">{{ $s.role }}</span>
|
||||
{{ $s.bio | markdownify }}
|
||||
{{ if $s.contactUrl }}<p class="mt-1"><a class="more" href="{{ $s.contactUrl }}">{{ replaceRE `\s*[→↗]\s*$` "" $s.contactLabel }}<span class="arrow" aria-hidden="true">→</span></a></p>{{ end }}
|
||||
</div>
|
||||
</div>
|
||||
@@ -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 }}
|
||||
@@ -0,0 +1,109 @@
|
||||
{{/*
|
||||
The opening of every page: an optional note (the eyebrow), the statement,
|
||||
the dek, the actions — then either the layer panel (when `fit:` is set)
|
||||
or the field, a blue plate with the platform's four layers drawn as bars.
|
||||
`bars: false` drops the field.
|
||||
*/}}
|
||||
{{ $s := . }}
|
||||
<header class="hero{{ if $s.compact }} hero-compact{{ end }}">
|
||||
{{ 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 }}
|
||||
|
||||
{{ with $s.log }}
|
||||
{{/*
|
||||
An agent's activity log, typed out line by line (see the script below).
|
||||
Lines take `tag` + `text`; `think: true` marks a reasoning step (greyed,
|
||||
with a pause); `done: true` closes the run. The window keeps a fixed
|
||||
height and scrolls as lines arrive. `logImage` swaps the blue field for
|
||||
a photo; `logCaption` credits it.
|
||||
*/}}
|
||||
<figure class="hero-field hero-log{{ if $s.logImage }} has-image{{ end }}" data-reveal style="--d:260ms">
|
||||
{{ with $s.logImage }}<img class="log-bg" src="{{ . | relURL }}" alt="" fetchpriority="high">{{ end }}
|
||||
<div class="log-window">
|
||||
<div class="log-bar">
|
||||
<span class="log-title">{{ $s.logTitle | default "agent · your-company.federated.computer" }}</span>
|
||||
<span class="log-live"><span class="note-live" aria-hidden="true"></span>Live</span>
|
||||
</div>
|
||||
<div class="log-view">
|
||||
<ol class="log-lines" aria-label="An AI agent at work on a Federated Core">
|
||||
{{ 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) "Thinking" "")) }}</span>
|
||||
<span class="log-text">{{ .text }}</span>
|
||||
</li>
|
||||
{{ end }}
|
||||
</ol>
|
||||
</div>
|
||||
</div>
|
||||
{{ with $s.logCaption }}<figcaption class="log-caption">{{ . }}</figcaption>{{ end }}
|
||||
</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 }}
|
||||
{{ with $s.fit }}
|
||||
<aside class="hero-fit" aria-label="Platform layers this path uses" data-reveal style="--d:260ms">
|
||||
<div class="hero-fit-head">
|
||||
<p class="hero-fit-title">What this path uses</p>
|
||||
<a class="more" href="{{ "/solutions/#compare" | relURL }}">Compare all four<span class="arrow" aria-hidden="true">→</span></a>
|
||||
</div>
|
||||
{{ partial "fit.html" (dict "fit" .) }}
|
||||
</aside>
|
||||
{{ else }}
|
||||
{{ if ne $s.bars false }}
|
||||
<figure class="hero-field" aria-hidden="true" data-reveal style="--d:260ms">
|
||||
{{/* Listed in stack order; drawn bottom-up so Core is the foundation. */}}
|
||||
<div class="field-stack">
|
||||
{{ range $i, $l := hugo.Data.layers }}
|
||||
<span class="field-layer" style="--i:{{ $i }}"><i></i><em>{{ printf "%02d" (add $i 1) }} · {{ $l.name }}</em></span>
|
||||
{{ end }}
|
||||
</div>
|
||||
</figure>
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
</header>
|
||||
@@ -0,0 +1,18 @@
|
||||
{{ $s := . }}
|
||||
{{ partial "sections/head.html" $s }}
|
||||
<ol class="entries layer-list">
|
||||
{{ range $i, $item := $s.items }}
|
||||
<li>
|
||||
{{ $key := "" }}{{ range hugo.Data.layers }}{{ if eq .url $item.link }}{{ $key = .key }}{{ end }}{{ end }}
|
||||
<a href="{{ $item.link | relURL }}" class="entry layer-row"{{ with $key }} data-layer="{{ . }}"{{ end }}>
|
||||
<span class="entry-n">{{ printf "%03d" (add $i 1) }}</span>
|
||||
<span class="layer-body">
|
||||
<span class="entry-title">{{ $item.title }}</span>
|
||||
<span class="entry-text">{{ $item.body | markdownify }}</span>
|
||||
</span>
|
||||
<span class="entry-go">{{ with $item.linktext }}{{ replaceRE `\s*[→↗]\s*$` "" . }}{{ end }}<span class="arrow" aria-hidden="true">→</span></span>
|
||||
</a>
|
||||
</li>
|
||||
{{ end }}
|
||||
</ol>
|
||||
{{ if $s.ctas }}<div class="cta-row">{{ partial "sections/ctas.html" $s.ctas }}</div>{{ end }}
|
||||
@@ -0,0 +1,31 @@
|
||||
{{/*
|
||||
Capability matrix — rows are capabilities, columns are customer paths.
|
||||
Cell values: "yes" -> Included, "own" -> Your own, "no" -> Add anytime,
|
||||
or any other string, shown as written.
|
||||
*/}}
|
||||
{{ $s := . }}
|
||||
{{ partial "sections/head.html" $s }}
|
||||
<div class="table-scroll">
|
||||
<table class="compare-table matrix-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">Capability</th>
|
||||
{{ range $s.columns }}<th scope="col"><a href="{{ .link | relURL }}">{{ .name }}</a></th>{{ end }}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{ range $s.rows }}
|
||||
<tr>
|
||||
<th scope="row">{{ .label }}{{ with .layer }}<span class="matrix-layer">{{ . }}</span>{{ end }}</th>
|
||||
{{ range .cells }}
|
||||
{{ if eq . "yes" }}<td class="m-yes">Included</td>
|
||||
{{ else if eq . "own" }}<td class="m-own">Your own</td>
|
||||
{{ else if eq . "no" }}<td class="m-no">Add anytime</td>
|
||||
{{ else }}<td class="m-note">{{ . }}</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 }}
|
||||
@@ -0,0 +1,57 @@
|
||||
{{/*
|
||||
Customer paths — one row per customer type, each with its layer fit.
|
||||
`strip: true` adds a four-layer strip pinned above the list: hovering or
|
||||
focusing a path lights the layers it uses there, and in any `layers`
|
||||
section on the page (rows carry data-layer). Mouse devices only.
|
||||
*/}}
|
||||
{{ $s := . }}
|
||||
{{ partial "sections/head.html" $s }}
|
||||
{{ if $s.strip }}
|
||||
<div class="layer-strip" aria-hidden="true">
|
||||
<span class="layer-strip-label"><span class="strip-idle">Point at a path to see the layers it uses</span><span class="strip-on">Layers this path uses</span></span>
|
||||
<ol class="layer-strip-list">
|
||||
{{ range hugo.Data.layers }}<li class="strip-layer" data-layer="{{ .key }}"><i></i><em>{{ .name }}</em></li>{{ end }}
|
||||
</ol>
|
||||
</div>
|
||||
{{ end }}
|
||||
<ol class="entries path-list">
|
||||
{{ range $i, $p := $s.items }}
|
||||
<li>
|
||||
<a href="{{ $p.link | relURL }}" class="path-row" data-fit="{{ $p.fit | jsonify }}">
|
||||
<div class="path-body">
|
||||
<span class="path-who"><span class="entry-n">{{ printf "%03d" (add $i 1) }}</span>{{ $p.who }}</span>
|
||||
<h3>{{ $p.title }}</h3>
|
||||
<p>{{ $p.body | markdownify }}</p>
|
||||
<span class="more path-link">{{ replaceRE `\s*[→↗]\s*$` "" ($p.linktext | default "See this path") }}<span class="arrow" aria-hidden="true">→</span></span>
|
||||
</div>
|
||||
<div class="path-fit">{{ partial "fit.html" (dict "fit" $p.fit "compact" true) }}</div>
|
||||
</a>
|
||||
</li>
|
||||
{{ end }}
|
||||
</ol>
|
||||
{{ if $s.strip }}
|
||||
<script>
|
||||
(function () {
|
||||
var list = document.currentScript.parentNode.querySelector('.path-list');
|
||||
if (!list || !matchMedia('(hover: hover)').matches) return;
|
||||
var targets = document.querySelectorAll('.strip-layer[data-layer], .layer-row[data-layer]');
|
||||
function light(fit) {
|
||||
targets.forEach(function (el) {
|
||||
var state = fit ? (fit[el.dataset.layer] || 'no') : '';
|
||||
el.classList.toggle('is-lit', state === 'yes');
|
||||
el.classList.toggle('is-own', state === 'own');
|
||||
el.classList.toggle('is-off', state === 'no');
|
||||
});
|
||||
document.querySelectorAll('.layer-strip').forEach(function (el) { el.classList.toggle('is-active', !!fit); });
|
||||
}
|
||||
list.querySelectorAll('.path-row').forEach(function (row) {
|
||||
var fit = JSON.parse(row.dataset.fit || '{}');
|
||||
row.addEventListener('mouseenter', function () { light(fit); });
|
||||
row.addEventListener('focus', function () { light(fit); });
|
||||
});
|
||||
list.addEventListener('mouseleave', function () { light(null); });
|
||||
list.addEventListener('focusout', function (e) { if (!list.contains(e.relatedTarget)) light(null); });
|
||||
})();
|
||||
</script>
|
||||
{{ end }}
|
||||
{{ if $s.ctas }}<div class="cta-row">{{ partial "sections/ctas.html" $s.ctas }}</div>{{ end }}
|
||||
@@ -0,0 +1,100 @@
|
||||
{{/*
|
||||
Pricing — one or more plan groups (e.g. "You host" / "We host") behind a
|
||||
switch. The switch is a set of radio inputs styled as tabs, so it works
|
||||
without JS; up to four groups. Each group can have:
|
||||
tiers — bordered rows: name, price, unit, note, features
|
||||
includes — a checklist shared by every tier, plus an optional note
|
||||
addons — small priced tiles
|
||||
apps — a compact table of single apps: name, category, body, price
|
||||
*/}}
|
||||
{{ $s := . }}
|
||||
{{ $key := partial "section-id.html" $s | default "plans" }}
|
||||
{{ partial "sections/head.html" $s }}
|
||||
<div class="plan-switch">
|
||||
{{ if gt (len $s.groups) 1 }}
|
||||
{{ range $i, $g := $s.groups }}
|
||||
<input type="radio" class="plan-radio" name="{{ $key }}" id="{{ $key }}-{{ $i }}" aria-label="{{ $g.label }}"{{ if eq $i 0 }} checked{{ end }}>
|
||||
{{ end }}
|
||||
<div class="plan-tabs" aria-hidden="true">
|
||||
{{ range $i, $g := $s.groups }}<label for="{{ $key }}-{{ $i }}">{{ $g.label }}</label>{{ end }}
|
||||
</div>
|
||||
{{ end }}
|
||||
|
||||
<div class="plan-panels">
|
||||
{{ range $s.groups }}
|
||||
<div class="plan-panel"{{ with .id }} id="{{ . }}"{{ end }}>
|
||||
{{ with .lede }}<p class="plan-lede">{{ . | markdownify }}</p>{{ end }}
|
||||
|
||||
{{ with .tiers }}
|
||||
<ol class="tier-list">
|
||||
{{ range . }}
|
||||
<li class="tier">
|
||||
<div class="tier-head">
|
||||
<h3 class="tier-name">{{ .name }}</h3>
|
||||
<p class="tier-price"><span class="tier-amount">{{ .price }}</span>{{ with .unit }}<span class="tier-unit">{{ . }}</span>{{ end }}</p>
|
||||
{{ with .note }}<p class="tier-note">{{ . | markdownify }}</p>{{ end }}
|
||||
</div>
|
||||
<div class="tier-body">
|
||||
{{ with .intro }}<p class="tier-intro">{{ . | markdownify }}</p>{{ end }}
|
||||
<ul class="tier-features">
|
||||
{{ range .features }}<li>{{ . | markdownify }}</li>{{ end }}
|
||||
</ul>
|
||||
</div>
|
||||
</li>
|
||||
{{ end }}
|
||||
</ol>
|
||||
{{ end }}
|
||||
|
||||
{{ with .includes }}
|
||||
<div class="plan-block">
|
||||
{{ with .headline }}<h3 class="plan-subhead">{{ . }}</h3>{{ end }}
|
||||
<ul class="check-list">
|
||||
{{ range .items }}<li>{{ . | markdownify }}</li>{{ end }}
|
||||
</ul>
|
||||
{{ with .note }}<p class="plan-note">{{ . | markdownify }}</p>{{ end }}
|
||||
</div>
|
||||
{{ end }}
|
||||
|
||||
{{ with .addons }}
|
||||
<div class="plan-block">
|
||||
{{ with .headline }}<h3 class="plan-subhead">{{ . }}</h3>{{ end }}
|
||||
{{ with .lede }}<p class="plan-note plan-note-top">{{ . | markdownify }}</p>{{ end }}
|
||||
<ul class="addon-row">
|
||||
{{ range .items }}
|
||||
<li class="addon">
|
||||
<h4 class="addon-name">{{ .name }}</h4>
|
||||
<p class="tier-price"><span class="tier-amount">{{ .price }}</span>{{ with .unit }}<span class="tier-unit">{{ . }}</span>{{ end }}</p>
|
||||
{{ with .body }}<p class="addon-body">{{ . | markdownify }}</p>{{ end }}
|
||||
</li>
|
||||
{{ end }}
|
||||
</ul>
|
||||
</div>
|
||||
{{ end }}
|
||||
|
||||
{{ with .apps }}
|
||||
<div class="plan-block">
|
||||
{{ with .headline }}<h3 class="plan-subhead">{{ . }}</h3>{{ end }}
|
||||
{{ with .lede }}<p class="plan-note plan-note-top">{{ . | markdownify }}</p>{{ end }}
|
||||
<div class="table-scroll">
|
||||
<table class="compare-table app-price-table">
|
||||
<thead><tr><th scope="col">App</th><th scope="col">What it does</th><th scope="col">Price</th></tr></thead>
|
||||
<tbody>
|
||||
{{ range .items }}
|
||||
<tr>
|
||||
<th scope="row">{{ .name }}{{ with .category }}<span class="matrix-layer">{{ . }}</span>{{ end }}</th>
|
||||
<td>{{ .body | markdownify }}</td>
|
||||
<td class="app-price">{{ .price }}</td>
|
||||
</tr>
|
||||
{{ end }}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
{{ end }}
|
||||
|
||||
{{ with .ctas }}<div class="cta-row mt-2">{{ partial "sections/ctas.html" . }}</div>{{ end }}
|
||||
</div>
|
||||
{{ end }}
|
||||
</div>
|
||||
</div>
|
||||
{{ if $s.ctas }}<div class="cta-row mt-2">{{ partial "sections/ctas.html" $s.ctas }}</div>{{ end }}
|
||||
@@ -0,0 +1,63 @@
|
||||
{{/*
|
||||
One pull quote (`text`, `cite`), or a stack of them (`items`, each with
|
||||
`text` and `cite`). With `cards: true`, items render as a two-column grid
|
||||
of short testimonials: `text`, `name`, optional `role` (Markdown, for a
|
||||
linked company) and `photo`; without a photo, the person's initials show.
|
||||
`savings` adds a compact savings calculator below: `apps` (names) and
|
||||
`selected` (names ticked to start) are looked up in the `savings` section
|
||||
of the page at `from`, so prices live in one place; `headline` and `more`
|
||||
(label + url of the full calculator) are optional.
|
||||
*/}}
|
||||
{{ $s := . }}
|
||||
{{ partial "sections/head.html" $s }}
|
||||
{{ $items := $s.items | default (slice (dict "text" $s.text "cite" $s.cite)) }}
|
||||
{{ if $s.cards }}
|
||||
<ul class="said-cards">
|
||||
{{ range $items }}
|
||||
<li class="said-card">
|
||||
<blockquote>{{ .text | markdownify }}</blockquote>
|
||||
<div class="said-who">
|
||||
{{ with .photo }}
|
||||
<img class="said-photo" src="{{ . | relURL }}" alt="" width="44" height="44" loading="lazy">
|
||||
{{ else }}
|
||||
{{ $initials := "" }}{{ range first 2 (split .name " ") }}{{ $initials = printf "%s%s" $initials (substr . 0 1) }}{{ end }}
|
||||
<span class="said-photo said-initials" aria-hidden="true">{{ $initials }}</span>
|
||||
{{ end }}
|
||||
<span class="said-id">
|
||||
<span class="said-name">{{ .name }}</span>
|
||||
{{ with .role }}<span class="said-role">{{ . | markdownify }}</span>{{ end }}
|
||||
</span>
|
||||
</div>
|
||||
</li>
|
||||
{{ end }}
|
||||
</ul>
|
||||
{{ else }}
|
||||
<div class="said-list">
|
||||
{{ range $items }}
|
||||
<figure class="said">
|
||||
<blockquote>{{ .text | markdownify }}</blockquote>
|
||||
{{ with .cite }}<figcaption>{{ . | markdownify }}</figcaption>{{ end }}
|
||||
</figure>
|
||||
{{ end }}
|
||||
</div>
|
||||
{{ end }}
|
||||
{{ with $s.savings }}
|
||||
{{ $cfg := . }}
|
||||
{{ with site.GetPage $cfg.from }}
|
||||
{{ $src := dict }}
|
||||
{{ range .Params.sections }}{{ if eq .type "savings" }}{{ $src = . }}{{ end }}{{ end }}
|
||||
{{ $apps := slice }}
|
||||
{{ range $name := $cfg.apps }}
|
||||
{{ range $src.apps }}{{ if eq .name $name }}
|
||||
{{ $apps = $apps | append (dict "name" .name "cost" .cost "perSeat" .perSeat "selected" (in ($cfg.selected | default slice) $name)) }}
|
||||
{{ end }}{{ end }}
|
||||
{{ end }}
|
||||
<div class="said-savings">
|
||||
{{ with $cfg.headline }}<h3 class="said-savings-title">{{ . }}</h3>{{ end }}
|
||||
{{ partial "savings-calc.html" (dict "key" (printf "%s-savings" (partial "section-id.html" $s)) "apps" $apps "annual" $src.federatedAnnual "compact" true "more" $cfg.more) }}
|
||||
</div>
|
||||
{{ else }}
|
||||
{{ errorf "quote section: savings.from page %q not found" $cfg.from }}
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
{{ if $s.ctas }}<div class="cta-row mt-2">{{ partial "sections/ctas.html" $s.ctas }}</div>{{ end }}
|
||||
@@ -0,0 +1,31 @@
|
||||
{{/*
|
||||
Savings — an optional bar comparison (`bars`: name, amount, highlight) and
|
||||
a savings calculator (`apps`: name, cost, perSeat, selected; compared with
|
||||
`federatedAnnual`), rendered by partials/savings-calc.html.
|
||||
*/}}
|
||||
{{ $s := . }}
|
||||
{{ $key := partial "section-id.html" $s | default "savings" }}
|
||||
{{ partial "sections/head.html" $s }}
|
||||
|
||||
{{ with $s.bars }}
|
||||
{{ $max := 0 }}
|
||||
{{ range . }}{{ if gt .amount $max }}{{ $max = .amount }}{{ end }}{{ end }}
|
||||
<figure class="bar-chart">
|
||||
{{ with $s.barsTitle }}<figcaption class="bar-chart-title">{{ . }}</figcaption>{{ end }}
|
||||
<ol class="bar-list">
|
||||
{{ range . }}
|
||||
<li class="bar-row{{ if .highlight }} is-highlight{{ end }}">
|
||||
<span class="bar-name">{{ .name }}</span>
|
||||
<span class="bar-track"><span class="bar-fill" style="width: {{ div (mul .amount 100.0) $max }}%"></span></span>
|
||||
<span class="bar-value">${{ lang.FormatNumber 0 .amount }}</span>
|
||||
</li>
|
||||
{{ end }}
|
||||
</ol>
|
||||
{{ with $s.barsNote }}<p class="plan-note">{{ . | markdownify }}</p>{{ end }}
|
||||
</figure>
|
||||
{{ end }}
|
||||
|
||||
{{ with $s.apps }}
|
||||
{{ partial "savings-calc.html" (dict "key" $key "apps" . "annual" $s.federatedAnnual "note" $s.calcNote) }}
|
||||
{{ end }}
|
||||
{{ if $s.ctas }}<div class="cta-row mt-2">{{ partial "sections/ctas.html" $s.ctas }}</div>{{ end }}
|
||||
@@ -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 }}
|
||||
@@ -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 }}</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 }}
|
||||
@@ -0,0 +1,13 @@
|
||||
{{ $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>
|
||||
@@ -0,0 +1,28 @@
|
||||
{{ $s := . }}
|
||||
{{ partial "sections/head.html" $s }}
|
||||
<div class="table-scroll">
|
||||
<table class="compare-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th></th>
|
||||
{{ range $s.headers }}<th>{{ . }}</th>{{ end }}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{ range $s.rows }}
|
||||
{{ $n := len .cells }}
|
||||
<tr>
|
||||
<th>{{ .label }}</th>
|
||||
{{ range $i, $cell := .cells }}
|
||||
{{ if eq (add $i 1) $n }}
|
||||
<td class="good">{{ $cell }}</td>
|
||||
{{ else }}
|
||||
<td class="bad">{{ $cell }}</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 }}
|
||||
@@ -0,0 +1,6 @@
|
||||
{{ $s := . }}
|
||||
{{ partial "sections/head.html" $s }}
|
||||
<div class="prose{{ if $s.centered }} centered{{ end }}">
|
||||
{{ $s.body | markdownify }}
|
||||
</div>
|
||||
{{ if $s.ctas }}<div class="cta-row mt-2">{{ partial "sections/ctas.html" $s.ctas }}</div>{{ end }}
|
||||
@@ -0,0 +1,5 @@
|
||||
{{ $s := . }}
|
||||
{{ $id := partial "section-id.html" $s }}
|
||||
<section class="section section-{{ $s.type }}"{{ with $id }} id="{{ . }}"{{ end }} data-reveal>
|
||||
{{ partial (printf "sections/%s.html" $s.type) $s }}
|
||||
</section>
|
||||
@@ -0,0 +1,3 @@
|
||||
{{ define "main" }}
|
||||
{{ partial "render-sections.html" . }}
|
||||
{{ end }}
|
||||
Reference in New Issue
Block a user