Skip to content

Commit 5088af8

Browse files
committed
feat: implement interactive 3D book models entirely in discovery
1 parent ddd68be commit 5088af8

2 files changed

Lines changed: 149 additions & 35 deletions

File tree

‎app.js‎

Lines changed: 9 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,9 @@ class BookRenderer {
134134
<img src="${thumb.replace('http:', 'https:')}" alt="${title}">
135135
</div>
136136
<div class="book__face book__face--spine" style="background: ${randomSpine}"></div>
137-
<div class="book__face book_face--right"></div>
137+
<div class="book__face book__face--right"></div>
138+
<div class="book__face book__face--top"></div>
139+
<div class="book__face book__face--bottom"></div>
138140
<div class="book__face book__face--back">
139141
<div style="overflow-y: auto; height: 100%; padding-right: 5px; scrollbar-width: thin;">
140142
<div style="font-weight: bold; font-size: 0.9rem; margin-bottom: 0.5rem; color: var(--text-main);">${title}</div>
@@ -797,35 +799,13 @@ class GenreManager {
797799
}
798800
}
799801

800-
renderBooks(books) {
802+
async renderBooks(books) {
801803
this.booksGrid.innerHTML = '';
802-
803-
books.forEach(book => {
804-
const info = book.volumeInfo;
805-
const title = info.title || 'Untitled';
806-
const author = info.authors ? info.authors[0] : 'Unknown';
807-
const thumbnail = info.imageLinks ?
808-
(info.imageLinks.thumbnail || info.imageLinks.smallThumbnail) :
809-
'https://via.placeholder.com/128x196?text=No+Cover';
810-
811-
const card = document.createElement('div');
812-
card.className = 'genre-book-card';
813-
card.innerHTML = `
814-
<img src="${thumbnail}" alt="${title}" loading="lazy">
815-
<div class="genre-book-info">
816-
<h4>${title}</h4>
817-
<p>${author}</p>
818-
</div>
819-
`;
820-
821-
// Add click listener to open detailed view (using existing renderer logic if possible, or just mock it)
822-
// For now, let's just use the existing BookRenderer's modal if accessible,
823-
// or just simple log. The user asked for "modal should open up with some books".
824-
// The books themselves inside the modal don't necessarily need to open *another* modal,
825-
// but it would be nice.
826-
827-
this.booksGrid.appendChild(card);
828-
});
804+
const renderer = new BookRenderer(new LibraryManager());
805+
for (const book of books) {
806+
const el = await renderer.createBookElement(book);
807+
this.booksGrid.appendChild(el);
808+
}
829809
}
830810
}
831811

‎style.css‎

Lines changed: 140 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1827,9 +1827,12 @@ main {
18271827

18281828
/* Genre Books Grid */
18291829
.genre-books-grid {
1830-
display: grid;
1831-
grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
1832-
gap: 1.5rem;
1830+
display: flex;
1831+
flex-wrap: wrap;
1832+
justify-content: center;
1833+
gap: 3rem;
1834+
padding: 2rem;
1835+
perspective: 1000px;
18331836
}
18341837

18351838
.genre-book-card {
@@ -4939,9 +4942,12 @@ main {
49394942

49404943
/* Genre Books Grid */
49414944
.genre-books-grid {
4942-
display: grid;
4943-
grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
4944-
gap: 1.5rem;
4945+
display: flex;
4946+
flex-wrap: wrap;
4947+
justify-content: center;
4948+
gap: 3rem;
4949+
padding: 2rem;
4950+
perspective: 1000px;
49454951
}
49464952

49474953
.genre-book-card {
@@ -6570,3 +6576,131 @@ main {
65706576
background-color: var(--wood-dark);
65716577
color: #fff;
65726578
}
6579+
/* =========================================
6580+
OVERRIDE & FIX 3D BOOK BOX MODEL
6581+
========================================= */
6582+
6583+
.book-scene {
6584+
width: var(--book-width);
6585+
height: var(--book-height);
6586+
perspective: 1500px;
6587+
cursor: pointer;
6588+
}
6589+
6590+
.book {
6591+
width: 100%;
6592+
height: 100%;
6593+
position: relative;
6594+
transform-style: preserve-3d;
6595+
transform-origin: center center;
6596+
/* Move whole book back by half its thickness so it sits flush when flipped */
6597+
transform: translateZ(calc(var(--book-thick) * -0.5));
6598+
transition: transform 0.6s cubic-bezier(0.34, 1.56, 0.64, 1), box-shadow 0.6s, z-index 0.6s;
6599+
animation: floatIn 0.8s ease-out backwards;
6600+
}
6601+
6602+
.book__face {
6603+
position: absolute;
6604+
backface-visibility: hidden;
6605+
/* Default rounded corners just in case */
6606+
border-radius: 4px;
6607+
}
6608+
6609+
.book__face--front {
6610+
width: 100%;
6611+
height: 100%;
6612+
top: 0;
6613+
left: 0;
6614+
z-index: 2;
6615+
transform: translateZ(calc(var(--book-thick) * 0.5));
6616+
background: var(--book-bg);
6617+
border-radius: 4px 8px 8px 4px;
6618+
box-shadow: inset 4px 0 10px rgba(0, 0, 0, 0.1);
6619+
}
6620+
6621+
.book__face--front img {
6622+
width: 100%;
6623+
height: 100%;
6624+
object-fit: cover;
6625+
border-radius: 4px 8px 8px 4px;
6626+
}
6627+
6628+
.book__face--back {
6629+
width: 100%;
6630+
height: 100%;
6631+
top: 0;
6632+
left: 0;
6633+
z-index: 1;
6634+
transform: rotateY(180deg) translateZ(calc(var(--book-thick) * 0.5));
6635+
background: var(--paper-color);
6636+
border-radius: 6px 4px 4px 6px;
6637+
padding: 1.5rem;
6638+
display: flex;
6639+
flex-direction: column;
6640+
justify-content: space-between;
6641+
border: 1px solid var(--glass-border);
6642+
color: #2c2420;
6643+
}
6644+
6645+
.book__face--spine {
6646+
width: var(--book-thick);
6647+
height: 100%;
6648+
top: 0;
6649+
left: 0; /* Center connects at X = half thickness */
6650+
transform: rotateY(-90deg) translateZ(calc(var(--book-thick) * 0.5));
6651+
background: #333; /* Override randomly via JS inline style */
6652+
box-shadow: inset -3px 0 6px rgba(0,0,0,0.3), inset 3px 0 6px rgba(255,255,255,0.1);
6653+
}
6654+
6655+
.book__face--right {
6656+
width: var(--book-thick);
6657+
height: 100%;
6658+
top: 0;
6659+
right: 0; /* Placed at the right edge */
6660+
left: auto;
6661+
transform: rotateY(90deg) translateZ(calc(var(--book-thick) * 0.5));
6662+
background: repeating-linear-gradient(to right, #fdfbf7 0px, #fdfbf7 2px, #dfd8c8 3px);
6663+
box-shadow: inset 0 0 5px rgba(0,0,0,0.1);
6664+
}
6665+
6666+
.book__face--top {
6667+
width: 100%;
6668+
height: var(--book-thick);
6669+
top: 0;
6670+
left: 0;
6671+
transform: rotateX(90deg) translateZ(calc(var(--book-thick) * 0.5));
6672+
background: repeating-linear-gradient(to bottom, #fdfbf7 0px, #fdfbf7 2px, #dfd8c8 3px);
6673+
box-shadow: inset 0 0 5px rgba(0,0,0,0.1);
6674+
}
6675+
6676+
.book__face--bottom {
6677+
width: 100%;
6678+
height: var(--book-thick);
6679+
bottom: 0;
6680+
left: 0;
6681+
transform: rotateX(-90deg) translateZ(calc(var(--book-thick) * 0.5));
6682+
background: repeating-linear-gradient(to top, #fdfbf7 0px, #fdfbf7 2px, #dfd8c8 3px);
6683+
box-shadow: inset 0 0 5px rgba(0,0,0,0.1);
6684+
}
6685+
6686+
/* Enhancing Interactivity: Better 3D Spin and Popup on Hover */
6687+
.book-scene:hover {
6688+
z-index: 50; /* Bring scene forward immediately */
6689+
}
6690+
6691+
.book-scene:hover .book:not(.flipped) {
6692+
transform: translateZ(50px) translateY(-15px) rotateX(15deg) rotateY(-25deg);
6693+
box-shadow: -10px 20px 30px rgba(0,0,0,0.2);
6694+
}
6695+
6696+
/* Enhanced Flipped State with 3D Pop */
6697+
.book.flipped {
6698+
transform: rotateY(180deg) scale(1.3) translateZ(80px);
6699+
z-index: 100;
6700+
}
6701+
6702+
/* Fixing Flipped Transform */
6703+
.book.flipped {
6704+
transform: translateY(-20px) translateZ(100px) rotateY(180deg) scale(1.1) !important;
6705+
z-index: 100 !important;
6706+
}

0 commit comments

Comments
 (0)