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:
+5
-1
@@ -1,9 +1,11 @@
|
|||||||
|
import AwardBand from "@/components/AwardBand";
|
||||||
import Filmography from "@/components/Filmography";
|
import Filmography from "@/components/Filmography";
|
||||||
import Hero from "@/components/Hero";
|
import Hero from "@/components/Hero";
|
||||||
import ProjectCard from "@/components/ProjectCard";
|
import ProjectCard from "@/components/ProjectCard";
|
||||||
import Reveal from "@/components/Reveal";
|
import Reveal from "@/components/Reveal";
|
||||||
import SectionLabel from "@/components/SectionLabel";
|
import SectionLabel from "@/components/SectionLabel";
|
||||||
import {
|
import {
|
||||||
|
awards,
|
||||||
filmography,
|
filmography,
|
||||||
projects,
|
projects,
|
||||||
services,
|
services,
|
||||||
@@ -40,8 +42,10 @@ export default function Home() {
|
|||||||
<>
|
<>
|
||||||
<Hero />
|
<Hero />
|
||||||
|
|
||||||
|
{awards[0] && <AwardBand award={awards[0]} />}
|
||||||
|
|
||||||
{/* Filmography */}
|
{/* 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">
|
<div className="mx-auto max-w-7xl px-5 py-24 md:px-8 md:py-36">
|
||||||
<SectionLabel index="01">Filmography</SectionLabel>
|
<SectionLabel index="01">Filmography</SectionLabel>
|
||||||
<div className="mb-14 flex flex-col gap-6 md:flex-row md:items-end md:justify-between">
|
<div className="mb-14 flex flex-col gap-6 md:flex-row md:items-end md:justify-between">
|
||||||
|
|||||||
@@ -2,7 +2,8 @@ import type { Metadata } from "next";
|
|||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { notFound } from "next/navigation";
|
import { notFound } from "next/navigation";
|
||||||
import Frame from "@/components/Frame";
|
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;
|
export const dynamicParams = false;
|
||||||
|
|
||||||
@@ -38,6 +39,7 @@ export default async function ProjectPage({ params }: PageProps<"/work/[slug]">)
|
|||||||
if (index === -1) notFound();
|
if (index === -1) notFound();
|
||||||
const project = projects[index];
|
const project = projects[index];
|
||||||
const next = projects[(index + 1) % projects.length];
|
const next = projects[(index + 1) % projects.length];
|
||||||
|
const award = awardFor(project.slug);
|
||||||
|
|
||||||
const meta = [
|
const meta = [
|
||||||
{ k: "Client", v: project.client },
|
{ k: "Client", v: project.client },
|
||||||
@@ -55,6 +57,17 @@ export default async function ProjectPage({ params }: PageProps<"/work/[slug]">)
|
|||||||
← All work
|
← All work
|
||||||
</Link>
|
</Link>
|
||||||
<h1 className="mt-8 font-display text-5xl leading-[0.9] font-thin uppercase md:text-8xl">{project.title}</h1>
|
<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>
|
||||||
|
|
||||||
<div className="mx-auto mt-12 max-w-7xl px-5 md:px-8">
|
<div className="mx-auto mt-12 max-w-7xl px-5 md:px-8">
|
||||||
|
|||||||
@@ -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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import Reveal from "./Reveal";
|
import Reveal from "./Reveal";
|
||||||
import { filmography } from "@/content/site";
|
import { awardFor, filmography } from "@/content/site";
|
||||||
|
|
||||||
export default function Filmography() {
|
export default function Filmography() {
|
||||||
return (
|
return (
|
||||||
@@ -23,6 +23,11 @@ export default function Filmography() {
|
|||||||
Upcoming
|
Upcoming
|
||||||
</span>
|
</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 && (
|
{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">
|
<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}
|
{c.network}
|
||||||
|
|||||||
@@ -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
@@ -65,6 +65,39 @@ export const studioStills = [
|
|||||||
"/capabilities/penguin-box.jpg",
|
"/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 = {
|
export type Credit = {
|
||||||
slug: string;
|
slug: string;
|
||||||
title: string;
|
title: string;
|
||||||
@@ -105,7 +138,7 @@ export const stats = [
|
|||||||
{ value: `${yearsActive}+`, label: "Years in production" },
|
{ value: `${yearsActive}+`, label: "Years in production" },
|
||||||
{ value: String(filmography.length), label: "Film & series credits" },
|
{ value: String(filmography.length), label: "Film & series credits" },
|
||||||
{ value: String(filmography.filter((c) => c.network === "Netflix").length), label: "Netflix titles" },
|
{ 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 = {
|
export type Project = {
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 65 KiB |
Reference in New Issue
Block a user