# CTA Section v7

> Centered CTA with a Windows Terminal style window: PowerShell and WSL tabs, each showing the shadcn command that adds a uiception block, at its real prompt with a copy button, then a default and a secondary button.

A shadcn/ui block from the [@uiception](https://uiception.com) registry, listed on [shadcn.exchange](https://shadcn.exchange/r/uiception/cta-section-v7).

- Page, with a live preview: https://shadcn.exchange/r/uiception/cta-section-v7
- Screenshot: https://abzjhwgwpdpz7ua5.public.blob.vercel-storage.com/shots/uiception/cta-section-v7-light-bd0034de3d728b9a.png
- Registry item: https://uiception.com/r/cta-section-v7.json
- Listed: 2026-10-13, source last changed 2026-10-13

## Install

```bash
npx shadcn@latest add https://uiception.com/r/cta-section-v7.json
```

Run it in a project that has shadcn/ui set up. It adds the files below and installs what they depend on.

## Dependencies

- npm packages: `lucide-react`
- Registry items: `button`

## Files

### `registry/new-york/blocks/cta-section-v7/page.tsx`

```tsx
import CtaSectionV7 from "./components/cta-section-v7"

export default function Page() {
  return <CtaSectionV7 />
}
```

### `registry/new-york/blocks/cta-section-v7/components/cta-section-v7.tsx`

```tsx
import Link from "next/link"

import { Button } from "@/components/ui/button"

import WindowsTerminal from "./windows-terminal"
import { ctaSectionV7Content } from "../lib/config"

export default function CtaSectionV7() {
  const { heading, description, primaryCta, secondaryCta } = ctaSectionV7Content

  return (
    <section className="py-16 md:py-24">
      <div className="mx-auto max-w-6xl px-4">
        <div className="mx-auto flex max-w-2xl flex-col items-center gap-5 text-center">
          <h2 className="text-3xl leading-[1.05] font-medium tracking-[-0.035em] text-balance text-foreground sm:text-4xl lg:text-5xl">
            {heading}
          </h2>
          <p className="max-w-xl text-base leading-relaxed text-pretty text-muted-foreground md:text-lg">
            {description}
          </p>
        </div>

        <WindowsTerminal className="mx-auto mt-10 max-w-2xl md:mt-12" />

        <div className="mt-8 flex flex-wrap items-center justify-center gap-3">
          <Button size="lg" variant="default" asChild className="rounded-full">
            <Link href={primaryCta.href}>{primaryCta.label}</Link>
          </Button>
          <Button size="lg" variant="secondary" asChild className="rounded-full">
            <Link href={secondaryCta.href}>{secondaryCta.label}</Link>
          </Button>
        </div>
      </div>
    </section>
  )
}
```

### `registry/new-york/blocks/cta-section-v7/components/windows-terminal.tsx`

```tsx
"use client"

import { useEffect, useRef, useState, type KeyboardEvent } from "react"
import { Check, Copy } from "lucide-react"

import { cn } from "@/lib/utils"

import { commandText, sessions, type Segment, type Tone } from "../lib/commands"

import "../styles/cta-section-v7.css"

const toneClass: Record<Tone, string> = {
  fg: "text-(--wt-fg)",
  cyan: "text-(--wt-cyan)",
  "bright-yellow": "text-(--wt-bright-yellow)",
}

function Segments({ segments }: { segments: Segment[] }) {
  return segments.map((segment, i) => (
    <span key={i} className={toneClass[segment.tone ?? "fg"]}>
      {segment.text}
    </span>
  ))
}

export default function WindowsTerminal({ className }: { className?: string }) {
  const [activeId, setActiveId] = useState(sessions[0].id)
  const [copied, setCopied] = useState(false)
  const tabRefs = useRef<(HTMLButtonElement | null)[]>([])
  const active = sessions.find((session) => session.id === activeId) ?? sessions[0]

  const selectTab = (index: number) => {
    setActiveId(sessions[index].id)
    setCopied(false)
  }

  const onTabKeyDown = (event: KeyboardEvent<HTMLButtonElement>, index: number) => {
    const last = sessions.length - 1
    let next: number
    switch (event.key) {
      case "ArrowRight":
        next = index === last ? 0 : index + 1
        break
      case "ArrowLeft":
        next = index === 0 ? last : index - 1
        break
      case "Home":
        next = 0
        break
      case "End":
        next = last
        break
      default:
        return
    }
    event.preventDefault()
    selectTab(next)
    tabRefs.current[next]?.focus()
  }

  useEffect(() => {
    if (!copied) return
    const timeout = setTimeout(() => setCopied(false), 2000)
    return () => clearTimeout(timeout)
  }, [copied])

  const copy = async () => {
    try {
      await navigator.clipboard.writeText(commandText(active))
      setCopied(true)
    } catch {
      // Clipboard can be blocked (insecure context, permissions); nothing to do.
    }
  }

  return (
    <div
      className={cn(
        "wt-window w-full min-w-0 overflow-hidden rounded-lg border border-(--wt-border) bg-(--wt-tabrow-bg)",
        className
      )}
    >
      {/* Tab row, Windows Terminal style */}
      <div className="wt-ui flex h-10 items-stretch select-none">
        <div
          role="tablist"
          aria-label="Install command"
          className="flex min-w-0 flex-1 items-end pl-2"
        >
          {sessions.map((session, i) => {
            const selected = session.id === activeId
            const nextSelected = sessions[i + 1]?.id === activeId

            return (
              <button
                key={session.id}
                ref={(el) => {
                  tabRefs.current[i] = el
                }}
                type="button"
                role="tab"
                id={`wt-tab-${session.id}`}
                aria-selected={selected}
                aria-controls={`wt-panel-${session.id}`}
                tabIndex={selected ? 0 : -1}
                onClick={() => selectTab(i)}
                onKeyDown={(event) => onTabKeyDown(event, i)}
                className={cn(
                  "relative flex h-8 items-center rounded-t-lg px-4 text-xs",
                  selected
                    ? "wt-tab-selected z-10 bg-(--wt-term-bg) text-(--wt-tab-fg-selected)"
                    : "text-(--wt-tab-fg) hover:bg-(--wt-tab-hover)"
                )}
              >
                {session.title}
                {/* Separator between two unselected tabs */}
                {!selected && !nextSelected && i < sessions.length - 1 ? (
                  <span className="absolute top-2 right-0 h-4 w-px bg-(--wt-tab-sep)" />
                ) : null}
              </button>
            )
          })}
        </div>
      </div>

      {/* Terminal buffer: one prompt with the install command */}
      <div
        role="tabpanel"
        id={`wt-panel-${active.id}`}
        aria-labelledby={`wt-tab-${active.id}`}
        className="flex items-center gap-2 bg-(--wt-term-bg) py-3 pr-2 pl-3"
      >
        <code className="wt-mono min-w-0 flex-1 overflow-x-auto text-[13px] leading-tight whitespace-nowrap text-(--wt-fg) [scrollbar-width:none]">
          <span className="select-none">
            {active.prompt}{" "}
          </span>
          <Segments segments={active.command} />
        </code>
        <button
          type="button"
          onClick={copy}
          aria-label={copied ? "Copied" : "Copy command"}
          className="flex size-7 shrink-0 items-center justify-center rounded-md text-(--wt-bright-black) transition-colors hover:bg-(--wt-copy-hover) hover:text-(--wt-fg)"
        >
          {copied ? <Check className="size-3.5" /> : <Copy className="size-3.5" />}
        </button>
        <span role="status" className="sr-only">
          {copied ? "Command copied" : ""}
        </span>
      </div>
    </div>
  )
}
```

### `registry/new-york/blocks/cta-section-v7/lib/config.ts`

```ts
export const ctaSectionV7Content = {
  heading: "Bring uiception to your project",
  description: "Install now and start building with uiception blocks in your Next.js app.",
  primaryCta: { label: "Try now", href: "#" },
  secondaryCta: { label: "Read docs", href: "#" },
} as const
```

### `registry/new-york/blocks/cta-section-v7/lib/commands.ts`

```ts
/** Color scheme slots used by the commands (see styles/cta-section-v7.css). */
export type Tone = "fg" | "cyan" | "bright-yellow"

export type Segment = { text: string; tone?: Tone }

export type Session = {
  id: string
  /** Tab label. */
  title: string
  /** Shell prompt; shown but not copied. */
  prompt: string
  /** The install command, split into highlighted tokens. */
  command: Segment[]
}

export const sessions: Session[] = [
  {
    id: "powershell",
    title: "PowerShell",
    prompt: ">",
    // PSReadLine defaults: commands bright yellow, strings dark cyan. The
    // registry name is quoted because PowerShell reads a leading @ as splatting.
    command: [
      { text: "npx", tone: "bright-yellow" },
      { text: " shadcn@latest add " },
      { text: "\"@uiception/cta-section-v7\"", tone: "cyan" },
    ],
  },
  {
    id: "wsl",
    title: "WSL",
    prompt: "$",
    // Same token colors as the PowerShell tab, the way a highlighting shell
    // (zsh-syntax-highlighting, fish) would color it. No quotes needed in bash.
    command: [
      { text: "pnpm", tone: "bright-yellow" },
      { text: " dlx shadcn@latest add " },
      { text: "@uiception/cta-section-v7", tone: "cyan" },
    ],
  },
]

export const commandText = (session: Session) =>
  session.command.map((segment) => segment.text).join("")
```

### `registry/new-york/blocks/cta-section-v7/styles/cta-section-v7.css`

```css
/* cta-section-v7: Windows Terminal chrome tokens and color schemes */

.wt-window {
  /* Windows 11 light chrome (Mica) */
  --wt-tabrow-bg: #eeeeee;
  --wt-border: rgb(0 0 0 / 0.14);
  --wt-tab-fg: #1a1a1a;
  --wt-tab-hover: rgb(0 0 0 / 0.05);
  --wt-tab-sep: rgb(0 0 0 / 0.16);

  /* Selected tab takes the terminal background (WT 1.16+ default theme) */
  --wt-tab-fg-selected: #1a1a1a;
  --wt-copy-hover: rgb(0 0 0 / 0.06);

  /* One Half Light on a white background; the bright slots use the
     scheme's normal colors so they stay readable on white */
  --wt-term-bg: #ffffff;
  --wt-fg: #383a42;
  --wt-cyan: #0997b3;
  --wt-bright-black: #a0a1a7;
  --wt-bright-yellow: #c18301;
}

.dark .wt-window {
  /* Windows 11 dark chrome (Mica) */
  --wt-tabrow-bg: #202020;
  --wt-border: rgb(255 255 255 / 0.1);
  --wt-tab-fg: #c5c5c5;
  --wt-tab-hover: rgb(255 255 255 / 0.05);
  --wt-tab-sep: rgb(255 255 255 / 0.14);

  --wt-tab-fg-selected: #f2f2f2;
  --wt-copy-hover: rgb(255 255 255 / 0.08);

  /* Campbell, the Windows Terminal default */
  --wt-term-bg: #0c0c0c;
  --wt-fg: #cccccc;
  --wt-cyan: #3a96dd;
  --wt-bright-black: #767676;
  --wt-bright-yellow: #f9f1a5;
}

.wt-ui {
  font-family: "Segoe UI Variable Text", "Segoe UI", system-ui, sans-serif;
}

.wt-mono {
  font-family: "Cascadia Mono", "Cascadia Code", Consolas, "Courier New", ui-monospace,
    monospace;
}

/* WinUI TabView: the selected tab flares into the content below it */
.wt-tab-selected::before,
.wt-tab-selected::after {
  content: "";
  position: absolute;
  bottom: 0;
  width: 8px;
  height: 8px;
  pointer-events: none;
}

.wt-tab-selected::before {
  left: -8px;
  background: radial-gradient(circle at 0 0, transparent 7.5px, var(--wt-term-bg) 8px);
}

.wt-tab-selected::after {
  right: -8px;
  background: radial-gradient(circle at 100% 0, transparent 7.5px, var(--wt-term-bg) 8px);
}
```
