Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 15 additions & 11 deletions src/app/brampton/vote/2026/pledge/[slug]/SharedPledgeClient.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,29 +42,33 @@ export default function SharedPledgeClient({ name }: { name: string }) {
}

return (
<div className="relative h-[calc(100dvh-20px)] min-h-[480px] border-2 border-dark bg-[#efe4da] overflow-clip">
/* On small screens the copy would sit on top of the stamp, so the three
bands flow vertically instead; from md up they overlay it again. */
<div className="relative flex flex-col h-[calc(100dvh-20px)] min-h-[560px] border-2 border-dark bg-[#efe4da] overflow-clip">
{/* ── The stamp, full bleed ────────────────────────────── */}
<div className="absolute inset-0">
<div className="relative order-2 min-h-[220px] flex-1 md:absolute md:inset-0">
<StampScene stampSrc={stampImage.src} />
</div>

{/* ── Overlaid header ──────────────────────────────────── */}
<div className="pointer-events-none absolute top-0 inset-x-0 flex flex-wrap items-start justify-between gap-x-8 gap-y-5 p-6 md:p-10">
<div className="order-1 shrink-0 flex flex-wrap items-start justify-between gap-x-8 gap-y-4 p-6 md:pointer-events-none md:absolute md:top-0 md:inset-x-0 md:gap-y-5 md:p-10">
<div>
<p className="type-label text-accent mb-4">{ELECTION.eyebrow}</p>
<h1 className="font-sans font-medium leading-[0.98] tracking-[-0.04em] text-[clamp(2.25rem,4.5vw,3.75rem)] max-w-[16ch] text-balance">
<p className="type-label text-accent mb-2 md:mb-4">
{ELECTION.eyebrow}
</p>
<h1 className="font-sans font-medium leading-[0.98] tracking-[-0.04em] text-[clamp(1.875rem,7vw,3.75rem)] md:text-[clamp(2.25rem,4.5vw,3.75rem)] max-w-[16ch] text-balance">
{name} pledged to vote.
</h1>
</div>

{/* ── Share + join in ────────────────────────────────── */}
<div className="pointer-events-auto flex flex-col items-start gap-4 max-w-[24rem]">
<p className="type-label-sm text-text-secondary">
<div className="md:pointer-events-auto flex flex-wrap items-center gap-x-4 gap-y-3 md:flex-col md:items-start md:gap-4 md:max-w-[24rem]">
<p className="type-label-sm text-text-secondary w-full md:w-auto">
{ELECTION.cityLabel} votes {ELECTION.voteDayLabel}
</p>
<Link
href={ELECTION.pledgePath}
className="group/btn inline-flex items-center gap-3 type-button text-bg bg-accent px-6 py-3.5 transition-colors hover:bg-accent-hover"
className="group/btn inline-flex items-center gap-3 type-button text-bg bg-accent px-5 py-3 md:px-6 md:py-3.5 transition-colors hover:bg-accent-hover"
>
Pledge to vote too
<ArrowRight className="size-4 shrink-0 transition-transform group-hover/btn:translate-x-0.5" />
Expand All @@ -90,15 +94,15 @@ export default function SharedPledgeClient({ name }: { name: string }) {
</div>

{/* ── Overlaid footer ──────────────────────────────────── */}
<div className="pointer-events-none absolute bottom-0 inset-x-0 flex flex-wrap items-center justify-between gap-4 p-6 md:px-10 md:py-8">
<div className="order-3 shrink-0 flex flex-wrap items-center justify-between gap-3 p-6 md:pointer-events-none md:absolute md:bottom-0 md:inset-x-0 md:gap-4 md:px-10 md:py-8">
<Link
href={ELECTION.basePath}
className="pointer-events-auto group/btn inline-flex items-center gap-3 type-button text-dark border-2 border-dark px-6 py-3.5 transition-colors hover:bg-dark hover:text-bg"
className="pointer-events-auto group/btn inline-flex items-center gap-3 type-button text-dark border-2 border-dark px-5 py-3 md:px-6 md:py-3.5 transition-colors hover:bg-dark hover:text-bg"
>
<ArrowLeft className="size-4 shrink-0 transition-transform group-hover/btn:-translate-x-0.5" />
Back to the election tracker
</Link>
<p className="type-label-sm text-text-secondary">
<p className="type-label-sm text-text-secondary text-balance">
Drag the stamp around · Polls open {ELECTION.pollHoursLabel}
</p>
</div>
Expand Down
24 changes: 13 additions & 11 deletions src/app/toronto/vote/2026/pledge/[slug]/SharedPledgeClient.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,31 +40,33 @@ export default function SharedPledgeClient({ name }: { name: string }) {

return (
<div className="theme-election bg-bg text-dark">
<div className="relative h-[calc(100dvh-20px)] min-h-[480px] border-2 border-dark bg-bg overflow-clip">
{/* On small screens the copy would sit on top of the stamp, so the three

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Clipped stacked mobile layout

When a long pledge name and the footer controls wrap on a narrow mobile viewport, the non-shrinking header and footer plus the stamp's 220px minimum height exceed this fixed-height container; overflow-clip then hides the bottom content without an internal scrolling path, making navigation or explanatory text inaccessible. The same layout is used by the Brampton shared pledge page.

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/app/toronto/elections/2026/pledge/[slug]/SharedPledgeClient.tsx
Line: 43

Comment:
**Clipped stacked mobile layout**

When a long pledge name and the footer controls wrap on a narrow mobile viewport, the non-shrinking header and footer plus the stamp's 220px minimum height exceed this fixed-height container; `overflow-clip` then hides the bottom content without an internal scrolling path, making navigation or explanatory text inaccessible. The same layout is used by the Brampton shared pledge page.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Fix in Codex Fix in Claude Code

bands flow vertically instead; from md up they overlay it again. */}
<div className="relative flex flex-col h-[calc(100dvh-20px)] min-h-[560px] border-2 border-dark bg-bg overflow-clip">
{/* ── The stamp, full bleed ──────────────────────────── */}
<div className="absolute inset-0">
<div className="relative order-2 min-h-[220px] flex-1 md:absolute md:inset-0">
<StampScene stampSrc={stampImage.src} />
</div>

{/* ── Overlaid header ────────────────────────────────── */}
<div className="pointer-events-none absolute top-0 inset-x-0 flex flex-wrap items-start justify-between gap-x-8 gap-y-5 p-6 md:p-10">
<div className="order-1 shrink-0 flex flex-wrap items-start justify-between gap-x-8 gap-y-4 p-6 md:pointer-events-none md:absolute md:top-0 md:inset-x-0 md:gap-y-5 md:p-10">
<div>
<p className="type-label text-accent mb-4">
<p className="type-label text-accent mb-2 md:mb-4">
Municipal Election · City of Toronto
</p>
<h1 className="font-sans font-medium leading-[0.98] tracking-[-0.04em] text-[clamp(2.25rem,4.5vw,3.75rem)] max-w-[16ch] text-balance">
<h1 className="font-sans font-medium leading-[0.98] tracking-[-0.04em] text-[clamp(1.875rem,7vw,3.75rem)] md:text-[clamp(2.25rem,4.5vw,3.75rem)] max-w-[16ch] text-balance">
{name} pledged to vote.
</h1>
</div>

{/* ── Share + join in ──────────────────────────────── */}
<div className="pointer-events-auto flex flex-col items-start gap-4 max-w-[24rem]">
<p className="type-label-sm text-text-secondary">
<div className="md:pointer-events-auto flex flex-wrap items-center gap-x-4 gap-y-3 md:flex-col md:items-start md:gap-4 md:max-w-[24rem]">
<p className="type-label-sm text-text-secondary w-full md:w-auto">
Toronto votes Monday, October 26
</p>
<Link
href="/toronto/vote/2026/pledge"
className="group/btn inline-flex items-center gap-3 type-button text-bg bg-accent px-6 py-3.5 transition-colors hover:bg-auburn-700"
className="group/btn inline-flex items-center gap-3 type-button text-bg bg-accent px-5 py-3 md:px-6 md:py-3.5 transition-colors hover:bg-auburn-700"
>
Pledge to vote too
<ArrowRight className="size-4 shrink-0 transition-transform group-hover/btn:translate-x-0.5" />
Expand All @@ -90,15 +92,15 @@ export default function SharedPledgeClient({ name }: { name: string }) {
</div>

{/* ── Overlaid footer ────────────────────────────────── */}
<div className="pointer-events-none absolute bottom-0 inset-x-0 flex flex-wrap items-center justify-between gap-4 p-6 md:px-10 md:py-8">
<div className="order-3 shrink-0 flex flex-wrap items-center justify-between gap-3 p-6 md:pointer-events-none md:absolute md:bottom-0 md:inset-x-0 md:gap-4 md:px-10 md:py-8">
<Link
href="/toronto/vote/2026"
className="pointer-events-auto group/btn inline-flex items-center gap-3 type-button text-dark border-2 border-dark px-6 py-3.5 transition-colors hover:bg-dark hover:text-bg"
className="pointer-events-auto group/btn inline-flex items-center gap-3 type-button text-dark border-2 border-dark px-5 py-3 md:px-6 md:py-3.5 transition-colors hover:bg-dark hover:text-bg"
>
<ArrowLeft className="size-4 shrink-0 transition-transform group-hover/btn:-translate-x-0.5" />
Back to the election tracker
</Link>
<p className="type-label-sm text-text-secondary">
<p className="type-label-sm text-text-secondary text-balance">
Drag the stamp around · Polls open 10:00 a.m. – 8:00 p.m.
</p>
</div>
Expand Down
21 changes: 17 additions & 4 deletions src/components/elections/PledgeButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ export function PledgeButton({
}) {
const config = getElection(election);
const router = useRouter();
const [open, setOpen] = useState(false);
const [firstName, setFirstName] = useState("");
const [lastName, setLastName] = useState("");
const [email, setEmail] = useState("");
Expand Down Expand Up @@ -83,8 +84,7 @@ export function PledgeButton({
// No pledge recorded. Either the postal code couldn't be judged — say so
// and let them fix it, since they may well live here — or they're outside
// the jurisdiction, in which case they're subscribed but not pledged, and
// the landing page explains and invites them to explore. Keep the button
// disabled while we navigate.
// the landing page explains and invites them to explore.
if (data.outsideRegion) {
if (data.unverifiedPostalCode) {
setError(
Expand All @@ -97,6 +97,12 @@ export function PledgeButton({
source,
election: config.slug,
});
// Close this modal ourselves and drop the loading state: the redirect
// is often to the page we're already on (the landing page owns the
// ResidencyModal), so this component isn't unmounted by the navigation
// and would otherwise sit disabled on "Recording…" forever.
setLoading(false);
setOpen(false);
router.push(`${config.basePath}?residency=outside`);
return;
}
Expand All @@ -116,12 +122,19 @@ export function PledgeButton({

return (
<Dialog.Root
onOpenChange={(open) => {
if (open)
open={open}
onOpenChange={(next) => {
setOpen(next);
if (next) {
posthog.capture("pledge_modal_opened", {
source,
election: config.slug,
});
} else {
// never reopen onto a stale "Recording…" button or an old error
setLoading(false);
setError(null);
}
}}
>
<Dialog.Trigger className={className}>{children}</Dialog.Trigger>
Expand Down
Loading