Skip to content

Commit f9e73da

Browse files
committed
content(dnd): clinical note
1 parent 5840f66 commit f9e73da

File tree

10 files changed

+208
-65
lines changed

10 files changed

+208
-65
lines changed

public/roadmap/roadmap.piml

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -316,4 +316,15 @@
316316
(priority) High
317317
(assignee) Gemini
318318
(created_at) 2026-01-08T14:00:00+03:00
319-
(notes) Implemented Void Architect achievement.
319+
(notes) Implemented Void Architect achievement.
320+
321+
> (issues)
322+
(id) FEZ-25
323+
(title) DND Content Expansion
324+
(description) Added new stories, characters, places, and items sections to the Dungeons & Dragons pages. Includes advanced filtering, modal views, and search functionality.
325+
(category) Content
326+
(status) Completed
327+
(priority) High
328+
(assignee) Gemini
329+
(created_at) 2026-01-08T18:00:00+03:00
330+
(notes) Major content update for Serfs & Frauds campaign.

public/stories/books.piml

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,4 +111,24 @@
111111
(author) fezcode
112112
(date) 2026-01-08
113113
(updated) 2026-01-08
114-
(overlay) orange
114+
(overlay) orange
115+
116+
> (book)
117+
(bookId) 6
118+
(bookTitle) The Sanity Ward
119+
(episodes)
120+
> (episode)
121+
(id) 1
122+
(filename) the-sanity-ward/clinical-note.txt
123+
(title) The Hysteria of the Moth (EN)
124+
(author) fezcode
125+
(date) 2026-01-08
126+
(updated) 2026-01-08
127+
> (episode)
128+
(id) 2
129+
(filename) the-sanity-ward/klinik-not.txt
130+
(title) Güve Histerisi (TR)
131+
(author) fezcode
132+
(date) 2026-01-08
133+
(updated) 2026-01-08
134+
(overlay) gray

public/stories/characters.piml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,3 +75,10 @@
7575
(book) The Chronomancer's Dust
7676
(description) A master clockmaker who uses ash from destroyed cities to power his Chronometers. He allows people to relive their past, but he never lets them change it.
7777
(status) Alive (Cynical)
78+
79+
> (character)
80+
(name) Dr. Brann Corshield
81+
(role) Chief Alienist / Villain
82+
(book) The Sanity Ward
83+
(description) An immensely egotistical psychiatrist who runs St. Jude's Asylum. Obsessed with his own image (literally) and maintaining "order," he seeks to lobotomize the Sultan of Butterflies to silence her "delusions."
84+
(status) Alive (Narcissistic)

public/stories/places.piml

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,69 +2,87 @@
22
> (place)
33
(name) Corrigan Investigations
44
(type) Office
5+
(category) Residences
56
(book) The Broken Path
67
(description) A dusty office with a flickering neon sign, smelling of stale smoke and cheap bourbon. The sanctuary of a man fighting his own shadow.
78
(status) Active
89

910
> (place)
1011
(name) The Tipsy Lizard
1112
(type) Tavern
13+
(category) Establishments
1214
(book) The Broken Path
1315
(description) A dive bar where secrets are traded for ale. The kind of place where you wipe your feet on the way out.
1416
(status) Active
1517

1618
> (place)
1719
(name) St. Marlowe Hotel
1820
(type) Inn / Flophouse
21+
(category) Establishments
1922
(book) The Broken Path
2023
(description) A rundown hotel for has-beens and never-weres. Its walls hold more regrets than paint.
2124
(status) Active
2225

2326
> (place)
2427
(name) Blade's Clinic
2528
(type) Medical Facility
29+
(category) Establishments
2630
(book) The Tales of Doctor Blade
2731
(description) A sanctuary of white marble and lavender scent. A place of miraculous healing, and sometimes, quiet death.
2832
(status) Active
2933

3034
> (place)
3135
(name) The Lower Ward
3236
(type) District
37+
(category) Locations
3338
(book) The Tales of Doctor Blade
3439
(description) The poorer district of the city, where an orphanage stands funded by questionable means.
3540
(status) Active
3641

3742
> (place)
3843
(name) Craninal
39-
(type) Town
44+
(type) City
45+
(category) Settlements
4046
(book) The Butterfly's Jest
41-
(description) A town where truth is a dangerous commodity and the only safe place to speak it is on a comedy stage.
47+
(description) A city where truth is a dangerous commodity and the only safe place to speak it is on a comedy stage.
4248
(status) Active
4349

4450
> (place)
4551
(name) The Laughing Stock
4652
(type) Comedy Club
53+
(category) Establishments
4754
(book) The Butterfly's Jest
4855
(description) The comedy club where the Sultan of Butterflies performs and gathers her silent army.
4956
(status) Active
5057

5158
> (place)
5259
(name) Music Festival Grounds
5360
(type) Event Location
61+
(category) Locations
5462
(book) The Butterfly's Jest
5563
(description) The site where Hellbound played "Last Breath" and Galarian Freiton took his.
5664
(status) Inactive
5765

5866
> (place)
5967
(name) Glareach
6068
(type) City
69+
(category) Settlements
6170
(book) The Sins of Glareach
6271
(description) Once a poor town, destroyed in the War of Fourbournes, now rebuilt as a neon-soaked metropolis of casinos and vice. A monument to Aester Cloudwear's greed.
6372
(status) Active (Corrupt)
6473

6574
> (place)
6675
(name) The Clockwork Sanctum
6776
(type) Workshop / Shop
77+
(category) Establishments
6878
(book) The Chronomancer's Dust
6979
(description) Klaude Barral's workshop in Craninal. A place where the air is thick with the smell of oil and the dust of dead cities, and where every tick of a clock sounds like a funeral bell.
7080
(status) Active
81+
82+
> (place)
83+
(name) St. Jude's Asylum
84+
(type) Medical Institution / Prison
85+
(category) Institutions
86+
(book) The Sanity Ward
87+
(description) A sterile fortress of white walls and silence, run by Dr. Brann Corshield. It is where the "mad" are sent to be broken and rebuilt in the image of conformity.
88+
(status) Active
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
# Clinical Note: The Hysteria of the Moth
2+
3+
**Subject:** The so-called "Sultan of Butterflies" (Widow Freiton)
4+
**Observer:** Dr. Brann Corshield, Chief Alienist, St. Jude’s Asylum
5+
6+
I looked up from my desk. On the opposite wall, the oil painting of me receiving the "Key to Craninal" seemed to nod in agreement. It is a magnificent piece, capturing the steel in my eyes, the commanding set of my jaw. It is one of three hundred and twelve portraits I have commissioned. A man of my intellect *should* be documented. Future generations will need reference material when they build statues in my honor.
7+
8+
I adjusted my silk tie in the reflection of the silver tea service—another portrait, fleeting but perfect.
9+
10+
"She is a cancer," I dictated to my trembling stenographer. "A chaotic anomaly in the ordered mind of our society."
11+
12+
The widow Freiton. She calls herself a comedian. A revolutionary. I call her what she is: a hysteric. A broken woman who could not process the simple, natural death of her husband and instead invented a fairy tale about glowing energy and government assassins.
13+
14+
And the people... the sheep... they listen to her! They wear those ridiculous pins. They laugh at her jokes as if they are scripture. It is an insult to Reason. It is an insult to *me*.
15+
16+
I am the architect of the public mind. I have cured the unruly. I have silenced the loud. When Aester Cloudwear—a man of vision, almost my equal—came to me with concerns about her "cult," I assured him the solution was clinical, not criminal.
17+
18+
Martyrs are made by bullets. Madwomen are unmade by white walls and quiet rooms.
19+
20+
I walked to the window, catching a glimpse of my reflection in the glass. Impeccable.
21+
22+
"Draft the warrant," I commanded. "Cite 'Public Endangerment' and 'Acute Delusional Paranoia.' We will bring her here. To St. Jude's."
23+
24+
I turned to the portrait above the fireplace—Me, in a toga, holding the scales of Justice.
25+
26+
"I will not just lock her up," I whispered to my painted self. "I will dismantle her. I will peel away the 'Sultan' layer by layer until only the weeping widow remains. And when she finally admits that Galarian Freiton died of a weak heart, I will be there to accept her apology."
27+
28+
I smiled. It was a dazzling smile. I really must have a painter capture this lighting.
29+
30+
"The Butterflies think they are free," I said, picking up a scalpel from my tray. "But every insect looks better pinned under glass."
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
# Klinik Not: Güve Histerisi
2+
3+
**Konu:** Sözde "Kelebeklerin Sultanı" (Dul Freiton)
4+
**Gözlemci:** Dr. Brann Corshield, Baş Alienist, St. Jude Tımarhanesi
5+
6+
Masamdan başımı kaldırdım. Karşı duvarda, "Craninal'ın Anahtarı"nı alırken resmedildiğim yağlı boya tablo onaylarcasına başını sallıyor gibiydi. Gözlerimdeki çeliği, çenemin o emredici duruşunu yakalayan muazzam bir eser. Sipariş ettiğim üç yüz on iki portreden biri. Benim zekamdaki bir adam belgelenmeli. Gelecek nesiller onuruma heykeller dikerken referans malzemesine ihtiyaç duyacaklar.
7+
8+
Gümüş çay takımının yansımasında ipek kravatımı düzelttim—başka bir portre, geçici ama kusursuz.
9+
10+
"O bir kanser," diye dikte ettim titreyen stenografıma. "Toplumumuzun düzenli zihninde kaotik bir anomali."
11+
12+
Dul Freiton. Kendine komedyen diyor. Devrimci diyor. Ben ona neyse onu diyorum: bir histerik. Kocasının basit, doğal ölümünü işleyemeyen ve bunun yerine parlayan enerji ve hükümet suikastçıları hakkında bir peri masalı uyduran kırık bir kadın.
13+
14+
Ve insanlar... o koyunlar... onu dinliyorlar! O gülünç rozetleri takıyorlar. Şakalarına sanki kutsal metinmiş gibi gülüyorlar. Bu Akla bir hakaret. Bu *bana* bir hakaret.
15+
16+
Ben kamu zihninin mimarıyım. Asileri iyileştirdim. Gürültücüleri susturdum. Aester Cloudwear—vizyon sahibi bir adam, neredeyse benim dengim—onun "tarikatı" hakkındaki endişeleriyle bana geldiğinde, ona çözümün kriminal değil, klinik olduğu konusunda güvence verdim.
17+
18+
Şehitler mermilerle yaratılır. Deli kadınlar ise beyaz duvarlar ve sessiz odalarla yok edilir.
19+
20+
Pencereye yürüdüm, camdaki yansımama bir göz attım. Kusursuz.
21+
22+
"Emri hazırla," diye emrettim. "'Kamu Güvenliğini Tehlikeye Atma' ve 'Akut Sanrısal Paranoya' maddelerini ekle. Onu buraya getireceğiz. St. Jude'a."
23+
24+
Şöminenin üzerindeki portreye döndüm—Ben, toga giymiş, Adalet terazisini tutarken.
25+
26+
"Onu sadece kilitlemeyeceğim," diye fısıldadım boyalı benliğime. "Onu parçalarına ayıracağım. Sadece ağlayan dul kalana kadar 'Sultan'ı katman katman soyacağım. Ve sonunda Galarian Freiton'un zayıf bir kalpten öldüğünü itiraf ettiğinde, özrünü kabul etmek için orada olacağım."
27+
28+
Gülümsedim. Göz kamaştırıcı bir gülümsemeydi. Bir ressama bu ışığı kesinlikle yakalatmalıyım.
29+
30+
"Kelebekler özgür olduklarını sanıyorlar," dedim, tepsimden bir neşter alarak. "Ama her böcek camın altında iğnelendiğinde daha iyi görünür."

src/pages/dnd/DndCharactersPage.jsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ function DndCharactersPage() {
105105
<UsersThree size={48} weight="duotone" />
106106
</div>
107107

108-
<h3 className="text-3xl font-playfairDisplay italic font-black text-dnd-crimson uppercase tracking-tighter mb-2 relative z-10">
108+
<h3 className="text-3xl font-playfairDisplay italic font-black text-[#b8860b] uppercase tracking-tighter mb-2 relative z-10">
109109
{char.name}
110110
</h3>
111111
<p className="text-xs font-arvo text-black/60 uppercase tracking-widest mb-4 font-bold relative z-10">

src/pages/dnd/DndItemsPage.jsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -213,4 +213,4 @@ function DndItemsPage() {
213213
);
214214
}
215215

216-
export default DndItemsPage;
216+
export default DndItemsPage;

src/pages/dnd/DndLorePage.jsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ function DndLorePage() {
7373
</header>
7474

7575
<section className="space-y-24">
76-
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-12">
76+
<div className="grid grid-cols-1 md:grid-cols-2 gap-12 max-w-5xl mx-auto">
7777
{filteredBooks.map((book, idx) => (
7878
<motion.div
7979
key={book.bookId}

src/pages/dnd/DndPlacesPage.jsx

Lines changed: 85 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import DndLayout from '../../components/dnd/DndLayout';
55
import DndSearchInput from '../../components/dnd/DndSearchInput';
66
import useSeo from '../../hooks/useSeo';
77
import piml from 'piml';
8-
import { MapTrifold, X } from '@phosphor-icons/react';
8+
import { MapTrifold, X, MapPin } from '@phosphor-icons/react';
99

1010
function DndPlacesPage() {
1111
useSeo({
@@ -53,6 +53,18 @@ function DndPlacesPage() {
5353
);
5454
});
5555

56+
// Categorize filtered places
57+
const categorizedPlaces = filteredPlaces.reduce((acc, place) => {
58+
const category = place.category || 'Uncharted';
59+
if (!acc[category]) {
60+
acc[category] = [];
61+
}
62+
acc[category].push(place);
63+
return acc;
64+
}, {});
65+
66+
const categories = Object.keys(categorizedPlaces).sort();
67+
5668
return (
5769
<DndLayout>
5870
<div className="max-w-7xl mx-auto px-6 py-12">
@@ -74,63 +86,78 @@ function DndPlacesPage() {
7486
/>
7587
</header>
7688

77-
<section className="space-y-24">
78-
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
79-
{filteredPlaces.map((place, idx) => (
80-
<motion.div
81-
key={idx}
82-
initial={{ opacity: 0, scale: 0.9 }}
83-
whileInView={{ opacity: 1, scale: 1 }}
84-
viewport={{ once: true }}
85-
transition={{ delay: idx * 0.1 }}
86-
onClick={() => setSelectedPlace(place)}
87-
className="cursor-pointer"
88-
>
89-
<div className="block group relative p-8 dnd-fantasy-card text-center h-full min-h-[400px] flex flex-col items-center justify-center border-2 border-black/20 shadow-2xl overflow-hidden hover:scale-[1.02] transition-transform duration-300">
90-
{/* Background Runes */}
91-
<div className="dnd-card-rune top-8 left-8 -rotate-12"></div>
92-
<div className="dnd-card-rune bottom-8 right-8 rotate-12"></div>
93-
94-
{/* Ink Splatters */}
95-
<div className="dnd-ink-splatter w-8 h-8 top-1/4 right-8" />
96-
<div className="dnd-ink-splatter w-4 h-4 bottom-1/3 left-12" />
97-
98-
{/* Ornate corners */}
99-
<div className="absolute top-4 left-4 w-8 h-8 border-t-2 border-l-2 border-dnd-gold opacity-60 group-hover:opacity-100 transition-all duration-500" />
100-
<div className="absolute top-4 right-4 w-8 h-8 border-t-2 border-r-2 border-dnd-gold opacity-60 group-hover:opacity-100 transition-all duration-500" />
101-
<div className="absolute bottom-4 left-4 w-8 h-8 border-b-2 border-l-2 border-dnd-gold opacity-60 group-hover:opacity-100 transition-all duration-500" />
102-
<div className="absolute bottom-4 right-4 w-8 h-8 border-b-2 border-r-2 border-dnd-gold opacity-60 group-hover:opacity-100 transition-all duration-500" />
103-
104-
<div className="mb-6 text-dnd-crimson group-hover:scale-110 transition-transform duration-500 relative z-10">
105-
<MapTrifold size={48} weight="duotone" />
106-
</div>
107-
108-
<h3 className="text-3xl font-playfairDisplay italic font-black text-dnd-crimson uppercase tracking-tighter mb-2 relative z-10">
109-
{place.name}
110-
</h3>
111-
<p className="text-xs font-arvo text-black/60 uppercase tracking-widest mb-4 font-bold relative z-10">
112-
{place.type}
113-
</p>
114-
115-
<div className="h-px w-16 bg-dnd-crimson/20 mb-6" />
116-
117-
<p className="text-base font-arvo text-black/70 leading-relaxed line-clamp-4 relative z-10 px-4">
118-
{place.description}
119-
</p>
120-
121-
<div className="mt-6 opacity-0 group-hover:opacity-100 transition-opacity duration-300 text-xs font-mono text-dnd-crimson uppercase tracking-widest">
122-
View Details
123-
</div>
124-
</div>
125-
</motion.div>
126-
))}
127-
{filteredPlaces.length === 0 && (
128-
<div className="col-span-full text-center py-12 text-white/60 font-arvo italic">
129-
No landmarks found in the atlas.
130-
</div>
131-
)}
132-
</div>
133-
</section>
89+
<div className="space-y-20">
90+
{categories.map((category) => (
91+
<section key={category} className="space-y-8">
92+
<div className="flex items-center gap-4 mb-8">
93+
<div className="h-px flex-grow bg-dnd-gold/20" />
94+
<h2 className="text-3xl md:text-4xl font-playfairDisplay italic font-black dnd-gold-gradient-text uppercase tracking-tighter flex items-center gap-3">
95+
<MapPin size={32} weight="duotone" className="text-dnd-gold" />
96+
{category}
97+
</h2>
98+
<div className="h-px flex-grow bg-dnd-gold/20" />
99+
</div>
100+
101+
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
102+
{categorizedPlaces[category].map((place, idx) => (
103+
<motion.div
104+
key={`${category}-${idx}`}
105+
initial={{ opacity: 0, scale: 0.9 }}
106+
whileInView={{ opacity: 1, scale: 1 }}
107+
viewport={{ once: true }}
108+
transition={{ delay: idx * 0.1 }}
109+
onClick={() => setSelectedPlace(place)}
110+
className="cursor-pointer"
111+
>
112+
<div className="block group relative p-8 dnd-fantasy-card text-center h-full min-h-[350px] flex flex-col items-center justify-center border-2 border-black/20 shadow-2xl overflow-hidden hover:scale-[1.02] transition-transform duration-300">
113+
{/* Background Runes */}
114+
<div className="dnd-card-rune top-8 left-8 -rotate-12"></div>
115+
<div className="dnd-card-rune bottom-8 right-8 rotate-12"></div>
116+
117+
{/* Ink Splatters */}
118+
<div className="dnd-ink-splatter w-8 h-8 top-1/4 right-8" />
119+
<div className="dnd-ink-splatter w-4 h-4 bottom-1/3 left-12" />
120+
121+
{/* Ornate corners */}
122+
<div className="absolute top-4 left-4 w-8 h-8 border-t-2 border-l-2 border-dnd-gold opacity-60 group-hover:opacity-100 transition-all duration-500" />
123+
<div className="absolute top-4 right-4 w-8 h-8 border-t-2 border-r-2 border-dnd-gold opacity-60 group-hover:opacity-100 transition-all duration-500" />
124+
<div className="absolute bottom-4 left-4 w-8 h-8 border-b-2 border-l-2 border-dnd-gold opacity-60 group-hover:opacity-100 transition-all duration-500" />
125+
<div className="absolute bottom-4 right-4 w-8 h-8 border-b-2 border-r-2 border-dnd-gold opacity-60 group-hover:opacity-100 transition-all duration-500" />
126+
127+
<div className="mb-6 text-dnd-crimson group-hover:scale-110 transition-transform duration-500 relative z-10">
128+
<MapTrifold size={48} weight="duotone" />
129+
</div>
130+
131+
<h3 className="text-2xl font-playfairDisplay italic font-black text-dnd-crimson uppercase tracking-tighter mb-2 relative z-10">
132+
{place.name}
133+
</h3>
134+
135+
<p className="text-xs font-arvo text-black/60 uppercase tracking-widest mb-4 font-bold relative z-10">
136+
{place.type}
137+
</p>
138+
139+
<div className="h-px w-16 bg-dnd-crimson/20 mb-6 mt-4" />
140+
141+
<p className="text-sm font-arvo text-black/70 leading-relaxed line-clamp-4 relative z-10 px-4">
142+
{place.description}
143+
</p>
144+
145+
<div className="mt-6 opacity-0 group-hover:opacity-100 transition-opacity duration-300 text-xs font-mono text-dnd-crimson uppercase tracking-widest">
146+
View Details
147+
</div>
148+
</div>
149+
</motion.div>
150+
))}
151+
</div>
152+
</section>
153+
))}
154+
155+
{filteredPlaces.length === 0 && (
156+
<div className="col-span-full text-center py-12 text-white/60 font-arvo italic">
157+
No landmarks found in the atlas.
158+
</div>
159+
)}
160+
</div>
134161

135162
{/* Place Modal */}
136163
<AnimatePresence>

0 commit comments

Comments
 (0)