OhMyDitzzy
commited on
Commit
·
c586cfa
1
Parent(s):
56ca819
anything
Browse files- src/modules/comic/ComicLanding.css +113 -2
- src/modules/comic/ComicLanding.tsx +64 -25
src/modules/comic/ComicLanding.css
CHANGED
|
@@ -292,12 +292,38 @@
|
|
| 292 |
font-size: 1.25rem;
|
| 293 |
}
|
| 294 |
|
| 295 |
-
.synopsis
|
| 296 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 297 |
line-height: 1.7;
|
| 298 |
opacity: 0.85;
|
| 299 |
}
|
| 300 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 301 |
.chapters-section {
|
| 302 |
max-width: 1200px;
|
| 303 |
margin: 0 auto;
|
|
@@ -359,10 +385,39 @@
|
|
| 359 |
border-color: #667eea;
|
| 360 |
}
|
| 361 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 362 |
.chapters-list {
|
| 363 |
display: flex;
|
| 364 |
flex-direction: column;
|
| 365 |
gap: 0.75rem;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 366 |
}
|
| 367 |
|
| 368 |
.chapter-item {
|
|
@@ -375,12 +430,15 @@
|
|
| 375 |
border-radius: 12px;
|
| 376 |
cursor: pointer;
|
| 377 |
transition: all 0.3s;
|
|
|
|
|
|
|
| 378 |
}
|
| 379 |
|
| 380 |
.chapter-item:hover {
|
| 381 |
background: rgba(255, 255, 255, 0.08);
|
| 382 |
border-color: #667eea;
|
| 383 |
transform: translateX(4px);
|
|
|
|
| 384 |
}
|
| 385 |
|
| 386 |
.chapter-info {
|
|
@@ -417,6 +475,36 @@
|
|
| 417 |
transform: translateX(4px);
|
| 418 |
}
|
| 419 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 420 |
.no-results {
|
| 421 |
text-align: center;
|
| 422 |
padding: 3rem;
|
|
@@ -493,4 +581,27 @@
|
|
| 493 |
.auth-card {
|
| 494 |
padding: 2rem;
|
| 495 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 496 |
}
|
|
|
|
| 292 |
font-size: 1.25rem;
|
| 293 |
}
|
| 294 |
|
| 295 |
+
.synopsis-content {
|
| 296 |
+
position: relative;
|
| 297 |
+
}
|
| 298 |
+
|
| 299 |
+
.synopsis-content p {
|
| 300 |
+
margin: 0 0 0.5rem 0;
|
| 301 |
line-height: 1.7;
|
| 302 |
opacity: 0.85;
|
| 303 |
}
|
| 304 |
|
| 305 |
+
.expand-btn {
|
| 306 |
+
background: none;
|
| 307 |
+
border: none;
|
| 308 |
+
color: #667eea;
|
| 309 |
+
font-size: 0.9rem;
|
| 310 |
+
font-weight: 600;
|
| 311 |
+
cursor: pointer;
|
| 312 |
+
padding: 0.5rem 0;
|
| 313 |
+
transition: all 0.2s ease;
|
| 314 |
+
text-decoration: none;
|
| 315 |
+
display: inline-block;
|
| 316 |
+
}
|
| 317 |
+
|
| 318 |
+
.expand-btn:hover {
|
| 319 |
+
color: #5568d3;
|
| 320 |
+
text-decoration: underline;
|
| 321 |
+
}
|
| 322 |
+
|
| 323 |
+
.expand-btn:active {
|
| 324 |
+
color: #4451b8;
|
| 325 |
+
}
|
| 326 |
+
|
| 327 |
.chapters-section {
|
| 328 |
max-width: 1200px;
|
| 329 |
margin: 0 auto;
|
|
|
|
| 385 |
border-color: #667eea;
|
| 386 |
}
|
| 387 |
|
| 388 |
+
.chapters-list-wrapper {
|
| 389 |
+
position: relative;
|
| 390 |
+
}
|
| 391 |
+
|
| 392 |
.chapters-list {
|
| 393 |
display: flex;
|
| 394 |
flex-direction: column;
|
| 395 |
gap: 0.75rem;
|
| 396 |
+
position: relative;
|
| 397 |
+
}
|
| 398 |
+
|
| 399 |
+
.chapters-list.has-fade {
|
| 400 |
+
position: relative;
|
| 401 |
+
}
|
| 402 |
+
|
| 403 |
+
.chapters-list.has-fade::after {
|
| 404 |
+
content: '';
|
| 405 |
+
position: absolute;
|
| 406 |
+
bottom: 0;
|
| 407 |
+
left: 0;
|
| 408 |
+
right: 0;
|
| 409 |
+
height: 180px;
|
| 410 |
+
background: linear-gradient(
|
| 411 |
+
to bottom,
|
| 412 |
+
transparent 0%,
|
| 413 |
+
rgba(15, 15, 30, 0.3) 20%,
|
| 414 |
+
rgba(15, 15, 30, 0.6) 40%,
|
| 415 |
+
rgba(15, 15, 30, 0.85) 60%,
|
| 416 |
+
rgba(15, 15, 30, 0.95) 80%,
|
| 417 |
+
#0f0f1e 100%
|
| 418 |
+
);
|
| 419 |
+
pointer-events: none;
|
| 420 |
+
z-index: 1;
|
| 421 |
}
|
| 422 |
|
| 423 |
.chapter-item {
|
|
|
|
| 430 |
border-radius: 12px;
|
| 431 |
cursor: pointer;
|
| 432 |
transition: all 0.3s;
|
| 433 |
+
position: relative;
|
| 434 |
+
z-index: 0;
|
| 435 |
}
|
| 436 |
|
| 437 |
.chapter-item:hover {
|
| 438 |
background: rgba(255, 255, 255, 0.08);
|
| 439 |
border-color: #667eea;
|
| 440 |
transform: translateX(4px);
|
| 441 |
+
z-index: 2;
|
| 442 |
}
|
| 443 |
|
| 444 |
.chapter-info {
|
|
|
|
| 475 |
transform: translateX(4px);
|
| 476 |
}
|
| 477 |
|
| 478 |
+
.show-more-container {
|
| 479 |
+
display: flex;
|
| 480 |
+
justify-content: center;
|
| 481 |
+
padding: 2rem 0 1rem;
|
| 482 |
+
margin-top: -0.5rem;
|
| 483 |
+
position: relative;
|
| 484 |
+
z-index: 2;
|
| 485 |
+
}
|
| 486 |
+
|
| 487 |
+
.show-more-chapters {
|
| 488 |
+
font-size: 1rem;
|
| 489 |
+
padding: 0.75rem 1.5rem;
|
| 490 |
+
background: rgba(102, 126, 234, 0.15);
|
| 491 |
+
border: 1px solid rgba(102, 126, 234, 0.3);
|
| 492 |
+
border-radius: 10px;
|
| 493 |
+
transition: all 0.3s ease;
|
| 494 |
+
color: #667eea;
|
| 495 |
+
}
|
| 496 |
+
|
| 497 |
+
.show-more-chapters:hover {
|
| 498 |
+
background: rgba(102, 126, 234, 0.25);
|
| 499 |
+
border-color: #667eea;
|
| 500 |
+
transform: translateY(-2px);
|
| 501 |
+
box-shadow: 0 4px 12px rgba(102, 126, 234, 0.3);
|
| 502 |
+
}
|
| 503 |
+
|
| 504 |
+
.show-more-chapters:active {
|
| 505 |
+
transform: translateY(0);
|
| 506 |
+
}
|
| 507 |
+
|
| 508 |
.no-results {
|
| 509 |
text-align: center;
|
| 510 |
padding: 3rem;
|
|
|
|
| 581 |
.auth-card {
|
| 582 |
padding: 2rem;
|
| 583 |
}
|
| 584 |
+
|
| 585 |
+
.chapters-list.has-fade::after {
|
| 586 |
+
height: 150px;
|
| 587 |
+
}
|
| 588 |
+
|
| 589 |
+
.expand-btn {
|
| 590 |
+
font-size: 0.85rem;
|
| 591 |
+
}
|
| 592 |
+
|
| 593 |
+
.show-more-chapters {
|
| 594 |
+
font-size: 0.9rem;
|
| 595 |
+
padding: 0.65rem 1.25rem;
|
| 596 |
+
}
|
| 597 |
+
}
|
| 598 |
+
|
| 599 |
+
@media (prefers-reduced-motion: reduce) {
|
| 600 |
+
*,
|
| 601 |
+
*::before,
|
| 602 |
+
*::after {
|
| 603 |
+
animation-duration: 0.01ms !important;
|
| 604 |
+
animation-iteration-count: 1 !important;
|
| 605 |
+
transition-duration: 0.01ms !important;
|
| 606 |
+
}
|
| 607 |
}
|
src/modules/comic/ComicLanding.tsx
CHANGED
|
@@ -34,8 +34,11 @@ export function ComicLanding() {
|
|
| 34 |
const [searchQuery, setSearchQuery] = useState('');
|
| 35 |
const [sortOrder, setSortOrder] = useState<'asc' | 'desc'>('desc');
|
| 36 |
const [loadingChapter, setLoadingChapter] = useState(false);
|
| 37 |
-
|
|
|
|
| 38 |
const [ws, setWs] = useState<WebSocket | null>(null);
|
|
|
|
|
|
|
| 39 |
|
| 40 |
useEffect(() => {
|
| 41 |
const storedSession = sessionStorage.getItem(`comic_${sessionId}`);
|
|
@@ -219,6 +222,16 @@ export function ComicLanding() {
|
|
| 219 |
return sortOrder === 'asc' ? numA - numB : numB - numA;
|
| 220 |
});
|
| 221 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 222 |
if (loading) {
|
| 223 |
return (
|
| 224 |
<div className="comic-landing">
|
|
@@ -349,7 +362,17 @@ export function ComicLanding() {
|
|
| 349 |
|
| 350 |
<div className="synopsis">
|
| 351 |
<h3>Synopsis</h3>
|
| 352 |
-
<
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 353 |
</div>
|
| 354 |
</div>
|
| 355 |
</div>
|
|
@@ -378,33 +401,49 @@ export function ComicLanding() {
|
|
| 378 |
</div>
|
| 379 |
</div>
|
| 380 |
|
| 381 |
-
<div className="chapters-list">
|
| 382 |
-
{
|
| 383 |
-
|
| 384 |
-
<
|
| 385 |
-
|
| 386 |
-
|
| 387 |
-
|
| 388 |
-
|
| 389 |
-
|
| 390 |
-
|
| 391 |
-
|
| 392 |
-
|
| 393 |
-
|
| 394 |
-
<div className="chapter-
|
| 395 |
-
|
| 396 |
-
<
|
| 397 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 398 |
</div>
|
| 399 |
</div>
|
| 400 |
-
|
| 401 |
-
|
| 402 |
-
|
| 403 |
-
|
| 404 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 405 |
)}
|
| 406 |
</div>
|
| 407 |
</div>
|
| 408 |
</div>
|
| 409 |
);
|
| 410 |
-
}
|
|
|
|
| 34 |
const [searchQuery, setSearchQuery] = useState('');
|
| 35 |
const [sortOrder, setSortOrder] = useState<'asc' | 'desc'>('desc');
|
| 36 |
const [loadingChapter, setLoadingChapter] = useState(false);
|
| 37 |
+
const [isSynopsisExpanded, setIsSynopsisExpanded] = useState(false);
|
| 38 |
+
const [isChaptersExpanded, setIsChaptersExpanded] = useState(false);
|
| 39 |
const [ws, setWs] = useState<WebSocket | null>(null);
|
| 40 |
+
const SYNOPSIS_CHAR_LIMIT = 200;
|
| 41 |
+
const CHAPTERS_INITIAL_DISPLAY = 20;
|
| 42 |
|
| 43 |
useEffect(() => {
|
| 44 |
const storedSession = sessionStorage.getItem(`comic_${sessionId}`);
|
|
|
|
| 222 |
return sortOrder === 'asc' ? numA - numB : numB - numA;
|
| 223 |
});
|
| 224 |
|
| 225 |
+
const shouldTruncateSynopsis = comicData && comicData.synopsis.length > SYNOPSIS_CHAR_LIMIT;
|
| 226 |
+
const displayedSynopsis = comicData && shouldTruncateSynopsis && !isSynopsisExpanded
|
| 227 |
+
? comicData.synopsis.slice(0, SYNOPSIS_CHAR_LIMIT) + '...'
|
| 228 |
+
: comicData?.synopsis;
|
| 229 |
+
|
| 230 |
+
const shouldShowMoreChapters = sortedChapters.length > CHAPTERS_INITIAL_DISPLAY;
|
| 231 |
+
const displayedChapters = isChaptersExpanded || searchQuery
|
| 232 |
+
? sortedChapters
|
| 233 |
+
: sortedChapters.slice(0, CHAPTERS_INITIAL_DISPLAY);
|
| 234 |
+
|
| 235 |
if (loading) {
|
| 236 |
return (
|
| 237 |
<div className="comic-landing">
|
|
|
|
| 362 |
|
| 363 |
<div className="synopsis">
|
| 364 |
<h3>Synopsis</h3>
|
| 365 |
+
<div className="synopsis-content">
|
| 366 |
+
<p>{displayedSynopsis}</p>
|
| 367 |
+
{shouldTruncateSynopsis && (
|
| 368 |
+
<button
|
| 369 |
+
className="expand-btn"
|
| 370 |
+
onClick={() => setIsSynopsisExpanded(!isSynopsisExpanded)}
|
| 371 |
+
>
|
| 372 |
+
{isSynopsisExpanded ? 'Show Less' : 'Read More'}
|
| 373 |
+
</button>
|
| 374 |
+
)}
|
| 375 |
+
</div>
|
| 376 |
</div>
|
| 377 |
</div>
|
| 378 |
</div>
|
|
|
|
| 401 |
</div>
|
| 402 |
</div>
|
| 403 |
|
| 404 |
+
<div className="chapters-list-wrapper">
|
| 405 |
+
<div className={`chapters-list ${!isChaptersExpanded && shouldShowMoreChapters ? 'has-fade' : ''}`}>
|
| 406 |
+
{displayedChapters.length === 0 ? (
|
| 407 |
+
<div className="no-results">
|
| 408 |
+
<p>No chapters found</p>
|
| 409 |
+
</div>
|
| 410 |
+
) : (
|
| 411 |
+
displayedChapters.map((chapter, index) => (
|
| 412 |
+
<div
|
| 413 |
+
key={index}
|
| 414 |
+
className="chapter-item"
|
| 415 |
+
onClick={() => handleReadChapter(chapter.slug)}
|
| 416 |
+
>
|
| 417 |
+
<div className="chapter-info">
|
| 418 |
+
<div className="chapter-number">{chapter.chapter}</div>
|
| 419 |
+
<div className="chapter-meta">
|
| 420 |
+
<span className="chapter-date">{chapter.date}</span>
|
| 421 |
+
<span className="chapter-views">{chapter.views}</span>
|
| 422 |
+
</div>
|
| 423 |
+
</div>
|
| 424 |
+
<div className="chapter-action">
|
| 425 |
+
<span className="read-btn">Read →</span>
|
| 426 |
</div>
|
| 427 |
</div>
|
| 428 |
+
))
|
| 429 |
+
)}
|
| 430 |
+
</div>
|
| 431 |
+
|
| 432 |
+
{shouldShowMoreChapters && !searchQuery && (
|
| 433 |
+
<div className="show-more-container">
|
| 434 |
+
<button
|
| 435 |
+
className="expand-btn show-more-chapters"
|
| 436 |
+
onClick={() => setIsChaptersExpanded(!isChaptersExpanded)}
|
| 437 |
+
>
|
| 438 |
+
{isChaptersExpanded
|
| 439 |
+
? 'Show Less'
|
| 440 |
+
: `Show More (${sortedChapters.length - CHAPTERS_INITIAL_DISPLAY} more chapters)`
|
| 441 |
+
}
|
| 442 |
+
</button>
|
| 443 |
+
</div>
|
| 444 |
)}
|
| 445 |
</div>
|
| 446 |
</div>
|
| 447 |
</div>
|
| 448 |
);
|
| 449 |
+
}
|