diff --git a/lib/rdoc/generator/template/aliki/DESIGN.md b/lib/rdoc/generator/template/aliki/DESIGN.md new file mode 100644 index 0000000000..b4ca6b945b --- /dev/null +++ b/lib/rdoc/generator/template/aliki/DESIGN.md @@ -0,0 +1,536 @@ +# DESIGN.md + +Single source of truth for the **Aliki** theme's visual language. The generated +HTML documentation — served from `localhost` (`rdoc --server`), +[docs.ruby-lang.org](https://docs.ruby-lang.org), and static GitHub Pages — +implements what's specified here. The implementation lives in one stylesheet, +[`css/rdoc.css`](css/rdoc.css). When a value here drifts from that file, **the +stylesheet is wrong** and should be brought back in line. + +This document follows the +[Stitch DESIGN.md format](https://github.com/VoltAgent/awesome-design-md) — +nine sections covering theme, color, type, components, layout, elevation, +guardrails, responsive behavior, and an agent quick reference. + +> Scope: this is the **visual** contract only. It does not cover the generator +> pipeline, templates, or JS architecture. + +--- + +## 1. Visual Theme & Atmosphere + +**Personality.** Modern, light, content-first. Aliki is a quiet reading surface +for Ruby API docs — the documentation is the product, and the chrome recedes. +It should feel like a well-made developer-docs site, not a framework's default +output. + +**Context of use.** Developers read reference docs while coding — scanning for a +method, a signature, a constant. The job is to find the thing and read it +comfortably. Reading measure and code legibility matter more than decoration. + +**Mood.** Calm and neutral with a single decisive accent. Warm "stone" grays +carry the text hierarchy; one red accent carries identity and wayfinding +(headings, links-on-hover, active TOC, signature cards). Dark mode is a +first-class, hand-tuned surface — not an inverted afterthought. + +**Density.** Comfortable, not cramped. A capped 800 px reading measure, generous +`--space-12` page padding, and a token-driven spacing rhythm. Every element +earns its space; there is no decorative chrome. + +**Anti-references.** What Aliki must **not** look or feel like: + +- **Not Darkfish** — the predecessor theme it deliberately departs from. +- **Not corporate** — no heavy, dense doc-portal styling. +- **Not cluttered** — sidebars and chrome stay quiet; content leads. + +**References (inferred from the implementation, not externally specified).** The +system-font stack, GitHub-style heading anchors, and a flat token system place +Aliki in the family of modern, lightweight developer-doc themes. Treat positive +references as open — the brand is currently defined by its principles and +anti-references, not a named lookalike. + +**Surface contexts.** + +| Surface | Theme | Atmosphere | +|----------------------------|----------------------------------------|-------------------------------------------------------------------| +| Generated docs (static) | Light default · Dark via `data-theme` | The primary reading surface; must work offline / from static hosts | +| Live server (`--server`) | Same light/dark | Servlet pages (root, 404) use lighter chrome — no header/TOC/footer | + +Aliki ships a **single brand palette** (red) with explicit **light and dark** +variants — not a multi-theme system. + +## 2. Color Palette & Roles + +All color is defined as CSS custom properties in +[`css/rdoc.css`](css/rdoc.css): light tokens in `:root`, dark tokens in a +`[data-theme="dark"]` override block that re-declares **only** what differs. +Components reference semantic tokens, never raw hex. + +### Semantic roles + +| Role | Token (`--color-…`) | Meaning | +|--------------------|----------------------------|--------------------------------------------------| +| Brand accent | `accent-primary` | Identity + wayfinding (headings, active TOC, sig border) | +| Accent hover | `accent-hover` | Hover/darker accent | +| Accent subtle | `accent-subtle` | Tinted accent fills (buttons, focus ring) | +| Primary text | `text-primary` | Body copy, method names | +| Secondary text | `text-secondary` | Meta, secondary labels, branch | +| Tertiary text | `text-tertiary` | Placeholders, snippets, signatures, footer-bottom | +| Page background | `background-primary` | Main surface | +| Raised/​sunk bg | `background-secondary` / `-tertiary` | Footer, modal close, hover fills | +| Borders | `border-subtle` / `-default` / `-emphasis` | Hairlines from quiet → strong | +| Link | `link-default` / `link-hover` | Body links (default = text color; hover = accent) | +| Code surface | `code-bg` / `code-border` | `
` / inline `code`                          |
+| Signature card     | `sig-bg` / `sig-border`    | Method/attribute header card                     |
+| Page title kind    | `title-kind-border`        | Border for the class/module kind badge           |
+| Nav                | `nav-bg` / `nav-text`      | Left sidebar surface + text                      |
+| Table              | `th-background` / `td-background` | Header row + zebra rows                     |
+
+### Brand palette — red ramp (identity, **fixed**)
+
+| Step | Hex       | | Step | Hex       |
+|------|-----------|-|------|-----------|
+| 50   | `#fdeae9` | | 500  | `#eb544f` |
+| 100  | `#fadad3` | | 600  | `#e62923` ← light accent |
+| 200  | `#f8bfbd` | | 700  | `#b8211c` |
+| 300  | `#f5a9a7` | | 800  | `#8a1915` |
+| 400  | `#f07f7b` | | 900  | `#5c100e` |
+
+Light accent = `primary-600` (`#e62923`); dark accent = `primary-500`
+(`#eb544f`). **The hue is a fixed brand identity — do not retune it without
+approval** (see §7).
+
+### Neutral palette — warm "stone" ramp
+
+`50 #fafaf9 · 100 #f5f5f4 · 200 #e7e5e4 · 300 #d6d3d1 · 400 #a8a29e · 500 #78716c · 600 #57534e · 700 #44403c · 800 #292524 · 900 #1c1917`
+
+### Semantic tokens — resolved values
+
+| Role                  | Light                     | Dark                      |
+|-----------------------|---------------------------|---------------------------|
+| `text-primary`        | `#1c1917` (neutral-900)   | `#fafaf9` (neutral-50)    |
+| `text-secondary`      | `#57534e` (neutral-600)   | `#e7e5e4` (neutral-200)   |
+| `text-tertiary`       | `#78716c` (neutral-500)   | `#a8a29e` (neutral-400)   |
+| `background-primary`  | `#ffffff`                 | `#1c1917` (neutral-900)   |
+| `background-secondary`| `#fafaf9` (neutral-50)    | `#292524` (neutral-800)   |
+| `background-tertiary` | `#f5f5f4` (neutral-100)   | `#44403c` (neutral-700)   |
+| `border-subtle`       | `#e7e5e4` (neutral-200)   | `#44403c` (neutral-700)   |
+| `border-default`      | `#d6d3d1` (neutral-300)   | `#57534e` (neutral-600)   |
+| `border-emphasis`     | `#a8a29e` (neutral-400)   | `#d6d3d1` (neutral-300)   |
+| `link-default`        | `#1c1917`                 | `#fafaf9`                 |
+| `link-hover`          | `#e62923` (primary-600)   | `#eb544f` (primary-500)   |
+| `accent-primary`      | `#e62923` (primary-600)   | `#eb544f` (primary-500)   |
+| `accent-hover`        | `#b8211c` (primary-700)   | `#f07f7b` (primary-400)   |
+| `accent-subtle`       | `#fdeae9` (primary-50)    | `rgb(235 84 79 / 10%)`    |
+| `title-kind-border`   | `#f8bfbd` (primary-200)   | `rgb(235 84 79 / 35%)`    |
+| `code-bg`             | `#f6f8fa`                 | `#292524` (neutral-800)   |
+| `code-border`         | `#d6d3d1`                 | `#44403c` (neutral-700)   |
+| `sig-bg`              | `#f5f5f4` (neutral-100)   | `#211f1e` (hand-picked)   |
+| `sig-border`          | `#d6d3d1` (neutral-300)   | `#44403c` (neutral-700)   |
+| `nav-bg` / `nav-text` | `#ffffff` / `#44403c`     | `#1c1917` / `#fafaf9`     |
+
+### Syntax-highlight palette (`--code-*`)
+
+One palette shared across **Ruby, C, and Shell** highlighting. Brighter in dark
+mode so tokens stay legible on the dark code surface.
+
+| Token   | Light     | Dark      |
+|---------|-----------|-----------|
+| blue    | `#1d4ed8` | `#93c5fd` |
+| green   | `#047857` | `#34d399` |
+| orange  | `#d97706` | `#fbbf24` |
+| purple  | `#7e22ce` | `#c084fc` |
+| red     | `#dc2626` | `#f87171` |
+| cyan    | `#0891b2` | `#22d3ee` |
+| gray    | `#78716c` | `#a8a29e` |
+
+### Search-type badge colors
+
+| Type     | Light bg / text       | Dark bg / text        |
+|----------|-----------------------|-----------------------|
+| class    | `#e6f0ff` / `#0050a0` | `#1e3a5f` / `#93c5fd` |
+| module   | `#e6ffe6` / `#006600` | `#14532d` / `#86efac` |
+| constant | `#fff0e6` / `#995200` | `#451a03` / `#fcd34d` |
+| method   | `#f0e6ff` / `#5200a0` | `#3b0764` / `#d8b4fe` |
+
+### Theme-agnostic tokens (not overridden in dark)
+
+| Token                  | Value                    | Use                              |
+|------------------------|--------------------------|----------------------------------|
+| `overlay`              | `rgb(0 0 0 / 50%)`       | Modal / mobile-nav backdrop      |
+| `emphasis-bg`          | `rgb(255 111 97 / 10%)`  | `strong` / `em` highlight        |
+| `emphasis-decoration`  | `rgb(52 48 64 / 25%)`    | `em` dotted underline            |
+| `search-highlight-bg`  | `rgb(224 108 117 / 10%)` | Matched-term highlight           |
+| `success-bg`           | `rgb(34 197 94 / 10%)`   | Copy-button "copied" state       |
+
+### Success palette
+`green-400 #4ade80 · green-500 #22c55e · green-600 #16a34a` — copy-button feedback only.
+
+## 3. Typography Rules
+
+### Font stacks
+
+| Family | Stack                                                                                          |
+|--------|------------------------------------------------------------------------------------------------|
+| Sans (base + headings) | `-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Helvetica Neue', sans-serif` |
+| Mono   | `ui-monospace, 'SFMono-Regular', 'SF Mono', Menlo, Consolas, 'Liberation Mono', monospace`     |
+
+System fonts only — **no web fonts are shipped** (keeps output lightweight; see
+§7). Headings reuse the base sans; mono is used for code, method headings, and
+type signatures.
+
+### Type scale
+
+| Token   | rem      | px | Token   | rem      | px |
+|---------|----------|----|---------|----------|----|
+| `xs`    | 0.75     | 12 | `2xl`   | 1.5      | 24 |
+| `sm`    | 0.875    | 14 | `3xl`   | 1.875    | 30 |
+| `base`  | 1        | 16 | `4xl`   | 2.25     | 36 |
+| `lg`    | 1.125    | 18 | `5xl`   | 3        | 48 |
+| `xl`    | 1.25     | 20 |         |          |    |
+
+Weights: `normal 400 · medium 500 · semibold 600 · bold 700`.
+Line heights: `tight 1.25 · normal 1.5 · relaxed 1.625` (body is relaxed).
+
+### Heading ladder (`main`)
+
+| Element | Size       | Weight    | Notes                                   |
+|---------|------------|-----------|-----------------------------------------|
+| `h1.page-title` | kind badge: `sm` / name: `3xl` (30) | semibold / bold | Type kind renders as an accent badge above the class/module name; long names may wrap |
+| `h1`    | `3xl` (30) | bold      | tight line-height                       |
+| `h2`    | `2xl` (24) | semibold  | `margin-top: space-8`; section headers add a 1 px top rule + `space-3` padding |
+| `h3`    | `xl` (20)  | semibold  | `margin-top: space-8`; section headers add a 1 px top rule + `space-3` padding |
+| `h4`    | `lg` (18)  | medium    |                                         |
+| `h5`/`h6` | `base` (16) | medium  |                                         |
+
+All headings render in `accent-primary` and carry
+`scroll-margin-top: calc(header-height + 2rem)` so anchored links clear the
+sticky header.
+
+### Color × text pairing
+
+- Body links default to **text color**, not accent — only hover reveals accent
+  (`link-hover`). Keeps prose calm.
+- Status/role meaning in search badges is carried by **both** a tinted
+  background and a text color, never color alone.
+- `strong` and `em` share the accent color + `emphasis-bg` tint; `em` adds a
+  dotted underline so emphasis survives in monochrome.
+
+## 4. Component Stylings
+
+Spacing/radius tokens referenced below resolve in §5/§6.
+
+### Header bar (`header.top-navbar`)
+
+| Property   | Value                                                       |
+|------------|-------------------------------------------------------------|
+| Layout     | Sticky top, `z-fixed` (300), `height: 64px`; desktop grid mirrors the page shell so search aligns to the main content rail |
+| Background | `background-primary` + `border-bottom` hairline + `shadow-sm` |
+| Padding    | Desktop columns are unpadded; brand/toggle use `space-6` side offsets, search rail uses main content `space-8` inset (mobile header: `space-4`) |
+| Brand      | `xl` (20) semibold; hover → accent                          |
+| Search     | content-rail aligned desktop field, `width: 400px` within the main rail (see Search below) |
+
+### Theme toggle (`.theme-toggle`)
+
+| Property | Value                                                            |
+|----------|------------------------------------------------------------------|
+| Box      | `2.5rem` square · `radius-md` · 1px `border-default`             |
+| Hover    | `background-secondary`, accent border + text, `scale(1.05)`      |
+| Active   | `scale(0.95)`                                                    |
+| Focus    | accent border + `0 0 0 3px accent-subtle` ring                   |
+| Icon     | rotates `15deg` + `scale(1.1)` on hover, `--ease-out-smooth`     |
+
+### Left navigation (`#sidebar-navigation` + `.nav-section`)
+
+| Property        | Value                                                        |
+|-----------------|--------------------------------------------------------------|
+| Surface         | `nav-bg`, `border-right` hairline, sticky under header, full-height scroll; rules are scoped to `#sidebar-navigation` so the right TOC can use its own `
` semantics | +| Scrollbar | 6 px, `border-default` thumb (custom, thin) | +| Section heading | `lg` semibold in **accent**, `border-bottom` | +| Section padding | `margin-top: space-6`, `padding: 0 space-6` | +| Link hover | `padding-left: space-1` nudge + `link-hover` color + underline | +| Collapsible | `
` with `::details-content` `block-size` 200 ms ease + `interpolate-size: allow-keywords` | +| Section icon | `1.25rem`, accent color · chevron `1rem`, tertiary, rotates `90deg` open | +| Nested list | `border-left` subtle hairline, `margin-left: 9px` (aligns to icon center) | + +### Signature card (`.method-header` / `.method-heading`) + +The defining content component — a method or attribute presented as a card. + +| Property | Value | +|-----------------|--------------------------------------------------------------| +| Card | `sig-bg` background · 1px `sig-border` full border · `radius-md` | +| Layout | two-column grid, `minmax(0, 1fr) auto`, `gap: space-4`; narrows to one column on `≤480px` | +| Padding | `space-4` desktop, `space-2` on `≤480px` | +| Heading group | `.method-heading-group`, `min-width: 0`; contains method heading(s) and optional method type signature | +| Heading | flex column, **mono**, `lg`, semibold; name `overflow-wrap: anywhere` | +| Type signature (method) | `pre.method-type-signature` — transparent, dotted top rule via `::before`, `sm` mono, `text-tertiary`, tight, `pre-wrap` | +| Type signature (attribute) | inline after `[RW]` badge, `sm` mono, `text-secondary` | +| Source toggle (`.method-controls summary`) | static grid action inside the card header; inline-flex with `{}` `.method-source-icon`, accent text on `accent-subtle`, `radius-sm`, `sm` medium; hover `primary-100` bg + `primary-300` border + `translateY(-1px)`; active `scale(0.96)` | +| Source reveal | `.method-source-code` animates `max-height`+`opacity`+`translateY`, `--duration-medium`/`-fast` + `--ease-out-smooth`; revealed `
` uses the standard code border |
+| `:target`       | method-detail gets a neutral left-rail indent treatment       |
+
+### Code (`pre`, `code`) + copy button
+
+| Property      | Value                                                          |
+|---------------|----------------------------------------------------------------|
+| `pre`         | mono, `code-bg`, 1px `code-border`, `radius-md`, `space-4` pad, `sm`, `x-scroll` |
+| inline `code` | `code-bg`, 1px `border-subtle`, `0.125rem 0.375rem` pad, `radius-sm`, `0.9em` |
+| Copy button   | absolute top/right `space-2`, `2rem` square, `radius-sm`, opacity `0.6` |
+| Copy hover    | opacity `1`, `background-tertiary`, `translateY(-1px)`, `shadow-md` |
+| Copy active   | `scale(0.92)`                                                  |
+| Copied        | `success-bg` + green-500 border + green-600 check (green-400 in dark) |
+
+### Tables
+
+`th`/`td` padding `0.2em 0.4em`, 1px `border-default`; `th` uses
+`th-background`; even rows use `td-background`. On `≤480px` tables scroll
+horizontally.
+
+### Search
+
+| Element                          | Value                                                       |
+|----------------------------------|-------------------------------------------------------------|
+| Desktop field (`#search-field`)  | full-width border-box, `space-2 space-4` pad, 1px border, `radius-md`, `base`; focus → accent border + `0 0 0 3px accent-subtle` |
+| Desktop dropdown (`#search-results-desktop`) | absolute inside the search form, full-width border-box to match the field, `max-height: 60vh`, `radius-lg`, `shadow-lg`, `z-popover` (500) |
+| Mobile modal (`.search-modal-content`) | centered card, `max-width: 600px`, `max-height: 80vh`, `radius-lg`, `shadow-xl` |
+| Modal result item                | `space-3 space-4` pad, `radius-md`, hover `background-secondary` |
+| Servlet field                    | pill `border-radius: 1.25rem`, leading 🔍 (`\1F50D`) glyph   |
+| Result lines                     | `.search-match` `base` · `.search-namespace` `sm` secondary · `.search-snippet` `sm` tertiary |
+| Type badge (`.search-type-*`)    | inline-block, `space-0 space-2` pad, `xs`, weight 500, `radius-sm`, colors per §2 |
+| Matched term (`li em`)           | `search-highlight-bg`, `font-style: normal`                 |
+
+### Right TOC (`#table-of-contents`)
+
+| Property      | Value                                                          |
+|---------------|----------------------------------------------------------------|
+| Layout        | sticky under header, `padding: space-8 space-6`, `border-left` hairline; its internal `.toc-nav` owns its scroll area and does not inherit left-navigation chrome |
+| Heading       | `lg` semibold, `text-primary`                                  |
+| Indent        | `.toc-h2` `margin-left: space-4`, `.toc-h3` `space-8`; nested `ul` border-left + `space-4` pad |
+| Link          | block, `text-secondary`; hover `link-hover` + underline; focus-visible accent outline |
+| **Active (scroll-spy)** | `accent-primary` + `font-weight: medium`             |
+| Visibility    | hidden `≤1279px`                                               |
+
+### Footer (`footer.site-footer`) + breadcrumb
+
+Footer: `background-secondary`, `border-top`, `padding: space-12 space-6`;
+columns via `repeat(auto-fit, minmax(200px, 1fr))`, gap `space-8`; column `h3`
+is `sm` semibold with `letter-spacing: 0.05em`; `.footer-bottom` is centered
+`xs` `text-tertiary` credit. Breadcrumb (`ol.breadcrumb`) is a wrapping flex row
+at `125%` font-size; long namespace parts may wrap on narrow screens.
+
+## 5. Layout Principles
+
+### Spacing scale (`--space-*`)
+
+```
+1 · 2 · 3 · 4 · 5 · 6 · 8 · 12 · 16        (4 · 8 · 12 · 16 · 20 · 24 · 32 · 48 · 64 px)
+```
+
+Non-linear above `6` — there is no `7`/`9`/`10`/`11`. Anything outside this list
+is suspect; reach for the nearest token.
+
+### Page grid (`body`)
+
+| Mode               | Grid                                                            |
+|--------------------|----------------------------------------------------------------|
+| Default (2-col)    | areas `header / "nav main" / "nav footer"`; columns `300px 1fr` |
+| `.has-toc` (3-col) | adds a TOC column `minmax(240px, 18%)`                          |
+| `≤1023px`          | collapses to `flex` column; nav becomes an off-canvas drawer    |
+
+Key layout tokens: `sidebar-width 300px · content-max-width 800px ·
+header-height 64px · search-width 400px · toc-width minmax(240px, 18%)`.
+
+### Reading measure & density
+
+`main` is capped at **800 px** and centered, with `space-12 space-8` (48 / 32 px)
+padding, `min-width: 0` inside the page grid, and `relaxed` (1.625) line-height
+— a comfortable column for prose and signatures. The left nav and right TOC are
+sticky and independently scrollable, so the reading column never jumps.
+
+### Alignment
+
+- Content + nav + TOC: leading-aligned.
+- Method source toggle: static trailing action inside the signature card grid.
+- Centered text is reserved for empty states and the footer credit.
+
+## 6. Depth & Elevation
+
+Hierarchy comes from **hairline borders, surface tinting, and a restrained
+shadow set** — used together. (Unlike some flat systems, Aliki *does* use
+shadows, but only for genuinely floating surfaces.)
+
+### Radius scale
+
+| Radius            | Usage                                                   |
+|-------------------|----------------------------------------------------------|
+| `sm` (4 px)       | inline code, badges, copy button, source-toggle, chips   |
+| `md` (6 px)       | `pre`, header controls, theme toggle, search field, modal (small screens) |
+| `lg` (8 px)       | search dropdown, search modal content                    |
+| `1.25rem` (20 px) | servlet search field (pill)                              |
+
+### Shadow scale
+
+| Token | Light (`rgb(0 0 0 / 10%)`)                 | Dark (40% opacity) | Used by                |
+|-------|--------------------------------------------|--------------------|------------------------|
+| `sm`  | `0 1px 3px`, `0 1px 2px -1px`              | same geometry      | header bar             |
+| `md`  | `0 2px 8px`                                | same               | copy-button hover      |
+| `lg`  | `0 10px 15px -3px`, `0 4px 6px -4px`       | same               | nav drawer, search dropdown |
+| `xl`  | `0 20px 25px -5px`, `0 8px 10px -6px`      | same               | search modal           |
+
+### Borders & focus
+
+- Three hairline weights: `border-subtle` → `border-default` → `border-emphasis`.
+  The 1 px hairline is the primary divider everywhere (nav, header, cards, table).
+- **Focus ring:** `box-shadow: 0 0 0 3px accent-subtle` on interactive controls
+  (theme toggle, search field, copy button) paired with an accent border.
+
+### Motion
+
+| Token             | Value                       | Where                                  |
+|-------------------|-----------------------------|----------------------------------------|
+| `transition-fast` | `150ms ease-in-out`         | color/background hovers                |
+| `transition-base` | `200ms ease-in-out`         | links, chevron rotation                |
+| `transition-slow` | `350ms ease-in-out`         | —                                      |
+| `ease-out-smooth` | `cubic-bezier(0.4, 0, 0.2, 1)` | theme-toggle icon, source reveal    |
+| `duration-fast/​base/​medium` | `250 / 300 / 350ms` | source-code reveal, theme icon         |
+
+Z-index ladder: `fixed 300 · modal 400 · popover 500`.
+
+## 7. Do's and Don'ts
+
+### Load-bearing guardrails
+
+1. **The red is fixed identity.** Reskin via other tokens freely, but **do not
+   change the `--color-primary-*` hue without approval** — it cascades across
+   headings, links, active TOC, signature borders, and badges.
+2. **Never break static / offline viewing.** Output must render with no server
+   on GitHub Pages and `file://`. Don't introduce anything that needs a backend
+   or a same-origin `fetch()`.
+3. **Accessibility bar = WCAG 2.1 AA + honor `prefers-reduced-motion`.** Treat
+   gaps as bugs. ⚠️ **`prefers-reduced-motion` is not yet implemented** (nav
+   collapse, TOC scroll, source-reveal, and `::details-content` all animate
+   unconditionally) — adding a reduced-motion block is the top open item (§8).
+4. **Edit light *and* dark together.** Any new color/semantic token must be
+   declared in both `:root` and `[data-theme="dark"]`.
+5. **Change tokens, not literals.** Components must reference `--*` tokens.
+
+### Do / Don't
+
+| Don't                                               | Do                                                        |
+|-----------------------------------------------------|-----------------------------------------------------------|
+| Retune the brand red                                | Reskin via neutrals / semantic tokens; keep the red hue   |
+| Hard-code hex in a component rule                   | Reference a semantic token (`accent-primary`, `text-…`)   |
+| Add a color in `:root` only                         | Add it to the `[data-theme="dark"]` block too             |
+| Use `prefers-color-scheme` for dark                 | Key off the `[data-theme="dark"]` attribute (JS-set)      |
+| Add a syntax token class without CSS                | Add `.{ruby,c,sh}-*` rule **and** the emitter together    |
+| Ship a web font or image sprite                     | Stay on the system font stack; inline SVG only            |
+| Invent a 6th syntax color                           | Map onto the existing `--code-*` seven                    |
+| Animate with no reduced-motion guard                | Wrap non-essential motion in `prefers-reduced-motion`     |
+| Convey meaning by color alone                       | Pair color with text/weight (badges, emphasis)            |
+| Add decorative shadows                              | Use the `sm/md/lg/xl` set only for truly floating surfaces |
+
+## 8. Responsive Behavior
+
+The generated docs are fluid; the layout adapts at five breakpoints.
+
+| Breakpoint            | Behavior                                                                                   |
+|-----------------------|--------------------------------------------------------------------------------------------|
+| `1024–1279px`         | Right **TOC hidden**; `.has-toc` grid drops to 2 columns while the footer stays in the content column |
+| `≤1023px`             | Right **TOC hidden**; body → flex column; **left nav becomes an off-canvas drawer** (`300px`, `shadow-lg`) with an `overlay` backdrop + hamburger (`#sidebar-navigation-toggle`); desktop search swaps to a mobile **search modal**; header padding/gap tighten to `space-4` |
+| `768–1023px` (tablet) | Header `0 space-6`; main `space-8 space-6`, full-width                                       |
+| `≤480px`              | Nav `width: 85%` (max `320px`); main padding `space-4`; tables scroll; method heading → `base`; signature card padding tightens |
+| `≤420px`              | Search modal padding tightens                                                               |
+| `(hover: none)`       | Copy button rests at opacity `0.7`                                                           |
+
+### Color scheme
+
+Dark mode is the **`[data-theme="dark"]` attribute** on the document root, set by
+`theme-toggle.js` and persisted in `localStorage` (`rdoc-theme`). It is **not**
+driven by `prefers-color-scheme`. Light is the default.
+
+### Reduced motion ⚠️ (target, not yet implemented)
+
+The standard: under `prefers-reduced-motion: reduce`, all non-essential
+transitions/animations should collapse to instant — the nav-section
+`::details-content` block-size tween, the method-source-code reveal, the
+theme-toggle icon spin, chevron rotations, and any smooth-scroll. This guard
+does not exist yet and should be added to satisfy the AA + reduced-motion bar.
+
+## 9. Agent Prompt Guide
+
+A cheat-sheet for prompting AI tools (or new contributors) to produce
+Aliki-consistent UI.
+
+### One-line palette identifier
+
+| Mode  | accent    | bg        | fg        | accent-hover |
+|-------|-----------|-----------|-----------|--------------|
+| Light | `#e62923` | `#ffffff` | `#1c1917` | `#b8211c`    |
+| Dark  | `#eb544f` | `#1c1917` | `#fafaf9` | `#f07f7b`    |
+
+Neutral ramp (warm stone): `#fafaf9 #f5f5f4 #e7e5e4 #d6d3d1 #a8a29e #78716c #57534e #44403c #292524 #1c1917`.
+
+### Syntax colors (light → dark)
+
+```
+blue   #1d4ed8 → #93c5fd     purple #7e22ce → #c084fc
+green  #047857 → #34d399     red    #dc2626 → #f87171
+orange #d97706 → #fbbf24     cyan   #0891b2 → #22d3ee
+gray   #78716c → #a8a29e
+```
+
+### Font stacks
+
+```
+Sans/Headings: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, … sans-serif
+Mono:          ui-monospace, 'SFMono-Regular', 'SF Mono', Menlo, Consolas, … monospace
+(no web fonts — system stacks only)
+```
+
+### Ladders
+
+```
+Type:    12 · 14 · 16 · 18 · 20 · 24 · 30 · 36 · 48   (xs…5xl)
+Spacing:  4 ·  8 · 12 · 16 · 20 · 24 · 32 · 48 · 64   (space-1…16)
+Radius:   4 ·  6 ·  8   (+ 20px servlet pill)
+Layout:  sidebar 300 · content 800 · header 64 · search 400 · toc minmax(240, 18%)
+```
+
+### Ready-to-use prompt fragments
+
+> "Use the Aliki theme: red accent `#e62923` (light) / `#eb544f` (dark), warm
+> stone neutrals, page bg `#fff` / `#1c1917`, text `#1c1917` / `#fafaf9`. Body
+> links are text-colored and only turn red on hover. Dark mode keys off a
+> `[data-theme="dark"]` attribute, not `prefers-color-scheme`."
+
+> "Layout: 2-column (300 px nav + 800 px content), optional 3rd TOC column
+> `minmax(240px, 18%)`; 64 px sticky header. Spacing scale
+> 4/8/12/16/20/24/32/48/64; radii 4/6/8. Hairline 1 px borders + a restrained
+> sm/md/lg/xl shadow set for floating surfaces only."
+
+> "Type: system sans for everything, mono for code + method headings + RBS
+> signatures. Heading ladder h1 30 / h2 24 / h3 20, all in the red accent.
+> Class/module page titles render the kind as a small accent badge above the
+> red `3xl` object name.
+> Comfortable density, 1.625 body line-height. No web fonts, no image sprites."
+
+> "Don't retune the red hue, don't hard-code hex (use `--color-*` tokens), don't
+> add a token to `:root` without the dark block, and wrap any new animation in a
+> `prefers-reduced-motion` guard."
+
+### Where to look in `css/rdoc.css`
+
+| Asking about…        | Read this                                                       |
+|----------------------|-----------------------------------------------------------------|
+| Tokens (light)       | `:root` — §1 banner "Design System"                             |
+| Tokens (dark)        | `[data-theme="dark"]` block                                     |
+| Layout grid          | §2 "Global Styles & Layout"                                     |
+| Code / copy button   | §5 "Code and Pre"                                               |
+| Header / theme toggle| §6 "Header (Top Navbar)"                                        |
+| Left nav (`#sidebar-navigation`) | §7 "Navigation (Left Sidebar)"                             |
+| Signature cards, syntax classes | §8 "Main Content" (syntax classes ~`.ruby/.c/.sh-*`) |
+| Search (modal + dropdown + badges) | §9 "Search Modal" + the `.search-results` block   |
+| TOC scroll-spy       | §10 "Right Sidebar - Table of Contents"                         |
+| Footer               | §11 "Footer"                                                    |
+| Theme attribute / persistence | `js/theme-toggle.js` (`data-theme`, `localStorage`)    |
diff --git a/lib/rdoc/generator/template/aliki/_aside_toc.rhtml b/lib/rdoc/generator/template/aliki/_aside_toc.rhtml
index 3cb2584f3a..6bd1335588 100644
--- a/lib/rdoc/generator/template/aliki/_aside_toc.rhtml
+++ b/lib/rdoc/generator/template/aliki/_aside_toc.rhtml
@@ -1,4 +1,4 @@
-
+

On This Page

diff --git a/lib/rdoc/generator/template/aliki/_sidebar_toggle.rhtml b/lib/rdoc/generator/template/aliki/_sidebar_toggle.rhtml index ed2cbe31a0..1e183eb1c6 100644 --- a/lib/rdoc/generator/template/aliki/_sidebar_toggle.rhtml +++ b/lib/rdoc/generator/template/aliki/_sidebar_toggle.rhtml @@ -1,3 +1,3 @@ -
+
☰
diff --git a/lib/rdoc/generator/template/aliki/class.rhtml b/lib/rdoc/generator/template/aliki/class.rhtml index 6cb99ee214..c37cc68f71 100644 --- a/lib/rdoc/generator/template/aliki/class.rhtml +++ b/lib/rdoc/generator/template/aliki/class.rhtml @@ -3,7 +3,7 @@ <%= render '_header.rhtml' %> <%= render '_sidebar_toggle.rhtml' %> -
+
<%= render '_sidebar_pages.rhtml' %> <%= render '_sidebar_sections.rhtml' %> <%= render '_sidebar_ancestors.rhtml' %> @@ -30,8 +30,9 @@ <% end %> -

- <%= klass.type %> <%= klass.full_name %> +

+ <%= klass.type %> + <%= klass.full_name %>

@@ -127,44 +128,49 @@ <%- methods.each do |method| %>
">
- <%- if (call_seq = method.call_seq) %> - <%- call_seq.strip.split("\n").each_with_index do |call_seq, i| %> +
+ <%- if (call_seq = method.call_seq) %> + <%- call_seq.strip.split("\n").each_with_index do |call_seq, i| %> +
+ + + <%= h(call_seq.strip. + gsub( /^\w+\./m, '')). + gsub(/(.*)[-=]>/, '\1→') %> + + +
+ <%- end %> + <%- elsif method.has_call_seq? %>
- - <%= h(call_seq.strip. - gsub( /^\w+\./m, '')). - gsub(/(.*)[-=]>/, '\1→') %> - + <%= h method.name %> + +
+ <%- else %> +
+ + <%= h method.name %> + <%= h method.param_seq %>
<%- end %> - <%- elsif method.has_call_seq? %> -
- - <%= h method.name %> - -
- <%- else %> -
- - <%= h method.name %> - <%= h method.param_seq %> - -
- <%- end %> - <%- if (sig_html = type_signature_html(method, klass.path)) %> -
<%= sig_html %>
+ <%- if (sig_html = type_signature_html(method, klass.path)) %> +
<%= sig_html %>
+ <%- end %> +
+ + <%- if method.token_stream %> +
+
+ {} Source +
+
<%- end %>
<%- if method.token_stream %> -
-
- Source -
-
<%= method.markup_code %>
diff --git a/lib/rdoc/generator/template/aliki/css/rdoc.css b/lib/rdoc/generator/template/aliki/css/rdoc.css index a0bbae2d7b..1c641ba4ce 100644 --- a/lib/rdoc/generator/template/aliki/css/rdoc.css +++ b/lib/rdoc/generator/template/aliki/css/rdoc.css @@ -68,10 +68,11 @@ --color-accent-primary: var(--color-primary-600); --color-accent-hover: var(--color-primary-700); --color-accent-subtle: var(--color-primary-50); + --color-title-kind-border: var(--color-primary-200); --color-code-bg: #f6f8fa; --color-code-border: var(--color-neutral-300); --color-sig-bg: var(--color-neutral-100); - --color-sig-border: var(--color-primary-200); + --color-sig-border: var(--color-border-default); --color-nav-bg: #fff; --color-nav-text: var(--color-neutral-700); --color-th-background: var(--color-neutral-100); @@ -203,10 +204,11 @@ --color-accent-hover: var(--color-primary-400); --color-accent-subtle: rgb(235 84 79 / 10%); --color-accent-subtle-hover: rgb(235 84 79 / 20%); + --color-title-kind-border: rgb(235 84 79 / 35%); --color-code-bg: var(--color-neutral-800); --color-code-border: var(--color-neutral-700); --color-sig-bg: #211f1e; /* between neutral-900 and neutral-800 */ - --color-sig-border: var(--color-accent-primary); + --color-sig-border: var(--color-border-subtle); --color-nav-bg: var(--color-neutral-900); --color-nav-text: var(--color-neutral-50); --color-th-background: var(--color-background-tertiary); @@ -433,16 +435,35 @@ header.top-navbar { z-index: var(--z-fixed); background: var(--color-background-primary); border-bottom: 1px solid var(--color-border-default); - display: flex; + display: grid; + grid-template-columns: + var(--layout-sidebar-width) + minmax(0, 1fr); align-items: center; - justify-content: flex-start; - padding: 0 var(--space-6); - gap: var(--space-8); + padding: 0; height: var(--layout-header-height); box-shadow: var(--shadow-sm); } +body.has-toc header.top-navbar { + grid-template-columns: + var(--layout-sidebar-width) + minmax(0, 1fr) + minmax(var(--layout-toc-width-min), var(--layout-toc-width-max)); +} + +@media (width <= 1279px) { + body.has-toc header.top-navbar { + grid-template-columns: + var(--layout-sidebar-width) + minmax(0, 1fr); + } +} + header.top-navbar .navbar-brand { + grid-column: 1; + justify-self: start; + margin-left: var(--space-6); font-size: var(--font-size-xl); font-weight: var(--font-weight-semibold); color: var(--color-text-primary); @@ -455,12 +476,16 @@ header.top-navbar .navbar-brand:hover { } header.top-navbar .navbar-search { - position: relative; - flex: 0 1 auto; - width: var(--layout-search-width); + grid-column: 2; + justify-self: center; + box-sizing: border-box; + width: min(100%, var(--layout-content-max-width)); + padding: 0 var(--space-8); } header.top-navbar .navbar-search form { + position: relative; + width: min(100%, var(--layout-search-width)); margin: 0; padding: 0; } @@ -474,11 +499,13 @@ header.top-navbar .navbar-search form { /* Theme toggle button */ .theme-toggle { + position: absolute; + top: 50%; + right: var(--space-6); display: flex; align-items: center; justify-content: center; padding: var(--space-2); - margin-left: auto; background: transparent; border: 1px solid var(--color-border-default); border-radius: var(--radius-md); @@ -493,13 +520,14 @@ header.top-navbar .navbar-search form { line-height: 1; width: 2.5rem; height: 2.5rem; + transform: translateY(-50%); } .theme-toggle:hover { background: var(--color-background-secondary); border-color: var(--color-accent-primary); color: var(--color-accent-primary); - transform: scale(1.05); + transform: translateY(-50%) scale(1.05); } .theme-toggle:focus { @@ -509,7 +537,7 @@ header.top-navbar .navbar-search form { } .theme-toggle:active { - transform: scale(0.95); + transform: translateY(-50%) scale(0.95); } .theme-toggle-icon { @@ -562,10 +590,24 @@ header.top-navbar .navbar-search form { text-overflow: ellipsis; white-space: nowrap; } + + .theme-toggle { + position: static; + transform: none; + margin-right: 0; + } + + .theme-toggle:hover { + transform: scale(1.05); + } + + .theme-toggle:active { + transform: scale(0.95); + } } /* 7. Navigation (Left Sidebar) */ -nav { +#sidebar-navigation { grid-area: nav; font-family: var(--font-heading); font-size: var(--font-size-base); @@ -584,27 +626,27 @@ nav { } /* Custom scrollbar for WebKit browsers */ -nav::-webkit-scrollbar { +#sidebar-navigation::-webkit-scrollbar { width: 6px; } -nav::-webkit-scrollbar-track { +#sidebar-navigation::-webkit-scrollbar-track { background: transparent; } -nav::-webkit-scrollbar-thumb { +#sidebar-navigation::-webkit-scrollbar-thumb { background: var(--color-border-default); border-radius: var(--radius-sm); transition: background var(--transition-fast); } -nav::-webkit-scrollbar-thumb:hover { +#sidebar-navigation::-webkit-scrollbar-thumb:hover { background: var(--color-border-emphasis); } /* Mobile navigation */ @media (width <= 1023px) { - nav { + #sidebar-navigation { position: fixed; top: var(--layout-header-height); bottom: 0; @@ -616,7 +658,7 @@ nav::-webkit-scrollbar-thumb:hover { /* Don't set height - let top/bottom define it */ } - nav[hidden] { + #sidebar-navigation[hidden] { display: none; } @@ -633,7 +675,7 @@ nav::-webkit-scrollbar-thumb:hover { } /* Show backdrop when nav is open */ - body.nav-open::before { + body.sidebar-navigation-open::before { opacity: 1; pointer-events: auto; } @@ -641,17 +683,18 @@ nav::-webkit-scrollbar-thumb:hover { /* Desktop: hide nav when [hidden] attribute is set */ @media (width >= 1024px) { - nav[hidden] { + #sidebar-navigation[hidden] { display: none; } } -nav .nav-section { +#sidebar-navigation .nav-section { margin-top: var(--space-6); padding: 0 var(--space-6); } -nav h2, nav h3 { +#sidebar-navigation h2, +#sidebar-navigation h3 { font-size: var(--font-size-lg); font-weight: var(--font-weight-semibold); margin: 0 0 var(--space-4); @@ -660,51 +703,51 @@ nav h2, nav h3 { border-bottom: 1px solid var(--color-border-default); } -nav ul, -nav dl, -nav p { +#sidebar-navigation ul, +#sidebar-navigation dl, +#sidebar-navigation p { padding: 0; list-style: none; margin: var(--space-3) 0; } -nav ul li { +#sidebar-navigation ul li { margin-bottom: var(--space-2); line-height: var(--line-height-relaxed); } -nav ul li a { +#sidebar-navigation ul li a { transition: color var(--transition-fast), transform var(--transition-fast), padding var(--transition-fast); } -nav ul li a:hover { +#sidebar-navigation ul li a:hover { padding-left: var(--space-1); } -nav ul ul { +#sidebar-navigation ul ul { padding-left: var(--space-5); - margin-top: var(--space-2); + margin: var(--space-2) 0 0; } -nav ul ul ul { +#sidebar-navigation ul ul ul { padding-left: var(--space-5); } -nav ul ul ul ul { +#sidebar-navigation ul ul ul ul { padding-left: var(--space-5); } -nav a { +#sidebar-navigation a { text-decoration: none; } /* Truncation for direct nav links (not links inside code tags) */ -nav .nav-list > li > a, -nav .nav-section > ul > li > a, -nav .nav-section > dl > dd > a { +#sidebar-navigation .nav-list > li > a, +#sidebar-navigation .nav-section > ul > li > a, +#sidebar-navigation .nav-section > dl > dd > a { display: block; max-width: 100%; overflow: hidden; @@ -712,22 +755,22 @@ nav .nav-section > dl > dd > a { white-space: nowrap; } -nav footer { +#sidebar-navigation footer { padding: var(--space-4); border-top: 1px solid var(--color-border-default); } -nav footer a { +#sidebar-navigation footer a { color: var(--color-accent-hover); } -#navigation-toggle { +#sidebar-navigation-toggle { display: none; /* Hidden by default, shown on mobile */ } /* Mobile toggle button */ @media (width <= 1023px) { - #navigation-toggle { + #sidebar-navigation-toggle { display: flex; align-items: center; justify-content: center; @@ -747,7 +790,7 @@ nav footer a { -webkit-user-select: none; } - #navigation-toggle:hover { + #sidebar-navigation-toggle:hover { color: var(--color-accent-primary); } } @@ -758,23 +801,23 @@ nav footer a { * Uses block-size animation with interpolate-size for smooth height transitions. * Both nav-section-collapsible and nested link-list details share this pattern. */ -nav details { +#sidebar-navigation details { interpolate-size: allow-keywords; } -nav details::details-content { +#sidebar-navigation details::details-content { overflow: hidden; block-size: 0; transition: block-size 200ms ease, content-visibility 200ms ease allow-discrete; } -nav details[open]::details-content { +#sidebar-navigation details[open]::details-content { block-size: auto; } /* Collapsible Navigation Section Headers */ -nav .nav-section-header { +#sidebar-navigation .nav-section-header { display: flex; align-items: center; gap: var(--space-3); @@ -788,15 +831,15 @@ nav .nav-section-header { transition: color var(--transition-fast); } -nav .nav-section-header::-webkit-details-marker { +#sidebar-navigation .nav-section-header::-webkit-details-marker { display: none; } -nav .nav-section-header:hover { +#sidebar-navigation .nav-section-header:hover { color: var(--color-accent-primary); } -nav .nav-section-icon { +#sidebar-navigation .nav-section-icon { display: flex; align-items: center; justify-content: center; @@ -806,12 +849,12 @@ nav .nav-section-icon { color: var(--color-accent-primary); } -nav .nav-section-icon svg { +#sidebar-navigation .nav-section-icon svg { width: 100%; height: 100%; } -nav .nav-section-title { +#sidebar-navigation .nav-section-title { font-size: var(--font-size-base); font-weight: var(--font-weight-semibold); color: inherit; @@ -822,7 +865,7 @@ nav .nav-section-title { white-space: nowrap; } -nav .nav-section-chevron { +#sidebar-navigation .nav-section-chevron { display: flex; align-items: center; justify-content: center; @@ -833,46 +876,50 @@ nav .nav-section-chevron { transition: transform var(--transition-base); } -nav .nav-section-chevron svg { +#sidebar-navigation .nav-section-chevron svg { width: 100%; height: 100%; } /* Rotate chevron when open */ -nav .nav-section-collapsible[open] > .nav-section-header .nav-section-chevron { +#sidebar-navigation .nav-section-collapsible[open] > .nav-section-header .nav-section-chevron { transform: rotate(90deg); } -nav .nav-section-collapsible > ul, -nav .nav-section-collapsible > dl, -nav .nav-section-collapsible > p { +#sidebar-navigation .nav-section-collapsible > ul, +#sidebar-navigation .nav-section-collapsible > dl, +#sidebar-navigation .nav-section-collapsible > p { margin-top: 0; } -nav .nav-section-collapsible > .nav-list { +#sidebar-navigation .nav-section-collapsible > .nav-list { padding-left: var(--space-5); border-left: 1px solid var(--color-border-subtle); margin-left: 9px; /* Align with the section icon center */ } -nav .nav-section-collapsible .nav-list .link-list { +#sidebar-navigation .nav-section-collapsible .nav-list .link-list { border-left: none; margin-left: 0; padding-left: var(--space-5); } +#sidebar-navigation .nav-section-collapsible .nav-list .link-list > li:last-child { + margin-bottom: 0; +} + /* Improve chevron styling for details under link-list, using SVG chevron that matches nav-section-chevron We need to avoid adding the element in class content generation so it doesn't break darkfish styles */ -nav li details:has(.link-list) > summary { +#sidebar-navigation li details:has(.link-list) > summary { display: inline-flex; align-items: center; gap: var(--space-2); cursor: pointer; } -nav li details:has(.link-list) > summary::after { +#sidebar-navigation li details:has(.link-list) > summary::after { content: ''; position: static; display: inline-block; @@ -892,11 +939,11 @@ nav li details:has(.link-list) > summary::after { transition: transform var(--transition-base), background-color var(--transition-fast); } -nav li details:has(.link-list) > summary:hover::after { +#sidebar-navigation li details:has(.link-list) > summary:hover::after { background-color: var(--color-accent-primary); } -nav li details:has(.link-list)[open] > summary::after { +#sidebar-navigation li details:has(.link-list)[open] > summary::after { transform: rotate(90deg); background-color: var(--color-accent-primary); } @@ -905,6 +952,7 @@ nav li details:has(.link-list)[open] > summary::after { main { grid-area: main; width: 100%; + min-width: 0; max-width: var(--layout-content-max-width); margin: 0 auto; padding: var(--space-12) var(--space-8); @@ -916,7 +964,7 @@ main { /* Desktop: hide hamburger */ @media (width >= 1024px) { - #navigation-toggle { + #sidebar-navigation-toggle { display: none; } } @@ -939,13 +987,38 @@ main { } } -main h1[class] { +main h1.page-title { + display: grid; + gap: var(--space-2); + justify-items: start; margin-top: 0; - margin-bottom: 1em; - font-size: 2.5em; + margin-bottom: var(--space-8); color: var(--color-accent-primary); } +main h1.page-title .page-title-kind { + display: inline-flex; + align-items: center; + min-height: 1.625rem; + padding: 0 var(--space-2); + border: 1px solid var(--color-title-kind-border); + border-radius: var(--radius-md); + background: var(--color-accent-subtle); + color: var(--color-accent-primary); + font-size: var(--font-size-sm); + font-weight: var(--font-weight-semibold); + line-height: 1; +} + +main h1.page-title .page-title-name { + max-width: 100%; + color: var(--color-accent-primary); + font-size: var(--font-size-3xl); + font-weight: var(--font-weight-bold); + line-height: var(--line-height-tight); + overflow-wrap: anywhere; +} + main h1, main h2, main h3, @@ -1170,17 +1243,19 @@ main dd p:first-child { /* Headers within Main */ main header h2 { - margin-top: 2em; + margin: var(--space-8) 0 var(--space-4); + padding-top: var(--space-3); border-width: 0; - border-top: 4px solid var(--color-border-default); - font-size: 130%; + border-top: 1px solid var(--color-border-default); + font-size: var(--font-size-2xl); } main header h3 { - margin: 2em 0 1.5em; + margin: var(--space-8) 0 var(--space-6); + padding-top: var(--space-3); border-width: 0; - border-top: 3px solid var(--color-border-default); - font-size: 120%; + border-top: 1px solid var(--color-border-default); + font-size: var(--font-size-xl); } h1:target, @@ -1253,8 +1328,7 @@ main .method-source-code { } main .method-source-code pre { - border-color: var(--color-accent-hover); - border-left: 3px solid var(--color-accent-primary); + border-color: var(--color-code-border); width: 100%; box-sizing: border-box; transition: border-color var(--transition-fast); @@ -1309,11 +1383,18 @@ main .method-detail:target { } main .method-header { + display: grid; + grid-template-columns: minmax(0, 1fr) auto; + gap: var(--space-4); + align-items: center; background: var(--color-sig-bg); - border-left: 3px solid var(--color-sig-border); + border: 1px solid var(--color-sig-border); border-radius: var(--radius-md); - padding: var(--space-3); - padding-right: 6em; + padding: var(--space-4); +} + +main .method-heading-group { + min-width: 0; } main .method-heading { @@ -1392,13 +1473,16 @@ main .method-heading > .method-type-signature { } main .method-controls { - position: absolute; - top: var(--space-3); - right: var(--space-3); + position: static; + align-self: center; } main .method-controls summary { - display: inline-block; + display: inline-flex; + align-items: center; + justify-content: center; + gap: var(--space-2); + min-height: 30px; line-height: 20px; color: var(--color-accent-primary); cursor: pointer; @@ -1415,6 +1499,7 @@ main .method-controls summary { transform var(--transition-fast); user-select: none; -webkit-user-select: none; + white-space: nowrap; list-style: none; } @@ -1432,6 +1517,13 @@ main .method-controls summary:active { transform: scale(0.96); } +main .method-source-icon { + font-family: var(--font-code); + font-size: var(--font-size-xs); + line-height: 1; + opacity: 0.8; +} + [data-theme="dark"] main .method-controls summary:hover { background: var(--color-accent-subtle-hover); border-color: var(--color-primary-500); @@ -1479,7 +1571,7 @@ main .attribute-access-type { /* Small screen adjustments */ @media (width <= 480px) { - nav { + #sidebar-navigation { width: 85%; max-width: 320px; } @@ -1505,13 +1597,13 @@ main .attribute-access-type { } main .method-header { + grid-template-columns: 1fr; + gap: var(--space-3); padding: var(--space-2); - padding-right: var(--space-2); } main .method-controls { - position: static; - margin-top: var(--space-2); + justify-self: start; } } @@ -1693,7 +1785,7 @@ main .attribute-access-type { } /* 10. Right Sidebar - Table of Contents */ -aside.table-of-contents { +#table-of-contents { grid-area: toc; align-self: start; position: sticky; @@ -1704,43 +1796,45 @@ aside.table-of-contents { font-size: var(--font-size-base); } -aside.table-of-contents * { - border-right: none !important; - outline: none !important; -} - -aside.table-of-contents .toc-sticky { +#table-of-contents .toc-sticky { display: flex; flex-direction: column; - /* Exclude header height and top/bottom padding of aside.table-of-contents */ + /* Exclude header height and top/bottom padding of #table-of-contents */ height: calc(100vh - var(--layout-header-height) - var(--space-8) * 2); } -aside.table-of-contents .toc-sticky nav { - height: auto; +#table-of-contents .toc-nav { + flex: 1 1 auto; + min-height: 0; + overflow-y: auto; + scrollbar-width: thin; + scrollbar-color: var(--color-border-default) transparent; } -aside.table-of-contents .toc-list > .toc-h2 { +#table-of-contents .toc-list > .toc-h2 { margin-left: var(--space-4); } -aside.table-of-contents .toc-list > .toc-h3 { +#table-of-contents .toc-list > .toc-h3 { margin-left: var(--space-8); } /* Hide TOC on mobile/tablet */ @media (width <= 1279px) { - aside.table-of-contents { + #table-of-contents { display: none; } +} +/* Preserve the desktop sidebar/content grid when the TOC is hidden */ +@media (width >= 1024px) and (width <= 1279px) { body.has-toc { grid-template-columns: var(--layout-sidebar-width) 1fr; grid-template-areas: "header header" "nav main" - "footer footer"; + "nav footer"; } } @@ -1756,31 +1850,31 @@ aside.table-of-contents .toc-list > .toc-h3 { } } -.table-of-contents h3 { +#table-of-contents h3 { font-size: var(--font-size-lg); font-weight: var(--font-weight-semibold); margin: 0 0 var(--space-5) 0; color: var(--color-text-primary); } -.table-of-contents ul { +#table-of-contents ul { margin: 0; padding: 0; list-style: none; } -.table-of-contents ul ul { +#table-of-contents ul ul { margin-top: var(--space-3); margin-left: var(--space-5); border-left: 1px solid var(--color-border-default); padding-left: var(--space-4); } -.table-of-contents li { +#table-of-contents li { margin-bottom: var(--space-3); } -.table-of-contents a { +#table-of-contents a { display: block; color: var(--color-text-secondary); text-decoration: none; @@ -1791,32 +1885,46 @@ aside.table-of-contents .toc-list > .toc-h3 { } /* Nav hover styles sit here to keep specificity ordering with TOC links */ -nav a:hover { +#sidebar-navigation a:hover { color: var(--color-link-hover); text-decoration: underline; } -.table-of-contents a:hover { +#table-of-contents a:hover { color: var(--color-link-hover); + text-decoration: underline; } -.table-of-contents a.active { +#table-of-contents a:focus-visible { + outline: 2px solid var(--color-accent-primary); + outline-offset: 2px; + border-radius: var(--radius-sm); +} + +#table-of-contents a.active { color: var(--color-accent-primary); font-weight: var(--font-weight-medium); } ol.breadcrumb { display: flex; + flex-wrap: wrap; padding: 0; margin: 0 0 1em; } ol.breadcrumb li { display: block; + min-width: 0; list-style: none; font-size: 125%; } +ol.breadcrumb a, +ol.breadcrumb li > span:not(.separator) { + overflow-wrap: anywhere; +} + /* 11. Footer */ footer.site-footer { grid-area: footer; @@ -2008,6 +2116,8 @@ footer.site-footer .footer-bottom:first-child { } header.top-navbar #search-field { + display: block; + box-sizing: border-box; width: 100%; padding: var(--space-2) var(--space-4); border: 1px solid var(--color-border-default); @@ -2030,10 +2140,11 @@ header.top-navbar #search-field::placeholder { /* Search results dropdown in navbar */ header.top-navbar #search-results-desktop { + box-sizing: border-box; position: absolute; top: calc(100% + var(--space-2)); left: 0; - width: var(--layout-search-width); + width: 100%; max-height: 60vh; background: var(--color-background-primary); border: 1px solid var(--color-border-default); diff --git a/lib/rdoc/generator/template/aliki/index.rhtml b/lib/rdoc/generator/template/aliki/index.rhtml index 23103345b1..675cede57e 100644 --- a/lib/rdoc/generator/template/aliki/index.rhtml +++ b/lib/rdoc/generator/template/aliki/index.rhtml @@ -3,7 +3,7 @@ <%= render '_header.rhtml' %> <%= render '_sidebar_toggle.rhtml' %> -
+
<%= render '_sidebar_pages.rhtml' %> <%= render '_sidebar_classes.rhtml' %>
diff --git a/lib/rdoc/generator/template/aliki/js/aliki.js b/lib/rdoc/generator/template/aliki/js/aliki.js index 4dcfa826fc..59cbe69ab2 100644 --- a/lib/rdoc/generator/template/aliki/js/aliki.js +++ b/lib/rdoc/generator/template/aliki/js/aliki.js @@ -137,32 +137,32 @@ function hookFocus() { /* ===== Mobile Navigation ===== */ function hookSidebar() { - const navigation = document.querySelector('#navigation'); - const navigationToggle = document.querySelector('#navigation-toggle'); + const sidebarNavigation = document.querySelector('#sidebar-navigation'); + const sidebarNavigationToggle = document.querySelector('#sidebar-navigation-toggle'); - if (!navigation || !navigationToggle) return; + if (!sidebarNavigation || !sidebarNavigationToggle) return; const closeNav = () => { - navigation.hidden = true; - navigationToggle.ariaExpanded = 'false'; - document.body.classList.remove('nav-open'); + sidebarNavigation.hidden = true; + sidebarNavigationToggle.ariaExpanded = 'false'; + document.body.classList.remove('sidebar-navigation-open'); }; const openNav = () => { - navigation.hidden = false; - navigationToggle.ariaExpanded = 'true'; - document.body.classList.add('nav-open'); + sidebarNavigation.hidden = false; + sidebarNavigationToggle.ariaExpanded = 'true'; + document.body.classList.add('sidebar-navigation-open'); }; const toggleNav = () => { - if (navigation.hidden) { + if (sidebarNavigation.hidden) { openNav(); } else { closeNav(); } }; - navigationToggle.addEventListener('click', (e) => { + sidebarNavigationToggle.addEventListener('click', (e) => { e.stopPropagation(); toggleNav(); }); @@ -174,18 +174,20 @@ function hookSidebar() { // This is better than the opposite approach of hiding it with JavaScript // because it avoids flickering the sidebar when the page is loaded, especially on mobile devices if (isSmallViewport) { + closeNav(); + // Close nav when clicking links inside it document.addEventListener('click', (e) => { - if (e.target.closest('#navigation a')) { + if (e.target.closest('#sidebar-navigation a')) { closeNav(); } }); // Close nav when clicking backdrop document.addEventListener('click', (e) => { - if (!navigation.hidden && - !e.target.closest('#navigation') && - !e.target.closest('#navigation-toggle')) { + if (!sidebarNavigation.hidden && + !e.target.closest('#sidebar-navigation') && + !e.target.closest('#sidebar-navigation-toggle')) { closeNav(); } }); @@ -233,7 +235,7 @@ function generateToc() { tocNav.appendChild(tocList); } else { // Hide TOC if no headings found - const tocContainer = document.querySelector('.table-of-contents'); + const tocContainer = document.querySelector('#table-of-contents'); if (tocContainer) { tocContainer.style.display = 'none'; } diff --git a/lib/rdoc/generator/template/aliki/page.rhtml b/lib/rdoc/generator/template/aliki/page.rhtml index e7b0dc66cc..1d9330da24 100644 --- a/lib/rdoc/generator/template/aliki/page.rhtml +++ b/lib/rdoc/generator/template/aliki/page.rhtml @@ -3,7 +3,7 @@ <%= render '_header.rhtml' %> <%= render '_sidebar_toggle.rhtml' %> -
+
<%= render '_sidebar_pages.rhtml' %> <%= render '_sidebar_classes.rhtml' %>
diff --git a/lib/rdoc/generator/template/aliki/servlet_not_found.rhtml b/lib/rdoc/generator/template/aliki/servlet_not_found.rhtml index 6ad306c625..42157b5185 100644 --- a/lib/rdoc/generator/template/aliki/servlet_not_found.rhtml +++ b/lib/rdoc/generator/template/aliki/servlet_not_found.rhtml @@ -1,7 +1,7 @@ <%= render '_sidebar_toggle.rhtml' %> -
+
<%= render '_sidebar_pages.rhtml' %> <%= render '_sidebar_classes.rhtml' %>
diff --git a/lib/rdoc/generator/template/aliki/servlet_root.rhtml b/lib/rdoc/generator/template/aliki/servlet_root.rhtml index 0698bbde68..dbebc63863 100644 --- a/lib/rdoc/generator/template/aliki/servlet_root.rhtml +++ b/lib/rdoc/generator/template/aliki/servlet_root.rhtml @@ -1,7 +1,7 @@ <%= render '_sidebar_toggle.rhtml' %> -
+

diff --git a/test/rdoc/generator/aliki_test.rb b/test/rdoc/generator/aliki_test.rb index b12ba230f5..50742b0df4 100644 --- a/test/rdoc/generator/aliki_test.rb +++ b/test/rdoc/generator/aliki_test.rb @@ -228,7 +228,8 @@ def test_generate index = File.binread('index.html') assert_match %r{}, index assert_match %r{}, index + assert_match %r{
}, index + assert_match %r{
}, index end