Files
ttvfx/app/page.tsx
T
twotalesanimation ef0c2ef78e Mobile polish: fix 320px overflow, 44px tap targets, readable badges
- Project cards stack credits under title on small screens
- Fluid headings on /work and project pages; next-project banner wraps
- Footer, back, award and nav links padded to ~44px touch targets
- theRender.Fish marked as in-house project

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 19:27:53 +02:00

241 lines
9.1 KiB
TypeScript

import AwardBand from "@/components/AwardBand";
import Filmography from "@/components/Filmography";
import Hero from "@/components/Hero";
import ProjectCard from "@/components/ProjectCard";
import Reveal from "@/components/Reveal";
import SectionLabel from "@/components/SectionLabel";
import {
awards,
filmography,
projects,
services,
site,
stats,
studioStills,
} from "@/content/site";
const pipeline = [
{
step: "Breakdown",
body: "Script and plate breakdowns, bids and a shot list everyone agrees on.",
},
{
step: "Build",
body: "Assets, creatures and environments, approved in context, not in isolation.",
},
{
step: "Shot",
body: "Artists work in tracked versions, reviewed daily on our own review platform.",
},
{
step: "QC",
body: "Renders are checked automatically for frame range, bad pixels and colour space before a human signs off.",
},
{
step: "Deliver",
body: "Spec-correct, frame-accurate deliveries with full shot metadata.",
},
];
export default function Home() {
return (
<>
<Hero />
{awards[0] && <AwardBand award={awards[0]} />}
{/* Filmography */}
<section id="filmography" className="scroll-mt-16">
<div className="mx-auto max-w-7xl px-5 py-24 md:px-8 md:py-36">
<SectionLabel index="01">Filmography</SectionLabel>
<div className="mb-14 flex flex-col gap-6 md:flex-row md:items-end md:justify-between">
<Reveal>
<h2 className="max-w-3xl font-display text-4xl leading-tight font-thin uppercase md:text-6xl">
{filmography.length} titles.{" "}
<span className="font-light">Seen</span> across the world.
</h2>
</Reveal>
<p className="max-w-sm text-sm leading-relaxed text-muted">
Visual effects for features and series from South Africa and
Nigeria, released in cinemas and on Netflix, Prime Video, M-Net
and kykNET.
</p>
</div>
<Filmography />
</div>
</section>
{/* Selected work */}
<section id="work" className="border-t border-line">
<div className="mx-auto max-w-7xl px-5 py-24 md:px-8 md:py-36">
<SectionLabel index="02">Selected work</SectionLabel>
<div className="grid gap-x-8 gap-y-16 md:grid-cols-2">
{projects.slice(0, 4).map((p, i) => (
<Reveal
key={p.slug}
delay={(i % 2) * 120}
className={i % 2 === 1 ? "md:mt-24" : ""}
>
<ProjectCard project={p} />
</Reveal>
))}
</div>
</div>
</section>
{/* Services */}
<section id="services" className="border-t border-line bg-ink-2">
<div className="mx-auto max-w-7xl px-5 py-24 md:px-8 md:py-36">
<SectionLabel index="03">Services</SectionLabel>
<Reveal>
<h2 className="max-w-4xl font-display text-4xl leading-tight font-thin uppercase md:text-6xl">
From photoreal <span className="text-accent">VFX</span> to
stylised animation.
</h2>
</Reveal>
{services.map((g) => (
<div key={g.group} className="mt-16">
<h3 className="mb-6 font-mono text-xs tracking-[0.3em] text-accent uppercase">
{g.group}
</h3>
<div className="grid grid-cols-2 gap-px bg-line md:grid-cols-3 [&>*:last-child:nth-child(odd)]:col-span-2 md:[&>*:last-child:nth-child(odd)]:col-span-1">
{g.items.map((s, i) => (
<Reveal
key={s.title}
delay={(i % 3) * 80}
className="bg-ink-2"
>
<div className="group relative aspect-[16/9] overflow-hidden md:aspect-[2.2/1]">
{/* eslint-disable-next-line @next/next/no-img-element */}
<img
src={s.image}
alt=""
loading="lazy"
className="h-full w-full object-cover opacity-70 transition duration-700 ease-out group-hover:scale-105 group-hover:opacity-100"
/>
<div className="absolute inset-0 bg-gradient-to-t from-black/85 via-black/10 to-transparent" />
<p className="absolute bottom-3 left-4 font-display text-base font-light tracking-[0.08em] uppercase md:bottom-5 md:left-5 md:text-xl">
{s.title}
</p>
</div>
</Reveal>
))}
</div>
</div>
))}
</div>
</section>
{/* Studio / pipeline */}
<section
id="studio"
className="mx-auto max-w-7xl px-5 py-24 md:px-8 md:py-36"
>
<SectionLabel index="04">Studio</SectionLabel>
<div className="grid gap-16 lg:grid-cols-2">
<Reveal>
<h2 className="font-display text-4xl leading-tight font-thin uppercase md:text-6xl">
Two tales. <br />
One <span className="font-light">obsession</span>.
</h2>
<div className="mt-8 space-y-5 text-base leading-relaxed text-muted md:text-lg">
<p>
Founded in {site.founded} in Pretoria as Two Tales Animation, we
bring stories to life with creativity, technology and a passion
for visual storytelling, from photoreal creatures and
simulations to stylised character animation.
</p>
<p>
We&apos;re small on purpose: senior artists, direct lines to
supervisors, and an in-house pipeline for review, shot tracking
and automated QC that lets a tight team deliver like a big one.
</p>
</div>
<div className="mt-10 grid grid-cols-2 gap-2">
{studioStills.map((src) => (
// eslint-disable-next-line @next/next/no-img-element
<img
key={src}
src={src}
alt=""
loading="lazy"
className="aspect-[2.39/1] w-full object-cover"
/>
))}
</div>
</Reveal>
<ol className="relative self-start border-l border-line">
{pipeline.map((p, i) => (
<Reveal key={p.step} delay={i * 80}>
<li className="relative pb-10 pl-10 last:pb-0">
<span className="absolute top-1.5 -left-[5px] h-[9px] w-[9px] rotate-45 bg-accent" />
<div className="flex items-baseline gap-4">
<span className="font-mono text-xs text-muted">
{String(i + 1).padStart(2, "0")}
</span>
<h3 className="font-display text-xl font-light tracking-[0.15em] uppercase">
{p.step}
</h3>
</div>
<p className="mt-2 text-sm leading-relaxed text-muted">
{p.body}
</p>
</li>
</Reveal>
))}
</ol>
</div>
<div className="mt-24 grid grid-cols-2 border-t border-line md:grid-cols-4">
{stats.map((s) => (
<div
key={s.label}
className="border-b border-line py-10 pr-6 md:border-b-0"
>
<p className="font-display text-5xl font-thin md:text-6xl">
{s.value}
</p>
<p className="mt-3 font-mono text-[11px] tracking-[0.2em] text-muted uppercase">
{s.label}
</p>
</div>
))}
</div>
</section>
{/* Contact */}
<section
id="contact"
className="relative overflow-hidden border-t border-line"
>
<div
className="pointer-events-none absolute -bottom-1/2 left-1/2 h-[80vmax] w-[80vmax] -translate-x-1/2 rounded-full bg-[radial-gradient(circle,rgb(232_87_42/0.14),transparent_60%)]"
aria-hidden="true"
/>
<div className="relative mx-auto max-w-7xl px-5 py-28 text-center md:px-8 md:py-44">
<SectionLabel index="05" className="justify-center">
Contact
</SectionLabel>
<Reveal>
<h2 className="font-display text-5xl leading-[0.95] font-thin uppercase md:text-8xl">
Got a story
<br />
to tell?
</h2>
<p className="mx-auto mt-8 max-w-lg text-muted md:text-lg">
Send us a script, a breakdown or just an idea. We&apos;ll come
back with a plan and a bid.
</p>
<a
href={`mailto:${site.email}?subject=New%20project%20enquiry`}
className="mt-10 inline-block border-b border-accent pt-2 pb-3 font-display text-xl font-light tracking-wider break-all transition-colors hover:text-accent md:text-4xl"
>
{site.email}
</a>
</Reveal>
</div>
</section>
</>
);
}