1347 lines
76 KiB
HTML
1347 lines
76 KiB
HTML
<!doctype html>
|
|
<html lang="{{ lang || 'en' }}" theme="@if(typeof theme !== 'undefined'){{ theme }}@endif">
|
|
<head>
|
|
<title>{{ domain }} — doomscroll</title>
|
|
<meta charset="utf-8" />
|
|
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
|
|
<meta name="theme-color" content="#000" />
|
|
<link rel="manifest" href="/manifest.json">
|
|
<link rel="icon" @if(custom_favicon && custom_favicon.length > 0)href="{{ custom_favicon }}"@else type="image/gif" href="/s/img/favicon.gif"@endif />
|
|
<link rel="stylesheet" href="/s/fa/all.min.css">
|
|
<link rel="preload" href="/s/vcr.ttf" as="font" type="font/ttf" crossorigin>
|
|
@if(typeof page_meta !== 'undefined')
|
|
<meta property="og:site_name" content="{{ domain }}" />
|
|
<meta property="og:title" content="{{ page_meta.title }}" />
|
|
<meta property="og:url" content="{{ page_meta.url }}" />
|
|
<meta property="og:image" content="{{ page_meta.image }}" />
|
|
<meta property="og:description" content="{{ page_meta.description }}" />
|
|
<meta property="og:type" content="website" />
|
|
<meta property="twitter:card" content="summary" />
|
|
<meta property="twitter:title" content="{{ page_meta.title }}" />
|
|
<meta property="twitter:image" content="{{ page_meta.image }}" />
|
|
<meta property="twitter:url" content="{{ page_meta.url }}" />
|
|
@endif
|
|
<style>
|
|
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
|
/* Kill all mobile tap highlights globally */
|
|
* { -webkit-tap-highlight-color: transparent; }
|
|
|
|
:root {
|
|
--accent: #fff;
|
|
--overlay-bg: linear-gradient(to top, rgba(0,0,0,.9) 0%, rgba(0,0,0,.35) 15%, transparent 60%);
|
|
--badge-sfw: #4caf50;
|
|
--badge-nsfw: #e91e8c;
|
|
--badge-nsfl: #c62828;
|
|
--panel-bg: rgba(10,10,12,.97);
|
|
--panel-border: rgba(255,255,255,.07);
|
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
|
}
|
|
html[theme='f0ck'] { --accent: #9f0; }
|
|
html[theme='p1nk'] { --accent: #ff00d0; }
|
|
html[theme='orange'] { --accent: #ff6f00; }
|
|
html[theme='atmos'] { --accent: #1fb2b0; }
|
|
html[theme='iced'] { --accent: #0084ff; }
|
|
html[theme='amoled'] { --accent: #fff; }
|
|
|
|
html, body {
|
|
width: 100%; height: 100vh; height: 100dvh; overflow: hidden; background: #000; color: #fff;
|
|
touch-action: pan-y;
|
|
/* Prevent text/image selection on long-press mobile */
|
|
-webkit-user-select: none; user-select: none;
|
|
}
|
|
|
|
/* When loaded via PJAX: hide the main-site navbar and pagewrapper since
|
|
the scroller has its own full-screen topbar (#scroller-topbar). */
|
|
body.scroller-active .navbar,
|
|
body.scroller-active .pagewrapper > :not(#main),
|
|
body.scroller-active .global-sidebar-right { display: none !important; }
|
|
|
|
/* #main wrapper needed for PJAX content swap — must fill full viewport
|
|
so #scroller-feed { height: 100% } has a sized parent to stretch into. */
|
|
#main { width: 100%; height: 100vh; height: 100dvh; }
|
|
|
|
/* ── TOP BAR ──────────────────────────────────── */
|
|
#scroller-topbar {
|
|
position: fixed; top: 0; left: 0; right: 0;
|
|
z-index: 500;
|
|
display: flex; align-items: center; justify-content: space-between;
|
|
padding: 10px 12px; pointer-events: none;
|
|
}
|
|
.topbar-left, .topbar-right {
|
|
display: flex; align-items: center; gap: 8px; pointer-events: all;
|
|
}
|
|
.topbar-icon-btn {
|
|
background: rgba(0,0,0,.6);
|
|
backdrop-filter: blur(12px); -webkit-backdrop-filter: blur(12px);
|
|
border: 1px solid rgba(255,255,255,.11); border-radius: 50%;
|
|
width: 40px; height: 40px;
|
|
display: flex; align-items: center; justify-content: center;
|
|
color: #fff; font-size: .92rem;
|
|
text-decoration: none; cursor: pointer;
|
|
transition: background .17s, transform .12s, border-color .17s; flex-shrink: 0;
|
|
}
|
|
.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);
|
|
border: 1px solid var(--accent); border-radius: 50px;
|
|
padding: 6px 12px; font-size: .7rem; font-weight: 700; letter-spacing: .03em;
|
|
color: var(--accent); max-width: 190px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
|
|
}
|
|
#filter-active-summary.show { display: block; }
|
|
|
|
/* ── VOLUME POPUP ─────────────────────────────── */
|
|
#volume-popup {
|
|
position: fixed; top: 60px; right: 12px;
|
|
z-index: 510;
|
|
background: rgba(10,10,12,.95);
|
|
backdrop-filter: blur(16px); -webkit-backdrop-filter: blur(16px);
|
|
border: 1px solid rgba(255,255,255,.1); border-radius: 14px;
|
|
padding: 14px 12px;
|
|
display: flex; flex-direction: column; align-items: center; gap: 10px;
|
|
opacity: 0; pointer-events: none;
|
|
transform: translateY(-8px) scale(.96);
|
|
transition: opacity .2s, transform .2s;
|
|
}
|
|
#volume-popup.open { opacity: 1; pointer-events: all; transform: translateY(0) scale(1); }
|
|
#volume-slider {
|
|
-webkit-appearance: none; appearance: none;
|
|
writing-mode: vertical-lr; direction: rtl;
|
|
width: 5px; height: 100px;
|
|
background: rgba(255,255,255,.15); border-radius: 3px; outline: none; cursor: pointer;
|
|
}
|
|
#volume-slider::-webkit-slider-thumb {
|
|
-webkit-appearance: none; appearance: none;
|
|
width: 16px; height: 16px; border-radius: 50%;
|
|
background: var(--accent); cursor: pointer;
|
|
box-shadow: 0 0 6px rgba(0,0,0,.4);
|
|
}
|
|
#volume-slider::-moz-range-thumb {
|
|
width: 16px; height: 16px; border-radius: 50%;
|
|
background: var(--accent); border: none; cursor: pointer;
|
|
}
|
|
.volume-label { font-size: .65rem; font-weight: 700; color: rgba(255,255,255,.5); }
|
|
|
|
/* ── FEED ─────────────────────────────────────── */
|
|
#scroller-feed {
|
|
width: 100%; height: 100%;
|
|
overflow-y: scroll; scroll-snap-type: y mandatory;
|
|
-webkit-overflow-scrolling: touch; overscroll-behavior: contain;
|
|
}
|
|
#scroller-feed::-webkit-scrollbar { display: none; }
|
|
#scroller-feed { scrollbar-width: none; }
|
|
|
|
/* ── SLIDE ────────────────────────────────────── */
|
|
.scroll-slide {
|
|
position: relative; width: 100%;
|
|
height: 100vh; height: 100dvh;
|
|
scroll-snap-align: start; scroll-snap-stop: always;
|
|
display: flex; align-items: center; justify-content: center;
|
|
background: #000; overflow: hidden;
|
|
-webkit-touch-callout: none; -webkit-user-select: none; user-select: none;
|
|
}
|
|
.scroll-bg-blur {
|
|
position: absolute; inset: 0; z-index: 0;
|
|
filter: blur(28px) brightness(.38) saturate(1.3); transform: scale(1.15);
|
|
}
|
|
/* canvas bg: stretch to fill the container */
|
|
.scroll-bg-blur canvas, canvas.scroll-bg-blur {
|
|
width: 100%; height: 100%; display: block; object-fit: cover;
|
|
}
|
|
.scroll-media {
|
|
position: absolute; inset: 0; display: flex; align-items: center; justify-content: center;
|
|
overflow: hidden; z-index: 1;
|
|
}
|
|
.scroll-media video, .scroll-media img { max-width: 100%; max-height: 100%; width: auto; height: auto; object-fit: contain; display: block; }
|
|
.scroll-media video { width: 100%; height: 100%; background: transparent; opacity: 0; }
|
|
.scroll-media video.ready { opacity: 1; }
|
|
.audio-cover {
|
|
width: 200px; height: 200px; border-radius: 50%;
|
|
background: rgba(255,255,255,.12) center/cover;
|
|
backdrop-filter: blur(6px); -webkit-backdrop-filter: blur(6px);
|
|
border: 3px solid rgba(255,255,255,.2);
|
|
box-shadow: 0 0 60px rgba(0,0,0,.6), inset 0 0 0 1px rgba(255,255,255,.08);
|
|
animation: vinylSpin 12s linear infinite; animation-play-state: paused;
|
|
display: flex; align-items: center; justify-content: center;
|
|
position: relative;
|
|
}
|
|
.audio-cover::after {
|
|
content: ''; width: 44px; height: 44px; border-radius: 50%;
|
|
background: rgba(0,0,0,.65); border: 3px solid rgba(255,255,255,.18);
|
|
position: absolute; /* stays centred on top of cover art */
|
|
}
|
|
.audio-cover.playing { animation-play-state: running; }
|
|
@keyframes vinylSpin { to { transform: rotate(360deg); } }
|
|
.scroll-overlay { position: absolute; inset: 0; background: var(--overlay-bg); z-index: 2; pointer-events: none; }
|
|
/* Long-press peek: hide all UI so user can view the media cleanly */
|
|
.ui-peek .scroll-overlay,
|
|
.ui-peek .scroll-meta,
|
|
.ui-peek .scroll-actions,
|
|
.ui-peek .scroll-progress-bar { opacity: 0; pointer-events: none; transition: opacity .15s; }
|
|
|
|
/* tap layer z:3 — actions/meta at z:10 win */
|
|
.tap-overlay { position: absolute; inset: 0; z-index: 3; cursor: pointer; }
|
|
.tap-pause-icon {
|
|
position: absolute; top: 50%; left: 50%;
|
|
transform: translate(-50%, -50%) scale(0);
|
|
font-size: 4rem; color: rgba(255,255,255,.88); pointer-events: none;
|
|
transition: opacity .28s, transform .22s cubic-bezier(.34,1.56,.64,1); opacity: 0;
|
|
}
|
|
.tap-pause-icon.show { opacity: 1; transform: translate(-50%, -50%) scale(1); }
|
|
.tap-pause-icon.hide { opacity: 0; transform: translate(-50%, -50%) scale(.55); }
|
|
|
|
/* ── DOUBLE-TAP FAV FLASH ─────────────────────── */
|
|
.fav-flash {
|
|
position: absolute; top: 50%; left: 50%;
|
|
font-size: 5rem; color: #ff4081; pointer-events: none;
|
|
transform: translate(-50%, -50%) scale(0); opacity: 0; z-index: 15;
|
|
text-shadow: 0 2px 20px rgba(0,0,0,.5);
|
|
transition: transform .25s cubic-bezier(.34,1.56,.64,1), opacity .35s;
|
|
}
|
|
.fav-flash.show { transform: translate(-50%, -50%) scale(1); opacity: 1; }
|
|
.fav-flash.hide { transform: translate(-50%, -50%) scale(.6); opacity: 0; }
|
|
|
|
/* ── Floating heart (double-tap) ───────────────── */
|
|
@keyframes tapHeartFloat {
|
|
0% { transform: translate(-50%, -50%) scale(0); opacity: 0; }
|
|
15% { transform: translate(-50%, -60%) scale(1.2); opacity: 1; }
|
|
35% { transform: translate(calc(-50% + var(--drift, 0px)), -80%) scale(1); opacity: 1; }
|
|
100% { transform: translate(calc(-50% + var(--drift, 0px)), -200%) scale(.7); opacity: 0; }
|
|
}
|
|
.tap-heart {
|
|
position: absolute; pointer-events: none; z-index: 20;
|
|
transform: translate(-50%, -50%) scale(0);
|
|
animation: tapHeartFloat .52s cubic-bezier(.22,1,.36,1) forwards;
|
|
filter: drop-shadow(0 2px 8px rgba(0,0,0,.4));
|
|
line-height: 1;
|
|
}
|
|
|
|
/* ── META z:10 ────────────────────────────────── */
|
|
.scroll-meta { position: absolute; bottom: 0; left: 0; right: 72px; z-index: 10; padding: 16px 16px 44px; pointer-events: none; }
|
|
.scroll-meta-inner { display: flex; flex-direction: column; gap: 7px; }
|
|
.scroll-meta-top { display: flex; align-items: center; gap: 10px; }
|
|
.scroll-avatar { width: 38px; height: 38px; border-radius: 50%; object-fit: cover; border: 2px solid rgba(255,255,255,.25); flex-shrink: 0; }
|
|
.scroll-username { font-weight: 700; font-size: .9rem; text-shadow: 0 1px 6px rgba(0,0,0,.9); }
|
|
.scroll-timeago { font-size: .7rem; color: rgba(255,255,255,.6); margin-top: 1px; }
|
|
.scroll-tags { font-size: .74rem; color: rgba(255,255,255,.7); display: flex; flex-wrap: wrap; align-items: center; gap: 4px; }
|
|
.scroll-tag-pill {
|
|
display: inline-block; padding: 2px 8px; border-radius: 50px;
|
|
background: rgba(255,255,255,.12); border: 1px solid rgba(255,255,255,.18);
|
|
font-size: .68rem; font-weight: 600; color: rgba(255,255,255,.85);
|
|
cursor: pointer; pointer-events: all; position: relative; z-index: 11;
|
|
transition: background .13s, border-color .13s;
|
|
}
|
|
.scroll-tag-pill:hover { background: var(--accent); border-color: var(--accent); color: #000; }
|
|
.scroll-tag-pill-del {
|
|
display: none; align-items: center; justify-content: center;
|
|
margin-left: 2px; font-size: .6rem; opacity: .6; cursor: pointer;
|
|
background: none; border: none; color: inherit; padding: 0 2px;
|
|
}
|
|
.scroll-tag-pill:hover .scroll-tag-pill-del { display: inline-flex; }
|
|
/* Notification bell in scroller topbar */
|
|
#scroller-notif-btn { position: relative; }
|
|
#scroller-notif-badge {
|
|
display: none; position: absolute; top: 2px; right: 2px;
|
|
min-width: 16px; height: 16px; padding: 0 4px;
|
|
background: #e91e8c; color: #fff; border-radius: 50px;
|
|
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;
|
|
}
|
|
.scroll-id-link:hover { text-decoration: underline; }
|
|
.scroll-user-link { pointer-events: all; position: relative; z-index: 11; text-decoration: none; color: inherit; }
|
|
.scroll-badges { display: flex; gap: 6px; align-items: center; margin-bottom: 4px; pointer-events: all; position: relative; z-index: 11; }
|
|
.scroll-rating { display: inline-flex; align-items: center; padding: 2px 8px; border-radius: 4px; font-size: .67rem; font-weight: 700; text-transform: uppercase; letter-spacing: .04em; }
|
|
.scroll-rating.sfw { background: var(--badge-sfw); color: #fff; }
|
|
.scroll-rating.nsfw { background: var(--badge-nsfw); color: #fff; }
|
|
.scroll-rating.nsfl { background: var(--badge-nsfl); color: #fff; }
|
|
.scroll-rating.untagged { background: transparent; color: rgba(255,255,255,.5); border: 1px dashed rgba(255,255,255,.3); }
|
|
.scroll-rating.can-cycle { cursor: pointer; pointer-events: all; position: relative; z-index: 11; }
|
|
.scroll-rating.can-cycle:hover { filter: brightness(1.2); }
|
|
.scroll-oc { display: inline-flex; align-items: center; gap: 4px; padding: 2px 8px; border-radius: 4px; font-size: .67rem; font-weight: 700; background: var(--accent); color: #000; }
|
|
|
|
/* ── SIDE ACTIONS z:10 ────────────────────────── */
|
|
.scroll-actions { position: absolute; right: 10px; bottom: 40px; z-index: 10; display: flex; flex-direction: column; align-items: center; gap: 18px; }
|
|
.scroll-btn {
|
|
display: flex; flex-direction: column; align-items: center; gap: 3px;
|
|
cursor: pointer; text-decoration: none; color: #fff; background: none; border: none; padding: 0;
|
|
transition: transform .12s;
|
|
}
|
|
/*.scroll-btn:hover { transform: scale(1.14); }*/
|
|
.scroll-btn-icon {
|
|
position: relative;
|
|
width: 46px; height: 46px; border-radius: 50%;
|
|
display: flex; align-items: center; justify-content: center;
|
|
font-size: 1.05rem; transition: background .17s;
|
|
}
|
|
/*.scroll-btn:hover .scroll-btn-icon { background: rgba(255,255,255,.22); }*/
|
|
.scroll-btn-label { font-size: .6rem; font-weight: 600; letter-spacing: .03em; color: rgba(255,255,255,.75); }
|
|
.scroll-btn-count {
|
|
font-size: .62rem; font-weight: 800; color: rgb(255, 255, 255); line-height: 1;
|
|
min-width: 20px; text-align: center;
|
|
}
|
|
/*.scroll-btn.faved .scroll-btn-icon { background: rgba(255,64,129,.25); border-color: #ff4081; } */
|
|
.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;
|
|
background: rgba(255,255,255,.15); z-index: 10; cursor: pointer; transition: height .15s;
|
|
}
|
|
.scroll-progress-bar:hover { height: 12px; }
|
|
.scroll-progress-fill { height: 100%; background: var(--accent); width: 0%; pointer-events: none; }
|
|
.scroll-progress-thumb {
|
|
display: none; position: absolute; top: 50%; right: 0; width: 12px; height: 12px; border-radius: 50%;
|
|
background: var(--accent); transform: translate(50%, -50%) scale(0);
|
|
transition: transform .15s; pointer-events: none;
|
|
}
|
|
.scroll-progress-bar:hover .scroll-progress-thumb,
|
|
.scroll-progress-bar.dragging .scroll-progress-thumb { transform: translate(50%, -50%) scale(1); }
|
|
|
|
/* ── EMPTY / SENTINEL ────────────────────────── */
|
|
#scroller-sentinel { width: 100%; height: 10px; }
|
|
#scroller-empty {
|
|
width: 100%; height: 100vh; height: 100dvh; display: none;
|
|
flex-direction: column; align-items: center; justify-content: center;
|
|
gap: 14px; scroll-snap-align: start; color: rgba(255,255,255,.35);
|
|
}
|
|
#scroller-empty.show { display: flex; }
|
|
#scroller-empty i { font-size: 2.8rem; }
|
|
|
|
/* ── LOADER ─────────────────────────────────── */
|
|
#scroller-loader {
|
|
position: fixed; inset: 0; background: #000;
|
|
display: flex; flex-direction: column; align-items: center; justify-content: center;
|
|
z-index: 1000; gap: 16px; transition: opacity .3s;
|
|
}
|
|
#scroller-loader.hidden { opacity: 0; pointer-events: none; }
|
|
.loader-logo { font-size: 2.2rem; font-weight: 800; letter-spacing: -.04em; color: var(--accent, #fff); font-family: 'VCR', 'Courier New', monospace; }
|
|
.loader-sub { font-size: .82rem; color: rgba(255,255,255,.4); letter-spacing: .1em; font-weight: 500; }
|
|
.loader-spinner { width: 28px; height: 28px; border: 2.5px solid rgba(255,255,255,.1); border-top-color: var(--accent, #fff); border-radius: 50%; animation: spin .65s linear infinite; }
|
|
@keyframes spin { to { transform: rotate(360deg); } }
|
|
.speed-indicator {
|
|
position: fixed; top: 70px; left: 50%; transform: translateX(-50%);
|
|
background: rgba(0,0,0,.7); backdrop-filter: blur(8px); -webkit-backdrop-filter: blur(8px);
|
|
border: 1px solid rgba(255,255,255,.15); border-radius: 50px;
|
|
padding: 8px 16px; font-size: .8rem; font-weight: 800; color: var(--accent);
|
|
z-index: 1000; pointer-events: none; opacity: 0; transition: opacity .15s;
|
|
display: flex; align-items: center; gap: 8px;
|
|
}
|
|
.speed-indicator.show { opacity: 1; }
|
|
|
|
/* ── YT EMBED ────────────────────────────────── */
|
|
.yt-container { position: relative; width: 100%; height: 100%; display: flex; align-items: center; justify-content: center; }
|
|
.yt-container iframe { width: 100%; height: 56.25vw; max-height: 100%; max-width: 177.78vh; border: 0; }
|
|
|
|
/* ══════════════════════════════════════════════
|
|
SHARED PANEL SYSTEM
|
|
══════════════════════════════════════════════ */
|
|
.scroller-backdrop {
|
|
position: fixed; inset: 0; background: rgba(0,0,0,.5);
|
|
backdrop-filter: blur(4px); -webkit-backdrop-filter: blur(4px);
|
|
z-index: 800; opacity: 0; pointer-events: none; transition: opacity .28s;
|
|
}
|
|
.scroller-backdrop.open { opacity: 1; pointer-events: all; }
|
|
.scroller-panel {
|
|
position: fixed; bottom: 0; left: 0; right: 0; z-index: 801;
|
|
background: var(--panel-bg); border-top: 1px solid var(--panel-border);
|
|
border-radius: 22px 22px 0 0; transform: translateY(105%);
|
|
transition: transform .32s cubic-bezier(.32,.72,0,1);
|
|
max-height: 92dvh; max-height: 92vh; overflow: hidden; display: flex; flex-direction: column;
|
|
}
|
|
.scroller-panel.open { transform: translateY(0); }
|
|
.scroller-panel::-webkit-scrollbar { display: none; }
|
|
.panel-handle { display: flex; justify-content: center; padding: 14px 0 4px; flex-shrink: 0; }
|
|
.panel-handle-bar { width: 40px; height: 4px; background: rgba(255,255,255,.18); border-radius: 2px; }
|
|
.panel-header {
|
|
display: flex; align-items: center; justify-content: space-between;
|
|
padding: 4px 20px 14px; flex-shrink: 0; border-bottom: 1px solid rgba(255,255,255,.06);
|
|
}
|
|
.panel-title { font-size: 1rem; font-weight: 800; letter-spacing: -.01em; }
|
|
|
|
/* ── FILTER PANEL ─────────────────────────────── */
|
|
#filter-panel { z-index: 802; }
|
|
.filter-reset-btn { font-size: .74rem; color: var(--accent); font-weight: 700; background: none; border: none; cursor: pointer; padding: 4px 8px; border-radius: 6px; transition: background .15s; }
|
|
.filter-reset-btn:hover { background: rgba(255,255,255,.08); }
|
|
.filter-scroll-area { overflow-y: auto; flex: 1; }
|
|
.filter-scroll-area::-webkit-scrollbar { display: none; }
|
|
.filter-section { padding: 16px 20px; border-bottom: 1px solid rgba(255,255,255,.05); }
|
|
.filter-section:last-of-type { border-bottom: none; }
|
|
.filter-section-label { font-size: .68rem; font-weight: 700; letter-spacing: .09em; text-transform: uppercase; color: rgba(255,255,255,.42); margin-bottom: 12px; }
|
|
.pill-group { display: flex; flex-wrap: wrap; gap: 8px; }
|
|
.filter-pill { padding: 7px 16px; border-radius: 50px; border: 1px solid rgba(255,255,255,.13); background: rgba(255,255,255,.07); color: #fff; font-size: .81rem; font-weight: 600; cursor: pointer; white-space: nowrap; user-select: none; transition: background .14s, border-color .14s, color .14s; }
|
|
.filter-pill:hover { background: rgba(255,255,255,.13); }
|
|
.filter-pill.active { background: var(--accent); border-color: var(--accent); color: #000; }
|
|
.filter-pill i { margin-right: 5px; font-size: .72rem; }
|
|
.tag-search-wrap { position: relative; }
|
|
#filter-tag-input { width: 100%; padding: 10px 38px 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; transition: border-color .17s; }
|
|
#filter-tag-input:focus { border-color: var(--accent); }
|
|
#filter-tag-input::placeholder { color: rgba(255,255,255,.32); }
|
|
.tag-search-icon { position: absolute; right: 12px; top: 50%; transform: translateY(-50%); color: rgba(255,255,255,.34); font-size: .82rem; pointer-events: none; }
|
|
#filter-tag-clear { position: absolute; right: 12px; top: 50%; transform: translateY(-50%); color: rgba(255,255,255,.5); cursor: pointer; display: none; background: none; border: none; padding: 4px; }
|
|
#filter-tag-clear.show { display: block; }
|
|
#tag-suggestions { margin-top: 8px; display: flex; flex-wrap: wrap; gap: 6px; }
|
|
.tag-suggestion { padding: 5px 12px; border-radius: 50px; background: rgba(255,255,255,.07); border: 1px solid rgba(255,255,255,.1); font-size: .76rem; color: rgba(255,255,255,.82); cursor: pointer; transition: background .12s; }
|
|
.tag-suggestion:hover { background: rgba(255,255,255,.13); }
|
|
.tag-suggestion .uses { color: rgba(255,255,255,.36); font-size: .67rem; margin-left: 4px; }
|
|
.tag-suggestion.active { background: var(--accent); border-color: var(--accent); color: #000; }
|
|
.tag-suggestion.active .uses { color: rgba(0,0,0,.45); }
|
|
.tag-suggestion.keyboard-focus { background: rgba(255,255,255,.18); border-color: rgba(255,255,255,.35); outline: 2px solid var(--accent); outline-offset: 1px; }
|
|
.tag-suggestion.active.keyboard-focus { outline: 2px solid rgba(0,0,0,.5); outline-offset: 1px; }
|
|
#filter-active-tags { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 10px; }
|
|
.active-tag-pill { display: inline-flex; align-items: center; gap: 6px; padding: 5px 10px; border-radius: 50px; background: var(--accent); color: #000; font-size: .76rem; font-weight: 700; cursor: pointer; }
|
|
.active-tag-pill i { font-size: .62rem; }
|
|
.filter-apply-btn { display: block; margin: 12px 20px 20px; padding: 15px; background: var(--accent); color: #000; font-size: .9rem; font-weight: 800; border: none; border-radius: 14px; cursor: pointer; flex-shrink: 0; transition: opacity .14s, transform .12s; }
|
|
.filter-apply-btn:hover { opacity: .88; }
|
|
.filter-apply-btn:active { transform: scale(.98); }
|
|
|
|
/* ── SETTINGS PANEL ────────────────────────────── */
|
|
#settings-panel { z-index: 802; }
|
|
.settings-toggle-row { display: flex; align-items: center; gap: 14px; padding: 10px 0; }
|
|
.settings-toggle-info { flex: 1; min-width: 0; }
|
|
.settings-toggle-name { display: block; font-size: .88rem; font-weight: 700; color: #fff; }
|
|
.settings-toggle-desc { display: block; font-size: .74rem; color: rgba(255,255,255,.45); margin-top: 2px; line-height: 1.35; }
|
|
.settings-toggle-switch {
|
|
width: 44px; height: 24px; border-radius: 50px; flex-shrink: 0;
|
|
background: rgba(255,255,255,.14); border: 1px solid rgba(255,255,255,.18);
|
|
position: relative; cursor: pointer; transition: background .2s, border-color .2s;
|
|
}
|
|
.settings-toggle-switch.on { background: var(--accent); border-color: var(--accent); }
|
|
.settings-toggle-knob {
|
|
position: absolute; top: 2px; left: 2px;
|
|
width: 18px; height: 18px; border-radius: 50%;
|
|
background: rgba(255,255,255,.6); transition: transform .2s, background .2s;
|
|
}
|
|
.settings-toggle-switch.on .settings-toggle-knob { transform: translateX(20px); background: #000; }
|
|
/* Save preset button */
|
|
.settings-save-preset-btn {
|
|
width: 100%; padding: 11px 16px; margin-bottom: 10px;
|
|
background: rgba(255,255,255,.07); border: 1px solid rgba(255,255,255,.13);
|
|
border-radius: 12px; color: rgba(255,255,255,.85); font-size: .84rem; font-weight: 600;
|
|
cursor: pointer; text-align: left; transition: background .14s;
|
|
}
|
|
.settings-save-preset-btn:hover { background: rgba(255,255,255,.12); }
|
|
.settings-save-preset-btn i { margin-right: 6px; }
|
|
/* Preset rows */
|
|
.preset-row {
|
|
display: flex; align-items: center; gap: 8px; padding: 9px 12px; border-radius: 12px;
|
|
background: rgba(255,255,255,.05); border: 1px solid rgba(255,255,255,.08); margin-bottom: 7px;
|
|
cursor: pointer; transition: background .14s;
|
|
}
|
|
.preset-row:hover { background: rgba(255,255,255,.1); }
|
|
.preset-row-info { flex: 1; min-width: 0; }
|
|
.preset-row-name { font-size: .86rem; font-weight: 700; color: #fff; }
|
|
.preset-row-meta { font-size: .72rem; color: rgba(255,255,255,.42); margin-top: 2px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
.preset-row-del { color: rgba(255,255,255,.35); background: none; border: none; cursor: pointer; padding: 4px 6px; border-radius: 6px; font-size: .8rem; flex-shrink: 0; transition: color .14s, background .14s; }
|
|
.preset-row-del:hover { color: #e55; background: rgba(255,60,60,.12); }
|
|
.preset-row-edit { color: rgba(255,255,255,.35); background: none; border: none; cursor: pointer; padding: 4px 6px; border-radius: 6px; font-size: .8rem; flex-shrink: 0; transition: color .14s, background .14s; }
|
|
.preset-row-edit:hover { color: var(--accent); background: rgba(255,255,255,.08); }
|
|
.preset-row-update { color: rgba(255,255,255,.35); background: none; border: none; cursor: pointer; padding: 4px 6px; border-radius: 6px; font-size: .8rem; flex-shrink: 0; transition: color .14s, background .14s; }
|
|
.preset-row-update:hover { color: #4caf50; background: rgba(76,175,80,.12); }
|
|
/* Preset inline editor */
|
|
.preset-editor { display: none; padding: 8px 12px 10px; border-top: 1px solid rgba(255,255,255,.07); margin-top: 4px; }
|
|
.preset-editor.open { display: block; }
|
|
.preset-editor-tags { display: flex; flex-wrap: wrap; gap: 5px; margin-bottom: 8px; min-height: 22px; }
|
|
.preset-editor-tag {
|
|
display: inline-flex; align-items: center; gap: 4px; padding: 2px 8px 2px 9px; border-radius: 50px;
|
|
background: rgba(255,255,255,.12); border: 1px solid rgba(255,255,255,.18);
|
|
font-size: .7rem; font-weight: 600; color: rgba(255,255,255,.85);
|
|
}
|
|
.preset-editor-tag-del { background: none; border: none; cursor: pointer; color: inherit; opacity: .55; padding: 0; font-size: .68rem; line-height: 1; }
|
|
.preset-editor-tag-del:hover { opacity: 1; color: #e55; }
|
|
.preset-editor-add { display: flex; gap: 6px; margin-bottom: 8px; }
|
|
.preset-editor-add input {
|
|
flex: 1; background: rgba(255,255,255,.07); border: 1px solid rgba(255,255,255,.15);
|
|
border-radius: 8px; color: #fff; font-size: .82rem; padding: 5px 10px; outline: none;
|
|
}
|
|
.preset-editor-add input::placeholder { color: rgba(255,255,255,.3); }
|
|
.preset-editor-add input:focus { border-color: var(--accent); }
|
|
.preset-editor-add button {
|
|
padding: 5px 12px; border-radius: 8px; background: rgba(255,255,255,.1);
|
|
border: 1px solid rgba(255,255,255,.15); color: #fff; font-size: .8rem; cursor: pointer;
|
|
}
|
|
.preset-editor-add button:hover { background: var(--accent); border-color: var(--accent); color: #000; }
|
|
.preset-editor-actions { display: flex; flex-direction: column; gap: 6px; }
|
|
.preset-editor-save {
|
|
width: 100%; padding: 8px 14px; border-radius: 8px; background: var(--accent); border: none;
|
|
color: #000; font-size: .82rem; font-weight: 700; cursor: pointer; text-align: left;
|
|
}
|
|
.preset-editor-save:hover { filter: brightness(1.12); }
|
|
.preset-editor-overwrite {
|
|
width: 100%; padding: 8px 14px; border-radius: 8px; background: rgba(255,255,255,.08);
|
|
border: 1px solid rgba(255,255,255,.15); color: #fff; font-size: .82rem; cursor: pointer; text-align: left;
|
|
}
|
|
.preset-editor-overwrite:hover { background: rgba(255,255,255,.15); }
|
|
/* When a preset row is open, don't highlight it on hover */
|
|
.preset-row.editing { cursor: default; }
|
|
.preset-row.editing:hover { background: rgba(255,255,255,.05); }
|
|
.preset-row.editing { flex-wrap: wrap; }
|
|
/* ui-hidden: hides topbar content + action buttons, but keeps settings button accessible */
|
|
.ui-hidden .topbar-left { opacity: 0; pointer-events: none; transition: opacity .3s; }
|
|
.ui-hidden #filter-open-btn,
|
|
.ui-hidden #scroller-mute-btn {
|
|
opacity: 0; pointer-events: none;
|
|
width: 0; min-width: 0; padding: 0; margin: 0; border-width: 0; overflow: hidden;
|
|
transition: opacity .3s, width .3s, padding .3s, margin .3s, border-width .3s;
|
|
}
|
|
/* When not hidden, make sure the buttons transition back smoothly */
|
|
#filter-open-btn, #scroller-mute-btn {
|
|
transition: background .17s, transform .12s, border-color .17s,
|
|
opacity .3s, width .3s, padding .3s, margin .3s, border-width .3s;
|
|
}
|
|
.ui-hidden #settings-open-btn { opacity: .55; transition: opacity .3s; } /* always reachable */
|
|
.ui-hidden #settings-open-btn:hover { opacity: 1; }
|
|
.ui-hidden .scroll-actions { opacity: 0; pointer-events: none; transition: opacity .3s; }
|
|
.ui-hidden .scroll-meta { opacity: 0; pointer-events: none; transition: opacity .3s; }
|
|
|
|
/* ── COMMENTS PANEL ───────────────────────────── */
|
|
#comments-panel { z-index: 802; }
|
|
#comments-list { flex: 1; overflow-y: auto; padding: 0 16px 8px; }
|
|
#comments-list::-webkit-scrollbar { width: 10px; }
|
|
#comments-list::-webkit-scrollbar-thumb {
|
|
background: rgba(255,255,255,.12);
|
|
background-clip: content-box;
|
|
border: 3px solid transparent;
|
|
border-radius: 10px;
|
|
}
|
|
.comment-item { display: flex; gap: 10px; padding: 12px 0; border-bottom: 1px solid rgba(255,255,255,.05); }
|
|
.comment-item:last-child { border-bottom: none; }
|
|
.comment-avatar { width: 32px; height: 32px; border-radius: 50%; object-fit: cover; flex-shrink: 0; border: 1px solid rgba(255,255,255,.12); }
|
|
.comment-body { flex: 1; min-width: 0; }
|
|
.comment-username { font-size: .78rem; font-weight: 700; color: rgba(255,255,255,.9); margin-bottom: 3px; }
|
|
.comment-content { font-size: .82rem; color: rgba(255,255,255,.78); line-height: 1.45; word-break: break-word; }
|
|
.scroller-greentext { color: #789922; display: block; }
|
|
.scroller-spoiler {
|
|
background: #111; color: #111; border-radius: 3px; cursor: pointer;
|
|
padding: 0 3px; transition: color .15s, background .15s; user-select: none;
|
|
}
|
|
.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-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, .comment-quote-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, .comment-quote-btn:hover { color: var(--accent, #fff); }
|
|
.comment-context-link { color: var(--accent); text-decoration: none; font-family: 'VCR', monospace; }
|
|
.comment-context-link:hover { text-decoration: underline; }
|
|
@keyframes comment-highlight {
|
|
0% { background: rgba(255,255,255,.15); border-color: var(--accent); }
|
|
100% { background: rgba(255,255,255,.03); border-color: rgba(255,255,255,.05); }
|
|
}
|
|
.highlight-comment { animation: comment-highlight 2.5s cubic-bezier(0.2, 0, 0, 1); }
|
|
#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; }
|
|
|
|
/* comment input */
|
|
#comments-input-area {
|
|
display: flex; flex-direction: column;
|
|
padding: 10px 14px 16px;
|
|
border-top: 1px solid rgba(255,255,255,.07); flex-shrink: 0;
|
|
padding-bottom: calc(16px + env(safe-area-inset-bottom, 0px));
|
|
gap: 8px;
|
|
}
|
|
#comments-input-area.hidden { display: none; }
|
|
|
|
/* mention dropdown */
|
|
#mention-dropdown {
|
|
background: rgba(14,14,18,.98); border: 1px solid rgba(255,255,255,.1);
|
|
border-radius: 10px; max-height: 160px; overflow-y: auto;
|
|
display: none;
|
|
}
|
|
#mention-dropdown.show { display: block; }
|
|
.mention-item {
|
|
display: flex; align-items: center; gap: 8px;
|
|
padding: 8px 12px; cursor: pointer; font-size: .82rem;
|
|
transition: background .12s;
|
|
}
|
|
.mention-item:hover, .mention-item.selected { background: rgba(255,255,255,.08); }
|
|
.mention-avatar { width: 24px; height: 24px; border-radius: 50%; object-fit: cover; }
|
|
|
|
/* sitewide emoji autocomplete portal (matches comments.js .emoji-autocomplete) */
|
|
.emoji-autocomplete {
|
|
position: fixed; z-index: 9999;
|
|
background: rgba(10,10,14,.97); border: 1px solid rgba(255,255,255,.12);
|
|
border-radius: 10px; display: flex; flex-wrap: wrap; gap: 2px; padding: 6px;
|
|
max-height: 200px; overflow-y: auto; box-shadow: 0 8px 32px rgba(0,0,0,.6);
|
|
}
|
|
.emoji-ac-item {
|
|
display: flex; flex-direction: column; align-items: center; gap: 3px;
|
|
padding: 6px 8px; border-radius: 8px; cursor: pointer;
|
|
transition: background .12s; min-width: 54px;
|
|
}
|
|
.emoji-ac-item:hover, .emoji-ac-item.active { background: rgba(255,255,255,.1); }
|
|
.emoji-ac-item img { width: 32px; height: 32px; object-fit: contain; }
|
|
.emoji-ac-item span { font-size: .58rem; color: rgba(255,255,255,.5); text-align: center; }
|
|
/* emoji trigger (☺ button) */
|
|
.emoji-trigger {
|
|
background: none; border: none; font-size: 2.2rem; cursor: pointer;
|
|
padding: 4px 6px; border-radius: 8px; color: rgba(255,255,255,.6);
|
|
transition: background .12s, color .12s; line-height: 1; flex-shrink: 0;
|
|
}
|
|
.emoji-trigger:hover { background: rgba(255,255,255,.1); color: #fff; }
|
|
|
|
.comment-input-row { display: flex; align-items: flex-end; gap: 8px; }
|
|
#comment-input {
|
|
flex: 1; padding: 10px 14px; background: rgba(255,255,255,.08);
|
|
border: 1px solid rgba(255,255,255,.12); border-radius: 22px;
|
|
color: #fff; font-size: .84rem; outline: none; transition: border-color .17s;
|
|
resize: none; line-height: 1.4;
|
|
}
|
|
#comment-input:focus { border-color: var(--accent); }
|
|
#comment-input::placeholder { color: rgba(255,255,255,.32); }
|
|
#comment-send-btn {
|
|
width: 38px; height: 38px; border-radius: 50%; background: var(--accent); border: none; cursor: pointer;
|
|
display: flex; align-items: center; justify-content: center; color: #000; font-size: .88rem;
|
|
flex-shrink: 0; transition: opacity .15s, transform .12s;
|
|
}
|
|
#comment-send-btn:hover { opacity: .85; }
|
|
#comment-send-btn:active { transform: scale(.93); }
|
|
#comment-send-btn:disabled { opacity: .4; cursor: default; }
|
|
.comments-login-note { text-align: center; padding: 12px 16px 20px; font-size: .78rem; color: rgba(255,255,255,.38); }
|
|
.comments-login-note a { color: var(--accent); }
|
|
/* ── Tag bar ── slide-up panel, opened by Tag action button */
|
|
#tag-bar {
|
|
position: fixed; bottom: 0; left: 0; right: 0; z-index: 1200;
|
|
background: rgba(14,14,18,.97); border-top: 1px solid rgba(255,255,255,.1);
|
|
padding: 12px 14px calc(12px + env(safe-area-inset-bottom, 0px));
|
|
transform: translateY(100%); transition: transform .22s cubic-bezier(.32,0,.67,0);
|
|
backdrop-filter: blur(16px); -webkit-backdrop-filter: blur(16px);
|
|
}
|
|
#tag-bar.open { transform: translateY(0); }
|
|
#tag-bar-inner { display: flex; align-items: center; gap: 8px; }
|
|
#scroll-tag-input {
|
|
flex: 1; padding: 10px 14px; background: rgba(255,255,255,.08);
|
|
border: 1px solid rgba(255,255,255,.12); border-radius: 22px;
|
|
color: #fff; font-size: .84rem; outline: none; transition: border-color .17s;
|
|
}
|
|
#scroll-tag-input:focus { border-color: var(--accent); }
|
|
#scroll-tag-input::placeholder { color: rgba(255,255,255,.32); }
|
|
#scroll-tag-send-btn {
|
|
width: 38px; height: 38px; border-radius: 50%; background: var(--accent);
|
|
border: none; cursor: pointer; display: flex; align-items: center; justify-content: center;
|
|
color: #000; font-size: .88rem; flex-shrink: 0; transition: opacity .15s, transform .12s;
|
|
}
|
|
#scroll-tag-send-btn:hover { opacity: .85; }
|
|
#scroll-tag-send-btn:active { transform: scale(.93); }
|
|
#tag-bar-close-btn {
|
|
width: 34px; height: 34px; border-radius: 50%; background: rgba(255,255,255,.08);
|
|
border: 1px solid rgba(255,255,255,.15); cursor: pointer;
|
|
display: flex; align-items: center; justify-content: center;
|
|
color: rgba(255,255,255,.6); font-size: .8rem; flex-shrink: 0;
|
|
transition: background .12s;
|
|
}
|
|
#tag-bar-close-btn:hover { background: rgba(255,255,255,.15); color: #fff; }
|
|
/* tag autocomplete dropdown — opens upward */
|
|
#scroll-tag-suggestions {
|
|
position: absolute; bottom: calc(100% + 6px); left: 0; right: 0;
|
|
background: rgba(14,14,18,.98); border: 1px solid rgba(255,255,255,.1);
|
|
border-radius: 12px; max-height: 180px; overflow-y: auto;
|
|
display: none; z-index: 1300; box-shadow: 0 -4px 24px rgba(0,0,0,.5);
|
|
}
|
|
#scroll-tag-suggestions.show { display: block; }
|
|
/* ── Share panel ────────────────────────────────── */
|
|
#share-backdrop {
|
|
display: none; position: fixed; inset: 0; z-index: 1290;
|
|
background: rgba(0,0,0,.55); backdrop-filter: blur(3px); -webkit-backdrop-filter: blur(3px);
|
|
}
|
|
#share-backdrop.show { display: block; }
|
|
#share-panel {
|
|
position: fixed; bottom: 0; left: 0; right: 0; z-index: 1300;
|
|
background: rgba(14,14,18,.98); border-top: 1px solid rgba(255,255,255,.1);
|
|
padding: 18px 16px calc(18px + env(safe-area-inset-bottom, 0px));
|
|
transform: translateY(100%); transition: transform .22s cubic-bezier(.32,0,.67,0);
|
|
backdrop-filter: blur(20px); -webkit-backdrop-filter: blur(20px);
|
|
border-radius: 20px 20px 0 0;
|
|
}
|
|
#share-panel.open { transform: translateY(0); }
|
|
#share-panel-handle {
|
|
width: 36px; height: 4px; border-radius: 2px; background: rgba(255,255,255,.2);
|
|
margin: 0 auto 16px;
|
|
}
|
|
#share-panel-title {
|
|
font-size: .72rem; font-weight: 700; letter-spacing: .08em;
|
|
color: rgba(255,255,255,.4); text-transform: uppercase; margin-bottom: 14px;
|
|
}
|
|
.share-row {
|
|
display: flex; align-items: center; gap: 14px; padding: 13px 14px;
|
|
border-radius: 12px; cursor: pointer; transition: background .14s;
|
|
background: rgba(255,255,255,.05); border: 1px solid rgba(255,255,255,.07); margin-bottom: 8px;
|
|
}
|
|
.share-row:last-child { margin-bottom: 0; }
|
|
.share-row:hover { background: rgba(255,255,255,.1); }
|
|
.share-row-icon {
|
|
width: 40px; height: 40px; border-radius: 50%; flex-shrink: 0;
|
|
display: flex; align-items: center; justify-content: center;
|
|
font-size: 1rem;
|
|
}
|
|
.share-row-icon.copy-icon { background: rgba(99,179,237,.18); color: #63b3ed; }
|
|
.share-row-icon.dm-icon { background: rgba(154,117,255,.18); color: #9a75ff; }
|
|
.share-row-text { flex: 1; min-width: 0; }
|
|
.share-row-title { font-size: .88rem; font-weight: 600; color: #fff; }
|
|
.share-row-sub { font-size: .72rem; color: rgba(255,255,255,.45); margin-top: 2px; }
|
|
/* DM user search */
|
|
#share-dm-search {
|
|
margin-top: 14px; display: none;
|
|
}
|
|
#share-dm-search.show { display: block; }
|
|
#share-user-input {
|
|
width: 100%; padding: 10px 14px; background: rgba(255,255,255,.08);
|
|
border: 1px solid rgba(255,255,255,.12); border-radius: 22px;
|
|
color: #fff; font-size: .84rem; outline: none; transition: border-color .17s; box-sizing: border-box;
|
|
}
|
|
#share-user-input:focus { border-color: #9a75ff; }
|
|
#share-user-input::placeholder { color: rgba(255,255,255,.32); }
|
|
#share-user-results {
|
|
margin-top: 8px; max-height: 200px; overflow-y: auto;
|
|
border-radius: 12px; border: 1px solid rgba(255,255,255,.08);
|
|
background: rgba(14,14,18,.98); display: none;
|
|
}
|
|
#share-user-results.show { display: block; }
|
|
.share-user-row {
|
|
display: flex; align-items: center; gap: 10px; padding: 10px 12px;
|
|
cursor: pointer; transition: background .12s;
|
|
}
|
|
.share-user-row:hover { background: rgba(255,255,255,.08); }
|
|
.share-user-row img {
|
|
width: 32px; height: 32px; border-radius: 50%; object-fit: cover; flex-shrink: 0;
|
|
}
|
|
.share-user-row-name { font-size: .84rem; font-weight: 600; color: #fff; flex: 1; min-width: 0; }
|
|
.share-user-row-label { font-size: .7rem; color: rgba(255,255,255,.4); margin-left: auto; flex-shrink: 0; }
|
|
.share-recents-label {
|
|
padding: 6px 12px 4px; font-size: .7rem; font-weight: 700; letter-spacing: .06em;
|
|
color: rgba(255,255,255,.35); text-transform: uppercase;
|
|
}
|
|
.scroll-tag-sugg-item {
|
|
padding: 9px 14px; cursor: pointer; font-size: .84rem; color: rgba(255,255,255,.85);
|
|
transition: background .1s; display: flex; align-items: center; gap: 8px;
|
|
}
|
|
.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%;
|
|
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">
|
|
<div id="main">
|
|
<script>
|
|
window.f0ckThemes = {{ themes_json }};
|
|
window.f0ckDomain = "{{ domain }}";
|
|
window.scrollerMode = @if(typeof session !== 'undefined' && session){{ session.mode || 0 }}@else 0@endif;
|
|
window.scrollerLoggedIn = @if(typeof session !== 'undefined' && session)true@else false@endif;
|
|
window.scrollerIsMod = @if(typeof session !== 'undefined' && session && (session.admin || session.is_moderator))true@else false@endif;
|
|
window.scrollerCsrf = "@if(typeof session !== 'undefined' && session){{ session.csrf_token || '' }}@else@endif";
|
|
window.scrollerEnableNsfl = {{ enable_nsfl ? 'true' : 'false' }};
|
|
window.scrollerEnableSwf = {{ enable_swf ? 'true' : 'false' }};
|
|
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) }};
|
|
window.f0ckI18n = {
|
|
lang: "{{ lang }}",
|
|
// 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') }}",
|
|
// scroller labels
|
|
just_now: "{{ t('scroller.just_now') }}",
|
|
failed_load_comments: "{{ t('scroller.failed_load_comments') }}",
|
|
no_comments: "{{ t('scroller.no_comments') }}",
|
|
write_comment: "{{ t('scroller.write_comment') }}",
|
|
login_required: "{{ t('scroller.login_required') }}",
|
|
login_to_comment: "{{ t('scroller.login_to_comment') }}"
|
|
};
|
|
@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') }}";
|
|
Object.assign(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
|
|
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') }}",
|
|
no_custom_emojis: "{{ t('scroller.no_custom_emojis') }}",
|
|
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') }}"
|
|
});
|
|
@endif
|
|
</script>
|
|
|
|
<!-- Loader -->
|
|
<div id="scroller-loader">
|
|
<div class="loader-logo">{{ domain }}</div>
|
|
<div class="loader-sub">{{ t('scroller.doomscroll') }}</div>
|
|
<div class="loader-spinner"></div>
|
|
</div>
|
|
<div id="speed-indicator" class="speed-indicator"><i class="fa-solid fa-forward"></i> 2x speed</div>
|
|
|
|
<!-- Top bar -->
|
|
<div id="scroller-topbar">
|
|
<div class="topbar-left">
|
|
<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="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)
|
|
<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="{{ t('scroller.volume') }} (M)"><i class="fa-solid fa-volume-xmark"></i></button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Volume popup -->
|
|
<div id="volume-popup">
|
|
<span class="volume-label" id="volume-pct">0%</span>
|
|
<input type="range" id="volume-slider" min="0" max="1" step="0.02" value="1" orient="vertical">
|
|
<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>{{ 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>
|
|
|
|
<!-- FILTER PANEL -->
|
|
<div id="filter-backdrop" class="scroller-backdrop"></div>
|
|
<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">{{ 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">{{ 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)
|
|
<button class="filter-pill" data-mode="1"><i class="fa-solid fa-fire"></i>NSFW</button>
|
|
@endif
|
|
@if(session && enable_nsfl)
|
|
<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">{{ t('scroller.all') }}</button>
|
|
@endif
|
|
@if(session && (session.admin || session.is_moderator))
|
|
<button class="filter-pill" data-mode="2">{{ t('scroller.untagged') }}</button>
|
|
@endif
|
|
</div>
|
|
</div>
|
|
<div class="filter-section">
|
|
<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>{{ t('scroller.all') }}</button>
|
|
@if(scroller_mime_cats.includes('video'))
|
|
<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>{{ 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>{{ t('scroller.audio') }}</button>
|
|
@endif
|
|
</div>
|
|
</div>
|
|
<div class="filter-section">
|
|
<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>{{ 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">{{ t('scroller.tags') }}</div>
|
|
<div class="tag-search-wrap">
|
|
<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>
|
|
<div id="tag-suggestions"></div>
|
|
<div id="filter-active-tags"></div>
|
|
</div>
|
|
<div class="filter-section" style="margin-top:8px">
|
|
<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>{{ 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 -->
|
|
<div id="settings-backdrop" class="scroller-backdrop"></div>
|
|
<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">{{ t('scroller.settings') }}</span>
|
|
</div>
|
|
<div class="filter-scroll-area">
|
|
<div class="filter-section">
|
|
<div class="filter-section-label">{{ t('scroller.appearance') }}</div>
|
|
<div class="settings-toggle-row">
|
|
<div class="settings-toggle-info">
|
|
<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">{{ 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">{{ 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 class="settings-toggle-row">
|
|
<div class="settings-toggle-info">
|
|
<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">{{ 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;" />
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
</div>
|
|
|
|
<!-- COMMENTS PANEL -->
|
|
<div id="comments-backdrop" class="scroller-backdrop"></div>
|
|
<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">{{ 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>{{ t('scroller.open') }}
|
|
</a>
|
|
</div>
|
|
<div id="comments-list">
|
|
<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="{{ 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">{{ t('scroller.login_to_comment') }}</a></div>
|
|
@endif
|
|
</div>
|
|
|
|
<!-- TAG BAR — slide-up input, opened by the Tag action button -->
|
|
@if(typeof session !== 'undefined' && session)
|
|
<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="{{ 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="{{ 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
|
|
|
|
<!-- SHARE PANEL — slide-up bottom sheet, opened by Share action button -->
|
|
<div id="share-backdrop"></div>
|
|
<div id="share-panel">
|
|
<div id="share-panel-handle"></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">{{ 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">{{ 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="{{ t('scroller.search_user') }}" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false">
|
|
<div id="share-user-results"></div>
|
|
</div>
|
|
@endif
|
|
</div>
|
|
|
|
@if(enable_swf)
|
|
<script src="/s/ruffle/ruffle.js"></script>
|
|
@endif
|
|
<script src="/s/js/theme.js?v={{ ts }}"></script>
|
|
<script src="/s/js/scroller.js?v={{ ts }}"></script>
|
|
</div><!-- /#main -->
|
|
</body>
|
|
</html>
|