# Agency: Latest and careers

> News, writing and a role as cards with drawn covers, then the open roles as a ruled list.

A shadcn/ui block from the [@eased](https://eased.dev/?utm_source=shadcn.exchange) registry, listed on [shadcn.exchange](https://shadcn.exchange/r/eased/agency-latest).

- Page, with a live preview: https://shadcn.exchange/r/eased/agency-latest
- Screenshot: https://abzjhwgwpdpz7ua5.public.blob.vercel-storage.com/shots/eased/agency-latest-light-9203902fdb87cc78.png
- Registry item: https://eased.dev/r/agency-latest.json
- Listed: 2026-10-23, source last changed 2026-10-23

## Install

```bash
npx shadcn@latest add https://shadcn.exchange/r/eased/agency-latest.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: `motion`
- Registry items: `https://eased.dev/r/eased-tokens.json`, `https://eased.dev/r/eased-motion.json`, `https://eased.dev/r/agency-core.json`, `https://eased.dev/r/badge.json`

## Files

### `src/registry/eased/templates/agency/latest.tsx`

```tsx
"use client";

import { Badge } from "../../badge/badge";
import { latest } from "./content";
import { Arrow, Reveal, Section, useHref } from "./kit";

const DATE = new Intl.DateTimeFormat("en-GB", { day: "numeric", month: "short", year: "numeric", timeZone: "UTC" });

type Art = (typeof latest.items)[number]["art"];

/**
 * The careers and latest strip: four notes (press, writing, a role) as cards with small drawn
 * covers in the theme's own ink, then the open roles as a ruled list that shares one grid, so
 * every place and arrow lines up (rule 103).
 */
export function Latest() {
  const to = useHref();
  return (
    <Section id="latest" label={latest.label} className="flex flex-col gap-group">
      <Reveal>
        <p className="max-w-2xl font-display text-display-3xl font-medium tracking-display text-balance text-text">{latest.title}</p>
      </Reveal>
      <ul className="grid gap-x-tiles gap-y-pad-lg sm:grid-cols-2 lg:grid-cols-4">
        {latest.items.map((item, i) => (
          <li key={item.title}>
            <Reveal index={i} className="group relative flex flex-col gap-in">
              <div className="image-ring relative aspect-[3/2] overflow-hidden rounded-panel bg-surface-sunken sm:aspect-square text-text">
                <Cover art={item.art} />
              </div>
              <div className="flex flex-col gap-1.5">
                <p className="flex items-center gap-2 text-ui-sm text-text-muted">
                  <Badge tone={item.kind === "Careers" ? "brand" : "neutral"}>{item.kind}</Badge>
                  <time dateTime={item.date} className="tabular-nums">
                    {DATE.format(new Date(`${item.date}T00:00:00Z`))}
                  </time>
                </p>
                <h3 className="text-ui-md font-medium text-pretty text-text">
                  {"href" in item ? (
                    <a href={to(item.href)} className="focus-ring rounded-control-xs after:absolute after:inset-0 after:rounded-panel">
                      {item.title}
                    </a>
                  ) : (
                    item.title
                  )}
                </h3>
              </div>
            </Reveal>
          </li>
        ))}
      </ul>
      {/* The cards' four columns, so the list starts on the second card's edge (rule 101). */}
      <Reveal className="grid gap-x-tiles gap-y-in lg:grid-cols-4">
        <h3 id="roles" className="scroll-mt-4 text-ui-md font-medium text-text">
          {latest.roles.title}
        </h3>
        <div className="flex flex-col gap-pad-base lg:col-span-3">
          <ul className="grid grid-cols-[minmax(0,1fr)_auto] border-t border-line-subtle sm:grid-cols-[minmax(0,1fr)_auto_auto]">
            {latest.roles.items.map((role) => (
              <li key={role.title} className="group relative col-span-full grid grid-cols-subgrid items-center gap-x-pad-md border-b border-line-subtle py-pad-sm">
                <a
                  href={`${latest.roles.link.href}?subject=${encodeURIComponent(role.title)}`}
                  className="focus-ring rounded-control-xs text-ui-md text-text after:absolute after:inset-0"
                >
                  {role.title}
                </a>
                <span className="text-ui text-text-muted max-sm:hidden">{role.place}</span>
                <Arrow className="text-text-muted" />
              </li>
            ))}
          </ul>
          <p className="text-ui text-text-secondary">
            Write to{" "}
            <a href={latest.roles.link.href} className="focus-ring rounded-control-xs font-medium text-text underline decoration-line-strong underline-offset-4 hover-fine:decoration-text">
              {latest.roles.link.label}
            </a>{" "}
            with your work and a few lines about you.
          </p>
        </div>
      </Reveal>
    </Section>
  );
}

/** The notes' covers, drawn in the text and brand inks so they follow the theme. */
function Cover({ art }: { art: Art }) {
  if (art === "award")
    return (
      <div aria-hidden className="absolute inset-0 flex flex-col items-center justify-center gap-in">
        <svg viewBox="0 0 64 64" className="w-1/4 text-brand-text" fill="none">
          <circle cx="32" cy="32" r="30" stroke="currentColor" strokeWidth="1.5" />
          <path d="m32 16 4.4 9.6 10.4 1.1-7.8 7 2.2 10.3L32 38.8 22.8 44l2.2-10.3-7.8-7 10.4-1.1L32 16Z" fill="currentColor" />
        </svg>
        <span className="font-serif text-display-4xl tabular-nums">2026</span>
      </div>
    );
  if (art === "motion")
    return (
      <svg aria-hidden viewBox="0 0 100 100" className="absolute inset-0 size-full" fill="none">
        <path d="M14 80 C 44 80, 50 20, 86 20" stroke="currentColor" strokeOpacity=".25" strokeWidth=".75" />
        {[0.08, 0.3, 0.55, 0.78, 0.94].map((t, i) => {
          // Points along the curve at even times: they bunch where the curve eases.
          const x = (1 - t) ** 3 * 14 + 3 * (1 - t) ** 2 * t * 44 + 3 * (1 - t) * t ** 2 * 50 + t ** 3 * 86;
          const y = (1 - t) ** 3 * 80 + 3 * (1 - t) ** 2 * t * 80 + 3 * (1 - t) * t ** 2 * 20 + t ** 3 * 20;
          return <circle key={t} cx={x} cy={y} r={2.5 + i * 0.4} fill="currentColor" fillOpacity={0.3 + i * 0.17} />;
        })}
      </svg>
    );
  if (art === "roles")
    return (
      <svg aria-hidden viewBox="0 0 100 100" className="absolute inset-0 size-full">
        {Array.from({ length: 25 }, (_, i) => (
          <circle
            key={i}
            cx={22 + (i % 5) * 14}
            cy={22 + Math.floor(i / 5) * 14}
            r="4"
            className={i === 17 ? "fill-brand" : "fill-current"}
            fillOpacity={i === 17 ? 1 : 0.18}
          />
        ))}
      </svg>
    );
  return (
    <svg aria-hidden viewBox="0 0 100 100" className="absolute inset-0 size-full" fill="none">
      {[10, 18, 26, 34, 42].map((r, i) => (
        <circle key={r} cx="50" cy="50" r={r} stroke="currentColor" strokeOpacity={0.5 - i * 0.08} strokeWidth=".75" />
      ))}
    </svg>
  );
}
```
