cat / goq
clone

turns a linux box into a private steam streaming appliance: plug in, tap your key, play from another computer

feat: add fail-closed Portal platform matrix

Dami committed dcbd80f
commit
dcbd80f0182e07b951bcdf4ce7fdd733b367f1cd
parent
8eb370c851635cd99a3d4fef4586160c3615572e
author
Dami <dami.etoile@gmail.com>
4 files changed, +151 -13
Mscripts/verify-website.sh3+
Mwebsite/dither.js70++++++++++++++++++++++++++++++
Mwebsite/index.html30++++++++++++-
Mwebsite/styles.css61+++++++++++++++++++++-----
M scripts/verify-website.sh +3 -0
M website/dither.js +70 -0
@@ -368,6 +368,76 @@
368368 });369369 });370370 371+ /* ── Portal platform matrix. One map is the single source of truth:372+ flip a null to an entry when an artifact passes the signing gate.373+ Combos without an artifact grey out the download button — the374+ matrix is shown, but nothing unavailable is advertised as a link. */375+ var PORTAL_BUILDS = {376+ "macos-arm": null,377+ "macos-x86": null,378+ "linux-arm": null,379+ "linux-x86": null,380+ "windows-arm": null,381+ "windows-x86": null,382+ };383+ var ARCH_NAMES = { arm: "arm64", x86: "x86_64" };384+385+ function pickedPortalValue(name) {386+ var el = document.querySelector('input[name="' + name + '"]:checked');387+ return el ? el.value : null;388+ }389+390+ function updatePortalDownload() {391+ var button = document.getElementById("portal-download");392+ var note = document.getElementById("portal-release-status");393+ var os = pickedPortalValue("portal-os");394+ var arch = pickedPortalValue("portal-arch");395+ if (!button || !note || !os || !arch) return;396+ var build = PORTAL_BUILDS[os + "-" + arch];397+ if (build) {398+ button.classList.remove("disabled");399+ button.removeAttribute("aria-disabled");400+ button.setAttribute("href", build.href);401+ button.textContent = build.label;402+ note.textContent = build.note;403+ } else {404+ button.classList.add("disabled");405+ button.setAttribute("aria-disabled", "true");406+ button.removeAttribute("href"); // href-less anchor: unfocusable, unclickable407+ button.textContent = "no " + os + " " + ARCH_NAMES[arch] + " build yet";408+ note.textContent = "portal for " + os + "/" + ARCH_NAMES[arch] +409+ " is not released yet · macos arm64 comes first";410+ }411+ }412+413+ document.querySelectorAll('input[name="portal-os"], input[name="portal-arch"]').forEach(function (radio) {414+ radio.addEventListener("change", updatePortalDownload);415+ });416+417+ // Best-effort platform preselect: a visitor lands on their own OS/arch418+ // and immediately sees whether a build exists. Falls back to macos+arm.419+ (function preselectPortalPlatform() {420+ var uad = navigator.userAgentData;421+ var platform = ((uad && uad.platform) || navigator.platform || "").toLowerCase();422+ var ua = (navigator.userAgent || "").toLowerCase();423+ var os = null;424+ if (platform.indexOf("mac") >= 0) os = "macos";425+ else if (platform.indexOf("win") >= 0) os = "windows";426+ else if (platform.indexOf("linux") >= 0 || ua.indexOf("linux") >= 0) os = "linux";427+ // Plain UA can't distinguish Apple Silicon from Intel; default macs to428+ // arm (the shipping target). Elsewhere, x86 unless the UA says ARM.429+ var arch = os === "macos"430+ ? "arm"431+ : /aarch64|arm64|armv8/.test(ua) ? "arm" : "x86";432+ if (os) {433+ var osRadio = document.querySelector('input[name="portal-os"][value="' + os + '"]');434+ var archRadio = document.querySelector('input[name="portal-arch"][value="' + arch + '"]');435+ if (osRadio) osRadio.checked = true;436+ if (archRadio) archRadio.checked = true;437+ }438+ updatePortalDownload();439+ })();440+371441 /* ── Copy-to-clipboard for the install command(s). ── */372442 document.querySelectorAll(".install").forEach(function (box) {373443 var btn = box.querySelector(".install-copy");
M website/index.html +28 -2
@@ -52,8 +52,34 @@
5252 </label>5353 </div>5454 <div class="release-action" data-release-target="portal">55- <a class="portal-download" href="https://github.com/FelineStateMachine/goq/releases" rel="noopener">Portal downloads</a>56- <p class="release-note">signed + notarized macOS builds will appear here · public alpha pending</p>55+ <div class="portal-picker">56+ <div class="seg" role="radiogroup" aria-label="Operating system">57+ <label class="seg-opt">58+ <input type="radio" name="portal-os" value="macos" checked />59+ <span>macos</span>60+ </label>61+ <label class="seg-opt">62+ <input type="radio" name="portal-os" value="linux" />63+ <span>linux</span>64+ </label>65+ <label class="seg-opt">66+ <input type="radio" name="portal-os" value="windows" />67+ <span>windows</span>68+ </label>69+ </div>70+ <div class="seg" role="radiogroup" aria-label="Architecture">71+ <label class="seg-opt">72+ <input type="radio" name="portal-arch" value="arm" checked />73+ <span>arm</span>74+ </label>75+ <label class="seg-opt">76+ <input type="radio" name="portal-arch" value="x86" />77+ <span>x86</span>78+ </label>79+ </div>80+ </div>81+ <a class="portal-download disabled" id="portal-download" aria-disabled="true">no macos arm64 build yet</a>82+ <p class="release-note"><span id="portal-release-status">portal for macos/arm64 is not released yet · macos arm64 comes first</span> · <a class="release-history" href="https://github.com/FelineStateMachine/goq/releases" rel="noopener">release history</a></p>5783 </div>5884 <div class="release-action" data-release-target="sigil" hidden>5985 <div class="install" data-copy="curl --proto '=https' --tlsv1.2 -fsS https://goq.sh/install-sigil -o /tmp/install-sigil && bash /tmp/install-sigil">
M website/styles.css +50 -11
@@ -234,28 +234,67 @@ a { color: inherit; }
234234 .portal-download {235235 display: inline-flex;236236 align-items: center;237- justify-content: center;238- min-height: 44px;239- padding: 10px 22px;240- border: 1px solid var(--ink);241- background: var(--ink);242- color: var(--paper);237+ gap: 10px;238+ min-height: 40px;239+ padding: 9px 18px;240+ border: 1px solid var(--hair-strong);241+ background: color-mix(in srgb, var(--paper-2) 82%, transparent);242+ color: var(--ink);243243 font-family: var(--mono);244- font-size: 12px;244+ font-size: 12.5px;245245 font-weight: 700;246- letter-spacing: 0.06em;246+ letter-spacing: 0.04em;247247 text-decoration: none;248- text-transform: uppercase;249- transition: background 0.12s, color 0.12s;248+ text-transform: lowercase;249+ box-shadow: 4px 4px 0 var(--hair);250+ transition: background 0.12s, color 0.12s, box-shadow 0.12s, transform 0.12s;251+}252+/* pixel marker, same language as the hero-note square */253+.portal-download::before {254+ content: "";255+ width: 8px;256+ height: 8px;257+ flex-shrink: 0;258+ background: var(--terracotta);259+}260+.portal-download:hover { background: var(--ink); color: var(--paper); }261+.portal-download:hover::before { background: var(--ochre); }262+.portal-download:active {263+ transform: translate(2px, 2px);264+ box-shadow: 1px 1px 0 var(--hair);250265 }251-.portal-download:hover { background: var(--terracotta); }252266 .portal-download:focus-visible { outline: 2px solid var(--moss); outline-offset: 3px; }267+.portal-picker {268+ display: flex;269+ flex-wrap: wrap;270+ justify-content: center;271+ gap: 8px;272+}273+/* combo without an artifact: dashed slot, no shadow — a pending space,274+ not a pressable thing */275+.portal-download.disabled {276+ background: transparent;277+ color: var(--ink-muted);278+ border: 1px dashed var(--hair-strong);279+ box-shadow: none;280+ cursor: not-allowed;281+}282+.portal-download.disabled::before { background: var(--hair-strong); }283+.portal-download.disabled:hover {284+ background: transparent;285+ color: var(--ink-muted);286+}287+@media (prefers-reduced-motion: reduce) {288+ .portal-download:active { transform: none; }289+}253290 .release-note {254291 color: var(--ink-muted);255292 font-family: var(--mono);256293 font-size: 10.5px;257294 line-height: 1.45;258295 }296+.release-history { text-decoration: underline; text-underline-offset: 2px; }297+.release-history:hover { color: var(--ink); }259298 /* platform segmented radio */260299 .seg {261300 display: inline-flex;