The open roles as one ruled grid with what everyone gets and how the studio hires, and a job post per role with its facts and an apply link.
src/registry/eased/templates/agency/careers.tsx
"use client";
import type { ReactNode } from "react";
import { Button } from "../../button/button";
import { latest, type RoleSlug } from "./content";
import { careers, jobPosts } from "./content-careers";
import { Arrow, COLUMN, LABEL, PageHead, Reveal, Section, useHref } from "./kit";
/**
* The careers index, /careers: the open roles as one ruled grid (title, team, place, arrow) so
* every column lines up (rule 103), then what everyone gets and how the studio hires.
*/
export function CareersIndex() {
const to = useHref();
return (
<>
<div className={`${COLUMN} pt-section-sm`}>
<PageHead title={careers.title} lead={careers.lead} />
</div>
<Section id="roles" label={careers.roles.label}>
<ul className="grid grid-cols-[minmax(0,1fr)_auto] border-t border-line-subtle md:grid-cols-[minmax(0,5fr)_minmax(0,4fr)_minmax(0,3fr)_auto]">
{latest.roles.items.map((role, i) => (
<li key={role.slug} className="col-span-full grid grid-cols-subgrid">
<Reveal
index={i}
className="group relative col-span-full grid grid-cols-subgrid items-center gap-x-pad-md border-b border-line-subtle py-pad-md"
>
<span className="flex min-w-0 flex-col gap-1 md:contents">
<a
href={to(`/careers/${role.slug}`)}
className="focus-ring self-start rounded-control-xs text-display-xl font-medium text-text transition-colors duration-(--eased-duration-fast) ease-standard group-hover-fine:text-text-secondary after:absolute after:inset-0 md:self-center"
>
{role.title}
</a>
<span className="text-ui text-text-secondary">{jobPosts[role.slug].team}</span>
<span className="text-ui text-text-muted">{role.place}</span>
</span>
<Arrow className="text-text-muted" />
</Reveal>
</li>
))}
</ul>
</Section>
<Section id="benefits" label={careers.benefits.label}>
<ul className="grid gap-x-tiles gap-y-pad-lg sm:grid-cols-2 lg:grid-cols-4">
{careers.benefits.items.map((b, i) => (
<li key={b.title}>
<Reveal index={i} className="flex flex-col gap-1.5 border-t border-line-subtle pt-in">
<h3 className="text-display-xl font-medium text-text">{b.title}</h3>
<p className="text-ui-md text-pretty text-text-secondary">{b.body}</p>
</Reveal>
</li>
))}
</ul>
</Section>
<Section id="hiring" label={careers.process.label}>
<ol className="grid gap-x-tiles gap-y-pad-lg sm:grid-cols-2 lg:grid-cols-4">
{careers.process.items.map((step, i) => (
<li key={step.title}>
<Reveal index={i} className="flex flex-col gap-1.5 border-t border-line-subtle pt-in">
<span className="text-ui-sm text-text-muted tabular-nums">{String(i + 1).padStart(2, "0")}</span>
<h3 className="text-display-xl font-medium text-text">{step.title}</h3>
<p className="text-ui-md text-pretty text-text-secondary">{step.body}</p>
</Reveal>
</li>
))}
</ol>
</Section>
</>
);
}
/**
* A job post, /careers/<slug>: the role's title, its facts in a ruled list beside the post (the
* role, what you will do, what you bring), and how to apply, by email with the role as subject.
*/
export function JobPost({ slug }: { slug: RoleSlug }) {
const role = latest.roles.items.find((r) => r.slug === slug) ?? latest.roles.items[0];
const post = jobPosts[slug];
const { facts, labels, apply } = careers.post;
const rows = [
{ term: facts.team, value: post.team },
{ term: facts.place, value: role.place },
{ term: facts.type, value: post.type },
{ term: facts.salary, value: post.salary },
];
const mail = `mailto:${apply.email}?subject=${encodeURIComponent(role.title)}`;
return (
<article aria-labelledby="page-title" className={`${COLUMN} pt-section-sm pb-group`}>
<PageHead back={careers.back} title={role.title} size="md" />
<div className="mt-group grid gap-group lg:grid-cols-[minmax(0,5fr)_minmax(0,7fr)] lg:gap-group-lg">
<Reveal className="lg:sticky lg:top-pad-lg lg:self-start">
<dl className="grid grid-cols-[auto_minmax(0,1fr)] gap-x-pad-md border-t border-line-subtle text-ui">
{rows.map((f) => (
<div key={f.term} className="col-span-full grid grid-cols-subgrid border-b border-line-subtle py-in">
<dt className="text-text-muted">{f.term}</dt>
<dd className="text-text tabular-nums">{f.value}</dd>
</div>
))}
</dl>
<Button variant="primary" className="mt-pad-md" nativeButton={false} render={<a href={mail} />}>
{careers.roles.apply}
</Button>
</Reveal>
<Reveal index={1} className="flex max-w-2xl flex-col gap-group">
<PostPart label={labels.role}>
{post.intro.map((p) => (
<p key={p.slice(0, 24)} className="text-ui-md text-pretty text-text-secondary sm:text-ui-lg">
{p}
</p>
))}
</PostPart>
<PostPart label={labels.doing}>
<PostList items={post.doing} />
</PostPart>
<PostPart label={labels.bring}>
<PostList items={post.bring} />
</PostPart>
<PostPart label={labels.bonus}>
<PostList items={post.bonus} />
</PostPart>
<div className="flex flex-col items-start gap-pad-base rounded-panel bg-surface-sunken p-pad-md">
<h2 className="text-display-xl font-medium text-text">{apply.title}</h2>
<p className="max-w-lg text-ui-md text-pretty text-text-secondary">{apply.body}</p>
<a
href={mail}
className="focus-ring rounded-control-xs font-display text-display-2xl font-medium tracking-display break-all text-text underline decoration-line-strong decoration-2 underline-offset-[0.18em] transition-colors duration-(--eased-duration-fast) ease-standard hover-fine:decoration-text"
>
{apply.email}
</a>
</div>
</Reveal>
</div>
</article>
);
}
function PostPart({ label, children }: { label: string; children: ReactNode }) {
return (
<section className="flex flex-col gap-pad-base">
<h2 className={`${LABEL} border-t border-line-subtle pt-in`}>{label}</h2>
{children}
</section>
);
}
/** A post's list: a short rule for a bullet, so each line starts on the text's own edge. */
function PostList({ items }: { items: readonly string[] }) {
return (
<ul className="flex flex-col gap-2.5">
{items.map((item) => (
<li key={item} className="grid grid-cols-[1rem_minmax(0,1fr)] items-baseline gap-2 text-ui-md text-pretty text-text-secondary">
<span aria-hidden className="h-px w-2.5 translate-y-[-0.3em] bg-text-muted" />
{item}
</li>
))}
</ul>
);
}