- // ==UserScript==
- // @name Fullchan X
- // @namespace Violentmonkey Scripts
- // @match *://8chan.moe/*
- // @match *://8chan.se/*
- // @match *://8chan.cc/*
- // @match *://8chan.cc/*
- // @run-at document-end
- // @grant none
- // @version 1.14.2
- // @author vfyxe
- // @description 8chan features script
- // ==/UserScript==
-
-
- class fullChanX extends HTMLElement {
- constructor() {
- super();
- }
-
- init() {
- this.settingsEl = document.querySelector('fullchan-x-settings');
- this.settingsAll = this.settingsEl.settings;
- this.settings = this.settingsAll.main;
-
- this.settingsThreadBanisher = this.settingsAll.threadBanisher;
- this.settingsMascot = this.settingsAll.mascot;
- this.isThread = !!document.querySelector('.opCell');
- this.isDisclaimer = window.location.href.includes('disclaimer');
- Object.keys(this.settings).forEach(key => {
- this[key] = this.settings[key]?.value;
- });
-
- this.settingsButton = this.querySelector('#fcx-settings-btn');
- this.settingsButton.addEventListener('click', () => this.settingsEl.toggle());
- this.handleBoardLinks();
- if (!this.isThread) {
- if (this.settingsThreadBanisher.enableThreadBanisher.value) this.banishThreads(this.settingsThreadBanisher);
- 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.enableFileExtensions) this.handleTruncatedFilenames();
- if (this.settingsMascot.enableMascot.value) this.showMascot(this.settingsMascot);
-
- this.styleUI();
-
- if (this.settings.doNotShowLocation) {
- const checkbox = document.getElementById('qrcheckboxNoFlag');
- if (checkbox) checkbox.checked = true;
- checkbox.dispatchEvent(new Event('change', { bubbles: true }));
- }
- }
-
- styleUI () {
- this.style.setProperty('--top', this.uiTopPosition);
- this.style.setProperty('--right', this.uiRightPosition);
- this.classList.toggle('fcx-in-nav', this.moveToNav)
- this.classList.toggle('fcx--dim', this.uiDimWhenInactive && !this.moveToNave);
- this.classList.toggle('page-thread', this.isThread);
- document.body.classList.toggle('fcx-replies-plus', this.enableEnhancedReplies);
- document.body.classList.toggle('fcx-hide-delete', this.hideDeletionBox);
- const style = document.createElement('style');
-
- if (this.hideDefaultBoards !== '' && this.hideDefaultBoards.toLowerCase() !== 'all') {
- style.textContent += '#navTopBoardsSpan{display:block!important;}'
- }
- document.body.appendChild(style);
- }
-
- checkRegexList(string, regexList) {
- const regexObjects = regexList.map(r => {
- const match = r.match(/^\/(.*)\/([gimsuy]*)$/);
- return match ? new RegExp(match[1], match[2]) : null;
- }).filter(Boolean);
-
- return regexObjects.some(regex => regex.test(string));
- }
-
- banishThreads(banisher) {
- this.threadsContainer = document.querySelector('#divThreads');
- if (!this.threadsContainer) return;
- this.threadsContainer.classList.add('fcx-threads');
-
- const currentBoard = document.querySelector('#labelBoard')?.textContent.replace(/\//g,'');
- const boards = banisher.boards.value?.split(',') || [''];
- if (!boards.includes(currentBoard)) return;
-
- const minCharacters = banisher.minimumCharacters.value || 0;
- const banishTerms = banisher.banishTerms.value?.split('\n') || [];
- const banishAnchored = banisher.banishAnchored.value;
- const wlCyclical = banisher.whitelistCyclical.value;
- const wlReplyCount = parseInt(banisher.whitelistReplyCount.value);
-
- const banishSorter = (thread) => {
- if (thread.querySelector('.pinIndicator') || thread.classList.contains('fcx-sorted')) return;
- let shouldBanish = false;
-
- const isAnchored = thread.querySelector('.bumpLockIndicator');
- const isCyclical = thread.querySelector('.cyclicIndicator');
- const replyCount = parseInt(thread.querySelector('.labelReplies')?.textContent?.trim()) || 0;
- const threadSubject = thread.querySelector('.labelSubject')?.textContent?.trim() || '';
- const threadMessage = thread.querySelector('.divMessage')?.textContent?.trim() || '';
- const threadContent = threadSubject + ' ' + threadMessage;
-
- const hasMinChars = threadMessage.length > minCharacters;
- const hasWlReplyCount = replyCount > wlReplyCount;
-
- if (!hasMinChars) shouldBanish = true;
- if (isAnchored && banishAnchored) shouldBanish = true;
- if (isCyclical && wlCyclical) shouldBanish = false;
- if (hasWlReplyCount) shouldBanish = false;
-
- // run heavy regex process only if needed
- if (!shouldBanish && this.checkRegexList(threadContent, banishTerms)) shouldBanish = true;
- if (shouldBanish) thread.classList.add('shit-thread');
- thread.classList.add('fcx-sorted');
- };
-
- const banishThreads = () => {
- this.threads = this.threadsContainer.querySelectorAll('.catalogCell');
- this.threads.forEach(thread => banishSorter(thread));
- };
- banishThreads();
-
- const observer = new MutationObserver((mutationsList) => {
- for (const mutation of mutationsList) {
- if (mutation.type === 'childList' && mutation.addedNodes.length > 0) {
- banishThreads();
- break;
- }
- }
- });
-
- observer.observe(this.threadsContainer, { childList: true });
- }
-
- 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') {
- document.body.classList.add('hide-navboard');
- } 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();
- });
-
-
- // Thread click
- this.threadParent.addEventListener('click', event => this.handleClick(event));
-
-
- // Your (You)s
- 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.enableFileExtensions) this.handleTruncatedFilenames();
- }
- }
- };
- const threadObserver = new MutationObserver(observerCallback);
- threadObserver.observe(this.thread, { childList: true, subtree: false });
-
-
- // Gallery
- this.galleryButton.addEventListener('click', () => this.gallery.open());
- this.myYousLabel.addEventListener('click', (event) => {
- if (this.myYousLabel.classList.contains('unseen')) {
- this.yousContainer.querySelector('.unseen').click();
- }
- });
-
- if (!this.enableEnhancedReplies) return;
- const setReplyLocation = (replyPreview) => {
- const parent = replyPreview.parentElement;
- if (!parent || (!parent.classList.contains('innerPost') && !parent.classList.contains('innerOP'))) return;
- const parentMessage = parent.querySelector('.divMessage');
-
- if (parentMessage && parentMessage.parentElement === parent) {
- parentMessage.insertAdjacentElement('beforebegin', replyPreview);
- }
- };
-
- const observer = new MutationObserver(mutations => {
- for (const mutation of mutations) {
- for (const node of mutation.addedNodes) {
- if (node.nodeType !== 1) continue;
-
- if (node.classList.contains('inlineQuote')) {
- const replyPreview = node.closest('.replyPreview');
- if (replyPreview) {
- setReplyLocation(replyPreview);
- }
- }
- }
- }
- });
-
- if (this.threadParent) observer.observe(this.threadParent, {childList: true, subtree: true });
- }
-
- handleClick (event) {
- const clicked = event.target;
- let replyLink = clicked.closest('.panelBacklinks a');
- const parentPost = clicked.closest('.innerPost, .innerOP');
- const closeButton = clicked.closest('.postInfo > a:first-child');
- const anonId = clicked.closest('.labelId');
-
- if (closeButton) this.handleReplyCloseClick(closeButton, parentPost);
- if (replyLink) this.handleReplyClick(replyLink, parentPost);
- if (anonId) this.handleAnonIdClick(anonId, event);
- }
-
- handleReplyCloseClick(closeButton, parentPost) {
- const replyLink = document.querySelector(`[data-close-id="${closeButton.id}"]`);
- if (!replyLink) return;
- const linkParent = replyLink.closest('.innerPost, .innerOP');
- this.handleReplyClick(replyLink, linkParent);
- }
-
- handleReplyClick(replyLink, parentPost) {
- replyLink.classList.toggle('active');
- let replyColor = replyLink.dataset.color;
- const replyId = replyLink.href.split('#').pop();
- let replyPost = false;
- let labelId = false;
-
- const randomNum = () => `${Math.floor(Math.random() * 16777215).toString(16).padStart(6, '0')}`
-
- if (!replyColor) {
- replyPost = document.querySelector(`#${CSS.escape(replyId)}`);
- labelId = replyPost?.querySelector('.labelId');
- replyColor = labelId?.textContent || randomNum();
- }
-
- const linkQuote = [...parentPost.querySelectorAll('.replyPreview .linkQuote')]
- .find(link => link.textContent === replyId);
- if (!labelId && linkQuote) linkQuote.style = `--active-color: #${replyColor};`;
-
- const closeId = randomNum();
- const closeButton = linkQuote?.closest('.innerPost').querySelector('.postInfo > a:first-child');
- if (closeButton) closeButton.id = closeId;
-
- replyLink.style = `--active-color: #${replyColor};`;
- replyLink.dataset.color = `${replyColor}`;
- replyLink.dataset.closeId = closeId;
- }
-
- 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}`;
- return newLink;
- });
-
- postIds.forEach(postId => this.anonIdPosts.appendChild(postId));
- anonId.insertAdjacentElement('afterend', this.anonIdPosts);
-
- this.setPostListeners(this.anonIdPosts);
- }
-
- setPostListeners(parentPost) {
- const postLinks = [...parentPost.querySelectorAll('.quoteLink')];
-
- 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));
- }
-
- 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);
- }
- });
- }
-
- showMascot(settings) {
- const mascot = document.createElement('img');
- mascot.classList.add('fcx-mascot');
- mascot.src = settings.image.value;
- mascot.style.opacity = settings.opacity.value * 0.01;
- mascot.style.top = settings.top.value;
- mascot.style.left = settings.left.value;
- mascot.style.right = settings.right.value;
- mascot.style.bottom = settings.bottom.value;
- mascot.style.height = settings.height.value;
- mascot.style.width = settings.width.value;
- document.body.appendChild(mascot);
- }
- };
-
- 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 = {
- main: {
- moveToNav: {
- info: 'Move Fullchan-X controls into the navbar.',
- type: 'checkbox',
- value: true
- },
- enableEnhancedReplies: {
- info: "Enhances 8chan's native reply post previews.<p>Inline replies are now a <b>native feature</b> of 8chan, remember to enable them.</p>",
- type: 'checkbox',
- value: true
- },
- hideDeletionBox: {
- info: "Not much point in seeing this if you're not an mod.",
- type: 'checkbox',
- value: false
- },
- doNotShowLocation: {
- info: "Board with location option will be set to false by default.",
- type: 'checkbox',
- value: false
- },
- enableFileExtensions: {
- 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
- },
- hideNavbar: {
- info: 'Hide navbar until hover.',
- type: 'checkbox',
- value: false
- },
- replyTabIcon: {
- info: 'Set the icon/text added to tab title when you get a new (You).',
- type: 'input',
- value: '❗'
- }
- },
- mascot: {
- enableMascot: {
- info: 'Enable mascot image.',
- type: 'checkbox',
- value: false
- },
- image: {
- info: 'Image URL (8chan image recommended).',
- type: 'input',
- value: '/.static/logo.png'
- },
- opacity: {
- info: 'Opacity (1 to 100)',
- type: 'input',
- inputType: 'number',
- value: '75'
- },
- width: {
- info: 'Width of image.',
- type: 'input',
- value: '300px'
- },
- height: {
- info: 'Height of image.',
- type: 'input',
- value: 'auto'
- },
- bottom: {
- info: 'Bottom position.',
- type: 'input',
- value: '0px'
- },
- right: {
- info: 'Right position.',
- type: 'input',
- value: '0px'
- },
- top: {
- info: 'Top position.',
- type: 'input',
- value: ''
- },
- left: {
- info: 'Left position.',
- type: 'input',
- value: ''
- }
- },
- threadBanisher: {
- enableThreadBanisher: {
- info: 'Banish shit threads to the bottom of the calalog.',
- type: 'checkbox',
- value: true
- },
- boards: {
- info: 'Banish theads on these boards (seperated by comma).',
- type: 'input',
- value: 'v,a'
- },
- minimumCharacters: {
- info: 'Minimum character requirements',
- type: 'input',
- inputType: 'number',
- value: 100
- },
- banishTerms: {
- info: `<p>Banish threads with these terms to the bottom of the catalog (new line per term).</p>
- <p>How to use regex: <a href="https://www.datacamp.com/cheat-sheet/regular-expresso" target="__blank">Regex Cheatsheet</a>.</p>
- <p>NOTE: word breaks (\\b) MUST be entered as double escapes (\\\\b), they will appear as (\\b) when saved.</p>
- `,
- type: 'textarea',
- value: '/\\bcuck\\b/i\n/\\bchud\\b/i\n/\\bblacked\\b/i\n/\\bnormie\\b/i\n/\\bincel\\b/i\n/\\btranny\\b/i\n/\\bslop\\b/i\n'
- },
- whitelistCyclical: {
- info: 'Whitelist cyclical threads.',
- type: 'checkbox',
- value: true
- },
- banishAnchored: {
- info: 'Banish anchored threads that are under minimum reply count.',
- type: 'checkbox',
- value: true
- },
- whitelistReplyCount: {
- info: 'Threads above this reply count (excluding those with banish terms) will be whitelisted.',
- type: 'input',
- inputType: 'number',
- value: 100
- },
- }
- };
- }
-
- init() {
- this.fcx = document.querySelector('fullchan-x');
- this.settingsMain = this.querySelector('.fcxs-main');
- this.settingsThreadBanisher = this.querySelector('.fcxs-thread-banisher');
- this.settingsMascot = this.querySelector('.fcxs-mascot');
- this.getSavedSettings();
- if (this.settings.main) {
- this.fcx.init();
- this.loaded = true;
- };
- this.buildSettingsOptions('main', this.settingsMain);
- this.buildSettingsOptions('threadBanisher', this.settingsThreadBanisher);
- this.buildSettingsOptions('mascot', this.settingsMascot);
- this.listeners();
- this.querySelector('.fcx-settings__close').addEventListener('click', () => this.close());
-
- if (!this.loaded) this.fcx.init();
- this.fcx.styleUI();
- document.body.classList.toggle('fcx-hide-navbar',this.settings.main.hideNavbar.value);
- }
-
- setSavedSettings(updated) {
- localStorage.setItem(this.settingsKey, JSON.stringify(this.settings));
- if (updated) this.classList.add('fcxs-updated');
- }
-
- getSavedSettings() {
- let saved = JSON.parse(localStorage.getItem(this.settingsKey));
- if (!saved) return;
-
- // Ensure all top-level keys exist
- for (const key in this.settingsTemplate) {
- if (!saved[key]) saved[key] = {};
- }
-
- this.settings = saved;
- }
-
- listeners() {
- this.inputs.forEach(input => {
- input.addEventListener('change', () => {
- const section = input.dataset.section;
- const key = input.name;
- const value = input.type === 'checkbox' ? input.checked : input.value;
- this.settings[section][key].value = value;
- this.setSavedSettings(true);
- });
- });
- }
-
- buildSettingsOptions(subSettings, parent) {
- if (!this.settings[subSettings]) this.settings[subSettings] = {}
-
- Object.entries(this.settingsTemplate[subSettings]).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.innerHTML = config.info;
- infoWrapper.appendChild(info);
- }
-
- const savedValue = this.settings[subSettings][key]?.value ?? config.value;
- let input;
-
- if (config.type === 'checkbox') {
- input = document.createElement('input');
- input.type = 'checkbox';
- input.checked = savedValue;
- } else if (config.type === 'textarea') {
- input = document.createElement('textarea');
- input.value = savedValue;
- } else if (config.type === 'input') {
- input = document.createElement('input');
- input.type = config.inputType || 'text';
- input.value = savedValue;
- } else if (config.type === 'select' && config.options) {
- 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;
- input.dataset.section = subSettings;
- wrapper.appendChild(input);
- this.inputs.push(input);
- this.settings[subSettings][key] = {
- value: input.type === 'checkbox' ? input.checked : input.value
- };
- }
-
- parent.appendChild(wrapper);
- });
- }
-
- open() {
- this.classList.add('open');
- }
-
- close() {
- this.classList.remove('open');
- }
-
- toggle() {
- this.classList.toggle('open');
- }
- }
-
- window.customElements.define('fullchan-x-settings', fullChanXSettings);
-
-
-
- class ToggleButton extends HTMLElement {
- constructor() {
- super();
- const data = this.dataset;
- this.onclick = () => {
- const target = data.target ? document.querySelector(data.target) : this;
- const value = data.value || 'active';
- !!data.set ? target.dataset[data.set] = value : target.classList.toggle(value);
- }
- }
- }
-
- window.customElements.define('toggle-button', ToggleButton);
-
-
-
- // 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('.navHeader') || document.body).appendChild(fcx);
-
-
-
- // Create fullchan-x settings
- const fcxs = document.createElement('fullchan-x-settings');
- fcxs.innerHTML = `
- <div class="fcx-settings fcxs" data-tab="main">
- <header>
- <div class="fcxs__heading">
- <span class="fcx-settings__title">
- <img class="fcxs_logo" src="/.static/logo/logo_blue.png" height="25px" width="auto">
- <span>
- Fullchan-X Settings
- </span>
- </span>
- <button class="fcx-settings__close fullchan-x__option">Close</button>
- </div>
-
- <div class="fcx-settings__tab-buttons">
- <toggle-button data-target=".fcxs" data-set="tab" data-value="main">
- Main
- </toggle-button>
- <toggle-button data-target=".fcxs" data-set="tab" data-value="catalog">
- catalog
- </toggle-button>
- <toggle-button data-target=".fcxs" data-set="tab" data-value="mascot">
- Mascot
- </toggle-button>
- </div>
- </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 class="fcxs-main fcxs-tab"></div>
- <div class="fcxs-mascot fcxs-tab"></div>
- <div class="fcxs-catalog fcxs-tab">
- <div class="fcxs-thread-banisher"></div>
- </div>
- </div>
- </main>
-
- <footer>
- </footer>
- </div>
- `;
-
-
- // Styles
- const style = document.createElement('style');
- style.innerHTML = `
- .hide-navboard #navTopBoardsSpan {
- display: none!important;
- }
-
- 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;
- }
-
- toggle-button {
- cursor: pointer;
- }
-
- /* Fullchan-X in nav styles */
- .fcx-in-nav {
- padding: 0;
- border-width: 0;
- line-height: 20px;
- margin-right: 2px;
- background: none;
- }
-
- .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 {
- position: relative
- 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;
- }
-
- .bottom-header .fcx-in-nav .my-yous__yous {
- top: 0;
- translate: -50% -100%;
- }
-
- /* 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);
- }
-
- .panelBacklinks a.active {
- color: #dd003e;
- }
-
- /*--- 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;
- min-width: 500px;
- z-index: 1000;
- }
-
- fullchan-x-settings:not(.open) {
- display: none;
- }
-
- .fcxs__heading,
- .fcxs-tab,
- .fcxs footer {
- padding: 0 20px;
- }
-
- .fcx-settings header {
- margin: 0 0 15px;
- border-bottom: 1px solid var(--navbar-text-color);
- }
-
- .fcxs__heading {
- display: flex;
- align-items: center;
- justify-content: space-between;
- padding-bottom: 20px;
- }
-
- .fcx-settings__title {
- display: flex;
- align-items: center;
- gap: 10px;
- font-size: 24px;
- font-size: 24px;
- letter-spacing: 0.04em;
- }
-
- .fcxs_logo {
- .margin-top: -2px;
- }
-
- .fcx-settings__tab-buttons {
- border-top: 1px solid var(--navbar-text-color);
- display: flex;
- align-items: center;
- }
-
- .fcx-settings__tab-buttons toggle-button {
- flex: 1;
- padding: 15px;
- font-size: 14px;
- }
-
- .fcx-settings__tab-buttons toggle-button + toggle-button {
- border-left: 1px solid var(--navbar-text-color);
- }
-
- .fcx-settings__tab-buttons toggle-button:hover {
- color: var(--role-color);
- }
-
- fullchan-x-settings:not(.fcxs-updated) .fcxs__updated-message {
- display: none;
- }
-
- .fcxs:not([data-tab="main"]) .fcxs-main,
- .fcxs:not([data-tab="catalog"]) .fcxs-catalog,
- .fcxs:not([data-tab="mascot"]) .fcxs-mascot {
- display: none;
- }
-
- .fcxs[data-tab="main"] [data-value="main"],
- .fcxs[data-tab="catalog"] [data-value="catalog"],
- .fcxs[data-tab="mascot"] [data-value="mascot"] {
- font-weight: 700;
- }
-
- .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 input[type="number"],
- .fcx-setting select,
- .fcx-setting textarea {
- padding: 4px 6px;
- min-width: 35%;
- }
-
- .fcx-setting textarea {
- min-height: 100px;
- }
-
- .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;
- }
-
- /*-- Enhanced replies --*/
- .fcx-replies-plus .panelBacklinks a.active {
- --active-color: red;
- color: var(--active-color);
- }
-
- .fcx-replies-plus .replyPreview .linkQuote {
- color: var(--active-color);
- }
-
- .fcx-replies-plus .replyPreview {
- padding-left: 40px;
- padding-right: 10px;
- margin-top: 10px;
- }
-
- .fcx-replies-plus .replyPreview .inlineQuote + .inlineQuote {
- margin-top: 8px;
- }
-
- .fcx-replies-plus .inlineQuote .innerPost {
- border: solid 1px var(--navbar-text-color)
- }
-
- .fcx-replies-plus .quoteLink + .inlineQuote {
- margin-top: 6px;
- }
-
- .fcx-replies-plus .inlineQuote .postInfo > a:first-child {
- position: absolute;
- display: inline-block;
- font-size: 0;
- width: 14px;
- height: 14px;
- background: var(--link-color);
- border-radius: 50%;
- translate: 6px 0.5px;
- }
-
- .fcx-replies-plus .inlineQuote .postInfo > a:first-child:after {
- content: '+';
- display: block;
- position: absolute;
- left: 50%;
- top: 50%;
- font-size: 18px;
- color: var(--contrast-color);
- transform: translate(-50%, -50%) rotate(45deg);
- z-index: 1;
- }
-
- .fcx-replies-plus .inlineQuote .postInfo > a:first-child:hover {
- background: var(--link-hover-color);
- }
-
- .fcx-replies-plus .inlineQuote .hideButton {
- margin-left: 25px;
- }
-
- /*-- 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;
- }
-
- /* mascot */
- .fcx-mascot {
- position: fixed;
- z-index: -1;
- }
-
- .fct-gallery-open .fcx-mascot {
- display: none;
- }
-
- /*-- Thread sorting --*/
- #divThreads.fcx-threads {
- display: flex!important;
- flex-wrap: wrap;
- justify-content: center;
- }
-
- .catalogCell.shit-thread {
- order: 10;
- filter: sepia(0.17);
- }
-
- .catalogCell.shit-thread .labelPage:after {
- content: " 💩";
- }
-
- /* Hide navbar */
- .fcx-hide-navbar .navHeader {
- --translateY: -100%;
- translate: 0 var(--translateY);
- transition: ease 300ms translate;
- }
-
- .bottom-header.fcx-hide-navbar .navHeader {
- --translateY: 100%;
- }
-
- .fcx-hide-navbar .navHeader:after {
- content: "";
- display: block;
- height: 100%;
- width: 100%;
- left: 0;
- position: absolute;
- top: 100%;
- }
-
- .fcx-hide-navbar .navHeader:hover {
- --translateY: -0%;
- }
-
- .bottom-header .fcx-hide-navbar .navHeader:not(:hover) {
- --translateY: 100%;
- }
-
- .bottom-header .fcx-hide-navbar .navHeader:after {
- top: -100%;
- }
-
- /* Extra styles */
- .fcx-hide-delete .postInfo .deletionCheckBox {
- display: none;
- }
- `;
-
- document.head.appendChild(style);
-
- document.body.appendChild(fcxs);
- fcxs.init();
-
- // Asuka and Eris (fantasy Asuka) are best girls