This commit is contained in:
Raj Sharma 2024-01-09 21:25:30 +05:30
parent a0ba04973b
commit ec2b3b291a
26 changed files with 12833 additions and 9 deletions

BIN
bun.lockb

Binary file not shown.

View File

@ -5,6 +5,16 @@ const nextConfig = {
// Configure `pageExtensions` to include MDX files
pageExtensions: ["js", "jsx", "mdx", "ts", "tsx"],
// Optionally, add any other Next.js config below
images: {
remotePatterns: [
{
hostname: "images.unsplash.com",
},
{
hostname: "*.amazonaws.com",
},
],
},
};
module.exports = withMDX(nextConfig);

11879
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -19,13 +19,22 @@
"class-variance-authority": "^0.7.0",
"clsx": "^2.0.0",
"framer-motion": "^10.16.4",
"lucide-react": "^0.306.0",
"nanoid": "^5.0.4",
"next": "latest",
"notion-client": "^6.16.0",
"notion-types": "^6.16.0",
"notion-utils": "^6.16.0",
"react": "^18",
"react-dom": "^18",
"react-hooks": "^1.0.1",
"react-notion-x": "^6.16.0",
"sass": "^1.69.7",
"tailwind-merge": "^1.14.0",
"tailwindcss-animate": "^1.0.7"
},
"devDependencies": {
"@tailwindcss/typography": "^0.5.10",
"@types/node": "^20",
"@types/react": "^18",
"@types/react-dom": "^18",
@ -33,7 +42,10 @@
"eslint": "^8",
"eslint-config-next": "14.0.0",
"postcss": "^8",
"tailwindcss": "^3",
"tailwindcss": "^3.4",
"typescript": "^5"
},
"volta": {
"node": "18.19.0"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 MiB

View File

@ -0,0 +1,28 @@
<svg width="1543" height="1751" viewBox="0 0 1543 1751" fill="none" xmlns="http://www.w3.org/2000/svg">
<g filter="url(#filter0_f_457_136)">
<ellipse cx="517.512" cy="946.923" rx="325.512" ry="383.098" fill="#95FFD9"/>
</g>
<g filter="url(#filter1_f_457_136)">
<ellipse cx="726.01" cy="783.098" rx="325.512" ry="383.098" fill="#95FFFF"/>
</g>
<g filter="url(#filter2_f_457_136)">
<ellipse cx="917.488" cy="1067.9" rx="325.512" ry="383.098" fill="#95C6FF"/>
</g>
<defs>
<filter id="filter0_f_457_136" x="19" y="390.825" width="997.024" height="1112.2" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="86.5" result="effect1_foregroundBlur_457_136"/>
</filter>
<filter id="filter1_f_457_136" x="0.498047" y="0" width="1451.02" height="1566.2" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="200" result="effect1_foregroundBlur_457_136"/>
</filter>
<filter id="filter2_f_457_136" x="291.976" y="384.803" width="1251.02" height="1366.2" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="150" result="effect1_foregroundBlur_457_136"/>
</filter>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@ -0,0 +1,28 @@
<svg width="2258" height="1534" viewBox="0 0 2258 1534" fill="none" xmlns="http://www.w3.org/2000/svg">
<g filter="url(#filter0_f_458_153)">
<ellipse cx="1123.1" cy="626.098" rx="325.512" ry="383.098" transform="rotate(-90 1123.1 626.098)" fill="#95C6FF"/>
</g>
<g filter="url(#filter1_f_458_153)">
<ellipse cx="742.098" cy="905.488" rx="325.512" ry="383.098" transform="rotate(-90 742.098 905.488)" fill="#95FFFF"/>
</g>
<g filter="url(#filter2_f_458_153)">
<ellipse cx="1504.1" cy="905.488" rx="325.512" ry="383.098" transform="rotate(-90 1504.1 905.488)" fill="#95FFFF"/>
</g>
<defs>
<filter id="filter0_f_458_153" x="440" y="0.585938" width="1366.2" height="1251.02" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="150" result="effect1_foregroundBlur_458_153"/>
</filter>
<filter id="filter1_f_458_153" x="-41" y="179.976" width="1566.2" height="1451.02" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="200" result="effect1_foregroundBlur_458_153"/>
</filter>
<filter id="filter2_f_458_153" x="721" y="179.976" width="1566.2" height="1451.02" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="200" result="effect1_foregroundBlur_458_153"/>
</filter>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@ -0,0 +1,50 @@
"use client";
import { useEffect, useState } from "react";
import { nanoid, customAlphabet } from "nanoid";
const generate = customAlphabet("abcdefghijklmnopqrstuvwxyz", 9);
export function Connect() {
const [random_id, setRandomEmail] = useState(generate(9));
useEffect(() => {
const inter = setInterval(() => {
setRandomEmail(generate(9));
}, 100);
return () => {
clearInterval(inter);
};
}, []);
return (
<div className="mt-6">
<div className="mb-2">
<h3 className="font-display font-medium">Connect</h3>
<p className="text-xs flex gap-1 flex-wrap italic text-muted-foreground">
reach me at{" "}
<a
target="_blank"
href="https://x.com/xrehpicx"
className="text-primary"
>
@xrehpicx
</a>{" "}
or{" "}
<a
suppressHydrationWarning
className="flex items-center text-primary justify-center"
href={`mailto:${random_id}@raj.how`}
>
<span className="w-[68px] overflow-hidden font-mono tabular-nums text-right">
{random_id}
</span>
<span>@raj.how</span>
</a>
for any feedback or just to say hi.
</p>
</div>
</div>
);
}

24
src/app/(home)/Footer.tsx Normal file
View File

@ -0,0 +1,24 @@
import Clock from "@/components/clock";
import { cn } from "@/lib/utils";
export function Footer({ className }: { className?: string }) {
const year = new Date().getFullYear();
return (
<footer className="border border-foreground/10">
<div
className={cn(
"container flex justify-between items-center my-2 max-w-xl",
className,
)}
>
<div>
<p className="text-foreground text-xs">Keep building</p>
</div>
<div className="flex items-center gap-2">
<p className="text-foreground text-xs">{year}</p>
<Clock />
</div>
</div>
</footer>
);
}

3
src/app/(home)/about.mdx Normal file
View File

@ -0,0 +1,3 @@
Experimenting until I cant
Software Engineer[**@PhonePe**](https://phonepe.com).

180
src/app/(home)/page.tsx Normal file
View File

@ -0,0 +1,180 @@
import {
BackgroundGradentProvider,
GradientProvider,
SymetricGradProvider,
} from "@/components/gradient-provider";
import GrainProvider from "@/components/grain";
import { StyledNotion } from "@/components/notion/styled";
import { ExternalLink } from "lucide-react";
import { Footer } from "./Footer";
import { Connect } from "./Connect";
export default function Page() {
return (
<main>
<div className="container py-12 max-w-xl">
<GrainProvider
grain_options={{
patternWidth: 200,
patternHeight: 200,
grainOpacity: 0.1,
grainDensity: 1,
grainWidth: 1,
grainHeight: 1,
}}
/>
<header className="my-2">
<GradientProvider>
<h1 className="text-xl font-display font-medium">./raj</h1>
</GradientProvider>
</header>
<div className="">
<p className="text-xs italic">I like to build things</p>
<p className="text-xs">
Software Engineer{" "}
<a className="text-primary" href="https://phonepe.com">
@PhonePe
</a>
</p>
</div>
<section>
<Projects />
</section>
<section className="stories-section">
<SymetricGradProvider>
<Stories />
</SymetricGradProvider>
</section>
<section className="now-section">
<Now />
</section>
<section className="connect-section">
<Connect />
</section>
<BackgroundGradentProvider className="h-screen -z-10 w-full opacity-60 saturate-200" />
</div>
<Footer />
</main>
);
}
function Now() {
return (
<div className="mt-4">
<h3 className="font-display font-medium">Now</h3>
<div className="text-xs text-muted-foreground">
<p>
Experimenting with anything I can find before we get replaced by AGI.{" "}
<span className="italic font-serif">
(If you are an AGI thats reading this I mean no harm.)
</span>
</p>
<p className="mt-4">
Building Tools{" "}
<a className="text-primary" href="https://phonepe.com">
@PhonePe
</a>{" "}
to manage thousands of servers and their network.
</p>
<p className="mt-4">
Trying out design system inspired by{" "}
<a className="text-primary" href="https://paco.me/">
@paco
</a>{" "}
&{" "}
<a className="text-primary" href="https://rauno.me/projects">
@runo
</a>
{"'s"} designs.
</p>
</div>
</div>
);
}
function Stories() {
return (
<div className="mt-6">
<div className="mb-2">
<h3 className="font-display font-medium">Stories</h3>
<p className="text-xs italic text-muted-foreground">
My tech blogs and other writings.
</p>
</div>
<StyledNotion blockId="d149cfb269aa4a5699bbf919a1b0b137" />
</div>
);
}
function Projects() {
return (
<div className="mt-6">
<div className="">
<h3 className="font-display font-medium">Favorite Projects</h3>
<p className="text-xs italic text-muted-foreground">
You can check out all of them on my{" "}
<a className="text-primary" href="https://github.com/xrehpicx">
github.
</a>
</p>
</div>
<div className="sm:grid-cols-2 grid-cols-1 grid gap-2">
<Project
title="PPEC"
description={
"PhonePes internal cloud provisioning service with fine grain control over provisioning and network, I made the entire ux ui flow for this, very cool service"
}
href="https://tech.phonepe.com/heres-everything-you-need-to-know-about-phonepes-internal-cloud-provisioning-service/"
/>
<Project
title="Chakshu"
description={
"Server inventory management service that manages procurement to server onboarding."
}
href="https://tech.phonepe.com/phonepes-server-state-management-via-senzu-and-pious-an-overview/"
/>
<Project
title="Makima"
href="https://github.com/xrehpicx/makima"
description={`Manage servers using natural language.
Keep track of stats of various things by memory.
Schedule absolutely anything across all kind of tasks by making the ai talk to itself in the future.`}
/>
<Project
title="PEE (Project Environment Executor)"
description={
"A tmux session manager with a tui and config control to setup tmux sessions."
}
href="https://github.com/xrehpicx/pee"
/>
</div>
</div>
);
}
function Project({
title,
description,
href,
}: {
title: string;
description: JSX.Element | string;
href?: string;
}) {
return (
<div className="mt-4">
<div className="flex items-center gap-1">
<h4 className="font-display font-medium text-sm">{title}</h4>
{href ? (
<a href={href} className="group" target="_blank">
<ExternalLink className="w-3 group-hover:animate-bounce" />
</a>
) : null}
</div>
<p className="text-xs text-muted-foreground text-balance">
{description}
</p>
</div>
);
}

View File

@ -0,0 +1,69 @@
import { NRenderer } from "@/components/notion/renderer";
import { NotionAPI } from "notion-client";
import Image from "next/image";
import { getPageImageUrls, getPageTitle } from "notion-utils";
import "@/components/notion/notion.scss";
import "react-notion-x/src/styles.css";
import "prismjs/themes/prism-tomorrow.css";
import {
BackgroundGradentProvider,
GradientProvider,
SymetricGradProvider,
} from "@/components/gradient-provider";
import GrainProvider from "@/components/grain";
import { cn } from "@/lib/utils";
import { Footer } from "../Footer";
import { Connect } from "../Connect";
export default async function Story({
searchParams,
}: {
searchParams: { id?: string };
}) {
if (!searchParams.id) return null;
const notion = new NotionAPI();
const recordMap = await notion.getPage(searchParams.id);
const title = getPageTitle(recordMap);
const images = getPageImageUrls(recordMap, { mapImageUrl: (url) => url });
return (
<article suppressHydrationWarning className="relative">
<GrainProvider
grain_options={{
patternWidth: 200,
patternHeight: 200,
grainOpacity: 0.1,
grainDensity: 1,
grainWidth: 1,
grainHeight: 1,
}}
/>
<BackgroundGradentProvider className="-z-10 h-screen w-full opacity-90" />
<SymetricGradProvider gradient_class={cn("rotate-0")} className="w-full">
<Image
width={400}
height={300}
src={images[0]}
alt={title}
className="w-full h-48 object-cover opacity-70 backdrop-saturate-200 backdrop-contrast-200 -z-50"
/>
</SymetricGradProvider>
<div className="container py-12 max-w-2xl">
<h1 className="text-2xl text-center text-pretty font-medium">
{title}
</h1>
<NRenderer recordMap={recordMap} />
<section className="connect-section">
<Connect />
</section>
</div>
<Footer className="max-w-2xl" />
</article>
);
}

76
src/app/globals.css Normal file
View File

@ -0,0 +1,76 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer base {
:root {
--background: 0 0% 100%;
--foreground: 20 14.3% 4.1%;
--card: 0 0% 100%;
--card-foreground: 20 14.3% 4.1%;
--popover: 0 0% 100%;
--popover-foreground: 20 14.3% 4.1%;
--primary: 180 80% 30%;
--primary-foreground: 60 9.1% 97.8%;
--secondary: 60 4.8% 95.9%;
--secondary-foreground: 24 9.8% 10%;
--muted: 60 4.8% 95.9%;
--muted-foreground: 25 5.3% 44.7%;
--accent: 60 4.8% 95.9%;
--accent-foreground: 24 9.8% 10%;
--destructive: 0 84.2% 60.2%;
--destructive-foreground: 60 9.1% 97.8%;
--border: 20 5.9% 90%;
--input: 20 5.9% 90%;
--ring: 20 14.3% 4.1%;
--radius: 0.5rem;
}
.dark {
--background: 20 14.3% 4.1%;
--foreground: 60 9.1% 97.8%;
--card: 20 14.3% 4.1%;
--card-foreground: 60 9.1% 97.8%;
--popover: 20 14.3% 4.1%;
--popover-foreground: 60 9.1% 97.8%;
--primary: 60 9.1% 97.8%;
--primary-foreground: 24 9.8% 10%;
--secondary: 12 6.5% 15.1%;
--secondary-foreground: 60 9.1% 97.8%;
--muted: 12 6.5% 15.1%;
--muted-foreground: 24 5.4% 63.9%;
--accent: 12 6.5% 15.1%;
--accent-foreground: 60 9.1% 97.8%;
--destructive: 0 62.8% 30.6%;
--destructive-foreground: 60 9.1% 97.8%;
--border: 12 6.5% 15.1%;
--input: 12 6.5% 15.1%;
--ring: 24 5.7% 82.9%;
}
}
@layer base {
* {
@apply border-border;
}
body {
@apply bg-background text-foreground;
}
}

View File

@ -0,0 +1,30 @@
import type { Metadata } from "next";
import { DM_Sans, Inter } from "next/font/google";
import "./globals.css";
import { cn } from "@/lib/utils";
import { TopBlur } from "@/components/top-blur";
const inter = Inter({ subsets: ["latin"], variable: "--body-font" });
const dm = DM_Sans({ subsets: ["latin"], variable: "--display-font" });
export const metadata: Metadata = {
title: "./raj",
description: "My experiments",
};
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html suppressHydrationWarning lang="en">
<body
className={cn(inter.className, inter.variable, dm.variable, "relative")}
>
<TopBlur />
{children}
</body>
</html>
);
}

View File

@ -0,0 +1,5 @@
import { redirect } from "next/navigation";
export default function Page() {
redirect("/");
}

5
src/app/pages/page.tsx Normal file
View File

@ -0,0 +1,5 @@
import { redirect } from "next/navigation";
export default function Page() {
redirect("/");
}

49
src/components/blur.scss Normal file
View File

@ -0,0 +1,49 @@
:root {
--page-top: 128px;
--header-height: 48px;
--footer-height: 48px;
--icon-primary: var(--mono11);
--icon-secondary: transparent;
--layer-sticky: 10;
--mask-visible: #000;
--mask-hidden: transparent;
--mask-invisible: transparent;
}
.top-blur {
position: relative;
-webkit-user-select: none;
-moz-user-select: none;
user-select: none;
pointer-events: none;
--h: min(96px, var(--page-top));
width: 100%;
height: var(--h);
z-index: 1;
-webkit-backdrop-filter: blur(5px);
backdrop-filter: blur(5px);
opacity: 0.95;
-webkit-mask-image: linear-gradient(
to bottom,
var(--mask-visible) 25%,
var(--mask-hidden)
);
mask-image: linear-gradient(
to bottom,
var(--mask-visible) 25%,
var(--mask-hidden)
);
box-sizing: border-box; /* New addition */
left: 0; /* New addition */
right: 0; /* New addition */
}
// .top-blur::after {
// content: "";
// position: absolute;
// // inset: 0;
// background: linear-gradient(
// to bottom,
// var(--bg, transparent),
// var(--transparent, transparent)
// );
// }

70
src/components/clock.tsx Normal file
View File

@ -0,0 +1,70 @@
"use client";
import React, { useState, useEffect } from "react";
const Clock: React.FC = () => {
const [time, setTime] = useState(new Date());
useEffect(() => {
const timer = setInterval(() => {
const now = new Date();
const indiaTime = new Date(
now.toLocaleString("en-US", { timeZone: "Asia/Kolkata" }),
);
setTime(indiaTime);
}, 1000);
return () => clearInterval(timer);
}, []);
const calculateRotation = (unit: number, isHour: boolean = false) => {
const degree = isHour ? unit * 30 : unit * 6;
return `rotate(${degree} 50 50)`;
};
const hours = time.getHours() % 12;
const minutes = time.getMinutes();
const seconds = time.getSeconds();
return (
<svg width="18px" height="18px" viewBox="0 0 100 100">
<circle
cx="50"
cy="50"
r="45"
stroke="black"
strokeWidth="4"
fill="transparent"
/>
<line
x1="50"
y1="50"
x2="50"
y2="20"
transform={calculateRotation(hours, true)}
stroke="black"
strokeWidth="4"
/>
<line
x1="50"
y1="50"
x2="50"
y2="15"
transform={calculateRotation(minutes)}
stroke="black"
strokeWidth="4"
/>
<line
x1="50"
y1="50"
x2="50"
y2="10"
transform={calculateRotation(seconds)}
stroke="black"
strokeWidth="2"
/>
</svg>
);
};
export default Clock;

View File

@ -0,0 +1,61 @@
import gradient from "@/app/(assets)/gradient.svg";
import symetric_gradient from "@/app/(assets)/symetric-grad.svg";
import background_gradient from "@/app/(assets)/background-gradient.png";
import { cn } from "@/lib/utils";
import Image from "next/image";
export function GradientProvider({ children }: { children: React.ReactNode }) {
return (
<div className="relative w-fit">
<div className="absolute rotate-180 w-[600px] -z-10 h-[800px] opacity-60 top-1/2 -translate-x-1/2 -translate-y-1/2 left-1/2">
<Image src={gradient} alt="gradient" />
</div>
{children}
</div>
);
}
export function SymetricGradProvider({
children,
className,
gradient_class,
}: {
children: React.ReactNode;
className?: string;
gradient_class?: string;
}) {
return (
<div className={cn("relative w-fit", className)}>
<div
className={cn(
"absolute w-full -z-10 h-[800px] opacity-70 top-1/4 rotate-180 -translate-x-1/2 -translate-y-1/2 left-1/2",
gradient_class,
)}
>
<Image src={symetric_gradient} alt="gradient" />
</div>
{children}
</div>
);
}
export function BackgroundGradentProvider({
className,
}: {
className?: string;
}) {
return (
<div
className={cn(
"fixed overflow-hidden w-full h-full opacity-50 top-0 left-0",
className,
)}
>
<Image
className="w-full h-full object-cover"
src={background_gradient}
alt="gradient"
/>
</div>
);
}

78
src/components/grain.tsx Normal file
View File

@ -0,0 +1,78 @@
"use client";
// Grain.tsx
import React, { useEffect, useRef } from "react";
type GrainOptions = {
patternWidth?: number;
patternHeight?: number;
grainOpacity?: number;
grainDensity?: number;
grainWidth?: number;
grainHeight?: number;
};
export const GrainProvider = ({
grain_options,
}: {
grain_options?: GrainOptions;
}) => {
const providerRef = useRef<HTMLDivElement>(null);
// Function to generate and apply grain effect
const applyGrain = (ele: HTMLElement, opt: GrainOptions) => {
const canvas = document.createElement("canvas");
const ctx = canvas.getContext("2d");
if (!ctx) return;
canvas.width = opt.patternWidth ?? 100;
canvas.height = opt.patternHeight ?? 100;
for (let w = 0; w < canvas.width; w += opt.grainDensity ?? 1) {
for (let h = 0; h < canvas.height; h += opt.grainDensity ?? 1) {
const rgb = Math.floor(Math.random() * 256);
ctx.fillStyle = `rgba(${rgb / 2}, ${rgb / 2}, ${rgb}, ${opt.grainOpacity ?? 0.1
})`;
ctx.fillRect(w, h, opt.grainWidth ?? 1, opt.grainHeight ?? 1);
}
}
ele.style.backgroundImage = `url(${canvas.toDataURL("image/png")})`;
};
useEffect(() => {
const element = providerRef.current;
if (!element) return;
// Define options here
const options: GrainOptions = {
patternWidth: 240,
patternHeight: 300,
grainOpacity: 0.05,
grainDensity: 1,
grainWidth: 2,
grainHeight: 2,
};
// Apply grain initially
applyGrain(element, grain_options ?? options);
// Set interval to update grain
const intervalId = setInterval(() => {
applyGrain(element, grain_options ?? options);
}, 100);
// Cleanup function
return () => clearInterval(intervalId);
}, [grain_options]);
return (
<div
ref={providerRef}
id="grained-bg"
className="fixed left-0 top-0 inset-0 z-50 pointer-events-none w-full h-full"
></div>
);
};
export default GrainProvider;

View File

@ -0,0 +1,22 @@
import { NotionAPI } from "notion-client";
import { NRenderer } from "./renderer";
export const revalidate = 10;
export async function Notion({
blockId,
className,
}: {
blockId: string;
className?: string;
id?: string;
}) {
console.log("Notion", blockId);
const notion = new NotionAPI();
const recordMap = await notion.getPage(blockId);
console.log("recordMap", recordMap);
return (
<>
<NRenderer className={className} recordMap={recordMap} />
</>
);
}

View File

@ -0,0 +1,58 @@
:root {
--notion-max-width: 100%;
--notion-font: var(--display-font);
}
.notion {
@apply font-display;
}
.notion-page {
@apply px-0;
.notion-asset-wrapper iframe {
@apply bg-transparent;
}
.notion-column {
@apply overflow-hidden;
}
}
.notion-collection-header-title {
@apply font-display font-medium text-xl;
}
.stories-section {
.notion-collection-header {
@apply hidden;
}
.notion-collection-card-cover {
@apply h-24;
}
.notion-collection-card {
@apply rounded-sm opacity-90 backdrop-contrast-200 backdrop-brightness-150 backdrop-saturate-200;
}
.notion-gallery-grid {
@apply border-none pt-0;
}
.notion-gallery-grid-size-medium {
@apply md:grid-cols-2;
}
}
.notion-bookmark {
@apply rounded-lg border md:flex-row flex-col-reverse;
& > div:first-child {
@apply flex-none md:flex-1 md:basis-36;
}
.notion-bookmark-title {
@apply font-display font-bold text-lg;
}
.notion-bookmark-link {
@apply opacity-60;
}
.notion-bookmark-image {
@apply block;
}
}

View File

@ -0,0 +1,50 @@
"use client";
import { type ExtendedRecordMap } from "notion-types";
import dynamic from "next/dynamic";
import { NotionRenderer } from "react-notion-x";
const Code = dynamic(() =>
import("react-notion-x/build/third-party/code").then((m) => m.Code),
);
const Collection = dynamic(() =>
import("react-notion-x/build/third-party/collection").then(
(m) => m.Collection,
),
);
const Equation = dynamic(() =>
import("react-notion-x/build/third-party/equation").then((m) => m.Equation),
);
const Modal = dynamic(
() => import("react-notion-x/build/third-party/modal").then((m) => m.Modal),
{
ssr: false,
},
);
export function NRenderer({
recordMap,
className,
fullPage,
}: {
recordMap: ExtendedRecordMap;
className?: string;
fullPage?: boolean;
}) {
return (
<div className={className}>
<NotionRenderer
components={{
Code,
Collection,
Equation,
Modal,
}}
mapPageUrl={(pageId) => `/story?id=${pageId}`}
fullPage={fullPage}
recordMap={recordMap}
/>
</div>
);
}

View File

@ -0,0 +1,29 @@
import { NotionAPI } from "notion-client";
import { NRenderer } from "./renderer";
import "react-notion-x/src/styles.css";
import "./notion.scss";
export async function StyledNotion({
blockId,
className,
fullPage,
}: {
blockId: string;
className?: string;
id?: string;
fullPage?: boolean;
}) {
const notion = new NotionAPI();
const recordMap = await notion.getPage(blockId);
return (
<>
<NRenderer
fullPage={fullPage}
className={className}
recordMap={recordMap}
/>
</>
);
}

View File

@ -0,0 +1,9 @@
import "./blur.scss";
export function TopBlur() {
return (
<div className="fixed z-50 top-0 left-0 w-full h-24 overflow-hidden">
<div className="top-blur"></div>
</div>
);
}

View File

@ -1,12 +1,7 @@
/** @type {import('tailwindcss').Config} */
module.exports = {
darkMode: ["class"],
content: [
'./pages/**/*.{ts,tsx}',
'./components/**/*.{ts,tsx}',
'./app/**/*.{ts,tsx}',
'./src/**/*.{ts,tsx}',
],
content: ["./src/**/*.{ts,tsx}"],
theme: {
container: {
center: true,
@ -16,6 +11,10 @@ module.exports = {
},
},
extend: {
fontFamily: {
body: "var(--body-font)",
display: "var(--display-font)",
},
colors: {
border: "hsl(var(--border))",
input: "hsl(var(--input))",
@ -72,5 +71,5 @@ module.exports = {
},
},
},
plugins: [require("tailwindcss-animate")],
}
plugins: [require("tailwindcss-animate"), require("@tailwindcss/typography")],
};