您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Improves your Pokemon Showdown homepage.
当前为
// ==UserScript== // @name [PS] Homepage Enhancements // @namespace https://greasyfork.org/en/users/1357767-indigeau // @version 0.1 // @description Improves your Pokemon Showdown homepage. // @match https://play.pokemonshowdown.com/* // @exclude https://play.pokemonshowdown.com/sprites/* // @author indigeau // @license GNU GPLv3 // @icon https://www.google.com/s2/favicons?sz=64&domain=pokemonshowdown.com // @grant none // ==/UserScript== const styleSheet = (() => { const styleElement = document.createElement('style'); document.head.appendChild(styleElement); return styleElement.sheet; })(); // Style (() => { const rules = [ ['body > :not(#room-.ps-room)', [['z-index', '1']]], // necessary to avoid blocking the header's pointer events ['#room-.ps-room', [ // ['padding-bottom', '50px'], // pushes the footer offscreen ['top', '0'], ['height', '100%'], ]], ['.mainmenuwrapper', [ ['display', 'flex'], ['align-items', 'center'], ['width', '100%'], ['height', '100%'], ['box-sizing', 'border-box'], ['position', 'initial'], ]], ['.leftmenu', [ ['display', 'flex'], ['flex-wrap', 'nowrap'], ['flex-direction', 'row-reverse'], ['width', '100%'], ['height', '100%'], ['box-sizing', 'border-box'], ['padding', '0px'], ['justify-content', 'center'], ['padding-top', '77px'], // headerHeight + 20px ['padding-bottom', '70px'], // footerHeight + 20px ['position', 'absolute'], ['top', '0'], ]], ['.activitymenu', [['display', 'contents']]], ['.pmbox', [ ['flex-grow', '1'], ['display', 'flex'], ['flex-direction', 'column'], ['height', '100%'], ['flex-wrap', 'wrap'], ['overflow', 'auto'], ['background', 'rgba(0, 0, 0, .2)'], ['border-radius', '20px'], ['padding', '10px'], ['align-content', 'flex-start'], ['overscroll-behavior', 'contain'], ['margin-right', '22px'], ['box-sizing', 'border-box'], ['align-items', 'center'], ]], ['.pmbox > *', [ ['width', '270px'], ['max-width', '100%'], ['margin', '4px'], ]], ['.mainmenu', [ ['width', '270px'], ['padding', '0'], ['margin', '0 22px'], ['height', '100%'], ['display', 'flex'], ['flex-direction', 'column'], ['overflow-y', 'auto'], ['scrollbar-width', 'none'], ['overscroll-behavior', 'contain'], ['background', 'rgba(0, 0, 0, .2)'], ['border-radius', '20px'], ['justify-content', 'space-evenly'], ]], ['.mainmenu > .menugroup', [ ['background', 'none'], ['margin', '0'], ['padding', '0'], ]], ['.mainmenu > .menugroup:not(:first-child) p', [['margin-top', '-1px']]], ['.mainmenu > .menugroup:not(:first-child) p > button', [['box-shadow', 'inset #000d1733 0 0 200px 0px']]], ['.dark > body .mainmenufooter', [ ['background', 'rgba(0, 0, 0, .3)'], ['border-top', '6px solid #555'], ]], [':not(.dark) > body .mainmenufooter', [ ['background', 'url(../fx/client-topbar-bg.png) repeat-x left top scroll'], ['border-top', '6px solid #f8f8f8'], ]], ['.mainmenufooter', [ ['height', '50px'], ['width', '100%'], ['bottom', '0'], ['left', '0'], ['display', 'flex'], ]], ['.mainmenufooter small', [ ['flex-grow', '1'], ['display', 'flex'], ['font-size', '0'], ['border-top', '1px solid #34373b'], ]], ['.dark > body .mainmenufooter > small > a', [ ['color', '#fff'], ['box-shadow', 'inset -0.5px -0.5px 1px 0.5px rgba(255, 255, 255, .5)'], ]], ['.dark > body .mainmenufooter > small > a:visited', [['color', '#fff']]], [':not(.dark) > body .mainmenufooter > small > a', [ ['color', '#222'], ['box-shadow', 'inset -0.5px -0.5px 1px 0.5px rgba(255, 255, 255, .5)'], ]], [':not(.dark) > body .mainmenufooter > small > a:visited', [['color', '#222']]], ['.mainmenufooter > small > a', [ ['font-size', '12pt'], ['text-align', 'center'], ['text-decoration', 'none'], ['border-top-right-radius', '0'], ['border-top-left-radius', '0'], ['margin', '0 5px'], ['padding', '4px 12px'], ['height', '28px'], ]], ['.mainmenufooter > small > a:hover', [ ['text-decoration', 'none'], ]], ]; for (let rule of rules) { styleSheet.insertRule(`${rule[0]}{${rule[1].map(([property, value]) => `${property}:${value};`).join('')}}`); } for (const button of document.querySelectorAll('.mainmenufooter > small > a')) { button.classList.add('roomtab', 'button'); } })(); class ConditionalDisplay { static source = document.querySelector('.mainmenuwrapper'); static listeners = []; constructor(width) { this.width = width; this.isShown = this.doShow(); } doShow() { return ConditionalDisplay.source.clientWidth >= this.width; } add(listener) { ConditionalDisplay.listeners.push(() => { if (this.doShow() === this.isShown) { return; } this.isShown = !this.isShown; listener(this.isShown); }); listener(this.isShown); } } (new ResizeObserver(() => { for (const listener of ConditionalDisplay.listeners) { listener(); } })).observe(ConditionalDisplay.source); // todo handle html:not(.dark) & html.dark differently // footer bg 'url(../fx/client-topbar-bg.png) repeat-x left top scroll' // Footer extras (() => { const parent = document.querySelector('.mainmenufooter > small'); const container = document.createElement('div'); container.style.fontSize = '12pt'; container.style.flexGrow = '1'; container.style.margin = '0 20px'; container.style.alignItems = 'center'; const showContainer = (doShow) => { container.style.display = doShow ? 'flex' : 'none'; }; (new ConditionalDisplay(850)).add(showContainer); const text = document.createElement('span'); text.style.marginLeft = '6px'; text.innerText = 'Script by indigeau'; const getButton = () => { const button = document.createElement('img'); button.classList.add('icon', 'button'); button.style.margin = '0 3px'; button.style.height = '21px'; button.style.borderRadius = '5px'; button.style.cursor = 'pointer'; button.style.padding = '2px'; button.style.boxShadow = '.5px 1px 2px rgba(255, 255, 255, .45), inset .5px 1px 1px rgba(255, 255, 255, .5)'; return button; }; // PS button const psButton = (() => { const button = getButton(); button.src = 'https://www.google.com/s2/favicons?sz=64&domain=pokemonshowdown.com'; button.addEventListener('click', () => { app.rooms[''].focusPM('indigeau'); }); return button; })(); // GreasyFork button const gfButton = (() => { const button = getButton(); button.src = 'https://www.google.com/s2/favicons?sz=64&domain=greasyfork.org'; button.addEventListener('click', () => { open('https://greasyfork.org/en/scripts/506533-ps-homepage-enhancements/feedback'); }); return button; })(); container.append(psButton, gfButton, text); parent.insertBefore(container, parent.firstChild); })(); // Side mascots (() => { const imageButtonClass = 'home-style-team-image'; const rules = [ [`.${imageButtonClass}:hover + img`, [ ['scale', '1.2'], ['filter', 'drop-shadow(black 3px 4px 2px) drop-shadow(black 0 0 10px)'], ['z-index', '1'], ]], ]; for (let rule of rules) { styleSheet.insertRule(`${rule[0]}{${rule[1].map(([property, value]) => `${property}:${value};`).join('')}}`); } const IMAGE_BACKUP = [ { src: 'https://play.pokemonshowdown.com/sprites/gen5ani/meloetta.gif', transform: 'translateX(-2%) scaleX(-1)', }, { src: 'https://play.pokemonshowdown.com/sprites/gen5ani/meloetta-pirouette.gif', transform: 'translateX(-2%) scaleX(-1)', }, ]; const swapImages = (toShow, toHide) => { for (const image of toHide) { image.style.display = 'none'; } for (const image of toShow) { image.style.removeProperty('display'); } }; const setImageSrc = (backupImages, teamImages, teamButtons, doListen = true) => { const button = document.querySelector('.select.teamselect'); if (doListen) { (new MutationObserver(() => { setImageSrc(backupImages, teamImages, teamButtons, false); })).observe(button.parentElement, { characterData: true, childList: true, subtree: true, }); } const teamIndex = Number.parseInt(button.value); if (!Number.isInteger(teamIndex)) { swapImages(backupImages, teamImages); return; } const teamName = button.firstChild.innerText; const team = Storage.teams.find(({name}) => name === teamName); if (!team) { swapImages(backupImages, teamImages); return; } swapImages(teamImages, backupImages); for (const [i, mon] of Teams.unpack(team.team).entries()) { const {url, pixelated} = Dex.getSpriteData(mon.species, true, {...mon, ...team}); teamImages[i].src = url; teamImages[i].style.imageRendering = pixelated ? 'pixelated' : 'auto'; teamButtons[i].onclick = () => { app.addRoom('teambuilder'); app.focusRoom('teambuilder'); const {teambuilder} = app.rooms; teambuilder.edit(teamIndex); teambuilder.selectPokemon(i); teambuilder.stats(); // You need to wait for this because it clears the stats if (teambuilder.formatResources[team.format] === true) { Object.defineProperty(teambuilder.formatResources, team.format, { // Avatar change listener set(value) { delete teambuilder.formatResources[team.format]; teambuilder.formatResources[team.format] = value; window.setTimeout(() => { teambuilder.updateChart(); }, 0); }, get() { return true; }, }); } }; } }; const getTeamImage = (index) => { const image = document.createElement('img'); image.style.position = 'absolute'; image.style.top = `${(index + 1) / 7 * 100}%`; image.style.transform = 'translateY(-50%) scaleX(-1)'; image.style.width = '100%'; image.style.pointerEvents = 'none'; image.style.transformOrigin = 'top'; return image; }; const getTeamButton = (index) => { const button = document.createElement('div'); button.classList.add(imageButtonClass); button.style.position = 'absolute'; button.style.top = `${((index + 1) * 2 - 1) / 14 * 100}%`; button.style.width = '75%'; button.style.height = `${1 / 7 * 100}%`; button.style.cursor = 'pointer'; button.style.alignSelf = 'center'; return button; }; const getBackupImage = ({src, transform}) => { const image = document.createElement('img'); image.src = src; image.style.imageRendering = 'pixelated'; image.style.transform = transform; return image; }; const parent = document.querySelector('.leftmenu'); const container = document.createElement('div'); container.style.height = '100%'; container.style.background = 'rgba(0, 0, 0, .2)'; container.style.alignItems = 'stretch'; container.style.position = 'relative'; container.style.overflow = 'hidden'; container.style.flexDirection = 'column'; container.style.marginLeft = '22px'; container.style.borderRadius = '20px'; container.style.maxWidth = '300px'; container.style.width = '10%'; container.style.placeContent = 'stretch space-around'; container.style.flexDirection = 'column'; container.style.justifyContent = 'center'; const teamImages = []; const teamButtons = []; const backupImages = []; for (let i = 0; i < 6; ++i) { const image = getTeamImage(i); const button = getTeamButton(i); container.append(button, image); teamImages.push(image); teamButtons.push(button); } for (const data of IMAGE_BACKUP) { const image = getBackupImage(data); container.append(image); backupImages.push(image); } setImageSrc(backupImages, teamImages, teamButtons); const showContainer = (doShow) => { container.style.display = doShow ? 'flex' : 'none'; }; (new ConditionalDisplay((() => { const unconditionalWidth // margins between elements = 22 * 3 // mainmenu width + 270 // pm-window width + pm-window margin + pmbox padding + 270 + 4 * 2 + 10 * 2; return Math.ceil(unconditionalWidth * (1 / 0.9)) + 22; })())).add(showContainer); parent.append(container); })(); /* global app Dex Teams */