Skip to content

Commit 7c88073

Browse files
committed
feat: new modals
1 parent fc9b88e commit 7c88073

File tree

4 files changed

+109
-213
lines changed

4 files changed

+109
-213
lines changed

src/components/BrutalistModal.jsx

Lines changed: 43 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -1,118 +1,71 @@
1-
import React from 'react';
1+
import React, { useEffect } from 'react';
22
import { motion, AnimatePresence } from 'framer-motion';
3-
import { XIcon, ArrowSquareOutIcon } from '@phosphor-icons/react';
4-
import GenerativeArt from './GenerativeArt';
3+
import { XIcon } from '@phosphor-icons/react';
54

6-
const BrutalistModal = ({
7-
isOpen,
8-
onClose,
9-
item,
10-
children
11-
}) => {
12-
if (!item) return null;
5+
const BrutalistModal = ({ isOpen, onClose, title, children }) => {
6+
useEffect(() => {
7+
const handleKeyDown = (event) => {
8+
if (event.key === 'Escape' && isOpen) {
9+
onClose();
10+
}
11+
};
1312

14-
const title = item.label || item.title;
15-
const description = item.description;
16-
const url = item.url;
13+
if (isOpen) {
14+
document.addEventListener('keydown', handleKeyDown);
15+
document.body.style.overflow = 'hidden';
16+
} else {
17+
document.removeEventListener('keydown', handleKeyDown);
18+
document.body.style.overflow = '';
19+
}
20+
21+
return () => {
22+
document.removeEventListener('keydown', handleKeyDown);
23+
document.body.style.overflow = '';
24+
};
25+
}, [isOpen, onClose]);
1726

1827
return (
1928
<AnimatePresence>
2029
{isOpen && (
21-
<div className="fixed inset-0 z-[100] flex items-center justify-center p-4 md:p-12">
22-
{/* Backdrop */}
30+
<div className="fixed inset-0 z-[1000] flex items-center justify-center p-4">
2331
<motion.div
2432
initial={{ opacity: 0 }}
2533
animate={{ opacity: 1 }}
2634
exit={{ opacity: 0 }}
2735
onClick={onClose}
28-
className="absolute inset-0 bg-black/90 backdrop-blur-xl"
36+
className="absolute inset-0 bg-black/80 backdrop-blur-sm"
2937
/>
3038

31-
{/* Modal Container */}
3239
<motion.div
33-
initial={{ opacity: 0, scale: 0.95, y: 40 }}
40+
initial={{ opacity: 0, scale: 0.95, y: 20 }}
3441
animate={{ opacity: 1, scale: 1, y: 0 }}
35-
exit={{ opacity: 0, scale: 0.95, y: 40 }}
36-
className="relative w-full max-w-4xl bg-[#050505] border border-white/20 rounded-sm shadow-2xl overflow-hidden flex flex-col md:flex-row h-full md:h-auto max-h-[90vh]"
42+
exit={{ opacity: 0, scale: 0.95, y: 20 }}
43+
transition={{ duration: 0.3, ease: [0.23, 1, 0.32, 1] }}
44+
className="relative w-full max-w-xl bg-[#050505] border border-white/10 rounded-sm shadow-2xl overflow-hidden flex flex-col"
3745
onClick={(e) => e.stopPropagation()}
3846
>
39-
{/* Left/Top side: Large Art */}
40-
<div className="relative w-full md:w-1/2 h-64 md:h-auto border-b md:border-b-0 md:border-r border-white/20">
41-
{item.image ? (
42-
<img src={item.image} alt={title} className="w-full h-full object-cover transition-all duration-700" />
43-
) : (
44-
<GenerativeArt seed={"2"+title+"[]"} className="w-full h-full" />
45-
)}
46-
<div className="absolute inset-0 bg-gradient-to-t md:bg-gradient-to-r from-[#050505] to-transparent opacity-60" />
47+
<div className="flex items-center justify-between p-6 border-b border-white/10 bg-white/[0.02]">
48+
<h2 className="text-2xl font-black uppercase tracking-tighter text-white">
49+
{title}
50+
</h2>
51+
<button
52+
onClick={onClose}
53+
className="p-2 text-gray-500 hover:text-emerald-500 hover:bg-white/5 rounded-sm transition-all"
54+
>
55+
<XIcon size={24} weight="bold" />
56+
</button>
4757
</div>
4858

49-
{/* Right side: Content */}
50-
<div className="flex-1 flex flex-col p-8 md:p-12 relative overflow-y-auto">
51-
{/* Decorative background logo */}
52-
<div className="absolute top-0 right-0 p-12 opacity-[0.02] pointer-events-none select-none">
53-
<h1 className="text-9xl font-black font-playfairDisplay leading-none">FC</h1>
54-
</div>
55-
56-
<div className="flex justify-between items-start mb-12">
57-
<div className="space-y-1">
58-
<div className="h-1 w-12 bg-emerald-500 mb-4" />
59-
<h2 className="text-4xl md:text-5xl font-normal font-playfairDisplay tracking-tighter text-white uppercase leading-none break-all">
60-
{title}
61-
</h2>
62-
</div>
63-
<button
64-
onClick={onClose}
65-
className="p-2 text-gray-500 hover:text-white transition-colors"
66-
>
67-
<XIcon weight="bold" size={32} />
68-
</button>
69-
</div>
70-
71-
{children ? (
72-
<div className="flex-grow">
73-
{children}
74-
</div>
75-
) : (
76-
<>
77-
<div className="flex-grow space-y-8">
78-
<p className="text-xl md:text-2xl text-gray-300 font-arvo leading-relaxed">
79-
{description}
80-
</p>
81-
{item.author && (
82-
<div className="pt-4 flex items-center gap-3">
83-
<div className="w-8 h-px bg-white/20" />
84-
<span className="text-xs font-mono uppercase tracking-[0.4em] text-gray-500">
85-
Authored by // {item.author}
86-
</span>
87-
</div>
88-
)}
89-
</div>
90-
91-
{url && url !== '#' && (
92-
<div className="mt-12">
93-
<a
94-
href={url}
95-
target="_blank"
96-
rel="noopener noreferrer"
97-
className="group inline-flex items-center gap-4 bg-white text-black px-8 py-5 font-mono font-black text-xs uppercase tracking-[0.4em] hover:bg-emerald-400 transition-all w-full md:w-auto"
98-
>
99-
<span>{item.actionLabel || "Visit"}</span>
100-
<ArrowSquareOutIcon weight="bold" size={20} />
101-
</a>
102-
</div>
103-
)}
104-
</>
105-
)}
106-
107-
{/* Grid corner markers */}
108-
<div className="absolute bottom-0 right-0 w-4 h-4 border-b border-r border-white/20 m-4" />
109-
<div className="absolute bottom-0 right-6 w-1 h-1 bg-emerald-500 m-4 rounded-full animate-pulse" />
59+
<div className="p-8 max-h-[70vh] overflow-y-auto scrollbar-hide text-gray-300 font-mono text-sm leading-relaxed">
60+
{children}
11061
</div>
62+
63+
<div className="h-1 w-full bg-gradient-to-r from-emerald-500/0 via-emerald-500/50 to-emerald-500/0 opacity-30" />
11164
</motion.div>
11265
</div>
11366
)}
11467
</AnimatePresence>
11568
);
11669
};
11770

118-
export default BrutalistModal;
71+
export default BrutalistModal;

src/components/ContactModal.jsx

Lines changed: 25 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ import {
99
ArrowUpRightIcon,
1010
} from '@phosphor-icons/react';
1111
import GenericModal from './GenericModal';
12-
import LuxeModal from './LuxeModal';
1312
import { useSiteConfig } from '../context/SiteConfigContext';
1413
import { useVisualSettings } from '../context/VisualSettingsContext';
1514

@@ -24,19 +23,30 @@ const socialIcons = {
2423
const ContactModal = ({ isOpen, onClose }) => {
2524
const { config } = useSiteConfig();
2625
const { fezcodexTheme } = useVisualSettings();
26+
const isLuxe = fezcodexTheme === 'luxe';
2727

28-
if (fezcodexTheme === 'luxe') {
29-
return (
30-
<LuxeModal isOpen={isOpen} onClose={onClose} title="Establish Contact">
31-
<div className="flex flex-col gap-8">
32-
<p className="font-outfit text-sm text-[#1A1A1A]/60 italic leading-relaxed">
28+
return (
29+
<GenericModal
30+
isOpen={isOpen}
31+
onClose={onClose}
32+
title={isLuxe ? "Establish Contact" : "Contact"}
33+
>
34+
<div className="flex flex-col gap-6">
35+
{isLuxe ? (
36+
<p className="font-outfit text-sm text-[#1A1A1A]/60 italic leading-relaxed mb-2">
3337
Choose a preferred channel to initiate communication with the primary node.
3438
</p>
39+
) : (
40+
<p className="text-gray-400 mb-2 font-mono uppercase tracking-widest text-[10px]">
41+
{'//'} Establish connection via established protocols:
42+
</p>
43+
)}
3544

36-
<div className="grid grid-cols-1 gap-4">
37-
{config?.socials &&
38-
config.socials.map((link) => {
39-
const Icon = socialIcons[link.icon] || GlobeIcon;
45+
<div className={`grid grid-cols-1 ${isLuxe ? 'gap-4' : 'gap-3'}`}>
46+
{config?.socials &&
47+
config.socials.map((link) => {
48+
const Icon = socialIcons[link.icon] || GlobeIcon;
49+
if (isLuxe) {
4050
return (
4151
<LuxeContactLink
4252
key={link.id}
@@ -46,27 +56,9 @@ const ContactModal = ({ isOpen, onClose }) => {
4656
value={link.url.replace(/^mailto:/, '').replace(/^https?:\/\//, '')}
4757
/>
4858
);
49-
})}
50-
</div>
51-
</div>
52-
</LuxeModal>
53-
);
54-
}
55-
56-
return (
57-
<GenericModal isOpen={isOpen} onClose={onClose} title="Contact">
58-
<div className="flex flex-col gap-6">
59-
<p className="text-gray-400 mb-2 font-mono uppercase tracking-widest text-[10px]">
60-
{/* // CONNECT_PROTOCOLS */}
61-
{'//'} Establish connection via established protocols:
62-
</p>
63-
64-
<div className="flex flex-col gap-3">
65-
{config?.socials &&
66-
config.socials.map((link) => {
67-
const Icon = socialIcons[link.icon] || GlobeIcon;
59+
}
6860
return (
69-
<ContactLink
61+
<BrutalistContactLink
7062
key={link.id}
7163
href={link.url}
7264
icon={Icon}
@@ -103,7 +95,7 @@ const LuxeContactLink = ({ href, icon: Icon, label, value }) => (
10395
</a>
10496
);
10597

106-
const ContactLink = ({ href, icon: Icon, label, value }) => (
98+
const BrutalistContactLink = ({ href, icon: Icon, label, value }) => (
10799
<a
108100
href={href}
109101
target="_blank"
@@ -116,7 +108,7 @@ const ContactLink = ({ href, icon: Icon, label, value }) => (
116108
weight="bold"
117109
className="text-emerald-500 group-hover:text-black transition-colors"
118110
/>
119-
<div className="flex flex-col">
111+
<div className="flex flex-col text-white group-hover:text-black">
120112
<span className="text-[10px] font-mono uppercase tracking-widest opacity-50">
121113
{label}
122114
</span>
@@ -126,4 +118,4 @@ const ContactLink = ({ href, icon: Icon, label, value }) => (
126118
</a>
127119
);
128120

129-
export default ContactModal;
121+
export default ContactModal;

src/components/GenericModal.jsx

Lines changed: 11 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -1,77 +1,16 @@
1-
import React, { useEffect } from 'react';
2-
import { motion, AnimatePresence } from 'framer-motion';
3-
import { XIcon } from '@phosphor-icons/react';
1+
import React from 'react';
2+
import { useVisualSettings } from '../context/VisualSettingsContext';
3+
import BrutalistModal from './BrutalistModal';
4+
import LuxeModal from './LuxeModal';
45

5-
const GenericModal = ({ isOpen, onClose, title, children }) => {
6-
useEffect(() => {
7-
const handleKeyDown = (event) => {
8-
if (event.key === 'Escape' && isOpen) {
9-
onClose();
10-
}
11-
};
6+
const GenericModal = (props) => {
7+
const { fezcodexTheme } = useVisualSettings();
128

13-
if (isOpen) {
14-
document.addEventListener('keydown', handleKeyDown);
15-
document.body.style.overflow = 'hidden';
16-
} else {
17-
document.removeEventListener('keydown', handleKeyDown);
18-
document.body.style.overflow = '';
19-
}
9+
if (fezcodexTheme === 'luxe') {
10+
return <LuxeModal {...props} />;
11+
}
2012

21-
return () => {
22-
document.removeEventListener('keydown', handleKeyDown);
23-
document.body.style.overflow = '';
24-
};
25-
}, [isOpen, onClose]);
26-
27-
return (
28-
<AnimatePresence>
29-
{isOpen && (
30-
<div className="fixed inset-0 z-[1000] flex items-center justify-center p-4">
31-
{/* Overlay */}
32-
<motion.div
33-
initial={{ opacity: 0 }}
34-
animate={{ opacity: 1 }}
35-
exit={{ opacity: 0 }}
36-
onClick={onClose}
37-
className="absolute inset-0 bg-black/80 backdrop-blur-sm"
38-
/>
39-
40-
{/* Modal Container */}
41-
<motion.div
42-
initial={{ opacity: 0, scale: 0.95, y: 20 }}
43-
animate={{ opacity: 1, scale: 1, y: 0 }}
44-
exit={{ opacity: 0, scale: 0.95, y: 20 }}
45-
transition={{ duration: 0.3, ease: [0.23, 1, 0.32, 1] }}
46-
className="relative w-full max-w-xl bg-[#050505] border border-white/10 rounded-sm shadow-2xl overflow-hidden flex flex-col"
47-
onClick={(e) => e.stopPropagation()}
48-
>
49-
{/* Header */}
50-
<div className="flex items-center justify-between p-6 border-b border-white/10 bg-white/[0.02]">
51-
<h2 className="text-2xl font-black uppercase tracking-tighter text-white">
52-
{title}
53-
</h2>
54-
<button
55-
onClick={onClose}
56-
className="p-2 text-gray-500 hover:text-emerald-500 hover:bg-white/5 rounded-sm transition-all"
57-
aria-label="Close Modal"
58-
>
59-
<XIcon size={24} weight="bold" />
60-
</button>
61-
</div>
62-
63-
{/* Body */}
64-
<div className="p-8 max-h-[70vh] overflow-y-auto scrollbar-hide text-gray-300 font-mono text-sm leading-relaxed">
65-
{children}
66-
</div>
67-
68-
{/* Footer Accent (Optional) */}
69-
<div className="h-1 w-full bg-gradient-to-r from-emerald-500/0 via-emerald-500/50 to-emerald-500/0 opacity-30" />
70-
</motion.div>
71-
</div>
72-
)}
73-
</AnimatePresence>
74-
);
13+
return <BrutalistModal {...props} />;
7514
};
7615

77-
export default GenericModal;
16+
export default GenericModal;

0 commit comments

Comments
 (0)