From 8ea75ea604a44c018c5e7ab38d99bbf87991201a Mon Sep 17 00:00:00 2001 From: Wassim Chegham Date: Mon, 19 Oct 2026 15:50:57 +0200 Subject: [PATCH] feat(website): highlight KEDA autoscaling and deployment docs Refresh the Kubernetes section with a light surface, consistent feature tiles, official artwork, and autoscaling documentation links. Add Kubernetes deployment navigation to the Learn footer and cover the layout and links across themes and screen sizes. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: f70936ad-69ed-4b1d-9366-e26c2ba84244 --- website/README.md | 23 ++++++- website/public/keda-horizontal-color.svg | 1 + website/public/kubernetes-stacked-color.svg | 1 + website/src/components/Home.astro | 39 +++++++++-- website/src/components/SiteFooter.astro | 1 + website/src/styles/landing.css | 31 +++++---- website/tests/accessibility.spec.ts | 72 +++++++++++++++++++++ 7 files changed, 147 insertions(+), 21 deletions(-) create mode 100644 website/public/keda-horizontal-color.svg create mode 100644 website/public/kubernetes-stacked-color.svg diff --git a/website/README.md b/website/README.md index 0c74b242f..48592d510 100644 --- a/website/README.md +++ b/website/README.md @@ -57,13 +57,28 @@ Sidebar order is defined in `astro.config.mjs`, not by directory order. The landing page places its Kubernetes infrastructure section before the final Get started call to action in [Home.astro](src/components/Home.astro), styled with Kubernetes blue -(`#326CE5`). Its unmodified white logo is vendored from the +(`#326CE5`) accents on a light background in both site themes. Its +unmodified full-color logo is vendored from the [CNCF Kubernetes artwork](https://github.com/cncf/artwork/tree/main/projects/kubernetes) -in [public/kubernetes-stacked-white.svg](public/kubernetes-stacked-white.svg). +in [public/kubernetes-stacked-color.svg](public/kubernetes-stacked-color.svg). Keep the official artwork intact rather than recreating or recoloring it. The Kubernetes section's Host on Kubernetes link opens the [Kubernetes self-hosting overview](src/content/docs/self-hosting/overview.md). +The infrastructure description and autoscaling tile both name KEDA. +The feature row starts with queue-driven scaling; its headline +opens the [Autoscaling with KEDA guide](src/content/docs/self-hosting/scaling.md). +The compact full-color [KEDA](https://keda.sh/) wordmark links to the +project site and sits directly on the light section background, without a +separate badge, keeping Kubernetes as the primary brand. +The three borderless feature tiles share an equal-height graphic area +above aligned headlines and descriptions. The KEDA wordmark, container +icon, and GitOps icon are vertically centered in that area; the tiles +stack on narrow screens. +The unmodified SVG is vendored from the +[CNCF KEDA artwork](https://github.com/cncf/artwork/tree/main/projects/keda) +in [public/keda-horizontal-color.svg](public/keda-horizontal-color.svg), +under the [CNCF artwork trademark policy](https://github.com/cncf/artwork/blob/main/LICENSE.md). Mermaid diagrams in Markdown and MDX are rendered client-side by `astro-mermaid`, which must remain before Starlight in the `integrations` @@ -170,7 +185,9 @@ table of contents, code examples, and previous/next navigation. Green and red communicate criterion outcomes, always with text. - **Navigation:** a landing header with direct documentation and demo links, plus task-oriented guide groups: run experiments, design a - benchmark, and connect tools. Existing document URLs are preserved. + benchmark, and connect tools. The footer's Learn group links to the + [Kubernetes deployment guide](src/content/docs/self-hosting/deploy-helm.md). + Existing document URLs are preserved. - **Onboarding:** a concrete example before terminology, followed by separate Portal, CLI, and API entry points. Access requirements are explicit rather than promising instant access to a hosted service. diff --git a/website/public/keda-horizontal-color.svg b/website/public/keda-horizontal-color.svg new file mode 100644 index 000000000..821cdd531 --- /dev/null +++ b/website/public/keda-horizontal-color.svg @@ -0,0 +1 @@ +keda-word diff --git a/website/public/kubernetes-stacked-color.svg b/website/public/kubernetes-stacked-color.svg new file mode 100644 index 000000000..27b5580f5 --- /dev/null +++ b/website/public/kubernetes-stacked-color.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/website/src/components/Home.astro b/website/src/components/Home.astro index 29d743204..878a765e9 100644 --- a/website/src/components/Home.astro +++ b/website/src/components/Home.astro @@ -144,18 +144,47 @@ const paths = [

Your experiments deserve serious infrastructure. Scope runs on Kubernetes, bringing containerized agent workers, queue-driven - scaling, and GitOps deployments to your benchmarking stack. + scaling with KEDA, and GitOps deployments to your benchmarking stack.

Host on Kubernetes
- Kubernetes + Kubernetes Open infrastructure. Repeatable experiments.
diff --git a/website/src/components/SiteFooter.astro b/website/src/components/SiteFooter.astro index d0d91e6f0..81e27fce4 100644 --- a/website/src/components/SiteFooter.astro +++ b/website/src/components/SiteFooter.astro @@ -29,6 +29,7 @@ const groups: { title: string; links: FooterLink[] }[] = [ { label: 'What is Scope?', href: 'introduction/what-is-scope/' }, { label: 'Concepts', href: 'introduction/concepts/' }, { label: 'Choose a coding agent', href: 'guides/choosing-a-coding-agent/' }, + { label: 'Kubernetes deployment', href: 'self-hosting/deploy-helm/' }, { label: 'REST API', href: 'reference/rest-api/' }, ], }, diff --git a/website/src/styles/landing.css b/website/src/styles/landing.css index c13bffb58..c9ae5343e 100644 --- a/website/src/styles/landing.css +++ b/website/src/styles/landing.css @@ -109,21 +109,26 @@ scope-showreel { display: block; max-width: 60rem; margin: 3rem auto 0; } .scope-strip-divider { width: 1px; height: 1.1rem; background: var(--scope-border); } .scope-section { margin-top: clamp(3.5rem, 7vw, 6rem); } .scope-demo-section { margin-top: 3.5rem; } -.scope-kubernetes { display: grid; grid-template-columns: minmax(0, 1.4fr) minmax(0, 1fr); gap: 2.5rem; padding: clamp(1.5rem, 5vw, 4rem); border: 1px solid #326ce5; border-radius: 1rem; color: #fff; background: radial-gradient(ellipse at 100% 0, #326ce5 0, transparent 65%), #102b64; box-shadow: 0 24px 70px -36px #326ce580; } -.scope-kubernetes .scope-eyebrow { color: #c5dcff; } -.scope-kubernetes h2 { margin-top: 1rem; font-size: clamp(2.5rem, 5vw, 4.5rem); font-weight: 650; line-height: 1.08; letter-spacing: -0.05em; } -.scope-kubernetes h2 span { color: #fff; } -.scope-home .scope-kubernetes-description { max-width: 49ch; margin-top: 1.5rem; color: #d9e6ff; font-size: 1rem; } -.scope-kubernetes-link { margin-top: 1.75rem; background: #fff; color: #143572; } -.scope-kubernetes-link:hover { background: #e5eeff; } -.scope-kubernetes :focus-visible { outline-color: #fff; } +.scope-kubernetes { display: grid; grid-template-columns: minmax(0, 1.4fr) minmax(0, 1fr); gap: 2.5rem; padding: clamp(1.5rem, 5vw, 4rem); border: 1px solid #cbdcf3; border-radius: 1rem; color: #16365f; background: #f7faff; box-shadow: 0 24px 70px -36px #326ce530; } +.scope-kubernetes .scope-eyebrow { color: #315ca3; } +.scope-kubernetes h2 { margin-top: 1rem; color: #16365f; font-size: clamp(2.5rem, 5vw, 4.5rem); font-weight: 650; line-height: 1.08; letter-spacing: -0.05em; } +.scope-kubernetes h2 span { color: #326ce5; } +.scope-home .scope-kubernetes-description { max-width: 49ch; margin-top: 1.5rem; color: #405675; font-size: 1rem; } +.scope-kubernetes-link { margin-top: 1.75rem; background: #326ce5; color: #fff; } +.scope-kubernetes-link:hover { background: #255bbf; } +.scope-kubernetes :focus-visible { outline-color: #2456ad; } .scope-kubernetes-brand { display: flex; flex-direction: column; justify-content: center; align-items: center; gap: 1.5rem; text-align: center; } .scope-kubernetes-brand img { display: block; width: min(100%, 240px); height: auto; } -.scope-kubernetes-brand > span { max-width: 25ch; color: #d9e6ff; font: 0.75rem/1.6 var(--sl-font-mono); } -.scope-home .scope-kubernetes-features { grid-column: 1 / -1; display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 1.5rem; list-style: none; margin: 0; padding: 1.75rem 0 0; border-top: 1px solid #ffffff40; } -.scope-kubernetes-features li { display: flex; flex-direction: column; gap: 0.4rem; } -.scope-kubernetes-features strong { font-size: 0.9rem; font-weight: 600; } -.scope-kubernetes-features span { color: #d9e6ff; font-size: 0.8rem; } +.scope-kubernetes-brand > span { max-width: 25ch; color: #405675; font: 0.75rem/1.6 var(--sl-font-mono); } +.scope-home .scope-kubernetes-features { grid-column: 1 / -1; display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 1.5rem; list-style: none; margin: 0; padding: 1.75rem 0 0; border-top: 1px solid #cbdcf3; } +.scope-kubernetes-features li { display: flex; flex-direction: column; gap: 0.5rem; } +.scope-feature-graphic { display: flex; align-items: center; height: 3rem; color: #326ce5; } +.scope-kubernetes-features strong { color: #16365f; font-size: 0.9rem; font-weight: 600; } +.scope-kubernetes-features strong a { color: inherit; text-decoration: underline; text-underline-offset: 0.15em; } +.scope-kubernetes-features strong a:hover { color: #2456ad; } +.scope-kubernetes-features span { color: #405675; font-size: 0.8rem; } +.scope-keda-brand { display: inline-flex; align-items: center; min-height: 3rem; border-radius: 0.2rem; } +.scope-keda-brand img { display: block; width: 6rem; height: auto; } @media (max-width: 48rem) { .scope-kubernetes { grid-template-columns: minmax(0, 1fr); } .scope-kubernetes-brand img { width: 160px; } diff --git a/website/tests/accessibility.spec.ts b/website/tests/accessibility.spec.ts index 88792da1d..8f0ac67c3 100644 --- a/website/tests/accessibility.spec.ts +++ b/website/tests/accessibility.spec.ts @@ -17,6 +17,25 @@ async function expectAccessible(page: Page) { test('landing page and interactive example are accessible', async ({ page }) => { await page.goto('/scope/'); + const kubernetesSection = page.locator('.scope-kubernetes'); + await expect(kubernetesSection).toHaveCSS('background-color', 'rgb(247, 250, 255)'); + await expect(kubernetesSection.locator('.scope-kubernetes-description')) + .toContainText('queue-driven scaling with KEDA'); + const kubernetesLogo = kubernetesSection.getByRole('img', { name: 'Kubernetes', exact: true }); + await expect(kubernetesLogo).toBeVisible(); + await expect(kubernetesLogo).toHaveAttribute('src', '/scope/kubernetes-stacked-color.svg'); + const scalingFeature = kubernetesSection.getByRole('listitem').first(); + const scalingDocsLink = scalingFeature.getByRole('link', { name: 'Queue-driven scaling', exact: true }); + await expect(scalingDocsLink).toHaveAttribute('href', '/scope/self-hosting/scaling/'); + const kedaLink = scalingFeature.getByRole('link', { name: 'KEDA (opens in a new tab)', exact: true }); + await expect(kedaLink).toBeVisible(); + await expect(kedaLink).toHaveAttribute('href', 'https://keda.sh/'); + await expect(kedaLink).toHaveAttribute('target', '_blank'); + await expect(kedaLink).toHaveAttribute('rel', 'noopener noreferrer'); + await expect(kedaLink).toHaveCSS('background-color', 'rgba(0, 0, 0, 0)'); + const kedaLogo = kedaLink.getByRole('img', { name: 'KEDA', exact: true }); + await expect(kedaLogo).toBeVisible(); + await expect(kedaLogo).toHaveAttribute('src', '/scope/keda-horizontal-color.svg'); await expect(page.locator('scope-flow [data-play]')).toBeEnabled(); await expectAccessible(page); @@ -27,6 +46,55 @@ test('landing page and interactive example are accessible', async ({ page }) => await page.locator('[data-step="3"]').click(); await page.getByText('Inspect per-criterion outcomes', { exact: true }).click(); await expectAccessible(page); + + await scalingDocsLink.click(); + await expect(page).toHaveURL(/\/scope\/self-hosting\/scaling\/$/); + await expect(page.getByRole('heading', { name: 'Autoscaling with KEDA', level: 1, exact: true })) + .toBeVisible(); +}); + +test('infrastructure feature tiles align their graphics and copy', async ({ page }) => { + await page.goto('/scope/'); + const features = page.locator('.scope-kubernetes-features'); + await expect(features.locator('.scope-feature-graphic')).toHaveCount(3); + await expect(features.locator('.scope-feature-graphic > svg[aria-hidden="true"]')).toHaveCount(2); + await expect(features.locator(':scope > li > span')).toHaveText([ + 'KEDA scales workers with demand.', + 'Coding agents run in containers.', + 'Flux keeps deployments in sync.', + ]); + + const layout = await features.evaluate((grid) => { + const topSpread = (selector: string) => { + const positions = Array.from(grid.children, (item) => { + const element = item.querySelector(selector); + if (!element) throw new Error(`Feature tile is missing ${selector}`); + return element.getBoundingClientRect().top; + }); + return Math.max(...positions) - Math.min(...positions); + }; + return { + columns: getComputedStyle(grid).gridTemplateColumns.split(' ').length, + graphics: topSpread('.scope-feature-graphic'), + headings: topSpread('strong'), + descriptions: topSpread(':scope > span'), + graphicCenterOffsets: Array.from(grid.querySelectorAll('.scope-feature-graphic'), (graphic) => { + const visual = graphic.querySelector('img, svg'); + if (!visual) throw new Error('Feature tile is missing its graphic'); + const frame = graphic.getBoundingClientRect(); + const image = visual.getBoundingClientRect(); + return Math.abs(frame.top + frame.height / 2 - image.top - image.height / 2); + }), + }; + }); + for (const offset of layout.graphicCenterOffsets) { + expect(offset).toBeLessThanOrEqual(1); + } + if (layout.columns === 3) { + expect(layout.graphics).toBeLessThanOrEqual(1); + expect(layout.headings).toBeLessThanOrEqual(1); + expect(layout.descriptions).toBeLessThanOrEqual(1); + } }); for (const path of ['/', '/introduction/what-is-scope/', '/community/articles-and-talks/', '/reference/api/']) { @@ -40,6 +108,10 @@ for (const path of ['/', '/introduction/what-is-scope/', '/community/articles-an await page.goto(`/scope${path}`); const footer = page.getByRole('contentinfo'); await expect(footer).toHaveCount(1); + const deploymentLink = footer.getByRole('list', { name: /^learn$/i }) + .getByRole('link', { name: 'Kubernetes deployment', exact: true }); + await expect(deploymentLink).toBeVisible(); + await expect(deploymentLink).toHaveAttribute('href', '/scope/self-hosting/deploy-helm/'); for (const [name, href] of [ ['Your Privacy Choices', 'https://aka.ms/yourcaliforniaprivacychoices'], ['Consumer Health Privacy', 'https://go.microsoft.com/fwlink/?linkid=2259814'],