ecklf 1.0.0 → 1.0.1

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/App.js CHANGED
@@ -79,7 +79,7 @@ export const App = () => {
79
79
  const d = data;
80
80
  const projects = d.projects;
81
81
  const current = projects[selected];
82
- return (_jsxs(Box, { flexDirection: "column", paddingX: 2, paddingY: 1, children: [_jsxs(Box, { flexDirection: "row", children: [_jsxs(Box, { flexDirection: "column", flexGrow: 1, justifyContent: "center", children: [_jsx(Box, { children: _jsx(Greeting, {}) }), _jsx(Text, { bold: true, color: "whiteBright", children: d.name }), _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsx(Text, { color: "gray", children: d.tagline }), _jsx(Text, { color: "gray", children: d.subtitle })] })] }), _jsx(Box, { width: 28, height: 14, justifyContent: "flex-end", children: _jsx(Knot, { cols: 26, rows: 14 }) })] }), _jsxs(Box, { marginTop: 1, marginBottom: 1, children: [_jsx(Text, { bold: true, color: "whiteBright", children: "Projects" }), _jsxs(Text, { color: "gray", children: [" ", d.source === "live" ? "● live" : "○ offline"] })] }), _jsx(Box, { flexDirection: "column", children: projects.map((p, i) => {
82
+ return (_jsxs(Box, { flexDirection: "column", paddingX: 2, paddingY: 1, children: [_jsxs(Box, { flexDirection: "row", children: [_jsxs(Box, { flexDirection: "column", flexGrow: 1, children: [_jsx(Box, { height: 1, children: _jsx(Greeting, {}) }), _jsx(Text, { bold: true, color: "whiteBright", children: d.name }), _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsx(Text, { color: "gray", children: d.tagline }), _jsx(Text, { color: "gray", children: d.subtitle })] })] }), _jsx(Box, { width: 28, height: 14, justifyContent: "flex-end", children: _jsx(Knot, { cols: 26, rows: 14 }) })] }), _jsxs(Box, { marginTop: 1, marginBottom: 1, children: [_jsx(Text, { bold: true, color: "whiteBright", children: "Projects" }), _jsxs(Text, { color: "gray", children: [" ", d.source === "live" ? "● live" : "○ offline"] })] }), _jsx(Box, { flexDirection: "column", children: projects.map((p, i) => {
83
83
  const active = i === selected;
84
84
  return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Box, { children: [_jsx(Text, { color: active ? ACCENT : "gray", children: active ? "❯ " : " " }), _jsx(Text, { bold: true, color: active ? "whiteBright" : "white", children: p.title })] }), active && (_jsx(Box, { marginLeft: 2, children: _jsx(Text, { color: "gray", dimColor: true, children: p.description }) }))] }, p.github));
85
85
  }) }), mode === "action" && (_jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsxs(Text, { color: "gray", children: ["Open ", _jsx(Text, { color: "whiteBright", children: current.title }), " \u2192"] }), _jsxs(Box, { marginTop: 0, children: [_jsx(Text, { color: actionChoice === 0 ? "black" : ACCENT, backgroundColor: actionChoice === 0 ? ACCENT : undefined, children: " Website " }), _jsx(Text, { children: " " }), _jsx(Text, { color: actionChoice === 1 ? "black" : ACCENT, backgroundColor: actionChoice === 1 ? ACCENT : undefined, children: " GitHub " })] })] })), opened && mode === "list" && (_jsxs(Box, { marginTop: 1, children: [_jsx(Text, { color: "greenBright", children: "\u2197 opened " }), _jsx(Text, { color: "gray", children: opened })] })), _jsx(Box, { marginTop: 1, children: mode === "list" ? (_jsx(Text, { color: "gray", dimColor: true, children: "\u2191/\u2193 navigate \u00B7 \u21B5 open\u2026 \u00B7 w website \u00B7 g github \u00B7 q quit" })) : (_jsx(Text, { color: "gray", dimColor: true, children: "\u2190/\u2192 choose \u00B7 \u21B5 confirm \u00B7 esc back" })) })] }));
package/dist/Greeting.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useEffect, useRef, useState } from "react";
3
2
  import { Text } from "ink";
3
+ import { useClock } from "./hooks.js";
4
4
  // Multi-language greetings, cycled with a typewriter effect — mirrors the
5
5
  // animated headline on ecklf.com.
6
6
  const GREETINGS = [
@@ -17,46 +17,45 @@ const GREETINGS = [
17
17
  "Hej",
18
18
  "Merhaba",
19
19
  ];
20
- export const Greeting = () => {
21
- const [index, setIndex] = useState(0);
22
- const [text, setText] = useState("");
23
- const [phase, setPhase] = useState("typing");
24
- const [cursorOn, setCursorOn] = useState(true);
25
- const timer = useRef();
26
- // Blinking cursor.
27
- useEffect(() => {
28
- const id = setInterval(() => setCursorOn((c) => !c), 450);
29
- return () => clearInterval(id);
30
- }, []);
31
- useEffect(() => {
32
- const word = GREETINGS[index];
33
- const schedule = (fn, ms) => {
34
- timer.current = setTimeout(fn, ms);
35
- };
36
- if (phase === "typing") {
37
- if (text.length < word.length) {
38
- schedule(() => setText(word.slice(0, text.length + 1)), 90);
39
- }
40
- else {
41
- setPhase("holding");
42
- }
43
- }
44
- else if (phase === "holding") {
45
- schedule(() => setPhase("deleting"), 1400);
46
- }
47
- else {
48
- if (text.length > 0) {
49
- schedule(() => setText(word.slice(0, text.length - 1)), 45);
20
+ const TYPE_MS = 110; // per character while typing
21
+ const DELETE_MS = 50; // per character while deleting
22
+ const HOLD_MS = 1300; // pause once fully typed
23
+ /**
24
+ * Derive the typewriter state purely from elapsed time. Driven by a single
25
+ * clock so there is no setTimeout/re-render race.
26
+ */
27
+ function compute(ms) {
28
+ let t = ms;
29
+ let i = 0;
30
+ // Walk through words until we land inside the current cycle.
31
+ // Guard against runaway loops by bounding iterations.
32
+ for (let guard = 0; guard < 100000; guard++) {
33
+ const word = GREETINGS[i % GREETINGS.length];
34
+ const typeDur = word.length * TYPE_MS;
35
+ const deleteDur = word.length * DELETE_MS;
36
+ const cycle = typeDur + HOLD_MS + deleteDur;
37
+ if (t < cycle) {
38
+ if (t < typeDur) {
39
+ const chars = Math.floor(t / TYPE_MS) + 1;
40
+ return { text: word.slice(0, Math.min(chars, word.length)), word };
50
41
  }
51
- else {
52
- setIndex((i) => (i + 1) % GREETINGS.length);
53
- setPhase("typing");
42
+ if (t < typeDur + HOLD_MS) {
43
+ return { text: word, word };
54
44
  }
45
+ const dt = t - typeDur - HOLD_MS;
46
+ const removed = Math.floor(dt / DELETE_MS) + 1;
47
+ const len = Math.max(0, word.length - removed);
48
+ return { text: word.slice(0, len), word };
55
49
  }
56
- return () => {
57
- if (timer.current)
58
- clearTimeout(timer.current);
59
- };
60
- }, [text, phase, index]);
50
+ t -= cycle;
51
+ i++;
52
+ }
53
+ return { text: "", word: GREETINGS[0] };
54
+ }
55
+ export const Greeting = () => {
56
+ const seconds = useClock(20);
57
+ const ms = seconds * 1000;
58
+ const { text } = compute(ms);
59
+ const cursorOn = Math.floor(seconds * 2) % 2 === 0;
61
60
  return (_jsxs(Text, { bold: true, color: "white", children: [text, _jsx(Text, { color: "cyanBright", children: cursorOn ? "▋" : " " })] }));
62
61
  };
package/dist/cli.js CHANGED
@@ -20,7 +20,7 @@ if (args.includes("--help") || args.includes("-h")) {
20
20
  process.exit(0);
21
21
  }
22
22
  if (args.includes("--version") || args.includes("-v")) {
23
- console.log("1.0.0");
23
+ console.log("1.0.1");
24
24
  process.exit(0);
25
25
  }
26
26
  if (!process.stdin.isTTY) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ecklf",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "description": "A terminal UI for ecklf.com — browse Florentin's projects and open them on the web or GitHub, with a spinning ASCII wireframe and typed greetings.",
5
5
  "keywords": [
6
6
  "ecklf",