feat(web+infra): polished front page, app at /, tracker at /status
- apps/web: redesigned landing (Hero/Scanner/HowItWorks/Features/Footer), honest live-vs-coming-soon badges, same-origin /api/scan, ember theme. - ecosystem.config.cjs: runnable — pyre-api/worker via `node --import tsx`, pyre-web via `next start`, fork mode, env wired. pm2 web+api verified online (api /health 200, scan 200, web 200). - infra/nginx/feedthepyre.com.conf: app at / (proxy :3000), API at /api (proxy :4000, prefix preserved), dev tracker at /status (static). - scripts/deploy-web.sh: sudo cutover (install vhost, nginx -t, reload, certbot --nginx --keep-until-expiring). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
68
apps/web/src/components/Features.tsx
Normal file
68
apps/web/src/components/Features.tsx
Normal file
@@ -0,0 +1,68 @@
|
||||
/**
|
||||
* Feature / status cards. HONEST about what is live vs coming soon — coming-soon
|
||||
* items carry a clear badge and must not imply they work today.
|
||||
* Server component (static content, no hooks).
|
||||
*/
|
||||
type Status = "live" | "partial" | "soon";
|
||||
|
||||
const FEATURES: ReadonlyArray<{
|
||||
title: string;
|
||||
body: string;
|
||||
status: Status;
|
||||
statusLabel: string;
|
||||
}> = [
|
||||
{
|
||||
title: "Scan & classify",
|
||||
body: "Read your SPL token accounts and conservatively classify every one. Unknown means skip.",
|
||||
status: "live",
|
||||
statusLabel: "Live",
|
||||
},
|
||||
{
|
||||
title: "Reclaim ATA rent",
|
||||
body: "The scan already estimates recoverable rent from empty accounts. One-click close to send that SOL back to you is next.",
|
||||
status: "partial",
|
||||
statusLabel: "Scan live · close soon",
|
||||
},
|
||||
{
|
||||
title: "Burn dead tokens",
|
||||
body: "Burn worthless leftover balances to zero, then close the emptied accounts to recover their rent.",
|
||||
status: "soon",
|
||||
statusLabel: "Phase 3",
|
||||
},
|
||||
{
|
||||
title: "Prometheus AI meme rebirth",
|
||||
body: "Turn burned remnants into an AI-generated meme Spawn for human-reviewed launch. Entertainment, not a promise of value.",
|
||||
status: "soon",
|
||||
statusLabel: "Coming soon",
|
||||
},
|
||||
];
|
||||
|
||||
function badgeClass(status: Status): string {
|
||||
if (status === "live") return "badge badge--live";
|
||||
if (status === "partial") return "badge badge--partial";
|
||||
return "badge badge--soon";
|
||||
}
|
||||
|
||||
export function Features() {
|
||||
return (
|
||||
<section className="features" aria-labelledby="features-heading">
|
||||
<h2 className="section-heading" id="features-heading">
|
||||
What's live, what's coming
|
||||
</h2>
|
||||
<div className="features__grid">
|
||||
{FEATURES.map(({ title, body, status, statusLabel }) => (
|
||||
<article
|
||||
key={title}
|
||||
className={`feature-card feature-card--${status}`}
|
||||
>
|
||||
<div className="feature-card__top">
|
||||
<h3 className="feature-card__title">{title}</h3>
|
||||
<span className={badgeClass(status)}>{statusLabel}</span>
|
||||
</div>
|
||||
<p className="feature-card__body">{body}</p>
|
||||
</article>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user