From 8ff2abf3aa93c38d4145102c35e56158df1f0ddf Mon Sep 17 00:00:00 2001 From: Filip Leonarski Date: Sat, 24 Oct 2026 22:01:12 +0200 Subject: [PATCH] docs: a standalone front page with the three programs and the downloads, the Sphinx tree one page below it docs/landing/ is a self-contained page (HTML, CSS, a little JS, no framework): a hero, a card per program, the download section with the archives of the newest release and the reader's own platform marked, the licence in one paragraph, light and dark. The jfjoch_downloads extension renders it into the HTML output root as index.html at the end of every Sphinx build, so it is served wherever the documentation is - Read the Docs, the gitea-pages branch and the broker's frontend bundle - and its download buttons are the same ones as on the Sphinx download pages, built from the configured release and repointed at the newest published one by the shared script. The Sphinx root page moves to documentation.html (root_doc), and the frontend's documentation frame opens that page. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_015CeEGgpKdfkCTJb3YNMJcu (cherry picked from commit 64cde61f0b941b3e8c99eaa9b74afeaa75aefc78) --- docs/_ext/jfjoch_downloads.py | 27 +- docs/conf.py | 4 + docs/{index.rst => documentation.rst} | 11 +- docs/landing/index.html | 149 +++++++++++ docs/landing/landing.css | 369 ++++++++++++++++++++++++++ docs/landing/landing.js | 60 +++++ frontend/src/App.tsx | 2 +- 7 files changed, 615 insertions(+), 7 deletions(-) rename docs/{index.rst => documentation.rst} (92%) create mode 100644 docs/landing/index.html create mode 100644 docs/landing/landing.css create mode 100644 docs/landing/landing.js diff --git a/docs/_ext/jfjoch_downloads.py b/docs/_ext/jfjoch_downloads.py index 0fe07e327..f93438361 100644 --- a/docs/_ext/jfjoch_downloads.py +++ b/docs/_ext/jfjoch_downloads.py @@ -12,11 +12,18 @@ script matches against the release's asset list, written loosely enough to survi bump. """ +import pathlib +import shutil + from docutils import nodes from docutils.parsers.rst import Directive RELEASES = "https://gitea.psi.ch/mx/jungfraujoch/releases" +# docs/landing/: the front page, rendered from its template into the output root as index.html at +# the end of the build (write_landing below), with the same download blocks as the Sphinx pages. +LANDING_FILES = ("landing.css", "landing.js") + # program -> [(platform, requirements, [(label, asset name with {v}, regex for the live lookup)])] # The first button of a platform is the recommended one and is drawn filled. PROGRAMS = { @@ -69,7 +76,8 @@ def render(program, version): ' · all releases

' % (version, RELEASES, version, RELEASES), '
'] for platform, requirements, archives in PROGRAMS[program]: - out.append('

%s

' % platform) + out.append('

%s

' + % (platform.lower(), platform)) for i, (label, pattern, regex) in enumerate(archives): name = pattern.format(v=version) cls = "jfj-dl__btn" if i == 0 else "jfj-dl__btn jfj-dl__btn--secondary" @@ -93,6 +101,23 @@ class JfjochDownloads(Directive): return [nodes.raw("", render(program, version), format="html")] +def write_landing(app, exception): + """Render docs/landing/index.html into the HTML output root, next to the Sphinx pages.""" + if exception is not None or app.builder.name != "html": + return + src = pathlib.Path(app.confdir) / "landing" + out = pathlib.Path(app.outdir) + version = app.config.release + html = (src / "index.html").read_text(encoding="utf-8") + html = (html.replace("{{release}}", version) + .replace("{{downloads:rugnux}}", render("rugnux", version)) + .replace("{{downloads:viewer}}", render("viewer", version))) + (out / "index.html").write_text(html, encoding="utf-8") + for name in LANDING_FILES: + shutil.copy(src / name, out / name) + + def setup(app): app.add_directive("jfjoch-downloads", JfjochDownloads) + app.connect("build-finished", write_landing) return {"parallel_read_safe": True, "parallel_write_safe": True} diff --git a/docs/conf.py b/docs/conf.py index 9284e684d..393d3a17b 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -26,6 +26,10 @@ extensions = [ 'jfjoch_downloads', ] +# The site's front page is docs/landing/index.html, a standalone page the jfjoch_downloads +# extension renders into the output root as index.html; the Sphinx tree starts one page below it. +root_doc = 'documentation' + templates_path = ['_templates'] # docs/review/ is gitignored working material (review reports, work plans); Sphinx renders # whatever is on disk, so without this a local review file would become a published page. diff --git a/docs/index.rst b/docs/documentation.rst similarity index 92% rename from docs/index.rst rename to docs/documentation.rst index 9e177b386..0cdd8c704 100644 --- a/docs/index.rst +++ b/docs/documentation.rst @@ -1,11 +1,12 @@ -PSI Jungfraujoch -================ +Documentation +============= .. raw:: html -

Open-source software from the Paul Scherrer - Institute for X-ray diffraction data, from the detector to the merged reflections. Three - programs, each usable on its own.

+

The manuals and reference of Jungfraujoch and Rugnux, open-source software + from the Paul Scherrer Institute for X-ray diffraction data. + The front page has the overview and the downloads; this page is the + map of the documentation.

diff --git a/docs/landing/index.html b/docs/landing/index.html new file mode 100644 index 000000000..31e9016f4 --- /dev/null +++ b/docs/landing/index.html @@ -0,0 +1,149 @@ + + + + + + +Jungfraujoch and Rugnux: X-ray diffraction data, from the detector to the merged reflections + + + + + + + +
+ Jungfraujoch + + +
+ +
+ +
+ +
+

Paul Scherrer Institute · open source

+

From the detector to the merged reflections.

+

Software for X-ray diffraction data: Rugnux processes a stored + dataset with one command, the Viewer shows what is in it, and + Jungfraujoch acquires it from the detector at full speed.

+ +

rugnux <diffraction image file>is the whole command: cell, space group and resolution come out of the data.

+
+
+ +
+
+ +
+

Data processing

+

Rugnux

+

Takes a macromolecular diffraction dataset, a rotation sweep or serial stills, from the + images to merged reflections: spot finding, indexing, integration, scaling and merging, with + the space group, cell and resolution decided from the data and a report that says on what + evidence. Writes .mtz, .cif and .hkl for the programs + that come next.

+

Linux · macOS · Windows · command line · fast on an NVIDIA GPU, works without one

+ +
+ +
+

Desktop application

+

Jungfraujoch Viewer

+

Shows what is in a dataset: every image with its spots, predictions and resolution rings, + per-image statistics over the sweep, grid-scan maps, and the same processing pipeline as + Rugnux, run from a settings panel on the open file. Follows a live collection from a running + Jungfraujoch broker.

+

Linux · macOS · Windows · Qt 6

+ +
+ +
+

Data acquisition

+

Jungfraujoch

+

The acquisition and real-time analysis system for the PSI JUNGFRAU and EIGER detectors, + and for DECTRIS detectors through their control unit: an FPGA and GPU pipeline that takes + the detector's frames at full rate, analyses them on the fly, streams them to the NXmx HDF5 + writer and serves a web interface and REST API to the beamline.

+

Linux servers · AMD Alveo U55C FPGA · NVIDIA GPUs

+ +
+ +
+
+ +
+
+

Download

+

Pre-built, signed where the platform asks for it, nothing to install beside them. +

+
+ +
+

Rugnux

+

One self-contained executable. Unpack the archive into a directory of its own and run + bin/rugnux; no CUDA toolkit, no Qt, no service. On a machine with an NVIDIA GPU + take the GPU build, which also runs without one. Details, packages and building from source.

+ {{downloads:rugnux}} +
+ +
+

Jungfraujoch Viewer

+

A desktop application with the Qt runtime included: a portable archive on Linux, a + notarized disk image on macOS, an installer on Windows. Details and building from source.

+ {{downloads:viewer}} +
+ +
+

Jungfraujoch, the acquisition system

+

Installed on the beamline's Linux servers from the RPM and APT + repositories (jfjoch, jfjoch-writer, jfjoch-driver-dkms, + jfjoch-viewer, rugnux), with the FPGA firmware and the writer's + packages attached to every release. + Deployment is the step-by-step guide; Release contents says what each package needs on the machine.

+
+
+ +
+

Free to use, in academia and industry

+

Rugnux, the viewer and the Jungfraujoch services are free software under the + GNU General Public License v3; the FPGA design is released under the + CERN Open Hardware Licence v2, strongly reciprocal. Both let + anyone use, study and modify the software and the hardware design at no charge, commercial use + included; what they ask is that a modified version which is passed on is shared under the same + terms. The released binaries bundle third-party components under GPL-compatible licences, and the + CUDA builds the NVIDIA CUDA runtime under NVIDIA's EULA: see the + third-party notices.

+
+ +
+ + + + + + + diff --git a/docs/landing/landing.css b/docs/landing/landing.css new file mode 100644 index 000000000..f95f805fc --- /dev/null +++ b/docs/landing/landing.css @@ -0,0 +1,369 @@ +/* The front page (docs/landing/index.html). Self-contained: system fonts, no framework, one + palette per theme. The download blocks use the same jfj-dl markup as the Sphinx download pages + (docs/_ext/jfjoch_downloads.py) and are styled here for this page. */ + +:root { + --bg: #fbfbfd; + --bg-raised: #ffffff; + --bg-tint: #eef0fa; + --fg: #1b1d2a; + --fg-muted: #5b6072; + --line: rgba(27, 29, 42, 0.12); + --primary: #3949ab; + --primary-strong: #2c3a8f; + --primary-soft: rgba(57, 73, 171, 0.1); + --on-primary: #ffffff; + --accent: #e9a23b; + --accent-soft: rgba(233, 162, 59, 0.16); + --shadow: 0 1px 2px rgba(27, 29, 42, 0.06), 0 8px 24px rgba(27, 29, 42, 0.06); + --ring: rgba(57, 73, 171, 0.22); + --spot: rgba(57, 73, 171, 0.55); +} + +@media (prefers-color-scheme: dark) { + :root:not([data-theme="light"]) { + --bg: #11131c; + --bg-raised: #181b27; + --bg-tint: #1d2130; + --fg: #e8e9f1; + --fg-muted: #9ea3b8; + --line: rgba(232, 233, 241, 0.12); + --primary: #8e9dff; + --primary-strong: #aab5ff; + --primary-soft: rgba(142, 157, 255, 0.14); + --on-primary: #0d1030; + --accent: #f0b65b; + --accent-soft: rgba(240, 182, 91, 0.16); + --shadow: 0 1px 2px rgba(0, 0, 0, 0.4), 0 8px 24px rgba(0, 0, 0, 0.35); + --ring: rgba(142, 157, 255, 0.24); + --spot: rgba(170, 181, 255, 0.7); + } +} + +:root[data-theme="dark"] { + --bg: #11131c; + --bg-raised: #181b27; + --bg-tint: #1d2130; + --fg: #e8e9f1; + --fg-muted: #9ea3b8; + --line: rgba(232, 233, 241, 0.12); + --primary: #8e9dff; + --primary-strong: #aab5ff; + --primary-soft: rgba(142, 157, 255, 0.14); + --on-primary: #0d1030; + --accent: #f0b65b; + --accent-soft: rgba(240, 182, 91, 0.16); + --shadow: 0 1px 2px rgba(0, 0, 0, 0.4), 0 8px 24px rgba(0, 0, 0, 0.35); + --ring: rgba(142, 157, 255, 0.24); + --spot: rgba(170, 181, 255, 0.7); +} + +* { box-sizing: border-box; } + +html { scroll-behavior: smooth; } + +body { + margin: 0; + background: var(--bg); + color: var(--fg); + font: 16px/1.55 -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; + -webkit-font-smoothing: antialiased; +} + +a { color: var(--primary); text-decoration: none; } +a:hover { color: var(--primary-strong); text-decoration: underline; } + +code { + font-family: "SFMono-Regular", Menlo, Consolas, "Roboto Mono", monospace; + font-size: 0.92em; + background: var(--primary-soft); + padding: 0.1em 0.35em; + border-radius: 4px; +} + +h1, h2, h3 { line-height: 1.2; letter-spacing: -0.01em; margin: 0; } + +main > section, .top, .foot { + max-width: 1120px; + margin: 0 auto; + padding: 0 24px; +} + +/* ---- header ---------------------------------------------------------------------------------- */ + +.top { + display: flex; + align-items: center; + gap: 24px; + padding-top: 18px; + padding-bottom: 18px; +} + +.brand { + display: inline-flex; + align-items: center; + gap: 10px; + color: var(--fg); + font-weight: 600; + font-size: 1.05em; +} +.brand:hover { text-decoration: none; } +.brand img { display: block; } + +.top__nav { display: flex; gap: 22px; margin-left: auto; font-weight: 500; } +.top__nav a { color: var(--fg-muted); } +.top__nav a:hover { color: var(--primary); text-decoration: none; } + +.theme-toggle { + display: inline-flex; + align-items: center; + justify-content: center; + width: 38px; height: 38px; + border: 1px solid var(--line); + border-radius: 50%; + background: var(--bg-raised); + color: var(--fg-muted); + cursor: pointer; +} +.theme-toggle:hover { color: var(--primary); border-color: var(--primary); } +.theme-toggle__moon { display: none; } +:root[data-theme="dark"] .theme-toggle__sun { display: none; } +:root[data-theme="dark"] .theme-toggle__moon { display: block; } +@media (prefers-color-scheme: dark) { + :root:not([data-theme="light"]) .theme-toggle__sun { display: none; } + :root:not([data-theme="light"]) .theme-toggle__moon { display: block; } +} + +/* ---- hero ------------------------------------------------------------------------------------ */ + +.hero { + position: relative; + display: grid; + grid-template-columns: minmax(0, 1.2fr) minmax(0, 1fr); + align-items: center; + gap: 32px; + padding-top: 56px; + padding-bottom: 64px; +} + +.hero__art { + grid-column: 2; + grid-row: 1; + width: 100%; + max-width: 460px; + justify-self: end; + aspect-ratio: 1; +} +.hero__art .ring { fill: none; stroke: var(--ring); stroke-width: 1; } +.hero__art .ring--strong { stroke-width: 1.6; } +.hero__art .spot { fill: var(--spot); } +.hero__art .beam { stroke: var(--accent); stroke-width: 2; } + +.hero__text { grid-column: 1; grid-row: 1; max-width: 600px; } + +.eyebrow { + margin: 0 0 14px; + font-size: 0.78em; + font-weight: 700; + letter-spacing: 0.1em; + text-transform: uppercase; + color: var(--primary); +} + +.hero h1 { + font-size: clamp(2.1rem, 4.6vw, 3.4rem); + font-weight: 700; + letter-spacing: -0.025em; + margin-bottom: 18px; +} + +.lede { font-size: 1.2em; color: var(--fg-muted); margin: 0 0 26px; } +.lede strong { color: var(--fg); font-weight: 600; } + +.hero__actions { display: flex; flex-wrap: wrap; gap: 12px; margin-bottom: 26px; } + +.btn { + display: inline-block; + padding: 11px 20px; + border-radius: 8px; + font-weight: 600; + border: 1px solid transparent; + transition: transform 0.12s, box-shadow 0.12s, background 0.12s; +} +.btn:hover { text-decoration: none; transform: translateY(-1px); } +.btn--primary { background: var(--primary); color: var(--on-primary); box-shadow: var(--shadow); } +.btn--primary:hover { background: var(--primary-strong); color: var(--on-primary); } +.btn--ghost { border-color: var(--line); color: var(--fg); background: var(--bg-raised); } +.btn--ghost:hover { border-color: var(--primary); color: var(--primary); } + +.hero__run { + display: flex; + flex-wrap: wrap; + align-items: baseline; + gap: 10px 14px; + margin: 0; + color: var(--fg-muted); + font-size: 0.95em; +} +.hero__run code { font-size: 1em; padding: 0.35em 0.7em; } + +/* ---- programs -------------------------------------------------------------------------------- */ + +.programs { padding-bottom: 56px; } + +.programs__grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); + gap: 20px; +} + +.program { + display: flex; + flex-direction: column; + padding: 24px 24px 20px; + border: 1px solid var(--line); + border-radius: 14px; + background: var(--bg-raised); + box-shadow: var(--shadow); +} +.program__kicker { + margin: 0 0 8px; + font-size: 0.74em; + font-weight: 700; + letter-spacing: 0.1em; + text-transform: uppercase; + color: var(--primary); +} +.program h2 { font-size: 1.45em; font-weight: 650; margin-bottom: 12px; } +.program > p:not([class]) { margin: 0 0 14px; color: var(--fg); flex: 1 1 auto; } +.program__meta { margin: 0 0 14px; font-size: 0.82em; color: var(--fg-muted); } +.program__links { + margin: 0; + padding-top: 14px; + border-top: 1px solid var(--line); + display: flex; + flex-wrap: wrap; + gap: 6px 18px; + font-weight: 500; +} +.program__links a:first-child { font-weight: 700; } + +/* ---- download -------------------------------------------------------------------------------- */ + +.download { + padding-top: 48px; + padding-bottom: 24px; + border-top: 1px solid var(--line); +} +.download__head { max-width: 640px; margin-bottom: 28px; } +.download__head h2 { font-size: 2em; font-weight: 700; margin-bottom: 10px; } +.download__head p { margin: 0; color: var(--fg-muted); font-size: 1.05em; } +.download__head [data-os-note]:not(:empty) { color: var(--fg); font-weight: 500; } + +.download__program { margin-bottom: 36px; } +.download__program h3 { font-size: 1.3em; font-weight: 650; margin-bottom: 8px; } +.download__program > p { margin: 0 0 16px; max-width: 720px; color: var(--fg-muted); } +.download__program--text > p { color: var(--fg); } + +.jfj-dl__release { margin: 0 0 14px; font-size: 0.95em; color: var(--fg-muted); } +.jfj-dl__release strong { color: var(--fg); } + +.jfj-dl__grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); + gap: 16px; +} + +.jfj-dl__card { + position: relative; + padding: 18px 18px 8px; + border: 1px solid var(--line); + border-radius: 12px; + background: var(--bg-raised); +} +.jfj-dl__card.is-yours { + border-color: var(--accent); + box-shadow: 0 0 0 3px var(--accent-soft), var(--shadow); +} +.jfj-dl__card.is-yours::after { + content: "Your system"; + position: absolute; + top: -11px; right: 14px; + padding: 2px 10px; + border-radius: 999px; + background: var(--accent); + color: #1b1d2a; + font-size: 0.72em; + font-weight: 700; + letter-spacing: 0.04em; +} + +.jfj-dl__platform { font-size: 1.1em; font-weight: 650; margin-bottom: 12px; } + +a.jfj-dl__btn { + display: block; + margin-bottom: 10px; + padding: 10px 14px; + border: 1px solid var(--primary); + border-radius: 8px; + background: var(--primary); + color: var(--on-primary); + font-weight: 600; + line-height: 1.3; + transition: background 0.12s, box-shadow 0.12s; +} +a.jfj-dl__btn:hover { text-decoration: none; background: var(--primary-strong); box-shadow: var(--shadow); color: var(--on-primary); } +a.jfj-dl__btn--secondary { background: transparent; color: var(--primary); } +a.jfj-dl__btn--secondary:hover { background: var(--primary-soft); color: var(--primary-strong); } +a.jfj-dl__btn--missing, a.jfj-dl__btn--missing:hover { + background: transparent; + border-style: dashed; + border-color: var(--line); + color: var(--fg-muted); + box-shadow: none; + cursor: default; +} +.jfj-dl__file { + display: block; + margin-top: 2px; + font-family: "SFMono-Regular", Menlo, Consolas, "Roboto Mono", monospace; + font-size: 0.74em; + font-weight: 400; + opacity: 0.85; + word-break: break-all; +} +.jfj-dl__req { margin: 6px 0 8px; font-size: 0.82em; line-height: 1.45; color: var(--fg-muted); } +.jfj-dl__note { margin: 10px 0 0; font-size: 0.82em; color: var(--fg-muted); } +.jfj-dl__note:empty { display: none; } + +/* ---- licence, footer ------------------------------------------------------------------------- */ + +.licence { + padding-top: 40px; + padding-bottom: 48px; + border-top: 1px solid var(--line); +} +.licence h2 { font-size: 1.5em; font-weight: 650; margin-bottom: 12px; } +.licence p { margin: 0; max-width: 760px; color: var(--fg-muted); } + +.foot { + padding-top: 24px; + padding-bottom: 40px; + border-top: 1px solid var(--line); + font-size: 0.88em; + color: var(--fg-muted); +} +.foot p { margin: 0 0 6px; } + +/* ---- small screens --------------------------------------------------------------------------- */ + +@media (max-width: 760px) { + body { font-size: 15px; } + .top { gap: 14px; flex-wrap: wrap; } + .top__nav { gap: 14px; margin-left: 0; order: 3; width: 100%; font-size: 0.95em; } + .theme-toggle { margin-left: auto; } + .hero { grid-template-columns: 1fr; padding-top: 28px; padding-bottom: 40px; } + .hero__art { grid-column: 1; grid-row: 1; max-width: 220px; justify-self: center; opacity: 0.9; } + .hero__text { grid-row: 2; } + .programs__grid { grid-template-columns: 1fr; } +} diff --git a/docs/landing/landing.js b/docs/landing/landing.js new file mode 100644 index 000000000..c4fdd47f0 --- /dev/null +++ b/docs/landing/landing.js @@ -0,0 +1,60 @@ +// The front page: theme toggle, the hero's diffraction-pattern drawing, and the download section's +// "your system" highlight. The download buttons themselves are repointed at the newest release by +// _static/jfjoch_downloads.js, shared with the Sphinx download pages. +(function () { + // ---- theme: follows the system until the toggle is used, then remembered ----------------- + var root = document.documentElement; + var toggle = document.querySelector(".theme-toggle"); + function isDark() { + var set = root.getAttribute("data-theme"); + if (set) return set === "dark"; + return window.matchMedia && window.matchMedia("(prefers-color-scheme: dark)").matches; + } + if (toggle) toggle.addEventListener("click", function () { + var next = isDark() ? "light" : "dark"; + root.setAttribute("data-theme", next); + try { localStorage.setItem("jfj-theme", next); } catch (e) {} + }); + + // ---- hero art: resolution rings and a scatter of spots, the same every time -------------- + var art = document.querySelector(".hero__art"); + if (art) { + var seed = 7; + function rand() { seed = (seed * 16807) % 2147483647; return seed / 2147483647; } + var ns = "http://www.w3.org/2000/svg", out = []; + function el(name, attrs) { + var e = document.createElementNS(ns, name); + for (var k in attrs) e.setAttribute(k, attrs[k]); + return e; + } + var cx = 300, cy = 300; + [60, 110, 160, 210, 260].forEach(function (r, i) { + art.appendChild(el("circle", { cx: cx, cy: cy, r: r, "class": "ring" + (i === 2 ? " ring--strong" : "") })); + }); + // beam centre + art.appendChild(el("path", { d: "M" + (cx - 9) + " " + cy + "h18M" + cx + " " + (cy - 9) + "v18", "class": "beam" })); + // spots: denser near the middle, fainter and smaller towards the edge, a few bright ones + for (var i = 0; i < 230; i++) { + var a = rand() * Math.PI * 2; + var r = Math.sqrt(rand()) * 285; + if (r < 25) continue; + var x = cx + r * Math.cos(a), y = cy + r * Math.sin(a); + var fade = 1 - r / 320; + var size = 1.2 + fade * 2.2 * rand() + (rand() > 0.94 ? 2.5 : 0); + var c = el("circle", { cx: x.toFixed(1), cy: y.toFixed(1), r: size.toFixed(1), "class": "spot" }); + c.style.opacity = (0.25 + 0.75 * fade * rand()).toFixed(2); + art.appendChild(c); + } + } + + // ---- "your system": highlight the platform the browser runs on ---------------------------- + var ua = navigator.userAgent || "", platform = navigator.platform || ""; + var os = /Windows/.test(ua) ? "windows" : /Mac/.test(platform) || /Macintosh/.test(ua) ? "macos" : + /Linux|X11/.test(platform + ua) && !/Android/.test(ua) ? "linux" : null; + if (os) { + document.querySelectorAll('.jfj-dl__card[data-platform="' + os + '"]').forEach(function (c) { c.classList.add("is-yours"); }); + var note = document.querySelector("[data-os-note]"); + var names = { windows: "Windows", macos: "macOS", linux: "Linux" }; + if (note) note.textContent = "The " + names[os] + " builds are marked for you."; + } +})(); diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 0ab6a4cdc..3b6287ac6 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -254,7 +254,7 @@ function App() { )}, { id: 'documentation', label: 'Documentation', icon: , render: () => ( - + )}, { id: 'changelog', label: 'Changelog', icon: , render: () => (