@gridland/demo 0.2.52 → 0.2.53
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/demo-names.json +1 -1
- package/dist/landing.js +703 -87
- package/dist/run.js +1035 -386
- package/package.json +3 -3
package/dist/landing.js
CHANGED
|
@@ -52,6 +52,33 @@ function textStyle(opts) {
|
|
|
52
52
|
|
|
53
53
|
// ../ui/components/status-bar/status-bar.tsx
|
|
54
54
|
import { jsx as jsx3 } from "react/jsx-runtime";
|
|
55
|
+
function StatusBar({ items, extra }) {
|
|
56
|
+
const theme = useTheme();
|
|
57
|
+
const parts = [];
|
|
58
|
+
if (extra !== void 0) {
|
|
59
|
+
parts.push(
|
|
60
|
+
/* @__PURE__ */ jsx3("span", { children: extra }, "extra")
|
|
61
|
+
);
|
|
62
|
+
parts.push(
|
|
63
|
+
/* @__PURE__ */ jsx3("span", { style: textStyle({ dim: true, fg: theme.placeholder }), children: " \u2502 " }, "pipe")
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
items.forEach((item, i) => {
|
|
67
|
+
if (i > 0) {
|
|
68
|
+
parts.push(/* @__PURE__ */ jsx3("span", { children: " " }, `gap-${i}`));
|
|
69
|
+
}
|
|
70
|
+
parts.push(
|
|
71
|
+
/* @__PURE__ */ jsx3("span", { style: textStyle({ bold: true, fg: theme.background, bg: theme.muted }), children: ` ${item.key} ` }, `key-${i}`)
|
|
72
|
+
);
|
|
73
|
+
parts.push(
|
|
74
|
+
/* @__PURE__ */ jsx3("span", { style: textStyle({ dim: true, fg: theme.placeholder }), children: ` ${item.label}` }, `label-${i}`)
|
|
75
|
+
);
|
|
76
|
+
});
|
|
77
|
+
if (parts.length === 0) {
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
return /* @__PURE__ */ jsx3("text", { children: parts });
|
|
81
|
+
}
|
|
55
82
|
|
|
56
83
|
// ../ui/components/provider/provider.tsx
|
|
57
84
|
import { createContext as createContext2, useContext as useContext2 } from "react";
|
|
@@ -185,7 +212,7 @@ import {
|
|
|
185
212
|
createContext as createContext5,
|
|
186
213
|
useContext as useContext5
|
|
187
214
|
} from "react";
|
|
188
|
-
import {
|
|
215
|
+
import { jsx as jsx16, jsxs as jsxs10 } from "react/jsx-runtime";
|
|
189
216
|
var PromptInputControllerCtx = createContext5(null);
|
|
190
217
|
var useOptionalController = () => useContext5(PromptInputControllerCtx);
|
|
191
218
|
var PromptInputContext = createContext5(null);
|
|
@@ -212,10 +239,11 @@ function resolveStatusHintText(status, submittedText, streamingText, errorText,
|
|
|
212
239
|
if (status === "error") return errorText;
|
|
213
240
|
return disabledText;
|
|
214
241
|
}
|
|
215
|
-
var DIVIDER_LINE = "\u2500".repeat(500);
|
|
216
242
|
function PromptInputDivider() {
|
|
217
|
-
const { theme } = usePromptInput();
|
|
218
|
-
|
|
243
|
+
const { dividerColor, dividerDashed, theme } = usePromptInput();
|
|
244
|
+
const color = dividerColor ?? theme.muted;
|
|
245
|
+
const char = dividerDashed ? "\u254C" : "\u2500";
|
|
246
|
+
return /* @__PURE__ */ jsx16("text", { wrapMode: "none", marginLeft: -1, marginRight: -1, children: /* @__PURE__ */ jsx16("span", { style: textStyle({ dim: !dividerColor, fg: color }), children: char.repeat(500) }) });
|
|
219
247
|
}
|
|
220
248
|
function PromptInputSuggestions() {
|
|
221
249
|
const { suggestions, sugIdx, maxSuggestions, theme } = usePromptInput();
|
|
@@ -230,18 +258,25 @@ function PromptInputSuggestions() {
|
|
|
230
258
|
] }, sug.text);
|
|
231
259
|
}) });
|
|
232
260
|
}
|
|
233
|
-
var CURSOR_CHAR = "\u258D";
|
|
234
261
|
function PromptInputTextarea() {
|
|
235
|
-
const { value, disabled, statusHintText, placeholder, prompt, promptColor, theme } = usePromptInput();
|
|
236
|
-
return /* @__PURE__ */ jsxs10("
|
|
237
|
-
/* @__PURE__ */ jsx16("span", { style: textStyle({ fg: promptColor }), children: prompt }),
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
262
|
+
const { value, isFocused, disabled, statusHintText, placeholder, prompt, promptColor, theme, handleInput, handleInputSubmit, handleInputKeyDown } = usePromptInput();
|
|
263
|
+
return /* @__PURE__ */ jsxs10("box", { flexDirection: "row", children: [
|
|
264
|
+
/* @__PURE__ */ jsx16("text", { children: /* @__PURE__ */ jsx16("span", { style: textStyle({ fg: promptColor }), children: prompt }) }),
|
|
265
|
+
isFocused ? /* @__PURE__ */ jsx16(
|
|
266
|
+
"input",
|
|
267
|
+
{
|
|
268
|
+
value,
|
|
269
|
+
placeholder,
|
|
270
|
+
focused: true,
|
|
271
|
+
onInput: handleInput,
|
|
272
|
+
onSubmit: handleInputSubmit,
|
|
273
|
+
onKeyDown: handleInputKeyDown,
|
|
274
|
+
cursorColor: theme.muted,
|
|
275
|
+
cursorStyle: { style: "line", blinking: !value },
|
|
276
|
+
placeholderColor: theme.placeholder,
|
|
277
|
+
textColor: theme.foreground
|
|
278
|
+
}
|
|
279
|
+
) : disabled && value.length === 0 ? /* @__PURE__ */ jsx16("text", { children: /* @__PURE__ */ jsx16("span", { style: textStyle({ dim: true, fg: theme.placeholder }), children: statusHintText }) }) : /* @__PURE__ */ jsx16("text", { children: /* @__PURE__ */ jsx16("span", { style: textStyle({ fg: value ? theme.foreground : theme.placeholder, dim: !value }), children: value || placeholder }) })
|
|
245
280
|
] });
|
|
246
281
|
}
|
|
247
282
|
function PromptInputSubmit(props) {
|
|
@@ -288,13 +323,16 @@ function PromptInput({
|
|
|
288
323
|
maxSuggestions = 5,
|
|
289
324
|
enableHistory = true,
|
|
290
325
|
model,
|
|
326
|
+
focus = true,
|
|
291
327
|
showDividers = true,
|
|
292
328
|
autoFocus = false,
|
|
329
|
+
dividerColor,
|
|
330
|
+
dividerDashed,
|
|
293
331
|
useKeyboard: useKeyboardProp,
|
|
294
332
|
children
|
|
295
333
|
}) {
|
|
296
334
|
const theme = useTheme();
|
|
297
|
-
const
|
|
335
|
+
const useKeyboard3 = useKeyboardContext(useKeyboardProp);
|
|
298
336
|
useEffect2(() => {
|
|
299
337
|
if (!autoFocus) return;
|
|
300
338
|
if (typeof document === "undefined") return;
|
|
@@ -305,6 +343,7 @@ function PromptInput({
|
|
|
305
343
|
}, [autoFocus]);
|
|
306
344
|
const resolvedPromptColor = promptColor ?? theme.muted;
|
|
307
345
|
const disabled = status ? status === "submitted" || status === "streaming" : disabledProp;
|
|
346
|
+
const isFocused = focus && !disabled;
|
|
308
347
|
const statusHintText = resolveStatusHintText(status, submittedText, streamingLabel, errorText, disabledText);
|
|
309
348
|
const controller = useOptionalController();
|
|
310
349
|
const usingProvider = !!controller;
|
|
@@ -395,18 +434,86 @@ function PromptInput({
|
|
|
395
434
|
clearInput();
|
|
396
435
|
}
|
|
397
436
|
}, [onSubmit, clearInput]);
|
|
398
|
-
|
|
437
|
+
const handleInputSubmit = (text) => {
|
|
438
|
+
const trimmed = text.trim();
|
|
439
|
+
if (!trimmed) return;
|
|
440
|
+
if (enableHistory) {
|
|
441
|
+
setHist([trimmed, ...historyRef.current]);
|
|
442
|
+
}
|
|
443
|
+
updateValue("");
|
|
444
|
+
setHistI(-1);
|
|
445
|
+
handleSubmit(trimmed);
|
|
446
|
+
};
|
|
447
|
+
const handleInputKeyDown = (key) => {
|
|
448
|
+
if (key.name === "return" && suggestionsRef.current.length > 0) {
|
|
449
|
+
const sel = suggestionsRef.current[sugIdxRef.current];
|
|
450
|
+
if (sel) {
|
|
451
|
+
if (valueRef.current.startsWith("/")) {
|
|
452
|
+
updateValue("");
|
|
453
|
+
if (enableHistory) {
|
|
454
|
+
setHist([sel.text, ...historyRef.current]);
|
|
455
|
+
}
|
|
456
|
+
setHistI(-1);
|
|
457
|
+
handleSubmit(sel.text);
|
|
458
|
+
} else {
|
|
459
|
+
const base = valueRef.current.slice(0, valueRef.current.lastIndexOf("@"));
|
|
460
|
+
updateValue(base + sel.text + " ");
|
|
461
|
+
setSug([]);
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
key.preventDefault();
|
|
465
|
+
return;
|
|
466
|
+
}
|
|
467
|
+
if (key.name === "tab" && suggestionsRef.current.length > 0) {
|
|
468
|
+
setSugI((sugIdxRef.current + 1) % suggestionsRef.current.length);
|
|
469
|
+
key.preventDefault();
|
|
470
|
+
return;
|
|
471
|
+
}
|
|
472
|
+
if (key.name === "up") {
|
|
473
|
+
if (suggestionsRef.current.length > 0) {
|
|
474
|
+
setSugI(Math.max(0, sugIdxRef.current - 1));
|
|
475
|
+
} else if (enableHistory && historyRef.current.length > 0) {
|
|
476
|
+
const idx = Math.min(historyRef.current.length - 1, histIdxRef.current + 1);
|
|
477
|
+
setHistI(idx);
|
|
478
|
+
updateValue(historyRef.current[idx]);
|
|
479
|
+
}
|
|
480
|
+
key.preventDefault();
|
|
481
|
+
return;
|
|
482
|
+
}
|
|
483
|
+
if (key.name === "down") {
|
|
484
|
+
if (suggestionsRef.current.length > 0) {
|
|
485
|
+
setSugI(Math.min(suggestionsRef.current.length - 1, sugIdxRef.current + 1));
|
|
486
|
+
} else if (enableHistory && histIdxRef.current > 0) {
|
|
487
|
+
const nextIdx = histIdxRef.current - 1;
|
|
488
|
+
setHistI(nextIdx);
|
|
489
|
+
updateValue(historyRef.current[nextIdx]);
|
|
490
|
+
} else if (enableHistory && histIdxRef.current === 0) {
|
|
491
|
+
setHistI(-1);
|
|
492
|
+
updateValue("");
|
|
493
|
+
}
|
|
494
|
+
key.preventDefault();
|
|
495
|
+
return;
|
|
496
|
+
}
|
|
497
|
+
if (key.name === "escape") {
|
|
498
|
+
if (suggestionsRef.current.length > 0) {
|
|
499
|
+
setSug([]);
|
|
500
|
+
key.preventDefault();
|
|
501
|
+
}
|
|
502
|
+
return;
|
|
503
|
+
}
|
|
504
|
+
};
|
|
505
|
+
useKeyboard3?.((event) => {
|
|
399
506
|
if (event.name === "escape" && (status === "streaming" || status === "submitted") && onStop) {
|
|
400
507
|
onStop();
|
|
401
508
|
return;
|
|
402
509
|
}
|
|
510
|
+
if (isFocused) return;
|
|
403
511
|
if (disabled) return;
|
|
404
512
|
if (event.name === "return") {
|
|
405
513
|
if (suggestionsRef.current.length > 0) {
|
|
406
514
|
const sel = suggestionsRef.current[sugIdxRef.current];
|
|
407
515
|
if (sel) {
|
|
408
516
|
if (valueRef.current.startsWith("/")) {
|
|
409
|
-
setSug([]);
|
|
410
517
|
updateValue("");
|
|
411
518
|
if (enableHistory) {
|
|
412
519
|
setHist([sel.text, ...historyRef.current]);
|
|
@@ -427,7 +534,6 @@ function PromptInput({
|
|
|
427
534
|
}
|
|
428
535
|
updateValue("");
|
|
429
536
|
setHistI(-1);
|
|
430
|
-
setSug([]);
|
|
431
537
|
handleSubmit(trimmed);
|
|
432
538
|
}
|
|
433
539
|
return;
|
|
@@ -481,6 +587,7 @@ function PromptInput({
|
|
|
481
587
|
const visibleSuggestions = suggestions.slice(0, maxSuggestions);
|
|
482
588
|
const ctxValue = {
|
|
483
589
|
value,
|
|
590
|
+
isFocused,
|
|
484
591
|
disabled,
|
|
485
592
|
status,
|
|
486
593
|
onStop,
|
|
@@ -493,7 +600,12 @@ function PromptInput({
|
|
|
493
600
|
maxSuggestions,
|
|
494
601
|
errorText,
|
|
495
602
|
model,
|
|
496
|
-
|
|
603
|
+
dividerColor,
|
|
604
|
+
dividerDashed,
|
|
605
|
+
theme,
|
|
606
|
+
handleInput: updateValue,
|
|
607
|
+
handleInputSubmit,
|
|
608
|
+
handleInputKeyDown
|
|
497
609
|
};
|
|
498
610
|
if (children) {
|
|
499
611
|
return /* @__PURE__ */ jsx16(PromptInputContext.Provider, { value: ctxValue, children: /* @__PURE__ */ jsx16("box", { flexDirection: "column", flexShrink: 0, children }) });
|
|
@@ -521,7 +633,7 @@ import { jsx as jsx17, jsxs as jsxs11 } from "react/jsx-runtime";
|
|
|
521
633
|
|
|
522
634
|
// ../ui/components/chain-of-thought/chain-of-thought.tsx
|
|
523
635
|
import { createContext as createContext6, memo, useContext as useContext6, useEffect as useEffect3, useMemo as useMemo4, useState as useState7 } from "react";
|
|
524
|
-
import { Fragment as
|
|
636
|
+
import { Fragment as Fragment5, jsx as jsx18, jsxs as jsxs12 } from "react/jsx-runtime";
|
|
525
637
|
var DOTS = ["\u25CB", "\u25D4", "\u25D1", "\u25D5", "\u25CF"];
|
|
526
638
|
var SPINNER_INTERVAL = 150;
|
|
527
639
|
var ChainOfThoughtContext = createContext6(null);
|
|
@@ -580,7 +692,7 @@ var ChainOfThoughtHeader = memo(({
|
|
|
580
692
|
var ChainOfThoughtContent = memo(({ children }) => {
|
|
581
693
|
const { isOpen } = useChainOfThought();
|
|
582
694
|
if (!isOpen) return null;
|
|
583
|
-
return /* @__PURE__ */ jsx18(
|
|
695
|
+
return /* @__PURE__ */ jsx18(Fragment5, { children });
|
|
584
696
|
});
|
|
585
697
|
var ChainOfThoughtStep = memo(({
|
|
586
698
|
label,
|
|
@@ -793,7 +905,7 @@ function useBreakpoints() {
|
|
|
793
905
|
}
|
|
794
906
|
|
|
795
907
|
// src/landing/landing-app.tsx
|
|
796
|
-
import { useMemo as
|
|
908
|
+
import { useMemo as useMemo8, useRef as useRef9, useState as useState12 } from "react";
|
|
797
909
|
|
|
798
910
|
// src/landing/install-box.tsx
|
|
799
911
|
import { jsx as jsx21, jsxs as jsxs15 } from "react/jsx-runtime";
|
|
@@ -818,10 +930,12 @@ function InstallBox() {
|
|
|
818
930
|
}
|
|
819
931
|
|
|
820
932
|
// src/landing/links-box.tsx
|
|
933
|
+
import { isBrowser } from "@gridland/utils";
|
|
821
934
|
import { jsx as jsx22, jsxs as jsxs16 } from "react/jsx-runtime";
|
|
822
935
|
var UNDERLINE3 = 1 << 3;
|
|
823
936
|
function LinksBox() {
|
|
824
937
|
const theme = useTheme();
|
|
938
|
+
const docsHref = isBrowser() ? `${window.location.origin}/docs` : "https://gridland.io/docs";
|
|
825
939
|
return /* @__PURE__ */ jsx22(
|
|
826
940
|
"box",
|
|
827
941
|
{
|
|
@@ -836,7 +950,7 @@ function LinksBox() {
|
|
|
836
950
|
/* @__PURE__ */ jsx22("a", { href: "https://github.com/thoughtfulllc/gridland", style: { attributes: UNDERLINE3, fg: theme.accent }, children: " GitHub" }),
|
|
837
951
|
/* @__PURE__ */ jsx22("span", { children: " " }),
|
|
838
952
|
/* @__PURE__ */ jsx22("span", { children: "\u{1F4D6}" }),
|
|
839
|
-
/* @__PURE__ */ jsx22("a", { href:
|
|
953
|
+
/* @__PURE__ */ jsx22("a", { href: docsHref, style: { attributes: UNDERLINE3, fg: theme.accent }, children: " Docs" })
|
|
840
954
|
] })
|
|
841
955
|
}
|
|
842
956
|
);
|
|
@@ -845,22 +959,22 @@ function LinksBox() {
|
|
|
845
959
|
// src/landing/logo.tsx
|
|
846
960
|
import { useState as useState8, useEffect as useEffect4, useRef as useRef5, useMemo as useMemo5 } from "react";
|
|
847
961
|
import figlet from "figlet";
|
|
848
|
-
import
|
|
849
|
-
import { Fragment as
|
|
850
|
-
figlet.parseFont("
|
|
851
|
-
function makeArt(text) {
|
|
852
|
-
return figlet.textSync(text, { font
|
|
962
|
+
import blockFont from "figlet/importable-fonts/Block.js";
|
|
963
|
+
import { Fragment as Fragment6, jsx as jsx23, jsxs as jsxs17 } from "react/jsx-runtime";
|
|
964
|
+
figlet.parseFont("Block", blockFont);
|
|
965
|
+
function makeArt(text, font = "Block") {
|
|
966
|
+
return figlet.textSync(text, { font }).split("\n").filter((l) => l.trimEnd().length > 0).join("\n");
|
|
853
967
|
}
|
|
854
|
-
var fullArt = makeArt("gridland");
|
|
855
|
-
var gridArt = makeArt("grid");
|
|
856
|
-
var landArt = makeArt("land");
|
|
857
|
-
var ART_HEIGHT =
|
|
968
|
+
var fullArt = makeArt("gridland", "Block");
|
|
969
|
+
var gridArt = makeArt("grid", "Block");
|
|
970
|
+
var landArt = makeArt("land", "Block");
|
|
971
|
+
var ART_HEIGHT = fullArt.split("\n").length;
|
|
858
972
|
function useAnimation(duration = 1e3) {
|
|
859
|
-
const
|
|
860
|
-
const [progress, setProgress] = useState8(
|
|
973
|
+
const isBrowser2 = typeof document !== "undefined";
|
|
974
|
+
const [progress, setProgress] = useState8(isBrowser2 ? 0 : 1);
|
|
861
975
|
const startTime = useRef5(null);
|
|
862
976
|
useEffect4(() => {
|
|
863
|
-
if (!
|
|
977
|
+
if (!isBrowser2) return;
|
|
864
978
|
let raf;
|
|
865
979
|
const tick = (time) => {
|
|
866
980
|
if (startTime.current === null) startTime.current = time;
|
|
@@ -875,12 +989,19 @@ function useAnimation(duration = 1e3) {
|
|
|
875
989
|
}, []);
|
|
876
990
|
return progress;
|
|
877
991
|
}
|
|
992
|
+
function darkenHex(hex, factor = 0.4) {
|
|
993
|
+
const r = Math.round(parseInt(hex.slice(1, 3), 16) * factor);
|
|
994
|
+
const g = Math.round(parseInt(hex.slice(3, 5), 16) * factor);
|
|
995
|
+
const b = Math.round(parseInt(hex.slice(5, 7), 16) * factor);
|
|
996
|
+
return `#${r.toString(16).padStart(2, "0")}${g.toString(16).padStart(2, "0")}${b.toString(16).padStart(2, "0")}`;
|
|
997
|
+
}
|
|
878
998
|
function RevealGradient({ children, revealCol }) {
|
|
879
999
|
const gradientColors = GRADIENTS.instagram;
|
|
880
1000
|
const lines = children.split("\n");
|
|
881
1001
|
const maxLength = Math.max(...lines.map((l) => l.length));
|
|
882
1002
|
if (maxLength === 0) return /* @__PURE__ */ jsx23("text", { children });
|
|
883
1003
|
const hexColors = useMemo5(() => generateGradient(gradientColors, maxLength), [maxLength]);
|
|
1004
|
+
const bgColors = useMemo5(() => hexColors.map((c) => darkenHex(c)), [hexColors]);
|
|
884
1005
|
return /* @__PURE__ */ jsx23("box", { position: "relative", width: maxLength, height: lines.length, shouldFill: false, children: lines.map((line, lineIndex) => {
|
|
885
1006
|
const runs = [];
|
|
886
1007
|
let current = null;
|
|
@@ -911,7 +1032,7 @@ function RevealGradient({ children, revealCol }) {
|
|
|
911
1032
|
children: /* @__PURE__ */ jsx23("text", { shouldFill: false, children: run.chars.map((char, ci) => /* @__PURE__ */ jsx23(
|
|
912
1033
|
"span",
|
|
913
1034
|
{
|
|
914
|
-
style: { fg: hexColors[run.start + ci] },
|
|
1035
|
+
style: { fg: hexColors[run.start + ci], bg: bgColors[run.start + ci] },
|
|
915
1036
|
children: char
|
|
916
1037
|
},
|
|
917
1038
|
ci
|
|
@@ -922,15 +1043,15 @@ function RevealGradient({ children, revealCol }) {
|
|
|
922
1043
|
}) });
|
|
923
1044
|
}
|
|
924
1045
|
function Logo({ compact, narrow, mobile }) {
|
|
925
|
-
const
|
|
1046
|
+
const isBrowser2 = typeof document !== "undefined";
|
|
926
1047
|
const progress = useAnimation(900);
|
|
927
|
-
const artHeight = compact ? 1 : narrow ? ART_HEIGHT * 2 : ART_HEIGHT;
|
|
1048
|
+
const artHeight = compact ? 1 : narrow && !mobile ? ART_HEIGHT * 2 : ART_HEIGHT;
|
|
928
1049
|
const dropOffset = Math.round((1 - progress) * -artHeight);
|
|
929
1050
|
const revealProgress = Math.max(0, Math.min(1, (progress - 0.1) / 0.7));
|
|
930
|
-
const maxWidth = compact ? 8 : narrow ?
|
|
1051
|
+
const maxWidth = compact ? 8 : narrow ? 35 : 69;
|
|
931
1052
|
const revealCol = Math.round(revealProgress * (maxWidth + 4)) - 2;
|
|
932
1053
|
const taglineOpacity = Math.max(0, Math.min(1, (progress - 0.7) / 0.3));
|
|
933
|
-
const subtitle = /* @__PURE__ */ jsxs17(
|
|
1054
|
+
const subtitle = /* @__PURE__ */ jsxs17(Fragment6, { children: [
|
|
934
1055
|
/* @__PURE__ */ jsx23("text", { children: " " }),
|
|
935
1056
|
/* @__PURE__ */ jsx23("box", { flexDirection: "column", alignItems: "center", width: "100%", shouldFill: false, children: /* @__PURE__ */ jsxs17("text", { style: textStyle({ fg: "#d4b0e8" }), opacity: taglineOpacity, wrapMode: "word", textAlign: "center", width: "100%", shouldFill: false, children: [
|
|
936
1057
|
"A framework for building terminal apps, built on ",
|
|
@@ -938,8 +1059,8 @@ function Logo({ compact, narrow, mobile }) {
|
|
|
938
1059
|
" + React." + (mobile ? " " : "\n") + "(Gridland apps, like this website, work in the browser and terminal.)"
|
|
939
1060
|
] }) })
|
|
940
1061
|
] });
|
|
941
|
-
if (!
|
|
942
|
-
const art = compact ? "gridland" : narrow ? gridArt + "\n" + landArt : fullArt;
|
|
1062
|
+
if (!isBrowser2) {
|
|
1063
|
+
const art = compact ? "gridland" : narrow && !mobile ? gridArt + "\n" + landArt : fullArt;
|
|
943
1064
|
return /* @__PURE__ */ jsxs17("box", { flexDirection: "column", flexShrink: 0, width: "100%", alignItems: "center", shouldFill: false, children: [
|
|
944
1065
|
/* @__PURE__ */ jsx23(Gradient, { name: "instagram", children: art }),
|
|
945
1066
|
/* @__PURE__ */ jsx23("text", { children: " " }),
|
|
@@ -956,7 +1077,7 @@ function Logo({ compact, narrow, mobile }) {
|
|
|
956
1077
|
subtitle
|
|
957
1078
|
] });
|
|
958
1079
|
}
|
|
959
|
-
if (narrow) {
|
|
1080
|
+
if (narrow && !mobile) {
|
|
960
1081
|
return /* @__PURE__ */ jsxs17("box", { flexDirection: "column", flexShrink: 0, width: "100%", shouldFill: false, children: [
|
|
961
1082
|
/* @__PURE__ */ jsx23("box", { height: artHeight, overflow: "hidden", position: "relative", width: "100%", flexShrink: 0, shouldFill: false, children: /* @__PURE__ */ jsxs17("box", { position: "absolute", top: dropOffset, width: "100%", flexDirection: "column", alignItems: "center", shouldFill: false, children: [
|
|
962
1083
|
/* @__PURE__ */ jsx23(RevealGradient, { revealCol, children: gridArt }),
|
|
@@ -975,7 +1096,7 @@ function Logo({ compact, narrow, mobile }) {
|
|
|
975
1096
|
import { useMemo as useMemo6 } from "react";
|
|
976
1097
|
|
|
977
1098
|
// src/landing/use-matrix.ts
|
|
978
|
-
import { useState as useState9, useEffect as useEffect5, useRef as useRef6 } from "react";
|
|
1099
|
+
import { useState as useState9, useEffect as useEffect5, useLayoutEffect, useRef as useRef6 } from "react";
|
|
979
1100
|
var CHARS = "abcdefghijklmnopqrstuvwxyz0123456789@#$%^&*(){}[]|;:<>,.?/~`";
|
|
980
1101
|
function randomChar() {
|
|
981
1102
|
return CHARS[Math.floor(Math.random() * CHARS.length)];
|
|
@@ -989,7 +1110,11 @@ function createDrop(height, seeded = false) {
|
|
|
989
1110
|
chars: Array.from({ length }, randomChar)
|
|
990
1111
|
};
|
|
991
1112
|
}
|
|
992
|
-
|
|
1113
|
+
var PULL_RADIUS = 18;
|
|
1114
|
+
var PULL_STRENGTH = 7;
|
|
1115
|
+
var RIPPLE_DURATION_MS = 3200;
|
|
1116
|
+
var RIPPLE_SPEED = 8e-3;
|
|
1117
|
+
function buildGrid(columns, width, height, mousePos, ripples, now = Date.now()) {
|
|
993
1118
|
const grid = Array.from({ length: height }, () => Array(width).fill(" "));
|
|
994
1119
|
const brightness = Array.from({ length: height }, () => Array(width).fill(0));
|
|
995
1120
|
for (let x = 0; x < width; x++) {
|
|
@@ -998,17 +1123,53 @@ function buildGrid(columns, width, height) {
|
|
|
998
1123
|
for (let i = 0; i < drop.length; i++) {
|
|
999
1124
|
const row = Math.floor(drop.y) - i;
|
|
1000
1125
|
if (row < 0 || row >= height) continue;
|
|
1001
|
-
|
|
1002
|
-
if (
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1126
|
+
let renderX = x;
|
|
1127
|
+
if (mousePos) {
|
|
1128
|
+
const dx = mousePos.x - x;
|
|
1129
|
+
const dy = mousePos.y - row;
|
|
1130
|
+
const dist = Math.sqrt(dx * dx + dy * dy);
|
|
1131
|
+
if (dist < PULL_RADIUS && dist > 0.5) {
|
|
1132
|
+
const t = 1 - dist / PULL_RADIUS;
|
|
1133
|
+
const strength = t * t * PULL_STRENGTH;
|
|
1134
|
+
renderX = Math.round(x + dx / dist * strength);
|
|
1135
|
+
renderX = Math.max(0, Math.min(width - 1, renderX));
|
|
1136
|
+
}
|
|
1137
|
+
}
|
|
1138
|
+
const b = i === 0 ? 1 : Math.max(0.15, 1 - i / drop.length);
|
|
1139
|
+
if (brightness[row][renderX] < b) {
|
|
1140
|
+
grid[row][renderX] = drop.chars[i];
|
|
1141
|
+
brightness[row][renderX] = b;
|
|
1142
|
+
}
|
|
1143
|
+
}
|
|
1144
|
+
}
|
|
1145
|
+
for (const ripple of ripples) {
|
|
1146
|
+
const elapsed = now - ripple.createdAt;
|
|
1147
|
+
if (elapsed > RIPPLE_DURATION_MS || elapsed < 0) continue;
|
|
1148
|
+
const radius = elapsed * RIPPLE_SPEED;
|
|
1149
|
+
const fade = 1 - elapsed / RIPPLE_DURATION_MS;
|
|
1150
|
+
const maxR = Math.ceil(radius) + 2;
|
|
1151
|
+
const rx = Math.round(ripple.x);
|
|
1152
|
+
const ry = Math.round(ripple.y);
|
|
1153
|
+
for (let dy = -maxR; dy <= maxR; dy++) {
|
|
1154
|
+
for (let dx = -maxR; dx <= maxR; dx++) {
|
|
1155
|
+
const cy = ry + dy;
|
|
1156
|
+
const cx = rx + dx;
|
|
1157
|
+
if (cy < 0 || cy >= height || cx < 0 || cx >= width) continue;
|
|
1158
|
+
const dist = Math.sqrt(dx * dx + dy * dy);
|
|
1159
|
+
const ringDist = Math.abs(dist - radius);
|
|
1160
|
+
if (ringDist < 2) {
|
|
1161
|
+
const boost = (1 - ringDist / 2) * fade * 0.7;
|
|
1162
|
+
brightness[cy][cx] = Math.min(1, brightness[cy][cx] + boost);
|
|
1163
|
+
if (grid[cy][cx] === " " && boost > 0.2) {
|
|
1164
|
+
grid[cy][cx] = randomChar();
|
|
1165
|
+
}
|
|
1166
|
+
}
|
|
1006
1167
|
}
|
|
1007
1168
|
}
|
|
1008
1169
|
}
|
|
1009
1170
|
return { grid, brightness };
|
|
1010
1171
|
}
|
|
1011
|
-
function useMatrix(width, height) {
|
|
1172
|
+
function useMatrix(width, height, mousePosRef, ripplesRef) {
|
|
1012
1173
|
const columnsRef = useRef6([]);
|
|
1013
1174
|
const [state, setState] = useState9(() => {
|
|
1014
1175
|
const columns = Array.from(
|
|
@@ -1016,12 +1177,13 @@ function useMatrix(width, height) {
|
|
|
1016
1177
|
() => Math.random() < 0.5 ? createDrop(height, true) : null
|
|
1017
1178
|
);
|
|
1018
1179
|
columnsRef.current = columns;
|
|
1019
|
-
return buildGrid(columns, width, height);
|
|
1180
|
+
return buildGrid(columns, width, height, null, []);
|
|
1020
1181
|
});
|
|
1021
1182
|
useEffect5(() => {
|
|
1022
1183
|
if (width < 2 || height < 2) return;
|
|
1023
1184
|
const id = setInterval(() => {
|
|
1024
1185
|
const columns = columnsRef.current;
|
|
1186
|
+
const now = Date.now();
|
|
1025
1187
|
for (let x = 0; x < width; x++) {
|
|
1026
1188
|
if (columns[x] === null || columns[x] === void 0) {
|
|
1027
1189
|
if (Math.random() < 0.03) {
|
|
@@ -1039,16 +1201,23 @@ function useMatrix(width, height) {
|
|
|
1039
1201
|
columns[x] = null;
|
|
1040
1202
|
}
|
|
1041
1203
|
}
|
|
1042
|
-
|
|
1204
|
+
if (ripplesRef?.current) {
|
|
1205
|
+
ripplesRef.current = ripplesRef.current.filter(
|
|
1206
|
+
(r) => now - r.createdAt < RIPPLE_DURATION_MS
|
|
1207
|
+
);
|
|
1208
|
+
}
|
|
1209
|
+
const mousePos = mousePosRef?.current ?? null;
|
|
1210
|
+
const ripples = ripplesRef?.current ?? [];
|
|
1211
|
+
setState(buildGrid(columns, width, height, mousePos, ripples, now));
|
|
1043
1212
|
}, 80);
|
|
1044
1213
|
return () => clearInterval(id);
|
|
1045
1214
|
}, [width, height]);
|
|
1046
|
-
|
|
1215
|
+
useLayoutEffect(() => {
|
|
1047
1216
|
columnsRef.current = Array.from(
|
|
1048
1217
|
{ length: width },
|
|
1049
1218
|
() => Math.random() < 0.5 ? createDrop(height, true) : null
|
|
1050
1219
|
);
|
|
1051
|
-
setState(buildGrid(columnsRef.current, width, height));
|
|
1220
|
+
setState(buildGrid(columnsRef.current, width, height, null, []));
|
|
1052
1221
|
}, [width, height]);
|
|
1053
1222
|
return state;
|
|
1054
1223
|
}
|
|
@@ -1070,8 +1239,8 @@ function colorForCell(mutedColors, b) {
|
|
|
1070
1239
|
const idx = Math.min(Math.floor(b * (MUTE_LEVELS.length - 1)), MUTE_LEVELS.length - 2);
|
|
1071
1240
|
return mutedColors[idx];
|
|
1072
1241
|
}
|
|
1073
|
-
function MatrixBackground({ width, height, clearRect, clearRects }) {
|
|
1074
|
-
const { grid, brightness } = useMatrix(width, height);
|
|
1242
|
+
function MatrixBackground({ width, height, clearRect, clearRects, mousePosRef, ripplesRef }) {
|
|
1243
|
+
const { grid, brightness } = useMatrix(width, height, mousePosRef, ripplesRef);
|
|
1075
1244
|
const theme = useTheme();
|
|
1076
1245
|
const columnColors = useMemo6(
|
|
1077
1246
|
() => width > 0 ? generateGradient([theme.accent, theme.secondary, theme.primary], width) : [],
|
|
@@ -1102,48 +1271,495 @@ function MatrixBackground({ width, height, clearRect, clearRects }) {
|
|
|
1102
1271
|
}) }, y)) });
|
|
1103
1272
|
}
|
|
1104
1273
|
|
|
1105
|
-
//
|
|
1274
|
+
// demos/ripple.tsx
|
|
1275
|
+
import { useState as useState10, useEffect as useEffect6, useRef as useRef7, useCallback as useCallback3 } from "react";
|
|
1276
|
+
import { useKeyboard } from "@gridland/utils";
|
|
1106
1277
|
import { jsx as jsx25, jsxs as jsxs18 } from "react/jsx-runtime";
|
|
1107
|
-
|
|
1278
|
+
var DEFAULT_COLS = 40;
|
|
1279
|
+
var DEFAULT_ROWS = 10;
|
|
1280
|
+
var CHARS2 = ["\xB7", "\u2591", "\u2592", "\u2593", "\u2588"];
|
|
1281
|
+
function hexToRgb2(hex) {
|
|
1282
|
+
const h = hex.replace("#", "");
|
|
1283
|
+
return [
|
|
1284
|
+
parseInt(h.slice(0, 2), 16),
|
|
1285
|
+
parseInt(h.slice(2, 4), 16),
|
|
1286
|
+
parseInt(h.slice(4, 6), 16)
|
|
1287
|
+
];
|
|
1288
|
+
}
|
|
1289
|
+
function rgbToHex2(r, g, b) {
|
|
1290
|
+
const clamp = (v) => Math.max(0, Math.min(255, Math.round(v)));
|
|
1291
|
+
return "#" + clamp(r).toString(16).padStart(2, "0") + clamp(g).toString(16).padStart(2, "0") + clamp(b).toString(16).padStart(2, "0");
|
|
1292
|
+
}
|
|
1293
|
+
function lerp2(a, b, t) {
|
|
1294
|
+
return a + (b - a) * t;
|
|
1295
|
+
}
|
|
1296
|
+
function RippleApp({ mouseOffset = { x: 0, y: 0 }, containerWidth, containerHeight } = {}) {
|
|
1297
|
+
const theme = useTheme();
|
|
1298
|
+
const [, setTick] = useState10(0);
|
|
1299
|
+
const COLS2 = containerWidth ? containerWidth - 2 : DEFAULT_COLS;
|
|
1300
|
+
const ROWS2 = containerHeight ? Math.max(3, containerHeight - 2 - 2) : DEFAULT_ROWS;
|
|
1301
|
+
const cursorRef = useRef7({ x: Math.floor(COLS2 / 2), y: Math.floor(ROWS2 / 2) });
|
|
1302
|
+
const ripplesRef = useRef7([]);
|
|
1303
|
+
const frameRef = useRef7(0);
|
|
1304
|
+
const mousePosRef = useRef7(null);
|
|
1305
|
+
const accentRgb = hexToRgb2(theme.accent);
|
|
1306
|
+
const dimRgb = [40, 40, 50];
|
|
1307
|
+
const baseRgb = [60, 60, 70];
|
|
1308
|
+
const addRipple = useCallback3((x, y) => {
|
|
1309
|
+
ripplesRef.current = [
|
|
1310
|
+
...ripplesRef.current,
|
|
1311
|
+
{ x, y, time: frameRef.current }
|
|
1312
|
+
];
|
|
1313
|
+
}, []);
|
|
1314
|
+
useEffect6(() => {
|
|
1315
|
+
const interval = setInterval(() => {
|
|
1316
|
+
frameRef.current++;
|
|
1317
|
+
ripplesRef.current = ripplesRef.current.filter(
|
|
1318
|
+
(r) => frameRef.current - r.time < 30
|
|
1319
|
+
);
|
|
1320
|
+
setTick((t) => t + 1);
|
|
1321
|
+
}, 60);
|
|
1322
|
+
return () => clearInterval(interval);
|
|
1323
|
+
}, []);
|
|
1324
|
+
useKeyboard((event) => {
|
|
1325
|
+
const cursor2 = cursorRef.current;
|
|
1326
|
+
if (event.name === "up") {
|
|
1327
|
+
cursorRef.current = { ...cursor2, y: Math.max(0, cursor2.y - 1) };
|
|
1328
|
+
} else if (event.name === "down") {
|
|
1329
|
+
cursorRef.current = { ...cursor2, y: Math.min(ROWS2 - 1, cursor2.y + 1) };
|
|
1330
|
+
} else if (event.name === "left") {
|
|
1331
|
+
cursorRef.current = { ...cursor2, x: Math.max(0, cursor2.x - 1) };
|
|
1332
|
+
} else if (event.name === "right") {
|
|
1333
|
+
cursorRef.current = { ...cursor2, x: Math.min(COLS2 - 1, cursor2.x + 1) };
|
|
1334
|
+
} else if (event.name === "return") {
|
|
1335
|
+
addRipple(cursorRef.current.x, cursorRef.current.y);
|
|
1336
|
+
}
|
|
1337
|
+
event.preventDefault();
|
|
1338
|
+
});
|
|
1339
|
+
const cursor = cursorRef.current;
|
|
1340
|
+
const frame = frameRef.current;
|
|
1341
|
+
const ripples = ripplesRef.current;
|
|
1342
|
+
const grid = Array.from({ length: ROWS2 }, (_, row) => /* @__PURE__ */ jsx25("text", { children: Array.from({ length: COLS2 }, (_2, col) => {
|
|
1343
|
+
const isCursor = col === cursor.x && row === cursor.y;
|
|
1344
|
+
const baseWave = Math.sin(frame * 0.08 + col * 0.3 + row * 0.5) * 0.5 + 0.5;
|
|
1345
|
+
let intensity = baseWave * 0.15;
|
|
1346
|
+
for (const ripple of ripples) {
|
|
1347
|
+
const dx = col - ripple.x;
|
|
1348
|
+
const dy = row - ripple.y;
|
|
1349
|
+
const dist = Math.sqrt(dx * dx + dy * dy);
|
|
1350
|
+
const age = frame - ripple.time;
|
|
1351
|
+
const radius = age * 0.5;
|
|
1352
|
+
const fade = 1 - age / 30;
|
|
1353
|
+
const ringDist = Math.abs(dist - radius);
|
|
1354
|
+
if (ringDist < 1.5) {
|
|
1355
|
+
const ringIntensity = (1 - ringDist / 1.5) * fade;
|
|
1356
|
+
intensity = Math.max(intensity, ringIntensity);
|
|
1357
|
+
} else if (dist < radius) {
|
|
1358
|
+
const innerIntensity = fade * 0.3 * (1 - dist / radius);
|
|
1359
|
+
intensity = Math.max(intensity, innerIntensity);
|
|
1360
|
+
}
|
|
1361
|
+
}
|
|
1362
|
+
intensity = Math.max(0, Math.min(1, intensity));
|
|
1363
|
+
const charIndex = Math.min(
|
|
1364
|
+
CHARS2.length - 1,
|
|
1365
|
+
Math.floor(intensity * CHARS2.length)
|
|
1366
|
+
);
|
|
1367
|
+
const char = isCursor ? "\u25C6" : CHARS2[charIndex];
|
|
1368
|
+
let fg;
|
|
1369
|
+
if (isCursor) {
|
|
1370
|
+
fg = theme.primary;
|
|
1371
|
+
} else {
|
|
1372
|
+
const r = lerp2(dimRgb[0], accentRgb[0], intensity);
|
|
1373
|
+
const g = lerp2(dimRgb[1], accentRgb[1], intensity);
|
|
1374
|
+
const b = lerp2(dimRgb[2], accentRgb[2], intensity);
|
|
1375
|
+
fg = rgbToHex2(r, g, b);
|
|
1376
|
+
}
|
|
1377
|
+
return /* @__PURE__ */ jsx25("span", { style: { fg, bold: isCursor || intensity > 0.7 }, children: char }, col);
|
|
1378
|
+
}) }, row));
|
|
1379
|
+
return /* @__PURE__ */ jsxs18("box", { flexDirection: "column", flexGrow: 1, children: [
|
|
1380
|
+
/* @__PURE__ */ jsxs18(
|
|
1381
|
+
"box",
|
|
1382
|
+
{
|
|
1383
|
+
flexDirection: "column",
|
|
1384
|
+
flexGrow: 1,
|
|
1385
|
+
paddingX: 1,
|
|
1386
|
+
onMouseMove: (e) => {
|
|
1387
|
+
const gx = e.x - mouseOffset.x - 1;
|
|
1388
|
+
const gy = e.y - mouseOffset.y - 2;
|
|
1389
|
+
if (gx >= 0 && gx < COLS2 && gy >= 0 && gy < ROWS2) {
|
|
1390
|
+
mousePosRef.current = { x: gx, y: gy };
|
|
1391
|
+
cursorRef.current = { x: gx, y: gy };
|
|
1392
|
+
}
|
|
1393
|
+
},
|
|
1394
|
+
onMouseDown: (e) => {
|
|
1395
|
+
const gx = e.x - mouseOffset.x - 1;
|
|
1396
|
+
const gy = e.y - mouseOffset.y - 2;
|
|
1397
|
+
if (gx >= 0 && gx < COLS2 && gy >= 0 && gy < ROWS2) {
|
|
1398
|
+
addRipple(gx, gy);
|
|
1399
|
+
}
|
|
1400
|
+
},
|
|
1401
|
+
children: [
|
|
1402
|
+
/* @__PURE__ */ jsx25("text", { style: { dim: true, fg: theme.muted }, children: "Click or press Enter to create ripples" }),
|
|
1403
|
+
/* @__PURE__ */ jsx25("box", { flexDirection: "column", children: grid })
|
|
1404
|
+
]
|
|
1405
|
+
}
|
|
1406
|
+
),
|
|
1407
|
+
/* @__PURE__ */ jsx25("box", { flexGrow: 1 }),
|
|
1408
|
+
/* @__PURE__ */ jsx25("box", { paddingX: 1, children: /* @__PURE__ */ jsx25(
|
|
1409
|
+
StatusBar,
|
|
1410
|
+
{
|
|
1411
|
+
items: [
|
|
1412
|
+
{ key: "\u2191\u2193\u2190\u2192", label: "move" },
|
|
1413
|
+
{ key: "enter/click", label: "ripple" }
|
|
1414
|
+
]
|
|
1415
|
+
}
|
|
1416
|
+
) })
|
|
1417
|
+
] });
|
|
1418
|
+
}
|
|
1419
|
+
|
|
1420
|
+
// demos/puzzle.tsx
|
|
1421
|
+
import { useState as useState11, useEffect as useEffect7, useRef as useRef8, useMemo as useMemo7 } from "react";
|
|
1422
|
+
import { useKeyboard as useKeyboard2 } from "@gridland/utils";
|
|
1423
|
+
import { jsx as jsx26, jsxs as jsxs19 } from "react/jsx-runtime";
|
|
1424
|
+
var COLS = 4;
|
|
1425
|
+
var ROWS = 3;
|
|
1426
|
+
var TILE_COUNT = COLS * ROWS;
|
|
1427
|
+
var DEFAULT_TILE_WIDTH = 8;
|
|
1428
|
+
var DEFAULT_TILE_HEIGHT = 3;
|
|
1429
|
+
var SOLVED = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 0];
|
|
1430
|
+
var tileColors = [
|
|
1431
|
+
"#ef4444",
|
|
1432
|
+
"#f97316",
|
|
1433
|
+
"#eab308",
|
|
1434
|
+
"#22c55e",
|
|
1435
|
+
"#3b82f6",
|
|
1436
|
+
"#8b5cf6",
|
|
1437
|
+
"#ec4899",
|
|
1438
|
+
"#14b8a6",
|
|
1439
|
+
"#f43f5e",
|
|
1440
|
+
"#6366f1",
|
|
1441
|
+
"#84cc16"
|
|
1442
|
+
];
|
|
1443
|
+
function isSolved(board) {
|
|
1444
|
+
for (let i = 0; i < TILE_COUNT; i++) {
|
|
1445
|
+
if (board[i] !== SOLVED[i]) return false;
|
|
1446
|
+
}
|
|
1447
|
+
return true;
|
|
1448
|
+
}
|
|
1449
|
+
function getEmptyIndex(board) {
|
|
1450
|
+
return board.indexOf(0);
|
|
1451
|
+
}
|
|
1452
|
+
function getNeighbor(emptyIdx, direction) {
|
|
1453
|
+
const row = Math.floor(emptyIdx / COLS);
|
|
1454
|
+
const col = emptyIdx % COLS;
|
|
1455
|
+
switch (direction) {
|
|
1456
|
+
case "up":
|
|
1457
|
+
return row > 0 ? emptyIdx - COLS : null;
|
|
1458
|
+
case "down":
|
|
1459
|
+
return row < ROWS - 1 ? emptyIdx + COLS : null;
|
|
1460
|
+
case "left":
|
|
1461
|
+
return col > 0 ? emptyIdx - 1 : null;
|
|
1462
|
+
case "right":
|
|
1463
|
+
return col < COLS - 1 ? emptyIdx + 1 : null;
|
|
1464
|
+
default:
|
|
1465
|
+
return null;
|
|
1466
|
+
}
|
|
1467
|
+
}
|
|
1468
|
+
function swap(board, a, b) {
|
|
1469
|
+
const next = [...board];
|
|
1470
|
+
next[a] = board[b];
|
|
1471
|
+
next[b] = board[a];
|
|
1472
|
+
return next;
|
|
1473
|
+
}
|
|
1474
|
+
function shuffle(board, count) {
|
|
1475
|
+
let current = [...board];
|
|
1476
|
+
const directions = ["up", "down", "left", "right"];
|
|
1477
|
+
let lastDir = "";
|
|
1478
|
+
for (let i = 0; i < count; i++) {
|
|
1479
|
+
const emptyIdx = getEmptyIndex(current);
|
|
1480
|
+
const validMoves = directions.filter((d) => {
|
|
1481
|
+
if (d === lastDir) return false;
|
|
1482
|
+
return getNeighbor(emptyIdx, d) !== null;
|
|
1483
|
+
});
|
|
1484
|
+
const dir = validMoves[Math.floor(Math.random() * validMoves.length)];
|
|
1485
|
+
const neighbor = getNeighbor(emptyIdx, dir);
|
|
1486
|
+
current = swap(current, emptyIdx, neighbor);
|
|
1487
|
+
const opposites = { up: "down", down: "up", left: "right", right: "left" };
|
|
1488
|
+
lastDir = opposites[dir];
|
|
1489
|
+
}
|
|
1490
|
+
return current;
|
|
1491
|
+
}
|
|
1492
|
+
function isAdjacentToEmpty(board, tileIdx) {
|
|
1493
|
+
const emptyIdx = getEmptyIndex(board);
|
|
1494
|
+
const eRow = Math.floor(emptyIdx / COLS);
|
|
1495
|
+
const eCol = emptyIdx % COLS;
|
|
1496
|
+
const tRow = Math.floor(tileIdx / COLS);
|
|
1497
|
+
const tCol = tileIdx % COLS;
|
|
1498
|
+
return Math.abs(eRow - tRow) === 1 && eCol === tCol || Math.abs(eCol - tCol) === 1 && eRow === tRow;
|
|
1499
|
+
}
|
|
1500
|
+
function PuzzleApp({ containerWidth, containerHeight } = {}) {
|
|
1501
|
+
const theme = useTheme();
|
|
1502
|
+
const TILE_WIDTH = containerWidth ? Math.floor((containerWidth - 2) / COLS) : DEFAULT_TILE_WIDTH;
|
|
1503
|
+
const TILE_HEIGHT = containerHeight ? Math.max(3, Math.floor((containerHeight - 2 - 4) / ROWS)) : DEFAULT_TILE_HEIGHT;
|
|
1504
|
+
const [board, setBoard] = useState11(SOLVED);
|
|
1505
|
+
const [moves, setMoves] = useState11(0);
|
|
1506
|
+
const [solved, setSolved] = useState11(false);
|
|
1507
|
+
const boardRef = useRef8(board);
|
|
1508
|
+
const movesRef = useRef8(moves);
|
|
1509
|
+
boardRef.current = board;
|
|
1510
|
+
movesRef.current = moves;
|
|
1511
|
+
const doMove = (direction) => {
|
|
1512
|
+
const current = boardRef.current;
|
|
1513
|
+
if (isSolved(current) && movesRef.current > 0) return;
|
|
1514
|
+
const emptyIdx = getEmptyIndex(current);
|
|
1515
|
+
const neighbor = getNeighbor(emptyIdx, direction);
|
|
1516
|
+
if (neighbor === null) return;
|
|
1517
|
+
const next = swap(current, emptyIdx, neighbor);
|
|
1518
|
+
boardRef.current = next;
|
|
1519
|
+
movesRef.current++;
|
|
1520
|
+
setBoard(next);
|
|
1521
|
+
setMoves(movesRef.current);
|
|
1522
|
+
setSolved(isSolved(next));
|
|
1523
|
+
};
|
|
1524
|
+
const doShuffle = () => {
|
|
1525
|
+
const shuffled = shuffle(SOLVED, 30);
|
|
1526
|
+
boardRef.current = shuffled;
|
|
1527
|
+
movesRef.current = 0;
|
|
1528
|
+
setBoard(shuffled);
|
|
1529
|
+
setMoves(0);
|
|
1530
|
+
setSolved(false);
|
|
1531
|
+
};
|
|
1532
|
+
useEffect7(() => {
|
|
1533
|
+
doShuffle();
|
|
1534
|
+
}, []);
|
|
1535
|
+
useKeyboard2((event) => {
|
|
1536
|
+
if (event.name === "up") {
|
|
1537
|
+
doMove("down");
|
|
1538
|
+
event.preventDefault();
|
|
1539
|
+
} else if (event.name === "down") {
|
|
1540
|
+
doMove("up");
|
|
1541
|
+
event.preventDefault();
|
|
1542
|
+
} else if (event.name === "left") {
|
|
1543
|
+
doMove("right");
|
|
1544
|
+
event.preventDefault();
|
|
1545
|
+
} else if (event.name === "right") {
|
|
1546
|
+
doMove("left");
|
|
1547
|
+
event.preventDefault();
|
|
1548
|
+
} else if (event.key === "r") {
|
|
1549
|
+
doShuffle();
|
|
1550
|
+
event.preventDefault();
|
|
1551
|
+
}
|
|
1552
|
+
});
|
|
1553
|
+
const rows = useMemo7(() => {
|
|
1554
|
+
const result = [];
|
|
1555
|
+
for (let r = 0; r < ROWS; r++) {
|
|
1556
|
+
result.push(board.slice(r * COLS, r * COLS + COLS));
|
|
1557
|
+
}
|
|
1558
|
+
return result;
|
|
1559
|
+
}, [board]);
|
|
1560
|
+
return /* @__PURE__ */ jsxs19("box", { flexDirection: "column", flexGrow: 1, paddingX: 1, children: [
|
|
1561
|
+
/* @__PURE__ */ jsx26("text", { style: textStyle({ dim: true, fg: theme.muted }), children: "Slide tiles to solve the puzzle" }),
|
|
1562
|
+
/* @__PURE__ */ jsx26("box", { flexDirection: "column", children: rows.map((row, rowIdx) => /* @__PURE__ */ jsx26("box", { flexDirection: "row", children: row.map((tile, colIdx) => {
|
|
1563
|
+
const idx = rowIdx * COLS + colIdx;
|
|
1564
|
+
if (tile === 0) {
|
|
1565
|
+
return /* @__PURE__ */ jsx26("box", { width: TILE_WIDTH, height: TILE_HEIGHT }, idx);
|
|
1566
|
+
}
|
|
1567
|
+
const color = tileColors[tile - 1];
|
|
1568
|
+
return /* @__PURE__ */ jsx26(
|
|
1569
|
+
"box",
|
|
1570
|
+
{
|
|
1571
|
+
width: TILE_WIDTH,
|
|
1572
|
+
height: TILE_HEIGHT,
|
|
1573
|
+
border: true,
|
|
1574
|
+
borderStyle: "rounded",
|
|
1575
|
+
borderColor: solved ? theme.success : color,
|
|
1576
|
+
onMouseDown: () => {
|
|
1577
|
+
if (isAdjacentToEmpty(boardRef.current, idx)) {
|
|
1578
|
+
const emptyIdx = getEmptyIndex(boardRef.current);
|
|
1579
|
+
const next = swap(boardRef.current, emptyIdx, idx);
|
|
1580
|
+
boardRef.current = next;
|
|
1581
|
+
movesRef.current++;
|
|
1582
|
+
setBoard(next);
|
|
1583
|
+
setMoves(movesRef.current);
|
|
1584
|
+
setSolved(isSolved(next));
|
|
1585
|
+
}
|
|
1586
|
+
},
|
|
1587
|
+
children: /* @__PURE__ */ jsx26("text", { style: textStyle({
|
|
1588
|
+
fg: solved ? theme.success : color,
|
|
1589
|
+
bold: true
|
|
1590
|
+
}), children: String(tile).padStart(2) })
|
|
1591
|
+
},
|
|
1592
|
+
idx
|
|
1593
|
+
);
|
|
1594
|
+
}) }, rowIdx)) }),
|
|
1595
|
+
/* @__PURE__ */ jsx26("box", { height: 1 }),
|
|
1596
|
+
/* @__PURE__ */ jsxs19("box", { flexDirection: "row", gap: 2, children: [
|
|
1597
|
+
/* @__PURE__ */ jsxs19("text", { style: textStyle({ dim: true, fg: theme.muted }), children: [
|
|
1598
|
+
"Moves: ",
|
|
1599
|
+
moves
|
|
1600
|
+
] }),
|
|
1601
|
+
solved && moves > 0 && /* @__PURE__ */ jsx26("text", { style: textStyle({ fg: theme.success, bold: true }), children: "Solved!" })
|
|
1602
|
+
] }),
|
|
1603
|
+
/* @__PURE__ */ jsx26("box", { flexGrow: 1 }),
|
|
1604
|
+
/* @__PURE__ */ jsx26("box", { paddingX: 1, children: /* @__PURE__ */ jsx26(StatusBar, { items: [
|
|
1605
|
+
{ key: "\u2191\u2193\u2190\u2192", label: "slide" },
|
|
1606
|
+
{ key: "click", label: "slide tile" },
|
|
1607
|
+
{ key: "r", label: "shuffle" }
|
|
1608
|
+
] }) })
|
|
1609
|
+
] });
|
|
1610
|
+
}
|
|
1611
|
+
|
|
1612
|
+
// src/landing/landing-app.tsx
|
|
1613
|
+
import { Fragment as Fragment7, jsx as jsx27, jsxs as jsxs20 } from "react/jsx-runtime";
|
|
1614
|
+
var DEMOS = ["ripple", "puzzle"];
|
|
1615
|
+
var TAB_HEIGHT = 2;
|
|
1616
|
+
var TAB_WIDTHS = DEMOS.map((n) => n.length + 4);
|
|
1617
|
+
var TAB_POSITIONS = [];
|
|
1618
|
+
var _pos = 0;
|
|
1619
|
+
for (const w of TAB_WIDTHS) {
|
|
1620
|
+
TAB_POSITIONS.push(_pos);
|
|
1621
|
+
_pos += w;
|
|
1622
|
+
}
|
|
1623
|
+
function LandingApp({ useKeyboard: useKeyboard3 }) {
|
|
1108
1624
|
const theme = useTheme();
|
|
1109
1625
|
const { width, height, isNarrow, isTiny, isMobile } = useBreakpoints();
|
|
1110
|
-
const
|
|
1111
|
-
const
|
|
1112
|
-
|
|
1113
|
-
|
|
1626
|
+
const [activeIndex, setActiveIndex] = useState12(0);
|
|
1627
|
+
const mousePosRef = useRef9(null);
|
|
1628
|
+
const matrixRipplesRef = useRef9([]);
|
|
1629
|
+
useKeyboard3((event) => {
|
|
1630
|
+
if (event.name === "tab") {
|
|
1631
|
+
setActiveIndex((prev) => (prev + 1) % DEMOS.length);
|
|
1632
|
+
event.preventDefault();
|
|
1633
|
+
}
|
|
1634
|
+
});
|
|
1635
|
+
const isBrowser2 = typeof document !== "undefined";
|
|
1636
|
+
const { clearRect, installLinksClearRect, boxTop } = useMemo8(() => {
|
|
1637
|
+
const logoHeight = isTiny ? 2 : isMobile ? 7 : isNarrow ? 13 : 7;
|
|
1638
|
+
const logoExtra = isBrowser2 ? 1 : 0;
|
|
1114
1639
|
const gap = isMobile ? 0 : 1;
|
|
1115
|
-
const
|
|
1640
|
+
const paddingTop = isMobile ? 1 : 3;
|
|
1641
|
+
const installLinksTop = paddingTop + logoHeight + logoExtra + gap;
|
|
1116
1642
|
const installLinksHeight = 3;
|
|
1117
|
-
const
|
|
1118
|
-
const bh = height -
|
|
1643
|
+
const boxTop2 = installLinksTop + installLinksHeight + gap + 1;
|
|
1644
|
+
const bh = height - boxTop2 - 1;
|
|
1645
|
+
const bw = Math.min(82, width - 2);
|
|
1646
|
+
const bl = Math.floor((width - bw) / 2);
|
|
1119
1647
|
return {
|
|
1120
|
-
clearRect: { top:
|
|
1121
|
-
installLinksClearRect: { top: installLinksTop, left: 1, width: width - 2, height: installLinksHeight }
|
|
1648
|
+
clearRect: { top: boxTop2, left: bl, width: bw, height: bh },
|
|
1649
|
+
installLinksClearRect: { top: installLinksTop, left: 1, width: width - 2, height: installLinksHeight },
|
|
1650
|
+
boxTop: boxTop2
|
|
1122
1651
|
};
|
|
1123
|
-
}, [width, height, isTiny, isNarrow, isMobile,
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1652
|
+
}, [width, height, isTiny, isNarrow, isMobile, isBrowser2]);
|
|
1653
|
+
const MAX_BOX_WIDTH = 82;
|
|
1654
|
+
const availableWidth = width - 2;
|
|
1655
|
+
const boxWidth = Math.min(MAX_BOX_WIDTH, availableWidth);
|
|
1656
|
+
const boxLeft = Math.floor((width - boxWidth) / 2);
|
|
1657
|
+
const mouseOffset = useMemo8(() => ({
|
|
1658
|
+
x: boxLeft + 1,
|
|
1659
|
+
// box left edge + border(1)
|
|
1660
|
+
y: boxTop + TAB_HEIGHT + 1
|
|
1661
|
+
// boxTop + tab rows + box top border(1)
|
|
1662
|
+
}), [boxTop, boxLeft]);
|
|
1663
|
+
const containerWidth = boxWidth - 2;
|
|
1664
|
+
const maxBoxHeight = 20;
|
|
1665
|
+
const containerHeight = Math.min(height - boxTop - 1 - TAB_HEIGHT, maxBoxHeight - TAB_HEIGHT);
|
|
1666
|
+
const activeStart = TAB_POSITIONS[activeIndex];
|
|
1667
|
+
const activeWidth = TAB_WIDTHS[activeIndex];
|
|
1668
|
+
const activeEnd = activeStart + activeWidth;
|
|
1669
|
+
const connectParts = [];
|
|
1670
|
+
if (activeStart === 0) {
|
|
1671
|
+
connectParts.push(/* @__PURE__ */ jsx27("span", { style: textStyle({ fg: theme.border }), children: "\u2502" }, "cl"));
|
|
1672
|
+
connectParts.push(/* @__PURE__ */ jsx27("span", { style: textStyle({ fg: theme.border }), children: " ".repeat(activeWidth - 2) }, "gap"));
|
|
1673
|
+
connectParts.push(/* @__PURE__ */ jsx27("span", { style: textStyle({ fg: theme.border }), children: "\u2570" }, "cr"));
|
|
1674
|
+
} else {
|
|
1675
|
+
connectParts.push(
|
|
1676
|
+
/* @__PURE__ */ jsx27("span", { style: textStyle({ fg: theme.border }), children: "\u256D" + "\u2500".repeat(activeStart - 1) }, "left")
|
|
1677
|
+
);
|
|
1678
|
+
connectParts.push(/* @__PURE__ */ jsx27("span", { style: textStyle({ fg: theme.border }), children: "\u256F" }, "cl"));
|
|
1679
|
+
connectParts.push(/* @__PURE__ */ jsx27("span", { style: textStyle({ fg: theme.border }), children: " ".repeat(activeWidth - 2) }, "gap"));
|
|
1680
|
+
connectParts.push(/* @__PURE__ */ jsx27("span", { style: textStyle({ fg: theme.border }), children: "\u2570" }, "cr"));
|
|
1681
|
+
}
|
|
1682
|
+
const rightFill = boxWidth - activeEnd - 1;
|
|
1683
|
+
if (rightFill > 0) {
|
|
1684
|
+
connectParts.push(
|
|
1685
|
+
/* @__PURE__ */ jsx27("span", { style: textStyle({ fg: theme.border }), children: "\u2500".repeat(rightFill) }, "right")
|
|
1686
|
+
);
|
|
1687
|
+
}
|
|
1688
|
+
connectParts.push(/* @__PURE__ */ jsx27("span", { style: textStyle({ fg: theme.border }), children: "\u256E" }, "corner-r"));
|
|
1689
|
+
return /* @__PURE__ */ jsxs20("box", { width: "100%", height: "100%", position: "relative", children: [
|
|
1690
|
+
/* @__PURE__ */ jsx27(MatrixBackground, { width, height, clearRect, clearRects: isBrowser2 ? void 0 : [installLinksClearRect], mousePosRef, ripplesRef: matrixRipplesRef }),
|
|
1691
|
+
/* @__PURE__ */ jsx27(
|
|
1692
|
+
"box",
|
|
1693
|
+
{
|
|
1694
|
+
position: "absolute",
|
|
1695
|
+
top: 0,
|
|
1696
|
+
left: 0,
|
|
1697
|
+
width,
|
|
1698
|
+
height,
|
|
1699
|
+
zIndex: 1,
|
|
1700
|
+
flexDirection: "column",
|
|
1701
|
+
shouldFill: false,
|
|
1702
|
+
onMouseMove: (e) => {
|
|
1703
|
+
mousePosRef.current = { x: e.x, y: e.y };
|
|
1704
|
+
},
|
|
1705
|
+
onMouseDown: (e) => {
|
|
1706
|
+
matrixRipplesRef.current = [
|
|
1707
|
+
...matrixRipplesRef.current,
|
|
1708
|
+
{ x: e.x, y: e.y, createdAt: Date.now() }
|
|
1709
|
+
];
|
|
1710
|
+
},
|
|
1711
|
+
children: /* @__PURE__ */ jsxs20("box", { flexGrow: 1, flexDirection: "column", paddingTop: isMobile ? 1 : 3, paddingLeft: 1, paddingRight: 1, paddingBottom: 1, gap: isMobile ? 0 : 1, shouldFill: false, children: [
|
|
1712
|
+
/* @__PURE__ */ jsx27("box", { flexShrink: 0, shouldFill: false, children: /* @__PURE__ */ jsx27(Logo, { compact: isTiny, narrow: isNarrow, mobile: isMobile }) }),
|
|
1713
|
+
/* @__PURE__ */ jsxs20("box", { flexDirection: "row", flexWrap: "wrap", justifyContent: "center", gap: isMobile ? 0 : 1, flexShrink: 0, shouldFill: false, children: [
|
|
1714
|
+
!isMobile && /* @__PURE__ */ jsx27("box", { border: true, borderStyle: "rounded", borderColor: theme.border, paddingX: 1, flexDirection: "column", flexShrink: 0, children: /* @__PURE__ */ jsxs20("text", { children: [
|
|
1715
|
+
/* @__PURE__ */ jsx27("span", { style: textStyle({ dim: true }), children: "$ " }),
|
|
1716
|
+
/* @__PURE__ */ jsx27("span", { style: textStyle({ bold: true }), children: "bunx " }),
|
|
1717
|
+
/* @__PURE__ */ jsx27("span", { style: textStyle({ fg: theme.accent }), children: "@gridland/demo landing" })
|
|
1718
|
+
] }) }),
|
|
1719
|
+
!isMobile && /* @__PURE__ */ jsx27(InstallBox, {}),
|
|
1720
|
+
/* @__PURE__ */ jsx27(LinksBox, {})
|
|
1721
|
+
] }),
|
|
1722
|
+
/* @__PURE__ */ jsxs20("box", { flexDirection: "column", width: boxWidth, maxWidth: MAX_BOX_WIDTH, maxHeight: 20, alignSelf: "center", flexGrow: 1, children: [
|
|
1723
|
+
/* @__PURE__ */ jsx27("box", { height: 1, flexShrink: 0, flexDirection: "row", shouldFill: false, children: DEMOS.map((name, i) => {
|
|
1724
|
+
const isActive = i === activeIndex;
|
|
1725
|
+
const w = TAB_WIDTHS[i];
|
|
1726
|
+
return /* @__PURE__ */ jsx27("box", { width: w, onMouseDown: () => setActiveIndex(i), children: /* @__PURE__ */ jsx27("text", { style: textStyle({ fg: theme.border }), children: isActive ? "\u256D" + "\u2500".repeat(w - 2) + "\u256E" : " ".repeat(w) }) }, name);
|
|
1727
|
+
}) }),
|
|
1728
|
+
/* @__PURE__ */ jsx27("box", { height: 1, flexShrink: 0, flexDirection: "row", shouldFill: false, children: DEMOS.map((name, i) => {
|
|
1729
|
+
const isActive = i === activeIndex;
|
|
1730
|
+
const w = TAB_WIDTHS[i];
|
|
1731
|
+
return /* @__PURE__ */ jsx27("box", { width: w, onMouseDown: () => setActiveIndex(i), children: /* @__PURE__ */ jsx27("text", { children: isActive ? /* @__PURE__ */ jsxs20(Fragment7, { children: [
|
|
1732
|
+
/* @__PURE__ */ jsx27("span", { style: textStyle({ fg: theme.border }), children: "\u2502" }),
|
|
1733
|
+
/* @__PURE__ */ jsx27("span", { style: textStyle({ bold: true, fg: theme.foreground }), children: ` ${name} ` }),
|
|
1734
|
+
/* @__PURE__ */ jsx27("span", { style: textStyle({ fg: theme.border }), children: "\u2502" })
|
|
1735
|
+
] }) : /* @__PURE__ */ jsx27("span", { style: textStyle({ fg: theme.muted }), children: ` ${name} ` }) }) }, name);
|
|
1736
|
+
}) }),
|
|
1737
|
+
/* @__PURE__ */ jsxs20("box", { position: "relative", flexGrow: 1, children: [
|
|
1738
|
+
/* @__PURE__ */ jsx27("box", { position: "absolute", top: 0, left: 0, width: boxWidth, height: 1, zIndex: 2, children: /* @__PURE__ */ jsx27("text", { children: connectParts }) }),
|
|
1739
|
+
/* @__PURE__ */ jsxs20("box", { border: true, borderStyle: "rounded", borderColor: theme.border, flexGrow: 1, flexDirection: "column", overflow: "hidden", children: [
|
|
1740
|
+
activeIndex === 0 && /* @__PURE__ */ jsx27(RippleApp, { mouseOffset, containerWidth, containerHeight }),
|
|
1741
|
+
activeIndex === 1 && /* @__PURE__ */ jsx27(PuzzleApp, { containerWidth, containerHeight })
|
|
1742
|
+
] })
|
|
1743
|
+
] }),
|
|
1744
|
+
/* @__PURE__ */ jsx27("box", { height: 1 }),
|
|
1745
|
+
/* @__PURE__ */ jsx27("box", { width: "100%", alignItems: "center", flexDirection: "column", shouldFill: false, children: /* @__PURE__ */ jsxs20("text", { style: textStyle({ dim: true, fg: theme.muted }), children: [
|
|
1746
|
+
"Made with \u2764\uFE0F by ",
|
|
1747
|
+
/* @__PURE__ */ jsx27("a", { href: "https://cjroth.com", style: { attributes: 1 << 3, fg: theme.muted }, children: "Chris Roth" }),
|
|
1748
|
+
" + ",
|
|
1749
|
+
/* @__PURE__ */ jsx27("a", { href: "https://jessicacheng.studio", style: { attributes: 1 << 3, fg: theme.muted }, children: "Jessica Cheng" })
|
|
1750
|
+
] }) })
|
|
1751
|
+
] })
|
|
1752
|
+
] })
|
|
1753
|
+
}
|
|
1754
|
+
)
|
|
1139
1755
|
] });
|
|
1140
1756
|
}
|
|
1141
1757
|
|
|
1142
1758
|
// src/landing/matrix-rain.tsx
|
|
1143
|
-
import { jsx as
|
|
1759
|
+
import { jsx as jsx28 } from "react/jsx-runtime";
|
|
1144
1760
|
|
|
1145
1761
|
// src/landing/about-modal.tsx
|
|
1146
|
-
import { jsx as
|
|
1762
|
+
import { jsx as jsx29, jsxs as jsxs21 } from "react/jsx-runtime";
|
|
1147
1763
|
export {
|
|
1148
1764
|
LandingApp
|
|
1149
1765
|
};
|