@brandon_m_behring/book-scaffold-astro 4.26.0 → 4.26.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { AstroUserConfig, AstroIntegration, MarkdownHeading } from 'astro';
2
- import { c as BookConfigOptions, f as BookScaffoldIntegrationOptions, h as ChaptersRenderer, l as Style } from './types-BO5qyDqT.js';
3
- export { B as BOOK_PRESETS, a as BOOK_PROFILES, b as BookConfigError, d as BookPreset, e as BookProfile, g as BookSchemasOptions, C as ChapterFor, F as FreshnessAffordance, i as FrontmatterRouteConfig, P as PartKey, j as PartialRouteToggles, k as ProfileDefinition, R as RouteToggles, S as StatusBadge, m as StyleInput, V as VolatilityBadge, n as composeStyles, o as defineProfile, p as defineStyle, q as normalizeFrontmatterConfig, r as resolvePreset, s as resolveProfile } from './types-BO5qyDqT.js';
2
+ import { c as BookConfigOptions, f as BookScaffoldIntegrationOptions, h as ChaptersRenderer, l as Style } from './types-Hue-uSeQ.js';
3
+ export { B as BOOK_PRESETS, a as BOOK_PROFILES, b as BookConfigError, d as BookPreset, e as BookProfile, g as BookSchemasOptions, C as ChapterFor, F as FreshnessAffordance, i as FrontmatterRouteConfig, P as PartKey, j as PartialRouteToggles, k as ProfileDefinition, R as RouteToggles, S as StatusBadge, m as StyleInput, V as VolatilityBadge, n as composeStyles, o as defineProfile, p as defineStyle, q as normalizeFrontmatterConfig, r as resolvePreset, s as resolveProfile } from './types-Hue-uSeQ.js';
4
4
  import { E as volatilityLevels, c as academicParts, Q as Question } from './schemas-CKipJ5Ie.js';
5
5
  export { A as AcademicChapter, B as BloomLevel, C as CourseNotesChapter, G as GlossaryTerm, M as MinimalChapter, P as Provenance, a as QuestionType, R as ResearchPortfolioChapter, T as ToolsChapter, b as academicChapterSchema, d as bloomLevels, e as changeKinds, f as changelogSchema, g as chapterStatus, h as citationBackstops, i as courseNotesChapterSchema, j as glossarySchema, l as layoutModes, m as minimalChapterSchema, p as patternCategories, k as patternsSchema, n as provenanceObject, o as provenanceSchema, q as questionDifficulties, r as questionSchema, s as questionTypes, t as refineQuestion, u as refinedQuestionSchema, v as researchPortfolioChapterSchema, w as sourceTiers, x as sourceTiersResearch, y as sourcesSchema, z as toolSlugs, D as toolsChapterSchema } from './schemas-CKipJ5Ie.js';
6
6
  export { KIND_LABEL, ResolvedTheoremLabel, THEOREM_KINDS, TheoremKind, TheoremLabelProps, resolveTheoremNumber, theoremLabel } from './lib/theorem-label.js';
@@ -144,18 +144,31 @@ declare const academicChaptersRenderer: ChaptersRenderer;
144
144
 
145
145
  /**
146
146
  * src/profiles/renderers/fallback-chapters.ts — ChaptersRenderer used by
147
- * profiles that don't ship a dedicated renderer (minimal, course-notes,
148
- * research-portfolio). Dispatches by field presence — exactly the v3.5.2
147
+ * profiles that don't ship a dedicated renderer (minimal, course-notes).
148
+ * Dispatches by field presence — exactly the v3.5.2
149
149
  * logic that lived inline in pages/chapters.astro before #35.
150
150
  *
151
151
  * Safety net for shapes we haven't designed for explicitly. If a consumer
152
- * opts a course-notes or research-portfolio book into `routes.chapters: true`,
152
+ * opts a course-notes book into `routes.chapters: true`,
153
153
  * the fallback renders reasonably without crashing. Custom output for those
154
154
  * profiles is a v4+ extension point (consumer-overridable renderer).
155
155
  */
156
156
 
157
157
  declare const fallbackChaptersRenderer: ChaptersRenderer;
158
158
 
159
+ /**
160
+ * Research-portfolio /chapters renderer.
161
+ *
162
+ * The portfolio schema intentionally permits numeric Parts through 20. The
163
+ * generic fallback renderer inherits the tools profile's historical
164
+ * `part >= 6` appendix convention, which mislabels a portfolio's sixth Part
165
+ * as "Appendices" and its chapters as Appendix a/b/.... Keep the fallback's
166
+ * field-shape dispatch for every other affordance, but make numeric Parts
167
+ * ordinary numbered Parts throughout the portfolio schema's full range.
168
+ */
169
+
170
+ declare const researchPortfolioChaptersRenderer: ChaptersRenderer;
171
+
159
172
  /**
160
173
  * src/lib/academic-parts.ts — single source of truth for academic-profile
161
174
  * part labels (#95).
@@ -598,4 +611,4 @@ type TipsConfigInput = Omit<TipsConfig, typeof TipsConfigBrand | '__tipsConfigVe
598
611
  */
599
612
  declare function defineTips(opts: TipsConfigInput): TipsConfig;
600
613
 
601
- export { ACADEMIC_PART_NAMES, BRANDON_PORTFOLIO_DEFAULT, BUILTIN_STYLES, BookConfigOptions, BookScaffoldIntegrationOptions, type ChapterLike, ChaptersRenderer, DEFAULT_GITHUB_BRANCH, type Freshness, type FreshnessStatus, type PartReviewGroup, type PartReviewSelection, Question, type ReviewChapter, type ReviewExercise, Style, type TipsConfig, type TipsConfigInput, UNKNOWN_PART_ORDINAL, type VisibleHeading, type VolatilityLevel, academicChaptersRenderer, academicPartHeading, academicPartName, academicPartOrdinal, academicParts, academicStyle, apparatusHref, assertEnumProp, assertKnownDomain, bookOf, bookScaffoldIntegration, buildGithubUrl, chapterHref, chapterLabel, chapterSortKey, courseNotesStyle, defineBookConfig, defineMdxComponents, defineTips, deriveObjectiveMap, distinctChaptersSorted, fallbackChaptersRenderer, freshnessLabel, getFreshness, groupByChapter, groupByDomain, isCurrentChapter, minimalStyle, originUrlFromGitConfig, parseRepoSlug, pickActive, researchPortfolioStyle, resolveBookHref, resolveGithubRepo, selectPartExercises, slugOf, sortQuestions, tocHeadings, toolsChaptersRenderer, toolsStyle, volatilityLevels };
614
+ export { ACADEMIC_PART_NAMES, BRANDON_PORTFOLIO_DEFAULT, BUILTIN_STYLES, BookConfigOptions, BookScaffoldIntegrationOptions, type ChapterLike, ChaptersRenderer, DEFAULT_GITHUB_BRANCH, type Freshness, type FreshnessStatus, type PartReviewGroup, type PartReviewSelection, Question, type ReviewChapter, type ReviewExercise, Style, type TipsConfig, type TipsConfigInput, UNKNOWN_PART_ORDINAL, type VisibleHeading, type VolatilityLevel, academicChaptersRenderer, academicPartHeading, academicPartName, academicPartOrdinal, academicParts, academicStyle, apparatusHref, assertEnumProp, assertKnownDomain, bookOf, bookScaffoldIntegration, buildGithubUrl, chapterHref, chapterLabel, chapterSortKey, courseNotesStyle, defineBookConfig, defineMdxComponents, defineTips, deriveObjectiveMap, distinctChaptersSorted, fallbackChaptersRenderer, freshnessLabel, getFreshness, groupByChapter, groupByDomain, isCurrentChapter, minimalStyle, originUrlFromGitConfig, parseRepoSlug, pickActive, researchPortfolioChaptersRenderer, researchPortfolioStyle, resolveBookHref, resolveGithubRepo, selectPartExercises, slugOf, sortQuestions, tocHeadings, toolsChaptersRenderer, toolsStyle, volatilityLevels };
package/dist/index.mjs CHANGED
@@ -851,6 +851,17 @@ var courseNotesProfile = defineProfile({
851
851
  sitemapFilter: (page) => !page.includes("/print/")
852
852
  });
853
853
 
854
+ // src/profiles/renderers/research-portfolio-chapters.ts
855
+ var researchPortfolioChaptersRenderer = {
856
+ ...fallbackChaptersRenderer,
857
+ formatPartLabel(part) {
858
+ return typeof part === "number" ? `Part ${part}` : fallbackChaptersRenderer.formatPartLabel(part);
859
+ },
860
+ isAppendix(_part) {
861
+ return false;
862
+ }
863
+ };
864
+
854
865
  // src/profiles/research-portfolio.ts
855
866
  var researchPortfolioProfile = defineProfile({
856
867
  name: "research-portfolio",
@@ -883,8 +894,9 @@ var researchPortfolioProfile = defineProfile({
883
894
  styles: ["tokens.css", "layout.css", "callouts.css", "chapter.css", "typography.css", "print.css", "section-map.css"],
884
895
  katex: true,
885
896
  // math is common in research content
886
- // v3.7.0 (#35): portfolio schema is a union of academic + tools shapes fallback renderer dispatches per chapter via field presence
887
- chaptersRenderer: fallbackChaptersRenderer
897
+ // Portfolio schema is a union of academic + tools shapes, but unlike the
898
+ // tools profile its numeric Parts remain numbered through the schema max 20.
899
+ chaptersRenderer: researchPortfolioChaptersRenderer
888
900
  });
889
901
 
890
902
  // src/profiles/index.ts
@@ -1199,6 +1211,7 @@ function bookScaffoldIntegration(opts) {
1199
1211
  // v4.5.0: landing-page data, propagated via virtual module to /index.astro.
1200
1212
  title,
1201
1213
  subtitle,
1214
+ releaseStatus,
1202
1215
  description,
1203
1216
  portfolio,
1204
1217
  // v4.6.0: book-level author + SEO config, propagated through the
@@ -1268,6 +1281,7 @@ function bookScaffoldIntegration(opts) {
1268
1281
  makeBookConfigVitePlugin({
1269
1282
  title: title ?? null,
1270
1283
  subtitle: subtitle ?? null,
1284
+ releaseStatus: releaseStatus ?? null,
1271
1285
  description: description ?? null,
1272
1286
  portfolio: portfolio ?? false,
1273
1287
  enabledRoutes: enabledRouteNames,
@@ -1413,6 +1427,8 @@ async function defineBookConfig(opts) {
1413
1427
  title: opts.title,
1414
1428
  // v4.23.0 (#135): sidebar brand subtitle.
1415
1429
  subtitle: opts.subtitle,
1430
+ // v4.27.0 (#149): release-state banner (top-level beats the style chain).
1431
+ releaseStatus: opts.releaseStatus ?? composed.releaseStatus,
1416
1432
  description: opts.description,
1417
1433
  portfolio: resolvedPortfolio,
1418
1434
  // v4.6.0: book-level author + SEO config (ogImage, twitterHandle),
@@ -1470,6 +1486,8 @@ async function defineBookConfig(opts) {
1470
1486
  // v4.5.0: strip new landing-related opts so they don't leak into AstroUserConfig.
1471
1487
  title: _title,
1472
1488
  subtitle: _subtitle,
1489
+ // v4.27.0 (#149): strip the release-state banner config.
1490
+ releaseStatus: _releaseStatus,
1473
1491
  description: _description,
1474
1492
  portfolio: _portfolio,
1475
1493
  // v4.6.0: strip new book-level SEO opts (author + seo block).
@@ -1500,6 +1518,7 @@ async function defineBookConfig(opts) {
1500
1518
  void _katexMacros;
1501
1519
  void _title;
1502
1520
  void _subtitle;
1521
+ void _releaseStatus;
1503
1522
  void _description;
1504
1523
  void _portfolio;
1505
1524
  void _author;
@@ -1953,6 +1972,7 @@ export {
1953
1972
  refineQuestion,
1954
1973
  refinedQuestionSchema,
1955
1974
  researchPortfolioChapterSchema,
1975
+ researchPortfolioChaptersRenderer,
1956
1976
  researchPortfolioStyle,
1957
1977
  resolveBookHref,
1958
1978
  resolveGithubRepo,
package/dist/schemas.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { defineCollection } from 'astro:content';
2
- import { g as BookSchemasOptions } from './types-BO5qyDqT.js';
2
+ import { g as BookSchemasOptions } from './types-Hue-uSeQ.js';
3
3
  import 'astro';
4
4
  import './schemas-CKipJ5Ie.js';
5
5
  import 'astro/zod';
package/dist/schemas.mjs CHANGED
@@ -729,6 +729,17 @@ var courseNotesProfile = defineProfile({
729
729
  sitemapFilter: (page) => !page.includes("/print/")
730
730
  });
731
731
 
732
+ // src/profiles/renderers/research-portfolio-chapters.ts
733
+ var researchPortfolioChaptersRenderer = {
734
+ ...fallbackChaptersRenderer,
735
+ formatPartLabel(part) {
736
+ return typeof part === "number" ? `Part ${part}` : fallbackChaptersRenderer.formatPartLabel(part);
737
+ },
738
+ isAppendix(_part) {
739
+ return false;
740
+ }
741
+ };
742
+
732
743
  // src/profiles/research-portfolio.ts
733
744
  var researchPortfolioProfile = defineProfile({
734
745
  name: "research-portfolio",
@@ -761,8 +772,9 @@ var researchPortfolioProfile = defineProfile({
761
772
  styles: ["tokens.css", "layout.css", "callouts.css", "chapter.css", "typography.css", "print.css", "section-map.css"],
762
773
  katex: true,
763
774
  // math is common in research content
764
- // v3.7.0 (#35): portfolio schema is a union of academic + tools shapes fallback renderer dispatches per chapter via field presence
765
- chaptersRenderer: fallbackChaptersRenderer
775
+ // Portfolio schema is a union of academic + tools shapes, but unlike the
776
+ // tools profile its numeric Parts remain numbered through the schema max 20.
777
+ chaptersRenderer: researchPortfolioChaptersRenderer
766
778
  });
767
779
 
768
780
  // src/profiles/index.ts
@@ -379,6 +379,12 @@ interface Style {
379
379
  * - `'pages'`: Cloudflare Pages (default for research-portfolio/course-notes)
380
380
  * Closes #50. */
381
381
  readonly deploy?: 'pages' | 'workers';
382
+ /** v4.27.0 (#149): release-state banner; shallow override (last wins). */
383
+ readonly releaseStatus?: {
384
+ state: 'alpha' | 'beta' | 'rc' | 'locked';
385
+ dismissAt?: string;
386
+ message?: string;
387
+ };
382
388
  /**
383
389
  * Scoped consumer-side metadata. Ignored by the toolkit; survives composition
384
390
  * as per-key spread (last wins per key). Use this for workflow data that
@@ -600,6 +606,17 @@ interface BookConfigOptions {
600
606
  * hardcoded both strings, so every consumer shipped the placeholder).
601
607
  */
602
608
  subtitle?: string;
609
+ /**
610
+ * v4.27.0 (#149): book-level release state. When set, Base.astro renders
611
+ * the existing <PreReleaseBanner> site-wide (top of <body>) with these
612
+ * props — previously the banner was author-import-only, so auto-layout
613
+ * consumers (ssm-foundations) had no way to wire it. Omit for no banner.
614
+ */
615
+ releaseStatus?: {
616
+ state: 'alpha' | 'beta' | 'rc' | 'locked';
617
+ dismissAt?: string;
618
+ message?: string;
619
+ };
603
620
  /**
604
621
  * v4.5.0: Book description. Read by the auto-injected `/` landing page (lead paragraph + <meta description>).
605
622
  * Optional; landing renders no description paragraph if unset.
@@ -752,6 +769,13 @@ interface BookScaffoldIntegrationOptions {
752
769
  /** v4.23.0 (#135): sidebar brand subtitle, propagated via the book-config
753
770
  * virtual module to Sidebar.astro. */
754
771
  subtitle?: string;
772
+ /** v4.27.0 (#149): release-state banner, propagated via the book-config
773
+ * virtual module; Base.astro renders <PreReleaseBanner> when set. */
774
+ releaseStatus?: {
775
+ state: 'alpha' | 'beta' | 'rc' | 'locked';
776
+ dismissAt?: string;
777
+ message?: string;
778
+ };
755
779
  /** v4.5.0: book description, propagated to `/` landing via vite.define. */
756
780
  description?: string;
757
781
  /**
@@ -59,6 +59,8 @@ import '../styles/print.css';
59
59
  import VersionSelector from '@brandon_m_behring/book-scaffold-astro/components/VersionSelector';
60
60
  import ToolFilter from '@brandon_m_behring/book-scaffold-astro/components/ToolFilter';
61
61
  import Sidebar from '../components/Sidebar.astro';
62
+ // v4.27.0 (#149): site-wide release-state banner, driven by defineBookConfig.
63
+ import PreReleaseBanner from '../components/PreReleaseBanner.astro';
62
64
  // v4.26.0 (#80): the mobile/tablet drawer reuses the same book-scoped nav source.
63
65
  import NavContent from '../components/NavContent.astro';
64
66
  // v4.6.0: SEO meta tags read book-level identity (title fallback,
@@ -165,6 +167,16 @@ const baseUrl = (import.meta.env.BASE_URL ?? '/').replace(/\/*$/, '/');
165
167
  <slot name="head" />
166
168
  </head>
167
169
  <body>
170
+ {/* v4.27.0 (#149): book-level release banner — first element in <body> so
171
+ every page (chapters, apparatus, landing) declares its state. Renders
172
+ only when defineBookConfig({ releaseStatus }) is set. */}
173
+ {bookConfig.releaseStatus && (
174
+ <PreReleaseBanner
175
+ state={bookConfig.releaseStatus.state}
176
+ dismissAt={bookConfig.releaseStatus.dismissAt}
177
+ message={bookConfig.releaseStatus.message}
178
+ />
179
+ )}
168
180
  <div class="chrome-buttons">
169
181
  {/* v4.26.0 (#80): hamburger → mobile/tablet nav drawer. <a href="#nav-drawer">
170
182
  so it opens via :target with no JS; the controller below enhances it. */}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@brandon_m_behring/book-scaffold-astro",
3
3
  "description": "Astro 6 + MDX toolkit for long-form technical books. Profile-aware (academic / tools / minimal); ships Tufte typography, KaTeX, BibTeX citations, Pagefind, Cloudflare Workers deploy. See PACKAGE_DESIGN.md for the API contract.",
4
- "version": "4.26.0",
4
+ "version": "4.26.2",
5
5
  "type": "module",
6
6
  "license": "MIT",
7
7
  "author": "Brandon Behring",
@@ -123,6 +123,8 @@ export interface Style {
123
123
  * - `'pages'`: Cloudflare Pages (default for research-portfolio/course-notes)
124
124
  * Closes #50. */
125
125
  readonly deploy?: 'pages' | 'workers';
126
+ /** v4.27.0 (#149): release-state banner; shallow override (last wins). */
127
+ readonly releaseStatus?: { state: 'alpha' | 'beta' | 'rc' | 'locked'; dismissAt?: string; message?: string };
126
128
 
127
129
  /**
128
130
  * Scoped consumer-side metadata. Ignored by the toolkit; survives composition
@@ -1,11 +1,11 @@
1
1
  /**
2
2
  * src/profiles/renderers/fallback-chapters.ts — ChaptersRenderer used by
3
- * profiles that don't ship a dedicated renderer (minimal, course-notes,
4
- * research-portfolio). Dispatches by field presence — exactly the v3.5.2
3
+ * profiles that don't ship a dedicated renderer (minimal, course-notes).
4
+ * Dispatches by field presence — exactly the v3.5.2
5
5
  * logic that lived inline in pages/chapters.astro before #35.
6
6
  *
7
7
  * Safety net for shapes we haven't designed for explicitly. If a consumer
8
- * opts a course-notes or research-portfolio book into `routes.chapters: true`,
8
+ * opts a course-notes book into `routes.chapters: true`,
9
9
  * the fallback renders reasonably without crashing. Custom output for those
10
10
  * profiles is a v4+ extension point (consumer-overridable renderer).
11
11
  */
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Research-portfolio /chapters renderer.
3
+ *
4
+ * The portfolio schema intentionally permits numeric Parts through 20. The
5
+ * generic fallback renderer inherits the tools profile's historical
6
+ * `part >= 6` appendix convention, which mislabels a portfolio's sixth Part
7
+ * as "Appendices" and its chapters as Appendix a/b/.... Keep the fallback's
8
+ * field-shape dispatch for every other affordance, but make numeric Parts
9
+ * ordinary numbered Parts throughout the portfolio schema's full range.
10
+ */
11
+ import type { ChaptersRenderer } from '../../lib/chapters-renderer.js';
12
+ import { fallbackChaptersRenderer } from './fallback-chapters.js';
13
+
14
+ export const researchPortfolioChaptersRenderer: ChaptersRenderer = {
15
+ ...fallbackChaptersRenderer,
16
+
17
+ formatPartLabel(part) {
18
+ return typeof part === 'number'
19
+ ? `Part ${part}`
20
+ : fallbackChaptersRenderer.formatPartLabel(part);
21
+ },
22
+
23
+ isAppendix(_part) {
24
+ return false;
25
+ },
26
+ };
@@ -22,7 +22,7 @@
22
22
  */
23
23
  import { defineProfile } from '../profile-kit.js';
24
24
  import { researchPortfolioChapterSchema } from '../schemas.js';
25
- import { fallbackChaptersRenderer } from './renderers/fallback-chapters.js';
25
+ import { researchPortfolioChaptersRenderer } from './renderers/research-portfolio-chapters.js';
26
26
 
27
27
  export type { ResearchPortfolioChapter } from '../schemas.js';
28
28
 
@@ -46,6 +46,7 @@ export const researchPortfolioProfile = defineProfile({
46
46
  },
47
47
  styles: ['tokens.css', 'layout.css', 'callouts.css', 'chapter.css', 'typography.css', 'print.css', 'section-map.css'],
48
48
  katex: true, // math is common in research content
49
- // v3.7.0 (#35): portfolio schema is a union of academic + tools shapes fallback renderer dispatches per chapter via field presence
50
- chaptersRenderer: fallbackChaptersRenderer,
49
+ // Portfolio schema is a union of academic + tools shapes, but unlike the
50
+ // tools profile its numeric Parts remain numbered through the schema max 20.
51
+ chaptersRenderer: researchPortfolioChaptersRenderer,
51
52
  });
@@ -244,4 +244,15 @@
244
244
  overflow-x: auto;
245
245
  /* a little room so the horizontal scrollbar doesn't clip the equation's descenders */
246
246
  padding-block-end: 0.25rem;
247
+ /* Edge scroll-shadow when a wide equation scrolls (v4.25.3; docs/responsive-reading.md).
248
+ * Same Lea Verou layered technique as code blocks, page-bg covers. */
249
+ background-image:
250
+ linear-gradient(to right, var(--color-bg) 30%, rgba(0, 0, 0, 0)),
251
+ linear-gradient(to right, rgba(0, 0, 0, 0), var(--color-bg) 70%),
252
+ radial-gradient(farthest-side at 0 50%, rgba(0, 0, 0, 0.12), rgba(0, 0, 0, 0)),
253
+ radial-gradient(farthest-side at 100% 50%, rgba(0, 0, 0, 0.12), rgba(0, 0, 0, 0));
254
+ background-position: 0 0, 100% 0, 0 0, 100% 0;
255
+ background-repeat: no-repeat;
256
+ background-size: 30px 100%, 30px 100%, 12px 100%, 12px 100%;
257
+ background-attachment: local, local, scroll, scroll;
247
258
  }
package/styles/tokens.css CHANGED
@@ -108,6 +108,7 @@
108
108
  */
109
109
  --measure-main: 60ch; /* default: main+gutter fits beside the sidebar at ≥64rem (v4.26.0, #80) */
110
110
  --measure-side: 20ch; /* right-margin sidenote / section-map column */
111
+ --measure-code: 48rem; /* code-block break-out width: fits 80-char lines comfortably (≈88 mono chars @14px before padding); docs/responsive-reading.md */
111
112
  --breakpoint-narrow: 48rem; /* ~768px — mobile fold (sidenotes inline) */
112
113
  --breakpoint-wide: 90rem; /* ~1440px — wide-desktop tier */
113
114
  --breakpoint-ultrawide: 120rem; /* ~1920px — ultrawide tier */
@@ -89,6 +89,43 @@ pre code {
89
89
  font-size: var(--text-sm);
90
90
  }
91
91
 
92
+ /* ===== Responsive reading (v4.25.3) — docs/responsive-reading.md =====
93
+ * Code blocks break out past the prose text measure to fit ~80-char lines,
94
+ * capped at --measure-code and centered within .prose. CONTAINER-bounded, not
95
+ * viewport-bounded: the width comes from the .prose column itself, so a left
96
+ * sidebar offset + the scrollbar gutter can never push the block into horizontal
97
+ * PAGE scroll. (The earlier `width: min(100vw - …)` overflowed ~8px at the
98
+ * 1024px sidebar boundary — #171 review finding A1, guarded by
99
+ * gallery/tests/fixtures/layout-overflow.spec.ts.) `.wide`/`.column-page` keep
100
+ * their full-bleed behavior (excluded here). */
101
+ .prose > pre:not(.wide):not(.column-page) {
102
+ max-width: var(--measure-code);
103
+ margin-inline: auto;
104
+ /* Lea Verou layered scroll-shadow: bg-colored covers move with content
105
+ * (attachment:local); shadow layers are fixed → shown only when scrollable. */
106
+ background-image:
107
+ linear-gradient(to right, var(--color-code-bg) 30%, rgba(0, 0, 0, 0)),
108
+ linear-gradient(to right, rgba(0, 0, 0, 0), var(--color-code-bg) 70%),
109
+ radial-gradient(farthest-side at 0 50%, rgba(0, 0, 0, 0.18), rgba(0, 0, 0, 0)),
110
+ radial-gradient(farthest-side at 100% 50%, rgba(0, 0, 0, 0.18), rgba(0, 0, 0, 0));
111
+ background-position: 0 0, 100% 0, 0 0, 100% 0;
112
+ background-repeat: no-repeat;
113
+ background-size: 40px 100%, 40px 100%, 14px 100%, 14px 100%;
114
+ background-attachment: local, local, scroll, scroll;
115
+ }
116
+ /* Phone (≤40rem): shrink code a touch so more fits before scrolling. Tighter
117
+ * than the 48rem mobile/table breakpoint below — phones benefit from the smaller
118
+ * glyphs; tablets (40–48rem) keep the full size. */
119
+ @media (max-width: 40rem) {
120
+ pre code { font-size: 0.75rem; }
121
+ }
122
+ /* Mobile: scroll a wide table within its own block instead of overflowing the
123
+ * page. Sticky thead intentionally NOT used — it can't stick inside a
124
+ * horizontal-scroll wrapper (CSS limitation; see docs/responsive-reading.md). */
125
+ @media (max-width: 48rem) {
126
+ .prose table { display: block; max-width: 100%; overflow-x: auto; }
127
+ }
128
+
92
129
  /* ===== Links ===== */
93
130
  a {
94
131
  color: var(--color-link);