fix layout and implement new design (#470)

* fix layout and implement new design + fix dark mode styles
This commit is contained in:
Kalidou Diagne
2025-06-20 13:09:55 +01:00
committed by GitHub
parent 75934467e5
commit 8ea062d57b
23 changed files with 429 additions and 274 deletions

48
app/(pages)/not-found.tsx Normal file
View File

@@ -0,0 +1,48 @@
"use client"
import "@/styles/globals.css"
import React from "react"
import Image from "next/image"
import Link from "next/link"
import { Metadata } from "next"
import { Button } from "@/components/ui/button"
import { LABELS } from "../labels"
export const metadata: Metadata = {
title: "404: Page Not Found",
}
export default function NotFound() {
return (
<div className="relative flex h-screen flex-col bg-anakiwa-50 dark:bg-black">
<div className="container m-auto">
<div className="-mt-16 flex flex-col gap-7">
<div className="flex flex-col items-center justify-center gap-3 text-center">
<div className="flex flex-col gap-2">
<Image
width={176}
height={256}
src="/icons/404-search.svg"
alt="emotion sad"
className="mx-auto h-12 w-12 text-anakiwa-400 md:h-64 md:w-44"
/>
</div>
<div className="flex flex-col gap-5">
<span className="font-display text-2xl font-bold text-primary md:text-6xl">
{LABELS.COMMON.ERROR["404"].TITLE}
</span>
<span className="font-sans text-base font-normal md:text-lg">
{LABELS.COMMON.ERROR["404"].DESCRIPTION}
</span>
</div>
</div>
<Link href="/" className="mx-auto">
<Button variant="black">{LABELS.COMMON.GO_TO_HOME}</Button>
</Link>
</div>
</div>
</div>
)
}

View File

@@ -162,7 +162,7 @@ export const ProjectContent = ({ id }: { id: string }) => {
{projectStatusMessage?.length > 0 && (
<span className="relative pl-6 text-tuatara-500">
<div className="border-l-[4px] border-l-orangeDark absolute left-0 top-0 bottom-0"></div>
<span className="text-tuatara-500">
<span className="text-tuatara-500 dark:text-white">
{projectStatusMessage}
</span>
</span>

View File

@@ -0,0 +1,50 @@
import { cn } from "@/lib/utils"
import { classed } from "@tw-classed/react"
import { ReactNode } from "react"
interface SectionWrapperProps {
title: string
description?: string
showHeader?: boolean
children?: ReactNode
className?: string
}
export const SectionWrapperTitle = classed.h3(
"relative font-sans text-base font-bold uppercase tracking-[3.36px] text-anakiwa-950 dark:text-anakiwa-400 dark:text-white",
{
variants: {
variant: {
default:
"after:ml-8 after:absolute after:top-1/2 after:h-[1px] after:w-full after:translate-y-1/2 after:bg-anakiwa-300 after:content-['']",
},
},
defaultVariants: {
variant: "default",
},
}
)
export const SectionWrapper = ({
title = "",
description = "",
showHeader = true,
children = null,
className = "",
}: SectionWrapperProps) => {
return (
<div className={cn("flex w-full flex-col gap-10", className)}>
{showHeader && (
<div className="flex flex-col gap-6 overflow-hidden">
<SectionWrapperTitle>{title}</SectionWrapperTitle>
{description?.length > 0 && (
<span className="font-sans text-base italic text-primary">
{description}
</span>
)}
</div>
)}
{children}
</div>
)
}

View File

@@ -47,6 +47,7 @@ export const LABELS = {
RECENT_ARTICLES: "Recent articles",
SEE_MORE: "See more",
READ_MORE: "Read more",
SEARCH_PLACEHOLDER: "Search PSE's blog",
},
BLOG_TAGS_PAGE: {
TITLE: "Blog tags",
@@ -168,6 +169,7 @@ export const LABELS = {
ERROR_LOADING_VIDEOS: "Error loading videos",
PROJECT_TEAM: "Team",
YOUTUBE_VIDEOS: "YouTube Videos",
MORE_POSTS: "More posts",
},
HOMEPAGE: {
HEADER_TITLE: "Privacy + Scaling Explorations",

View File

@@ -17,27 +17,30 @@ interface ArticleInEvidenceCardProps {
titleClassName?: string
contentClassName?: string
showDate?: boolean
backgroundCover?: boolean
}
const AsLinkWrapper = ({
children,
href,
asLink,
className = "",
}: {
children: React.ReactNode
href: string
asLink: boolean
className?: string
}) => {
return asLink ? (
<Link className="group" href={href}>
<Link className={cn("group", className)} href={href}>
{children}
</Link>
) : (
<>{children}</>
<div className={cn("group", className)}>{children}</div>
)
}
export const ArticleInEvidenceCard = async ({
export const ArticleInEvidenceCard = ({
article,
showReadMore = false,
size = "lg",
@@ -47,6 +50,7 @@ export const ArticleInEvidenceCard = async ({
titleClassName = "",
contentClassName = "",
showDate = true,
backgroundCover = true,
}: ArticleInEvidenceCardProps) => {
const hideTldr = variant === "compact"
@@ -59,42 +63,40 @@ export const ArticleInEvidenceCard = async ({
})
}
return (
<AsLinkWrapper href={`/blog/${article.id}`} asLink={asLink}>
const ArticleContent = ({ backgroundCover = true }: any) => {
return (
<div
className={cn(
"min-h-[177px] lg:min-h-[190px] relative flex flex-col gap-5 w-full items-center after:absolute after:inset-0 after:content-[''] after:bg-black after:opacity-20 group-hover:after:opacity-80 transition-opacity duration-300 after:z-[0]",
"duration-200 flex flex-col gap-4 text-left relative z-[1] w-full h-full",
{
"aspect-video": !className?.includes("h-full"),
"px-5 lg:px-16 py-6 lg:py-16 ": size === "lg",
"px-6 py-4 lg:p-8": size === "sm",
"px-6 lg:p-16": size === "xl",
"!p-0 lg:!p-0": !backgroundCover,
},
className
contentClassName
)}
style={{
backgroundImage: `url(${article.image ?? "/fallback.webp"})`,
backgroundSize: "cover",
backgroundPosition: "center centers",
}}
>
<div
className={cn(
"duration-200 flex flex-col gap-[10px] text-left relative z-[1] w-full h-full",
{
"px-5 lg:px-16 py-6 lg:py-16 ": size === "lg",
"px-6 py-4 lg:p-8": size === "sm",
"px-6 lg:p-16": size === "xl",
},
contentClassName
)}
>
{article.date && showDate && (
<span className="text-white text-xs font-sans font-bold tracking-[2.5px] text-left uppercase">
{formatDate(article.date)}
</span>
)}
{article.date && showDate && (
<span
className={cn(
"text-white text-xs font-sans font-bold tracking-[2.5px] text-left uppercase",
backgroundCover
? "text-white dark:text-black"
: "text-black dark:text-white"
)}
>
{formatDate(article.date)}
</span>
)}
<div className="flex flex-col gap-2">
<Link
href={`/blog/${article.id}`}
className={cn(
" text-white font-display hover:text-anakiwa-400 transition-colors",
"font-display hover:text-anakiwa-400 group-hover:text-anakiwa-400 transition-colors",
backgroundCover
? "text-white dark:text-white"
: "text-black dark:text-white group-hover:underline",
{
"text-[20px] font-semibold lg:font-bold lg:text-lg line-clamp-2 mt-auto":
variant === "compact",
@@ -108,32 +110,84 @@ export const ArticleInEvidenceCard = async ({
>
{article.title}
</Link>
<span className="text-sm text-white/80 uppercase font-inter">
{article.authors?.join(", ")}
</span>
{(article?.authors ?? [])?.length > 0 && (
<span
className={cn("text-sm font-sans", {
"text-white/80": backgroundCover,
"text-tuatara-600 dark:text-white/80": !backgroundCover,
})}
>
{article.authors?.join(", ")}
</span>
)}
{article.tldr && !hideTldr && (
<span
className={
"text-sm font-sans text-white font-normal line-clamp-2 lg:line-clamp-5 mt-auto hidden lg:block"
}
className={cn(
"text-sm font-san font-normal line-clamp-2 lg:line-clamp-5 mt-auto hidden lg:block",
{
"text-white/80": backgroundCover,
"text-tuatara-600 dark:text-white/80": !backgroundCover,
}
)}
>
{article.tldr}
</span>
)}
{showReadMore && (
<Link href={`/blog/${article.id}`} className="ml-auto mt-4">
<Button className="uppercase ml-auto" variant="secondary">
<div className="flex items-center gap-2">
<span className="!text-center">
{LABELS.BLOG_PAGE.READ_MORE}
</span>
<Icons.arrowRight className="w-4 h-4" />
</div>
</Button>
</Link>
)}
</div>
{(article?.tags ?? [])?.length > 0 && (
<div className="flex flex-wrap gap-2">
{article?.tags?.slice(0, 3)?.map((tag) => (
<Link key={tag.id} href={`/blog/tags/${tag.id}`}>
<Button size="xs" variant="secondary">
{tag.name}
</Button>
</Link>
))}
</div>
)}
{showReadMore && (
<Link href={`/blog/${article.id}`} className="ml-auto mt-4">
<Button className="uppercase ml-auto" variant="secondary">
<div className="flex items-center gap-2">
<span className="!text-center">
{LABELS.BLOG_PAGE.READ_MORE}
</span>
<Icons.arrowRight className="w-4 h-4" />
</div>
</Button>
</Link>
)}
</div>
)
}
return (
<AsLinkWrapper
href={`/blog/${article.id}`}
asLink={asLink}
className="flex flex-col gap-4"
>
<div
className={cn(
"relative flex flex-col gap-5 w-full items-center after:absolute after:inset-0 after:content-[''] after:bg-black after:opacity-20 group-hover:after:opacity-80 transition-opacity duration-300 after:z-[0]",
{
"aspect-video": !className?.includes("h-full"),
"min-h-[148px]": !backgroundCover,
"min-h-[177px] lg:min-h-[190px]": backgroundCover,
},
className
)}
style={{
backgroundImage: `url(${article.image ?? "/fallback.webp"})`,
backgroundSize: "cover",
backgroundPosition: "center centers",
}}
>
{backgroundCover && (
<ArticleContent backgroundCover={backgroundCover} />
)}
</div>
{!backgroundCover && <ArticleContent backgroundCover={backgroundCover} />}
</AsLinkWrapper>
)
}

View File

@@ -56,9 +56,11 @@ export const ArticleListCard = ({
<span className="text-xs font-display lg:text-[22px] font-bold text-primary group-hover:text-anakiwa-500 group-hover:underline duration-200 lg:leading-6">
{article.title}
</span>
<span className="lg:uppercase text-tuatara-400 lg:text-xs text-[10px] leading-none font-sans dark:text-tuatara-300">
{article.authors?.map((author) => author).join(", ")}
</span>
{(article?.authors ?? [])?.length > 0 && (
<span className="text-tuatara-400 lg:text-xs text-[10px] leading-none font-sans dark:text-tuatara-300">
{article.authors?.map((author) => author).join(", ")}
</span>
)}
<div className="hidden lg:block">
<Markdown
components={{

View File

@@ -1,11 +1,18 @@
"use client"
import { useQuery } from "@tanstack/react-query"
import { Article } from "@/lib/content"
import { Article, ArticleTag } from "@/lib/content"
import { ArticleListCard } from "./article-list-card"
import { cn, getBackgroundImage } from "@/lib/utils"
import Link from "next/link"
import { cva } from "class-variance-authority"
import { ArticleInEvidenceCard } from "./article-in-evidance-card"
import { Input } from "../ui/input"
import { Button } from "../ui/button"
import { LABELS } from "@/app/labels"
import { Search as SearchIcon } from "lucide-react"
import { useState } from "react"
import { useDebounce } from "react-use"
import { useParams, useRouter, useSearchParams } from "next/navigation"
const ArticleTitle = cva(
"text-white font-display hover:text-anakiwa-400 transition-colors group-hover:text-anakiwa-400",
@@ -21,121 +28,6 @@ const ArticleTitle = cva(
}
)
const ArticleInEvidenceCard = ({
article,
size = "lg",
variant = "default",
className,
asLink = false,
titleClassName = "",
contentClassName = "",
showDate = true,
}: {
article: Article
showReadMore?: boolean
size?: "sm" | "lg" | "xl"
variant?: "default" | "compact" | "xl"
className?: string
asLink?: boolean
titleClassName?: string
contentClassName?: string
showDate?: boolean
}) => {
const hideTldr = variant === "compact"
const formatDate = (dateString: string) => {
const date = new Date(dateString)
return date.toLocaleDateString("en-US", {
month: "long",
day: "numeric",
year: "numeric",
})
}
const AsLinkWrapper = ({
children,
href,
asLink,
}: {
children: React.ReactNode
href: string
asLink: boolean
}) => {
return asLink ? (
<Link className="group" href={href}>
{children}
</Link>
) : (
<div className="group">{children}</div>
)
}
const backgroundImage = getBackgroundImage(article?.image)
return (
<AsLinkWrapper href={`/blog/${article.id}`} asLink={asLink}>
<div
className={cn(
"min-h-[177px] lg:min-h-[190px] relative flex flex-col gap-5 w-full items-center after:absolute after:inset-0 after:content-[''] after:bg-black after:opacity-20 group-hover:after:opacity-80 transition-opacity duration-300 after:z-[0]",
{
"aspect-video": !className?.includes("h-full"),
},
className
)}
style={{
backgroundImage: backgroundImage
? `url(${backgroundImage})`
: undefined,
backgroundSize: "cover",
backgroundPosition: "center centers",
}}
>
<div
className={cn(
"duration-200 flex flex-col gap-[10px] text-left relative z-[1] w-full h-full",
{
"px-5 lg:px-16 py-6 lg:py-16 ": size === "lg",
"px-6 py-4 lg:p-8": size === "sm",
"px-6 lg:p-16": size === "xl",
},
contentClassName
)}
>
{article.date && showDate && (
<span className="text-white text-xs font-sans font-bold tracking-[2.5px] text-left uppercase">
{formatDate(article.date)}
</span>
)}
{asLink === false ? (
<Link
href={`/blog/${article.id}`}
className={cn(ArticleTitle({ variant }), titleClassName)}
>
{article.title}
</Link>
) : (
<span className={cn(ArticleTitle({ variant }), titleClassName)}>
{article.title}
</span>
)}
<span className="text-sm text-white/80 uppercase font-inter">
{article.authors?.join(", ")}
</span>
{article.tldr && !hideTldr && (
<span
className={
"text-sm font-sans text-white font-normal line-clamp-2 lg:line-clamp-5 mt-auto hidden lg:block"
}
>
{article.tldr}
</span>
)}
</div>
</div>
</AsLinkWrapper>
)
}
async function fetchArticles(tag?: string) {
try {
const params = new URLSearchParams()
@@ -164,6 +56,12 @@ interface ArticlesListProps {
export const ArticlesList: React.FC<ArticlesListProps> = ({
tag,
}: ArticlesListProps) => {
const router = useRouter()
const params = useSearchParams()
const query = params.get("query")
const [searchQuery, setSearchQuery] = useState(query ?? "")
const {
data: articles = [],
isLoading,
@@ -185,25 +83,70 @@ export const ArticlesList: React.FC<ArticlesListProps> = ({
)
}
const hasSearchParams =
(searchQuery ?? "")?.length > 0 && searchQuery !== "all"
const lastArticle = articles[0]
const featuredArticles = !tag ? articles.slice(1, 3) : []
const otherArticles = !tag ? articles.slice(3) : articles
let otherArticles = !tag && !hasSearchParams ? articles.slice(3, 6) : []
if (searchQuery === "all") {
otherArticles = articles
} else if (searchQuery?.length > 0) {
otherArticles = articles.filter((article: Article) => {
const title = article.title.toLowerCase()
const content = article.content.toLowerCase()
const tags =
article.tags?.map((tag: ArticleTag) => tag.name.toLowerCase()) ?? []
return (
title.includes(searchQuery.toLowerCase()) ||
tags.some((tag: string) => tag.includes(searchQuery.toLowerCase()))
)
})
}
const hasTag = tag !== undefined
const onSearchArticles = (query: string) => {
setSearchQuery(query)
router.push(`/blog?query=${query}`)
}
useDebounce(
() => {
if (searchQuery === "") return null
onSearchArticles(searchQuery)
},
500, // debounce timeout in ms when user is typing
[searchQuery]
)
return (
<div className="flex flex-col gap-10 lg:gap-16">
{!hasTag && (
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4 lg:gap-6 items-stretch">
<div className="lg:col-span-2 h-full">
{!hasTag && !hasSearchParams && searchQuery !== "all" && (
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4 lg:gap-10 items-stretch">
<div className="lg:col-span-2 h-full flex flex-col gap-4">
<ArticleInEvidenceCard
article={lastArticle}
size="sm"
className="h-full "
asLink
/>
<>
{featuredArticles?.map((article: Article) => {
return (
<ArticleInEvidenceCard
key={article.id}
article={article}
size="sm"
className="h-full lg:hidden"
asLink
/>
)
})}
</>
</div>
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4 lg:gap-6 lg:col-span-2 h-full">
<div className="hidden lg:grid grid-cols-1 lg:grid-cols-2 gap-4 lg:gap-10 lg:col-span-2 h-full">
{featuredArticles?.map((article: Article) => {
return (
<ArticleInEvidenceCard
@@ -212,6 +155,7 @@ export const ArticlesList: React.FC<ArticlesListProps> = ({
variant="compact"
size="sm"
className="h-full"
backgroundCover={false}
asLink
/>
)
@@ -219,10 +163,37 @@ export const ArticlesList: React.FC<ArticlesListProps> = ({
</div>
</div>
)}
<div className="flex flex-col gap-5 lg:gap-14">
{otherArticles.map((article: Article) => {
return <ArticleListCard key={article.id} article={article} />
})}
<div className="flex flex-col gap-10 lg:gap-16 lg:px-12">
<div className="flex flex-col gap-10 ">
<Input
className="max-w-[500px] mx-auto w-full"
placeholder={LABELS.BLOG_PAGE.SEARCH_PLACEHOLDER}
icon={SearchIcon}
onChange={(e) => {
onSearchArticles(e?.target?.value ?? "")
}}
onIconClick={() => {
onSearchArticles(searchQuery)
}}
/>
<div className="flex flex-col gap-5 lg:gap-14 ">
{otherArticles
.filter((article: Article) => article.id !== lastArticle.id)
.map((article: Article) => {
return <ArticleListCard key={article.id} article={article} />
})}
</div>
</div>
{searchQuery?.length === 0 && (
<Button
className="mx-auto uppercase"
onClick={() => {
onSearchArticles("all")
}}
>
{LABELS.COMMON.MORE_POSTS}
</Button>
)}
</div>
</div>
)

View File

@@ -75,7 +75,7 @@ export function BlogContent({ post, isNewsletter = false }: BlogContentProps) {
</span>
<Link
href="/blog"
className="text-black font-bold text-base leading-6 hover:underline hover:text-anakiwa-500"
className="text-black font-bold text-lg leading-6 hover:underline hover:text-anakiwa-500"
>
View all
</Link>

View File

@@ -70,7 +70,7 @@ export const WikiCard = ({ project, className = "" }: WikiCardProps) => {
return (
<div className={cn("flex flex-col gap-6", className)}>
<div className="mx-auto flex max-w-[290px] flex-col gap-6">
<div className="mx-auto flex max-w-[290px] flex-col gap-6 w-full">
<Card className="bg-background" padding="none">
<div className="relative flex h-[140px] items-center justify-center overflow-hidden rounded-t-lg">
<Image

View File

@@ -126,7 +126,7 @@ export default function ProjectCard({
)}
<div
className="px-[6px] py-[2px] text-xs font-normal leading-none flex items-center justify-center rounded-[3px] text-white dark:text-black"
className="px-[6px] py-[2px] text-xs font-normal leading-none flex items-center justify-center rounded-[3px] text-black dark:text-black"
style={{
backgroundColor: ProjectStatusColorMapping[projectStatus],
}}

View File

@@ -3,7 +3,6 @@
import React, { useCallback, useEffect, useRef, useState } from "react"
import Image from "next/image"
import NoResultIcon from "@/public/icons/no-result.svg"
import { cva } from "class-variance-authority"
import {
ProjectInterface,
@@ -18,21 +17,7 @@ import { LABELS } from "@/app/labels"
import ProjectCard from "./project-card"
import { useProjects } from "@/app/providers/ProjectsProvider"
const sectionTitleClass = cva(
"relative font-sans text-base font-bold uppercase tracking-[3.36px] text-anakiwa-950 dark:text-anakiwa-400 dark:text-white",
{
variants: {
variant: {
default:
"after:ml-8 after:absolute after:top-1/2 after:h-[1px] after:w-full after:translate-y-1/2 after:bg-anakiwa-300 after:content-['']",
},
},
defaultVariants: {
variant: "default",
},
}
)
import { SectionWrapper } from "@/app/components/wrappers/SectionWrapper"
const NoResults = () => {
return (
@@ -178,14 +163,11 @@ export const ProjectList = () => {
className="flex justify-between gap-10"
>
<div className={cn("flex w-full flex-col gap-10 pt-10")}>
{!hasSearchParams && (
<div className="flex flex-col gap-6 overflow-hidden">
<h3 className={cn(sectionTitleClass())}>{status}</h3>
<span className="font-sans text-base italic text-primary">
{description}
</span>
</div>
)}
<SectionWrapper
title={status}
description={description}
showHeader={!hasSearchParams}
/>
<div className="grid grid-cols-1 gap-4 md:grid-cols-2 md:gap-x-6 md:gap-y-10 lg:grid-cols-4">
{projects.map((project: any) => (
<ProjectCard

View File

@@ -3,19 +3,17 @@
import React, { useEffect, useState, useMemo } from "react"
import Image from "next/image"
import NoResultIcon from "@/public/icons/no-result.svg"
import { cva } from "class-variance-authority"
import { ProjectInterface, ProjectStatus } from "@/lib/types"
import { cn } from "@/lib/utils"
import { LABELS } from "@/app/labels"
import { useProjects } from "@/app/providers/ProjectsProvider"
import ResearchCard from "./research-card"
import Link from "next/link"
const sectionTitleClass = cva(
"relative font-sans text-base font-bold uppercase tracking-[3.36px] text-anakiwa-950 after:ml-8 after:absolute after:top-1/2 after:h-[1px] after:w-full after:translate-y-1/2 after:bg-anakiwa-300 after:content-[''] dark:text-white"
)
import {
SectionWrapper,
SectionWrapperTitle,
} from "@/app/components/wrappers/SectionWrapper"
const NoResults = () => {
return (
@@ -38,10 +36,9 @@ const ProjectStatusOrderList = ["active", "maintained", "inactive"]
export const ResearchList = () => {
const [isMounted, setIsMounted] = useState(false)
const { researchs, searchQuery, queryString } = useProjects()
const { researchs } = useProjects()
const noItems = researchs?.length === 0
const hasActiveFilters = searchQuery !== "" || queryString !== ""
useEffect(() => {
setIsMounted(true)
@@ -65,14 +62,11 @@ export const ResearchList = () => {
return (
<div className="flex flex-col gap-10">
<div className="flex flex-col gap-6 overflow-hidden">
<div
className={cn(
"after:left-[100px] lg:after:left-[200px]",
sectionTitleClass()
)}
<SectionWrapperTitle
className={"after:left-[100px] lg:after:left-[200px]"}
>
<div className="h-3 lg:h-4 w-[120px] lg:w-[220px] bg-gray-200 animate-pulse rounded-lg"></div>
</div>
</SectionWrapperTitle>
</div>
<div className="grid items-start justify-between w-full grid-cols-1 gap-2 md:grid-cols-3 md:gap-6 ">
<div className="min-h-[200px] border border-gray-200 bg-gray-200 animate-pulse rounded-lg overflow-hidden"></div>
@@ -92,14 +86,7 @@ export const ResearchList = () => {
data-section="active-researchs"
className="flex flex-col justify-between gap-10"
>
<div className={cn("flex w-full flex-col gap-10")}>
{!hasActiveFilters && (
<div className="flex flex-col gap-6 overflow-hidden">
<h3 className={cn(sectionTitleClass())}>
{LABELS.RESEARCH_PAGE.ACTIVE_RESEARCH}
</h3>
</div>
)}
<SectionWrapper title={LABELS.RESEARCH_PAGE.ACTIVE_RESEARCH}>
<div className="grid grid-cols-1 gap-4 md:gap-x-6 md:gap-y-10 lg:grid-cols-3">
{activeResearchs.map((project: ProjectInterface) => (
<ResearchCard
@@ -114,13 +101,12 @@ export const ResearchList = () => {
/>
))}
</div>
</div>
<div className={cn("flex w-full flex-col gap-10 pt-10")}>
<div className="flex flex-col gap-6 overflow-hidden">
<h3 className={cn(sectionTitleClass())}>
{LABELS.RESEARCH_PAGE.PAST_RESEARCH}
</h3>
</div>
</SectionWrapper>
<SectionWrapper
title={LABELS.RESEARCH_PAGE.PAST_RESEARCH}
className="pt-10"
>
<div className="flex flex-col gap-5">
{pastResearchs.map((project: ProjectInterface) => (
<Link
@@ -132,7 +118,7 @@ export const ResearchList = () => {
</Link>
))}
</div>
</div>
</SectionWrapper>
</div>
</div>
)

View File

@@ -109,7 +109,7 @@ function Hit({
})
return (
<span className="text-secondary font-sans text-base font-normal">
<span className="text-secondary font-sans text-lg font-normal">
{textContent}
</span>
)

View File

@@ -73,7 +73,7 @@ export const HomepageVideoFeed = () => {
<section className="mx-auto px-6 lg:px-8 py-10 lg:py-16 bg-tuatara-950 dark:bg-black">
<AppContent className="flex flex-col gap-8 lg:max-w-[1200px] w-full">
<div className="col-span-1 lg:col-span-4">
<h2 className="font-sans text-base font-bold uppercase tracking-[4px] text-primary text-center">
<h2 className="font-sans text-base font-bold uppercase tracking-[4px] text-white text-center">
{LABELS.HOMEPAGE.VIDEOS}
</h2>
</div>

View File

@@ -34,7 +34,6 @@ export const SiteHeaderMobile = () => {
className="text-[#171C1B] dark:text-anakiwa-400"
/>
</button>
{header && (
<div
className="z-5 fixed inset-0 flex justify-end bg-black opacity-50"
@@ -71,9 +70,19 @@ export const SiteHeaderMobile = () => {
})}
<button
onClick={() => setIsDarkMode(!isDarkMode)}
className="text-black dark:text-anakiwa-400 ml-auto mt-10"
className=" ml-auto mt-10"
>
{isDarkMode ? <SunIcon size={20} /> : <MoonIcon size={20} />}
{isDarkMode ? (
<SunIcon
className="text-white dark:text-anakiwa-400"
size={20}
/>
) : (
<MoonIcon
className="text-white dark:text-anakiwa-400"
size={20}
/>
)}
</button>
</div>

View File

@@ -1,10 +1,10 @@
import React from "react"
interface AppContentProps extends React.HTMLAttributes<HTMLDivElement> {
children: React.ReactNode
children?: React.ReactNode
className?: string
}
export const AppContent = ({ children, className }: AppContentProps) => {
export const AppContent = ({ children = null, className }: AppContentProps) => {
return <div className={`container ${className}`}>{children}</div>
}

View File

@@ -1,11 +1,12 @@
import { InputHTMLAttributes, forwardRef } from "react"
import { cva, type VariantProps } from "class-variance-authority"
import { LucideIcon } from "lucide-react"
import { cn } from "@/lib/utils"
const inputVariants = cva(
[
"rounded-md bg-zinc-50",
"w-full rounded-md bg-zinc-50",
"text-anakiwa-950 placeholder-anakiwa-950",
"border-[1.5px] border-tuatara-200",
"transition-colors duration-100 animate",
@@ -30,16 +31,63 @@ const inputVariants = cva(
interface InputProps
extends VariantProps<typeof inputVariants>,
Omit<InputHTMLAttributes<HTMLInputElement>, "size" | "id" | "children"> {}
Omit<InputHTMLAttributes<HTMLInputElement>, "size" | "id" | "children"> {
icon?: LucideIcon
iconPosition?: "left" | "right"
onIconClick?: () => void
}
const Input = forwardRef<HTMLInputElement, InputProps>(
({ size, className, ...props }, ref) => {
(
{
size,
className,
icon: Icon,
iconPosition = "left",
onIconClick,
...props
},
ref
) => {
if (!Icon) {
return (
<input
ref={ref}
{...props}
className={cn(inputVariants({ size, className }))}
/>
)
}
return (
<input
ref={ref}
{...props}
className={cn(inputVariants({ size, className }))}
/>
<div className={cn("relative w-full", className)}>
<input
ref={ref}
{...props}
className={cn(
inputVariants({ size, className }),
iconPosition === "left" ? "pl-10" : "pr-10"
)}
/>
{onIconClick ? (
<button
onClick={onIconClick}
className={cn(
"flex items-center justify-center absolute top-1/2 -translate-y-1/2 text-anakiwa-950 dark:text-anakiwa-300 size-4",
iconPosition === "left" ? "left-3" : "right-3"
)}
>
<Icon size={16} />
</button>
) : (
<Icon
className={cn(
"absolute top-1/2 -translate-y-1/2 text-anakiwa-950 dark:text-anakiwa-300 size-4",
iconPosition === "left" ? "left-3" : "right-3"
)}
/>
)}
</div>
)
}
)

View File

@@ -563,7 +563,7 @@ const REACT_MARKDOWN_CONFIG = (darkMode: boolean): CustomComponents => ({
if (containsMath(text)) {
return (
<p
className={`${darkMode ? "text-white" : "text-secondary"} font-sans text-base font-normal ${isMathOnly ? "math-only" : ""}`}
className={`${darkMode ? "text-white" : "text-secondary"} font-sans text-lg font-normal ${isMathOnly ? "math-only" : ""}`}
>
<MathText text={text} />
</p>
@@ -572,7 +572,7 @@ const REACT_MARKDOWN_CONFIG = (darkMode: boolean): CustomComponents => ({
return (
<p
className={`${darkMode ? "text-white" : "text-secondary"} font-sans text-base font-normal`}
className={`${darkMode ? "text-white" : "text-secondary"} font-sans text-lg font-normal`}
>
{children}
</p>
@@ -591,31 +591,27 @@ const REACT_MARKDOWN_CONFIG = (darkMode: boolean): CustomComponents => ({
if (containsMath(text)) {
return (
<li
className="text-secondary font-sans text-base font-normal"
{...props}
>
<li className="text-secondary font-sans text-lg font-normal" {...props}>
<MathText text={text} />
</li>
)
}
return (
<li className="text-secondary font-sans text-base font-normal" {...props}>
<li className="text-secondary font-sans text-lg font-normal" {...props}>
{children}
</li>
)
},
ul: ({ ordered, ...props }) =>
createMarkdownElement(ordered ? "ol" : "ul", {
className:
"ml-6 list-disc text-secondary font-sans text-base font-normal",
className: "ml-6 list-disc text-secondary font-sans text-lg font-normal",
...props,
}),
ol: ({ ordered, ...props }) =>
createMarkdownElement(ordered ? "ol" : "ul", {
className:
"list-decimal text-secondary font-sans text-base font-normal mt-3",
"list-decimal text-secondary font-sans text-lg font-normal mt-3",
...props,
}),
table: Table,

View File

@@ -1,7 +1,7 @@
export type SiteConfig = typeof siteConfig
export const siteConfig = {
name: "Privacy & Scaling Explorations",
name: "PSE",
description:
"Enhancing Ethereum through cryptographic research and collective experimentation.",
url: "https://pse.dev",

View File

@@ -8,8 +8,6 @@ tags: ["ethereum", "privacy", "pir"]
projects: ["semaphore", "scaling-semaphore-pir"]
---
# Ethereum Privacy: Private Information Retrieval
_Thanks to [Cperezz](https://github.com/cperezz), [Vivian](https://github.com/vplasencia) and [Oskar](https://github.com/oskarth) for feedback and review._
Ethereum presents several [privacy challenges](https://hackmd.io/@pcaversaccio/ethereum-privacy-the-road-to-self-sovereignty#Status-Quo). One of them is that read operations can expose user data and behavior patterns, potentially leading to [deanonymization](https://en.wikipedia.org/wiki/Data_re-identification). In this scenario, users are safe only if they run their own node or host the data locally. Otherwise, all requests go through third parties, such as relayers, providers, and [wallets that process IP addresses](https://consensys.io/privacy-notice).

View File

@@ -27,6 +27,7 @@
"@radix-ui/react-dropdown-menu": "^2.0.5",
"@radix-ui/react-slot": "^1.0.2",
"@tanstack/react-query": "^5.74.4",
"@tw-classed/react": "^1.8.0",
"@types/prismjs": "^1.26.5",
"algoliasearch": "^4",
"class-variance-authority": "^0.4.0",

View File

@@ -13,11 +13,6 @@
--gradient-research-card: linear-gradient(84deg, #FFF -1.95%, #EAFAFF 59.98%, #FFF 100.64%);
--skeleton-background: #f3f4f6;
/* TODO: */
--active-selection: #50C3E0;
--primary: 222.2 47.4% 11.2%;
@@ -66,12 +61,6 @@
--gradient-research-card: linear-gradient(179deg, #29ACCE -202.54%, rgba(0, 0, 0, 0.00) 192.47%);
--skeleton-background: #175e75;
/* TODO: */
--active-selection: #E1523A;
--primary: #E1523A;

View File

@@ -1804,6 +1804,24 @@ __metadata:
languageName: node
linkType: hard
"@tw-classed/core@npm:1.7.0":
version: 1.7.0
resolution: "@tw-classed/core@npm:1.7.0"
checksum: 10/7a3e1fb154a2297276d46d0be5847124b01dcea30e13b2b6b82cf2b99b8dacd59f6cbb88124a1dfffae274d71eb62c38df1086abd9485d69e0327186c990f272
languageName: node
linkType: hard
"@tw-classed/react@npm:^1.8.0":
version: 1.8.0
resolution: "@tw-classed/react@npm:1.8.0"
dependencies:
"@tw-classed/core": "npm:1.7.0"
peerDependencies:
react: ">=16.8.0"
checksum: 10/7bfc82d7d099da8429e8178860a92b6f08987e84e8b94d3167aa03bae1619c4479712a9696f1fcaede54267b20856aed9914cb958978a4cf6a72ca19767fcb05
languageName: node
linkType: hard
"@tybys/wasm-util@npm:^0.9.0":
version: 0.9.0
resolution: "@tybys/wasm-util@npm:0.9.0"
@@ -6795,6 +6813,7 @@ __metadata:
"@radix-ui/react-dropdown-menu": "npm:^2.0.5"
"@radix-ui/react-slot": "npm:^1.0.2"
"@tanstack/react-query": "npm:^5.74.4"
"@tw-classed/react": "npm:^1.8.0"
"@types/js-yaml": "npm:^4.0.9"
"@types/node": "npm:^17.0.45"
"@types/prismjs": "npm:^1.26.5"