Files
webcoredesktop/layouts/partials/sections/hero.html
T
2026-10-28 09:17:40 -06:00

140 lines
6.9 KiB
HTML

{{/*
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>