1264 lines
40 KiB
HTML
1264 lines
40 KiB
HTML
@include(snippets/header)
|
||
<div class="pagewrapper">
|
||
<div id="main" class="chan-page chan-thread-page">
|
||
|
||
<!-- Top Header Navigation -->
|
||
<div class="chan-header-bar chan-thread-header">
|
||
<div class="chan-thread-nav-row">
|
||
<a href="/4/{{ board }}/catalogue" class="chan-back-btn">
|
||
<i class="fa-solid fa-arrow-left"></i> /{{ board }}/ Catalogue
|
||
</a>
|
||
<div class="chan-thread-actions">
|
||
@if(first_media_no)
|
||
<a href="/4/{{ board }}/{{ tid }}/{{ first_media_no }}" class="chan-action-btn primary" title="Open thread media in player view">
|
||
<i class="fa-solid fa-play"></i> Player View
|
||
</a>
|
||
@endif
|
||
<a href="/abyss/{{ board }}/{{ tid }}" class="chan-action-btn" title="Open in Abyss vertical video scroller">
|
||
<i class="fa-solid fa-dice-d6"></i> Abyss
|
||
</a>
|
||
<a href="https://boards.4chan.org/{{ board }}/thread/{{ tid }}" target="_blank" rel="noopener noreferrer" class="chan-action-btn" title="View original thread on 4chan">
|
||
<i class="fa-solid fa-arrow-up-right-from-square"></i> 4chan.org
|
||
</a>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="chan-thread-title-row">
|
||
<span class="chan-clover-icon"><i class="fa-solid fa-clover"></i></span>
|
||
<div class="chan-thread-title-info">
|
||
<h2>
|
||
@if(op.sub)
|
||
{{ op.sub }}
|
||
@else
|
||
Thread #{{ tid }}
|
||
@endif
|
||
</h2>
|
||
<div class="chan-thread-meta">
|
||
<span class="chan-meta-item"><i class="fa-solid fa-comments"></i> {{ posts.length }} posts</span>
|
||
<span class="chan-meta-item"><i class="fa-solid fa-image"></i> {{ media_count }} media files</span>
|
||
@if(rehosts_count > 0)
|
||
<span class="chan-meta-item" style="color: #4ade80;"><i class="fa-solid fa-check"></i> {{ rehosts_count }} rehosted</span>
|
||
@endif
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Filter Toggle: All vs Media Only vs Rehosted -->
|
||
<div class="chan-filter-toggle">
|
||
<button type="button" class="chan-toggle-btn active" id="filter-all-btn">All Posts ({{ posts.length }})</button>
|
||
<button type="button" class="chan-toggle-btn" id="filter-media-btn">Media Only ({{ media_count }})</button>
|
||
@if(rehosts_count > 0)
|
||
<button type="button" class="chan-toggle-btn" id="filter-rehosted-btn" style="color: #4ade80;"><i class="fa-solid fa-check"></i> Rehosted ({{ rehosts_count }})</button>
|
||
@endif
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Posts Stream -->
|
||
<div class="chan-posts-stream" id="chan-posts-stream">
|
||
@each(posts as p)
|
||
<article class="chan-post-card {{ p.no === tid ? 'is-op' : 'is-reply' }} {{ p.has_media ? 'has-media' : 'no-media' }} {{ p.local_id ? 'is-rehosted' : '' }} {{ p.is_onara_active ? 'onara-active active-thread-post' : '' }}" id="p{{ p.no }}" data-no="{{ p.no }}" data-has-media="{{ p.has_media ? 1 : 0 }}" data-is-rehosted="{{ p.local_id ? 1 : 0 }}">
|
||
|
||
<!-- Post Header -->
|
||
<div class="chan-post-header">
|
||
<span class="chan-post-name">{{ p.name }}</span>
|
||
@if(p.trip)
|
||
<span class="chan-post-trip">{{ p.trip }}</span>
|
||
@endif
|
||
<time class="chan-post-time timeago" data-iso="{{ p.iso_str }}" tooltip="{{ p.iso_str }}" title="{{ p.date_str }}">{{ p.timeago }}</time>
|
||
<a href="#p{{ p.no }}" class="chan-post-no">No. {{ p.no }}</a>
|
||
@if(p.local_id)
|
||
<a href="/{{ p.local_path || p.local_id }}" target="_blank" class="chan-post-rehosted-pill" title="Already rehosted on f0ckm as /{{ p.local_path || p.local_id }}">
|
||
<i class="fa-solid fa-check"></i> /{{ p.local_path || p.local_id }}
|
||
</a>
|
||
@endif
|
||
@if(p.sub && p.no !== tid)
|
||
<span class="chan-reply-sub">{{ p.sub }}</span>
|
||
@endif
|
||
@if(p.replies && p.replies.length > 0)
|
||
<div class="chan-post-backlinks" title="{{ p.replies.length }} {{ p.replies.length === 1 ? 'reply' : 'replies' }}">
|
||
<span class="chan-backlinks-icon"><i class="fa-solid fa-reply"></i></span>
|
||
<span class="chan-backlinks-items">
|
||
@each(p.replies as rNo)
|
||
<a href="#p{{ rNo }}" class="chan-ref chan-backlink" data-post="{{ rNo }}">>>{{ rNo }}</a>
|
||
@endeach
|
||
</span>
|
||
</div>
|
||
@endif
|
||
@if(p.has_media)
|
||
<div class="chan-post-media-badge">
|
||
<i class="fa-solid fa-paperclip"></i>
|
||
<span>{{ p.ext.toUpperCase().replace('.', '') }}</span>
|
||
</div>
|
||
@endif
|
||
</div>
|
||
|
||
<!-- File info: one row above the thumbnail -->
|
||
@if(p.has_media)
|
||
<div class="chan-file-info">
|
||
<div class="chan-filename-row">
|
||
<a href="{{ p.external_media_url }}" target="_blank" rel="noopener noreferrer" class="chan-file-download" title="Open full media in new tab">
|
||
<i class="fa-solid fa-arrow-up-right-from-square"></i>
|
||
<span class="chan-file-title">{{ p.filename || ('File ' + p.tim + p.ext) }}</span>
|
||
</a>
|
||
</div>
|
||
<div class="chan-file-meta-row">
|
||
@if(p.fsize)<span>{{ p.fsize }}</span>@endif
|
||
@if(p.w && p.h)<span>{{ p.w }}×{{ p.h }}</span>@endif
|
||
</div>
|
||
|
||
<!-- Rehost & Favorite actions -->
|
||
<div class="chan-rehost-container" id="rehost-box-{{ p.no }}">
|
||
@if(p.local_id)
|
||
<span class="chan-rehosted-wrap" style="display:inline-flex;gap:4px;align-items:center;flex:1;">
|
||
<a href="/{{ p.local_path || p.local_id }}" class="chan-rehost-btn rehosted" target="_blank">
|
||
<i class="fa-solid fa-check"></i> Rehosted /{{ p.local_path || p.local_id }}
|
||
</a>
|
||
<button type="button" class="chan-rehost-edit-btn" data-item-id="{{ p.local_id }}" title="Edit rating" style="padding:4px 7px;border-radius:0;background:rgba(255,255,255,0.1);color:#fff;border:1px solid rgba(255,255,255,0.2);cursor:pointer;font-size:0.75rem;">
|
||
<i class="fa-solid fa-pen"></i>
|
||
</button>
|
||
</span>
|
||
@else
|
||
<button type="button" class="chan-rehost-btn" data-url="{{ p.external_media_url }}" data-board="{{ board }}" data-tid="{{ tid }}" data-post="{{ p.no }}" data-filename="{{ p.filename || '' }}" data-width="{{ p.w || '' }}" data-height="{{ p.h || '' }}">
|
||
<i class="fa-solid fa-cloud-arrow-up"></i> Rehost
|
||
</button>
|
||
@endif
|
||
<button type="button" class="chan-card-fav-btn {{ p.user_has_favorited ? 'favorited' : '' }}" data-post="{{ p.no }}" data-local-id="{{ p.local_id || '' }}" data-url="{{ p.external_media_url }}" data-board="{{ board }}" data-tid="{{ tid }}" data-filename="{{ p.filename || '' }}" data-width="{{ p.w || '' }}" data-height="{{ p.h || '' }}" title="{{ p.user_has_favorited ? 'Remove from favorites' : 'Favorite' }}">
|
||
<i class="fa-{{ p.user_has_favorited ? 'solid' : 'regular' }} fa-heart"></i>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
@endif
|
||
|
||
<!-- Post Content (Media + Comment) -->
|
||
<div class="chan-post-content">
|
||
@if(p.has_media)
|
||
<div class="chan-media-wrapper">
|
||
<div class="chan-thumb-box">
|
||
<a href="/4/{{ board }}/{{ tid }}/{{ p.no }}" class="chan-thumb-link {{ p.is_onara_active ? 'onara-active' : '' }}" title="Open in player view">
|
||
@if(p.thumb)
|
||
<img src="{{ p.thumb }}" alt="Media #{{ p.no }}" class="chan-thumb-img" loading="lazy" />
|
||
@else
|
||
<div class="chan-no-thumb"><i class="fa-solid fa-file"></i></div>
|
||
@endif
|
||
@if(p.is_video)
|
||
<span class="chan-video-indicator"><i class="fa-solid fa-play"></i></span>
|
||
@endif
|
||
@if(p.local_id)
|
||
<span class="chan-thumb-rehost-badge" title="Rehosted on f0ckm as /{{ p.local_path || p.local_id }}"><i class="fa-solid fa-check"></i> /{{ p.local_path || p.local_id }}</span>
|
||
@endif
|
||
</a>
|
||
</div>
|
||
|
||
</div>
|
||
@endif
|
||
|
||
<!-- Post Comment -->
|
||
@if(p.com_raw)
|
||
<div class="chan-post-comment">
|
||
{{ p.com_formatted }}
|
||
</div>
|
||
@endif
|
||
</div>
|
||
|
||
</article>
|
||
@endeach
|
||
</div>
|
||
|
||
</div>
|
||
</div>
|
||
|
||
<style>
|
||
.chan-thread-page {
|
||
max-width: 1200px;
|
||
margin: 0 auto;
|
||
padding: 20px 15px 80px;
|
||
}
|
||
.chan-thread-header {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 16px;
|
||
margin-bottom: 25px;
|
||
background: var(--bg-card, rgba(25, 25, 25, 0.85));
|
||
border: 1px solid var(--border-color, rgba(255, 255, 255, 0.08));
|
||
border-radius: 0;
|
||
padding: 16px 20px;
|
||
backdrop-filter: blur(8px);
|
||
}
|
||
.chan-thread-nav-row {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
flex-wrap: wrap;
|
||
gap: 12px;
|
||
}
|
||
.chan-back-btn {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
padding: 6px 14px;
|
||
border-radius: 0;
|
||
background: rgba(255, 255, 255, 0.06);
|
||
color: var(--text-main, #fff);
|
||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||
text-decoration: none;
|
||
font-weight: 600;
|
||
font-size: 0.88rem;
|
||
transition: all 0.2s;
|
||
}
|
||
.chan-back-btn:hover {
|
||
background: rgba(255, 255, 255, 0.12);
|
||
border-color: rgba(255, 255, 255, 0.2);
|
||
}
|
||
.chan-thread-actions {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
flex-wrap: wrap;
|
||
}
|
||
.chan-action-btn {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 6px;
|
||
padding: 6px 12px;
|
||
border-radius: 0;
|
||
font-size: 0.85rem;
|
||
font-weight: 600;
|
||
background: rgba(255, 255, 255, 0.06);
|
||
color: var(--text-muted, #aaa);
|
||
border: 1px solid rgba(255, 255, 255, 0.08);
|
||
text-decoration: none;
|
||
transition: all 0.2s;
|
||
}
|
||
.chan-action-btn:hover {
|
||
background: rgba(255, 255, 255, 0.12);
|
||
color: #fff;
|
||
}
|
||
.chan-action-btn.primary {
|
||
background: rgba(74, 222, 128, 0.15);
|
||
color: #4ade80;
|
||
border-color: rgba(74, 222, 128, 0.3);
|
||
}
|
||
.chan-action-btn.primary:hover {
|
||
background: #4ade80;
|
||
color: #111;
|
||
}
|
||
|
||
.chan-thread-title-row {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 14px;
|
||
flex-wrap: wrap;
|
||
justify-content: space-between;
|
||
border-top: 1px solid rgba(255, 255, 255, 0.06);
|
||
padding-top: 14px;
|
||
}
|
||
.chan-thread-title-info h2 {
|
||
font-size: 1.35rem;
|
||
font-weight: 800;
|
||
margin: 0 0 4px;
|
||
color: #fff;
|
||
}
|
||
.chan-thread-meta {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 12px;
|
||
font-size: 0.82rem;
|
||
color: #888;
|
||
}
|
||
.chan-meta-item {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 5px;
|
||
}
|
||
|
||
/* Filter toggle */
|
||
.chan-filter-toggle {
|
||
display: inline-flex;
|
||
background: rgba(255, 255, 255, 0.05);
|
||
border: 1px solid rgba(255, 255, 255, 0.08);
|
||
border-radius: 0;
|
||
padding: 3px;
|
||
gap: 3px;
|
||
}
|
||
.chan-toggle-btn {
|
||
background: none;
|
||
border: none;
|
||
padding: 5px 12px;
|
||
border-radius: 0;
|
||
font-size: 0.82rem;
|
||
font-weight: 600;
|
||
color: #aaa;
|
||
cursor: pointer;
|
||
transition: all 0.2s;
|
||
}
|
||
.chan-toggle-btn.active {
|
||
background: rgba(255, 255, 255, 0.15);
|
||
color: #fff;
|
||
}
|
||
|
||
/* Posts stream */
|
||
.chan-posts-stream {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 12px;
|
||
}
|
||
.chan-post-card {
|
||
background: var(--bg-card, rgba(25, 25, 25, 0.85));
|
||
border: 1px solid var(--border-color, rgba(255, 255, 255, 0.08));
|
||
border-radius: 0;
|
||
padding: 14px 18px;
|
||
transition: border-color 0.2s, background-color 0.2s, box-shadow 0.2s;
|
||
scroll-margin-top: 80px;
|
||
}
|
||
.chan-post-card.is-rehosted {
|
||
border-color: rgba(74, 222, 128, 0.3);
|
||
background: rgba(74, 222, 128, 0.03);
|
||
}
|
||
.chan-post-card.is-op {
|
||
border-left: 4px solid #4ade80;
|
||
background: rgba(30, 30, 30, 0.95);
|
||
}
|
||
.chan-post-card.is-reply {
|
||
background: rgba(24, 24, 28, 0.8);
|
||
border: 1px solid rgba(255, 255, 255, 0.06);
|
||
}
|
||
.chan-post-card.is-reply:hover {
|
||
border-color: rgba(255, 255, 255, 0.12);
|
||
}
|
||
.chan-post-card.is-reply.has-media {
|
||
background: rgba(26, 26, 31, 0.88);
|
||
border-color: rgba(255, 255, 255, 0.08);
|
||
}
|
||
.chan-post-card:target {
|
||
border-color: #4ade80 !important;
|
||
background: rgba(74, 222, 128, 0.08) !important;
|
||
box-shadow: 0 0 0 2px rgba(74, 222, 128, 0.5), 0 0 20px rgba(74, 222, 128, 0.25) !important;
|
||
position: relative;
|
||
z-index: 5;
|
||
}
|
||
@keyframes chanHighlightPulse {
|
||
0% {
|
||
border-color: #4ade80 !important;
|
||
background: rgba(74, 222, 128, 0.18) !important;
|
||
box-shadow: 0 0 0 3px #4ade80, 0 0 30px rgba(74, 222, 128, 0.6) !important;
|
||
}
|
||
50% {
|
||
border-color: #4ade80 !important;
|
||
background: rgba(74, 222, 128, 0.10) !important;
|
||
box-shadow: 0 0 0 2px rgba(74, 222, 128, 0.7), 0 0 20px rgba(74, 222, 128, 0.4) !important;
|
||
}
|
||
100% {
|
||
border-color: rgba(74, 222, 128, 0.4) !important;
|
||
background: rgba(74, 222, 128, 0.04) !important;
|
||
box-shadow: 0 0 0 1px rgba(74, 222, 128, 0.3) !important;
|
||
}
|
||
}
|
||
.chan-post-card.chan-flash-highlight {
|
||
animation: chanHighlightPulse 1.8s ease-out forwards !important;
|
||
position: relative;
|
||
z-index: 6;
|
||
}
|
||
body.onara-modal-open:not(.onara-modal-closing) .chan-thumb-link.onara-active .chan-thumb-img {
|
||
box-shadow: 0 0 0 2px #4ade80, 0 0 15px rgba(74, 222, 128, 0.6) !important;
|
||
}
|
||
|
||
body.onara-modal-open:not(.onara-modal-closing) .chan-post-card:not(.onara-active):not(.active-thread-post) {
|
||
opacity: var(--onara-grid-dim-opacity, 0.5) !important;
|
||
transition: opacity 0.25s cubic-bezier(0.16, 1, 0.3, 1) !important;
|
||
}
|
||
body.onara-modal-open:not(.onara-modal-closing) .chan-post-card.onara-active,
|
||
body.onara-modal-open:not(.onara-modal-closing) .chan-post-card.active-thread-post {
|
||
opacity: 1 !important;
|
||
border-color: #4ade80 !important;
|
||
box-shadow: 0 0 0 3px #4ade80, 0 0 25px rgba(74, 222, 128, 0.5) !important;
|
||
position: relative !important;
|
||
z-index: 52 !important;
|
||
transition: box-shadow 0.28s cubic-bezier(0.16, 1, 0.3, 1), opacity 0.25s ease !important;
|
||
}
|
||
body.onara-modal-closing .chan-post-card.onara-active,
|
||
body.onara-modal-closing .chan-post-card.active-thread-post {
|
||
opacity: 1 !important;
|
||
border-color: rgba(255, 255, 255, 0.08) !important;
|
||
box-shadow: none !important;
|
||
transition: box-shadow 0.25s cubic-bezier(0.16, 1, 0.3, 1), border-color 0.25s ease !important;
|
||
}
|
||
body.onara-modal-closing .chan-thumb-link.onara-active .chan-thumb-img {
|
||
box-shadow: none !important;
|
||
transition: box-shadow 0.25s ease !important;
|
||
}
|
||
|
||
.chan-post-rehosted-pill {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 4px;
|
||
padding: 2px 7px;
|
||
border-radius: 0;
|
||
background: rgba(74, 222, 128, 0.2);
|
||
color: #4ade80;
|
||
border: 1px solid rgba(74, 222, 128, 0.4);
|
||
font-size: 0.75rem;
|
||
font-weight: 700;
|
||
text-decoration: none;
|
||
transition: all 0.2s;
|
||
}
|
||
.chan-post-rehosted-pill:hover {
|
||
background: #4ade80;
|
||
color: #111;
|
||
}
|
||
|
||
.chan-thumb-rehost-badge {
|
||
position: absolute;
|
||
top: 6px;
|
||
left: 6px;
|
||
background: rgba(16, 185, 129, 0.92);
|
||
color: #fff;
|
||
font-size: 0.68rem;
|
||
font-weight: 700;
|
||
padding: 2px 6px;
|
||
border-radius: 0;
|
||
backdrop-filter: blur(4px);
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 3px;
|
||
box-shadow: 0 2px 5px rgba(0,0,0,0.5);
|
||
z-index: 2;
|
||
}
|
||
|
||
.chan-post-header {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 10px;
|
||
flex-wrap: wrap;
|
||
font-size: 0.82rem;
|
||
margin-bottom: 10px;
|
||
border-bottom: 1px solid rgba(255, 255, 255, 0.04);
|
||
padding-bottom: 6px;
|
||
}
|
||
.chan-post-name {
|
||
font-weight: 700;
|
||
color: #10b981;
|
||
}
|
||
.chan-post-trip {
|
||
color: #888;
|
||
font-weight: 600;
|
||
}
|
||
.chan-post-time {
|
||
color: #777;
|
||
}
|
||
.chan-post-no {
|
||
color: #aaa;
|
||
text-decoration: none;
|
||
font-family: monospace;
|
||
}
|
||
.chan-post-no:hover {
|
||
color: #4ade80;
|
||
text-decoration: underline;
|
||
}
|
||
.chan-reply-sub {
|
||
color: #fbbf24;
|
||
font-weight: 600;
|
||
}
|
||
.chan-post-media-badge {
|
||
margin-left: auto;
|
||
font-size: 0.75rem;
|
||
font-weight: 600;
|
||
color: #888;
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 4px;
|
||
}
|
||
|
||
/* Post Content Layout (Media + Comment) */
|
||
.chan-post-content {
|
||
display: flex;
|
||
gap: 16px;
|
||
align-items: flex-start;
|
||
}
|
||
@media (max-width: 640px) {
|
||
.chan-post-content {
|
||
flex-direction: column;
|
||
gap: 12px;
|
||
}
|
||
}
|
||
|
||
/* Media Wrapper */
|
||
.chan-media-wrapper {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 8px;
|
||
width: 170px;
|
||
flex-shrink: 0;
|
||
}
|
||
.chan-post-card.is-reply .chan-media-wrapper {
|
||
width: 160px;
|
||
}
|
||
.chan-post-card.is-op .chan-media-wrapper {
|
||
width: 220px;
|
||
}
|
||
@media (max-width: 640px) {
|
||
.chan-media-wrapper,
|
||
.chan-post-card.is-reply .chan-media-wrapper,
|
||
.chan-post-card.is-op .chan-media-wrapper {
|
||
width: 100%;
|
||
max-width: 260px;
|
||
}
|
||
}
|
||
|
||
/* Thumb Box & Link */
|
||
.chan-thumb-box {
|
||
position: relative;
|
||
width: 100%;
|
||
aspect-ratio: 16 / 10;
|
||
background: #0d0d0d;
|
||
border-radius: 0;
|
||
overflow: hidden;
|
||
border: 1px solid rgba(255, 255, 255, 0.08);
|
||
}
|
||
.chan-thumb-link {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
width: 100%;
|
||
height: 100%;
|
||
position: relative;
|
||
overflow: hidden;
|
||
text-decoration: none;
|
||
background: #0d0d0d;
|
||
}
|
||
.chan-thumb-img {
|
||
width: 100%;
|
||
height: 100%;
|
||
object-fit: cover;
|
||
display: block;
|
||
transition: transform 0.25s ease;
|
||
}
|
||
.chan-thumb-link:hover .chan-thumb-img {
|
||
transform: scale(1.05);
|
||
}
|
||
.chan-no-thumb {
|
||
width: 100%;
|
||
height: 100%;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
color: #444;
|
||
font-size: 1.5rem;
|
||
background: #111;
|
||
}
|
||
|
||
/* Video Indicator Overlay */
|
||
.chan-video-indicator {
|
||
position: absolute;
|
||
bottom: 6px;
|
||
right: 6px;
|
||
width: 24px;
|
||
height: 24px;
|
||
border-radius: 0;
|
||
background: rgba(0, 0, 0, 0.75);
|
||
backdrop-filter: blur(4px);
|
||
color: #fff;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
font-size: 0.65rem;
|
||
pointer-events: none;
|
||
border: 1px solid rgba(255, 255, 255, 0.2);
|
||
box-shadow: 0 2px 5px rgba(0,0,0,0.5);
|
||
z-index: 2;
|
||
}
|
||
|
||
/* File Info (filename, dimensions, size) */
|
||
.chan-file-info {
|
||
display: flex;
|
||
flex-direction: row;
|
||
align-items: center;
|
||
gap: 10px;
|
||
width: 100%;
|
||
min-width: 0;
|
||
margin-bottom: 8px;
|
||
}
|
||
.chan-file-info .chan-filename-row {
|
||
flex: 0 1 auto;
|
||
min-width: 0;
|
||
}
|
||
.chan-file-info .chan-file-meta-row {
|
||
flex-shrink: 0;
|
||
white-space: nowrap;
|
||
}
|
||
.chan-file-info .chan-rehost-container {
|
||
flex-shrink: 0;
|
||
margin-left: auto;
|
||
width: auto;
|
||
}
|
||
.chan-file-info .chan-rehost-btn {
|
||
width: auto;
|
||
flex: 0 0 auto;
|
||
}
|
||
.chan-filename-row {
|
||
display: flex;
|
||
align-items: center;
|
||
overflow: hidden;
|
||
}
|
||
.chan-file-download {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 5px;
|
||
color: #999;
|
||
text-decoration: none;
|
||
font-size: 0.73rem;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
max-width: 100%;
|
||
transition: color 0.15s ease;
|
||
}
|
||
.chan-file-download:hover {
|
||
color: #4ade80;
|
||
text-decoration: underline;
|
||
}
|
||
.chan-file-download i {
|
||
font-size: 0.68rem;
|
||
flex-shrink: 0;
|
||
opacity: 0.7;
|
||
}
|
||
.chan-file-title {
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
}
|
||
.chan-file-meta-row {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
color: #666;
|
||
font-size: 0.7rem;
|
||
font-family: monospace;
|
||
}
|
||
|
||
/* Rehost button */
|
||
.chan-rehost-btn {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 6px;
|
||
padding: 4px 10px;
|
||
border-radius: 0;
|
||
font-size: 0.78rem;
|
||
font-weight: 600;
|
||
background: rgba(74, 222, 128, 0.15);
|
||
color: #4ade80;
|
||
border: 1px solid rgba(74, 222, 128, 0.3);
|
||
cursor: pointer;
|
||
text-decoration: none;
|
||
transition: all 0.2s;
|
||
width: 100%;
|
||
flex: 1;
|
||
justify-content: center;
|
||
box-sizing: border-box;
|
||
}
|
||
.chan-rehost-btn:hover {
|
||
background: #4ade80;
|
||
color: #111;
|
||
}
|
||
.chan-rehost-btn.rehosted {
|
||
background: rgba(74, 222, 128, 0.2);
|
||
color: #4ade80;
|
||
cursor: default;
|
||
}
|
||
.chan-rehost-btn.loading {
|
||
opacity: 0.6;
|
||
pointer-events: none;
|
||
}
|
||
|
||
.chan-rehost-container {
|
||
display: flex;
|
||
gap: 6px;
|
||
align-items: center;
|
||
width: 100%;
|
||
}
|
||
.chan-rehosted-wrap {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 4px;
|
||
flex: 1;
|
||
min-width: 0;
|
||
}
|
||
.chan-rehosted-wrap .chan-rehost-btn {
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
}
|
||
.chan-card-fav-btn {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
height: 28px;
|
||
width: 28px;
|
||
min-width: 28px;
|
||
padding: 0;
|
||
border-radius: 0;
|
||
font-size: 0.85rem;
|
||
background: rgba(255, 255, 255, 0.08);
|
||
border: 1px solid rgba(255, 255, 255, 0.15);
|
||
color: #888;
|
||
cursor: pointer;
|
||
transition: all 0.2s;
|
||
box-sizing: border-box;
|
||
}
|
||
.chan-card-fav-btn:hover {
|
||
background: rgba(255, 75, 75, 0.15);
|
||
border-color: rgba(255, 75, 75, 0.4);
|
||
color: #ff4b4b;
|
||
}
|
||
.chan-card-fav-btn.favorited {
|
||
background: rgba(255, 75, 75, 0.2);
|
||
border-color: rgba(255, 75, 75, 0.5);
|
||
color: #ff4b4b;
|
||
}
|
||
.chan-card-fav-btn.loading {
|
||
opacity: 0.6;
|
||
pointer-events: none;
|
||
}
|
||
|
||
/* Comments & Greentext */
|
||
.chan-post-comment {
|
||
color: #ddd;
|
||
font-size: 0.88rem;
|
||
line-height: 1.5;
|
||
word-break: break-word;
|
||
flex: 1;
|
||
}
|
||
.chan-quote,
|
||
.chan-post-comment .quote {
|
||
color: #789922;
|
||
}
|
||
/* Backlinks in post header */
|
||
.chan-post-backlinks {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
flex-wrap: wrap;
|
||
gap: 4px 6px;
|
||
background: rgba(255, 255, 255, 0.03);
|
||
border: 1px solid rgba(255, 255, 255, 0.07);
|
||
border-radius: 0;
|
||
padding: 2px 7px;
|
||
font-size: 0.78rem;
|
||
max-width: 100%;
|
||
}
|
||
.chan-backlinks-icon {
|
||
color: #71717a;
|
||
font-size: 0.72rem;
|
||
display: inline-flex;
|
||
align-items: center;
|
||
user-select: none;
|
||
}
|
||
.chan-backlinks-icon i {
|
||
transform: scaleX(-1);
|
||
}
|
||
.chan-backlinks-items {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
flex-wrap: wrap;
|
||
gap: 4px 6px;
|
||
}
|
||
.chan-backlink {
|
||
color: #f87171 !important;
|
||
text-decoration: none !important;
|
||
font-family: monospace;
|
||
font-size: 0.78rem;
|
||
cursor: pointer;
|
||
padding: 1px 5px;
|
||
border-radius: 0;
|
||
background: rgba(239, 68, 68, 0.1);
|
||
border: 1px solid rgba(239, 68, 68, 0.2);
|
||
transition: all 0.15s ease;
|
||
line-height: 1.3;
|
||
}
|
||
.chan-backlink:hover {
|
||
color: #fff !important;
|
||
background: rgba(239, 68, 68, 0.35) !important;
|
||
border-color: rgba(239, 68, 68, 0.6) !important;
|
||
box-shadow: 0 0 8px rgba(239, 68, 68, 0.35);
|
||
}
|
||
|
||
.chan-ref,
|
||
.chan-post-comment .quotelink {
|
||
color: #f87171;
|
||
text-decoration: underline;
|
||
font-family: monospace;
|
||
cursor: pointer;
|
||
transition: color 0.15s ease;
|
||
}
|
||
.chan-ref:hover,
|
||
.chan-post-comment .quotelink:hover {
|
||
color: #ff4d4d;
|
||
}
|
||
.chan-post-comment s,
|
||
.chan-post-comment .spoiler {
|
||
background: #333;
|
||
color: #333;
|
||
padding: 0 3px;
|
||
border-radius: 0;
|
||
text-decoration: none;
|
||
cursor: pointer;
|
||
transition: color 0.15s;
|
||
}
|
||
.chan-post-comment s:hover,
|
||
.chan-post-comment .spoiler:hover {
|
||
color: #fff;
|
||
}
|
||
|
||
/* Quote preview popup */
|
||
.chan-quote-preview-popup {
|
||
position: fixed;
|
||
z-index: 99999;
|
||
width: max-content;
|
||
max-width: 520px;
|
||
max-height: 80vh;
|
||
overflow-y: auto;
|
||
background: rgba(22, 22, 24, 0.98);
|
||
border: 1px solid #4ade80;
|
||
border-radius: 0;
|
||
padding: 12px 16px;
|
||
box-shadow: 0 12px 35px rgba(0, 0, 0, 0.8), 0 0 20px rgba(74, 222, 128, 0.2);
|
||
pointer-events: none;
|
||
color: #eee;
|
||
font-size: 0.85rem;
|
||
backdrop-filter: blur(12px);
|
||
}
|
||
.chan-quote-preview-popup .chan-post-header {
|
||
margin-bottom: 8px;
|
||
padding-bottom: 4px;
|
||
}
|
||
.chan-quote-preview-popup .chan-rehost-container {
|
||
display: none !important;
|
||
}
|
||
.chan-quote-preview-popup .chan-media-wrapper {
|
||
margin-bottom: 8px;
|
||
}
|
||
.chan-quote-preview-popup .chan-thumb-box {
|
||
max-width: 120px;
|
||
max-height: 120px;
|
||
}
|
||
.chan-quote-preview-popup .chan-thumb-img {
|
||
max-width: 120px;
|
||
max-height: 120px;
|
||
object-fit: cover;
|
||
}
|
||
.chan-quote-preview-popup .chan-post-backlinks {
|
||
display: none !important;
|
||
}
|
||
|
||
/* Style guide: no rounded corners anywhere in the thread (covers site CSS, inline and JS-inserted elements) */
|
||
.chan-thread-page,
|
||
.chan-thread-page *,
|
||
.chan-thread-page *::before,
|
||
.chan-thread-page *::after {
|
||
border-radius: 0 !important;
|
||
}
|
||
</style>
|
||
|
||
<script>
|
||
(() => {
|
||
// Toggle filter: All vs Media Only
|
||
const filterAllBtn = document.getElementById('filter-all-btn');
|
||
const filterMediaBtn = document.getElementById('filter-media-btn');
|
||
const filterRehostedBtn = document.getElementById('filter-rehosted-btn');
|
||
const posts = document.querySelectorAll('.chan-post-card');
|
||
|
||
if (filterAllBtn && filterMediaBtn) {
|
||
filterAllBtn.addEventListener('click', () => {
|
||
filterAllBtn.classList.add('active');
|
||
filterMediaBtn.classList.remove('active');
|
||
if (filterRehostedBtn) filterRehostedBtn.classList.remove('active');
|
||
posts.forEach(p => p.style.display = 'block');
|
||
});
|
||
|
||
filterMediaBtn.addEventListener('click', () => {
|
||
filterMediaBtn.classList.add('active');
|
||
filterAllBtn.classList.remove('active');
|
||
if (filterRehostedBtn) filterRehostedBtn.classList.remove('active');
|
||
posts.forEach(p => {
|
||
p.style.display = p.dataset.hasMedia === '1' ? 'block' : 'none';
|
||
});
|
||
});
|
||
|
||
if (filterRehostedBtn) {
|
||
filterRehostedBtn.addEventListener('click', () => {
|
||
filterRehostedBtn.classList.add('active');
|
||
filterAllBtn.classList.remove('active');
|
||
filterMediaBtn.classList.remove('active');
|
||
posts.forEach(p => {
|
||
p.style.display = p.dataset.isRehosted === '1' ? 'block' : 'none';
|
||
});
|
||
});
|
||
}
|
||
}
|
||
|
||
// Inline Rehosting
|
||
document.addEventListener('click', async (e) => {
|
||
const btn = e.target.closest('.chan-rehost-btn:not(.rehosted)');
|
||
if (!btn) return;
|
||
|
||
e.preventDefault();
|
||
const url = btn.dataset.url;
|
||
const board = btn.dataset.board;
|
||
const tid = btn.dataset.tid;
|
||
const postNo = btn.dataset.post;
|
||
const filename = btn.dataset.filename;
|
||
if (!url) return;
|
||
|
||
btn.classList.add('loading');
|
||
btn.innerHTML = '<i class="fa-solid fa-circle-notch fa-spin"></i> Rehosting…';
|
||
|
||
try {
|
||
const csrfToken = window.f0ckSession?.csrf_token || '';
|
||
const resp = await fetch('/api/v2/scroller/rehost', {
|
||
method: 'POST',
|
||
headers: {
|
||
'Content-Type': 'application/x-www-form-urlencoded',
|
||
...(csrfToken ? { 'X-CSRF-Token': csrfToken } : {})
|
||
},
|
||
body: new URLSearchParams({
|
||
url,
|
||
original_filename: filename || '',
|
||
width: btn.dataset.width || '',
|
||
height: btn.dataset.height || ''
|
||
})
|
||
});
|
||
|
||
const data = await resp.json();
|
||
if (data.success && data.item_id) {
|
||
btn.classList.remove('loading');
|
||
btn.classList.add('rehosted');
|
||
btn.outerHTML = '<span class="chan-rehosted-wrap" style="display:inline-flex;gap:4px;align-items:center;">' +
|
||
'<a href="/' + (data.item_path || data.item_id) + '" class="chan-rehost-btn rehosted" target="_blank">' +
|
||
'<i class="fa-solid fa-check"></i> Rehosted /' + (data.item_path || data.item_id) + '</a>' +
|
||
'<button type="button" class="chan-rehost-edit-btn" data-item-id="' + data.item_id + '" title="Edit rating" style="padding:4px 7px;border-radius:0;background:rgba(255,255,255,0.1);color:#fff;border:1px solid rgba(255,255,255,0.2);cursor:pointer;font-size:0.75rem;">' +
|
||
'<i class="fa-solid fa-pen"></i></button></span>';
|
||
|
||
const postCard = document.getElementById('p' + postNo) || btn.closest('.chan-post-card');
|
||
if (postCard) {
|
||
const timeEl = postCard.querySelector('.chan-post-time, .timeago');
|
||
if (timeEl) {
|
||
const nowIso = new Date().toISOString();
|
||
timeEl.dataset.iso = nowIso;
|
||
const fullDate = typeof window.f0ckFormatDateFull === 'function' ? window.f0ckFormatDateFull(nowIso) : new Date().toLocaleString();
|
||
timeEl.setAttribute('tooltip', fullDate);
|
||
timeEl.setAttribute('title', fullDate);
|
||
timeEl.textContent = (window.f0ckTimeAgo ? window.f0ckTimeAgo(nowIso) : (window.f0ckI18n?.timeago_just_now || 'just now'));
|
||
}
|
||
}
|
||
|
||
if (typeof window.showToast === 'function') {
|
||
window.showToast(data.repost ? 'Already on site — linked to existing post' : 'Rehosted successfully!');
|
||
}
|
||
if (typeof window.openChanRehostModal === 'function') {
|
||
const postCardEl = document.getElementById('p' + postNo) || postCard;
|
||
const anchor = (postCardEl ? (postCardEl.querySelector('.chan-rehost-edit-btn') || postCardEl.querySelector('.chan-rehosted-wrap')) : null) || document.getElementById('rehost-box-' + postNo);
|
||
window.openChanRehostModal({ itemId: data.item_id, anchorEl: anchor });
|
||
}
|
||
} else {
|
||
btn.classList.remove('loading');
|
||
btn.innerHTML = '<i class="fa-solid fa-cloud-arrow-up"></i> Retry';
|
||
alert(data.msg || data.message || 'Rehosting failed');
|
||
}
|
||
} catch (err) {
|
||
console.error('[CHAN] Rehost error:', err);
|
||
btn.classList.remove('loading');
|
||
btn.innerHTML = '<i class="fa-solid fa-cloud-arrow-up"></i> Error';
|
||
}
|
||
});
|
||
|
||
document.addEventListener('click', (e) => {
|
||
const editBtn = e.target.closest('.chan-rehost-edit-btn');
|
||
if (editBtn) {
|
||
e.preventDefault();
|
||
e.stopImmediatePropagation();
|
||
const itemId = editBtn.dataset.itemId;
|
||
if (itemId && typeof window.openChanRehostModal === 'function') {
|
||
window.openChanRehostModal({ itemId: Number(itemId), anchorEl: editBtn });
|
||
}
|
||
}
|
||
});
|
||
|
||
// Favorite from Thread Card (Auto-rehost if needed, then toggle favorite)
|
||
document.addEventListener('click', async (e) => {
|
||
const favBtn = e.target.closest('.chan-card-fav-btn');
|
||
if (!favBtn || favBtn.classList.contains('loading')) return;
|
||
|
||
e.preventDefault();
|
||
|
||
if (window.f0ckSession?.is_anon && window.f0ckSession?.anon_permissions && window.f0ckSession.anon_permissions.favorite === false) {
|
||
if (typeof window.flashMessage === 'function') window.flashMessage('Anonymous favoriting is disabled.', 3000, 'error');
|
||
return;
|
||
}
|
||
if (!window.f0ckSession || !window.f0ckSession.user) {
|
||
if (typeof window.flashMessage === 'function') window.flashMessage('Login to favorite posts', 3000, 'warning');
|
||
return;
|
||
}
|
||
|
||
const postNo = favBtn.dataset.post;
|
||
let localId = favBtn.dataset.localId;
|
||
const mediaUrl = favBtn.dataset.url;
|
||
|
||
const wasFaved = favBtn.classList.contains('favorited');
|
||
const isNowFav = !wasFaved;
|
||
|
||
// 1. Instantly apply client UI
|
||
favBtn.classList.toggle('favorited', isNowFav);
|
||
favBtn.innerHTML = '<i class="fa-' + (isNowFav ? 'solid' : 'regular') + ' fa-heart"></i>';
|
||
favBtn.title = isNowFav ? 'Remove from favorites' : 'Favorite';
|
||
if (typeof window.flashMessage === 'function') {
|
||
window.flashMessage(isNowFav ? 'ADDED TO FAVORITES' : 'REMOVED FROM FAVORITES');
|
||
}
|
||
if (navigator.vibrate) navigator.vibrate(50);
|
||
|
||
// 2. Perform server sync in background
|
||
(async () => {
|
||
try {
|
||
// If not yet rehosted, auto-rehost first
|
||
if (!localId && mediaUrl) {
|
||
const csrfToken = window.f0ckSession?.csrf_token || '';
|
||
const rehostResp = await fetch('/api/v2/scroller/rehost', {
|
||
method: 'POST',
|
||
headers: {
|
||
'Content-Type': 'application/x-www-form-urlencoded',
|
||
...(csrfToken ? { 'X-CSRF-Token': csrfToken } : {})
|
||
},
|
||
body: new URLSearchParams({
|
||
url: mediaUrl,
|
||
original_filename: favBtn.dataset.filename || '',
|
||
width: favBtn.dataset.width || '',
|
||
height: favBtn.dataset.height || ''
|
||
})
|
||
});
|
||
const rehostData = await rehostResp.json();
|
||
if (rehostData.success && rehostData.item_id) {
|
||
localId = rehostData.item_id;
|
||
const localPath = rehostData.item_path || localId;
|
||
favBtn.dataset.localId = localId;
|
||
|
||
// Update rehost box
|
||
const rehostBox = document.getElementById('rehost-box-' + postNo);
|
||
if (rehostBox) {
|
||
const rehostBtn = rehostBox.querySelector('.chan-rehost-btn');
|
||
if (rehostBtn) {
|
||
rehostBtn.outerHTML = '<span class="chan-rehosted-wrap" style="display:inline-flex;gap:4px;align-items:center;flex:1;">' +
|
||
'<a href="/' + localPath + '" class="chan-rehost-btn rehosted" target="_blank">' +
|
||
'<i class="fa-solid fa-check"></i> Rehosted /' + localPath + '</a>' +
|
||
'<button type="button" class="chan-rehost-edit-btn" data-item-id="' + localId + '" title="Edit rating" style="padding:4px 7px;border-radius:0;background:rgba(255,255,255,0.1);color:#fff;border:1px solid rgba(255,255,255,0.2);cursor:pointer;font-size:0.75rem;">' +
|
||
'<i class="fa-solid fa-pen"></i></button></span>';
|
||
}
|
||
}
|
||
// Add thumb badge if missing
|
||
const postCard = document.getElementById('p' + postNo) || favBtn.closest('.chan-post-card');
|
||
if (postCard) {
|
||
const thumbLink = postCard.querySelector('.chan-thumb-link');
|
||
if (thumbLink && !thumbLink.querySelector('.chan-thumb-rehost-badge')) {
|
||
const badge = document.createElement('span');
|
||
badge.className = 'chan-thumb-rehost-badge';
|
||
badge.title = 'Rehosted on f0ckm as /' + localPath;
|
||
badge.innerHTML = '<i class="fa-solid fa-check"></i> /' + localPath;
|
||
thumbLink.appendChild(badge);
|
||
}
|
||
const timeEl = postCard.querySelector('.chan-post-time, .timeago');
|
||
if (timeEl) {
|
||
const nowIso = new Date().toISOString();
|
||
timeEl.dataset.iso = nowIso;
|
||
const fullDate = typeof window.f0ckFormatDateFull === 'function' ? window.f0ckFormatDateFull(nowIso) : new Date().toLocaleString();
|
||
timeEl.setAttribute('tooltip', fullDate);
|
||
timeEl.setAttribute('title', fullDate);
|
||
timeEl.textContent = (window.f0ckTimeAgo ? window.f0ckTimeAgo(nowIso) : (window.f0ckI18n?.timeago_just_now || 'just now'));
|
||
}
|
||
}
|
||
} else {
|
||
throw new Error(rehostData.msg || 'Rehost failed');
|
||
}
|
||
}
|
||
|
||
// Now sync favorite to server
|
||
const csrfToken = window.f0ckSession?.csrf_token || '';
|
||
const favResp = await fetch('/api/v2/togglefav', {
|
||
method: 'POST',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
...(csrfToken ? { 'X-CSRF-Token': csrfToken } : {})
|
||
},
|
||
body: JSON.stringify({
|
||
postid: Number(localId),
|
||
chan_url: mediaUrl,
|
||
action: isNowFav ? 'add' : 'delete',
|
||
favorited: isNowFav
|
||
})
|
||
});
|
||
const favData = await favResp.json();
|
||
|
||
if (favData.success) {
|
||
const finalIsFav = (favData.favorited !== undefined) ? !!favData.favorited : isNowFav;
|
||
favBtn.classList.toggle('favorited', finalIsFav);
|
||
favBtn.innerHTML = '<i class="fa-' + (finalIsFav ? 'solid' : 'regular') + ' fa-heart"></i>';
|
||
favBtn.title = finalIsFav ? 'Remove from favorites' : 'Favorite';
|
||
} else {
|
||
// Rollback on server rejection
|
||
favBtn.classList.toggle('favorited', wasFaved);
|
||
favBtn.innerHTML = '<i class="fa-' + (wasFaved ? 'solid' : 'regular') + ' fa-heart"></i>';
|
||
favBtn.title = wasFaved ? 'Remove from favorites' : 'Favorite';
|
||
if (typeof window.flashMessage === 'function') {
|
||
window.flashMessage(favData.msg || 'Failed to update favorite', 3000, 'error');
|
||
}
|
||
}
|
||
} catch (err) {
|
||
console.error('[CHAN-CARD-FAV] Error:', err);
|
||
// Rollback on network/rehost error
|
||
favBtn.classList.toggle('favorited', wasFaved);
|
||
favBtn.innerHTML = '<i class="fa-' + (wasFaved ? 'solid' : 'regular') + ' fa-heart"></i>';
|
||
favBtn.title = wasFaved ? 'Remove from favorites' : 'Favorite';
|
||
if (typeof window.flashMessage === 'function') {
|
||
window.flashMessage('Failed to update favorite', 3000, 'error');
|
||
}
|
||
}
|
||
})();
|
||
});
|
||
|
||
// Flash highlight a post card
|
||
const highlightPost = (el) => {
|
||
if (!el) return;
|
||
el.classList.remove('chan-flash-highlight');
|
||
void el.offsetWidth;
|
||
el.classList.add('chan-flash-highlight');
|
||
setTimeout(() => {
|
||
el.classList.remove('chan-flash-highlight');
|
||
}, 2000);
|
||
};
|
||
|
||
// Quote click navigation (smooth jump + flash highlight)
|
||
document.addEventListener('click', (e) => {
|
||
const ref = e.target.closest('.chan-ref, .quotelink');
|
||
if (!ref) return;
|
||
|
||
const targetPostNo = ref.dataset.post || ref.getAttribute('href')?.replace(/^#p?/, '');
|
||
if (!targetPostNo) return;
|
||
const targetCard = document.getElementById('p' + targetPostNo);
|
||
if (!targetCard) return;
|
||
|
||
e.preventDefault();
|
||
if (previewEl) previewEl.style.display = 'none';
|
||
|
||
// If card is hidden due to active filter ("Media" or "Rehosted"), reset to "All"
|
||
if (targetCard.style.display === 'none') {
|
||
const filterAllBtn = document.getElementById('filter-all-btn');
|
||
if (filterAllBtn) filterAllBtn.click();
|
||
}
|
||
|
||
history.pushState(null, '', '#p' + targetPostNo);
|
||
try {
|
||
targetCard.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
||
} catch (_) {
|
||
targetCard.scrollIntoView();
|
||
}
|
||
highlightPost(targetCard);
|
||
});
|
||
|
||
// Handle browser back/forward buttons navigating hash
|
||
window.addEventListener('hashchange', () => {
|
||
if (window.location.hash && window.location.hash.startsWith('#p')) {
|
||
const el = document.querySelector(window.location.hash);
|
||
if (el) {
|
||
if (el.style.display === 'none') {
|
||
const filterAllBtn = document.getElementById('filter-all-btn');
|
||
if (filterAllBtn) filterAllBtn.click();
|
||
}
|
||
try {
|
||
el.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
||
} catch (_) {
|
||
el.scrollIntoView();
|
||
}
|
||
highlightPost(el);
|
||
}
|
||
}
|
||
});
|
||
|
||
// Quote hover preview
|
||
let previewEl = null;
|
||
document.addEventListener('mouseover', (e) => {
|
||
const ref = e.target.closest('.chan-ref, .quotelink');
|
||
if (!ref) return;
|
||
|
||
const targetPostNo = ref.dataset.post || ref.getAttribute('href')?.replace(/^#p?/, '');
|
||
if (!targetPostNo) return;
|
||
const targetCard = document.getElementById('p' + targetPostNo);
|
||
if (!targetCard) return;
|
||
|
||
if (!previewEl) {
|
||
previewEl = document.createElement('div');
|
||
previewEl.className = 'chan-quote-preview-popup';
|
||
document.body.appendChild(previewEl);
|
||
}
|
||
|
||
previewEl.innerHTML = targetCard.innerHTML;
|
||
previewEl.style.display = 'block';
|
||
|
||
const rect = ref.getBoundingClientRect();
|
||
const popHeight = previewEl.offsetHeight || 150;
|
||
const popWidth = previewEl.offsetWidth || 380;
|
||
|
||
let top = rect.bottom + 8;
|
||
if (top + popHeight > window.innerHeight - 10) {
|
||
top = rect.top - popHeight - 8;
|
||
}
|
||
if (top < 10) top = 10;
|
||
|
||
let left = rect.left;
|
||
if (left + popWidth > window.innerWidth - 10) {
|
||
left = window.innerWidth - popWidth - 10;
|
||
}
|
||
if (left < 10) left = 10;
|
||
|
||
previewEl.style.top = top + 'px';
|
||
previewEl.style.left = left + 'px';
|
||
});
|
||
|
||
document.addEventListener('mouseout', (e) => {
|
||
const ref = e.target.closest('.chan-ref, .quotelink');
|
||
if (ref && previewEl) {
|
||
previewEl.style.display = 'none';
|
||
}
|
||
});
|
||
|
||
// Jump to active post in thread if onara active or hash present
|
||
const jumpToInitialActive = () => {
|
||
let target = document.querySelector('.chan-post-card.active-thread-post, .chan-post-card.onara-active');
|
||
if (!target && window.location.hash) {
|
||
target = document.querySelector(window.location.hash);
|
||
}
|
||
if (target) {
|
||
try {
|
||
target.scrollIntoView({ block: 'center', behavior: 'instant' });
|
||
} catch (_) {
|
||
target.scrollIntoView();
|
||
}
|
||
if (window.location.hash) {
|
||
highlightPost(target);
|
||
}
|
||
}
|
||
};
|
||
// Clear active card highlight whenever onara modal closes
|
||
const clearOnaraActiveCards = () => {
|
||
if (!document.body.classList.contains('onara-modal-open')) {
|
||
document.querySelectorAll('.chan-post-card.active-thread-post, .chan-post-card.onara-active, .chan-thumb-link.onara-active').forEach(el => {
|
||
el.classList.remove('active-thread-post', 'onara-active');
|
||
});
|
||
}
|
||
};
|
||
const bodyObserver = new MutationObserver(() => {
|
||
clearOnaraActiveCards();
|
||
});
|
||
bodyObserver.observe(document.body, { attributes: true, attributeFilter: ['class'] });
|
||
|
||
jumpToInitialActive();
|
||
setTimeout(jumpToInitialActive, 100);
|
||
})();
|
||
</script>
|
||
|
||
@include(snippets/footer)
|