Add living styleguide endpoint and vanilla JS utilities

- New /styleguide endpoint with CSS Grid layout system, design tokens,
  and component library (buttons, forms, alerts, cards, tables, etc.)
- custom.js replaces jQuery patterns with native DOM APIs
- Pure CSS with custom properties, no preprocessors, no flexbox
- Capability-driven presentation with progressive enhancement
- Wire styleguide app into middleware includes
- Update CLAUDE.md with UI modernization goals
This commit is contained in:
russell@unturf.com 2026-02-19 16:03:21 -05:00
parent 4f73837822
commit 340e25aa71
7 changed files with 1979 additions and 0 deletions

View file

@ -0,0 +1,772 @@
<!DOCTYPE html>
<html lang="en" class="no-js">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Style Guide — RhodeCode</title>
<link rel="stylesheet" href="${h.asset('css/styleguide.css')}">
<noscript>
<style>.js-only { display: none !important; }</style>
</noscript>
</head>
<body class="sg-body">
<div class="sg-layout">
<!-- Header -->
<header class="sg-header">
<h1>RhodeCode Style Guide</h1>
<span></span>
<span class="sg-version">v${c.rhodecode_version}</span>
</header>
<!-- Sidebar Navigation -->
<aside class="sg-sidebar">
<nav>
<h3>Foundation</h3>
<a href="#colors">Colors</a>
<a href="#typography">Typography</a>
<a href="#spacing">Spacing</a>
<a href="#grid">CSS Grid Layout</a>
<h3>Components</h3>
<a href="#buttons">Buttons</a>
<a href="#forms">Forms</a>
<a href="#alerts">Alerts</a>
<a href="#cards">Cards</a>
<a href="#tables">Tables</a>
<a href="#badges">Badges</a>
<a href="#details">Details / Accordion</a>
<a href="#dialog">Dialog</a>
<h3>Patterns</h3>
<a href="#navigation">Navigation Bar</a>
<a href="#repo-list">Repository List</a>
<a href="#diff">Diff View</a>
<a href="#code">Code Blocks</a>
<h3>Principles</h3>
<a href="#capability">Capability-Driven</a>
</nav>
</aside>
<!-- Main Content -->
<main class="sg-main">
<!-- ============================================================ -->
<!-- COLORS -->
<!-- ============================================================ -->
<section class="sg-section" id="colors">
<h2>Colors</h2>
<p class="sg-desc">
All colors are CSS custom properties defined on <code>:root</code>.
No preprocessor variables. Change the token, change the world.
</p>
<div class="sg-subsection">
<h3>Core</h3>
<div class="sg-colors">
<div class="sg-swatch">
<div class="sg-swatch-color" style="background: var(--bg)"></div>
<div class="sg-swatch-info">
<div class="sg-swatch-name">--bg</div>
<div class="sg-swatch-value">#fafafa</div>
</div>
</div>
<div class="sg-swatch">
<div class="sg-swatch-color" style="background: var(--surface)"></div>
<div class="sg-swatch-info">
<div class="sg-swatch-name">--surface</div>
<div class="sg-swatch-value">#ffffff</div>
</div>
</div>
<div class="sg-swatch">
<div class="sg-swatch-color" style="background: var(--text)"></div>
<div class="sg-swatch-info">
<div class="sg-swatch-name">--text</div>
<div class="sg-swatch-value">#1a1a1a</div>
</div>
</div>
<div class="sg-swatch">
<div class="sg-swatch-color" style="background: var(--text-muted)"></div>
<div class="sg-swatch-info">
<div class="sg-swatch-name">--text-muted</div>
<div class="sg-swatch-value">#6b7280</div>
</div>
</div>
<div class="sg-swatch">
<div class="sg-swatch-color" style="background: var(--border)"></div>
<div class="sg-swatch-info">
<div class="sg-swatch-name">--border</div>
<div class="sg-swatch-value">#e0e0e0</div>
</div>
</div>
<div class="sg-swatch">
<div class="sg-swatch-color" style="background: var(--accent)"></div>
<div class="sg-swatch-info">
<div class="sg-swatch-name">--accent</div>
<div class="sg-swatch-value">#2563eb</div>
</div>
</div>
</div>
</div>
<div class="sg-subsection">
<h3>Semantic</h3>
<div class="sg-colors">
<div class="sg-swatch">
<div class="sg-swatch-color" style="background: var(--success)"></div>
<div class="sg-swatch-info">
<div class="sg-swatch-name">--success</div>
<div class="sg-swatch-value">#059669</div>
</div>
</div>
<div class="sg-swatch">
<div class="sg-swatch-color" style="background: var(--warning)"></div>
<div class="sg-swatch-info">
<div class="sg-swatch-name">--warning</div>
<div class="sg-swatch-value">#d97706</div>
</div>
</div>
<div class="sg-swatch">
<div class="sg-swatch-color" style="background: var(--danger)"></div>
<div class="sg-swatch-info">
<div class="sg-swatch-name">--danger</div>
<div class="sg-swatch-value">#dc2626</div>
</div>
</div>
<div class="sg-swatch">
<div class="sg-swatch-color" style="background: var(--info)"></div>
<div class="sg-swatch-info">
<div class="sg-swatch-name">--info</div>
<div class="sg-swatch-value">#2563eb</div>
</div>
</div>
</div>
</div>
</section>
<!-- ============================================================ -->
<!-- TYPOGRAPHY -->
<!-- ============================================================ -->
<section class="sg-section" id="typography">
<h2>Typography</h2>
<p class="sg-desc">
System font stack for sans-serif. Modern monospace stack for code.
16px base. 1.6 line height. Maximum 65ch measure for body text.
</p>
<div class="sg-showcase">
<div class="sg-type-scale">
<div class="sg-type-specimen">
<span class="sg-type-label">2rem</span>
<span style="font-size: 2rem; font-weight: 700; letter-spacing: -0.02em">Page Title</span>
</div>
<div class="sg-type-specimen">
<span class="sg-type-label">1.5rem</span>
<span style="font-size: 1.5rem; font-weight: 700; letter-spacing: -0.02em">Section Title</span>
</div>
<div class="sg-type-specimen">
<span class="sg-type-label">1.125rem</span>
<span style="font-size: 1.125rem; font-weight: 600">Subsection Title</span>
</div>
<div class="sg-type-specimen">
<span class="sg-type-label">1rem</span>
<span style="font-size: 1rem">Body text &mdash; the quick brown fox jumps over the lazy dog.</span>
</div>
<div class="sg-type-specimen">
<span class="sg-type-label">0.875rem</span>
<span style="font-size: 0.875rem">Secondary text, form labels, table cells.</span>
</div>
<div class="sg-type-specimen">
<span class="sg-type-label">0.75rem</span>
<span style="font-size: 0.75rem; color: var(--text-muted)">Caption, metadata, timestamps.</span>
</div>
<div class="sg-type-specimen">
<span class="sg-type-label">mono</span>
<span style="font-family: var(--font-mono); font-size: 0.875rem">git commit -m "code outlasts authors"</span>
</div>
</div>
</div>
</section>
<!-- ============================================================ -->
<!-- SPACING -->
<!-- ============================================================ -->
<section class="sg-section" id="spacing">
<h2>Spacing</h2>
<p class="sg-desc">
4px base unit. 8-step scale. Use CSS custom properties for consistency.
</p>
<div class="sg-showcase">
<div style="display: grid; gap: var(--space-3);">
% for i, size in enumerate(['4px', '8px', '12px', '16px', '24px', '32px', '48px', '64px'], 1):
<div style="display: grid; grid-template-columns: 80px 1fr; align-items: center; gap: var(--space-4);">
<code style="font-size: 0.75rem;">--space-${i}</code>
<div style="height: 16px; width: var(--space-${i}); background: var(--accent); border-radius: 2px;"></div>
</div>
% endfor
</div>
</div>
</section>
<!-- ============================================================ -->
<!-- CSS GRID LAYOUT -->
<!-- ============================================================ -->
<section class="sg-section" id="grid">
<h2>CSS Grid Layout</h2>
<p class="sg-desc">
All layout uses CSS Grid. No flexbox. No floats. Grid handles both
macro layout (page structure) and micro layout (component internals).
</p>
<div class="sg-subsection">
<h3>12-column grid</h3>
<div class="sg-showcase">
<div style="display: grid; grid-template-columns: repeat(12, 1fr); gap: var(--space-2);">
% for i in range(12):
<div style="background: var(--accent-light); padding: var(--space-2); text-align: center; font-size: 0.75rem; border-radius: var(--radius-sm); color: var(--accent);">${i+1}</div>
% endfor
</div>
<div style="display: grid; grid-template-columns: repeat(12, 1fr); gap: var(--space-2); margin-top: var(--space-3);">
<div style="grid-column: span 8; background: var(--accent); color: var(--text-inverse); padding: var(--space-3); border-radius: var(--radius-sm); font-size: 0.8125rem;">span 8 &mdash; main content</div>
<div style="grid-column: span 4; background: var(--surface-alt); padding: var(--space-3); border-radius: var(--radius-sm); font-size: 0.8125rem; border: 1px solid var(--border);">span 4 &mdash; sidebar</div>
</div>
<div style="display: grid; grid-template-columns: repeat(12, 1fr); gap: var(--space-2); margin-top: var(--space-3);">
<div style="grid-column: span 4; background: var(--surface-alt); padding: var(--space-3); border-radius: var(--radius-sm); font-size: 0.8125rem; border: 1px solid var(--border);">span 4</div>
<div style="grid-column: span 4; background: var(--surface-alt); padding: var(--space-3); border-radius: var(--radius-sm); font-size: 0.8125rem; border: 1px solid var(--border);">span 4</div>
<div style="grid-column: span 4; background: var(--surface-alt); padding: var(--space-3); border-radius: var(--radius-sm); font-size: 0.8125rem; border: 1px solid var(--border);">span 4</div>
</div>
</div>
</div>
<div class="sg-code">&lt;div style="display: grid; grid-template-columns: repeat(12, 1fr); gap: 8px;"&gt;
&lt;div style="grid-column: span 8;"&gt;Main content&lt;/div&gt;
&lt;div style="grid-column: span 4;"&gt;Sidebar&lt;/div&gt;
&lt;/div&gt;</div>
</section>
<!-- ============================================================ -->
<!-- BUTTONS -->
<!-- ============================================================ -->
<section class="sg-section" id="buttons">
<h2>Buttons</h2>
<p class="sg-desc">
Simple, accessible buttons. Keyboard-focusable with visible focus rings.
Use <code>&lt;button&gt;</code> for actions, <code>&lt;a&gt;</code> for navigation.
</p>
<div class="sg-showcase">
<div class="sg-button-grid">
<button class="btn btn-primary">Primary</button>
<button class="btn btn-secondary">Secondary</button>
<button class="btn btn-danger">Danger</button>
<button class="btn btn-ghost">Ghost</button>
<button class="btn btn-primary" disabled>Disabled</button>
</div>
</div>
<div class="sg-subsection">
<h3>Sizes</h3>
<div class="sg-showcase">
<div class="sg-button-grid">
<button class="btn btn-primary btn-sm">Small</button>
<button class="btn btn-primary">Default</button>
<button class="btn btn-primary btn-lg">Large</button>
</div>
</div>
</div>
<div class="sg-code">&lt;button class="btn btn-primary"&gt;Primary&lt;/button&gt;
&lt;button class="btn btn-secondary"&gt;Secondary&lt;/button&gt;
&lt;button class="btn btn-danger"&gt;Danger&lt;/button&gt;
&lt;button class="btn btn-ghost"&gt;Ghost&lt;/button&gt;</div>
</section>
<!-- ============================================================ -->
<!-- FORMS -->
<!-- ============================================================ -->
<section class="sg-section" id="forms">
<h2>Forms</h2>
<p class="sg-desc">
Native HTML5 form elements. No custom dropdowns unless absolutely necessary.
Use <code>&lt;datalist&gt;</code> for autocomplete. Use <code>required</code>,
<code>pattern</code>, and <code>type</code> for validation.
</p>
<div class="sg-showcase">
<div class="form-row">
<div class="form-group">
<label class="form-label" for="sg-repo-name">Repository name</label>
<input class="form-input" type="text" id="sg-repo-name" placeholder="my-project" required>
<span class="form-hint">Letters, numbers, hyphens, and underscores only.</span>
</div>
<div class="form-group">
<label class="form-label" for="sg-repo-type">Type</label>
<select class="form-select" id="sg-repo-type">
<option>Git</option>
<option>Mercurial</option>
<option>Subversion</option>
</select>
</div>
</div>
<div class="form-group">
<label class="form-label" for="sg-desc">Description</label>
<textarea class="form-textarea" id="sg-desc" placeholder="A short description of the repository..."></textarea>
</div>
<div class="form-group">
<label class="form-label">
<input type="checkbox"> Make this repository private
</label>
</div>
<button class="btn btn-primary">Create Repository</button>
</div>
</section>
<!-- ============================================================ -->
<!-- ALERTS -->
<!-- ============================================================ -->
<section class="sg-section" id="alerts">
<h2>Alerts</h2>
<p class="sg-desc">
Capability-driven: dismiss buttons are <code>js-only</code>. Without JavaScript,
users see the message but not a broken dismiss button.
</p>
<div class="sg-showcase" style="display: grid; gap: var(--space-3);">
<div class="alert alert-success">
<span>&#10003;</span>
<span>Repository <strong>my-project</strong> created successfully.</span>
<button class="alert-dismiss js-only" onclick="dismissAlert(this.parentElement)">&times;</button>
</div>
<div class="alert alert-warning">
<span>&#9888;</span>
<span>Your session will expire in 5 minutes.</span>
<button class="alert-dismiss js-only" onclick="dismissAlert(this.parentElement)">&times;</button>
</div>
<div class="alert alert-danger">
<span>&#10007;</span>
<span>Failed to merge pull request: conflicts detected.</span>
<button class="alert-dismiss js-only" onclick="dismissAlert(this.parentElement)">&times;</button>
</div>
<div class="alert alert-info">
<span>&#8505;</span>
<span>A new version of RhodeCode is available.</span>
<button class="alert-dismiss js-only" onclick="dismissAlert(this.parentElement)">&times;</button>
</div>
</div>
</section>
<!-- ============================================================ -->
<!-- CARDS -->
<!-- ============================================================ -->
<section class="sg-section" id="cards">
<h2>Cards</h2>
<p class="sg-desc">
Content containers with optional header and footer. Use CSS Grid for card layouts.
</p>
<div class="card-grid">
<div class="card">
<div class="card-header">Pull Request #142</div>
<div class="card-body">
<p style="font-size: 0.875rem;">Fix session handling for cookie-based authentication. Remove beaker dependency entirely.</p>
</div>
<div class="card-footer">
<span class="badge badge-success">Approved</span>
</div>
</div>
<div class="card">
<div class="card-header">Pull Request #143</div>
<div class="card-body">
<p style="font-size: 0.875rem;">Modernize UI: remove jQuery, switch to CSS Grid, add living style guide endpoint.</p>
</div>
<div class="card-footer">
<span class="badge badge-warning">Review Required</span>
</div>
</div>
<div class="card">
<div class="card-header">Pull Request #144</div>
<div class="card-body">
<p style="font-size: 0.875rem;">Add SQLite3 support for single-instance deployments on permacomputer hardware.</p>
</div>
<div class="card-footer">
<span class="badge badge-accent">Draft</span>
</div>
</div>
</div>
</section>
<!-- ============================================================ -->
<!-- TABLES -->
<!-- ============================================================ -->
<section class="sg-section" id="tables">
<h2>Tables</h2>
<p class="sg-desc">
Native HTML tables with minimal styling. Wrapped for horizontal scroll on narrow screens.
</p>
<div class="table-wrap">
<table class="sg-table">
<thead>
<tr>
<th>Commit</th>
<th>Message</th>
<th>Author</th>
<th>Date</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>4f73837</code></td>
<td>Remove beaker dependency, switch to SignedCookieSessionFactory</td>
<td>fox</td>
<td>2 hours ago</td>
</tr>
<tr>
<td><code>ceb23e4</code></td>
<td>release(version-bump): 5.11.3 &rarr; 5.11.4</td>
<td>andverb</td>
<td>1 day ago</td>
</tr>
<tr>
<td><code>93d4bc5</code></td>
<td>Cleanup, better handling for multiline comments</td>
<td>andverb</td>
<td>3 days ago</td>
</tr>
</tbody>
</table>
</div>
</section>
<!-- ============================================================ -->
<!-- BADGES -->
<!-- ============================================================ -->
<section class="sg-section" id="badges">
<h2>Badges</h2>
<div class="sg-showcase">
<div style="display: grid; grid-auto-flow: column; gap: var(--space-3); justify-content: start;">
<span class="badge badge-accent">Feature</span>
<span class="badge badge-success">Merged</span>
<span class="badge badge-warning">Pending</span>
<span class="badge badge-danger">Rejected</span>
<span class="badge badge-neutral">Draft</span>
</div>
</div>
</section>
<!-- ============================================================ -->
<!-- DETAILS / ACCORDION -->
<!-- ============================================================ -->
<section class="sg-section" id="details">
<h2>Details / Accordion</h2>
<p class="sg-desc">
Native <code>&lt;details&gt;</code> element. Works without JavaScript.
Progressive enhancement at its finest.
</p>
<details class="sg-details">
<summary>Repository Settings</summary>
<div class="sg-details-body">
<p style="font-size: 0.875rem;">Configure repository name, description, landing page, and permissions. All changes take effect immediately.</p>
</div>
</details>
<details class="sg-details">
<summary>VCS Configuration</summary>
<div class="sg-details-body">
<p style="font-size: 0.875rem;">Configure hooks, large file storage, and protocol settings for this repository.</p>
</div>
</details>
<details class="sg-details" open>
<summary>Danger Zone</summary>
<div class="sg-details-body">
<p style="font-size: 0.875rem; margin-bottom: var(--space-4);">Destructive actions that cannot be undone. Proceed with caution.</p>
<button class="btn btn-danger btn-sm">Delete Repository</button>
</div>
</details>
</section>
<!-- ============================================================ -->
<!-- DIALOG -->
<!-- ============================================================ -->
<section class="sg-section" id="dialog">
<h2>Dialog</h2>
<p class="sg-desc">
Native <code>&lt;dialog&gt;</code> element with <code>showModal()</code>.
Accessible by default. Backdrop handled by CSS.
The trigger button is <code>js-only</code> &mdash; no broken button without JS.
</p>
<div class="sg-showcase">
<button class="btn btn-primary js-only" onclick="openDialog('#sg-demo-dialog')">Open Dialog</button>
<noscript><p style="font-size: 0.875rem; color: var(--text-muted);">Dialog requires JavaScript.</p></noscript>
</div>
<dialog id="sg-demo-dialog" class="sg-dialog">
<div class="sg-dialog-header">
<h3>Confirm Merge</h3>
<button class="btn btn-ghost btn-sm" onclick="closeDialog('#sg-demo-dialog')">&times;</button>
</div>
<div class="sg-dialog-body">
<p style="font-size: 0.875rem;">
You are about to merge pull request <strong>#142</strong> into <code>main</code>.
This action cannot be undone.
</p>
</div>
<div class="sg-dialog-footer">
<span></span>
<button class="btn btn-secondary" onclick="closeDialog('#sg-demo-dialog')">Cancel</button>
<button class="btn btn-primary" onclick="closeDialog('#sg-demo-dialog')">Merge</button>
</div>
</dialog>
</section>
<!-- ============================================================ -->
<!-- NAVIGATION BAR -->
<!-- ============================================================ -->
<section class="sg-section" id="navigation">
<h2>Navigation Bar</h2>
<p class="sg-desc">
Top navigation using CSS Grid. Three-column layout: logo, links, actions.
</p>
<div class="sg-showcase" style="padding: 0;">
<nav class="nav-bar">
<a class="nav-logo" href="#">RhodeCode</a>
<div class="nav-links">
<a href="#">Repositories</a>
<a href="#">Groups</a>
<a href="#">Admin</a>
</div>
<div class="nav-actions">
<button class="btn btn-primary btn-sm">New Repo</button>
</div>
</nav>
</div>
</section>
<!-- ============================================================ -->
<!-- REPOSITORY LIST -->
<!-- ============================================================ -->
<section class="sg-section" id="repo-list">
<h2>Repository List</h2>
<p class="sg-desc">
Repository listing with icon, info, and metadata. CSS Grid three-column layout.
</p>
<div class="sg-showcase" style="padding: 0;">
<div class="card">
<div class="repo-item">
<div class="repo-icon">G</div>
<div class="repo-info">
<h4><a href="#">rhodecode-enterprise-ce</a></h4>
<p>Enterprise Source Code Management Platform</p>
</div>
<div class="repo-meta">
<span>&#9733; 42</span>
<span>&#8916; 7</span>
</div>
</div>
<div class="repo-item">
<div class="repo-icon" style="background: var(--success-light); color: var(--success);">M</div>
<div class="repo-info">
<h4><a href="#">make_post_sell</a></h4>
<p>Commission-free digital and physical sales</p>
</div>
<div class="repo-meta">
<span>&#9733; 18</span>
<span>&#8916; 3</span>
</div>
</div>
<div class="repo-item">
<div class="repo-icon" style="background: var(--warning-light); color: var(--warning);">H</div>
<div class="repo-info">
<h4><a href="#">remarkbox</a></h4>
<p>Hosted comment system, embeds in pages</p>
</div>
<div class="repo-meta">
<span>&#9733; 31</span>
<span>&#8916; 5</span>
</div>
</div>
</div>
</div>
</section>
<!-- ============================================================ -->
<!-- DIFF VIEW -->
<!-- ============================================================ -->
<section class="sg-section" id="diff">
<h2>Diff View</h2>
<p class="sg-desc">
Side-by-side line numbers with CSS Grid. Monospace font. Addition and deletion colors.
</p>
<div class="diff-block">
<div class="diff-header">rhodecode/lib/rc_beaker.py</div>
<div class="diff-line diff-del">
<span class="diff-line-num">8</span>
<span class="diff-line-num"></span>
<span class="diff-line-content">-from beaker import cache</span>
</div>
<div class="diff-line diff-del">
<span class="diff-line-num">9</span>
<span class="diff-line-num"></span>
<span class="diff-line-content">-from beaker.session import SessionObject, Session</span>
</div>
<div class="diff-line diff-add">
<span class="diff-line-num"></span>
<span class="diff-line-num">8</span>
<span class="diff-line-content">+from pyramid.session import SignedCookieSessionFactory</span>
</div>
<div class="diff-line diff-add">
<span class="diff-line-num"></span>
<span class="diff-line-num">9</span>
<span class="diff-line-content">+from pyramid.settings import asbool</span>
</div>
<div class="diff-line">
<span class="diff-line-num">10</span>
<span class="diff-line-num">10</span>
<span class="diff-line-content"> </span>
</div>
<div class="diff-line">
<span class="diff-line-num">11</span>
<span class="diff-line-num">11</span>
<span class="diff-line-content"> </span>
</div>
<div class="diff-line diff-del">
<span class="diff-line-num">12</span>
<span class="diff-line-num"></span>
<span class="diff-line-content">-class CustomSession(Session):</span>
</div>
<div class="diff-line diff-del">
<span class="diff-line-num">13</span>
<span class="diff-line-num"></span>
<span class="diff-line-content">- pass</span>
</div>
<div class="diff-line diff-add">
<span class="diff-line-num"></span>
<span class="diff-line-num">12</span>
<span class="diff-line-content">+def session_factory_from_settings(settings):</span>
</div>
</div>
</section>
<!-- ============================================================ -->
<!-- CODE BLOCKS -->
<!-- ============================================================ -->
<section class="sg-section" id="code">
<h2>Code Blocks</h2>
<p class="sg-desc">
Inline <code>code</code> and block-level pre/code. Monospace font stack.
</p>
<div class="sg-code">def session_factory_from_settings(settings):
"""Return a Pyramid session factory using signed cookie session settings."""
prefixes = ("session.", "beaker.session.")
options = {}
for k, v in settings.items():
for prefix in prefixes:
if k.startswith(prefix):
options[k[len(prefix):]] = v
return SignedCookieSessionFactory(**factory_kwargs)</div>
</section>
<!-- ============================================================ -->
<!-- CAPABILITY-DRIVEN PRESENTATION -->
<!-- ============================================================ -->
<section class="sg-section" id="capability">
<h2>Capability-Driven Presentation</h2>
<p class="sg-desc">
A web page does not need to look the same on every browser or device.
Capability drives presentation. Deliver identical content regardless of
viewer capabilities. Use the viewer's capabilities to gracefully enhance
the presentation.
</p>
<div class="sg-subsection">
<h3>The <code>js-only</code> Pattern</h3>
<p style="font-size: 0.875rem; color: var(--text-muted); margin-bottom: var(--space-4); max-width: 65ch;">
Elements with <code>class="js-only"</code> are hidden when JavaScript is
unavailable. This prevents presenting broken interactive elements to users
whose browser cannot execute them.
</p>
<div class="sg-code">&lt;noscript&gt;
&lt;style&gt;.js-only { display: none !important; }&lt;/style&gt;
&lt;/noscript&gt;
&lt;!-- The dismiss button only shows when JS can handle it --&gt;
&lt;div class="alert alert-success"&gt;
&lt;span&gt;Repository created.&lt;/span&gt;
&lt;button class="alert-dismiss js-only"&gt;&amp;times;&lt;/button&gt;
&lt;/div&gt;</div>
</div>
<div class="sg-subsection">
<h3>The Rules</h3>
<div class="sg-showcase">
<ol style="font-size: 0.875rem; padding-left: var(--space-5); display: grid; gap: var(--space-3);">
<li>Maintain a single canonical URI for all users.</li>
<li>Deliver identical content regardless of viewer capabilities.</li>
<li>Use the viewer's capabilities to gracefully enhance the presentation.</li>
<li>Never present a broken button to the user.</li>
<li>CSS Grid only. No flexbox. No floats.</li>
<li>No jQuery. No CSS preprocessors. No framework bloat.</li>
<li>Native HTML5 elements first: <code>&lt;details&gt;</code>, <code>&lt;dialog&gt;</code>, <code>&lt;datalist&gt;</code>.</li>
<li>Code outlasts authors. Infrastructure outlasts builders.</li>
</ol>
</div>
</div>
</section>
</main>
</div>
<script src="${h.asset('js/custom.js')}"></script>
<script>
// Sidebar active state on scroll
onReady(function() {
var links = $$('.sg-sidebar nav a');
var sections = $$('.sg-section[id]');
// Smooth scroll for sidebar links
links.forEach(function(link) {
link.addEventListener('click', function(e) {
var href = this.getAttribute('href');
if (href && href.startsWith('#')) {
e.preventDefault();
scrollToElement(href);
history.replaceState(null, '', href);
}
});
});
// Highlight active section on scroll
var main = $('.sg-main');
if (main) {
main.addEventListener('scroll', function() {
var scrollTop = main.scrollTop;
var current = '';
sections.forEach(function(section) {
if (section.offsetTop - 100 <= scrollTop) {
current = section.id;
}
});
links.forEach(function(link) {
link.classList.remove('active');
if (link.getAttribute('href') === '#' + current) {
link.classList.add('active');
}
});
});
}
});
</script>
</body>
</html>