From bd3aedd71fad56bb48c79b4f8598136f342bfb7f Mon Sep 17 00:00:00 2001 From: Adithya Krishna Date: Tue, 17 Mar 2026 21:49:28 +0530 Subject: [PATCH 01/31] chore: fix conflicts --- .../(landing)/blog/[slug]/animated-blocks.tsx | 164 +++++++++++++ .../(landing)/blog/[slug]/article-sidebar.tsx | 132 +++++++++++ apps/sim/app/(landing)/blog/[slug]/page.tsx | 189 +++++++-------- .../(landing)/blog/[slug]/prose-studio.css | 157 ++++++++++++ .../(landing)/blog/[slug]/share-button.tsx | 65 ++--- .../blog/[slug]/table-of-contents.tsx | 107 +++++++++ .../app/(landing)/blog/authors/[id]/page.tsx | 105 +++++---- apps/sim/app/(landing)/blog/hero.tsx | 29 +++ apps/sim/app/(landing)/blog/layout.tsx | 16 +- apps/sim/app/(landing)/blog/page.tsx | 147 +++++++----- apps/sim/app/(landing)/blog/post-grid.tsx | 223 +++++++++++++----- apps/sim/app/(landing)/blog/sidebar.tsx | 96 ++++++++ apps/sim/app/(landing)/blog/tag-colors.ts | 96 ++++++++ apps/sim/app/(landing)/blog/tags/page.tsx | 71 ++++-- 14 files changed, 1284 insertions(+), 313 deletions(-) create mode 100644 apps/sim/app/(landing)/blog/[slug]/animated-blocks.tsx create mode 100644 apps/sim/app/(landing)/blog/[slug]/article-sidebar.tsx create mode 100644 apps/sim/app/(landing)/blog/[slug]/prose-studio.css create mode 100644 apps/sim/app/(landing)/blog/[slug]/table-of-contents.tsx create mode 100644 apps/sim/app/(landing)/blog/hero.tsx create mode 100644 apps/sim/app/(landing)/blog/sidebar.tsx create mode 100644 apps/sim/app/(landing)/blog/tag-colors.ts diff --git a/apps/sim/app/(landing)/blog/[slug]/animated-blocks.tsx b/apps/sim/app/(landing)/blog/[slug]/animated-blocks.tsx new file mode 100644 index 00000000000..2ecee16fa20 --- /dev/null +++ b/apps/sim/app/(landing)/blog/[slug]/animated-blocks.tsx @@ -0,0 +1,164 @@ +'use client' + +import { useEffect, useRef, useState } from 'react' + +const COLORS = ['#2ABBF8', '#FA4EDF', '#FFCC02', '#00F701'] as const + +const ENTER_STAGGER_MS = 60 +const ENTER_DURATION_MS = 300 +const HOLD_MS = 3000 +const EXIT_STAGGER_MS = 120 +const EXIT_DURATION_MS = 500 + +interface BlockState { + opacity: number + transitioning: boolean +} + +export function AnimatedColorBlocks() { + const prefersReducedMotion = usePrefersReducedMotion() + const [blocks, setBlocks] = useState( + COLORS.map(() => ({ opacity: prefersReducedMotion ? 1 : 0, transitioning: false })) + ) + const mounted = useRef(true) + + useEffect(() => { + mounted.current = true + if (prefersReducedMotion) return + + COLORS.forEach((_, i) => { + setTimeout(() => { + if (!mounted.current) return + setBlocks((prev) => + prev.map((b, idx) => (idx === i ? { opacity: 1, transitioning: true } : b)) + ) + }, i * ENTER_STAGGER_MS) + }) + + const totalEnterMs = COLORS.length * ENTER_STAGGER_MS + ENTER_DURATION_MS + HOLD_MS + const cycleTimer = setTimeout(() => { + if (!mounted.current) return + startCycle() + }, totalEnterMs) + + return () => { + mounted.current = false + clearTimeout(cycleTimer) + } + }, [prefersReducedMotion]) + + function startCycle() { + if (!mounted.current) return + + COLORS.forEach((_, i) => { + setTimeout(() => { + if (!mounted.current) return + setBlocks((prev) => + prev.map((b, idx) => (idx === i ? { opacity: 0.15, transitioning: true } : b)) + ) + }, i * EXIT_STAGGER_MS) + }) + + const exitTotalMs = COLORS.length * EXIT_STAGGER_MS + EXIT_DURATION_MS + setTimeout(() => { + if (!mounted.current) return + COLORS.forEach((_, i) => { + setTimeout(() => { + if (!mounted.current) return + setBlocks((prev) => + prev.map((b, idx) => + idx === i ? { opacity: [1, 0.8, 0.6, 0.9][i], transitioning: true } : b + ) + ) + }, i * ENTER_STAGGER_MS) + }) + }, exitTotalMs + 200) + + const cycleDuration = + exitTotalMs + 200 + COLORS.length * ENTER_STAGGER_MS + ENTER_DURATION_MS + HOLD_MS + setTimeout(() => startCycle(), cycleDuration) + } + + return ( + + ) +} + +export function AnimatedColorBlocksVertical() { + const prefersReducedMotion = usePrefersReducedMotion() + const [blocks, setBlocks] = useState( + COLORS.slice(0, 3).map(() => ({ + opacity: prefersReducedMotion ? 1 : 0, + transitioning: false, + })) + ) + const mounted = useRef(true) + + useEffect(() => { + mounted.current = true + if (prefersReducedMotion) return + + const baseDelay = COLORS.length * ENTER_STAGGER_MS + 100 + + COLORS.slice(0, 3).forEach((_, i) => { + setTimeout( + () => { + if (!mounted.current) return + setBlocks((prev) => + prev.map((b, idx) => (idx === i ? { opacity: 1, transitioning: true } : b)) + ) + }, + baseDelay + i * ENTER_STAGGER_MS + ) + }) + + return () => { + mounted.current = false + } + }, [prefersReducedMotion]) + + const verticalColors = [COLORS[0], COLORS[1], COLORS[2]] + + return ( + + ) +} + +function usePrefersReducedMotion(): boolean { + const [prefersReduced, setPrefersReduced] = useState(false) + + useEffect(() => { + const mq = window.matchMedia('(prefers-reduced-motion: reduce)') + setPrefersReduced(mq.matches) + + const handler = (e: MediaQueryListEvent) => setPrefersReduced(e.matches) + mq.addEventListener('change', handler) + return () => mq.removeEventListener('change', handler) + }, []) + + return prefersReduced +} diff --git a/apps/sim/app/(landing)/blog/[slug]/article-sidebar.tsx b/apps/sim/app/(landing)/blog/[slug]/article-sidebar.tsx new file mode 100644 index 00000000000..47e57ab9149 --- /dev/null +++ b/apps/sim/app/(landing)/blog/[slug]/article-sidebar.tsx @@ -0,0 +1,132 @@ +import Image from 'next/image' +import Link from 'next/link' +import type { Author, BlogMeta } from '@/lib/blog/schema' +import { TableOfContents } from '@/app/(landing)/studio/[slug]/table-of-contents' +import { getTagColor } from '@/app/(landing)/studio/tag-colors' + +interface ArticleSidebarProps { + author: Author + authors: Author[] + tags: string[] + headings: { text: string; id: string }[] + related: BlogMeta[] +} + +function formatDate(iso: string) { + return new Date(iso).toLocaleDateString('en-US', { + month: 'short', + day: 'numeric', + year: 'numeric', + }) +} + +export function ArticleSidebar({ author, authors, tags, headings, related }: ArticleSidebarProps) { + const displayAuthors = authors.length > 0 ? authors : [author] + + return ( + + ) +} diff --git a/apps/sim/app/(landing)/blog/[slug]/page.tsx b/apps/sim/app/(landing)/blog/[slug]/page.tsx index d5ed263e2b5..f8981ad5177 100644 --- a/apps/sim/app/(landing)/blog/[slug]/page.tsx +++ b/apps/sim/app/(landing)/blog/[slug]/page.tsx @@ -1,13 +1,18 @@ +import { ArrowLeft } from 'lucide-react' import type { Metadata } from 'next' -import Image from 'next/image' import Link from 'next/link' -import { Avatar, AvatarFallback, AvatarImage } from '@/components/emcn' import { FAQ } from '@/lib/blog/faq' +import '@/app/(landing)/studio/[slug]/prose-studio.css' import { getAllPostMeta, getPostBySlug, getRelatedPosts } from '@/lib/blog/registry' import { buildArticleJsonLd, buildBreadcrumbJsonLd, buildPostMetadata } from '@/lib/blog/seo' import { getBaseUrl } from '@/lib/core/utils/urls' -import { BackLink } from '@/app/(landing)/blog/[slug]/back-link' -import { ShareButton } from '@/app/(landing)/blog/[slug]/share-button' +import { + AnimatedColorBlocks, + AnimatedColorBlocksVertical, +} from '@/app/(landing)/blog/[slug]/animated-blocks' +import { ArticleSidebar } from '@/app/(landing)/blog/[slug]/article-sidebar' +import { ShareButtons } from '@/app/(landing)/blog/[slug]/share-button' +import { getPrimaryCategory } from '@/app/(landing)/blog/tag-colors' export async function generateStaticParams() { const posts = await getAllPostMeta() @@ -34,6 +39,11 @@ export default async function Page({ params }: { params: Promise<{ slug: string const breadcrumbLd = buildBreadcrumbJsonLd(post) const related = await getRelatedPosts(slug, 3) + const category = getPrimaryCategory(post.tags) + const categoryColor = category.color + const displayAuthors = post.authors && post.authors.length > 0 ? post.authors : [post.author] + const shareUrl = `${getBaseUrl()}/studio/${slug}` + return (