@togo-framework/ui 0.1.6 → 0.1.7

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/index.d.ts CHANGED
@@ -4104,10 +4104,12 @@ interface TypingTerminalProps {
4104
4104
  typeMs?: number;
4105
4105
  /** ms between streamed output lines. */
4106
4106
  lineMs?: number;
4107
- /** Loop the playback (default true). */
4107
+ /** Loop the playback. Default false — plays once, then offers Replay. */
4108
4108
  loop?: boolean;
4109
+ /** Fixed body height (px). The window never grows/jumps while typing. */
4110
+ height?: number;
4109
4111
  }
4110
- declare function TypingTerminal({ steps, endSlot, title, className, typeMs, lineMs, loop }: TypingTerminalProps): React$1.JSX.Element;
4112
+ declare function TypingTerminal({ steps, endSlot, title, className, typeMs, lineMs, loop, height }: TypingTerminalProps): React$1.JSX.Element;
4111
4113
  declare namespace TypingTerminal {
4112
4114
  var displayName: string;
4113
4115
  }
package/dist/index.js CHANGED
@@ -16269,6 +16269,7 @@ CommandPalette.displayName = "CommandPalette";
16269
16269
 
16270
16270
  // src/components/marketing/Terminal.tsx
16271
16271
  import * as React32 from "react";
16272
+ import { RotateCcw as RotateCcw3 } from "lucide-react";
16272
16273
  import { jsx as jsx101, jsxs as jsxs89 } from "react/jsx-runtime";
16273
16274
  function fullFrame(steps) {
16274
16275
  const lines = [];
@@ -16284,16 +16285,25 @@ function fullFrame(steps) {
16284
16285
  return { lines, showEnd: true };
16285
16286
  }
16286
16287
  var keepStaticFrame = () => typeof window !== "undefined" && (window.matchMedia?.("(prefers-reduced-motion: reduce)").matches || navigator?.webdriver === true);
16287
- function TypingTerminal({ steps, endSlot, title = "~/myapp \u2014 togo", className, typeMs = 26, lineMs = 110, loop = true }) {
16288
+ function TypingTerminal({ steps, endSlot, title = "~/myapp \u2014 togo", className, typeMs = 26, lineMs = 110, loop = false, height = 360 }) {
16288
16289
  const [frame, setFrame] = React32.useState(() => fullFrame(steps));
16290
+ const [done, setDone] = React32.useState(false);
16291
+ const [runId, setRunId] = React32.useState(0);
16289
16292
  const scrollRef = React32.useRef(null);
16290
16293
  React32.useEffect(() => {
16291
- if (keepStaticFrame()) return;
16294
+ if (keepStaticFrame()) {
16295
+ setDone(true);
16296
+ return;
16297
+ }
16292
16298
  let alive = true;
16293
16299
  const timers = [];
16294
16300
  const wait = (ms) => new Promise((res) => timers.push(setTimeout(res, ms)));
16301
+ const toBottom = () => {
16302
+ if (scrollRef.current) scrollRef.current.scrollTop = scrollRef.current.scrollHeight;
16303
+ };
16295
16304
  async function run() {
16296
- while (alive) {
16305
+ setDone(false);
16306
+ do {
16297
16307
  const lines = [];
16298
16308
  setFrame({ lines: [], showEnd: false });
16299
16309
  for (let i = 0; i < steps.length && alive; i++) {
@@ -16308,6 +16318,7 @@ function TypingTerminal({ steps, endSlot, title = "~/myapp \u2014 togo", classNa
16308
16318
  ] }, `c${i}`)],
16309
16319
  showEnd: false
16310
16320
  });
16321
+ toBottom();
16311
16322
  await wait(typeMs);
16312
16323
  }
16313
16324
  lines.push(/* @__PURE__ */ jsxs89("div", { className: "whitespace-pre-wrap break-words", children: [
@@ -16318,31 +16329,47 @@ function TypingTerminal({ steps, endSlot, title = "~/myapp \u2014 togo", classNa
16318
16329
  for (let j = 0; j < (s.out?.length || 0) && alive; j++) {
16319
16330
  lines.push(/* @__PURE__ */ jsx101("div", { className: "whitespace-pre-wrap break-words text-muted-foreground", children: s.out[j] }, `o${i}-${j}`));
16320
16331
  setFrame({ lines: [...lines], showEnd: false });
16321
- if (scrollRef.current) scrollRef.current.scrollTop = scrollRef.current.scrollHeight;
16332
+ toBottom();
16322
16333
  await wait(lineMs);
16323
16334
  }
16324
16335
  }
16325
16336
  if (!alive) return;
16326
16337
  setFrame({ lines: [...lines], showEnd: true });
16327
- if (scrollRef.current) scrollRef.current.scrollTop = scrollRef.current.scrollHeight;
16328
- if (!loop) return;
16338
+ toBottom();
16339
+ if (!loop) {
16340
+ setDone(true);
16341
+ return;
16342
+ }
16329
16343
  await wait(4200);
16330
- }
16344
+ } while (alive);
16331
16345
  }
16332
16346
  run();
16333
16347
  return () => {
16334
16348
  alive = false;
16335
16349
  timers.forEach(clearTimeout);
16336
16350
  };
16337
- }, [steps, typeMs, lineMs, loop]);
16351
+ }, [steps, typeMs, lineMs, loop, runId]);
16338
16352
  return /* @__PURE__ */ jsxs89("div", { className: cn("rounded-2xl border border-border overflow-hidden bg-[#080b0f] shadow-2xl", className), children: [
16339
16353
  /* @__PURE__ */ jsxs89("div", { className: "flex items-center gap-2 px-4 py-3 border-b border-border", children: [
16340
16354
  /* @__PURE__ */ jsx101("span", { className: "w-3 h-3 rounded-full bg-[#ff5f57]" }),
16341
16355
  /* @__PURE__ */ jsx101("span", { className: "w-3 h-3 rounded-full bg-[#febc2e]" }),
16342
16356
  /* @__PURE__ */ jsx101("span", { className: "w-3 h-3 rounded-full bg-[#28c840]" }),
16343
- /* @__PURE__ */ jsx101("span", { className: "ms-2 font-mono text-xs text-muted-foreground", children: title })
16357
+ /* @__PURE__ */ jsx101("span", { className: "ms-2 font-mono text-xs text-muted-foreground", children: title }),
16358
+ done && !loop && /* @__PURE__ */ jsxs89(
16359
+ "button",
16360
+ {
16361
+ type: "button",
16362
+ onClick: () => setRunId((n) => n + 1),
16363
+ className: "ms-auto inline-flex items-center gap-1.5 rounded-md border border-white/12 bg-white/[0.04] px-2.5 py-1 font-mono text-[11px] text-muted-foreground hover:text-foreground hover:bg-white/[0.08] transition-colors",
16364
+ "aria-label": "Replay the terminal demo",
16365
+ children: [
16366
+ /* @__PURE__ */ jsx101(RotateCcw3, { size: 12 }),
16367
+ " Replay"
16368
+ ]
16369
+ }
16370
+ )
16344
16371
  ] }),
16345
- /* @__PURE__ */ jsxs89("div", { ref: scrollRef, className: "p-5 font-mono text-[12.5px] sm:text-[13px] leading-[1.9] max-h-[340px] overflow-auto", children: [
16372
+ /* @__PURE__ */ jsxs89("div", { ref: scrollRef, className: "p-5 font-mono text-[12.5px] sm:text-[13px] leading-[1.9] overflow-auto", style: { height }, children: [
16346
16373
  frame.lines,
16347
16374
  frame.showEnd && endSlot ? /* @__PURE__ */ jsx101("div", { className: "mt-4 pt-4 border-t border-white/10", children: endSlot }) : null
16348
16375
  ] })
@@ -16351,26 +16378,103 @@ function TypingTerminal({ steps, endSlot, title = "~/myapp \u2014 togo", classNa
16351
16378
  TypingTerminal.displayName = "TypingTerminal";
16352
16379
 
16353
16380
  // src/components/marketing/MascotMark.tsx
16381
+ import * as React33 from "react";
16354
16382
  import { jsx as jsx102, jsxs as jsxs90 } from "react/jsx-runtime";
16383
+ var EYES = [
16384
+ { x: 0.405, y: 0.45 },
16385
+ { x: 0.595, y: 0.45 }
16386
+ ];
16387
+ var EYE_W = 0.085;
16388
+ var PUPIL_RATIO = 0.52;
16389
+ var MAX_OFF = (1 - PUPIL_RATIO) / 2 * 100;
16355
16390
  var KEYFRAMES = `
16356
16391
  @keyframes tgMascotFloat { 0%,100%{transform:translateY(0)} 50%{transform:translateY(-12px)} }
16357
- @keyframes tgMascotChar {
16358
- 0%,100% { transform: rotate(0deg) scaleX(1) scaleY(1); }
16359
- 44% { transform: rotate(0deg) scaleX(1) scaleY(1); }
16360
- 48% { transform: rotate(-3deg) scaleX(1.04) scaleY(0.92); } /* squash nod */
16361
- 52% { transform: rotate(-3deg) scaleX(0.98) scaleY(1.05); } /* stretch */
16362
- 60% { transform: rotate(3deg) scaleX(1) scaleY(1); }
16363
- 72% { transform: rotate(0deg) scaleX(1) scaleY(1); }
16364
- }
16365
16392
  .tg-mascot { will-change: transform; animation: tgMascotFloat 6s ease-in-out infinite; }
16366
- .tg-mascot > img { display:block; width:100%; height:100%; transform-origin: 50% 70%; animation: tgMascotChar 5.5s ease-in-out infinite; }
16367
- @media (prefers-reduced-motion: reduce) {
16368
- .tg-mascot, .tg-mascot > img { animation: none !important; }
16369
- }`;
16393
+ @media (prefers-reduced-motion: reduce) { .tg-mascot { animation: none !important; } }`;
16370
16394
  function MascotMark({ src = "/togo-mark.svg", alt = "ToGO", className }) {
16371
- return /* @__PURE__ */ jsxs90("div", { className: cn("tg-mascot", className), children: [
16395
+ const ref = React33.useRef(null);
16396
+ const [active, setActive] = React33.useState(false);
16397
+ const [look, setLook] = React33.useState({ dx: 0, dy: 0 });
16398
+ React33.useEffect(() => {
16399
+ if (typeof window === "undefined" || !window.matchMedia) return;
16400
+ if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) return;
16401
+ if (!window.matchMedia("(hover: hover) and (pointer: fine)").matches) return;
16402
+ setActive(true);
16403
+ let raf = 0;
16404
+ const onMove = (e) => {
16405
+ const el = ref.current;
16406
+ if (!el) return;
16407
+ const r = el.getBoundingClientRect();
16408
+ const cx = r.left + r.width / 2;
16409
+ const cy = r.top + r.height * 0.45;
16410
+ const dx = Math.max(-1, Math.min(1, (e.clientX - cx) / (r.width * 0.85)));
16411
+ const dy = Math.max(-1, Math.min(1, (e.clientY - cy) / (r.height * 0.85)));
16412
+ cancelAnimationFrame(raf);
16413
+ raf = requestAnimationFrame(() => setLook({ dx, dy }));
16414
+ };
16415
+ const recenter = () => setLook({ dx: 0, dy: 0 });
16416
+ window.addEventListener("pointermove", onMove, { passive: true });
16417
+ window.addEventListener("pointerleave", recenter);
16418
+ document.addEventListener("mouseleave", recenter);
16419
+ return () => {
16420
+ window.removeEventListener("pointermove", onMove);
16421
+ window.removeEventListener("pointerleave", recenter);
16422
+ document.removeEventListener("mouseleave", recenter);
16423
+ cancelAnimationFrame(raf);
16424
+ };
16425
+ }, []);
16426
+ const tilt = active ? look.dx * 4 : 0;
16427
+ return /* @__PURE__ */ jsxs90("div", { ref, className: cn("tg-mascot", className), style: { position: "relative", display: "inline-block" }, children: [
16372
16428
  /* @__PURE__ */ jsx102("style", { children: KEYFRAMES }),
16373
- /* @__PURE__ */ jsx102("img", { src, alt, draggable: false })
16429
+ /* @__PURE__ */ jsxs90(
16430
+ "div",
16431
+ {
16432
+ style: {
16433
+ position: "relative",
16434
+ transform: active ? `rotate(${tilt}deg)` : void 0,
16435
+ transformOrigin: "50% 75%",
16436
+ transition: "transform .25s ease-out"
16437
+ },
16438
+ children: [
16439
+ /* @__PURE__ */ jsx102("img", { src, alt, draggable: false, style: { display: "block", width: "100%", height: "100%" } }),
16440
+ active && EYES.map((eye, i) => /* @__PURE__ */ jsx102(
16441
+ "span",
16442
+ {
16443
+ "aria-hidden": "true",
16444
+ style: {
16445
+ position: "absolute",
16446
+ left: `${eye.x * 100}%`,
16447
+ top: `${eye.y * 100}%`,
16448
+ width: `${EYE_W * 100}%`,
16449
+ aspectRatio: "1",
16450
+ transform: "translate(-50%,-50%)",
16451
+ borderRadius: "9999px",
16452
+ background: "rgba(255,255,255,.94)",
16453
+ boxShadow: "inset 0 2px 4px rgba(8,16,40,.28)"
16454
+ },
16455
+ children: /* @__PURE__ */ jsx102(
16456
+ "span",
16457
+ {
16458
+ style: {
16459
+ position: "absolute",
16460
+ left: `${50 + look.dx * MAX_OFF}%`,
16461
+ top: `${50 + look.dy * MAX_OFF}%`,
16462
+ width: `${PUPIL_RATIO * 100}%`,
16463
+ aspectRatio: "1",
16464
+ transform: "translate(-50%,-50%)",
16465
+ borderRadius: "9999px",
16466
+ background: "#0a1733",
16467
+ boxShadow: "inset 1.5px -1.5px 2px rgba(255,255,255,.35)",
16468
+ transition: "left .12s ease-out, top .12s ease-out"
16469
+ }
16470
+ }
16471
+ )
16472
+ },
16473
+ i
16474
+ ))
16475
+ ]
16476
+ }
16477
+ )
16374
16478
  ] });
16375
16479
  }
16376
16480
  MascotMark.displayName = "MascotMark";