diff --git a/src/app/comparison/layout.jsx b/src/app/comparison/layout.jsx index b9d1c77..cec017f 100644 --- a/src/app/comparison/layout.jsx +++ b/src/app/comparison/layout.jsx @@ -1,4 +1,4 @@ import { MarketingPage } from '@/components/MarketingPage' -const Layout = ({ children }) => {children} +const Layout = ({ children }) => {children} export default Layout diff --git a/src/app/migrate/layout.jsx b/src/app/migrate/layout.jsx index b9d1c77..cec017f 100644 --- a/src/app/migrate/layout.jsx +++ b/src/app/migrate/layout.jsx @@ -1,4 +1,4 @@ import { MarketingPage } from '@/components/MarketingPage' -const Layout = ({ children }) => {children} +const Layout = ({ children }) => {children} export default Layout diff --git a/src/app/showcase/page.jsx b/src/app/showcase/page.jsx index b0a0c68..c8c162c 100644 --- a/src/app/showcase/page.jsx +++ b/src/app/showcase/page.jsx @@ -1,9 +1,6 @@ -'use client' - -import { useEffect, useState } from 'react' import Image from 'next/image' import Link from 'next/link' -import { formatNumber } from '@/lib/common' +import { ShowcaseGradient } from '@/components/ShowcaseGradient' import companies from './companies.json' import testimonials from '../../../utils/testimonials.json' @@ -30,44 +27,17 @@ const logoMap = { } export default function ShowcasePage() { - // Display the cached count if available; otherwise leave empty rather - // than show a stale fallback ("3500+" was years out of date). - const [githubStars, setGithubStars] = useState(null) - - useEffect(() => { - let cached = localStorage.getItem('githubStars') - if (cached) { - setGithubStars(formatNumber(Number(cached))) - return - } - // No cached value — fetch live so first-time visitors see a number. - fetch('https://api.github.com/repos/gofr-dev/gofr') - .then((r) => (r.ok ? r.json() : null)) - .then((d) => { - if (d?.watchers) { - setGithubStars(formatNumber(d.watchers)) - try { localStorage.setItem('githubStars', String(d.watchers)) } catch {} - } - }) - .catch(() => {}) - }, []) - return ( -
-
- {/* Hero */} +
+ +
+ {/* Hero — star count is already shown live in the header, + so we don't repeat it here (the cached client-side number + could lag the header by hours). */}

Used by engineers around the world

- {githubStars && ( -
- - - - {githubStars} stars on GitHub -
- )}
{/* Company Grid */} diff --git a/src/app/why-gofr/layout.jsx b/src/app/why-gofr/layout.jsx index b9d1c77..cec017f 100644 --- a/src/app/why-gofr/layout.jsx +++ b/src/app/why-gofr/layout.jsx @@ -1,4 +1,4 @@ import { MarketingPage } from '@/components/MarketingPage' -const Layout = ({ children }) => {children} +const Layout = ({ children }) => {children} export default Layout diff --git a/src/components/AnimatedTimelineItem.jsx b/src/components/AnimatedTimelineItem.jsx index 559a167..2f50722 100644 --- a/src/components/AnimatedTimelineItem.jsx +++ b/src/components/AnimatedTimelineItem.jsx @@ -2,17 +2,28 @@ import React, { useRef, useEffect, useState } from 'react'; const AnimatedTimelineItem = ({ date, title, description, imageSrc, isLeft }) => { const itemRef = useRef(null); - const [isVisible, setIsVisible] = useState(false); + // Default `isVisible: true` so the image renders fully visible + // for users with `prefers-reduced-motion: reduce`, for SSR/no-JS, + // and for any environment where the IntersectionObserver doesn't + // fire (e.g. Playwright fullPage screenshot capture). Without + // this, the image stayed at opacity 0 and looked like the + // timeline was missing half its photos. + const [isVisible, setIsVisible] = useState(true); const [isExiting, setIsExiting] = useState(false); useEffect(() => { + if (typeof window === 'undefined') return; + const reduce = window.matchMedia?.('(prefers-reduced-motion: reduce)').matches; + if (reduce) return; // already visible=true, skip the observer-driven fade + // Start hidden post-hydration so the entry animation can play. + setIsVisible(false); const observer = new IntersectionObserver( ([entry]) => { if (entry.isIntersecting) { - setIsVisible(true); - setIsExiting(false); + setIsVisible(true); + setIsExiting(false); } else { - setIsVisible(false); + setIsVisible(false); setIsExiting(true); } }, diff --git a/src/components/CompanyTrustedList.jsx b/src/components/CompanyTrustedList.jsx index 0b1952a..092e86c 100644 --- a/src/components/CompanyTrustedList.jsx +++ b/src/components/CompanyTrustedList.jsx @@ -35,11 +35,15 @@ export default function CompanyList() { />{' '} Developers at Companies Like: -
+ {/* 9 logos: pick column counts that divide evenly so we + don't end on a ragged orphan row. 3 cols at every + breakpoint (3+3+3) is more deliberate than the previous + 4+4+1 (mobile), 6+3 (sm), 5+4 (lg) ladder. */} +
{imageLink.map((item, index) => (
{item.name} +
+ {/* Density-matched section padding. Uniform `py-24` everywhere made the page feel padded-for-padding's-sake when section content was thin (a 100 px marquee strip, a 1-row logo wall). diff --git a/src/components/MarketingPage.jsx b/src/components/MarketingPage.jsx index 73db1ec..73524df 100644 --- a/src/components/MarketingPage.jsx +++ b/src/components/MarketingPage.jsx @@ -1,13 +1,26 @@ +import { ShowcaseGradient } from '@/components/ShowcaseGradient' + // Wrapper for the SEO-only Markdoc pages (/why-gofr, /comparison/*, // /migrate/*, /learn, /faq). It supplies the flex container DocsLayout // expects, but unlike DocsPage it does NOT render a left sidebar — these // pages aren't core navigation, they're SEO landing content. Right-rail // "On this page" TOC from DocsLayout still renders. Cross-links to the // other SEO pages live in the global footer. -export function MarketingPage({ children }) { +// +// `withGradient` adds the decorative orb layer (ShowcaseGradient). +// Opt-in because /faq and /learn are reading-mode reference pages +// where the gradient is just visual noise; /why-gofr, /comparison/*, +// /migrate/* are positioning surfaces where it belongs. +export function MarketingPage({ children, withGradient = false }) { return (
-
+
+ {withGradient && } {children}
diff --git a/src/components/Prose.jsx b/src/components/Prose.jsx index 1fe1d63..3a9db4d 100644 --- a/src/components/Prose.jsx +++ b/src/components/Prose.jsx @@ -16,13 +16,26 @@ export function Prose({ as, className, ...props }) { 'prose-a:font-semibold dark:prose-a:text-sky-400', // link underline 'prose-a:no-underline prose-a:shadow-[inset_0_-2px_0_0_var(--tw-prose-background,#fff),inset_0_calc(-1*(var(--tw-prose-underline-size,4px)+2px))_0_0_var(--tw-prose-underline,theme(colors.sky.300))] hover:prose-a:[--tw-prose-underline-size:6px] dark:[--tw-prose-background:theme(colors.slate.900)] dark:prose-a:shadow-[inset_0_calc(-1*var(--tw-prose-underline-size,2px))_0_0_var(--tw-prose-underline,theme(colors.sky.800))] dark:hover:prose-a:[--tw-prose-underline-size:6px]', - // pre - 'prose-pre:rounded-lg prose-pre:bg-slate-900 prose-pre:shadow-lg dark:prose-pre:bg-slate-800/60 dark:prose-pre:shadow-none dark:prose-pre:ring-1 dark:prose-pre:ring-slate-300/10', - // inline code - 'prose-code:rounded prose-code:bg-slate-100 prose-code:px-1.5 prose-code:py-0.5 prose-code:text-[0.8125em] prose-code:font-medium prose-code:before:content-none prose-code:after:content-none dark:prose-code:bg-slate-800 dark:prose-code:text-slate-300', + // pre — `overflow-x-auto` lets long lines scroll inside the + // block instead of widening the prose column (the latter + // forced page-level horizontal scroll on docs/configs etc. + // at narrow viewports). + 'prose-pre:overflow-x-auto prose-pre:rounded-lg prose-pre:bg-slate-900 prose-pre:shadow-lg dark:prose-pre:bg-slate-800/60 dark:prose-pre:shadow-none dark:prose-pre:ring-1 dark:prose-pre:ring-slate-300/10', + // inline code — `break-words` so long unbreakable tokens + // (env-var names, connection strings, fully-qualified Go + // identifiers) wrap instead of pushing the column wider + // than the viewport. + 'prose-code:rounded prose-code:bg-slate-100 prose-code:px-1.5 prose-code:py-0.5 prose-code:text-[0.8125em] prose-code:font-medium prose-code:break-words prose-code:before:content-none prose-code:after:content-none dark:prose-code:bg-slate-800 dark:prose-code:text-slate-300', // hr 'dark:prose-hr:border-slate-800', // tables + // Intentionally NOT applying `display: block` + horizontal + // scroll here. For reference pages like /docs/references/configs + // the page-level overflow at 375px is actually more useful than + // a per-table scroll: every column stays visible at once (small + // but scannable), versus hiding 2 of 4 columns behind a thin, + // discovery-hostile scrollbar. The trade-off favors information + // density on these reference surfaces. 'prose-table:text-sm', 'prose-thead:border-b prose-thead:border-slate-200 dark:prose-thead:border-slate-700', 'prose-th:py-2 prose-th:text-left prose-th:font-semibold prose-th:text-slate-900 dark:prose-th:text-slate-200', diff --git a/src/components/Reviews.jsx b/src/components/Reviews.jsx index 81d9d51..c132d79 100644 --- a/src/components/Reviews.jsx +++ b/src/components/Reviews.jsx @@ -9,9 +9,13 @@ export default function Testimonials() { {/*

*/} {/* User Testimonials*/} {/*

*/} -
+
{testimonials.map((testimonial, index) => ( -
+ // `block break-inside-avoid` keeps each card whole inside + // the CSS multi-column flow. Without it, a tall card gets + // split at the column boundary and the two halves render + // on top of each other on narrow viewports. +
diff --git a/src/components/Search.jsx b/src/components/Search.jsx index e34c0f9..37ff309 100644 --- a/src/components/Search.jsx +++ b/src/components/Search.jsx @@ -165,27 +165,47 @@ function HighlightQuery({ text, query }) { ) } +// Known-token map for breadcrumb/segment prettifying. Naive Title +// Case turned `gofr-vs-gin` into `Gofr Vs Gin` — wrong on both +// counts (brand mark is `GoFr`, comparator is lowercase `vs`) and +// `wrap-grpc` into `Wrap Grpc` instead of `Wrap gRPC`. Anything +// not in this map falls back to plain Title Case. +const PRETTY_TOKENS = { + gofr: 'GoFr', + gofrcli: 'GoFr CLI', + vs: 'vs', + grpc: 'gRPC', + graphql: 'GraphQL', + api: 'API', + cli: 'CLI', + http: 'HTTP', + https: 'HTTPS', + rest: 'REST', + faq: 'FAQ', +} + // Turn a path segment slug into a Title Case label. -// "wrap-grpc" → "Wrap Grpc", "from-fiber" → "From Fiber". -// Strict slug-to-words; preserves embedded acronyms only when they're already uppercase. +// "wrap-grpc" → "Wrap gRPC", "from-fiber" → "From Fiber", +// "gofr-vs-gin" → "GoFr vs Gin". function prettifySegment(seg) { if (!seg) return '' return seg .split(/[-_]/) - .map((w) => - w.length === 0 - ? '' - : w === w.toUpperCase() - ? w - : w.charAt(0).toUpperCase() + w.slice(1), - ) + .map((w) => { + if (w.length === 0) return '' + const known = PRETTY_TOKENS[w.toLowerCase()] + if (known) return known + // Already uppercase — preserve embedded acronyms. + if (w === w.toUpperCase()) return w + return w.charAt(0).toUpperCase() + w.slice(1) + }) .join(' ') } // Build a breadcrumb-shaped path label from a URL. // "/docs/datasources/mongodb" → "Datasources › Mongodb" -// "/docs/references/gofrcli/init" → "References › Gofrcli › Init" -// "/comparison/gofr-vs-gin" → "Comparison › Gofr Vs Gin" +// "/docs/references/gofrcli/init" → "References › GoFr CLI › Init" +// "/comparison/gofr-vs-gin" → "Comparison › GoFr vs Gin" function pathBreadcrumb(rawUrl) { if (!rawUrl) return '' let p = rawUrl.split('#')[0].split('?')[0] diff --git a/src/components/ShowcaseGradient.jsx b/src/components/ShowcaseGradient.jsx new file mode 100644 index 0000000..e6cb387 --- /dev/null +++ b/src/components/ShowcaseGradient.jsx @@ -0,0 +1,56 @@ +import Image from 'next/image' +import blurCyanImage from '@/images/blur-cyan.png' +import blurIndigoImage from '@/images/blur-indigo.png' + +// Decorative gradient layer for the framework's pitch / showcase +// surfaces — homepage, /why-gofr, /comparison/*, /showcase, /migrate/*. +// Three pointer-events-none orbs distributed down the page at +// alternating sides, reusing the cyan + indigo vocabulary the hero +// already establishes. Drop inside any `relative overflow-x-clip` +// wrapper. +// +// Why an absolute layer instead of a CSS background-image: +// - Matches the existing Hero implementation (Image PNGs, opacity). +// - Lets us anchor each orb relative to a known parent, so different +// pages can opt in without re-tuning a body-level gradient. +// +// Why `unoptimized` is mandatory: the site builds with +// `output: 'export'`, which disables /_next/image. Without unoptimized, +// Next emits a /_next/image?url=... src that 404s in production and +// renders as a broken-image icon. +export function ShowcaseGradient() { + return ( + <> + + + + + ) +} diff --git a/src/lib/navigation.js b/src/lib/navigation.js index b51d240..f46b104 100644 --- a/src/lib/navigation.js +++ b/src/lib/navigation.js @@ -1,12 +1,12 @@ export const navigation = [ { title: 'Quick Start Guide', - desc: "Get started with GoFR through our Quick Start Guide. Learn to build scalable applications with easy-to-follow instructions on server setup, database connections, configuration management, and more. Boost your productivity and streamline your development process.", + desc: "Get started with GoFr through our Quick Start Guide. Learn to build scalable applications with easy-to-follow instructions on server setup, database connections, configuration management, and more. Boost your productivity and streamline your development process.", links: [ { title: 'Hello Server', href: '/docs/quick-start/introduction', - desc: "Getting started with how to write a server using GoFR with basic examples and explanations. Boost your productivity with efficient coding practices and learn to build scalable applications quickly." + desc: "Getting started with how to write a server using GoFr with basic examples and explanations. Boost your productivity with efficient coding practices and learn to build scalable applications quickly." }, { @@ -17,12 +17,12 @@ export const navigation = [ { title: 'Connecting Redis', href: '/docs/quick-start/connecting-redis', - desc: "Discover how to connect your GoFR application to Redis for fast in-memory data storage." + desc: "Discover how to connect your GoFr application to Redis for fast in-memory data storage." }, { title: 'Connecting MySQL', href: '/docs/quick-start/connecting-mysql', - desc: "Step-by-step guide on integrating MySQL with your GoFR application. With managed database connections and new methods for increasing your productivity." + desc: "Step-by-step guide on integrating MySQL with your GoFr application. With managed database connections and new methods for increasing your productivity." }, { title: 'Observability', @@ -82,7 +82,7 @@ export const navigation = [ { title: 'Authentication', href: '/docs/advanced-guide/authentication', - desc: "Implement various authentication methods to secure your GoFR application and protect sensitive endpoints across HTTP and gRPC." + desc: "Implement various authentication methods to secure your GoFr application and protect sensitive endpoints across HTTP and gRPC." }, { title: 'Role-Based Access Control (RBAC)', @@ -358,12 +358,12 @@ export const navigation = [ { title: 'Context', href: '/docs/references/context', - desc: "Discover the GoFR context, an injected object that simplifies request-specific data handling for HTTP, gRPC, and Pub/Sub calls. Learn how it extends Go's context, providing easy access to dependencies like databases, loggers, and HTTP clients. Explore features for reading HTTP requests, binding data, and accessing query and path parameters efficiently, all while reducing application complexity." + desc: "Discover the GoFr context, an injected object that simplifies request-specific data handling for HTTP, gRPC, and Pub/Sub calls. Learn how it extends Go's context, providing easy access to dependencies like databases, loggers, and HTTP clients. Explore features for reading HTTP requests, binding data, and accessing query and path parameters efficiently, all while reducing application complexity." }, { title: 'Configs', href: '/docs/references/configs', - desc: "Learn how to manage configuration settings in your GoFR applications, including default values for environment variables. This section provides a comprehensive list of all available configurations to streamline your setup." + desc: "Learn how to manage configuration settings in your GoFr applications, including default values for environment variables. This section provides a comprehensive list of all available configurations to streamline your setup." }, { title: 'Testing',