@gridland/demo 0.2.49 → 0.2.51

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,11 @@
1
1
  // src/run.tsx
2
- import { createCliRenderer, createRoot, useKeyboard as useKeyboard2 } from "@gridland/bun";
2
+ import { createCliRenderer, createRoot, useKeyboard as useKeyboard19 } from "@gridland/bun";
3
3
 
4
- // ../ui/scripts/demo-apps.tsx
5
- import { useState as useState11, useCallback as useCallback4, useRef as useRef9, useEffect as useEffect5 } from "react";
4
+ // demos/index.tsx
5
+ import { useKeyboard as useKeyboard18 } from "@gridland/utils";
6
+
7
+ // demos/gradient.tsx
8
+ import { useState as useState8 } from "react";
6
9
  import { useKeyboard } from "@gridland/utils";
7
10
 
8
11
  // ../ui/components/theme/themes.ts
@@ -33,19 +36,6 @@ import { jsx as jsx2 } from "react/jsx-runtime";
33
36
  var UNDERLINE = 1 << 3;
34
37
  var UNDERLINE_DASHED = 1 << 4;
35
38
  var UNDERLINE_DOTTED = 1 << 6;
36
- function Link({ children, url, underline = "solid", color }) {
37
- const theme = useTheme();
38
- const resolvedColor = color ?? theme.accent;
39
- let attributes = 0;
40
- if (underline === "solid") {
41
- attributes = UNDERLINE;
42
- } else if (underline === "dashed") {
43
- attributes = UNDERLINE | UNDERLINE_DASHED;
44
- } else if (underline === "dotted") {
45
- attributes = UNDERLINE | UNDERLINE_DOTTED;
46
- }
47
- return /* @__PURE__ */ jsx2("text", { children: /* @__PURE__ */ jsx2("a", { href: url, style: { attributes, fg: resolvedColor }, children }) });
48
- }
49
39
 
50
40
  // ../ui/components/link/link-demo.tsx
51
41
  import { useState } from "react";
@@ -72,7 +62,7 @@ function textStyle(opts) {
72
62
 
73
63
  // ../ui/components/status-bar/status-bar.tsx
74
64
  import { jsx as jsx3 } from "react/jsx-runtime";
75
- function StatusBar({ items, extra }) {
65
+ function StatusBar({ items: items3, extra }) {
76
66
  const theme = useTheme();
77
67
  const parts = [];
78
68
  if (extra !== void 0) {
@@ -83,7 +73,7 @@ function StatusBar({ items, extra }) {
83
73
  /* @__PURE__ */ jsx3("span", { style: textStyle({ dim: true, fg: theme.placeholder }), children: " \u2502 " }, "pipe")
84
74
  );
85
75
  }
86
- items.forEach((item, i) => {
76
+ items3.forEach((item, i) => {
87
77
  if (i > 0) {
88
78
  parts.push(/* @__PURE__ */ jsx3("span", { children: " " }, `gap-${i}`));
89
79
  }
@@ -111,35 +101,6 @@ function useKeyboardContext(propOverride) {
111
101
 
112
102
  // ../ui/components/link/link-demo.tsx
113
103
  import { jsx as jsx5, jsxs } from "react/jsx-runtime";
114
- var MODES = ["solid", "dashed", "dotted", "none"];
115
- function LinkDemo({
116
- url = "https://opentui.com",
117
- label = "Visit opentui.com",
118
- useKeyboard: useKeyboardProp
119
- }) {
120
- const useKeyboard3 = useKeyboardContext(useKeyboardProp);
121
- const [modeIndex, setModeIndex] = useState(0);
122
- const mode = MODES[modeIndex];
123
- useKeyboard3?.((event) => {
124
- if (event.name === "right") {
125
- setModeIndex((i) => (i + 1) % MODES.length);
126
- } else if (event.name === "left") {
127
- setModeIndex((i) => (i - 1 + MODES.length) % MODES.length);
128
- }
129
- });
130
- return /* @__PURE__ */ jsxs("box", { flexDirection: "column", gap: 1, children: [
131
- /* @__PURE__ */ jsx5(Link, { url, underline: mode, children: label }),
132
- /* @__PURE__ */ jsx5(
133
- StatusBar,
134
- {
135
- extra: /* @__PURE__ */ jsx5("span", { style: textStyle({ bold: true }), children: mode.padEnd(6) }),
136
- items: [
137
- { key: "\u2190\u2192", label: "underline style" }
138
- ]
139
- }
140
- )
141
- ] });
142
- }
143
104
 
144
105
  // ../ui/components/ascii/ascii.tsx
145
106
  import { jsx as jsx6 } from "react/jsx-runtime";
@@ -148,25 +109,43 @@ import { jsx as jsx6 } from "react/jsx-runtime";
148
109
  import { useEffect, useState as useState2 } from "react";
149
110
  import { jsx as jsx7, jsxs as jsxs2 } from "react/jsx-runtime";
150
111
  var VARIANTS = {
151
- dots: { frames: ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"], interval: 83 },
112
+ dots: { frames: ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"], interval: 80 },
152
113
  pulse: { frames: ["\xB7", "\u2219", "\u25CF", "\u2219", "\xB7", "\xB7", "\xB7"], interval: 180 },
153
114
  meter: { frames: ["\u25B1\u25B1\u25B1", "\u25B0\u25B1\u25B1", "\u25B0\u25B0\u25B1", "\u25B0\u25B0\u25B0", "\u25B0\u25B0\u25B1", "\u25B0\u25B1\u25B1", "\u25B1\u25B1\u25B1"], interval: 143 },
154
115
  bloom: { frames: ["\xB7", "\u2726", "\u2727", "\u2739", "\u273A", "\u274B", "\u2738", "\u2735", "\u2738", "\u274B", "\u273A", "\u2739", "\u2727", "\u2726", "\xB7", "\xB7"], interval: 100 },
155
116
  ellipsis: { frames: [" ", ". ", ".. ", "..."], interval: 333 }
156
117
  };
157
118
  var VARIANT_NAMES = Object.keys(VARIANTS);
158
- function Spinner({ variant = "dots", text, color }) {
119
+ var STATUS_SYMBOLS = {
120
+ success: "\u2714",
121
+ error: "\u2716",
122
+ warning: "\u26A0",
123
+ info: "\u2139"
124
+ };
125
+ function Spinner({ variant = "dots", text, color, status = "spinning" }) {
159
126
  const theme = useTheme();
160
- const resolvedColor = color ?? theme.accent;
161
127
  const { frames, interval } = VARIANTS[variant];
162
128
  const [frame, setFrame] = useState2(0);
163
129
  useEffect(() => {
130
+ if (status !== "spinning") return;
164
131
  setFrame(0);
165
132
  const timer = setInterval(() => {
166
133
  setFrame((prev) => (prev + 1) % frames.length);
167
134
  }, interval);
168
135
  return () => clearInterval(timer);
169
- }, [variant]);
136
+ }, [variant, status]);
137
+ if (status !== "spinning") {
138
+ const symbol = STATUS_SYMBOLS[status];
139
+ const statusColor = status === "success" ? theme.success : status === "error" ? theme.error : status === "warning" ? theme.warning : theme.accent;
140
+ return /* @__PURE__ */ jsxs2("text", { children: [
141
+ /* @__PURE__ */ jsx7("span", { style: { fg: statusColor }, children: symbol }),
142
+ text ? /* @__PURE__ */ jsxs2("span", { style: { fg: theme.foreground }, children: [
143
+ " ",
144
+ text
145
+ ] }) : null
146
+ ] });
147
+ }
148
+ const resolvedColor = color ?? theme.accent;
170
149
  return /* @__PURE__ */ jsxs2("text", { children: [
171
150
  /* @__PURE__ */ jsx7("span", { style: { fg: resolvedColor }, children: frames[frame] }),
172
151
  text ? /* @__PURE__ */ jsxs2("span", { style: { fg: theme.foreground }, children: [
@@ -181,9 +160,9 @@ import { useState as useState3 } from "react";
181
160
  import { jsx as jsx8, jsxs as jsxs3 } from "react/jsx-runtime";
182
161
  function SpinnerPicker({ useKeyboard: useKeyboardProp }) {
183
162
  const theme = useTheme();
184
- const useKeyboard3 = useKeyboardContext(useKeyboardProp);
163
+ const useKeyboard20 = useKeyboardContext(useKeyboardProp);
185
164
  const [selected, setSelected] = useState3(0);
186
- useKeyboard3?.((event) => {
165
+ useKeyboard20?.((event) => {
187
166
  if (event.name === "left") {
188
167
  setSelected((s) => s > 0 ? s - 1 : VARIANT_NAMES.length - 1);
189
168
  } else if (event.name === "right") {
@@ -296,7 +275,7 @@ var RADIO = "\u25CB";
296
275
  var CURSOR = "\u25B8";
297
276
  var SEPARATOR = "\u2500";
298
277
  function SelectInput({
299
- items = [],
278
+ items: items3 = [],
300
279
  defaultValue,
301
280
  value: controlledValue,
302
281
  onChange,
@@ -312,14 +291,14 @@ function SelectInput({
312
291
  useKeyboard: useKeyboardProp
313
292
  }) {
314
293
  const theme = useTheme();
315
- const useKeyboard3 = useKeyboardContext(useKeyboardProp);
294
+ const useKeyboard20 = useKeyboardContext(useKeyboardProp);
316
295
  const resolvedHighlight = highlightColor ?? theme.primary;
317
296
  const isControlled = controlledValue !== void 0;
318
297
  const controlledRef = useRef2(isControlled);
319
298
  if (controlledRef.current !== isControlled) {
320
299
  console.warn("SelectInput: switching between controlled and uncontrolled is not supported.");
321
300
  }
322
- const initialIndex = items.findIndex((i) => i.value === (isControlled ? controlledValue : defaultValue));
301
+ const initialIndex = items3.findIndex((i) => i.value === (isControlled ? controlledValue : defaultValue));
323
302
  const [state, dispatch] = useReducer(reducer, {
324
303
  cursor: Math.max(0, initialIndex),
325
304
  submitted: false
@@ -329,7 +308,7 @@ function SelectInput({
329
308
  const selectable = [];
330
309
  let index = 0;
331
310
  const grouped = /* @__PURE__ */ new Map();
332
- for (const item of items) {
311
+ for (const item of items3) {
333
312
  const group = item.group ?? "";
334
313
  const list = grouped.get(group) ?? [];
335
314
  list.push(item);
@@ -349,13 +328,13 @@ function SelectInput({
349
328
  }
350
329
  }
351
330
  return { flatRows: rows, selectableItems: selectable };
352
- }, [items]);
331
+ }, [items3]);
353
332
  const visibleCount = limit ?? VISIBLE;
354
333
  const cursorRowIndex = flatRows.findIndex((r) => r.type === "item" && r.index === state.cursor);
355
334
  const scrollOffset = Math.max(0, Math.min(cursorRowIndex - Math.floor(visibleCount / 2), flatRows.length - visibleCount));
356
335
  const visibleRows = flatRows.slice(scrollOffset, scrollOffset + visibleCount);
357
336
  const diamondColor = invalid ? theme.error : disabled ? theme.muted : theme.accent;
358
- useKeyboard3?.((event) => {
337
+ useKeyboard20?.((event) => {
359
338
  if (state.submitted || disabled) return;
360
339
  if (event.name === "up" || event.name === "k") {
361
340
  const direction = -1;
@@ -384,7 +363,7 @@ function SelectInput({
384
363
  }
385
364
  });
386
365
  if (state.submitted) {
387
- const selectedItem = isControlled ? items.find((i) => i.value === controlledValue) : selectableItems[state.cursor]?.item;
366
+ const selectedItem = isControlled ? items3.find((i) => i.value === controlledValue) : selectableItems[state.cursor]?.item;
388
367
  return /* @__PURE__ */ jsxs5("box", { flexDirection: "column", children: [
389
368
  /* @__PURE__ */ jsxs5("text", { children: [
390
369
  /* @__PURE__ */ jsx10("span", { style: textStyle({ fg: theme.success }), children: "\u25C6 " }),
@@ -498,7 +477,7 @@ var UNCHECKED = "\u25CB";
498
477
  var CURSOR2 = "\u25B8";
499
478
  var SEPARATOR2 = "\u2500";
500
479
  function MultiSelect({
501
- items = [],
480
+ items: items3 = [],
502
481
  defaultSelected = [],
503
482
  selected: controlledSelected,
504
483
  onChange,
@@ -519,7 +498,7 @@ function MultiSelect({
519
498
  useKeyboard: useKeyboardProp
520
499
  }) {
521
500
  const theme = useTheme();
522
- const useKeyboard3 = useKeyboardContext(useKeyboardProp);
501
+ const useKeyboard20 = useKeyboardContext(useKeyboardProp);
523
502
  const resolvedHighlight = highlightColor ?? theme.primary;
524
503
  const resolvedCheckbox = checkboxColor ?? theme.accent;
525
504
  const isControlled = controlledSelected !== void 0;
@@ -539,7 +518,7 @@ function MultiSelect({
539
518
  const selectable = [];
540
519
  let index = 0;
541
520
  const grouped = /* @__PURE__ */ new Map();
542
- for (const item of items) {
521
+ for (const item of items3) {
543
522
  const group = item.group ?? "";
544
523
  const list = grouped.get(group) ?? [];
545
524
  list.push(item);
@@ -559,7 +538,7 @@ function MultiSelect({
559
538
  }
560
539
  }
561
540
  return { flatRows: rows, selectableItems: selectable };
562
- }, [items]);
541
+ }, [items3]);
563
542
  const hasSubmitRow = allowEmpty || currentSelected.size > 0;
564
543
  const totalPositions = selectableItems.length + (hasSubmitRow ? 1 : 0);
565
544
  const isOnSubmit = hasSubmitRow && state.cursor === selectableItems.length;
@@ -575,7 +554,7 @@ function MultiSelect({
575
554
  }
576
555
  };
577
556
  const diamondColor = invalid ? theme.error : disabled ? theme.muted : theme.accent;
578
- useKeyboard3?.((event) => {
557
+ useKeyboard20?.((event) => {
579
558
  if (state.submitted || disabled) return;
580
559
  const move = (direction) => {
581
560
  let next = cursorRef.current + direction;
@@ -605,14 +584,14 @@ function MultiSelect({
605
584
  }
606
585
  }
607
586
  } else if (event.name === "a" && enableSelectAll) {
608
- const enabledValues = items.filter((i) => !i.disabled).map((i) => i.value);
587
+ const enabledValues = items3.filter((i) => !i.disabled).map((i) => i.value);
609
588
  setSelected(maxCount !== void 0 ? enabledValues.slice(0, maxCount) : enabledValues);
610
589
  } else if (event.name === "x" && enableClear) {
611
590
  setSelected([]);
612
591
  }
613
592
  });
614
593
  if (state.submitted) {
615
- const selectedItems = items.filter((i) => currentSelected.has(i.value));
594
+ const selectedItems = items3.filter((i) => currentSelected.has(i.value));
616
595
  return /* @__PURE__ */ jsxs6("box", { flexDirection: "column", children: [
617
596
  /* @__PURE__ */ jsxs6("text", { children: [
618
597
  /* @__PURE__ */ jsx11("span", { style: textStyle({ fg: theme.success }), children: "\u25C6 " }),
@@ -722,91 +701,145 @@ function MultiSelect({
722
701
  }
723
702
 
724
703
  // ../ui/components/table/table.tsx
725
- import { Fragment } from "react";
704
+ import { createContext as createContext3, useContext as useContext3, Children, isValidElement, Fragment } from "react";
726
705
  import { jsx as jsx12, jsxs as jsxs7 } from "react/jsx-runtime";
727
- function getColumns(data, columnsProp) {
706
+ var TableContext = createContext3(null);
707
+ function useTableContext() {
708
+ const ctx = useContext3(TableContext);
709
+ if (!ctx) throw new Error("Table compound components must be used within <TableRoot>");
710
+ return ctx;
711
+ }
712
+ function getColumns(data2, columnsProp) {
728
713
  if (columnsProp) return columnsProp;
729
714
  const keys = /* @__PURE__ */ new Set();
730
- for (const row of data) {
715
+ for (const row of data2) {
731
716
  for (const key in row) keys.add(key);
732
717
  }
733
718
  return Array.from(keys);
734
719
  }
735
- function calculateColumnWidths(columns, data, padding) {
736
- return columns.map((field) => {
737
- const headerWidth = String(field).length;
738
- const maxDataWidth = data.reduce((max, row) => {
739
- const val = row[field];
740
- return Math.max(max, val == null ? 0 : String(val).length);
741
- }, 0);
742
- return { field, width: Math.max(headerWidth, maxDataWidth) + padding * 2 };
743
- });
744
- }
745
720
  function padCell(value, width, padding) {
746
721
  const rightPad = width - value.length - padding;
747
722
  return " ".repeat(padding) + value + " ".repeat(Math.max(0, rightPad));
748
723
  }
724
+ function extractCellText(node) {
725
+ if (node == null) return "";
726
+ if (typeof node === "string") return node;
727
+ if (typeof node === "number" || typeof node === "boolean") return String(node);
728
+ if (Array.isArray(node)) return node.map(extractCellText).join("");
729
+ return "";
730
+ }
731
+ function collectColumnWidths(children, padding) {
732
+ const columnMaxWidths = [];
733
+ Children.forEach(children, (section) => {
734
+ if (!isValidElement(section)) return;
735
+ if (section.type === TableCaption) return;
736
+ Children.forEach(section.props.children, (row) => {
737
+ if (!isValidElement(row)) return;
738
+ let colIdx = 0;
739
+ Children.forEach(row.props.children, (cell) => {
740
+ if (!isValidElement(cell)) return;
741
+ const text = extractCellText(cell.props.children);
742
+ const width = text.length + padding * 2;
743
+ if (colIdx >= columnMaxWidths.length) {
744
+ columnMaxWidths.push(width);
745
+ } else {
746
+ columnMaxWidths[colIdx] = Math.max(columnMaxWidths[colIdx], width);
747
+ }
748
+ colIdx++;
749
+ });
750
+ });
751
+ });
752
+ return columnMaxWidths;
753
+ }
754
+ function getTotalWidth(columnWidths) {
755
+ if (columnWidths.length === 0) return 0;
756
+ return columnWidths.reduce((sum, w) => sum + w, 0) + (columnWidths.length - 1);
757
+ }
758
+ function TableRoot({ children, padding = 1, headerColor, borderColor }) {
759
+ const theme = useTheme();
760
+ const resolvedHeaderColor = headerColor ?? theme.foreground;
761
+ const resolvedBorderColor = borderColor ?? theme.muted;
762
+ const columnWidths = collectColumnWidths(children, padding);
763
+ return /* @__PURE__ */ jsx12(
764
+ TableContext.Provider,
765
+ {
766
+ value: {
767
+ columnWidths,
768
+ padding,
769
+ headerColor: resolvedHeaderColor,
770
+ borderColor: resolvedBorderColor,
771
+ foregroundColor: theme.foreground
772
+ },
773
+ children: /* @__PURE__ */ jsx12("box", { children })
774
+ }
775
+ );
776
+ }
777
+ function TableHeader({ children }) {
778
+ const ctx = useTableContext();
779
+ const totalWidth = getTotalWidth(ctx.columnWidths);
780
+ return /* @__PURE__ */ jsxs7("box", { children: [
781
+ children,
782
+ /* @__PURE__ */ jsx12("text", { children: /* @__PURE__ */ jsx12("span", { style: textStyle({ fg: ctx.borderColor }), children: "\u2500".repeat(totalWidth) }) })
783
+ ] });
784
+ }
785
+ function TableBody({ children }) {
786
+ const ctx = useTableContext();
787
+ const totalWidth = getTotalWidth(ctx.columnWidths);
788
+ const rows = Children.toArray(children);
789
+ return /* @__PURE__ */ jsx12("box", { children: rows.map((row, index) => /* @__PURE__ */ jsxs7(Fragment, { children: [
790
+ row,
791
+ index < rows.length - 1 && /* @__PURE__ */ jsx12("text", { children: /* @__PURE__ */ jsx12("span", { style: textStyle({ fg: ctx.borderColor, dim: true }), children: "\u2500".repeat(totalWidth) }) })
792
+ ] }, index)) });
793
+ }
794
+ function TableRow({ children }) {
795
+ const ctx = useTableContext();
796
+ const parts = [];
797
+ let colIdx = 0;
798
+ Children.forEach(children, (child) => {
799
+ if (!isValidElement(child)) return;
800
+ const text = extractCellText(child.props.children);
801
+ const width = ctx.columnWidths[colIdx] ?? text.length + ctx.padding * 2;
802
+ const padded = padCell(text, width, ctx.padding);
803
+ const isHead = child.type === TableHead;
804
+ if (colIdx > 0) {
805
+ parts.push(
806
+ /* @__PURE__ */ jsx12("span", { style: textStyle({ fg: ctx.borderColor, dim: true }), children: " " }, `sep-${colIdx}`)
807
+ );
808
+ }
809
+ if (isHead) {
810
+ parts.push(
811
+ /* @__PURE__ */ jsx12("span", { style: textStyle({ fg: ctx.headerColor }), children: padded }, `cell-${colIdx}`)
812
+ );
813
+ } else {
814
+ parts.push(
815
+ /* @__PURE__ */ jsx12("span", { style: textStyle({ fg: ctx.foregroundColor, dim: true }), children: padded }, `cell-${colIdx}`)
816
+ );
817
+ }
818
+ colIdx++;
819
+ });
820
+ return /* @__PURE__ */ jsx12("text", { children: parts });
821
+ }
822
+ function TableHead(_props) {
823
+ return null;
824
+ }
825
+ function TableCell(_props) {
826
+ return null;
827
+ }
828
+ function TableCaption({ children }) {
829
+ const ctx = useTableContext();
830
+ return /* @__PURE__ */ jsx12("text", { children: /* @__PURE__ */ jsx12("span", { style: textStyle({ fg: ctx.borderColor, dim: true }), children: extractCellText(children) }) });
831
+ }
749
832
  function Table({
750
- data,
833
+ data: data2,
751
834
  columns: columnsProp,
752
835
  padding = 1,
753
836
  headerColor,
754
837
  borderColor
755
838
  }) {
756
- const theme = useTheme();
757
- const resolvedHeaderColor = headerColor ?? theme.primary;
758
- const resolvedBorderColor = borderColor ?? theme.border;
759
- const columns = getColumns(data, columnsProp);
760
- const colInfo = calculateColumnWidths(columns, data, padding);
761
- const borderLine = (left, mid, right) => {
762
- const inner = colInfo.map((c) => "\u2500".repeat(c.width)).join(mid);
763
- return /* @__PURE__ */ jsx12("text", { children: /* @__PURE__ */ jsxs7("span", { style: textStyle({ fg: resolvedBorderColor, bold: true }), children: [
764
- left,
765
- inner,
766
- right
767
- ] }) });
768
- };
769
- const contentRow = (rowData, isHeader) => {
770
- const parts = [];
771
- parts.push(
772
- /* @__PURE__ */ jsx12("span", { style: textStyle({ fg: resolvedBorderColor, bold: true }), children: "\u2502" }, "left-border")
773
- );
774
- colInfo.forEach((col, i) => {
775
- const val = rowData[col.field];
776
- const str = val == null ? "" : String(val);
777
- const padded = padCell(str, col.width, padding);
778
- if (isHeader) {
779
- parts.push(
780
- /* @__PURE__ */ jsx12("span", { style: textStyle({ fg: resolvedHeaderColor, bold: true }), children: padded }, `cell-${i}`)
781
- );
782
- } else {
783
- parts.push(
784
- /* @__PURE__ */ jsx12("span", { style: textStyle({ fg: theme.foreground }), children: padded }, `cell-${i}`)
785
- );
786
- }
787
- if (i < colInfo.length - 1) {
788
- parts.push(
789
- /* @__PURE__ */ jsx12("span", { style: textStyle({ fg: resolvedBorderColor, bold: true }), children: "\u2502" }, `sep-${i}`)
790
- );
791
- }
792
- });
793
- parts.push(
794
- /* @__PURE__ */ jsx12("span", { style: textStyle({ fg: resolvedBorderColor, bold: true }), children: "\u2502" }, "right-border")
795
- );
796
- return /* @__PURE__ */ jsx12("text", { children: parts });
797
- };
798
- const headerData = columns.reduce(
799
- (acc, col) => ({ ...acc, [col]: col }),
800
- {}
801
- );
802
- return /* @__PURE__ */ jsxs7("box", { children: [
803
- borderLine("\u250C", "\u252C", "\u2510"),
804
- contentRow(headerData, true),
805
- data.map((row, index) => /* @__PURE__ */ jsxs7(Fragment, { children: [
806
- borderLine("\u251C", "\u253C", "\u2524"),
807
- contentRow(row, false)
808
- ] }, index)),
809
- borderLine("\u2514", "\u2534", "\u2518")
839
+ const cols = getColumns(data2, columnsProp);
840
+ return /* @__PURE__ */ jsxs7(TableRoot, { padding, headerColor, borderColor, children: [
841
+ /* @__PURE__ */ jsx12(TableHeader, { children: /* @__PURE__ */ jsx12(TableRow, { children: cols.map((col) => /* @__PURE__ */ jsx12(TableHead, { children: String(col) }, String(col))) }) }),
842
+ /* @__PURE__ */ jsx12(TableBody, { children: data2.map((row, index) => /* @__PURE__ */ jsx12(TableRow, { children: cols.map((col) => /* @__PURE__ */ jsx12(TableCell, { children: row[col] == null ? "" : String(row[col]) }, String(col))) }, index)) })
810
843
  ] });
811
844
  }
812
845
 
@@ -865,23 +898,23 @@ function generateGradient(colors, steps) {
865
898
  }
866
899
  return result;
867
900
  }
868
- function Gradient({ children, name, colors }) {
869
- if (name && colors) throw new Error("The `name` and `colors` props are mutually exclusive");
870
- if (!name && !colors) throw new Error("Either `name` or `colors` prop must be provided");
871
- const gradientColors = name ? GRADIENTS[name] : colors;
872
- const lines = children.split("\n");
873
- const maxLength = Math.max(...lines.map((l) => l.length));
901
+ function Gradient({ children, name: name2, colors }) {
902
+ if (name2 && colors) throw new Error("The `name` and `colors` props are mutually exclusive");
903
+ if (!name2 && !colors) throw new Error("Either `name` or `colors` prop must be provided");
904
+ const gradientColors = name2 ? GRADIENTS[name2] : colors;
905
+ const lines2 = children.split("\n");
906
+ const maxLength = Math.max(...lines2.map((l) => l.length));
874
907
  if (maxLength === 0) return /* @__PURE__ */ jsx13("text", { children });
875
908
  const hexColors = generateGradient(gradientColors, maxLength);
876
- return /* @__PURE__ */ jsx13(Fragment2, { children: lines.map((line, lineIndex) => /* @__PURE__ */ jsx13("text", { children: line.split("").map((char, charIndex) => /* @__PURE__ */ jsx13("span", { style: { fg: hexColors[charIndex] }, children: char }, charIndex)) }, lineIndex)) });
909
+ return /* @__PURE__ */ jsx13(Fragment2, { children: lines2.map((line, lineIndex) => /* @__PURE__ */ jsx13("text", { children: line.split("").map((char, charIndex) => /* @__PURE__ */ jsx13("span", { style: { fg: hexColors[charIndex] }, children: char }, charIndex)) }, lineIndex)) });
877
910
  }
878
911
 
879
912
  // ../ui/components/tab-bar/tab-bar.tsx
880
- import { createContext as createContext3, useContext as useContext3, useState as useState5, Children, isValidElement } from "react";
913
+ import { createContext as createContext4, useContext as useContext4, useState as useState5, Children as Children2, isValidElement as isValidElement2 } from "react";
881
914
  import { Fragment as Fragment3, jsx as jsx14, jsxs as jsxs8 } from "react/jsx-runtime";
882
- var TabsContext = createContext3(null);
915
+ var TabsContext = createContext4(null);
883
916
  function useTabsContext() {
884
- const ctx = useContext3(TabsContext);
917
+ const ctx = useContext4(TabsContext);
885
918
  if (!ctx) throw new Error("Tabs compound components must be used within <Tabs>");
886
919
  return ctx;
887
920
  }
@@ -907,8 +940,8 @@ function TabsList({
907
940
  const { value } = useTabsContext();
908
941
  const color = activeColor ?? theme.accent;
909
942
  const triggers = [];
910
- Children.forEach(children, (child) => {
911
- if (isValidElement(child) && "value" in child.props) {
943
+ Children2.forEach(children, (child) => {
944
+ if (isValidElement2(child) && "value" in child.props) {
912
945
  triggers.push({ value: child.props.value, label: child.props.children });
913
946
  }
914
947
  });
@@ -971,9 +1004,9 @@ function Modal({
971
1004
  useKeyboard: useKeyboardProp
972
1005
  }) {
973
1006
  const theme = useTheme();
974
- const useKeyboard3 = useKeyboardContext(useKeyboardProp);
1007
+ const useKeyboard20 = useKeyboardContext(useKeyboardProp);
975
1008
  const resolvedBorderColor = borderColor ?? theme.muted;
976
- useKeyboard3?.((event) => {
1009
+ useKeyboard20?.((event) => {
977
1010
  if (event.name === "escape" && onClose) {
978
1011
  onClose();
979
1012
  }
@@ -999,28 +1032,29 @@ import {
999
1032
  useState as useState6,
1000
1033
  useRef as useRef4,
1001
1034
  useCallback as useCallback2,
1035
+ useEffect as useEffect2,
1002
1036
  useMemo as useMemo3,
1003
- createContext as createContext4,
1004
- useContext as useContext4
1037
+ createContext as createContext5,
1038
+ useContext as useContext5
1005
1039
  } from "react";
1006
1040
  import { Fragment as Fragment5, jsx as jsx16, jsxs as jsxs10 } from "react/jsx-runtime";
1007
- var PromptInputControllerCtx = createContext4(null);
1008
- var useOptionalController = () => useContext4(PromptInputControllerCtx);
1009
- var PromptInputContext = createContext4(null);
1041
+ var PromptInputControllerCtx = createContext5(null);
1042
+ var useOptionalController = () => useContext5(PromptInputControllerCtx);
1043
+ var PromptInputContext = createContext5(null);
1010
1044
  function usePromptInput() {
1011
- const ctx = useContext4(PromptInputContext);
1045
+ const ctx = useContext5(PromptInputContext);
1012
1046
  if (!ctx) {
1013
1047
  throw new Error("usePromptInput must be used within a <PromptInput> component");
1014
1048
  }
1015
1049
  return ctx;
1016
1050
  }
1017
- function computeDefaultSuggestions(input, commands, files) {
1018
- if (input.startsWith("/") && commands.length > 0) {
1019
- return commands.filter((c) => c.cmd.startsWith(input)).map((c) => ({ text: c.cmd, desc: c.desc }));
1051
+ function computeDefaultSuggestions(input, commands2, files2) {
1052
+ if (input.startsWith("/") && commands2.length > 0) {
1053
+ return commands2.filter((c) => c.cmd.startsWith(input)).map((c) => ({ text: c.cmd, desc: c.desc }));
1020
1054
  }
1021
- if (input.includes("@") && files.length > 0) {
1055
+ if (input.includes("@") && files2.length > 0) {
1022
1056
  const query = input.split("@").pop() ?? "";
1023
- return files.filter((f) => f.toLowerCase().includes(query.toLowerCase())).map((f) => ({ text: "@" + f }));
1057
+ return files2.filter((f) => f.toLowerCase().includes(query.toLowerCase())).map((f) => ({ text: "@" + f }));
1024
1058
  }
1025
1059
  return [];
1026
1060
  }
@@ -1030,9 +1064,10 @@ function resolveStatusHintText(status, submittedText, streamingText, errorText,
1030
1064
  if (status === "error") return errorText;
1031
1065
  return disabledText;
1032
1066
  }
1067
+ var DIVIDER_LINE = "\u2500".repeat(500);
1033
1068
  function PromptInputDivider() {
1034
1069
  const { theme } = usePromptInput();
1035
- return /* @__PURE__ */ jsx16("text", { wrapMode: "none", children: /* @__PURE__ */ jsx16("span", { style: textStyle({ dim: true, fg: theme.muted }), children: "\u2500".repeat(500) }) });
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 }) });
1036
1071
  }
1037
1072
  function PromptInputSuggestions() {
1038
1073
  const { suggestions, sugIdx, maxSuggestions, theme } = usePromptInput();
@@ -1099,18 +1134,27 @@ function PromptInput({
1099
1134
  errorText = "An error occurred. Try again.",
1100
1135
  disabled: disabledProp = false,
1101
1136
  disabledText = "Generating...",
1102
- commands = [],
1103
- files = [],
1137
+ commands: commands2 = [],
1138
+ files: files2 = [],
1104
1139
  getSuggestions: customGetSuggestions,
1105
1140
  maxSuggestions = 5,
1106
1141
  enableHistory = true,
1107
1142
  model,
1108
1143
  showDividers = true,
1144
+ autoFocus = false,
1109
1145
  useKeyboard: useKeyboardProp,
1110
1146
  children
1111
1147
  }) {
1112
1148
  const theme = useTheme();
1113
- const useKeyboard3 = useKeyboardContext(useKeyboardProp);
1149
+ const useKeyboard20 = useKeyboardContext(useKeyboardProp);
1150
+ useEffect2(() => {
1151
+ if (!autoFocus) return;
1152
+ if (typeof document === "undefined") return;
1153
+ const canvas = document.querySelector("canvas");
1154
+ if (canvas && document.activeElement !== canvas) {
1155
+ canvas.focus();
1156
+ }
1157
+ }, [autoFocus]);
1114
1158
  const resolvedPromptColor = promptColor ?? theme.muted;
1115
1159
  const disabled = status ? status === "submitted" || status === "streaming" : disabledProp;
1116
1160
  const statusHintText = resolveStatusHintText(status, submittedText, streamingLabel, errorText, disabledText);
@@ -1167,8 +1211,8 @@ function PromptInput({
1167
1211
  }, []);
1168
1212
  const computeSuggestions = useCallback2((input) => {
1169
1213
  if (customGetSuggestions) return customGetSuggestions(input);
1170
- return computeDefaultSuggestions(input, commands, files);
1171
- }, [customGetSuggestions, commands, files]);
1214
+ return computeDefaultSuggestions(input, commands2, files2);
1215
+ }, [customGetSuggestions, commands2, files2]);
1172
1216
  const updateValue = useCallback2((next) => {
1173
1217
  valueRef.current = next;
1174
1218
  if (isControlled) {
@@ -1203,7 +1247,7 @@ function PromptInput({
1203
1247
  clearInput();
1204
1248
  }
1205
1249
  }, [onSubmit, clearInput]);
1206
- useKeyboard3?.((event) => {
1250
+ useKeyboard20?.((event) => {
1207
1251
  if (event.name === "escape" && (status === "streaming" || status === "submitted") && onStop) {
1208
1252
  onStop();
1209
1253
  return;
@@ -1304,14 +1348,16 @@ function PromptInput({
1304
1348
  theme
1305
1349
  };
1306
1350
  if (children) {
1307
- return /* @__PURE__ */ jsx16(PromptInputContext.Provider, { value: ctxValue, children: /* @__PURE__ */ jsx16("box", { flexDirection: "column", children }) });
1351
+ return /* @__PURE__ */ jsx16(PromptInputContext.Provider, { value: ctxValue, children: /* @__PURE__ */ jsx16("box", { flexDirection: "column", flexShrink: 0, children }) });
1308
1352
  }
1309
- return /* @__PURE__ */ jsx16(PromptInputContext.Provider, { value: ctxValue, children: /* @__PURE__ */ jsxs10("box", { flexDirection: "column", children: [
1353
+ return /* @__PURE__ */ jsx16(PromptInputContext.Provider, { value: ctxValue, children: /* @__PURE__ */ jsxs10("box", { flexDirection: "column", flexShrink: 0, children: [
1310
1354
  showDividers && /* @__PURE__ */ jsx16(PromptInputDivider, {}),
1311
- /* @__PURE__ */ jsx16(PromptInputSuggestions, {}),
1312
- /* @__PURE__ */ jsx16(PromptInputTextarea, {}),
1313
- /* @__PURE__ */ jsx16(PromptInputStatusText, {}),
1314
- /* @__PURE__ */ jsx16(PromptInputModel, {}),
1355
+ /* @__PURE__ */ jsxs10("box", { flexDirection: "column", paddingX: 1, children: [
1356
+ /* @__PURE__ */ jsx16(PromptInputSuggestions, {}),
1357
+ /* @__PURE__ */ jsx16(PromptInputTextarea, {}),
1358
+ /* @__PURE__ */ jsx16(PromptInputStatusText, {}),
1359
+ /* @__PURE__ */ jsx16(PromptInputModel, {})
1360
+ ] }),
1315
1361
  showDividers && /* @__PURE__ */ jsx16(PromptInputDivider, {})
1316
1362
  ] }) });
1317
1363
  }
@@ -1443,13 +1489,19 @@ function ChatPanel({
1443
1489
  ] });
1444
1490
  }
1445
1491
 
1446
- // ../ui/components/timeline/timeline.tsx
1447
- import { useState as useState7, useEffect as useEffect2, useRef as useRef5 } from "react";
1448
- import { jsx as jsx18, jsxs as jsxs12 } from "react/jsx-runtime";
1492
+ // ../ui/components/chain-of-thought/chain-of-thought.tsx
1493
+ 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";
1449
1495
  var DOTS = ["\u25CB", "\u25D4", "\u25D1", "\u25D5", "\u25CF"];
1450
- function getStepDot(status) {
1451
- return status === "pending" ? "\u25CB" : "\u25CF";
1452
- }
1496
+ var SPINNER_INTERVAL = 150;
1497
+ var ChainOfThoughtContext = createContext6(null);
1498
+ var useChainOfThought = () => {
1499
+ const context = useContext6(ChainOfThoughtContext);
1500
+ if (!context) {
1501
+ throw new Error("ChainOfThought components must be used within <ChainOfThought>");
1502
+ }
1503
+ return context;
1504
+ };
1453
1505
  function getStepColor(status, theme) {
1454
1506
  switch (status) {
1455
1507
  case "done":
@@ -1464,78 +1516,89 @@ function getStepColor(status, theme) {
1464
1516
  return theme.muted;
1465
1517
  }
1466
1518
  }
1467
- function StepRow({ step, isLast, theme, frame }) {
1468
- const color = getStepColor(step.status, theme);
1469
- const isActive = step.status === "running";
1470
- const isPending = step.status === "pending";
1519
+ var ChainOfThought = memo(({
1520
+ open,
1521
+ defaultOpen = false,
1522
+ onOpenChange,
1523
+ children
1524
+ }) => {
1525
+ const [internalOpen, setInternalOpen] = useState7(defaultOpen);
1526
+ const isOpen = open ?? internalOpen;
1527
+ const setIsOpen = onOpenChange ?? setInternalOpen;
1528
+ const context = useMemo4(
1529
+ () => ({ isOpen, setIsOpen }),
1530
+ [isOpen, setIsOpen]
1531
+ );
1532
+ return /* @__PURE__ */ jsx18(ChainOfThoughtContext.Provider, { value: context, children: /* @__PURE__ */ jsx18("box", { flexDirection: "column", children }) });
1533
+ });
1534
+ var ChainOfThoughtHeader = memo(({
1535
+ duration,
1536
+ children = "Thought for"
1537
+ }) => {
1538
+ const theme = useTheme();
1539
+ const { isOpen } = useChainOfThought();
1540
+ const arrow = isOpen ? "\u25BC" : "\u25B6";
1541
+ return /* @__PURE__ */ jsxs12("text", { children: [
1542
+ /* @__PURE__ */ jsx18("span", { style: textStyle({ fg: theme.muted }), children: arrow }),
1543
+ /* @__PURE__ */ jsxs12("span", { style: textStyle({ dim: true, fg: theme.muted }), children: [
1544
+ " ",
1545
+ children,
1546
+ duration ? " " + duration : ""
1547
+ ] })
1548
+ ] });
1549
+ });
1550
+ var ChainOfThoughtContent = memo(({ children }) => {
1551
+ const { isOpen } = useChainOfThought();
1552
+ if (!isOpen) return null;
1553
+ return /* @__PURE__ */ jsx18(Fragment6, { children });
1554
+ });
1555
+ var ChainOfThoughtStep = memo(({
1556
+ label,
1557
+ description,
1558
+ status = "done",
1559
+ isLast = false,
1560
+ children
1561
+ }) => {
1562
+ const theme = useTheme();
1563
+ const isActive = status === "running";
1564
+ const isPending = status === "pending";
1565
+ const color = getStepColor(status, theme);
1471
1566
  const pipe = "\u2502";
1472
- const dot = isActive ? DOTS[frame % DOTS.length] : getStepDot(step.status);
1473
- const dashIdx = step.label.indexOf(" \u2014 ");
1474
- const mainLabel = dashIdx >= 0 ? step.label.slice(0, dashIdx) : step.label;
1475
- const detail = dashIdx >= 0 ? step.label.slice(dashIdx) : "";
1567
+ const [frame, setFrame] = useState7(0);
1568
+ useEffect3(() => {
1569
+ if (!isActive) {
1570
+ setFrame(0);
1571
+ return;
1572
+ }
1573
+ const id = setInterval(() => setFrame((f) => f + 1), SPINNER_INTERVAL);
1574
+ return () => clearInterval(id);
1575
+ }, [isActive]);
1576
+ const dot = isActive ? DOTS[frame % DOTS.length] : isPending ? "\u25CB" : "\u25CF";
1476
1577
  return /* @__PURE__ */ jsxs12("box", { flexDirection: "column", marginLeft: 1, children: [
1477
1578
  /* @__PURE__ */ jsxs12("text", { children: [
1478
1579
  /* @__PURE__ */ jsx18("span", { style: textStyle({ fg: color }), children: dot }),
1479
1580
  /* @__PURE__ */ jsx18("span", { style: textStyle({ fg: theme.foreground }), children: " " }),
1480
- /* @__PURE__ */ jsx18("span", { style: textStyle({ fg: isPending ? theme.muted : color, dim: isPending, bold: isActive }), children: mainLabel }),
1481
- detail && /* @__PURE__ */ jsx18("span", { style: textStyle({ dim: true, fg: theme.muted }), children: detail })
1581
+ /* @__PURE__ */ jsx18("span", { style: textStyle({ fg: isPending ? theme.muted : color, dim: isPending, bold: isActive }), children: label }),
1582
+ description && /* @__PURE__ */ jsx18("span", { style: textStyle({ dim: true, fg: theme.muted }), children: " \u2014 " + description })
1482
1583
  ] }),
1483
- step.output && /* @__PURE__ */ jsxs12("text", { children: [
1584
+ children && /* @__PURE__ */ jsxs12("text", { children: [
1484
1585
  /* @__PURE__ */ jsx18("span", { style: textStyle({ fg: color, dim: true }), children: pipe + " " }),
1485
- /* @__PURE__ */ jsx18("span", { style: textStyle({ fg: step.status === "error" ? theme.error : theme.accent }), children: step.output })
1586
+ /* @__PURE__ */ jsx18("span", { style: textStyle({ fg: status === "error" ? theme.error : theme.accent }), children })
1486
1587
  ] }),
1487
1588
  !isLast && /* @__PURE__ */ jsx18("text", { children: /* @__PURE__ */ jsx18("span", { style: textStyle({ fg: color, dim: true }), children: pipe }) })
1488
1589
  ] });
1489
- }
1490
- function Timeline({
1491
- steps,
1492
- duration,
1493
- collapsed = true,
1494
- headerLabel = "Thought for"
1495
- }) {
1496
- const theme = useTheme();
1497
- const arrow = collapsed ? "\u25B6" : "\u25BC";
1498
- const durationStr = duration ?? "0ms";
1499
- const hasRunning = steps?.some((s) => s.status === "running") ?? false;
1500
- const [frame, setFrame] = useState7(0);
1501
- const alive = useRef5(true);
1502
- useEffect2(() => {
1503
- alive.current = true;
1504
- return () => {
1505
- alive.current = false;
1506
- };
1507
- }, []);
1508
- useEffect2(() => {
1509
- if (!hasRunning) return;
1510
- const id = setInterval(() => {
1511
- if (alive.current) setFrame((f) => f + 1);
1512
- }, 150);
1513
- return () => clearInterval(id);
1514
- }, [hasRunning]);
1515
- return /* @__PURE__ */ jsxs12("box", { flexDirection: "column", children: [
1516
- /* @__PURE__ */ jsxs12("text", { children: [
1517
- /* @__PURE__ */ jsx18("span", { style: textStyle({ fg: theme.muted }), children: arrow }),
1518
- /* @__PURE__ */ jsx18("span", { style: textStyle({ dim: true, fg: theme.muted }), children: " " + headerLabel + " " + durationStr })
1519
- ] }),
1520
- !collapsed && steps && steps.map((step, i) => /* @__PURE__ */ jsx18(
1521
- StepRow,
1522
- {
1523
- step,
1524
- isLast: i === steps.length - 1,
1525
- theme,
1526
- frame
1527
- },
1528
- `step-${i}`
1529
- ))
1530
- ] });
1531
- }
1590
+ });
1591
+ ChainOfThought.displayName = "ChainOfThought";
1592
+ ChainOfThoughtHeader.displayName = "ChainOfThoughtHeader";
1593
+ ChainOfThoughtContent.displayName = "ChainOfThoughtContent";
1594
+ ChainOfThoughtStep.displayName = "ChainOfThoughtStep";
1532
1595
 
1533
1596
  // ../ui/components/message/message.tsx
1534
- import { createContext as createContext5, useContext as useContext5 } from "react";
1597
+ import { createContext as createContext7, useContext as useContext7 } from "react";
1535
1598
  import { jsx as jsx19, jsxs as jsxs13 } from "react/jsx-runtime";
1536
- var MessageContext = createContext5(null);
1599
+ var MessageContext = createContext7(null);
1537
1600
  function useMessage() {
1538
- const ctx = useContext5(MessageContext);
1601
+ const ctx = useContext7(MessageContext);
1539
1602
  if (!ctx) throw new Error("useMessage must be used within <Message>");
1540
1603
  return ctx;
1541
1604
  }
@@ -1544,23 +1607,25 @@ function getBubbleColors(theme) {
1544
1607
  return isDark ? { assistantBg: "#2a2a4a", userBg: "#2a3a3a" } : { assistantBg: "#F1F5F9", userBg: "#E2E8F0" };
1545
1608
  }
1546
1609
  var TOOL_STATE_ICONS = {
1547
- "partial-call": "\u2022",
1610
+ pending: "\u2022",
1548
1611
  // •
1549
- "call": "\u280B",
1612
+ running: "\u280B",
1550
1613
  // ⠋
1551
- "result": "\u2713"
1614
+ completed: "\u2713",
1552
1615
  // ✓
1616
+ error: "\u2715"
1617
+ // ✕
1553
1618
  };
1554
1619
  function getToolStateColor(state, theme) {
1555
1620
  switch (state) {
1556
- case "partial-call":
1621
+ case "pending":
1557
1622
  return theme.muted;
1558
- case "call":
1623
+ case "running":
1559
1624
  return theme.warning;
1560
- case "result":
1625
+ case "completed":
1561
1626
  return theme.success;
1562
- default:
1563
- return theme.muted;
1627
+ case "error":
1628
+ return theme.error;
1564
1629
  }
1565
1630
  }
1566
1631
  function MessageContent({ children }) {
@@ -1585,45 +1650,57 @@ function MessageText({ children, isLast = false }) {
1585
1650
  isLast && isStreaming && /* @__PURE__ */ jsx19("span", { style: textStyle({ fg: textColor, dim: true, bg: backgroundColor }), children: streamingCursor })
1586
1651
  ] });
1587
1652
  }
1588
- function MessageReasoning({ part }) {
1589
- return /* @__PURE__ */ jsx19(
1590
- Timeline,
1591
- {
1592
- steps: part.steps,
1593
- duration: part.duration,
1594
- collapsed: part.collapsed
1595
- }
1596
- );
1653
+ function MessageReasoning({ duration, steps, collapsed = true, children }) {
1654
+ return /* @__PURE__ */ jsxs13(ChainOfThought, { defaultOpen: !collapsed, children: [
1655
+ /* @__PURE__ */ jsx19(ChainOfThoughtHeader, { duration }),
1656
+ /* @__PURE__ */ jsxs13(ChainOfThoughtContent, { children: [
1657
+ steps?.map((step, i) => /* @__PURE__ */ jsx19(
1658
+ ChainOfThoughtStep,
1659
+ {
1660
+ label: step.label,
1661
+ description: step.description,
1662
+ status: step.status,
1663
+ isLast: i === (steps?.length ?? 0) - 1,
1664
+ children: step.output
1665
+ },
1666
+ i
1667
+ )),
1668
+ children
1669
+ ] })
1670
+ ] });
1597
1671
  }
1598
- function MessageToolInvocation({ part, toolColors }) {
1672
+ function MessageToolCall({ name: name2, state = "pending", result, color }) {
1599
1673
  const theme = useTheme();
1600
1674
  const { backgroundColor, textColor } = useMessage();
1601
- const { toolName, state, result } = part.toolInvocation;
1602
- const icon = TOOL_STATE_ICONS[state] || "\u2022";
1603
- const stateColor = toolColors?.[toolName] ?? getToolStateColor(state, theme);
1604
- const isActive = state === "partial-call" || state === "call";
1675
+ const icon = TOOL_STATE_ICONS[state];
1676
+ const stateColor = color ?? getToolStateColor(state, theme);
1677
+ const isActive = state === "pending" || state === "running";
1605
1678
  return /* @__PURE__ */ jsxs13("box", { flexDirection: "column", children: [
1606
1679
  /* @__PURE__ */ jsxs13("text", { children: [
1607
1680
  /* @__PURE__ */ jsx19("span", { style: textStyle({ fg: stateColor, bg: backgroundColor }), children: icon }),
1608
1681
  /* @__PURE__ */ jsx19("span", { style: textStyle({ fg: textColor, bg: backgroundColor }), children: " " }),
1609
- /* @__PURE__ */ jsx19("span", { style: textStyle({ fg: stateColor, bold: isActive, bg: backgroundColor }), children: toolName }),
1682
+ /* @__PURE__ */ jsx19("span", { style: textStyle({ fg: stateColor, bold: isActive, bg: backgroundColor }), children: name2 }),
1610
1683
  isActive && /* @__PURE__ */ jsx19("span", { style: textStyle({ fg: textColor, dim: true, bg: backgroundColor }), children: " ..." })
1611
1684
  ] }),
1612
- state === "result" && result !== void 0 && /* @__PURE__ */ jsxs13("text", { children: [
1685
+ state === "completed" && result !== void 0 && /* @__PURE__ */ jsxs13("text", { children: [
1613
1686
  /* @__PURE__ */ jsx19("span", { style: textStyle({ fg: textColor, dim: true, bg: backgroundColor }), children: " \u2514\u2500 " }),
1614
1687
  /* @__PURE__ */ jsx19("span", { style: textStyle({ fg: textColor, dim: true, bg: backgroundColor }), children: String(result).slice(0, 120) })
1688
+ ] }),
1689
+ state === "error" && result !== void 0 && /* @__PURE__ */ jsxs13("text", { children: [
1690
+ /* @__PURE__ */ jsx19("span", { style: textStyle({ fg: theme.error, dim: true, bg: backgroundColor }), children: " \u2514\u2500 " }),
1691
+ /* @__PURE__ */ jsx19("span", { style: textStyle({ fg: theme.error, dim: true, bg: backgroundColor }), children: String(result).slice(0, 120) })
1615
1692
  ] })
1616
1693
  ] });
1617
1694
  }
1618
- function MessageSource({ part, index }) {
1695
+ function MessageSource({ title, url, index }) {
1619
1696
  const theme = useTheme();
1620
1697
  const { backgroundColor, textColor } = useMessage();
1621
- const title = part.source.title || part.source.url || "source";
1698
+ const displayTitle = title || url || "source";
1622
1699
  return /* @__PURE__ */ jsxs13("text", { children: [
1623
1700
  /* @__PURE__ */ jsx19("span", { style: textStyle({ fg: textColor, dim: true, bg: backgroundColor }), children: "[" }),
1624
1701
  /* @__PURE__ */ jsx19("span", { style: textStyle({ fg: theme.accent, bg: backgroundColor }), children: String(index + 1) }),
1625
1702
  /* @__PURE__ */ jsx19("span", { style: textStyle({ fg: textColor, dim: true, bg: backgroundColor }), children: "] " }),
1626
- /* @__PURE__ */ jsx19("span", { style: textStyle({ fg: theme.accent, bg: backgroundColor }), children: title })
1703
+ /* @__PURE__ */ jsx19("span", { style: textStyle({ fg: theme.accent, bg: backgroundColor }), children: displayTitle })
1627
1704
  ] });
1628
1705
  }
1629
1706
  function MessageFooter({ model, timestamp }) {
@@ -1659,7 +1736,7 @@ function Message({
1659
1736
  Message.Content = MessageContent;
1660
1737
  Message.Text = MessageText;
1661
1738
  Message.Reasoning = MessageReasoning;
1662
- Message.ToolInvocation = MessageToolInvocation;
1739
+ Message.ToolCall = MessageToolCall;
1663
1740
  Message.Source = MessageSource;
1664
1741
  Message.Footer = MessageFooter;
1665
1742
 
@@ -1685,1058 +1762,1122 @@ function useBreakpoints() {
1685
1762
  };
1686
1763
  }
1687
1764
 
1688
- // ../docs/components/landing/landing-app.tsx
1689
- import { useCallback as useCallback3, useRef as useRef8, useState as useState10 } from "react";
1690
-
1691
- // ../docs/components/landing/about-modal.tsx
1692
- import { jsx as jsx21, jsxs as jsxs15 } from "react/jsx-runtime";
1693
- function AboutModal({ onClose, useKeyboard: useKeyboard3 }) {
1765
+ // demos/gradient.tsx
1766
+ import figlet from "figlet";
1767
+ import ansiShadow from "figlet/importable-fonts/ANSI Shadow.js";
1768
+ figlet.parseFont("ANSI Shadow", ansiShadow);
1769
+ var art = figlet.textSync("gridland", { font: "ANSI Shadow" });
1770
+ var lines = art.split("\n").filter((l) => l.trimEnd().length > 0);
1771
+ var gradientNames = Object.keys(GRADIENTS);
1772
+ function GradientApp() {
1694
1773
  const theme = useTheme();
1695
- return /* @__PURE__ */ jsx21(Modal, { title: "About Gridland", useKeyboard: useKeyboard3, onClose, children: /* @__PURE__ */ jsxs15("box", { paddingX: 1, flexDirection: "column", gap: 1, children: [
1696
- /* @__PURE__ */ jsx21("text", { style: textStyle({ bold: true, fg: theme.accent }), children: "What is Gridland?" }),
1697
- /* @__PURE__ */ jsx21("text", { children: "Gridland renders terminal UIs to HTML5 Canvas with React." }),
1698
- /* @__PURE__ */ jsx21("text", { children: "No xterm.js. No terminal emulator. Just pixels." }),
1699
- /* @__PURE__ */ jsx21("text", { style: textStyle({ bold: true, fg: theme.accent }), children: "Features" }),
1700
- /* @__PURE__ */ jsxs15("text", { children: [
1701
- /* @__PURE__ */ jsxs15("span", { style: textStyle({ dim: true }), children: [
1702
- "\u2022",
1703
- " "
1704
- ] }),
1705
- "Canvas-rendered TUI components"
1706
- ] }),
1707
- /* @__PURE__ */ jsxs15("text", { children: [
1708
- /* @__PURE__ */ jsxs15("span", { style: textStyle({ dim: true }), children: [
1709
- "\u2022",
1710
- " "
1711
- ] }),
1712
- "React reconciler with JSX"
1713
- ] }),
1714
- /* @__PURE__ */ jsxs15("text", { children: [
1715
- /* @__PURE__ */ jsxs15("span", { style: textStyle({ dim: true }), children: [
1716
- "\u2022",
1717
- " "
1718
- ] }),
1719
- "Yoga flexbox layout engine"
1720
- ] }),
1721
- /* @__PURE__ */ jsxs15("text", { children: [
1722
- /* @__PURE__ */ jsxs15("span", { style: textStyle({ dim: true }), children: [
1723
- "\u2022",
1724
- " "
1725
- ] }),
1726
- "Keyboard, mouse, and clipboard support"
1727
- ] }),
1728
- /* @__PURE__ */ jsxs15("text", { children: [
1729
- /* @__PURE__ */ jsxs15("span", { style: textStyle({ dim: true }), children: [
1730
- "\u2022",
1731
- " "
1732
- ] }),
1733
- "Next.js and Vite plugins"
1734
- ] }),
1735
- /* @__PURE__ */ jsx21("text", { style: textStyle({ bold: true, fg: theme.accent }), children: "Tech Stack" }),
1736
- /* @__PURE__ */ jsx21("text", { children: "React + opentui engine + yoga-layout + HTML5 Canvas" }),
1737
- /* @__PURE__ */ jsx21("text", { style: textStyle({ dim: true }), children: "Press q to close" })
1738
- ] }) });
1774
+ const [index, setIndex] = useState8(gradientNames.indexOf("instagram"));
1775
+ const name2 = gradientNames[index];
1776
+ useKeyboard((event) => {
1777
+ if (event.name === "left") setIndex((i) => i > 0 ? i - 1 : gradientNames.length - 1);
1778
+ if (event.name === "right") setIndex((i) => i < gradientNames.length - 1 ? i + 1 : 0);
1779
+ });
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
+ )));
1739
1787
  }
1740
1788
 
1741
- // ../docs/components/landing/install-box.tsx
1742
- import { jsx as jsx22, jsxs as jsxs16 } from "react/jsx-runtime";
1743
- function InstallBox() {
1789
+ // demos/ascii.tsx
1790
+ import { useState as useState9 } from "react";
1791
+ import { useKeyboard as useKeyboard2 } from "@gridland/utils";
1792
+ import figlet2 from "figlet";
1793
+ import ansiShadow2 from "figlet/importable-fonts/ANSI Shadow.js";
1794
+ import big from "figlet/importable-fonts/Big.js";
1795
+ import doom from "figlet/importable-fonts/Doom.js";
1796
+ import slant from "figlet/importable-fonts/Slant.js";
1797
+ import speed from "figlet/importable-fonts/Speed.js";
1798
+ import standard from "figlet/importable-fonts/Standard.js";
1799
+ import block from "figlet/importable-fonts/Block.js";
1800
+ import colossal from "figlet/importable-fonts/Colossal.js";
1801
+ var fonts = [
1802
+ { name: "ANSI Shadow", data: ansiShadow2 },
1803
+ { name: "Standard", data: standard },
1804
+ { name: "Big", data: big },
1805
+ { name: "Doom", data: doom },
1806
+ { name: "Slant", data: slant },
1807
+ { name: "Speed", data: speed },
1808
+ { name: "Block", data: block },
1809
+ { name: "Colossal", data: colossal }
1810
+ ];
1811
+ for (const f of fonts) {
1812
+ figlet2.parseFont(f.name, f.data);
1813
+ }
1814
+ function getLines(fontName) {
1815
+ const art2 = figlet2.textSync("gridland", { font: fontName });
1816
+ return art2.split("\n").filter((l) => l.trimEnd().length > 0);
1817
+ }
1818
+ function AsciiApp() {
1744
1819
  const theme = useTheme();
1745
- return /* @__PURE__ */ jsx22(
1746
- "box",
1820
+ const [fontIndex, setFontIndex] = useState9(fonts.findIndex((f) => f.name === "Colossal"));
1821
+ const font = fonts[fontIndex];
1822
+ const lines2 = getLines(font.name);
1823
+ useKeyboard2((event) => {
1824
+ if (event.name === "left") setFontIndex((i) => i > 0 ? i - 1 : fonts.length - 1);
1825
+ if (event.name === "right") setFontIndex((i) => i < fonts.length - 1 ? i + 1 : 0);
1826
+ });
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,
1747
1829
  {
1748
- border: true,
1749
- borderStyle: "rounded",
1750
- borderColor: theme.border,
1751
- paddingX: 1,
1752
- flexDirection: "column",
1753
- flexShrink: 0,
1754
- children: /* @__PURE__ */ jsxs16("text", { children: [
1755
- /* @__PURE__ */ jsx22("span", { style: textStyle({ dim: true }), children: "$ " }),
1756
- /* @__PURE__ */ jsx22("span", { style: textStyle({ bold: true }), children: "bun create " }),
1757
- /* @__PURE__ */ jsx22("span", { style: textStyle({ fg: theme.accent }), children: "gridland" })
1758
- ] })
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))
1759
1832
  }
1760
- );
1833
+ )));
1761
1834
  }
1762
1835
 
1763
- // ../docs/components/landing/links-box.tsx
1764
- import { jsx as jsx23, jsxs as jsxs17 } from "react/jsx-runtime";
1765
- var UNDERLINE3 = 1 << 3;
1766
- function LinksBox() {
1767
- const theme = useTheme();
1768
- return /* @__PURE__ */ jsx23(
1769
- "box",
1836
+ // demos/table.tsx
1837
+ function TableApp() {
1838
+ const data2 = [
1839
+ { name: "Alice", role: "Engineer", status: "Active" },
1840
+ { name: "Bob", role: "Designer", status: "Active" },
1841
+ { name: "Charlie", role: "PM", status: "Away" }
1842
+ ];
1843
+ return /* @__PURE__ */ React.createElement("box", { padding: 1, flexGrow: 1 }, /* @__PURE__ */ React.createElement(Table, { data: data2, headerColor: "cyan", borderColor: "#5e81ac" }));
1844
+ }
1845
+
1846
+ // demos/spinner.tsx
1847
+ import { useKeyboard as useKeyboard3 } from "@gridland/utils";
1848
+ 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" }] })));
1850
+ }
1851
+
1852
+ // demos/select-input.tsx
1853
+ import { useState as useState10 } from "react";
1854
+ import { useKeyboard as useKeyboard4 } from "@gridland/utils";
1855
+ var items = [
1856
+ { label: "TypeScript", value: "ts" },
1857
+ { label: "JavaScript", value: "js" },
1858
+ { label: "Python", value: "py" },
1859
+ { label: "Rust", value: "rs" }
1860
+ ];
1861
+ function SelectInputApp() {
1862
+ const [submitted, setSubmitted] = useState10(false);
1863
+ const [resetKey, setResetKey] = useState10(0);
1864
+ useKeyboard4((event) => {
1865
+ if (submitted && event.name === "r") {
1866
+ setSubmitted(false);
1867
+ setResetKey((k) => k + 1);
1868
+ }
1869
+ });
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,
1770
1872
  {
1771
- border: true,
1772
- borderStyle: "rounded",
1773
- borderColor: theme.border,
1774
- paddingX: 1,
1775
- flexDirection: "column",
1776
- flexShrink: 0,
1777
- children: /* @__PURE__ */ jsxs17("text", { children: [
1778
- /* @__PURE__ */ jsx23("span", { children: "\u{1F431}" }),
1779
- /* @__PURE__ */ jsx23("a", { href: "https://github.com/thoughtfulllc/gridland", style: { attributes: UNDERLINE3, fg: theme.accent }, children: " GitHub" }),
1780
- /* @__PURE__ */ jsx23("span", { children: " " }),
1781
- /* @__PURE__ */ jsx23("span", { children: "\u{1F4D6}" }),
1782
- /* @__PURE__ */ jsx23("a", { href: "https://gridland.io/docs", style: { attributes: UNDERLINE3, fg: theme.accent }, children: " Docs" })
1783
- ] })
1873
+ key: resetKey,
1874
+ items,
1875
+ title: "Choose a language",
1876
+ useKeyboard: useKeyboard4,
1877
+ onSubmit: () => setSubmitted(true)
1784
1878
  }
1785
- );
1879
+ )), /* @__PURE__ */ React.createElement(StatusBar, { items: submitted ? [{ key: "r", label: "reset demo" }] : [
1880
+ { key: "\u2191\u2193", label: "select" },
1881
+ { key: "enter", label: "submit" }
1882
+ ] }));
1786
1883
  }
1787
1884
 
1788
- // ../docs/components/landing/logo.tsx
1789
- import { useState as useState8, useEffect as useEffect3, useRef as useRef6, useMemo as useMemo4 } from "react";
1790
- import figlet from "figlet";
1791
- import ansiShadow from "figlet/importable-fonts/ANSI Shadow.js";
1792
- import { Fragment as Fragment6, jsx as jsx24, jsxs as jsxs18 } from "react/jsx-runtime";
1793
- figlet.parseFont("ANSI Shadow", ansiShadow);
1794
- function makeArt(text) {
1795
- return figlet.textSync(text, { font: "ANSI Shadow" }).split("\n").filter((l) => l.trimEnd().length > 0).join("\n");
1885
+ // demos/multi-select.tsx
1886
+ import { useState as useState11 } from "react";
1887
+ import { useKeyboard as useKeyboard5 } from "@gridland/utils";
1888
+ var items2 = [
1889
+ { label: "TypeScript", value: "ts" },
1890
+ { label: "JavaScript", value: "js" },
1891
+ { label: "Python", value: "py" },
1892
+ { label: "Rust", value: "rs" }
1893
+ ];
1894
+ function MultiSelectApp() {
1895
+ const [submitted, setSubmitted] = useState11(false);
1896
+ const [resetKey, setResetKey] = useState11(0);
1897
+ useKeyboard5((event) => {
1898
+ if (submitted && event.name === "r") {
1899
+ setSubmitted(false);
1900
+ setResetKey((k) => k + 1);
1901
+ }
1902
+ });
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
+ ] }));
1796
1918
  }
1797
- var fullArt = makeArt("gridland");
1798
- var gridArt = makeArt("grid");
1799
- var landArt = makeArt("land");
1800
- var ART_HEIGHT = 6;
1801
- function useAnimation(duration = 1e3) {
1802
- const isBrowser = typeof document !== "undefined";
1803
- const [progress, setProgress] = useState8(isBrowser ? 0 : 1);
1804
- const startTime = useRef6(null);
1805
- useEffect3(() => {
1806
- if (!isBrowser) return;
1807
- let raf;
1808
- const tick = (time) => {
1809
- if (startTime.current === null) startTime.current = time;
1810
- const elapsed = time - startTime.current;
1811
- const t = Math.min(1, elapsed / duration);
1812
- const eased = 1 - Math.pow(1 - t, 3);
1813
- setProgress(eased);
1814
- if (t < 1) raf = requestAnimationFrame(tick);
1815
- };
1816
- raf = requestAnimationFrame(tick);
1817
- return () => cancelAnimationFrame(raf);
1818
- }, []);
1819
- return progress;
1820
- }
1821
- function RevealGradient({ children, revealCol }) {
1822
- const gradientColors = GRADIENTS.instagram;
1823
- const lines = children.split("\n");
1824
- const maxLength = Math.max(...lines.map((l) => l.length));
1825
- if (maxLength === 0) return /* @__PURE__ */ jsx24("text", { children });
1826
- const hexColors = useMemo4(() => generateGradient(gradientColors, maxLength), [maxLength]);
1827
- return /* @__PURE__ */ jsx24("box", { position: "relative", width: maxLength, height: lines.length, shouldFill: false, children: lines.map((line, lineIndex) => {
1828
- const runs = [];
1829
- let current = null;
1830
- for (let i = 0; i < line.length; i++) {
1831
- const revealed = i <= revealCol;
1832
- const char = line[i];
1833
- const isVisible = revealed && char !== " ";
1834
- if (isVisible) {
1835
- if (!current) {
1836
- current = { start: i, chars: [] };
1837
- }
1838
- current.chars.push(char);
1839
- } else {
1840
- if (current) {
1841
- runs.push(current);
1842
- current = null;
1843
- }
1844
- }
1919
+
1920
+ // demos/prompt-input.tsx
1921
+ import { useState as useState12 } from "react";
1922
+ import { useKeyboard as useKeyboard6 } from "@gridland/utils";
1923
+ var commands = [{ cmd: "/model", desc: "Switch model" }];
1924
+ var files = ["src/index.ts", "src/routes.ts", "src/auth.ts", "package.json"];
1925
+ var models = [
1926
+ { label: "Claude Opus", value: "opus" },
1927
+ { label: "Claude Sonnet", value: "sonnet" },
1928
+ { label: "Claude Haiku", value: "haiku" }
1929
+ ];
1930
+ function PromptInputApp() {
1931
+ const theme = useTheme();
1932
+ const [lastMessage, setLastMessage] = useState12("");
1933
+ const [model, setModel] = useState12("opus");
1934
+ const [showModelPicker, setShowModelPicker] = useState12(false);
1935
+ const [resetKey, setResetKey] = useState12(0);
1936
+ const handleSubmit = (msg) => {
1937
+ if (msg.text === "/model") {
1938
+ setShowModelPicker(true);
1939
+ setResetKey((k) => k + 1);
1940
+ return;
1845
1941
  }
1846
- if (current) runs.push(current);
1847
- return runs.map((run, runIndex) => /* @__PURE__ */ jsx24(
1848
- "box",
1942
+ setLastMessage(msg.text);
1943
+ };
1944
+ 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(
1946
+ SelectInput,
1849
1947
  {
1850
- position: "absolute",
1851
- top: lineIndex,
1852
- left: run.start,
1853
- shouldFill: false,
1854
- children: /* @__PURE__ */ jsx24("text", { shouldFill: false, children: run.chars.map((char, ci) => /* @__PURE__ */ jsx24(
1855
- "span",
1856
- {
1857
- style: { fg: hexColors[run.start + ci] },
1858
- children: char
1859
- },
1860
- ci
1861
- )) })
1862
- },
1863
- `${lineIndex}-${runIndex}`
1864
- ));
1865
- }) });
1866
- }
1867
- function Logo({ compact, narrow, mobile }) {
1868
- const isBrowser = typeof document !== "undefined";
1869
- const progress = useAnimation(900);
1870
- const artHeight = compact ? 1 : narrow ? ART_HEIGHT * 2 : ART_HEIGHT;
1871
- const dropOffset = Math.round((1 - progress) * -artHeight);
1872
- const revealProgress = Math.max(0, Math.min(1, (progress - 0.1) / 0.7));
1873
- const maxWidth = compact ? 8 : narrow ? 40 : 62;
1874
- const revealCol = Math.round(revealProgress * (maxWidth + 4)) - 2;
1875
- const taglineOpacity = Math.max(0, Math.min(1, (progress - 0.7) / 0.3));
1876
- const subtitle = /* @__PURE__ */ jsxs18(Fragment6, { children: [
1877
- /* @__PURE__ */ jsx24("text", { children: " " }),
1878
- /* @__PURE__ */ jsx24("box", { flexDirection: "column", alignItems: "center", width: "100%", shouldFill: false, children: /* @__PURE__ */ jsxs18("text", { style: textStyle({ fg: "#d4b0e8" }), opacity: taglineOpacity, wrapMode: "word", textAlign: "center", width: "100%", shouldFill: false, children: [
1879
- "A framework for building terminal apps, built on ",
1880
- /* @__PURE__ */ jsx24("a", { href: "https://opentui.com", style: { attributes: 72, fg: "#d4b0e8" }, children: "OpenTUI" }),
1881
- " + React." + (mobile ? " " : "\n") + "(Gridland apps, like this website, work in the browser and terminal.)"
1882
- ] }) })
1883
- ] });
1884
- if (!isBrowser) {
1885
- const art = compact ? "gridland" : narrow ? gridArt + "\n" + landArt : fullArt;
1886
- return /* @__PURE__ */ jsxs18("box", { flexDirection: "column", flexShrink: 0, width: "100%", alignItems: "center", children: [
1887
- /* @__PURE__ */ jsx24(Gradient, { name: "instagram", children: art }),
1888
- /* @__PURE__ */ jsx24("text", { children: " " }),
1889
- /* @__PURE__ */ jsx24("box", { flexDirection: "column", alignItems: "center", width: "100%", shouldFill: false, children: /* @__PURE__ */ jsxs18("text", { style: textStyle({ fg: "#d4b0e8" }), shouldFill: false, children: [
1890
- "A framework for building terminal apps, built on OpenTUI + React.",
1891
- "\n",
1892
- "(Gridland apps, like this website, work in the browser and terminal.)"
1893
- ] }) })
1894
- ] });
1895
- }
1896
- if (compact) {
1897
- return /* @__PURE__ */ jsxs18("box", { flexDirection: "column", flexShrink: 0, width: "100%", shouldFill: false, children: [
1898
- /* @__PURE__ */ jsx24("box", { height: artHeight, overflow: "hidden", position: "relative", width: "100%", flexShrink: 0, shouldFill: false, children: /* @__PURE__ */ jsx24("box", { position: "absolute", top: dropOffset, width: "100%", flexDirection: "column", alignItems: "center", shouldFill: false, children: /* @__PURE__ */ jsx24(RevealGradient, { revealCol, children: "gridland" }) }) }),
1899
- subtitle
1900
- ] });
1901
- }
1902
- if (narrow) {
1903
- return /* @__PURE__ */ jsxs18("box", { flexDirection: "column", flexShrink: 0, width: "100%", shouldFill: false, children: [
1904
- /* @__PURE__ */ jsx24("box", { height: artHeight, overflow: "hidden", position: "relative", width: "100%", flexShrink: 0, shouldFill: false, children: /* @__PURE__ */ jsxs18("box", { position: "absolute", top: dropOffset, width: "100%", flexDirection: "column", alignItems: "center", shouldFill: false, children: [
1905
- /* @__PURE__ */ jsx24(RevealGradient, { revealCol, children: gridArt }),
1906
- /* @__PURE__ */ jsx24(RevealGradient, { revealCol, children: landArt })
1907
- ] }) }),
1908
- subtitle
1909
- ] });
1948
+ items: models,
1949
+ defaultValue: model,
1950
+ useKeyboard: useKeyboard6,
1951
+ onSubmit: (value) => {
1952
+ setModel(value);
1953
+ setShowModelPicker(false);
1954
+ }
1955
+ }
1956
+ ))));
1910
1957
  }
1911
- return /* @__PURE__ */ jsxs18("box", { flexDirection: "column", flexShrink: 0, width: "100%", shouldFill: false, children: [
1912
- /* @__PURE__ */ jsx24("box", { height: artHeight, overflow: "hidden", position: "relative", width: "100%", flexShrink: 0, shouldFill: false, children: /* @__PURE__ */ jsx24("box", { position: "absolute", top: dropOffset, width: "100%", flexDirection: "column", alignItems: "center", shouldFill: false, children: /* @__PURE__ */ jsx24(RevealGradient, { revealCol, children: fullArt }) }) }),
1913
- subtitle
1914
- ] });
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
+ ] })));
1915
1974
  }
1916
1975
 
1917
- // ../docs/components/landing/matrix-background.tsx
1918
- import { useMemo as useMemo5 } from "react";
1976
+ // demos/text-input.tsx
1977
+ import { useState as useState13 } from "react";
1978
+ import { useKeyboard as useKeyboard7 } from "@gridland/utils";
1979
+ var FIELDS = [
1980
+ { label: "Username", placeholder: "enter your name", maxLength: 30, required: true },
1981
+ { label: "Email", placeholder: "user@example.com", maxLength: 50, required: true, description: "We'll never share your email" },
1982
+ { label: "Password", placeholder: "enter password", maxLength: 40 },
1983
+ { label: "API Key", placeholder: "sk-...", maxLength: 60, disabled: true }
1984
+ ];
1985
+ function TextInputApp() {
1986
+ const [activeField, setActiveField] = useState13(0);
1987
+ const [values, setValues] = useState13(FIELDS.map(() => ""));
1988
+ useKeyboard7((event) => {
1989
+ if (event.name === "up") setActiveField((i) => Math.max(0, i - 1));
1990
+ if (event.name === "down") setActiveField((i) => Math.min(FIELDS.length - 1, i + 1));
1991
+ });
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" }] })));
2007
+ }
1919
2008
 
1920
- // ../docs/components/landing/use-matrix.ts
1921
- import { useState as useState9, useEffect as useEffect4, useRef as useRef7 } from "react";
1922
- var CHARS = "abcdefghijklmnopqrstuvwxyz0123456789@#$%^&*(){}[]|;:<>,.?/~`";
1923
- function randomChar() {
1924
- return CHARS[Math.floor(Math.random() * CHARS.length)];
2009
+ // demos/link.tsx
2010
+ import { useState as useState14 } from "react";
2011
+ import { useKeyboard as useKeyboard8 } from "@gridland/utils";
2012
+ var MODES = ["solid", "dashed", "dotted", "none"];
2013
+ function LinkApp() {
2014
+ const theme = useTheme();
2015
+ const [modeIndex, setModeIndex] = useState14(0);
2016
+ const mode = MODES[modeIndex];
2017
+ useKeyboard8((event) => {
2018
+ if (event.name === "right") setModeIndex((i) => (i + 1) % MODES.length);
2019
+ if (event.name === "left") setModeIndex((i) => (i - 1 + MODES.length) % MODES.length);
2020
+ });
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
+ )));
1925
2028
  }
1926
- function createDrop(height, seeded = false) {
1927
- const length = Math.floor(Math.random() * Math.floor(height * 0.6)) + 4;
1928
- return {
1929
- y: seeded ? Math.floor(Math.random() * (height + length)) : -Math.floor(Math.random() * height),
1930
- speed: 1,
1931
- length,
1932
- chars: Array.from({ length }, randomChar)
1933
- };
2029
+
2030
+ // demos/tabs.tsx
2031
+ import { useState as useState15 } from "react";
2032
+ import { useKeyboard as useKeyboard9 } from "@gridland/utils";
2033
+ var tabs = ["Files", "Search", "Git", "Debug"];
2034
+ function TabBarApp() {
2035
+ const [selectedIndex, setSelectedIndex] = useState15(0);
2036
+ useKeyboard9((event) => {
2037
+ if (event.name === "left") setSelectedIndex((i) => i > 0 ? i - 1 : tabs.length - 1);
2038
+ if (event.name === "right") setSelectedIndex((i) => i < tabs.length - 1 ? i + 1 : 0);
2039
+ });
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" }] })));
1934
2041
  }
1935
- function buildGrid(columns, width, height) {
1936
- const grid = Array.from({ length: height }, () => Array(width).fill(" "));
1937
- const brightness = Array.from({ length: height }, () => Array(width).fill(0));
1938
- for (let x = 0; x < width; x++) {
1939
- const drop = columns[x];
1940
- if (!drop) continue;
1941
- for (let i = 0; i < drop.length; i++) {
1942
- const row = Math.floor(drop.y) - i;
1943
- if (row < 0 || row >= height) continue;
1944
- grid[row][x] = drop.chars[i];
1945
- if (i === 0) {
1946
- brightness[row][x] = 1;
1947
- } else {
1948
- brightness[row][x] = Math.max(0.15, 1 - i / drop.length);
1949
- }
2042
+
2043
+ // demos/status-bar.tsx
2044
+ import { useState as useState16 } from "react";
2045
+ import { useKeyboard as useKeyboard10 } from "@gridland/utils";
2046
+ var shortcuts = [
2047
+ { key: "Tab", label: "switch focus" },
2048
+ { key: "\u2190\u2192", label: "cycle" },
2049
+ { key: "b", label: "back" },
2050
+ { key: "z", label: "reset" }
2051
+ ];
2052
+ function StatusBarApp() {
2053
+ const theme = useTheme();
2054
+ const [lastKey, setLastKey] = useState16(null);
2055
+ useKeyboard10((event) => {
2056
+ if (event.name === "tab") setLastKey("switch focus (Tab)");
2057
+ else if (event.name === "left") setLastKey("cycle (\u2190)");
2058
+ else if (event.name === "right") setLastKey("cycle (\u2192)");
2059
+ else if (event.name === "b") setLastKey("back (b)");
2060
+ else if (event.name === "z") setLastKey("reset (z)");
2061
+ });
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")
1950
2067
  }
2068
+ ));
2069
+ }
2070
+
2071
+ // demos/modal.tsx
2072
+ import { useState as useState17 } from "react";
2073
+ import { useKeyboard as useKeyboard11 } from "@gridland/utils";
2074
+ function ModalApp() {
2075
+ const theme = useTheme();
2076
+ const [isOpen, setIsOpen] = useState17(false);
2077
+ useKeyboard11((event) => {
2078
+ if (!isOpen && event.name === "m") setIsOpen(true);
2079
+ if (isOpen && (event.name === "q" || event.name === "escape")) setIsOpen(false);
2080
+ });
2081
+ 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" }] })));
1951
2083
  }
1952
- return { grid, brightness };
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")));
1953
2085
  }
1954
- function useMatrix(width, height) {
1955
- const columnsRef = useRef7([]);
1956
- const [state, setState] = useState9(() => {
1957
- const columns = Array.from(
1958
- { length: width },
1959
- () => Math.random() < 0.5 ? createDrop(height, true) : null
1960
- );
1961
- columnsRef.current = columns;
1962
- return buildGrid(columns, width, height);
2086
+
2087
+ // demos/primitives.tsx
2088
+ 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"));
2101
+ }
2102
+
2103
+ // demos/chat.tsx
2104
+ import { useState as useState18, useCallback as useCallback3, useRef as useRef5 } from "react";
2105
+ import { useKeyboard as useKeyboard12 } from "@gridland/utils";
2106
+ var initialMessages = [
2107
+ { id: "1", role: "user", content: "Show me my portfolio" },
2108
+ { id: "2", role: "assistant", content: "Here's your current portfolio allocation:" },
2109
+ { id: "3", role: "user", content: "Calculate rebalancing trades" },
2110
+ { id: "4", role: "assistant", content: "I've calculated the optimal trades to rebalance your portfolio." }
2111
+ ];
2112
+ var nextId = 5;
2113
+ function ChatApp() {
2114
+ const [messages, setMessages] = useState18(initialMessages);
2115
+ const [isLoading, setIsLoading] = useState18(false);
2116
+ const [streamingText, setStreamingText] = useState18("");
2117
+ const [activeToolCalls, setActiveToolCalls] = useState18([]);
2118
+ const intervalRef = useRef5(null);
2119
+ const handleSend = useCallback3((text) => {
2120
+ const userMsg = { id: String(nextId++), role: "user", content: text };
2121
+ setMessages((prev) => [...prev, userMsg]);
2122
+ setIsLoading(true);
2123
+ const toolCallId = `tc-${nextId}`;
2124
+ setTimeout(() => {
2125
+ setIsLoading(false);
2126
+ setActiveToolCalls([{ id: toolCallId, title: "process_request", status: "in_progress" }]);
2127
+ }, 500);
2128
+ setTimeout(() => {
2129
+ setActiveToolCalls([{ id: toolCallId, title: "process_request", status: "completed" }]);
2130
+ }, 1200);
2131
+ const response = `You said: "${text}". This is a demo response.`;
2132
+ let charIndex = 0;
2133
+ setTimeout(() => {
2134
+ intervalRef.current = setInterval(() => {
2135
+ charIndex = Math.min(charIndex + 3, response.length);
2136
+ if (charIndex < response.length) {
2137
+ setStreamingText(response.slice(0, charIndex));
2138
+ } else {
2139
+ if (intervalRef.current) clearInterval(intervalRef.current);
2140
+ setStreamingText("");
2141
+ setActiveToolCalls([]);
2142
+ setMessages((prev) => [
2143
+ ...prev,
2144
+ { id: String(nextId++), role: "assistant", content: response }
2145
+ ]);
2146
+ }
2147
+ }, 50);
2148
+ }, 1400);
2149
+ }, []);
2150
+ const handleCancel = useCallback3(() => {
2151
+ if (intervalRef.current) clearInterval(intervalRef.current);
2152
+ setIsLoading(false);
2153
+ setStreamingText("");
2154
+ setActiveToolCalls([]);
2155
+ }, []);
2156
+ return /* @__PURE__ */ React.createElement(
2157
+ ChatPanel,
2158
+ {
2159
+ messages,
2160
+ streamingText,
2161
+ isLoading,
2162
+ activeToolCalls,
2163
+ onSendMessage: handleSend,
2164
+ onCancel: handleCancel,
2165
+ placeholder: "Ask about your portfolio...",
2166
+ useKeyboard: useKeyboard12
2167
+ }
2168
+ );
2169
+ }
2170
+
2171
+ // demos/chain-of-thought.tsx
2172
+ import { useState as useState19, useEffect as useEffect4, useRef as useRef6 } from "react";
2173
+ import { useKeyboard as useKeyboard13 } from "@gridland/utils";
2174
+ var ALL_STEPS = [
2175
+ { tool: "Read", label: "Reading codebase", description: "src/", status: "done", delay: 1800 },
2176
+ { tool: "Think", label: "Planning changes", description: "auth module", status: "done", delay: 2500 },
2177
+ { tool: "Edit", label: "Editing files", description: "4 files", status: "done", delay: 3200 },
2178
+ { tool: "Bash", label: "Running tests", description: "vitest", status: "done", delay: 2e3 },
2179
+ { tool: "Edit", label: "Fixing test", description: "routes.test.ts", status: "done", delay: 1500 }
2180
+ ];
2181
+ function ChainOfThoughtApp() {
2182
+ const [expanded, setExpanded] = useState19(true);
2183
+ const [phase, setPhase] = useState19("running");
2184
+ const [stepIndex, setStepIndex] = useState19(0);
2185
+ const timerRef = useRef6(null);
2186
+ useKeyboard13((event) => {
2187
+ if (event.name === "E" && event.ctrl && event.shift) setExpanded((v) => !v);
2188
+ if (event.name === "r") restart();
1963
2189
  });
2190
+ function restart() {
2191
+ if (timerRef.current) clearTimeout(timerRef.current);
2192
+ setPhase("running");
2193
+ setStepIndex(0);
2194
+ }
1964
2195
  useEffect4(() => {
1965
- if (width < 2 || height < 2) return;
1966
- const id = setInterval(() => {
1967
- const columns = columnsRef.current;
1968
- for (let x = 0; x < width; x++) {
1969
- if (columns[x] === null || columns[x] === void 0) {
1970
- if (Math.random() < 0.03) {
1971
- columns[x] = createDrop(height);
1972
- }
1973
- continue;
1974
- }
1975
- const drop = columns[x];
1976
- drop.y += drop.speed;
1977
- if (Math.random() < 0.1) {
1978
- const idx = Math.floor(Math.random() * drop.chars.length);
1979
- drop.chars[idx] = randomChar();
1980
- }
1981
- if (drop.y - drop.length > height) {
1982
- columns[x] = null;
1983
- }
1984
- }
1985
- setState(buildGrid(columns, width, height));
1986
- }, 80);
1987
- return () => clearInterval(id);
1988
- }, [width, height]);
2196
+ if (phase !== "running") return;
2197
+ if (stepIndex < ALL_STEPS.length) {
2198
+ const delay = ALL_STEPS[stepIndex].delay;
2199
+ timerRef.current = setTimeout(() => setStepIndex((i) => i + 1), delay);
2200
+ } else {
2201
+ timerRef.current = setTimeout(() => setPhase("done"), 500);
2202
+ }
2203
+ return () => {
2204
+ if (timerRef.current) clearTimeout(timerRef.current);
2205
+ };
2206
+ }, [phase, stepIndex]);
1989
2207
  useEffect4(() => {
1990
- columnsRef.current = Array.from(
1991
- { length: width },
1992
- () => Math.random() < 0.5 ? createDrop(height, true) : null
1993
- );
1994
- setState(buildGrid(columnsRef.current, width, height));
1995
- }, [width, height]);
1996
- return state;
2208
+ if (phase === "done") {
2209
+ timerRef.current = setTimeout(() => restart(), 3e3);
2210
+ }
2211
+ return () => {
2212
+ if (timerRef.current) clearTimeout(timerRef.current);
2213
+ };
2214
+ }, [phase]);
2215
+ const steps = ALL_STEPS.map((s, i) => {
2216
+ if (i < stepIndex) return { ...s, status: "done" };
2217
+ if (i === stepIndex && phase === "running") return { ...s, status: "running" };
2218
+ return { ...s, status: phase === "done" ? "done" : "pending" };
2219
+ });
2220
+ const elapsedMs = ALL_STEPS.slice(0, stepIndex).reduce((sum, s) => sum + s.delay, 0);
2221
+ const totalMs = ALL_STEPS.reduce((sum, s) => sum + s.delay, 0);
2222
+ 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
+ ] })));
1997
2237
  }
1998
2238
 
1999
- // ../docs/components/landing/matrix-background.tsx
2000
- import { jsx as jsx25 } from "react/jsx-runtime";
2001
- var MUTE_LEVELS = [0.12, 0.18, 0.24, 0.3, 0.38];
2002
- var BG = hexToRgb("#1a1a2e");
2003
- function buildMutedColors(baseHex) {
2004
- const fg = hexToRgb(baseHex);
2005
- return MUTE_LEVELS.map((factor) => rgbToHex({
2006
- r: Math.round(BG.r + (fg.r - BG.r) * factor),
2007
- g: Math.round(BG.g + (fg.g - BG.g) * factor),
2008
- b: Math.round(BG.b + (fg.b - BG.b) * factor)
2009
- }));
2010
- }
2011
- function colorForCell(mutedColors, b) {
2012
- if (b >= 1) return mutedColors[4];
2013
- const idx = Math.min(Math.floor(b * (MUTE_LEVELS.length - 1)), MUTE_LEVELS.length - 2);
2014
- return mutedColors[idx];
2015
- }
2016
- function MatrixBackground({ width, height, clearRect, clearRects }) {
2017
- const { grid, brightness } = useMatrix(width, height);
2018
- const theme = useTheme();
2019
- const columnColors = useMemo5(
2020
- () => width > 0 ? generateGradient([theme.accent, theme.secondary, theme.primary], width) : [],
2021
- [width, theme.accent, theme.secondary, theme.primary]
2022
- );
2023
- const columnMutedColors = useMemo5(
2024
- () => columnColors.map(buildMutedColors),
2025
- [columnColors]
2026
- );
2027
- return /* @__PURE__ */ jsx25("box", { flexDirection: "column", children: grid.map((row, y) => /* @__PURE__ */ jsx25("text", { children: row.map((cell, x) => {
2028
- const inClearRect = clearRect && y >= clearRect.top && y < clearRect.top + clearRect.height && x >= clearRect.left && x < clearRect.left + clearRect.width || clearRects && clearRects.some(
2029
- (r) => y >= r.top && y < r.top + r.height && x >= r.left && x < r.left + r.width
2030
- );
2031
- const mutedColors = columnMutedColors[x];
2032
- if (cell === " " || inClearRect || !mutedColors) {
2033
- return /* @__PURE__ */ jsx25("span", { children: " " }, x);
2239
+ // demos/message.tsx
2240
+ import { useState as useState20, useEffect as useEffect5, useRef as useRef7 } from "react";
2241
+ import { useKeyboard as useKeyboard14 } from "@gridland/utils";
2242
+ 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
+ function MessageApp() {
2244
+ const [phase, setPhase] = useState20("idle");
2245
+ const [streamedText, setStreamedText] = useState20("");
2246
+ const timerRef = useRef7(null);
2247
+ useKeyboard14((event) => {
2248
+ if (event.name === "r") restart();
2249
+ });
2250
+ function restart() {
2251
+ if (timerRef.current) clearTimeout(timerRef.current);
2252
+ setPhase("idle");
2253
+ setStreamedText("");
2254
+ }
2255
+ useEffect5(() => {
2256
+ if (phase === "idle") {
2257
+ timerRef.current = setTimeout(() => setPhase("streaming"), 800);
2034
2258
  }
2035
- return /* @__PURE__ */ jsx25(
2036
- "span",
2037
- {
2038
- style: {
2039
- fg: colorForCell(mutedColors, brightness[y][x])
2040
- },
2041
- children: cell
2042
- },
2043
- x
2044
- );
2045
- }) }, y)) });
2259
+ return () => {
2260
+ if (timerRef.current) clearTimeout(timerRef.current);
2261
+ };
2262
+ }, [phase]);
2263
+ useEffect5(() => {
2264
+ if (phase !== "streaming") return;
2265
+ if (streamedText.length < RESPONSE.length) {
2266
+ timerRef.current = setTimeout(() => {
2267
+ setStreamedText(RESPONSE.slice(0, streamedText.length + 2));
2268
+ }, 25);
2269
+ } else {
2270
+ timerRef.current = setTimeout(() => setPhase("done"), 500);
2271
+ }
2272
+ return () => {
2273
+ if (timerRef.current) clearTimeout(timerRef.current);
2274
+ };
2275
+ }, [phase, streamedText]);
2276
+ useEffect5(() => {
2277
+ if (phase === "done") {
2278
+ timerRef.current = setTimeout(() => restart(), 3e3);
2279
+ }
2280
+ return () => {
2281
+ if (timerRef.current) clearTimeout(timerRef.current);
2282
+ };
2283
+ }, [phase]);
2284
+ const isStreaming = phase === "streaming";
2285
+ const isDone = phase === "done";
2286
+ 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" }] })));
2288
+ }
2289
+
2290
+ // demos/terminal-window.tsx
2291
+ function TerminalWindowApp() {
2292
+ 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 }) }, "$ _"));
2046
2294
  }
2047
2295
 
2048
- // ../docs/components/landing/landing-app.tsx
2049
- import { jsx as jsx26, jsxs as jsxs19 } from "react/jsx-runtime";
2050
- var DEMO_RESPONSES = [
2051
- "Gridland is a framework for building terminal apps with React. It works in both the browser and terminal!",
2052
- "You can get started with `bun create gridland` to scaffold a new project.",
2053
- "OpenTUI provides the layout primitives \u2014 flexbox, borders, text styling \u2014 while React handles the component model.",
2054
- "Yes! Gridland apps are universal \u2014 the same code renders in a terminal emulator and in the browser.",
2055
- "Check out the docs for examples of interactive components like inputs, selects, and tables."
2296
+ // demos/focus.tsx
2297
+ import { useState as useState21, useRef as useRef8 } from "react";
2298
+ import { useKeyboard as useKeyboard15 } from "@gridland/utils";
2299
+ var focusPanels = [
2300
+ {
2301
+ label: "Language",
2302
+ items: [
2303
+ { label: "TypeScript", value: "ts" },
2304
+ { label: "JavaScript", value: "js" },
2305
+ { label: "Python", value: "py" }
2306
+ ]
2307
+ },
2308
+ {
2309
+ label: "Framework",
2310
+ items: [
2311
+ { label: "React", value: "react" },
2312
+ { label: "Vue", value: "vue" },
2313
+ { label: "Svelte", value: "svelte" }
2314
+ ]
2315
+ },
2316
+ {
2317
+ label: "Runtime",
2318
+ items: [
2319
+ { label: "Bun", value: "bun" },
2320
+ { label: "Node", value: "node" },
2321
+ { label: "Deno", value: "deno" }
2322
+ ]
2323
+ }
2056
2324
  ];
2057
- function LandingApp({ useKeyboard: useKeyboard3 }) {
2058
- const theme = useTheme();
2059
- const { width, height, isNarrow, isTiny, isMobile } = useBreakpoints();
2060
- const [showAbout, setShowAbout] = useState10(false);
2061
- const [messages, setMessages] = useState10([]);
2062
- const [chatStatus, setChatStatus] = useState10("ready");
2063
- const responseIdx = useRef8(0);
2064
- const handleChatSubmit = useCallback3(({ text }) => {
2065
- const userMsg = { id: `u-${Date.now()}`, role: "user", content: text };
2066
- setMessages((prev) => [...prev, userMsg]);
2067
- setChatStatus("streaming");
2068
- setTimeout(() => {
2069
- const response = DEMO_RESPONSES[responseIdx.current % DEMO_RESPONSES.length];
2070
- responseIdx.current += 1;
2071
- const assistantMsg = { id: `a-${Date.now()}`, role: "assistant", content: response };
2072
- setMessages((prev) => [...prev, assistantMsg]);
2073
- setChatStatus("ready");
2074
- }, 1200);
2075
- }, []);
2076
- useKeyboard3((event) => {
2077
- if (event.name === "a" && !showAbout) {
2078
- setShowAbout(true);
2079
- }
2080
- if (event.name === "q" && showAbout) {
2081
- setShowAbout(false);
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;
2336
+ 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
+ }
2082
2377
  }
2378
+ event.preventDefault();
2083
2379
  });
2084
- if (showAbout) {
2085
- return /* @__PURE__ */ jsxs19("box", { flexDirection: "column", width: "100%", height: "100%", children: [
2086
- /* @__PURE__ */ jsx26("box", { flexGrow: 1, children: /* @__PURE__ */ jsx26(AboutModal, { onClose: () => setShowAbout(false), useKeyboard: useKeyboard3 }) }),
2087
- /* @__PURE__ */ jsx26(StatusBar, { items: [{ key: "q", label: "close" }] })
2088
- ] });
2089
- }
2090
- const isBrowser = typeof document !== "undefined";
2091
- const logoHeight = isTiny ? 2 : isNarrow ? 13 : 7;
2092
- const logoExtra = isBrowser ? 1 : 0;
2093
- const gap = isMobile ? 0 : 1;
2094
- const installLinksTop = 3 + logoHeight + logoExtra + gap;
2095
- const installLinksHeight = 3;
2096
- const boxTop = installLinksTop + installLinksHeight + gap + 1;
2097
- const boxHeight = height - boxTop - 1 - 1;
2098
- const clearRect = { top: boxTop, left: 1, width: width - 2, height: boxHeight };
2099
- const installLinksClearRect = { top: installLinksTop, left: 1, width: width - 2, height: installLinksHeight };
2100
- return /* @__PURE__ */ jsxs19("box", { width: "100%", height: "100%", position: "relative", children: [
2101
- /* @__PURE__ */ jsx26(MatrixBackground, { width, height, clearRect, clearRects: [installLinksClearRect] }),
2102
- /* @__PURE__ */ jsxs19("box", { position: "absolute", top: 0, left: 0, width, height, zIndex: 1, flexDirection: "column", shouldFill: false, children: [
2103
- /* @__PURE__ */ jsxs19("box", { flexGrow: 1, flexDirection: "column", paddingTop: 3, paddingLeft: 1, paddingRight: 1, paddingBottom: 1, gap: isMobile ? 0 : 1, shouldFill: false, children: [
2104
- /* @__PURE__ */ jsx26("box", { flexShrink: 0, shouldFill: false, children: /* @__PURE__ */ jsx26(Logo, { compact: isTiny, narrow: isNarrow, mobile: isMobile }) }),
2105
- /* @__PURE__ */ jsxs19("box", { flexDirection: "row", flexWrap: "wrap", justifyContent: "center", gap: isMobile ? 0 : 1, flexShrink: 0, shouldFill: false, children: [
2106
- /* @__PURE__ */ jsx26("box", { border: true, borderStyle: "rounded", borderColor: theme.border, paddingX: 1, flexDirection: "column", flexShrink: 0, children: /* @__PURE__ */ jsxs19("text", { children: [
2107
- /* @__PURE__ */ jsx26("span", { style: textStyle({ dim: true }), children: "$ " }),
2108
- /* @__PURE__ */ jsx26("span", { style: textStyle({ bold: true }), children: "bunx " }),
2109
- /* @__PURE__ */ jsx26("span", { style: textStyle({ fg: theme.accent }), children: "@gridland/demo landing" })
2110
- ] }) }),
2111
- /* @__PURE__ */ jsx26(InstallBox, {}),
2112
- /* @__PURE__ */ jsx26(LinksBox, {})
2113
- ] }),
2114
- /* @__PURE__ */ jsxs19("box", { flexGrow: 1, border: true, borderStyle: "rounded", borderColor: theme.border, flexDirection: "column", overflow: "hidden", children: [
2115
- /* @__PURE__ */ jsx26("box", { flexGrow: 1, flexDirection: "column", paddingX: 1, overflow: "hidden", children: messages.map((msg) => /* @__PURE__ */ jsx26(Message, { role: msg.role, children: /* @__PURE__ */ jsx26(Message.Content, { children: /* @__PURE__ */ jsx26(Message.Text, { children: msg.content }) }) }, msg.id)) }),
2116
- /* @__PURE__ */ jsx26("box", { flexShrink: 0, paddingX: 1, paddingBottom: 0, children: /* @__PURE__ */ jsx26(PromptInput, { splaceholder: "Ask about Gridland...", status: chatStatus, onSubmit: handleChatSubmit, useKeyboard: useKeyboard3 }) })
2117
- ] })
2118
- ] }),
2119
- /* @__PURE__ */ jsx26(StatusBar, { items: [{ key: "a", label: "about" }] })
2120
- ] })
2121
- ] });
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",
2386
+ {
2387
+ key: panel.label,
2388
+ border: true,
2389
+ borderStyle: "rounded",
2390
+ borderColor: active ? "#22c55e" : focused ? "#3b82f6" : "#555",
2391
+ flexGrow: 1
2392
+ },
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,
2406
+ {
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" }]
2408
+ }
2409
+ )));
2122
2410
  }
2123
2411
 
2124
- // ../docs/components/landing/matrix-rain.tsx
2125
- import { jsx as jsx27 } from "react/jsx-runtime";
2126
-
2127
- // ../ui/scripts/demo-apps.tsx
2128
- import figlet2 from "figlet";
2129
- import ansiShadow2 from "figlet/importable-fonts/ANSI Shadow.js";
2130
- import big from "figlet/importable-fonts/Big.js";
2131
- import doom from "figlet/importable-fonts/Doom.js";
2132
- import slant from "figlet/importable-fonts/Slant.js";
2133
- import speed from "figlet/importable-fonts/Speed.js";
2134
- import standard from "figlet/importable-fonts/Standard.js";
2135
- import block from "figlet/importable-fonts/Block.js";
2136
- import colossal from "figlet/importable-fonts/Colossal.js";
2137
- import { jsx as jsx28, jsxs as jsxs20 } from "react/jsx-runtime";
2138
- var fonts = [
2139
- { name: "ANSI Shadow", data: ansiShadow2 },
2140
- { name: "Standard", data: standard },
2141
- { name: "Big", data: big },
2142
- { name: "Doom", data: doom },
2143
- { name: "Slant", data: slant },
2144
- { name: "Speed", data: speed },
2145
- { name: "Block", data: block },
2146
- { name: "Colossal", data: colossal }
2147
- ];
2148
- for (const f of fonts) {
2149
- figlet2.parseFont(f.name, f.data);
2150
- }
2151
- function getFigletLines(fontName, text = "gridland") {
2152
- const art = figlet2.textSync(text, { font: fontName });
2153
- return art.split("\n").filter((l) => l.trimEnd().length > 0);
2412
+ // demos/pointer.tsx
2413
+ import { useState as useState22, useRef as useRef9 } from "react";
2414
+ import { useKeyboard as useKeyboard16 } from "@gridland/utils";
2415
+ var pointerColors = ["#ef4444", "#f97316", "#eab308", "#22c55e", "#3b82f6", "#8b5cf6"];
2416
+ var pointerColorNames = ["Red", "Orange", "Yellow", "Green", "Blue", "Purple"];
2417
+ function HoverBox() {
2418
+ const [hovering, setHovering] = useState22(false);
2419
+ return /* @__PURE__ */ React.createElement(
2420
+ "box",
2421
+ {
2422
+ border: true,
2423
+ borderStyle: "rounded",
2424
+ borderColor: hovering ? "#22c55e" : "#555",
2425
+ width: 20,
2426
+ height: 5,
2427
+ 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"))
2431
+ );
2154
2432
  }
2155
- var gradientNames = Object.keys(GRADIENTS);
2156
- function GradientApp() {
2157
- const [index, setIndex] = useState11(0);
2158
- const name = gradientNames[index];
2159
- const lines = getFigletLines("ANSI Shadow");
2160
- useKeyboard((event) => {
2161
- if (event.name === "left") setIndex((i) => i > 0 ? i - 1 : gradientNames.length - 1);
2162
- if (event.name === "right") setIndex((i) => i < gradientNames.length - 1 ? i + 1 : 0);
2433
+ 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);
2439
+ selectedRef.current = selected;
2440
+ clickCountRef.current = clickCount;
2441
+ useKeyboard16((event) => {
2442
+ const cur = selectedRef.current ?? -1;
2443
+ if (event.name === "right" || event.name === "tab") {
2444
+ const next = (cur + 1) % pointerColors.length;
2445
+ selectedRef.current = next;
2446
+ setSelected(next);
2447
+ } else if (event.name === "left") {
2448
+ const next = (cur - 1 + pointerColors.length) % pointerColors.length;
2449
+ selectedRef.current = next;
2450
+ setSelected(next);
2451
+ }
2452
+ event.preventDefault();
2163
2453
  });
2164
- return /* @__PURE__ */ jsxs20("box", { flexDirection: "column", flexGrow: 1, children: [
2165
- /* @__PURE__ */ jsx28("box", { padding: 1, flexDirection: "column", alignItems: "center", justifyContent: "center", flexGrow: 1, children: /* @__PURE__ */ jsx28(Gradient, { name, children: lines.join("\n") }) }),
2166
- /* @__PURE__ */ jsx28("box", { paddingX: 1, paddingBottom: 1, children: /* @__PURE__ */ jsx28(
2167
- StatusBar,
2168
- {
2169
- items: [{ key: "\u2190\u2192", label: "gradient" }, { key: "q", label: "quit" }],
2170
- extra: /* @__PURE__ */ jsx28("span", { style: textStyle({ fg: "cyan", bold: true }), children: name.padEnd(11) })
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 });
2171
2469
  }
2172
- ) })
2173
- ] });
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
+ ] })));
2174
2476
  }
2175
- function AsciiApp() {
2176
- const [fontIndex, setFontIndex] = useState11(0);
2177
- const font = fonts[fontIndex];
2178
- const lines = getFigletLines(font.name);
2179
- useKeyboard((event) => {
2180
- if (event.name === "left") setFontIndex((i) => i > 0 ? i - 1 : fonts.length - 1);
2181
- if (event.name === "right") setFontIndex((i) => i < fonts.length - 1 ? i + 1 : 0);
2182
- });
2183
- return /* @__PURE__ */ jsxs20("box", { flexDirection: "column", flexGrow: 1, children: [
2184
- /* @__PURE__ */ jsx28("box", { padding: 1, flexDirection: "column", alignItems: "center", justifyContent: "center", flexGrow: 1, children: lines.map((line, i) => /* @__PURE__ */ jsx28("text", { fg: "#88c0d0", bold: true, children: line }, i)) }),
2185
- /* @__PURE__ */ jsx28("box", { paddingX: 1, paddingBottom: 1, children: /* @__PURE__ */ jsx28(
2477
+
2478
+ // demos/cursor-highlight.tsx
2479
+ import { useState as useState23 } from "react";
2480
+ function CursorHighlightApp() {
2481
+ const [pos, setPos] = useState23(null);
2482
+ return /* @__PURE__ */ React.createElement(
2483
+ "box",
2484
+ {
2485
+ flexDirection: "column",
2486
+ flexGrow: 1,
2487
+ onMouseMove: (e) => {
2488
+ 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(
2186
2499
  StatusBar,
2187
2500
  {
2188
- items: [{ key: "\u2190\u2192", label: "change font" }, { key: "q", label: "quit" }],
2189
- extra: /* @__PURE__ */ jsx28("span", { style: textStyle({ fg: "cyan", bold: true }), children: font.name.padEnd(11) })
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) : " -"}`))
2190
2503
  }
2191
- ) })
2192
- ] });
2504
+ ))
2505
+ );
2193
2506
  }
2194
- function TableApp() {
2195
- const data = [
2196
- { name: "Alice", role: "Engineer", status: "Active" },
2197
- { name: "Bob", role: "Designer", status: "Active" },
2198
- { name: "Charlie", role: "PM", status: "Away" }
2199
- ];
2200
- return /* @__PURE__ */ jsxs20("box", { flexDirection: "column", flexGrow: 1, children: [
2201
- /* @__PURE__ */ jsx28("box", { padding: 1, flexGrow: 1, children: /* @__PURE__ */ jsx28(Table, { data, headerColor: "cyan", borderColor: "#5e81ac" }) }),
2202
- /* @__PURE__ */ jsx28("box", { paddingX: 1, paddingBottom: 1, children: /* @__PURE__ */ jsx28(StatusBar, { items: [{ key: "q", label: "quit" }] }) })
2203
- ] });
2507
+
2508
+ // demos/text-style.tsx
2509
+ function TextStyleApp() {
2510
+ const theme = useTheme();
2511
+ 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 ", "}", ")")));
2204
2513
  }
2205
- function SpinnerApp() {
2206
- return /* @__PURE__ */ jsxs20("box", { flexDirection: "column", flexGrow: 1, children: [
2207
- /* @__PURE__ */ jsx28("box", { flexGrow: 1, children: /* @__PURE__ */ jsx28(SpinnerPicker, { useKeyboard }) }),
2208
- /* @__PURE__ */ jsx28("box", { paddingX: 1, paddingBottom: 1, children: /* @__PURE__ */ jsx28(StatusBar, { items: [{ key: "\u2190\u2192", label: "change variant" }, { key: "q", label: "quit" }] }) })
2209
- ] });
2514
+
2515
+ // demos/headless.tsx
2516
+ var data = [
2517
+ { name: "Alice", role: "Engineer", status: "Active" },
2518
+ { name: "Bob", role: "Designer", status: "Active" },
2519
+ { name: "Charlie", role: "PM", status: "Away" }
2520
+ ];
2521
+ function HeadlessApp() {
2522
+ return /* @__PURE__ */ React.createElement("box", { padding: 1 }, /* @__PURE__ */ React.createElement(Table, { data }));
2210
2523
  }
2211
- function SelectInputApp() {
2212
- const [submitted, setSubmitted] = useState11(false);
2213
- const items = [
2214
- { label: "TypeScript", value: "ts" },
2215
- { label: "JavaScript", value: "js" },
2216
- { label: "Python", value: "py" },
2217
- { label: "Rust", value: "rs" }
2218
- ];
2219
- return /* @__PURE__ */ jsxs20("box", { flexDirection: "column", flexGrow: 1, children: [
2220
- /* @__PURE__ */ jsx28("box", { padding: 1, flexDirection: "column", flexGrow: 1, children: /* @__PURE__ */ jsx28(SelectInput, { items, title: "Choose a language", useKeyboard, onSubmit: () => setSubmitted(true) }) }),
2221
- /* @__PURE__ */ jsx28("box", { paddingX: 1, paddingBottom: 1, children: /* @__PURE__ */ jsx28(StatusBar, { items: submitted ? [{ key: "q", label: "quit" }] : [
2222
- { key: "\u2191\u2193", label: "select" },
2223
- { key: "enter", label: "submit" },
2224
- { key: "q", label: "quit" }
2225
- ] }) })
2226
- ] });
2524
+
2525
+ // demos/theming.tsx
2526
+ import { useKeyboard as useKeyboard17 } from "@gridland/utils";
2527
+ var tableData = [
2528
+ { name: "Alice", role: "Engineer", status: "Active" },
2529
+ { name: "Bob", role: "Designer", status: "Away" }
2530
+ ];
2531
+ var selectItems = [
2532
+ { label: "TypeScript", value: "ts" },
2533
+ { label: "JavaScript", value: "js" },
2534
+ { label: "Python", value: "py" }
2535
+ ];
2536
+ 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 }));
2227
2538
  }
2228
- function MultiSelectApp() {
2229
- const [submitted, setSubmitted] = useState11(false);
2230
- const items = [
2231
- { label: "TypeScript", value: "ts" },
2232
- { label: "JavaScript", value: "js" },
2233
- { label: "Python", value: "py" },
2234
- { label: "Rust", value: "rs" }
2235
- ];
2236
- return /* @__PURE__ */ jsxs20("box", { flexDirection: "column", flexGrow: 1, children: [
2237
- /* @__PURE__ */ jsx28("box", { padding: 1, flexDirection: "column", flexGrow: 1, children: /* @__PURE__ */ jsx28(MultiSelect, { items, title: "Select languages", useKeyboard, onSubmit: () => setSubmitted(true) }) }),
2238
- /* @__PURE__ */ jsx28("box", { paddingX: 1, paddingBottom: 1, children: /* @__PURE__ */ jsx28(StatusBar, { items: submitted ? [{ key: "q", label: "quit" }] : [
2239
- { key: "\u2191\u2193", label: "move" },
2240
- { key: "enter", label: "select" },
2241
- { key: "a", label: "all" },
2242
- { key: "x", label: "clear" },
2243
- { key: "q", label: "quit" }
2244
- ] }) })
2245
- ] });
2539
+
2540
+ // src/landing/landing-app.tsx
2541
+ import { useMemo as useMemo7 } from "react";
2542
+
2543
+ // src/landing/install-box.tsx
2544
+ function InstallBox() {
2545
+ const theme = useTheme();
2546
+ return /* @__PURE__ */ React.createElement(
2547
+ "box",
2548
+ {
2549
+ border: true,
2550
+ borderStyle: "rounded",
2551
+ borderColor: theme.border,
2552
+ paddingX: 1,
2553
+ 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"))
2557
+ );
2246
2558
  }
2247
- function PromptInputApp() {
2248
- const [lastMessage, setLastMessage] = useState11("");
2249
- const [model, setModel] = useState11("opus");
2250
- const [showModelPicker, setShowModelPicker] = useState11(false);
2251
- const [resetKey, setResetKey] = useState11(0);
2252
- const commands = [
2253
- { cmd: "/help", desc: "Show commands" },
2254
- { cmd: "/model", desc: "Switch model" },
2255
- { cmd: "/clear", desc: "Clear conversation" }
2256
- ];
2257
- const files = ["src/index.ts", "src/routes.ts", "src/auth.ts", "package.json"];
2258
- const models = [
2259
- { label: "Claude Opus", value: "opus" },
2260
- { label: "Claude Sonnet", value: "sonnet" },
2261
- { label: "Claude Haiku", value: "haiku" }
2262
- ];
2263
- const handleSubmit = (msg) => {
2264
- if (msg.text === "/model") {
2265
- setShowModelPicker(true);
2266
- setResetKey((k) => k + 1);
2267
- return;
2268
- }
2269
- if (msg.text === "/clear") {
2270
- setLastMessage("");
2271
- setResetKey((k) => k + 1);
2272
- return;
2273
- }
2274
- setLastMessage(msg.text);
2275
- };
2276
- if (showModelPicker) {
2277
- return /* @__PURE__ */ jsxs20("box", { flexDirection: "column", flexGrow: 1, children: [
2278
- /* @__PURE__ */ jsx28(
2279
- Modal,
2280
- {
2281
- title: "Select Model",
2282
- useKeyboard,
2283
- onClose: () => setShowModelPicker(false),
2284
- children: /* @__PURE__ */ jsx28("box", { paddingX: 1, children: /* @__PURE__ */ jsx28(
2285
- SelectInput,
2286
- {
2287
- items: models,
2288
- defaultValue: model,
2289
- useKeyboard,
2290
- onSubmit: (value) => {
2291
- setModel(value);
2292
- setShowModelPicker(false);
2293
- }
2294
- }
2295
- ) })
2559
+
2560
+ // src/landing/links-box.tsx
2561
+ var UNDERLINE3 = 1 << 3;
2562
+ function LinksBox() {
2563
+ const theme = useTheme();
2564
+ return /* @__PURE__ */ React.createElement(
2565
+ "box",
2566
+ {
2567
+ border: true,
2568
+ borderStyle: "rounded",
2569
+ borderColor: theme.border,
2570
+ paddingX: 1,
2571
+ 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"))
2575
+ );
2576
+ }
2577
+
2578
+ // src/landing/logo.tsx
2579
+ import { useState as useState24, useEffect as useEffect6, useRef as useRef10, useMemo as useMemo5 } from "react";
2580
+ 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;
2590
+ function useAnimation(duration = 1e3) {
2591
+ const isBrowser = typeof document !== "undefined";
2592
+ const [progress, setProgress] = useState24(isBrowser ? 0 : 1);
2593
+ const startTime = useRef10(null);
2594
+ useEffect6(() => {
2595
+ if (!isBrowser) return;
2596
+ let raf;
2597
+ const tick = (time) => {
2598
+ if (startTime.current === null) startTime.current = time;
2599
+ const elapsed = time - startTime.current;
2600
+ const t = Math.min(1, elapsed / duration);
2601
+ const eased = 1 - Math.pow(1 - t, 3);
2602
+ setProgress(eased);
2603
+ if (t < 1) raf = requestAnimationFrame(tick);
2604
+ };
2605
+ raf = requestAnimationFrame(tick);
2606
+ return () => cancelAnimationFrame(raf);
2607
+ }, []);
2608
+ return progress;
2609
+ }
2610
+ function RevealGradient({ children, revealCol }) {
2611
+ const gradientColors = GRADIENTS.instagram;
2612
+ const lines2 = children.split("\n");
2613
+ const maxLength = Math.max(...lines2.map((l) => l.length));
2614
+ if (maxLength === 0) return /* @__PURE__ */ React.createElement("text", null, children);
2615
+ 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) => {
2617
+ const runs = [];
2618
+ let current = null;
2619
+ for (let i = 0; i < line.length; i++) {
2620
+ const revealed = i <= revealCol;
2621
+ const char = line[i];
2622
+ const isVisible = revealed && char !== " ";
2623
+ if (isVisible) {
2624
+ if (!current) {
2625
+ current = { start: i, chars: [] };
2296
2626
  }
2297
- ),
2298
- /* @__PURE__ */ jsx28("box", { paddingX: 1, paddingBottom: 1, children: /* @__PURE__ */ jsx28(StatusBar, { items: [
2299
- { key: "\u23CE", label: "select" },
2300
- { key: "esc", label: "cancel" },
2301
- { key: "q", label: "quit" }
2302
- ] }) })
2303
- ] });
2304
- }
2305
- return /* @__PURE__ */ jsxs20("box", { flexDirection: "column", flexGrow: 1, children: [
2306
- /* @__PURE__ */ jsx28("box", { padding: 1, flexDirection: "column", flexGrow: 1, children: lastMessage ? /* @__PURE__ */ jsxs20("text", { children: [
2307
- /* @__PURE__ */ jsx28("span", { children: "Sent: " }),
2308
- /* @__PURE__ */ jsx28("span", { children: lastMessage })
2309
- ] }) : /* @__PURE__ */ jsx28("text", { style: textStyle({ dim: true }), children: "Type a message and press enter" }) }),
2310
- /* @__PURE__ */ jsx28("box", { paddingX: 1, children: /* @__PURE__ */ jsx28(
2311
- PromptInput,
2627
+ current.chars.push(char);
2628
+ } else {
2629
+ if (current) {
2630
+ runs.push(current);
2631
+ current = null;
2632
+ }
2633
+ }
2634
+ }
2635
+ if (current) runs.push(current);
2636
+ return runs.map((run, runIndex) => /* @__PURE__ */ React.createElement(
2637
+ "box",
2312
2638
  {
2313
- commands,
2314
- files,
2315
- placeholder: "Message Claude...",
2316
- showDividers: true,
2317
- useKeyboard,
2318
- onSubmit: handleSubmit
2639
+ key: `${lineIndex}-${runIndex}`,
2640
+ position: "absolute",
2641
+ top: lineIndex,
2642
+ left: run.start,
2643
+ shouldFill: false
2319
2644
  },
2320
- resetKey
2321
- ) }),
2322
- /* @__PURE__ */ jsx28("box", { paddingX: 1, children: /* @__PURE__ */ jsxs20("text", { children: [
2323
- /* @__PURE__ */ jsx28("span", { style: textStyle({ fg: "#C4A8FF" }), children: "[\u22A1_\u22A1]" }),
2324
- /* @__PURE__ */ jsx28("span", { style: textStyle({ dim: true }), children: " " + model })
2325
- ] }) }),
2326
- /* @__PURE__ */ jsx28("box", { paddingX: 1, paddingBottom: 1, children: /* @__PURE__ */ jsx28(StatusBar, { items: [
2327
- { key: "\u23CE", label: "send" },
2328
- { key: "/", label: "commands" },
2329
- { key: "@", label: "files" },
2330
- { key: "\u2191", label: "history" },
2331
- { key: "q", label: "quit" }
2332
- ] }) })
2333
- ] });
2334
- }
2335
- var TEXT_INPUT_FIELDS = [
2336
- { label: "Username", placeholder: "enter your name", maxLength: 30, required: true },
2337
- { label: "Email", placeholder: "user@example.com", maxLength: 50, required: true, description: "We'll never share your email" },
2338
- { label: "Password", placeholder: "enter password", maxLength: 40 },
2339
- { label: "API Key", placeholder: "sk-...", maxLength: 60, disabled: true }
2340
- ];
2341
- function TextInputApp() {
2342
- const [activeField, setActiveField] = useState11(0);
2343
- const [values, setValues] = useState11(TEXT_INPUT_FIELDS.map(() => ""));
2344
- useKeyboard((event) => {
2345
- if (event.name === "up") setActiveField((i) => Math.max(0, i - 1));
2346
- if (event.name === "down" || event.name === "tab") setActiveField((i) => Math.min(TEXT_INPUT_FIELDS.length - 1, i + 1));
2347
- });
2348
- return /* @__PURE__ */ jsxs20("box", { flexDirection: "column", flexGrow: 1, children: [
2349
- /* @__PURE__ */ jsx28("box", { paddingX: 1, paddingTop: 1, children: /* @__PURE__ */ jsxs20("text", { children: [
2350
- /* @__PURE__ */ jsx28("span", { style: textStyle({ fg: "#FF71CE", bold: true }), children: "TextInput" }),
2351
- /* @__PURE__ */ jsx28("span", { style: textStyle({ dim: true }), children: " Form with multiple input types" })
2352
- ] }) }),
2353
- /* @__PURE__ */ jsx28("box", { flexDirection: "column", paddingX: 1, paddingTop: 1, flexGrow: 1, children: TEXT_INPUT_FIELDS.map((field, i) => /* @__PURE__ */ jsx28("box", { marginBottom: 1, children: /* @__PURE__ */ jsx28(
2354
- TextInput,
2355
- {
2356
- label: field.label,
2357
- placeholder: field.placeholder,
2358
- prompt: "> ",
2359
- focus: i === activeField,
2360
- maxLength: field.maxLength,
2361
- value: values[i],
2362
- onChange: (v) => setValues((prev) => prev.map((old, j) => j === i ? v : old)),
2363
- required: field.required,
2364
- disabled: field.disabled,
2365
- description: field.description
2366
- }
2367
- ) }, field.label)) }),
2368
- /* @__PURE__ */ jsx28("box", { paddingX: 1, paddingBottom: 1, children: /* @__PURE__ */ jsx28(StatusBar, { items: [
2369
- { key: "\u2191\u2193", label: "field" },
2370
- { key: "\u2190\u2192", label: "cursor" },
2371
- { key: "tab", label: "complete" },
2372
- { key: "^k/^u", label: "kill" }
2373
- ] }) })
2374
- ] });
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
+ )))
2653
+ ));
2654
+ }));
2375
2655
  }
2376
- function LinkApp() {
2377
- return /* @__PURE__ */ jsxs20("box", { flexDirection: "column", flexGrow: 1, children: [
2378
- /* @__PURE__ */ jsx28("box", { padding: 1, flexGrow: 1, children: /* @__PURE__ */ jsx28(LinkDemo, { useKeyboard }) }),
2379
- /* @__PURE__ */ jsx28("box", { paddingX: 1, paddingBottom: 1, children: /* @__PURE__ */ jsx28(StatusBar, { items: [{ key: "q", label: "quit" }] }) })
2380
- ] });
2656
+ function Logo({ compact, narrow, mobile }) {
2657
+ const isBrowser = typeof document !== "undefined";
2658
+ const progress = useAnimation(900);
2659
+ const artHeight = compact ? 1 : narrow ? ART_HEIGHT * 2 : ART_HEIGHT;
2660
+ const dropOffset = Math.round((1 - progress) * -artHeight);
2661
+ const revealProgress = Math.max(0, Math.min(1, (progress - 0.1) / 0.7));
2662
+ const maxWidth = compact ? 8 : narrow ? 40 : 62;
2663
+ const revealCol = Math.round(revealProgress * (maxWidth + 4)) - 2;
2664
+ 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.)")));
2669
+ }
2670
+ 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);
2672
+ }
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);
2675
+ }
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);
2381
2677
  }
2382
- function TabBarApp() {
2383
- const tabs = ["Files", "Search", "Git", "Debug"];
2384
- const [selectedIndex, setSelectedIndex] = useState11(0);
2385
- useKeyboard((event) => {
2386
- if (event.name === "left") setSelectedIndex((i) => i > 0 ? i - 1 : tabs.length - 1);
2387
- if (event.name === "right") setSelectedIndex((i) => i < tabs.length - 1 ? i + 1 : 0);
2388
- });
2389
- return /* @__PURE__ */ jsxs20("box", { flexDirection: "column", flexGrow: 1, children: [
2390
- /* @__PURE__ */ jsxs20("box", { flexDirection: "column", gap: 1, padding: 1, flexGrow: 1, children: [
2391
- /* @__PURE__ */ jsx28(TabBar, { label: "View", options: tabs, selectedIndex }),
2392
- /* @__PURE__ */ jsx28("text", { style: textStyle({ dim: true }), children: "Use \u2190/\u2192 arrow keys to switch tabs" })
2393
- ] }),
2394
- /* @__PURE__ */ jsx28("box", { paddingX: 1, paddingBottom: 1, children: /* @__PURE__ */ jsx28(StatusBar, { items: [{ key: "\u2190\u2192", label: "switch tab" }, { key: "q", label: "quit" }] }) })
2395
- ] });
2678
+
2679
+ // src/landing/matrix-background.tsx
2680
+ import { useMemo as useMemo6 } from "react";
2681
+
2682
+ // src/landing/use-matrix.ts
2683
+ import { useState as useState25, useEffect as useEffect7, useRef as useRef11 } from "react";
2684
+ var CHARS = "abcdefghijklmnopqrstuvwxyz0123456789@#$%^&*(){}[]|;:<>,.?/~`";
2685
+ function randomChar() {
2686
+ return CHARS[Math.floor(Math.random() * CHARS.length)];
2396
2687
  }
2397
- function StatusBarApp() {
2398
- const shortcuts = [
2399
- { key: "Tab", label: "switch focus" },
2400
- { key: "\u2190\u2192", label: "cycle" },
2401
- { key: "b", label: "back" },
2402
- { key: "z", label: "reset" }
2403
- ];
2404
- const [lastKey, setLastKey] = useState11(null);
2405
- useKeyboard((event) => {
2406
- if (event.name === "tab") setLastKey("switch focus (Tab)");
2407
- else if (event.name === "left") setLastKey("cycle (\u2190)");
2408
- else if (event.name === "right") setLastKey("cycle (\u2192)");
2409
- else if (event.name === "b") setLastKey("back (b)");
2410
- else if (event.name === "z") setLastKey("reset (z)");
2411
- });
2412
- return /* @__PURE__ */ jsxs20("box", { flexDirection: "column", gap: 1, padding: 1, children: [
2413
- lastKey ? /* @__PURE__ */ jsxs20("text", { children: [
2414
- /* @__PURE__ */ jsx28("span", { children: "Pressed: " }),
2415
- /* @__PURE__ */ jsx28("span", { style: textStyle({ bold: true, fg: "cyan" }), children: lastKey })
2416
- ] }) : /* @__PURE__ */ jsx28("text", { style: textStyle({ dim: true }), children: "Press a key to trigger an action" }),
2417
- /* @__PURE__ */ jsx28(
2418
- StatusBar,
2419
- {
2420
- items: [...shortcuts, { key: "q", label: "quit" }],
2421
- extra: /* @__PURE__ */ jsx28("span", { style: textStyle({ fg: "green" }), children: "\u25CF Ready" })
2688
+ function createDrop(height, seeded = false) {
2689
+ const length = Math.floor(Math.random() * Math.floor(height * 0.6)) + 4;
2690
+ return {
2691
+ y: seeded ? Math.floor(Math.random() * (height + length)) : -Math.floor(Math.random() * height),
2692
+ speed: 1,
2693
+ length,
2694
+ chars: Array.from({ length }, randomChar)
2695
+ };
2696
+ }
2697
+ function buildGrid(columns, width, height) {
2698
+ const grid = Array.from({ length: height }, () => Array(width).fill(" "));
2699
+ const brightness = Array.from({ length: height }, () => Array(width).fill(0));
2700
+ for (let x = 0; x < width; x++) {
2701
+ const drop = columns[x];
2702
+ if (!drop) continue;
2703
+ for (let i = 0; i < drop.length; i++) {
2704
+ const row = Math.floor(drop.y) - i;
2705
+ 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);
2422
2711
  }
2423
- )
2424
- ] });
2712
+ }
2713
+ }
2714
+ return { grid, brightness };
2425
2715
  }
2426
- function ModalApp() {
2427
- const [isOpen, setIsOpen] = useState11(false);
2428
- useKeyboard((event) => {
2429
- if (!isOpen && event.name === "m") setIsOpen(true);
2430
- if (isOpen && (event.name === "q" || event.name === "escape")) setIsOpen(false);
2716
+ function useMatrix(width, height) {
2717
+ const columnsRef = useRef11([]);
2718
+ const [state, setState] = useState25(() => {
2719
+ const columns = Array.from(
2720
+ { length: width },
2721
+ () => Math.random() < 0.5 ? createDrop(height, true) : null
2722
+ );
2723
+ columnsRef.current = columns;
2724
+ return buildGrid(columns, width, height);
2431
2725
  });
2432
- if (isOpen) {
2433
- return /* @__PURE__ */ jsxs20("box", { flexDirection: "column", flexGrow: 1, children: [
2434
- /* @__PURE__ */ jsx28(Modal, { title: "Example Modal", borderColor: "blue", useKeyboard, onClose: () => setIsOpen(false), children: /* @__PURE__ */ jsxs20("box", { paddingX: 1, flexDirection: "column", children: [
2435
- /* @__PURE__ */ jsx28("text", { children: "This is a modal overlay component." }),
2436
- /* @__PURE__ */ jsx28("text", { children: " " }),
2437
- /* @__PURE__ */ jsx28("text", { style: textStyle({ dim: true }), children: "It stretches to fill the full terminal height." })
2438
- ] }) }),
2439
- /* @__PURE__ */ jsx28("box", { paddingX: 1, paddingBottom: 1, children: /* @__PURE__ */ jsx28(StatusBar, { items: [{ key: "q", label: "close" }, { key: "Esc", label: "quit" }] }) })
2440
- ] });
2441
- }
2442
- return /* @__PURE__ */ jsxs20("box", { flexDirection: "column", flexGrow: 1, children: [
2443
- /* @__PURE__ */ jsx28("box", { flexDirection: "column", flexGrow: 1, alignItems: "center", justifyContent: "center", children: /* @__PURE__ */ jsxs20("text", { children: [
2444
- /* @__PURE__ */ jsx28("span", { style: textStyle({ dim: true }), children: "Press " }),
2445
- /* @__PURE__ */ jsx28("span", { style: textStyle({ inverse: true, bold: true }), children: " m " }),
2446
- /* @__PURE__ */ jsx28("span", { style: textStyle({ dim: true }), children: " to open modal" })
2447
- ] }) }),
2448
- /* @__PURE__ */ jsx28("box", { paddingX: 1, paddingBottom: 1, children: /* @__PURE__ */ jsx28(StatusBar, { items: [{ key: "m", label: "open modal" }, { key: "q", label: "quit" }] }) })
2449
- ] });
2726
+ useEffect7(() => {
2727
+ if (width < 2 || height < 2) return;
2728
+ const id = setInterval(() => {
2729
+ const columns = columnsRef.current;
2730
+ for (let x = 0; x < width; x++) {
2731
+ if (columns[x] === null || columns[x] === void 0) {
2732
+ if (Math.random() < 0.03) {
2733
+ columns[x] = createDrop(height);
2734
+ }
2735
+ continue;
2736
+ }
2737
+ const drop = columns[x];
2738
+ drop.y += drop.speed;
2739
+ if (Math.random() < 0.1) {
2740
+ const idx = Math.floor(Math.random() * drop.chars.length);
2741
+ drop.chars[idx] = randomChar();
2742
+ }
2743
+ if (drop.y - drop.length > height) {
2744
+ columns[x] = null;
2745
+ }
2746
+ }
2747
+ setState(buildGrid(columns, width, height));
2748
+ }, 80);
2749
+ return () => clearInterval(id);
2750
+ }, [width, height]);
2751
+ useEffect7(() => {
2752
+ columnsRef.current = Array.from(
2753
+ { length: width },
2754
+ () => Math.random() < 0.5 ? createDrop(height, true) : null
2755
+ );
2756
+ setState(buildGrid(columnsRef.current, width, height));
2757
+ }, [width, height]);
2758
+ return state;
2450
2759
  }
2451
- function PrimitivesApp() {
2452
- return /* @__PURE__ */ jsxs20("box", { flexDirection: "column", flexGrow: 1, children: [
2453
- /* @__PURE__ */ jsxs20("box", { flexDirection: "column", padding: 1, flexGrow: 1, children: [
2454
- /* @__PURE__ */ jsx28("box", { border: true, borderStyle: "rounded", borderColor: "#5e81ac", title: "Layout", titleAlignment: "center", padding: 1, children: /* @__PURE__ */ jsxs20("box", { flexDirection: "row", gap: 2, children: [
2455
- /* @__PURE__ */ jsx28("box", { border: true, borderStyle: "single", borderColor: "#a3be8c", padding: 1, flexGrow: 1, children: /* @__PURE__ */ jsx28("text", { fg: "#a3be8c", bold: true, children: "Box 1" }) }),
2456
- /* @__PURE__ */ jsx28("box", { border: true, borderStyle: "single", borderColor: "#ebcb8b", padding: 1, flexGrow: 1, children: /* @__PURE__ */ jsx28("text", { fg: "#ebcb8b", bold: true, children: "Box 2" }) }),
2457
- /* @__PURE__ */ jsx28("box", { border: true, borderStyle: "single", borderColor: "#b48ead", padding: 1, flexGrow: 1, children: /* @__PURE__ */ jsx28("text", { fg: "#b48ead", bold: true, children: "Box 3" }) })
2458
- ] }) }),
2459
- /* @__PURE__ */ jsx28("text", { fg: "#d8dee9", dim: true, children: " Nested boxes with borders, colors & flexbox layout" })
2460
- ] }),
2461
- /* @__PURE__ */ jsx28("box", { paddingX: 1, paddingBottom: 1, children: /* @__PURE__ */ jsx28(StatusBar, { items: [{ key: "q", label: "quit" }] }) })
2462
- ] });
2760
+
2761
+ // src/landing/matrix-background.tsx
2762
+ var MUTE_LEVELS = [0.12, 0.18, 0.24, 0.3, 0.38];
2763
+ var BG = hexToRgb("#1a1a2e");
2764
+ function buildMutedColors(baseHex) {
2765
+ const fg = hexToRgb(baseHex);
2766
+ return MUTE_LEVELS.map((factor) => rgbToHex({
2767
+ r: Math.round(BG.r + (fg.r - BG.r) * factor),
2768
+ g: Math.round(BG.g + (fg.g - BG.g) * factor),
2769
+ b: Math.round(BG.b + (fg.b - BG.b) * factor)
2770
+ }));
2463
2771
  }
2464
- function TerminalWindowApp() {
2465
- return /* @__PURE__ */ jsxs20("box", { flexDirection: "column", flexGrow: 1, children: [
2466
- /* @__PURE__ */ jsxs20("box", { flexDirection: "column", padding: 1, flexGrow: 1, children: [
2467
- /* @__PURE__ */ jsx28("text", { style: textStyle({ fg: "green" }), children: '$ echo "Hello from OpenTUI"' }),
2468
- /* @__PURE__ */ jsx28("text", { children: "Hello from OpenTUI" }),
2469
- /* @__PURE__ */ jsx28("text", { style: textStyle({ fg: "green" }), children: "$ _" })
2470
- ] }),
2471
- /* @__PURE__ */ jsx28("box", { paddingX: 1, paddingBottom: 1, children: /* @__PURE__ */ jsx28(StatusBar, { items: [{ key: "q", label: "quit" }] }) })
2472
- ] });
2772
+ function colorForCell(mutedColors, b) {
2773
+ if (b >= 1) return mutedColors[4];
2774
+ const idx = Math.min(Math.floor(b * (MUTE_LEVELS.length - 1)), MUTE_LEVELS.length - 2);
2775
+ return mutedColors[idx];
2473
2776
  }
2474
- var TIMELINE_STEPS = [
2475
- { tool: "Read", label: "Reading codebase \u2014 src/", status: "done", delay: 1800 },
2476
- { tool: "Think", label: "Planning changes \u2014 auth module", status: "done", delay: 2500 },
2477
- { tool: "Edit", label: "Editing files \u2014 4 files", status: "done", delay: 3200 },
2478
- { tool: "Bash", label: "Running tests \u2014 vitest", status: "done", delay: 2e3 },
2479
- { tool: "Edit", label: "Fixing test \u2014 routes.test.ts", status: "done", delay: 1500 }
2480
- ];
2481
- function TimelineApp() {
2482
- const [expanded, setExpanded] = useState11(true);
2483
- const [phase, setPhase] = useState11("running");
2484
- const [stepIndex, setStepIndex] = useState11(0);
2485
- const timerRef = useRef9(null);
2486
- useKeyboard((event) => {
2487
- if (event.name === "E" && event.ctrl && event.shift) setExpanded((v) => !v);
2488
- if (event.name === "r") timelineRestart();
2489
- });
2490
- function timelineRestart() {
2491
- if (timerRef.current) clearTimeout(timerRef.current);
2492
- setPhase("running");
2493
- setStepIndex(0);
2494
- }
2495
- useEffect5(() => {
2496
- if (phase !== "running") return;
2497
- if (stepIndex < TIMELINE_STEPS.length) {
2498
- const delay = TIMELINE_STEPS[stepIndex].delay;
2499
- timerRef.current = setTimeout(() => setStepIndex((i) => i + 1), delay);
2500
- } else {
2501
- timerRef.current = setTimeout(() => setPhase("done"), 500);
2502
- }
2503
- return () => {
2504
- if (timerRef.current) clearTimeout(timerRef.current);
2505
- };
2506
- }, [phase, stepIndex]);
2507
- useEffect5(() => {
2508
- if (phase === "done") {
2509
- timerRef.current = setTimeout(() => timelineRestart(), 3e3);
2777
+ function MatrixBackground({ width, height, clearRect, clearRects }) {
2778
+ const { grid, brightness } = useMatrix(width, height);
2779
+ const theme = useTheme();
2780
+ const columnColors = useMemo6(
2781
+ () => width > 0 ? generateGradient([theme.accent, theme.secondary, theme.primary], width) : [],
2782
+ [width, theme.accent, theme.secondary, theme.primary]
2783
+ );
2784
+ const columnMutedColors = useMemo6(
2785
+ () => columnColors.map(buildMutedColors),
2786
+ [columnColors]
2787
+ );
2788
+ return /* @__PURE__ */ React.createElement("box", { flexDirection: "column" }, grid.map((row, y) => /* @__PURE__ */ React.createElement("text", { key: y }, row.map((cell, x) => {
2789
+ const inClearRect = clearRect && y >= clearRect.top && y < clearRect.top + clearRect.height && x >= clearRect.left && x < clearRect.left + clearRect.width || clearRects && clearRects.some(
2790
+ (r) => y >= r.top && y < r.top + r.height && x >= r.left && x < r.left + r.width
2791
+ );
2792
+ const mutedColors = columnMutedColors[x];
2793
+ if (cell === " " || inClearRect || !mutedColors) {
2794
+ return /* @__PURE__ */ React.createElement("span", { key: x }, " ");
2510
2795
  }
2511
- return () => {
2512
- if (timerRef.current) clearTimeout(timerRef.current);
2513
- };
2514
- }, [phase]);
2515
- const steps = TIMELINE_STEPS.map((s, i) => {
2516
- if (i < stepIndex) return { ...s, status: "done" };
2517
- if (i === stepIndex && phase === "running") return { ...s, status: "running" };
2518
- return { ...s, status: phase === "done" ? "done" : "pending" };
2519
- });
2520
- const elapsedMs = TIMELINE_STEPS.slice(0, stepIndex).reduce((sum, s) => sum + s.delay, 0);
2521
- const totalMs = TIMELINE_STEPS.reduce((sum, s) => sum + s.delay, 0);
2522
- return /* @__PURE__ */ jsxs20("box", { flexDirection: "column", flexGrow: 1, children: [
2523
- /* @__PURE__ */ jsx28("box", { flexDirection: "column", padding: 1, flexGrow: 1, children: /* @__PURE__ */ jsx28(
2524
- Timeline,
2796
+ return /* @__PURE__ */ React.createElement(
2797
+ "span",
2525
2798
  {
2526
- steps,
2527
- duration: phase === "done" ? `${(totalMs / 1e3).toFixed(1)}s` : `${(elapsedMs / 1e3).toFixed(1)}s`,
2528
- collapsed: !expanded
2529
- }
2530
- ) }),
2531
- /* @__PURE__ */ jsx28("box", { paddingX: 1, paddingBottom: 1, children: /* @__PURE__ */ jsx28(StatusBar, { items: [
2532
- { key: "ctrl+shift+e", label: "toggle" },
2533
- { key: "r", label: "restart" },
2534
- { key: "q", label: "quit" }
2535
- ] }) })
2536
- ] });
2799
+ key: x,
2800
+ style: {
2801
+ fg: colorForCell(mutedColors, brightness[y][x])
2802
+ }
2803
+ },
2804
+ cell
2805
+ );
2806
+ }))));
2537
2807
  }
2538
- var BUBBLE_STEPS = [
2539
- { tool: "Read", label: "Reading codebase \u2014 src/", status: "done", delay: 1800 },
2540
- { tool: "Think", label: "Planning changes \u2014 auth module", status: "done", delay: 2500 },
2541
- { tool: "Edit", label: "Editing files \u2014 4 files", status: "done", delay: 3200 },
2542
- { tool: "Bash", label: "Running tests \u2014 vitest", status: "done", delay: 2e3 },
2543
- { tool: "Edit", label: "Fixing test \u2014 routes.test.ts", status: "done", delay: 1500 }
2544
- ];
2545
- var BUBBLE_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.";
2546
- var BUBBLE_TOTAL_MS = BUBBLE_STEPS.reduce((sum, s) => sum + s.delay, 0);
2547
- function MessageApp() {
2548
- const [expanded, setExpanded] = useState11(true);
2549
- const [phase, setPhase] = useState11("idle");
2550
- const [stepIndex, setStepIndex] = useState11(0);
2551
- const [streamedText, setStreamedText] = useState11("");
2552
- const timerRef = useRef9(null);
2553
- useKeyboard((event) => {
2554
- if (event.name === "E" && event.ctrl && event.shift) setExpanded((v) => !v);
2555
- if (event.name === "r") bubbleRestart();
2556
- });
2557
- function bubbleRestart() {
2558
- if (timerRef.current) clearTimeout(timerRef.current);
2559
- setPhase("idle");
2560
- setStepIndex(0);
2561
- setStreamedText("");
2562
- }
2563
- useEffect5(() => {
2564
- if (phase === "idle") {
2565
- timerRef.current = setTimeout(() => setPhase("thinking"), 800);
2566
- }
2567
- return () => {
2568
- if (timerRef.current) clearTimeout(timerRef.current);
2569
- };
2570
- }, [phase]);
2571
- useEffect5(() => {
2572
- if (phase !== "thinking") return;
2573
- if (stepIndex < BUBBLE_STEPS.length) {
2574
- const delay = BUBBLE_STEPS[stepIndex].delay;
2575
- timerRef.current = setTimeout(() => setStepIndex((i) => i + 1), delay);
2576
- } else {
2577
- timerRef.current = setTimeout(() => setPhase("streaming"), 500);
2578
- }
2579
- return () => {
2580
- if (timerRef.current) clearTimeout(timerRef.current);
2581
- };
2582
- }, [phase, stepIndex]);
2583
- useEffect5(() => {
2584
- if (phase !== "streaming") return;
2585
- if (streamedText.length < BUBBLE_RESPONSE.length) {
2586
- timerRef.current = setTimeout(() => {
2587
- setStreamedText(BUBBLE_RESPONSE.slice(0, streamedText.length + 2));
2588
- }, 25);
2589
- } else {
2590
- timerRef.current = setTimeout(() => setPhase("done"), 500);
2591
- }
2592
- return () => {
2593
- if (timerRef.current) clearTimeout(timerRef.current);
2594
- };
2595
- }, [phase, streamedText]);
2596
- useEffect5(() => {
2597
- if (phase === "done") {
2598
- timerRef.current = setTimeout(() => bubbleRestart(), 3e3);
2599
- }
2600
- return () => {
2601
- if (timerRef.current) clearTimeout(timerRef.current);
2808
+
2809
+ // src/landing/landing-app.tsx
2810
+ function LandingApp({ useKeyboard: useKeyboard20 }) {
2811
+ const theme = useTheme();
2812
+ 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;
2817
+ const gap = isMobile ? 0 : 1;
2818
+ const installLinksTop = 3 + logoHeight + logoExtra + gap;
2819
+ const installLinksHeight = 3;
2820
+ const boxTop = installLinksTop + installLinksHeight + gap + 1;
2821
+ const bh = height - boxTop - 1;
2822
+ return {
2823
+ clearRect: { top: boxTop, left: 1, width: width - 2, height: bh },
2824
+ installLinksClearRect: { top: installLinksTop, left: 1, width: width - 2, height: installLinksHeight }
2602
2825
  };
2603
- }, [phase]);
2604
- const steps = BUBBLE_STEPS.map((s, i) => {
2605
- if (i < stepIndex) return { ...s, status: "done" };
2606
- if (i === stepIndex) return { ...s, status: "running" };
2607
- return { ...s, status: "pending" };
2608
- });
2609
- const isThinking = phase === "thinking";
2610
- const isStreaming = phase === "streaming";
2611
- const isDone = phase === "done";
2612
- const showAssistant = phase !== "idle";
2613
- const elapsedMs = BUBBLE_STEPS.slice(0, stepIndex).reduce((sum, s) => sum + s.delay, 0);
2614
- const reasoningDuration = isThinking ? `${(elapsedMs / 1e3).toFixed(1)}s` : `${(BUBBLE_TOTAL_MS / 1e3).toFixed(1)}s`;
2615
- const reasoningSteps = isThinking ? steps : BUBBLE_STEPS.map((s) => ({ ...s, status: "done" }));
2616
- return /* @__PURE__ */ jsxs20("box", { flexDirection: "column", flexGrow: 1, children: [
2617
- /* @__PURE__ */ jsxs20("box", { flexDirection: "column", padding: 1, gap: 1, flexGrow: 1, children: [
2618
- /* @__PURE__ */ jsx28(Message, { role: "user", children: /* @__PURE__ */ jsx28(Message.Content, { children: /* @__PURE__ */ jsx28(Message.Text, { children: "Can you refactor the auth module?" }) }) }),
2619
- showAssistant && /* @__PURE__ */ jsxs20(Message, { role: "assistant", isStreaming, children: [
2620
- /* @__PURE__ */ jsx28(Message.Reasoning, { part: {
2621
- type: "reasoning",
2622
- duration: reasoningDuration,
2623
- collapsed: !expanded,
2624
- steps: reasoningSteps
2625
- } }),
2626
- (isStreaming || isDone) && /* @__PURE__ */ jsx28(Message.Content, { children: /* @__PURE__ */ jsx28(Message.Text, { isLast: true, children: isDone ? BUBBLE_RESPONSE : streamedText }) })
2627
- ] })
2628
- ] }),
2629
- /* @__PURE__ */ jsx28("box", { paddingX: 1, paddingBottom: 1, children: /* @__PURE__ */ jsx28(StatusBar, { items: [{ key: "ctrl+shift+e", label: "toggle timeline" }, { key: "r", label: "restart" }, { key: "q", label: "quit" }] }) })
2630
- ] });
2631
- }
2632
- var initialChatMessages = [
2633
- { id: "1", role: "user", content: "Show me my portfolio" },
2634
- { id: "2", role: "assistant", content: "Here's your current portfolio allocation:" },
2635
- { id: "3", role: "user", content: "Calculate rebalancing trades" },
2636
- { id: "4", role: "assistant", content: "I've calculated the optimal trades to rebalance your portfolio." }
2637
- ];
2638
- var chatNextId = 5;
2639
- function ChatApp() {
2640
- const [messages, setMessages] = useState11(initialChatMessages);
2641
- const [isLoading, setIsLoading] = useState11(false);
2642
- const [streamingText, setStreamingText] = useState11("");
2643
- const [activeToolCalls, setActiveToolCalls] = useState11([]);
2644
- const intervalRef = useRef9(null);
2645
- const handleSend = useCallback4((text) => {
2646
- const userMsg = { id: String(chatNextId++), role: "user", content: text };
2647
- setMessages((prev) => [...prev, userMsg]);
2648
- setIsLoading(true);
2649
- const toolCallId = `tc-${chatNextId}`;
2650
- setTimeout(() => {
2651
- setIsLoading(false);
2652
- setActiveToolCalls([{ id: toolCallId, title: "process_request", status: "in_progress" }]);
2653
- }, 500);
2654
- setTimeout(() => {
2655
- setActiveToolCalls([{ id: toolCallId, title: "process_request", status: "completed" }]);
2656
- }, 1200);
2657
- const response = `You said: "${text}". This is a demo response.`;
2658
- let charIndex = 0;
2659
- setTimeout(() => {
2660
- intervalRef.current = setInterval(() => {
2661
- charIndex = Math.min(charIndex + 3, response.length);
2662
- if (charIndex < response.length) {
2663
- setStreamingText(response.slice(0, charIndex));
2664
- } else {
2665
- if (intervalRef.current) clearInterval(intervalRef.current);
2666
- setStreamingText("");
2667
- setActiveToolCalls([]);
2668
- setMessages((prev) => [
2669
- ...prev,
2670
- { id: String(chatNextId++), role: "assistant", content: response }
2671
- ]);
2672
- }
2673
- }, 50);
2674
- }, 1400);
2675
- }, []);
2676
- const handleCancel = useCallback4(() => {
2677
- if (intervalRef.current) clearInterval(intervalRef.current);
2678
- setIsLoading(false);
2679
- setStreamingText("");
2680
- setActiveToolCalls([]);
2681
- }, []);
2682
- return /* @__PURE__ */ jsxs20("box", { flexDirection: "column", flexGrow: 1, children: [
2683
- /* @__PURE__ */ jsx28(
2684
- ChatPanel,
2685
- {
2686
- messages,
2687
- streamingText,
2688
- isLoading,
2689
- activeToolCalls,
2690
- onSendMessage: handleSend,
2691
- onCancel: handleCancel,
2692
- placeholder: "Ask about your portfolio...",
2693
- useKeyboard
2694
- }
2695
- ),
2696
- /* @__PURE__ */ jsx28("box", { paddingX: 1, paddingBottom: 1, children: /* @__PURE__ */ jsx28(StatusBar, { items: [{ key: "q", label: "quit" }] }) })
2697
- ] });
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" }))));
2698
2828
  }
2829
+
2830
+ // demos/index.tsx
2699
2831
  var demos = [
2700
- { name: "gradient", app: () => /* @__PURE__ */ jsx28(GradientApp, {}) },
2701
- { name: "ascii", app: () => /* @__PURE__ */ jsx28(AsciiApp, {}) },
2702
- { name: "table", app: () => /* @__PURE__ */ jsx28(TableApp, {}) },
2703
- { name: "spinner", app: () => /* @__PURE__ */ jsx28(SpinnerApp, {}) },
2704
- { name: "select-input", app: () => /* @__PURE__ */ jsx28(SelectInputApp, {}) },
2705
- { name: "multi-select", app: () => /* @__PURE__ */ jsx28(MultiSelectApp, {}) },
2706
- { name: "prompt-input", app: () => /* @__PURE__ */ jsx28(PromptInputApp, {}) },
2707
- { name: "text-input", app: () => /* @__PURE__ */ jsx28(TextInputApp, {}) },
2708
- { name: "link", app: () => /* @__PURE__ */ jsx28(LinkApp, {}) },
2709
- { name: "tabs", app: () => /* @__PURE__ */ jsx28(TabBarApp, {}) },
2710
- { name: "status-bar", app: () => /* @__PURE__ */ jsx28(StatusBarApp, {}) },
2711
- { name: "modal", app: () => /* @__PURE__ */ jsx28(ModalApp, {}) },
2712
- { name: "primitives", app: () => /* @__PURE__ */ jsx28(PrimitivesApp, {}) },
2713
- { name: "chat", app: () => /* @__PURE__ */ jsx28(ChatApp, {}) },
2714
- { name: "timeline", app: () => /* @__PURE__ */ jsx28(TimelineApp, {}) },
2715
- { name: "message", app: () => /* @__PURE__ */ jsx28(MessageApp, {}) },
2716
- { name: "terminal-window", app: () => /* @__PURE__ */ jsx28(TerminalWindowApp, {}) },
2717
- { name: "landing", app: () => /* @__PURE__ */ jsx28(LandingApp, { useKeyboard }) }
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 }) }
2718
2856
  ];
2719
2857
 
2720
2858
  // src/run.tsx
2721
- import { jsx as jsx29 } from "react/jsx-runtime";
2722
2859
  var _renderer;
2723
2860
  function DemoShell({ children }) {
2724
- useKeyboard2((event) => {
2861
+ useKeyboard19((event) => {
2725
2862
  if (event.name === "q" || event.name === "escape") {
2726
2863
  _renderer.destroy();
2727
2864
  }
2728
2865
  });
2729
- return /* @__PURE__ */ jsx29("box", { flexDirection: "column", flexGrow: 1, children });
2866
+ return /* @__PURE__ */ React.createElement("box", { flexDirection: "column", flexGrow: 1 }, children);
2730
2867
  }
2731
- async function runDemo(name) {
2732
- const demo = demos.find((d) => d.name === name);
2868
+ async function runDemo(name2) {
2869
+ const demo = demos.find((d) => d.name === name2);
2733
2870
  if (!demo) {
2734
- console.error(`Unknown demo: "${name}"`);
2871
+ console.error(`Unknown demo: "${name2}"`);
2735
2872
  console.error(`Available: ${demos.map((d) => d.name).join(", ")}`);
2736
2873
  process.exit(1);
2737
2874
  }
2738
2875
  _renderer = await createCliRenderer({ exitOnCtrlC: true });
2739
- createRoot(_renderer).render(/* @__PURE__ */ jsx29(DemoShell, { children: demo.app() }));
2876
+ createRoot(_renderer).render(/* @__PURE__ */ React.createElement(DemoShell, null, demo.app()));
2877
+ }
2878
+ var name = process.argv[2];
2879
+ if (name) {
2880
+ runDemo(name);
2740
2881
  }
2741
2882
  export {
2742
2883
  demos,