diff --git a/_assets/styles/_docs.scss b/_assets/styles/_docs.scss
index 3474e545c..745eee199 100644
--- a/_assets/styles/_docs.scss
+++ b/_assets/styles/_docs.scss
@@ -14,10 +14,6 @@ $docs-primary-tint: tint-color($primary, 30%);
$docs-muted: #4b433c;
$docs-hairline: rgb(167 151 139 / 55%);
-// A pale wash with deep-shade text, so the chip clears AA at this size.
-$docs-flag-bg: tint-color($quaternary, 88%);
-$docs-flag-text: shade-color($quaternary, 25%);
-
// Scoped to `main` to keep the resets off the navbar and footer, which are
// its siblings under the same body class. The padding replaces what
// `main > .container` gives the footer elsewhere.
@@ -40,8 +36,10 @@ $docs-flag-text: shade-color($quaternary, 25%);
// Ink, so it carries on from the navbar rather than leaving a pale band
// between the two.
.docs-head {
+ position: relative;
padding-top: calc(#{$navbar-height} + clamp(2rem, 5vw, 3.5rem));
padding-bottom: clamp(2.25rem, 5vw, 3.5rem);
+ overflow: hidden;
color: $body-bg;
background-color: $body-color;
}
@@ -50,7 +48,30 @@ $docs-flag-text: shade-color($quaternary, 25%);
outline-color: $body-bg;
}
+// The ring off the logogram, the same drawing home's masthead and the news
+// head use, tuned for the glance panel it sits behind here.
+.docs-head-ring {
+ position: absolute;
+ inset-block-start: 0;
+ inset-inline-end: 0;
+ z-index: 0;
+ width: min(48vw, 360px);
+ aspect-ratio: 1;
+ color: $docs-primary-tint;
+ pointer-events: none;
+ opacity: 0.12;
+ transform: translate(22%, -30%);
+}
+
+.docs-head-ring svg {
+ display: block;
+ width: 100%;
+ height: auto;
+}
+
.docs-head-grid {
+ position: relative;
+ z-index: 1;
display: grid;
grid-template-columns: minmax(0, 1.4fr) minmax(0, 0.85fr);
gap: clamp(2rem, 5vw, 4rem);
@@ -79,18 +100,20 @@ $docs-flag-text: shade-color($quaternary, 25%);
color: rgb(231 231 214 / 76%);
}
-/* --- Head: the invite -----------------------------------------------
- The shape `.join` has on the home page, carrying the handbook's own gap.
- Both branches read the count the list further down is built from.
+/* --- Head: at a glance ---------------------------------------------------
+ The shape `.join` has on the home page and `.hb-stat` gives the handbook:
+ an eyebrow, a headline number, and a row that leads on. Here the number
+ is every page this index gathers, so the two children read as one family
+ rather than the handbook having copied the parent.
*/
-.invite {
+.docs-glance {
padding: 1.25rem 1.375rem;
background-color: rgb(231 231 214 / 4%);
border: 1px solid rgb(231 231 214 / 20%);
}
-.invite-eyebrow {
+.docs-glance-eyebrow {
font-family: $font-family-monospace;
font-size: 0.6875rem;
color: $tertiary;
@@ -98,7 +121,7 @@ $docs-flag-text: shade-color($quaternary, 25%);
letter-spacing: 0.08em;
}
-.invite-title {
+.docs-glance-title {
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
@@ -110,34 +133,29 @@ $docs-flag-text: shade-color($quaternary, 25%);
color: $body-bg;
}
-.invite-num {
+.docs-glance-num {
font-family: $font-family-monospace;
font-size: clamp(2.25rem, 5vw, 2.75rem);
line-height: 0.9;
color: $docs-primary-tint;
}
-.invite-what {
+.docs-glance-what {
margin-top: 0.75rem !important;
font-size: 0.9375rem;
line-height: 1.6;
color: rgb(231 231 214 / 70%);
}
-.invite-row {
+.docs-glance-row {
padding-top: 0.875rem;
margin-top: 1rem !important;
border-top: 1px solid rgb(231 231 214 / 16%);
}
-// Links on the ink. `.docs main a` is (0,1,2), so a band rule needs `main`
-// to reach them.
-.docs-index main .docs-head a {
- color: $body-bg;
-}
-
-// The treatment `.join-row a` gives the home page's equivalent link.
-.docs-index main .invite-row a {
+// The treatment `.join-row a` gives the home page's equivalent link. Scoped
+// to the head, where the ink is: parchment on a band would be invisible.
+.docs-index main .docs-head .docs-glance-row a {
font-family: $font-family-monospace;
font-weight: 700;
color: $body-bg;
@@ -176,6 +194,17 @@ $docs-flag-text: shade-color($quaternary, 25%);
border-bottom: 1px solid $docs-hairline;
}
+// The numbered voice `.area-num` gives the home page's SDK areas and
+// `.rule-card-num` gives the handbook's grid, so a reader who has seen either
+// already knows this is one entry in a counted set.
+.docs-index .part-num {
+ display: block;
+ margin-bottom: 0.375rem;
+ font-family: $font-family-monospace;
+ font-size: 0.75rem;
+ color: $docs-link;
+}
+
.docs-index .part-name {
font-size: 1.0625rem;
font-weight: 700;
@@ -188,9 +217,10 @@ $docs-flag-text: shade-color($quaternary, 25%);
color: $docs-muted;
}
-/* --- The handbook -------------------------------------------------------
- Alphabetical, one row per rule, name and point side by side so the names
- read as an index. The numeral uses `.area-num`'s recipe from the home page.
+/* --- The handbook --------------------------------------------------------
+ The rules live at their own page now; this is the teaser that sends a
+ reader there, styled as the arrow-link `.featured-link` gives the news
+ page's lead story.
*/
.docs-index .handbook-lede {
@@ -201,70 +231,27 @@ $docs-flag-text: shade-color($quaternary, 25%);
color: $docs-muted;
}
-.docs-index .rule-list {
- margin-top: 1.75rem !important;
-}
-
-.docs-index .rule {
- display: grid;
- grid-template-columns: minmax(0, 19rem) minmax(0, 1fr);
- gap: 0.25rem 2rem;
- padding-block: 0.875rem;
- border-bottom: 1px solid rgb(167 151 139 / 35%);
-}
-
-.docs-index .rule:first-child {
- border-top: 1px solid rgb(167 151 139 / 35%);
+.docs-index .handbook-more {
+ margin-top: 1.25rem !important;
}
-// A grid, so the numeral keeps its own column and stays beside the first
-// line of a title long enough to wrap.
-.docs-index .rule-name {
- display: grid;
- grid-template-columns: 1.375rem minmax(0, 1fr);
- gap: 0.5rem;
- align-items: baseline;
- font-size: 0.9375rem;
+.docs-index .handbook-link {
+ display: inline-flex;
+ gap: 0.375rem;
+ align-items: center;
font-weight: 700;
-}
-
-.docs-index .rule-num {
- font-family: $font-family-monospace;
- font-size: 0.75rem;
- font-weight: 400;
color: $docs-link;
+ text-decoration: none;
+ border-bottom: 2px solid currentcolor;
}
-.docs-index .rule-heading {
- display: flex;
- flex-wrap: wrap;
- gap: 0.5rem;
- align-items: baseline;
+.docs-index .handbook-link-arrow {
+ display: inline-block;
+ transition: transform 0.15s ease;
}
-.docs-index .rule-point {
- font-size: 0.9375rem;
- line-height: 1.6;
-}
-
-// The chip carries the state in text, so it does not rest on color alone.
-.docs-index .rule-flag {
- padding: 0.0625rem 0.4375rem;
- font-family: $font-family-monospace;
- font-size: 0.6875rem;
- font-weight: 400;
- color: $docs-flag-text;
- text-transform: uppercase;
- letter-spacing: 0.06em;
- background-color: $docs-flag-bg;
-}
-
-.docs-index .handbook-gap {
- max-width: 60ch;
- margin-top: 1.5rem !important;
- font-size: 0.9375rem;
- line-height: 1.65;
- color: $docs-muted;
+.docs-index .handbook-link:hover .handbook-link-arrow {
+ transform: translateX(3px);
}
/* --- Not here yet --------------------------------------------------------
@@ -305,10 +292,10 @@ $docs-flag-text: shade-color($quaternary, 25%);
.docs-index .part-list {
grid-template-columns: minmax(0, 1fr);
}
+}
- // The name column stops earning its width once the point beside it wraps.
- .docs-index .rule {
- grid-template-columns: minmax(0, 1fr);
- gap: 0.375rem;
+@media (prefers-reduced-motion: reduce) {
+ .docs-index .handbook-link-arrow {
+ transition: none;
}
}
diff --git a/_assets/styles/_handbook.scss b/_assets/styles/_handbook.scss
new file mode 100644
index 000000000..0df6d5af8
--- /dev/null
+++ b/_assets/styles/_handbook.scss
@@ -0,0 +1,326 @@
+@use 'sass:color';
+
+// The handbook index: the front door to the nine rules. It shares the
+// two-column hero and the count `_docs.scss` gives the rest of the site's
+// index pages, but the rules themselves are the point of this one page, so
+// they get the room a plain list does not: a grid of cards, drawn the way
+// the SDK areas are drawn on the home page — hairline dividers between cells
+// that share the section's own ground, not a border around each one.
+
+// Colors come from _sublime-theme.scss and reuse the exact tones
+// _docs.scss, _about.scss, and _news.scss give a hero band and a quiet
+// section, so this page agrees with the rest of the site on what a link, a
+// muted caption, and a wash look like.
+
+$hb-wrap: 1200px;
+$hb-gutter: clamp(1rem, 4vw, 3rem);
+$hb-link: shade-color($primary, 25%);
+$hb-primary-tint: tint-color($primary, 30%);
+$hb-muted: #4b433c;
+$hb-hairline: rgb(167 151 139 / 55%);
+
+// A pale wash with deep-shade text, so the flag clears AA at this size.
+$hb-flag-bg: tint-color($quaternary, 88%);
+$hb-flag-text: shade-color($quaternary, 25%);
+
+// The wash `_docs.scss` gives its own quiet section, reused here as the
+// ground the rule grid sits on.
+$hb-quiet: color.mix($tertiary, $body-bg, 12%);
+
+// Scoped to `main` to keep the resets off the navbar and footer, which are
+// its siblings under the same body class. The padding replaces what
+// `main > .container` gives the footer elsewhere.
+.handbook-index main h1,
+.handbook-index main h2,
+.handbook-index main p,
+.handbook-index main dl,
+.handbook-index main dd,
+.handbook-index main ol,
+.handbook-index main ul {
+ margin: 0;
+}
+
+.handbook-index main ol,
+.handbook-index main ul {
+ padding: 0;
+ list-style: none;
+}
+
+/* --- Head ---------------------------------------------------------------
+ The same hero shape `.docs-head` and `.about-head` use: ink, the ring off
+ the logogram bleeding from the corner, a lede on one side and a stat on the
+ other. Its own classes, because the content beside the ring — a live count
+ of rules, not a link out — is what makes this page a destination rather
+ than a repeat of the index it is reached from.
+*/
+
+.hb-head {
+ position: relative;
+ padding-top: calc(#{$navbar-height} + clamp(2rem, 5vw, 3.5rem));
+ padding-bottom: clamp(2.25rem, 5vw, 3.5rem);
+ overflow: hidden;
+ color: $body-bg;
+ background-color: $body-color;
+}
+
+.hb-head :focus-visible {
+ outline-color: $body-bg;
+}
+
+// The ring off the logogram, the same drawing the home masthead and the news
+// head use, tuned for this band: lower and further into the corner than
+// home's, since it sits behind a narrower second column here.
+.hb-ring {
+ position: absolute;
+ inset-block-start: 0;
+ inset-inline-end: 0;
+ z-index: 0;
+ width: min(52vw, 400px);
+ aspect-ratio: 1;
+ color: $hb-primary-tint;
+ pointer-events: none;
+ opacity: 0.12;
+ transform: translate(24%, -26%);
+}
+
+.hb-ring svg {
+ display: block;
+ width: 100%;
+ height: auto;
+}
+
+.hb-head-grid {
+ position: relative;
+ z-index: 1;
+ display: grid;
+ grid-template-columns: minmax(0, 1.4fr) minmax(0, 0.85fr);
+ gap: clamp(2rem, 5vw, 4rem);
+ align-items: start;
+}
+
+// _custom.scss draws a rule after every h1. Beside a two-column grid it
+// would stop at this column's edge, so it is off here.
+.hb-title {
+ display: block;
+ font-size: clamp(1.875rem, 4.5vw, 2.75rem);
+ font-weight: 700;
+ line-height: 1.08;
+ letter-spacing: -0.02em;
+}
+
+.hb-title::after {
+ content: none;
+}
+
+.hb-lede {
+ max-width: 42ch;
+ margin-top: 1rem !important;
+ font-size: clamp(1rem, 1.6vw, 1.0625rem);
+ line-height: 1.6;
+ color: rgb(231 231 214 / 76%);
+}
+
+/* --- Head: the stat -------------------------------------------------------
+ How much of the handbook is written, read at a glance, with the same
+ progress a reader would otherwise have to count from the grid below.
+*/
+
+.hb-stat {
+ padding: 1.25rem 1.375rem 1.375rem;
+ background-color: rgb(231 231 214 / 4%);
+ border: 1px solid rgb(231 231 214 / 20%);
+}
+
+.hb-stat-eyebrow {
+ font-family: $font-family-monospace;
+ font-size: 0.6875rem;
+ color: $tertiary;
+ text-transform: uppercase;
+ letter-spacing: 0.08em;
+}
+
+.hb-stat-title {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 0.5rem;
+ align-items: baseline;
+ margin-top: 0.625rem !important;
+ font-size: 1.1875rem;
+ font-weight: 700;
+ line-height: 1.3;
+ color: $body-bg;
+}
+
+.hb-stat-num {
+ font-family: $font-family-monospace;
+ font-size: clamp(2.25rem, 5vw, 2.75rem);
+ line-height: 0.9;
+ color: $hb-primary-tint;
+}
+
+.hb-stat-what {
+ margin-top: 0.75rem !important;
+ font-size: 0.9375rem;
+ line-height: 1.6;
+ color: rgb(231 231 214 / 70%);
+}
+
+// One square a rule, filled once it is written. The same information the
+// numeral above states in words, read a second way.
+.hb-progress {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 0.25rem;
+ margin-top: 0.875rem !important;
+}
+
+.hb-progress-cell {
+ width: 0.625rem;
+ height: 0.625rem;
+ background-color: $hb-primary-tint;
+ border: 1px solid $hb-primary-tint;
+}
+
+.hb-progress-cell-open {
+ background-color: transparent;
+ border-color: rgb(231 231 214 / 30%);
+}
+
+.hb-stat-row {
+ padding-top: 0.875rem;
+ margin-top: 1rem !important;
+ border-top: 1px solid rgb(231 231 214 / 16%);
+}
+
+// Links on the ink. `.handbook-index main a` is (0,1,2), so a band rule needs
+// `main` to reach them.
+.handbook-index main .hb-head .hb-stat-row a {
+ font-family: $font-family-monospace;
+ font-weight: 700;
+ color: $body-bg;
+}
+
+/* --- Shared section furniture ------------------------------------------ */
+
+.handbook-index .band-quiet {
+ background-color: $hb-quiet;
+ border-block: 1px solid $hb-hairline;
+}
+
+.handbook-index .section-count {
+ font-family: $font-family-monospace;
+ font-size: 0.8125rem;
+ color: $hb-muted;
+ text-transform: uppercase;
+ letter-spacing: 0.06em;
+}
+
+/* --- The rules: a grid, not a list ---------------------------------------
+ Nine rules divide evenly into three columns of three, so the grid is
+ counted rather than fitted: the same reasoning `_home.scss` gives the SDK
+ areas. The 1px gap over the hairline background is what draws the lines
+ between cells; each cell's own background matches the section's, so the
+ grid reads as one drawn surface rather than nine boxes set on top of it.
+*/
+
+.rule-grid {
+ display: grid;
+ grid-template-columns: repeat(3, minmax(0, 1fr));
+ gap: 1px;
+ margin-top: 1.75rem !important;
+ background-color: $hb-hairline;
+ border: 1px solid $hb-hairline;
+}
+
+.rule-card {
+ position: relative;
+ display: flex;
+ flex-direction: column;
+ gap: 0.625rem;
+ height: 100%;
+ padding: 1.375rem 1.5rem 1.5rem;
+ color: inherit;
+ text-decoration: none;
+ background-color: $hb-quiet;
+ transition: background-color 150ms ease;
+}
+
+// Lighter than the wash it sits on, so the card the pointer is over reads as
+// lifted rather than tinted; this is what keeps the title's contrast well
+// clear of AA rather than trading it away for a coloured hover.
+.rule-card:hover,
+.rule-card:focus-visible {
+ background-color: $body-bg;
+}
+
+.rule-card-num {
+ display: block;
+ font-family: $font-family-monospace;
+ font-size: 0.75rem;
+ color: $hb-link;
+}
+
+.rule-card-heading {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 0.5rem;
+ align-items: baseline;
+}
+
+.rule-card-title {
+ font-size: 1.0625rem;
+ font-weight: 700;
+ line-height: 1.3;
+ color: $hb-link;
+}
+
+.rule-card:hover .rule-card-title,
+.rule-card:focus-visible .rule-card-title {
+ text-decoration: underline;
+}
+
+// The flag carries the state in text, so it does not rest on color alone.
+.rule-card-flag {
+ padding: 0.0625rem 0.4375rem;
+ font-family: $font-family-monospace;
+ font-size: 0.6875rem;
+ font-weight: 400;
+ color: $hb-flag-text;
+ text-transform: uppercase;
+ letter-spacing: 0.06em;
+ background-color: $hb-flag-bg;
+}
+
+.rule-card-point {
+ font-size: 0.9375rem;
+ line-height: 1.6;
+ color: $hb-muted;
+}
+
+.hb-gap {
+ max-width: 60ch;
+ margin-top: 1.5rem !important;
+ font-size: 0.9375rem;
+ line-height: 1.65;
+ color: $hb-muted;
+}
+
+/* --- Narrow -------------------------------------------------------------- */
+
+@media (width < 960px) {
+ .hb-head-grid {
+ grid-template-columns: minmax(0, 1fr);
+ }
+}
+
+@media (width < 760px) {
+ .rule-grid {
+ grid-template-columns: minmax(0, 1fr);
+ }
+}
+
+@media (prefers-reduced-motion: reduce) {
+ .rule-card {
+ transition: none;
+ }
+}
diff --git a/_assets/styles/main.scss b/_assets/styles/main.scss
index 13a2c130e..8876f0899 100644
--- a/_assets/styles/main.scss
+++ b/_assets/styles/main.scss
@@ -22,6 +22,7 @@
@import 'docs';
@import 'docs-page';
@import 'error';
+@import 'handbook';
@import 'home';
@import 'nav';
@import 'news';
diff --git a/_data/sections.json b/_data/sections.json
index 8b19302eb..0967ef424 100644
--- a/_data/sections.json
+++ b/_data/sections.json
@@ -1,6 +1 @@
-{
- "handbook": {
- "name": "Style handbook",
- "url": "/docs/#handbook"
- }
-}
+{}
diff --git a/collections/_pages/about.html b/collections/_pages/about.html
index 8b9281aac..26921a25e 100644
--- a/collections/_pages/about.html
+++ b/collections/_pages/about.html
@@ -48,8 +48,9 @@
In practice
The rules are written down
How prose is styled, how a commit is worded, how a change gets
- reviewed. It lives in the handbook,
- not in the head of whoever reviews you.
+ reviewed. It lives in the
+ handbook, not in the head of
+ whoever reviews you.
- {{ written_count }} of {{ handbook_total }} written
-
+ {%- if open_count > 0 %}
+
+ {{ written_count }} of {{ handbook_total }} written
+
+ {%- endif %}
The house style our documentation and commit messages follow, one rule
- to a page. Each rule is stated below, so you can find the one you need.
+ to a page, so a question about how something is written has one place to
+ be answered.
diff --git a/collections/_pages/handbook.html b/collections/_pages/handbook.html
new file mode 100644
index 000000000..3b35d1e3b
--- /dev/null
+++ b/collections/_pages/handbook.html
@@ -0,0 +1,116 @@
+---
+title: Style Handbook
+layout: default
+permalink: /docs/handbook/style/
+description:
+ The house style OpenINF documentation and commit messages follow, one rule
+ to a page.
+body_classes:
+ - docs
+ - handbook-index
+eleventyImport:
+ collections: ['handbook']
+---
+{%- assign handbook_total = collections.handbook | size -%}
+{%- assign written = collections.handbook
+ | where_exp: 'doc', 'doc.data.stub != true'
+-%}
+{%- assign written_count = written | size -%}
+{%- assign open_count = handbook_total | minus: written_count -%}
+
+
+
+
+ {% include 'assets/img/ring.svg' %}
+
+
+
+
+
Style Handbook
+
+ The house style our documentation and commit messages follow, one rule
+ to a page. Each rule is stated below, so you can find the one you need
+ without reading the rest.
+
+
+
+
+
+
+
+
+
+
+
+
Every rule
+ {%- if open_count > 0 %}
+
+ {{ written_count }} of {{ handbook_total }} written
+