# Glyph Field CTA

> A closing call to action on a dark panel: the headline, the install command with a copy button, and two links beside your logo printed in monospace glyphs. The mark decodes into place as it scrolls into view and inks in the accent under the pointer.

A CTA Section block from KeyframeUI: a whole landing-page section built around the Glyph Field component, free to install as source with the shadcn CLI. Built with React, Tailwind CSS and Motion.

- Live preview: https://keyframeui.com/blocks/cta-sections/glyph-field-cta
- Category: CTA Sections
- Built with: [Glyph Field](https://keyframeui.com/components/glyph-field.md)
- npm dependencies: motion, clsx, tailwind-merge

## Installation

Register the @keyframeui registry in `components.json` once per project:

```json
{
  "registries": {
    "@keyframeui": "https://keyframeui.com/r/{name}.json"
  }
}
```

Then add the block. It lands in `components/glyph-field-cta.tsx`, and the CLI also adds the components it's built with (`components/ui/glyph-field.tsx`) and `lib/utils.ts`, and installs the npm dependencies:

```bash
npx shadcn@latest add @keyframeui/glyph-field-cta
```

Or install by URL, without registering the namespace:

```bash
npx shadcn@latest add https://keyframeui.com/r/glyph-field-cta.json
```

## Usage

Put it where the section goes. The copy, links and data sit in constants at the top of the file; edit them there.

```tsx
import { GlyphFieldCta } from "@/components/glyph-field-cta";

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

## Source

```tsx title="components/glyph-field-cta.tsx"
"use client";

import * as React from "react";

import { GlyphField } from "@/components/ui/glyph-field";

/* ── Content ─────────────────────────────────────────────────
 * Everything you'd change to make the section yours. MARK is
 * your logo as one SVG path; Glyph Field prints it in type.
 * ─────────────────────────────────────────────────────────── */

/** The one accent: the glyph ink and the primary button. */
const ACCENT = { light: "#ea580c", dark: "#fb923c" };

/** Your logo as a single path, and the viewBox it was drawn in. */
const MARK = {
  path: "M1 7.5c4.2-.9 8 1.2 11 5 3-3.8 6.8-5.9 11-5-4.4 1.4-8.1 5-11 10.5C9.1 12.5 5.4 8.9 1 7.5Z",
  viewBox: "0 3 24 18",
  label: "The Tern mark, printed in type",
};

const HEADLINE = "Your next chart is";
const HEADLINE_QUIET = "one sentence away.";
const SUBLINE =
  "Install the CLI and sign in once, in your own terminal. From then on your agent adds the events, the tracking and the charts you ask for.";
const COMMAND = "npx tern@latest init";
const PRIMARY_CTA = { label: "Start free", href: "#start" };
const SECONDARY_CTA = { label: "Read the docs", href: "#docs" };

/* ── Section ─────────────────────────────────────────────── */

const focusRing =
  "outline-none focus-visible:ring-2 focus-visible:ring-white/70 focus-visible:ring-offset-2 focus-visible:ring-offset-zinc-950";

export function GlyphFieldCta() {
  return (
    <section
      style={{ "--accent-light": ACCENT.light, "--accent-dark": ACCENT.dark } as React.CSSProperties}
      className="bg-white [--accent:var(--accent-light)] dark:bg-zinc-950 dark:[--accent:var(--accent-dark)]"
    >
      <div className="mx-auto max-w-6xl px-6 py-24 sm:py-32">
        <div className="grid overflow-hidden rounded-3xl bg-zinc-950 text-white ring-1 ring-zinc-950/10 lg:grid-cols-[minmax(0,1fr)_minmax(0,1fr)] dark:bg-zinc-900 dark:ring-white/10">
          <div className="flex flex-col justify-center px-6 pt-12 pb-4 sm:px-12 sm:pt-16 lg:py-20">
            <h2 className="max-w-[14ch] text-4xl/[1.05] font-semibold tracking-[-0.035em] text-balance sm:text-5xl/[1.02]">
              {HEADLINE} <span className="text-zinc-500">{HEADLINE_QUIET}</span>
            </h2>
            <p className="mt-5 max-w-md text-base/7 text-pretty text-zinc-400">{SUBLINE}</p>

            <CopyCommand command={COMMAND} />

            <div className="mt-8 flex flex-wrap items-center gap-x-6 gap-y-3">
              <a
                href={PRIMARY_CTA.href}
                className={`inline-flex h-11 items-center gap-1.5 rounded-full bg-(--accent) px-5 text-sm font-medium text-white transition-[filter,scale] duration-150 hover:brightness-110 active:scale-[0.97] dark:text-zinc-950 ${focusRing}`}
              >
                {PRIMARY_CTA.label}
                <ArrowIcon className="size-3.5" />
              </a>
              <a
                href={SECONDARY_CTA.href}
                className={`rounded-md text-sm font-medium text-zinc-300 underline-offset-4 transition-colors hover:text-white hover:underline ${focusRing}`}
              >
                {SECONDARY_CTA.label}
              </a>
            </div>
          </div>

          <GlyphField
            path={MARK.path}
            viewBox={MARK.viewBox}
            label={MARK.label}
            cellSize={11}
            glyphClassName="text-zinc-500"
            accentClassName="text-(--accent)"
            className="h-72 sm:h-96 lg:h-auto lg:min-h-[30rem]"
          />
        </div>
      </div>
    </section>
  );
}

/* ── The install command ─────────────────────────────────── */

function CopyCommand({ command }: { command: string }) {
  const [copied, setCopied] = React.useState(false);
  const timer = React.useRef(0);
  React.useEffect(() => () => window.clearTimeout(timer.current), []);

  const copy = async () => {
    try {
      await navigator.clipboard.writeText(command);
    } catch {
      return;
    }
    setCopied(true);
    window.clearTimeout(timer.current);
    timer.current = window.setTimeout(() => setCopied(false), 1600);
  };

  return (
    <div className="mt-8 flex h-11 max-w-sm items-center gap-3 rounded-xl bg-white/[0.06] pr-1.5 pl-4 ring-1 ring-white/10">
      <span className="text-zinc-500 select-none" aria-hidden>
        $
      </span>
      <code className="min-w-0 flex-1 truncate font-mono text-[13px] text-zinc-100">{command}</code>
      <button
        type="button"
        onClick={copy}
        aria-label={copied ? "Copied" : "Copy the install command"}
        className={`grid size-8 shrink-0 place-items-center rounded-lg text-zinc-400 transition-colors hover:bg-white/10 hover:text-white ${focusRing}`}
      >
        {copied ? <CheckIcon className="size-4 text-(--accent)" /> : <CopyIcon className="size-4" />}
      </button>
      <span className="sr-only" aria-live="polite">
        {copied ? "Copied to the clipboard" : ""}
      </span>
    </div>
  );
}

/* ── Icons ───────────────────────────────────────────────── */

function ArrowIcon({ className }: { className?: string }) {
  return (
    <svg viewBox="0 0 16 16" className={className} fill="none" stroke="currentColor" strokeWidth={1.6} aria-hidden>
      <path d="M3.5 8h9m-3.5-3.5L12.5 8 9 11.5" strokeLinecap="round" strokeLinejoin="round" />
    </svg>
  );
}

function CopyIcon({ className }: { className?: string }) {
  return (
    <svg viewBox="0 0 16 16" className={className} fill="none" stroke="currentColor" strokeWidth={1.4} aria-hidden>
      <rect x="5.5" y="5.5" width="8" height="8" rx="1.8" />
      <path d="M10.5 3.5a1.5 1.5 0 0 0-1.5-1H4A1.5 1.5 0 0 0 2.5 4v5A1.5 1.5 0 0 0 4 10.5" strokeLinecap="round" />
    </svg>
  );
}

function CheckIcon({ className }: { className?: string }) {
  return (
    <svg viewBox="0 0 16 16" className={className} fill="none" stroke="currentColor" strokeWidth={1.8} aria-hidden>
      <path d="m3.5 8.5 3 3 6-7" strokeLinecap="round" strokeLinejoin="round" />
    </svg>
  );
}
```
