Update base
This commit is contained in:
@@ -70,6 +70,10 @@
|
||||
}
|
||||
.topbar-icon-btn:hover { background: rgba(255,255,255,.18); transform: scale(1.07); }
|
||||
.topbar-icon-btn.has-filter { border-color: var(--accent); color: var(--accent); }
|
||||
@media (max-width: 600px) {
|
||||
.topbar-left, .topbar-right { gap: 4px; }
|
||||
.topbar-icon-btn { width: 32px; height: 32px; font-size: .78rem; }
|
||||
}
|
||||
#filter-active-summary {
|
||||
display: none;
|
||||
background: rgba(0,0,0,.6); backdrop-filter: blur(12px); -webkit-backdrop-filter: blur(12px);
|
||||
@@ -236,6 +240,65 @@
|
||||
font-size: .6rem; font-weight: 700; line-height: 16px;
|
||||
text-align: center; pointer-events: none;
|
||||
}
|
||||
/* Notification dropdown inside scroller */
|
||||
#scroller-notif-dropdown {
|
||||
width: 340px; max-width: calc(100vw - 16px);
|
||||
background: #1a1a1a; border: 1px solid rgba(255,255,255,.1);
|
||||
box-shadow: 0 8px 32px rgba(0,0,0,.6); border-radius: 8px;
|
||||
overflow: hidden; opacity: 0; transform: translateY(-6px);
|
||||
transition: opacity .2s ease, transform .2s ease;
|
||||
}
|
||||
#scroller-notif-dropdown.visible { opacity: 1; transform: translateY(0); display: block !important; }
|
||||
.notif-header {
|
||||
padding: 8px 10px 6px; border-bottom: 1px solid rgba(255,255,255,.08);
|
||||
display: flex; justify-content: space-between; align-items: center; gap: 8px;
|
||||
font-size: .82rem; font-weight: 700; color: rgba(255,255,255,.75); background: #111;
|
||||
}
|
||||
.notif-header button#scroller-mark-all-read {
|
||||
background: none; border: none; color: var(--accent, #e91e8c); cursor: pointer;
|
||||
font-size: .8rem; padding: 2px 4px; flex-shrink: 0;
|
||||
}
|
||||
.notif-header button#scroller-mark-all-read:hover { color: #fff; }
|
||||
.notif-tabs { display: flex; gap: 2px; flex: 1; min-width: 0; }
|
||||
.notif-tab {
|
||||
background: none; border: none; color: #888; font-size: .72rem; font-weight: 600;
|
||||
padding: 4px 10px; cursor: pointer; border-radius: 4px; transition: all .15s ease;
|
||||
text-transform: uppercase; letter-spacing: .5px; white-space: nowrap; position: relative;
|
||||
}
|
||||
.notif-tab:hover { color: #ccc; background: rgba(255,255,255,.05); }
|
||||
.notif-tab.active { color: var(--accent, #e91e8c); background: rgba(233,30,140,.1); }
|
||||
.notif-tab-badge {
|
||||
display: inline-block; background: #e91e8c; color: #fff; font-size: 9px;
|
||||
min-width: 14px; height: 14px; line-height: 14px; text-align: center;
|
||||
border-radius: 7px; padding: 0 3px; margin-left: 3px; font-weight: 700; vertical-align: middle;
|
||||
}
|
||||
.notif-list { max-height: 360px; overflow-y: auto; }
|
||||
.notif-item {
|
||||
padding: 10px 14px; border-bottom: 1px solid rgba(255,255,255,.05);
|
||||
background: #1a1a1a; cursor: pointer; transition: background .15s;
|
||||
font-size: .82rem; color: #ddd; display: block; text-decoration: none;
|
||||
}
|
||||
.notif-item:hover { background: #262626; color: #fff; text-decoration: none; }
|
||||
.notif-item.unread { border-left: 3px solid var(--accent, #e91e8c); background: rgba(255,255,255,.03); }
|
||||
.notif-item.notif-with-thumb { display: flex; align-items: flex-start; gap: 10px; }
|
||||
.notif-thumb {
|
||||
flex-shrink: 0; width: 56px; height: 56px; border-radius: 4px;
|
||||
overflow: hidden; background: rgba(255,255,255,.05); border: 1px solid rgba(255,255,255,.08);
|
||||
}
|
||||
.notif-thumb img { width: 100%; height: 100%; object-fit: cover; }
|
||||
.notif-content { flex: 1; min-width: 0; }
|
||||
.notif-user { font-size: .8rem; margin-bottom: 2px; }
|
||||
.notif-msg { color: #aaa; font-size: .78rem; line-height: 1.3; }
|
||||
.notif-time { font-size: .7rem; color: #666; margin-top: 3px; display: block; }
|
||||
.notif-empty { text-align: center; padding: 20px; color: #666; font-size: .82rem; }
|
||||
.notif-footer {
|
||||
padding: 8px 14px; border-top: 1px solid rgba(255,255,255,.08);
|
||||
text-align: center; background: #111;
|
||||
}
|
||||
.notif-footer a, .view-all-notifs {
|
||||
color: var(--accent, #e91e8c); font-size: .78rem; text-decoration: none;
|
||||
}
|
||||
.notif-footer a:hover { text-decoration: underline; }
|
||||
.scroll-id-link {
|
||||
display: inline-flex; align-items: center; gap: 5px; font-size: .76rem; color: var(--accent); font-weight: 700;
|
||||
text-decoration: none; margin-top: 2px; pointer-events: all; position: relative; z-index: 11; width: fit-content;
|
||||
@@ -277,6 +340,36 @@
|
||||
.scroll-btn.faved .scroll-btn-icon i { color: #ff4081; }
|
||||
.scroll-btn.faved .scroll-btn-count { color: #ff4081; }
|
||||
|
||||
/* Add to site button (External items) */
|
||||
.scroll-btn.rehost-btn .scroll-btn-icon {
|
||||
background: rgba(255, 255, 255, .15);
|
||||
border-color: var(--accent);
|
||||
color: var(--accent);
|
||||
font-size: 1.4rem;
|
||||
}
|
||||
.scroll-btn.rehost-btn:hover .scroll-btn-icon {
|
||||
background: var(--accent);
|
||||
color: #000;
|
||||
}
|
||||
.scroll-btn.rehost-btn.loading .scroll-btn-icon i {
|
||||
animation: spin .8s linear infinite;
|
||||
}
|
||||
.scroll-btn.rehost-btn.success .scroll-btn-icon {
|
||||
background: #4caf50;
|
||||
border-color: #4caf50;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
/* Progress indicators for rehosting */
|
||||
.rehost-progress-toast {
|
||||
position: fixed; bottom: 80px; left: 50%; transform: translateX(-50%);
|
||||
background: rgba(0,0,0,0.85); backdrop-filter: blur(10px);
|
||||
padding: 10px 20px; border-radius: 50px; border: 1px solid var(--accent);
|
||||
z-index: 1000; font-size: 0.85rem; font-weight: 700; color: #fff;
|
||||
display: none; align-items: center; gap: 10px;
|
||||
}
|
||||
.rehost-progress-toast.show { display: flex; }
|
||||
|
||||
/* ── PROGRESS BAR ─────────────────────────────── */
|
||||
.scroll-progress-bar {
|
||||
position: absolute; bottom: 0; left: 0; right: 0; height: 4px;
|
||||
@@ -498,7 +591,26 @@
|
||||
.scroller-spoiler.revealed, .scroller-spoiler:hover { color: inherit; background: rgba(255,255,255,.1); }
|
||||
.scroller-blur { filter: blur(5px); cursor: pointer; transition: filter .2s; display: inline-block; }
|
||||
.scroller-blur.revealed, .scroller-blur:hover { filter: none; }
|
||||
.comment-time { font-size: .67rem; color: rgba(255,255,255,.38); margin-top: 4px; }
|
||||
.comment-meta { display: flex; align-items: center; gap: 10px; margin-top: 4px; }
|
||||
.comment-time { font-size: .67rem; color: rgba(255,255,255,.38); }
|
||||
.comment-reply-btn {
|
||||
background: none; border: none; color: rgba(255,255,255,.45); font-size: .67rem;
|
||||
font-weight: 700; cursor: pointer; padding: 0; text-transform: none;
|
||||
}
|
||||
.comment-reply-btn:hover { color: var(--accent, #fff); }
|
||||
#reply-indicator {
|
||||
display: flex; align-items: center; gap: 8px;
|
||||
padding: 6px 12px; background: rgba(255,255,255,.05);
|
||||
border-top: 2px solid var(--accent, #4fc3f7);
|
||||
font-size: .78rem; color: rgba(255,255,255,.65);
|
||||
}
|
||||
.reply-indicator-icon { font-size: .72rem; color: var(--accent, #4fc3f7); }
|
||||
.reply-indicator-text { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||
#reply-cancel-btn {
|
||||
background: none; border: none; color: rgba(255,255,255,.4); cursor: pointer;
|
||||
padding: 2px 4px; font-size: .8rem;
|
||||
}
|
||||
#reply-cancel-btn:hover { color: #fff; }
|
||||
#comments-loading, #comments-empty { text-align: center; padding: 40px 20px; color: rgba(255,255,255,.35); font-size: .85rem; }
|
||||
#comments-loading .loader-spinner { margin: 0 auto 10px; }
|
||||
|
||||
@@ -687,6 +799,101 @@
|
||||
}
|
||||
.scroll-tag-sugg-item:hover, .scroll-tag-sugg-item.selected { background: rgba(255,255,255,.08); }
|
||||
.scroll-tag-sugg-count { font-size: .72rem; color: rgba(255,255,255,.35); margin-left: auto; }
|
||||
|
||||
/* ── 4CHAN CATALOG GRID ──────────────────────── */
|
||||
#chan-panel { z-index: 802; }
|
||||
.chan-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); gap: 10px; }
|
||||
.chan-thread-card {
|
||||
background: rgba(255,255,255,.05); border: 1px solid rgba(255,255,255,.08);
|
||||
border-radius: 12px; overflow: hidden; cursor: pointer; transition: background .14s, border-color .14s, transform .12s;
|
||||
}
|
||||
.chan-thread-card:hover { background: rgba(255,255,255,.1); border-color: var(--accent); transform: translateY(-2px); }
|
||||
.chan-thread-card.sticky { border-color: rgba(255,200,0,.3); }
|
||||
.chan-thread-thumb {
|
||||
width: 100%; aspect-ratio: 16/9; object-fit: cover; display: block;
|
||||
background: rgba(255,255,255,.03);
|
||||
}
|
||||
.chan-thread-info { padding: 8px 10px; }
|
||||
.chan-thread-sub {
|
||||
font-size: .76rem; font-weight: 700; color: #fff; line-height: 1.3;
|
||||
display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
|
||||
min-height: 1.3em;
|
||||
}
|
||||
.chan-thread-com {
|
||||
font-size: .68rem; color: rgba(255,255,255,.5); margin-top: 3px; line-height: 1.25;
|
||||
display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
|
||||
}
|
||||
.chan-thread-stats {
|
||||
display: flex; gap: 10px; margin-top: 6px; font-size: .65rem; color: rgba(255,255,255,.4); font-weight: 600;
|
||||
}
|
||||
.chan-thread-stats i { margin-right: 3px; font-size: .58rem; }
|
||||
#chan-open-btn i { color: #789922; }
|
||||
#chan-open-btn:hover i { color: var(--accent); }
|
||||
#chan-gallery-btn.active i { color: var(--accent); }
|
||||
|
||||
/* ── Thread Gallery Sidebar ──────── */
|
||||
#chan-gallery-sidebar {
|
||||
position: fixed; top: 55px; right: 0; bottom: 5px; width: 180px;
|
||||
background: rgba(10,10,14,.92); backdrop-filter: blur(20px);
|
||||
border-left: 1px solid rgba(255,255,255,.08);
|
||||
overflow-y: auto; overflow-x: hidden;
|
||||
z-index: 90; display: none;
|
||||
scrollbar-width: thin; scrollbar-color: rgba(255,255,255,.15) transparent;
|
||||
}
|
||||
#chan-gallery-sidebar.open { display: block; }
|
||||
#chan-gallery-sidebar::-webkit-scrollbar { width: 4px; }
|
||||
#chan-gallery-sidebar::-webkit-scrollbar-thumb { background: rgba(255,255,255,.15); border-radius: 2px; }
|
||||
.gallery-thumb {
|
||||
width: 100%; aspect-ratio: 1; object-fit: cover; display: block;
|
||||
cursor: pointer; opacity: .6; transition: opacity .15s, outline-color .15s;
|
||||
border-bottom: 1px solid rgba(255,255,255,.06);
|
||||
outline: 2px solid transparent; outline-offset: -2px;
|
||||
}
|
||||
.gallery-thumb:hover { opacity: 1; }
|
||||
.gallery-thumb.active { opacity: 1; outline-color: var(--accent); }
|
||||
.gallery-thumb-wrap {
|
||||
position: relative;
|
||||
}
|
||||
.gallery-thumb-idx {
|
||||
position: absolute; top: 3px; left: 5px;
|
||||
font-size: .6rem; font-weight: 700; color: rgba(255,255,255,.7);
|
||||
text-shadow: 0 1px 3px rgba(0,0,0,.8);
|
||||
pointer-events: none;
|
||||
}
|
||||
/* When gallery is open, narrow the feed */
|
||||
body.gallery-open #scroller-feed { margin-right: 180px; }
|
||||
body.gallery-open .scroll-actions { right: 192px; }
|
||||
@media (max-width: 600px) {
|
||||
#chan-gallery-sidebar { width: 100px; top: 50px; }
|
||||
body.gallery-open #scroller-feed { margin-right: 100px; }
|
||||
body.gallery-open .scroll-actions { right: 112px; }
|
||||
}
|
||||
|
||||
/* ── LEFT HAND MODE ──────────────────────── */
|
||||
body.left-hand-mode .scroll-actions { right: auto; left: 10px; }
|
||||
body.left-hand-mode .scroll-meta { right: 0; left: 72px; padding-left: 0; padding-right: 16px; text-align: right; }
|
||||
body.left-hand-mode .scroll-meta-inner { align-items: flex-end; }
|
||||
body.left-hand-mode .scroll-meta-top { flex-direction: row-reverse; }
|
||||
body.left-hand-mode .scroll-tags { justify-content: flex-end; }
|
||||
body.left-hand-mode .scroll-badges { justify-content: flex-end; }
|
||||
body.left-hand-mode.gallery-open .scroll-actions { left: 10px; right: auto; }
|
||||
body.left-hand-mode .topbar-right #chan-open-btn,
|
||||
body.left-hand-mode .topbar-right #chan-gallery-btn { display: none !important; }
|
||||
/* 4chan buttons styled as side-action buttons when inside scroll-actions */
|
||||
.scroll-actions .chan-action-btn {
|
||||
display: flex; flex-direction: column; align-items: center; gap: 3px;
|
||||
cursor: pointer; color: #fff; background: none; border: none; padding: 0;
|
||||
transition: transform .12s;
|
||||
}
|
||||
.scroll-actions .chan-action-btn:hover { transform: scale(1.14); }
|
||||
.scroll-actions .chan-action-btn .scroll-btn-icon {
|
||||
width: 46px; height: 46px; border-radius: 50%;
|
||||
background: rgba(255,255,255,.1); backdrop-filter: blur(8px); -webkit-backdrop-filter: blur(8px);
|
||||
border: 1px solid rgba(255,255,255,.14); display: flex; align-items: center; justify-content: center;
|
||||
font-size: 1.05rem; transition: background .17s;
|
||||
}
|
||||
.scroll-actions .chan-action-btn:hover .scroll-btn-icon { background: rgba(255,255,255,.22); }
|
||||
.scroll-actions .chan-action-btn .scroll-btn-label { font-size: .6rem; font-weight: 600; letter-spacing: .03em; color: rgba(255,255,255,.75); }
|
||||
</style>
|
||||
</head>
|
||||
<body class="scroller-active">
|
||||
@@ -703,36 +910,128 @@
|
||||
window.scrollerRuffleVolume = @if(typeof session !== 'undefined' && session && session.ruffle_volume !== undefined && session.ruffle_volume !== null){{ session.ruffle_volume }}@else 0.5@endif;
|
||||
window.f0ckAllowedImages = {{ allowed_comment_images_json }};
|
||||
window.scrollerPublic = {{ private_society ? 'false' : 'true' }};
|
||||
window.scrollerMimeCats = {{ JSON.stringify(scroller_mime_cats) }};
|
||||
@if(typeof session !== 'undefined' && session)
|
||||
window.scrollerUsername = "{{ session.user || '' }}";
|
||||
window.scrollerDisplayName = "{{ session.display_name || session.user || '' }}";
|
||||
window.scrollerUserAvatar = "{{ session.avatar_file ? '/a/' + session.avatar_file : (session.avatar ? '/t/' + session.avatar + '.webp' : '/a/default.png') }}";
|
||||
window.f0ckI18n = {
|
||||
// notifications
|
||||
notif_upload_approved: "{{ t('notifications.upload_approved_short') }}",
|
||||
notif_upload_pending: "{{ t('notifications.upload_pending_short') }}",
|
||||
notif_new_report: "{{ t('notifications.new_report_short') }}",
|
||||
notif_upload_denied: "{{ t('notifications.upload_denied_short') }}",
|
||||
notif_upload_deleted: "{{ t('notifications.upload_deleted_short') }}",
|
||||
notif_upload_success: "{{ t('notifications.upload_success') }}",
|
||||
notif_upload_error: "{{ t('notifications.upload_error') }}",
|
||||
notif_replied: "{{ t('notifications.replied_short') }}",
|
||||
notif_subscribed: "{{ t('notifications.subscribed_short') }}",
|
||||
notif_mentioned: "{{ t('notifications.mentioned_short') }}",
|
||||
notif_commented: "{{ t('notifications.commented') }}",
|
||||
notif_system: "{{ t('notifications.system') }}",
|
||||
notif_admin: "{{ t('notifications.admin') }}",
|
||||
notif_moderation: "{{ t('notifications.moderation') }}",
|
||||
notif_tab_user: "{{ t('nav.notif_tab_user') }}",
|
||||
notif_tab_system: "{{ t('nav.notif_tab_system') }}",
|
||||
no_notifications: "{{ t('nav.no_notifications') }}",
|
||||
// scroller
|
||||
just_now: "{{ t('scroller.just_now') }}",
|
||||
add: "{{ t('scroller.add') }}",
|
||||
update_preset: "{{ t('scroller.update_preset') }}",
|
||||
update_preset_sub: "{{ t('scroller.update_preset_sub') }}",
|
||||
no_presets: "{{ t('scroller.no_presets') }}",
|
||||
copy_clipboard: "{{ t('scroller.copy_clipboard') }}",
|
||||
copied: "{{ t('scroller.copied') }}",
|
||||
recent: "{{ t('scroller.recent') }}",
|
||||
nothing_found: "{{ t('scroller.nothing_found') }}",
|
||||
adjust_filters: "{{ t('scroller.adjust_filters') }}",
|
||||
failed_load_comments: "{{ t('scroller.failed_load_comments') }}",
|
||||
no_custom_emojis: "{{ t('scroller.no_custom_emojis') }}",
|
||||
login_required: "{{ t('scroller.login_required') }}",
|
||||
rehost_failed: "{{ t('scroller.rehost_failed') }}",
|
||||
chan_load_failed: "{{ t('scroller.chan_load_failed') }}",
|
||||
fetch_failed: "{{ t('scroller.fetch_failed') }}",
|
||||
invalid_chan_url: "{{ t('scroller.invalid_chan_url') }}",
|
||||
chan_catalog_failed: "{{ t('scroller.chan_catalog_failed') }}",
|
||||
anonymous: "{{ t('scroller.anonymous') }}",
|
||||
// timeago
|
||||
ta_just_now: "{{ t('timeago.just_now') }}",
|
||||
ta_second: "{{ t('timeago.second') }}",
|
||||
ta_seconds: "{{ t('timeago.seconds') }}",
|
||||
ta_minute: "{{ t('timeago.minute') }}",
|
||||
ta_minutes: "{{ t('timeago.minutes') }}",
|
||||
ta_hour: "{{ t('timeago.hour') }}",
|
||||
ta_hours: "{{ t('timeago.hours') }}",
|
||||
ta_day: "{{ t('timeago.day') }}",
|
||||
ta_days: "{{ t('timeago.days') }}",
|
||||
ta_week: "{{ t('timeago.week') }}",
|
||||
ta_weeks: "{{ t('timeago.weeks') }}",
|
||||
ta_month: "{{ t('timeago.month') }}",
|
||||
ta_months: "{{ t('timeago.months') }}",
|
||||
ta_year: "{{ t('timeago.year') }}",
|
||||
ta_years: "{{ t('timeago.years') }}",
|
||||
ta_ago: "{{ t('timeago.ago') }}",
|
||||
// actions
|
||||
favourite: "{{ t('scroller.favourite') }}",
|
||||
comments_label: "{{ t('scroller.comments') }}",
|
||||
add_tag: "{{ t('scroller.add_tag') }}",
|
||||
share_label: "{{ t('scroller.share') }}",
|
||||
open_label: "{{ t('scroller.open') }}",
|
||||
add_label: "{{ t('scroller.add') }}",
|
||||
view_label: "{{ t('scroller.view') }}",
|
||||
open_post: "{{ t('scroller.open_post') }}",
|
||||
already_added: "{{ t('scroller.already_added') }}",
|
||||
add_to_site: "{{ t('scroller.add_to_site') }}",
|
||||
add_to_site_first: "{{ t('scroller.add_to_site_first') }}",
|
||||
// reply
|
||||
replying_to: "{{ t('scroller.replying_to') }}",
|
||||
reply: "{{ t('scroller.reply') }}"
|
||||
};
|
||||
@endif
|
||||
</script>
|
||||
|
||||
<!-- Loader -->
|
||||
<div id="scroller-loader">
|
||||
<div class="loader-logo">{{ domain }}</div>
|
||||
<div class="loader-sub">doomscroll</div>
|
||||
<div class="loader-sub">{{ t('scroller.doomscroll') }}</div>
|
||||
<div class="loader-spinner"></div>
|
||||
</div>
|
||||
|
||||
<!-- Top bar -->
|
||||
<div id="scroller-topbar">
|
||||
<div class="topbar-left">
|
||||
<a id="scroller-back" class="topbar-icon-btn" href="/" title="Back"><i class="fa-solid fa-arrow-left"></i></a>
|
||||
<a id="scroller-back" class="topbar-icon-btn" href="/" title="{{ t('scroller.back') }}"><i class="fa-solid fa-arrow-left"></i></a>
|
||||
<div id="filter-active-summary"></div>
|
||||
</div>
|
||||
<div class="topbar-right">
|
||||
<button id="settings-open-btn" class="topbar-icon-btn" title="Settings"><i class="fa-solid fa-gear"></i></button>
|
||||
<button id="filter-open-btn" class="topbar-icon-btn" title="Filters (F)"><i class="fa-solid fa-sliders"></i></button>
|
||||
<button id="chan-open-btn" class="topbar-icon-btn" title="{{ t('scroller.chan_threads') }}" style="display:none"><i class="fa-solid fa-clover"></i></button>
|
||||
<button id="chan-gallery-btn" class="topbar-icon-btn" title="{{ t('scroller.thread_gallery') }} (G)" style="display:none"><i class="fa-solid fa-grip"></i></button>
|
||||
<button id="settings-open-btn" class="topbar-icon-btn" title="{{ t('scroller.settings') }}"><i class="fa-solid fa-gear"></i></button>
|
||||
<button id="filter-open-btn" class="topbar-icon-btn" title="{{ t('scroller.filters') }} (F)"><i class="fa-solid fa-sliders"></i></button>
|
||||
@if(typeof session !== 'undefined' && session)
|
||||
<a id="scroller-notif-btn" class="topbar-icon-btn" href="/notifications" title="Notifications">
|
||||
<i class="fa-solid fa-bell"></i>
|
||||
<span id="scroller-notif-badge"></span>
|
||||
</a>
|
||||
<div id="scroller-notif-wrap" style="position:relative;">
|
||||
<button id="scroller-notif-btn" class="topbar-icon-btn" title="Notifications">
|
||||
<i class="fa-solid fa-bell"></i>
|
||||
<span id="scroller-notif-badge"></span>
|
||||
</button>
|
||||
<div id="scroller-notif-dropdown" class="notif-dropdown" style="position:fixed; z-index:99999; display:none;">
|
||||
<div class="notif-header">
|
||||
<div class="notif-tabs">
|
||||
<button class="notif-tab active" data-tab="user">{{ t('nav.notif_tab_user') }} <span class="notif-tab-badge" id="scroller-notif-tab-badge-user" style="display:none">0</span></button>
|
||||
<button class="notif-tab" data-tab="system">{{ t('nav.notif_tab_system') }} <span class="notif-tab-badge" id="scroller-notif-tab-badge-system" style="display:none">0</span></button>
|
||||
</div>
|
||||
<button id="scroller-mark-all-read" title="{{ t('nav.mark_all_read') }}"><i class="fa-solid fa-check-double"></i></button>
|
||||
</div>
|
||||
<div class="notif-list" id="scroller-notif-list" data-active-tab="user">
|
||||
<div class="notif-empty">{{ t('nav.no_notifications') }}</div>
|
||||
</div>
|
||||
<div class="notif-footer">
|
||||
<a href="/notifications" target="_blank" class="view-all-notifs">{{ t('nav.view_all_notifications') }}</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
<button id="scroller-mute-btn" class="topbar-icon-btn" title="Volume (M)"><i class="fa-solid fa-volume-xmark"></i></button>
|
||||
<button id="scroller-mute-btn" class="topbar-icon-btn" title="{{ t('scroller.volume') }} (M)"><i class="fa-solid fa-volume-xmark"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -743,13 +1042,17 @@
|
||||
<span class="volume-label"><i class="fa-solid fa-volume-low"></i></span>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- Thread Gallery Sidebar -->
|
||||
<div id="chan-gallery-sidebar"></div>
|
||||
|
||||
<!-- Feed -->
|
||||
<div id="scroller-feed" role="feed" aria-label="Doomscroll feed">
|
||||
<div id="scroller-sentinel"></div>
|
||||
<div id="scroller-empty">
|
||||
<i class="fa-solid fa-binoculars"></i>
|
||||
<p>Nothing found with current filters</p>
|
||||
<button onclick="document.getElementById('filter-open-btn').click()" style="margin-top:8px;padding:8px 20px;border-radius:50px;background:rgba(255,255,255,.08);border:1px solid rgba(255,255,255,.15);color:#fff;cursor:pointer;font-size:.8rem;">Adjust filters</button>
|
||||
<p>{{ t('scroller.nothing_found') }}</p>
|
||||
<button onclick="document.getElementById('filter-open-btn').click()" style="margin-top:8px;padding:8px 20px;border-radius:50px;background:rgba(255,255,255,.08);border:1px solid rgba(255,255,255,.15);color:#fff;cursor:pointer;font-size:.8rem;">{{ t('scroller.adjust_filters') }}</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -758,12 +1061,12 @@
|
||||
<div id="filter-panel" class="scroller-panel" role="dialog" aria-label="Feed filters">
|
||||
<div class="panel-handle"><div class="panel-handle-bar"></div></div>
|
||||
<div class="panel-header">
|
||||
<span class="panel-title">Filters</span>
|
||||
<button class="filter-reset-btn" id="filter-reset-btn">Reset all</button>
|
||||
<span class="panel-title">{{ t('scroller.filters') }}</span>
|
||||
<button class="filter-reset-btn" id="filter-reset-btn">{{ t('scroller.reset_all') }}</button>
|
||||
</div>
|
||||
<div class="filter-scroll-area">
|
||||
<div class="filter-section">
|
||||
<div class="filter-section-label">Rating</div>
|
||||
<div class="filter-section-label">{{ t('scroller.rating') }}</div>
|
||||
<div class="pill-group" id="mode-pills">
|
||||
<button class="filter-pill active" data-mode="0"><i class="fa-solid fa-shield-halved"></i>SFW</button>
|
||||
@if(session)
|
||||
@@ -773,40 +1076,41 @@
|
||||
<button class="filter-pill" data-mode="4"><i class="fa-solid fa-skull"></i>NSFL</button>
|
||||
@endif
|
||||
@if(session)
|
||||
<button class="filter-pill" data-mode="3">All</button>
|
||||
<button class="filter-pill" data-mode="3">{{ t('scroller.all') }}</button>
|
||||
@endif
|
||||
@if(session && (session.admin || session.is_moderator))
|
||||
<button class="filter-pill" data-mode="2">Untagged</button>
|
||||
<button class="filter-pill" data-mode="2">{{ t('scroller.untagged') }}</button>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
<div class="filter-section">
|
||||
<div class="filter-section-label">Media type</div>
|
||||
<div class="filter-section-label">{{ t('scroller.media_type') }}</div>
|
||||
<div class="pill-group" id="mime-pills">
|
||||
<button class="filter-pill active" data-mime=""><i class="fa-solid fa-layer-group"></i>All</button>
|
||||
<button class="filter-pill active" data-mime=""><i class="fa-solid fa-layer-group"></i>{{ t('scroller.all') }}</button>
|
||||
@if(scroller_mime_cats.includes('video'))
|
||||
<button class="filter-pill" data-mime="video"><i class="fa-solid fa-film"></i>Video</button>
|
||||
<button class="filter-pill" data-mime="video"><i class="fa-solid fa-film"></i>{{ t('scroller.video') }}</button>
|
||||
@endif
|
||||
@if(scroller_mime_cats.includes('image'))
|
||||
<button class="filter-pill" data-mime="image"><i class="fa-solid fa-image"></i>Image</button>
|
||||
<button class="filter-pill" data-mime="image"><i class="fa-solid fa-image"></i>{{ t('scroller.image') }}</button>
|
||||
@endif
|
||||
@if(scroller_mime_cats.includes('audio'))
|
||||
<button class="filter-pill" data-mime="audio"><i class="fa-solid fa-music"></i>Audio</button>
|
||||
<button class="filter-pill" data-mime="audio"><i class="fa-solid fa-music"></i>{{ t('scroller.audio') }}</button>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
<div class="filter-section">
|
||||
<div class="filter-section-label">Order</div>
|
||||
<div class="filter-section-label">{{ t('scroller.order') }}</div>
|
||||
<div class="pill-group" id="order-pills">
|
||||
<button class="filter-pill active" data-order="random"><i class="fa-solid fa-shuffle"></i>Random</button>
|
||||
<button class="filter-pill" data-order="newest"><i class="fa-solid fa-clock-rotate-left"></i>Newest</button>
|
||||
<button class="filter-pill" data-order="oldest"><i class="fa-solid fa-hourglass-start"></i>Oldest</button>
|
||||
<button class="filter-pill active" data-order="random"><i class="fa-solid fa-shuffle"></i>{{ t('scroller.random') }}</button>
|
||||
<button class="filter-pill" data-order="newest"><i class="fa-solid fa-clock-rotate-left"></i>{{ t('scroller.newest') }}</button>
|
||||
<button class="filter-pill" data-order="oldest"><i class="fa-solid fa-hourglass-start"></i>{{ t('scroller.oldest') }}</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="filter-section">
|
||||
<div class="filter-section-label">Tags</div>
|
||||
<div class="filter-section-label">{{ t('scroller.tags') }}</div>
|
||||
<div class="tag-search-wrap">
|
||||
<input type="text" id="filter-tag-input" placeholder="Search tags…" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" />
|
||||
<input type="text" id="filter-tag-input" placeholder="{{ t('scroller.search_tags') }}" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" />
|
||||
<span class="tag-search-icon"><i class="fa-solid fa-tag"></i></span>
|
||||
<button id="filter-tag-clear"><i class="fa-solid fa-xmark"></i></button>
|
||||
</div>
|
||||
@@ -814,12 +1118,48 @@
|
||||
<div id="filter-active-tags"></div>
|
||||
</div>
|
||||
<div class="filter-section" style="margin-top:8px">
|
||||
<div class="filter-section-label">Saved presets</div>
|
||||
<button class="settings-save-preset-btn" id="settings-save-preset-btn"><i class="fa-solid fa-floppy-disk"></i> Save current filters as preset</button>
|
||||
<div class="filter-section-label">{{ t('scroller.saved_presets') }}</div>
|
||||
<button class="settings-save-preset-btn" id="settings-save-preset-btn"><i class="fa-solid fa-floppy-disk"></i> {{ t('scroller.save_preset') }}</button>
|
||||
<div id="settings-presets-list"></div>
|
||||
</div>
|
||||
</div>
|
||||
<button class="filter-apply-btn" id="filter-apply-btn"><i class="fa-solid fa-check" style="margin-right:8px"></i>Apply & Reload</button>
|
||||
<button class="filter-apply-btn" id="filter-apply-btn"><i class="fa-solid fa-check" style="margin-right:8px"></i>{{ t('scroller.apply_reload') }}</button>
|
||||
</div>
|
||||
|
||||
<!-- 4CHAN PANEL -->
|
||||
<div id="chan-backdrop" class="scroller-backdrop"></div>
|
||||
<div id="chan-panel" class="scroller-panel" role="dialog" aria-label="4chan threads">
|
||||
<div class="panel-handle"><div class="panel-handle-bar"></div></div>
|
||||
<div class="panel-header">
|
||||
<span class="panel-title"><i class="fa-solid fa-clover" style="margin-right:6px;color:var(--accent)"></i>{{ t('scroller.chan_threads') }}</span>
|
||||
</div>
|
||||
<div class="filter-scroll-area">
|
||||
<div class="filter-section">
|
||||
<div class="filter-section-label">{{ t('scroller.load_by_url') }}</div>
|
||||
<div style="display:flex;gap:8px">
|
||||
<input type="text" id="chan-url-input" style="flex:1;padding:10px 14px;background:rgba(255,255,255,.06);border:1px solid rgba(255,255,255,.12);border-radius:10px;color:#fff;font-size:.86rem;outline:none;" placeholder="https://boards.4chan.org/wsg/thread/..." autocomplete="off">
|
||||
<button id="chan-url-load-btn" style="padding:10px 18px;background:var(--accent);color:#000;border:none;border-radius:10px;font-weight:700;font-size:.84rem;cursor:pointer;white-space:nowrap;">{{ t('scroller.load') }}</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="filter-section">
|
||||
<div class="filter-section-label">{{ t('scroller.browse_boards') }}</div>
|
||||
<div class="pill-group" id="chan-board-pills">
|
||||
<button class="filter-pill" data-board="gif"><i class="fa-solid fa-fire"></i>/gif/</button>
|
||||
<button class="filter-pill active" data-board="wsg"><i class="fa-solid fa-shield-halved"></i>/wsg/</button>
|
||||
<div style="display:flex;align-items:center;gap:4px;margin-left:4px;">
|
||||
<span style="color:rgba(255,255,255,.4);font-size:.8rem">/</span>
|
||||
<input type="text" id="chan-custom-board" style="width:48px;padding:6px 8px;background:rgba(255,255,255,.06);border:1px solid rgba(255,255,255,.12);border-radius:8px;color:#fff;font-size:.82rem;outline:none;text-align:center;" placeholder="v" maxlength="6" autocomplete="off">
|
||||
<span style="color:rgba(255,255,255,.4);font-size:.8rem">/</span>
|
||||
<button id="chan-custom-board-btn" class="filter-pill" style="padding:6px 10px;font-size:.78rem;">{{ t('scroller.go') }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="filter-section" style="padding-top:8px">
|
||||
<input type="text" id="chan-catalog-search" style="width:100%;padding:10px 14px;background:rgba(255,255,255,.06);border:1px solid rgba(255,255,255,.12);border-radius:10px;color:#fff;font-size:.86rem;outline:none;margin-bottom:12px;box-sizing:border-box;" placeholder="{{ t('scroller.search_threads') }}" autocomplete="off">
|
||||
<div id="chan-catalog-grid" class="chan-grid"></div>
|
||||
<div id="chan-catalog-loading" style="text-align:center;padding:30px;color:rgba(255,255,255,.35);display:none"><div class="loader-spinner" style="margin:0 auto 10px"></div>{{ t('scroller.loading_catalog') }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- SETTINGS PANEL -->
|
||||
@@ -827,46 +1167,53 @@
|
||||
<div id="settings-panel" class="scroller-panel" role="dialog" aria-label="Scroller settings">
|
||||
<div class="panel-handle"><div class="panel-handle-bar"></div></div>
|
||||
<div class="panel-header">
|
||||
<span class="panel-title">Settings</span>
|
||||
<span class="panel-title">{{ t('scroller.settings') }}</span>
|
||||
</div>
|
||||
<div class="filter-scroll-area">
|
||||
<div class="filter-section">
|
||||
<div class="filter-section-label">Appearance</div>
|
||||
<div class="filter-section-label">{{ t('scroller.appearance') }}</div>
|
||||
<div class="settings-toggle-row">
|
||||
<div class="settings-toggle-info">
|
||||
<span class="settings-toggle-name">Hide UI</span>
|
||||
<span class="settings-toggle-desc">Hides the top bar and action buttons for full immersion</span>
|
||||
<span class="settings-toggle-name">{{ t('scroller.hide_ui') }}</span>
|
||||
<span class="settings-toggle-desc">{{ t('scroller.hide_ui_desc') }}</span>
|
||||
</div>
|
||||
<div class="settings-toggle-switch" id="st-hide-ui"><div class="settings-toggle-knob"></div></div>
|
||||
</div>
|
||||
<div class="settings-toggle-row">
|
||||
<div class="settings-toggle-info">
|
||||
<span class="settings-toggle-name">Start with sound</span>
|
||||
<span class="settings-toggle-desc">Automatically unmute when you open the scroller</span>
|
||||
<span class="settings-toggle-name">{{ t('scroller.start_sound') }}</span>
|
||||
<span class="settings-toggle-desc">{{ t('scroller.start_sound_desc') }}</span>
|
||||
</div>
|
||||
<div class="settings-toggle-switch" id="st-start-unmuted"><div class="settings-toggle-knob"></div></div>
|
||||
</div>
|
||||
<div class="settings-toggle-row">
|
||||
<div class="settings-toggle-info">
|
||||
<span class="settings-toggle-name">Animated background</span>
|
||||
<span class="settings-toggle-desc">Live video frames behind the player; disable for static thumbnail</span>
|
||||
<span class="settings-toggle-name">{{ t('scroller.animated_bg') }}</span>
|
||||
<span class="settings-toggle-desc">{{ t('scroller.animated_bg_desc') }}</span>
|
||||
</div>
|
||||
<div class="settings-toggle-switch" id="st-canvas-bg"><div class="settings-toggle-knob"></div></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="filter-section">
|
||||
<div class="filter-section-label">Playback</div>
|
||||
<div class="settings-toggle-row">
|
||||
<div class="settings-toggle-info">
|
||||
<span class="settings-toggle-name">Auto-next</span>
|
||||
<span class="settings-toggle-desc">Automatically advance to the next item when media ends</span>
|
||||
<span class="settings-toggle-name">{{ t('scroller.left_hand') }}</span>
|
||||
<span class="settings-toggle-desc">{{ t('scroller.left_hand_desc') }}</span>
|
||||
</div>
|
||||
<div class="settings-toggle-switch" id="st-left-hand"><div class="settings-toggle-knob"></div></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="filter-section">
|
||||
<div class="filter-section-label">{{ t('scroller.playback') }}</div>
|
||||
<div class="settings-toggle-row">
|
||||
<div class="settings-toggle-info">
|
||||
<span class="settings-toggle-name">{{ t('scroller.auto_next') }}</span>
|
||||
<span class="settings-toggle-desc">{{ t('scroller.auto_next_desc') }}</span>
|
||||
</div>
|
||||
<div class="settings-toggle-switch" id="st-auto-next"><div class="settings-toggle-knob"></div></div>
|
||||
</div>
|
||||
<div class="settings-toggle-row" style="margin-top:2px;">
|
||||
<div class="settings-toggle-info">
|
||||
<span class="settings-toggle-name">Loops before next</span>
|
||||
<span class="settings-toggle-desc">How many times to play before advancing (videos & audio)</span>
|
||||
<span class="settings-toggle-name">{{ t('scroller.loops_before_next') }}</span>
|
||||
<span class="settings-toggle-desc">{{ t('scroller.loops_before_next_desc') }}</span>
|
||||
</div>
|
||||
<input type="number" id="st-auto-next-loops" min="0" max="99" value="1"
|
||||
style="width:56px;padding:5px 8px;background:rgba(255,255,255,.08);border:1px solid rgba(255,255,255,.15);border-radius:8px;color:#fff;font-size:.88rem;text-align:center;outline:none;" />
|
||||
@@ -881,26 +1228,31 @@
|
||||
<div id="comments-panel" class="scroller-panel" role="dialog" aria-label="Comments">
|
||||
<div class="panel-handle"><div class="panel-handle-bar"></div></div>
|
||||
<div class="panel-header">
|
||||
<span class="panel-title">Comments <span id="comments-count" style="color:rgba(255,255,255,.45);font-weight:400;font-size:.85rem"></span></span>
|
||||
<span class="panel-title">{{ t('scroller.comments') }} <span id="comments-count" style="color:rgba(255,255,255,.45);font-weight:400;font-size:.85rem"></span></span>
|
||||
<a id="comments-open-link" href="#" target="_blank" style="color:var(--accent);font-size:.78rem;font-weight:700;text-decoration:none;padding:4px 8px;">
|
||||
<i class="fa-solid fa-up-right-from-square" style="margin-right:4px;font-size:.72rem"></i>Open
|
||||
<i class="fa-solid fa-up-right-from-square" style="margin-right:4px;font-size:.72rem"></i>{{ t('scroller.open') }}
|
||||
</a>
|
||||
</div>
|
||||
<div id="comments-list">
|
||||
<div id="comments-loading"><div class="loader-spinner"></div>Loading…</div>
|
||||
<div id="comments-empty" style="display:none"><i class="fa-regular fa-comment" style="font-size:2rem;display:block;margin-bottom:10px"></i>No comments yet</div>
|
||||
<div id="comments-loading"><div class="loader-spinner"></div>{{ t('scroller.loading') }}</div>
|
||||
<div id="comments-empty" style="display:none"><i class="fa-regular fa-comment" style="font-size:2rem;display:block;margin-bottom:10px"></i>{{ t('scroller.no_comments') }}</div>
|
||||
</div>
|
||||
@if(typeof session !== 'undefined' && session)
|
||||
<div id="comments-input-area">
|
||||
<div id="mention-dropdown"></div>
|
||||
<div id="reply-indicator" style="display:none">
|
||||
<i class="fa-solid fa-reply reply-indicator-icon"></i>
|
||||
<span class="reply-indicator-text"></span>
|
||||
<button id="reply-cancel-btn" type="button"><i class="fa-solid fa-xmark"></i></button>
|
||||
</div>
|
||||
<div class="comment-input-row">
|
||||
<button id="comment-emoji-trigger" class="emoji-trigger" title="Emoji" type="button">☺</button>
|
||||
<textarea id="comment-input" rows="1" placeholder="Write a comment..." maxlength="2000"></textarea>
|
||||
<textarea id="comment-input" rows="1" placeholder="{{ t('scroller.write_comment') }}" maxlength="2000"></textarea>
|
||||
<button id="comment-send-btn" disabled><i class="fa-solid fa-paper-plane"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
@else
|
||||
<div class="comments-login-note"><a href="/login">Log in</a> to comment</div>
|
||||
<div class="comments-login-note"><a href="/login">{{ t('scroller.login_to_comment') }}</a></div>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
@@ -909,11 +1261,11 @@
|
||||
<div id="tag-bar">
|
||||
<div id="tag-bar-inner">
|
||||
<div style="position:relative;flex:1;min-width:0">
|
||||
<input id="scroll-tag-input" type="text" placeholder="Add a tag to this item…" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" maxlength="70">
|
||||
<input id="scroll-tag-input" type="text" placeholder="{{ t('scroller.add_tag_placeholder') }}" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" maxlength="70">
|
||||
<div id="scroll-tag-suggestions"></div>
|
||||
</div>
|
||||
<button id="scroll-tag-send-btn" title="Add tag"><i class="fa-solid fa-plus"></i></button>
|
||||
<button id="tag-bar-close-btn" title="Close"><i class="fa-solid fa-xmark"></i></button>
|
||||
<button id="scroll-tag-send-btn" title="{{ t('scroller.add_tag') }}"><i class="fa-solid fa-plus"></i></button>
|
||||
<button id="tag-bar-close-btn" title="{{ t('scroller.close') }}"><i class="fa-solid fa-xmark"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
@@ -922,24 +1274,24 @@
|
||||
<div id="share-backdrop"></div>
|
||||
<div id="share-panel">
|
||||
<div id="share-panel-handle"></div>
|
||||
<div id="share-panel-title">Share</div>
|
||||
<div id="share-panel-title">{{ t('scroller.share') }}</div>
|
||||
<div class="share-row" id="share-copy-row">
|
||||
<div class="share-row-icon copy-icon"><i class="fa-solid fa-link"></i></div>
|
||||
<div class="share-row-text">
|
||||
<div class="share-row-title">Copy link</div>
|
||||
<div class="share-row-sub" id="share-copy-sub">Copy to clipboard</div>
|
||||
<div class="share-row-title">{{ t('scroller.copy_link') }}</div>
|
||||
<div class="share-row-sub" id="share-copy-sub">{{ t('scroller.copy_clipboard') }}</div>
|
||||
</div>
|
||||
</div>
|
||||
@if(typeof session !== 'undefined' && session && private_messages)
|
||||
<div class="share-row" id="share-dm-row">
|
||||
<div class="share-row-icon dm-icon"><i class="fa-solid fa-paper-plane"></i></div>
|
||||
<div class="share-row-text">
|
||||
<div class="share-row-title">Send via DM</div>
|
||||
<div class="share-row-sub">Share to a user's inbox</div>
|
||||
<div class="share-row-title">{{ t('scroller.send_dm') }}</div>
|
||||
<div class="share-row-sub">{{ t('scroller.share_inbox') }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="share-dm-search">
|
||||
<input id="share-user-input" type="text" placeholder="Search for a user…" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false">
|
||||
<input id="share-user-input" type="text" placeholder="{{ t('scroller.search_user') }}" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false">
|
||||
<div id="share-user-results"></div>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
Reference in New Issue
Block a user