Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions site/src/components/Analytics.astro
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,16 @@ const enabled = Boolean(measurementId) && import.meta.env.PROD;
});
});

// The copy button writes through the Clipboard API, which raises no
// copy event. It announces itself instead, so both paths are counted
// the same way.
document.addEventListener("fanout:copy", function (event) {
gtag("event", "copy_command", {
action: event.detail || "unknown",
location: "closing_cta",
});
});

var tag = document.createElement("script");
tag.async = true;
tag.src = "https://www.googletagmanager.com/gtag/js?id=" + encodeURIComponent(measurementId);
Expand Down
30 changes: 6 additions & 24 deletions site/src/components/landing/Benchmark.astro
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ const signals = [
const gates = ["server drops 0", "no restarts", "Parquet verified"];
---

<div class="fo-benchmark" role="group" aria-labelledby="fo-benchmark-title" data-fo-benchmark>
<div class="fo-benchmark not-content" role="group" aria-labelledby="fo-benchmark-title" data-fo-benchmark>
<div class="fo-benchmark-status">
<span class="fo-candidate-dot" aria-hidden="true"></span>
<span>Publication candidate</span>
Expand Down Expand Up @@ -143,8 +143,8 @@ const gates = ["server drops 0", "no restarts", "Parquet verified"];

<style>
.fo-benchmark {
--fo-bench-pass: #7fd962;
--fo-bench-warn: #ffb454;
--fo-bench-pass: var(--fo-status-ok);
--fo-bench-warn: var(--fo-status-warn);
/* Small type on this card sits at the 0.6rem site floor, where gray-3 on
* the light background is only 4.16:1 — under AA. The muted token drops a
* step darker in light mode and stays gray-3 in dark, where the card is
Expand All @@ -159,36 +159,18 @@ const gates = ["server drops 0", "no restarts", "Parquet verified"];
}

:global(:root[data-theme="light"]) .fo-benchmark {
--fo-bench-pass: #3c7a2a;
--fo-bench-warn: #9a5b00;
--fo-bench-muted: var(--sl-color-gray-2);
}

/* fanout.css forces the light palette for every theme under @media print, so
* a dark-mode reader printing this card would otherwise keep the dark-mode
* status and gate colours on white paper. These are local tokens, so the
* global print override cannot reach them — restate them here. */
/* fanout.css forces the light palette for every theme under @media print. The
* status hues follow it now that they are site tokens; this one is local, so
* the global print override cannot reach it — restate it here. */
@media print {
.fo-benchmark {
--fo-bench-pass: #3c7a2a;
--fo-bench-warn: #9a5b00;
--fo-bench-muted: var(--sl-color-gray-2);
}
}

/* Starlight gives every block that follows a sibling a 1rem top margin inside
* rendered content. This card draws its own separators and lays out its own
* grids, so each region opts out: otherwise the rules float away from what
* they divide, and the tiles in a row stop sharing a baseline because only
* the first one is exempt. */
.fo-benchmark .fo-benchmark-main,
.fo-benchmark .fo-benchmark-signals,
.fo-benchmark .fo-signals-grid,
.fo-benchmark .fo-signals-grid > div,
.fo-benchmark .fo-benchmark-foot {
margin-top: 0;
}

/* Header rail. The card reads as an assembled instrument rather than a panel
* of text because its three surfaces step apart tonally — rail, body, and the
* recessed plotting well — with hairlines rather than shadows between them. */
Expand Down
2 changes: 1 addition & 1 deletion site/src/components/landing/Contrast.astro
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ const single = [
];
---

<div class="fo-contrast">
<div class="fo-contrast not-content">
<div class="fo-pane fo-pane-many">
<p class="fo-pane-label">The usual arrangement</p>
<ul>
Expand Down
125 changes: 123 additions & 2 deletions site/src/components/landing/CtaBar.astro
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,13 @@
* The command is the one from the install page, in the form that verifies its
* download. Keep the two in step; a copyable command that has drifted is worse
* than no command.
*
* The copy button reports the copy itself. Analytics counts installs from the
* browser's own `copy` event, which fires for a hand-made selection and not for
* the Clipboard API — so a button that only wrote to the clipboard would take
* over the common path and stop the metric without anything looking broken.
*/
import Icon from "../Icon.astro";
---

<div class="fo-cta">
Expand All @@ -18,7 +24,24 @@
One binary, one data directory. The installer checks the archive against the
release checksums before it extracts anything.
</p>
<pre data-analytics-copy="install_command"><code>curl -fsSL https://raw.githubusercontent.com/labstack/fanout/main/scripts/install.sh | sh</code></pre>
<div class="fo-cta-command">
<pre
data-analytics-copy="install_command"
tabindex="0"
role="region"
aria-label="Install command, scrollable"
><code id="fo-install-command">curl -fsSL https://raw.githubusercontent.com/labstack/fanout/main/scripts/install.sh | sh</code></pre>
<button
type="button"
class="fo-cta-copy"
data-copy-target="fo-install-command"
data-copy-event="install_command"
>
<Icon name="copy" class="fo-copy-idle" />
<Icon name="check" class="fo-copy-done" />
<span data-copy-label aria-live="polite">Copy</span>
</button>
</div>
<div class="fo-cta-actions">
<a
class="fo-cta-primary"
Expand Down Expand Up @@ -67,11 +90,20 @@
max-width: 60ch;
}

.fo-cta-command {
display: flex;
align-items: stretch;
gap: 0.5rem;
margin-top: 1.5rem;
}

/* The command scrolls inside its own box rather than widening the page — the
same rule the reference tables follow, and it is focusable for the same
reason: an overflow a keyboard cannot reach cannot be read. */
.fo-cta pre {
margin: 1.5rem 0 0;
flex: 1;
min-width: 0;
margin: 0;
padding: 0.85rem 1rem;
overflow-x: auto;
border: 1px solid var(--sl-color-gray-5);
Expand All @@ -91,6 +123,56 @@
white-space: pre;
}

/* Beside the command rather than floating over it: the command scrolls, and a
button laid on top of scrolling text either covers a character or needs an
opaque patch to hide behind. */
.fo-cta-copy {
flex: 0 0 auto;
/* The margin is the point: Starlight gives an element following a sibling a
1rem top margin, and a flex item stretches to the line minus its own
margins — so the button came up exactly 1rem short of the command box it
sits against, and sat low by the same amount. */
margin: 0;
align-self: stretch;
display: inline-flex;
align-items: center;
gap: 0.4rem;
padding: 0 0.85rem;
border: 1px solid var(--sl-color-gray-5);
border-radius: 8px;
background: var(--sl-color-black);
font-family: var(--fo-font-display);
font-size: 0.78rem;
color: var(--sl-color-gray-2);
cursor: pointer;
}

.fo-cta-copy:hover {
border-color: var(--sl-color-gray-4);
color: var(--sl-color-white);
}

.fo-cta-copy:focus-visible {
outline: 2px solid var(--sl-color-accent);
outline-offset: 2px;
}

.fo-cta-copy span {
/* The label swaps between two words of different width; reserving the wider
one keeps the button from resizing under the pointer. */
min-width: 4ch;
text-align: start;
}

.fo-cta-copy .fo-copy-done,
.fo-cta-copy[data-copied] .fo-copy-idle {
display: none;
}

.fo-cta-copy[data-copied] .fo-copy-done {
display: inline-block;
}

.fo-cta-actions {
display: flex;
flex-wrap: wrap;
Expand Down Expand Up @@ -133,3 +215,42 @@
outline-offset: 2px;
}
</style>

<script>
const button = document.querySelector<HTMLButtonElement>("[data-copy-target]");
const target = button && document.getElementById(button.dataset.copyTarget!);

// A control that silently fails is worse than no control: without the
// Clipboard API the button cannot do its job, so it does not appear.
if (button && (!target || !navigator.clipboard)) {
button.remove();
} else if (button && target) {
const label = button.querySelector<HTMLElement>("[data-copy-label]")!;
const idle = label.textContent ?? "Copy";
let revert: number | undefined;

button.addEventListener("click", async () => {
try {
await navigator.clipboard.writeText((target.textContent ?? "").trim());
} catch {
return;
}

button.dataset.copied = "";
label.textContent = "Copied";

// Analytics counts the browser's own copy event, which this path does not
// raise. Announcing it here keeps the measurement whole without giving the
// button a dependency on whether analytics loaded at all.
document.dispatchEvent(
new CustomEvent("fanout:copy", { detail: button.dataset.copyEvent }),
);

window.clearTimeout(revert);
revert = window.setTimeout(() => {
delete button.dataset.copied;
label.textContent = idle;
}, 2000);
});
}
</script>
14 changes: 7 additions & 7 deletions site/src/components/landing/Hero.astro
Original file line number Diff line number Diff line change
Expand Up @@ -65,16 +65,16 @@ const { title = data.title, tagline, actions = [] } = data.hero ?? {};
<svg class="fo-scope-chart" viewBox="0 0 400 120" preserveAspectRatio="none" aria-hidden="true">
<defs>
<linearGradient id="fo-scope-fill" x1="0" x2="0" y1="0" y2="1">
<stop offset="0" stop-color="#f26d78" stop-opacity="0.28"></stop>
<stop offset="1" stop-color="#f26d78" stop-opacity="0"></stop>
<stop offset="0" stop-color="var(--fo-status-bad)" stop-opacity="0.28"></stop>
<stop offset="1" stop-color="var(--fo-status-bad)" stop-opacity="0"></stop>
</linearGradient>
</defs>
<path class="fo-scope-area" d={`${series} L400,120 L0,120 Z`} fill="url(#fo-scope-fill)"></path>
<path class="fo-scope-line" d={series} fill="none" stroke="#f26d78" stroke-width="2"
<path class="fo-scope-line" d={series} fill="none" stroke="var(--fo-status-bad)" stroke-width="2"
stroke-linecap="round" stroke-linejoin="round"></path>
<line class="fo-scope-mark" x1="252" y1="8" x2="252" y2="120"
stroke="var(--sl-color-gray-4)" stroke-width="1" stroke-dasharray="3 3"></line>
<circle class="fo-scope-dot" cx="252" cy="52" r="4.5" fill="#f26d78"></circle>
<circle class="fo-scope-dot" cx="252" cy="52" r="4.5" fill="var(--fo-status-bad)"></circle>
</svg>

<div class="fo-scope-axis">
Expand Down Expand Up @@ -273,7 +273,7 @@ const { title = data.title, tagline, actions = [] } = data.hero ?? {};
}

.fo-scope-at {
color: #f26d78;
color: var(--fo-status-bad);
}

.fo-scope-readout {
Expand Down Expand Up @@ -320,7 +320,7 @@ const { title = data.title, tagline, actions = [] } = data.hero ?? {};
}

.fo-up {
color: #f26d78 !important;
color: var(--fo-status-bad) !important;
}

/* The question, in the words a person would use. Set as a quotation rather
Expand All @@ -346,7 +346,7 @@ const { title = data.title, tagline, actions = [] } = data.hero ?? {};
}

.fo-scope-found :global(svg) {
color: #7fd962;
color: var(--fo-status-ok);
margin-top: 0.15rem;
}

Expand Down
52 changes: 46 additions & 6 deletions site/src/components/landing/Preview.astro
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ const state = { ok: "healthy", warn: "degraded", bad: "unhealthy" };
const spark = "M0,30 L12,26 L24,28 L36,20 L48,24 L60,16 L72,19 L84,12 L96,15 L108,9 L120,13 L132,22 L144,31 L156,27 L168,18 L180,13 L192,9 L200,7";
---

<div class="fo-preview">
<div class="fo-preview not-content">
<div class="fo-chrome">
<span class="fo-dots"><i></i><i></i><i></i></span>
<span class="fo-url">demo.fanout.run<span> · Overview</span></span>
Expand Down Expand Up @@ -69,6 +69,7 @@ const spark = "M0,30 L12,26 L24,28 L36,20 L48,24 L60,16 L72,19 L84,12 L96,15 L10
</tbody>
</table>
</div>
<p class="fo-panel-foot">5 of 47 services · worst first</p>
</section>

<section class="fo-panel">
Expand Down Expand Up @@ -161,6 +162,34 @@ const spark = "M0,30 L12,26 L24,28 L36,20 L48,24 L60,16 L72,19 L84,12 L96,15 L10
background: var(--sl-color-gray-6);
}

/* Closes the left pane. Without it the table stopped halfway down a panel
whose neighbour runs to the bottom, which read as a rendering fault rather
than as a short list. The count agrees with the Services tile beside it. */
.fo-panel-foot {
margin: 1.1rem 0 0;
padding-top: 0.8rem;
border-top: 1px solid var(--sl-color-hairline-shade);
font-family: var(--fo-font-display);
font-size: 0.66rem;
letter-spacing: 0.06em;
color: var(--sl-color-gray-4);
}

.fo-panel {
display: flex;
flex-direction: column;
}

.fo-panel .table-wrap {
flex: 1;
}

/* The scroll wrapper is a documentation-content affordance and carries that
stylesheet's top margin; inside a panel the heading already sets the gap. */
.fo-panel .table-wrap {
margin-top: 0;
}

.fo-panel h3 {
font-family: var(--fo-font-display);
font-size: 0.66rem;
Expand Down Expand Up @@ -203,13 +232,24 @@ const spark = "M0,30 L12,26 L24,28 L36,20 L48,24 L60,16 L72,19 L84,12 L96,15 L10
color: var(--sl-color-white);
}

/* Right-aligned, and the last column loses its trailing padding: with every
column left-aligned the table's slack collected in the final cell, so the
numbers huddled against the service names with an empty half-pane beside
them. Aligning on the right also lets the figures compare down a column. */
.fo-panel th:nth-child(n + 3),
.fo-num {
text-align: right;
font-variant-numeric: tabular-nums;
}

.fo-panel th:last-child,
.fo-panel td:last-child {
padding-inline-end: 0;
}

.fo-delta {
margin-inline-start: 0.4rem;
color: #f26d78;
color: var(--fo-status-bad);
}

/* The three status hues are the palette's load-bearing ones: they mean the
Expand All @@ -226,9 +266,9 @@ const spark = "M0,30 L12,26 L24,28 L36,20 L48,24 L60,16 L72,19 L84,12 L96,15 L10
border-radius: 50%;
}

.fo-ok i { background: #7fd962; }
.fo-warn i { background: #ffb454; }
.fo-bad i { background: #f26d78; }
.fo-ok i { background: var(--fo-status-ok); }
.fo-warn i { background: var(--fo-status-warn); }
.fo-bad i { background: var(--fo-status-bad); }

.fo-kpis {
display: grid;
Expand Down Expand Up @@ -262,7 +302,7 @@ const spark = "M0,30 L12,26 L24,28 L36,20 L48,24 L60,16 L72,19 L84,12 L96,15 L10
font-variant-numeric: tabular-nums;
}

.fo-hot { color: #f26d78 !important; }
.fo-hot { color: var(--fo-status-bad) !important; }

.fo-kpi em {
font-family: var(--fo-font-body);
Expand Down
Loading
Loading