From 4cac48a0a12428301c0f3e79548611966482c9b3 Mon Sep 17 00:00:00 2001 From: Xianpeng Shen Date: Thu, 24 Sep 2026 12:59:50 +0300 Subject: [PATCH 1/2] feat: redesign the landing page and brand The home page now says what Commit Check does in its first line and walks a visitor to the entry point that fits them: CLI, pre-commit hook, GitHub Action, GitHub App or MCP server. Two animated demos show the tool at work: a commit-msg hook rejecting and then accepting a message, and the GitHub Action's PR comment, job summary, PR-title, message and branch checks. Both are plain HTML driven by a small timeline script, rest on their final frame without JavaScript or under reduced motion, and pause while off screen. The brand is one system across the site: a new mark (a commit on a history line, ticked), an Ink header and footer on every page, Signal Blue kept at the README badge's #2c9ccd, Bricolage Grotesque for headings and Instrument Sans for text. Favicon, touch icon and social cards follow it. The Team plan is shown as coming soon, on the home page and in the App guide, since it is not on sale yet. --- docs/assets/extra.css | 73 +- docs/assets/favicon.svg | 12 +- docs/assets/logo.svg | 19 +- docs/guides/github-app.md | 4 +- docs/index.md | 772 +++++---- docs/javascripts/landing.js | 219 +++ docs/overrides/main.html | 3 + docs/static/apple-touch-icon.png | Bin 10249 -> 3170 bytes docs/static/favicon.ico | Bin 2863 -> 15086 bytes docs/stylesheets/landing.css | 2549 ++++++++++++++++++++++++++++-- mkdocs.yml | 13 +- 11 files changed, 3141 insertions(+), 523 deletions(-) create mode 100644 docs/javascripts/landing.js diff --git a/docs/assets/extra.css b/docs/assets/extra.css index 7b2b300..fd32020 100644 --- a/docs/assets/extra.css +++ b/docs/assets/extra.css @@ -1,23 +1,42 @@ /* Brand palette ----------------------------------------------------------- - Material's "custom" primary/accent read these variables. */ + Material's "custom" primary/accent read these variables. + + Ink is the brand's dark and Signal Blue its accent. The header and footer + are Ink on every page, so the docs and the landing page share one frame; + Signal Blue is the same #2c9ccd the README badge has always used, so badges + already in the wild still match. */ :root { + --cc-ink: #0b1620; + --cc-ink-raised: #13212c; --cc-brand: #2c9ccd; --cc-brand-dark: #1e85a8; --cc-brand-light: #5bb3d9; - /* The brand blue only reaches 4.2:1 on white; links need 4.5:1. */ + /* The brand blue only reaches 3.1:1 on white; links need 4.5:1. */ --cc-link: #176b89; - /* The hero sits on brand blue, so its button is white rather than branded. */ - --cc-hero-button-bg: #ffffff; - - --md-primary-fg-color: var(--cc-brand); - --md-primary-fg-color--light: var(--cc-brand-light); - --md-primary-fg-color--dark: var(--cc-brand-dark); + --md-primary-fg-color: var(--cc-ink); + --md-primary-fg-color--light: var(--cc-ink-raised); + --md-primary-fg-color--dark: #060d13; --md-accent-fg-color: var(--cc-brand-dark); + + /* A literal stack: the theme defines --md-text-font-family below :root, + so a var() of it here would resolve to nothing. */ + --cc-display-font: "Bricolage Grotesque", "Instrument Sans", -apple-system, "Segoe UI", sans-serif; +} + +/* The theme sets the footer colours per scheme, so they are overridden per + scheme too; on :root alone they would lose to the theme's own. */ +[data-md-color-scheme="default"], +[data-md-color-scheme="slate"] { + --md-footer-bg-color: #081119; + --md-footer-bg-color--dark: #060d13; } [data-md-color-scheme="slate"] { - --md-primary-fg-color: var(--cc-brand); + /* Material tints every slate grey from one hue; 205 is Ink's. */ + --md-hue: 205; + --md-default-bg-color: #0e1a23; + --md-primary-fg-color: var(--cc-ink); --md-accent-fg-color: var(--cc-brand-light); --md-typeset-a-color: var(--cc-brand-light); } @@ -26,6 +45,42 @@ --md-typeset-a-color: var(--cc-link); } +/* A primary button is Signal Blue with Ink text: white on this blue is 3.1:1, + under the 4.5:1 a button label needs; Ink on it is 5.9:1. */ +.md-typeset .md-button--primary { + background-color: var(--cc-brand); + border-color: var(--cc-brand); + color: var(--cc-ink); +} + +.md-typeset .md-button--primary:focus, +.md-typeset .md-button--primary:hover { + background-color: var(--cc-brand-light); + border-color: var(--cc-brand-light); + color: var(--cc-ink); +} + +/* Display face for page titles, section headings and the site name. Body text + stays in the theme's text font. */ +.md-typeset h1, +.md-typeset h2 { + font-family: var(--cc-display-font); + font-weight: 700; + letter-spacing: -0.02em; +} + +/* The theme draws page titles in a light grey at weight 300; in the display + face they read as headings only at full strength. */ +.md-typeset h1 { + color: var(--md-default-fg-color); +} + +.md-header__title { + font-family: var(--cc-display-font); + font-weight: 700; + letter-spacing: -0.01em; +} + /* Feature grid: uses Material's card grid, tightened up a little. */ .md-typeset .grid.cards > ul > li { border-radius: 0.4rem; diff --git a/docs/assets/favicon.svg b/docs/assets/favicon.svg index 896e52a..13f3d13 100644 --- a/docs/assets/favicon.svg +++ b/docs/assets/favicon.svg @@ -1,8 +1,6 @@ - - - - - - - + + + + diff --git a/docs/assets/logo.svg b/docs/assets/logo.svg index a4d1132..1f13a0f 100644 --- a/docs/assets/logo.svg +++ b/docs/assets/logo.svg @@ -1,13 +1,8 @@ - - - - - - - - - - - - + + + + + + diff --git a/docs/guides/github-app.md b/docs/guides/github-app.md index 584e5cd..4241e0b 100644 --- a/docs/guides/github-app.md +++ b/docs/guides/github-app.md @@ -181,13 +181,13 @@ without a wall of red. Public repositories are free on any account, and everything on a personal account is free, and both stay free. Only an organization's private -repositories are paid: +repositories will be paid, on a Team plan that is not on sale yet: | Plan | Price | Covers | |---|---|---| | Open Source | Free | Public repositories, on any account | | Personal | Free | Private repositories on a personal account | -| Team | $19 / month | Private repositories in an organization, 14-day free trial | +| Team | Coming soon | Private repositories in an organization | The Team plan is not tiered by seat count. Twenty of you and twenty thousand of you pay the same, so growing the team never changes the bill. diff --git a/docs/index.md b/docs/index.md index 4a0ae50..c4ca2b5 100644 --- a/docs/index.md +++ b/docs/index.md @@ -4,320 +4,453 @@ hide: - toc template: landing.html title: Commit Check -description: One config file enforced in your commit-msg hook, in CI, on every pull request and in your AI agent. Rules for commit messages, branch names, author identity and signoff. +description: Catch bad commits before they merge. Messages, branches, authors and AI attribution, checked from your commit hook to every pull request. --- -
-
- -# One config file. Every place your team commits. - -Laptop, CI, pull request, AI agent — the same `cchk.toml`, the same rules, -the same diagnostics, with a fix you can paste. - -[Get started :octicons-arrow-right-24:](getting-started.md){ .md-button .md-button--primary } -[Browse the rules](rules.md){ .md-button } - -
-
- -```console -$ echo 'Fix: add streaming support' | commit-check --message -CC001 message check failed ==> Fix: add streaming support -The commit message should follow Conventional Commits. See https://www.conventionalcommits.org -Suggest: Use "fix: add streaming support" -Docs: https://commit-check.com/rules/#cc001 -``` - -When the correction is unambiguous, it hands you the line. - -
-
- -
- -**Commit Check runs in repositories across these organizations, and in -[many more](https://github.com/commit-check/commit-check-action/network/dependents).** - -
-
- Apache - Apache -
-
- Discovery Unicamp - Discovery Unicamp -
-
- Texas Instruments - Texas Instruments -
-
- OpenCADC - OpenCADC -
-
- Extrawest - Extrawest -
-
- Chainlift - Chainlift -
-
- Mila - Mila -
-
- RLinf - RLinf -
-
- Istio Ecosystem - Istio Ecosystem -
-
- Juniper Networks - Juniper Networks -
-
- French National Parks - French National Parks -
-
- OpenDriveLab - OpenDriveLab -
-
- UT Austin RobIn - UT Austin RobIn -
-
- WorldArena2 - WorldArena2 -
-
- moniqo - moniqo -
-
- elu mobility - elu mobility -
-
- Open Energy Platform - Open Energy Platform -
-
- Collective - Collective -
-
- -
- -## Why check commit metadata at all - -
- -- __Changelog tools have nothing to group by__ - - --- - - `git-cliff` and `semantic-release` read the `type:` prefix on the subject - line to decide what a commit was. Without a consistent subject there is - nothing to read, and the release notes get written by hand from `git log`. - -- __`git bisect` stops at a merge commit__ - - --- - - When the first bad commit is a merge, the change is in one of two parents - or in the conflict resolution. Bisect cannot narrow it any further. - -- __The author is `ec2-user`__ - - --- - - A build box with no `user.name` set writes itself into the history. - `git log --author` finds the commit; there is no person on the other end - of it. - -- __A DCO check fails on a branch you already wrote__ - - --- - - `Signed-off-by` costs one `-s` at commit time. Adding it afterwards means - `git rebase --signoff` across the whole branch and a force-push. - -
- -None of these are caught by a linter, a type checker or a test suite. They are -caught in review — which means inconsistently, and after the work is done. - -The check that runs in CI is the same one that runs in your `commit-msg` hook. -Fixing a subject line at commit time costs a second; fixing it after CI costs a -full run and a force-push. - -## What your team actually sees - -On a pull request, every finding carries a rule ID, the value that failed, and -what to do about it — in the job summary, as annotations on the changed files, -and as a single comment that is edited in place rather than added to. - -| Scope | Checked value | Failed checks | -|---|---|---| -| Commit 2/2 (5584f46) | `bad msg` | CC001 message | -| Branch | `Feature/Add-Login` | CC201 branch | - -```text -Commit message - ✔ PR title (feat: add login page) - ✔ Commit 1/2 (d87faca) (feat: add login page) - ✖ Commit 2/2 (5584f46) (1 failure) - CC001 message - value: bad msg - The commit message should follow Conventional Commits. - Suggest: Use (): -Branch - ✖ Branch (1 failure) - CC201 branch - value: Feature/Add-Login - The branch should follow Conventional Branch. - Suggest: Rename the branch to "feature/Add-Login" (git branch -m feature/Add-Login) - Fix: feature/Add-Login -``` - -And in the merge box, where the decision actually gets made: - -
-![The Commit Check check run in a pull request merge box, reporting 2 of 4 checks failed](assets/merge-box.png){ loading=lazy } -
The hosted GitHub App reports one check run per commit. The title -names what failed, so nobody opens Details to learn whether it was the message, -the branch or an author email.
-
- -
-![A terminal recording: a commit message and a branch name are rejected, then accepted once corrected](assets/demo.gif){ .cc-motion loading=lazy } -![The same terminal at the end of the recording, both checks having run](assets/demo-poster.png){ .cc-still loading=lazy } -
The same engine on the command line. The recording is replaced by a -still frame when your system asks for reduced motion.
+ + +
+ +
+
+

Open source · MIT licensed

+

Catch bad commits before they merge.

+

Commit Check validates commit messages, branch names, authors and sign-offs against one cchk.toml — in your commit hook, in CI, on every pull request and inside your AI agent. When the fix is obvious, it hands you the line.

+
+Add to GitHub Actions +
pip install commit-check
+
+
+ +
+ +
+
❯ git commit -m "Fix: add streaming support"
+
check commit messageFailed
+
- hook id: check-message
+
- exit code: 1
+
Commit rejected by Commit-Check.
+
CC001 message check failed ==> Fix: add streaming support
+
The commit message should follow Conventional Commits. See https://www.conventionalcommits.org
+
Suggest: Use "fix: add streaming support"
+
Docs: https://commit-check.com/rules/#cc001
+
❯ git commit -m "fix: add streaming support"
+
check commit messagePassed
+
[feature/streaming 77663ff] fix: add streaming support
+
- -## Start with two commands - -```console -$ pip install commit-check -$ commit-check --message --branch -``` - -No configuration file needed to start: the defaults check Conventional Commits, -Conventional Branch and subject length, and you tighten them when you are ready. Every release -carries a signed [build provenance attestation](https://docs.github.com/en/actions/concepts/security/artifact-attestations), -so you can verify an artifact came from this repository's pipeline before you -install it. - -## Pick where it runs - -One policy engine, five places to enforce it. Every one of them reads the same -`cchk.toml`. - -
- -- __Command line__ - - --- - - The engine itself. Any forge, any CI, plus a JSON mode and a Python API for - scripts and agents. - - [:octicons-arrow-right-24: Getting started](getting-started.md) - -- __pre-commit hook__ - - --- - - It rejects a bad commit before Git records it. - Opt-in by nature, so pair it with one of the enforced surfaces. - - [:octicons-arrow-right-24: Guide](guides/pre-commit.md) - -- __GitHub Action__ - - --- - - Runs in CI whether or not the hook ran. Make it a required check and a - violation cannot merge, with per-rule outputs later steps can gate on. - - [:octicons-arrow-right-24: Guide](guides/github-actions.md) - -- __GitHub App__ - - --- - - No workflow file and no CI minutes. Install it once and every push and pull - request gets a check run. - - [:octicons-arrow-right-24: Guide](guides/github-app.md) - -- __MCP server__ - - --- - - The validations as structured tools, so an AI coding agent checks its own - commit before it writes it. - - [:octicons-arrow-right-24: Guide](guides/mcp.md) - +
+
    +
  • MIT licensed, no account needed
  • +
  • Python 3.10 – 3.14 on Linux, macOS and Windows
  • +
  • Signed build provenance on every release
  • +
  • CLI and hook work with any Git host
  • +
+
+ +
+ +
+ +
+
+
+

commit-check-action

+

Every pull request, checked and explained.

+

The Action reports where your reviewers already look — a PR comment, the job summary, inline annotations — and exposes a JSON result the next step can gate on.

+
+ +
+
+ + + + +
-## Pricing - -The CLI, the pre-commit hook, the GitHub Action and the MCP server are MIT -licensed — no account, no limits, nothing to buy. The plans below are for the -hosted GitHub App, the one surface we run for you. - -
- -- __Open Source__ · Free - - --- - - Public repositories, on any account. - -- __Personal__ · Free - - --- - - Private repositories on a personal account. - -- __Team__ · $19 / month +
+

One report on the pull request, edited in place on every push.Turn it on with pr-comments: true

+
+ +
+

feat: add login page #128Open

+

alex wants to merge 3 commits into main from feature/add-login

+ +
+
+
+ +
+

github-actionsbot commentededited

+
+

Commit Check

+
+

2 of 5 checks failed

+ + + + + + +
ScopeChecked valueFailed checks
Commit 2/3 (5584f46)bad msgCC001 message
Commit 3/3 (37d6def)Fix: handle empty passwordCC001 message
+

▸ Show all 5 checks

+
+
+

All 5 checks passed

+

▸ Show all 5 checks

+
+

Rules reference

+
+
+
+

alex force-pushed feature/add-login, rewording 2 commits

+

The same comment was updated. No new comment, no noise in the thread.

+
+
+
- --- +
+

The full breakdown lands in the workflow run: each check, the value, the fix.Turn it on with job-summary: true

+
+ +
+ +
+

Triggered via pull_requestalex pushed 5584f46

StatusFailure

Total duration14s

+
+

commit-check summary

+
+

Commit Check

+

2 of 5 checks failed

+

▾ Show all 5 checks

+
+
Commit message
+
✔ PR title (feat: add login page)
+
✔ Commit 1/3 (d87faca) (feat: add login page)
+
✖ Commit 2/3 (5584f46) (1 failure)
+
CC001 message
+
value: bad msg
+
Suggest: Use <type>(<scope>): <description>, where <type> is one of: feat, fix, docs, style, refactor, test, chore, perf, build, ci
+
✖ Commit 3/3 (37d6def) (1 failure)
+
CC001 message
+
value: Fix: handle empty password
+
Fix: fix: handle empty password
+
Branch
+
✔ Branch (feature/add-login)
+
+
+
+
+
+
+
- Private repositories in an organization, however many of you there are. - 14-day free trial. +
+

Squash merges turn the PR title into the commit, so the title is checked too.Turn it on with pr-title: true

+
+ +
+

add login page #128Open

+

feat: add login pageSave

+

feat: add login page #128Open

+

alex wants to merge 3 commits into main from feature/add-login

+ +
+
+
+

Checks

+
+ +

Commit Check / commit-check (pull_request)CC001 message · PR title (add login page)Re-running on the edited title…✔ PR title (feat: add login page)

+
+
+
+

Squash and merge creates this commit on main:

+

feat: add login page (#128)

+
Squash and mergeSquash and mergeA required check has not passedAll checks have passed
+
+
+
+
+
+

Every commit in the pull request, checked against Conventional Commits.On by default: message: true

+
+ +
+

feat: add login page #128Open

+

alex wants to merge 3 commits into main from feature/add-login

+ +
+
+
+

Commit message

+
feat: add login paged87faca
+
bad msg5584f46
+
Fix: handle empty password37d6def
+
+
+

Annotations 2 errors

+

CC001 message

Commit 2/3 (5584f46): The commit message should follow Conventional Commits.
value: bad msg
Suggest: Use <type>(<scope>): <description>, where <type> is one of: feat, fix, docs, …

+

CC001 message

Commit 3/3 (37d6def): The commit message should follow Conventional Commits.
value: Fix: handle empty password
Fix: fix: handle empty password

+
+
+
-[:octicons-arrow-right-24: Install the App](https://github.com/apps/commit-check) +
+

Branch names checked against Conventional Branch, with the rename to run.On by default: branch: true

+
+ +
+

feat: add login page #131Open

+

alex wants to merge 1 commit into main from Feature/Add-Login

+ +
+
+
+
+

BranchFeature/Add-Login

+

CC201 The branch should follow Conventional Branch.

value: Feature/Add-Login
Fix: feature/Add-Login

+
+
+
❯ git branch -m feature/Add-Login
+
❯ commit-check --branch && echo passed
+
passed
+
+
+
+

Conventional Branch types

+
  • feature/
  • bugfix/
  • hotfix/
  • release/
  • chore/
+

main and master always pass. Add your own types in cchk.toml.

+
+
+
+
+
-Nothing is blocked while you try it. Without a config file the App reports its -findings but leaves the check run neutral, and it never rejects a push — the -only way Commit Check blocks a merge is if you make it a required check -yourself. +
+
+

.github/workflows/commit-check.yml

+
on: + pull_request: + types: [opened, synchronize, reopened, edited] +permissions: + contents: read + pull-requests: write +jobs: + commit-check: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v7 + with: + fetch-depth: 0 + - uses: commit-check/commit-check-action@v2 + with: + pr-title: true + pr-comments: true
+
+
+

Make it a required check

Branch protection does the rest: a violating commit cannot merge, however it was made.

+

Verified before it installs

The Action checks the wheel's signed build provenance and fails the step if it does not match.

+

Try it without blocking anyone

dry-run: true reports every finding as a warning and always exits 0.

+ +
+
+
+
+ +
+
+
+
+

What it checks

+

Rules grouped by what they protect.

+

A few are on by default. Turn on what your project needs, rule by rule, or set any rule to warn instead of fail.

+
+Browse all rules → +
+
    +
  • +

    Commit messagesCC001–CC012

    +

    Conventional Commits, subject length, imperative mood, sign-off, and merge, revert, fixup or WIP commits.

    +

    Rejected: Fix: add streaming support

    +

    Accepted: fix: add streaming support

    +
  • +
  • +

    Branch namesCC201–CC202

    +

    Conventional Branch naming, and whether the branch is rebased onto its target.

    +

    Rejected: my-new-thing

    +

    Accepted: feature/streaming-support

    +
  • +
  • +

    AI attributionCC013–CC016

    +

    Spots the trailers Claude Code, Cursor and others add. Forbid them, or require a disclosure instead.

    +

    Rejected: Co-authored-by: Claude

    +

    Accepted: Assisted-by: Claude

    +
  • +
  • +

    Author identityCC101–CC102

    +

    Names and emails that match the patterns you set — here author_email_pattern = "@acme\.dev$" — so a build box cannot write itself into the history.

    +

    Rejected: ec2-user <root@ip-10-0-0-12>

    +

    Accepted: Jane Doe <jane@acme.dev>

    +
  • +
  • +

    Pushes and filesCC301–CC304

    +

    No force-pushes, no oversized files, no forbidden paths — caught at pre-push, before they leave the laptop.

    +

    Rejected: git push --force origin main

    +

    Rejected: assets/model.bin, 240 MB

    +
  • +
  • +

    Across an organization

    +

    One shared policy for every repository. Each repo inherits it and overrides only what differs.

    +

    inherit_from = "github:acme/.github:cchk.toml"

    +Organization guide → +
  • +
+
+
+ +
+
+
+

Every finding

+

Not just “failed”. What failed, and the fix.

+
    +
  1. A stable rule ID that links to its documentation.
  2. +
  3. The exact value that was checked.
  4. +
  5. Why it failed, in one sentence.
  6. +
  7. A fix you can paste, whenever the correction is unambiguous.
  8. +
+
+ +
+

$ echo "Fix: add streaming support" | commit-check -m

+

1CC001 message check failed ==>

+

2Fix: add streaming support

+

3The commit message should follow Conventional Commits.

+

4Suggest: Use "fix: add streaming support"

+

Docs: https://commit-check.com/rules/#cc001

+
+
+
+ +
+
+
    +
  • 1.6M+downloads on PyPI
  • +
  • 160+repositories run the Action
  • +
  • Apacheallowlists the Action for its projects' CI
  • +
+

In the CI ofApacheTexas InstrumentsMilaIstio EcosystemOpenDriveLaband more →

+
+
+ +
+
+
+

Pricing

+

Free for open source. Team plan, soon.

+

The CLI, the hook, the Action and the MCP server are MIT licensed — nothing to buy. Plans only cover the hosted GitHub App, the one surface we run for you.

+
+ +

Nothing is blocked while you try it: without a config file the App reports its findings but leaves the check run neutral. GitHub's own commit-metadata rules sit behind its Enterprise plan — the arithmetic, and the caveats →

+
+
-GitHub can enforce some of the same policies natively, but the commit-metadata -rules sit behind its Enterprise plan. For a twenty-person team that is the -difference between $4 and $21 a seat — about $340 a month for a regular -expression, which reports a bare mismatch where Commit Check reports a rule ID, -a suggestion and a link. The Team plan here is $19 a month whatever the team -size. -[The arithmetic, and the caveats](compare/github-rules.md). +
+
-## Questions +

Questions

??? question "Does it read my source code?" @@ -376,24 +509,29 @@ size. if the hosted App ever stops, the [GitHub Action](guides/github-actions.md) reads the same config and reports the same rule IDs. -
- -## Questions, bugs, contributions - -**Start a [discussion](https://github.com/commit-check/commit-check/discussions)** -if you are weighing up a policy, are not sure whether something is a bug, or -want to know how other projects have handled it. - -**Open an [issue](https://github.com/commit-check/commit-check/issues)** when -something is broken or missing — include the output of -`commit-check --format json`, which carries the rule ID and the value that -failed. -**Send a pull request** to any of the -[repositories](https://github.com/commit-check). The engine, the Action, the App -and the MCP server are separate projects. - -[Discussions :fontawesome-brands-github:](https://github.com/commit-check/commit-check/discussions){ .md-button .md-button--primary } -[Issues :fontawesome-brands-github:](https://github.com/commit-check/commit-check/issues){ .md-button } +
+
+
+
+
+

Start with two commands.

+

No config file needed. The defaults check Conventional Commits, Conventional Branch and subject length; tighten them when you are ready.

+ +
+

$ pip install commit-check

$ commit-check --message --branch

+
+
+ + diff --git a/docs/javascripts/landing.js b/docs/javascripts/landing.js new file mode 100644 index 0000000..79da01d --- /dev/null +++ b/docs/javascripts/landing.js @@ -0,0 +1,219 @@ +/* + * Landing page demos: the commit-msg terminal in the hero and the tabbed + * GitHub Action window. + * + * Loaded on every page through extra_javascript, and a no-op on every page + * but the home page. Instant navigation swaps the page content without a + * reload, so setup runs from the theme's document$ observable on each page + * view, and whatever the previous view started is stopped first. + * + * The demos are plain HTML: index.md documents the data-* timeline + * attributes, landing.css says what each state looks like, and this file only + * moves the clock. Nothing animates under prefers-reduced-motion, or while a + * demo is scrolled out of view. + */ +(function () { + "use strict"; + + var stops = []; + + function prefersReducedMotion() { + return window.matchMedia("(prefers-reduced-motion: reduce)").matches; + } + + function each(root, selector, fn) { + Array.prototype.forEach.call(root.querySelectorAll(selector), fn); + } + + function prepare(root) { + each(root, "[data-type]", function (el) { + if (el.dataset.full === undefined) el.dataset.full = el.textContent; + }); + } + + /* Put a timeline at `t` milliseconds. */ + function render(root, t) { + each(root, "[data-on]", function (el) { + el.classList.toggle("is-on", t >= Number(el.dataset.on)); + }); + each(root, "[data-from]", function (el) { + el.classList.toggle("is-on", t >= Number(el.dataset.from)); + }); + each(root, "[data-until]", function (el) { + el.classList.toggle("is-gone", t >= Number(el.dataset.until)); + }); + each(root, "[data-hl]", function (el) { + el.classList.toggle("is-hl", t >= Number(el.dataset.hl)); + }); + each(root, "[data-type]", function (el) { + var full = el.dataset.full; + var speed = Number(el.dataset.speed || 50); + var n = Math.floor((t - Number(el.dataset.type)) / speed); + var text = full.slice(0, Math.max(0, Math.min(full.length, n))); + if (el.textContent !== text) el.textContent = text; + }); + } + + /* Back to the final frame, as if the script had never run. */ + function rest(root) { + root.classList.remove("is-live"); + each(root, "[data-type]", function (el) { + el.textContent = el.dataset.full; + }); + } + + /* Runs `onFrame(now)` on every animation frame while `el` is on screen. */ + function whileVisible(el, onShow, onFrame) { + var raf = null; + function frame(now) { + onFrame(now); + raf = requestAnimationFrame(frame); + } + var io = new IntersectionObserver(function (entries) { + entries.forEach(function (entry) { + if (entry.isIntersecting && raf === null) { + onShow(performance.now()); + raf = requestAnimationFrame(frame); + } else if (!entry.isIntersecting && raf !== null) { + cancelAnimationFrame(raf); + raf = null; + } + }); + }, { threshold: 0.25 }); + io.observe(el); + return function () { + io.disconnect(); + if (raf !== null) cancelAnimationFrame(raf); + }; + } + + /* A timeline that plays on a loop: the hero terminal. */ + function setupLoop(root) { + var loop = Number(root.dataset.loop); + var start = 0; + prepare(root); + root.classList.add("is-live"); + render(root, 0); + stops.push(whileVisible(root, function (now) { + start = now; + }, function (now) { + render(root, (now - start) % loop); + })); + } + + /* The tabbed Action demo. */ + function setupDemo(demo, motion) { + var tabs = Array.prototype.slice.call(demo.querySelectorAll('[role="tab"]')); + var panels = tabs.map(function (tab) { + return document.getElementById(tab.getAttribute("aria-controls")); + }); + var cycle = Number(demo.dataset.cycle); + var current = 0; + var start = performance.now(); + // Cycles through the tabs until the reader picks one, then stays put. + var autoplay = motion; + var hovering = false; + + function select(index, focus) { + tabs.forEach(function (tab, i) { + var on = i === index; + tab.setAttribute("aria-selected", on ? "true" : "false"); + tab.tabIndex = on ? 0 : -1; + tab.style.setProperty("--p", "0"); + panels[i].hidden = !on; + rest(panels[i]); + }); + current = index; + start = performance.now(); + if (motion) { + panels[index].classList.add("is-live"); + render(panels[index], 0); + } + if (focus) tabs[index].focus(); + } + + function choose(index, focus) { + autoplay = false; + select(index, focus); + } + + tabs.forEach(function (tab, i) { + tab.addEventListener("click", function () { + choose(i, false); + }); + }); + + demo.querySelector('[role="tablist"]').addEventListener("keydown", function (event) { + var last = tabs.length - 1; + var next = { + ArrowRight: current === last ? 0 : current + 1, + ArrowLeft: current === 0 ? last : current - 1, + Home: 0, + End: last + }[event.key]; + if (next === undefined) return; + event.preventDefault(); + choose(next, true); + }); + + demo.addEventListener("pointerenter", function () { hovering = true; }); + demo.addEventListener("pointerleave", function () { hovering = false; }); + demo.addEventListener("focusin", function () { hovering = true; }); + demo.addEventListener("focusout", function () { hovering = false; }); + + panels.forEach(prepare); + demo.classList.add("is-ready"); + select(0, false); + + if (!motion) return; + + stops.push(whileVisible(demo, function (now) { + start = now; + }, function (now) { + var t = now - start; + render(panels[current], t); + if (!autoplay) return; + tabs[current].style.setProperty("--p", String(Math.min(1, t / cycle))); + if (t >= cycle && !hovering) select((current + 1) % tabs.length, false); + })); + } + + function setupCopy(button) { + button.addEventListener("click", function () { + var source = button.dataset.copyFrom + ? document.getElementById(button.dataset.copyFrom).textContent + : button.dataset.copy; + if (!navigator.clipboard) return; + navigator.clipboard.writeText(source).then(function () { + var label = button.getAttribute("aria-label"); + var text = button.textContent; + button.classList.add("is-done"); + if (label) button.setAttribute("aria-label", "Copied"); + if (!label) button.textContent = "Copied"; + setTimeout(function () { + button.classList.remove("is-done"); + if (label) button.setAttribute("aria-label", label); + if (!label) button.textContent = text; + }, 1600); + }); + }); + } + + function init() { + stops.forEach(function (stop) { stop(); }); + stops = []; + + var motion = !prefersReducedMotion() && "IntersectionObserver" in window; + if (motion) each(document, ".cc-term .cc-tl[data-loop]", setupLoop); + each(document, ".cc-demo", function (demo) { setupDemo(demo, motion); }); + each(document, ".cc-copy", setupCopy); + } + + if (typeof document$ !== "undefined") { + document$.subscribe(init); + } else if (document.readyState === "loading") { + document.addEventListener("DOMContentLoaded", init); + } else { + init(); + } +})(); diff --git a/docs/overrides/main.html b/docs/overrides/main.html index 4451dce..4b6289a 100644 --- a/docs/overrides/main.html +++ b/docs/overrides/main.html @@ -12,6 +12,9 @@ {% endblock %} {% block extrahead %} + {# The display face for headings and the site name (--cc-display-font in + assets/extra.css). The theme loads the text and code faces itself. #} + {% endblock %} diff --git a/docs/static/apple-touch-icon.png b/docs/static/apple-touch-icon.png index a499f936b72301e1ec7bbe99bd3eae3a538e343f..744be145499c8bbb705beccb109a94104103b07c 100644 GIT binary patch literal 3170 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 literal 10249 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 diff --git a/docs/static/favicon.ico b/docs/static/favicon.ico index ed1872b50b4d8c4162db14a10878e2e11c05b559..6727ead7f257653d9539402a2200e71595db5653 100644 GIT binary patch literal 15086 zcmeHO>5mje6fXu7*6mr?U3O;nK&EHdWl!#eO92t#gRsjb;36O>Q4~dqh@vs9F;Sz2 z0D=mvfH5Y-0~ax9G|>e`5m;384_H(%cqD#=gjl{`Pj^qxSl!bzeauW^CzYD6uJ_)r zUcFb>t6^*a8_3Gb8RartcO7FB8Dp}Xct1qt`>AZ~SoOVuu{j~eny3zaB8o&gWsFfh z(Gv}oj+4XEaY|Px@eJvT9FcnEkVFz0Q)#bepb_!B3LZpvA_v+8Kj1LIJbOYVU)NF1 z8)}0h*;<*fy(@1G^KbXG^1kjizI{VXt7|L2e|`-=v#(8|1wGP+MYXnQA6;6f(|x(C zg@5{Tlk$FgZ!7PZT9uK<$yXDYKi}TWXO6AlGsjf$uU~6Xc=YaU;S(FnEqUD6Ud?ws z66cefhUoQH&W`Aq-g|Z=Z>%fRzuE1#t~EF4Jhne1XH6fk4)OzAM(8}2Q~%hNt<@VP zm+@mSHYz+iZwVRcg*Kqk8S|FmWqjJGiga2>_8YA#9ZxcH@*nt~;)4kuz+tB?sB=Q2 z?HV8&5$u(L7UPUe>1Ik8pOgm!kM$FfBf&f5aPSCWxk%|6C8J(*`3)Y#E7(K4my!5@ z2tqVc5{pHovo6T9#2Af8XCTXnVR)n@PI51ME!)8VvRqvvs~7@Q-e>q`iQlby&;!Y* z{+2r3;+ZucvIm-LKFpfVxlO7%4t_wgFXd0Z^ef5E%Eu-*58-b;RnJeo-m2u2nylxJ z3~kFw_Vm#e{IhM%`go5y^ymwX9OG{WIgY>2n>>_%wJR}?YV)_2@7WaR@rdJb$`s?0 z86p0SI$vut)AP-p!%XR&=;of=s`(jOXQazZe?LoWo24_dpMR~etzS^X!8cu&+55@e zt$cc$BYV_!xM6Xvsob}pZs1#2C)X}UJV&2*#~#1fk!t6fjJ;%QSZl%FV0T)%F?Yir zMe?_;m-&dA78(YhTe(8oi&Yd)r zZ(LHxk(=Q}IqY6!rx-7Q&x+QqZ%-^w?h%Z7^J{!W-|XJ#ojuyola)Q9AMNmPTF1;D z;ZII^+oc!Tqo4ADbCY4u3VXEJ>t?kEu&V>qv*Evx{aZFXU9NkP{N^K4sL><5Y#Kg|z>9dlD&ZP|hR z?}_$L2lOfJ#$L6Z&hv1_vvzJZ9}^p#^4%>t20oal|1rGhEU$TZkaurR#=Nk%!a2j@ zX(_R*ocWtzbOrmE_+b9-GYu){I@(zU&XZ@2t8^Q;Yy1!+#aW7mSCoH3aoN${+fvqT z5}lii@T2VDll2^NRTDe#y&Um+#Hh{iipp_TSP1wRPY>yFb`egivQ={;d2IvapH}ZW zSm8ALmQR0vfZ_(}cP-3tT9xO+pSP?SX4}?}zmnN6{PgFw6f3gQr}5jSWp#WKjj48g z+b@6adCq!#2Y$rD?wwbo`zG$+*olw%;m@Zs_$$7r-EmRcVHNAr`V8?$+#T|AT(#0a zVtXA^%gTSYa7J$-luz1X4tiIul@Pq+iO$i ztV3HH_|z7AF|0!I=cZvnK4-#EKBXmjU(Ifx7m`2Q#hhLbeW=-sS)R}NIUTR)-Ht!! zV}2H46_t%7e}2bguVd^Jul-qs&2E`(f4-|Q*4v_X+3{zr6{op$Z$yN_X_=k*`N4(B zHHlMsUeIUq=R(JCH9r@+{b>D(GYswSxN!`!c~{4kGawC1u6bLujBmNKj_-Rc&T(ga z;Z5diLW2xo|I6_1*&5Ib8PTxx2l4&M3*J8Q{DI`W>60w?`!*u&kA{PbiWfh{rzw6! zcJxaTw&4P>BMwRbO}M+*@FT`A;X@rsHeI$-gS0L6pX6qHXRK20%pf}3J9V)%*D#JF_*Lh6fy)RrIBxy~2= zfGo%)*#~6Ar`2&rg!dW>XE63Ra_X6W{{m8#Qe^-D literal 2863 zcmai$cQhN07RQ5_uiB(k)r^u@wTa!LK?Jc^qXaQ(&#EGbUDRs%QPi$l)ke+6C_j6& zRa7;#ik8|lv7X=W_s;w4o%8NLcb#+pxu5Sn7XY9E&;dXoz_~#HytDuS{5<01{3oxY z0|3m^{=E1;+$f5n2K8Zc9_$snnLIjQ~s0< z(pA$qb@A_G1B(ma%bz9KGJKfoAD}5-??S@#Xjg_@*lD<`qa*8|Ul!4Uc2=PlLp{iJ zF3Ek2{5LLImyWU!=mnLGQR6A)`bf3Do@zp;Dmkn12gSdRzV3ML{l`VO$giL@P_dQ9 zJO}Fcl{PC`6>C6ol9)niAQ8Vnrg+wnsU?DZ3A1&PtH>_1aWJ%I`HwnkCnxrk##eJQ zd#>IA-;84Oj-ZhI*l*yHyN=50Q7}x!SGOSb>k&23{aqowc-N)T#R82)s6ipc!#pH@ zAyf9iJvKeZCr`93{2H5J>~vv?F+;YaS=ZYt_Bg{d>*U7rv)#Fd+wuUwo7hR=xQ&qw z^8Dvph3YurLwZ@k$w`X)=c4nq1N5}fh*~Wy;eWvK7fzgi!!aAIet6DM@IP>@S=@Cq zw*nR91Z{50<}^mXp7cg(?ygh02Wb=1gTJX(2`C z#k4W|ZM2*MF5+X0tZJ_Rc>1Ar^u(gIQE;4d%{Zoz-y;~rBI;nGcYux z0Yo#_ftAz92KZ5y-2(0xvy9@5E2g=1N8-X9w^sWR-XaWInKL!f#~70XnLj z74C@Lc@!VyrKlL$8(>}b9Ju?z1DgsPnfX?KoIllVERwzo@+q3pUll@zfTLKo+%Mam zd6Sn{D^K4Xv5PNz^pze1e)a`!OFCD3g={`hld{k3byh>~Q!c>%>`F+m4Sg3Z&ZW(W zgD<9iAh|uzbj~PzGIZ@w_Y@W-qA!8d=&_F;s2-ml46=;H5vI4N6`&A!<3)b#Q3a3;~}K<&^@OTN;D z?zv?2yXM&x-PSz}=2t>M^B=uhMtGW8u%C{Ezw+^#^h&)CEB6^d`?+a&BI--_8h~QQg2V7aZjWT=M0e^n2T35c#kcPD?YFijcQuLr8+Gv{i&H zwN+Aze)$UhMEM;FRx6BW-W~hm&&Iu;f|v)7l%GkzJX|lZAhKZ@60G?n++h(wt#qq+ zr_@e|Fi4E0zy;wU&+^81yLk$T0wTx4;BX88nI3vet;r;SW5Z!Eui~c7ON6jKzOW4M zz|vXh=(^Vy$t1mq7npZ=v$Ig-q{eTmu<3oP-X@@B@=pJ0{nED5lG<|6GuF_zvI{&er8DIT8$=IPWhL~BSo+wsOIxr#mJsaPD;+9D%Q9nrwnTM) zhD|T>1$G3M(-^c-Ua<`a1zF<#!awKJRZM-CQ5PWk{DL1?#bZ+0N=$3kS#?Ko8p5BP zwJ*^$*~_*k>|#}c_wH6Lk<3XFRAnB={5n}v_pi3(hEy0{%!aY>)^qy#vqO(b`IUi$ zsjzFMYbv|l$qbGvgQY>d4+Gul=v+JA5jwnml&JK;0i?00jMP?Ki9MT%Xo47xXMBA- zIQJCoIZRNZjhb`%EY-3DVN&dwoo+PRIEhUK?2{B)Lq`U1jxY#$=jP4(M(`;Na~2tk z$T3D-&PdD#bxXQ%0Qn%ZevVBvCKs{q%iJLGD_$R$1C1!2;vWx~!G@m(g=d`iPbhLo zBPMG!@m8~yl=zlh7f5S*`lEDXSL~ITajS2OvupN5H85xf#5_@B&6jXu< zL9drFYJGptkaLQ1eZPPiX5UAniAVxj`<2 zgN>Ake~_p0_ObR8apN1?#eB@vNwp4n*B*y#c}n>3nvH|-yjbX?n8Dj=@ZSbhM$kL0 zv81U1*=SEvlX+vt7Vq6EpNO8TUtTE2OP1Pbf+NLTHGPl98lkGG;SHwc4A*mC4E0A! zm@$jWiB9CzswN<0FfJqZbJ8*Sr~>Dvi7H;W)0ywnR(8fTw)jt4(tUAL)FjAf{q<0}}ZZa0S4 zt{xV20(pzOE&0j(SK`d1yDVFU-(U{Xq+NAdXbt?b++x#1If<-0( diff --git a/docs/stylesheets/landing.css b/docs/stylesheets/landing.css index 2cfa79e..1588b3b 100644 --- a/docs/stylesheets/landing.css +++ b/docs/stylesheets/landing.css @@ -2,23 +2,66 @@ * Landing page only. * * This file is linked from docs/overrides/landing.html rather than listed in - * mkdocs.yml's extra_css, so the twenty-odd documentation pages never load it. - * Instant navigation diffs between documents, so the link is added when - * a reader arrives here and removed when they leave -- which is also why rules - * below may safely reach outside the content area, to .md-header. A body class - * could not do that: instant navigation does not touch . + * mkdocs.yml's extra_css, so the documentation pages never load it. Instant + * navigation diffs between documents, so the link is added when a + * reader arrives here and removed when they leave -- which is also why rules + * below may safely reach outside the content area, to .md-header and + * .md-main. A body class could not do that: instant navigation does not + * touch . + * + * Sizes are in px, not the theme's rem: the theme scales its root font size + * up on wide screens, and the landing page is laid out to fixed measures + * (the demo window, the five-column row) that should not grow with it. + * + * The brand tokens are Ink, Signal Blue and Paper; see the brand kit. The + * demo window is drawn light in both colour schemes on purpose -- it stands + * for a page on github.com, which the reader sees in its own theme. */ -/* ---------------------------------------------------------------- header -- */ +:root { + --cc-ink: #0b1620; + --cc-ink-2: #13212c; + --cc-ink-3: #0f1d28; + --cc-ink-line: #22323f; + --cc-on-ink: #e8eef2; + --cc-on-ink-2: #b9c7d1; + --cc-on-ink-3: #8fa3b1; + --cc-blue: #2c9ccd; + --cc-blue-soft: #7fc6e6; + --cc-blue-deep: #176b89; + --cc-tint: #e3f2f9; + + --cc-paper: #f4f6f7; + --cc-card: #ffffff; + --cc-line: #dce3e8; + --cc-text: #0e1b24; + --cc-muted: #4f6270; + + --cc-pass: #1a7f4b; + --cc-fail: #c8322b; + --cc-wait: #9a6a00; -/* Marc Lou's first rule for a landing page: the brand is small and out of the - way, because nobody arrives caring about it. The stock header already puts - the logo and the name top left; the search box is what competes with the - headline, and a reader who wants search is one click into the docs. */ -.md-header__title .md-header__topic:first-child { - font-size: 0.9rem; + /* Literal stacks: the theme defines its font variables below :root, so a + var() of them here would resolve to nothing. */ + --cc-display: "Bricolage Grotesque", "Instrument Sans", -apple-system, "Segoe UI", sans-serif; + --cc-mono: "JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; } +[data-md-color-scheme="slate"] { + --cc-paper: #0e1a23; + --cc-card: #13212c; + --cc-line: #263744; + --cc-text: #e8eef2; + --cc-muted: #a3b4c0; + --cc-tint: #16303e; + --cc-blue-deep: #7fc6e6; +} + +/* ---------------------------------------------------------------- chrome -- */ + +/* The brand is small and out of the way in the header: nobody arrives caring + about it. The search box is what competes with the headline, and a reader + who wants search is one click into the docs. */ .md-header .md-search { display: none; } @@ -29,250 +72,2408 @@ } } +/* The hero is a dark band that meets the header, so the theme's gap above the + content, and the spacer it draws inside it, go. */ +.md-main__inner { + margin-top: 0; +} + +.cc-landing .md-content__inner { + padding-top: 0; + margin-bottom: 0; +} + +.cc-landing .md-content__inner::before { + display: none; +} + +/* The "edit this page" pencil points at index.md, which is not what a + first-time reader wants to do next. */ +.cc-landing .md-content__button { + display: none; +} + +.md-main { + background: var(--cc-paper); + /* The bands below span the window with `margin-inline: calc(50% - 50vw)`. + 100vw includes a classic scrollbar, so they can end a few pixels wide; + `clip` rather than `hidden`, because `hidden` would make .md-main a + scroll container and break the sticky header and in-page anchors. */ + overflow-x: clip; +} + +/* ---------------------------------------------------------------- resets -- */ + +/* The theme styles bare elements inside .md-typeset. These resets use + :where() so they stay no stronger than the theme's own rules and every + component class below still wins. The FAQ band is left alone: it is + ordinary Markdown and should look like the rest of the docs. */ +.md-typeset :where(.cc-band:not(.cc-faq)) :is(p, ul, ol, figure, h1, h2, h3, table) { + margin: 0; +} + +/* The theme indents lists with [dir=ltr]-prefixed rules, hence [dir] here. */ +[dir] .md-typeset :where(.cc-band:not(.cc-faq)) :is(ul, ol) { + margin: 0; + padding: 0; + list-style: none; +} + +[dir] .md-typeset :where(.cc-band:not(.cc-faq)) :is(ul, ol) > li { + margin: 0; +} + +.md-typeset :where(.cc-band:not(.cc-faq)) code { + padding: 0; + background: none; + border-radius: 0; + box-shadow: none; + color: inherit; + font-size: inherit; + word-break: normal; +} + +.md-typeset :where(.cc-band) svg { + max-width: none; +} + +.cc-sr { + position: absolute; + width: 1px; + height: 1px; + overflow: hidden; + clip-path: inset(50%); + white-space: nowrap; +} + +/* ----------------------------------------------------------------- bands -- */ + +/* Each band spans the window while its text stays in the content column: + the negative margin pulls the edges out, the padding puts the text back. + Percentages, not viewport units, for the margin: .md-content is a flex + item, and a `-100vw` margin counts towards its min-content width, which + once stretched the column to 2754px on a 1440px screen. */ +.md-typeset .cc-band { + position: relative; + margin-inline: calc(50% - 50vw); + padding: 104px calc(50vw - 50%); + color: var(--cc-text); + font-size: 16px; + line-height: 1.55; +} + +.md-typeset .cc-band--ink { + background: var(--cc-ink); + color: var(--cc-on-ink); +} + +.md-typeset .cc-band--white { + background: var(--cc-card); +} + +.cc-wrap { + position: relative; + max-width: 1200px; + margin-inline: auto; +} + +/* ------------------------------------------------------------ type scale -- */ + +.md-typeset .cc-display { + font-family: var(--cc-display); + font-size: clamp(42px, 6.2vw, 70px); + line-height: 1; + font-weight: 800; + letter-spacing: -0.035em; + color: #ffffff; + text-wrap: balance; +} + +.md-typeset .cc-accent { + color: var(--cc-blue); +} + +.md-typeset .cc-h2 { + font-family: var(--cc-display); + font-size: clamp(32px, 4.2vw, 50px); + line-height: 1.05; + font-weight: 800; + letter-spacing: -0.03em; + color: inherit; + text-wrap: balance; +} + +.md-typeset .cc-h2--xl { + font-size: clamp(36px, 4.6vw, 54px); +} + +.md-typeset .cc-eyebrow { + font-family: var(--cc-mono); + font-size: 13px; + font-weight: 500; + letter-spacing: 0.06em; + text-transform: uppercase; + color: var(--cc-blue-deep); +} + +.md-typeset .cc-band--ink .cc-eyebrow { + color: var(--cc-blue-soft); +} + +.md-typeset .cc-sub { + font-size: 18px; + line-height: 1.55; + color: var(--cc-muted); + text-wrap: pretty; +} + +.md-typeset .cc-band--ink .cc-sub { + color: var(--cc-on-ink-2); +} + +.md-typeset .cc-sub code, +.md-typeset .cc-lede code { + font-family: var(--cc-mono); + font-size: 0.88em; +} + +.cc-head { + display: flex; + flex-direction: column; + gap: 16px; + max-width: 760px; +} + +.cc-head--center { + align-items: center; + margin-inline: auto; + text-align: center; +} + +.cc-head--split { + flex-direction: row; + flex-wrap: wrap; + justify-content: space-between; + align-items: flex-end; + gap: 24px 40px; + max-width: none; +} + +.cc-head--split > div { + display: flex; + flex-direction: column; + gap: 16px; + max-width: 720px; +} + +.md-typeset .cc-mono { + font-family: var(--cc-mono); +} + +.md-typeset .cc-b { + font-weight: 700; +} + +.md-typeset .cc-dim { + color: var(--cc-muted); +} + +/* Terminal colours, tuned for the Ink background. */ +.md-typeset .cc-t-fail { color: #ff7a6e; } +.md-typeset .cc-t-pass { color: #4fc98b; } +.md-typeset .cc-t-blue { color: var(--cc-blue-soft); } +.md-typeset .cc-t-num { color: #f2b866; } +.md-typeset .cc-t-white { color: #ffffff; } +.md-typeset .cc-t-dim { color: var(--cc-on-ink-3); } + +/* The same meanings, dark enough for the light demo window. */ +.md-typeset .cc-t-okd { color: #177245; } +.md-typeset .cc-t-bad { color: #a62a24; } +.md-typeset .cc-t-wait { color: #7a5500; } + +/* --------------------------------------------------------------- buttons -- */ + +.cc-actions { + display: flex; + flex-wrap: wrap; + gap: 12px; + align-items: center; +} + +.md-typeset .cc-btn { + display: inline-flex; + align-items: center; + gap: 10px; + height: 52px; + padding: 0 22px; + border-radius: 11px; + font-size: 16px; + font-weight: 600; + text-decoration: none; + transition: background-color 150ms, border-color 150ms; +} + +.md-typeset .cc-btn svg { + width: 16px; + height: 16px; +} + +/* Ink on Signal Blue is 5.9:1; white on it would be 3.1:1. */ +.md-typeset .cc-btn--primary, +.md-typeset .cc-btn--primary:hover, +.md-typeset .cc-btn--primary:focus { + background: var(--cc-blue); + color: #06131b; +} + +.md-typeset .cc-btn--primary:hover { + background: #5bb8e0; +} + +.md-typeset .cc-btn--ghost { + border: 1px solid #2a3b48; + color: var(--cc-on-ink); +} + +.md-typeset .cc-btn--ghost:hover { + background: rgba(255, 255, 255, 0.06); + color: #ffffff; +} + +.md-typeset .cc-btn--quiet { + height: 48px; + border: 1px solid var(--cc-line); + background: var(--cc-card); + color: var(--cc-text); + font-size: 15px; +} + +.md-typeset .cc-btn--quiet:hover { + border-color: var(--cc-blue); + color: var(--cc-text); +} + +.md-typeset .cc-install { + display: inline-flex; + align-items: center; + gap: 12px; + height: 52px; + box-sizing: border-box; + padding: 0 5px 0 16px; + border: 1px solid #2a3b48; + border-radius: 11px; + background: var(--cc-ink-3); + font-family: var(--cc-mono); + font-size: 14.5px; + color: var(--cc-on-ink); +} + +.cc-install__sigil { + color: var(--cc-blue); +} + +.md-typeset .cc-copy { + display: inline-flex; + align-items: center; + justify-content: center; + width: 44px; + height: 44px; + border: 0; + border-radius: 8px; + background: #16252f; + color: #9db0be; + cursor: pointer; +} + +.md-typeset .cc-copy:hover, +.md-typeset .cc-copy:focus-visible { + color: #ffffff; +} + +.md-typeset .cc-copy svg { + width: 16px; + height: 16px; +} + +.md-typeset .cc-copy.is-done { + color: #4fc98b; +} + +.md-typeset .cc-copy--text { + width: auto; + height: 32px; + padding: 0 10px; + background: transparent; + font-family: "Instrument Sans", -apple-system, "Segoe UI", sans-serif; + font-size: 13px; + font-weight: 600; + color: var(--cc-blue-soft); +} + /* ------------------------------------------------------------------ hero -- */ -.cc-hero { +.md-typeset .cc-hero { + padding-top: 72px; + padding-bottom: 0; + overflow: hidden; +} + +.md-typeset .cc-hero__graph { + position: absolute; + top: 0; + right: 0; + width: 480px; + height: 100%; + pointer-events: none; +} + +.cc-hero__grid { display: grid; - gap: 2.4rem; + gap: 48px; align-items: center; - margin: 1.2rem 0 3.2rem; } -@media screen and (min-width: 60em) { - .cc-hero { - grid-template-columns: minmax(0, 1fr) minmax(0, 1.1fr); - gap: 3.2rem; +@media screen and (min-width: 960px) { + .cc-hero__grid { + grid-template-columns: minmax(0, 1fr) minmax(0, 600px); + gap: 56px; } } -/* The h1 is the promise, so it gets the size budget the site name used to - spend. clamp() keeps it from wrapping to four lines on a phone. */ -.md-typeset .cc-hero__copy h1 { - margin: 0 0 0.8rem; - font-size: clamp(1.7rem, 5.2vw, 2.6rem); - font-weight: 700; - line-height: 1.15; - letter-spacing: -0.01em; - color: var(--md-default-fg-color); +.cc-hero__copy { + display: flex; + flex-direction: column; + gap: 26px; +} + +.md-typeset .cc-pill { + align-self: flex-start; + display: inline-flex; + align-items: center; + gap: 8px; + height: 30px; + padding: 0 12px; + border: 1px solid #22404f; + border-radius: 999px; + background: #102836; + color: var(--cc-blue-soft); + font-size: 13.5px; + font-weight: 500; +} + +.cc-pill__dot { + width: 7px; + height: 7px; + border-radius: 50%; + background: var(--cc-blue); +} + +.md-typeset .cc-lede { + font-size: 19px; + line-height: 1.55; + color: var(--cc-on-ink-2); + text-wrap: pretty; } -.md-typeset .cc-hero__copy > p:first-of-type { - font-size: 0.82rem; - line-height: 1.65; - color: var(--md-default-fg-color--light); +.md-typeset .cc-lede code { + color: var(--cc-on-ink); } -.cc-hero__copy .md-button { - margin: 0.4rem 0.4rem 0 0; +.md-typeset ul.cc-facts { + display: flex; + flex-wrap: wrap; + gap: 8px 32px; + margin-top: 64px; + padding: 22px 0 30px; + border-top: 1px solid #1a2934; + font-size: 14px; + color: var(--cc-on-ink-3); } -/* The terminal sample is the proof, so it carries the visual weight of a - screenshot without being one: real text, selectable, and it recolours with - the palette instead of burning a light theme into a PNG. */ -.md-typeset .cc-hero__demo > .highlight, -.md-typeset .cc-hero__demo pre { +/* -------------------------------------------------------------- terminal -- */ + +.md-typeset .cc-term { + display: flex; + flex-direction: column; + width: 100%; + max-width: none; margin: 0; + border-radius: 14px; + overflow: hidden; + background: var(--cc-ink-3); + box-shadow: 0 30px 70px rgba(0, 0, 0, 0.45), 0 0 0 1px rgba(255, 255, 255, 0.07); + color: #d6e1e8; + font-family: var(--cc-mono); + text-align: left; +} + +.cc-term__bar { + display: flex; + align-items: center; + gap: 14px; + height: 38px; + padding: 0 16px; + background: var(--cc-ink-2); + border-bottom: 1px solid var(--cc-ink-line); + font-size: 12px; + color: var(--cc-on-ink-3); + white-space: nowrap; + overflow: hidden; +} + +.cc-dots { + display: flex; + gap: 7px; } -.md-typeset .cc-hero__demo > .highlight { - border: 1px solid var(--md-default-fg-color--lightest); - border-radius: 0.4rem; - box-shadow: 0 2px 10px rgba(0, 0, 0, 0.08); +.cc-dots i { + width: 11px; + height: 11px; + border-radius: 50%; + background: #2a3b48; } -/* The transcript is real output, so its longest line is 93 characters and - cannot be shortened without making it a fake. In the hero it has to be - readable at a glance rather than scrolled to, so it soft-wraps -- which is - what an 80-column terminal does with that line anyway. The source in - index.md is untouched, so tests/docs_sync_test.py still checks the rule - name against the installed package. +.cc-term__body { + padding: 18px 20px 22px; + font-size: 13px; + line-height: 21px; +} - Not `overflow: hidden` on the wrapper: an earlier version did that and - silently cut the line off at the border. */ -.md-typeset .cc-hero__demo pre > code { - font-size: 0.65rem; +.cc-line { + min-height: 21px; white-space: pre-wrap; overflow-wrap: anywhere; } -.md-typeset .cc-hero__demo > p { - margin: 0.8rem 0 0; - font-size: 0.72rem; - color: var(--md-default-fg-color--light); +.cc-gap { + margin-top: 21px; +} + +.cc-row { + display: flex; + gap: 2px; + white-space: nowrap; +} + +.cc-leader { + flex: 1; + min-width: 24px; + overflow: hidden; } -/* ------------------------------------------------------------ full bleed -- */ +.cc-leader::before { + content: "................................................................................"; + color: var(--cc-on-ink-3); +} -/* .cc-proof and .cc-community are tinted bands that span the whole window - while their text stays in the content column. They do it with the pair - below, and the units matter more than they look: +.cc-sigil { + color: var(--cc-blue); +} - margin-inline: calc(50% - 50vw) pull each edge out to the window - padding-inline: calc(50vw - 50% + gutter) put the text back +.cc-caret { + display: inline-block; + width: 8px; + height: 16px; + margin-left: 1px; + vertical-align: -3px; + background: var(--cc-blue); + animation: cc-blink 1s steps(1) infinite; +} - The percentages are load-bearing. `.md-content` is a flex item, so its - min-content width is the widest thing inside it; a viewport-unit margin - like `-100vw` counts towards that and stretched the column to 2754px on a - 1440px viewport. The document itself did not scroll -- the width went into - the column, which then rendered its own content off the right edge. A - percentage margin resolves against the containing block and contributes - nothing to intrinsic width, so the column stays viewport-sized. +.cc-caret--ink { + width: 2px; + height: 22px; + vertical-align: -4px; + background: #0e1b24; +} - `overflow-x: clip` is the belt to that pair of braces: 100vw includes the - classic scrollbar, so the bands can still end a few pixels wide of the - viewport. `clip` rather than `hidden` because `hidden` would make .md-main - a scroll container and break the sticky header and in-page anchors. */ -.md-main { - overflow-x: clip; +@keyframes cc-blink { + 50% { opacity: 0; } } -/* ---------------------------------------------------- social proof strip -- */ +/* ------------------------------------------------------ timeline states -- */ -.cc-proof { - margin: 0 calc(50% - 50vw) 3.2rem; - padding: 2rem calc(50vw - 50% + 0.8rem); - background: var(--md-code-bg-color); - text-align: center; +/* See the comment at the top of index.md. A timeline (.cc-tl) is `is-live` + only while the script plays it; otherwise it shows its final frame. */ +.cc-tl:not(.is-live) [data-until], +.cc-tl.is-live [data-until].is-gone, +.cc-tl.is-live [data-from]:not(.is-on) { + display: none; +} + +.cc-tl [data-on] { + transition: opacity 0.45s ease, transform 0.45s ease; +} + +.cc-tl.is-live [data-on]:not(.is-on) { + opacity: 0; + transform: translateY(6px); +} + +.cc-tl.is-live .cc-flash.is-on { + animation: cc-flash 1.4s ease; +} + +@keyframes cc-flash { + from { background: var(--cc-tint); } + to { background: transparent; } +} + +/* ------------------------------------------------------ where it runs -- */ + +.md-typeset .cc-config { + width: min(380px, 100%); + margin: 52px auto 0; + border-radius: 14px; + overflow: hidden; + background: var(--cc-ink); + color: #d6e1e8; + box-shadow: 0 16px 40px rgba(11, 22, 32, 0.18); +} + +.md-typeset .cc-config__bar { + display: flex; + align-items: center; + gap: 8px; + height: 34px; + padding: 0 14px; + background: var(--cc-ink-2); + font-family: var(--cc-mono); + font-size: 12.5px; + color: #9db0be; } -.md-typeset .cc-proof > p { - margin: 0 auto 1.6rem; - max-width: 34rem; - font-size: 0.72rem; - color: var(--md-default-fg-color--light); +.cc-config__bar svg { + width: 14px; + height: 14px; + color: var(--cc-blue); } -.cc-proof .logo-grid { +.cc-config__body { + padding: 14px 18px 16px; + font-family: var(--cc-mono); + font-size: 13px; + line-height: 21px; + white-space: pre; +} + +.md-typeset .cc-fan { + display: none; + width: 100%; + height: 64px; + color: #9fc9dc; +} + +.cc-fan path { + stroke: currentColor; + vector-effect: non-scaling-stroke; +} + +.cc-fan .cc-fan__main { + stroke: var(--cc-blue); + stroke-width: 2.4; +} + +.md-typeset ul.cc-surfaces { display: grid; - grid-template-columns: repeat(auto-fit, minmax(110px, 1fr)); - gap: 1.2rem 0.8rem; - max-width: 52rem; - margin: 0 auto; + grid-template-columns: repeat(auto-fit, minmax(208px, 1fr)); + gap: 16px; + margin-top: 40px; +} + +@media screen and (min-width: 1100px) { + .md-typeset .cc-fan { + display: block; + } + + .md-typeset ul.cc-surfaces { + grid-template-columns: repeat(5, minmax(0, 1fr)); + gap: 20px; + margin-top: 0; + } } -.cc-proof .logo-item { +.md-typeset .cc-surface { display: flex; flex-direction: column; + gap: 12px; + height: 100%; + box-sizing: border-box; + padding: 20px; + border: 1px solid var(--cc-line); + border-radius: 14px; + background: var(--cc-card); + color: var(--cc-text); + transition: border-color 150ms, box-shadow 150ms, transform 150ms; +} + +.md-typeset .cc-surface:hover, +.md-typeset .cc-surface:focus-visible { + border-color: var(--cc-blue); + color: var(--cc-text); + transform: translateY(-2px); +} + +.md-typeset .cc-surface--main { + padding: 19px; + border: 2px solid var(--cc-blue); + box-shadow: 0 12px 30px rgba(44, 156, 205, 0.16); +} + +.cc-surface__icon { + display: flex; align-items: center; - gap: 0.4rem; + justify-content: center; + width: 42px; + height: 42px; + border-radius: 11px; + background: var(--cc-ink); + color: var(--cc-blue); } -.cc-proof .logo-item img { - width: 44px; - height: 44px; - border-radius: 50%; - filter: grayscale(1); - opacity: 0.7; - transition: filter 125ms, opacity 125ms; +.cc-surface--main .cc-surface__icon { + background: var(--cc-blue); + color: #06131b; } -.cc-proof .logo-item:hover img { - filter: grayscale(0); - opacity: 1; +.cc-surface__icon svg { + width: 22px; + height: 22px; } -.cc-proof .logo-item span { - font-size: 0.6rem; +/* Sits on the card's top edge, like a tab, so it never crowds the icon. */ +.cc-surface__flag { + position: absolute; + top: -12px; + left: 50%; + transform: translateX(-50%); + white-space: nowrap; + padding: 4px 8px; + border-radius: 999px; + background: var(--cc-tint); + color: var(--cc-blue-deep); + font-size: 11.5px; + font-weight: 700; line-height: 1.3; - color: var(--md-default-fg-color--light); } -/* ----------------------------------------------------------------- cards -- */ - -/* `.md-typeset ul:not([hidden])` in the theme sets `display: flow-root`, and - that `:not()` counts as an attribute selector -- so a plain `.cc-cards > ul` - loses the cascade and every card stacks full width. Matching the theme's - shape wins on specificity instead of relying on load order. */ -.md-typeset .cc-cards > ul:not([hidden]), -.md-typeset .cc-pricing > ul:not([hidden]) { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr)); - gap: 0.8rem; - padding: 0; - margin: 1.2rem 0 2rem; - list-style: none; +.md-typeset .cc-surface--main { + position: relative; } -.md-typeset .cc-cards > ul > li, -.md-typeset .cc-pricing > ul > li { - margin: 0; - padding: 1rem 1.1rem; - border: 1px solid var(--md-default-fg-color--lightest); - border-radius: 0.4rem; - transition: border-color 125ms, box-shadow 125ms; +.cc-surface__kicker { + font-size: 12.5px; + font-weight: 600; + color: var(--cc-muted); } -.md-typeset .cc-cards > ul > li:hover, -.md-typeset .cc-pricing > ul > li:hover { - border-color: var(--cc-brand, #2c9ccd); - box-shadow: 0 0 0 1px var(--cc-brand, #2c9ccd); +.cc-surface__name { + font-family: var(--cc-display); + font-size: 22px; + font-weight: 700; + letter-spacing: -0.015em; + line-height: 1.15; } -.md-typeset .cc-cards > ul > li > hr, -.md-typeset .cc-pricing > ul > li > hr { - margin: 0.6rem 0; - border-color: var(--md-default-fg-color--lightest); +.cc-surface__desc { + flex-grow: 1; + font-size: 14.5px; + line-height: 1.5; + color: var(--cc-muted); } -.md-typeset .cc-cards > ul > li > p, -.md-typeset .cc-pricing > ul > li > p { - font-size: 0.72rem; - line-height: 1.6; +.md-typeset .cc-surface__code { + padding: 8px; + border-radius: 7px; + background: var(--cc-paper); + font-family: var(--cc-mono); + font-size: 11.5px; + overflow-wrap: anywhere; } -/* ---------------------------------------------------------- screenshots -- */ +.cc-surface__go { + font-size: 14px; + font-weight: 600; + color: var(--cc-blue-deep); +} -.md-typeset .cc-shot { - margin: 1.6rem 0 2rem; +.md-typeset .cc-more-link { + margin-top: 28px; text-align: center; + font-size: 15px; + font-weight: 600; } -.md-typeset .cc-shot img { - max-width: 100%; - height: auto; - border: 1px solid var(--md-default-fg-color--lightest); - border-radius: 0.4rem; +/* On a phone the five cards become a list of rows: the icon, the name and + what it is for. The details are one tap away on the guide. */ +@media screen and (max-width: 599px) { + .md-typeset ul.cc-surfaces { + grid-template-columns: minmax(0, 1fr); + gap: 10px; + margin-top: 28px; + } + + .md-typeset .cc-surface { + display: grid; + grid-template-columns: 40px minmax(0, 1fr); + grid-template-areas: "icon name" "icon kicker"; + column-gap: 14px; + row-gap: 2px; + align-items: center; + padding: 14px 16px; + } + + .md-typeset .cc-surface--main { + padding: 13px 15px; + } + + .cc-surface__icon { grid-area: icon; width: 40px; height: 40px; } + .cc-surface__name { grid-area: name; font-size: 17px; } + .cc-surface__kicker { grid-area: kicker; font-size: 13.5px; font-weight: 500; } + .cc-surface__desc, + .md-typeset .cc-surface__code, + .cc-surface__go, + .cc-surface__flag { display: none; } } -.md-typeset .cc-shot figcaption { - margin-top: 0.6rem; - font-size: 0.66rem; - color: var(--md-default-fg-color--light); +/* ----------------------------------------------------------- Action demo -- */ + +.cc-demo { + display: flex; + flex-direction: column; + align-items: center; + gap: 22px; + margin-top: 52px; } -/* The recording autoplays and cannot be paused, so a reader who has asked for - reduced motion gets the last frame of it instead. */ -.md-typeset .cc-shot .cc-still { +.cc-demo:not(.is-ready) .cc-tabs { display: none; } -@media (prefers-reduced-motion: reduce) { - .md-typeset .cc-shot .cc-motion { - display: none; - } +.cc-tabs { + display: flex; + justify-content: center; + gap: 10px; + max-width: 100%; + overflow-x: auto; + padding: 2px; + scrollbar-width: none; +} - .md-typeset .cc-shot .cc-still { - display: inline-block; - } +.md-typeset .cc-tab { + position: relative; + flex-shrink: 0; + height: 44px; + padding: 0 18px; + overflow: hidden; + border: 1px solid #2a3b48; + border-radius: 999px; + background: transparent; + color: var(--cc-on-ink-2); + font-family: var(--cc-mono); + font-size: 14px; + font-weight: 500; + cursor: pointer; + transition: background-color 250ms, color 250ms, border-color 250ms; } -/* ------------------------------------------------------------- community -- */ +.md-typeset .cc-tab:hover { + border-color: var(--cc-blue); + color: #ffffff; +} -.cc-community { - margin: 3.2rem calc(50% - 50vw) 0; - padding: 2rem calc(50vw - 50% + 0.8rem) 2.4rem; - background: var(--md-code-bg-color); +.md-typeset .cc-tab[aria-selected="true"] { + border-color: var(--cc-blue); + background: var(--cc-blue); + color: #06131b; } -.md-typeset .cc-community h2 { - margin-top: 0; +.cc-tab__bar { + position: absolute; + left: 0; + bottom: 0; + height: 3px; + width: calc(var(--p, 0) * 100%); + background: rgba(6, 19, 27, 0.45); } -/* ------------------------------------------------------------------ chrome -- */ +.cc-panel { + display: flex; + flex-direction: column; + align-items: center; + gap: 20px; + width: 100%; +} -/* The "edit this page" pencil belongs on a documentation page. Here it is the - only control in the hero and it points at index.md, which is not what a - first-time reader wants to do next. */ -.cc-landing .md-content__button { +.cc-panel[hidden] { display: none; } + +/* Without the script every panel is listed, one under the other. */ +.cc-demo:not(.is-ready) .cc-panel + .cc-panel { + margin-top: 48px; +} + +.cc-panel:focus-visible { + outline: 2px solid var(--cc-blue); + outline-offset: 6px; + border-radius: 14px; +} + +.md-typeset .cc-caption { + display: flex; + flex-direction: column; + gap: 6px; + text-align: center; + font-size: 14px; + color: #9db0be; +} + +.md-typeset .cc-caption strong { + font-size: 19px; + font-weight: 600; + color: #f2f6f8; + text-wrap: balance; +} + +.md-typeset .cc-caption code { + padding: 2px 7px; + border-radius: 5px; + background: var(--cc-ink-2); + color: var(--cc-blue-soft); + font-family: var(--cc-mono); + font-size: 13px; +} + +/* The window: a page on github.com, drawn light in either scheme. */ +.cc-window { + --w-text: #0e1b24; + --w-muted: #4f6270; + --w-line: #dce3e8; + --w-soft: #f4f6f7; + display: flex; + flex-direction: column; + width: 100%; + max-width: 1000px; + border-radius: 14px; + overflow: hidden; + background: #ffffff; + color: var(--w-text); + font-size: 14px; + line-height: 1.5; + text-align: left; + box-shadow: 0 30px 80px rgba(0, 0, 0, 0.45), 0 0 0 1px rgba(255, 255, 255, 0.06); +} + +.cc-demo.is-ready .cc-window { + min-height: 600px; +} + +.cc-window .cc-dim { + color: var(--w-muted); +} + +.cc-window__bar { + display: flex; + align-items: center; + gap: 14px; + height: 36px; + flex-shrink: 0; + padding: 0 16px; + background: #eef2f4; + border-bottom: 1px solid var(--w-line); +} + +.cc-window__bar .cc-dots i { + background: #d5dce1; +} + +.cc-url { + flex-grow: 1; + height: 22px; + padding: 0 10px; + border-radius: 6px; + background: #ffffff; + font-family: var(--cc-mono); + font-size: 12px; + line-height: 22px; + color: #5b6d7a; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.cc-pr { + display: flex; + flex-direction: column; + gap: 8px; + padding: 18px 28px 0; + border-bottom: 1px solid var(--w-line); +} + +.md-typeset .cc-pr__title { + display: flex; + flex-wrap: wrap; + align-items: center; + gap: 4px 10px; + min-height: 40px; + font-size: 24px; + font-weight: 600; + letter-spacing: -0.01em; + line-height: 1.25; +} + +.cc-pr__num { + font-weight: 400; + color: #6b7c88; +} + +.cc-open { + display: inline-flex; + align-items: center; + height: 26px; + padding: 0 11px; + border-radius: 999px; + background: var(--cc-pass); + color: #ffffff; + font-size: 13px; + font-weight: 600; + letter-spacing: 0; +} + +.cc-editbox { + display: flex; + align-items: center; + flex: 1; + min-width: 0; + height: 40px; + box-sizing: border-box; + padding: 0 12px; + border: 2px solid var(--cc-blue); + border-radius: 8px; + font-size: 21px; + white-space: pre; +} + +.cc-save { + display: inline-flex; + align-items: center; + height: 34px; + padding: 0 14px; + border: 1px solid #c9d3da; + border-radius: 8px; + background: var(--w-soft); + font-size: 14px; +} + +.md-typeset .cc-pr__meta { + font-size: 14px; + color: var(--w-muted); +} + +.md-typeset .cc-pr__meta b { + color: var(--w-text); +} + +.md-typeset .cc-window .cc-ref { + padding: 2px 6px; + border-radius: 5px; + background: var(--cc-tint); + color: #135a74; + font-family: var(--cc-mono); + font-size: 12.5px; +} + +.md-typeset .cc-window .cc-ref--bad { + background: #fce3e0; + color: #a62a24; +} + +.md-typeset .cc-pr__tabs { + display: flex; + gap: 22px; + margin-top: 4px; + font-size: 14px; + color: var(--w-muted); + white-space: nowrap; +} + +.cc-pr__tabs span { + display: inline-flex; + align-items: center; + gap: 6px; + padding: 10px 2px 11px; + border-bottom: 2px solid transparent; +} + +.cc-pr__tabs .is-active { + border-bottom-color: var(--cc-blue); + color: var(--w-text); + font-weight: 600; +} + +.cc-pr__tabs i { + padding: 1px 7px; + border-radius: 999px; + background: #eef2f4; + font-size: 12px; + font-style: normal; + font-weight: 400; +} + +.cc-window__body { + flex-grow: 1; + padding: 22px 28px; + background: #f7f9fa; +} + +/* Status icons: a filled circle with a glyph, drawn in CSS so the markup + stays one empty element per icon. */ +.cc-st { + position: relative; + display: inline-block; + flex-shrink: 0; + width: 18px; + height: 18px; + border-radius: 50%; + vertical-align: -3px; +} + +.cc-st::after { + content: ""; + position: absolute; + inset: 0; + background: #ffffff; + -webkit-mask: var(--glyph) center / 12px no-repeat; + mask: var(--glyph) center / 12px no-repeat; +} + +.cc-st--pass { + background: var(--cc-pass); + --glyph: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12'%3E%3Cpath d='M2.4 6.3l2.3 2.3 4.9-5.2' fill='none' stroke='black' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E"); +} + +.cc-st--fail { + background: var(--cc-fail); + --glyph: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12'%3E%3Cpath d='M3.2 3.2l5.6 5.6M8.8 3.2l-5.6 5.6' fill='none' stroke='black' stroke-width='1.8' stroke-linecap='round'/%3E%3C/svg%3E"); +} + +.cc-st--wait { + border: 2px dashed var(--cc-wait); + box-sizing: border-box; +} + +.cc-st--wait::after { + display: none; +} + +/* pr-comments */ +.cc-conv { + display: flex; + flex-direction: column; + gap: 14px; +} + +.cc-comment { + display: flex; + gap: 14px; +} + +.cc-avatar { + display: flex; + align-items: center; + justify-content: center; + flex-shrink: 0; + width: 36px; + height: 36px; + border-radius: 50%; + background: var(--cc-ink); +} + +.cc-avatar svg { + width: 22px; + height: 22px; +} + +.cc-comment__box { + flex-grow: 1; + min-width: 0; + border: 1px solid #d3dce2; + border-radius: 10px; + background: #ffffff; + overflow: hidden; +} + +.md-typeset .cc-comment__head { + display: flex; + align-items: center; + gap: 8px; + height: 40px; + padding: 0 16px; + border-bottom: 1px solid var(--w-line); + background: #eef2f4; + font-size: 13.5px; + color: var(--w-muted); +} + +.cc-comment__head b { + color: var(--w-text); +} + +.cc-chip { + padding: 0 7px; + border: 1px solid #c9d3da; + border-radius: 999px; + font-size: 11.5px; +} + +.cc-chip--end { + margin-left: auto; +} + +.cc-comment__body { + display: flex; + flex-direction: column; + gap: 12px; + padding: 16px 18px 14px; +} + +.cc-comment__body > div { + display: flex; + flex-direction: column; + gap: 12px; + border-radius: 8px; +} + +.md-typeset .cc-report__title { + padding-bottom: 8px; + border-bottom: 1px solid #e3e9ed; + font-size: 19px; + font-weight: 700; +} + +.md-typeset .cc-verdict { + display: flex; + align-items: center; + gap: 8px; + font-size: 15px; + font-weight: 700; +} + +.md-typeset .cc-table { + width: 100%; + border: 1px solid var(--w-line); + border-radius: 8px; + border-collapse: separate; + border-spacing: 0; + overflow: hidden; + font-size: 13.5px; +} + +.md-typeset .cc-table th, +.md-typeset .cc-table td { + padding: 9px 12px; + border-bottom: 1px solid #e8edf0; + text-align: left; + vertical-align: top; +} + +.md-typeset .cc-table th { + background: var(--w-soft); + font-weight: 600; +} + +.md-typeset .cc-table tr:last-child td { + border-bottom: 0; +} + +.md-typeset .cc-table code { + padding: 1px 5px; + border-radius: 4px; + background: #f1f4f6; + font-family: var(--cc-mono); + font-size: 12.5px; +} + +.cc-lnk { + color: var(--cc-blue-deep); +} + +.cc-window .cc-lnk { + color: #176b89; +} + +.md-typeset .cc-fold { + font-size: 13.5px; + color: var(--w-muted); +} + +.md-typeset .cc-report__foot { + font-size: 12.5px; + font-style: italic; + color: #176b89; +} + +.md-typeset .cc-event { + display: flex; + align-items: center; + gap: 10px; + margin-left: 50px; + font-size: 14px; + color: var(--w-muted); +} + +.cc-event svg { + width: 16px; + height: 16px; + flex-shrink: 0; +} + +.md-typeset .cc-event b { + color: var(--w-text); +} + +.md-typeset .cc-event code { + font-family: var(--cc-mono); + font-size: 12.5px; +} + +.md-typeset .cc-note { + margin-left: 50px; + font-size: 13.5px; + font-weight: 600; + color: #176b89; +} + +/* job-summary */ +.cc-run { + display: flex; + padding: 0; +} + +.cc-run__side { + display: flex; + flex-direction: column; + gap: 6px; + width: 200px; + flex-shrink: 0; + box-sizing: border-box; + padding: 20px 14px; + border-right: 1px solid var(--w-line); + background: #ffffff; +} + +.cc-run__side span { + display: flex; + align-items: center; + gap: 8px; + padding: 7px 10px; + border-radius: 7px; +} + +.cc-run__side .is-active { + background: #eef2f4; + font-weight: 600; +} + +.cc-run__side .cc-run__label { + padding-top: 14px; + font-size: 12px; + font-weight: 600; + color: #5b6d7a; +} + +.cc-run__main { + display: flex; + flex-direction: column; + gap: 14px; + flex-grow: 1; + min-width: 0; + padding: 20px 24px; +} + +.cc-run__meta { + display: flex; + flex-wrap: wrap; + gap: 8px 36px; + padding: 12px 16px; + border: 1px solid var(--w-line); + border-radius: 10px; + background: #ffffff; +} + +.md-typeset .cc-run__meta p { + display: flex; + flex-direction: column; + font-size: 13px; + color: #5b6d7a; +} + +.cc-run__meta b { + font-size: 14px; + color: var(--w-text); +} + +.cc-card { + border: 1px solid var(--w-line); + border-radius: 10px; + background: #ffffff; + overflow: hidden; +} + +.md-typeset .cc-card__head { + display: flex; + justify-content: space-between; + padding: 10px 16px; + border-bottom: 1px solid #e3e9ed; + font-size: 14px; + font-weight: 600; +} + +.cc-card__head .cc-dim { + font-weight: 400; +} + +.cc-card__body { + display: flex; + flex-direction: column; + gap: 10px; + padding: 14px 18px; +} + +.cc-card--pad { + display: flex; + flex-direction: column; + gap: 12px; + padding: 16px; +} + +.cc-tree { + padding: 12px 14px; + border-radius: 8px; + background: var(--w-soft); + font-family: var(--cc-mono); + font-size: 12.5px; + line-height: 19px; +} + +.cc-tree > div { + min-height: 19px; + white-space: pre; +} + +/* The one long line, a Suggest: that lists every commit type, wraps with a + hanging indent under its own text instead of scrolling the box. */ +.cc-tree > .cc-hang { + white-space: pre-wrap; + padding-left: 8ch; + text-indent: -8ch; +} + +/* pr-title, message, branch */ +.cc-stack { + display: flex; + flex-direction: column; + gap: 16px; +} + +.cc-check { + display: flex; + align-items: center; + gap: 12px; + padding: 14px 16px; +} + +.md-typeset .cc-check p { + display: flex; + flex-direction: column; + gap: 2px; + min-width: 0; +} + +.cc-check .cc-mono { + font-size: 13px; +} + +.md-typeset .cc-squash__label { + font-size: 14px; + color: var(--w-muted); +} + +.md-typeset .cc-squash__label code { + font-family: var(--cc-mono); + font-size: 12.5px; +} + +.md-typeset .cc-squash { + padding: 12px 14px; + border-radius: 8px; + background: var(--w-soft); + font-family: var(--cc-mono); + font-size: 14px; + white-space: pre-wrap; +} + +.cc-merge { + display: flex; + flex-wrap: wrap; + align-items: center; + gap: 8px 14px; + font-size: 13px; +} + +.cc-mergebtn { + display: inline-flex; + align-items: center; + height: 38px; + padding: 0 16px; + border-radius: 8px; + background: #e4e9ed; + color: #6b7c88; + font-size: 14px; + font-weight: 600; +} + +.cc-mergebtn--ready { + background: var(--cc-pass); + color: #ffffff; +} + +.cc-commit { + display: flex; + align-items: center; + gap: 12px; + padding: 12px 16px; + border-bottom: 1px solid #eef2f4; + font-size: 15px; + font-weight: 600; +} + +.cc-commit:last-child { + border-bottom: 0; +} + +.cc-commit span { + flex-grow: 1; + min-width: 0; +} + +.md-typeset .cc-commit code { + padding: 2px 7px; + border: 1px solid var(--w-line); + border-radius: 6px; + font-family: var(--cc-mono); + font-size: 12.5px; + font-weight: 400; + color: var(--w-muted); +} + +.cc-annot { + display: flex; + flex-direction: column; + gap: 6px; + padding: 12px 16px; + border-bottom: 1px solid #eef2f4; +} + +.cc-annot:last-child { + border-bottom: 0; +} + +.md-typeset .cc-annot__title { + display: flex; + align-items: center; + gap: 8px; + font-size: 14px; + font-weight: 700; +} + +.md-typeset .cc-annot .cc-mono, +.md-typeset .cc-finding .cc-mono { + font-size: 12.5px; + line-height: 1.6; + color: #3a2320; + overflow-wrap: anywhere; +} + +.cc-annot .cc-mono { + margin-left: 26px; +} + +.cc-split { + display: grid; + grid-template-columns: minmax(0, 1fr) 250px; + gap: 20px; + align-items: start; +} + +.md-typeset .cc-branchrow { + display: flex; + flex-wrap: wrap; + align-items: center; + gap: 12px; + font-size: 14px; +} + +.md-typeset .cc-branchrow code { + font-size: 15px; +} + +.cc-finding { + display: flex; + flex-direction: column; + gap: 6px; + padding: 11px 14px; + border-radius: 8px; + background: #fcefed; +} + +.md-typeset .cc-finding p:first-child { + display: flex; + align-items: center; + gap: 10px; + font-size: 13.5px; +} + +.cc-rule { + padding: 1px 7px; + border-radius: 4px; + background: var(--cc-fail); + color: #ffffff; + font-family: var(--cc-mono); + font-size: 12.5px; + font-weight: 700; +} + +.md-typeset .cc-term--inline { + padding: 14px 16px; + border-radius: 10px; + box-shadow: none; + font-size: 13.5px; + line-height: 22px; +} + +.md-typeset .cc-types { + font-size: 12.5px; +} + +.md-typeset .cc-types__head { + font-size: 13px; + font-weight: 600; + color: #5b6d7a; +} + +.md-typeset .cc-types ul { + display: flex; + flex-direction: column; + gap: 8px; +} + +.md-typeset .cc-types li { + padding: 7px 10px; + border-radius: 7px; + background: var(--w-soft); + font-family: var(--cc-mono); + font-size: 14px; + color: #33444f; + transition: background-color 400ms, color 400ms; +} + +.md-typeset .cc-tl:not(.is-live) .cc-types li[data-hl], +.md-typeset .cc-types li.is-hl { + background: #ddf3e6; + color: #146b3e; + font-weight: 700; +} + +.md-typeset .cc-types code { + font-family: var(--cc-mono); +} + +/* Below the demo: the workflow and what makes it worth running. */ +.cc-action__more { + display: grid; + gap: 24px; + width: 100%; + max-width: 1080px; + margin: 56px auto 0; +} + +@media screen and (min-width: 900px) { + .cc-action__more { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } +} + +.cc-yaml { + border: 1px solid #1f2f3b; + border-radius: 14px; + background: var(--cc-ink-3); + overflow: hidden; +} + +.md-typeset .cc-yaml__bar { + display: flex; + align-items: center; + justify-content: space-between; + height: 40px; + padding: 0 8px 0 16px; + background: var(--cc-ink-2); + font-family: var(--cc-mono); + font-size: 12.5px; + color: #9db0be; +} + +.cc-yaml__body { + padding: 16px 18px 18px; + overflow-x: auto; + font-family: var(--cc-mono); + font-size: 13px; + line-height: 21px; + color: #d6e1e8; + white-space: pre; +} + +.cc-yaml__body b { + color: #ffffff; +} + +.cc-points { + display: flex; + flex-direction: column; + gap: 14px; +} + +.cc-point { + display: flex; + flex-direction: column; + gap: 6px; + padding: 20px 22px; + border: 1px solid #1f2f3b; + border-radius: 14px; + font-size: 14.5px; + line-height: 1.5; + color: #9db0be; +} + +.md-typeset .cc-point__title { + font-size: 16px; + font-weight: 600; + color: #ffffff; +} + +.md-typeset .cc-point code { + font-family: var(--cc-mono); + font-size: 13px; + color: #d6e1e8; +} + +.md-typeset .cc-point__links { + display: flex; + flex-wrap: wrap; + gap: 8px 24px; + padding: 6px 4px 0; + font-size: 15px; + font-weight: 600; +} + +.md-typeset .cc-band--ink a:not(.cc-btn) { + color: var(--cc-blue-soft); +} + +.md-typeset .cc-band--ink a:not(.cc-btn):hover { + color: #ffffff; +} + +/* ---------------------------------------------------------------- rules -- */ + +.md-typeset ul.cc-groups { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); + gap: 24px; + margin-top: 52px; +} + +@media screen and (min-width: 1000px) { + .md-typeset ul.cc-groups { + grid-template-columns: repeat(3, minmax(0, 1fr)); + } +} + +.md-typeset .cc-group { + display: flex; + flex-direction: column; + gap: 10px; + padding: 26px; + border: 1px solid var(--cc-line); + border-radius: 16px; + background: var(--cc-card); +} + +.md-typeset .cc-group__head { + display: flex; + justify-content: space-between; + align-items: center; + gap: 12px; + font-family: var(--cc-display); + font-size: 23px; + font-weight: 700; + letter-spacing: -0.015em; + line-height: 1.2; +} + +.md-typeset .cc-group__head code { + flex-shrink: 0; + padding: 3px 8px; + border-radius: 6px; + background: var(--cc-tint); + color: var(--cc-blue-deep); + font-family: var(--cc-mono); + font-size: 12px; + font-weight: 500; + letter-spacing: 0; +} + +.md-typeset .cc-group__desc { + flex-grow: 1; + font-size: 14.5px; + line-height: 1.5; + color: var(--cc-muted); +} + +.md-typeset .cc-group__desc code { + font-family: var(--cc-mono); + font-size: 12.5px; + overflow-wrap: anywhere; +} + +.md-typeset .cc-ex { + display: flex; + gap: 10px; + padding: 8px 10px; + border-radius: 7px; + font-family: var(--cc-mono); + font-size: 13px; + overflow-wrap: anywhere; +} + +.md-typeset .cc-ex--bad { + background: #fcefed; + color: #8e231e; +} + +.md-typeset .cc-ex--ok { + background: #e6f4ec; + color: #146b3e; +} + +.md-typeset .cc-ex > span:first-child { + font-weight: 700; +} + +.md-typeset .cc-group--ink { + border-color: var(--cc-ink); + background: var(--cc-ink); + color: var(--cc-on-ink); +} + +.md-typeset .cc-group--ink .cc-group__desc { + color: #9db0be; +} + +.md-typeset .cc-group__code { + padding: 10px 12px; + border-radius: 8px; + background: var(--cc-ink-2); + font-family: var(--cc-mono); + font-size: 12.5px; + line-height: 1.5; + color: #d6e1e8; + overflow-wrap: anywhere; +} + +.md-typeset .cc-group__link, +.md-typeset .cc-group__link:hover { + font-size: 14px; + font-weight: 600; + color: var(--cc-blue-soft); +} + +/* -------------------------------------------------------------- anatomy -- */ + +.cc-anatomy__grid { + display: grid; + gap: 48px; + align-items: center; +} + +@media screen and (min-width: 960px) { + .cc-anatomy__grid { + grid-template-columns: minmax(0, 470px) minmax(0, 1fr); + gap: 72px; + } +} + +.cc-anatomy__grid > div:first-child { + display: flex; + flex-direction: column; + gap: 18px; +} + +.md-typeset ol.cc-steps { + display: flex; + flex-direction: column; + gap: 16px; + margin-top: 10px; + counter-reset: cc-step; +} + +.md-typeset .cc-steps li { + display: flex; + gap: 14px; + font-size: 16px; + line-height: 1.5; + color: var(--cc-muted); + counter-increment: cc-step; +} + +.md-typeset .cc-steps li::before, +.cc-mark { + content: counter(cc-step); + display: flex; + align-items: center; + justify-content: center; + flex-shrink: 0; + width: 28px; + height: 28px; + border-radius: 50%; + background: var(--cc-blue); + color: #06131b; + font-family: "Instrument Sans", -apple-system, "Segoe UI", sans-serif; + font-size: 14px; + font-weight: 700; +} + +.md-typeset .cc-steps b { + color: var(--cc-text); +} + +.cc-diag { + display: flex; + flex-direction: column; + padding: 30px 32px; + border-radius: 16px; + background: var(--cc-ink); + box-shadow: 0 24px 60px rgba(11, 22, 32, 0.2); + font-family: var(--cc-mono); + font-size: 14.5px; + line-height: 2.1; + color: #d6e1e8; + overflow-x: auto; +} + +.md-typeset .cc-diag p { + display: flex; + align-items: center; + gap: 12px; + white-space: nowrap; +} + +.cc-diag .cc-mark { + width: 22px; + height: 22px; + font-size: 12px; +} + +.md-typeset .cc-diag__docs { + padding-left: 34px; +} + +/* ---------------------------------------------------------------- proof -- */ + +.md-typeset .cc-proof { + padding-top: 88px; + padding-bottom: 88px; +} + +.md-typeset ul.cc-stats { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); + gap: 24px; +} + +.md-typeset .cc-stats li { + display: flex; + flex-direction: column; + gap: 6px; + padding-top: 18px; + border-top: 2px solid var(--cc-text); +} + +.cc-stats b { + font-family: var(--cc-display); + font-size: clamp(38px, 4.4vw, 52px); + font-weight: 800; + letter-spacing: -0.03em; + line-height: 1.1; +} + +.cc-stats span { + font-size: 15.5px; + color: var(--cc-muted); +} + +.md-typeset .cc-users { + display: flex; + flex-wrap: wrap; + align-items: baseline; + gap: 12px 40px; + margin-top: 44px; + padding-top: 32px; + border-top: 1px solid var(--cc-line); +} + +.cc-users span { + font-size: 14px; + color: var(--cc-muted); +} + +.md-typeset .cc-users a { + font-family: var(--cc-display); + font-size: 23px; + font-weight: 700; + letter-spacing: -0.015em; + color: #5b6d7a; +} + +[data-md-color-scheme="slate"] .md-typeset .cc-users a { + color: #a3b4c0; +} + +.md-typeset .cc-users a:hover { + color: var(--cc-text); +} + +.md-typeset .cc-users a:last-child { + font-family: "Instrument Sans", -apple-system, "Segoe UI", sans-serif; + font-size: 15px; + font-weight: 600; + letter-spacing: 0; + color: var(--cc-blue-deep); +} + +/* -------------------------------------------------------------- pricing -- */ + +.md-typeset ul.cc-plans { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); + gap: 24px; + margin-top: 48px; +} + +.md-typeset .cc-plan { + display: flex; + flex-direction: column; + gap: 10px; + padding: 28px; + border: 1px solid var(--cc-line); + border-radius: 16px; +} + +.md-typeset .cc-plan--soon { + padding: 27px; + border: 2px dashed #9fc9dc; + background: #f5fafd; +} + +[data-md-color-scheme="slate"] .md-typeset .cc-plan--soon { + border-color: #2e5467; + background: #10222d; +} + +.md-typeset .cc-plan__name { + font-size: 16px; + font-weight: 600; +} + +.md-typeset .cc-plan__price { + font-family: var(--cc-display); + font-size: 44px; + font-weight: 800; + letter-spacing: -0.03em; + line-height: 1.15; +} + +.md-typeset .cc-plan--soon .cc-plan__price { + color: var(--cc-muted); +} + +.md-typeset .cc-plan__desc { + flex-grow: 1; + font-size: 15px; + color: var(--cc-muted); +} + +.md-typeset .cc-plan a { + font-size: 15px; + font-weight: 600; +} + +.md-typeset .cc-plans__note { + margin-top: 28px; + max-width: 820px; + font-size: 15px; + color: var(--cc-muted); +} + +/* ------------------------------------------------------------------ FAQ -- */ + +.md-typeset .cc-faq { + padding-top: 96px; + padding-bottom: 96px; +} + +.md-typeset .cc-faq .cc-wrap { + max-width: 820px; +} + +.md-typeset .cc-faq .cc-h2 { + margin: 0 0 28px; +} + +.md-typeset .cc-faq details { + margin: 0 0 10px; + border: 1px solid var(--cc-line); + border-radius: 12px; + background: var(--cc-card); + box-shadow: none; + font-size: 15px; +} + +.md-typeset .cc-faq details > summary { + padding: 14px 44px 14px 18px; + background: none; + font-size: 16px; + font-weight: 600; +} + +.md-typeset .cc-faq details > summary::before { + display: none; +} + +.md-typeset .cc-faq details > summary::after { + top: 16px; + right: 14px; +} + +.md-typeset .cc-faq details > p { + margin: 0 18px 14px; +} + +/* ------------------------------------------------------------------ CTA -- */ + +.cc-cta__grid { + display: grid; + gap: 40px; + align-items: center; +} + +@media screen and (min-width: 960px) { + .cc-cta__grid { + grid-template-columns: minmax(0, 1fr) minmax(0, 500px); + gap: 60px; + } +} + +.cc-cta__grid > div:first-child { + display: flex; + flex-direction: column; + gap: 16px; +} + +.cc-cta__grid .cc-actions { + margin-top: 10px; +} + +.md-typeset .cc-h2--xl { + color: #ffffff; +} + +.cc-cta__code { + padding: 26px 28px; + border: 1px solid #1f2f3b; + border-radius: 14px; + background: var(--cc-ink-3); + font-family: var(--cc-mono); + font-size: 16px; + line-height: 2.1; + color: var(--cc-on-ink); + overflow-x: auto; + white-space: nowrap; +} + +/* -------------------------------------------------------------- sitemap -- */ + +.md-typeset .cc-sitemap { + padding-top: 56px; + padding-bottom: 48px; + background: #081119; + border-top: 1px solid #16242f; +} + +.cc-sitemap__grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); + gap: 28px 24px; + font-size: 14.5px; +} + +.cc-sitemap__grid > div { + display: flex; + flex-direction: column; + gap: 11px; +} + +.md-typeset .cc-sitemap p { + font-weight: 600; + color: #ffffff; +} + +.md-typeset .cc-sitemap a { + color: #9db0be; +} + +.md-typeset .cc-sitemap a:hover { + color: #ffffff; +} + +/* ---------------------------------------------------------- small screens -- */ + +@media screen and (max-width: 759px) { + .md-typeset .cc-band { + padding-top: 64px; + padding-bottom: 64px; + font-size: 15px; + } + + .md-typeset .cc-hero { + padding-top: 40px; + padding-bottom: 0; + } + + .md-typeset .cc-hero__graph { + display: none; + } + + .md-typeset .cc-lede { + font-size: 16.5px; + } + + .cc-actions, + .md-typeset .cc-btn, + .md-typeset .cc-install { + width: 100%; + } + + .md-typeset .cc-btn { + justify-content: center; + } + + .md-typeset .cc-install code { + flex-grow: 1; + } + + .md-typeset ul.cc-facts { + margin-top: 40px; + flex-direction: column; + } + + .cc-term__body { + padding: 14px 16px 18px; + font-size: 12px; + line-height: 19px; + } + + .cc-line { min-height: 19px; } + .cc-gap { margin-top: 19px; } + + .md-typeset .cc-sub { + font-size: 16px; + } + + .cc-head--center { + align-items: flex-start; + text-align: left; + } + + /* The demo keeps its story on a phone, with the chrome pared back: no + sidebar, no avatar, and the tab strip scrolls sideways. */ + .cc-tabs { + justify-content: flex-start; + width: calc(100% + 32px); + margin-inline: -16px; + padding-inline: 16px; + } + + .md-typeset .cc-tab { + padding: 0 15px; + font-size: 13px; + } + + .md-typeset .cc-caption { + text-align: left; + align-self: stretch; + } + + .md-typeset .cc-caption strong { + font-size: 16px; + } + + .cc-demo.is-ready .cc-window { + min-height: 640px; + } + + .cc-pr { + padding: 14px 16px 0; + } + + .md-typeset .cc-pr__title { + font-size: 18px; + min-height: 32px; + } + + .cc-editbox { + height: 34px; + font-size: 16px; + } + + .cc-pr__tabs .cc-wide, + .cc-avatar, + .cc-run__side { + display: none; + } + + .md-typeset .cc-pr__tabs { + gap: 16px; + font-size: 13px; + } + + .cc-window__body { + padding: 16px; + } + + .cc-run__main { + padding: 16px; + } + + .md-typeset .cc-event, + .md-typeset .cc-note { + margin-left: 0; + } + + /* The report table turns into one block per failing scope. */ + .md-typeset .cc-table thead { + display: none; + } + + .md-typeset .cc-table tr { + display: flex; + flex-direction: column; + padding: 8px 10px; + border-bottom: 1px solid #e8edf0; + } + + .md-typeset .cc-table tr:last-child { + border-bottom: 0; + } + + .md-typeset .cc-table td { + padding: 1px 0; + border: 0; + } + + .cc-tree { + font-size: 11.5px; + line-height: 18px; + } + + .cc-tree > div { + white-space: pre-wrap; + overflow-wrap: anywhere; + } + + .cc-split { + grid-template-columns: minmax(0, 1fr); + } + + .md-typeset .cc-types ul { + flex-direction: row; + flex-wrap: wrap; + } + + .cc-annot .cc-mono { + margin-left: 0; + } + + .cc-commit { + font-size: 14px; + padding: 11px 12px; + } + + .cc-diag { + padding: 20px; + font-size: 12.5px; + line-height: 1.9; + } + + .md-typeset .cc-diag p { + white-space: normal; + align-items: flex-start; + } + + .md-typeset .cc-diag__docs { + padding-left: 0; + overflow-wrap: anywhere; + } + + .cc-cta__code { + font-size: 13.5px; + padding: 18px 20px; + } + + .md-typeset .cc-users a { + font-size: 19px; + } + + .cc-sitemap__grid { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } +} + +/* ------------------------------------------------------- reduced motion -- */ + +/* The script never starts a timeline under reduced motion, so every demo + already rests on its final frame; this stops the carets and hover lifts. */ +@media (prefers-reduced-motion: reduce) { + .cc-caret { + animation: none; + } + + .md-typeset .cc-surface, + .md-typeset .cc-surface:hover { + transition: none; + transform: none; + } +} + +/* JetBrains Mono draws `==>` as one arrow. The transcripts must show what the + terminal printed, character for character. */ +.md-typeset .cc-band :is(.cc-term, .cc-diag, .cc-tree, .cc-mono, code, .cc-yaml__body, .cc-config__body, .cc-cta__code, .cc-squash, .cc-install, .cc-tab, .cc-ex, .cc-group__code) { + font-variant-ligatures: none; +} + +/* In the dark scheme the cards are already near Ink; an outline keeps the + organization card from dissolving into its neighbours. */ +[data-md-color-scheme="slate"] .md-typeset .cc-group--ink { + border-color: #22404f; +} diff --git a/mkdocs.yml b/mkdocs.yml index 0b0b75c..bcbfca8 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -35,8 +35,11 @@ theme: favicon: assets/favicon.svg icon: repo: fontawesome/brands/github + # Instrument Sans for text and JetBrains Mono for code; the display face, + # Bricolage Grotesque, is loaded in overrides/main.html because the theme + # only takes two families. font: - text: Inter + text: Instrument Sans code: JetBrains Mono palette: - media: "(prefers-color-scheme)" @@ -80,6 +83,11 @@ extra_css: - assets/extra.css - stylesheets/extra.css +# The landing page's two demos. Loaded everywhere, because instant navigation +# does not run scripts that arrive with a page; it does nothing elsewhere. +extra_javascript: + - javascripts/landing.js + extra: social: - icon: fontawesome/brands/github @@ -136,7 +144,8 @@ plugins: - social: enabled: !ENV [SOCIAL_CARDS, true] cards_layout_options: - background_color: "#2c9ccd" + background_color: "#0b1620" + color: "#ffffff" hooks: - scripts/mkdocs_hooks.py From cdf216c3662ec0dc78ed9de397f0d375c2d3ba32 Mon Sep 17 00:00:00 2001 From: Xianpeng Shen Date: Thu, 24 Sep 2026 13:50:59 +0300 Subject: [PATCH 2/2] fix: make the theme toggle visibly change the page The header, the hero and the footer were Ink in both schemes, so at the top of the page the sun and moon changed nothing a reader could see, and the three-state toggle added a "follow system" step that, on a light system, changed nothing at all. The light scheme now has a white header and a light hero, and the toggle has two states; the first visit still follows the system through the palette's media queries. --- docs/assets/extra.css | 21 ++++++++++++++ docs/stylesheets/landing.css | 56 ++++++++++++++++++++++++++++++++++++ mkdocs.yml | 8 +++--- 3 files changed, 81 insertions(+), 4 deletions(-) diff --git a/docs/assets/extra.css b/docs/assets/extra.css index fd32020..72aa9a9 100644 --- a/docs/assets/extra.css +++ b/docs/assets/extra.css @@ -43,6 +43,27 @@ [data-md-color-scheme="default"] { --md-typeset-a-color: var(--cc-link); + /* A light header in the light scheme, Ink in the dark one, so the header + answers the theme toggle like the rest of the page. */ + --md-primary-fg-color: #ffffff; + --md-primary-fg-color--light: #f4f6f7; + --md-primary-fg-color--dark: #eef2f4; + --md-primary-bg-color: var(--cc-ink); + --md-primary-bg-color--light: #4f6270; +} + +[data-md-color-scheme="default"] .md-header { + box-shadow: inset 0 -1px 0 #dce3e8; +} + +/* The theme's search field is a translucent black meant for a coloured + header; on a white one it needs a solid tint instead. */ +[data-md-color-scheme="default"] .md-search__form { + background-color: #eef2f4; +} + +[data-md-color-scheme="default"] .md-search__form:hover { + background-color: #e4e9ed; } /* A primary button is Signal Blue with Ink text: white on this blue is 3.1:1, diff --git a/docs/stylesheets/landing.css b/docs/stylesheets/landing.css index 1588b3b..95cb5ef 100644 --- a/docs/stylesheets/landing.css +++ b/docs/stylesheets/landing.css @@ -2477,3 +2477,59 @@ [data-md-color-scheme="slate"] .md-typeset .cc-group--ink { border-color: #22404f; } + +/* --------------------------------------------------- hero, light scheme -- */ + +/* In the light scheme the hero is light too, so the theme toggle changes what + is on screen at the top of the page. The terminal and the install command + stay dark: they stand for a terminal, which is dark in either theme. The + Action band and the closing band stay Ink in both schemes as a stage for + the demo. */ +[data-md-color-scheme="default"] .md-typeset .cc-hero { + background: #ffffff; + color: var(--cc-text); + box-shadow: inset 0 -1px 0 var(--cc-line); +} + +[data-md-color-scheme="default"] .md-typeset .cc-hero .cc-display { + color: var(--cc-text); +} + +[data-md-color-scheme="default"] .md-typeset .cc-hero .cc-accent { + /* Signal Blue is 3.1:1 on white, enough for display type (3:1 from 24px). */ + color: var(--cc-blue); +} + +[data-md-color-scheme="default"] .md-typeset .cc-hero .cc-lede { + color: var(--cc-muted); +} + +[data-md-color-scheme="default"] .md-typeset .cc-hero .cc-lede code { + color: var(--cc-text); +} + +[data-md-color-scheme="default"] .md-typeset .cc-hero .cc-pill { + border-color: #b9dcec; + background: var(--cc-tint); + color: #135a74; +} + +[data-md-color-scheme="default"] .md-typeset .cc-hero ul.cc-facts { + border-top-color: var(--cc-line); + color: var(--cc-muted); +} + +[data-md-color-scheme="default"] .md-typeset .cc-hero .cc-install { + border-color: var(--cc-ink); + background: var(--cc-ink); +} + +[data-md-color-scheme="default"] .md-typeset .cc-hero .cc-term { + box-shadow: 0 24px 60px rgba(11, 22, 32, 0.22); +} + +/* The history-line motif's nodes are filled with the ground colour so the + lines pass behind them; on the light hero that ground is white. */ +[data-md-color-scheme="default"] .md-typeset .cc-hero__graph circle { + fill: #ffffff; +} diff --git a/mkdocs.yml b/mkdocs.yml index bcbfca8..5c73338 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -41,11 +41,11 @@ theme: font: text: Instrument Sans code: JetBrains Mono + # Two states, not three. The first visit still follows the system through + # the media queries; after that every click visibly changes the page. A + # third "follow system" state made one click in three do nothing a reader + # could see, which read as a broken toggle. palette: - - media: "(prefers-color-scheme)" - toggle: - icon: material/brightness-auto - name: Follow system theme - media: "(prefers-color-scheme: light)" scheme: default primary: custom