Add SAFTA nomination: award band, project badge, filmography tag

Summertide nominated for Best Achievement in Visual Effects - TV at the
19th South African Film and Television Awards (2026).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
twotalesanimation
2026-10-05 19:15:22 +02:00
parent 96ad9c42b3
commit aba0bcde86
7 changed files with 155 additions and 4 deletions
+5 -1
View File
@@ -1,9 +1,11 @@
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,
@@ -40,8 +42,10 @@ export default function Home() {
<>
<Hero />
{awards[0] && <AwardBand award={awards[0]} />}
{/* Filmography */}
<section id="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">
+14 -1
View File
@@ -2,7 +2,8 @@ import type { Metadata } from "next";
import Link from "next/link";
import { notFound } from "next/navigation";
import Frame from "@/components/Frame";
import { projects, site } from "@/content/site";
import Laurel from "@/components/Laurel";
import { awardFor, projects, site } from "@/content/site";
export const dynamicParams = false;
@@ -38,6 +39,7 @@ export default async function ProjectPage({ params }: PageProps<"/work/[slug]">)
if (index === -1) notFound();
const project = projects[index];
const next = projects[(index + 1) % projects.length];
const award = awardFor(project.slug);
const meta = [
{ k: "Client", v: project.client },
@@ -55,6 +57,17 @@ export default async function ProjectPage({ params }: PageProps<"/work/[slug]">)
← All work
</Link>
<h1 className="mt-8 font-display text-5xl leading-[0.9] font-thin uppercase md:text-8xl">{project.title}</h1>
{award && (
<div className="mt-8 inline-flex items-center gap-4 border border-accent/40 bg-accent/[0.06] py-3 pr-6 pl-3">
<Laurel className="h-12 w-12 shrink-0 text-accent" />
<div>
<p className="font-mono text-[10px] tracking-[0.25em] text-accent uppercase">
{award.edition} {award.body} · {award.result}
</p>
<p className="mt-1 text-sm md:text-base">{award.category}</p>
</div>
</div>
)}
</div>
<div className="mx-auto mt-12 max-w-7xl px-5 md:px-8">
+51
View File
@@ -0,0 +1,51 @@
import Link from "next/link";
import Laurel from "./Laurel";
import PlayReel from "./PlayReel";
import Reveal from "./Reveal";
import type { Award } from "@/content/site";
// Full-bleed award callout over a still from the nominated work.
export default function AwardBand({ award }: { award: Award }) {
return (
<section className="relative overflow-hidden border-y border-line">
{/* eslint-disable-next-line @next/next/no-img-element */}
<img src={award.image} alt="" className="absolute inset-0 h-full w-full object-cover object-[70%_40%] opacity-60" />
<div className="absolute inset-0 bg-gradient-to-r from-ink via-ink/75 to-transparent" />
<div className="relative mx-auto grid max-w-7xl items-center gap-12 px-5 py-20 md:grid-cols-[auto_1fr] md:gap-16 md:px-8 md:py-28">
<Reveal>
<div className="relative mx-auto flex h-52 w-52 items-center justify-center text-accent md:h-64 md:w-64">
<Laurel className="absolute inset-0 h-full w-full" />
<div className="text-center">
<p className="font-mono text-[10px] tracking-[0.3em] text-muted uppercase">{award.edition}</p>
<p className="mt-1 font-display text-4xl font-light tracking-[0.15em] text-fg md:text-5xl">{award.body}</p>
<p className="mt-1 font-mono text-[10px] tracking-[0.3em] uppercase">{award.result}</p>
</div>
</div>
</Reveal>
<Reveal delay={120}>
<p className="font-mono text-xs tracking-[0.3em] text-accent uppercase">
{award.result} · {award.year}
</p>
<h2 className="mt-5 font-display text-4xl leading-tight font-thin uppercase md:text-6xl">
{award.category}
</h2>
<p className="mt-6 max-w-xl text-base leading-relaxed text-muted md:text-lg">
Our creature and simulation work on <span className="text-fg">{award.title}</span> was nominated at the{" "}
{award.longName}.
</p>
<div className="mt-10 flex flex-wrap items-center gap-6">
{award.reel && <PlayReel src={award.reel} poster={award.image} label="Watch the breakdown" />}
{award.project && (
<Link
href={`/work/${award.project}/`}
className="font-mono text-xs tracking-[0.2em] text-muted uppercase transition-colors hover:text-fg"
>
See the project →
</Link>
)}
</div>
</Reveal>
</div>
</section>
);
}
+6 -1
View File
@@ -1,5 +1,5 @@
import Reveal from "./Reveal";
import { filmography } from "@/content/site";
import { awardFor, filmography } from "@/content/site";
export default function Filmography() {
return (
@@ -23,6 +23,11 @@ export default function Filmography() {
Upcoming
</span>
)}
{awardFor(c.slug) && (
<span className="bg-fg px-1.5 py-0.5 font-mono text-[9px] tracking-[0.2em] text-ink uppercase">
{awardFor(c.slug)!.body} {awardFor(c.slug)!.result}
</span>
)}
{c.network && (
<span className="bg-black/70 px-1.5 py-0.5 font-mono text-[9px] tracking-[0.2em] text-fg uppercase backdrop-blur-sm">
{c.network}
+45
View File
@@ -0,0 +1,45 @@
// Laurel wreath: a left branch of leaves curving from the bottom up the side,
// mirrored for the right. SVG angles run clockwise from +x (90° = bottom).
const R = 40;
const C = 50;
const leaves = Array.from({ length: 8 }, (_, i) => {
const deg = 112 + i * 18; // bottom-left round to top-left
const a = (deg * Math.PI) / 180;
return { x: C + R * Math.cos(a), y: C + R * Math.sin(a), deg };
});
function Branch() {
const start = leaves[0];
const end = leaves[leaves.length - 1];
return (
<g>
<path
d={`M${start.x.toFixed(2)} ${start.y.toFixed(2)} A${R} ${R} 0 0 1 ${end.x.toFixed(2)} ${end.y.toFixed(2)}`}
fill="none"
stroke="currentColor"
strokeWidth="1"
/>
{leaves.map((l, i) => {
// Outer and inner leaf at each node, angled toward the top of the wreath.
const tangent = l.deg + 90;
return (
<g key={i} transform={`translate(${l.x.toFixed(2)} ${l.y.toFixed(2)})`}>
<ellipse cx="0" cy="-5" rx="2.4" ry="5.5" fill="currentColor" transform={`rotate(${tangent - 35})`} />
<ellipse cx="0" cy="-5" rx="2.4" ry="5.5" fill="currentColor" transform={`rotate(${tangent + 35})`} opacity="0.75" />
</g>
);
})}
</g>
);
}
export default function Laurel({ className = "" }: { className?: string }) {
return (
<svg viewBox="0 0 100 100" className={className} aria-hidden="true">
<Branch />
<g transform="matrix(-1 0 0 1 100 0)">
<Branch />
</g>
</svg>
);
}
+34 -1
View File
@@ -65,6 +65,39 @@ export const studioStills = [
"/capabilities/penguin-box.jpg",
];
export type Award = {
body: string;
longName: string;
edition: string;
year: number;
result: "Nominee" | "Winner";
category: string;
title: string;
// Filmography slug and/or project slug this award belongs to.
credit?: string;
project?: string;
image: string;
reel?: string;
};
export const awards: Award[] = [
{
body: "SAFTA",
longName: "19th South African Film and Television Awards",
edition: "19th",
year: 2026,
result: "Nominee",
category: "Best Achievement in Visual Effects – TV",
title: "Summertide",
credit: "summertide",
project: "summertide",
image: "/work/summertide/award-bg.jpg",
reel: "/reel/summertide-reel.mp4",
},
];
export const awardFor = (slug: string) => awards.find((a) => a.credit === slug || a.project === slug);
export type Credit = {
slug: string;
title: string;
@@ -105,7 +138,7 @@ export const stats = [
{ value: `${yearsActive}+`, label: "Years in production" },
{ value: String(filmography.length), label: "Film & series credits" },
{ value: String(filmography.filter((c) => c.network === "Netflix").length), label: "Netflix titles" },
{ value: "PTA", label: "Pretoria, South Africa" },
{ value: "SAFTA", label: "2026 VFX nominee" },
];
export type Project = {
Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB