From ed11785c3f9ba3f7eb87be7a8ea2f5f764393125 Mon Sep 17 00:00:00 2001 From: yigitdot Date: Sat, 9 May 2026 03:59:55 +0300 Subject: [PATCH 1/6] feat(hero): polish CTAs and rework page-load animation (#45) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - drop opacity-55 dim on the `source` CTA so it matches its siblings at rest - replace letter-spacing jiggle on a.underline-brutal hover with a whisper-green underline + brighter arrow nudge — no layout reflow - rework brutal-rise: 28px translate, 850ms expo-out, restaggered cascade Co-Authored-By: Claude --- app/globals.css | 23 ++++++++++++----------- components/site/Hero.tsx | 2 +- 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/app/globals.css b/app/globals.css index 02db03b..810ba0b 100644 --- a/app/globals.css +++ b/app/globals.css @@ -121,7 +121,7 @@ body { @keyframes brutal-rise { from { opacity: 0; - transform: translateY(18px); + transform: translateY(28px); } to { opacity: 1; @@ -131,26 +131,26 @@ body { .rise { opacity: 0; - animation: brutal-rise 700ms cubic-bezier(0.2, 0.8, 0.2, 1) forwards; + animation: brutal-rise 850ms cubic-bezier(0.16, 1, 0.3, 1) forwards; } .rise-0 { animation-delay: 0ms; } .rise-1 { - animation-delay: 120ms; + animation-delay: 80ms; } .rise-2 { - animation-delay: 180ms; + animation-delay: 200ms; } .rise-3 { - animation-delay: 260ms; + animation-delay: 320ms; } .rise-4 { - animation-delay: 340ms; + animation-delay: 460ms; } .rise-5 { - animation-delay: 420ms; + animation-delay: 600ms; } @media (prefers-reduced-motion: reduce) { @@ -234,19 +234,20 @@ a.underline-brutal { text-decoration: none; padding-bottom: 2px; border-bottom: 1px solid currentColor; - transition: letter-spacing 220ms ease; + transition: border-bottom-color 260ms cubic-bezier(0.2, 0.8, 0.2, 1); } a.underline-brutal:hover { - letter-spacing: 0.02em; + border-bottom-color: var(--whisper); } a.underline-brutal .arrow { - transition: transform 220ms cubic-bezier(0.2, 0.8, 0.2, 1); + transition: transform 260ms cubic-bezier(0.16, 1, 0.3, 1); } a.underline-brutal:hover .arrow { - transform: translateX(4px); + transform: translateX(6px); + color: var(--whisper); } /* --- hero terminal ----------------------------------------------------- diff --git a/components/site/Hero.tsx b/components/site/Hero.tsx index 9a7fbb1..29aaee6 100644 --- a/components/site/Hero.tsx +++ b/components/site/Hero.tsx @@ -71,7 +71,7 @@ export function Hero() { From 55ee37e13efa168f6aacb794a4af63637298241d Mon Sep 17 00:00:00 2001 From: yigitdot Date: Sat, 9 May 2026 04:00:12 +0300 Subject: [PATCH 2/6] feat(sitemap): emit sitemap index referencing docs.decdn.org (#38) - replace the single-URL urlset with a sitemap index at /sitemap.xml that points at /sitemap-pages.xml (marketing) and the Mintlify-served https://docs.decdn.org/sitemap.xml so docs URLs are discoverable from the marketing-site sitemap as docs grow - new route handlers use force-static + GET to emit static XML under output: "export"; robots.txt already points at /sitemap.xml so no change needed there Cross-host sitemap entries require both hosts to be verified in Google Search Console for the index references to be honored. Co-Authored-By: Claude --- app/sitemap-pages.xml/route.ts | 19 +++++++++++++++++++ app/sitemap.ts | 9 --------- app/sitemap.xml/route.ts | 23 +++++++++++++++++++++++ 3 files changed, 42 insertions(+), 9 deletions(-) create mode 100644 app/sitemap-pages.xml/route.ts delete mode 100644 app/sitemap.ts create mode 100644 app/sitemap.xml/route.ts diff --git a/app/sitemap-pages.xml/route.ts b/app/sitemap-pages.xml/route.ts new file mode 100644 index 0000000..19fc8b9 --- /dev/null +++ b/app/sitemap-pages.xml/route.ts @@ -0,0 +1,19 @@ +import { SITE_URL } from "@/lib/links"; + +// Required by Next 16 under `output: "export"` for route handlers that emit +// static files at build time. GET-only is the only verb supported in export. +export const dynamic = "force-static"; + +// Marketing-site URLs. Add a new entry here when shipping a new page; +// the sibling `sitemap.xml` route serves the index that references this file. +const BODY = ` + + ${SITE_URL} + +`; + +export function GET() { + return new Response(BODY, { + headers: { "Content-Type": "application/xml" }, + }); +} diff --git a/app/sitemap.ts b/app/sitemap.ts deleted file mode 100644 index 9666446..0000000 --- a/app/sitemap.ts +++ /dev/null @@ -1,9 +0,0 @@ -import type { MetadataRoute } from "next"; -import { SITE_URL } from "@/lib/links"; - -// Required by Next 16 under `output: "export"` for metadata route handlers. -export const dynamic = "force-static"; - -export default function sitemap(): MetadataRoute.Sitemap { - return [{ url: SITE_URL }]; -} diff --git a/app/sitemap.xml/route.ts b/app/sitemap.xml/route.ts new file mode 100644 index 0000000..c6de56c --- /dev/null +++ b/app/sitemap.xml/route.ts @@ -0,0 +1,23 @@ +import { SITE_URL, links } from "@/lib/links"; + +// Required by Next 16 under `output: "export"` for route handlers that emit +// static files at build time. GET-only is the only verb supported in export. +export const dynamic = "force-static"; + +// Sitemap index. Marketing URLs live in `sitemap-pages.xml`; docs are served +// by Mintlify at `docs.decdn.org/sitemap.xml`. Cross-host entries require +// both hosts to be verified in Search Console for Google to honor them. +const DOCS_SITEMAP = `${new URL(links.docs).origin}/sitemap.xml`; + +const BODY = ` + + ${SITE_URL}sitemap-pages.xml + ${DOCS_SITEMAP} + +`; + +export function GET() { + return new Response(BODY, { + headers: { "Content-Type": "application/xml" }, + }); +} From eae0acfe1e2b009e26c55cc679632fd951ec2db6 Mon Sep 17 00:00:00 2001 From: yigitdot Date: Sat, 9 May 2026 04:10:31 +0300 Subject: [PATCH 3/6] fix(hero): transition arrow color in sync with the nudge The hover state on .underline-brutal .arrow flips color to whisper-green but the arrow's transition rule only covered `transform`, so the color snapped while the translateX animated. Extend the rule to cover `color` on the same 260ms expoOut curve. Flagged in PR #46 review (Copilot, Gemini). Co-Authored-By: Claude --- app/globals.css | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/globals.css b/app/globals.css index 810ba0b..195a766 100644 --- a/app/globals.css +++ b/app/globals.css @@ -242,7 +242,9 @@ a.underline-brutal:hover { } a.underline-brutal .arrow { - transition: transform 260ms cubic-bezier(0.16, 1, 0.3, 1); + transition: + transform 260ms cubic-bezier(0.16, 1, 0.3, 1), + color 260ms cubic-bezier(0.16, 1, 0.3, 1); } a.underline-brutal:hover .arrow { From 3ce619d4e0c8de3c44da4a9cff4a291d4c24cf6f Mon Sep 17 00:00:00 2001 From: yigitdot Date: Sat, 9 May 2026 04:18:01 +0300 Subject: [PATCH 4/6] chore(sitemap): trim redundant header comments MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Drop WHAT/process narration on the two sitemap route handlers per AGENTS.md ("default to no comments; only add when WHY is non-obvious"): - sitemap.xml/route.ts: keep only the cross-host Search Console caveat - sitemap-pages.xml/route.ts: drop the maintenance comment entirely The force-static headers stay — they capture a real Next 16 export constraint that's not inferrable from the code. Flagged in PR #46 review. Co-Authored-By: Claude --- app/sitemap-pages.xml/route.ts | 2 -- app/sitemap.xml/route.ts | 5 ++--- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/app/sitemap-pages.xml/route.ts b/app/sitemap-pages.xml/route.ts index 19fc8b9..cefc622 100644 --- a/app/sitemap-pages.xml/route.ts +++ b/app/sitemap-pages.xml/route.ts @@ -4,8 +4,6 @@ import { SITE_URL } from "@/lib/links"; // static files at build time. GET-only is the only verb supported in export. export const dynamic = "force-static"; -// Marketing-site URLs. Add a new entry here when shipping a new page; -// the sibling `sitemap.xml` route serves the index that references this file. const BODY = ` ${SITE_URL} diff --git a/app/sitemap.xml/route.ts b/app/sitemap.xml/route.ts index c6de56c..f44c056 100644 --- a/app/sitemap.xml/route.ts +++ b/app/sitemap.xml/route.ts @@ -4,9 +4,8 @@ import { SITE_URL, links } from "@/lib/links"; // static files at build time. GET-only is the only verb supported in export. export const dynamic = "force-static"; -// Sitemap index. Marketing URLs live in `sitemap-pages.xml`; docs are served -// by Mintlify at `docs.decdn.org/sitemap.xml`. Cross-host entries require -// both hosts to be verified in Search Console for Google to honor them. +// Cross-host sitemap entries are only honored by Google when both hosts are +// verified in Search Console. const DOCS_SITEMAP = `${new URL(links.docs).origin}/sitemap.xml`; const BODY = ` From 66572f08a1bba0a6a67b1cc909681098f9900268 Mon Sep 17 00:00:00 2001 From: yigitdot Date: Sat, 9 May 2026 05:17:44 +0300 Subject: [PATCH 5/6] feat(seo): add Service + FAQPage JSON-LD and tighten Organization schema MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add Service schema (CDN, provider → Organization, areaServed: Worldwide); omit offers since per-MB USDC pricing isn't a clean ISO-4217 Offer. Add FAQPage sourced from a new FAQ_ITEMS export in components/site/Faq.tsx, so rendered DOM and structured data share one source of truth. Tighten Organization.description to describe the entity, not the product (was the marketing tagline shared with WebSite). Switch Organization.logo from icon.svg to /d_logo.png — Google's structured-data spec lists JPG/PNG/GIF only. Co-Authored-By: Claude Opus 4.7 (1M context) --- app/layout.tsx | 47 +++++++++++++++++++++++++------- components/site/Faq.tsx | 60 ++++++++++++++++++++--------------------- 2 files changed, 68 insertions(+), 39 deletions(-) diff --git a/app/layout.tsx b/app/layout.tsx index 24483d8..4edbfb1 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -2,6 +2,7 @@ import type { Metadata, Viewport } from "next"; import { Geist } from "next/font/google"; import { links, SITE_URL, INDEXABLE } from "@/lib/links"; import { Chrome } from "@/components/site/Chrome"; +import { FAQ_ITEMS } from "@/components/site/Faq"; import { ScrollReveal } from "@/components/site/ScrollReveal"; import "./globals.css"; @@ -46,6 +47,8 @@ export const viewport: Viewport = { const ORG_ID = `${SITE_URL}#organization`; const SITE_ID = `${SITE_URL}#website`; +const SERVICE_ID = `${SITE_URL}#service`; +const FAQ_ID = `${SITE_URL}#faq`; const organizationSchema = { "@context": "https://schema.org", @@ -53,8 +56,9 @@ const organizationSchema = { "@id": ORG_ID, name: "deCDN", url: SITE_URL, - logo: `${SITE_URL}icon.svg`, - description: DESCRIPTION, + logo: `${SITE_URL}d_logo.png`, + description: + "Organization developing deCDN, a peer-to-peer content delivery network with per-megabyte settlement in USDC.", sameAs: [links.github, links.x], }; @@ -68,6 +72,29 @@ const websiteSchema = { publisher: { "@id": ORG_ID }, }; +const serviceSchema = { + "@context": "https://schema.org", + "@type": "Service", + "@id": SERVICE_ID, + name: "deCDN", + url: SITE_URL, + serviceType: "Content Delivery Network", + provider: { "@id": ORG_ID }, + areaServed: "Worldwide", + description: DESCRIPTION, +}; + +const faqSchema = { + "@context": "https://schema.org", + "@type": "FAQPage", + "@id": FAQ_ID, + mainEntity: FAQ_ITEMS.map(({ q, a }) => ({ + "@type": "Question", + name: q, + acceptedAnswer: { "@type": "Answer", text: a }, + })), +}; + // Inline JSON in ` in any field // can't close the tag and create an XSS sink. < decodes back to `<` in // every JSON parser, so structured-data consumers are unaffected. @@ -85,13 +112,15 @@ export default function RootLayout({ className={`${geistSans.variable} h-full motion-safe:scroll-smooth`} > - {[organizationSchema, websiteSchema].map((schema) => ( -