@tomehq/theme 0.2.4 → 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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Tome Contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -562,7 +562,7 @@ var ChevDown = () => /* @__PURE__ */ jsx2(Icon, { d: "M6 9l6 6 6-6", size: 14 })
562
562
  var MenuIcon = () => /* @__PURE__ */ jsx2(Icon, { d: "M3 12h18M3 6h18M3 18h18", size: 20 });
563
563
  var XIcon = () => /* @__PURE__ */ jsx2(Icon, { d: "M18 6L6 18M6 6l12 12", size: 18 });
564
564
  var MoonIcon = () => /* @__PURE__ */ jsx2(Icon, { d: "M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z" });
565
- var SunIcon = () => /* @__PURE__ */ jsx2(Icon, { d: "M12 7a5 5 0 1 0 0 10 5 5 0 0 0 0-10Z" });
565
+ var SunIcon = () => /* @__PURE__ */ jsx2(Icon, { d: "M12 8a4 4 0 1 0 0 8 4 4 0 0 0 0-8Zm0-4a1 1 0 0 1 1-1v-1a1 1 0 0 1-2 0v1a1 1 0 0 1 1 1Zm0 16a1 1 0 0 1 1 1v1a1 1 0 0 1-2 0v-1a1 1 0 0 1 1-1ZM4 12a1 1 0 0 1-1 1H2a1 1 0 0 1 0-2h1a1 1 0 0 1 1 1Zm18-1h-1a1 1 0 0 1 0 2h1a1 1 0 0 1 0-2ZM6.34 6.34a1 1 0 0 1-1.41 0l-.71-.71a1 1 0 0 1 1.41-1.41l.71.71a1 1 0 0 1 0 1.41Zm12.73-2.12-.71.71a1 1 0 0 1-1.41-1.41l.71-.71a1 1 0 1 1 1.41 1.41ZM6.34 17.66l-.71.71a1 1 0 0 1-1.41-1.41l.71-.71a1 1 0 0 1 1.41 1.41Zm12.73 2.12-.71-.71a1 1 0 0 1 1.41-1.41l.71.71a1 1 0 0 1-1.41 1.41Z" });
566
566
  var ArrowLeft = () => /* @__PURE__ */ jsx2(Icon, { d: "M19 12H5M12 19l-7-7 7-7", size: 14 });
567
567
  var ArrowRight = () => /* @__PURE__ */ jsx2(Icon, { d: "M5 12h14M12 5l7 7-7 7", size: 14 });
568
568
  var PencilIcon = () => /* @__PURE__ */ jsx2(Icon, { d: "M17 3a2.83 2.83 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5Z", size: 13 });
@@ -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,12 @@ 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", {}),
1056
+ /* @__PURE__ */ jsxs2("span", { style: { fontSize: 11, color: "var(--txM)", letterSpacing: 0.2 }, children: [
1057
+ "Built with ",
1058
+ "\u2661",
1059
+ " by Tome"
1060
+ ] }),
1028
1061
  /* @__PURE__ */ jsx2("span", { style: { fontFamily: "var(--font-code)", fontSize: 10, color: "var(--txM)" }, children: typeof __TOME_VERSION__ !== "undefined" && __TOME_VERSION__ ? `v${__TOME_VERSION__}` : "v0.1.0" })
1029
1062
  ] })
1030
1063
  ] }),
@@ -1032,14 +1065,14 @@ function Shell({
1032
1065
  /* @__PURE__ */ jsxs2("header", { style: {
1033
1066
  display: "flex",
1034
1067
  alignItems: "center",
1035
- gap: 12,
1036
- padding: "10px 24px",
1068
+ gap: mobile ? 8 : 12,
1069
+ padding: mobile ? "8px 12px" : "10px 24px",
1037
1070
  borderBottom: "1px solid var(--bd)",
1038
1071
  background: "var(--hdBg)",
1039
1072
  backdropFilter: "blur(12px)"
1040
1073
  }, children: [
1041
- /* @__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, {}) }),
1042
- /* @__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) => {
1043
1076
  const f = s.pages.find((p) => p.id === currentPageId);
1044
1077
  if (!f) return null;
1045
1078
  return /* @__PURE__ */ jsxs2("span", { style: { display: "flex", alignItems: "center", gap: 8 }, children: [
@@ -1048,7 +1081,7 @@ function Shell({
1048
1081
  /* @__PURE__ */ jsx2("span", { style: { color: "var(--ac)" }, children: f.title })
1049
1082
  ] }, s.section);
1050
1083
  }) }),
1051
- 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: [
1052
1085
  config2.topNav.map((link) => {
1053
1086
  const isExternal = link.href.startsWith("http") || !link.href.startsWith("#");
1054
1087
  return /* @__PURE__ */ jsxs2(
@@ -1278,8 +1311,8 @@ function Shell({
1278
1311
  }
1279
1312
  ),
1280
1313
  /* @__PURE__ */ jsxs2("div", { ref: contentRef, style: { flex: 1, overflow: "auto", display: "flex" }, children: [
1281
- /* @__PURE__ */ jsxs2("main", { style: { flex: 1, maxWidth: 760, padding: "40px 48px 80px", margin: "0 auto" }, children: [
1282
- /* @__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 }),
1283
1316
  pageDescription && /* @__PURE__ */ jsx2("p", { style: { fontSize: 16, color: "var(--tx2)", lineHeight: 1.6, marginBottom: 32 }, children: pageDescription }),
1284
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(
1285
1318
  "div",
@@ -1288,7 +1321,7 @@ function Shell({
1288
1321
  dangerouslySetInnerHTML: { __html: (pageHtml || "").replace(/<h1[^>]*>[\s\S]*?<\/h1>\s*/, "") }
1289
1322
  }
1290
1323
  ) }),
1291
- (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: [
1292
1325
  editUrl && /* @__PURE__ */ jsx2("div", { "data-testid": "edit-page-link", children: /* @__PURE__ */ jsxs2(
1293
1326
  "a",
1294
1327
  {
@@ -1318,7 +1351,7 @@ function Shell({
1318
1351
  formatRelativeDate(lastUpdated)
1319
1352
  ] })
1320
1353
  ] }),
1321
- /* @__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: [
1322
1355
  prev ? /* @__PURE__ */ jsxs2("button", { onClick: () => onNavigate(prev.id), style: {
1323
1356
  display: "flex",
1324
1357
  alignItems: "center",
@@ -1401,7 +1434,7 @@ ${d.content}`).join("\n\n") ?? allPages.map((p) => `- ${p.title}${p.description
1401
1434
  )
1402
1435
  ] });
1403
1436
  }
1404
- function SearchModal({ allPages, onNavigate, onClose }) {
1437
+ function SearchModal({ allPages, onNavigate, onClose, mobile }) {
1405
1438
  const [q, setQ] = useState2("");
1406
1439
  const [results, setResults] = useState2([]);
1407
1440
  const [selected, setSelected] = useState2(0);
@@ -1473,17 +1506,20 @@ function SearchModal({ allPages, onNavigate, onClose }) {
1473
1506
  background: "rgba(0,0,0,0.55)",
1474
1507
  backdropFilter: "blur(6px)",
1475
1508
  display: "flex",
1476
- alignItems: "flex-start",
1509
+ alignItems: mobile ? "stretch" : "flex-start",
1477
1510
  justifyContent: "center",
1478
- paddingTop: "12vh"
1511
+ paddingTop: mobile ? 0 : "12vh"
1479
1512
  }, children: /* @__PURE__ */ jsxs2("div", { onClick: (e) => e.stopPropagation(), style: {
1480
1513
  background: "var(--sf)",
1481
- border: "1px solid var(--bd)",
1482
- borderRadius: 2,
1514
+ border: mobile ? "none" : "1px solid var(--bd)",
1515
+ borderRadius: mobile ? 0 : 2,
1483
1516
  width: "100%",
1484
- maxWidth: 520,
1485
- boxShadow: "0 24px 80px rgba(0,0,0,0.4)",
1486
- 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%" } : {}
1487
1523
  }, children: [
1488
1524
  /* @__PURE__ */ jsxs2("div", { style: { display: "flex", alignItems: "center", gap: 10, padding: "14px 18px", borderBottom: "1px solid var(--bd)" }, children: [
1489
1525
  /* @__PURE__ */ jsx2(SearchIcon, {}),
@@ -1500,7 +1536,7 @@ function SearchModal({ allPages, onNavigate, onClose }) {
1500
1536
  ),
1501
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" })
1502
1538
  ] }),
1503
- 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(
1504
1540
  "button",
1505
1541
  {
1506
1542
  onClick: () => onNavigate(r.id),
@@ -1582,6 +1618,15 @@ var contentStyles = `
1582
1618
  .tome-content img { max-width: 100%; border-radius: 2px; }
1583
1619
  .tome-content hr { border: none; border-top: 1px solid var(--bd); margin: 2em 0; }
1584
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
+
1585
1630
  /* Selection style */
1586
1631
  ::selection { background: var(--acD); color: var(--ac); }
1587
1632
 
package/dist/entry.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  entry_default
3
- } from "./chunk-S47BRMNQ.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-S47BRMNQ.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.4",
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",
@@ -8,14 +8,9 @@
8
8
  ".": "./src/index.tsx",
9
9
  "./entry": "./src/entry.tsx"
10
10
  },
11
- "scripts": {
12
- "build": "tsup src/index.tsx src/entry.tsx --format esm --dts --external react --external react-dom --external 'virtual:tome/config' --external 'virtual:tome/routes' --external 'virtual:tome/page-loader' --external 'virtual:tome/doc-context' --external '@tomehq/components'",
13
- "dev": "tsup src/index.tsx src/entry.tsx --format esm --dts --external react --external react-dom --external 'virtual:tome/config' --external 'virtual:tome/routes' --external 'virtual:tome/page-loader' --external 'virtual:tome/doc-context' --external '@tomehq/components' --watch",
14
- "clean": "rm -rf dist"
15
- },
16
11
  "dependencies": {
17
- "@tomehq/components": "workspace:*",
18
- "@tomehq/core": "workspace:*"
12
+ "@tomehq/components": "0.2.6",
13
+ "@tomehq/core": "0.2.6"
19
14
  },
20
15
  "peerDependencies": {
21
16
  "react": "^18.0.0 || ^19.0.0",
@@ -48,5 +43,10 @@
48
43
  "type": "git",
49
44
  "url": "https://github.com/vxcozy/tome.git",
50
45
  "directory": "packages/theme"
46
+ },
47
+ "scripts": {
48
+ "build": "tsup src/index.tsx src/entry.tsx --format esm --dts --external react --external react-dom --external 'virtual:tome/config' --external 'virtual:tome/routes' --external 'virtual:tome/page-loader' --external 'virtual:tome/doc-context' --external '@tomehq/components'",
49
+ "dev": "tsup src/index.tsx src/entry.tsx --format esm --dts --external react --external react-dom --external 'virtual:tome/config' --external 'virtual:tome/routes' --external 'virtual:tome/page-loader' --external 'virtual:tome/doc-context' --external '@tomehq/components' --watch",
50
+ "clean": "rm -rf dist"
51
51
  }
52
- }
52
+ }
package/src/Shell.tsx CHANGED
@@ -41,7 +41,7 @@ const CheckIcon = () => <Icon d="M20 6L9 17l-5-5" size={14} />;
41
41
  const MenuIcon = () => <Icon d="M3 12h18M3 6h18M3 18h18" size={20} />;
42
42
  const XIcon = () => <Icon d="M18 6L6 18M6 6l12 12" size={18} />;
43
43
  const MoonIcon = () => <Icon d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z" />;
44
- const SunIcon = () => <Icon d="M12 7a5 5 0 1 0 0 10 5 5 0 0 0 0-10Z" />;
44
+ const SunIcon = () => <Icon d="M12 8a4 4 0 1 0 0 8 4 4 0 0 0 0-8Zm0-4a1 1 0 0 1 1-1v-1a1 1 0 0 1-2 0v1a1 1 0 0 1 1 1Zm0 16a1 1 0 0 1 1 1v1a1 1 0 0 1-2 0v-1a1 1 0 0 1 1-1ZM4 12a1 1 0 0 1-1 1H2a1 1 0 0 1 0-2h1a1 1 0 0 1 1 1Zm18-1h-1a1 1 0 0 1 0 2h1a1 1 0 0 1 0-2ZM6.34 6.34a1 1 0 0 1-1.41 0l-.71-.71a1 1 0 0 1 1.41-1.41l.71.71a1 1 0 0 1 0 1.41Zm12.73-2.12-.71.71a1 1 0 0 1-1.41-1.41l.71-.71a1 1 0 1 1 1.41 1.41ZM6.34 17.66l-.71.71a1 1 0 0 1-1.41-1.41l.71-.71a1 1 0 0 1 1.41 1.41Zm12.73 2.12-.71-.71a1 1 0 0 1 1.41-1.41l.71.71a1 1 0 0 1-1.41 1.41Z" />;
45
45
  const ArrowLeft = () => <Icon d="M19 12H5M12 19l-7-7 7-7" size={14} />;
46
46
  const ArrowRight = () => <Icon d="M5 12h14M12 5l7 7-7 7" size={14} />;
47
47
  const PencilIcon = () => <Icon d="M17 3a2.83 2.83 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5Z" size={13} />;
@@ -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,10 +590,11 @@ 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 />}
597
+ <span style={{ fontSize: 11, color: "var(--txM)", letterSpacing: 0.2 }}>Built with {"\u2661"} by Tome</span>
574
598
  <span style={{ fontFamily: "var(--font-code)", fontSize: 10, color: "var(--txM)" }}>{typeof __TOME_VERSION__ !== "undefined" && __TOME_VERSION__ ? `v${__TOME_VERSION__}` : "v0.1.0"}</span>
575
599
  </div>
576
600
  </aside>
@@ -579,24 +603,30 @@ export function Shell({
579
603
  <div style={{ flex: 1, display: "flex", flexDirection: "column", overflow: "hidden" }}>
580
604
  {/* Header */}
581
605
  <header style={{
582
- display: "flex", alignItems: "center", gap: 12, padding: "10px 24px",
606
+ display: "flex", alignItems: "center", gap: mobile ? 8 : 12, padding: mobile ? "8px 12px" : "10px 24px",
583
607
  borderBottom: "1px solid var(--bd)", background: "var(--hdBg)", backdropFilter: "blur(12px)",
584
608
  }}>
585
- <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" }}>
586
610
  {sbOpen ? <XIcon /> : <MenuIcon />}
587
611
  </button>
588
- <div style={{ display: "flex", alignItems: "center", gap: 8, fontFamily: "var(--font-code)", fontSize: 11, color: "var(--txM)", letterSpacing: ".03em", flex: 1 }}>
589
- {navigation.map(s => {
590
- const f = s.pages.find(p => p.id === currentPageId);
591
- if (!f) return null;
592
- return <span key={s.section} style={{ display: "flex", alignItems: "center", gap: 8 }}>
593
- <span>{s.section}</span><ChevRight /><span style={{ color: "var(--ac)" }}>{f.title}</span>
594
- </span>;
595
- })}
596
- </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
+ )}
597
627
 
598
628
  {/* Top Nav Links */}
599
- {config.topNav && config.topNav.length > 0 && (
629
+ {config.topNav && config.topNav.length > 0 && !mobile && (
600
630
  <div style={{ display: "flex", alignItems: "center", gap: 12 }}>
601
631
  {config.topNav.map((link) => {
602
632
  const isExternal = link.href.startsWith("http") || !link.href.startsWith("#");
@@ -770,8 +800,8 @@ export function Shell({
770
800
 
771
801
  {/* Content + TOC */}
772
802
  <div ref={contentRef} style={{ flex: 1, overflow: "auto", display: "flex" }}>
773
- <main style={{ flex: 1, maxWidth: 760, padding: "40px 48px 80px", margin: "0 auto" }}>
774
- <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 }}>
775
805
  {pageTitle}
776
806
  </h1>
777
807
  {pageDescription && <p style={{ fontSize: 16, color: "var(--tx2)", lineHeight: 1.6, marginBottom: 32 }}>{pageDescription}</p>}
@@ -793,7 +823,7 @@ export function Shell({
793
823
 
794
824
  {/* TOM-48: Edit this page link + TOM-54: Last updated */}
795
825
  {(editUrl || lastUpdated) && (
796
- <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 }}>
797
827
  {editUrl && (
798
828
  <div data-testid="edit-page-link">
799
829
  <a
@@ -821,7 +851,7 @@ export function Shell({
821
851
  )}
822
852
 
823
853
  {/* Prev / Next */}
824
- <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 }}>
825
855
  {prev ? (
826
856
  <button onClick={() => onNavigate(prev.id)} style={{
827
857
  display: "flex", alignItems: "center", gap: 8, background: "none",
@@ -897,10 +927,11 @@ interface SearchResult {
897
927
  }
898
928
 
899
929
  // ── SEARCH MODAL (TOM-15) ────────────────────────────────
900
- function SearchModal({ allPages, onNavigate, onClose }: {
930
+ function SearchModal({ allPages, onNavigate, onClose, mobile }: {
901
931
  allPages: Array<{ id: string; title: string; description?: string }>;
902
932
  onNavigate: (id: string) => void;
903
933
  onClose: () => void;
934
+ mobile?: boolean;
904
935
  }) {
905
936
  const [q, setQ] = useState("");
906
937
  const [results, setResults] = useState<SearchResult[]>([]);
@@ -992,12 +1023,15 @@ function SearchModal({ allPages, onNavigate, onClose }: {
992
1023
  return (
993
1024
  <div onClick={onClose} style={{
994
1025
  position: "fixed", inset: 0, zIndex: 1000, background: "rgba(0,0,0,0.55)",
995
- backdropFilter: "blur(6px)", display: "flex", alignItems: "flex-start",
996
- justifyContent: "center", paddingTop: "12vh",
1026
+ backdropFilter: "blur(6px)", display: "flex",
1027
+ alignItems: mobile ? "stretch" : "flex-start",
1028
+ justifyContent: "center", paddingTop: mobile ? 0 : "12vh",
997
1029
  }}>
998
1030
  <div onClick={e => e.stopPropagation()} style={{
999
- background: "var(--sf)", border: "1px solid var(--bd)", borderRadius: 2,
1000
- 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%" } : {}),
1001
1035
  }}>
1002
1036
  <div style={{ display: "flex", alignItems: "center", gap: 10, padding: "14px 18px", borderBottom: "1px solid var(--bd)" }}>
1003
1037
  <SearchIcon />
@@ -1008,7 +1042,7 @@ function SearchModal({ allPages, onNavigate, onClose }: {
1008
1042
  />
1009
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>
1010
1044
  </div>
1011
- {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 }}>
1012
1046
  {results.map((r, i) => (
1013
1047
  <button key={r.id + i} onClick={() => onNavigate(r.id)} style={{
1014
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