diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml new file mode 100644 index 0000000..c24b25b --- /dev/null +++ b/.github/workflows/pages.yml @@ -0,0 +1,44 @@ +name: pages + +on: + push: + branches: [main] + paths: + - "site/**" + - ".github/workflows/pages.yml" + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: pages + cancel-in-progress: true + +jobs: + build: + runs-on: ubuntu-24.04 + steps: + - name: Checkout + uses: actions/checkout@v6 + - name: Configure Pages + uses: actions/configure-pages@v5 + - name: Upload static site + uses: actions/upload-pages-artifact@v4 + with: + path: site + include-hidden-files: true + + deploy: + needs: build + runs-on: ubuntu-24.04 + permissions: + pages: write + id-token: write + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + steps: + - name: Deploy + id: deployment + uses: actions/deploy-pages@v4 diff --git a/site/.nojekyll b/site/.nojekyll new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/site/.nojekyll @@ -0,0 +1 @@ + diff --git a/site/app.js b/site/app.js new file mode 100644 index 0000000..a2225b5 --- /dev/null +++ b/site/app.js @@ -0,0 +1,133 @@ +document.documentElement.classList.add("reveal-ready"); + +const requestedTheme = new URLSearchParams(window.location.search).get("theme"); +if (requestedTheme === "light" || requestedTheme === "dark") { + document.documentElement.dataset.theme = requestedTheme; +} + +const menuButton = document.querySelector(".menu-button"); +const siteNav = document.querySelector(".site-nav"); + +if (menuButton && siteNav) { + menuButton.addEventListener("click", () => { + const isOpen = menuButton.getAttribute("aria-expanded") === "true"; + menuButton.setAttribute("aria-expanded", String(!isOpen)); + siteNav.classList.toggle("is-open", !isOpen); + }); + + siteNav.addEventListener("click", (event) => { + if (event.target instanceof HTMLAnchorElement) { + menuButton.setAttribute("aria-expanded", "false"); + siteNav.classList.remove("is-open"); + } + }); +} + +const tabs = [...document.querySelectorAll('[role="tab"]')]; + +function activateTab(nextTab) { + for (const tab of tabs) { + const isActive = tab === nextTab; + tab.setAttribute("aria-selected", String(isActive)); + tab.tabIndex = isActive ? 0 : -1; + + const panelId = tab.getAttribute("aria-controls"); + const panel = panelId ? document.getElementById(panelId) : null; + if (panel) { + panel.hidden = !isActive; + } + } +} + +for (const tab of tabs) { + tab.addEventListener("click", () => activateTab(tab)); + tab.addEventListener("keydown", (event) => { + if (!["ArrowLeft", "ArrowRight", "Home", "End"].includes(event.key)) { + return; + } + + event.preventDefault(); + const index = tabs.indexOf(tab); + let nextIndex = index; + + if (event.key === "ArrowLeft") { + nextIndex = (index - 1 + tabs.length) % tabs.length; + } else if (event.key === "ArrowRight") { + nextIndex = (index + 1) % tabs.length; + } else if (event.key === "Home") { + nextIndex = 0; + } else if (event.key === "End") { + nextIndex = tabs.length - 1; + } + + const nextTab = tabs[nextIndex]; + activateTab(nextTab); + nextTab.focus(); + }); +} + +const copyButton = document.querySelector(".copy-button"); +const copyStatus = document.querySelector(".copy-status"); +let copyStatusTimer; + +function showCopyStatus(message) { + if (!copyStatus) { + return; + } + + copyStatus.textContent = message; + copyStatus.classList.add("is-visible"); + window.clearTimeout(copyStatusTimer); + copyStatusTimer = window.setTimeout(() => { + copyStatus.classList.remove("is-visible"); + }, 2400); +} + +if (copyButton) { + copyButton.addEventListener("click", async () => { + const targetId = copyButton.getAttribute("data-copy-target"); + const target = targetId ? document.getElementById(targetId) : null; + const text = target?.textContent?.trim(); + + if (!text) { + showCopyStatus("Command unavailable."); + return; + } + + try { + await navigator.clipboard.writeText(text); + copyButton.textContent = "Copied"; + showCopyStatus("Helm command copied."); + window.setTimeout(() => { + copyButton.textContent = "Copy"; + }, 1800); + } catch { + showCopyStatus("Copy failed. Select the command manually."); + } + }); +} + +const revealItems = document.querySelectorAll("[data-reveal]"); +const reduceMotion = window.matchMedia("(prefers-reduced-motion: reduce)").matches; + +if (reduceMotion || !("IntersectionObserver" in window)) { + for (const item of revealItems) { + item.classList.add("is-visible"); + } +} else { + const observer = new IntersectionObserver( + (entries) => { + for (const entry of entries) { + if (entry.isIntersecting) { + entry.target.classList.add("is-visible"); + observer.unobserve(entry.target); + } + } + }, + { rootMargin: "0px 0px -10% 0px", threshold: 0.12 }, + ); + + for (const item of revealItems) { + observer.observe(item); + } +} diff --git a/site/assets/IBMPlexSans-Bold-Latin1.woff2 b/site/assets/IBMPlexSans-Bold-Latin1.woff2 new file mode 100644 index 0000000..6e9387b Binary files /dev/null and b/site/assets/IBMPlexSans-Bold-Latin1.woff2 differ diff --git a/site/assets/IBMPlexSans-LICENSE.txt b/site/assets/IBMPlexSans-LICENSE.txt new file mode 100644 index 0000000..670c6c0 --- /dev/null +++ b/site/assets/IBMPlexSans-LICENSE.txt @@ -0,0 +1,93 @@ +Copyright © 2017 IBM Corp. with Reserved Font Name "Plex" + +This Font Software is licensed under the SIL Open Font License, Version 1.1. + +This license is copied below, and is also available with a FAQ at: http://scripts.sil.org/OFL + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/site/assets/IBMPlexSans-Regular-Latin1.woff2 b/site/assets/IBMPlexSans-Regular-Latin1.woff2 new file mode 100644 index 0000000..a9c6407 Binary files /dev/null and b/site/assets/IBMPlexSans-Regular-Latin1.woff2 differ diff --git a/site/assets/IBMPlexSans-SemiBold-Latin1.woff2 b/site/assets/IBMPlexSans-SemiBold-Latin1.woff2 new file mode 100644 index 0000000..bcdeaa5 Binary files /dev/null and b/site/assets/IBMPlexSans-SemiBold-Latin1.woff2 differ diff --git a/site/assets/rustqueue-brokers-1100.webp b/site/assets/rustqueue-brokers-1100.webp new file mode 100644 index 0000000..6f14bce Binary files /dev/null and b/site/assets/rustqueue-brokers-1100.webp differ diff --git a/site/assets/rustqueue-brokers-720.webp b/site/assets/rustqueue-brokers-720.webp new file mode 100644 index 0000000..ccb5d92 Binary files /dev/null and b/site/assets/rustqueue-brokers-720.webp differ diff --git a/site/assets/rustqueue-brokers.webp b/site/assets/rustqueue-brokers.webp new file mode 100644 index 0000000..6122ab6 Binary files /dev/null and b/site/assets/rustqueue-brokers.webp differ diff --git a/site/assets/rustqueue-storage-640.webp b/site/assets/rustqueue-storage-640.webp new file mode 100644 index 0000000..954e486 Binary files /dev/null and b/site/assets/rustqueue-storage-640.webp differ diff --git a/site/assets/rustqueue-storage.webp b/site/assets/rustqueue-storage.webp new file mode 100644 index 0000000..e30e48c Binary files /dev/null and b/site/assets/rustqueue-storage.webp differ diff --git a/site/favicon.svg b/site/favicon.svg new file mode 100644 index 0000000..0d1e363 --- /dev/null +++ b/site/favicon.svg @@ -0,0 +1,12 @@ + + + RQ + diff --git a/site/index.html b/site/index.html new file mode 100644 index 0000000..e6940a5 --- /dev/null +++ b/site/index.html @@ -0,0 +1,441 @@ + + + + + + + + + + + + + + + + + + + + RustQueue | Durable messaging for Kubernetes + + + + + + +
+
+
+
+

NSQ-compatible messaging for Kubernetes

+

Durable queues.Share nothing.

+

+ NSQ-compatible messaging with one durable PVC per Kubernetes broker and no central + coordinator. +

+ +
+ +
+ Independent broker nodes, each connected to its own storage disk +
+ Independent brokers. Independent storage. One explicit failure boundary. +
+
+
+
+ +
+
+
+
ACK boundary
+
Local fsync by default
+
+
+
Delivery
+
At least once
+
+
+
Topology
+
Share-nothing brokers
+
+
+
Protocol
+
NSQ V2 core
+
+
+
+ +
+
+

Every broker owns its queue.

+

+ Kubernetes handles placement and discovery. RustQueue keeps message ownership local, + visible, and operationally direct. +

+
+ +
+
+ Publish + NSQ producer + Existing client +
+
+ + Proxy or Gateway +
+
+
+ Broker A + Topic owner + RWO PVC +
+
+ Broker B + Topic owner + RWO PVC +
+
+ Broker C + Topic owner + RWO PVC +
+
+
+ +
+

+ Consumers discover every broker that owns a Topic, then connect directly over the NSQ + protocol. +

+ + Read the Kubernetes runbook + +
+
+ +
+
+
+ A broker module persisting message capsules to its local storage platter +
+ +
+

Choose the acknowledgement boundary

+

Durability is a setting, not a footnote.

+

+ The default waits for local persistence. Two explicit relaxed modes are available + when the crash-loss window is an accepted tradeoff. +

+ +
+ + + +
+ +
+
+
+
+
Response
+
After local segment fsync
+
+
+
Visibility
+
Only committed data is delivered
+
+
+

The production default and strongest local acknowledgement contract.

+
+ + +
+
+
+
+ +
+
+

Keep the clients. Change the core.

+

+ Use official Go and Python NSQ clients across publish, consume, lookup, TLS, AUTH, and + compression paths. +

+
+ +
+ IDENTIFY + AUTH + SUB + PUB + MPUB + DPUB + RDY + FIN + REQ + TOUCH + NOP + CLS +
+ +
+
+ TLS and mTLS +

Encrypted client paths with optional external AUTH.

+
+
+ Snappy and Deflate +

Protocol compression without changing the client contract.

+
+
+ Lookup and fan-out +

Discovery returns the real broker owners for each Topic.

+
+
+ Kodo profile +

A default-off gateway and discovery compatibility boundary.

+
+
+
+ +
+
+

Performance only matters when the durability boundary is named.

+ + Review the NSQ comparison contract + +
+ +
+
+ 01 +

Strict and relaxed profiles are reported separately.

+
+
+ 02 +

Consumer runs require complete unique delivery.

+
+
+ 03 +

Unexpected duplicates invalidate a benchmark result.

+
+
+
+ +
+
+
+

Start on Kubernetes

+

One Helm command.

+

+ Point the chart at your image and an SSD-backed RWO StorageClass. RustQueue handles + the rest of the topology. +

+
+ +
+
+ shell + +
+
helm upgrade --install rustqueue deploy/helm/rustqueue \
+  --namespace rustqueue --create-namespace \
+  --set queue.image=registry.example/rustqueue:0.8.3 \
+  --set queue.storageClassName=ssd-rwo
+
+
+
+
+ + + +
+ + diff --git a/site/robots.txt b/site/robots.txt new file mode 100644 index 0000000..eb6a569 --- /dev/null +++ b/site/robots.txt @@ -0,0 +1,4 @@ +User-agent: * +Allow: / + +Sitemap: https://samuelsupe.github.io/rustqueue/sitemap.xml diff --git a/site/sitemap.xml b/site/sitemap.xml new file mode 100644 index 0000000..fdc2ccb --- /dev/null +++ b/site/sitemap.xml @@ -0,0 +1,6 @@ + + + + https://samuelsupe.github.io/rustqueue/ + + diff --git a/site/styles.css b/site/styles.css new file mode 100644 index 0000000..9e7f3ef --- /dev/null +++ b/site/styles.css @@ -0,0 +1,1307 @@ +@font-face { + font-family: "IBM Plex Sans"; + src: url("./assets/IBMPlexSans-Regular-Latin1.woff2") format("woff2"); + font-style: normal; + font-weight: 400; + font-display: swap; +} + +@font-face { + font-family: "IBM Plex Sans"; + src: url("./assets/IBMPlexSans-SemiBold-Latin1.woff2") format("woff2"); + font-style: normal; + font-weight: 600; + font-display: swap; +} + +@font-face { + font-family: "IBM Plex Sans"; + src: url("./assets/IBMPlexSans-Bold-Latin1.woff2") format("woff2"); + font-style: normal; + font-weight: 700; + font-display: swap; +} + +:root { + color-scheme: light dark; + --page: #f1f4ef; + --surface: #e7ebe5; + --surface-strong: #dce2da; + --ink: #151a16; + --muted: #59635b; + --hairline: #c8d0c7; + --accent: #2f6f47; + --accent-strong: #245c39; + --accent-ink: #f4f8f2; + --code: #161a17; + --code-ink: #eef4eb; + --header: rgb(241 244 239 / 0.9); + --shadow: rgb(28 52 34 / 0.14); + --focus: #2f6f47; + --radius: 16px; + --radius-control: 10px; + --radius-nested: 8px; + --page-pad: clamp(1.25rem, 4vw, 4.5rem); + --content: 1420px; +} + +@media (prefers-color-scheme: dark) { + :root { + --page: #101311; + --surface: #171b18; + --surface-strong: #1e2420; + --ink: #edf2eb; + --muted: #aab5ac; + --hairline: #303a32; + --accent: #77c38e; + --accent-strong: #93daa7; + --accent-ink: #0f1711; + --code: #0b0e0c; + --code-ink: #e7efe5; + --header: rgb(16 19 17 / 0.88); + --shadow: rgb(0 0 0 / 0.3); + --focus: #93daa7; + } +} + +:root[data-theme="light"] { + color-scheme: light; + --page: #f1f4ef; + --surface: #e7ebe5; + --surface-strong: #dce2da; + --ink: #151a16; + --muted: #59635b; + --hairline: #c8d0c7; + --accent: #2f6f47; + --accent-strong: #245c39; + --accent-ink: #f4f8f2; + --code: #161a17; + --code-ink: #eef4eb; + --header: rgb(241 244 239 / 0.9); + --shadow: rgb(28 52 34 / 0.14); + --focus: #2f6f47; +} + +:root[data-theme="dark"] { + color-scheme: dark; + --page: #101311; + --surface: #171b18; + --surface-strong: #1e2420; + --ink: #edf2eb; + --muted: #aab5ac; + --hairline: #303a32; + --accent: #77c38e; + --accent-strong: #93daa7; + --accent-ink: #0f1711; + --code: #0b0e0c; + --code-ink: #e7efe5; + --header: rgb(16 19 17 / 0.88); + --shadow: rgb(0 0 0 / 0.3); + --focus: #93daa7; +} + +* { + box-sizing: border-box; +} + +html { + scroll-behavior: smooth; + scroll-padding-top: 5.5rem; +} + +body { + margin: 0; + background: var(--page); + color: var(--ink); + font-family: "IBM Plex Sans", "Helvetica Neue", "Segoe UI", sans-serif; + font-size: 1rem; + line-height: 1.6; + text-rendering: optimizeLegibility; +} + +img { + display: block; + height: auto; + max-width: 100%; +} + +a { + color: inherit; +} + +button, +a { + -webkit-tap-highlight-color: transparent; +} + +button { + font: inherit; +} + +:focus-visible { + outline: 3px solid var(--focus); + outline-offset: 4px; +} + +::selection { + background: var(--accent); + color: var(--accent-ink); +} + +.sr-only { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + white-space: nowrap; + border: 0; +} + +.skip-link { + position: fixed; + top: 1rem; + left: 1rem; + z-index: 20; + padding: 0.7rem 1rem; + border-radius: var(--radius-control); + background: var(--accent); + color: var(--accent-ink); + font-weight: 700; + text-decoration: none; + transform: translateY(-180%); +} + +.skip-link:focus { + transform: translateY(0); +} + +.site-header { + position: sticky; + top: 0; + z-index: 10; + border-bottom: 1px solid var(--hairline); + background: var(--header); + backdrop-filter: blur(18px); + -webkit-backdrop-filter: blur(18px); +} + +.nav-shell { + display: flex; + width: min(100%, var(--content)); + min-height: 72px; + padding: 0 var(--page-pad); + margin: 0 auto; + align-items: center; + justify-content: space-between; + gap: 2rem; +} + +.brand { + display: inline-flex; + align-items: center; + gap: 0.75rem; + color: var(--ink); + font-size: 1.02rem; + font-weight: 760; + letter-spacing: -0.02em; + text-decoration: none; +} + +.brand-mark { + display: grid; + width: 2.05rem; + height: 2.05rem; + border-radius: var(--radius-control); + background: var(--ink); + color: var(--page); + place-items: center; + font-family: ui-monospace, "SFMono-Regular", Consolas, monospace; + font-size: 0.71rem; + font-weight: 780; + letter-spacing: -0.08em; +} + +.site-nav { + display: flex; + align-items: center; + gap: clamp(1.2rem, 2.5vw, 2.2rem); + white-space: nowrap; +} + +.site-nav a, +.site-footer nav a { + color: var(--muted); + font-size: 0.9rem; + font-weight: 640; + text-decoration: none; + transition: + color 180ms ease, + transform 180ms ease; +} + +.site-nav a:hover, +.site-footer nav a:hover { + color: var(--ink); +} + +.site-nav a:active, +.site-footer nav a:active { + transform: translateY(1px); +} + +.site-nav .nav-action { + padding: 0.55rem 0.85rem; + border: 1px solid var(--hairline); + border-radius: var(--radius-control); + color: var(--ink); +} + +.menu-button { + display: none; + width: 2.7rem; + height: 2.7rem; + padding: 0; + border: 1px solid var(--hairline); + border-radius: var(--radius-control); + background: transparent; + color: var(--ink); + cursor: pointer; + place-items: center; +} + +.menu-line { + position: absolute; + width: 1rem; + height: 1px; + background: currentColor; + transition: transform 180ms ease; +} + +.menu-line:first-child { + transform: translateY(-3px); +} + +.menu-line:nth-child(2) { + transform: translateY(3px); +} + +.menu-button[aria-expanded="true"] .menu-line:first-child { + transform: rotate(45deg); +} + +.menu-button[aria-expanded="true"] .menu-line:nth-child(2) { + transform: rotate(-45deg); +} + +.hero { + display: grid; + min-height: calc(100dvh - 73px); + align-items: center; + overflow: hidden; +} + +.hero-grid { + display: grid; + width: min(100%, var(--content)); + padding: clamp(2.5rem, 6vh, 5.5rem) var(--page-pad); + margin: 0 auto; + align-items: center; + grid-template-columns: minmax(0, 0.86fr) minmax(31rem, 1.24fr); + gap: clamp(2.4rem, 6vw, 7.5rem); +} + +.hero-copy { + position: relative; + z-index: 1; + padding-bottom: 1rem; + animation: hero-enter 700ms cubic-bezier(0.16, 1, 0.3, 1) both; +} + +.eyebrow { + margin: 0 0 1.2rem; + color: var(--accent); + font-family: ui-monospace, "SFMono-Regular", Consolas, monospace; + font-size: 0.74rem; + font-weight: 750; + letter-spacing: 0.08em; + text-transform: uppercase; +} + +h1, +h2, +p { + margin-top: 0; +} + +h1 { + max-width: 11ch; + margin-bottom: 1.6rem; + font-size: clamp(3.2rem, 4.6vw, 5.6rem); + font-weight: 760; + letter-spacing: -0.065em; + line-height: 0.96; +} + +h1 span { + display: block; + white-space: nowrap; +} + +h2 { + max-width: 14ch; + margin-bottom: 1.35rem; + font-size: clamp(2.4rem, 4.7vw, 5.1rem); + font-weight: 740; + letter-spacing: -0.06em; + line-height: 0.98; +} + +.hero-summary { + max-width: 34rem; + margin-bottom: 2.15rem; + color: var(--muted); + font-size: clamp(1.05rem, 1.45vw, 1.28rem); + line-height: 1.5; +} + +.hero-actions { + display: flex; + align-items: center; + gap: 0.75rem; + flex-wrap: wrap; +} + +.button { + display: inline-flex; + min-height: 3rem; + padding: 0.7rem 1rem; + border: 1px solid transparent; + border-radius: var(--radius-control); + align-items: center; + justify-content: center; + font-size: 0.9rem; + font-weight: 740; + text-decoration: none; + white-space: nowrap; + transition: + transform 180ms ease, + background 180ms ease, + border-color 180ms ease; +} + +.button:hover { + transform: translateY(-2px); +} + +.button:active { + transform: translateY(1px) scale(0.99); +} + +.button-primary { + background: var(--accent); + color: var(--accent-ink); +} + +.button-primary:hover { + background: var(--accent-strong); +} + +.button-secondary { + border-color: var(--hairline); + background: var(--surface); + color: var(--ink); +} + +.button-secondary:hover { + border-color: var(--muted); +} + +.hero-visual { + position: relative; + min-width: 0; + margin: 0; + animation: hero-image-enter 900ms 120ms cubic-bezier(0.16, 1, 0.3, 1) both; +} + +.hero-visual::before { + position: absolute; + inset: 10% 7% -3% 12%; + z-index: -1; + border-radius: var(--radius); + background: var(--accent); + content: ""; + opacity: 0.1; + filter: blur(42px); +} + +.hero-visual img { + width: 100%; + min-height: 28rem; + border-radius: var(--radius); + box-shadow: 0 2rem 5rem var(--shadow); + object-fit: cover; + object-position: 64% center; +} + +.hero-visual figcaption { + max-width: 32rem; + margin: 1rem 0 0 auto; + color: var(--muted); + font-family: ui-monospace, "SFMono-Regular", Consolas, monospace; + font-size: 0.72rem; + line-height: 1.5; +} + +.contract-strip { + border-top: 1px solid var(--hairline); + border-bottom: 1px solid var(--hairline); +} + +.contract-strip dl { + display: grid; + width: min(100%, var(--content)); + padding: 0 var(--page-pad); + margin: 0 auto; + grid-template-columns: repeat(4, minmax(0, 1fr)); +} + +.contract-strip dl > div { + padding: 1.5rem clamp(1rem, 2.4vw, 2rem); + border-left: 1px solid var(--hairline); +} + +.contract-strip dl > div:first-child { + padding-left: 0; + border-left: 0; +} + +.contract-strip dt, +.mode-panel dt { + margin-bottom: 0.2rem; + color: var(--muted); + font-family: ui-monospace, "SFMono-Regular", Consolas, monospace; + font-size: 0.68rem; + letter-spacing: 0.04em; + text-transform: uppercase; +} + +.contract-strip dd, +.mode-panel dd { + margin: 0; + color: var(--ink); + font-size: 0.95rem; + font-weight: 690; +} + +.section { + width: min(100%, var(--content)); + padding: clamp(6rem, 11vw, 10rem) var(--page-pad); + margin: 0 auto; +} + +.section-heading { + max-width: 49rem; + margin-bottom: clamp(3.5rem, 7vw, 6rem); +} + +.section-heading p, +.durability-copy > p, +.compatibility-intro p, +.install-copy p { + max-width: 42rem; + margin-bottom: 0; + color: var(--muted); + font-size: clamp(1rem, 1.35vw, 1.16rem); +} + +.architecture-map { + display: grid; + padding: clamp(1.3rem, 3.2vw, 3rem); + border: 1px solid var(--hairline); + border-radius: var(--radius); + background: + linear-gradient(90deg, transparent 49.8%, var(--hairline) 50%, transparent 50.2%), + linear-gradient(transparent 49.8%, var(--hairline) 50%, transparent 50.2%), + var(--surface); + background-size: 4rem 4rem; + grid-template-columns: minmax(9rem, 0.55fr) minmax(8rem, 0.48fr) minmax(0, 1.6fr); + align-items: center; + gap: clamp(1.5rem, 4vw, 4.5rem); +} + +.map-source, +.broker-unit { + display: flex; + min-height: 11rem; + padding: 1.35rem; + border: 1px solid var(--hairline); + border-radius: var(--radius-control); + background: var(--page); + flex-direction: column; + justify-content: space-between; +} + +.map-source strong, +.broker-unit strong { + font-size: 1.1rem; + letter-spacing: -0.025em; +} + +.map-source small { + color: var(--muted); +} + +.map-label, +.broker-index { + color: var(--accent); + font-family: ui-monospace, "SFMono-Regular", Consolas, monospace; + font-size: 0.68rem; + font-weight: 720; + letter-spacing: 0.04em; + text-transform: uppercase; +} + +.map-route { + display: grid; + color: var(--muted); + font-family: ui-monospace, "SFMono-Regular", Consolas, monospace; + font-size: 0.72rem; + text-align: center; + gap: 0.75rem; +} + +.route-line { + position: relative; + display: block; + height: 1px; + background: var(--accent); +} + +.route-line::after { + position: absolute; + top: -4px; + right: -1px; + width: 8px; + height: 8px; + border-top: 1px solid var(--accent); + border-right: 1px solid var(--accent); + content: ""; + transform: rotate(45deg); +} + +.broker-cluster { + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: 0.8rem; +} + +.broker-unit { + min-height: 13rem; +} + +.disk { + display: inline-flex; + width: fit-content; + padding: 0.4rem 0.58rem; + border: 1px solid color-mix(in srgb, var(--accent) 42%, var(--hairline)); + border-radius: var(--radius-nested); + color: var(--accent); + font-family: ui-monospace, "SFMono-Regular", Consolas, monospace; + font-size: 0.72rem; + font-weight: 680; +} + +.architecture-notes { + display: flex; + padding: 1.8rem 0 0; + align-items: flex-start; + justify-content: space-between; + gap: 2rem; +} + +.architecture-notes p { + max-width: 39rem; + margin: 0; + color: var(--muted); +} + +.text-link { + display: inline; + color: var(--ink); + font-size: 0.88rem; + font-weight: 740; + text-decoration-color: var(--accent); + text-decoration-thickness: 2px; + text-underline-offset: 5px; +} + +.text-link:hover { + color: var(--accent); +} + +.durability-section { + width: 100%; + max-width: none; + padding-right: 0; + padding-left: 0; + border-top: 1px solid var(--hairline); + border-bottom: 1px solid var(--hairline); + background: var(--surface); +} + +.durability-grid { + display: grid; + width: min(100%, var(--content)); + padding: 0 var(--page-pad); + margin: 0 auto; + align-items: center; + grid-template-columns: minmax(18rem, 0.72fr) minmax(0, 1.28fr); + gap: clamp(3rem, 8vw, 8rem); +} + +.storage-visual { + max-width: 33rem; + margin: 0; +} + +.storage-visual img { + width: 100%; + max-height: 44rem; + border-radius: var(--radius); + box-shadow: 0 2rem 5rem var(--shadow); + object-fit: cover; +} + +.durability-copy { + max-width: 45rem; +} + +.durability-copy h2 { + max-width: 12ch; +} + +.mode-tabs { + display: flex; + padding: 0.35rem; + margin-top: 2.5rem; + border: 1px solid var(--hairline); + border-radius: var(--radius-control); + background: var(--page); + gap: 0.35rem; +} + +.mode-tabs button { + min-width: 0; + padding: 0.65rem 0.85rem; + border: 0; + border-radius: var(--radius-nested); + background: transparent; + color: var(--muted); + font-size: 0.82rem; + font-weight: 690; + cursor: pointer; + flex: 1; + white-space: nowrap; +} + +.mode-tabs button[aria-selected="true"] { + background: var(--accent); + color: var(--accent-ink); +} + +.mode-tabs button:active { + transform: translateY(1px); +} + +.mode-panel { + min-height: 13rem; + padding: 2rem 0 0; +} + +.mode-panel[hidden] { + display: none; +} + +.mode-panel dl { + display: grid; + margin: 0 0 2rem; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 1.5rem; +} + +.mode-panel dl > div { + padding-left: 1rem; + border-left: 2px solid var(--accent); +} + +.mode-panel p { + max-width: 36rem; + margin: 0; + color: var(--muted); +} + +.compatibility-intro { + max-width: 49rem; + margin: 0 0 3.4rem 14%; +} + +.compatibility-intro h2 { + max-width: 12ch; +} + +.command-rail { + display: flex; + width: calc(100% + var(--page-pad)); + padding: 0.25rem var(--page-pad) 1.1rem 0; + overflow-x: auto; + scroll-snap-type: x mandatory; + scrollbar-color: var(--accent) transparent; + gap: 0.65rem; +} + +.command-rail span { + min-width: 7rem; + padding: 1rem 1.15rem; + border: 1px solid var(--hairline); + border-radius: var(--radius-control); + background: var(--surface); + color: var(--ink); + font-family: ui-monospace, "SFMono-Regular", Consolas, monospace; + font-size: 0.78rem; + font-weight: 730; + scroll-snap-align: start; +} + +.compatibility-details { + display: grid; + padding-top: clamp(3rem, 6vw, 5rem); + grid-template-columns: 1.35fr 0.95fr; + gap: 0; +} + +.compatibility-details article { + min-height: 11rem; + padding: 1.7rem; + border-top: 1px solid var(--hairline); +} + +.compatibility-details article:nth-child(odd) { + padding-left: 0; + border-right: 1px solid var(--hairline); +} + +.compatibility-details strong { + font-size: 1.12rem; + letter-spacing: -0.025em; +} + +.compatibility-details p { + max-width: 26rem; + margin: 0.7rem 0 0; + color: var(--muted); +} + +.evidence-section { + display: grid; + grid-template-columns: minmax(0, 1.35fr) minmax(18rem, 0.65fr); + gap: clamp(4rem, 10vw, 10rem); + border-top: 1px solid var(--hairline); +} + +.evidence-statement > p { + max-width: 16ch; + margin-bottom: 2.5rem; + font-size: clamp(2.6rem, 5vw, 5.4rem); + font-weight: 720; + letter-spacing: -0.06em; + line-height: 1; +} + +.evidence-facts > div { + display: grid; + padding: 1.4rem 0; + border-bottom: 1px solid var(--hairline); + grid-template-columns: 3rem 1fr; + gap: 0.8rem; +} + +.evidence-facts > div:first-child { + padding-top: 0; +} + +.evidence-facts span { + color: var(--accent); + font-family: ui-monospace, "SFMono-Regular", Consolas, monospace; + font-size: 0.72rem; + font-weight: 720; +} + +.evidence-facts p { + margin: 0; + color: var(--muted); +} + +.install-section { + width: 100%; + max-width: none; + padding-right: 0; + padding-left: 0; + border-top: 1px solid var(--hairline); + background: var(--surface); +} + +.install-shell { + display: grid; + width: min(100%, var(--content)); + padding: 0 var(--page-pad); + margin: 0 auto; + align-items: end; + grid-template-columns: minmax(16rem, 0.7fr) minmax(0, 1.3fr); + gap: clamp(3rem, 8vw, 7rem); +} + +.install-copy h2 { + margin-bottom: 1rem; +} + +.install-command { + overflow: hidden; + border: 1px solid var(--hairline); + border-radius: var(--radius); + background: var(--code); + box-shadow: 0 2rem 5rem var(--shadow); +} + +.code-header { + display: flex; + min-height: 3.2rem; + padding: 0 0.75rem 0 1.15rem; + border-bottom: 1px solid #313a33; + align-items: center; + justify-content: space-between; + color: #aab5ac; + font-family: ui-monospace, "SFMono-Regular", Consolas, monospace; + font-size: 0.7rem; +} + +.copy-button { + padding: 0.45rem 0.75rem; + border: 1px solid #435047; + border-radius: var(--radius-nested); + background: #222a24; + color: #edf2eb; + font-size: 0.75rem; + font-weight: 680; + cursor: pointer; +} + +.copy-button:hover { + border-color: #778479; +} + +.copy-button:active { + transform: translateY(1px); +} + +pre { + max-width: 100%; + padding: clamp(1.2rem, 3vw, 2rem); + margin: 0; + overflow-x: auto; + color: var(--code-ink); + font-family: ui-monospace, "SFMono-Regular", Consolas, monospace; + font-size: clamp(0.74rem, 1.1vw, 0.9rem); + line-height: 1.75; +} + +.site-footer { + display: flex; + width: min(100%, var(--content)); + min-height: 16rem; + padding: 4rem var(--page-pad); + margin: 0 auto; + align-items: flex-start; + justify-content: space-between; + gap: 3rem; +} + +.footer-brand { + margin-bottom: 1.2rem; +} + +.site-footer p { + max-width: 27rem; + margin: 0; + color: var(--muted); +} + +.site-footer nav { + display: flex; + gap: 1.8rem; +} + +.copy-status { + position: fixed; + right: 1.25rem; + bottom: 1.25rem; + z-index: 15; + max-width: min(22rem, calc(100vw - 2.5rem)); + padding: 0.75rem 1rem; + border: 1px solid var(--hairline); + border-radius: var(--radius-control); + background: var(--ink); + color: var(--page); + font-size: 0.82rem; + font-weight: 680; + opacity: 0; + pointer-events: none; + transform: translateY(1rem); + transition: + opacity 180ms ease, + transform 180ms ease; +} + +.copy-status.is-visible { + opacity: 1; + transform: translateY(0); +} + +.reveal-ready [data-reveal] { + opacity: 0; + transform: translateY(28px); + transition: + opacity 700ms cubic-bezier(0.16, 1, 0.3, 1), + transform 700ms cubic-bezier(0.16, 1, 0.3, 1); +} + +.reveal-ready [data-reveal].is-visible { + opacity: 1; + transform: translateY(0); +} + +@keyframes hero-enter { + from { + transform: translateY(26px); + } +} + +@keyframes hero-image-enter { + from { + transform: translateX(28px) scale(0.98); + } +} + +@media (max-width: 1040px) { + .hero { + min-height: auto; + } + + .hero-grid { + padding-top: 4rem; + padding-bottom: 4rem; + grid-template-columns: minmax(0, 0.86fr) minmax(24rem, 1.14fr); + gap: 2.5rem; + } + + h1 { + font-size: clamp(3rem, 4.6vw, 4.6rem); + } + + .contract-strip dl { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } + + .contract-strip dl > div:nth-child(3) { + padding-left: 0; + border-top: 1px solid var(--hairline); + border-left: 0; + } + + .contract-strip dl > div:nth-child(4) { + border-top: 1px solid var(--hairline); + } + + .architecture-map { + grid-template-columns: minmax(8rem, 0.55fr) minmax(6rem, 0.35fr) minmax(0, 1.45fr); + gap: 1.2rem; + } + + .broker-cluster { + grid-template-columns: 1fr; + } + + .broker-unit { + min-height: 8rem; + } + + .durability-grid { + grid-template-columns: minmax(16rem, 0.76fr) minmax(0, 1.24fr); + gap: 3rem; + } +} + +@media (max-width: 800px) { + html { + scroll-padding-top: 4.75rem; + } + + .nav-shell { + min-height: 66px; + } + + .menu-button { + position: relative; + display: grid; + } + + .site-nav { + position: absolute; + top: calc(100% + 1px); + right: 0; + left: 0; + display: none; + padding: 1rem var(--page-pad) 1.25rem; + border-bottom: 1px solid var(--hairline); + background: var(--page); + align-items: stretch; + flex-direction: column; + gap: 0; + } + + .site-nav.is-open { + display: flex; + } + + .site-nav a { + padding: 0.85rem 0; + border-bottom: 1px solid var(--hairline); + } + + .site-nav .nav-action { + margin-top: 0.8rem; + border: 1px solid var(--hairline); + text-align: center; + } + + .hero-grid, + .durability-grid, + .install-shell, + .evidence-section { + grid-template-columns: 1fr; + } + + .hero-grid { + width: 100%; + padding-top: 3.5rem; + padding-bottom: 4rem; + gap: 3rem; + } + + h1 { + max-width: 9ch; + font-size: clamp(3.2rem, 15vw, 5.4rem); + } + + h1 span { + white-space: normal; + } + + .hero-summary { + max-width: 28rem; + } + + .hero-visual img { + min-height: 25rem; + object-position: 68% center; + } + + .section { + padding-top: 6rem; + padding-bottom: 6rem; + } + + .architecture-map { + padding: 1rem; + grid-template-columns: 1fr; + gap: 1rem; + } + + .map-source, + .broker-unit { + min-height: 8.5rem; + } + + .map-route { + min-height: 5rem; + align-content: center; + } + + .route-line { + width: 1px; + height: 2.6rem; + margin: 0 auto; + } + + .route-line::after { + top: auto; + right: -4px; + bottom: 0; + transform: rotate(135deg); + } + + .architecture-notes { + flex-direction: column; + } + + .durability-grid { + gap: 4rem; + } + + .storage-visual { + width: min(82%, 28rem); + margin-left: auto; + } + + .compatibility-intro { + margin-left: 0; + } + + .compatibility-details { + grid-template-columns: 1fr; + } + + .compatibility-details article, + .compatibility-details article:nth-child(odd) { + min-height: auto; + padding: 1.7rem 0; + border-right: 0; + } + + .evidence-section { + gap: 4rem; + } + + .install-shell { + gap: 3rem; + } + + .site-footer { + flex-direction: column; + } +} + +@media (max-width: 520px) { + :root { + --page-pad: 1.15rem; + --radius: 14px; + } + + .brand { + font-size: 0.95rem; + } + + .brand-mark { + width: 1.9rem; + height: 1.9rem; + } + + .hero-grid { + padding-top: 2.7rem; + } + + h1 { + font-size: clamp(3rem, 16vw, 4.5rem); + } + + h2 { + font-size: clamp(2.45rem, 12vw, 3.7rem); + } + + .hero-actions { + align-items: stretch; + flex-direction: column; + } + + .button { + width: 100%; + } + + .hero-visual img { + min-height: 21rem; + } + + .contract-strip dl { + grid-template-columns: 1fr; + } + + .contract-strip dl > div { + padding: 1rem 0; + border-top: 1px solid var(--hairline); + border-left: 0; + } + + .contract-strip dl > div:first-child { + border-top: 0; + } + + .mode-tabs { + align-items: stretch; + flex-direction: column; + } + + .mode-tabs button { + width: 100%; + } + + .mode-panel { + min-height: 17rem; + } + + .mode-panel dl { + grid-template-columns: 1fr; + } + + .storage-visual { + width: 90%; + } + + .command-rail { + width: calc(100% + 1.15rem); + } + + .site-footer nav { + align-items: flex-start; + flex-direction: column; + gap: 0.8rem; + } +} + +@media (prefers-reduced-motion: reduce) { + html { + scroll-behavior: auto; + } + + *, + *::before, + *::after { + animation-duration: 0.01ms !important; + animation-iteration-count: 1 !important; + transition-duration: 0.01ms !important; + } + + .reveal-ready [data-reveal] { + opacity: 1; + transform: none; + } +} + +@media (prefers-reduced-transparency: reduce) { + .site-header { + background: var(--page); + backdrop-filter: none; + -webkit-backdrop-filter: none; + } +}