@gridland/demo 0.2.51 → 0.2.53

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/run.js CHANGED
@@ -1,8 +1,8 @@
1
1
  // src/run.tsx
2
- import { createCliRenderer, createRoot, useKeyboard as useKeyboard19 } from "@gridland/bun";
2
+ import { createCliRenderer, createRoot, useKeyboard as useKeyboard22 } from "@gridland/bun";
3
3
 
4
4
  // demos/index.tsx
5
- import { useKeyboard as useKeyboard18 } from "@gridland/utils";
5
+ import { useKeyboard as useKeyboard21 } from "@gridland/utils";
6
6
 
7
7
  // demos/gradient.tsx
8
8
  import { useState as useState8 } from "react";
@@ -160,9 +160,9 @@ import { useState as useState3 } from "react";
160
160
  import { jsx as jsx8, jsxs as jsxs3 } from "react/jsx-runtime";
161
161
  function SpinnerPicker({ useKeyboard: useKeyboardProp }) {
162
162
  const theme = useTheme();
163
- const useKeyboard20 = useKeyboardContext(useKeyboardProp);
163
+ const useKeyboard23 = useKeyboardContext(useKeyboardProp);
164
164
  const [selected, setSelected] = useState3(0);
165
- useKeyboard20?.((event) => {
165
+ useKeyboard23?.((event) => {
166
166
  if (event.name === "left") {
167
167
  setSelected((s) => s > 0 ? s - 1 : VARIANT_NAMES.length - 1);
168
168
  } else if (event.name === "right") {
@@ -291,7 +291,7 @@ function SelectInput({
291
291
  useKeyboard: useKeyboardProp
292
292
  }) {
293
293
  const theme = useTheme();
294
- const useKeyboard20 = useKeyboardContext(useKeyboardProp);
294
+ const useKeyboard23 = useKeyboardContext(useKeyboardProp);
295
295
  const resolvedHighlight = highlightColor ?? theme.primary;
296
296
  const isControlled = controlledValue !== void 0;
297
297
  const controlledRef = useRef2(isControlled);
@@ -334,7 +334,7 @@ function SelectInput({
334
334
  const scrollOffset = Math.max(0, Math.min(cursorRowIndex - Math.floor(visibleCount / 2), flatRows.length - visibleCount));
335
335
  const visibleRows = flatRows.slice(scrollOffset, scrollOffset + visibleCount);
336
336
  const diamondColor = invalid ? theme.error : disabled ? theme.muted : theme.accent;
337
- useKeyboard20?.((event) => {
337
+ useKeyboard23?.((event) => {
338
338
  if (state.submitted || disabled) return;
339
339
  if (event.name === "up" || event.name === "k") {
340
340
  const direction = -1;
@@ -498,7 +498,7 @@ function MultiSelect({
498
498
  useKeyboard: useKeyboardProp
499
499
  }) {
500
500
  const theme = useTheme();
501
- const useKeyboard20 = useKeyboardContext(useKeyboardProp);
501
+ const useKeyboard23 = useKeyboardContext(useKeyboardProp);
502
502
  const resolvedHighlight = highlightColor ?? theme.primary;
503
503
  const resolvedCheckbox = checkboxColor ?? theme.accent;
504
504
  const isControlled = controlledSelected !== void 0;
@@ -554,7 +554,7 @@ function MultiSelect({
554
554
  }
555
555
  };
556
556
  const diamondColor = invalid ? theme.error : disabled ? theme.muted : theme.accent;
557
- useKeyboard20?.((event) => {
557
+ useKeyboard23?.((event) => {
558
558
  if (state.submitted || disabled) return;
559
559
  const move = (direction) => {
560
560
  let next = cursorRef.current + direction;
@@ -1004,9 +1004,9 @@ function Modal({
1004
1004
  useKeyboard: useKeyboardProp
1005
1005
  }) {
1006
1006
  const theme = useTheme();
1007
- const useKeyboard20 = useKeyboardContext(useKeyboardProp);
1007
+ const useKeyboard23 = useKeyboardContext(useKeyboardProp);
1008
1008
  const resolvedBorderColor = borderColor ?? theme.muted;
1009
- useKeyboard20?.((event) => {
1009
+ useKeyboard23?.((event) => {
1010
1010
  if (event.name === "escape" && onClose) {
1011
1011
  onClose();
1012
1012
  }
@@ -1037,7 +1037,7 @@ import {
1037
1037
  createContext as createContext5,
1038
1038
  useContext as useContext5
1039
1039
  } from "react";
1040
- import { Fragment as Fragment5, jsx as jsx16, jsxs as jsxs10 } from "react/jsx-runtime";
1040
+ import { jsx as jsx16, jsxs as jsxs10 } from "react/jsx-runtime";
1041
1041
  var PromptInputControllerCtx = createContext5(null);
1042
1042
  var useOptionalController = () => useContext5(PromptInputControllerCtx);
1043
1043
  var PromptInputContext = createContext5(null);
@@ -1064,10 +1064,11 @@ function resolveStatusHintText(status, submittedText, streamingText, errorText,
1064
1064
  if (status === "error") return errorText;
1065
1065
  return disabledText;
1066
1066
  }
1067
- var DIVIDER_LINE = "\u2500".repeat(500);
1068
1067
  function PromptInputDivider() {
1069
- const { theme } = usePromptInput();
1070
- return /* @__PURE__ */ jsx16("text", { wrapMode: "none", marginLeft: -1, marginRight: -1, children: /* @__PURE__ */ jsx16("span", { style: textStyle({ dim: true, fg: theme.muted }), children: DIVIDER_LINE }) });
1068
+ const { dividerColor, dividerDashed, theme } = usePromptInput();
1069
+ const color = dividerColor ?? theme.muted;
1070
+ const char = dividerDashed ? "\u254C" : "\u2500";
1071
+ return /* @__PURE__ */ jsx16("text", { wrapMode: "none", marginLeft: -1, marginRight: -1, children: /* @__PURE__ */ jsx16("span", { style: textStyle({ dim: !dividerColor, fg: color }), children: char.repeat(500) }) });
1071
1072
  }
1072
1073
  function PromptInputSuggestions() {
1073
1074
  const { suggestions, sugIdx, maxSuggestions, theme } = usePromptInput();
@@ -1082,18 +1083,25 @@ function PromptInputSuggestions() {
1082
1083
  ] }, sug.text);
1083
1084
  }) });
1084
1085
  }
1085
- var CURSOR_CHAR = "\u258D";
1086
1086
  function PromptInputTextarea() {
1087
- const { value, disabled, statusHintText, placeholder, prompt, promptColor, theme } = usePromptInput();
1088
- return /* @__PURE__ */ jsxs10("text", { children: [
1089
- /* @__PURE__ */ jsx16("span", { style: textStyle({ fg: promptColor }), children: prompt }),
1090
- value.length === 0 ? /* @__PURE__ */ jsxs10(Fragment5, { children: [
1091
- !disabled && /* @__PURE__ */ jsx16("span", { style: textStyle({ fg: theme.muted }), children: CURSOR_CHAR }),
1092
- /* @__PURE__ */ jsx16("span", { style: textStyle({ dim: true, fg: theme.placeholder }), children: disabled ? statusHintText : " " + placeholder })
1093
- ] }) : /* @__PURE__ */ jsxs10(Fragment5, { children: [
1094
- /* @__PURE__ */ jsx16("span", { style: textStyle({ fg: theme.foreground }), children: value }),
1095
- !disabled && /* @__PURE__ */ jsx16("span", { style: textStyle({ fg: theme.muted }), children: CURSOR_CHAR })
1096
- ] })
1087
+ const { value, isFocused, disabled, statusHintText, placeholder, prompt, promptColor, theme, handleInput, handleInputSubmit, handleInputKeyDown } = usePromptInput();
1088
+ return /* @__PURE__ */ jsxs10("box", { flexDirection: "row", children: [
1089
+ /* @__PURE__ */ jsx16("text", { children: /* @__PURE__ */ jsx16("span", { style: textStyle({ fg: promptColor }), children: prompt }) }),
1090
+ isFocused ? /* @__PURE__ */ jsx16(
1091
+ "input",
1092
+ {
1093
+ value,
1094
+ placeholder,
1095
+ focused: true,
1096
+ onInput: handleInput,
1097
+ onSubmit: handleInputSubmit,
1098
+ onKeyDown: handleInputKeyDown,
1099
+ cursorColor: theme.muted,
1100
+ cursorStyle: { style: "line", blinking: !value },
1101
+ placeholderColor: theme.placeholder,
1102
+ textColor: theme.foreground
1103
+ }
1104
+ ) : disabled && value.length === 0 ? /* @__PURE__ */ jsx16("text", { children: /* @__PURE__ */ jsx16("span", { style: textStyle({ dim: true, fg: theme.placeholder }), children: statusHintText }) }) : /* @__PURE__ */ jsx16("text", { children: /* @__PURE__ */ jsx16("span", { style: textStyle({ fg: value ? theme.foreground : theme.placeholder, dim: !value }), children: value || placeholder }) })
1097
1105
  ] });
1098
1106
  }
1099
1107
  function PromptInputSubmit(props) {
@@ -1140,13 +1148,16 @@ function PromptInput({
1140
1148
  maxSuggestions = 5,
1141
1149
  enableHistory = true,
1142
1150
  model,
1151
+ focus = true,
1143
1152
  showDividers = true,
1144
1153
  autoFocus = false,
1154
+ dividerColor,
1155
+ dividerDashed,
1145
1156
  useKeyboard: useKeyboardProp,
1146
1157
  children
1147
1158
  }) {
1148
1159
  const theme = useTheme();
1149
- const useKeyboard20 = useKeyboardContext(useKeyboardProp);
1160
+ const useKeyboard23 = useKeyboardContext(useKeyboardProp);
1150
1161
  useEffect2(() => {
1151
1162
  if (!autoFocus) return;
1152
1163
  if (typeof document === "undefined") return;
@@ -1157,6 +1168,7 @@ function PromptInput({
1157
1168
  }, [autoFocus]);
1158
1169
  const resolvedPromptColor = promptColor ?? theme.muted;
1159
1170
  const disabled = status ? status === "submitted" || status === "streaming" : disabledProp;
1171
+ const isFocused = focus && !disabled;
1160
1172
  const statusHintText = resolveStatusHintText(status, submittedText, streamingLabel, errorText, disabledText);
1161
1173
  const controller = useOptionalController();
1162
1174
  const usingProvider = !!controller;
@@ -1247,18 +1259,86 @@ function PromptInput({
1247
1259
  clearInput();
1248
1260
  }
1249
1261
  }, [onSubmit, clearInput]);
1250
- useKeyboard20?.((event) => {
1262
+ const handleInputSubmit = (text) => {
1263
+ const trimmed = text.trim();
1264
+ if (!trimmed) return;
1265
+ if (enableHistory) {
1266
+ setHist([trimmed, ...historyRef.current]);
1267
+ }
1268
+ updateValue("");
1269
+ setHistI(-1);
1270
+ handleSubmit(trimmed);
1271
+ };
1272
+ const handleInputKeyDown = (key) => {
1273
+ if (key.name === "return" && suggestionsRef.current.length > 0) {
1274
+ const sel = suggestionsRef.current[sugIdxRef.current];
1275
+ if (sel) {
1276
+ if (valueRef.current.startsWith("/")) {
1277
+ updateValue("");
1278
+ if (enableHistory) {
1279
+ setHist([sel.text, ...historyRef.current]);
1280
+ }
1281
+ setHistI(-1);
1282
+ handleSubmit(sel.text);
1283
+ } else {
1284
+ const base = valueRef.current.slice(0, valueRef.current.lastIndexOf("@"));
1285
+ updateValue(base + sel.text + " ");
1286
+ setSug([]);
1287
+ }
1288
+ }
1289
+ key.preventDefault();
1290
+ return;
1291
+ }
1292
+ if (key.name === "tab" && suggestionsRef.current.length > 0) {
1293
+ setSugI((sugIdxRef.current + 1) % suggestionsRef.current.length);
1294
+ key.preventDefault();
1295
+ return;
1296
+ }
1297
+ if (key.name === "up") {
1298
+ if (suggestionsRef.current.length > 0) {
1299
+ setSugI(Math.max(0, sugIdxRef.current - 1));
1300
+ } else if (enableHistory && historyRef.current.length > 0) {
1301
+ const idx = Math.min(historyRef.current.length - 1, histIdxRef.current + 1);
1302
+ setHistI(idx);
1303
+ updateValue(historyRef.current[idx]);
1304
+ }
1305
+ key.preventDefault();
1306
+ return;
1307
+ }
1308
+ if (key.name === "down") {
1309
+ if (suggestionsRef.current.length > 0) {
1310
+ setSugI(Math.min(suggestionsRef.current.length - 1, sugIdxRef.current + 1));
1311
+ } else if (enableHistory && histIdxRef.current > 0) {
1312
+ const nextIdx = histIdxRef.current - 1;
1313
+ setHistI(nextIdx);
1314
+ updateValue(historyRef.current[nextIdx]);
1315
+ } else if (enableHistory && histIdxRef.current === 0) {
1316
+ setHistI(-1);
1317
+ updateValue("");
1318
+ }
1319
+ key.preventDefault();
1320
+ return;
1321
+ }
1322
+ if (key.name === "escape") {
1323
+ if (suggestionsRef.current.length > 0) {
1324
+ setSug([]);
1325
+ key.preventDefault();
1326
+ }
1327
+ return;
1328
+ }
1329
+ };
1330
+ useKeyboard23?.((event) => {
1251
1331
  if (event.name === "escape" && (status === "streaming" || status === "submitted") && onStop) {
1252
1332
  onStop();
1253
1333
  return;
1254
1334
  }
1335
+ if (isFocused) return;
1255
1336
  if (disabled) return;
1256
1337
  if (event.name === "return") {
1257
1338
  if (suggestionsRef.current.length > 0) {
1258
1339
  const sel = suggestionsRef.current[sugIdxRef.current];
1259
1340
  if (sel) {
1260
1341
  if (valueRef.current.startsWith("/")) {
1261
- setSug([]);
1262
1342
  updateValue("");
1263
1343
  if (enableHistory) {
1264
1344
  setHist([sel.text, ...historyRef.current]);
@@ -1279,7 +1359,6 @@ function PromptInput({
1279
1359
  }
1280
1360
  updateValue("");
1281
1361
  setHistI(-1);
1282
- setSug([]);
1283
1362
  handleSubmit(trimmed);
1284
1363
  }
1285
1364
  return;
@@ -1333,6 +1412,7 @@ function PromptInput({
1333
1412
  const visibleSuggestions = suggestions.slice(0, maxSuggestions);
1334
1413
  const ctxValue = {
1335
1414
  value,
1415
+ isFocused,
1336
1416
  disabled,
1337
1417
  status,
1338
1418
  onStop,
@@ -1345,7 +1425,12 @@ function PromptInput({
1345
1425
  maxSuggestions,
1346
1426
  errorText,
1347
1427
  model,
1348
- theme
1428
+ dividerColor,
1429
+ dividerDashed,
1430
+ theme,
1431
+ handleInput: updateValue,
1432
+ handleInputSubmit,
1433
+ handleInputKeyDown
1349
1434
  };
1350
1435
  if (children) {
1351
1436
  return /* @__PURE__ */ jsx16(PromptInputContext.Provider, { value: ctxValue, children: /* @__PURE__ */ jsx16("box", { flexDirection: "column", flexShrink: 0, children }) });
@@ -1438,6 +1523,7 @@ function ChatPanel({
1438
1523
  userColor,
1439
1524
  assistantColor,
1440
1525
  loadingText = "Thinking...",
1526
+ focus,
1441
1527
  useKeyboard: useKeyboardProp
1442
1528
  }) {
1443
1529
  const theme = useTheme();
@@ -1483,6 +1569,7 @@ function ChatPanel({
1483
1569
  promptColor: resolvedPromptColor,
1484
1570
  foregroundColor: theme.foreground,
1485
1571
  submittedText: loadingText,
1572
+ focus,
1486
1573
  useKeyboard: useKeyboardProp
1487
1574
  }
1488
1575
  ) })
@@ -1491,7 +1578,7 @@ function ChatPanel({
1491
1578
 
1492
1579
  // ../ui/components/chain-of-thought/chain-of-thought.tsx
1493
1580
  import { createContext as createContext6, memo, useContext as useContext6, useEffect as useEffect3, useMemo as useMemo4, useState as useState7 } from "react";
1494
- import { Fragment as Fragment6, jsx as jsx18, jsxs as jsxs12 } from "react/jsx-runtime";
1581
+ import { Fragment as Fragment5, jsx as jsx18, jsxs as jsxs12 } from "react/jsx-runtime";
1495
1582
  var DOTS = ["\u25CB", "\u25D4", "\u25D1", "\u25D5", "\u25CF"];
1496
1583
  var SPINNER_INTERVAL = 150;
1497
1584
  var ChainOfThoughtContext = createContext6(null);
@@ -1550,7 +1637,7 @@ var ChainOfThoughtHeader = memo(({
1550
1637
  var ChainOfThoughtContent = memo(({ children }) => {
1551
1638
  const { isOpen } = useChainOfThought();
1552
1639
  if (!isOpen) return null;
1553
- return /* @__PURE__ */ jsx18(Fragment6, { children });
1640
+ return /* @__PURE__ */ jsx18(Fragment5, { children });
1554
1641
  });
1555
1642
  var ChainOfThoughtStep = memo(({
1556
1643
  label,
@@ -1765,6 +1852,7 @@ function useBreakpoints() {
1765
1852
  // demos/gradient.tsx
1766
1853
  import figlet from "figlet";
1767
1854
  import ansiShadow from "figlet/importable-fonts/ANSI Shadow.js";
1855
+ import { jsx as jsx21, jsxs as jsxs15 } from "react/jsx-runtime";
1768
1856
  figlet.parseFont("ANSI Shadow", ansiShadow);
1769
1857
  var art = figlet.textSync("gridland", { font: "ANSI Shadow" });
1770
1858
  var lines = art.split("\n").filter((l) => l.trimEnd().length > 0);
@@ -1777,13 +1865,16 @@ function GradientApp() {
1777
1865
  if (event.name === "left") setIndex((i) => i > 0 ? i - 1 : gradientNames.length - 1);
1778
1866
  if (event.name === "right") setIndex((i) => i < gradientNames.length - 1 ? i + 1 : 0);
1779
1867
  });
1780
- return /* @__PURE__ */ React.createElement("box", { flexDirection: "column", flexGrow: 1 }, /* @__PURE__ */ React.createElement("box", { padding: 1, flexDirection: "column", alignItems: "center", justifyContent: "center", flexGrow: 1 }, /* @__PURE__ */ React.createElement(Gradient, { name: name2 }, lines.join("\n"))), /* @__PURE__ */ React.createElement("box", { paddingX: 1, paddingBottom: 1 }, /* @__PURE__ */ React.createElement(
1781
- StatusBar,
1782
- {
1783
- items: [{ key: "\u2190\u2192", label: "gradient" }],
1784
- extra: /* @__PURE__ */ React.createElement("span", { style: textStyle({ fg: theme.accent, bold: true }) }, name2.padEnd(11))
1785
- }
1786
- )));
1868
+ return /* @__PURE__ */ jsxs15("box", { flexDirection: "column", flexGrow: 1, children: [
1869
+ /* @__PURE__ */ jsx21("box", { padding: 1, flexDirection: "column", alignItems: "center", justifyContent: "center", flexGrow: 1, children: /* @__PURE__ */ jsx21(Gradient, { name: name2, children: lines.join("\n") }) }),
1870
+ /* @__PURE__ */ jsx21("box", { paddingX: 1, paddingBottom: 1, children: /* @__PURE__ */ jsx21(
1871
+ StatusBar,
1872
+ {
1873
+ items: [{ key: "\u2190\u2192", label: "gradient" }],
1874
+ extra: /* @__PURE__ */ jsx21("span", { style: textStyle({ fg: theme.accent, bold: true }), children: name2.padEnd(11) })
1875
+ }
1876
+ ) })
1877
+ ] });
1787
1878
  }
1788
1879
 
1789
1880
  // demos/ascii.tsx
@@ -1798,6 +1889,7 @@ import speed from "figlet/importable-fonts/Speed.js";
1798
1889
  import standard from "figlet/importable-fonts/Standard.js";
1799
1890
  import block from "figlet/importable-fonts/Block.js";
1800
1891
  import colossal from "figlet/importable-fonts/Colossal.js";
1892
+ import { jsx as jsx22, jsxs as jsxs16 } from "react/jsx-runtime";
1801
1893
  var fonts = [
1802
1894
  { name: "ANSI Shadow", data: ansiShadow2 },
1803
1895
  { name: "Standard", data: standard },
@@ -1824,34 +1916,40 @@ function AsciiApp() {
1824
1916
  if (event.name === "left") setFontIndex((i) => i > 0 ? i - 1 : fonts.length - 1);
1825
1917
  if (event.name === "right") setFontIndex((i) => i < fonts.length - 1 ? i + 1 : 0);
1826
1918
  });
1827
- return /* @__PURE__ */ React.createElement("box", { flexDirection: "column", flexGrow: 1 }, /* @__PURE__ */ React.createElement("box", { padding: 1, flexDirection: "column", alignItems: "center", justifyContent: "center", flexGrow: 1 }, lines2.map((line, i) => /* @__PURE__ */ React.createElement("text", { key: i, fg: theme.accent, bold: true }, line))), /* @__PURE__ */ React.createElement("box", { paddingX: 1, paddingBottom: 1 }, /* @__PURE__ */ React.createElement(
1828
- StatusBar,
1829
- {
1830
- items: [{ key: "\u2190\u2192", label: "change font" }],
1831
- extra: /* @__PURE__ */ React.createElement("span", { style: textStyle({ fg: theme.accent, bold: true }) }, font.name.padEnd(11))
1832
- }
1833
- )));
1919
+ return /* @__PURE__ */ jsxs16("box", { flexDirection: "column", flexGrow: 1, children: [
1920
+ /* @__PURE__ */ jsx22("box", { padding: 1, flexDirection: "column", alignItems: "center", justifyContent: "center", flexGrow: 1, children: lines2.map((line, i) => /* @__PURE__ */ jsx22("text", { fg: theme.accent, bold: true, children: line }, i)) }),
1921
+ /* @__PURE__ */ jsx22("box", { paddingX: 1, paddingBottom: 1, children: /* @__PURE__ */ jsx22(
1922
+ StatusBar,
1923
+ {
1924
+ items: [{ key: "\u2190\u2192", label: "change font" }],
1925
+ extra: /* @__PURE__ */ jsx22("span", { style: textStyle({ fg: theme.accent, bold: true }), children: font.name.padEnd(11) })
1926
+ }
1927
+ ) })
1928
+ ] });
1834
1929
  }
1835
1930
 
1836
1931
  // demos/table.tsx
1932
+ import { jsx as jsx23 } from "react/jsx-runtime";
1837
1933
  function TableApp() {
1838
1934
  const data2 = [
1839
1935
  { name: "Alice", role: "Engineer", status: "Active" },
1840
1936
  { name: "Bob", role: "Designer", status: "Active" },
1841
1937
  { name: "Charlie", role: "PM", status: "Away" }
1842
1938
  ];
1843
- return /* @__PURE__ */ React.createElement("box", { padding: 1, flexGrow: 1 }, /* @__PURE__ */ React.createElement(Table, { data: data2, headerColor: "cyan", borderColor: "#5e81ac" }));
1939
+ return /* @__PURE__ */ jsx23("box", { padding: 1, flexGrow: 1, children: /* @__PURE__ */ jsx23(Table, { data: data2, headerColor: "cyan", borderColor: "#5e81ac" }) });
1844
1940
  }
1845
1941
 
1846
1942
  // demos/spinner.tsx
1847
1943
  import { useKeyboard as useKeyboard3 } from "@gridland/utils";
1944
+ import { jsx as jsx24 } from "react/jsx-runtime";
1848
1945
  function SpinnerApp() {
1849
- return /* @__PURE__ */ React.createElement("box", { flexDirection: "column", flexGrow: 1 }, /* @__PURE__ */ React.createElement("box", { flexGrow: 1 }, /* @__PURE__ */ React.createElement(SpinnerPicker, { useKeyboard: useKeyboard3 })), /* @__PURE__ */ React.createElement("box", { paddingX: 1, paddingBottom: 1 }, /* @__PURE__ */ React.createElement(StatusBar, { items: [{ key: "\u2190\u2192", label: "change variant" }] })));
1946
+ return /* @__PURE__ */ jsx24("box", { flexDirection: "column", flexGrow: 1, children: /* @__PURE__ */ jsx24(SpinnerPicker, { useKeyboard: useKeyboard3 }) });
1850
1947
  }
1851
1948
 
1852
1949
  // demos/select-input.tsx
1853
1950
  import { useState as useState10 } from "react";
1854
1951
  import { useKeyboard as useKeyboard4 } from "@gridland/utils";
1952
+ import { jsx as jsx25, jsxs as jsxs17 } from "react/jsx-runtime";
1855
1953
  var items = [
1856
1954
  { label: "TypeScript", value: "ts" },
1857
1955
  { label: "JavaScript", value: "js" },
@@ -1867,24 +1965,28 @@ function SelectInputApp() {
1867
1965
  setResetKey((k) => k + 1);
1868
1966
  }
1869
1967
  });
1870
- return /* @__PURE__ */ React.createElement("box", { flexDirection: "column", flexGrow: 1, padding: 1 }, /* @__PURE__ */ React.createElement("box", { flexDirection: "column", flexGrow: 1 }, /* @__PURE__ */ React.createElement(
1871
- SelectInput,
1872
- {
1873
- key: resetKey,
1874
- items,
1875
- title: "Choose a language",
1876
- useKeyboard: useKeyboard4,
1877
- onSubmit: () => setSubmitted(true)
1878
- }
1879
- )), /* @__PURE__ */ React.createElement(StatusBar, { items: submitted ? [{ key: "r", label: "reset demo" }] : [
1880
- { key: "\u2191\u2193", label: "select" },
1881
- { key: "enter", label: "submit" }
1882
- ] }));
1968
+ return /* @__PURE__ */ jsxs17("box", { flexDirection: "column", flexGrow: 1, padding: 1, children: [
1969
+ /* @__PURE__ */ jsx25("box", { flexDirection: "column", flexGrow: 1, children: /* @__PURE__ */ jsx25(
1970
+ SelectInput,
1971
+ {
1972
+ items,
1973
+ title: "Choose a language",
1974
+ useKeyboard: useKeyboard4,
1975
+ onSubmit: () => setSubmitted(true)
1976
+ },
1977
+ resetKey
1978
+ ) }),
1979
+ /* @__PURE__ */ jsx25(StatusBar, { items: submitted ? [{ key: "r", label: "reset demo" }] : [
1980
+ { key: "\u2191\u2193", label: "select" },
1981
+ { key: "enter", label: "submit" }
1982
+ ] })
1983
+ ] });
1883
1984
  }
1884
1985
 
1885
1986
  // demos/multi-select.tsx
1886
1987
  import { useState as useState11 } from "react";
1887
1988
  import { useKeyboard as useKeyboard5 } from "@gridland/utils";
1989
+ import { jsx as jsx26, jsxs as jsxs18 } from "react/jsx-runtime";
1888
1990
  var items2 = [
1889
1991
  { label: "TypeScript", value: "ts" },
1890
1992
  { label: "JavaScript", value: "js" },
@@ -1900,26 +2002,30 @@ function MultiSelectApp() {
1900
2002
  setResetKey((k) => k + 1);
1901
2003
  }
1902
2004
  });
1903
- return /* @__PURE__ */ React.createElement("box", { flexDirection: "column", flexGrow: 1, padding: 1 }, /* @__PURE__ */ React.createElement("box", { flexDirection: "column", flexGrow: 1 }, /* @__PURE__ */ React.createElement(
1904
- MultiSelect,
1905
- {
1906
- key: resetKey,
1907
- items: items2,
1908
- title: "Select languages",
1909
- useKeyboard: useKeyboard5,
1910
- onSubmit: () => setSubmitted(true)
1911
- }
1912
- )), /* @__PURE__ */ React.createElement(StatusBar, { items: submitted ? [{ key: "r", label: "reset demo" }] : [
1913
- { key: "\u2191\u2193", label: "move" },
1914
- { key: "enter", label: "select" },
1915
- { key: "a", label: "all" },
1916
- { key: "x", label: "clear" }
1917
- ] }));
2005
+ return /* @__PURE__ */ jsxs18("box", { flexDirection: "column", flexGrow: 1, padding: 1, children: [
2006
+ /* @__PURE__ */ jsx26("box", { flexDirection: "column", flexGrow: 1, children: /* @__PURE__ */ jsx26(
2007
+ MultiSelect,
2008
+ {
2009
+ items: items2,
2010
+ title: "Select languages",
2011
+ useKeyboard: useKeyboard5,
2012
+ onSubmit: () => setSubmitted(true)
2013
+ },
2014
+ resetKey
2015
+ ) }),
2016
+ /* @__PURE__ */ jsx26(StatusBar, { items: submitted ? [{ key: "r", label: "reset demo" }] : [
2017
+ { key: "\u2191\u2193", label: "move" },
2018
+ { key: "enter", label: "select" },
2019
+ { key: "a", label: "all" },
2020
+ { key: "x", label: "clear" }
2021
+ ] })
2022
+ ] });
1918
2023
  }
1919
2024
 
1920
2025
  // demos/prompt-input.tsx
1921
2026
  import { useState as useState12 } from "react";
1922
2027
  import { useKeyboard as useKeyboard6 } from "@gridland/utils";
2028
+ import { jsx as jsx27, jsxs as jsxs19 } from "react/jsx-runtime";
1923
2029
  var commands = [{ cmd: "/model", desc: "Switch model" }];
1924
2030
  var files = ["src/index.ts", "src/routes.ts", "src/auth.ts", "package.json"];
1925
2031
  var models = [
@@ -1942,7 +2048,7 @@ function PromptInputApp() {
1942
2048
  setLastMessage(msg.text);
1943
2049
  };
1944
2050
  if (showModelPicker) {
1945
- return /* @__PURE__ */ React.createElement("box", { flexDirection: "column", flexGrow: 1, padding: 1 }, /* @__PURE__ */ React.createElement(Modal, { title: "Select Model", useKeyboard: useKeyboard6, onClose: () => setShowModelPicker(false) }, /* @__PURE__ */ React.createElement("box", { paddingX: 1 }, /* @__PURE__ */ React.createElement(
2051
+ return /* @__PURE__ */ jsx27("box", { flexDirection: "column", flexGrow: 1, padding: 1, children: /* @__PURE__ */ jsx27(Modal, { title: "Select Model", useKeyboard: useKeyboard6, onClose: () => setShowModelPicker(false), children: /* @__PURE__ */ jsx27("box", { paddingX: 1, children: /* @__PURE__ */ jsx27(
1946
2052
  SelectInput,
1947
2053
  {
1948
2054
  items: models,
@@ -1953,29 +2059,39 @@ function PromptInputApp() {
1953
2059
  setShowModelPicker(false);
1954
2060
  }
1955
2061
  }
1956
- ))));
2062
+ ) }) }) });
1957
2063
  }
1958
- return /* @__PURE__ */ React.createElement("box", { flexDirection: "column", flexGrow: 1, padding: 1 }, /* @__PURE__ */ React.createElement("box", { flexDirection: "column", flexGrow: 1 }, lastMessage ? /* @__PURE__ */ React.createElement("text", null, /* @__PURE__ */ React.createElement("span", { style: textStyle({ fg: theme.muted }) }, "Sent: "), /* @__PURE__ */ React.createElement("span", { style: textStyle({ fg: theme.foreground }) }, lastMessage)) : /* @__PURE__ */ React.createElement("text", null, /* @__PURE__ */ React.createElement("span", null, " "))), /* @__PURE__ */ React.createElement(
1959
- PromptInput,
1960
- {
1961
- key: resetKey,
1962
- commands,
1963
- files,
1964
- placeholder: "Message Claude...",
1965
- showDividers: true,
1966
- useKeyboard: useKeyboard6,
1967
- onSubmit: handleSubmit
1968
- }
1969
- ), /* @__PURE__ */ React.createElement("box", null, /* @__PURE__ */ React.createElement("text", null, /* @__PURE__ */ React.createElement("span", { style: textStyle({ dim: true }) }, "model: " + model))), /* @__PURE__ */ React.createElement("box", { paddingTop: 1, paddingLeft: 1, paddingBottom: 1 }, /* @__PURE__ */ React.createElement(StatusBar, { items: [
1970
- { key: "\u23CE enter", label: "send" },
1971
- { key: "/model", label: "change model" },
1972
- { key: "\u2191", label: "history" }
1973
- ] })));
2064
+ return /* @__PURE__ */ jsxs19("box", { flexDirection: "column", flexGrow: 1, padding: 1, children: [
2065
+ /* @__PURE__ */ jsx27("box", { flexDirection: "column", flexGrow: 1, children: lastMessage ? /* @__PURE__ */ jsxs19("text", { children: [
2066
+ /* @__PURE__ */ jsx27("span", { style: textStyle({ fg: theme.muted }), children: "Sent: " }),
2067
+ /* @__PURE__ */ jsx27("span", { style: textStyle({ fg: theme.foreground }), children: lastMessage })
2068
+ ] }) : /* @__PURE__ */ jsx27("text", { children: /* @__PURE__ */ jsx27("span", { children: " " }) }) }),
2069
+ /* @__PURE__ */ jsx27(
2070
+ PromptInput,
2071
+ {
2072
+ commands,
2073
+ files,
2074
+ placeholder: "Message Claude...",
2075
+ showDividers: true,
2076
+ autoFocus: true,
2077
+ useKeyboard: useKeyboard6,
2078
+ onSubmit: handleSubmit
2079
+ },
2080
+ resetKey
2081
+ ),
2082
+ /* @__PURE__ */ jsx27("box", { children: /* @__PURE__ */ jsx27("text", { children: /* @__PURE__ */ jsx27("span", { style: textStyle({ dim: true }), children: "model: " + model }) }) }),
2083
+ /* @__PURE__ */ jsx27("box", { paddingTop: 1, paddingLeft: 1, paddingBottom: 1, children: /* @__PURE__ */ jsx27(StatusBar, { items: [
2084
+ { key: "\u23CE enter", label: "send" },
2085
+ { key: "/model", label: "change model" },
2086
+ { key: "\u2191", label: "history" }
2087
+ ] }) })
2088
+ ] });
1974
2089
  }
1975
2090
 
1976
2091
  // demos/text-input.tsx
1977
2092
  import { useState as useState13 } from "react";
1978
2093
  import { useKeyboard as useKeyboard7 } from "@gridland/utils";
2094
+ import { jsx as jsx28, jsxs as jsxs20 } from "react/jsx-runtime";
1979
2095
  var FIELDS = [
1980
2096
  { label: "Username", placeholder: "enter your name", maxLength: 30, required: true },
1981
2097
  { label: "Email", placeholder: "user@example.com", maxLength: 50, required: true, description: "We'll never share your email" },
@@ -1989,26 +2105,30 @@ function TextInputApp() {
1989
2105
  if (event.name === "up") setActiveField((i) => Math.max(0, i - 1));
1990
2106
  if (event.name === "down") setActiveField((i) => Math.min(FIELDS.length - 1, i + 1));
1991
2107
  });
1992
- return /* @__PURE__ */ React.createElement("box", { flexDirection: "column", flexGrow: 1 }, /* @__PURE__ */ React.createElement("box", { flexDirection: "column", paddingX: 1, paddingTop: 1, flexGrow: 1 }, FIELDS.map((field, i) => /* @__PURE__ */ React.createElement("box", { key: field.label, marginBottom: 1 }, /* @__PURE__ */ React.createElement(
1993
- TextInput,
1994
- {
1995
- label: field.label,
1996
- placeholder: field.placeholder,
1997
- prompt: "> ",
1998
- focus: i === activeField,
1999
- maxLength: field.maxLength,
2000
- value: values[i],
2001
- onChange: (v) => setValues((prev) => prev.map((old, j) => j === i ? v : old)),
2002
- required: field.required,
2003
- disabled: field.disabled,
2004
- description: field.description
2005
- }
2006
- )))), /* @__PURE__ */ React.createElement("box", { paddingX: 1, paddingBottom: 1 }, /* @__PURE__ */ React.createElement(StatusBar, { items: [{ key: "\u2191\u2193", label: "field" }] })));
2108
+ return /* @__PURE__ */ jsxs20("box", { flexDirection: "column", flexGrow: 1, children: [
2109
+ /* @__PURE__ */ jsx28("box", { flexDirection: "column", paddingX: 1, paddingTop: 1, flexGrow: 1, children: FIELDS.map((field, i) => /* @__PURE__ */ jsx28("box", { marginBottom: 1, children: /* @__PURE__ */ jsx28(
2110
+ TextInput,
2111
+ {
2112
+ label: field.label,
2113
+ placeholder: field.placeholder,
2114
+ prompt: "> ",
2115
+ focus: i === activeField,
2116
+ maxLength: field.maxLength,
2117
+ value: values[i],
2118
+ onChange: (v) => setValues((prev) => prev.map((old, j) => j === i ? v : old)),
2119
+ required: field.required,
2120
+ disabled: field.disabled,
2121
+ description: field.description
2122
+ }
2123
+ ) }, field.label)) }),
2124
+ /* @__PURE__ */ jsx28("box", { paddingX: 1, paddingBottom: 1, children: /* @__PURE__ */ jsx28(StatusBar, { items: [{ key: "\u2191\u2193", label: "field" }] }) })
2125
+ ] });
2007
2126
  }
2008
2127
 
2009
2128
  // demos/link.tsx
2010
2129
  import { useState as useState14 } from "react";
2011
2130
  import { useKeyboard as useKeyboard8 } from "@gridland/utils";
2131
+ import { jsx as jsx29, jsxs as jsxs21 } from "react/jsx-runtime";
2012
2132
  var MODES = ["solid", "dashed", "dotted", "none"];
2013
2133
  function LinkApp() {
2014
2134
  const theme = useTheme();
@@ -2018,18 +2138,28 @@ function LinkApp() {
2018
2138
  if (event.name === "right") setModeIndex((i) => (i + 1) % MODES.length);
2019
2139
  if (event.name === "left") setModeIndex((i) => (i - 1 + MODES.length) % MODES.length);
2020
2140
  });
2021
- return /* @__PURE__ */ React.createElement("box", { flexDirection: "column", flexGrow: 1 }, /* @__PURE__ */ React.createElement("box", { padding: 1, flexGrow: 1 }, /* @__PURE__ */ React.createElement("text", { style: textStyle({ fg: theme.foreground }) }, "Made by ", /* @__PURE__ */ React.createElement("a", { href: "https://cjroth.com", style: { attributes: mode === "solid" ? 8 : mode === "dashed" ? 24 : mode === "dotted" ? 72 : 0, fg: theme.accent } }, "Chris Roth"), " and ", /* @__PURE__ */ React.createElement("a", { href: "https://jessicacheng.studio", style: { attributes: mode === "solid" ? 8 : mode === "dashed" ? 24 : mode === "dotted" ? 72 : 0, fg: theme.accent } }, "Jessica Cheng"), ".")), /* @__PURE__ */ React.createElement("box", { paddingX: 1, paddingBottom: 1 }, /* @__PURE__ */ React.createElement(
2022
- StatusBar,
2023
- {
2024
- extra: /* @__PURE__ */ React.createElement("span", { style: textStyle({ bold: true, fg: theme.foreground }) }, mode.padEnd(6)),
2025
- items: [{ key: "\u2190\u2192", label: "underline style" }]
2026
- }
2027
- )));
2141
+ return /* @__PURE__ */ jsxs21("box", { flexDirection: "column", flexGrow: 1, children: [
2142
+ /* @__PURE__ */ jsx29("box", { padding: 1, flexGrow: 1, children: /* @__PURE__ */ jsxs21("text", { style: textStyle({ fg: theme.foreground }), children: [
2143
+ "Made by ",
2144
+ /* @__PURE__ */ jsx29("a", { href: "https://cjroth.com", style: { attributes: mode === "solid" ? 8 : mode === "dashed" ? 24 : mode === "dotted" ? 72 : 0, fg: theme.accent }, children: "Chris Roth" }),
2145
+ " and ",
2146
+ /* @__PURE__ */ jsx29("a", { href: "https://jessicacheng.studio", style: { attributes: mode === "solid" ? 8 : mode === "dashed" ? 24 : mode === "dotted" ? 72 : 0, fg: theme.accent }, children: "Jessica Cheng" }),
2147
+ "."
2148
+ ] }) }),
2149
+ /* @__PURE__ */ jsx29("box", { paddingX: 1, paddingBottom: 1, children: /* @__PURE__ */ jsx29(
2150
+ StatusBar,
2151
+ {
2152
+ extra: /* @__PURE__ */ jsx29("span", { style: textStyle({ bold: true, fg: theme.foreground }), children: mode.padEnd(6) }),
2153
+ items: [{ key: "\u2190\u2192", label: "underline style" }]
2154
+ }
2155
+ ) })
2156
+ ] });
2028
2157
  }
2029
2158
 
2030
2159
  // demos/tabs.tsx
2031
2160
  import { useState as useState15 } from "react";
2032
2161
  import { useKeyboard as useKeyboard9 } from "@gridland/utils";
2162
+ import { jsx as jsx30, jsxs as jsxs22 } from "react/jsx-runtime";
2033
2163
  var tabs = ["Files", "Search", "Git", "Debug"];
2034
2164
  function TabBarApp() {
2035
2165
  const [selectedIndex, setSelectedIndex] = useState15(0);
@@ -2037,12 +2167,17 @@ function TabBarApp() {
2037
2167
  if (event.name === "left") setSelectedIndex((i) => i > 0 ? i - 1 : tabs.length - 1);
2038
2168
  if (event.name === "right") setSelectedIndex((i) => i < tabs.length - 1 ? i + 1 : 0);
2039
2169
  });
2040
- return /* @__PURE__ */ React.createElement("box", { flexDirection: "column", flexGrow: 1 }, /* @__PURE__ */ React.createElement("box", { padding: 1 }, /* @__PURE__ */ React.createElement(TabBar, { options: tabs, selectedIndex })), /* @__PURE__ */ React.createElement("box", { flexGrow: 1 }), /* @__PURE__ */ React.createElement("box", { paddingX: 1, paddingBottom: 1 }, /* @__PURE__ */ React.createElement(StatusBar, { items: [{ key: "\u2190\u2192", label: "switch tab" }] })));
2170
+ return /* @__PURE__ */ jsxs22("box", { flexDirection: "column", flexGrow: 1, children: [
2171
+ /* @__PURE__ */ jsx30("box", { padding: 1, children: /* @__PURE__ */ jsx30(TabBar, { options: tabs, selectedIndex }) }),
2172
+ /* @__PURE__ */ jsx30("box", { flexGrow: 1 }),
2173
+ /* @__PURE__ */ jsx30("box", { paddingX: 1, paddingBottom: 1, children: /* @__PURE__ */ jsx30(StatusBar, { items: [{ key: "\u2190\u2192", label: "switch tab" }] }) })
2174
+ ] });
2041
2175
  }
2042
2176
 
2043
2177
  // demos/status-bar.tsx
2044
2178
  import { useState as useState16 } from "react";
2045
2179
  import { useKeyboard as useKeyboard10 } from "@gridland/utils";
2180
+ import { jsx as jsx31, jsxs as jsxs23 } from "react/jsx-runtime";
2046
2181
  var shortcuts = [
2047
2182
  { key: "Tab", label: "switch focus" },
2048
2183
  { key: "\u2190\u2192", label: "cycle" },
@@ -2059,18 +2194,25 @@ function StatusBarApp() {
2059
2194
  else if (event.name === "b") setLastKey("back (b)");
2060
2195
  else if (event.name === "z") setLastKey("reset (z)");
2061
2196
  });
2062
- return /* @__PURE__ */ React.createElement("box", { flexDirection: "column", gap: 1, padding: 1 }, lastKey ? /* @__PURE__ */ React.createElement("text", null, /* @__PURE__ */ React.createElement("span", null, "Pressed: "), /* @__PURE__ */ React.createElement("span", { style: textStyle({ bold: true, fg: theme.accent }) }, lastKey)) : /* @__PURE__ */ React.createElement("text", { style: textStyle({ dim: true }) }, "Press a key to trigger an action"), /* @__PURE__ */ React.createElement(
2063
- StatusBar,
2064
- {
2065
- items: shortcuts,
2066
- extra: /* @__PURE__ */ React.createElement("span", { style: textStyle({ fg: theme.success }) }, "\u25CF Ready")
2067
- }
2068
- ));
2197
+ return /* @__PURE__ */ jsxs23("box", { flexDirection: "column", gap: 1, padding: 1, children: [
2198
+ lastKey ? /* @__PURE__ */ jsxs23("text", { children: [
2199
+ /* @__PURE__ */ jsx31("span", { children: "Pressed: " }),
2200
+ /* @__PURE__ */ jsx31("span", { style: textStyle({ bold: true, fg: theme.accent }), children: lastKey })
2201
+ ] }) : /* @__PURE__ */ jsx31("text", { style: textStyle({ dim: true }), children: "Press a key to trigger an action" }),
2202
+ /* @__PURE__ */ jsx31(
2203
+ StatusBar,
2204
+ {
2205
+ items: shortcuts,
2206
+ extra: /* @__PURE__ */ jsx31("span", { style: textStyle({ fg: theme.success }), children: "\u25CF Ready" })
2207
+ }
2208
+ )
2209
+ ] });
2069
2210
  }
2070
2211
 
2071
2212
  // demos/modal.tsx
2072
2213
  import { useState as useState17 } from "react";
2073
2214
  import { useKeyboard as useKeyboard11 } from "@gridland/utils";
2215
+ import { jsx as jsx32, jsxs as jsxs24 } from "react/jsx-runtime";
2074
2216
  function ModalApp() {
2075
2217
  const theme = useTheme();
2076
2218
  const [isOpen, setIsOpen] = useState17(false);
@@ -2079,30 +2221,50 @@ function ModalApp() {
2079
2221
  if (isOpen && (event.name === "q" || event.name === "escape")) setIsOpen(false);
2080
2222
  });
2081
2223
  if (isOpen) {
2082
- return /* @__PURE__ */ React.createElement("box", { flexDirection: "column", flexGrow: 1 }, /* @__PURE__ */ React.createElement(Modal, { title: "Example Modal", useKeyboard: useKeyboard11, onClose: () => setIsOpen(false) }, /* @__PURE__ */ React.createElement("box", { paddingX: 1, flexDirection: "column" }, /* @__PURE__ */ React.createElement("text", { style: textStyle({ fg: theme.foreground }) }, "This is a modal overlay component."), /* @__PURE__ */ React.createElement("text", null, " "), /* @__PURE__ */ React.createElement("text", { style: textStyle({ dim: true, fg: theme.muted }) }, "It stretches to fill the full terminal height."))), /* @__PURE__ */ React.createElement("box", { paddingX: 1, paddingBottom: 1 }, /* @__PURE__ */ React.createElement(StatusBar, { items: [{ key: "q", label: "close" }] })));
2224
+ return /* @__PURE__ */ jsxs24("box", { flexDirection: "column", flexGrow: 1, children: [
2225
+ /* @__PURE__ */ jsx32(Modal, { title: "Example Modal", useKeyboard: useKeyboard11, onClose: () => setIsOpen(false), children: /* @__PURE__ */ jsxs24("box", { paddingX: 1, flexDirection: "column", children: [
2226
+ /* @__PURE__ */ jsx32("text", { style: textStyle({ fg: theme.foreground }), children: "This is a modal overlay component." }),
2227
+ /* @__PURE__ */ jsx32("text", { children: " " }),
2228
+ /* @__PURE__ */ jsx32("text", { style: textStyle({ dim: true, fg: theme.muted }), children: "It stretches to fill the full terminal height." })
2229
+ ] }) }),
2230
+ /* @__PURE__ */ jsx32("box", { paddingX: 1, paddingBottom: 1, children: /* @__PURE__ */ jsx32(StatusBar, { items: [{ key: "q", label: "close" }] }) })
2231
+ ] });
2083
2232
  }
2084
- return /* @__PURE__ */ React.createElement("box", { flexDirection: "column", flexGrow: 1, alignItems: "center", justifyContent: "center" }, /* @__PURE__ */ React.createElement("text", null, /* @__PURE__ */ React.createElement("span", { style: textStyle({ dim: true, fg: theme.muted }) }, "Press "), /* @__PURE__ */ React.createElement("span", { style: textStyle({ bold: true, fg: theme.background, bg: theme.muted }) }, " m "), /* @__PURE__ */ React.createElement("span", { style: textStyle({ dim: true, fg: theme.muted }) }, " to open modal")));
2233
+ return /* @__PURE__ */ jsx32("box", { flexDirection: "column", flexGrow: 1, alignItems: "center", justifyContent: "center", children: /* @__PURE__ */ jsxs24("text", { children: [
2234
+ /* @__PURE__ */ jsx32("span", { style: textStyle({ dim: true, fg: theme.muted }), children: "Press " }),
2235
+ /* @__PURE__ */ jsx32("span", { style: textStyle({ bold: true, fg: theme.background, bg: theme.muted }), children: " m " }),
2236
+ /* @__PURE__ */ jsx32("span", { style: textStyle({ dim: true, fg: theme.muted }), children: " to open modal" })
2237
+ ] }) });
2085
2238
  }
2086
2239
 
2087
2240
  // demos/primitives.tsx
2241
+ import { jsx as jsx33, jsxs as jsxs25 } from "react/jsx-runtime";
2088
2242
  function PrimitivesApp() {
2089
- return /* @__PURE__ */ React.createElement("box", { flexDirection: "column", padding: 1 }, /* @__PURE__ */ React.createElement(
2090
- "box",
2091
- {
2092
- border: true,
2093
- borderStyle: "rounded",
2094
- borderColor: "#75715e",
2095
- title: "Layout",
2096
- titleAlignment: "center",
2097
- padding: 1
2098
- },
2099
- /* @__PURE__ */ React.createElement("box", { flexDirection: "row", gap: 2 }, /* @__PURE__ */ React.createElement("box", { border: true, borderStyle: "single", borderColor: "#a6e22e", padding: 1, flexGrow: 1 }, /* @__PURE__ */ React.createElement("text", { fg: "#a6e22e", bold: true }, "Box 1")), /* @__PURE__ */ React.createElement("box", { border: true, borderStyle: "single", borderColor: "#f92672", padding: 1, flexGrow: 1 }, /* @__PURE__ */ React.createElement("text", { fg: "#f92672", bold: true }, "Box 2")), /* @__PURE__ */ React.createElement("box", { border: true, borderStyle: "single", borderColor: "#66d9ef", padding: 1, flexGrow: 1 }, /* @__PURE__ */ React.createElement("text", { fg: "#66d9ef", bold: true }, "Box 3")))
2100
- ), /* @__PURE__ */ React.createElement("text", { dim: true, fg: "#75715e" }, " Nested boxes with borders, colors & flexbox layout"));
2243
+ return /* @__PURE__ */ jsxs25("box", { flexDirection: "column", padding: 1, children: [
2244
+ /* @__PURE__ */ jsx33(
2245
+ "box",
2246
+ {
2247
+ border: true,
2248
+ borderStyle: "rounded",
2249
+ borderColor: "#75715e",
2250
+ title: "Layout",
2251
+ titleAlignment: "center",
2252
+ padding: 1,
2253
+ children: /* @__PURE__ */ jsxs25("box", { flexDirection: "row", gap: 2, children: [
2254
+ /* @__PURE__ */ jsx33("box", { border: true, borderStyle: "single", borderColor: "#a6e22e", padding: 1, flexGrow: 1, children: /* @__PURE__ */ jsx33("text", { fg: "#a6e22e", bold: true, children: "Box 1" }) }),
2255
+ /* @__PURE__ */ jsx33("box", { border: true, borderStyle: "single", borderColor: "#f92672", padding: 1, flexGrow: 1, children: /* @__PURE__ */ jsx33("text", { fg: "#f92672", bold: true, children: "Box 2" }) }),
2256
+ /* @__PURE__ */ jsx33("box", { border: true, borderStyle: "single", borderColor: "#66d9ef", padding: 1, flexGrow: 1, children: /* @__PURE__ */ jsx33("text", { fg: "#66d9ef", bold: true, children: "Box 3" }) })
2257
+ ] })
2258
+ }
2259
+ ),
2260
+ /* @__PURE__ */ jsx33("text", { dim: true, fg: "#75715e", children: " Nested boxes with borders, colors & flexbox layout" })
2261
+ ] });
2101
2262
  }
2102
2263
 
2103
2264
  // demos/chat.tsx
2104
2265
  import { useState as useState18, useCallback as useCallback3, useRef as useRef5 } from "react";
2105
2266
  import { useKeyboard as useKeyboard12 } from "@gridland/utils";
2267
+ import { jsx as jsx34 } from "react/jsx-runtime";
2106
2268
  var initialMessages = [
2107
2269
  { id: "1", role: "user", content: "Show me my portfolio" },
2108
2270
  { id: "2", role: "assistant", content: "Here's your current portfolio allocation:" },
@@ -2153,7 +2315,7 @@ function ChatApp() {
2153
2315
  setStreamingText("");
2154
2316
  setActiveToolCalls([]);
2155
2317
  }, []);
2156
- return /* @__PURE__ */ React.createElement(
2318
+ return /* @__PURE__ */ jsx34(
2157
2319
  ChatPanel,
2158
2320
  {
2159
2321
  messages,
@@ -2171,6 +2333,7 @@ function ChatApp() {
2171
2333
  // demos/chain-of-thought.tsx
2172
2334
  import { useState as useState19, useEffect as useEffect4, useRef as useRef6 } from "react";
2173
2335
  import { useKeyboard as useKeyboard13 } from "@gridland/utils";
2336
+ import { jsx as jsx35, jsxs as jsxs26 } from "react/jsx-runtime";
2174
2337
  var ALL_STEPS = [
2175
2338
  { tool: "Read", label: "Reading codebase", description: "src/", status: "done", delay: 1800 },
2176
2339
  { tool: "Think", label: "Planning changes", description: "auth module", status: "done", delay: 2500 },
@@ -2220,25 +2383,32 @@ function ChainOfThoughtApp() {
2220
2383
  const elapsedMs = ALL_STEPS.slice(0, stepIndex).reduce((sum, s) => sum + s.delay, 0);
2221
2384
  const totalMs = ALL_STEPS.reduce((sum, s) => sum + s.delay, 0);
2222
2385
  const durationStr = phase === "done" ? `${(totalMs / 1e3).toFixed(1)}s` : `${(elapsedMs / 1e3).toFixed(1)}s`;
2223
- return /* @__PURE__ */ React.createElement("box", { flexDirection: "column", flexGrow: 1 }, /* @__PURE__ */ React.createElement("box", { flexDirection: "column", padding: 1, flexGrow: 1 }, /* @__PURE__ */ React.createElement(ChainOfThought, { open: expanded, onOpenChange: setExpanded }, /* @__PURE__ */ React.createElement(ChainOfThoughtHeader, { duration: durationStr }), /* @__PURE__ */ React.createElement(ChainOfThoughtContent, null, steps.map((step, i) => /* @__PURE__ */ React.createElement(
2224
- ChainOfThoughtStep,
2225
- {
2226
- key: i,
2227
- label: step.label,
2228
- description: step.description,
2229
- status: step.status,
2230
- isLast: i === steps.length - 1
2231
- },
2232
- step.output
2233
- ))))), /* @__PURE__ */ React.createElement("box", { paddingX: 1, paddingBottom: 1 }, /* @__PURE__ */ React.createElement(StatusBar, { items: [
2234
- { key: "ctrl+shift+e", label: "toggle" },
2235
- { key: "r", label: "restart" }
2236
- ] })));
2386
+ return /* @__PURE__ */ jsxs26("box", { flexDirection: "column", flexGrow: 1, children: [
2387
+ /* @__PURE__ */ jsx35("box", { flexDirection: "column", padding: 1, flexGrow: 1, children: /* @__PURE__ */ jsxs26(ChainOfThought, { open: expanded, onOpenChange: setExpanded, children: [
2388
+ /* @__PURE__ */ jsx35(ChainOfThoughtHeader, { duration: durationStr }),
2389
+ /* @__PURE__ */ jsx35(ChainOfThoughtContent, { children: steps.map((step, i) => /* @__PURE__ */ jsx35(
2390
+ ChainOfThoughtStep,
2391
+ {
2392
+ label: step.label,
2393
+ description: step.description,
2394
+ status: step.status,
2395
+ isLast: i === steps.length - 1,
2396
+ children: step.output
2397
+ },
2398
+ i
2399
+ )) })
2400
+ ] }) }),
2401
+ /* @__PURE__ */ jsx35("box", { paddingX: 1, paddingBottom: 1, children: /* @__PURE__ */ jsx35(StatusBar, { items: [
2402
+ { key: "ctrl+shift+e", label: "toggle" },
2403
+ { key: "r", label: "restart" }
2404
+ ] }) })
2405
+ ] });
2237
2406
  }
2238
2407
 
2239
2408
  // demos/message.tsx
2240
2409
  import { useState as useState20, useEffect as useEffect5, useRef as useRef7 } from "react";
2241
2410
  import { useKeyboard as useKeyboard14 } from "@gridland/utils";
2411
+ import { jsx as jsx36, jsxs as jsxs27 } from "react/jsx-runtime";
2242
2412
  var RESPONSE = "I've refactored the auth module. The changes include extracting the token validation into a shared helper, consolidating the middleware chain, and updating the test suite to match.";
2243
2413
  function MessageApp() {
2244
2414
  const [phase, setPhase] = useState20("idle");
@@ -2284,37 +2454,54 @@ function MessageApp() {
2284
2454
  const isStreaming = phase === "streaming";
2285
2455
  const isDone = phase === "done";
2286
2456
  const showAssistant = phase !== "idle";
2287
- return /* @__PURE__ */ React.createElement("box", { flexDirection: "column", flexGrow: 1 }, /* @__PURE__ */ React.createElement("box", { flexDirection: "column", padding: 1, gap: 1, flexGrow: 1 }, /* @__PURE__ */ React.createElement(Message, { role: "user" }, /* @__PURE__ */ React.createElement(Message.Content, null, /* @__PURE__ */ React.createElement(Message.Text, null, "Can you refactor the auth module?"))), showAssistant && /* @__PURE__ */ React.createElement(Message, { role: "assistant", isStreaming }, /* @__PURE__ */ React.createElement(Message.Content, null, /* @__PURE__ */ React.createElement(Message.Text, { isLast: true }, isDone ? RESPONSE : streamedText)))), /* @__PURE__ */ React.createElement("box", { paddingX: 1, paddingBottom: 1 }, /* @__PURE__ */ React.createElement(StatusBar, { items: [{ key: "r", label: "restart" }] })));
2457
+ return /* @__PURE__ */ jsxs27("box", { flexDirection: "column", flexGrow: 1, children: [
2458
+ /* @__PURE__ */ jsxs27("box", { flexDirection: "column", padding: 1, gap: 1, flexGrow: 1, children: [
2459
+ /* @__PURE__ */ jsx36(Message, { role: "user", children: /* @__PURE__ */ jsx36(Message.Content, { children: /* @__PURE__ */ jsx36(Message.Text, { children: "Can you refactor the auth module?" }) }) }),
2460
+ showAssistant && /* @__PURE__ */ jsx36(Message, { role: "assistant", isStreaming, children: /* @__PURE__ */ jsx36(Message.Content, { children: /* @__PURE__ */ jsx36(Message.Text, { isLast: true, children: isDone ? RESPONSE : streamedText }) }) })
2461
+ ] }),
2462
+ /* @__PURE__ */ jsx36("box", { paddingX: 1, paddingBottom: 1, children: /* @__PURE__ */ jsx36(StatusBar, { items: [{ key: "r", label: "restart" }] }) })
2463
+ ] });
2288
2464
  }
2289
2465
 
2290
2466
  // demos/terminal-window.tsx
2467
+ import { jsx as jsx37, jsxs as jsxs28 } from "react/jsx-runtime";
2291
2468
  function TerminalWindowApp() {
2292
2469
  const theme = useTheme();
2293
- return /* @__PURE__ */ React.createElement("box", { flexDirection: "column", padding: 1 }, /* @__PURE__ */ React.createElement("text", { style: textStyle({ fg: theme.secondary }) }, '$ echo "Hello from TerminalWindow"'), /* @__PURE__ */ React.createElement("text", { style: textStyle({ fg: theme.foreground }) }, "Hello from TerminalWindow"), /* @__PURE__ */ React.createElement("text", { style: textStyle({ fg: theme.secondary }) }, "$ _"));
2470
+ return /* @__PURE__ */ jsxs28("box", { flexDirection: "column", padding: 1, children: [
2471
+ /* @__PURE__ */ jsx37("text", { style: textStyle({ fg: theme.secondary }), children: '$ echo "Hello from TerminalWindow"' }),
2472
+ /* @__PURE__ */ jsx37("text", { style: textStyle({ fg: theme.foreground }), children: "Hello from TerminalWindow" }),
2473
+ /* @__PURE__ */ jsx37("text", { style: textStyle({ fg: theme.secondary }), children: "$ _" })
2474
+ ] });
2294
2475
  }
2295
2476
 
2296
2477
  // demos/focus.tsx
2297
- import { useState as useState21, useRef as useRef8 } from "react";
2298
- import { useKeyboard as useKeyboard15 } from "@gridland/utils";
2299
- var focusPanels = [
2478
+ import { useRef as useRef8, useCallback as useCallback4 } from "react";
2479
+ import { useKeyboard as useKeyboard15, useFocus, FocusProvider, useShortcuts, useFocusedShortcuts } from "@gridland/utils";
2480
+ import { jsx as jsx38, jsxs as jsxs29 } from "react/jsx-runtime";
2481
+ var focusMultiSelects = [
2300
2482
  {
2301
- label: "Language",
2483
+ id: "language",
2484
+ title: "Language",
2302
2485
  items: [
2303
2486
  { label: "TypeScript", value: "ts" },
2304
2487
  { label: "JavaScript", value: "js" },
2305
- { label: "Python", value: "py" }
2488
+ { label: "Python", value: "py" },
2489
+ { label: "Rust", value: "rs" }
2306
2490
  ]
2307
2491
  },
2308
2492
  {
2309
- label: "Framework",
2493
+ id: "framework",
2494
+ title: "Framework",
2310
2495
  items: [
2311
2496
  { label: "React", value: "react" },
2312
2497
  { label: "Vue", value: "vue" },
2313
- { label: "Svelte", value: "svelte" }
2498
+ { label: "Svelte", value: "svelte" },
2499
+ { label: "Solid", value: "solid" }
2314
2500
  ]
2315
2501
  },
2316
2502
  {
2317
- label: "Runtime",
2503
+ id: "runtime",
2504
+ title: "Runtime",
2318
2505
  items: [
2319
2506
  { label: "Bun", value: "bun" },
2320
2507
  { label: "Node", value: "node" },
@@ -2322,101 +2509,168 @@ var focusPanels = [
2322
2509
  ]
2323
2510
  }
2324
2511
  ];
2325
- function FocusApp() {
2326
- const [panelIndex, setPanelIndex] = useState21(0);
2327
- const [entered, setEntered] = useState21(false);
2328
- const [cursors, setCursors] = useState21([0, 0, 0]);
2329
- const [selections, setSelections] = useState21([null, null, null]);
2330
- const panelRef = useRef8(0);
2331
- const enteredRef = useRef8(false);
2332
- const cursorsRef = useRef8([0, 0, 0]);
2333
- panelRef.current = panelIndex;
2334
- enteredRef.current = entered;
2335
- cursorsRef.current = cursors;
2512
+ function FocusMultiSelectPanel({ id, title, items: items3, autoFocus }) {
2513
+ const { isFocused, isSelected, isAnySelected, focusId, focusRef } = useFocus({ id, autoFocus });
2514
+ const multiSelectHandlerRef = useRef8(null);
2515
+ const captureKeyboard = useCallback4((handler) => {
2516
+ multiSelectHandlerRef.current = handler;
2517
+ }, []);
2336
2518
  useKeyboard15((event) => {
2337
- const pi = panelRef.current;
2338
- if (enteredRef.current) {
2339
- const items3 = focusPanels[pi].items;
2340
- const cur = cursorsRef.current[pi];
2341
- if (event.name === "down" || event.name === "j") {
2342
- const next = (cur + 1) % items3.length;
2343
- cursorsRef.current = [...cursorsRef.current];
2344
- cursorsRef.current[pi] = next;
2345
- setCursors([...cursorsRef.current]);
2346
- } else if (event.name === "up" || event.name === "k") {
2347
- const next = (cur - 1 + items3.length) % items3.length;
2348
- cursorsRef.current = [...cursorsRef.current];
2349
- cursorsRef.current[pi] = next;
2350
- setCursors([...cursorsRef.current]);
2351
- } else if (event.name === "return") {
2352
- const selected = items3[cursorsRef.current[pi]].label;
2353
- setSelections((s) => {
2354
- const n = [...s];
2355
- n[pi] = selected;
2356
- return n;
2357
- });
2358
- enteredRef.current = false;
2359
- setEntered(false);
2360
- } else if (event.name === "escape") {
2361
- enteredRef.current = false;
2362
- setEntered(false);
2363
- }
2364
- } else {
2365
- if (event.name === "right" || event.name === "tab") {
2366
- const next = (pi + 1) % focusPanels.length;
2367
- panelRef.current = next;
2368
- setPanelIndex(next);
2369
- } else if (event.name === "left") {
2370
- const next = (pi - 1 + focusPanels.length) % focusPanels.length;
2371
- panelRef.current = next;
2372
- setPanelIndex(next);
2373
- } else if (event.name === "return") {
2374
- enteredRef.current = true;
2375
- setEntered(true);
2376
- }
2519
+ multiSelectHandlerRef.current?.(event);
2520
+ }, { focusId, selectedOnly: true });
2521
+ useShortcuts(
2522
+ isSelected ? [{ key: "\u2191\u2193", label: "move" }, { key: "enter", label: "toggle" }, { key: "esc", label: "back" }] : [{ key: "\u2190\u2192", label: "navigate" }, { key: "tab", label: "cycle" }, { key: "enter", label: "select" }],
2523
+ focusId
2524
+ );
2525
+ const borderStyle = isSelected ? "rounded" : isFocused ? "dashed" : "rounded";
2526
+ const borderColor = isSelected ? "#818cf8" : isAnySelected ? "transparent" : isFocused ? "#6366f1" : "#3b3466";
2527
+ return /* @__PURE__ */ jsx38("box", { ref: focusRef, border: true, borderStyle, borderColor, flexGrow: 1, children: /* @__PURE__ */ jsx38("box", { flexDirection: "column", paddingX: 1, children: /* @__PURE__ */ jsx38(
2528
+ MultiSelect,
2529
+ {
2530
+ items: items3,
2531
+ title,
2532
+ allowEmpty: true,
2533
+ enableSelectAll: false,
2534
+ enableClear: false,
2535
+ highlightColor: isSelected ? "#a5b4fc" : "#6366f1",
2536
+ checkboxColor: "#818cf8",
2537
+ useKeyboard: captureKeyboard
2377
2538
  }
2378
- event.preventDefault();
2379
- });
2380
- return /* @__PURE__ */ React.createElement("box", { flexDirection: "column", flexGrow: 1 }, /* @__PURE__ */ React.createElement("box", { flexDirection: "row", gap: 1, padding: 1, flexGrow: 1 }, focusPanels.map((panel, i) => {
2381
- const focused = i === panelIndex;
2382
- const active = focused && entered;
2383
- const selected = selections[i];
2384
- return /* @__PURE__ */ React.createElement(
2385
- "box",
2539
+ ) }) });
2540
+ }
2541
+ function FocusNavStatusBar() {
2542
+ const shortcuts2 = useFocusedShortcuts();
2543
+ return /* @__PURE__ */ jsx38("box", { paddingX: 1, paddingBottom: 1, children: /* @__PURE__ */ jsx38(StatusBar, { items: shortcuts2 }) });
2544
+ }
2545
+ function FocusApp() {
2546
+ return /* @__PURE__ */ jsx38(FocusProvider, { selectable: true, children: /* @__PURE__ */ jsxs29("box", { flexDirection: "column", flexGrow: 1, children: [
2547
+ /* @__PURE__ */ jsx38("box", { flexDirection: "row", gap: 1, padding: 1, flexGrow: 1, children: focusMultiSelects.map((panel, i) => /* @__PURE__ */ jsx38(
2548
+ FocusMultiSelectPanel,
2386
2549
  {
2387
- key: panel.label,
2388
- border: true,
2389
- borderStyle: "rounded",
2390
- borderColor: active ? "#22c55e" : focused ? "#3b82f6" : "#555",
2391
- flexGrow: 1
2550
+ id: panel.id,
2551
+ title: panel.title,
2552
+ items: panel.items,
2553
+ autoFocus: i === 0
2392
2554
  },
2393
- /* @__PURE__ */ React.createElement("box", { flexDirection: "column", padding: 1 }, /* @__PURE__ */ React.createElement("text", { style: {
2394
- fg: active ? "#22c55e" : focused ? "#3b82f6" : "#888",
2395
- bold: focused
2396
- } }, focused ? "\u25B8 " : " ", panel.label, selected ? `: ${selected}` : ""), (active || !entered && focused) && /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement("box", { height: 1 }), /* @__PURE__ */ React.createElement("box", { flexDirection: "column" }, panel.items.map((item, j) => {
2397
- const highlighted = active && j === cursors[i];
2398
- return /* @__PURE__ */ React.createElement("text", { key: item.value, style: {
2399
- fg: highlighted ? "#22c55e" : active ? "#ccc" : "#666",
2400
- bold: highlighted
2401
- } }, highlighted ? " \u25B8 " : " ", item.label);
2402
- }))))
2403
- );
2404
- })), /* @__PURE__ */ React.createElement("box", { paddingX: 1, paddingBottom: 1 }, /* @__PURE__ */ React.createElement(
2405
- StatusBar,
2555
+ panel.id
2556
+ )) }),
2557
+ /* @__PURE__ */ jsx38(FocusNavStatusBar, {})
2558
+ ] }) });
2559
+ }
2560
+
2561
+ // demos/focus-grid.tsx
2562
+ import { useFocus as useFocus2, FocusProvider as FocusProvider2, useShortcuts as useShortcuts2, useFocusedShortcuts as useFocusedShortcuts2 } from "@gridland/utils";
2563
+ import { jsx as jsx39, jsxs as jsxs30 } from "react/jsx-runtime";
2564
+ var gridItems = [
2565
+ { id: "cell-1" },
2566
+ { id: "cell-2" },
2567
+ { id: "cell-3" },
2568
+ { id: "cell-4" },
2569
+ { id: "cell-5" },
2570
+ { id: "cell-6" }
2571
+ ];
2572
+ function GridCell({ id, autoFocus }) {
2573
+ const { isFocused, isSelected, isAnySelected, focusId, focusRef } = useFocus2({ id, autoFocus });
2574
+ useShortcuts2(
2575
+ isSelected ? [{ key: "esc", label: "back" }] : [{ key: "\u2191\u2193\u2190\u2192", label: "navigate" }, { key: "enter", label: "select" }, { key: "tab", label: "cycle" }],
2576
+ focusId
2577
+ );
2578
+ const borderStyle = isSelected ? "rounded" : isFocused ? "dashed" : "rounded";
2579
+ const borderColor = isSelected ? "#818cf8" : isAnySelected ? "transparent" : isFocused ? "#6366f1" : "#3b3466";
2580
+ const fg = isSelected ? "#a5b4fc" : isFocused ? "#a5b4fc" : "#888";
2581
+ return /* @__PURE__ */ jsx39("box", { ref: focusRef, border: true, borderStyle, borderColor, width: 16, height: 5, children: /* @__PURE__ */ jsx39("box", { flexDirection: "column", alignItems: "center", justifyContent: "center", flexGrow: 1, children: /* @__PURE__ */ jsx39("text", { style: { fg, bold: isFocused || isSelected }, children: isSelected ? "selected" : "not selected" }) }) });
2582
+ }
2583
+ function FocusGridStatusBar() {
2584
+ const shortcuts2 = useFocusedShortcuts2();
2585
+ return /* @__PURE__ */ jsx39("box", { paddingX: 1, paddingBottom: 1, children: /* @__PURE__ */ jsx39(StatusBar, { items: shortcuts2 }) });
2586
+ }
2587
+ function FocusGridApp() {
2588
+ return /* @__PURE__ */ jsx39(FocusProvider2, { selectable: true, children: /* @__PURE__ */ jsxs30("box", { flexDirection: "column", flexGrow: 1, children: [
2589
+ /* @__PURE__ */ jsxs30("box", { flexDirection: "column", gap: 1, padding: 1, flexGrow: 1, alignItems: "center", children: [
2590
+ /* @__PURE__ */ jsx39("box", { flexDirection: "row", gap: 1, children: gridItems.slice(0, 3).map((item, i) => /* @__PURE__ */ jsx39(GridCell, { ...item, autoFocus: i === 0 }, item.id)) }),
2591
+ /* @__PURE__ */ jsx39("box", { flexDirection: "row", gap: 1, children: gridItems.slice(3, 6).map((item) => /* @__PURE__ */ jsx39(GridCell, { ...item }, item.id)) })
2592
+ ] }),
2593
+ /* @__PURE__ */ jsx39(FocusGridStatusBar, {})
2594
+ ] }) });
2595
+ }
2596
+
2597
+ // demos/focus-chat.tsx
2598
+ import { useState as useState22, useRef as useRef9, useCallback as useCallback5 } from "react";
2599
+ import { useKeyboard as useKeyboard16, useFocus as useFocus3, FocusProvider as FocusProvider3, useShortcuts as useShortcuts3, useFocusedShortcuts as useFocusedShortcuts3 } from "@gridland/utils";
2600
+ import { jsx as jsx40, jsxs as jsxs31 } from "react/jsx-runtime";
2601
+ function CotSection() {
2602
+ const { isFocused, isSelected, isAnySelected, focusId, focusRef } = useFocus3({ id: "cot", autoFocus: true });
2603
+ const [cotOpen, setCotOpen] = useState22(false);
2604
+ useKeyboard16((event) => {
2605
+ if (event.name === "return") {
2606
+ setCotOpen((v) => !v);
2607
+ event.preventDefault();
2608
+ }
2609
+ }, { focusId, selectedOnly: true });
2610
+ useShortcuts3(
2611
+ isSelected ? [{ key: "enter", label: "expand/collapse" }, { key: "esc", label: "back" }] : [{ key: "\u2191\u2193", label: "navigate" }, { key: "tab", label: "cycle" }, { key: "enter", label: "select" }],
2612
+ focusId
2613
+ );
2614
+ const borderStyle = isSelected ? "rounded" : isFocused ? "dashed" : "rounded";
2615
+ const borderColor = isSelected ? "#818cf8" : isAnySelected ? "transparent" : isFocused ? "#6366f1" : "#3b3466";
2616
+ return /* @__PURE__ */ jsx40("box", { ref: focusRef, marginTop: 1, border: true, borderStyle, borderColor, children: /* @__PURE__ */ jsxs31(ChainOfThought, { open: cotOpen, onOpenChange: setCotOpen, children: [
2617
+ /* @__PURE__ */ jsx40(ChainOfThoughtHeader, { duration: "1.2s" }),
2618
+ /* @__PURE__ */ jsx40(ChainOfThoughtContent, { children: /* @__PURE__ */ jsx40(ChainOfThoughtStep, { label: "Searched docs", status: "done", isLast: true }) })
2619
+ ] }) });
2620
+ }
2621
+ function PromptSection() {
2622
+ const { isFocused, isSelected, isAnySelected, focusId, focusRef } = useFocus3({ id: "prompt" });
2623
+ const promptHandlerRef = useRef9(null);
2624
+ const captureKeyboard = useCallback5((handler) => {
2625
+ promptHandlerRef.current = handler;
2626
+ }, []);
2627
+ useKeyboard16((event) => {
2628
+ promptHandlerRef.current?.(event);
2629
+ }, { focusId, selectedOnly: true });
2630
+ useShortcuts3(
2631
+ isSelected ? [{ key: "\u23CE", label: "send" }, { key: "esc", label: "back" }] : [{ key: "\u2191\u2193", label: "navigate" }, { key: "tab", label: "cycle" }, { key: "enter", label: "select" }],
2632
+ focusId
2633
+ );
2634
+ const dividerColor = isSelected ? "#818cf8" : isAnySelected ? void 0 : isFocused ? "#6366f1" : "#3b3466";
2635
+ const dividerDashed = isFocused && !isSelected && !isAnySelected;
2636
+ return /* @__PURE__ */ jsx40("box", { ref: focusRef, children: /* @__PURE__ */ jsx40(
2637
+ PromptInput,
2406
2638
  {
2407
- items: entered ? [{ key: "\u2191\u2193", label: "select" }, { key: "enter", label: "confirm" }, { key: "esc", label: "back" }] : [{ key: "\u2190\u2192", label: "navigate" }, { key: "enter", label: "select" }, { key: "tab", label: "next" }]
2639
+ placeholder: "Type a message...",
2640
+ status: "ready",
2641
+ dividerColor,
2642
+ dividerDashed,
2643
+ useKeyboard: captureKeyboard
2408
2644
  }
2409
- )));
2645
+ ) });
2646
+ }
2647
+ function FocusChatStatusBar() {
2648
+ const shortcuts2 = useFocusedShortcuts3();
2649
+ return /* @__PURE__ */ jsx40("box", { paddingX: 1, paddingBottom: 1, children: /* @__PURE__ */ jsx40(StatusBar, { items: shortcuts2 }) });
2650
+ }
2651
+ function FocusChatApp() {
2652
+ return /* @__PURE__ */ jsx40(FocusProvider3, { selectable: true, children: /* @__PURE__ */ jsxs31("box", { flexDirection: "column", flexGrow: 1, children: [
2653
+ /* @__PURE__ */ jsxs31("box", { flexDirection: "column", flexGrow: 1, children: [
2654
+ /* @__PURE__ */ jsxs31("box", { flexDirection: "column", paddingX: 1, paddingTop: 1, flexGrow: 1, children: [
2655
+ /* @__PURE__ */ jsx40(Message, { role: "user", children: /* @__PURE__ */ jsx40(Message.Content, { children: /* @__PURE__ */ jsx40(Message.Text, { children: "How do I set up keyboard navigation?" }) }) }),
2656
+ /* @__PURE__ */ jsx40(CotSection, {}),
2657
+ /* @__PURE__ */ jsx40(Message, { role: "assistant", children: /* @__PURE__ */ jsx40(Message.Content, { children: /* @__PURE__ */ jsx40(Message.Text, { children: "Use the useKeyboard hook to listen for key events. Wrap your app in a FocusProvider to enable tab navigation between focusable components." }) }) })
2658
+ ] }),
2659
+ /* @__PURE__ */ jsx40(PromptSection, {})
2660
+ ] }),
2661
+ /* @__PURE__ */ jsx40(FocusChatStatusBar, {})
2662
+ ] }) });
2410
2663
  }
2411
2664
 
2412
2665
  // demos/pointer.tsx
2413
- import { useState as useState22, useRef as useRef9 } from "react";
2414
- import { useKeyboard as useKeyboard16 } from "@gridland/utils";
2666
+ import { useState as useState23, useRef as useRef10 } from "react";
2667
+ import { useKeyboard as useKeyboard17 } from "@gridland/utils";
2668
+ import { jsx as jsx41, jsxs as jsxs32 } from "react/jsx-runtime";
2415
2669
  var pointerColors = ["#ef4444", "#f97316", "#eab308", "#22c55e", "#3b82f6", "#8b5cf6"];
2416
2670
  var pointerColorNames = ["Red", "Orange", "Yellow", "Green", "Blue", "Purple"];
2417
2671
  function HoverBox() {
2418
- const [hovering, setHovering] = useState22(false);
2419
- return /* @__PURE__ */ React.createElement(
2672
+ const [hovering, setHovering] = useState23(false);
2673
+ return /* @__PURE__ */ jsx41(
2420
2674
  "box",
2421
2675
  {
2422
2676
  border: true,
@@ -2425,20 +2679,20 @@ function HoverBox() {
2425
2679
  width: 20,
2426
2680
  height: 5,
2427
2681
  onMouseOver: () => setHovering(true),
2428
- onMouseOut: () => setHovering(false)
2429
- },
2430
- /* @__PURE__ */ React.createElement("box", { padding: 1 }, /* @__PURE__ */ React.createElement("text", { style: { fg: hovering ? "#22c55e" : "#888", bold: hovering } }, hovering ? "Mouse inside!" : "Hover me"))
2682
+ onMouseOut: () => setHovering(false),
2683
+ children: /* @__PURE__ */ jsx41("box", { padding: 1, children: /* @__PURE__ */ jsx41("text", { style: { fg: hovering ? "#22c55e" : "#888", bold: hovering }, children: hovering ? "Mouse inside!" : "Hover me" }) })
2684
+ }
2431
2685
  );
2432
2686
  }
2433
2687
  function PointerApp() {
2434
- const [selected, setSelected] = useState22(null);
2435
- const [clickCount, setClickCount] = useState22(0);
2436
- const [mousePos, setMousePos] = useState22(null);
2437
- const selectedRef = useRef9(null);
2438
- const clickCountRef = useRef9(0);
2688
+ const [selected, setSelected] = useState23(null);
2689
+ const [clickCount, setClickCount] = useState23(0);
2690
+ const [mousePos, setMousePos] = useState23(null);
2691
+ const selectedRef = useRef10(null);
2692
+ const clickCountRef = useRef10(0);
2439
2693
  selectedRef.current = selected;
2440
2694
  clickCountRef.current = clickCount;
2441
- useKeyboard16((event) => {
2695
+ useKeyboard17((event) => {
2442
2696
  const cur = selectedRef.current ?? -1;
2443
2697
  if (event.name === "right" || event.name === "tab") {
2444
2698
  const next = (cur + 1) % pointerColors.length;
@@ -2451,79 +2705,192 @@ function PointerApp() {
2451
2705
  }
2452
2706
  event.preventDefault();
2453
2707
  });
2454
- return /* @__PURE__ */ React.createElement("box", { flexDirection: "column", flexGrow: 1, padding: 1 }, /* @__PURE__ */ React.createElement("box", { flexDirection: "row", gap: 1 }, pointerColors.map((color, i) => /* @__PURE__ */ React.createElement(
2455
- "box",
2456
- {
2457
- key: color,
2458
- flexGrow: 1,
2459
- height: 3,
2460
- border: true,
2461
- borderStyle: "rounded",
2462
- borderColor: i === selected ? color : "#555",
2463
- onMouseDown: (e) => {
2464
- clickCountRef.current++;
2465
- setClickCount(clickCountRef.current);
2466
- selectedRef.current = i;
2467
- setSelected(i);
2468
- setMousePos({ x: e.x, y: e.y });
2469
- }
2470
- },
2471
- /* @__PURE__ */ React.createElement("text", { style: { fg: color, bold: i === selected } }, i === selected ? `\u25B8 ${pointerColorNames[i]}` : ` ${pointerColorNames[i]}`)
2472
- ))), /* @__PURE__ */ React.createElement("box", { height: 1 }), /* @__PURE__ */ React.createElement("box", { flexDirection: "row", gap: 2 }, /* @__PURE__ */ React.createElement(HoverBox, null), /* @__PURE__ */ React.createElement("box", { flexDirection: "column", flexGrow: 1, paddingTop: 1 }, /* @__PURE__ */ React.createElement("text", { style: { fg: selected !== null ? pointerColors[selected] : "#888" } }, selected !== null ? `Clicked ${pointerColorNames[selected]}` : "Click a color", clickCount > 0 ? ` (${clickCount} clicks)` : ""), /* @__PURE__ */ React.createElement("text", { style: { dim: true, fg: "#888" } }, mousePos ? `mouse: ${mousePos.x}, ${mousePos.y}` : ""))), /* @__PURE__ */ React.createElement("box", { paddingX: 1, paddingBottom: 1 }, /* @__PURE__ */ React.createElement(StatusBar, { items: [
2473
- { key: "click", label: "select" },
2474
- { key: "\u2190\u2192", label: "keyboard nav" }
2475
- ] })));
2708
+ return /* @__PURE__ */ jsxs32("box", { flexDirection: "column", flexGrow: 1, padding: 1, children: [
2709
+ /* @__PURE__ */ jsx41("box", { flexDirection: "row", gap: 1, children: pointerColors.map((color, i) => /* @__PURE__ */ jsx41(
2710
+ "box",
2711
+ {
2712
+ flexGrow: 1,
2713
+ height: 3,
2714
+ border: true,
2715
+ borderStyle: "rounded",
2716
+ borderColor: i === selected ? color : "#555",
2717
+ onMouseDown: (e) => {
2718
+ clickCountRef.current++;
2719
+ setClickCount(clickCountRef.current);
2720
+ selectedRef.current = i;
2721
+ setSelected(i);
2722
+ setMousePos({ x: e.x, y: e.y });
2723
+ },
2724
+ children: /* @__PURE__ */ jsx41("text", { style: { fg: color, bold: i === selected }, children: i === selected ? `\u25B8 ${pointerColorNames[i]}` : ` ${pointerColorNames[i]}` })
2725
+ },
2726
+ color
2727
+ )) }),
2728
+ /* @__PURE__ */ jsx41("box", { height: 1 }),
2729
+ /* @__PURE__ */ jsxs32("box", { flexDirection: "row", gap: 2, children: [
2730
+ /* @__PURE__ */ jsx41(HoverBox, {}),
2731
+ /* @__PURE__ */ jsxs32("box", { flexDirection: "column", flexGrow: 1, paddingTop: 1, children: [
2732
+ /* @__PURE__ */ jsxs32("text", { style: { fg: selected !== null ? pointerColors[selected] : "#888" }, children: [
2733
+ selected !== null ? `Clicked ${pointerColorNames[selected]}` : "Click a color",
2734
+ clickCount > 0 ? ` (${clickCount} clicks)` : ""
2735
+ ] }),
2736
+ /* @__PURE__ */ jsx41("text", { style: { dim: true, fg: "#888" }, children: mousePos ? `mouse: ${mousePos.x}, ${mousePos.y}` : "" })
2737
+ ] })
2738
+ ] }),
2739
+ /* @__PURE__ */ jsx41("box", { paddingX: 1, paddingBottom: 1, children: /* @__PURE__ */ jsx41(StatusBar, { items: [
2740
+ { key: "click", label: "select" },
2741
+ { key: "\u2190\u2192", label: "keyboard nav" }
2742
+ ] }) })
2743
+ ] });
2476
2744
  }
2477
2745
 
2478
2746
  // demos/cursor-highlight.tsx
2479
- import { useState as useState23 } from "react";
2747
+ import { useState as useState24 } from "react";
2748
+ import { jsx as jsx42, jsxs as jsxs33 } from "react/jsx-runtime";
2480
2749
  function CursorHighlightApp() {
2481
- const [pos, setPos] = useState23(null);
2482
- return /* @__PURE__ */ React.createElement(
2750
+ const [pos, setPos] = useState24(null);
2751
+ return /* @__PURE__ */ jsxs33(
2483
2752
  "box",
2484
2753
  {
2485
2754
  flexDirection: "column",
2486
2755
  flexGrow: 1,
2487
2756
  onMouseMove: (e) => {
2488
2757
  setPos({ x: e.x, y: e.y });
2489
- }
2490
- },
2491
- /* @__PURE__ */ React.createElement("box", { flexDirection: "column", flexGrow: 1, padding: 1 }, /* @__PURE__ */ React.createElement("text", { style: { bold: true, fg: "#fff" } }, "Cursor Highlight"), /* @__PURE__ */ React.createElement("text", { style: { dim: true, fg: "#888" } }, "Move your mouse over the grid"), /* @__PURE__ */ React.createElement("box", { height: 1 }), /* @__PURE__ */ React.createElement("box", { flexDirection: "column" }, Array.from({ length: 6 }, (_, row) => /* @__PURE__ */ React.createElement("text", { key: row }, Array.from({ length: 40 }, (_2, col) => {
2492
- const isEven = (row + col) % 2 === 0;
2493
- return /* @__PURE__ */ React.createElement("span", { key: col, style: {
2494
- fg: isEven ? "#3b82f6" : "#8b5cf6",
2495
- dim: !isEven
2496
- } }, isEven ? "\u2591\u2591" : "\u2593\u2593");
2497
- }))))),
2498
- /* @__PURE__ */ React.createElement("box", { paddingX: 1, paddingBottom: 1 }, /* @__PURE__ */ React.createElement(
2499
- StatusBar,
2500
- {
2501
- items: [],
2502
- extra: /* @__PURE__ */ React.createElement("span", null, /* @__PURE__ */ React.createElement("span", { style: textStyle({ bold: true, fg: "#1e1e2e", bg: "#888" }) }, " x "), /* @__PURE__ */ React.createElement("span", { style: textStyle({ dim: true, fg: "#888" }) }, ` ${pos ? String(pos.x).padStart(3) : " -"} `), /* @__PURE__ */ React.createElement("span", { style: textStyle({ bold: true, fg: "#1e1e2e", bg: "#888" }) }, " y "), /* @__PURE__ */ React.createElement("span", { style: textStyle({ dim: true, fg: "#888" }) }, ` ${pos ? String(pos.y).padStart(3) : " -"}`))
2503
- }
2504
- ))
2758
+ },
2759
+ children: [
2760
+ /* @__PURE__ */ jsxs33("box", { flexDirection: "column", flexGrow: 1, padding: 1, children: [
2761
+ /* @__PURE__ */ jsx42("text", { style: { bold: true, fg: "#fff" }, children: "Cursor Highlight" }),
2762
+ /* @__PURE__ */ jsx42("text", { style: { dim: true, fg: "#888" }, children: "Move your mouse over the grid" }),
2763
+ /* @__PURE__ */ jsx42("box", { height: 1 }),
2764
+ /* @__PURE__ */ jsx42("box", { flexDirection: "column", children: Array.from({ length: 6 }, (_, row) => /* @__PURE__ */ jsx42("text", { children: Array.from({ length: 40 }, (_2, col) => {
2765
+ const isEven = (row + col) % 2 === 0;
2766
+ return /* @__PURE__ */ jsx42("span", { style: {
2767
+ fg: isEven ? "#3b82f6" : "#8b5cf6",
2768
+ dim: !isEven
2769
+ }, children: isEven ? "\u2591\u2591" : "\u2593\u2593" }, col);
2770
+ }) }, row)) })
2771
+ ] }),
2772
+ /* @__PURE__ */ jsx42("box", { paddingX: 1, paddingBottom: 1, children: /* @__PURE__ */ jsx42(
2773
+ StatusBar,
2774
+ {
2775
+ items: [],
2776
+ extra: /* @__PURE__ */ jsxs33("span", { children: [
2777
+ /* @__PURE__ */ jsx42("span", { style: textStyle({ bold: true, fg: "#1e1e2e", bg: "#888" }), children: " x " }),
2778
+ /* @__PURE__ */ jsx42("span", { style: textStyle({ dim: true, fg: "#888" }), children: ` ${pos ? String(pos.x).padStart(3) : " -"} ` }),
2779
+ /* @__PURE__ */ jsx42("span", { style: textStyle({ bold: true, fg: "#1e1e2e", bg: "#888" }), children: " y " }),
2780
+ /* @__PURE__ */ jsx42("span", { style: textStyle({ dim: true, fg: "#888" }), children: ` ${pos ? String(pos.y).padStart(3) : " -"}` })
2781
+ ] })
2782
+ }
2783
+ ) })
2784
+ ]
2785
+ }
2505
2786
  );
2506
2787
  }
2507
2788
 
2508
2789
  // demos/text-style.tsx
2790
+ import { jsx as jsx43, jsxs as jsxs34 } from "react/jsx-runtime";
2509
2791
  function TextStyleApp() {
2510
2792
  const theme = useTheme();
2511
2793
  const desc = textStyle({ fg: theme.muted });
2512
- return /* @__PURE__ */ React.createElement("box", { flexDirection: "column", padding: 1, gap: 0 }, /* @__PURE__ */ React.createElement("text", null, /* @__PURE__ */ React.createElement("span", { style: textStyle({ fg: theme.foreground, bold: true }) }, "bold "), /* @__PURE__ */ React.createElement("span", { style: desc }, "textStyle(", "{", " bold: true ", "}", ")")), /* @__PURE__ */ React.createElement("text", null, /* @__PURE__ */ React.createElement("span", { style: textStyle({ fg: theme.foreground, dim: true }) }, "dim "), /* @__PURE__ */ React.createElement("span", { style: desc }, "textStyle(", "{", " dim: true ", "}", ")")), /* @__PURE__ */ React.createElement("text", null, /* @__PURE__ */ React.createElement("span", { style: textStyle({ fg: theme.foreground, italic: true }) }, "italic "), /* @__PURE__ */ React.createElement("span", { style: desc }, "textStyle(", "{", " italic: true ", "}", ")")), /* @__PURE__ */ React.createElement("text", null, /* @__PURE__ */ React.createElement("span", { style: textStyle({ fg: theme.foreground, underline: true }) }, "underline "), /* @__PURE__ */ React.createElement("span", { style: desc }, "textStyle(", "{", " underline: true ", "}", ")")), /* @__PURE__ */ React.createElement("text", null, /* @__PURE__ */ React.createElement("span", { style: textStyle({ inverse: true }) }, "inverse "), /* @__PURE__ */ React.createElement("span", { style: desc }, "textStyle(", "{", " inverse: true ", "}", ")")), /* @__PURE__ */ React.createElement("text", null, " "), /* @__PURE__ */ React.createElement("text", null, /* @__PURE__ */ React.createElement("span", { style: textStyle({ fg: theme.primary }) }, "fg color "), /* @__PURE__ */ React.createElement("span", { style: desc }, "textStyle(", "{", " fg: theme.primary ", "}", ")")), /* @__PURE__ */ React.createElement("text", null, /* @__PURE__ */ React.createElement("span", { style: textStyle({ fg: theme.foreground, bg: theme.secondary }) }, "bg color "), /* @__PURE__ */ React.createElement("span", { style: desc }, "textStyle(", "{", " fg: theme.foreground, bg: theme.secondary ", "}", ")")), /* @__PURE__ */ React.createElement("text", null, " "), /* @__PURE__ */ React.createElement("text", null, /* @__PURE__ */ React.createElement("span", { style: textStyle({ fg: theme.accent, bold: true, underline: true }) }, "combined "), /* @__PURE__ */ React.createElement("span", { style: desc }, "textStyle(", "{", " fg: theme.accent, bold: true, underline: true ", "}", ")")));
2794
+ return /* @__PURE__ */ jsxs34("box", { flexDirection: "column", padding: 1, gap: 0, children: [
2795
+ /* @__PURE__ */ jsxs34("text", { children: [
2796
+ /* @__PURE__ */ jsx43("span", { style: textStyle({ fg: theme.foreground, bold: true }), children: "bold " }),
2797
+ /* @__PURE__ */ jsxs34("span", { style: desc, children: [
2798
+ "textStyle(",
2799
+ "{",
2800
+ " bold: true ",
2801
+ "}",
2802
+ ")"
2803
+ ] })
2804
+ ] }),
2805
+ /* @__PURE__ */ jsxs34("text", { children: [
2806
+ /* @__PURE__ */ jsx43("span", { style: textStyle({ fg: theme.foreground, dim: true }), children: "dim " }),
2807
+ /* @__PURE__ */ jsxs34("span", { style: desc, children: [
2808
+ "textStyle(",
2809
+ "{",
2810
+ " dim: true ",
2811
+ "}",
2812
+ ")"
2813
+ ] })
2814
+ ] }),
2815
+ /* @__PURE__ */ jsxs34("text", { children: [
2816
+ /* @__PURE__ */ jsx43("span", { style: textStyle({ fg: theme.foreground, italic: true }), children: "italic " }),
2817
+ /* @__PURE__ */ jsxs34("span", { style: desc, children: [
2818
+ "textStyle(",
2819
+ "{",
2820
+ " italic: true ",
2821
+ "}",
2822
+ ")"
2823
+ ] })
2824
+ ] }),
2825
+ /* @__PURE__ */ jsxs34("text", { children: [
2826
+ /* @__PURE__ */ jsx43("span", { style: textStyle({ fg: theme.foreground, underline: true }), children: "underline " }),
2827
+ /* @__PURE__ */ jsxs34("span", { style: desc, children: [
2828
+ "textStyle(",
2829
+ "{",
2830
+ " underline: true ",
2831
+ "}",
2832
+ ")"
2833
+ ] })
2834
+ ] }),
2835
+ /* @__PURE__ */ jsxs34("text", { children: [
2836
+ /* @__PURE__ */ jsx43("span", { style: textStyle({ inverse: true }), children: "inverse " }),
2837
+ /* @__PURE__ */ jsxs34("span", { style: desc, children: [
2838
+ "textStyle(",
2839
+ "{",
2840
+ " inverse: true ",
2841
+ "}",
2842
+ ")"
2843
+ ] })
2844
+ ] }),
2845
+ /* @__PURE__ */ jsx43("text", { children: " " }),
2846
+ /* @__PURE__ */ jsxs34("text", { children: [
2847
+ /* @__PURE__ */ jsx43("span", { style: textStyle({ fg: theme.primary }), children: "fg color " }),
2848
+ /* @__PURE__ */ jsxs34("span", { style: desc, children: [
2849
+ "textStyle(",
2850
+ "{",
2851
+ " fg: theme.primary ",
2852
+ "}",
2853
+ ")"
2854
+ ] })
2855
+ ] }),
2856
+ /* @__PURE__ */ jsxs34("text", { children: [
2857
+ /* @__PURE__ */ jsx43("span", { style: textStyle({ fg: theme.foreground, bg: theme.secondary }), children: "bg color " }),
2858
+ /* @__PURE__ */ jsxs34("span", { style: desc, children: [
2859
+ "textStyle(",
2860
+ "{",
2861
+ " fg: theme.foreground, bg: theme.secondary ",
2862
+ "}",
2863
+ ")"
2864
+ ] })
2865
+ ] }),
2866
+ /* @__PURE__ */ jsx43("text", { children: " " }),
2867
+ /* @__PURE__ */ jsxs34("text", { children: [
2868
+ /* @__PURE__ */ jsx43("span", { style: textStyle({ fg: theme.accent, bold: true, underline: true }), children: "combined " }),
2869
+ /* @__PURE__ */ jsxs34("span", { style: desc, children: [
2870
+ "textStyle(",
2871
+ "{",
2872
+ " fg: theme.accent, bold: true, underline: true ",
2873
+ "}",
2874
+ ")"
2875
+ ] })
2876
+ ] })
2877
+ ] });
2513
2878
  }
2514
2879
 
2515
2880
  // demos/headless.tsx
2881
+ import { jsx as jsx44 } from "react/jsx-runtime";
2516
2882
  var data = [
2517
2883
  { name: "Alice", role: "Engineer", status: "Active" },
2518
2884
  { name: "Bob", role: "Designer", status: "Active" },
2519
2885
  { name: "Charlie", role: "PM", status: "Away" }
2520
2886
  ];
2521
2887
  function HeadlessApp() {
2522
- return /* @__PURE__ */ React.createElement("box", { padding: 1 }, /* @__PURE__ */ React.createElement(Table, { data }));
2888
+ return /* @__PURE__ */ jsx44("box", { padding: 1, children: /* @__PURE__ */ jsx44(Table, { data }) });
2523
2889
  }
2524
2890
 
2525
2891
  // demos/theming.tsx
2526
- import { useKeyboard as useKeyboard17 } from "@gridland/utils";
2892
+ import { useKeyboard as useKeyboard18 } from "@gridland/utils";
2893
+ import { jsx as jsx45, jsxs as jsxs35 } from "react/jsx-runtime";
2527
2894
  var tableData = [
2528
2895
  { name: "Alice", role: "Engineer", status: "Active" },
2529
2896
  { name: "Bob", role: "Designer", status: "Away" }
@@ -2534,16 +2901,21 @@ var selectItems = [
2534
2901
  { label: "Python", value: "py" }
2535
2902
  ];
2536
2903
  function ThemingApp() {
2537
- return /* @__PURE__ */ React.createElement("box", { flexDirection: "column", padding: 1, gap: 1, flexGrow: 1 }, /* @__PURE__ */ React.createElement(Spinner, { text: "Loading data..." }), /* @__PURE__ */ React.createElement(Table, { data: tableData }), /* @__PURE__ */ React.createElement(MultiSelect, { items: selectItems, useKeyboard: useKeyboard17 }));
2904
+ return /* @__PURE__ */ jsxs35("box", { flexDirection: "column", padding: 1, gap: 1, flexGrow: 1, children: [
2905
+ /* @__PURE__ */ jsx45(Spinner, { text: "Loading data..." }),
2906
+ /* @__PURE__ */ jsx45(Table, { data: tableData }),
2907
+ /* @__PURE__ */ jsx45(MultiSelect, { items: selectItems, useKeyboard: useKeyboard18 })
2908
+ ] });
2538
2909
  }
2539
2910
 
2540
2911
  // src/landing/landing-app.tsx
2541
- import { useMemo as useMemo7 } from "react";
2912
+ import { useMemo as useMemo8, useRef as useRef15, useState as useState29 } from "react";
2542
2913
 
2543
2914
  // src/landing/install-box.tsx
2915
+ import { jsx as jsx46, jsxs as jsxs36 } from "react/jsx-runtime";
2544
2916
  function InstallBox() {
2545
2917
  const theme = useTheme();
2546
- return /* @__PURE__ */ React.createElement(
2918
+ return /* @__PURE__ */ jsx46(
2547
2919
  "box",
2548
2920
  {
2549
2921
  border: true,
@@ -2551,17 +2923,24 @@ function InstallBox() {
2551
2923
  borderColor: theme.border,
2552
2924
  paddingX: 1,
2553
2925
  flexDirection: "column",
2554
- flexShrink: 0
2555
- },
2556
- /* @__PURE__ */ React.createElement("text", null, /* @__PURE__ */ React.createElement("span", { style: textStyle({ dim: true }) }, "$ "), /* @__PURE__ */ React.createElement("span", { style: textStyle({ bold: true }) }, "bun create "), /* @__PURE__ */ React.createElement("span", { style: textStyle({ fg: theme.accent }) }, "gridland"))
2926
+ flexShrink: 0,
2927
+ children: /* @__PURE__ */ jsxs36("text", { children: [
2928
+ /* @__PURE__ */ jsx46("span", { style: textStyle({ dim: true }), children: "$ " }),
2929
+ /* @__PURE__ */ jsx46("span", { style: textStyle({ bold: true }), children: "bun create " }),
2930
+ /* @__PURE__ */ jsx46("span", { style: textStyle({ fg: theme.accent }), children: "gridland" })
2931
+ ] })
2932
+ }
2557
2933
  );
2558
2934
  }
2559
2935
 
2560
2936
  // src/landing/links-box.tsx
2937
+ import { isBrowser } from "@gridland/utils";
2938
+ import { jsx as jsx47, jsxs as jsxs37 } from "react/jsx-runtime";
2561
2939
  var UNDERLINE3 = 1 << 3;
2562
2940
  function LinksBox() {
2563
2941
  const theme = useTheme();
2564
- return /* @__PURE__ */ React.createElement(
2942
+ const docsHref = isBrowser() ? `${window.location.origin}/docs` : "https://gridland.io/docs";
2943
+ return /* @__PURE__ */ jsx47(
2565
2944
  "box",
2566
2945
  {
2567
2946
  border: true,
@@ -2569,30 +2948,37 @@ function LinksBox() {
2569
2948
  borderColor: theme.border,
2570
2949
  paddingX: 1,
2571
2950
  flexDirection: "column",
2572
- flexShrink: 0
2573
- },
2574
- /* @__PURE__ */ React.createElement("text", null, /* @__PURE__ */ React.createElement("span", null, "\u{1F431}"), /* @__PURE__ */ React.createElement("a", { href: "https://github.com/thoughtfulllc/gridland", style: { attributes: UNDERLINE3, fg: theme.accent } }, " GitHub"), /* @__PURE__ */ React.createElement("span", null, " "), /* @__PURE__ */ React.createElement("span", null, "\u{1F4D6}"), /* @__PURE__ */ React.createElement("a", { href: "https://gridland.io/docs", style: { attributes: UNDERLINE3, fg: theme.accent } }, " Docs"))
2951
+ flexShrink: 0,
2952
+ children: /* @__PURE__ */ jsxs37("text", { children: [
2953
+ /* @__PURE__ */ jsx47("span", { children: "\u{1F431}" }),
2954
+ /* @__PURE__ */ jsx47("a", { href: "https://github.com/thoughtfulllc/gridland", style: { attributes: UNDERLINE3, fg: theme.accent }, children: " GitHub" }),
2955
+ /* @__PURE__ */ jsx47("span", { children: " " }),
2956
+ /* @__PURE__ */ jsx47("span", { children: "\u{1F4D6}" }),
2957
+ /* @__PURE__ */ jsx47("a", { href: docsHref, style: { attributes: UNDERLINE3, fg: theme.accent }, children: " Docs" })
2958
+ ] })
2959
+ }
2575
2960
  );
2576
2961
  }
2577
2962
 
2578
2963
  // src/landing/logo.tsx
2579
- import { useState as useState24, useEffect as useEffect6, useRef as useRef10, useMemo as useMemo5 } from "react";
2964
+ import { useState as useState25, useEffect as useEffect6, useRef as useRef11, useMemo as useMemo5 } from "react";
2580
2965
  import figlet3 from "figlet";
2581
- import ansiShadow3 from "figlet/importable-fonts/ANSI Shadow.js";
2582
- figlet3.parseFont("ANSI Shadow", ansiShadow3);
2583
- function makeArt(text) {
2584
- return figlet3.textSync(text, { font: "ANSI Shadow" }).split("\n").filter((l) => l.trimEnd().length > 0).join("\n");
2585
- }
2586
- var fullArt = makeArt("gridland");
2587
- var gridArt = makeArt("grid");
2588
- var landArt = makeArt("land");
2589
- var ART_HEIGHT = 6;
2966
+ import blockFont from "figlet/importable-fonts/Block.js";
2967
+ import { Fragment as Fragment6, jsx as jsx48, jsxs as jsxs38 } from "react/jsx-runtime";
2968
+ figlet3.parseFont("Block", blockFont);
2969
+ function makeArt(text, font = "Block") {
2970
+ return figlet3.textSync(text, { font }).split("\n").filter((l) => l.trimEnd().length > 0).join("\n");
2971
+ }
2972
+ var fullArt = makeArt("gridland", "Block");
2973
+ var gridArt = makeArt("grid", "Block");
2974
+ var landArt = makeArt("land", "Block");
2975
+ var ART_HEIGHT = fullArt.split("\n").length;
2590
2976
  function useAnimation(duration = 1e3) {
2591
- const isBrowser = typeof document !== "undefined";
2592
- const [progress, setProgress] = useState24(isBrowser ? 0 : 1);
2593
- const startTime = useRef10(null);
2977
+ const isBrowser2 = typeof document !== "undefined";
2978
+ const [progress, setProgress] = useState25(isBrowser2 ? 0 : 1);
2979
+ const startTime = useRef11(null);
2594
2980
  useEffect6(() => {
2595
- if (!isBrowser) return;
2981
+ if (!isBrowser2) return;
2596
2982
  let raf;
2597
2983
  const tick = (time) => {
2598
2984
  if (startTime.current === null) startTime.current = time;
@@ -2607,13 +2993,20 @@ function useAnimation(duration = 1e3) {
2607
2993
  }, []);
2608
2994
  return progress;
2609
2995
  }
2996
+ function darkenHex(hex, factor = 0.4) {
2997
+ const r = Math.round(parseInt(hex.slice(1, 3), 16) * factor);
2998
+ const g = Math.round(parseInt(hex.slice(3, 5), 16) * factor);
2999
+ const b = Math.round(parseInt(hex.slice(5, 7), 16) * factor);
3000
+ return `#${r.toString(16).padStart(2, "0")}${g.toString(16).padStart(2, "0")}${b.toString(16).padStart(2, "0")}`;
3001
+ }
2610
3002
  function RevealGradient({ children, revealCol }) {
2611
3003
  const gradientColors = GRADIENTS.instagram;
2612
3004
  const lines2 = children.split("\n");
2613
3005
  const maxLength = Math.max(...lines2.map((l) => l.length));
2614
- if (maxLength === 0) return /* @__PURE__ */ React.createElement("text", null, children);
3006
+ if (maxLength === 0) return /* @__PURE__ */ jsx48("text", { children });
2615
3007
  const hexColors = useMemo5(() => generateGradient(gradientColors, maxLength), [maxLength]);
2616
- return /* @__PURE__ */ React.createElement("box", { position: "relative", width: maxLength, height: lines2.length, shouldFill: false }, lines2.map((line, lineIndex) => {
3008
+ const bgColors = useMemo5(() => hexColors.map((c) => darkenHex(c)), [hexColors]);
3009
+ return /* @__PURE__ */ jsx48("box", { position: "relative", width: maxLength, height: lines2.length, shouldFill: false, children: lines2.map((line, lineIndex) => {
2617
3010
  const runs = [];
2618
3011
  let current = null;
2619
3012
  for (let i = 0; i < line.length; i++) {
@@ -2633,54 +3026,81 @@ function RevealGradient({ children, revealCol }) {
2633
3026
  }
2634
3027
  }
2635
3028
  if (current) runs.push(current);
2636
- return runs.map((run, runIndex) => /* @__PURE__ */ React.createElement(
3029
+ return runs.map((run, runIndex) => /* @__PURE__ */ jsx48(
2637
3030
  "box",
2638
3031
  {
2639
- key: `${lineIndex}-${runIndex}`,
2640
3032
  position: "absolute",
2641
3033
  top: lineIndex,
2642
3034
  left: run.start,
2643
- shouldFill: false
3035
+ shouldFill: false,
3036
+ children: /* @__PURE__ */ jsx48("text", { shouldFill: false, children: run.chars.map((char, ci) => /* @__PURE__ */ jsx48(
3037
+ "span",
3038
+ {
3039
+ style: { fg: hexColors[run.start + ci], bg: bgColors[run.start + ci] },
3040
+ children: char
3041
+ },
3042
+ ci
3043
+ )) })
2644
3044
  },
2645
- /* @__PURE__ */ React.createElement("text", { shouldFill: false }, run.chars.map((char, ci) => /* @__PURE__ */ React.createElement(
2646
- "span",
2647
- {
2648
- key: ci,
2649
- style: { fg: hexColors[run.start + ci] }
2650
- },
2651
- char
2652
- )))
3045
+ `${lineIndex}-${runIndex}`
2653
3046
  ));
2654
- }));
3047
+ }) });
2655
3048
  }
2656
3049
  function Logo({ compact, narrow, mobile }) {
2657
- const isBrowser = typeof document !== "undefined";
3050
+ const isBrowser2 = typeof document !== "undefined";
2658
3051
  const progress = useAnimation(900);
2659
- const artHeight = compact ? 1 : narrow ? ART_HEIGHT * 2 : ART_HEIGHT;
3052
+ const artHeight = compact ? 1 : narrow && !mobile ? ART_HEIGHT * 2 : ART_HEIGHT;
2660
3053
  const dropOffset = Math.round((1 - progress) * -artHeight);
2661
3054
  const revealProgress = Math.max(0, Math.min(1, (progress - 0.1) / 0.7));
2662
- const maxWidth = compact ? 8 : narrow ? 40 : 62;
3055
+ const maxWidth = compact ? 8 : narrow ? 35 : 69;
2663
3056
  const revealCol = Math.round(revealProgress * (maxWidth + 4)) - 2;
2664
3057
  const taglineOpacity = Math.max(0, Math.min(1, (progress - 0.7) / 0.3));
2665
- const subtitle = /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement("text", null, " "), /* @__PURE__ */ React.createElement("box", { flexDirection: "column", alignItems: "center", width: "100%", shouldFill: false }, /* @__PURE__ */ React.createElement("text", { style: textStyle({ fg: "#d4b0e8" }), opacity: taglineOpacity, wrapMode: "word", textAlign: "center", width: "100%", shouldFill: false }, "A framework for building terminal apps, built on ", /* @__PURE__ */ React.createElement("a", { href: "https://opentui.com", style: { attributes: 72, fg: "#d4b0e8" } }, "OpenTUI"), " + React." + (mobile ? " " : "\n") + "(Gridland apps, like this website, work in the browser and terminal.)")));
2666
- if (!isBrowser) {
2667
- const art2 = compact ? "gridland" : narrow ? gridArt + "\n" + landArt : fullArt;
2668
- return /* @__PURE__ */ React.createElement("box", { flexDirection: "column", flexShrink: 0, width: "100%", alignItems: "center", shouldFill: false }, /* @__PURE__ */ React.createElement(Gradient, { name: "instagram" }, art2), /* @__PURE__ */ React.createElement("text", null, " "), /* @__PURE__ */ React.createElement("box", { flexDirection: "column", alignItems: "center", width: "100%", shouldFill: false }, /* @__PURE__ */ React.createElement("text", { style: textStyle({ fg: "#d4b0e8" }), shouldFill: false }, "A framework for building terminal apps, built on OpenTUI + React.", "\n", "(Gridland apps, like this website, work in the browser and terminal.)")));
3058
+ const subtitle = /* @__PURE__ */ jsxs38(Fragment6, { children: [
3059
+ /* @__PURE__ */ jsx48("text", { children: " " }),
3060
+ /* @__PURE__ */ jsx48("box", { flexDirection: "column", alignItems: "center", width: "100%", shouldFill: false, children: /* @__PURE__ */ jsxs38("text", { style: textStyle({ fg: "#d4b0e8" }), opacity: taglineOpacity, wrapMode: "word", textAlign: "center", width: "100%", shouldFill: false, children: [
3061
+ "A framework for building terminal apps, built on ",
3062
+ /* @__PURE__ */ jsx48("a", { href: "https://opentui.com", style: { attributes: 72, fg: "#d4b0e8" }, children: "OpenTUI" }),
3063
+ " + React." + (mobile ? " " : "\n") + "(Gridland apps, like this website, work in the browser and terminal.)"
3064
+ ] }) })
3065
+ ] });
3066
+ if (!isBrowser2) {
3067
+ const art2 = compact ? "gridland" : narrow && !mobile ? gridArt + "\n" + landArt : fullArt;
3068
+ return /* @__PURE__ */ jsxs38("box", { flexDirection: "column", flexShrink: 0, width: "100%", alignItems: "center", shouldFill: false, children: [
3069
+ /* @__PURE__ */ jsx48(Gradient, { name: "instagram", children: art2 }),
3070
+ /* @__PURE__ */ jsx48("text", { children: " " }),
3071
+ /* @__PURE__ */ jsx48("box", { flexDirection: "column", alignItems: "center", width: "100%", shouldFill: false, children: /* @__PURE__ */ jsxs38("text", { style: textStyle({ fg: "#d4b0e8" }), shouldFill: false, children: [
3072
+ "A framework for building terminal apps, built on OpenTUI + React.",
3073
+ "\n",
3074
+ "(Gridland apps, like this website, work in the browser and terminal.)"
3075
+ ] }) })
3076
+ ] });
2669
3077
  }
2670
3078
  if (compact) {
2671
- return /* @__PURE__ */ React.createElement("box", { flexDirection: "column", flexShrink: 0, width: "100%", shouldFill: false }, /* @__PURE__ */ React.createElement("box", { height: artHeight, overflow: "hidden", position: "relative", width: "100%", flexShrink: 0, shouldFill: false }, /* @__PURE__ */ React.createElement("box", { position: "absolute", top: dropOffset, width: "100%", flexDirection: "column", alignItems: "center", shouldFill: false }, /* @__PURE__ */ React.createElement(RevealGradient, { revealCol }, "gridland"))), subtitle);
3079
+ return /* @__PURE__ */ jsxs38("box", { flexDirection: "column", flexShrink: 0, width: "100%", shouldFill: false, children: [
3080
+ /* @__PURE__ */ jsx48("box", { height: artHeight, overflow: "hidden", position: "relative", width: "100%", flexShrink: 0, shouldFill: false, children: /* @__PURE__ */ jsx48("box", { position: "absolute", top: dropOffset, width: "100%", flexDirection: "column", alignItems: "center", shouldFill: false, children: /* @__PURE__ */ jsx48(RevealGradient, { revealCol, children: "gridland" }) }) }),
3081
+ subtitle
3082
+ ] });
2672
3083
  }
2673
- if (narrow) {
2674
- return /* @__PURE__ */ React.createElement("box", { flexDirection: "column", flexShrink: 0, width: "100%", shouldFill: false }, /* @__PURE__ */ React.createElement("box", { height: artHeight, overflow: "hidden", position: "relative", width: "100%", flexShrink: 0, shouldFill: false }, /* @__PURE__ */ React.createElement("box", { position: "absolute", top: dropOffset, width: "100%", flexDirection: "column", alignItems: "center", shouldFill: false }, /* @__PURE__ */ React.createElement(RevealGradient, { revealCol }, gridArt), /* @__PURE__ */ React.createElement(RevealGradient, { revealCol }, landArt))), subtitle);
3084
+ if (narrow && !mobile) {
3085
+ return /* @__PURE__ */ jsxs38("box", { flexDirection: "column", flexShrink: 0, width: "100%", shouldFill: false, children: [
3086
+ /* @__PURE__ */ jsx48("box", { height: artHeight, overflow: "hidden", position: "relative", width: "100%", flexShrink: 0, shouldFill: false, children: /* @__PURE__ */ jsxs38("box", { position: "absolute", top: dropOffset, width: "100%", flexDirection: "column", alignItems: "center", shouldFill: false, children: [
3087
+ /* @__PURE__ */ jsx48(RevealGradient, { revealCol, children: gridArt }),
3088
+ /* @__PURE__ */ jsx48(RevealGradient, { revealCol, children: landArt })
3089
+ ] }) }),
3090
+ subtitle
3091
+ ] });
2675
3092
  }
2676
- return /* @__PURE__ */ React.createElement("box", { flexDirection: "column", flexShrink: 0, width: "100%", shouldFill: false }, /* @__PURE__ */ React.createElement("box", { height: artHeight, overflow: "hidden", position: "relative", width: "100%", flexShrink: 0, shouldFill: false }, /* @__PURE__ */ React.createElement("box", { position: "absolute", top: dropOffset, width: "100%", flexDirection: "column", alignItems: "center", shouldFill: false }, /* @__PURE__ */ React.createElement(RevealGradient, { revealCol }, fullArt))), subtitle);
3093
+ return /* @__PURE__ */ jsxs38("box", { flexDirection: "column", flexShrink: 0, width: "100%", shouldFill: false, children: [
3094
+ /* @__PURE__ */ jsx48("box", { height: artHeight, overflow: "hidden", position: "relative", width: "100%", flexShrink: 0, shouldFill: false, children: /* @__PURE__ */ jsx48("box", { position: "absolute", top: dropOffset, width: "100%", flexDirection: "column", alignItems: "center", shouldFill: false, children: /* @__PURE__ */ jsx48(RevealGradient, { revealCol, children: fullArt }) }) }),
3095
+ subtitle
3096
+ ] });
2677
3097
  }
2678
3098
 
2679
3099
  // src/landing/matrix-background.tsx
2680
3100
  import { useMemo as useMemo6 } from "react";
2681
3101
 
2682
3102
  // src/landing/use-matrix.ts
2683
- import { useState as useState25, useEffect as useEffect7, useRef as useRef11 } from "react";
3103
+ import { useState as useState26, useEffect as useEffect7, useLayoutEffect, useRef as useRef12 } from "react";
2684
3104
  var CHARS = "abcdefghijklmnopqrstuvwxyz0123456789@#$%^&*(){}[]|;:<>,.?/~`";
2685
3105
  function randomChar() {
2686
3106
  return CHARS[Math.floor(Math.random() * CHARS.length)];
@@ -2694,7 +3114,11 @@ function createDrop(height, seeded = false) {
2694
3114
  chars: Array.from({ length }, randomChar)
2695
3115
  };
2696
3116
  }
2697
- function buildGrid(columns, width, height) {
3117
+ var PULL_RADIUS = 18;
3118
+ var PULL_STRENGTH = 7;
3119
+ var RIPPLE_DURATION_MS = 3200;
3120
+ var RIPPLE_SPEED = 8e-3;
3121
+ function buildGrid(columns, width, height, mousePos, ripples, now = Date.now()) {
2698
3122
  const grid = Array.from({ length: height }, () => Array(width).fill(" "));
2699
3123
  const brightness = Array.from({ length: height }, () => Array(width).fill(0));
2700
3124
  for (let x = 0; x < width; x++) {
@@ -2703,30 +3127,67 @@ function buildGrid(columns, width, height) {
2703
3127
  for (let i = 0; i < drop.length; i++) {
2704
3128
  const row = Math.floor(drop.y) - i;
2705
3129
  if (row < 0 || row >= height) continue;
2706
- grid[row][x] = drop.chars[i];
2707
- if (i === 0) {
2708
- brightness[row][x] = 1;
2709
- } else {
2710
- brightness[row][x] = Math.max(0.15, 1 - i / drop.length);
3130
+ let renderX = x;
3131
+ if (mousePos) {
3132
+ const dx = mousePos.x - x;
3133
+ const dy = mousePos.y - row;
3134
+ const dist = Math.sqrt(dx * dx + dy * dy);
3135
+ if (dist < PULL_RADIUS && dist > 0.5) {
3136
+ const t = 1 - dist / PULL_RADIUS;
3137
+ const strength = t * t * PULL_STRENGTH;
3138
+ renderX = Math.round(x + dx / dist * strength);
3139
+ renderX = Math.max(0, Math.min(width - 1, renderX));
3140
+ }
3141
+ }
3142
+ const b = i === 0 ? 1 : Math.max(0.15, 1 - i / drop.length);
3143
+ if (brightness[row][renderX] < b) {
3144
+ grid[row][renderX] = drop.chars[i];
3145
+ brightness[row][renderX] = b;
3146
+ }
3147
+ }
3148
+ }
3149
+ for (const ripple of ripples) {
3150
+ const elapsed = now - ripple.createdAt;
3151
+ if (elapsed > RIPPLE_DURATION_MS || elapsed < 0) continue;
3152
+ const radius = elapsed * RIPPLE_SPEED;
3153
+ const fade = 1 - elapsed / RIPPLE_DURATION_MS;
3154
+ const maxR = Math.ceil(radius) + 2;
3155
+ const rx = Math.round(ripple.x);
3156
+ const ry = Math.round(ripple.y);
3157
+ for (let dy = -maxR; dy <= maxR; dy++) {
3158
+ for (let dx = -maxR; dx <= maxR; dx++) {
3159
+ const cy = ry + dy;
3160
+ const cx = rx + dx;
3161
+ if (cy < 0 || cy >= height || cx < 0 || cx >= width) continue;
3162
+ const dist = Math.sqrt(dx * dx + dy * dy);
3163
+ const ringDist = Math.abs(dist - radius);
3164
+ if (ringDist < 2) {
3165
+ const boost = (1 - ringDist / 2) * fade * 0.7;
3166
+ brightness[cy][cx] = Math.min(1, brightness[cy][cx] + boost);
3167
+ if (grid[cy][cx] === " " && boost > 0.2) {
3168
+ grid[cy][cx] = randomChar();
3169
+ }
3170
+ }
2711
3171
  }
2712
3172
  }
2713
3173
  }
2714
3174
  return { grid, brightness };
2715
3175
  }
2716
- function useMatrix(width, height) {
2717
- const columnsRef = useRef11([]);
2718
- const [state, setState] = useState25(() => {
3176
+ function useMatrix(width, height, mousePosRef, ripplesRef) {
3177
+ const columnsRef = useRef12([]);
3178
+ const [state, setState] = useState26(() => {
2719
3179
  const columns = Array.from(
2720
3180
  { length: width },
2721
3181
  () => Math.random() < 0.5 ? createDrop(height, true) : null
2722
3182
  );
2723
3183
  columnsRef.current = columns;
2724
- return buildGrid(columns, width, height);
3184
+ return buildGrid(columns, width, height, null, []);
2725
3185
  });
2726
3186
  useEffect7(() => {
2727
3187
  if (width < 2 || height < 2) return;
2728
3188
  const id = setInterval(() => {
2729
3189
  const columns = columnsRef.current;
3190
+ const now = Date.now();
2730
3191
  for (let x = 0; x < width; x++) {
2731
3192
  if (columns[x] === null || columns[x] === void 0) {
2732
3193
  if (Math.random() < 0.03) {
@@ -2744,21 +3205,29 @@ function useMatrix(width, height) {
2744
3205
  columns[x] = null;
2745
3206
  }
2746
3207
  }
2747
- setState(buildGrid(columns, width, height));
3208
+ if (ripplesRef?.current) {
3209
+ ripplesRef.current = ripplesRef.current.filter(
3210
+ (r) => now - r.createdAt < RIPPLE_DURATION_MS
3211
+ );
3212
+ }
3213
+ const mousePos = mousePosRef?.current ?? null;
3214
+ const ripples = ripplesRef?.current ?? [];
3215
+ setState(buildGrid(columns, width, height, mousePos, ripples, now));
2748
3216
  }, 80);
2749
3217
  return () => clearInterval(id);
2750
3218
  }, [width, height]);
2751
- useEffect7(() => {
3219
+ useLayoutEffect(() => {
2752
3220
  columnsRef.current = Array.from(
2753
3221
  { length: width },
2754
3222
  () => Math.random() < 0.5 ? createDrop(height, true) : null
2755
3223
  );
2756
- setState(buildGrid(columnsRef.current, width, height));
3224
+ setState(buildGrid(columnsRef.current, width, height, null, []));
2757
3225
  }, [width, height]);
2758
3226
  return state;
2759
3227
  }
2760
3228
 
2761
3229
  // src/landing/matrix-background.tsx
3230
+ import { jsx as jsx49 } from "react/jsx-runtime";
2762
3231
  var MUTE_LEVELS = [0.12, 0.18, 0.24, 0.3, 0.38];
2763
3232
  var BG = hexToRgb("#1a1a2e");
2764
3233
  function buildMutedColors(baseHex) {
@@ -2774,8 +3243,8 @@ function colorForCell(mutedColors, b) {
2774
3243
  const idx = Math.min(Math.floor(b * (MUTE_LEVELS.length - 1)), MUTE_LEVELS.length - 2);
2775
3244
  return mutedColors[idx];
2776
3245
  }
2777
- function MatrixBackground({ width, height, clearRect, clearRects }) {
2778
- const { grid, brightness } = useMatrix(width, height);
3246
+ function MatrixBackground({ width, height, clearRect, clearRects, mousePosRef, ripplesRef }) {
3247
+ const { grid, brightness } = useMatrix(width, height, mousePosRef, ripplesRef);
2779
3248
  const theme = useTheme();
2780
3249
  const columnColors = useMemo6(
2781
3250
  () => width > 0 ? generateGradient([theme.accent, theme.secondary, theme.primary], width) : [],
@@ -2785,85 +3254,561 @@ function MatrixBackground({ width, height, clearRect, clearRects }) {
2785
3254
  () => columnColors.map(buildMutedColors),
2786
3255
  [columnColors]
2787
3256
  );
2788
- return /* @__PURE__ */ React.createElement("box", { flexDirection: "column" }, grid.map((row, y) => /* @__PURE__ */ React.createElement("text", { key: y }, row.map((cell, x) => {
3257
+ return /* @__PURE__ */ jsx49("box", { flexDirection: "column", children: grid.map((row, y) => /* @__PURE__ */ jsx49("text", { children: row.map((cell, x) => {
2789
3258
  const inClearRect = clearRect && y >= clearRect.top && y < clearRect.top + clearRect.height && x >= clearRect.left && x < clearRect.left + clearRect.width || clearRects && clearRects.some(
2790
3259
  (r) => y >= r.top && y < r.top + r.height && x >= r.left && x < r.left + r.width
2791
3260
  );
2792
3261
  const mutedColors = columnMutedColors[x];
2793
3262
  if (cell === " " || inClearRect || !mutedColors) {
2794
- return /* @__PURE__ */ React.createElement("span", { key: x }, " ");
3263
+ return /* @__PURE__ */ jsx49("span", { children: " " }, x);
2795
3264
  }
2796
- return /* @__PURE__ */ React.createElement(
3265
+ return /* @__PURE__ */ jsx49(
2797
3266
  "span",
2798
3267
  {
2799
- key: x,
2800
3268
  style: {
2801
3269
  fg: colorForCell(mutedColors, brightness[y][x])
2802
- }
3270
+ },
3271
+ children: cell
2803
3272
  },
2804
- cell
3273
+ x
3274
+ );
3275
+ }) }, y)) });
3276
+ }
3277
+
3278
+ // demos/ripple.tsx
3279
+ import { useState as useState27, useEffect as useEffect8, useRef as useRef13, useCallback as useCallback6 } from "react";
3280
+ import { useKeyboard as useKeyboard19 } from "@gridland/utils";
3281
+ import { jsx as jsx50, jsxs as jsxs39 } from "react/jsx-runtime";
3282
+ var DEFAULT_COLS = 40;
3283
+ var DEFAULT_ROWS = 10;
3284
+ var CHARS2 = ["\xB7", "\u2591", "\u2592", "\u2593", "\u2588"];
3285
+ function hexToRgb2(hex) {
3286
+ const h = hex.replace("#", "");
3287
+ return [
3288
+ parseInt(h.slice(0, 2), 16),
3289
+ parseInt(h.slice(2, 4), 16),
3290
+ parseInt(h.slice(4, 6), 16)
3291
+ ];
3292
+ }
3293
+ function rgbToHex2(r, g, b) {
3294
+ const clamp = (v) => Math.max(0, Math.min(255, Math.round(v)));
3295
+ return "#" + clamp(r).toString(16).padStart(2, "0") + clamp(g).toString(16).padStart(2, "0") + clamp(b).toString(16).padStart(2, "0");
3296
+ }
3297
+ function lerp2(a, b, t) {
3298
+ return a + (b - a) * t;
3299
+ }
3300
+ function RippleApp({ mouseOffset = { x: 0, y: 0 }, containerWidth, containerHeight } = {}) {
3301
+ const theme = useTheme();
3302
+ const [, setTick] = useState27(0);
3303
+ const COLS2 = containerWidth ? containerWidth - 2 : DEFAULT_COLS;
3304
+ const ROWS2 = containerHeight ? Math.max(3, containerHeight - 2 - 2) : DEFAULT_ROWS;
3305
+ const cursorRef = useRef13({ x: Math.floor(COLS2 / 2), y: Math.floor(ROWS2 / 2) });
3306
+ const ripplesRef = useRef13([]);
3307
+ const frameRef = useRef13(0);
3308
+ const mousePosRef = useRef13(null);
3309
+ const accentRgb = hexToRgb2(theme.accent);
3310
+ const dimRgb = [40, 40, 50];
3311
+ const baseRgb = [60, 60, 70];
3312
+ const addRipple = useCallback6((x, y) => {
3313
+ ripplesRef.current = [
3314
+ ...ripplesRef.current,
3315
+ { x, y, time: frameRef.current }
3316
+ ];
3317
+ }, []);
3318
+ useEffect8(() => {
3319
+ const interval = setInterval(() => {
3320
+ frameRef.current++;
3321
+ ripplesRef.current = ripplesRef.current.filter(
3322
+ (r) => frameRef.current - r.time < 30
3323
+ );
3324
+ setTick((t) => t + 1);
3325
+ }, 60);
3326
+ return () => clearInterval(interval);
3327
+ }, []);
3328
+ useKeyboard19((event) => {
3329
+ const cursor2 = cursorRef.current;
3330
+ if (event.name === "up") {
3331
+ cursorRef.current = { ...cursor2, y: Math.max(0, cursor2.y - 1) };
3332
+ } else if (event.name === "down") {
3333
+ cursorRef.current = { ...cursor2, y: Math.min(ROWS2 - 1, cursor2.y + 1) };
3334
+ } else if (event.name === "left") {
3335
+ cursorRef.current = { ...cursor2, x: Math.max(0, cursor2.x - 1) };
3336
+ } else if (event.name === "right") {
3337
+ cursorRef.current = { ...cursor2, x: Math.min(COLS2 - 1, cursor2.x + 1) };
3338
+ } else if (event.name === "return") {
3339
+ addRipple(cursorRef.current.x, cursorRef.current.y);
3340
+ }
3341
+ event.preventDefault();
3342
+ });
3343
+ const cursor = cursorRef.current;
3344
+ const frame = frameRef.current;
3345
+ const ripples = ripplesRef.current;
3346
+ const grid = Array.from({ length: ROWS2 }, (_, row) => /* @__PURE__ */ jsx50("text", { children: Array.from({ length: COLS2 }, (_2, col) => {
3347
+ const isCursor = col === cursor.x && row === cursor.y;
3348
+ const baseWave = Math.sin(frame * 0.08 + col * 0.3 + row * 0.5) * 0.5 + 0.5;
3349
+ let intensity = baseWave * 0.15;
3350
+ for (const ripple of ripples) {
3351
+ const dx = col - ripple.x;
3352
+ const dy = row - ripple.y;
3353
+ const dist = Math.sqrt(dx * dx + dy * dy);
3354
+ const age = frame - ripple.time;
3355
+ const radius = age * 0.5;
3356
+ const fade = 1 - age / 30;
3357
+ const ringDist = Math.abs(dist - radius);
3358
+ if (ringDist < 1.5) {
3359
+ const ringIntensity = (1 - ringDist / 1.5) * fade;
3360
+ intensity = Math.max(intensity, ringIntensity);
3361
+ } else if (dist < radius) {
3362
+ const innerIntensity = fade * 0.3 * (1 - dist / radius);
3363
+ intensity = Math.max(intensity, innerIntensity);
3364
+ }
3365
+ }
3366
+ intensity = Math.max(0, Math.min(1, intensity));
3367
+ const charIndex = Math.min(
3368
+ CHARS2.length - 1,
3369
+ Math.floor(intensity * CHARS2.length)
2805
3370
  );
2806
- }))));
3371
+ const char = isCursor ? "\u25C6" : CHARS2[charIndex];
3372
+ let fg;
3373
+ if (isCursor) {
3374
+ fg = theme.primary;
3375
+ } else {
3376
+ const r = lerp2(dimRgb[0], accentRgb[0], intensity);
3377
+ const g = lerp2(dimRgb[1], accentRgb[1], intensity);
3378
+ const b = lerp2(dimRgb[2], accentRgb[2], intensity);
3379
+ fg = rgbToHex2(r, g, b);
3380
+ }
3381
+ return /* @__PURE__ */ jsx50("span", { style: { fg, bold: isCursor || intensity > 0.7 }, children: char }, col);
3382
+ }) }, row));
3383
+ return /* @__PURE__ */ jsxs39("box", { flexDirection: "column", flexGrow: 1, children: [
3384
+ /* @__PURE__ */ jsxs39(
3385
+ "box",
3386
+ {
3387
+ flexDirection: "column",
3388
+ flexGrow: 1,
3389
+ paddingX: 1,
3390
+ onMouseMove: (e) => {
3391
+ const gx = e.x - mouseOffset.x - 1;
3392
+ const gy = e.y - mouseOffset.y - 2;
3393
+ if (gx >= 0 && gx < COLS2 && gy >= 0 && gy < ROWS2) {
3394
+ mousePosRef.current = { x: gx, y: gy };
3395
+ cursorRef.current = { x: gx, y: gy };
3396
+ }
3397
+ },
3398
+ onMouseDown: (e) => {
3399
+ const gx = e.x - mouseOffset.x - 1;
3400
+ const gy = e.y - mouseOffset.y - 2;
3401
+ if (gx >= 0 && gx < COLS2 && gy >= 0 && gy < ROWS2) {
3402
+ addRipple(gx, gy);
3403
+ }
3404
+ },
3405
+ children: [
3406
+ /* @__PURE__ */ jsx50("text", { style: { dim: true, fg: theme.muted }, children: "Click or press Enter to create ripples" }),
3407
+ /* @__PURE__ */ jsx50("box", { flexDirection: "column", children: grid })
3408
+ ]
3409
+ }
3410
+ ),
3411
+ /* @__PURE__ */ jsx50("box", { flexGrow: 1 }),
3412
+ /* @__PURE__ */ jsx50("box", { paddingX: 1, children: /* @__PURE__ */ jsx50(
3413
+ StatusBar,
3414
+ {
3415
+ items: [
3416
+ { key: "\u2191\u2193\u2190\u2192", label: "move" },
3417
+ { key: "enter/click", label: "ripple" }
3418
+ ]
3419
+ }
3420
+ ) })
3421
+ ] });
3422
+ }
3423
+
3424
+ // demos/puzzle.tsx
3425
+ import { useState as useState28, useEffect as useEffect9, useRef as useRef14, useMemo as useMemo7 } from "react";
3426
+ import { useKeyboard as useKeyboard20 } from "@gridland/utils";
3427
+ import { jsx as jsx51, jsxs as jsxs40 } from "react/jsx-runtime";
3428
+ var COLS = 4;
3429
+ var ROWS = 3;
3430
+ var TILE_COUNT = COLS * ROWS;
3431
+ var DEFAULT_TILE_WIDTH = 8;
3432
+ var DEFAULT_TILE_HEIGHT = 3;
3433
+ var SOLVED = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 0];
3434
+ var tileColors = [
3435
+ "#ef4444",
3436
+ "#f97316",
3437
+ "#eab308",
3438
+ "#22c55e",
3439
+ "#3b82f6",
3440
+ "#8b5cf6",
3441
+ "#ec4899",
3442
+ "#14b8a6",
3443
+ "#f43f5e",
3444
+ "#6366f1",
3445
+ "#84cc16"
3446
+ ];
3447
+ function isSolved(board) {
3448
+ for (let i = 0; i < TILE_COUNT; i++) {
3449
+ if (board[i] !== SOLVED[i]) return false;
3450
+ }
3451
+ return true;
3452
+ }
3453
+ function getEmptyIndex(board) {
3454
+ return board.indexOf(0);
3455
+ }
3456
+ function getNeighbor(emptyIdx, direction) {
3457
+ const row = Math.floor(emptyIdx / COLS);
3458
+ const col = emptyIdx % COLS;
3459
+ switch (direction) {
3460
+ case "up":
3461
+ return row > 0 ? emptyIdx - COLS : null;
3462
+ case "down":
3463
+ return row < ROWS - 1 ? emptyIdx + COLS : null;
3464
+ case "left":
3465
+ return col > 0 ? emptyIdx - 1 : null;
3466
+ case "right":
3467
+ return col < COLS - 1 ? emptyIdx + 1 : null;
3468
+ default:
3469
+ return null;
3470
+ }
3471
+ }
3472
+ function swap(board, a, b) {
3473
+ const next = [...board];
3474
+ next[a] = board[b];
3475
+ next[b] = board[a];
3476
+ return next;
3477
+ }
3478
+ function shuffle(board, count) {
3479
+ let current = [...board];
3480
+ const directions = ["up", "down", "left", "right"];
3481
+ let lastDir = "";
3482
+ for (let i = 0; i < count; i++) {
3483
+ const emptyIdx = getEmptyIndex(current);
3484
+ const validMoves = directions.filter((d) => {
3485
+ if (d === lastDir) return false;
3486
+ return getNeighbor(emptyIdx, d) !== null;
3487
+ });
3488
+ const dir = validMoves[Math.floor(Math.random() * validMoves.length)];
3489
+ const neighbor = getNeighbor(emptyIdx, dir);
3490
+ current = swap(current, emptyIdx, neighbor);
3491
+ const opposites = { up: "down", down: "up", left: "right", right: "left" };
3492
+ lastDir = opposites[dir];
3493
+ }
3494
+ return current;
3495
+ }
3496
+ function isAdjacentToEmpty(board, tileIdx) {
3497
+ const emptyIdx = getEmptyIndex(board);
3498
+ const eRow = Math.floor(emptyIdx / COLS);
3499
+ const eCol = emptyIdx % COLS;
3500
+ const tRow = Math.floor(tileIdx / COLS);
3501
+ const tCol = tileIdx % COLS;
3502
+ return Math.abs(eRow - tRow) === 1 && eCol === tCol || Math.abs(eCol - tCol) === 1 && eRow === tRow;
3503
+ }
3504
+ function PuzzleApp({ containerWidth, containerHeight } = {}) {
3505
+ const theme = useTheme();
3506
+ const TILE_WIDTH = containerWidth ? Math.floor((containerWidth - 2) / COLS) : DEFAULT_TILE_WIDTH;
3507
+ const TILE_HEIGHT = containerHeight ? Math.max(3, Math.floor((containerHeight - 2 - 4) / ROWS)) : DEFAULT_TILE_HEIGHT;
3508
+ const [board, setBoard] = useState28(SOLVED);
3509
+ const [moves, setMoves] = useState28(0);
3510
+ const [solved, setSolved] = useState28(false);
3511
+ const boardRef = useRef14(board);
3512
+ const movesRef = useRef14(moves);
3513
+ boardRef.current = board;
3514
+ movesRef.current = moves;
3515
+ const doMove = (direction) => {
3516
+ const current = boardRef.current;
3517
+ if (isSolved(current) && movesRef.current > 0) return;
3518
+ const emptyIdx = getEmptyIndex(current);
3519
+ const neighbor = getNeighbor(emptyIdx, direction);
3520
+ if (neighbor === null) return;
3521
+ const next = swap(current, emptyIdx, neighbor);
3522
+ boardRef.current = next;
3523
+ movesRef.current++;
3524
+ setBoard(next);
3525
+ setMoves(movesRef.current);
3526
+ setSolved(isSolved(next));
3527
+ };
3528
+ const doShuffle = () => {
3529
+ const shuffled = shuffle(SOLVED, 30);
3530
+ boardRef.current = shuffled;
3531
+ movesRef.current = 0;
3532
+ setBoard(shuffled);
3533
+ setMoves(0);
3534
+ setSolved(false);
3535
+ };
3536
+ useEffect9(() => {
3537
+ doShuffle();
3538
+ }, []);
3539
+ useKeyboard20((event) => {
3540
+ if (event.name === "up") {
3541
+ doMove("down");
3542
+ event.preventDefault();
3543
+ } else if (event.name === "down") {
3544
+ doMove("up");
3545
+ event.preventDefault();
3546
+ } else if (event.name === "left") {
3547
+ doMove("right");
3548
+ event.preventDefault();
3549
+ } else if (event.name === "right") {
3550
+ doMove("left");
3551
+ event.preventDefault();
3552
+ } else if (event.key === "r") {
3553
+ doShuffle();
3554
+ event.preventDefault();
3555
+ }
3556
+ });
3557
+ const rows = useMemo7(() => {
3558
+ const result = [];
3559
+ for (let r = 0; r < ROWS; r++) {
3560
+ result.push(board.slice(r * COLS, r * COLS + COLS));
3561
+ }
3562
+ return result;
3563
+ }, [board]);
3564
+ return /* @__PURE__ */ jsxs40("box", { flexDirection: "column", flexGrow: 1, paddingX: 1, children: [
3565
+ /* @__PURE__ */ jsx51("text", { style: textStyle({ dim: true, fg: theme.muted }), children: "Slide tiles to solve the puzzle" }),
3566
+ /* @__PURE__ */ jsx51("box", { flexDirection: "column", children: rows.map((row, rowIdx) => /* @__PURE__ */ jsx51("box", { flexDirection: "row", children: row.map((tile, colIdx) => {
3567
+ const idx = rowIdx * COLS + colIdx;
3568
+ if (tile === 0) {
3569
+ return /* @__PURE__ */ jsx51("box", { width: TILE_WIDTH, height: TILE_HEIGHT }, idx);
3570
+ }
3571
+ const color = tileColors[tile - 1];
3572
+ return /* @__PURE__ */ jsx51(
3573
+ "box",
3574
+ {
3575
+ width: TILE_WIDTH,
3576
+ height: TILE_HEIGHT,
3577
+ border: true,
3578
+ borderStyle: "rounded",
3579
+ borderColor: solved ? theme.success : color,
3580
+ onMouseDown: () => {
3581
+ if (isAdjacentToEmpty(boardRef.current, idx)) {
3582
+ const emptyIdx = getEmptyIndex(boardRef.current);
3583
+ const next = swap(boardRef.current, emptyIdx, idx);
3584
+ boardRef.current = next;
3585
+ movesRef.current++;
3586
+ setBoard(next);
3587
+ setMoves(movesRef.current);
3588
+ setSolved(isSolved(next));
3589
+ }
3590
+ },
3591
+ children: /* @__PURE__ */ jsx51("text", { style: textStyle({
3592
+ fg: solved ? theme.success : color,
3593
+ bold: true
3594
+ }), children: String(tile).padStart(2) })
3595
+ },
3596
+ idx
3597
+ );
3598
+ }) }, rowIdx)) }),
3599
+ /* @__PURE__ */ jsx51("box", { height: 1 }),
3600
+ /* @__PURE__ */ jsxs40("box", { flexDirection: "row", gap: 2, children: [
3601
+ /* @__PURE__ */ jsxs40("text", { style: textStyle({ dim: true, fg: theme.muted }), children: [
3602
+ "Moves: ",
3603
+ moves
3604
+ ] }),
3605
+ solved && moves > 0 && /* @__PURE__ */ jsx51("text", { style: textStyle({ fg: theme.success, bold: true }), children: "Solved!" })
3606
+ ] }),
3607
+ /* @__PURE__ */ jsx51("box", { flexGrow: 1 }),
3608
+ /* @__PURE__ */ jsx51("box", { paddingX: 1, children: /* @__PURE__ */ jsx51(StatusBar, { items: [
3609
+ { key: "\u2191\u2193\u2190\u2192", label: "slide" },
3610
+ { key: "click", label: "slide tile" },
3611
+ { key: "r", label: "shuffle" }
3612
+ ] }) })
3613
+ ] });
2807
3614
  }
2808
3615
 
2809
3616
  // src/landing/landing-app.tsx
2810
- function LandingApp({ useKeyboard: useKeyboard20 }) {
3617
+ import { Fragment as Fragment7, jsx as jsx52, jsxs as jsxs41 } from "react/jsx-runtime";
3618
+ var DEMOS = ["ripple", "puzzle"];
3619
+ var TAB_HEIGHT = 2;
3620
+ var TAB_WIDTHS = DEMOS.map((n) => n.length + 4);
3621
+ var TAB_POSITIONS = [];
3622
+ var _pos = 0;
3623
+ for (const w of TAB_WIDTHS) {
3624
+ TAB_POSITIONS.push(_pos);
3625
+ _pos += w;
3626
+ }
3627
+ function LandingApp({ useKeyboard: useKeyboard23 }) {
2811
3628
  const theme = useTheme();
2812
3629
  const { width, height, isNarrow, isTiny, isMobile } = useBreakpoints();
2813
- const isBrowser = typeof document !== "undefined";
2814
- const { clearRect, installLinksClearRect } = useMemo7(() => {
2815
- const logoHeight = isTiny ? 2 : isNarrow ? 13 : 7;
2816
- const logoExtra = isBrowser ? 1 : 0;
3630
+ const [activeIndex, setActiveIndex] = useState29(0);
3631
+ const mousePosRef = useRef15(null);
3632
+ const matrixRipplesRef = useRef15([]);
3633
+ useKeyboard23((event) => {
3634
+ if (event.name === "tab") {
3635
+ setActiveIndex((prev) => (prev + 1) % DEMOS.length);
3636
+ event.preventDefault();
3637
+ }
3638
+ });
3639
+ const isBrowser2 = typeof document !== "undefined";
3640
+ const { clearRect, installLinksClearRect, boxTop } = useMemo8(() => {
3641
+ const logoHeight = isTiny ? 2 : isMobile ? 7 : isNarrow ? 13 : 7;
3642
+ const logoExtra = isBrowser2 ? 1 : 0;
2817
3643
  const gap = isMobile ? 0 : 1;
2818
- const installLinksTop = 3 + logoHeight + logoExtra + gap;
3644
+ const paddingTop = isMobile ? 1 : 3;
3645
+ const installLinksTop = paddingTop + logoHeight + logoExtra + gap;
2819
3646
  const installLinksHeight = 3;
2820
- const boxTop = installLinksTop + installLinksHeight + gap + 1;
2821
- const bh = height - boxTop - 1;
3647
+ const boxTop2 = installLinksTop + installLinksHeight + gap + 1;
3648
+ const bh = height - boxTop2 - 1;
3649
+ const bw = Math.min(82, width - 2);
3650
+ const bl = Math.floor((width - bw) / 2);
2822
3651
  return {
2823
- clearRect: { top: boxTop, left: 1, width: width - 2, height: bh },
2824
- installLinksClearRect: { top: installLinksTop, left: 1, width: width - 2, height: installLinksHeight }
3652
+ clearRect: { top: boxTop2, left: bl, width: bw, height: bh },
3653
+ installLinksClearRect: { top: installLinksTop, left: 1, width: width - 2, height: installLinksHeight },
3654
+ boxTop: boxTop2
2825
3655
  };
2826
- }, [width, height, isTiny, isNarrow, isMobile, isBrowser]);
2827
- return /* @__PURE__ */ React.createElement("box", { width: "100%", height: "100%", position: "relative" }, /* @__PURE__ */ React.createElement(MatrixBackground, { width, height, clearRect, clearRects: isBrowser ? void 0 : [installLinksClearRect] }), /* @__PURE__ */ React.createElement("box", { position: "absolute", top: 0, left: 0, width, height, zIndex: 1, flexDirection: "column", shouldFill: false }, /* @__PURE__ */ React.createElement("box", { flexGrow: 1, flexDirection: "column", paddingTop: 3, paddingLeft: 1, paddingRight: 1, paddingBottom: 1, gap: isMobile ? 0 : 1, shouldFill: false }, /* @__PURE__ */ React.createElement("box", { flexShrink: 0, shouldFill: false }, /* @__PURE__ */ React.createElement(Logo, { compact: isTiny, narrow: isNarrow, mobile: isMobile })), /* @__PURE__ */ React.createElement("box", { flexDirection: "row", flexWrap: "wrap", justifyContent: "center", gap: isMobile ? 0 : 1, flexShrink: 0, shouldFill: false }, /* @__PURE__ */ React.createElement("box", { border: true, borderStyle: "rounded", borderColor: theme.border, paddingX: 1, flexDirection: "column", flexShrink: 0 }, /* @__PURE__ */ React.createElement("text", null, /* @__PURE__ */ React.createElement("span", { style: textStyle({ dim: true }) }, "$ "), /* @__PURE__ */ React.createElement("span", { style: textStyle({ bold: true }) }, "bunx "), /* @__PURE__ */ React.createElement("span", { style: textStyle({ fg: theme.accent }) }, "@gridland/demo landing"))), /* @__PURE__ */ React.createElement(InstallBox, null), /* @__PURE__ */ React.createElement(LinksBox, null)), /* @__PURE__ */ React.createElement("box", { flexGrow: 1, border: true, borderStyle: "rounded", borderColor: theme.border, flexDirection: "column", overflow: "hidden" }))));
3656
+ }, [width, height, isTiny, isNarrow, isMobile, isBrowser2]);
3657
+ const MAX_BOX_WIDTH = 82;
3658
+ const availableWidth = width - 2;
3659
+ const boxWidth = Math.min(MAX_BOX_WIDTH, availableWidth);
3660
+ const boxLeft = Math.floor((width - boxWidth) / 2);
3661
+ const mouseOffset = useMemo8(() => ({
3662
+ x: boxLeft + 1,
3663
+ // box left edge + border(1)
3664
+ y: boxTop + TAB_HEIGHT + 1
3665
+ // boxTop + tab rows + box top border(1)
3666
+ }), [boxTop, boxLeft]);
3667
+ const containerWidth = boxWidth - 2;
3668
+ const maxBoxHeight = 20;
3669
+ const containerHeight = Math.min(height - boxTop - 1 - TAB_HEIGHT, maxBoxHeight - TAB_HEIGHT);
3670
+ const activeStart = TAB_POSITIONS[activeIndex];
3671
+ const activeWidth = TAB_WIDTHS[activeIndex];
3672
+ const activeEnd = activeStart + activeWidth;
3673
+ const connectParts = [];
3674
+ if (activeStart === 0) {
3675
+ connectParts.push(/* @__PURE__ */ jsx52("span", { style: textStyle({ fg: theme.border }), children: "\u2502" }, "cl"));
3676
+ connectParts.push(/* @__PURE__ */ jsx52("span", { style: textStyle({ fg: theme.border }), children: " ".repeat(activeWidth - 2) }, "gap"));
3677
+ connectParts.push(/* @__PURE__ */ jsx52("span", { style: textStyle({ fg: theme.border }), children: "\u2570" }, "cr"));
3678
+ } else {
3679
+ connectParts.push(
3680
+ /* @__PURE__ */ jsx52("span", { style: textStyle({ fg: theme.border }), children: "\u256D" + "\u2500".repeat(activeStart - 1) }, "left")
3681
+ );
3682
+ connectParts.push(/* @__PURE__ */ jsx52("span", { style: textStyle({ fg: theme.border }), children: "\u256F" }, "cl"));
3683
+ connectParts.push(/* @__PURE__ */ jsx52("span", { style: textStyle({ fg: theme.border }), children: " ".repeat(activeWidth - 2) }, "gap"));
3684
+ connectParts.push(/* @__PURE__ */ jsx52("span", { style: textStyle({ fg: theme.border }), children: "\u2570" }, "cr"));
3685
+ }
3686
+ const rightFill = boxWidth - activeEnd - 1;
3687
+ if (rightFill > 0) {
3688
+ connectParts.push(
3689
+ /* @__PURE__ */ jsx52("span", { style: textStyle({ fg: theme.border }), children: "\u2500".repeat(rightFill) }, "right")
3690
+ );
3691
+ }
3692
+ connectParts.push(/* @__PURE__ */ jsx52("span", { style: textStyle({ fg: theme.border }), children: "\u256E" }, "corner-r"));
3693
+ return /* @__PURE__ */ jsxs41("box", { width: "100%", height: "100%", position: "relative", children: [
3694
+ /* @__PURE__ */ jsx52(MatrixBackground, { width, height, clearRect, clearRects: isBrowser2 ? void 0 : [installLinksClearRect], mousePosRef, ripplesRef: matrixRipplesRef }),
3695
+ /* @__PURE__ */ jsx52(
3696
+ "box",
3697
+ {
3698
+ position: "absolute",
3699
+ top: 0,
3700
+ left: 0,
3701
+ width,
3702
+ height,
3703
+ zIndex: 1,
3704
+ flexDirection: "column",
3705
+ shouldFill: false,
3706
+ onMouseMove: (e) => {
3707
+ mousePosRef.current = { x: e.x, y: e.y };
3708
+ },
3709
+ onMouseDown: (e) => {
3710
+ matrixRipplesRef.current = [
3711
+ ...matrixRipplesRef.current,
3712
+ { x: e.x, y: e.y, createdAt: Date.now() }
3713
+ ];
3714
+ },
3715
+ children: /* @__PURE__ */ jsxs41("box", { flexGrow: 1, flexDirection: "column", paddingTop: isMobile ? 1 : 3, paddingLeft: 1, paddingRight: 1, paddingBottom: 1, gap: isMobile ? 0 : 1, shouldFill: false, children: [
3716
+ /* @__PURE__ */ jsx52("box", { flexShrink: 0, shouldFill: false, children: /* @__PURE__ */ jsx52(Logo, { compact: isTiny, narrow: isNarrow, mobile: isMobile }) }),
3717
+ /* @__PURE__ */ jsxs41("box", { flexDirection: "row", flexWrap: "wrap", justifyContent: "center", gap: isMobile ? 0 : 1, flexShrink: 0, shouldFill: false, children: [
3718
+ !isMobile && /* @__PURE__ */ jsx52("box", { border: true, borderStyle: "rounded", borderColor: theme.border, paddingX: 1, flexDirection: "column", flexShrink: 0, children: /* @__PURE__ */ jsxs41("text", { children: [
3719
+ /* @__PURE__ */ jsx52("span", { style: textStyle({ dim: true }), children: "$ " }),
3720
+ /* @__PURE__ */ jsx52("span", { style: textStyle({ bold: true }), children: "bunx " }),
3721
+ /* @__PURE__ */ jsx52("span", { style: textStyle({ fg: theme.accent }), children: "@gridland/demo landing" })
3722
+ ] }) }),
3723
+ !isMobile && /* @__PURE__ */ jsx52(InstallBox, {}),
3724
+ /* @__PURE__ */ jsx52(LinksBox, {})
3725
+ ] }),
3726
+ /* @__PURE__ */ jsxs41("box", { flexDirection: "column", width: boxWidth, maxWidth: MAX_BOX_WIDTH, maxHeight: 20, alignSelf: "center", flexGrow: 1, children: [
3727
+ /* @__PURE__ */ jsx52("box", { height: 1, flexShrink: 0, flexDirection: "row", shouldFill: false, children: DEMOS.map((name2, i) => {
3728
+ const isActive = i === activeIndex;
3729
+ const w = TAB_WIDTHS[i];
3730
+ return /* @__PURE__ */ jsx52("box", { width: w, onMouseDown: () => setActiveIndex(i), children: /* @__PURE__ */ jsx52("text", { style: textStyle({ fg: theme.border }), children: isActive ? "\u256D" + "\u2500".repeat(w - 2) + "\u256E" : " ".repeat(w) }) }, name2);
3731
+ }) }),
3732
+ /* @__PURE__ */ jsx52("box", { height: 1, flexShrink: 0, flexDirection: "row", shouldFill: false, children: DEMOS.map((name2, i) => {
3733
+ const isActive = i === activeIndex;
3734
+ const w = TAB_WIDTHS[i];
3735
+ return /* @__PURE__ */ jsx52("box", { width: w, onMouseDown: () => setActiveIndex(i), children: /* @__PURE__ */ jsx52("text", { children: isActive ? /* @__PURE__ */ jsxs41(Fragment7, { children: [
3736
+ /* @__PURE__ */ jsx52("span", { style: textStyle({ fg: theme.border }), children: "\u2502" }),
3737
+ /* @__PURE__ */ jsx52("span", { style: textStyle({ bold: true, fg: theme.foreground }), children: ` ${name2} ` }),
3738
+ /* @__PURE__ */ jsx52("span", { style: textStyle({ fg: theme.border }), children: "\u2502" })
3739
+ ] }) : /* @__PURE__ */ jsx52("span", { style: textStyle({ fg: theme.muted }), children: ` ${name2} ` }) }) }, name2);
3740
+ }) }),
3741
+ /* @__PURE__ */ jsxs41("box", { position: "relative", flexGrow: 1, children: [
3742
+ /* @__PURE__ */ jsx52("box", { position: "absolute", top: 0, left: 0, width: boxWidth, height: 1, zIndex: 2, children: /* @__PURE__ */ jsx52("text", { children: connectParts }) }),
3743
+ /* @__PURE__ */ jsxs41("box", { border: true, borderStyle: "rounded", borderColor: theme.border, flexGrow: 1, flexDirection: "column", overflow: "hidden", children: [
3744
+ activeIndex === 0 && /* @__PURE__ */ jsx52(RippleApp, { mouseOffset, containerWidth, containerHeight }),
3745
+ activeIndex === 1 && /* @__PURE__ */ jsx52(PuzzleApp, { containerWidth, containerHeight })
3746
+ ] })
3747
+ ] }),
3748
+ /* @__PURE__ */ jsx52("box", { height: 1 }),
3749
+ /* @__PURE__ */ jsx52("box", { width: "100%", alignItems: "center", flexDirection: "column", shouldFill: false, children: /* @__PURE__ */ jsxs41("text", { style: textStyle({ dim: true, fg: theme.muted }), children: [
3750
+ "Made with \u2764\uFE0F by ",
3751
+ /* @__PURE__ */ jsx52("a", { href: "https://cjroth.com", style: { attributes: 1 << 3, fg: theme.muted }, children: "Chris Roth" }),
3752
+ " + ",
3753
+ /* @__PURE__ */ jsx52("a", { href: "https://jessicacheng.studio", style: { attributes: 1 << 3, fg: theme.muted }, children: "Jessica Cheng" })
3754
+ ] }) })
3755
+ ] })
3756
+ ] })
3757
+ }
3758
+ )
3759
+ ] });
2828
3760
  }
2829
3761
 
3762
+ // src/landing/matrix-rain.tsx
3763
+ import { jsx as jsx53 } from "react/jsx-runtime";
3764
+
3765
+ // src/landing/about-modal.tsx
3766
+ import { jsx as jsx54, jsxs as jsxs42 } from "react/jsx-runtime";
3767
+
2830
3768
  // demos/index.tsx
3769
+ import { jsx as jsx55 } from "react/jsx-runtime";
2831
3770
  var demos = [
2832
- { name: "gradient", app: () => /* @__PURE__ */ React.createElement(GradientApp, null) },
2833
- { name: "ascii", app: () => /* @__PURE__ */ React.createElement(AsciiApp, null) },
2834
- { name: "table", app: () => /* @__PURE__ */ React.createElement(TableApp, null) },
2835
- { name: "spinner", app: () => /* @__PURE__ */ React.createElement(SpinnerApp, null) },
2836
- { name: "select-input", app: () => /* @__PURE__ */ React.createElement(SelectInputApp, null) },
2837
- { name: "multi-select", app: () => /* @__PURE__ */ React.createElement(MultiSelectApp, null) },
2838
- { name: "prompt-input", app: () => /* @__PURE__ */ React.createElement(PromptInputApp, null) },
2839
- { name: "text-input", app: () => /* @__PURE__ */ React.createElement(TextInputApp, null) },
2840
- { name: "link", app: () => /* @__PURE__ */ React.createElement(LinkApp, null) },
2841
- { name: "tabs", app: () => /* @__PURE__ */ React.createElement(TabBarApp, null) },
2842
- { name: "status-bar", app: () => /* @__PURE__ */ React.createElement(StatusBarApp, null) },
2843
- { name: "modal", app: () => /* @__PURE__ */ React.createElement(ModalApp, null) },
2844
- { name: "primitives", app: () => /* @__PURE__ */ React.createElement(PrimitivesApp, null) },
2845
- { name: "chat", app: () => /* @__PURE__ */ React.createElement(ChatApp, null) },
2846
- { name: "chain-of-thought", app: () => /* @__PURE__ */ React.createElement(ChainOfThoughtApp, null) },
2847
- { name: "message", app: () => /* @__PURE__ */ React.createElement(MessageApp, null) },
2848
- { name: "terminal-window", app: () => /* @__PURE__ */ React.createElement(TerminalWindowApp, null) },
2849
- { name: "focus", app: () => /* @__PURE__ */ React.createElement(FocusApp, null) },
2850
- { name: "pointer", app: () => /* @__PURE__ */ React.createElement(PointerApp, null) },
2851
- { name: "cursor-highlight", app: () => /* @__PURE__ */ React.createElement(CursorHighlightApp, null) },
2852
- { name: "text-style", app: () => /* @__PURE__ */ React.createElement(TextStyleApp, null) },
2853
- { name: "headless", app: () => /* @__PURE__ */ React.createElement(HeadlessApp, null) },
2854
- { name: "theming", app: () => /* @__PURE__ */ React.createElement(ThemingApp, null) },
2855
- { name: "landing", app: () => /* @__PURE__ */ React.createElement(LandingApp, { useKeyboard: useKeyboard18 }) }
3771
+ { name: "gradient", app: () => /* @__PURE__ */ jsx55(GradientApp, {}) },
3772
+ { name: "ascii", app: () => /* @__PURE__ */ jsx55(AsciiApp, {}) },
3773
+ { name: "table", app: () => /* @__PURE__ */ jsx55(TableApp, {}) },
3774
+ { name: "spinner", app: () => /* @__PURE__ */ jsx55(SpinnerApp, {}) },
3775
+ { name: "select-input", app: () => /* @__PURE__ */ jsx55(SelectInputApp, {}) },
3776
+ { name: "multi-select", app: () => /* @__PURE__ */ jsx55(MultiSelectApp, {}) },
3777
+ { name: "prompt-input", app: () => /* @__PURE__ */ jsx55(PromptInputApp, {}) },
3778
+ { name: "text-input", app: () => /* @__PURE__ */ jsx55(TextInputApp, {}) },
3779
+ { name: "link", app: () => /* @__PURE__ */ jsx55(LinkApp, {}) },
3780
+ { name: "tabs", app: () => /* @__PURE__ */ jsx55(TabBarApp, {}) },
3781
+ { name: "status-bar", app: () => /* @__PURE__ */ jsx55(StatusBarApp, {}) },
3782
+ { name: "modal", app: () => /* @__PURE__ */ jsx55(ModalApp, {}) },
3783
+ { name: "primitives", app: () => /* @__PURE__ */ jsx55(PrimitivesApp, {}) },
3784
+ { name: "chat", app: () => /* @__PURE__ */ jsx55(ChatApp, {}) },
3785
+ { name: "chain-of-thought", app: () => /* @__PURE__ */ jsx55(ChainOfThoughtApp, {}) },
3786
+ { name: "message", app: () => /* @__PURE__ */ jsx55(MessageApp, {}) },
3787
+ { name: "terminal-window", app: () => /* @__PURE__ */ jsx55(TerminalWindowApp, {}) },
3788
+ { name: "focus-grid", app: () => /* @__PURE__ */ jsx55(FocusGridApp, {}) },
3789
+ { name: "focus-chat", app: () => /* @__PURE__ */ jsx55(FocusChatApp, {}) },
3790
+ { name: "focus", app: () => /* @__PURE__ */ jsx55(FocusApp, {}) },
3791
+ { name: "pointer", app: () => /* @__PURE__ */ jsx55(PointerApp, {}) },
3792
+ { name: "cursor-highlight", app: () => /* @__PURE__ */ jsx55(CursorHighlightApp, {}) },
3793
+ { name: "text-style", app: () => /* @__PURE__ */ jsx55(TextStyleApp, {}) },
3794
+ { name: "headless", app: () => /* @__PURE__ */ jsx55(HeadlessApp, {}) },
3795
+ { name: "theming", app: () => /* @__PURE__ */ jsx55(ThemingApp, {}) },
3796
+ { name: "landing", app: () => /* @__PURE__ */ jsx55(LandingApp, { useKeyboard: useKeyboard21 }) },
3797
+ { name: "ripple", app: () => /* @__PURE__ */ jsx55(RippleApp, {}) },
3798
+ { name: "puzzle", app: () => /* @__PURE__ */ jsx55(PuzzleApp, {}) }
2856
3799
  ];
2857
3800
 
2858
3801
  // src/run.tsx
3802
+ import { jsx as jsx56 } from "react/jsx-runtime";
2859
3803
  var _renderer;
2860
3804
  function DemoShell({ children }) {
2861
- useKeyboard19((event) => {
2862
- if (event.name === "q" || event.name === "escape") {
3805
+ useKeyboard22((event) => {
3806
+ if (event.defaultPrevented) return;
3807
+ if (event.name === "escape") {
2863
3808
  _renderer.destroy();
2864
3809
  }
2865
3810
  });
2866
- return /* @__PURE__ */ React.createElement("box", { flexDirection: "column", flexGrow: 1 }, children);
3811
+ return /* @__PURE__ */ jsx56("box", { flexDirection: "column", flexGrow: 1, children });
2867
3812
  }
2868
3813
  async function runDemo(name2) {
2869
3814
  const demo = demos.find((d) => d.name === name2);
@@ -2873,7 +3818,7 @@ async function runDemo(name2) {
2873
3818
  process.exit(1);
2874
3819
  }
2875
3820
  _renderer = await createCliRenderer({ exitOnCtrlC: true });
2876
- createRoot(_renderer).render(/* @__PURE__ */ React.createElement(DemoShell, null, demo.app()));
3821
+ createRoot(_renderer).render(/* @__PURE__ */ jsx56(DemoShell, { children: demo.app() }));
2877
3822
  }
2878
3823
  var name = process.argv[2];
2879
3824
  if (name) {