mirror of
https://github.com/ramvignesh-b/pi-ku.git
synced 2026-05-04 08:56:52 +00:00
refactor: modularize envelope reveal and logo components for easier usage across the site
This commit is contained in:
@@ -1,26 +1,26 @@
|
|||||||
import { DotIcon } from "@phosphor-icons/react";
|
import { DotIcon } from "@phosphor-icons/react";
|
||||||
import "@fontsource/knewave/400.css";
|
import "@fontsource/knewave/400.css";
|
||||||
|
|
||||||
export default function Logo() {
|
export default function Logo({ scale = 2 }) {
|
||||||
return (
|
return (
|
||||||
<span
|
<div
|
||||||
role="img"
|
role="img"
|
||||||
aria-label="Pi Ku"
|
aria-label="Pi Ku"
|
||||||
className="inline-flex items-baseline justify-center leading-none select-none"
|
className="inline-flex items-baseline justify-center leading-none select-none"
|
||||||
style={{ fontFamily: "'Knewave', serif" }}
|
style={{ fontFamily: "'Knewave', serif", scale }}
|
||||||
>
|
>
|
||||||
<span className="text-2xl font-light text-accent"> Pi</span>
|
<span className={`text-xl font-light text-accent`}> Pi</span>
|
||||||
<DotIcon
|
<DotIcon
|
||||||
weight="fill"
|
weight="fill"
|
||||||
size={12}
|
size={6}
|
||||||
className="text-accent translate-y-[0.3em] -mx-px"
|
className={`text-primary translate-y-1 -mx-px`}
|
||||||
/>
|
/>
|
||||||
<span className="text-2xl font-light text-accent"> Ku</span>
|
<span className={`text-xl font-light text-accent`}> Ku</span>
|
||||||
<DotIcon
|
<DotIcon
|
||||||
weight="fill"
|
weight="fill"
|
||||||
size={12}
|
size={6}
|
||||||
className="text-accent translate-y-[0.3em] -mx-px"
|
className={`text-primary translate-y-1 -mx-px`}
|
||||||
/>
|
/>
|
||||||
</span>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ export interface EnvelopeRevealProps {
|
|||||||
date?: string;
|
date?: string;
|
||||||
onRevealComplete: () => void;
|
onRevealComplete: () => void;
|
||||||
ignite: boolean;
|
ignite: boolean;
|
||||||
|
isFlip?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function EnvelopeReveal({
|
export function EnvelopeReveal({
|
||||||
@@ -15,9 +16,14 @@ export function EnvelopeReveal({
|
|||||||
date,
|
date,
|
||||||
onRevealComplete,
|
onRevealComplete,
|
||||||
ignite,
|
ignite,
|
||||||
|
isFlip,
|
||||||
}: EnvelopeRevealProps) {
|
}: EnvelopeRevealProps) {
|
||||||
const [revealLetter, setRevealLetter] = useState(false);
|
const [revealLetter, setRevealLetter] = useState(false);
|
||||||
const [isFlipped, setIsFlipped] = useState(false);
|
const [isFlipped, setIsFlipped] = useState(!!isFlip);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setIsFlipped(!!isFlip);
|
||||||
|
}, [isFlip]);
|
||||||
|
|
||||||
const [burn, setBurn] = useState<{ width: number; height: number }>({
|
const [burn, setBurn] = useState<{ width: number; height: number }>({
|
||||||
width: 0,
|
width: 0,
|
||||||
@@ -27,7 +33,10 @@ export function EnvelopeReveal({
|
|||||||
const flapCheckbox = useRef<HTMLInputElement>(null);
|
const flapCheckbox = useRef<HTMLInputElement>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!ignite) return;
|
if (!ignite) {
|
||||||
|
setBurn({ width: 0, height: 0 });
|
||||||
|
return;
|
||||||
|
}
|
||||||
const burnInterval = setInterval(() => {
|
const burnInterval = setInterval(() => {
|
||||||
setBurn((prev) => ({ width: prev.width + 4, height: prev.height + 6 }));
|
setBurn((prev) => ({ width: prev.width + 4, height: prev.height + 6 }));
|
||||||
}, 100);
|
}, 100);
|
||||||
@@ -43,90 +52,86 @@ export function EnvelopeReveal({
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="h-screen mx-auto flex-col items-center flex justify-center">
|
<>
|
||||||
<div className="perspective-distant scale-80 duration-1000 transition-all animate-[pulse_2s_linear_1]">
|
<div
|
||||||
|
className={`relative h-70 w-105 transform-3d transition-transform duration-2000 ${isFlipped ? "rotate-y-180" : ""}`}
|
||||||
|
>
|
||||||
<div
|
<div
|
||||||
className={`relative h-70 w-105 transform-3d transition-transform duration-2000 ${isFlipped ? "rotate-y-180" : ""}`}
|
className={` flex backface-hidden rotate-y-180 justify-center transition-all duration-1000 ${isFlipped ? "" : "pointer-events-none"}`}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className={` flex backface-hidden rotate-y-180 justify-center transition-all duration-1000 ${isFlipped ? "" : "pointer-events-none"}`}
|
id="env-top"
|
||||||
|
className="z-4 delay-500 transition-all duration-2000 absolute peer h-40 w-54 mt-0 bg-base-200 mask mask-triangle-2 scale-x-234 has-checked:scale-y-[-1] has-checked:-translate-y-full has-checked:z-1 has-checked:duration-1000"
|
||||||
>
|
>
|
||||||
<div
|
<input
|
||||||
id="env-top"
|
type="checkbox"
|
||||||
className="z-4 delay-500 transition-all duration-2000 absolute peer h-40 w-54 mt-0 bg-base-200 mask mask-triangle-2 scale-x-234 has-checked:scale-y-[-1] has-checked:-translate-y-full has-checked:z-1 has-checked:duration-1000"
|
className="transition checkbox absolute h-full w-full text-transparent bg-transparent z-100"
|
||||||
>
|
ref={flapCheckbox}
|
||||||
<input
|
|
||||||
type="checkbox"
|
|
||||||
className="transition checkbox absolute h-full w-full text-transparent bg-transparent z-100"
|
|
||||||
ref={flapCheckbox}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<img
|
|
||||||
className={
|
|
||||||
"translate-y-24 delay-2000 absolute z-6 peer-has-checked:pointer-events-none peer-has-checked:opacity-0 peer-has-checked:delay-0 transition-opacity duration-1000 cursor-pointer"
|
|
||||||
}
|
|
||||||
src={waxSeal}
|
|
||||||
alt="Seal"
|
|
||||||
onClick={() => flapCheckbox.current?.click()}
|
|
||||||
onKeyDown={() => flapCheckbox.current?.click()}
|
|
||||||
/>
|
/>
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
id="letter"
|
|
||||||
className={`absolute mx-auto transition-all peer-has-checked:delay-800 peer-has-checked:duration-1000 duration-1000 mt-2 h-55 w-105 bg-paper peer-has-checked:-mt-12 hover:-mt-24 cursor-pointer ${revealLetter ? "duration-1000 peer-has-checked:duration-3000 w-screen max-w-4xl h-screen z-101 -translate-y-90" : "peer-has-checked:z-1"}`}
|
|
||||||
onClick={handleClick}
|
|
||||||
></button>
|
|
||||||
|
|
||||||
<div
|
|
||||||
id="env-right"
|
|
||||||
className="absolute h-70 w-105 bg-base-300 mask mask-triangle-3 -mr-48 z-3 pointer-events-none"
|
|
||||||
></div>
|
|
||||||
<div
|
|
||||||
id="env-left"
|
|
||||||
className="absolute h-70 w-105 bg-base-300 mask mask-triangle-4 -ml-48 z-3 pointer-events-none"
|
|
||||||
></div>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
id="env-bottom"
|
|
||||||
className="absolute h-70 w-45 bg-base-200 mask mask-triangle-2 scale-y-[-1] mt-15 scale-x-240 z-3"
|
|
||||||
></button>
|
|
||||||
</div>
|
</div>
|
||||||
|
<img
|
||||||
|
className={
|
||||||
|
"translate-y-24 delay-2000 absolute z-6 peer-has-checked:pointer-events-none peer-has-checked:opacity-0 peer-has-checked:delay-0 transition-opacity duration-1000 cursor-pointer"
|
||||||
|
}
|
||||||
|
src={waxSeal}
|
||||||
|
alt="Seal"
|
||||||
|
onClick={() => flapCheckbox.current?.click()}
|
||||||
|
onKeyDown={() => flapCheckbox.current?.click()}
|
||||||
|
/>
|
||||||
<button
|
<button
|
||||||
id="env-front"
|
|
||||||
type="button"
|
type="button"
|
||||||
className={`text-left p-10 absolute inset-0 backface-hidden w-110 bg-base-200 z-99 rounded-md -translate-x-2 ${isFlipped ? "pointer-events-none" : ""}`}
|
id="letter"
|
||||||
onClick={() => setIsFlipped((prev) => !prev)}
|
className={`absolute mx-auto transition-all peer-has-checked:delay-800 peer-has-checked:duration-1000 duration-1000 mt-2 h-55 w-105 bg-paper peer-has-checked:-mt-12 hover:-mt-24 cursor-pointer ${revealLetter ? "duration-1000 peer-has-checked:duration-3000 w-screen max-w-4xl h-screen z-101 -translate-y-90" : "peer-has-checked:z-1"}`}
|
||||||
>
|
onClick={handleClick}
|
||||||
<span className={"text-neutral-content/60 font-xs font-display"}>
|
></button>
|
||||||
to
|
|
||||||
</span>
|
<div
|
||||||
<h1 className="text-3xl font-bold text-base-content">
|
id="env-right"
|
||||||
{recipient}
|
className="absolute h-70 w-105 bg-base-300 mask mask-triangle-3 -mr-48 z-3 pointer-events-none"
|
||||||
</h1>
|
></div>
|
||||||
<p className="text-base-content/60 font-display mt-8">{date}</p>
|
<div
|
||||||
<img
|
id="env-left"
|
||||||
src={stamp}
|
className="absolute h-70 w-105 bg-base-300 mask mask-triangle-4 -ml-48 z-3 pointer-events-none"
|
||||||
alt={"stamp"}
|
></div>
|
||||||
className={
|
<button
|
||||||
"z-0 rotate-6 opacity-80 text-accent absolute mt-0 mr-1 top-4 right-0"
|
type="button"
|
||||||
}
|
id="env-bottom"
|
||||||
/>
|
className="absolute h-70 w-45 bg-base-200 mask mask-triangle-2 scale-y-[-1] mt-15 scale-x-240 z-3"
|
||||||
<WavesIcon
|
></button>
|
||||||
className={"absolute mt-0 mr-12 top-18 right-8 text-primary"}
|
|
||||||
size={50}
|
|
||||||
/>
|
|
||||||
<WavesIcon
|
|
||||||
className={"absolute mt-0 mr-4 top-18 right-8 text-primary"}
|
|
||||||
size={50}
|
|
||||||
/>
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<button
|
||||||
|
id="env-front"
|
||||||
|
type="button"
|
||||||
|
className={`text-left p-10 absolute inset-0 backface-hidden w-110 bg-base-200 z-99 rounded-md -translate-x-2 ${isFlipped ? "pointer-events-none" : ""}`}
|
||||||
|
onClick={() => setIsFlipped((prev) => !prev)}
|
||||||
|
>
|
||||||
|
<span className={"text-neutral-content/60 font-xs font-display"}>
|
||||||
|
to
|
||||||
|
</span>
|
||||||
|
<h1 className="text-3xl font-bold text-base-content">{recipient}</h1>
|
||||||
|
<p className="text-base-content/60 font-display mt-8">{date}</p>
|
||||||
|
<img
|
||||||
|
src={stamp}
|
||||||
|
alt={"stamp"}
|
||||||
|
className={
|
||||||
|
"z-0 rotate-6 opacity-80 text-accent absolute mt-0 mr-1 top-4 right-0"
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<WavesIcon
|
||||||
|
className={"absolute mt-0 mr-12 top-18 right-8 text-primary"}
|
||||||
|
size={50}
|
||||||
|
/>
|
||||||
|
<WavesIcon
|
||||||
|
className={"absolute mt-0 mr-4 top-18 right-8 text-primary"}
|
||||||
|
size={50}
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
{ignite && (
|
{ignite && (
|
||||||
<div className="absolute w-90 h-60 bg-transparent z-100 overflow-hidden flex align-baseline">
|
<div className="absolute w-115 h-70 z-100 overflow-hidden flex align-baseline -translate-y-70 -translate-x-5">
|
||||||
<div
|
<div
|
||||||
className="absolute border-2 border-amber-200 -bottom-3 -right-3 w-0 h-0 transition-all duration-500 bg-base-100 rounded-tl-full rounded-bl-full origin-bottom-right"
|
className="absolute z-1000 border-2 border-amber-200 -bottom-3 -right-3 w-0 h-0 transition-all duration-500 bg-base-100 rounded-tl-full rounded-bl-full origin-bottom-right"
|
||||||
style={{
|
style={{
|
||||||
width: 2 * burn.width,
|
width: 2 * burn.width,
|
||||||
height: 2 * burn.height,
|
height: 2 * burn.height,
|
||||||
@@ -134,6 +139,6 @@ export function EnvelopeReveal({
|
|||||||
></div>
|
></div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
+41
-54
@@ -2,73 +2,60 @@
|
|||||||
@plugin "daisyui";
|
@plugin "daisyui";
|
||||||
|
|
||||||
@plugin "daisyui/theme" {
|
@plugin "daisyui/theme" {
|
||||||
name: "piku";
|
name: "piku";
|
||||||
default: true;
|
default: true;
|
||||||
prefersdark: true;
|
prefersdark: true;
|
||||||
color-scheme: dark;
|
color-scheme: dark;
|
||||||
|
|
||||||
/* ── Base surfaces ── */
|
--color-base-100: oklch(14% 0.012 35);
|
||||||
--color-base-100: oklch(14% 0.012 35); /* was 0.018 hue 50 */
|
--color-base-200: oklch(18% 0.014 33);
|
||||||
--color-base-200: oklch(18% 0.014 33);
|
--color-base-300: oklch(22% 0.016 32);
|
||||||
--color-base-300: oklch(22% 0.016 32);
|
--color-base-content: oklch(82% 0.02 70);
|
||||||
--color-base-content: oklch(
|
|
||||||
82% 0.02 70
|
|
||||||
); /* aged parchment, not crisp white */
|
|
||||||
|
|
||||||
/* ── Primary: old lamp gold — warm, incandescent ── */
|
--color-primary: oklch(67% 0.11 78);
|
||||||
--color-primary: oklch(67% 0.11 78);
|
--color-primary-content: oklch(15% 0.03 70);
|
||||||
--color-primary-content: oklch(15% 0.03 70);
|
|
||||||
|
|
||||||
/* ── Secondary: dusty plum ── */
|
--color-secondary: oklch(48% 0.08 305);
|
||||||
--color-secondary: oklch(48% 0.08 305);
|
--color-secondary-content: oklch(92% 0.01 305);
|
||||||
--color-secondary-content: oklch(92% 0.01 305);
|
|
||||||
|
|
||||||
/* ── Accent: muted lavender-clay ── */
|
--color-accent: oklch(55% 0.06 325);
|
||||||
--color-accent: oklch(55% 0.06 325);
|
--color-accent-content: oklch(18% 0.03 295);
|
||||||
--color-accent-content: oklch(18% 0.03 295);
|
|
||||||
|
|
||||||
/* ── Neutral: warm stone ── */
|
--color-neutral: oklch(28% 0.02 45);
|
||||||
--color-neutral: oklch(28% 0.02 45);
|
--color-neutral-content: oklch(80% 0.015 60);
|
||||||
--color-neutral-content: oklch(80% 0.015 60);
|
|
||||||
|
|
||||||
/* ── Semantic — desaturated, no alarm ── */
|
--color-info: oklch(60% 0.07 240);
|
||||||
--color-info: oklch(60% 0.07 240);
|
--color-info-content: oklch(95% 0.01 240);
|
||||||
--color-info-content: oklch(95% 0.01 240);
|
--color-success: oklch(60% 0.08 150);
|
||||||
--color-success: oklch(60% 0.08 150);
|
--color-success-content: oklch(16% 0.03 150);
|
||||||
--color-success-content: oklch(16% 0.03 150);
|
--color-warning: oklch(68% 0.08 72);
|
||||||
--color-warning: oklch(68% 0.08 72); /* honey, not caution-sign amber */
|
--color-warning-content: oklch(18% 0.03 60);
|
||||||
--color-warning-content: oklch(18% 0.03 60);
|
--color-error: oklch(55% 0.1 22);
|
||||||
--color-error: oklch(55% 0.1 22);
|
--color-error-content: oklch(92% 0.01 22);
|
||||||
--color-error-content: oklch(92% 0.01 22);
|
|
||||||
|
|
||||||
/* ── Shape ── */
|
--radius-selector: 0.5rem;
|
||||||
--radius-selector: 0.5rem;
|
--radius-field: 0.375rem;
|
||||||
--radius-field: 0.375rem;
|
--radius-box: 0.5rem;
|
||||||
--radius-box: 0.5rem;
|
|
||||||
|
|
||||||
/* ── Effects ── */
|
--depth: 1;
|
||||||
--depth: 1;
|
--noise: 0.03;
|
||||||
--noise: 0.03;
|
|
||||||
|
|
||||||
/* ── Border ── */
|
--border: 1px;
|
||||||
--border: 1px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@theme {
|
@theme {
|
||||||
--font-display: "Playwrite HR Lijeva Variable", cursive;
|
--font-display: "Playwrite HR Lijeva Variable", cursive;
|
||||||
--font-sans: "Jost Variable", sans-serif;
|
--font-sans: "Jost Variable", sans-serif;
|
||||||
--font-serif: "Playfair Display Variable", serif;
|
--font-serif: "Playfair Display Variable", serif;
|
||||||
--color-glass-bg: rgba(
|
--color-glass-bg: rgba(28,
|
||||||
28,
|
22,
|
||||||
22,
|
16,
|
||||||
16,
|
0.45);
|
||||||
0.45
|
--shadow-warm: 0 20px 50px -12px rgba(30, 20, 12, 0.6);
|
||||||
); /* slightly deeper to match new base */
|
--radius-xl: 1.5rem;
|
||||||
--shadow-warm: 0 20px 50px -12px rgba(30, 20, 12, 0.6);
|
--color-paper: oklch(97% 0.008 80);
|
||||||
--radius-xl: 1.5rem;
|
|
||||||
--color-paper: oklch(97% 0.008 80);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.glass-card {
|
.glass-card {
|
||||||
@apply bg-glass-bg backdrop-blur-xl border border-white/5 shadow-warm rounded-xl;
|
@apply bg-glass-bg backdrop-blur-xl border border-white/5 shadow-warm rounded-xl;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -244,23 +244,27 @@ export default function Reader() {
|
|||||||
<section className="min-h-fit w-full bg-base-100 px-4 py-8 md:py-16 font-serif relative overflow-hidden">
|
<section className="min-h-fit w-full bg-base-100 px-4 py-8 md:py-16 font-serif relative overflow-hidden">
|
||||||
<div className="fixed inset-0 bg-[radial-gradient(circle_at_center,transparent_0%,rgba(0,0,0,0.5)_100%)] pointer-events-none z-0" />
|
<div className="fixed inset-0 bg-[radial-gradient(circle_at_center,transparent_0%,rgba(0,0,0,0.5)_100%)] pointer-events-none z-0" />
|
||||||
<div
|
<div
|
||||||
className={`transition-all duration-1000 relative ${
|
className={`transition-all delay-300 duration-1000 relative ${
|
||||||
revealState === "revealed"
|
revealState === "revealed"
|
||||||
? "opacity-0 w-0 h-0 overflow-hidden invisible"
|
? "opacity-0 w-0 h-0 overflow-hidden invisible"
|
||||||
: "opacity-100"
|
: "opacity-100"
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{revealState === "sealed" && (
|
{revealState === "sealed" && (
|
||||||
<EnvelopeReveal
|
<div className="h-[80vh] mx-auto flex-col items-center flex justify-center">
|
||||||
recipient={metadata?.recipient || "Someone dear"}
|
<div className="perspective-distant scale-80 duration-1000 transition-all animate-[pulse_2s_linear_1]">
|
||||||
date={
|
<EnvelopeReveal
|
||||||
metadata?.updated_at
|
recipient={metadata?.recipient || "Someone dear"}
|
||||||
? formatDate(new Date(metadata.updated_at))
|
date={
|
||||||
: undefined
|
metadata?.updated_at
|
||||||
}
|
? formatDate(new Date(metadata.updated_at))
|
||||||
onRevealComplete={() => setRevealState("revealed")}
|
: undefined
|
||||||
ignite={ignite}
|
}
|
||||||
/>
|
onRevealComplete={() => setRevealState("revealed")}
|
||||||
|
ignite={ignite}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -275,7 +279,7 @@ export default function Reader() {
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
{revealState === "revealed" && (
|
{revealState === "revealed" && (
|
||||||
<div className="max-w-4xl m-8 mx-auto space-y-8 relative inset-0 z-100">
|
<div className="max-w-4xl m-8 mx-auto space-y-8 h-full relative inset-0 z-100">
|
||||||
<div className="relative group perspective-1000">
|
<div className="relative group perspective-1000">
|
||||||
<div className="absolute inset-0 bg-primary/5 blur-3xl rounded-full scale-75 opacity-0 group-hover:opacity-100 transition-opacity duration-1000 pointer-events-none" />
|
<div className="absolute inset-0 bg-primary/5 blur-3xl rounded-full scale-75 opacity-0 group-hover:opacity-100 transition-opacity duration-1000 pointer-events-none" />
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user