diff --git a/Makefile b/Makefile
index c298a9e..0650f18 100644
--- a/Makefile
+++ b/Makefile
@@ -56,6 +56,7 @@ docs: prepare-web
cd contracts && fe doc -o ../web/static/api bundle
cd contracts && fe doc -o ../web/static/api static
sed -i 's/Fe Docs/Bountiful Docs/g; s/Fe Documentation/Bountiful Documentation/g' web/static/api/index.html
+ sed -i 's||\n |' web/static/api/index.html
# Remove all build artifacts
clean:
diff --git a/web/static/css/api-theme.css b/web/static/css/api-theme.css
new file mode 100644
index 0000000..769a977
--- /dev/null
+++ b/web/static/css/api-theme.css
@@ -0,0 +1,165 @@
+/* Bountiful C64 theme for the generated /api docs.
+ Uses the theming knobs exposed by fe-web's styles.css — override the
+ CSS custom properties and you retheme the whole viewer. */
+
+@import url('https://fonts.googleapis.com/css2?family=Press+Start+2P&family=Silkscreen:wght@400;700&display=swap');
+
+/* Apply C64 palette + pixel rendering in all modes.
+ Selectors mirror fe-web's so we win on specificity for dark mode. */
+:root,
+:root[data-theme="light"],
+:root[data-theme="dark"] {
+ /* Palette */
+ --bg: #40318D;
+ --bg-secondary: #2B1E5C;
+ --text: #DDD6F3;
+ --text-muted: #9E90D6;
+ --border: #7869C4;
+ --accent: #FFD866;
+ --accent-hover: #FFEF8F;
+ --text-accent: #FFD866;
+ --code-bg: #241750;
+
+ /* Highlight colors — tuned for the purple backdrop */
+ --hl-ref-bg: rgba(255, 216, 102, 0.10);
+ --hl-def-bg: rgba(255, 216, 102, 0.22);
+ --hl-def-underline: rgba(255, 216, 102, 0.55);
+ --target-bg: color-mix(in srgb, #FFD866 15%, #2B1E5C);
+
+ /* Root size: bump from the browser default 16px so everything rem-based
+ scales up together. Silkscreen at small rem values gets illegible. */
+}
+html { font-size: 18px; }
+:root, :root[data-theme="light"], :root[data-theme="dark"] {
+ /* Fonts — Silkscreen everywhere */
+ --fe-body-font: 'Silkscreen', 'Press Start 2P', monospace;
+ --fe-heading-font: 'Silkscreen', 'Press Start 2P', monospace;
+ --fe-mono-font: 'Silkscreen', 'Press Start 2P', monospace;
+ --fe-code-font: 'Silkscreen', 'Press Start 2P', monospace;
+
+ /* Pixel / retro rendering */
+ --fe-heading-weight: 400;
+ --fe-body-weight: 400;
+ --fe-sidebar-weight: 400;
+ --fe-current-weight: 400;
+ --fe-font-smoothing: none;
+ --fe-moz-font-smoothing: grayscale;
+ --fe-text-rendering: geometricPrecision;
+ --fe-letter-spacing: -0.02em;
+ /* Simulate bold via text-shadow — widens strokes without changing advance.
+ --fe-heading-text-shadow applies to h1-h6, --fe-emphasis-text-shadow
+ applies to the current sidebar item. */
+ --fe-heading-text-shadow: 1px 0 0 currentColor;
+ --fe-emphasis-text-shadow: 1px 0 0 currentColor;
+
+ /* Let content fill; 900px cap leaves empty space with pixel sidebar */
+ --fe-content-max-width: none;
+
+ /* Silkscreen is wider per-glyph than proportional sans, so give the
+ sidebar a bit more room than the 25vw default. 30vw keeps the
+ sidebar from dominating while still fitting most symbols; items
+ beyond that width truncate with `…` via the rule below. */
+ --fe-sidebar-max-width: 30vw;
+
+ /* fe-code-block shadow-DOM theming (inherits custom props into shadow) */
+ --fe-code-bg: #241750;
+ --fe-code-border: #7869C4;
+ --fe-code-text: #DDD6F3;
+ --fe-code-line-number: #7869C4;
+ --fe-code-size: 0.9rem;
+ --fe-code-line-height: 1.6;
+ --fe-code-radius: 3px;
+
+ /* Syntax highlight palette */
+ --fe-hl-keyword: #FF79C6;
+ --fe-hl-type: #FFD866;
+ --fe-hl-type-interface: #F8C8DC;
+ --fe-hl-type-variant: #FFB86C;
+ --fe-hl-function: #8BE9FD;
+ --fe-hl-string: #A6E22E;
+ --fe-hl-number: #FFB86C;
+ --fe-hl-comment: #7869C4;
+ --fe-hl-operator: #DDD6F3;
+ --fe-hl-punctuation: #DDD6F3;
+}
+
+/* Match fe-web's dark-mode media-query selector so we win there too */
+@media (prefers-color-scheme: dark) {
+ :root:not([data-theme="light"]) {
+ --bg: #40318D;
+ --bg-secondary: #2B1E5C;
+ --text: #DDD6F3;
+ --text-muted: #9E90D6;
+ --border: #7869C4;
+ --accent: #FFD866;
+ --accent-hover: #FFEF8F;
+ --text-accent: #FFD866;
+ --code-bg: #241750;
+ --hl-ref-bg: rgba(255, 216, 102, 0.10);
+ --hl-def-bg: rgba(255, 216, 102, 0.22);
+ --hl-def-underline: rgba(255, 216, 102, 0.55);
+ --target-bg: color-mix(in srgb, #FFD866 15%, #2B1E5C);
+ }
+}
+
+/* Bountiful-specific UI sugar that isn't covered by fe-web's knobs.
+ Keep this section thin — everything theme-parameterizable belongs above. */
+
+/* Explicit arrow marker for current sidebar item — a11y / colorblind-safe.
+ Every row reserves an invisible spacer so state changes don't reflow; the
+ current row just turns the glyph visible. */
+.fe-nav-items a::before,
+.fe-nav-mod-name a::before {
+ content: "▸";
+ display: inline-block;
+ color: transparent;
+ margin-right: 0.35em;
+ margin-left: -0.5em;
+ width: 0.7em;
+ flex-shrink: 0;
+}
+
+.fe-nav-items li.current > a::before,
+.fe-nav-mod-name.current > a::before {
+ color: var(--accent);
+}
+
+/* Pixel-style drop shadow on page h1 (additive to --fe-heading-text-shadow) */
+article h1,
+.item-header h1 {
+ color: var(--accent);
+ text-shadow:
+ 1px 0 0 currentColor,
+ 2px 2px 0 var(--bg-secondary);
+}
+
+article h2 {
+ color: var(--accent);
+ border-bottom: 2px solid var(--border);
+ padding-bottom: 0.25rem;
+}
+
+/* Member cards: CRT-phosphor inner glow */
+.member-item {
+ border: 1px solid var(--border);
+ border-radius: 3px;
+ background: color-mix(in srgb, var(--bg-secondary) 60%, var(--bg));
+ box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--accent) 8%, transparent);
+}
+
+/* Truncate long sidebar names with `…` instead of forcing the sidebar
+ to grow indefinitely. Silkscreen's wide glyphs mean some `test_*`
+ names exceed 50vw — truncation keeps the sidebar usable; horizontal
+ scroll inside the sidebar (overflow-x: auto from fe-web) remains as
+ the fallback if the user wants to see the full name. */
+.fe-nav-items a {
+ min-width: 0;
+ overflow: hidden;
+ text-overflow: ellipsis;
+}
+
+.fe-nav-items a > :last-child {
+ overflow: hidden;
+ text-overflow: ellipsis;
+ min-width: 0;
+}
diff --git a/web/static/css/c64.css b/web/static/css/c64.css
index f453bd0..e69c34c 100644
--- a/web/static/css/c64.css
+++ b/web/static/css/c64.css
@@ -5,13 +5,13 @@
--c64-text: #DDD6F3;
--c64-white: #FFFFFF;
--c64-dark: #2B1E5C;
- --font: 'Press Start 2P', monospace;
+ --font: 'Silkscreen', monospace;
/* Fe web component theming */
--fe-code-bg: #2B1E5C;
--fe-code-border: #7869C4;
--fe-code-text: #C8BFE7;
- --fe-code-font: 'Press Start 2P', monospace;
+ --fe-code-font: 'Silkscreen', monospace;
--fe-code-size: 0.6rem;
--fe-code-line-height: 1.8;
--fe-code-line-number: #7869C4;
@@ -34,7 +34,7 @@
/* ── BASE ── */
html {
- font-size: 14px;
+ font-size: 18px;
}
body {
@@ -42,8 +42,35 @@ body {
font-family: var(--font);
color: var(--c64-text);
line-height: 1.8;
+ letter-spacing: -0.06em;
height: 100vh;
overflow: hidden;
+ -webkit-font-smoothing: none;
+ -moz-osx-font-smoothing: grayscale;
+ font-smooth: never;
+ text-rendering: geometricPrecision;
+ /* Blend scrollbars with the C64 palette */
+ scrollbar-width: thin;
+ scrollbar-color: var(--c64-border) var(--c64-bg);
+}
+
+/* Webkit scrollbars — match the CRT palette */
+::-webkit-scrollbar {
+ width: 8px;
+ height: 8px;
+}
+::-webkit-scrollbar-track {
+ background: var(--c64-bg);
+}
+::-webkit-scrollbar-thumb {
+ background: var(--c64-border);
+ border-radius: 2px;
+}
+::-webkit-scrollbar-thumb:hover {
+ background: var(--c64-text);
+}
+::-webkit-scrollbar-corner {
+ background: var(--c64-bg);
}
/* ── CRT BORDER / FRAME ── */
@@ -92,6 +119,7 @@ body {
.c64-header .stars {
color: var(--c64-white);
+ font-family: 'Press Start 2P', monospace;
font-size: 1rem;
margin-bottom: 4px;
}
@@ -228,44 +256,6 @@ ol li::before {
flex-direction: column;
}
-/* ── DOC DETAILS (fields & impls in source viewer) ── */
-.doc-fields,
-.doc-impl {
- margin-top: 12px;
- border-top: 1px solid var(--c64-dark);
- padding-top: 8px;
-}
-
-.doc-field,
-.doc-method {
- padding: 6px 8px;
- margin-bottom: 4px;
- border-left: 2px solid var(--c64-border);
-}
-
-.doc-sig {
- font-family: var(--font);
- font-size: 0.65rem;
- color: var(--c64-text);
- word-break: break-all;
-}
-
-.doc-sig .sig-link {
- color: var(--c64-white);
- text-decoration: none;
-}
-
-.doc-sig .sig-link:hover {
- text-decoration: underline;
-}
-
-.doc-desc {
- font-size: 0.65rem;
- margin-top: 4px;
- margin-bottom: 0;
- opacity: 0.7;
-}
-
/* ── GLOBAL FOOTER ── */
.c64-footer {
display: flex;
@@ -557,8 +547,8 @@ ol li::before {
display: block;
}
-/* ── RESPONSIVE: single column on small screens ── */
-@media (max-width: 768px) {
+/* ── RESPONSIVE: single column on narrow viewports ── */
+@media (max-width: 1024px) {
body {
height: auto;
overflow: auto;
@@ -586,7 +576,7 @@ ol li::before {
}
html {
- font-size: 12px;
+ font-size: 22px;
}
.puzzle-board {
diff --git a/web/templates/base.html b/web/templates/base.html
index b55f321..eb55619 100644
--- a/web/templates/base.html
+++ b/web/templates/base.html
@@ -7,7 +7,7 @@
-
+