Timace

Plan

Shorten the pricing page2 of 4
  1. Done: Read the page
  2. Done: Find what repeats
  3. In progress: Draft the shorter version
  4. To do: Check the voice
components/ui/plan.tsx
import type { ReactNode } from "react";

const marks = {
  done: "border-(--tui-plan-done) bg-(--tui-plan-done) text-(--tui-plan-done-fg)",
  active: "border-(--tui-plan-active) bg-(--tui-plan-bg) text-(--tui-plan-active)",
  pending: "border-(--tui-plan-line) bg-(--tui-plan-bg) text-transparent",
};

const titles = {
  done: "text-(--tui-plan-muted) line-through decoration-(--tui-plan-line)",
  active: "font-(--tui-plan-active-weight) text-(--tui-plan-fg)",
  pending: "text-(--tui-plan-muted)",
};

export interface PlanProps {
  /** What the plan is for. */
  title?: ReactNode;
  /** A note beside the title: how far along it is. */
  meta?: ReactNode;
  /** How far along it is, from 0 to 1, drawn as a bar under the title. */
  progress?: number;
  children: ReactNode;
  className?: string;
}

/** The steps an agent means to take, and how far it has got. */
export function Plan({ title, meta, progress, children, className }: PlanProps) {
  const done = progress === undefined ? undefined : Math.round(Math.min(Math.max(progress, 0), 1) * 100);
  return (
    <div
      data-slot="plan"
      className={[
        "flex w-full flex-col gap-(--tui-plan-gap) rounded-(--tui-plan-radius) border-solid border-(length:--tui-plan-border-width) border-(--tui-plan-border) bg-(--tui-plan-bg) p-(--tui-plan-padding) font-(family-name:--tui-font) text-(length:--tui-plan-font-size)",
        className,
      ]
        .filter(Boolean)
        .join(" ")}
    >
      {(title || meta) && (
        <div className="flex items-baseline justify-between gap-3 leading-none">
          {title && <span className="font-(--tui-plan-title-weight) text-(--tui-plan-fg)">{title}</span>}
          {meta && <span className="text-(--tui-plan-muted) tabular-nums">{meta}</span>}
        </div>
      )}
      {done !== undefined && (
        <span role="progressbar" aria-label="Progress" aria-valuemin={0} aria-valuemax={100} aria-valuenow={done} className="-mt-[calc(var(--tui-plan-gap)_*_0.35)] block h-1 overflow-hidden rounded-full bg-(--tui-plan-line)">
          <span className="block h-full rounded-full bg-(--tui-plan-done) transition-[width] duration-(--tui-duration)" style={{ width: `${done}%` }} />
        </span>
      )}
      <ol className="m-0 flex list-none flex-col p-0">{children}</ol>
    </div>
  );
}

export interface PlanStepProps {
  status?: keyof typeof marks;
  children: ReactNode;
  /** A line under the step: what it is doing, or what it found. */
  detail?: ReactNode;
}

export function PlanStep({ status = "pending", children, detail }: PlanStepProps) {
  return (
    <li aria-current={status === "active" ? "step" : undefined} className="group/step relative flex gap-(--tui-plan-step-gap) pb-(--tui-plan-step-space) last:pb-0">
      {/* The rail to the next step. */}
      <span aria-hidden className="absolute top-(--tui-plan-mark-size) bottom-0 left-[calc(var(--tui-plan-mark-size)_/_2_-_var(--tui-plan-line-width)_/_2)] w-(--tui-plan-line-width) bg-(--tui-plan-line) group-last/step:hidden" />
      <span data-slot="plan-mark" data-status={status} className={`relative flex size-(--tui-plan-mark-size) shrink-0 items-center justify-center rounded-full border-solid border-(length:--tui-plan-mark-border-width) ${marks[status]}`}>
        {status === "done" && (
          <svg aria-hidden viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="2.4" strokeLinecap="round" strokeLinejoin="round" className="size-[60%]">
            <path d="M3.5 8.5l3 3 6-7" />
          </svg>
        )}
        {status === "active" && <span className="size-[40%] animate-pulse rounded-full bg-current" />}
      </span>
      <span className="flex min-w-0 flex-col gap-1 pt-[calc((var(--tui-plan-mark-size)_-_1.25em)_/_2)] leading-tight">
        <span className={titles[status]}>
          <span className="sr-only">{status === "done" ? "Done: " : status === "active" ? "In progress: " : "To do: "}</span>
          {children}
        </span>
        {detail && <span className="text-[0.92em] leading-snug text-(--tui-plan-muted)">{detail}</span>}
      </span>
    </li>
  );
}
styles/timace-ui.css
:root {
  color-scheme: light;
  --tui-bg: #ffffff;
  --tui-fg: #0a0a0a;
  --tui-muted: #f5f5f5;
  --tui-muted-fg: #737373;
  --tui-border: #e5e5e5;
  --tui-primary: #171717;
  --tui-primary-fg: #fafafa;
  --tui-ring: #a3a3a3;
  --tui-success: #16a34a;
  --tui-warning: #d97706;
  --tui-danger: #dc2626;
  --tui-radius: 8px;
  --tui-border-width: 1px;
  --tui-space: 4px;
  --tui-shadow: none;
  --tui-font: ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;
  --tui-text: 14px;
  --tui-duration: 150ms;

  /* Plan */
  --tui-plan-padding: calc(var(--tui-space) * 4);
  --tui-plan-gap: calc(var(--tui-space) * 4);
  --tui-plan-mark-size: calc(var(--tui-space) * 5);
  --tui-plan-step-gap: calc(var(--tui-space) * 3);
  --tui-plan-step-space: calc(var(--tui-space) * 4);
  --tui-plan-radius: calc(var(--tui-radius) * 1.5);
  --tui-plan-border-width: var(--tui-border-width);
  --tui-plan-mark-border-width: 1.5px;
  --tui-plan-line-width: 1.5px;
  --tui-plan-font-size: var(--tui-text);
  --tui-plan-title-weight: 600;
  --tui-plan-active-weight: 500;
  --tui-plan-bg: var(--tui-bg);
  --tui-plan-fg: var(--tui-fg);
  --tui-plan-muted: var(--tui-muted-fg);
  --tui-plan-border: var(--tui-border);
  --tui-plan-line: var(--tui-border);
  --tui-plan-done: var(--tui-fg);
  --tui-plan-done-fg: var(--tui-bg);
  --tui-plan-active: var(--tui-primary);
}

.dark,
[data-theme="dark"] {
  color-scheme: dark;
  --tui-bg: #0a0a0a;
  --tui-fg: #fafafa;
  --tui-muted: #1c1c1c;
  --tui-muted-fg: #a3a3a3;
  --tui-border: #2a2a2a;
  --tui-primary: #fafafa;
  --tui-primary-fg: #171717;
  --tui-ring: #737373;
  --tui-success: #22c55e;
  --tui-warning: #f59e0b;
  --tui-danger: #ef4444;
}

Needs Tailwind CSS v4. Import the stylesheet once, after Tailwind.