trx-cli 0.1.10

A Modern Cross-Platform Package Manager TUI
"use client";

import { useEffect, useRef, useState } from "react";
import { insetWell, raisedCrispPanel, RAISED_BORDER, RAISED_GRAD, RAISED_SHADOW } from "@/app/landing-material";
import { C, S } from "./tokens";
import { MX } from "./matrix-tokens";
import { DEMOS, type DemoPkg } from "./demo-data";

export function HeroTerminal() {
  const [demoIdx, setDemoIdx]      = useState(0);
  const [displayQuery, setDisplay] = useState("");
  const [selectedRow, setSelRow]   = useState(0);
  const [cursorVis, setCursorVis]  = useState(true);

  useEffect(() => {
    const t = setInterval(() => setCursorVis(v => !v), 530);
    return () => clearInterval(t);
  }, []);

  const charIdxRef = useRef(0);
  const demoIdxRef = useRef(0);
  const selRowRef  = useRef(0);
  const phaseRef   = useRef<"typing" | "browsing" | "deleting">("typing");

  useEffect(() => {
    let t: ReturnType<typeof setTimeout>;
    const tick = () => {
      const demo = DEMOS[demoIdxRef.current]!;
      if (phaseRef.current === "typing") {
        if (charIdxRef.current < demo.query.length) {
          charIdxRef.current++;
          setDisplay(demo.query.slice(0, charIdxRef.current));
          t = setTimeout(tick, 90 + Math.random() * 70);
        } else { phaseRef.current = "browsing"; t = setTimeout(tick, 900); }
      } else if (phaseRef.current === "browsing") {
        if (selRowRef.current < demo.packages.length - 1) {
          selRowRef.current++;
          setSelRow(selRowRef.current);
          t = setTimeout(tick, 520);
        } else { phaseRef.current = "deleting"; t = setTimeout(tick, 1100); }
      } else {
        if (charIdxRef.current > 0) {
          charIdxRef.current--;
          setDisplay(demo.query.slice(0, charIdxRef.current));
          t = setTimeout(tick, 48);
        } else {
          demoIdxRef.current = (demoIdxRef.current + 1) % DEMOS.length;
          selRowRef.current  = 0;
          phaseRef.current   = "typing";
          setDemoIdx(demoIdxRef.current);
          setSelRow(0);
          t = setTimeout(tick, 500);
        }
      }
    };
    t = setTimeout(tick, 1600);
    return () => clearTimeout(t);
  }, []);

  const demo   = DEMOS[demoIdx]!;
  const detail = demo.detail;
  const selPkg = demo.packages[selectedRow] ?? demo.packages[0]!;

  const statusCol = (s: DemoPkg["status"]) =>
    s === "installed" ? C.installed : s === "aur" ? C.aur : C.available;

  const mono: React.CSSProperties = { fontFamily: "var(--font-geist-mono), 'Courier New', monospace" };

  return (
    <div className={raisedCrispPanel("w-full select-none overflow-hidden")}>

      {/* ── Title bar ── */}
      <div className="flex items-center gap-2 border-b border-white/[0.06] bg-gradient-to-b from-[#242424] to-[#1a1a1a] px-3 py-2.5 shadow-[0_1px_0_#ffffff28_inset] sm:px-4">
        <div className="flex gap-1.5">
          <div className="h-2.5 w-2.5 rounded-full bg-[#7a4040]" />
          <div className="h-2.5 w-2.5 rounded-full bg-[#7a6a40]" />
          <div className="h-2.5 w-2.5 rounded-full bg-[#3d6b40]" />
        </div>
        <div className="flex flex-1 justify-center">
          <span style={{ ...mono, color: C.text3, fontSize: "12px" }}>trx-cli</span>
        </div>
        {/* Tabs — hidden on mobile */}
        <div className="hidden gap-0.5 sm:flex">
          {["Search", "Installed", "Updates"].map((tab, i) => (
            <span key={tab} style={{
              ...mono, fontSize: "11px", padding: "3px 10px", borderRadius: "5px",
              background: i === 0 ? C.surface3 : "transparent",
              color: i === 0 ? C.text : C.text3,
              boxShadow: i === 0 ? S.ring : "none",
            }}>{tab}</span>
          ))}
        </div>
      </div>

      {/* ── Three-panel body ── */}
      {/*
        Mobile  (<sm):  package list only
        sm–lg:          sidebar + package list
        lg+:            sidebar + package list + detail
      */}
      <div className="flex min-h-[360px] bg-[#101010] sm:min-h-[420px]">

        {/* Sidebar — sm+ only */}
        <div className="hidden w-[160px] shrink-0 flex-col border-r border-white/[0.05] bg-[#0e0e0e] py-3 shadow-[inset_-6px_0_12px_-8px_rgba(0,0,0,0.45)] sm:flex sm:w-[180px]">
          {[
            { label: "Search",    active: true },
            { label: "Installed", active: false },
            { label: "Updates",   active: false },
          ].map(item => (
            <div key={item.label} style={{
              ...mono, fontSize: "12.5px", padding: "5px 14px",
              color: item.active ? C.text : C.text2,
              background: item.active ? C.surface3 : "transparent",
              borderLeft: `2px solid ${item.active ? C.text3 : "transparent"}`,
            }}>{item.label}</div>
          ))}
          <div style={{ ...mono, fontSize: "10px", color: C.text3, padding: "14px 14px 6px", letterSpacing: "0.08em", textTransform: "uppercase" }}>
            Managers
          </div>
          {["pacman", "aur", "brew"].map(m => (
            <div key={m} style={{ ...mono, fontSize: "12.5px", padding: "4px 14px", color: C.text3 }}>{m}</div>
          ))}
          <div style={{ flex: 1 }} />
          <div style={{ ...mono, fontSize: "10px", color: C.text3, padding: "0 14px 10px" }}>50,342 packages</div>
        </div>

        {/* Package list */}
        <div className="flex flex-1 flex-col border-r border-white/[0.05] bg-[#0c0c0c]">

          {/* Search bar */}
          <div className="flex items-center gap-2 border-b border-white/[0.05] px-3 py-2.5 sm:px-3.5">
            <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke={C.text3} strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
              <circle cx="11" cy="11" r="8" /><line x1="21" y1="21" x2="16.65" y2="16.65" />
            </svg>
            <span style={{ ...mono, fontSize: "13px", color: C.text }}>{displayQuery}</span>
            <span style={{
              display: "inline-block", width: "6px", height: "13px",
              background: MX.emeraldText, borderRadius: "1px",
              opacity: cursorVis ? 1 : 0, transition: "opacity 0.05s",
            }} />
            {/* Results count — hidden on xs */}
            <span className="ml-auto hidden sm:inline" style={{ ...mono, fontSize: "11px", color: C.text3 }}>
              {demo.packages.length} results
            </span>
          </div>

          {/* Column headers — hidden on mobile, show version+status only on sm+ */}
          <div className="hidden border-b border-white/[0.04] px-3.5 py-1 sm:flex">
            <span style={{ ...mono, fontSize: "10px", color: C.text3, flex: 1, textTransform: "uppercase", letterSpacing: "0.08em" }}>Package</span>
            <span style={{ ...mono, fontSize: "10px", color: C.text3, width: "72px", textTransform: "uppercase", letterSpacing: "0.08em" }}>Version</span>
            <span style={{ ...mono, fontSize: "10px", color: C.text3, width: "72px", textTransform: "uppercase", letterSpacing: "0.08em" }}>Status</span>
          </div>

          {/* Package rows */}
          {demo.packages.map((pkg, i) => (
            <div key={`${demoIdx}-${pkg.name}`} style={{
              display: "flex", alignItems: "center",
              padding: "7px 12px",
              background: i === selectedRow ? MX.emeraldSubtle : "transparent",
              borderLeft: `2px solid ${i === selectedRow ? MX.emeraldBright : "transparent"}`,
              transition: "background 0.15s",
            }}>
              <span style={{ color: pkg.checked ? C.installed : C.surface3, fontSize: "8px", marginRight: "8px", flexShrink: 0 }}>●</span>
              <span style={{ ...mono, fontSize: "13px", flex: 1, minWidth: 0, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap", color: i === selectedRow ? C.text : C.text2, fontWeight: i === selectedRow ? "500" : "400" }}>
                {pkg.name}
              </span>
              {/* Version + status — hidden on xs */}
              <span className="hidden sm:inline" style={{ ...mono, fontSize: "12px", color: C.text3, width: "72px", flexShrink: 0 }}>{pkg.version}</span>
              <span className="hidden sm:inline" style={{ ...mono, fontSize: "11.5px", color: statusCol(pkg.status), width: "72px", flexShrink: 0 }}>{pkg.status}</span>
            </div>
          ))}
        </div>

        {/* Detail panel — lg+ only */}
        <div className="hidden w-[240px] shrink-0 flex-col gap-3.5 bg-[#101010] p-4 lg:flex lg:w-[260px]">
          <div>
            <div style={{ ...mono, fontSize: "14px", color: C.text, fontWeight: "600", marginBottom: "4px" }}>{selPkg.name}</div>
            <div style={{ ...mono, fontSize: "11.5px", color: C.text2, lineHeight: "1.6" }}>{detail.desc}</div>
          </div>
          <div className="border-b border-white/[0.05] pb-3.5">
            {[
              { label: "Version",  value: detail.version },
              { label: "Size",     value: detail.size },
              { label: "Provider", value: selPkg.status === "installed" ? "pacman" : selPkg.status },
            ].map(row => (
              <div key={row.label} style={{ display: "flex", justifyContent: "space-between", marginBottom: "6px" }}>
                <span style={{ ...mono, fontSize: "11.5px", color: C.text3 }}>{row.label}</span>
                <span style={{ ...mono, fontSize: "11.5px", color: C.text2 }}>{row.value}</span>
              </div>
            ))}
          </div>
          <div>
            <div style={{ ...mono, fontSize: "10px", color: C.text3, marginBottom: "4px", textTransform: "uppercase", letterSpacing: "0.08em" }}>Dependencies</div>
            <div style={{ ...mono, fontSize: "11.5px", color: C.text2, lineHeight: "1.7" }}>
              {detail.deps.split(", ").map(dep => (
                <span key={dep} className="mb-1 mr-1.5 inline-block">
                  <span className={[RAISED_GRAD, RAISED_BORDER, RAISED_SHADOW, "inline-block rounded px-1.5 py-0.5"].join(" ")} style={mono}>{dep}</span>
                </span>
              ))}
            </div>
          </div>
          <div className="mt-auto flex gap-1.5">
            <button type="button" className={[RAISED_GRAD, RAISED_BORDER, RAISED_SHADOW, "flex-1 cursor-pointer rounded-md border-none py-1.5 font-mono text-xs font-medium text-[#ebebeb] transition hover:brightness-105 active:scale-[0.98]"].join(" ")} style={mono}>Install</button>
            <button type="button" className={[insetWell("rounded-md"), "cursor-pointer border-none px-2.5 py-1.5 font-mono text-xs text-[#878787] transition hover:text-[#ebebeb]"].join(" ")} style={mono}>Remove</button>
          </div>
        </div>
      </div>

      {/* ── Status bar ── */}
      <div className="flex items-center gap-3 border-t border-white/[0.06] bg-gradient-to-b from-[#1c1c1c] to-[#161616] px-3 py-1 shadow-[0_-1px_0_#ffffff14_inset] sm:px-4" style={{ ...mono, fontSize: "11px" }}>
        <span style={{ color: C.text, fontWeight: "700", letterSpacing: "0.04em", flexShrink: 0 }}>NORMAL</span>
        {/* Keybindings — hidden on mobile */}
        <span className="hidden min-w-0 truncate sm:block" style={{ color: C.text3 }}>
          e:search · space:select · i:install · x:remove · U:upgrade · tab:switch
        </span>
        <span className="ml-auto shrink-0" style={{ color: C.text3 }}>
          {demo.packages.filter(p => p.checked).length} selected · {demo.packages.length} shown
        </span>
      </div>
    </div>
  );
}