diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml new file mode 100644 index 0000000..dfe403f --- /dev/null +++ b/.github/workflows/pages.yml @@ -0,0 +1,54 @@ +name: Pages + +on: + push: + branches: [main] + paths: + - 'site/**' + - '.github/workflows/pages.yml' + pull_request: + branches: [main] + paths: + - 'site/**' + - '.github/workflows/pages.yml' + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: pages-${{ github.ref }} + cancel-in-progress: false + +jobs: + validate: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 + - name: Build and check local links + run: python3 site/build.py + + deploy: + if: github.ref == 'refs/heads/main' && github.event_name != 'pull_request' + needs: validate + runs-on: ubuntu-latest + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + permissions: + contents: read + pages: write + id-token: write + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 + - name: Build site + run: python3 site/build.py + - name: Configure Pages + uses: actions/configure-pages@983d7736d9b0ae728b81ab479565c72886d7745b # v5 + - name: Upload site + uses: actions/upload-pages-artifact@7b1f4a764d45c48632c6b24a0339c27f5614fb0b # v4 + with: + path: site/_build + - name: Deploy Pages + id: deployment + uses: actions/deploy-pages@d6db90164ac5ed86f2b6aed7e0febac5b3c0c03e # v4 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 02cc754..0fce33c 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -2,8 +2,8 @@ name: Release # Tag-driven release. To cut a release: # -# git tag v0.1.0 -# git push origin v0.1.0 +# git tag v0.2.0 +# git push origin v0.2.0 # # Builds win-x64 and win-arm64 AOT executables, packages them with the README and # LICENSE into zips, computes SHA-256 checksums, and publishes a GitHub Release with diff --git a/.gitignore b/.gitignore index 65ea102..ae7f9ee 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ bin/ obj/ publish/ dist/ +/site/_build/ /packaging/release-assets/ /packaging/winget-manifests/ *.nupkg diff --git a/README.md b/README.md index 974f3a7..eff1f04 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,8 @@ [![Windows](https://img.shields.io/badge/Windows-x64%20%7C%20arm64-0078D4?style=flat&logo=windows&logoColor=white)](https://www.microsoft.com/windows) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow?style=flat)](LICENSE) +[Website](https://wingetscout.com/) · [Documentation](https://wingetscout.com/docs.html) · [Releases](https://github.com/harder/wingetscout/releases) + ## Install **Recommended: download the portable ZIP** from [GitHub Releases](https://github.com/harder/wingetscout/releases). It includes `wingetscout.exe` and the companion files needed for the full WinGet COM experience. No .NET runtime or installer is required. @@ -14,8 +16,6 @@ A WinGet community package is being prepared as `Harder.WinGetScout`. Use the re After that package is accepted, `winget install -e --id Harder.WinGetScout` will install the ZIP into WinGet's managed portable directory and add `wingetscout` to your user `PATH`. Open a **new terminal** to use the command from any folder. The WinGet manifest already requests this behavior; it does not require a separate installer or manual PATH editing. WinGet verifies the downloaded ZIP hash, but it does not sign the executable. See [signing and distribution](code-signing.md). -> **Release availability:** The Scout filenames below will appear with the first Scout release. If they are not on the Releases page yet, Scout has not been published; see [Build from source](#build-from-source) to try the current code. - 1. Choose the ZIP for your Windows PC: | PC | Download | diff --git a/WinGetScout.csproj b/WinGetScout.csproj index d20415d..3cc6496 100644 --- a/WinGetScout.csproj +++ b/WinGetScout.csproj @@ -20,7 +20,7 @@ wingetscout Scout for WinGet Scout for WinGet - 0.1.3 + 0.2.0 Scout for WinGet is a terminal app for finding, installing, and updating Windows packages. true true diff --git a/packaging/WINGET-SUBMISSION.md b/packaging/WINGET-SUBMISSION.md index e6d18d7..ddbc83d 100644 --- a/packaging/WINGET-SUBMISSION.md +++ b/packaging/WINGET-SUBMISSION.md @@ -8,7 +8,7 @@ The installer manifest has `InstallerType: zip`, `NestedInstallerType: portable` 2. Download that artifact from the successful release workflow run. The files are under `manifests/h/Harder/WinGetScout//`. To regenerate directly from the published ZIPs and validate in one command on Windows, run: ```powershell - pwsh ./packaging/prepare-winget-submission.ps1 -Version 0.1.4 + pwsh ./packaging/prepare-winget-submission.ps1 -Version 0.2.0 ``` The helper needs `gh`, `winget`, and a GitHub login with access to the public release. It downloads the exact published assets and uses [new-winget-manifest.ps1](new-winget-manifest.ps1) to check the archive layout and compute SHA-256 values. The release workflow also verifies its published ZIPs are byte-for-byte identical to its build artifacts. Before submitting, inspect the generated URLs and hashes against the GitHub Release. diff --git a/packaging/build-msix.ps1 b/packaging/build-msix.ps1 index 2cd212b..e426ff8 100644 --- a/packaging/build-msix.ps1 +++ b/packaging/build-msix.ps1 @@ -16,12 +16,12 @@ .EXAMPLE # CI: pack only (sign separately with Azure Artifact Signing, see code-signing.md): - pwsh ./packaging/build-msix.ps1 -Arch x64 -Version 0.1.3.0 + pwsh ./packaging/build-msix.ps1 -Arch x64 -Version 0.2.0.0 #> [CmdletBinding()] param( [ValidateSet('x64', 'arm64')] [string]$Arch = 'arm64', - [string]$Version = '0.1.3.0', + [string]$Version = '0.2.0.0', [string]$Publisher = 'CN=wingetscout (Dev)', [string]$CertPath, [string]$CertPassword = 'spike', @@ -33,7 +33,7 @@ param( $ErrorActionPreference = 'Stop' $repo = Split-Path -Parent $PSScriptRoot -if ($Version -notmatch '^\d+\.\d+\.\d+\.\d+$') { throw "Version must be 4-part (e.g. 0.1.3.0); got '$Version'." } +if ($Version -notmatch '^\d+\.\d+\.\d+\.\d+$') { throw "Version must be 4-part (e.g. 0.2.0.0); got '$Version'." } if (-not $OutDir) { $OutDir = Join-Path $repo 'dist' } New-Item -ItemType Directory -Path $OutDir -Force | Out-Null diff --git a/site/CNAME b/site/CNAME new file mode 100644 index 0000000..4e191c0 --- /dev/null +++ b/site/CNAME @@ -0,0 +1 @@ +wingetscout.com diff --git a/site/README.md b/site/README.md new file mode 100644 index 0000000..f626033 --- /dev/null +++ b/site/README.md @@ -0,0 +1,50 @@ +# WinGet Scout website + +The website lives in `site/` and is deployed by [Pages](../.github/workflows/pages.yml) +when site files reach `main`. It uses plain HTML, CSS, JavaScript, and Python's standard +library. [SkillView](https://github.com/harder/gh-skillview) uses the same build/deploy +shape, but this site has its own Windows-inspired design. + +Run `python site/build.py` from the repository root to copy published files to +`site/_build/` and check local links. The Pages workflow runs the same check on PRs. +The real application captures in `site/media/` were recorded with +[tuirec](https://github.com/tui-cs/tuirec) in `--mock` mode. They show demo data and do not +change installed packages. + +## Set up wingetscout.com + +The workflow can deploy after the site is merged. GitHub Pages needs **GitHub Actions** +as its publishing source and `wingetscout.com` saved as its custom domain in +**harder/wingetscout → Settings → Pages**. The included `CNAME` is a copy of the intended +domain for the built output; GitHub ignores that file for custom-workflow publishing, +so the Pages setting is required. + +At the domain's DNS provider, create these records (or an equivalent apex ALIAS/ANAME): + +| Type | Name | Value | +| --- | --- | --- | +| A | `@` | `185.199.108.153` | +| A | `@` | `185.199.109.153` | +| A | `@` | `185.199.110.153` | +| A | `@` | `185.199.111.153` | +| CNAME | `www` | `harder.github.io` | + +Remove conflicting apex A/AAAA/ALIAS/ANAME records and conflicting `www` records. +An optional IPv6 setup uses GitHub's published AAAA addresses; see the +[GitHub DNS guidance](https://docs.github.com/en/pages/configuring-a-custom-domain-for-your-github-pages-site/managing-a-custom-domain-for-your-github-pages-site). +The `www` record lets GitHub redirect `www.wingetscout.com` to the apex domain. +Avoid wildcard DNS records for this domain. + +Verify the domain under the GitHub account's **Settings → Pages → Verified domains**; +GitHub supplies a unique TXT record for this step. After DNS and the certificate are +ready, enable **Enforce HTTPS** in the repository's Pages settings. DNS propagation +and certificate issuance can take time. Confirm with: + +```powershell +Resolve-DnsName wingetscout.com -Type A +Resolve-DnsName www.wingetscout.com -Type CNAME +Invoke-WebRequest https://wingetscout.com/ -UseBasicParsing +``` + +See [GitHub's custom-domain instructions](https://docs.github.com/en/pages/configuring-a-custom-domain-for-your-github-pages-site/managing-a-custom-domain-for-your-github-pages-site) +for current DNS records and setup details. diff --git a/site/app.js b/site/app.js new file mode 100644 index 0000000..2afcecc --- /dev/null +++ b/site/app.js @@ -0,0 +1,22 @@ +const sectionLinks = [...document.querySelectorAll('.docs-sidebar a[href^="#"]')]; +if (sectionLinks.length && 'IntersectionObserver' in window) { + const visibility = new Map(); + const observer = new IntersectionObserver(entries => { + entries.forEach(entry => visibility.set(entry.target.id, entry.isIntersecting ? entry.intersectionRatio : 0)); + const visible = [...visibility].filter(([, ratio]) => ratio > 0).sort((a, b) => b[1] - a[1])[0]; + if (!visible) return; + sectionLinks.forEach(link => link.classList.toggle('active', link.hash === `#${visible[0]}`)); + }, { rootMargin: '-10% 0px -55% 0px', threshold: [0, 0.25, 0.5] }); + document.querySelectorAll('.docs-content section[id]').forEach(section => observer.observe(section)); +} + +const tourImage = document.querySelector('[data-tour-image]'); +const tourToggle = document.querySelector('[data-tour-toggle]'); +if (tourImage && tourToggle) { + tourToggle.addEventListener('click', () => { + const playing = tourToggle.getAttribute('aria-pressed') !== 'true'; + tourToggle.setAttribute('aria-pressed', String(playing)); + tourToggle.textContent = playing ? 'Pause tour' : 'Play tour'; + tourImage.src = playing ? tourImage.dataset.animatedSrc : tourImage.dataset.staticSrc; + }); +} diff --git a/site/build.py b/site/build.py new file mode 100644 index 0000000..c075e2f --- /dev/null +++ b/site/build.py @@ -0,0 +1,59 @@ +"""Build and validate the dependency-free Scout documentation site.""" + +from html.parser import HTMLParser +from pathlib import Path +from urllib.parse import unquote, urlsplit +import shutil + +SOURCE = Path(__file__).resolve().parent +OUTPUT = SOURCE / "_build" +FILES = ( + "index.html", "docs.html", "styles.css", "app.js", "favicon.svg", + "CNAME", "robots.txt", "sitemap.xml", "media/scout-discover.png", + "media/scout-installed.png", "media/scout-tour.gif", +) + + +class References(HTMLParser): + def __init__(self): + super().__init__() + self.values = [] + + def handle_starttag(self, tag, attrs): + for name, value in attrs: + if name in {"href", "src", "data-static-src", "data-animated-src"} and value: + self.values.append(value) + + +if not OUTPUT.resolve().is_relative_to(SOURCE.resolve()) or OUTPUT == SOURCE: + raise SystemExit("Build output must stay inside the site directory") +if OUTPUT.exists(): + shutil.rmtree(OUTPUT) +OUTPUT.mkdir() +for name in FILES: + source = SOURCE / name + if not source.is_file(): + raise SystemExit(f"Missing site file: {source}") + destination = OUTPUT / name + destination.parent.mkdir(parents=True, exist_ok=True) + shutil.copy2(source, destination) +(OUTPUT / ".nojekyll").touch() + +for page in ("index.html", "docs.html"): + parser = References() + parser.feed((OUTPUT / page).read_text(encoding="utf-8")) + for reference in parser.values: + parsed = urlsplit(reference) + if parsed.scheme or parsed.netloc or not parsed.path: + continue + local = unquote(parsed.path) + if local in {".", "./"}: + local = "index.html" + target = (OUTPUT / local).resolve() + if not target.is_relative_to(OUTPUT.resolve()) or not target.is_file(): + raise SystemExit(f"Broken local link in {page}: {reference}") + +if (OUTPUT / "CNAME").read_text(encoding="utf-8").strip() != "wingetscout.com": + raise SystemExit("CNAME must contain wingetscout.com") + +print(f"Built and validated {len(FILES)} site files in {OUTPUT}") diff --git a/site/docs.html b/site/docs.html new file mode 100644 index 0000000..5715b16 --- /dev/null +++ b/site/docs.html @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + Documentation — WinGet Scout + + + + +
+

WINGET SCOUT / DOCUMENTATION

Make yourself at home.

Everything you need to install Scout and manage packages with confidence.

+
+ +
+

01 / INSTALL

Install Scout

Scout runs on Windows 10 or 11 with WinGet available through App Installer. Windows Terminal is recommended. Choose the portable ZIP that matches your PC:

  1. Extract the complete ZIP to a folder you will keep, such as %LOCALAPPDATA%\Programs\WinGetScout.
  2. Open Windows Terminal or PowerShell in that folder.
  3. Run .\wingetscout.exe.
Keep the files together. The ZIP includes companion files used by the WinGet COM backend. The standalone EXE download uses the WinGet CLI backend.

The community WinGet package ID is Harder.WinGetScout. When its submission is accepted, winget install -e --id Harder.WinGetScout will place the portable package in WinGet's managed directory and add its command to your user PATH. The package is not yet available in the community source.

Downloads are currently unsigned. Check the release's SHA256SUMS file before running a download. See the signing status and plan.

+

02 / FIRST RUN

Find your way around

Scout opens on Installed packages. Move between Search, Installed, and Upgrades with the left and right arrow keys. The package list and details panel stay side by side.

WinGet Scout Installed view with a package list and details panel in demo modeInstalled view · demo data

Search for a package, inspect its details, and press i to install. Select several Search results with Space, then press B to review an install plan. On Upgrades, select packages and press U to review and upgrade them together.

+

03 / SHORTCUTS

Keyboard shortcuts

←→Switch tabs
↑↓Move through packages; j and k work too
/Search or filter the current list
iIInstall a package or choose a version
SpaceaSelect one or all visible packages
BUReview selected installs or upgrades
pPPin a package or cycle the pin filter
gLManage saved sets or view recent runs
t?qTheme, full help, or quit

Scout also supports mouse input. The status bar shows actions available in the current view; press ? for the complete in-app help.

+

04 / STAY CURRENT

Scheduled update checks

On the Upgrades tab, press C to check now or set a daily local time. Scout creates a current-user Windows Task Scheduler task. The first successful check establishes a baseline; later checks can notify you about new or changed unpinned upgrades or a failed check.

Checks never install updates automatically. Turn off the schedule in the same dialog to remove its task. Keep the extracted folder at the same path so the task can find Scout.

Scout keeps package sets, recent runs, update-check results, and schedule settings under %LOCALAPPDATA%\WinGetScout.

+

05 / UNDER THE HOOD

Backends and capabilities

The complete portable ZIP uses the structured WinGet COM API where it can activate. If COM is unavailable, Scout falls back to the winget CLI and shows the active backend in its header. The standalone EXE download uses the CLI because it lacks the COM companion files.

Both backends support core search, install, upgrade, uninstall, pins, saved sets, history, and scheduled checks. COM can provide richer details, installer previews, version lists, live progress, and supported verify or repair actions. Some information depends on what the active backend exposes.

+

06 / HELP

Troubleshooting

WinGet is missing

Run winget --version. If the command is unavailable, install or update Microsoft App Installer, then open a new terminal.

Windows warns about an unsigned download

Confirm the file came from the project's GitHub Release and compare its SHA-256 hash with the matching SHA256SUMS file. See the signing guide for the current status.

COM is unavailable

Make sure you extracted the whole ZIP, then check the active backend shown in Scout. CLI fallback still supports the core workflows. See COM activation details.

How do I get help or report a bug?

Press ? in Scout or open a GitHub issue.

+

07 / MORE

More resources

Read the README for contributor and build instructions, the known limitations, and the release notes.

Scout was inspired by Scott Hanselman's winget-tui and is built with Terminal.Gui. It is an independent MIT-licensed project.

+
+
+
+ + + diff --git a/site/favicon.svg b/site/favicon.svg new file mode 100644 index 0000000..91fbac7 --- /dev/null +++ b/site/favicon.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/site/index.html b/site/index.html new file mode 100644 index 0000000..a80815f --- /dev/null +++ b/site/index.html @@ -0,0 +1,90 @@ + + + + + + + + + + + + + + + + + WinGet Scout — Your Windows apps, in view + + + + + + + +
+
+
+

MADE FOR WINDOWS 10 & 11

+

Your Windows apps.
One clear view.

+

Discover software, plan installs, manage upgrades, and keep track of changes from a terminal that feels at home on Windows.

+ +
+ 01 Native AOT02 x64 + arm6403 Open source +
+
+
+ +
+
+ + WinGet Scout · Search + +
+ WinGet Scout search view showing package results and details in demo mode +
+
✓Know before you installReview packages and versions
+
↗Stay currentSee upgrades at a glance
+
+
+ +
SEARCHPLANINSTALLUPGRADEREVIEW
+ +
+

THE WORKSPACE

Everything in its place.

Scout brings the useful parts of WinGet together without hiding what will happen to your system.

+
+
01 / DISCOVER

Find the right package

Search your WinGet sources, inspect publisher and version details, then choose exactly what to install.

+
02 / DECIDE

Preview the plan

Select several results and review a source-aware install plan before Scout changes anything.

+
03 / MAINTAIN

Keep apps current

See upgrades, run them together, and manage pins when a package should stay on a specific version.

+
04 / REMEMBER

See what happened

Review recent runs, save package sets, export visible lists, and opt in to daily update checks.

+
+
+ +
+
+

A QUICK TOUR

A familiar window.
A faster flow.

Move between Search, Installed, and Upgrades with the keyboard. Details stay beside your list, so context is always close.

  • Keyboard shortcuts where you need them
  • Mouse support when you want it
  • COM details with a CLI fallback
Explore the shortcuts
+
RECORDED IN DEMO MODE
WinGet Scout demo showing Search, Installed, and Upgrades; use Play tour to start or pause the animation

Captured from the real application with tuirec. Demo data is used in this recording.

+
+
+ +

GET STARTED

Take Scout for a spin.

Download the portable ZIP for your PC, extract the whole folder, and run wingetscout.exe. No .NET runtime is needed.

01

Choose win-x64 or win-arm64.

02

Extract all files together to a folder you will keep.

03

Open a terminal there and run wingetscout.exe.

The WinGet community package is being prepared. The winget install command will be available after its submission is accepted.

+
+ + + + diff --git a/site/media/scout-discover.png b/site/media/scout-discover.png new file mode 100644 index 0000000..6891d65 Binary files /dev/null and b/site/media/scout-discover.png differ diff --git a/site/media/scout-installed.png b/site/media/scout-installed.png new file mode 100644 index 0000000..934389c Binary files /dev/null and b/site/media/scout-installed.png differ diff --git a/site/media/scout-tour.gif b/site/media/scout-tour.gif new file mode 100644 index 0000000..d52c430 Binary files /dev/null and b/site/media/scout-tour.gif differ diff --git a/site/robots.txt b/site/robots.txt new file mode 100644 index 0000000..712d07b --- /dev/null +++ b/site/robots.txt @@ -0,0 +1,3 @@ +User-agent: * +Allow: / +Sitemap: https://wingetscout.com/sitemap.xml diff --git a/site/sitemap.xml b/site/sitemap.xml new file mode 100644 index 0000000..48bb327 --- /dev/null +++ b/site/sitemap.xml @@ -0,0 +1,5 @@ + + + https://wingetscout.com/ + https://wingetscout.com/docs.html + diff --git a/site/styles.css b/site/styles.css new file mode 100644 index 0000000..1957390 --- /dev/null +++ b/site/styles.css @@ -0,0 +1,16 @@ +:root{font-family:"Segoe UI Variable","Segoe UI",Arial,sans-serif;color:#18304d;background:#f6f9fd;font-synthesis:none;scroll-behavior:smooth} +*{box-sizing:border-box}body{margin:0;min-width:320px;overflow-x:hidden}a{color:inherit;text-decoration:none}button{font:inherit}img{display:block;max-width:100%}.wrap{width:min(1200px,calc(100% - 64px));margin-inline:auto}.skip-link{position:absolute;left:16px;top:-60px;z-index:50;padding:10px 14px;background:#0d64c8;color:white}.skip-link:focus{top:12px}:focus-visible{outline:3px solid #187de5;outline-offset:3px} +.ambient{position:absolute;z-index:-1;pointer-events:none;filter:blur(6px)}.ambient-one{width:70vw;height:800px;top:-360px;right:-10vw;background:radial-gradient(circle,#d0eaff 0%,#eaf5ff 42%,transparent 70%)}.ambient-two{width:600px;height:600px;top:300px;left:-350px;background:radial-gradient(circle,#e4e1ff 0%,transparent 66%)} +.site-header{height:86px;display:flex;align-items:center;gap:32px;border-bottom:1px solid #d9e5f2;position:relative;z-index:5}.brand{display:inline-flex;align-items:center;gap:11px;white-space:nowrap;font-size:19px;letter-spacing:-.045em;font-weight:500;color:#183a60}.brand strong{font-weight:750}.brand-icon{width:31px;height:31px;display:grid;grid-template-columns:1fr 1fr;gap:3px;transform:skewY(-5deg)}.brand-icon i{background:#1975d2;border-radius:2px}.brand-icon i:nth-child(2){background:#4fa6ed}.brand-icon i:nth-child(3){background:#338de0}.brand-icon i:nth-child(4){background:#0f5eb7}.site-header nav{display:flex;align-items:center;gap:30px;margin:auto;font-size:14px;font-weight:600;color:#5c7088}.site-header nav a:hover,.site-header nav .nav-active{color:#0969c9}.nav-repo span{margin-left:3px}.header-download{padding:11px 18px;border:1px solid #c9d9ec;border-radius:8px;background:#fff9;font-size:13px;font-weight:700;color:#1b5da4;box-shadow:0 4px 18px #2364a00a}.header-download:hover{background:#fff;border-color:#77aadd} +.hero{min-height:638px;padding-top:92px;padding-bottom:76px;display:grid;grid-template-columns:.9fr 1.1fr;align-items:center;gap:48px}.eyebrow,.section-kicker{display:flex;align-items:center;gap:10px;margin:0 0 24px;color:#276bae;letter-spacing:.14em;font-size:11px;font-weight:800}.eyebrow-square{width:9px;height:9px;background:#2488e1;box-shadow:0 0 0 4px #cfe7ff}.hero h1{font-size:clamp(46px,5.1vw,74px);letter-spacing:-.066em;line-height:1.06;font-weight:710;margin:0 0 25px;color:#152a43}.hero h1 span{color:#1877d3}.lead{font-size:18px;line-height:1.65;color:#536b86;max-width:535px;margin:0 0 31px}.hero-actions,.install-buttons{display:flex;flex-wrap:wrap;gap:11px}.button{display:inline-flex;align-items:center;justify-content:center;gap:15px;min-height:48px;padding:12px 19px;border-radius:9px;font-size:14px;font-weight:700;transition:transform .2s,box-shadow .2s,background .2s}.button:hover{transform:translateY(-2px)}.button-primary{background:#126fcb;color:white;box-shadow:0 9px 22px #146bc036}.button-primary:hover{background:#095fb2;box-shadow:0 13px 26px #146bc045}.button-secondary{color:#255d96;background:#fff;border:1px solid #c6d9eb}.button-secondary:hover{background:#eef7ff}.hero-facts{display:flex;flex-wrap:wrap;gap:21px;margin-top:39px;font-size:12px;color:#4b6279;font-weight:650}.hero-facts b{color:#1266b1;font-size:11px;margin-right:5px} +.hero-stage{position:relative;min-width:0;padding:26px 0 42px}.stage-halo{position:absolute;inset:-50px -55px -10px;background:radial-gradient(ellipse at 57% 48%,#b6dcff 0%,#e4f2ff 45%,transparent 71%);z-index:-1}.terminal-card{background:#fff;border:1px solid #b5cde5;border-radius:15px;overflow:hidden;box-shadow:0 30px 62px #265b8a33,0 3px 10px #1b52801a;transform:perspective(1400px) rotateY(-3deg) rotateX(1deg)}.terminal-chrome{height:38px;background:#f4f7fc;border-bottom:1px solid #d7e3ee;display:flex;align-items:center;gap:10px;padding:0 15px;font-size:11px;color:#4b657f}.terminal-chrome em{font-style:normal;padding:0 3px;color:#9aabbc}.chrome-icon{color:#1774ce;font-weight:900;font-size:16px}.chrome-controls{display:flex;gap:16px;margin-left:auto;align-items:center}.chrome-controls i{width:9px;height:1px;background:#7890a6}.chrome-controls i:nth-child(2){height:8px;border:1px solid #7890a6;background:none}.chrome-controls i:nth-child(3){transform:rotate(45deg);position:relative}.chrome-controls i:nth-child(3):after{content:"";position:absolute;width:9px;height:1px;background:#7890a6;transform:rotate(90deg);left:0}.terminal-card img{width:100%;height:auto;aspect-ratio:915/595;object-fit:cover}.floating-tile{position:absolute;display:flex;align-items:center;gap:12px;padding:12px 16px;border:1px solid #dce9f4;border-radius:11px;background:#fffd;backdrop-filter:blur(12px);box-shadow:0 12px 28px #204f7b2b;font-size:11px;color:#57718d}.floating-tile strong,.floating-tile small{display:block}.floating-tile strong{font-size:12px;color:#24405e;margin-bottom:3px}.floating-tile-one{left:-30px;bottom:7px}.floating-tile-two{right:-23px;top:3px}.tile-mark{height:27px;width:27px;display:grid;place-items:center;border-radius:7px;background:#dbf6e9;color:#179759;font-size:16px;font-weight:800}.tile-blue{background:#d9eeff;color:#1676c9} +.ribbon{border-top:1px solid #deebf7;border-bottom:1px solid #deebf7;background:#eaf3fc9c}.ribbon-inner{display:flex;justify-content:space-between;gap:16px;padding:18px 0;color:#456887;font-size:11px;letter-spacing:.21em;font-weight:800}.ribbon-inner span+span:before{content:"+";color:#9bc5ec;margin-right:clamp(20px,5vw,72px)} +.section{padding-top:108px;padding-bottom:110px}.section-heading{display:flex;justify-content:space-between;gap:60px;align-items:end;margin-bottom:37px}.section-heading .section-kicker{margin-bottom:11px}.section-heading h2,.preview-copy h2,.install-copy h2{margin:0;color:#172e49;letter-spacing:-.05em;font-size:clamp(32px,3.4vw,47px);line-height:1.15}.section-heading>p{max-width:365px;color:#526c85;font-size:15px;line-height:1.7;margin:0 0 4px}.feature-grid{display:grid;grid-template-columns:repeat(4,1fr);gap:14px}.feature-card{min-height:280px;display:flex;flex-direction:column;padding:25px 24px;background:#fff;border:1px solid #dfe9f3;border-radius:12px;box-shadow:0 10px 25px #1f5e9809}.feature-card-blue{background:linear-gradient(155deg,#e7f3ff,#fff 85%);border-color:#bfdaf3}.feature-icon{height:46px;width:46px;display:grid;place-items:center;border-radius:10px;background:#edf6ff;color:#1776cd;font-size:24px;margin-bottom:auto}.feature-number{margin-top:27px;font-size:10px;font-weight:800;color:#1764a7;letter-spacing:.13em}.feature-card h3{font-size:18px;letter-spacing:-.03em;margin:10px 0;color:#233b56}.feature-card p{margin:0;color:#536b82;font-size:13px;line-height:1.58} +.preview-section{padding:98px 0;background:linear-gradient(115deg,#eaf3fc,#f7faff 75%);border-block:1px solid #dceaf7}.preview-layout{display:grid;grid-template-columns:.66fr 1fr;gap:70px;align-items:center}.preview-copy .section-kicker{margin-bottom:18px}.preview-copy p:not(.section-kicker){color:#4f6b84;line-height:1.72;font-size:15px;max-width:420px}.check-list{list-style:none;margin:26px 0 30px;padding:0;display:grid;gap:12px;color:#355576;font-size:14px;font-weight:600}.check-list li:before{content:"✓";display:inline-grid;place-items:center;width:19px;height:19px;margin-right:11px;border-radius:50%;background:#d5ebfe;color:#0e76ce;font-size:11px}.text-link{font-size:14px;color:#126ebf;font-weight:750;border-bottom:1px solid #9dc8ee;padding-bottom:5px}.text-link:hover{color:#084e91}.preview-media{padding:8px;background:#fff;border:1px solid #cbdced;border-radius:12px;box-shadow:0 21px 48px #376da624}.preview-label{display:flex;align-items:center;gap:8px;padding:9px 11px 14px;color:#1b669e;font-size:10px;letter-spacing:.12em;font-weight:800}.preview-label button{margin-left:auto;border:1px solid #bcd6ec;border-radius:6px;background:#eef7ff;color:#185f9c;padding:6px 10px;font-size:11px;font-weight:700;letter-spacing:0;cursor:pointer}.preview-label button:hover{background:#ddecfa}.live-dot{width:7px;height:7px;border-radius:50%;background:#33aa76}.preview-media img{width:100%;border-radius:3px;background:#0d1016}.preview-media p{margin:8px 6px 3px;font-size:11px;color:#526b82}.preview-media p a{text-decoration:underline} +.install-section{padding-top:105px;padding-bottom:92px}.install-panel{display:grid;grid-template-columns:1fr .64fr;gap:68px;padding:58px 64px;border-radius:15px;border:1px solid #b8d5ef;background:linear-gradient(112deg,#dceefe 0%,#eef7ff 57%,#fff 100%);box-shadow:0 18px 45px #3e77a912}.install-copy .section-kicker{margin-bottom:14px}.install-copy>p:not(.section-kicker){color:#5d7691;line-height:1.7;max-width:530px}.install-copy code,.install-footnote code{font-family:Consolas,monospace;font-size:.93em;color:#135ea7}.install-buttons{margin-top:27px}.install-notes{display:grid;gap:0}.install-notes>div{display:flex;gap:16px;align-items:center;padding:16px 0;border-bottom:1px solid #cbdff1}.install-notes>div:first-child{padding-top:0}.install-notes>div:last-child{border-bottom:0}.install-notes span{color:#1680d8;font-size:14px;font-weight:800}.install-notes p{margin:0;color:#536e89;font-size:13px;line-height:1.5}.install-notes strong{color:#204364}.install-footnote{margin:20px 3px 0;color:#4e687f;font-size:12px;line-height:1.6} +.site-footer{background:#132c47;color:#d7e6f4}.footer-inner{display:flex;justify-content:space-between;gap:30px;padding-top:48px;padding-bottom:44px}.footer-brand{color:white}.footer-inner p{color:#9bb3c9;font-size:12px;margin:14px 0 0}.footer-links{display:flex;flex-wrap:wrap;align-content:start;gap:18px 25px;font-size:13px}.footer-links a:hover{color:#83c5ff}.footer-bottom{display:flex;justify-content:space-between;gap:20px;padding:16px 0 25px;border-top:1px solid #29445f;color:#8fa8bf;font-size:11px} +.docs-page{background:#f8fbfe}.docs-hero{padding:75px 0 70px;background:linear-gradient(115deg,#e4f2ff,#f5faff);border-bottom:1px solid #dceaf5}.docs-hero .section-kicker{margin-bottom:15px}.docs-hero h1{margin:0 0 12px;font-size:clamp(38px,5vw,60px);letter-spacing:-.055em;color:#182e48}.docs-hero p:last-child{margin:0;color:#4f6a84;font-size:17px}.docs-layout{display:grid;grid-template-columns:220px minmax(0,800px);gap:74px;align-items:start;padding-top:65px;padding-bottom:110px}.docs-sidebar{position:sticky;top:24px;display:grid;gap:3px}.docs-sidebar p{font-size:10px;letter-spacing:.16em;color:#536c84;font-weight:800;margin:0 0 13px}.docs-sidebar a{border-left:2px solid transparent;padding:10px 13px;color:#57738f;font-size:13px;font-weight:600}.docs-sidebar a:hover,.docs-sidebar a.active{color:#096cca;background:#eaf4ff;border-color:#1679d6}.docs-content section{scroll-margin-top:35px;padding-bottom:54px;margin-bottom:48px;border-bottom:1px solid #dfeaf4}.docs-content section:last-child{border:0;margin:0;padding:0}.docs-content .section-kicker{margin-bottom:13px}.docs-content h2{margin:0 0 17px;font-size:35px;letter-spacing:-.045em;color:#1b344f}.docs-content h3{font-size:17px;color:#294866;margin:26px 0 8px}.docs-content p,.docs-content li{font-size:15px;line-height:1.76;color:#526d87}.docs-content p{margin:13px 0}.docs-content a{color:#126ebf;text-decoration:underline;text-underline-offset:3px}.docs-content a:hover{color:#074c8c}.docs-content ol{padding-left:23px}.docs-content li{padding-left:6px;margin:8px 0}.docs-content code{background:#edf4fb;border:1px solid #d9e8f5;border-radius:4px;padding:2px 5px;font:13px Consolas,"Cascadia Code",monospace;color:#145991;word-break:break-word}.download-grid{display:grid;grid-template-columns:1fr 1fr;gap:12px;margin:23px 0}.download-grid a{display:flex;flex-direction:column;gap:7px;padding:19px;border:1px solid #cce0f3;border-radius:9px;background:white;text-decoration:none;box-shadow:0 6px 18px #1a5a950a}.download-grid a:hover{border-color:#69a9e5;background:#f4faff}.download-grid strong{font-size:15px;color:#2a4e70}.download-grid span{font-size:12px;color:#506c84}.callout{border-left:3px solid #2786d8;background:#eaf5ff;padding:17px 20px;margin:24px 0;color:#4d6d89;font-size:14px;line-height:1.65}.callout strong{color:#205b8e}.docs-image{padding:8px;border:1px solid #d4e2f0;background:white;border-radius:10px;margin:26px 0;box-shadow:0 13px 32px #376da616}.docs-image img{width:100%}.docs-image span{display:block;color:#526c84;font-size:11px;padding:8px 6px 3px}.shortcut-table{border:1px solid #dce8f3;border-radius:9px;overflow:hidden;margin:24px 0}.shortcut-table>div{display:flex;align-items:center;gap:7px;min-height:48px;padding:9px 14px;background:white;border-bottom:1px solid #e7eff7}.shortcut-table>div:nth-child(even){background:#f3f8fc}.shortcut-table>div:last-child{border:0}.shortcut-table span{margin-left:auto;width:70%;color:#526c86;font-size:13px}.docs-content kbd{font:12px Consolas,monospace;color:#1d5e96;background:#eaf3fb;border:1px solid #ccdfef;border-radius:4px;padding:3px 6px;white-space:nowrap}.faq h3:first-child{margin-top:23px}.faq p{margin-top:0} +@media(max-width:1100px){.hero{gap:18px}.floating-tile-two{right:0}.feature-grid{grid-template-columns:repeat(2,1fr)}.feature-card{min-height:230px}.preview-layout{gap:32px}.install-panel{gap:30px;padding:46px}.site-header nav{gap:17px}} +@media(max-width:760px){.wrap{width:min(100% - 32px,560px)}.site-header{height:72px;gap:12px}.site-header nav{display:none}.header-download{margin-left:auto}.hero{display:block;padding-top:68px;padding-bottom:55px}.hero h1{font-size:clamp(42px,9vw,63px)}.lead{font-size:16px}.hero-stage{margin-top:55px}.floating-tile-one{left:0;bottom:4px}.floating-tile-two{right:-4px;top:-16px}.ribbon-inner{overflow:hidden;white-space:nowrap;font-size:9px}.ribbon-inner span+span:before{margin-right:15px}.section{padding-top:72px;padding-bottom:75px}.section-heading{display:block}.section-heading>p{margin-top:15px}.feature-grid{gap:10px}.preview-section{padding:70px 0}.preview-layout{display:block}.preview-copy{margin-bottom:36px}.install-panel{display:block;padding:35px 26px}.install-notes{margin-top:33px}.docs-hero{padding:56px 0}.docs-layout{display:block;padding-top:40px}.docs-sidebar{position:static;display:flex;overflow-x:auto;gap:4px;margin-bottom:45px;border-bottom:1px solid #dae8f4}.docs-sidebar p{display:none}.docs-sidebar a{white-space:nowrap;border-left:0;border-bottom:2px solid transparent}.docs-content h2{font-size:30px}.footer-inner{display:block}.footer-links{margin-top:32px}.footer-bottom{display:block}.footer-bottom span{display:block;margin:5px 0}} +@media(max-width:480px){.brand{font-size:17px}.brand-icon{width:26px;height:26px}.header-download{padding:9px 11px;font-size:12px}.hero-facts{gap:11px}.floating-tile{display:none}.feature-grid{grid-template-columns:1fr}.feature-card{min-height:220px}.download-grid{grid-template-columns:1fr}.shortcut-table span{width:62%}.preview-label span:last-child{display:none}.footer-links{gap:12px 18px}} +@media(prefers-reduced-motion:reduce){:root{scroll-behavior:auto}.button{transition:none}}