// ==UserScript==
// @name Fullchan X
// @namespace Violentmonkey Scripts
// @match https://8chan.moe/*/res/*
// @match https://8chan.se/*/res/*
// @match https://8chan.moe/*/catalog*
// @match https://8chan.se/*/catalog*
// @run-at document-end
// @grant none
// @version 1.10.0
// @author vfyxe
// @description 8chan features script
// ==/UserScript==
class fullChanX extends HTMLElement {
constructor() {
super();
this.settingsEl = document.querySelector('fullchan-x-settings');
this.settings = this.settingsEl.settings;
this.isThread = !!document.querySelector('.opCell');
this.isDisclaimer = window.location.href.includes('disclaimer');
Object.keys(this.settings).forEach(key => {
this[key] = this.settings[key]?.value;
});
}
init() {
this.settingsButton = this.querySelector('#fcx-settings-btn');
this.settingsButton.addEventListener('click', () => this.settingsEl.toggle());
this.handleBoardLinks();
if (!this.isThread) return;
this.quickReply = document.querySelector('#quick-reply');
this.qrbody = document.querySelector('#qrbody');
this.threadParent = document.querySelector('#divThreads');
this.threadId = this.threadParent.querySelector('.opCell').id;
this.thread = this.threadParent.querySelector('.divPosts');
this.posts = [...this.thread.querySelectorAll('.postCell')];
this.postOrder = 'default';
this.postOrderSelect = this.querySelector('#thread-sort');
this.myYousLabel = this.querySelector('.my-yous__label');
this.yousContainer = this.querySelector('#my-yous');
this.gallery = document.querySelector('fullchan-x-gallery');
this.galleryButton = this.querySelector('#fcx-gallery-btn');
this.updateYous();
this.observers();
if (this.enableFileExtentions) this.handleTruncatedFilenames();
}
styleUI () {
this.style.setProperty('--top', this.uiTopPosition);
this.style.setProperty('--right', this.uiRightPosition);
this.classList.toggle('fcx-in-nav', this.moveToNave)
this.classList.toggle('fcx--dim', this.uiDimWhenInactive && !this.moveToNave);
this.classList.toggle('page-thread', this.isThread);
const style = document.createElement('style');
if (this.hideDefaultBoards !== '') {
style.textContent += '#navTopBoardsSpan{display:block!important;}'
}
document.body.appendChild(style);
}
handleBoardLinks () {
const navBoards = document.querySelector('#navTopBoardsSpan');
const customBoardLinks = this.customBoardLinks?.toLowerCase().replace(/\s/g,'').split(',');
let hideDefaultBoards = this.hideDefaultBoards?.toLowerCase().replace(/\s/g,'') || '';
const urlCatalog = this.catalogBoardLinks ? '/catalog.html' : '';
if (hideDefaultBoards === 'all') {
navBoards.classList.add('hidden');
} else {
const waitForNavBoards = setInterval(() => {
const navBoards = document.querySelector('#navTopBoardsSpan');
if (!navBoards || !navBoards.querySelector('a')) return;
clearInterval(waitForNavBoards);
hideDefaultBoards = hideDefaultBoards.split(',');
const defaultLinks = [...navBoards.querySelectorAll('a')];
defaultLinks.forEach(link => {
link.href += urlCatalog;
const linkText = link.textContent;
const shouldHide = hideDefaultBoards.includes(linkText) || customBoardLinks.includes(linkText);
link.classList.toggle('hidden', shouldHide);
});
}, 50);
}
if (this.customBoardLinks.length > 0) {
const customNav = document.createElement('span');
customNav.classList = 'nav-boards nav-boards--custom';
customNav.innerHTML = '<span>[</span>';
customBoardLinks.forEach((board, index) => {
const link = document.createElement('a');
link.href = '/' + board + urlCatalog;
link.textContent = board;
customNav.appendChild(link);
if (index < customBoardLinks.length - 1) customNav.innerHTML += '<span>/</span>';
});
customNav.innerHTML += '<span>]</span>';
navBoards.parentNode.insertBefore(customNav, navBoards);
}
}
observers () {
this.postOrderSelect.addEventListener('change', (event) => {
this.postOrder = event.target.value;
this.assignPostOrder();
});
const observerCallback = (mutationsList, observer) => {
for (const mutation of mutationsList) {
if (mutation.type === 'childList') {
this.posts = [...this.thread.querySelectorAll('.postCell')];
if (this.postOrder !== 'default') this.assignPostOrder();
this.updateYous();
this.gallery.updateGalleryImages();
if (this.settings.enableFileExtentions) this.handleTruncatedFilenames();
}
}
};
const threadObserver = new MutationObserver(observerCallback);
threadObserver.observe(this.thread, { childList: true, subtree: false });
if (this.enableNestedQuotes) {
this.thread.addEventListener('click', event => {
this.handleClick(event);
});
}
this.galleryButton.addEventListener('click', () => this.gallery.open());
this.myYousLabel.addEventListener('click', (event) => {
if (this.myYousLabel.classList.contains('unseen')) {
this.yousContainer.querySelector('.unseen').click();
}
});
}
handleClick (event) {
const clicked = event.target;
const post = clicked.closest('.innerPost') || clicked.closest('.innerOP');
if (!post) return;
const isNested = !!post.closest('.innerNested');
const nestQuote = clicked.closest('.quoteLink') || clicked.closest('.panelBacklinks a');
const postMedia = clicked.closest('a[data-filemime]');
const postId = clicked.closest('.linkQuote');
const anonId = clicked.closest('.labelId');
if (nestQuote) {
if (event.target.closest('.fcx-prevent-nesting')) return;
event.preventDefault();
this.nestQuote(nestQuote, post);
} else if (postMedia && isNested) {
this.handleMediaClick(event, postMedia);
} else if (postId && isNested) {
this.handleIdClick(postId);
} else if (anonId) {
this.handleAnonIdClick(anonId, event);
}
}
handleAnonIdClick (anonId, event) {
this.anonIdPosts?.remove();
if (anonId === this.anonId) {
this.anonId = null;
return;
}
this.anonId = anonId;
const anonIdText = anonId.textContent.split(' ')[0];
this.anonIdPosts = document.createElement('div');
this.anonIdPosts.classList = 'fcx-id-posts fcx-prevent-nesting';
const match = window.location.pathname.match(/^\/[^/]+\/res\/\d+\.html/);
const prepend = match ? `${match[0]}#` : '';
const selector = `.postInfo:has(.labelId[style="background-color: #${anonIdText}"]) .linkQuote`;
const postIds = [...this.threadParent.querySelectorAll(selector)].map(link => {
const postId = link.getAttribute('href').split('#q').pop();
const newLink = document.createElement('a');
newLink.className = 'quoteLink';
newLink.href = prepend + postId;
newLink.textContent = `>>${postId}`;
console.log('newLink',newLink)
return newLink;
});
postIds.forEach(postId => this.anonIdPosts.appendChild(postId));
anonId.insertAdjacentElement('afterend', this.anonIdPosts);
this.setPostListeners(this.anonIdPosts);
}
handleMediaClick (event, postMedia) {
if (postMedia.dataset.filemime === "video/webm") return;
event.preventDefault();
const imageSrc = `${postMedia.href}`;
const imageEl = postMedia.querySelector('img');
if (!postMedia.dataset.thumbSrc) postMedia.dataset.thumbSrc = `${imageEl.src}`;
const isExpanding = imageEl.src !== imageSrc;
if (isExpanding) {
imageEl.src = imageSrc;
imageEl.classList
}
imageEl.src = isExpanding ? imageSrc : postMedia.dataset.thumbSrc;
imageEl.classList.toggle('imgExpanded', isExpanding);
}
handleIdClick (postId) {
const idNumber = '>>' + postId.textContent;
this.quickReply.style.display = 'block';
this.qrbody.value += idNumber + '\n';
}
handleTruncatedFilenames () {
this.postFileNames = [...this.threadParent.querySelectorAll('.originalNameLink[download]:not([data-file-ext])')];
this.postFileNames.forEach(fileName => {
if (!fileName.textContent.includes('.')) return;
const strings = fileName.textContent.split('.');
const typeStr = `.${strings.pop()}`;
const typeEl = document.createElement('a');
typeEl.classList = ('file-ext originalNameLink');
typeEl.textContent = typeStr;
fileName.dataset.fileExt = typeStr;
fileName.textContent = strings.join('.');
fileName.parentNode.insertBefore(typeEl, fileName.nextSibling);
});
}
assignPostOrder () {
const postOrderReplies = (post) => {
const replyCount = post.querySelectorAll('.panelBacklinks a').length;
post.style.order = 100 - replyCount;
}
const postOrderCatbox = (post) => {
const postContent = post.querySelector('.divMessage').textContent;
const matches = postContent.match(/catbox\.moe/g);
const catboxCount = matches ? matches.length : 0;
post.style.order = 100 - catboxCount;
}
if (this.postOrder === 'default') {
this.thread.style.display = 'block';
return;
}
this.thread.style.display = 'flex';
if (this.postOrder === 'replies') {
this.posts.forEach(post => postOrderReplies(post));
} else if (this.postOrder === 'catbox') {
this.posts.forEach(post => postOrderCatbox(post));
}
}
updateYous () {
this.yous = this.posts.filter(post => post.querySelector('.quoteLink.you'));
this.yousLinks = this.yous.map(you => {
const youLink = document.createElement('a');
youLink.textContent = '>>' + you.id;
youLink.href = '#' + you.id;
return youLink;
})
let hasUnseenYous = false;
this.setUnseenYous();
this.yousContainer.innerHTML = '';
this.yousLinks.forEach(you => {
const youId = you.textContent.replace('>>', '');
if (!this.seenYous.includes(youId)) {
you.classList.add('unseen');
hasUnseenYous = true
}
this.yousContainer.appendChild(you)
});
this.myYousLabel.classList.toggle('unseen', hasUnseenYous);
if (this.replyTabIcon === '') return;
const icon = this.replyTabIcon;
document.title = hasUnseenYous
? document.title.startsWith(`${icon} `)
? document.title
: `${icon} ${document.title}`
: document.title.replace(new RegExp(`^${icon} `), '');
}
observeUnseenYou(you) {
you.classList.add('observe-you');
const observer = new IntersectionObserver((entries, observer) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const id = you.id;
you.classList.remove('observe-you');
if (!this.seenYous.includes(id)) {
this.seenYous.push(id);
localStorage.setItem(this.seenKey, JSON.stringify(this.seenYous));
}
observer.unobserve(you);
this.updateYous();
}
});
}, { rootMargin: '0px', threshold: 0.1 });
observer.observe(you);
}
setUnseenYous() {
this.seenKey = `${this.threadId}-seen-yous`;
this.seenYous = JSON.parse(localStorage.getItem(this.seenKey));
if (!this.seenYous) {
this.seenYous = [];
localStorage.setItem(this.seenKey, JSON.stringify(this.seenYous));
}
this.unseenYous = this.yous.filter(you => !this.seenYous.includes(you.id));
this.unseenYous.forEach(you => {
if (!you.classList.contains('observe-you')) {
this.observeUnseenYou(you);
}
});
}
nestQuote(quoteLink, parentPost) {
const parentPostMessage = parentPost.querySelector('.divMessage');
const quoteId = quoteLink.href.split('#').pop();
const quotePost = document.getElementById(quoteId);
if (!quotePost) return;
const quotePostContent = quotePost.querySelector('.innerOP') || quotePost.querySelector('.innerPost');
if (!quotePostContent) return;
const existing = parentPost.querySelector(`.nestedPost[data-quote-id="${quoteId}"]`);
if (existing) {
existing.remove();
return;
}
const isReply = !quoteLink.classList.contains('quoteLink');
const wrapper = document.createElement('div');
wrapper.classList.add('nestedPost');
wrapper.setAttribute('data-quote-id', quoteId);
const clone = quotePostContent.cloneNode(true);
clone.style.whiteSpace = 'unset';
clone.classList.add('innerNested');
wrapper.appendChild(clone);
if (isReply) {
parentPostMessage.insertBefore(wrapper, parentPostMessage.firstChild);
} else {
quoteLink.insertAdjacentElement('afterend', wrapper);
}
this.setPostListeners(wrapper);
}
setPostListeners(parentPost) {
const postLinks = [
...parentPost.querySelectorAll('.quoteLink'),
...parentPost.querySelectorAll('.panelBacklinks a')
];
const hoverPost = (event, link) => {
const quoteId = link.href.split('#')[1];
let existingPost = document.querySelector(`.nestedPost[data-quote-id="${quoteId}"]`)
|| link.closest(`.postCell[id="${quoteId}"]`);
if (existingPost) {
this.markedPost = existingPost.querySelector('.innerPost') || existingPost.querySelector('.innerOP');
this.markedPost?.classList.add('markedPost');
return;
}
const quotePost = document.getElementById(quoteId);
tooltips.removeIfExists();
const tooltip = document.createElement('div');
tooltip.className = 'quoteTooltip';
document.body.appendChild(tooltip);
const rect = link.getBoundingClientRect();
if (!api.mobile) {
if (rect.left > window.innerWidth / 2) {
const right = window.innerWidth - rect.left - window.scrollX;
tooltip.style.right = `${right}px`;
} else {
const left = rect.right + 10 + window.scrollX;
tooltip.style.left = `${left}px`;
}
}
tooltip.style.top = `${rect.top + window.scrollY}px`;
tooltip.style.display = 'inline';
tooltips.loadTooltip(tooltip, link.href, quoteId);
tooltips.currentTooltip = tooltip;
}
const unHoverPost = (event, link) => {
if (!tooltips.currentTooltip) {
this.markedPost?.classList.remove('markedPost');
return false;
}
if (tooltips.unmarkReply) {
tooltips.currentTooltip.classList.remove('markedPost');
Array.from(tooltips.currentTooltip.getElementsByClassName('replyUnderline'))
.forEach((a) => a.classList.remove('replyUnderline'))
tooltips.unmarkReply = false;
} else {
tooltips.currentTooltip.remove();
}
tooltips.currentTooltip = null;
}
const addHoverPost = (link => {
link.addEventListener('mouseenter', (event) => hoverPost(event, link));
link.addEventListener('mouseleave', (event) => unHoverPost(event, link));
});
postLinks.forEach(link => addHoverPost(link));
}
};
window.customElements.define('fullchan-x', fullChanX);
class fullChanXGallery extends HTMLElement {
constructor() {
super();
}
init() {
this.fullchanX = document.querySelector('fullchan-x');
this.imageContainer = this.querySelector('.gallery__images');
this.mainImageContainer = this.querySelector('.gallery__main-image');
this.mainImage = this.mainImageContainer.querySelector('img');
this.sizeButtons = [...this.querySelectorAll('.gallery__scale-options .scale-option')];
this.closeButton = this.querySelector('.gallery__close');
this.listeners();
this.addGalleryImages();
this.initalized = true;
}
addGalleryImages () {
this.thumbs = [...this.fullchanX.threadParent.querySelectorAll('.imgLink')].map(thumb => {
return thumb.cloneNode(true);
});
this.thumbs.forEach(thumb => {
this.imageContainer.appendChild(thumb);
});
}
updateGalleryImages () {
if (!this.initalized) return;
const newThumbs = [...this.fullchanX.threadParent.querySelectorAll('.imgLink')].filter(thumb => {
return !this.thumbs.find(thisThumb.href === thumb.href);
}).map(thumb => {
return thumb.cloneNode(true);
});
newThumbs.forEach(thumb => {
this.thumbs.push(thumb);
this.imageContainer.appendChild(thumb);
});
}
listeners () {
this.addEventListener('click', event => {
const clicked = event.target;
let imgLink = clicked.closest('.imgLink');
if (imgLink?.dataset.filemime === 'video/webm') return;
if (imgLink) {
event.preventDefault();
this.mainImage.src = imgLink.href;
}
this.mainImageContainer.classList.toggle('active', !!imgLink);
const scaleButton = clicked.closest('.scale-option');
if (scaleButton) {
const scale = parseFloat(getComputedStyle(this.imageContainer).getPropertyValue('--scale')) || 1;
const delta = scaleButton.id === 'fcxg-smaller' ? -0.1 : 0.1;
const newScale = Math.max(0.1, scale + delta);
this.imageContainer.style.setProperty('--scale', newScale.toFixed(2));
}
if (clicked.closest('.gallery__close')) this.close();
});
}
open () {
if (!this.initalized) this.init();
this.classList.add('open');
document.body.classList.add('fct-gallery-open');
}
close () {
this.classList.remove('open');
document.body.classList.remove('fct-gallery-open');
}
}
window.customElements.define('fullchan-x-gallery', fullChanXGallery);
class fullChanXSettings extends HTMLElement {
constructor() {
super();
this.settingsKey = 'fullchan-x-settings';
this.inputs = [];
this.settings = {};
this.settingsTemplate = {
moveToNave: {
info: 'Move Fullchan-X controls into the navbar.',
type: 'checkbox',
value: false
},
enableNestedQuotes: {
info: 'Nest posts when clicking backlinks.',
type: 'checkbox',
value: true
},
enableFileExtentions: {
info: 'Always show filetype on shortened file names.',
type: 'checkbox',
value: true
},
customBoardLinks: {
info: 'List of custom boards in nav (seperate by comma)',
type: 'input',
value: 'v,a,b'
},
hideDefaultBoards: {
info: 'List of boards to remove from nav (seperate by comma). Set as "all" to remove all.',
type: 'input',
value: 'interracial,mlp'
},
catalogBoardLinks: {
info: 'Redirect nav board links to catalog pages.',
type: 'checkbox',
value: true
},
uiTopPosition: {
info: 'Position from top of screen e.g. 100px',
type: 'input',
value: '50px'
},
uiRightPosition: {
info: 'Position from right of screen e.g. 100px',
type: 'input',
value: '25px'
},
uiDimWhenInactive: {
info: 'Dim UI when not hovering with mouse.',
type: 'checkbox',
value: true
},
replyTabIcon: {
info: 'Set the icon/text added to tab title when you get a new (You).',
type: 'input',
value: '❗'
},
};
}
init() {
this.settingsContainer = this.querySelector('.fcx-settings__settings');
this.getSavedSettings();
this.buildSettingsOptions();
this.listeners();
this.querySelector('.fcx-settings__close').addEventListener('click', () => this.close());
}
setSavedSettings (updated) {
localStorage.setItem(this.settingsKey, JSON.stringify(this.settings));
if (updated) this.classList.add('fcxs-updated');
}
getSavedSettings() {
const saved = JSON.parse(localStorage.getItem(this.settingsKey));
if (saved) this.settings = saved;
}
listeners() {
this.inputs.forEach(input => {
input.addEventListener('change', () => {
const key = input.name;
const value = input.type === 'checkbox' ? input.checked : input.value;
this.settings[key].value = value;
this.setSavedSettings(true);
});
});
}
buildSettingsOptions() {
Object.entries(this.settingsTemplate).forEach(([key, config]) => {
const wrapper = document.createElement('div');
const infoWrapper = document.createElement('div');
wrapper.classList.add('fcx-setting');
infoWrapper.classList.add('fcx-setting__info');
wrapper.appendChild(infoWrapper);
const label = document.createElement('label');
label.textContent = key
.replace(/([A-Z])/g, ' $1')
.replace(/^./, str => str.toUpperCase());
label.setAttribute('for', key);
infoWrapper.appendChild(label);
if (config.info) {
const info = document.createElement('p');
info.textContent = config.info;
infoWrapper.appendChild(info);
}
const savedValue = this.settings[key]?.value ?? config.value;
let input;
if (config.type === 'checkbox') {
input = document.createElement('input');
input.type = 'checkbox';
input.checked = savedValue;
} else if (config.type === 'input') {
input = document.createElement('input');
input.type = 'text';
input.value = savedValue;
} else if (config.type === 'select') {
input = document.createElement('select');
const options = config.options.split(',');
options.forEach(opt => {
const option = document.createElement('option');
option.value = opt;
option.textContent = opt;
if (opt === savedValue) option.selected = true;
input.appendChild(option);
});
}
if (input) {
input.id = key;
input.name = key;
wrapper.appendChild(input);
this.inputs.push(input);
this.settings[key] = { value: input.type === 'checkbox' ? input.checked : input.value };
}
this.settingsContainer.appendChild(wrapper);
});
this.setSavedSettings();
}
open() {
this.classList.add('open');
}
close() {
this.classList.remove('open');
}
toggle() {
this.classList.toggle('open');
}
}
window.customElements.define('fullchan-x-settings', fullChanXSettings);
// Create fullchan-x settings
const fcxs = document.createElement('fullchan-x-settings');
fcxs.innerHTML = `
<div class="fcxs fcx-settings">
<header>
<span class="fcx-settings__title">
Fullchan-X Settings
</span>
<button class="fcx-settings__close fullchan-x__option">Close</button>
</header>
<main>
<div class="fcxs__updated-message">
<p>Settings updated, refresh page to apply</p>
<button class="fullchan-x__option" onClick="location.reload()">Reload page</button>
</div>
<div class="fcx-settings__settings"></div>
</main>
<footer>
</footer>
</div>
`;
document.body.appendChild(fcxs);
fcxs.init();
// Create fullchan-x gallery
const fcxg = document.createElement('fullchan-x-gallery');
fcxg.innerHTML = `
<div class="fcxg gallery">
<button id="fcxg-close" class="gallery__close fullchan-x__option">Close</button>
<div class="gallery__scale-options">
<button id="fcxg-smaller" class="scale-option fullchan-x__option">-</button>
<button id="fcxg-larger" class="scale-option fullchan-x__option">+</button>
</div>
<div id="fcxg-images" class="gallery__images" style="--scale:1.0"></div>
<div id="fcxg-main-image" class="gallery__main-image">
<img src="" />
</div>
</div>
`;
document.body.appendChild(fcxg);
// Create fullchan-x element
const fcx = document.createElement('fullchan-x');
fcx.innerHTML = `
<div class="fcx__controls">
<button id="fcx-settings-btn" class="fullchan-x__option fcx-settings-toggle">
<a>⚙️</a><span>Settings</span>
</button>
<div class="fullchan-x__option fullchan-x__sort thread-only">
<a>☰</a>
<select id="thread-sort">
<option value="default">Default</option>
<option value="replies">Replies</option>
<option value="catbox">Catbox</option>
</select>
</div>
<button id="fcx-gallery-btn" class="gallery__toggle fullchan-x__option thread-only">
<a>🖼️</a><span>Gallery</span>
</button>
<div class="fcx__my-yous thread-only">
<p class="my-yous__label fullchan-x__option"><a>💬</a><span>My (You)s</span></p>
<div class="my-yous__yous fcx-prevent-nesting" id="my-yous"></div>
</div>
</div>
`;
(document.querySelector('#dynamicHeaderThread') || document.body).appendChild(fcx);
fcx.styleUI()
onload = (event) => fcx.init();
// Styles
const style = document.createElement('style');
style.innerHTML = `
fullchan-x {
--top: 50px;
--right: 25px;
background: var(--background-color);
border: 1px solid var(--navbar-text-color);
color: var(--link-color);
font-size: 14px;
z-index: 3;
}
/* Fullchan-X in nav styles */
.fcx-in-nav {
padding: 0;
border-width: 0;
line-height: 20px;
margin-right: 2px;
}
.fcx-in-nav .fcx__controls:before,
.fcx-in-nav .fcx__controls:after {
color: var(--navbar-text-color);
font-size: 85%;
}
.fcx-in-nav .fcx__controls:before {
content: "]";
}
.fcx-in-nav .fcx__controls:after {
content: "[";
}
.fcx-in-nav .fcx__controls,
.fcx-in-nav:hover .fcx__controls:hover {
flex-direction: row-reverse;
}
.fcx-in-nav .fcx__controls .fullchan-x__option {
padding: 0!important;
justify-content: center;
background: none;
line-height: 0;
max-width: 20px;
min-width: 20px;
translate: 0 1px;
border: solid var(--navbar-text-color) 1px !important;
}
.fcx-in-nav .fcx__controls .fullchan-x__option:hover {
border: solid var(--subject-color) 1px !important;
}
.fcx-in-nav .fullchan-x__sort > a {
margin-bottom: 1px;
}
.fcx-in-nav .fcx__controls > * {
position: relative;
}
.fcx-in-nav .fcx__controls .fullchan-x__option > span,
.fcx-in-nav .fcx__controls .fullchan-x__option:not(:hover) > select {
display: none;
}
.fcx-in-nav .fcx__controls .fullchan-x__option > select {
appearance: none;
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
font-size: 0;
}
.fcx-in-nav .fcx__controls .fullchan-x__option > select option {
font-size: 12px;
}
.fcx-in-nav .my-yous__yous {
position: absolute;
left: 50%;
translate: -50%;
background: var(--background-color);
border: 1px solid var(--navbar-text-color);
padding: 14px;
}
/* Fullchan-X main styles */
fullchan-x:not(.fcx-in-nav) {
top: var(--top);
right: var(--right);
display: block;
padding: 10px;
position: fixed;
display: block;
}
fullchan-x:not(.page-thread) .thread-only,
fullchan-x:not(.page-catalog) .catalog-only {
display: none!important;
}
fullchan-x:hover {
z-index: 1000!important;
}
.navHeader:has(fullchan-x:hover) {
z-index: 1000!important;
}
fullchan-x.fcx--dim:not(:hover) {
opacity: 0.6;
}
.divPosts {
flex-direction: column;
}
.fcx__controls {
display: flex;
flex-direction: column;
gap: 6px;
}
fullchan-x:not(:hover):not(:has(select:focus)) span,
fullchan-x:not(:hover):not(:has(select:focus)) select {
display: none;
margin-left: 5px;
z-index:3;
}
.fcx__controls span,
.fcx__controls select {
margin-left: 5px;
}
.fcx__controls select {
cursor: pointer;
}
#thread-sort {
border: none;
background: none;
}
.my-yous__yous {
display: none;
flex-direction: column;
padding-top: 10px;
max-height: calc(100vh - 220px - var(--top));
overflow: auto;
}
.fcx__my-yous:hover .my-yous__yous {
display: flex;
}
.fullchan-x__option {
display: flex;
padding: 6px 8px;
background: white;
border: none !important;
border-radius: 0.2rem;
transition: all ease 150ms;
cursor: pointer;
margin: 0;
text-align: left;
min-width: 18px;
min-height: 18px;
align-items: center;
}
.fullchan-x__option,
.fullchan-x__option select {
font-size: 12px;
font-weight: 400;
color: #374369;
}
fullchan-x:not(:hover):not(:has(select:focus)) .fullchan-x__option {
display: flex;
justify-content: center;
}
#thread-sort {
padding-right: 0;
}
#thread-sort:hover {
display: block;
}
.innerPost:has(.quoteLink.you) {
border-left: solid #dd003e 6px;
}
.innerPost:has(.youName) {
border-left: solid #68b723 6px;
}
/* --- Nested quotes --- */
.divMessage .nestedPost {
display: inline-block;
width: 100%;
margin-bottom: 14px;
white-space: normal!important;
overflow-wrap: anywhere;
margin-top: 0.5em;
border: 1px solid var(--navbar-text-color);
}
.nestedPost .innerPost,
.nestedPost .innerOP {
width: 100%;
}
.nestedPost .imgLink .imgExpanded {
width: auto!important;
height: auto!important;
}
.my-yous__label.unseen {
background: var(--link-hover-color)!important;
color: white;
}
.my-yous__yous .unseen {
font-weight: 900;
color: var(--link-hover-color);
}
/*--- Settings --- */
.fcx-settings {
display: block;
position: fixed;
top: 50vh;
left: 50vw;
translate: -50% -50%;
padding: 20px 0;
background: var(--background-color);
border: 1px solid var(--navbar-text-color);
color: var(--link-color);
font-size: 14px;
max-width: 480px;
max-height: 80vh;
overflow: scroll;
z-index: 1000;
}
fullchan-x-settings:not(.open) {
display: none;
}
.fcx-settings > * {
padding: 0 20px;
}
.fcx-settings header {
display: flex;
align-items: center;
justify-content: space-between;
margin: 0 0 15px;
padding-bottom: 20px;
border-bottom: 1px solid var(--navbar-text-color);
}
.fcx-settings__title {
font-size: 24px;
font-size: 24px;
letter-spacing: 0.04em;
}
fullchan-x-settings:not(.fcxs-updated) .fcxs__updated-message {
display: none;
}
.fcx-setting {
display: flex;
justify-content: space-between;
align-items: center;
padding: 12px 0;
}
.fcx-setting__info {
max-width: 60%;
}
.fcx-setting input[type="text"],
.fcx-setting select {
padding: 4px 6px;
min-width: 35%;
}
.fcx-setting label {
font-weight: 600;
}
.fcx-setting p {
margin: 6px 0 0;
font-size: 12px;
}
.fcx-setting + .fcx-setting {
border-top: 1px solid var(--navbar-text-color);
}
.fcxs__updated-message {
margin: 10px 0;
text-align: center;
}
.fcxs__updated-message p {
font-size: 14px;
color: var(--error);
}
.fcxs__updated-message button {
margin: 14px auto 0;
}
/* --- Gallery --- */
.fct-gallery-open,
body.fct-gallery-open,
body.fct-gallery-open #mainPanel {
overflow: hidden!important;
}
body.fct-gallery-open fullchan-x:not(.fcx-in-nav),
body.fct-gallery-open #quick-reply {
display: none!important;
}
fullchan-x-gallery {
position: fixed;
top: 0;
left: 0;
width: 100%;
background: rgba(0,0,0,0.9);
display: none;
height: 100%;
overflow: auto;
}
fullchan-x-gallery.open {
display: block;
}
fullchan-x-gallery .gallery {
padding: 50px 10px 0
}
fullchan-x-gallery .gallery__images {
--scale: 1.0;
display: flex;
width: 100%;
height: 100%;
justify-content: center;
align-content: flex-start;
gap: 4px 8px;
flex-wrap: wrap;
}
fullchan-x-gallery .imgLink {
float: unset;
display: block;
zoom: var(--scale);
}
fullchan-x-gallery .imgLink img {
border: solid white 1px;
}
fullchan-x-gallery .imgLink[data-filemime="video/webm"] img {
border: solid #68b723 4px;
}
fullchan-x-gallery .gallery__close {
border: solid 1px var(--background-color)!important;
position: fixed;
top: 60px;
right: 35px;
padding: 6px 14px;
min-height: 30px;
z-index: 10;
}
.fcxg .gallery__scale-options {
position: fixed;
bottom: 30px;
right: 35px;
display: flex;
gap: 14px;
z-index: 10;
}
.fcxg .gallery__scale-options .fullchan-x__option {
border: solid 1px var(--background-color)!important;
width: 35px;
height: 35px;
font-size: 18px;
display: flex;
justify-content: center;
}
.gallery__main-image {
display: none;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
justify-content: center;
align-content: center;
background: rgba(0,0,0,0.5);
}
.gallery__main-image img {
padding: 40px 10px 15px;
height: auto;
max-width: calc(100% - 20px);
object-fit: contain;
}
.gallery__main-image.active {
display: flex;
}
/*-- Truncated file extentions --*/
.originalNameLink[data-file-ext] {
display: inline-block;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
max-width: 65px;
}
.originalNameLink[data-file-ext]:hover {
max-width: unset;
white-space: normal;
display: inline;
}
a[data-file-ext]:hover:after {
content: attr(data-file-ext);
}
a[data-file-ext] + .file-ext {
pointer-events: none;
}
a[data-file-ext]:hover + .file-ext {
display: none;
}
/*-- Nav Board Links --*/
.nav-boards--custom {
display: flex;
gap: 3px;
}
#navTopBoardsSpan.hidden ~ #navBoardsSpan,
#navTopBoardsSpan.hidden ~ .nav-fade,
#navTopBoardsSpan a.hidden + span {
display: none;
}
/*-- Anon Unique ID posts --*/
.postInfo .spanId {
position: relative;
}
.fcx-id-posts {
position: absolute;
top: 0;
left: 20px;
translate: 0 calc(-100% - 5px);
display: flex;
flex-direction: column;
padding: 10px;
background: var(--background-color);
border: 1px solid var(--navbar-text-color);
width: max-content;
max-width: 500px;
max-height: 500px;
overflow: auto;
z-index: 1000;
}
.fcx-id-posts .nestedPost {
pointer-events: none;
width: auto;
}
`;
document.head.appendChild(style);
// Asuka and Eris (fantasy Asuka) are best girls