mirror of
https://github.com/privacy-scaling-explorations/pse.dev.git
synced 2026-01-09 14:18:02 -05:00
Merge branch 'main' into fix-and-update-design
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
FROM node:18-alpine as builder
|
||||
FROM node:18-alpine3.18 as builder
|
||||
RUN apk add --no-cache git curl
|
||||
|
||||
WORKDIR /builder
|
||||
@@ -8,7 +8,7 @@ RUN pnpm install
|
||||
RUN pnpm build
|
||||
|
||||
# Create image by copying build artifacts
|
||||
FROM node:18-alpine as runner
|
||||
FROM node:18-alpine3.18 as runner
|
||||
RUN npm i -g pnpm
|
||||
|
||||
USER node
|
||||
|
||||
86
app/[lang]/layout.tsx
Normal file
86
app/[lang]/layout.tsx
Normal file
@@ -0,0 +1,86 @@
|
||||
import "@/styles/globals.css"
|
||||
import { Metadata } from "next"
|
||||
import Script from "next/script"
|
||||
|
||||
import { siteConfig } from "@/config/site"
|
||||
import { fontDisplay, fontSans } from "@/lib/fonts"
|
||||
import { SiteFooter } from "@/components/site-footer"
|
||||
import { SiteHeader } from "@/components/site-header"
|
||||
import { TailwindIndicator } from "@/components/tailwind-indicator"
|
||||
|
||||
import { fallbackLng, languages } from "../i18n/settings"
|
||||
|
||||
export async function generateStaticParams() {
|
||||
return languages.map((language) => ({ language }))
|
||||
}
|
||||
|
||||
export const metadata: Metadata = {
|
||||
metadataBase: new URL("https://appliedzkp.org"),
|
||||
title: {
|
||||
default: siteConfig.name,
|
||||
template: `%s - ${siteConfig.name}`,
|
||||
},
|
||||
description: siteConfig.description,
|
||||
themeColor: [
|
||||
{ media: "(prefers-color-scheme: light)", color: "white" },
|
||||
{ media: "(prefers-color-scheme: dark)", color: "black" },
|
||||
],
|
||||
icons: {
|
||||
icon: "/favicon.svg",
|
||||
shortcut: "/favicon.svg",
|
||||
apple: "/apple-touch-icon.png",
|
||||
},
|
||||
openGraph: {
|
||||
images: [
|
||||
{
|
||||
url: "/og-image.png",
|
||||
width: 1200,
|
||||
height: 630,
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
interface RootLayoutProps {
|
||||
children: React.ReactNode
|
||||
params: any
|
||||
}
|
||||
|
||||
export default function RootLayout({ children, params }: RootLayoutProps) {
|
||||
const lang = params.lang ?? fallbackLng
|
||||
|
||||
return (
|
||||
<>
|
||||
<html
|
||||
lang={lang}
|
||||
className={`${fontSans.variable} ${fontDisplay.variable}`}
|
||||
suppressHydrationWarning
|
||||
>
|
||||
<Script id="matomo-tracking" strategy="afterInteractive">
|
||||
{`
|
||||
var _paq = window._paq = window._paq || [];
|
||||
/* tracker methods like "setCustomDimension" should be called before "trackPageView" */
|
||||
_paq.push(['trackPageView']);
|
||||
_paq.push(['enableLinkTracking']);
|
||||
(function() {
|
||||
var u="https://psedev.matomo.cloud/";
|
||||
_paq.push(['setTrackerUrl', u+'matomo.php']);
|
||||
_paq.push(['setSiteId', '1']);
|
||||
var d=document, g=d.createElement('script'), s=d.getElementsByTagName('script')[0];
|
||||
g.async=true; g.src='//cdn.matomo.cloud/psedev.matomo.cloud/matomo.js'; s.parentNode.insertBefore(g,s);
|
||||
})();
|
||||
`}
|
||||
</Script>
|
||||
<head />
|
||||
<body className={"min-h-screen bg-background antialiased"}>
|
||||
<div className="relative flex min-h-screen flex-col">
|
||||
<SiteHeader lang={lang} />
|
||||
<div className="flex-1">{children}</div>
|
||||
<SiteFooter lang={lang} />
|
||||
</div>
|
||||
<TailwindIndicator />
|
||||
</body>
|
||||
</html>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
import Image from "next/image"
|
||||
import Link from "next/link"
|
||||
import { useParams } from "next/navigation"
|
||||
import PSELogo from "@/public/icons/archstar.webp"
|
||||
import { motion } from "framer-motion"
|
||||
|
||||
@@ -16,8 +15,7 @@ import { WhatWeDo } from "@/components/sections/WhatWeDo"
|
||||
import { useTranslation } from "../i18n/client"
|
||||
import { LocaleTypes } from "../i18n/settings"
|
||||
|
||||
export default function IndexPage() {
|
||||
const lang = useParams()?.locale as LocaleTypes
|
||||
export default function IndexPage({ params: { lang } }: any) {
|
||||
const { t } = useTranslation(lang, "homepage")
|
||||
const { t: ct } = useTranslation(lang, "common")
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ export const i18n = i18next
|
||||
)
|
||||
.init({
|
||||
...getOptions(),
|
||||
debug: true,
|
||||
debug: false,
|
||||
lng: undefined, // let detect the language on client side
|
||||
detection: {
|
||||
order: ["path", "htmlTag", "cookie", "navigator"],
|
||||
@@ -45,7 +45,6 @@ export function useTranslation(
|
||||
if (runsOnServerSide && lng && i18n.resolvedLanguage !== lng) {
|
||||
i18n.changeLanguage(lng)
|
||||
} else {
|
||||
return ret
|
||||
// eslint-disable-next-line react-hooks/rules-of-hooks
|
||||
const [activeLng, setActiveLng] = useState(i18n.resolvedLanguage)
|
||||
// eslint-disable-next-line react-hooks/rules-of-hooks
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
{
|
||||
"siteTitle": "Privacy & Scaling Explorations",
|
||||
"siteDescription": "Enhancing Ethereum through cryptographic research and collective experimentation.",
|
||||
"menu": {
|
||||
"home": "Home",
|
||||
"projectLibrary": "Project Library",
|
||||
|
||||
@@ -6,9 +6,26 @@ export type LocaleTypes = (typeof languages)[number]
|
||||
export const defaultNS = "translation"
|
||||
export const cookieName = "i18next"
|
||||
|
||||
export const LanguageMapping: Record<string, string> = {
|
||||
en: "English",
|
||||
it: "Italiano",
|
||||
ko: "한국어",
|
||||
fr: "Français",
|
||||
"zh-CN": "中文",
|
||||
es: "Español",
|
||||
}
|
||||
|
||||
export const languagesItems: { label: string; value: string }[] =
|
||||
Object.entries(LanguageMapping).map(([value, label]) => {
|
||||
return {
|
||||
label,
|
||||
value,
|
||||
}
|
||||
}) ?? []
|
||||
|
||||
export function getOptions(lng = fallbackLng, ns = defaultNS): InitOptions {
|
||||
return {
|
||||
// debug: true,
|
||||
debug: false,
|
||||
supportedLngs: languages,
|
||||
fallbackLng,
|
||||
lng,
|
||||
|
||||
@@ -1,21 +1,14 @@
|
||||
import "@/styles/globals.css"
|
||||
import { Metadata } from "next"
|
||||
import Script from "next/script"
|
||||
|
||||
import { siteConfig } from "@/config/site"
|
||||
import { fontDisplay, fontSans } from "@/lib/fonts"
|
||||
import { SiteFooter } from "@/components/site-footer"
|
||||
import { SiteHeader } from "@/components/site-header"
|
||||
import { TailwindIndicator } from "@/components/tailwind-indicator"
|
||||
|
||||
import { fallbackLng, languages } from "./i18n/settings"
|
||||
import { languages } from "./i18n/settings"
|
||||
|
||||
export async function generateStaticParams() {
|
||||
return languages.map((language) => ({ language }))
|
||||
}
|
||||
|
||||
// import { ThemeProvider } from "@/components/theme-provider"
|
||||
|
||||
export const metadata: Metadata = {
|
||||
metadataBase: new URL("https://appliedzkp.org"),
|
||||
title: {
|
||||
@@ -45,45 +38,9 @@ export const metadata: Metadata = {
|
||||
|
||||
interface RootLayoutProps {
|
||||
children: React.ReactNode
|
||||
params: any
|
||||
params?: any
|
||||
}
|
||||
|
||||
export default function RootLayout({
|
||||
children,
|
||||
params: { lang = fallbackLng },
|
||||
}: RootLayoutProps) {
|
||||
return (
|
||||
<>
|
||||
<html
|
||||
lang={lang}
|
||||
className={`${fontSans.variable} ${fontDisplay.variable}`}
|
||||
suppressHydrationWarning
|
||||
>
|
||||
<Script id="matomo-tracking" strategy="afterInteractive">
|
||||
{`
|
||||
var _paq = window._paq = window._paq || [];
|
||||
/* tracker methods like "setCustomDimension" should be called before "trackPageView" */
|
||||
_paq.push(['trackPageView']);
|
||||
_paq.push(['enableLinkTracking']);
|
||||
(function() {
|
||||
var u="https://psedev.matomo.cloud/";
|
||||
_paq.push(['setTrackerUrl', u+'matomo.php']);
|
||||
_paq.push(['setSiteId', '1']);
|
||||
var d=document, g=d.createElement('script'), s=d.getElementsByTagName('script')[0];
|
||||
g.async=true; g.src='//cdn.matomo.cloud/psedev.matomo.cloud/matomo.js'; s.parentNode.insertBefore(g,s);
|
||||
})();
|
||||
`}
|
||||
</Script>
|
||||
<head />
|
||||
<body className={"min-h-screen bg-background antialiased"}>
|
||||
<div className="relative flex min-h-screen flex-col">
|
||||
<SiteHeader lang={lang} />
|
||||
<div className="flex-1">{children}</div>
|
||||
<SiteFooter lang={lang} />
|
||||
</div>
|
||||
<TailwindIndicator />
|
||||
</body>
|
||||
</html>
|
||||
</>
|
||||
)
|
||||
export default function RootLayout({ children, params }: RootLayoutProps) {
|
||||
return <>{children}</>
|
||||
}
|
||||
|
||||
@@ -47,7 +47,7 @@ export default function NotFound({ lang }: LangProps["params"]) {
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<Link href={`/${lang}`} className="mx-auto">
|
||||
<Link href="/" className="mx-auto">
|
||||
<Button variant="black">{t("goToHome")}</Button>
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
@@ -6,17 +6,19 @@ import { usePathname } from "next/navigation"
|
||||
import PSELogo from "@/public/logos/header-logo.svg"
|
||||
|
||||
import { NavItem } from "@/types/nav"
|
||||
import { LocaleTypes, fallbackLng } from "@/app/i18n/settings"
|
||||
|
||||
export interface MainNavProps {
|
||||
items: NavItem[]
|
||||
lang?: LocaleTypes
|
||||
}
|
||||
|
||||
export function MainNav({ items }: MainNavProps) {
|
||||
export function MainNav({ items, lang = fallbackLng }: MainNavProps) {
|
||||
const router = usePathname()
|
||||
|
||||
return (
|
||||
<div className="flex gap-6 md:gap-10">
|
||||
<Link href="/" className="flex items-center space-x-2">
|
||||
<Link href={`/${lang}`} className="flex items-center space-x-2">
|
||||
<Image src={PSELogo} alt="PSE Logo" width={32} height={32} />
|
||||
</Link>
|
||||
<nav className="hidden items-center gap-6 md:flex">
|
||||
|
||||
@@ -3,11 +3,13 @@
|
||||
import { useState } from "react"
|
||||
import NextImage from "next/image"
|
||||
import NextLink from "next/link"
|
||||
import Link from "next/link"
|
||||
import CloseVector from "@/public/icons/close-fill.svg"
|
||||
import HeaderVector from "@/public/icons/menu-burger.svg"
|
||||
|
||||
import { LangProps } from "@/types/common"
|
||||
import { siteConfig } from "@/config/site"
|
||||
import { cn } from "@/lib/utils"
|
||||
import {
|
||||
Discord,
|
||||
Github,
|
||||
@@ -15,9 +17,55 @@ import {
|
||||
Twitter,
|
||||
} from "@/components/svgs/social-medias"
|
||||
import { useTranslation } from "@/app/i18n/client"
|
||||
import { LanguageMapping, languagesItems } from "@/app/i18n/settings"
|
||||
|
||||
import { Icons } from "./icons"
|
||||
|
||||
const LanguageSwitcher = ({ lang }: LangProps["params"]) => {
|
||||
const [isOpen, setIsOpen] = useState(false)
|
||||
|
||||
if (!siteConfig?.showLanguageSwitcher) return null
|
||||
|
||||
return (
|
||||
<div className="flex flex-col border-b-2 border-white px-[14px] py-[16px] pt-0">
|
||||
<button
|
||||
onClick={() => {
|
||||
setIsOpen(!isOpen)
|
||||
}}
|
||||
type="button"
|
||||
className="flex items-center gap-2 uppercase"
|
||||
>
|
||||
<Icons.globe className="text-white" size={22} fill="white" />
|
||||
<span className="text-base font-medium uppercase text-white">
|
||||
{LanguageMapping[lang] ?? LanguageMapping["en"]}
|
||||
</span>
|
||||
<Icons.arrowDown />
|
||||
</button>
|
||||
{isOpen && (
|
||||
<div className="ml-8 mt-4 flex flex-col gap-1">
|
||||
{languagesItems?.map(({ label, value: languageKey }, index) => {
|
||||
const isActive = languageKey === lang
|
||||
return (
|
||||
<Link
|
||||
className={cn(
|
||||
"py-2 uppercase",
|
||||
isActive
|
||||
? "font-medium text-anakiwa-500"
|
||||
: "font-normal text-white"
|
||||
)}
|
||||
href={`/${languageKey}`}
|
||||
key={index}
|
||||
>
|
||||
{label}
|
||||
</Link>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export function SiteHeaderMobile({ lang }: LangProps["params"]) {
|
||||
const [header, setHeader] = useState(false)
|
||||
const { t } = useTranslation(lang, "common")
|
||||
@@ -88,6 +136,8 @@ export function SiteHeaderMobile({ lang }: LangProps["params"]) {
|
||||
{t("menu.jobs")}
|
||||
<Icons.externalUrl className="w-6" fill="white" />
|
||||
</NextLink>
|
||||
|
||||
<LanguageSwitcher lang={lang} />
|
||||
</div>
|
||||
<div className="flex h-full w-full flex-col items-center justify-end gap-5 py-[40px] text-sm">
|
||||
<div className="flex gap-5">
|
||||
@@ -125,7 +175,7 @@ export function SiteHeaderMobile({ lang }: LangProps["params"]) {
|
||||
<h1>{t("footer.privacyPolicy")}</h1>
|
||||
<h1>{t("footer.termsOfUse")}</h1>
|
||||
</div>
|
||||
<h1 className="text-gray-400 text-center">
|
||||
<h1 className="text-center text-gray-400">
|
||||
{t("lastUpdatedAt", {
|
||||
date: "January 16, 2024",
|
||||
})}
|
||||
|
||||
@@ -1,36 +1,49 @@
|
||||
"use client"
|
||||
|
||||
import { siteConfig } from "@/config/site"
|
||||
import { useAppSettings } from "@/hooks/useAppSettings"
|
||||
import { MainNav, MainNavProps } from "@/components/main-nav"
|
||||
import { useTranslation } from "@/app/i18n/client"
|
||||
import { LocaleTypes } from "@/app/i18n/settings"
|
||||
import { MainNav } from "@/components/main-nav"
|
||||
import {
|
||||
LanguageMapping,
|
||||
LocaleTypes,
|
||||
languagesItems,
|
||||
} from "@/app/i18n/settings"
|
||||
|
||||
import { Icons } from "./icons"
|
||||
import { SiteHeaderMobile } from "./site-header-mobile"
|
||||
import { Dropdown } from "./ui/dropdown"
|
||||
|
||||
type SiteHeaderProps = {
|
||||
lang: LocaleTypes
|
||||
}
|
||||
|
||||
export function SiteHeader({ lang }: SiteHeaderProps) {
|
||||
const { t: i18n } = useTranslation(lang, "common")
|
||||
const { MAIN_NAV } = useAppSettings(lang)
|
||||
|
||||
return (
|
||||
<header className="sticky top-0 z-40 w-full bg-white px-6 shadow-sm xl:px-20">
|
||||
<div className="flex h-16 justify-between space-x-4 sm:space-x-0">
|
||||
<MainNav items={MAIN_NAV} />
|
||||
<div className="flex h-16 items-center justify-between space-x-4 sm:space-x-0">
|
||||
<MainNav items={MAIN_NAV} lang={lang} />
|
||||
<SiteHeaderMobile lang={lang} />
|
||||
<div className="hidden flex-1 items-center justify-end space-x-4 md:flex">
|
||||
<button type="button" className="flex gap-2">
|
||||
<Icons.globe size={22} />
|
||||
<span>
|
||||
{i18n("menu.languages", {
|
||||
locale: lang?.toUpperCase(),
|
||||
})}
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
{siteConfig?.showLanguageSwitcher && (
|
||||
<div className="hidden outline-none md:block">
|
||||
<Dropdown
|
||||
label={
|
||||
<div className="flex items-center gap-1">
|
||||
<Icons.globe size={22} />
|
||||
<span className="!text-base !font-normal text-tuatara-950">
|
||||
{LanguageMapping[lang] ?? LanguageMapping["en"]}
|
||||
</span>
|
||||
</div>
|
||||
}
|
||||
defaultItem={lang}
|
||||
items={languagesItems}
|
||||
onChange={(lang) => {
|
||||
window?.location?.replace(`/${lang}`)
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</header>
|
||||
)
|
||||
|
||||
@@ -11,7 +11,7 @@ interface DropdownItemProps {
|
||||
}
|
||||
|
||||
interface DropdownProps {
|
||||
label: string
|
||||
label: React.ReactNode
|
||||
items?: DropdownItemProps[]
|
||||
defaultItem?: string | number
|
||||
onChange?: (value: DropdownItemProps["value"]) => void
|
||||
@@ -53,7 +53,7 @@ const Dropdown = ({
|
||||
|
||||
<DropdownMenu.Portal>
|
||||
<DropdownMenu.Content
|
||||
className="max-h-[250px] min-w-[136px] overflow-scroll rounded-md border border-tuatara-200 bg-white py-2"
|
||||
className="z-[50] max-h-[250px] min-w-[136px] overflow-scroll rounded-md border border-tuatara-200 bg-white py-2"
|
||||
sideOffset={5}
|
||||
>
|
||||
{items?.map((item, index) => {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
export type SiteConfig = typeof siteConfig
|
||||
1
|
||||
|
||||
export const siteConfig = {
|
||||
showLanguageSwitcher: false, // TODO: enable when we have more languages
|
||||
name: "Privacy & Scaling Explorations",
|
||||
description:
|
||||
"Enhancing Ethereum through cryptographic research and collective experimentation.",
|
||||
|
||||
@@ -14,9 +14,13 @@ import { dslWorkingGroup } from "./projects/dsl-working-group"
|
||||
import { ECIPHalo2 } from "./projects/ecip-halo2"
|
||||
import { eigenTrust } from "./projects/eigen-trust"
|
||||
import { Interep } from "./projects/interep"
|
||||
import { jubmoji } from "./projects/jubmoji"
|
||||
import { maci } from "./projects/maci"
|
||||
import { nfctap } from "./projects/nfctap"
|
||||
import { p0tion } from "./projects/p0tion"
|
||||
import { p256 } from "./projects/p256"
|
||||
import { pollenLabs } from "./projects/pollen-labs"
|
||||
import { PerpetualPowersOfTau } from './projects/powers-of-tau'
|
||||
import { PerpetualPowersOfTau } from "./projects/powers-of-tau"
|
||||
import { pseSecurity } from "./projects/pse-security"
|
||||
import { rln } from "./projects/rln"
|
||||
import { semaphore } from "./projects/semaphore"
|
||||
@@ -25,15 +29,13 @@ import { tlsn } from "./projects/tlsn"
|
||||
import { trustedSetups } from "./projects/trusted-setups"
|
||||
import { unirepProtocol } from "./projects/unirep-protocol"
|
||||
import { wax } from "./projects/wax"
|
||||
import { ZKKit } from "./projects/zk-kit"
|
||||
import { zk3 } from "./projects/zk3"
|
||||
import { ZKKit } from "./projects/zk-kit"
|
||||
import { zkevmCommunity } from "./projects/zkevm-community"
|
||||
import { zkitter } from "./projects/zkitter"
|
||||
import { zkml } from "./projects/zkml"
|
||||
import { Zkopru } from "./projects/zkopru"
|
||||
import { zkp2p } from "./projects/zkp2p"
|
||||
import { p256 } from "./projects/p256"
|
||||
import { p0tion } from "./projects/p0tion"
|
||||
|
||||
export const ProjectLinkIconMap: ProjectLinkType = {
|
||||
github: GithubIcon,
|
||||
@@ -74,6 +76,8 @@ export const projects: ProjectInterface[] = [
|
||||
ZKKit,
|
||||
p256,
|
||||
p0tion,
|
||||
jubmoji,
|
||||
nfctap,
|
||||
/**
|
||||
* Grant projects hidden until we have grant tag
|
||||
zkp2p,
|
||||
|
||||
33
data/projects/jubmoji.ts
Normal file
33
data/projects/jubmoji.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import { ProjectInterface } from "@/lib/types"
|
||||
|
||||
const description = `
|
||||
Jubmoji.quest is a place to keep personal, provable digital mementos from people you meet and places you visit IRL. Each time you tap a card, you collect a Jubmoji, a unique cryptographic signature that you can store privately and share as you wish!
|
||||
`
|
||||
|
||||
export const jubmoji: ProjectInterface = {
|
||||
id: "jubmoji",
|
||||
projectStatus: "active",
|
||||
image: "",
|
||||
name: "jubmoji.quest",
|
||||
tldr: "Users of Jubmoji.quest tap NFC cards to collect signatures. By collecting these signatures, they complete quests.",
|
||||
description,
|
||||
links: {
|
||||
github: "https://github.com/jubmoji/jubmoji.quest",
|
||||
website: "https://www.jubmoji.quest/",
|
||||
},
|
||||
tags: {
|
||||
keywords: [
|
||||
"anonymity/privacy",
|
||||
"education",
|
||||
"data portability",
|
||||
"social",
|
||||
"wallets",
|
||||
"identity",
|
||||
"key management",
|
||||
"reputation",
|
||||
"toolkits",
|
||||
],
|
||||
builtWith: ["snarkjs", "circom", "node"],
|
||||
themes: ["build", "play"],
|
||||
},
|
||||
}
|
||||
@@ -7,7 +7,7 @@ Minimal Anti-Collusion Infrastructure (MACI) is a protocol designed to provide a
|
||||
export const maci: ProjectInterface = {
|
||||
id: "maci",
|
||||
projectStatus: "active",
|
||||
image: "maci.webp",
|
||||
image: "maci.png",
|
||||
name: "MACI",
|
||||
tldr: "An on-chain voting solution that protects privacy and minimizes the risk of collusion and bribery",
|
||||
description,
|
||||
|
||||
33
data/projects/nfctap.ts
Normal file
33
data/projects/nfctap.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import { ProjectInterface } from "@/lib/types"
|
||||
|
||||
const description = `
|
||||
NFC activations at SBC and FtC residency
|
||||
`
|
||||
|
||||
export const nfctap: ProjectInterface = {
|
||||
id: "nfctap",
|
||||
projectStatus: "active",
|
||||
image: "",
|
||||
name: "nfctap.xyz",
|
||||
tldr: "This project was built to activate NFCs at SBC and FtC and learn from it for a larger Devconnect experience with 200 cards and 5,000 attendees.",
|
||||
description,
|
||||
links: {
|
||||
github: "https://github.com/jubmoji/nfctap.xyz",
|
||||
website: "https://www.nfctap.xyz/",
|
||||
},
|
||||
tags: {
|
||||
keywords: [
|
||||
"anonymity/privacy",
|
||||
"education",
|
||||
"data portability",
|
||||
"social",
|
||||
"wallets",
|
||||
"identity",
|
||||
"key management",
|
||||
"reputation",
|
||||
"toolkits",
|
||||
],
|
||||
builtWith: ["snarkjs", "circom", "node"],
|
||||
themes: ["build", "play"],
|
||||
},
|
||||
}
|
||||
2533
package-lock.json
generated
2533
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
BIN
public/project-banners/maci.png
Normal file
BIN
public/project-banners/maci.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 509 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 58 KiB |
Reference in New Issue
Block a user