Bake Venezuela relief campaign into appeal surfaces
Resolve the terremoto-venezuela campaign (naddr) directly in the relief banner, popup, and dedicated page, turning each into an info + donation hybrid: - New useVenezuelaReliefCampaign hook + VenezuelaReliefGoal component surface the campaign's live raised/goal progress in all three surfaces. - Donate CTAs deep-link to the campaign naddr; on the dedicated page the full campaign detail (story, donate panel, ledger, comments) is embedded below the appeal hero, and "Donate to relief" smooth-scrolls to the donate panel and flashes a highlight ring (mobile inline + desktop sidebar). Scroll margin clears the sticky nav. - Drop the "Raise funds for Venezuela" button; shrink the page hero. - Add the goalOf locale key across all 16 locales.
This commit is contained in:
@@ -1,10 +1,10 @@
|
||||
import { Link } from 'react-router-dom';
|
||||
import { Trans, useTranslation } from 'react-i18next';
|
||||
import { HeartHandshake, PlusCircle, Share2 } from 'lucide-react';
|
||||
import { HeartHandshake, Share2 } from 'lucide-react';
|
||||
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { HeroBanner } from '@/components/HeroBanner';
|
||||
import { StartCampaignLink } from '@/components/StartCampaignLink';
|
||||
import { VenezuelaReliefGoal } from '@/components/VenezuelaReliefGoal';
|
||||
import { useShareOrigin } from '@/hooks/useShareOrigin';
|
||||
import { useToast } from '@/hooks/useToast';
|
||||
import { shareOrCopy } from '@/lib/share';
|
||||
@@ -40,11 +40,9 @@ const VENEZUELA_RELIEF_BANNER_IMAGES = VENEZUELA_RELIEF_IMAGES;
|
||||
* - A large display headline ("Venezuela needs you") with the final
|
||||
* word painted inside a solid brand-orange highlighter block — the
|
||||
* same idiom as the home hero's "unstoppable".
|
||||
* - Two unmistakable calls to action: **Donate to relief** deep-links
|
||||
* to the Venezuela-filtered campaign browse (`/campaigns?country=VE`)
|
||||
* so donors land straight on fundable relief campaigns; **Raise funds
|
||||
* for Venezuela** routes organizers through `StartCampaignLink`
|
||||
* (auth-gated) to publish a new fundraiser.
|
||||
* - A primary call to action — **Donate to relief** — deep-links
|
||||
* straight to the baked-in relief campaign (its naddr) so donors land
|
||||
* on the campaign's detail page, plus a **Share** action.
|
||||
*
|
||||
* Not dismissible by design — while the appeal is active it stays put
|
||||
* for every visitor (product decision). When the response winds down,
|
||||
@@ -162,6 +160,10 @@ export function VenezuelaReliefBanner({ className }: { className?: string }) {
|
||||
{t('campaigns.home.venezuelaRelief.body')}
|
||||
</p>
|
||||
|
||||
{/* Live fundraising progress for the baked-in relief campaign —
|
||||
the info half of this info + donation hybrid. */}
|
||||
<VenezuelaReliefGoal variant="overlay" className="mt-7" />
|
||||
|
||||
<div className="mt-7 flex flex-col sm:flex-row flex-wrap gap-3">
|
||||
{/* Primary CTA — donate to Venezuela-filtered relief campaigns */}
|
||||
<Button
|
||||
@@ -175,20 +177,7 @@ export function VenezuelaReliefBanner({ className }: { className?: string }) {
|
||||
</Link>
|
||||
</Button>
|
||||
|
||||
{/* Secondary CTA — start a relief fundraiser (auth-gated) */}
|
||||
<Button
|
||||
variant="outline"
|
||||
size="lg"
|
||||
asChild
|
||||
className="rounded-full h-12 px-6 text-base border-white/40 bg-white/10 text-white hover:bg-white/20 hover:text-white hover:border-white/60 [&_svg]:size-[18px]"
|
||||
>
|
||||
<StartCampaignLink>
|
||||
<PlusCircle className="mr-2" />
|
||||
{t('campaigns.home.venezuelaRelief.startCampaign')}
|
||||
</StartCampaignLink>
|
||||
</Button>
|
||||
|
||||
{/* Share: native share sheet or copy link to the relief page */}
|
||||
{/* Secondary CTA — share: native share sheet or copy link */}
|
||||
<Button
|
||||
variant="outline"
|
||||
size="lg"
|
||||
|
||||
@@ -0,0 +1,107 @@
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { Progress } from '@/components/ui/progress';
|
||||
import { Skeleton } from '@/components/ui/skeleton';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { formatCampaignAmount, formatUsdGoal } from '@/lib/formatCampaignAmount';
|
||||
import { useVenezuelaReliefCampaign } from '@/hooks/useVenezuelaReliefCampaign';
|
||||
|
||||
interface VenezuelaReliefGoalProps {
|
||||
/**
|
||||
* `overlay` — light text + translucent track, for the dark hero photo
|
||||
* backgrounds (banner + page). `card` — foreground text + muted track,
|
||||
* for the popup's light card surface.
|
||||
*/
|
||||
variant?: 'overlay' | 'card';
|
||||
className?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Live fundraising readout for the baked-in Venezuela relief campaign —
|
||||
* the raised total, goal, donation count, and a progress bar. Shared by
|
||||
* the home hero ({@link VenezuelaReliefBanner}), the session popup
|
||||
* ({@link VenezuelaReliefPopup}), and the dedicated page
|
||||
* ({@link VenezuelaReliefPage}) so each surface is an info + donation
|
||||
* hybrid backed by the same numbers as the campaign detail page.
|
||||
*
|
||||
* Renders nothing once loaded if the campaign can't be resolved or has no
|
||||
* goal/raised data — the surrounding appeal copy and CTAs stand on their
|
||||
* own, so this never leaves an empty box.
|
||||
*/
|
||||
export function VenezuelaReliefGoal({ variant = 'overlay', className }: VenezuelaReliefGoalProps) {
|
||||
const { t } = useTranslation();
|
||||
const { isLoading, raisedSats, goalUsd, donationCount, percent, btcPrice } =
|
||||
useVenezuelaReliefCampaign();
|
||||
|
||||
const isOverlay = variant === 'overlay';
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div className={cn('w-full max-w-md space-y-2', className)}>
|
||||
<Skeleton className={cn('h-6 w-40', isOverlay && 'bg-white/20')} />
|
||||
<Skeleton className={cn('h-2 w-full', isOverlay && 'bg-white/20')} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// Nothing meaningful to show — let the appeal copy carry the surface.
|
||||
if (raisedSats <= 0 && !goalUsd) return null;
|
||||
|
||||
const raisedLabel = formatCampaignAmount(raisedSats, btcPrice);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
'w-full max-w-md space-y-2',
|
||||
isOverlay
|
||||
? 'drop-shadow-[0_1px_8px_rgba(0,0,0,0.6)]'
|
||||
: 'rounded-lg border border-border bg-muted/40 p-3',
|
||||
className,
|
||||
)}
|
||||
>
|
||||
<div className="flex items-baseline justify-between gap-3">
|
||||
<span
|
||||
className={cn(
|
||||
'text-xl sm:text-2xl font-bold tracking-tight',
|
||||
isOverlay ? 'text-white' : 'text-foreground',
|
||||
)}
|
||||
>
|
||||
{raisedLabel}
|
||||
<span
|
||||
className={cn(
|
||||
'ml-1.5 text-sm font-normal',
|
||||
isOverlay ? 'text-white/70' : 'text-muted-foreground',
|
||||
)}
|
||||
>
|
||||
{t('campaignsDetail.raised')}
|
||||
</span>
|
||||
</span>
|
||||
{goalUsd ? (
|
||||
<span
|
||||
className={cn(
|
||||
'shrink-0 text-xs sm:text-sm',
|
||||
isOverlay ? 'text-white/70' : 'text-muted-foreground',
|
||||
)}
|
||||
>
|
||||
{t('campaigns.home.venezuelaRelief.goalOf', { amount: formatUsdGoal(goalUsd) })}
|
||||
</span>
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
{percent !== undefined && (
|
||||
<Progress
|
||||
value={percent}
|
||||
className={cn('h-2', isOverlay ? 'bg-white/25' : 'bg-foreground/15')}
|
||||
/>
|
||||
)}
|
||||
|
||||
{donationCount > 0 && (
|
||||
<p className={cn('text-xs', isOverlay ? 'text-white/70' : 'text-muted-foreground')}>
|
||||
{t('campaignsDetail.donationCount', { count: donationCount })}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default VenezuelaReliefGoal;
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
DialogTitle,
|
||||
} from '@/components/ui/dialog';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { VenezuelaReliefGoal } from '@/components/VenezuelaReliefGoal';
|
||||
import { useShareOrigin } from '@/hooks/useShareOrigin';
|
||||
import { useToast } from '@/hooks/useToast';
|
||||
import { shareOrCopy } from '@/lib/share';
|
||||
@@ -123,6 +124,9 @@ export function VenezuelaReliefPopup() {
|
||||
{t('campaigns.home.venezuelaRelief.popupBody')}
|
||||
</DialogDescription>
|
||||
|
||||
{/* Live fundraising progress — the info half of the hybrid. */}
|
||||
<VenezuelaReliefGoal variant="card" className="mt-4" />
|
||||
|
||||
<DialogFooter className="mt-5 sm:flex-row sm:justify-start sm:gap-2">
|
||||
<Button asChild className="rounded-full font-semibold [&_svg]:size-[18px]">
|
||||
<Link to={VENEZUELA_DONATE_PATH} onClick={() => setOpen(false)}>
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
import { useCampaign } from '@/hooks/useCampaign';
|
||||
import { useCampaignDonations } from '@/hooks/useCampaignDonations';
|
||||
import { useBtcPrice } from '@/hooks/useBtcPrice';
|
||||
import { satsToUsd } from '@/lib/formatCampaignAmount';
|
||||
import {
|
||||
VENEZUELA_RELIEF_CAMPAIGN_IDENTIFIER,
|
||||
VENEZUELA_RELIEF_CAMPAIGN_PUBKEY,
|
||||
} from '@/lib/venezuelaRelief';
|
||||
|
||||
/** Live fundraising snapshot for the baked-in Venezuela relief campaign. */
|
||||
export interface VenezuelaReliefGoalData {
|
||||
/** True while the campaign or its donation totals are still loading. */
|
||||
isLoading: boolean;
|
||||
/** Sats raised so far (cumulative amount ever sent to the address). */
|
||||
raisedSats: number;
|
||||
/** USD equivalent of {@link raisedSats}, or `undefined` if no BTC price. */
|
||||
raisedUsd: number | undefined;
|
||||
/** Campaign goal in whole USD (per NIP.md Kind 33863), if set. */
|
||||
goalUsd: number | undefined;
|
||||
/** Number of distinct donations, if known. */
|
||||
donationCount: number;
|
||||
/** Goal completion 0–100, clamped, or `undefined` if no goal/price. */
|
||||
percent: number | undefined;
|
||||
/** Live BTC/USD price, for sats↔USD formatting at the call site. */
|
||||
btcPrice: number | undefined;
|
||||
/** True once we have a real campaign + non-loading totals to show. */
|
||||
hasData: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves the baked-in Venezuela relief campaign (`terremoto-venezuela`,
|
||||
* kind 33863) and its live donation totals, shared by the home hero
|
||||
* ({@link VenezuelaReliefBanner}), the session popup
|
||||
* ({@link VenezuelaReliefPopup}), and the dedicated page
|
||||
* ({@link VenezuelaReliefPage}) so all three render the same goal/progress.
|
||||
*
|
||||
* Donation totals come from the same on-chain balance lookup the campaign
|
||||
* detail page uses ({@link useCampaignDonations}); no polling here, since
|
||||
* these surfaces are ambient rather than the primary donate destination.
|
||||
*/
|
||||
export function useVenezuelaReliefCampaign(): VenezuelaReliefGoalData {
|
||||
const { data: campaign, isLoading: campaignLoading } = useCampaign({
|
||||
pubkey: VENEZUELA_RELIEF_CAMPAIGN_PUBKEY,
|
||||
identifier: VENEZUELA_RELIEF_CAMPAIGN_IDENTIFIER,
|
||||
});
|
||||
const { data: btcPrice } = useBtcPrice();
|
||||
const { data: stats, isLoading: statsLoading } = useCampaignDonations(
|
||||
campaign ?? undefined,
|
||||
);
|
||||
|
||||
const raisedSats = stats?.totalSats ?? 0;
|
||||
const raisedUsd = satsToUsd(raisedSats, btcPrice);
|
||||
const goalUsd = campaign?.goalUsd;
|
||||
const donationCount = stats?.receipts?.length ?? 0;
|
||||
|
||||
const percent =
|
||||
goalUsd && goalUsd > 0 && raisedUsd !== undefined
|
||||
? Math.min(100, Math.round((raisedUsd / goalUsd) * 100))
|
||||
: undefined;
|
||||
|
||||
const isLoading = campaignLoading || statsLoading;
|
||||
|
||||
return {
|
||||
isLoading,
|
||||
raisedSats,
|
||||
raisedUsd,
|
||||
goalUsd,
|
||||
donationCount,
|
||||
percent,
|
||||
btcPrice,
|
||||
hasData: !!campaign && !statsLoading,
|
||||
};
|
||||
}
|
||||
@@ -799,3 +799,37 @@
|
||||
.hero-arc-flow { animation: none; stroke-dasharray: none; }
|
||||
.hero-node-pulse { animation: none; opacity: 0.7; }
|
||||
}
|
||||
|
||||
/* Transient highlight flash for the Venezuela relief donate panel /
|
||||
section, triggered by the page's "Donate to relief" CTA. A pulsing
|
||||
ring drawn with box-shadow (not `ring`) so it shows even when the
|
||||
target clips its overflow, and an offset gap so it reads as a frame. */
|
||||
.relief-donate-flash {
|
||||
animation: relief-donate-flash 2s ease-out;
|
||||
border-radius: 1rem;
|
||||
}
|
||||
|
||||
@keyframes relief-donate-flash {
|
||||
0%, 70% {
|
||||
box-shadow:
|
||||
0 0 0 3px hsl(var(--background)),
|
||||
0 0 0 7px hsl(var(--primary) / 0.75),
|
||||
0 0 22px 7px hsl(var(--primary) / 0.45);
|
||||
}
|
||||
100% {
|
||||
box-shadow:
|
||||
0 0 0 3px hsl(var(--background) / 0),
|
||||
0 0 0 7px hsl(var(--primary) / 0),
|
||||
0 0 22px 7px hsl(var(--primary) / 0);
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
/* No pulse — hold a steady ring for the lifetime of the class. */
|
||||
.relief-donate-flash {
|
||||
animation: none;
|
||||
box-shadow:
|
||||
0 0 0 3px hsl(var(--background)),
|
||||
0 0 0 7px hsl(var(--primary) / 0.75);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,8 +14,28 @@
|
||||
/** Public route for the dedicated relief page (shareable). */
|
||||
export const VENEZUELA_RELIEF_PATH = '/venezuela-relief';
|
||||
|
||||
/** Deep-link to the Venezuela-filtered campaign browse for donors. */
|
||||
export const VENEZUELA_DONATE_PATH = '/campaigns?country=VE';
|
||||
/**
|
||||
* The specific relief campaign baked into the appeal — `terremoto-venezuela`
|
||||
* (kind 33863). The hero, popup, and page resolve this `(pubkey, identifier)`
|
||||
* coordinate to surface the campaign's live raised/goal progress, turning
|
||||
* each surface into an info + donation hybrid. The donate CTA
|
||||
* ({@link VENEZUELA_DONATE_PATH}) deep-links to this same campaign's naddr.
|
||||
*/
|
||||
export const VENEZUELA_RELIEF_CAMPAIGN_PUBKEY =
|
||||
'7a303d62d6c9d2f0cabe2ca713a392f3ec4b1fab815ea60b79fe15aca274c71c';
|
||||
|
||||
/** The relief campaign's `d` tag (slug). */
|
||||
export const VENEZUELA_RELIEF_CAMPAIGN_IDENTIFIER = 'terremoto-venezuela';
|
||||
|
||||
/**
|
||||
* Deep-link straight to the specific Venezuela earthquake relief campaign
|
||||
* (`terremoto-venezuela`, kind 33863). Baked in as the donate CTA target
|
||||
* for the hero, popup, and dedicated page so donors land on the campaign's
|
||||
* detail page rather than a filtered browse. NIP-19 identifiers route at
|
||||
* the URL root (`/:nip19`), handled by `NIP19Page`.
|
||||
*/
|
||||
export const VENEZUELA_DONATE_PATH =
|
||||
'/naddr1qvzqqqyygupzq73s843ddjwj7r9tut98zw3e9ulvfv06hq275c9hnls44j38f3cuqqfhgetjwfjk6mm5dukhvetwv4a82etvvykrc9yj';
|
||||
|
||||
/**
|
||||
* Ordered set of news photographs from the Venezuela earthquake that
|
||||
|
||||
+2
-1
@@ -940,7 +940,8 @@
|
||||
"seoDescription": "ضرب زلزال قويّ شمال فنزويلا. تبرّع بالبيتكوين مباشرة إلى حملات الإغاثة على الأرض عبر Agora. لا منصة، لا حافظ، ولا إذن مطلوب.",
|
||||
"pageEyebrow": "نداء طوارئ",
|
||||
"pageHow": "كيف يساعد تبرّعك",
|
||||
"pageHowBody": "Agora غير حافظ: يذهب البيتكوين الخاص بك مباشرة من محفظتك إلى عنوان حملة الإغاثة. لا منصة تقتطع حصة، ولا حافظ يحمل الأموال، ولا أحد يحتاج إلى إذن ليعطي أو يأخذ. كل حملة أدناه يديرها أشخاص يستجيبون للزلزال على الأرض."
|
||||
"pageHowBody": "Agora غير حافظ: يذهب البيتكوين الخاص بك مباشرة من محفظتك إلى عنوان حملة الإغاثة. لا منصة تقتطع حصة، ولا حافظ يحمل الأموال، ولا أحد يحتاج إلى إذن ليعطي أو يأخذ. كل حملة أدناه يديرها أشخاص يستجيبون للزلزال على الأرض.",
|
||||
"goalOf": "من هدف {{amount}}"
|
||||
},
|
||||
"whyDifferent": {
|
||||
"eyebrow": "لماذا {{appName}}",
|
||||
|
||||
+2
-1
@@ -1476,7 +1476,8 @@
|
||||
"seoDescription": "A powerful earthquake has struck northern Venezuela. Donate Bitcoin directly to on-the-ground relief campaigns on Agora. No platform, no custodian, no permission required.",
|
||||
"pageEyebrow": "Emergency appeal",
|
||||
"pageHow": "How your donation helps",
|
||||
"pageHowBody": "Agora is non-custodial: your Bitcoin goes straight from your wallet to a relief campaign's address. No platform takes a cut, no custodian holds the funds, and no one needs permission to give or receive. Every campaign below is run by people responding to the quake on the ground."
|
||||
"pageHowBody": "Agora is non-custodial: your Bitcoin goes straight from your wallet to a relief campaign's address. No platform takes a cut, no custodian holds the funds, and no one needs permission to give or receive. Every campaign below is run by people responding to the quake on the ground.",
|
||||
"goalOf": "of {{amount}} goal"
|
||||
},
|
||||
"whyDifferent": {
|
||||
"eyebrow": "Why {{appName}}",
|
||||
|
||||
+2
-1
@@ -940,7 +940,8 @@
|
||||
"seoDescription": "Un fuerte terremoto ha sacudido el norte de Venezuela. Dona Bitcoin directamente a las campañas de ayuda sobre el terreno en Agora. Sin plataforma, sin custodio, sin permiso requerido.",
|
||||
"pageEyebrow": "Llamado de emergencia",
|
||||
"pageHow": "Cómo ayuda tu donación",
|
||||
"pageHowBody": "Agora no custodia los fondos: tu Bitcoin va directo desde tu monedero a la dirección de una campaña de ayuda. Ninguna plataforma se lleva una comisión, ningún custodio retiene los fondos y nadie necesita permiso para dar o recibir. Cada campaña a continuación está gestionada por personas que responden al terremoto sobre el terreno."
|
||||
"pageHowBody": "Agora no custodia los fondos: tu Bitcoin va directo desde tu monedero a la dirección de una campaña de ayuda. Ninguna plataforma se lleva una comisión, ningún custodio retiene los fondos y nadie necesita permiso para dar o recibir. Cada campaña a continuación está gestionada por personas que responden al terremoto sobre el terreno.",
|
||||
"goalOf": "de {{amount}} objetivo"
|
||||
},
|
||||
"whyDifferent": {
|
||||
"eyebrow": "Por qué {{appName}}",
|
||||
|
||||
+2
-1
@@ -940,7 +940,8 @@
|
||||
"seoDescription": "زمینلرزهای ویرانگر شمال Venezuela را لرزانده است. در Agora بیتکوین را مستقیماً به کمپینهای امدادرسانی در محل اهدا کنید. بدون پلتفرم، بدون امانتدار، بدون نیاز به اجازه.",
|
||||
"pageEyebrow": "فراخوان اضطراری",
|
||||
"pageHow": "کمک شما چگونه یاری میرساند",
|
||||
"pageHowBody": "Agora غیرامانی است: بیتکوین شما مستقیماً از کیفپولتان به نشانی کمپین امدادرسانی میرود. هیچ پلتفرمی سهمی برنمیدارد، هیچ امانتداری وجوه را نگه نمیدارد، و هیچکس برای دادن یا گرفتن به اجازه نیاز ندارد. هر کمپین زیر را افرادی اداره میکنند که در محل به زلزله واکنش نشان میدهند."
|
||||
"pageHowBody": "Agora غیرامانی است: بیتکوین شما مستقیماً از کیفپولتان به نشانی کمپین امدادرسانی میرود. هیچ پلتفرمی سهمی برنمیدارد، هیچ امانتداری وجوه را نگه نمیدارد، و هیچکس برای دادن یا گرفتن به اجازه نیاز ندارد. هر کمپین زیر را افرادی اداره میکنند که در محل به زلزله واکنش نشان میدهند.",
|
||||
"goalOf": "از هدف {{amount}}"
|
||||
},
|
||||
"whyDifferent": {
|
||||
"eyebrow": "چرا {{appName}}",
|
||||
|
||||
+2
-1
@@ -1382,7 +1382,8 @@
|
||||
"seoDescription": "Un puissant séisme a frappé le nord du Venezuela. Faites un don en Bitcoin directement aux campagnes de secours sur le terrain via Agora. Aucune plateforme, aucun dépositaire, aucune permission requise.",
|
||||
"pageEyebrow": "Appel d'urgence",
|
||||
"pageHow": "Comment votre don aide",
|
||||
"pageHowBody": "Agora est non dépositaire : votre Bitcoin va directement de votre portefeuille à l'adresse d'une campagne de secours. Aucune plateforme ne prélève de commission, aucun dépositaire ne détient les fonds et personne n'a besoin de permission pour donner ou recevoir. Chaque campagne ci-dessous est menée par des personnes qui répondent au séisme sur le terrain."
|
||||
"pageHowBody": "Agora est non dépositaire : votre Bitcoin va directement de votre portefeuille à l'adresse d'une campagne de secours. Aucune plateforme ne prélève de commission, aucun dépositaire ne détient les fonds et personne n'a besoin de permission pour donner ou recevoir. Chaque campagne ci-dessous est menée par des personnes qui répondent au séisme sur le terrain.",
|
||||
"goalOf": "sur un objectif de {{amount}}"
|
||||
},
|
||||
"whyDifferent": {
|
||||
"eyebrow": "Pourquoi {{appName}}",
|
||||
|
||||
+2
-1
@@ -1386,7 +1386,8 @@
|
||||
"seoDescription": "उत्तरी Venezuela में एक भीषण भूकंप आया है। Agora पर ज़मीन पर चल रहे राहत अभियानों को सीधे Bitcoin दान करें। कोई प्लेटफ़ॉर्म नहीं, कोई कस्टोडियन नहीं, अनुमति की ज़रूरत नहीं।",
|
||||
"pageEyebrow": "आपातकालीन अपील",
|
||||
"pageHow": "आपका दान कैसे मदद करता है",
|
||||
"pageHowBody": "Agora नॉन-कस्टोडियल है: आपका Bitcoin सीधे आपके वॉलेट से किसी राहत अभियान के पते पर जाता है। कोई प्लेटफ़ॉर्म हिस्सा नहीं काटता, कोई कस्टोडियन धन नहीं रखता, और देने या पाने के लिए किसी को अनुमति की ज़रूरत नहीं। नीचे दिया गया हर अभियान ज़मीन पर भूकंप से जूझ रहे लोगों द्वारा चलाया जा रहा है।"
|
||||
"pageHowBody": "Agora नॉन-कस्टोडियल है: आपका Bitcoin सीधे आपके वॉलेट से किसी राहत अभियान के पते पर जाता है। कोई प्लेटफ़ॉर्म हिस्सा नहीं काटता, कोई कस्टोडियन धन नहीं रखता, और देने या पाने के लिए किसी को अनुमति की ज़रूरत नहीं। नीचे दिया गया हर अभियान ज़मीन पर भूकंप से जूझ रहे लोगों द्वारा चलाया जा रहा है।",
|
||||
"goalOf": "{{amount}} के लक्ष्य में से"
|
||||
},
|
||||
"whyDifferent": {
|
||||
"eyebrow": "क्यों {{appName}}",
|
||||
|
||||
+2
-1
@@ -1386,7 +1386,8 @@
|
||||
"seoDescription": "Gempa bumi dahsyat telah mengguncang Venezuela bagian utara. Donasikan Bitcoin langsung ke kampanye bantuan di lapangan melalui Agora. Tanpa platform, tanpa kustodian, tanpa perlu izin.",
|
||||
"pageEyebrow": "Seruan darurat",
|
||||
"pageHow": "Bagaimana donasi Anda membantu",
|
||||
"pageHowBody": "Agora bersifat non-kustodial: Bitcoin Anda langsung mengalir dari dompet Anda ke alamat kampanye bantuan. Tidak ada platform yang mengambil potongan, tidak ada kustodian yang menahan dana, dan tidak ada yang perlu izin untuk memberi atau menerima. Setiap kampanye di bawah ini dijalankan oleh orang-orang yang merespons gempa di lapangan."
|
||||
"pageHowBody": "Agora bersifat non-kustodial: Bitcoin Anda langsung mengalir dari dompet Anda ke alamat kampanye bantuan. Tidak ada platform yang mengambil potongan, tidak ada kustodian yang menahan dana, dan tidak ada yang perlu izin untuk memberi atau menerima. Setiap kampanye di bawah ini dijalankan oleh orang-orang yang merespons gempa di lapangan.",
|
||||
"goalOf": "dari target {{amount}}"
|
||||
},
|
||||
"whyDifferent": {
|
||||
"eyebrow": "Mengapa {{appName}}",
|
||||
|
||||
+2
-1
@@ -940,7 +940,8 @@
|
||||
"seoDescription": "រញ្ជួយដីដ៏ខ្លាំងមួយបានវាយប្រហារភាគខាងជើងនៃ Venezuela។ បរិច្ចាគ Bitcoin ដោយផ្ទាល់ទៅកាន់យុទ្ធនាការសង្គ្រោះនៅនឹងកន្លែងលើ Agora។ គ្មានវេទិកា គ្មានអ្នកថែរក្សា គ្មានការអនុញ្ញាតចាំបាច់ឡើយ។",
|
||||
"pageEyebrow": "ការអំពាវនាវបន្ទាន់",
|
||||
"pageHow": "ការបរិច្ចាគរបស់អ្នកជួយយ៉ាងដូចម្ដេច",
|
||||
"pageHowBody": "Agora មិនថែរក្សាមូលនិធិទេ៖ Bitcoin របស់អ្នកទៅដោយផ្ទាល់ពីកាបូបរបស់អ្នកទៅកាន់អាសយដ្ឋានរបស់យុទ្ធនាការសង្គ្រោះ។ គ្មានវេទិកាកាត់កម្រៃ គ្មានអ្នកថែរក្សាកាន់មូលនិធិ ហើយគ្មាននរណាម្នាក់ត្រូវការការអនុញ្ញាតដើម្បីផ្ដល់ ឬទទួលឡើយ។ រាល់យុទ្ធនាការខាងក្រោមនេះ ត្រូវបានដំណើរការដោយមនុស្សដែលកំពុងឆ្លើយតបនឹងរញ្ជួយដីនៅនឹងកន្លែង។"
|
||||
"pageHowBody": "Agora មិនថែរក្សាមូលនិធិទេ៖ Bitcoin របស់អ្នកទៅដោយផ្ទាល់ពីកាបូបរបស់អ្នកទៅកាន់អាសយដ្ឋានរបស់យុទ្ធនាការសង្គ្រោះ។ គ្មានវេទិកាកាត់កម្រៃ គ្មានអ្នកថែរក្សាកាន់មូលនិធិ ហើយគ្មាននរណាម្នាក់ត្រូវការការអនុញ្ញាតដើម្បីផ្ដល់ ឬទទួលឡើយ។ រាល់យុទ្ធនាការខាងក្រោមនេះ ត្រូវបានដំណើរការដោយមនុស្សដែលកំពុងឆ្លើយតបនឹងរញ្ជួយដីនៅនឹងកន្លែង។",
|
||||
"goalOf": "នៃគោលដៅ {{amount}}"
|
||||
},
|
||||
"whyDifferent": {
|
||||
"eyebrow": "ហេតុអ្វី {{appName}}",
|
||||
|
||||
+2
-1
@@ -940,7 +940,8 @@
|
||||
"seoDescription": "یوې زورورې زلزلې د Venezuela شمال ولړزاوه. په Agora کې مستقیماً پر ځمکه فعالو مرستندویه کمپاینونو ته Bitcoin ډالۍ کړئ. هیڅ پلتفارم نشته، هیڅ حافظ نشته، هیڅ اجازې ته اړتیا نشته.",
|
||||
"pageEyebrow": "بیړنۍ غوښتنه",
|
||||
"pageHow": "ستاسو ډالۍ څنګه مرسته کوي",
|
||||
"pageHowBody": "Agora غیر حافظوي ده: ستاسو Bitcoin مستقیماً ستاسو له کیفپيسې څخه د مرستندویه کمپاین پتې ته ځي. هیڅ پلتفارم برخه نه اخلي، هیڅ حافظ پيسې نه ساتي، او هیڅوک د ورکولو یا ترلاسه کولو لپاره اجازې ته اړتیا نه لري. لاندې هر کمپاین د هغو خلکو لخوا چلیږي چې پر ځمکه د زلزلې غبرګون ښیي."
|
||||
"pageHowBody": "Agora غیر حافظوي ده: ستاسو Bitcoin مستقیماً ستاسو له کیفپيسې څخه د مرستندویه کمپاین پتې ته ځي. هیڅ پلتفارم برخه نه اخلي، هیڅ حافظ پيسې نه ساتي، او هیڅوک د ورکولو یا ترلاسه کولو لپاره اجازې ته اړتیا نه لري. لاندې هر کمپاین د هغو خلکو لخوا چلیږي چې پر ځمکه د زلزلې غبرګون ښیي.",
|
||||
"goalOf": "د {{amount}} هدف څخه"
|
||||
},
|
||||
"whyDifferent": {
|
||||
"eyebrow": "ولې {{appName}}",
|
||||
|
||||
+2
-1
@@ -1382,7 +1382,8 @@
|
||||
"seoDescription": "Um forte terremoto atingiu o norte da Venezuela. Doe Bitcoin diretamente para as campanhas de ajuda no local através do Agora. Sem plataforma, sem custodiante, sem necessidade de permissão.",
|
||||
"pageEyebrow": "Apelo de emergência",
|
||||
"pageHow": "Como sua doação ajuda",
|
||||
"pageHowBody": "O Agora é não custodial: seu Bitcoin vai direto da sua carteira para o endereço de uma campanha de ajuda. Nenhuma plataforma fica com uma parte, nenhum custodiante segura os fundos e ninguém precisa de permissão para doar ou receber. Cada campanha abaixo é conduzida por pessoas que respondem ao terremoto no local."
|
||||
"pageHowBody": "O Agora é não custodial: seu Bitcoin vai direto da sua carteira para o endereço de uma campanha de ajuda. Nenhuma plataforma fica com uma parte, nenhum custodiante segura os fundos e ninguém precisa de permissão para doar ou receber. Cada campanha abaixo é conduzida por pessoas que respondem ao terremoto no local.",
|
||||
"goalOf": "de {{amount}} da meta"
|
||||
},
|
||||
"whyDifferent": {
|
||||
"eyebrow": "Por que o {{appName}}",
|
||||
|
||||
+2
-1
@@ -1382,7 +1382,8 @@
|
||||
"seoDescription": "Мощное землетрясение обрушилось на север Венесуэлы. Жертвуйте Bitcoin напрямую кампаниям помощи на месте через Agora. Без платформы, без хранителя, без разрешений.",
|
||||
"pageEyebrow": "Экстренный призыв",
|
||||
"pageHow": "Как помогает ваше пожертвование",
|
||||
"pageHowBody": "Agora не хранит ваши средства: ваш Bitcoin идёт напрямую из вашего кошелька на адрес кампании помощи. Платформа не берёт комиссию, хранитель не держит средства, и никому не нужно разрешение, чтобы давать или получать. Каждая кампания ниже ведётся людьми, которые реагируют на землетрясение на месте."
|
||||
"pageHowBody": "Agora не хранит ваши средства: ваш Bitcoin идёт напрямую из вашего кошелька на адрес кампании помощи. Платформа не берёт комиссию, хранитель не держит средства, и никому не нужно разрешение, чтобы давать или получать. Каждая кампания ниже ведётся людьми, которые реагируют на землетрясение на месте.",
|
||||
"goalOf": "из {{amount}} цели"
|
||||
},
|
||||
"whyDifferent": {
|
||||
"eyebrow": "Почему {{appName}}",
|
||||
|
||||
+2
-1
@@ -940,7 +940,8 @@
|
||||
"seoDescription": "Kudengenyeka kwenyika kune simba kwakarova kuchamhembe kweVenezuela. Ipa Bitcoin yakananga kumishandirapamwe yekubatsira iri panzvimbo paAgora. Hapana puratifomu, hapana muchengeti, hapana mvumo inodiwa.",
|
||||
"pageEyebrow": "Chikumbiro chekukurumidza",
|
||||
"pageHow": "Kuti chipo chako chinobatsira sei",
|
||||
"pageHowBody": "Agora haichengeti mari: Bitcoin yako inonanga kubva muwallet yako kuenda kukero yemushandirapamwe wekubatsira. Hapana puratifomu inotora chikamu, hapana muchengeti anobata mari, uye hapana anoda mvumo yokupa kana kugamuchira. Mushandirapamwe wose uri pasi apa unotungamirirwa nevanhu vari kupindura kudengenyeka iri panzvimbo."
|
||||
"pageHowBody": "Agora haichengeti mari: Bitcoin yako inonanga kubva muwallet yako kuenda kukero yemushandirapamwe wekubatsira. Hapana puratifomu inotora chikamu, hapana muchengeti anobata mari, uye hapana anoda mvumo yokupa kana kugamuchira. Mushandirapamwe wose uri pasi apa unotungamirirwa nevanhu vari kupindura kudengenyeka iri panzvimbo.",
|
||||
"goalOf": "kubva pa{{amount}} chinangwa"
|
||||
},
|
||||
"whyDifferent": {
|
||||
"eyebrow": "Sei {{appName}}",
|
||||
|
||||
+2
-1
@@ -1386,7 +1386,8 @@
|
||||
"seoDescription": "Tetemeko kubwa la ardhi limeikumba kaskazini mwa Venezuela. Changia Bitcoin moja kwa moja kwa kampeni za misaada zilizoko eneo la tukio kwenye Agora. Hakuna jukwaa, hakuna mtunzaji, hakuna ruhusa inayohitajika.",
|
||||
"pageEyebrow": "Wito wa dharura",
|
||||
"pageHow": "Jinsi mchango wako unavyosaidia",
|
||||
"pageHowBody": "Agora haitunzi fedha: Bitcoin yako huenda moja kwa moja kutoka kwenye pochi yako hadi anwani ya kampeni ya misaada. Hakuna jukwaa linalochukua sehemu, hakuna mtunzaji anayeshikilia fedha, na hakuna mtu anayehitaji ruhusa kutoa au kupokea. Kila kampeni iliyo hapa chini inaendeshwa na watu wanaokabiliana na tetemeko hili eneo la tukio."
|
||||
"pageHowBody": "Agora haitunzi fedha: Bitcoin yako huenda moja kwa moja kutoka kwenye pochi yako hadi anwani ya kampeni ya misaada. Hakuna jukwaa linalochukua sehemu, hakuna mtunzaji anayeshikilia fedha, na hakuna mtu anayehitaji ruhusa kutoa au kupokea. Kila kampeni iliyo hapa chini inaendeshwa na watu wanaokabiliana na tetemeko hili eneo la tukio.",
|
||||
"goalOf": "kati ya lengo la {{amount}}"
|
||||
},
|
||||
"whyDifferent": {
|
||||
"eyebrow": "Kwa nini {{appName}}",
|
||||
|
||||
+2
-1
@@ -1386,7 +1386,8 @@
|
||||
"seoDescription": "Kuzey Venezuela'yı şiddetli bir deprem vurdu. Agora üzerinden doğrudan sahadaki yardım kampanyalarına Bitcoin bağışlayın. Platform yok, emanetçi yok, izin gerekmiyor.",
|
||||
"pageEyebrow": "Acil çağrı",
|
||||
"pageHow": "Bağışınız nasıl yardımcı olur",
|
||||
"pageHowBody": "Agora emanetsizdir: Bitcoin'iniz cüzdanınızdan doğrudan bir yardım kampanyasının adresine gider. Hiçbir platform pay almaz, hiçbir emanetçi parayı tutmaz ve vermek ya da almak için kimsenin iznine ihtiyaç yoktur. Aşağıdaki her kampanya, sahada depreme yanıt veren insanlar tarafından yürütülüyor."
|
||||
"pageHowBody": "Agora emanetsizdir: Bitcoin'iniz cüzdanınızdan doğrudan bir yardım kampanyasının adresine gider. Hiçbir platform pay almaz, hiçbir emanetçi parayı tutmaz ve vermek ya da almak için kimsenin iznine ihtiyaç yoktur. Aşağıdaki her kampanya, sahada depreme yanıt veren insanlar tarafından yürütülüyor.",
|
||||
"goalOf": "{{amount}} hedefinden"
|
||||
},
|
||||
"whyDifferent": {
|
||||
"eyebrow": "Neden {{appName}}",
|
||||
|
||||
@@ -944,7 +944,8 @@
|
||||
"seoDescription": "一場強烈地震襲擊了 Venezuela 北部。在 Agora 上將 Bitcoin 直接捐給當地一線的救援活動。沒有平台、沒有託管方,也不需要任何許可。",
|
||||
"pageEyebrow": "緊急募款",
|
||||
"pageHow": "你的捐款如何發揮作用",
|
||||
"pageHowBody": "Agora 是非託管的:你的 Bitcoin 會從你的錢包直接送到救援活動的位址。沒有平台從中抽成,沒有託管方握住資金,給予或接受都不需要任何人的許可。以下每一項活動,都是由在當地一線回應這場地震的人們所發起。"
|
||||
"pageHowBody": "Agora 是非託管的:你的 Bitcoin 會從你的錢包直接送到救援活動的位址。沒有平台從中抽成,沒有託管方握住資金,給予或接受都不需要任何人的許可。以下每一項活動,都是由在當地一線回應這場地震的人們所發起。",
|
||||
"goalOf": "目標 {{amount}}"
|
||||
},
|
||||
"whyDifferent": {
|
||||
"eyebrow": "為什麼選擇 {{appName}}",
|
||||
|
||||
+2
-1
@@ -940,7 +940,8 @@
|
||||
"seoDescription": "一场强烈地震袭击了委内瑞拉北部。在 Agora 上把 Bitcoin 直接捐给灾区一线的救援活动。无需平台、无需托管方、无需任何许可。",
|
||||
"pageEyebrow": "紧急呼吁",
|
||||
"pageHow": "你的捐款如何帮助灾区",
|
||||
"pageHowBody": "Agora 是非托管的:你的 Bitcoin 从你的钱包直接送达救援活动的地址。没有平台抽成,没有托管方掌控资金,无论是给予还是接收都无需任何许可。下方的每一个活动都由在灾区一线响应地震的人们运作。"
|
||||
"pageHowBody": "Agora 是非托管的:你的 Bitcoin 从你的钱包直接送达救援活动的地址。没有平台抽成,没有托管方掌控资金,无论是给予还是接收都无需任何许可。下方的每一个活动都由在灾区一线响应地震的人们运作。",
|
||||
"goalOf": "目标 {{amount}}"
|
||||
},
|
||||
"whyDifferent": {
|
||||
"eyebrow": "为什么选 {{appName}}",
|
||||
|
||||
@@ -405,7 +405,7 @@ function CampaignDetailContent({ campaign }: { campaign: ParsedCampaign }) {
|
||||
<div className="relative max-w-6xl mx-auto px-5 sm:px-6 lg:px-0 py-6 lg:py-10">
|
||||
<div className="lg:flex lg:gap-8 lg:items-start">
|
||||
{/* Mobile-only inline donate card */}
|
||||
<div className="lg:hidden mb-6">{donateColumn}</div>
|
||||
<div id="campaign-donate" className="lg:hidden mb-6 scroll-mt-[4.5rem]">{donateColumn}</div>
|
||||
|
||||
{/* Main article column */}
|
||||
<div className="flex-1 min-w-0 space-y-8">
|
||||
@@ -527,7 +527,7 @@ function CampaignDetailContent({ campaign }: { campaign: ParsedCampaign }) {
|
||||
content behind a second scrollbar and visually clips the
|
||||
bottom of the card. */}
|
||||
<aside className="hidden lg:block lg:w-[360px] lg:shrink-0 lg:self-start">
|
||||
<div className="lg:sticky lg:top-4">{donateColumn}</div>
|
||||
<div id="campaign-donate-desktop" className="lg:sticky lg:top-4 scroll-mt-20">{donateColumn}</div>
|
||||
</aside>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,17 +1,19 @@
|
||||
import { useSeoMeta } from '@unhead/react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { Trans, useTranslation } from 'react-i18next';
|
||||
import { Bitcoin, HandHeart, HeartHandshake, PlusCircle, ShieldCheck, Share2 } from 'lucide-react';
|
||||
import { useRef } from 'react';
|
||||
import { useTranslation, Trans } from 'react-i18next';
|
||||
import { HeartHandshake, Share2 } from 'lucide-react';
|
||||
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { HeroBanner } from '@/components/HeroBanner';
|
||||
import { StartCampaignLink } from '@/components/StartCampaignLink';
|
||||
import { VenezuelaReliefGoal } from '@/components/VenezuelaReliefGoal';
|
||||
import { CampaignDetailPage } from '@/pages/CampaignDetailPage';
|
||||
import { useAppContext } from '@/hooks/useAppContext';
|
||||
import { useShareOrigin } from '@/hooks/useShareOrigin';
|
||||
import { useToast } from '@/hooks/useToast';
|
||||
import { shareOrCopy } from '@/lib/share';
|
||||
import {
|
||||
VENEZUELA_DONATE_PATH,
|
||||
VENEZUELA_RELIEF_CAMPAIGN_IDENTIFIER,
|
||||
VENEZUELA_RELIEF_CAMPAIGN_PUBKEY,
|
||||
VENEZUELA_RELIEF_IMAGES,
|
||||
VENEZUELA_RELIEF_PATH,
|
||||
} from '@/lib/venezuelaRelief';
|
||||
@@ -19,17 +21,22 @@ import {
|
||||
/**
|
||||
* Dedicated, shareable Venezuela earthquake relief page (`/venezuela-relief`).
|
||||
*
|
||||
* Carries the same appeal as the home-page hero
|
||||
* ({@link VenezuelaReliefBanner}) and the session popup
|
||||
* ({@link VenezuelaReliefPopup}): same photo gallery, headline, body, and
|
||||
* donate / fundraise CTAs, all sourced from the shared
|
||||
* `campaigns.home.venezuelaRelief.*` locale keys. Existing as its own URL
|
||||
* lets the appeal be shared directly (social posts, messages, QR) and
|
||||
* gives the popup / hero a "Learn more" destination.
|
||||
* The loud appeal hero (headline, body, live goal progress, donate /
|
||||
* fundraise / share CTAs) sits on top, sourced from the shared
|
||||
* `campaigns.home.venezuelaRelief.*` locale keys. Beneath it, the baked-in
|
||||
* relief campaign (`terremoto-venezuela`, kind 33863) is embedded in full
|
||||
* via {@link CampaignDetailPage} — the same story, donate panel, ledger,
|
||||
* and comments a donor sees at the campaign's naddr — so this URL is a
|
||||
* self-contained info + donation page that can be shared directly (social
|
||||
* posts, messages, QR).
|
||||
*
|
||||
* Routed under the wide FundraiserLayout so the hero spans the viewport
|
||||
* like /about. Remove the route in AppRouter when the relief response
|
||||
* winds down.
|
||||
*
|
||||
* Note: the embedded {@link CampaignDetailPage} sets its own SEO meta from
|
||||
* the campaign event, so it intentionally wins over the appeal copy here —
|
||||
* shared links surface the live campaign's title and cover.
|
||||
*/
|
||||
export function VenezuelaReliefPage() {
|
||||
const { t } = useTranslation();
|
||||
@@ -37,6 +44,10 @@ export function VenezuelaReliefPage() {
|
||||
const shareOrigin = useShareOrigin();
|
||||
const { toast } = useToast();
|
||||
|
||||
// Timer for clearing the transient donate-panel highlight (see
|
||||
// handleScrollToCampaign).
|
||||
const highlightTimer = useRef<ReturnType<typeof setTimeout> | undefined>(undefined);
|
||||
|
||||
useSeoMeta({
|
||||
title: `${t('campaigns.home.venezuelaRelief.seoTitle')} | ${config.appName}`,
|
||||
description: t('campaigns.home.venezuelaRelief.seoDescription'),
|
||||
@@ -53,6 +64,38 @@ export function VenezuelaReliefPage() {
|
||||
}
|
||||
};
|
||||
|
||||
// "Donate to relief" scrolls down to the embedded campaign rather than
|
||||
// navigating away — this page *is* the campaign. The donate panel (QR +
|
||||
// pay buttons) is rendered twice inside CampaignDetailPage: an inline
|
||||
// card at the top of the body on mobile (`#campaign-donate`) and a
|
||||
// sticky sidebar on desktop (`#campaign-donate-desktop`). We scroll to
|
||||
// and flash whichever one is actually laid out, so the real donate
|
||||
// controls come into focus on both breakpoints.
|
||||
//
|
||||
// The donate panel lives inside the embedded CampaignDetailPage, so we
|
||||
// can't drive its highlight through this component's React state; we
|
||||
// toggle a utility class on the DOM node directly instead. The ring
|
||||
// classes (and their reduced-motion fallback) live in index.css under
|
||||
// `.relief-donate-flash`.
|
||||
const handleScrollToCampaign = () => {
|
||||
const isVisible = (el: HTMLElement | null) => !!el && el.getClientRects().length > 0;
|
||||
const mobile = document.getElementById('campaign-donate');
|
||||
const desktop = document.getElementById('campaign-donate-desktop');
|
||||
const target =
|
||||
(isVisible(mobile) && mobile) ||
|
||||
(isVisible(desktop) && desktop) ||
|
||||
document.getElementById('venezuela-relief-campaign');
|
||||
if (!target) return;
|
||||
|
||||
target.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
||||
|
||||
if (highlightTimer.current) clearTimeout(highlightTimer.current);
|
||||
target.classList.add('relief-donate-flash');
|
||||
highlightTimer.current = setTimeout(() => {
|
||||
target.classList.remove('relief-donate-flash');
|
||||
}, 2000);
|
||||
};
|
||||
|
||||
return (
|
||||
<main>
|
||||
{/* Hero: same loud disaster-appeal treatment as the home banner. */}
|
||||
@@ -70,14 +113,14 @@ export function VenezuelaReliefPage() {
|
||||
className="absolute inset-0 bg-[linear-gradient(to_right,rgba(0,0,0,0.72)_0%,rgba(0,0,0,0.5)_35%,rgba(0,0,0,0)_70%)] rtl:bg-[linear-gradient(to_left,rgba(0,0,0,0.72)_0%,rgba(0,0,0,0.5)_35%,rgba(0,0,0,0)_70%)]"
|
||||
/>
|
||||
|
||||
<div className="relative max-w-7xl mx-auto px-4 sm:px-6 py-20 sm:py-28 min-h-[70dvh] flex flex-col justify-center">
|
||||
<div className="relative max-w-7xl mx-auto px-4 sm:px-6 py-14 sm:py-16 flex flex-col justify-center">
|
||||
<div className="max-w-3xl">
|
||||
<p className="text-xs font-semibold uppercase tracking-widest text-primary/90 mb-4">
|
||||
{t('campaigns.home.venezuelaRelief.pageEyebrow')}
|
||||
</p>
|
||||
<h1
|
||||
id="venezuela-relief-page-title"
|
||||
className="font-display italic font-normal uppercase tracking-wide leading-[0.92] text-5xl sm:text-7xl lg:text-8xl drop-shadow-[0_2px_12px_rgba(0,0,0,0.45)]"
|
||||
className="font-display italic font-normal uppercase tracking-wide leading-[0.92] text-4xl sm:text-7xl lg:text-8xl drop-shadow-[0_2px_12px_rgba(0,0,0,0.45)]"
|
||||
style={{ WebkitTextStroke: '0.018em currentColor' }}
|
||||
>
|
||||
<Trans
|
||||
@@ -92,32 +135,21 @@ export function VenezuelaReliefPage() {
|
||||
/>
|
||||
</h1>
|
||||
|
||||
<p className="mt-7 text-base sm:text-lg lg:text-xl text-white max-w-xl leading-relaxed drop-shadow-[0_1px_8px_rgba(0,0,0,0.6)]">
|
||||
<p className="mt-7 text-sm sm:text-lg lg:text-xl text-white max-w-xl leading-relaxed drop-shadow-[0_1px_8px_rgba(0,0,0,0.6)]">
|
||||
{t('campaigns.home.venezuelaRelief.body')}
|
||||
</p>
|
||||
|
||||
{/* Live fundraising progress for the baked-in relief campaign. */}
|
||||
<VenezuelaReliefGoal variant="overlay" className="mt-7" />
|
||||
|
||||
<div className="mt-7 flex flex-col sm:flex-row flex-wrap gap-3">
|
||||
<Button
|
||||
size="lg"
|
||||
asChild
|
||||
onClick={handleScrollToCampaign}
|
||||
className="rounded-full text-white font-semibold text-base h-12 px-7 [&_svg]:size-[18px] motion-safe:transition-colors"
|
||||
>
|
||||
<Link to={VENEZUELA_DONATE_PATH}>
|
||||
<HeartHandshake className="mr-2" />
|
||||
{t('campaigns.home.venezuelaRelief.donate')}
|
||||
</Link>
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
variant="outline"
|
||||
size="lg"
|
||||
asChild
|
||||
className="rounded-full h-12 px-6 text-base border-white/40 bg-white/10 text-white hover:bg-white/20 hover:text-white hover:border-white/60 [&_svg]:size-[18px]"
|
||||
>
|
||||
<StartCampaignLink>
|
||||
<PlusCircle className="mr-2" />
|
||||
{t('campaigns.home.venezuelaRelief.startCampaign')}
|
||||
</StartCampaignLink>
|
||||
<HeartHandshake className="mr-2" />
|
||||
{t('campaigns.home.venezuelaRelief.donate')}
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
@@ -138,58 +170,18 @@ export function VenezuelaReliefPage() {
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* How your donation helps: the non-custodial pitch. */}
|
||||
<section className="bg-background">
|
||||
<div className="max-w-3xl mx-auto px-4 sm:px-6 py-16 sm:py-20">
|
||||
<h2 className="text-2xl sm:text-3xl font-bold tracking-tight">
|
||||
{t('campaigns.home.venezuelaRelief.pageHow')}
|
||||
</h2>
|
||||
<p className="mt-4 text-base sm:text-lg text-muted-foreground leading-relaxed">
|
||||
{t('campaigns.home.venezuelaRelief.pageHowBody')}
|
||||
</p>
|
||||
|
||||
<div className="mt-8 grid gap-4 sm:grid-cols-3">
|
||||
<div className="rounded-xl border border-border bg-card p-5">
|
||||
<Bitcoin className="size-6 text-primary" aria-hidden="true" />
|
||||
<p className="mt-3 text-sm text-muted-foreground leading-relaxed">
|
||||
<Trans i18nKey="campaigns.home.whyDifferent.lede">
|
||||
Direct Bitcoin from donor to recipient.
|
||||
</Trans>
|
||||
</p>
|
||||
</div>
|
||||
<div className="rounded-xl border border-border bg-card p-5">
|
||||
<ShieldCheck className="size-6 text-primary" aria-hidden="true" />
|
||||
<p className="mt-3 text-sm text-muted-foreground leading-relaxed">
|
||||
{t('campaigns.home.venezuelaRelief.pageHowBody')}
|
||||
</p>
|
||||
</div>
|
||||
<div className="rounded-xl border border-border bg-card p-5">
|
||||
<HandHeart className="size-6 text-primary" aria-hidden="true" />
|
||||
<p className="mt-3 text-sm text-muted-foreground leading-relaxed">
|
||||
{t('campaigns.home.venezuelaRelief.body')}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-10 flex flex-col sm:flex-row flex-wrap gap-3">
|
||||
<Button asChild size="lg" className="rounded-full font-semibold [&_svg]:size-[18px]">
|
||||
<Link to={VENEZUELA_DONATE_PATH}>
|
||||
<HeartHandshake className="mr-2" />
|
||||
{t('campaigns.home.venezuelaRelief.donate')}
|
||||
</Link>
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="lg"
|
||||
onClick={handleShare}
|
||||
className="rounded-full [&_svg]:size-[18px]"
|
||||
>
|
||||
<Share2 className="mr-2" />
|
||||
{t('campaigns.home.venezuelaRelief.share')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
{/* The actual relief campaign, baked in: story, donate panel,
|
||||
ledger, and comments — the full detail UI for the campaign at
|
||||
VENEZUELA_DONATE_PATH. The "Donate to relief" CTA flashes a
|
||||
highlight ring on the donate panel (mobile) or this section
|
||||
(desktop) via the `.relief-donate-flash` class — see
|
||||
handleScrollToCampaign. */}
|
||||
<div id="venezuela-relief-campaign" className="scroll-mt-4 rounded-2xl">
|
||||
<CampaignDetailPage
|
||||
pubkey={VENEZUELA_RELIEF_CAMPAIGN_PUBKEY}
|
||||
identifier={VENEZUELA_RELIEF_CAMPAIGN_IDENTIFIER}
|
||||
/>
|
||||
</div>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user