@tomehq/theme 0.2.5 → 0.2.6

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.
@@ -796,14 +796,15 @@ function Shell({
796
796
  if (themeMode === "light") return false;
797
797
  return window.matchMedia?.("(prefers-color-scheme: dark)").matches ?? true;
798
798
  });
799
- const [sbOpen, setSb] = useState2(true);
799
+ const [mobile, setMobile] = useState2(() => typeof window !== "undefined" && window.innerWidth < 768);
800
+ const [sbOpen, setSb] = useState2(() => typeof window !== "undefined" && window.innerWidth >= 768);
800
801
  const [searchOpen, setSearch] = useState2(false);
801
802
  const [versionDropdownOpen, setVersionDropdown] = useState2(false);
802
803
  const [localeDropdownOpen, setLocaleDropdown] = useState2(false);
803
804
  const isOldVersion = versioning && currentVersion && currentVersion !== versioning.current;
804
805
  const [expanded, setExpanded] = useState2(navigation2.map((n) => n.section));
805
806
  const contentRef = useRef2(null);
806
- const [wide, setWide] = useState2(true);
807
+ const [wide, setWide] = useState2(() => typeof window !== "undefined" && window.innerWidth > 1100);
807
808
  const preset = config2.theme?.preset || "amber";
808
809
  const baseTokens = THEME_PRESETS[preset]?.[isDark ? "dark" : "light"] || THEME_PRESETS.amber.dark;
809
810
  const accentOverride = config2.theme?.accent ? buildAccentOverride(config2.theme.accent, isDark) : null;
@@ -825,11 +826,23 @@ function Shell({
825
826
  document.documentElement.classList.toggle("dark", isDark);
826
827
  }, [isDark]);
827
828
  useEffect2(() => {
828
- const c = () => setWide(window.innerWidth > 1100);
829
- c();
830
- window.addEventListener("resize", c);
831
- return () => window.removeEventListener("resize", c);
829
+ const handleResize = () => {
830
+ const w = window.innerWidth;
831
+ setWide(w > 1100);
832
+ setMobile(w < 768);
833
+ };
834
+ handleResize();
835
+ window.addEventListener("resize", handleResize);
836
+ return () => window.removeEventListener("resize", handleResize);
832
837
  }, []);
838
+ useEffect2(() => {
839
+ if (mobile && sbOpen) {
840
+ document.body.style.overflow = "hidden";
841
+ return () => {
842
+ document.body.style.overflow = "";
843
+ };
844
+ }
845
+ }, [mobile, sbOpen]);
833
846
  useEffect2(() => {
834
847
  contentRef.current?.scrollTo(0, 0);
835
848
  }, [currentPageId]);
@@ -944,10 +957,18 @@ function Shell({
944
957
  onNavigate(id);
945
958
  setSearch(false);
946
959
  },
947
- onClose: () => setSearch(false)
960
+ onClose: () => setSearch(false),
961
+ mobile
948
962
  }
949
963
  ) : null,
950
964
  /* @__PURE__ */ jsxs2("div", { style: { display: "flex", height: "100vh" }, children: [
965
+ mobile && sbOpen && /* @__PURE__ */ jsx2("div", { onClick: () => setSb(false), style: {
966
+ position: "fixed",
967
+ inset: 0,
968
+ zIndex: 200,
969
+ background: "rgba(0,0,0,0.4)",
970
+ backdropFilter: "blur(2px)"
971
+ } }),
951
972
  /* @__PURE__ */ jsxs2("aside", { style: {
952
973
  width: sbOpen ? 270 : 0,
953
974
  minWidth: sbOpen ? 270 : 0,
@@ -956,13 +977,17 @@ function Shell({
956
977
  display: "flex",
957
978
  flexDirection: "column",
958
979
  transition: "width .2s, min-width .2s",
959
- overflow: "hidden"
980
+ overflow: "hidden",
981
+ ...mobile ? { position: "fixed", top: 0, left: 0, bottom: 0, zIndex: 201 } : {}
960
982
  }, children: [
961
983
  /* @__PURE__ */ jsxs2("a", { href: "/", style: { padding: "18px 20px", display: "flex", alignItems: "baseline", gap: 6, borderBottom: "1px solid var(--bd)", textDecoration: "none", color: "inherit" }, children: [
962
984
  /* @__PURE__ */ jsx2("span", { style: { fontFamily: "var(--font-heading)", fontSize: 22, fontWeight: 700, fontStyle: "italic" }, children: config2.name }),
963
985
  /* @__PURE__ */ jsx2("span", { style: { width: 5, height: 5, borderRadius: "50%", background: "var(--ac)", display: "inline-block" } })
964
986
  ] }),
965
- /* @__PURE__ */ jsx2("div", { style: { padding: "12px 14px" }, children: /* @__PURE__ */ jsxs2("button", { onClick: () => setSearch(true), style: {
987
+ /* @__PURE__ */ jsx2("div", { style: { padding: "12px 14px" }, children: /* @__PURE__ */ jsxs2("button", { onClick: () => {
988
+ setSearch(true);
989
+ if (mobile) setSb(false);
990
+ }, style: {
966
991
  display: "flex",
967
992
  alignItems: "center",
968
993
  gap: 8,
@@ -1003,7 +1028,10 @@ function Shell({
1003
1028
  ] }),
1004
1029
  expanded.includes(sec.section) && /* @__PURE__ */ jsx2("div", { style: { marginLeft: 8, borderLeft: "1px solid var(--bd)", paddingLeft: 0 }, children: sec.pages.map((p) => {
1005
1030
  const active = currentPageId === p.id;
1006
- return /* @__PURE__ */ jsx2("button", { onClick: () => onNavigate(p.id), style: {
1031
+ return /* @__PURE__ */ jsx2("button", { onClick: () => {
1032
+ onNavigate(p.id);
1033
+ if (mobile) setSb(false);
1034
+ }, style: {
1007
1035
  display: "flex",
1008
1036
  alignItems: "center",
1009
1037
  gap: 10,
@@ -1024,7 +1052,7 @@ function Shell({
1024
1052
  }) })
1025
1053
  ] }, sec.section)) }),
1026
1054
  /* @__PURE__ */ jsxs2("div", { style: { padding: "12px 16px", borderTop: "1px solid var(--bd)", display: "flex", alignItems: "center", justifyContent: "space-between" }, children: [
1027
- themeMode === "auto" ? /* @__PURE__ */ jsx2("button", { onClick: () => setDark((d) => !d), style: { background: "none", border: "none", color: "var(--txM)", cursor: "pointer", display: "flex" }, children: isDark ? /* @__PURE__ */ jsx2(SunIcon, {}) : /* @__PURE__ */ jsx2(MoonIcon, {}) }) : /* @__PURE__ */ jsx2("div", {}),
1055
+ themeMode === "auto" ? /* @__PURE__ */ jsx2("button", { "aria-label": isDark ? "Switch to light mode" : "Switch to dark mode", onClick: () => setDark((d) => !d), style: { background: "none", border: "none", color: "var(--txM)", cursor: "pointer", display: "flex" }, children: isDark ? /* @__PURE__ */ jsx2(SunIcon, {}) : /* @__PURE__ */ jsx2(MoonIcon, {}) }) : /* @__PURE__ */ jsx2("div", {}),
1028
1056
  /* @__PURE__ */ jsxs2("span", { style: { fontSize: 11, color: "var(--txM)", letterSpacing: 0.2 }, children: [
1029
1057
  "Built with ",
1030
1058
  "\u2661",
@@ -1037,14 +1065,14 @@ function Shell({
1037
1065
  /* @__PURE__ */ jsxs2("header", { style: {
1038
1066
  display: "flex",
1039
1067
  alignItems: "center",
1040
- gap: 12,
1041
- padding: "10px 24px",
1068
+ gap: mobile ? 8 : 12,
1069
+ padding: mobile ? "8px 12px" : "10px 24px",
1042
1070
  borderBottom: "1px solid var(--bd)",
1043
1071
  background: "var(--hdBg)",
1044
1072
  backdropFilter: "blur(12px)"
1045
1073
  }, children: [
1046
- /* @__PURE__ */ jsx2("button", { onClick: () => setSb(!sbOpen), style: { background: "none", border: "none", color: "var(--txM)", cursor: "pointer", display: "flex" }, children: sbOpen ? /* @__PURE__ */ jsx2(XIcon, {}) : /* @__PURE__ */ jsx2(MenuIcon, {}) }),
1047
- /* @__PURE__ */ jsx2("div", { style: { display: "flex", alignItems: "center", gap: 8, fontFamily: "var(--font-code)", fontSize: 11, color: "var(--txM)", letterSpacing: ".03em", flex: 1 }, children: navigation2.map((s) => {
1074
+ /* @__PURE__ */ jsx2("button", { "aria-label": sbOpen ? "Close sidebar" : "Open sidebar", onClick: () => setSb(!sbOpen), style: { background: "none", border: "none", color: "var(--txM)", cursor: "pointer", display: "flex" }, children: sbOpen ? /* @__PURE__ */ jsx2(XIcon, {}) : /* @__PURE__ */ jsx2(MenuIcon, {}) }),
1075
+ mobile ? /* @__PURE__ */ jsx2("span", { style: { fontSize: 13, color: "var(--ac)", fontFamily: "var(--font-code)", flex: 1, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }, children: navigation2.flatMap((s) => s.pages).find((p) => p.id === currentPageId)?.title || "" }) : /* @__PURE__ */ jsx2("div", { style: { display: "flex", alignItems: "center", gap: 8, fontFamily: "var(--font-code)", fontSize: 11, color: "var(--txM)", letterSpacing: ".03em", flex: 1 }, children: navigation2.map((s) => {
1048
1076
  const f = s.pages.find((p) => p.id === currentPageId);
1049
1077
  if (!f) return null;
1050
1078
  return /* @__PURE__ */ jsxs2("span", { style: { display: "flex", alignItems: "center", gap: 8 }, children: [
@@ -1053,7 +1081,7 @@ function Shell({
1053
1081
  /* @__PURE__ */ jsx2("span", { style: { color: "var(--ac)" }, children: f.title })
1054
1082
  ] }, s.section);
1055
1083
  }) }),
1056
- config2.topNav && config2.topNav.length > 0 && /* @__PURE__ */ jsxs2("div", { style: { display: "flex", alignItems: "center", gap: 12 }, children: [
1084
+ config2.topNav && config2.topNav.length > 0 && !mobile && /* @__PURE__ */ jsxs2("div", { style: { display: "flex", alignItems: "center", gap: 12 }, children: [
1057
1085
  config2.topNav.map((link) => {
1058
1086
  const isExternal = link.href.startsWith("http") || !link.href.startsWith("#");
1059
1087
  return /* @__PURE__ */ jsxs2(
@@ -1283,8 +1311,8 @@ function Shell({
1283
1311
  }
1284
1312
  ),
1285
1313
  /* @__PURE__ */ jsxs2("div", { ref: contentRef, style: { flex: 1, overflow: "auto", display: "flex" }, children: [
1286
- /* @__PURE__ */ jsxs2("main", { style: { flex: 1, maxWidth: 760, padding: "40px 48px 80px", margin: "0 auto" }, children: [
1287
- /* @__PURE__ */ jsx2("h1", { style: { fontFamily: "var(--font-heading)", fontSize: 38, fontWeight: 400, fontStyle: "italic", lineHeight: 1.15, marginBottom: 8 }, children: pageTitle }),
1314
+ /* @__PURE__ */ jsxs2("main", { style: { flex: 1, maxWidth: mobile ? "100%" : 760, padding: mobile ? "24px 16px 60px" : "40px 48px 80px", margin: "0 auto", minWidth: 0 }, children: [
1315
+ /* @__PURE__ */ jsx2("h1", { style: { fontFamily: "var(--font-heading)", fontSize: mobile ? 26 : 38, fontWeight: 400, fontStyle: "italic", lineHeight: 1.15, marginBottom: 8 }, children: pageTitle }),
1288
1316
  pageDescription && /* @__PURE__ */ jsx2("p", { style: { fontSize: 16, color: "var(--tx2)", lineHeight: 1.6, marginBottom: 32 }, children: pageDescription }),
1289
1317
  /* @__PURE__ */ jsx2("div", { style: { borderTop: "1px solid var(--bd)", paddingTop: 28 }, children: changelogEntries && changelogEntries.length > 0 ? /* @__PURE__ */ jsx2(ChangelogView, { entries: changelogEntries }) : PageComponent ? /* @__PURE__ */ jsx2("div", { className: "tome-content", children: /* @__PURE__ */ jsx2(PageComponent, { components: mdxComponents || {} }) }) : /* @__PURE__ */ jsx2(
1290
1318
  "div",
@@ -1293,7 +1321,7 @@ function Shell({
1293
1321
  dangerouslySetInnerHTML: { __html: (pageHtml || "").replace(/<h1[^>]*>[\s\S]*?<\/h1>\s*/, "") }
1294
1322
  }
1295
1323
  ) }),
1296
- (editUrl || lastUpdated) && /* @__PURE__ */ jsxs2("div", { style: { marginTop: 40, display: "flex", alignItems: "center", justifyContent: "space-between", gap: 16 }, children: [
1324
+ (editUrl || lastUpdated) && /* @__PURE__ */ jsxs2("div", { style: { marginTop: 40, display: "flex", flexDirection: mobile ? "column" : "row", alignItems: mobile ? "flex-start" : "center", justifyContent: "space-between", gap: mobile ? 8 : 16 }, children: [
1297
1325
  editUrl && /* @__PURE__ */ jsx2("div", { "data-testid": "edit-page-link", children: /* @__PURE__ */ jsxs2(
1298
1326
  "a",
1299
1327
  {
@@ -1323,7 +1351,7 @@ function Shell({
1323
1351
  formatRelativeDate(lastUpdated)
1324
1352
  ] })
1325
1353
  ] }),
1326
- /* @__PURE__ */ jsxs2("div", { style: { display: "flex", justifyContent: "space-between", marginTop: editUrl || lastUpdated ? 16 : 48, paddingTop: 24, borderTop: "1px solid var(--bd)", gap: 16 }, children: [
1354
+ /* @__PURE__ */ jsxs2("div", { style: { display: "flex", flexDirection: mobile ? "column" : "row", justifyContent: "space-between", marginTop: editUrl || lastUpdated ? 16 : 48, paddingTop: 24, borderTop: "1px solid var(--bd)", gap: mobile ? 12 : 16 }, children: [
1327
1355
  prev ? /* @__PURE__ */ jsxs2("button", { onClick: () => onNavigate(prev.id), style: {
1328
1356
  display: "flex",
1329
1357
  alignItems: "center",
@@ -1406,7 +1434,7 @@ ${d.content}`).join("\n\n") ?? allPages.map((p) => `- ${p.title}${p.description
1406
1434
  )
1407
1435
  ] });
1408
1436
  }
1409
- function SearchModal({ allPages, onNavigate, onClose }) {
1437
+ function SearchModal({ allPages, onNavigate, onClose, mobile }) {
1410
1438
  const [q, setQ] = useState2("");
1411
1439
  const [results, setResults] = useState2([]);
1412
1440
  const [selected, setSelected] = useState2(0);
@@ -1478,17 +1506,20 @@ function SearchModal({ allPages, onNavigate, onClose }) {
1478
1506
  background: "rgba(0,0,0,0.55)",
1479
1507
  backdropFilter: "blur(6px)",
1480
1508
  display: "flex",
1481
- alignItems: "flex-start",
1509
+ alignItems: mobile ? "stretch" : "flex-start",
1482
1510
  justifyContent: "center",
1483
- paddingTop: "12vh"
1511
+ paddingTop: mobile ? 0 : "12vh"
1484
1512
  }, children: /* @__PURE__ */ jsxs2("div", { onClick: (e) => e.stopPropagation(), style: {
1485
1513
  background: "var(--sf)",
1486
- border: "1px solid var(--bd)",
1487
- borderRadius: 2,
1514
+ border: mobile ? "none" : "1px solid var(--bd)",
1515
+ borderRadius: mobile ? 0 : 2,
1488
1516
  width: "100%",
1489
- maxWidth: 520,
1490
- boxShadow: "0 24px 80px rgba(0,0,0,0.4)",
1491
- overflow: "hidden"
1517
+ maxWidth: mobile ? "100%" : 520,
1518
+ boxShadow: mobile ? "none" : "0 24px 80px rgba(0,0,0,0.4)",
1519
+ overflow: "hidden",
1520
+ display: "flex",
1521
+ flexDirection: "column",
1522
+ ...mobile ? { height: "100%" } : {}
1492
1523
  }, children: [
1493
1524
  /* @__PURE__ */ jsxs2("div", { style: { display: "flex", alignItems: "center", gap: 10, padding: "14px 18px", borderBottom: "1px solid var(--bd)" }, children: [
1494
1525
  /* @__PURE__ */ jsx2(SearchIcon, {}),
@@ -1505,7 +1536,7 @@ function SearchModal({ allPages, onNavigate, onClose }) {
1505
1536
  ),
1506
1537
  /* @__PURE__ */ jsx2("kbd", { style: { fontFamily: "var(--font-code)", fontSize: 10, color: "var(--txM)", background: "var(--cdBg)", padding: "2px 6px", borderRadius: 2, border: "1px solid var(--bd)" }, children: "ESC" })
1507
1538
  ] }),
1508
- results.length > 0 && /* @__PURE__ */ jsx2("div", { style: { padding: 6, maxHeight: 360, overflow: "auto" }, children: results.map((r, i) => /* @__PURE__ */ jsxs2(
1539
+ results.length > 0 && /* @__PURE__ */ jsx2("div", { style: { padding: 6, maxHeight: mobile ? "none" : 360, overflow: "auto", flex: mobile ? 1 : void 0 }, children: results.map((r, i) => /* @__PURE__ */ jsxs2(
1509
1540
  "button",
1510
1541
  {
1511
1542
  onClick: () => onNavigate(r.id),
@@ -1587,6 +1618,15 @@ var contentStyles = `
1587
1618
  .tome-content img { max-width: 100%; border-radius: 2px; }
1588
1619
  .tome-content hr { border: none; border-top: 1px solid var(--bd); margin: 2em 0; }
1589
1620
 
1621
+ /* Mobile responsive content */
1622
+ @media (max-width: 767px) {
1623
+ .tome-content h2 { font-size: 1.2em; margin-top: 1.5em; }
1624
+ .tome-content h3 { font-size: 1.05em; }
1625
+ .tome-content pre code { font-size: 12px; padding: 0.8em 1em; }
1626
+ .tome-content table { display: block; overflow-x: auto; -webkit-overflow-scrolling: touch; }
1627
+ .tome-content blockquote { margin: 0.8em 0; }
1628
+ }
1629
+
1590
1630
  /* Selection style */
1591
1631
  ::selection { background: var(--acD); color: var(--ac); }
1592
1632
 
package/dist/entry.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  entry_default
3
- } from "./chunk-7MUTU5D4.js";
3
+ } from "./chunk-YZ3P3TNS.js";
4
4
  export {
5
5
  entry_default as default
6
6
  };
package/dist/index.js CHANGED
@@ -3,7 +3,7 @@ import {
3
3
  Shell,
4
4
  THEME_PRESETS,
5
5
  entry_default
6
- } from "./chunk-7MUTU5D4.js";
6
+ } from "./chunk-YZ3P3TNS.js";
7
7
  export {
8
8
  AiChat,
9
9
  entry_default as App,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tomehq/theme",
3
- "version": "0.2.5",
3
+ "version": "0.2.6",
4
4
  "description": "Tome default theme and React app shell",
5
5
  "type": "module",
6
6
  "main": "./src/index.tsx",
@@ -9,8 +9,8 @@
9
9
  "./entry": "./src/entry.tsx"
10
10
  },
11
11
  "dependencies": {
12
- "@tomehq/components": "0.2.5",
13
- "@tomehq/core": "0.2.5"
12
+ "@tomehq/components": "0.2.6",
13
+ "@tomehq/core": "0.2.6"
14
14
  },
15
15
  "peerDependencies": {
16
16
  "react": "^18.0.0 || ^19.0.0",
package/src/Shell.tsx CHANGED
@@ -331,7 +331,8 @@ export function Shell({
331
331
  return window.matchMedia?.("(prefers-color-scheme: dark)").matches ?? true;
332
332
  });
333
333
 
334
- const [sbOpen, setSb] = useState(true);
334
+ const [mobile, setMobile] = useState(() => typeof window !== "undefined" && window.innerWidth < 768);
335
+ const [sbOpen, setSb] = useState(() => typeof window !== "undefined" && window.innerWidth >= 768);
335
336
  const [searchOpen, setSearch] = useState(false);
336
337
  const [versionDropdownOpen, setVersionDropdown] = useState(false);
337
338
  const [localeDropdownOpen, setLocaleDropdown] = useState(false);
@@ -340,7 +341,7 @@ export function Shell({
340
341
  const isOldVersion = versioning && currentVersion && currentVersion !== versioning.current;
341
342
  const [expanded, setExpanded] = useState<string[]>(navigation.map(n => n.section));
342
343
  const contentRef = useRef<HTMLDivElement>(null);
343
- const [wide, setWide] = useState(true);
344
+ const [wide, setWide] = useState(() => typeof window !== "undefined" && window.innerWidth > 1100);
344
345
 
345
346
  const preset = (config.theme?.preset || "amber") as PresetName;
346
347
  const baseTokens = THEME_PRESETS[preset]?.[isDark ? "dark" : "light"] || THEME_PRESETS.amber.dark;
@@ -377,11 +378,24 @@ export function Shell({
377
378
  }, [isDark]);
378
379
 
379
380
  useEffect(() => {
380
- const c = () => setWide(window.innerWidth > 1100);
381
- c(); window.addEventListener("resize", c);
382
- return () => window.removeEventListener("resize", c);
381
+ const handleResize = () => {
382
+ const w = window.innerWidth;
383
+ setWide(w > 1100);
384
+ setMobile(w < 768);
385
+ };
386
+ handleResize();
387
+ window.addEventListener("resize", handleResize);
388
+ return () => window.removeEventListener("resize", handleResize);
383
389
  }, []);
384
390
 
391
+ // Lock body scroll when mobile sidebar is open
392
+ useEffect(() => {
393
+ if (mobile && sbOpen) {
394
+ document.body.style.overflow = "hidden";
395
+ return () => { document.body.style.overflow = ""; };
396
+ }
397
+ }, [mobile, sbOpen]);
398
+
385
399
  useEffect(() => { contentRef.current?.scrollTo(0, 0); }, [currentPageId]);
386
400
 
387
401
  // ── TOC: Config-based depth filtering + frontmatter opt-out ──
@@ -500,16 +514,25 @@ export function Shell({
500
514
  allPages={allPages}
501
515
  onNavigate={(id) => { onNavigate(id); setSearch(false); }}
502
516
  onClose={() => setSearch(false)}
517
+ mobile={mobile}
503
518
  />
504
519
  ) : null}
505
520
 
506
521
  <div style={{ display: "flex", height: "100vh" }}>
522
+ {/* Mobile sidebar backdrop */}
523
+ {mobile && sbOpen && (
524
+ <div onClick={() => setSb(false)} style={{
525
+ position: "fixed", inset: 0, zIndex: 200,
526
+ background: "rgba(0,0,0,0.4)", backdropFilter: "blur(2px)",
527
+ }} />
528
+ )}
507
529
  {/* Sidebar */}
508
530
  <aside style={{
509
531
  width: sbOpen ? 270 : 0, minWidth: sbOpen ? 270 : 0,
510
532
  background: "var(--sbBg)", borderRight: "1px solid var(--bd)",
511
533
  display: "flex", flexDirection: "column",
512
534
  transition: "width .2s, min-width .2s", overflow: "hidden",
535
+ ...(mobile ? { position: "fixed" as const, top: 0, left: 0, bottom: 0, zIndex: 201 } : {}),
513
536
  }}>
514
537
  <a href="/" style={{ padding: "18px 20px", display: "flex", alignItems: "baseline", gap: 6, borderBottom: "1px solid var(--bd)", textDecoration: "none", color: "inherit" }}>
515
538
  <span style={{ fontFamily: "var(--font-heading)", fontSize: 22, fontWeight: 700, fontStyle: "italic" }}>
@@ -519,7 +542,7 @@ export function Shell({
519
542
  </a>
520
543
 
521
544
  <div style={{ padding: "12px 14px" }}>
522
- <button onClick={() => setSearch(true)} style={{
545
+ <button onClick={() => { setSearch(true); if (mobile) setSb(false); }} style={{
523
546
  display: "flex", alignItems: "center", gap: 8, width: "100%",
524
547
  background: "var(--cdBg)", border: "1px solid var(--bd)", borderRadius: 2,
525
548
  padding: "8px 12px", cursor: "pointer", color: "var(--txM)", fontSize: 12.5,
@@ -545,7 +568,7 @@ export function Shell({
545
568
  {sec.pages.map(p => {
546
569
  const active = currentPageId === p.id;
547
570
  return (
548
- <button key={p.id} onClick={() => onNavigate(p.id)} style={{
571
+ <button key={p.id} onClick={() => { onNavigate(p.id); if (mobile) setSb(false); }} style={{
549
572
  display: "flex", alignItems: "center", gap: 10, width: "100%",
550
573
  textAlign: "left", background: "none",
551
574
  border: "none", borderRadius: 0,
@@ -567,7 +590,7 @@ export function Shell({
567
590
  <div style={{ padding: "12px 16px", borderTop: "1px solid var(--bd)", display: "flex", alignItems: "center", justifyContent: "space-between" }}>
568
591
  {/* TOM-12: Only show toggle when mode is "auto" */}
569
592
  {themeMode === "auto" ? (
570
- <button onClick={() => setDark(d => !d)} style={{ background: "none", border: "none", color: "var(--txM)", cursor: "pointer", display: "flex" }}>
593
+ <button aria-label={isDark ? "Switch to light mode" : "Switch to dark mode"} onClick={() => setDark(d => !d)} style={{ background: "none", border: "none", color: "var(--txM)", cursor: "pointer", display: "flex" }}>
571
594
  {isDark ? <SunIcon /> : <MoonIcon />}
572
595
  </button>
573
596
  ) : <div />}
@@ -580,24 +603,30 @@ export function Shell({
580
603
  <div style={{ flex: 1, display: "flex", flexDirection: "column", overflow: "hidden" }}>
581
604
  {/* Header */}
582
605
  <header style={{
583
- display: "flex", alignItems: "center", gap: 12, padding: "10px 24px",
606
+ display: "flex", alignItems: "center", gap: mobile ? 8 : 12, padding: mobile ? "8px 12px" : "10px 24px",
584
607
  borderBottom: "1px solid var(--bd)", background: "var(--hdBg)", backdropFilter: "blur(12px)",
585
608
  }}>
586
- <button onClick={() => setSb(!sbOpen)} style={{ background: "none", border: "none", color: "var(--txM)", cursor: "pointer", display: "flex" }}>
609
+ <button aria-label={sbOpen ? "Close sidebar" : "Open sidebar"} onClick={() => setSb(!sbOpen)} style={{ background: "none", border: "none", color: "var(--txM)", cursor: "pointer", display: "flex" }}>
587
610
  {sbOpen ? <XIcon /> : <MenuIcon />}
588
611
  </button>
589
- <div style={{ display: "flex", alignItems: "center", gap: 8, fontFamily: "var(--font-code)", fontSize: 11, color: "var(--txM)", letterSpacing: ".03em", flex: 1 }}>
590
- {navigation.map(s => {
591
- const f = s.pages.find(p => p.id === currentPageId);
592
- if (!f) return null;
593
- return <span key={s.section} style={{ display: "flex", alignItems: "center", gap: 8 }}>
594
- <span>{s.section}</span><ChevRight /><span style={{ color: "var(--ac)" }}>{f.title}</span>
595
- </span>;
596
- })}
597
- </div>
612
+ {mobile ? (
613
+ <span style={{ fontSize: 13, color: "var(--ac)", fontFamily: "var(--font-code)", flex: 1, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>
614
+ {navigation.flatMap(s => s.pages).find(p => p.id === currentPageId)?.title || ""}
615
+ </span>
616
+ ) : (
617
+ <div style={{ display: "flex", alignItems: "center", gap: 8, fontFamily: "var(--font-code)", fontSize: 11, color: "var(--txM)", letterSpacing: ".03em", flex: 1 }}>
618
+ {navigation.map(s => {
619
+ const f = s.pages.find(p => p.id === currentPageId);
620
+ if (!f) return null;
621
+ return <span key={s.section} style={{ display: "flex", alignItems: "center", gap: 8 }}>
622
+ <span>{s.section}</span><ChevRight /><span style={{ color: "var(--ac)" }}>{f.title}</span>
623
+ </span>;
624
+ })}
625
+ </div>
626
+ )}
598
627
 
599
628
  {/* Top Nav Links */}
600
- {config.topNav && config.topNav.length > 0 && (
629
+ {config.topNav && config.topNav.length > 0 && !mobile && (
601
630
  <div style={{ display: "flex", alignItems: "center", gap: 12 }}>
602
631
  {config.topNav.map((link) => {
603
632
  const isExternal = link.href.startsWith("http") || !link.href.startsWith("#");
@@ -771,8 +800,8 @@ export function Shell({
771
800
 
772
801
  {/* Content + TOC */}
773
802
  <div ref={contentRef} style={{ flex: 1, overflow: "auto", display: "flex" }}>
774
- <main style={{ flex: 1, maxWidth: 760, padding: "40px 48px 80px", margin: "0 auto" }}>
775
- <h1 style={{ fontFamily: "var(--font-heading)", fontSize: 38, fontWeight: 400, fontStyle: "italic", lineHeight: 1.15, marginBottom: 8 }}>
803
+ <main style={{ flex: 1, maxWidth: mobile ? "100%" : 760, padding: mobile ? "24px 16px 60px" : "40px 48px 80px", margin: "0 auto", minWidth: 0 }}>
804
+ <h1 style={{ fontFamily: "var(--font-heading)", fontSize: mobile ? 26 : 38, fontWeight: 400, fontStyle: "italic", lineHeight: 1.15, marginBottom: 8 }}>
776
805
  {pageTitle}
777
806
  </h1>
778
807
  {pageDescription && <p style={{ fontSize: 16, color: "var(--tx2)", lineHeight: 1.6, marginBottom: 32 }}>{pageDescription}</p>}
@@ -794,7 +823,7 @@ export function Shell({
794
823
 
795
824
  {/* TOM-48: Edit this page link + TOM-54: Last updated */}
796
825
  {(editUrl || lastUpdated) && (
797
- <div style={{ marginTop: 40, display: "flex", alignItems: "center", justifyContent: "space-between", gap: 16 }}>
826
+ <div style={{ marginTop: 40, display: "flex", flexDirection: mobile ? "column" : "row", alignItems: mobile ? "flex-start" : "center", justifyContent: "space-between", gap: mobile ? 8 : 16 }}>
798
827
  {editUrl && (
799
828
  <div data-testid="edit-page-link">
800
829
  <a
@@ -822,7 +851,7 @@ export function Shell({
822
851
  )}
823
852
 
824
853
  {/* Prev / Next */}
825
- <div style={{ display: "flex", justifyContent: "space-between", marginTop: (editUrl || lastUpdated) ? 16 : 48, paddingTop: 24, borderTop: "1px solid var(--bd)", gap: 16 }}>
854
+ <div style={{ display: "flex", flexDirection: mobile ? "column" : "row", justifyContent: "space-between", marginTop: (editUrl || lastUpdated) ? 16 : 48, paddingTop: 24, borderTop: "1px solid var(--bd)", gap: mobile ? 12 : 16 }}>
826
855
  {prev ? (
827
856
  <button onClick={() => onNavigate(prev.id)} style={{
828
857
  display: "flex", alignItems: "center", gap: 8, background: "none",
@@ -898,10 +927,11 @@ interface SearchResult {
898
927
  }
899
928
 
900
929
  // ── SEARCH MODAL (TOM-15) ────────────────────────────────
901
- function SearchModal({ allPages, onNavigate, onClose }: {
930
+ function SearchModal({ allPages, onNavigate, onClose, mobile }: {
902
931
  allPages: Array<{ id: string; title: string; description?: string }>;
903
932
  onNavigate: (id: string) => void;
904
933
  onClose: () => void;
934
+ mobile?: boolean;
905
935
  }) {
906
936
  const [q, setQ] = useState("");
907
937
  const [results, setResults] = useState<SearchResult[]>([]);
@@ -993,12 +1023,15 @@ function SearchModal({ allPages, onNavigate, onClose }: {
993
1023
  return (
994
1024
  <div onClick={onClose} style={{
995
1025
  position: "fixed", inset: 0, zIndex: 1000, background: "rgba(0,0,0,0.55)",
996
- backdropFilter: "blur(6px)", display: "flex", alignItems: "flex-start",
997
- justifyContent: "center", paddingTop: "12vh",
1026
+ backdropFilter: "blur(6px)", display: "flex",
1027
+ alignItems: mobile ? "stretch" : "flex-start",
1028
+ justifyContent: "center", paddingTop: mobile ? 0 : "12vh",
998
1029
  }}>
999
1030
  <div onClick={e => e.stopPropagation()} style={{
1000
- background: "var(--sf)", border: "1px solid var(--bd)", borderRadius: 2,
1001
- width: "100%", maxWidth: 520, boxShadow: "0 24px 80px rgba(0,0,0,0.4)", overflow: "hidden",
1031
+ background: "var(--sf)", border: mobile ? "none" : "1px solid var(--bd)", borderRadius: mobile ? 0 : 2,
1032
+ width: "100%", maxWidth: mobile ? "100%" : 520, boxShadow: mobile ? "none" : "0 24px 80px rgba(0,0,0,0.4)",
1033
+ overflow: "hidden", display: "flex", flexDirection: "column" as const,
1034
+ ...(mobile ? { height: "100%" } : {}),
1002
1035
  }}>
1003
1036
  <div style={{ display: "flex", alignItems: "center", gap: 10, padding: "14px 18px", borderBottom: "1px solid var(--bd)" }}>
1004
1037
  <SearchIcon />
@@ -1009,7 +1042,7 @@ function SearchModal({ allPages, onNavigate, onClose }: {
1009
1042
  />
1010
1043
  <kbd style={{ fontFamily: "var(--font-code)", fontSize: 10, color: "var(--txM)", background: "var(--cdBg)", padding: "2px 6px", borderRadius: 2, border: "1px solid var(--bd)" }}>ESC</kbd>
1011
1044
  </div>
1012
- {results.length > 0 && <div style={{ padding: 6, maxHeight: 360, overflow: "auto" }}>
1045
+ {results.length > 0 && <div style={{ padding: 6, maxHeight: mobile ? "none" : 360, overflow: "auto", flex: mobile ? 1 : undefined }}>
1013
1046
  {results.map((r, i) => (
1014
1047
  <button key={r.id + i} onClick={() => onNavigate(r.id)} style={{
1015
1048
  display: "block", width: "100%", textAlign: "left",
package/src/entry.tsx CHANGED
@@ -59,6 +59,15 @@ const contentStyles = `
59
59
  .tome-content img { max-width: 100%; border-radius: 2px; }
60
60
  .tome-content hr { border: none; border-top: 1px solid var(--bd); margin: 2em 0; }
61
61
 
62
+ /* Mobile responsive content */
63
+ @media (max-width: 767px) {
64
+ .tome-content h2 { font-size: 1.2em; margin-top: 1.5em; }
65
+ .tome-content h3 { font-size: 1.05em; }
66
+ .tome-content pre code { font-size: 12px; padding: 0.8em 1em; }
67
+ .tome-content table { display: block; overflow-x: auto; -webkit-overflow-scrolling: touch; }
68
+ .tome-content blockquote { margin: 0.8em 0; }
69
+ }
70
+
62
71
  /* Selection style */
63
72
  ::selection { background: var(--acD); color: var(--ac); }
64
73