zed/docs/theme/css/chrome.css
morgankrey 10504e3ce1
Some checks are pending
Congratsbot / check-author (push) Waiting to run
Congratsbot / congrats (push) Blocked by required conditions
deploy_nightly_docs / deploy_docs (push) Waiting to run
run_tests / orchestrate (push) Waiting to run
run_tests / check_style (push) Waiting to run
run_tests / clippy_windows (push) Blocked by required conditions
run_tests / clippy_linux (push) Blocked by required conditions
run_tests / clippy_mac (push) Blocked by required conditions
run_tests / clippy_mac_x86_64 (push) Blocked by required conditions
run_tests / run_tests_windows (push) Blocked by required conditions
run_tests / run_tests_linux (push) Blocked by required conditions
run_tests / run_tests_mac (push) Blocked by required conditions
run_tests / miri_scheduler (push) Blocked by required conditions
run_tests / doctests (push) Blocked by required conditions
run_tests / check_workspace_binaries (push) Blocked by required conditions
run_tests / build_visual_tests_binary (push) Blocked by required conditions
run_tests / check_wasm (push) Blocked by required conditions
run_tests / check_dependencies (push) Blocked by required conditions
run_tests / check_docs (push) Blocked by required conditions
run_tests / check_licenses (push) Blocked by required conditions
run_tests / check_scripts (push) Blocked by required conditions
run_tests / check_postgres_and_protobuf_migrations (push) Blocked by required conditions
run_tests / extension_tests (push) Blocked by required conditions
run_tests / tests_pass (push) Blocked by required conditions
Improve docs AI readiness (#59577)
Context

This PR makes the Zed docs easier for AI tools, search crawlers, and
users to consume without changing the visible docs content. The current
production docs are primarily optimized for browser navigation. They do
not expose first-class Markdown URLs, an `llms.txt` index, page-level
copy affordances, or machine-readable freshness metadata that let users
and agents grab clean, current page content.

Changes

- Generate Markdown copies for docs pages during the mdBook postprocess
step, including `/docs/index.md` as an alias for Getting Started.
- Generate `/docs/llms.txt` from the mdBook chapter list, grouped by
`SUMMARY.md` sections and annotated with page frontmatter descriptions.
- Generate `/docs/sitemap.xml` with `<lastmod>` values for every docs
page.
- Emit machine-readable freshness metadata in HTML via `last-modified`
and `article:modified_time` meta tags.
- Generate Cloudflare Pages `_redirects` for `.html`, extensionless, and
`.md` redirect variants, with channel-aware docs destinations.
- Add discovery hints for agents and crawlers: `rel="llms.txt"`,
`rel="alternate" type="text/markdown"`, and a short generated `llms.txt`
directive in copied Markdown pages.
- Update the docs proxy so `Accept: text/markdown`, `/docs.md`, and
direct `.md` requests can resolve to the generated Markdown artifacts.
- Move primary docs content earlier in the HTML source while preserving
the visible layout, so crawlers and agent scorers encounter the article
before sidebar chrome.
- Move the existing copy-as-Markdown control from the top navigation
into the page-title row, using the generated Markdown alternate link as
the source of truth.
- Split AI-discovery artifact generation out of
`docs_preprocessor/src/main.rs` into a focused module.

Best Practices Adopted

- Use `llms.txt` as a concise navigation index, not a dump of full page
content.
- Link to absolute, canonical Markdown URLs from `llms.txt`.
- Preserve the docs hierarchy in `llms.txt` instead of emitting a flat
sitemap-like list.
- Include short per-link descriptions from existing metadata rather than
inventing summaries.
- Keep `llms.txt`, Markdown copies, sitemap data, redirects, and
freshness metadata generated from the same mdBook source to avoid drift.
- Advertise Markdown alternates with standard HTML metadata and
same-origin URLs.
- Support both explicit Markdown URLs and content negotiation for
clients that prefer Markdown.
- Keep browser copy behavior pointed at generated Markdown alternate
links instead of duplicating route inference in JavaScript.
- Keep the copy-as-Markdown affordance in the page title row without
duplicating header chrome controls.

Validation

- `cargo check -p docs_preprocessor`
- `cargo test -p docs_preprocessor`
- `./script/clippy -p docs_preprocessor`
- `mdbook build ./docs --dest-dir=../target/deploy/docs/`
- `node --check docs/theme/plugins.js`
- `pnpm dlx prettier@3.5.0 docs/theme/plugins.js --check`
- `git diff --check`
- Local artifact checks confirmed generated Markdown pages, `llms.txt`,
`sitemap.xml` lastmod values, HTML freshness metadata, Markdown
alternate links, redirect targets, and preprocessed action/keybinding
tags resolve as expected.
- Worker URL rewrite mock covered `/docs/`, `/docs.md`,
`/docs/index.md`, extensionless docs routes, `.html` routes,
`/docs/llms.txt`, and `/docs/sitemap.xml`.
- High-effort adversarial subagent review found blockers around
channel-aware redirects, shallow-checkout date fallback, file size,
duplicated Markdown path inference, and process spawning. Those were
addressed.

Remaining Notes

- Local `python -m http.server` does not emulate Cloudflare Pages pretty
URLs, `_redirects`, or the docs-proxy Worker, so full local `afdocs`
still cannot prove content negotiation end to end.
- Existing production remains unchanged until this PR is deployed
through the docs workflow.
- Production baseline `npx afdocs check https://zed.dev/docs/ --fixes
--verbose` still reports the original failures before this PR is
deployed: 12 passed, 8 failed, 3 skipped.

Release Notes:

- Improved docs AI-readiness by adding machine-readable discovery,
Markdown access, and freshness metadata.

---------

Co-authored-by: Katie Geer <katie@zed.dev>
Co-authored-by: Ben Kunkle <ben@zed.dev>
2026-07-08 20:54:51 +00:00

868 lines
16 KiB
CSS
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* CSS for UI elements (a.k.a. chrome) */
@import "variables.css";
html {
background-color: var(--bg);
scrollbar-color: var(--scrollbar) var(--bg);
}
#searchresults a,
.content a:link,
a:visited,
a > .hljs {
color: var(--links);
}
.icon-logo-img {
display: block;
}
.icon-button {
position: relative;
height: 28px;
width: 28px;
z-index: 10;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: color 0.5s;
border: 0;
background-color: transparent;
border-radius: 4px;
color: var(--icons);
}
.icon-button:hover {
color: var(--icons-hover);
background-color: var(--icon-btn-bg-hover);
}
.ib-hidden-desktop {
display: none;
}
.header-bar {
position: sticky;
top: 0;
z-index: 100;
padding: 12px 16px;
background-color: var(--sidebar-bg);
border-bottom: 1px solid var(--divider);
display: flex;
align-items: center;
justify-content: space-between;
flex-shrink: 0;
}
.header-bar .left-container {
width: 160px;
display: flex;
align-items: center;
gap: 8px;
}
.header-bar .right-container {
width: 160px;
display: flex;
align-items: center;
gap: 4px;
}
.logo-nav {
display: flex;
align-items: center;
justify-content: center;
filter: var(--logo-brightness);
}
.nav-chapters {
font-size: 2.5em;
text-align: center;
text-decoration: none;
position: fixed;
top: 0;
bottom: 0;
margin: 0;
max-width: 150px;
min-width: 90px;
display: flex;
justify-content: center;
align-content: center;
flex-direction: column;
transition:
color 0.5s,
background-color 0.5s;
}
.nav-chapters:hover {
text-decoration: none;
background-color: var(--theme-hover);
transition:
background-color 0.15s,
color 0.15s;
}
.nav-wrapper {
margin-block-start: 50px;
display: none;
}
.footer-buttons {
display: flex;
justify-content: space-between;
align-items: center;
gap: 1rem;
padding: 24px 0;
}
.footer-button {
width: 100%;
padding: 12px;
display: flex;
align-items: center;
justify-content: center;
gap: 0.5rem;
background-color: var(--footer-btn-bg);
border: 1px solid var(--footer-btn-border);
border-radius: 0.5rem;
font-size: 0.9em;
}
.footer-button:hover {
background-color: var(--footer-btn-bg-hover);
border-color: var(--footer-btn-border-hover);
}
.footer-button i {
text-decoration: underline !important;
text-decoration-color: transparent !important;
}
.footer {
display: flex;
align-items: center;
justify-content: center;
flex-wrap: wrap;
gap: 0.5rem;
padding: 24px 0;
border-top: 1px dashed var(--border-footer);
}
.footer-link {
font-size: 1.25rem;
color: var(--links);
text-decoration: underline;
text-decoration-color: var(--link-line-decoration);
text-wrap-mode: nowrap;
:hover {
text-decoration-color: var(--link-line-decoration-hover);
}
}
.footer-separator {
color: var(--border-light);
font-size: 0.8em;
}
.footer-logo {
height: 16px;
border-radius: 0 !important;
border: none !important;
background: transparent !important;
}
.mobile-nav-chapters {
font-size: 2.5em;
text-align: center;
text-decoration: none;
width: 90px;
border-radius: 5px;
background-color: var(--sidebar-bg);
}
/* Only Firefox supports flow-relative values */
.previous {
float: left;
}
[dir="rtl"] .previous {
float: right;
}
/* Only Firefox supports flow-relative values */
.next {
float: right;
right: var(--page-padding);
}
[dir="rtl"] .next {
float: left;
right: unset;
left: var(--page-padding);
}
/* Use the correct buttons for RTL layouts*/
[dir="rtl"] .previous i.fa-angle-left:before {
content: "\f105";
}
[dir="rtl"] .next i.fa-angle-right:before {
content: "\f104";
}
@media only screen and (max-width: 1080px) {
.nav-wide-wrapper {
display: none;
}
.nav-wrapper {
display: block;
}
}
/* sidebar-visible */
@media only screen and (max-width: 1380px) {
#sidebar-toggle-anchor:checked ~ .page-wrapper .nav-wide-wrapper {
display: none;
}
#sidebar-toggle-anchor:checked ~ .page-wrapper .nav-wrapper {
display: block;
}
}
/* Inline code */
:not(pre) > .hljs {
display: inline;
padding: 0.1em 0.3em;
border-radius: 3px;
}
:not(pre):not(a) > .hljs {
color: var(--inline-code-color);
overflow-x: initial;
}
a:hover > .hljs {
text-decoration: underline;
}
pre {
background-color: var(--pre-bg);
border: 1px solid;
border-color: var(--pre-border);
box-shadow: var(--pre-shadow) 4px 4px 0px 0px;
position: relative;
}
pre > .hljs {
background-color: initial;
}
pre > .buttons {
position: absolute;
z-index: 100;
right: 0px;
top: 2px;
margin: 0px;
padding: 2px 0px;
color: var(--sidebar-fg);
cursor: pointer;
visibility: hidden;
opacity: 0;
transition:
visibility 0.1s linear,
opacity 0.1s linear;
}
pre:hover > .buttons {
visibility: visible;
opacity: 1;
}
pre > .buttons :hover {
color: var(--sidebar-active);
border-color: var(--border-hover);
background-color: var(--theme-hover);
}
pre > .buttons i {
margin-inline-start: 8px;
}
pre > .buttons button {
cursor: inherit;
margin: 0 4px;
height: 26px;
width: 26px;
font-size: 14px;
border-style: solid;
border-width: 1px;
border-radius: 4px;
border-color: var(--border);
background-color: var(--popover-bg);
transition: 100ms;
transition-property: color, border-color, background-color;
color: var(--icons);
}
pre > .playground {
border: none;
margin: 0;
box-shadow: none;
/* HACK: This serves to visually hide nested <pre> elements in "playground" code snippets.
A more robust solution would involve modifying the rendered HTML. */
}
@media (pointer: coarse) {
pre > .buttons button {
/* On mobile, make it easier to tap buttons. */
padding: 0.3rem 1rem;
}
.sidebar-resize-indicator {
/* Hide resize indicator on devices with limited accuracy */
display: none;
}
}
pre > code {
display: block;
padding: 1rem;
}
/* TODO: ACE editors overlap their buttons because ACE does absolute
positioning within the code block which breaks padding. The only solution I
can think of is to move the padding to the outer pre tag (or insert a div
wrapper), but that would require fixing a whole bunch of CSS rules.
*/
.hljs.ace_editor {
padding: 0rem 0rem;
}
pre > .result {
margin-block-start: 10px;
}
/* Search */
#searchresults a {
text-decoration: none;
}
mark {
border-radius: 2px;
padding-block-start: 0;
padding-block-end: 1px;
padding-inline-start: 3px;
padding-inline-end: 3px;
margin-block-start: 0;
margin-block-end: -1px;
margin-inline-start: -3px;
margin-inline-end: -3px;
background-color: var(--search-mark-bg);
transition: background-color 300ms linear;
cursor: pointer;
}
mark.fade-out {
background-color: rgba(0, 0, 0, 0) !important;
cursor: auto;
}
.searchbar-outer {
margin-inline-start: auto;
margin-inline-end: auto;
width: 100%;
max-width: var(--content-max-width);
box-sizing: border-box;
padding: 16px;
}
#searchbar {
width: 100%;
margin-block-start: 5px;
margin-block-end: 0;
margin-inline-start: auto;
margin-inline-end: auto;
padding: 10px 16px;
transition: box-shadow 300ms ease-in-out;
border: 1px solid var(--searchbar-border-color);
border-radius: 3px;
background-color: var(--searchbar-bg);
color: var(--searchbar-fg);
}
#searchbar:focus,
#searchbar.active {
box-shadow: 0 0 3px var(--searchbar-shadow-color);
outline: none;
border-color: var(--search-mark-bg);
}
.searchresults-header {
font-weight: bold;
font-size: 1em;
padding-block-start: 0;
padding-block-end: 0;
color: var(--searchresults-header-fg);
}
ul#searchresults {
list-style: none;
padding-inline-start: 0;
margin-block-end: 0;
}
ul#searchresults li {
margin: 10px 0px;
padding: 2px;
border-radius: 2px;
scroll-margin-block-end: 10px;
}
ul#searchresults li.focus {
background-color: var(--searchresults-li-bg);
}
ul#searchresults span.teaser {
display: block;
font-size: 0.8em;
margin-block-start: 5px;
margin-inline-start: 4px;
padding-inline-start: 2ch;
border-left: 1px solid var(--divider);
}
ul#searchresults span.teaser em {
font-weight: bold;
color: var(--full-contrast);
background: var(--code-bg);
}
/* Sidebar */
.sidebar {
position: relative;
order: 0;
width: var(--sidebar-width);
flex-shrink: 0;
display: flex;
flex-direction: column;
font-size: 0.875em;
box-sizing: border-box;
-webkit-overflow-scrolling: touch;
overscroll-behavior-y: none;
overflow: hidden;
background-color: var(--sidebar-bg);
color: var(--sidebar-fg);
border-right: 1px solid var(--divider);
}
[dir="rtl"] .sidebar {
left: unset;
right: 0;
}
.sidebar-resizing {
-moz-user-select: none;
-webkit-user-select: none;
-ms-user-select: none;
user-select: none;
}
.no-js .sidebar,
.js:not(.sidebar-resizing) .sidebar {
transition: transform 0.3s; /* Animation: slide away */
}
.sidebar code {
line-height: 2em;
}
.sidebar .sidebar-scrollbox {
flex: 1;
overflow-y: auto;
min-height: 0;
}
.sidebar .sidebar-resize-handle {
position: absolute;
cursor: col-resize;
width: 0;
right: calc(var(--sidebar-resize-indicator-width) * -1);
top: 0;
bottom: 0;
display: flex;
align-items: center;
}
.sidebar-resize-handle .sidebar-resize-indicator {
width: 100%;
height: 12px;
background-color: var(--icons);
margin-inline-start: var(--sidebar-resize-indicator-space);
}
[dir="rtl"] .sidebar .sidebar-resize-handle {
left: calc(var(--sidebar-resize-indicator-width) * -1);
right: unset;
}
.js .sidebar .sidebar-resize-handle {
cursor: col-resize;
width: calc(
var(--sidebar-resize-indicator-width) -
var(--sidebar-resize-indicator-space)
);
}
.sidebar::-webkit-scrollbar {
background: var(--sidebar-bg);
}
.sidebar::-webkit-scrollbar-thumb {
background: var(--scrollbar);
}
@media only screen and (max-width: 780px) {
.sidebar {
position: fixed;
top: 0;
left: 0;
height: 100vh;
padding-top: 57px; /* Account for header height */
transform: translateX(-100%);
z-index: 99;
transition: transform 0.1s ease;
}
[dir="rtl"] .sidebar {
left: unset;
right: 0;
transform: translateX(100%);
}
body.sidebar-open .sidebar {
box-shadow: var(--sidebar-mobile-shadow);
transform: translateX(0);
}
}
.chapter {
list-style: none outside none;
padding: 16px;
padding-top: 8px;
line-height: 2.2em;
margin: 0;
}
.chapter ol {
width: 100%;
}
.chapter li {
display: flex;
color: var(--sidebar-non-existant);
}
.chapter li a {
display: block;
padding: 0 4px;
text-decoration: none;
color: var(--sidebar-fg);
}
.chapter li a:hover {
color: var(--sidebar-active);
}
.chapter li a.active {
color: var(--sidebar-active);
background-color: var(--sidebar-active-bg);
}
.chapter li > a.toggle {
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
margin-inline-start: auto;
user-select: none;
opacity: 0.5;
border-radius: 4px;
transition:
opacity 0.15s ease,
background-color 0.15s ease;
}
.chapter li > a.toggle:hover {
opacity: 1;
background-color: var(--theme-hover);
}
.chapter li.chapter-item {
display: flex;
flex-wrap: wrap;
align-items: center;
line-height: 1.5em;
margin-block-start: 0.6em;
}
.section li.chapter-item {
margin-block-start: 0;
}
.chapter li.part-title + li.chapter-item {
margin-block-start: 0.2em;
}
.chapter li.chapter-item > a:first-child {
flex: 1;
min-width: 0;
}
.chapter li.expanded > a.toggle div {
transform: rotate(90deg);
}
.chapter li.part-title {
font-size: 1.4rem;
padding: 0 8px 0 4px;
color: var(--title-color);
cursor: pointer;
user-select: none;
display: flex;
align-items: center;
justify-content: space-between;
line-height: auto;
border-radius: 2px;
}
.chapter li.part-title.collapsible:hover {
background-color: var(--hover-section-title);
}
.chapter li.part-title.collapsible::after {
content: "";
display: inline-block;
font-size: 1rem;
opacity: 0.6;
transition: transform 0.2s ease;
flex-shrink: 0;
}
.chapter li.part-title.collapsible.expanded::after {
transform: rotate(90deg);
}
.chapter li.section-spacer {
height: 1.5rem;
list-style: none;
}
.chapter li.section-hidden {
display: none !important;
}
.section {
list-style: none outside none;
padding-inline-start: 1.5ch;
margin-inline-start: 1ch;
margin-top: 6px;
border-left: 1px solid var(--border-light);
line-height: 1.9em;
display: flex;
flex-direction: column;
gap: 0.6em;
}
.theme-popup {
position: absolute;
right: 155px;
top: calc(var(--menu-bar-height) - 18px);
z-index: 1000;
border-radius: 4px;
font-size: 1.4rem;
color: var(--fg);
background: var(--popover-bg);
border: 1px solid var(--popover-border);
margin: 0;
padding: 0;
list-style: none;
display: none;
overflow: hidden;
}
[dir="rtl"] .theme-popup {
left: unset;
right: 10px;
}
.theme-popup .default {
color: var(--icons);
}
.theme-popup .theme {
width: 100%;
border: 0;
margin: 0;
padding: 2px 24px;
line-height: 25px;
white-space: nowrap;
text-align: start;
cursor: pointer;
color: inherit;
background: inherit;
font-size: inherit;
font-family: inherit;
}
.theme-popup .theme:hover {
background-color: var(--theme-hover);
}
.theme-selected::before {
font-family: Arial, Helvetica, sans-serif;
text-align: center;
display: inline-block;
content: "✓";
margin-inline-start: -20px;
width: 20px;
}
.download-button {
max-height: 28px;
margin-left: 8px;
background: var(--download-btn-bg);
color: var(--download-btn-color);
padding: 4px 8px;
border: 1px solid;
border-color: var(--download-btn-border);
font-size: 1.4rem;
border-radius: 4px;
box-shadow: var(--download-btn-shadow) 0px -2px 0px 0px inset;
text-decoration: none;
transition: 100ms;
transition-property: box-shadow, border-color, background-color;
}
.download-button:hover {
background: var(--download-btn-bg);
border-color: var(--download-btn-border-hover);
box-shadow: none;
}
.search-button {
min-width: 100px;
max-width: 300px;
height: 28px;
width: 100%;
padding: 4px 4px 4px 8px;
display: flex;
gap: 8px;
background: var(--search-btn-bg);
border: 1px solid;
border-color: var(--search-btn-border);
font-size: 1.4rem;
font-family: var(--font);
color: var(--icons);
border-radius: 4px;
transition: 100ms;
transition-property: box-shadow, border-color, background-color;
}
.search-button:hover {
background: var(--search-btn-bg-hover);
}
.search-button .icon {
width: 12px;
height: 12px;
transform: translateY(10%);
scale: 0.9;
}
.search-content-desktop {
width: 100%;
display: flex;
justify-content: space-between;
}
.search-content-mobile {
display: none;
}
.search-container {
box-sizing: border-box;
position: fixed;
inset: 0;
z-index: 1000;
padding: 24px;
padding-top: 72px;
background-color: rgba(0, 0, 0, 0.5);
display: none;
justify-content: center;
}
.search-container:has(#search-wrapper:not(.hidden)) {
display: flex;
}
.search-modal {
box-sizing: border-box;
max-width: 600px;
min-width: 600px;
height: fit-content;
max-height: 600px;
display: flex;
flex-direction: column;
overflow-y: hidden;
border-radius: 8px;
background: var(--popover-bg);
border: 1px solid var(--popover-border);
box-shadow: var(--popover-shadow);
}
.searchresults-outer {
flex: 1;
min-height: 0;
overflow-y: auto;
padding: 0px 22px 22px 22px;
}
#searchbar {
margin: 0;
}
@media only screen and (max-width: 780px) {
.header-bar {
padding: 16px;
justify-content: start;
}
.download-button {
display: none;
}
.ib-hidden-mobile {
display: none;
}
.header-bar .left-container {
width: fit-content;
}
.header-bar .right-container {
width: fit-content;
}
.search-button {
width: 100px;
margin-left: auto;
margin-right: 8px;
}
.ib-hidden-desktop {
display: block;
}
.search-modal {
width: 90vw;
min-width: auto;
}
.search-content-desktop {
display: none;
}
.search-content-mobile {
display: flex;
}
.theme-popup {
right: 15px;
}
}