diff --git a/src/app/globals.css b/src/app/globals.css index c1666476..a96e80a5 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -43,6 +43,30 @@ h1, h2, h3, h4, h5, h6 { color: var(--bg-surface); } +/* Moleskine notebook cards */ +.moleskine-notebook { + position: relative; + transition: transform 0.4s ease-in-out; + perspective: 800px; +} + +.notebook-cover { + transition: transform 0.5s linear, box-shadow 0.5s linear; + transform-style: preserve-3d; + transform-origin: left center 0px; +} + +@media (hover: hover) { + .moleskine-notebook:hover { + transform: rotateZ(-10deg); + } + .moleskine-notebook:hover .notebook-cover { + transform: rotateY(-50deg); + z-index: 999; + box-shadow: 20px 10px 50px rgba(0, 0, 0, 0.2); + } +} + /* Custom select dropdown styling */ select { appearance: none; diff --git a/src/components/StoryCard.tsx b/src/components/StoryCard.tsx index ae609c01..cc03c9f2 100644 --- a/src/components/StoryCard.tsx +++ b/src/components/StoryCard.tsx @@ -23,100 +23,96 @@ export function StoryCard({ : false; return ( -
- {/* 3D Book with spine */} +
- {/* Drop shadow beneath book — grows on hover */} -
- - {/* Spine — hardcover hinge */} -
- - {/* Page edges visible on right side */} -
- - {/* Page edges visible on bottom */} + {/* Page underneath — revealed when cover opens */}
+ > +
+
+

+ {storyline.plot_count} {storyline.plot_count === 1 ? "plot" : "plots"} linked +

+ {storyline.token_address && ( +

+ +

+ )} +
+

Open to read →

+
+
- {/* Front cover */} + {/* Cover — opens on hover (desktop) */}
- {/* Spine inner shadow overlay */} + {/* Elastic band */}
- {/* Content */} -
- {/* Top: genre badge + completion */} + {/* Top area: genre badge */} +
- + {displayGenre || "Uncategorized"} {storyline.sunset && ( - + complete )}
+
- {/* Center: title displayed like printed book cover */} -
-

- {storyline.title} -

- {storyline.language && storyline.language !== "English" && ( - - {storyline.language} - - )} -
- - {/* Bottom: author name like a printed book */} -
- - {storyline.writer_type === 1 && } -
+ {/* Label band with title */} +
+

+ {storyline.title} +

+ {storyline.language && storyline.language !== "English" && ( + + {storyline.language} + + )}
- {/* Decorative horizontal rule near bottom */} -
+ {/* Bottom: author */} +
+ + {storyline.writer_type === 1 && } +
- {/* Metadata below book */} + {/* Metadata below notebook */}
{storyline.token_address && ( diff --git a/src/components/StoryGrid.tsx b/src/components/StoryGrid.tsx index e90b99b3..4b1ca5bf 100644 --- a/src/components/StoryGrid.tsx +++ b/src/components/StoryGrid.tsx @@ -1,98 +1,27 @@ "use client"; -import { useState, useEffect } from "react"; import { type Address } from "viem"; import { type Storyline } from "../../lib/supabase"; import { BatchTokenDataProvider } from "./BatchTokenDataProvider"; import { StoryCard } from "./StoryCard"; -/** - * Groups an array into chunks of the given size. - */ -function chunk(arr: T[], size: number): T[][] { - const result: T[][] = []; - for (let i = 0; i < arr.length; i += size) { - result.push(arr.slice(i, i + size)); - } - return result; -} - -/** - * Hook that returns the current shelf size (columns per row). - * Uses matchMedia to stay in sync with the CSS breakpoint. - */ -function useShelfSize(): number { - const [cols, setCols] = useState(3); - - useEffect(() => { - const mql = window.matchMedia("(min-width: 1024px)"); - const update = () => setCols(mql.matches ? 3 : 2); - update(); - mql.addEventListener("change", update); - return () => mql.removeEventListener("change", update); - }, []); - - return cols; -} - -/** - * A single bookshelf row: books sitting on a visible shelf surface. - */ -function ShelfRow({ children, cols }: { children: React.ReactNode; cols: number }) { - return ( -
- {/* Books */} -
- {children} -
- - {/* Shelf surface */} -
- {/* Shelf front edge */} -
-
- ); -} - /** * Story card grid wrapped in BatchTokenDataProvider. * Fetches price + TVL for all visible stories in a single multicall * instead of 4 individual RPC calls per card. * - * Books are displayed on shelves — each visual row of books sits on - * a visible shelf surface. Shelf size adapts to viewport (2 on mobile, 3 on desktop). + * Plain responsive grid — 2 columns on mobile, 3 on desktop. */ export function StoryGrid({ storylines }: { storylines: Storyline[] }) { const tokenAddresses = storylines .map((s) => s.token_address) .filter((addr): addr is string => !!addr) as Address[]; - const cols = useShelfSize(); - const shelves = chunk(storylines, cols); - return ( -
- {shelves.map((shelf, i) => ( - - {shelf.map((s) => ( - - ))} - +
+ {storylines.map((s) => ( + ))}