您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Allows you rotate your view 90 degrees and zoom in on neal.fun/internet-roadtrip
当前为
// ==UserScript== // @name Internet Roadtrip - Look Out the Window v1 // @description Allows you rotate your view 90 degrees and zoom in on neal.fun/internet-roadtrip // @namespace me.netux.site/user-scripts/internet-roadtrip/look-out-the-window-v1 // @version 1.17.0 // @author netux // @license MIT // @match https://neal.fun/internet-roadtrip/ // @icon https://neal.fun/favicons/internet-roadtrip.png // @grant GM.setValues // @grant GM.getValues // @grant GM.registerMenuCommand // @grant GM_addStyle // @run-at document-start // @require https://cdn.jsdelivr.net/combine/npm/@violentmonkey/dom@2,npm/@violentmonkey/[email protected] // @require https://cdn.jsdelivr.net/npm/[email protected] // ==/UserScript== (async () => { const CSS_PREFIX = 'lotwv1-'; const LEGACY_LOCAL_STORAGE_KEY = "internet-roadtrip/mod/look-out-the-window"; const DEFAULT_FRONT_OVERLAY_IMAGE = { imageSrc: null }; const DEFAULT_RIGHT_OVERLAY_IMAGE = { imageSrc: `https://cloudy.netux.site/neal_internet_roadtrip/side%20window.png`, transformOrigin: { x: "50%", y: "40%" } }; const DEFAULT_LEFT_OVERLAY_IMAGE = { imageSrc: `https://cloudy.netux.site/neal_internet_roadtrip/side%20window.png`, transformOrigin: { x: "50%", y: "40%" }, flip: true }; const DEFAULT_BACK_OVERLAY_IMAGE = { imageSrc: `https://cloudy.netux.site/neal_internet_roadtrip/back%20window.png`, transformOrigin: { x: "50%", y: "20%" } }; const Direction = Object.freeze({ FRONT: 0, RIGHT: 1, BACK: 2, LEFT: 3 }); const state = { settings: { lookingDirection: Direction.FRONT, zoom: 1, showVehicleUi: true, alwaysShowGameUi: false, frontOverlay: DEFAULT_FRONT_OVERLAY_IMAGE, backOverlay: DEFAULT_BACK_OVERLAY_IMAGE, leftOverlay: DEFAULT_LEFT_OVERLAY_IMAGE, rightOverlay: DEFAULT_RIGHT_OVERLAY_IMAGE, }, dom: {} }; { // migrate locals storage data form versions <=1.12.0 if (LEGACY_LOCAL_STORAGE_KEY in localStorage) { const localStorageSettings = JSON.parse(localStorage.getItem(LEGACY_LOCAL_STORAGE_KEY)); await GM.setValues(localStorageSettings); localStorage.removeItem(LEGACY_LOCAL_STORAGE_KEY); } } { const storedSettings = await GM.getValues(Object.keys(state.settings)) Object.assign( state.settings, storedSettings ); } { // migrate from single side overlay config from versions <=1.16.0 if (state.settings.sideOverlay) { state.settings.rightOverlay = state.settings.sideOverlay; state.settings.leftOverlay = { ... state.settings.sideOverlay, flip: true }; delete state.settings.sideOverlay; } } const cssClass = (names) => (Array.isArray(names) ? names : [names]).map((name) => `${CSS_PREFIX}${name}`).join(' '); function setupDom() { injectStylesheet(); preloadOverlayImages(); const containerEl = document.querySelector('.container'); state.dom.containerEl = containerEl; state.dom.panoIframeEls = Array.from(containerEl.querySelectorAll('.pano')); state.dom.overlayImageEl = VM.hm('div', { className: cssClass('overlay__image') }); state.dom.overlayEl = VM.hm('div', { className: cssClass('overlay') }, state.dom.overlayImageEl); state.dom.panoIframeEls.at(-1).insertAdjacentElement('afterend', state.dom.overlayEl); async function lookRight() { state.settings.lookingDirection = (state.settings.lookingDirection + 1) % 4; updateLookAt(); await saveSettings(); } async function lookLeft() { state.settings.lookingDirection = state.settings.lookingDirection - 1; if (state.settings.lookingDirection < 0) { state.settings.lookingDirection = 3; } updateLookAt(); await saveSettings(); } const chevronImage = (rotation) => VM.hm('img', { src: '/sell-sell-sell/arrow.svg', // yoink style: ` width: 10px; aspectRatio: 1; filter: invert(1); rotate: ${rotation}deg; ` }); state.dom.lookLeftButtonEl = VM.hm('button', { className: cssClass('look-left-btn') }, chevronImage(90)); state.dom.lookLeftButtonEl.addEventListener('click', lookLeft); containerEl.appendChild(state.dom.lookLeftButtonEl); state.dom.lookRightButtonEl = VM.hm('button', { className: cssClass('look-right-btn') }, chevronImage(-90)); state.dom.lookRightButtonEl.addEventListener('click', lookRight); containerEl.appendChild(state.dom.lookRightButtonEl); window.addEventListener("keydown", async (event) => { if (event.target !== document.body) { return; } switch (event.key) { case "ArrowLeft": { await lookLeft(); break; } case "ArrowRight": { await lookRight(); break; } } }); window.addEventListener("wheel", async (event) => { if (event.target !== document.documentElement) { // pointing at nothing but the backdrop return; } const scrollingForward = event.deltaY < 0; state.settings.zoom = Math.min(Math.max(1, state.settings.zoom * (scrollingForward ? 1.1 : 0.9)), 20); updateZoom(); await saveSettings(); }); createSettings(); updateUiFromSettings(); updateOverlays(); updateLookAt(); updateZoom(); } function injectStylesheet() { GM_addStyle(` body { & .${cssClass('look-right-btn')}, & .${cssClass('look-left-btn')} { position: fixed; bottom: 200px; transform: translateY(-50%); padding-block: 1.5rem; border: none; background-color: whitesmoke; cursor: pointer; } & .${cssClass('look-right-btn')} { right: 0; padding-inline: 0.35rem 0.125rem; border-radius: 15px 0 0 15px; } & .${cssClass('look-left-btn')} { left: 0; padding-inline: 0.125rem 0.25rem; border-radius: 0 15px 15px 0; } &:not(.${cssClass('always-show-game-ui')}):not([data-look-out-the-window-direction="${Direction.FRONT}"]) :is(.freshener-container, .wheel-container, .options) { display: none; } & .${cssClass('overlay')} { position: fixed; width: 100%; height: 100%; pointer-events: none; display: none; &.${cssClass('overlay--flipped')} { rotate: y 180deg; } & .${cssClass('overlay__image')} { position: absolute; top: 0%; left: 0%; width: 100%; height: 100%; background-size: cover; background-position: center; } } &[data-look-out-the-window-direction="${Direction.FRONT}"] .${cssClass('overlay__image')} { transform-origin: var(--${CSS_PREFIX}front-overlay-transform-origin); background-image: var(--${CSS_PREFIX}front-overlay-image-src); } &[data-look-out-the-window-direction="${Direction.LEFT}"] .${cssClass('overlay__image')} { transform-origin: var(--${CSS_PREFIX}left-overlay-transform-origin); background-image: var(--${CSS_PREFIX}left-overlay-image-src); } &[data-look-out-the-window-direction="${Direction.RIGHT}"] .${cssClass('overlay__image')} { transform-origin: var(--${CSS_PREFIX}right-overlay-transform-origin); background-image: var(--${CSS_PREFIX}right-overlay-image-src); } &[data-look-out-the-window-direction="${Direction.BACK}"] .${cssClass('overlay__image')} { transform-origin: var(--${CSS_PREFIX}back-overlay-transform-origin); background-image: var(--${CSS_PREFIX}back-overlay-image-src); } &.${cssClass('show-vehicle-ui')} .${cssClass('overlay')} { display: initial; } & .pano, & .${cssClass('overlay')}.${cssClass('overlay__image')} { transition: opacity 300ms linear, scale 100ms linear; } } `); } function preloadOverlayImages() { const configuredOverlayImagesSources = [state.settings.frontOverlay, state.settings.sideOverlay, state.settings.backOverlay] .map((overlay) => overlay?.imageSrc) .filter((imageSrc) => !!imageSrc); for (const imageSrc of configuredOverlayImagesSources) { if (imageSrc.startsWith('data:')) { continue; } const image = new Image(); image.onload = () => { console.debug(`Successfully preloaded Look Out the Window overlay image at "${imageSrc}"`); }; image.onerror = (event) => { console.error(`Failed to preload Look Out the Window overlay image at "${imageSrc}"`, event); }; image.src = imageSrc; } } function createSettings() { const settingsTab = IRF.ui.panel.createTabFor( { ... GM.info, script: { ... GM.info.script, name: GM.info.script.name.replace('Internet Roadtrip - ', '') } }, { tabName: 'Look Out the Window', style: ` .${cssClass('settings-tab-content')} { container-name: lotw-settings-tab-content; container-type: inline-size; & * { box-sizing: border-box; } & .${cssClass('field-group')} { margin-block: 1rem; gap: 0.25rem; display: flex; justify-content: space-between; & input[type="checkbox"] { vertical-align: middle; } &.${cssClass('field-group--plain-checkbox')} { justify-content: start; & > input[type="checkbox"] + label { width: 100%; } } } & .${cssClass('overlay-settings-container')} { display: grid; gap: 0.5rem; grid-template: 1fr / repeat(4, 1fr); & .${cssClass('overlay-setting')} { position: relative; display: flex; flex-direction: column; background-color: rgba(255 255 255 / 10%); & .${cssClass('overlay-setting__header')} { padding: 0.25rem; background-color: rgba(255 255 255 / 10%); align-items: center; justify-content: space-between; display: flex; } & .${cssClass('overlay-setting__button')} { padding: 0.25rem; margin-left: 0.125rem; gap: 0.25rem; cursor: pointer; border: none; background-color: white; display: inline-flex; & > img { width: 1rem; vertical-align: middle; user-select: none; } } & .${cssClass('overlay-preview')} { position: relative; height: fit-content; min-height: 100px; margin-block: auto; cursor: pointer; overflow: hidden; /* Checkerboard */ background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAIAAAACCAYAAABytg0kAAAAAXNSR0IArs4c6QAAABtJREFUGFdj7Ovr+29oaMjAuG///v8Xzl9gAABLjgjpsdlMqQAAAABJRU5ErkJggg=="); background-repeat: repeat; background-size: 10px; image-rendering: pixelated; & .${cssClass('overlay-preview__image')} { image-rendering: revert; width: 100%; display: block; } & .${cssClass('overlay-preview__transform-origin')} { position: absolute; left: var(--transform-origin-x); top: var(--transform-origin-y); translate: -50% -50%; zoom: 7; stroke: #bb1313; stroke-width: 0.3; pointer-events: none; } &.${cssClass('overlay-preview--flipped')} { rotate: y 180deg; } &.${cssClass('overlay-preview--no-image')} { background-image: none; &::before { content: "No image set"; position: absolute; top: 50%; left: 50%; translate: -50% -50%; pointer-events: none; color: grey; } & .${cssClass('overlay-preview__image')}, & .${cssClass('overlay-preview__transform-origin')} { display: none; } } &::after { content: ""; position: absolute; top: 0; left: 0; width: 100%; height: 100%; pointer-events: none; background-color: transparent; transition: background-color 0.15s linear; } &.${cssClass('overlay-preview--dropping-file')}::after { background-color: rgb(32 213 32 / 27%); } } & .${cssClass('overlay-fields')} { margin-top: 0.25rem; & .${cssClass('field-group')} { margin: 0.25rem; } } } } @container lotw-settings-tab-content (width < 600px) { .${cssClass('overlay-settings-container')} { grid-template-columns: repeat(2, 1fr); } } } `, className: cssClass('settings-tab-content') } ); state.dom.toggleVehicleOverlayInputEl = VM.hm('input', { id: `${CSS_PREFIX}toggle-vehicle-overlay`, type: 'checkbox', className: IRF.ui.panel.styles.toggle }); state.dom.toggleVehicleOverlayInputEl.addEventListener('change', async () => { state.settings.showVehicleUi = state.dom.toggleVehicleOverlayInputEl.checked; await saveSettings(); updateUiFromSettings(); }); const showVehicleOverlayFieldGroupEl = VM.hm('div', { className: cssClass('field-group') }, [ VM.hm('label', { labelFor: `${CSS_PREFIX}toggle-vehicle-overlay` }, 'Show Vehicle Overlay'), state.dom.toggleVehicleOverlayInputEl ]); state.dom.alwaysShowGameUIInputEl = VM.hm('input', { id: `${CSS_PREFIX}always-show-game-ui`, type: 'checkbox', className: IRF.ui.panel.styles.toggle }); state.dom.alwaysShowGameUIInputEl.addEventListener('change', async () => { state.settings.alwaysShowGameUi = state.dom.alwaysShowGameUIInputEl.checked; await saveSettings(); updateUiFromSettings(); }); const alwaysShowGameUiFieldGroupEl = VM.hm('div', { className: cssClass('field-group') }, [ VM.hm('label', { labelFor: `${CSS_PREFIX}always-show-game-ui` }, 'Always show Game UI'), state.dom.alwaysShowGameUIInputEl ]); const overlaySettingsContainerGroupEl = VM.hm('div', { className: cssClass('overlay-settings-container') }); for (const { fieldId, label, overlaySetting } of [ { fieldId: `${CSS_PREFIX}front-overlay`, label: 'Front Overlay', overlaySetting: state.settings.frontOverlay }, { fieldId: `${CSS_PREFIX}back-overlay`, label: 'Back Overlay', overlaySetting: state.settings.backOverlay }, { fieldId: `${CSS_PREFIX}left-overlay`, label: 'Left Overlay', overlaySetting: state.settings.leftOverlay }, { fieldId: `${CSS_PREFIX}right-overlay`, label: 'Right Overlay', overlaySetting: state.settings.rightOverlay }, ]) { function handleFileUpload(file) { const fileReader = new window.FileReader(); fileReader.onload = async (event) => { overlaySetting.imageSrc = event.target.result; await saveSettings(); updateDom(); }; fileReader.readAsDataURL(file); } const previewImageEl = VM.hm('img', { className: cssClass('overlay-preview__image') }); const transformOriginCrosshairEl = VM.hm('svg', { xmlns: 'http://www.w3.org/2000/svg', className: cssClass('overlay-preview__transform-origin'), width: 2, height: 2 }, [ VM.h('line', { x1: 1, y1: 0, x2: 1, y2: 2 }), VM.h('line', { x1: 0, y1: 1, x2: 2, y2: 1 }) ]); const fileInputEl = VM.hm('input', { type: 'file' }); fileInputEl.addEventListener('change', () => { const file = fileInputEl.files[0]; if (!file) { return; } handleFileUpload(file); }); const previewEl = VM.hm('div', { className: cssClass('overlay-preview') }, [ previewImageEl, transformOriginCrosshairEl ]); previewEl.addEventListener('click', () => fileInputEl.click()); previewEl.addEventListener('dragover', (event) => { event.preventDefault(); const containsValidData = event.dataTransfer.types.includes("Files"); event.dataTransfer.dropEffect = containsValidData ? "move" : "none"; previewEl.classList.toggle(cssClass('overlay-preview--dropping-file'), containsValidData); }); previewEl.addEventListener('dragleave', (event) => { previewEl.classList.toggle(cssClass('overlay-preview--dropping-file'), false); }); previewEl.addEventListener('drop', (event) => { event.preventDefault(); previewEl.classList.toggle(cssClass('overlay-preview--dropping-file'), false); const file = event.dataTransfer.files[0]; if (!file) { return; } handleFileUpload(file); }); const enterUrlButtonEl = VM.hm('button', { className: cssClass('overlay-setting__button') }, [ VM.h('img', { src: 'https://www.svgrepo.com/show/474041/edit.svg' }), 'URL' ]); enterUrlButtonEl.addEventListener('click', async () => { const url = prompt([ 'Enter image URL. Ensure it ends in .png, .jpeg, etc.', 'Do not use Discord CDN links, as they will eventually expire' ].join('\n')); if (!url) { return; } overlaySetting.imageSrc = url; await saveSettings(); updateDom(); }); const deleteButtonEl = VM.hm('button', { className: cssClass('overlay-setting__button') }, [ VM.h('img', { src: 'https://www.svgrepo.com/show/533007/trash.svg' }), ]); deleteButtonEl.addEventListener('click', async () => { if (overlaySetting.imageSrc == null) { return; } if (!confirm("Are you sure you want to delete this overlay's image?")) { return; } overlaySetting.imageSrc = null; await saveSettings(); updateDom(); }); const transformOriginXInputEl = VM.hm('input', { id: `${fieldId}-transform-origin-x` }); transformOriginXInputEl.addEventListener('change', async () => { overlaySetting.transformOrigin ??= { x: '50%', y: '50%' }; overlaySetting.transformOrigin.x = transformOriginXInputEl.value; await saveSettings(); updateDom(); }); const transformOriginYInputEl = VM.hm('input', { id: `${fieldId}-transform-origin-Y` }); transformOriginYInputEl.addEventListener('change', async () => { overlaySetting.transformOrigin ??= { x: '50%', y: '50%' }; overlaySetting.transformOrigin.y = transformOriginYInputEl.value; await saveSettings(); updateDom(); }); const flipToggleInputEl = VM.hm('input', { id: `${fieldId}-flip`, type: 'checkbox' }); flipToggleInputEl.addEventListener('change', async () => { overlaySetting.flip = flipToggleInputEl.checked; await saveSettings(); updateDom(); }); const overlaySettingEl = VM.hm('div', { className: cssClass('overlay-setting') }, [ VM.h('header', { className: cssClass('overlay-setting__header') }, [ VM.h('label', { labelFor: fieldId }, label), VM.h('div', {}, [ enterUrlButtonEl, deleteButtonEl ]) ]), previewEl, VM.h('div', { className: cssClass('overlay-fields') }, [ VM.h('div', { className: cssClass(['field-group', 'field-group--plain-checkbox']) }, [ flipToggleInputEl, VM.h('label', { labelFor: `${fieldId}-flip` }, 'Flip'), ]), VM.h('div', { className: cssClass(['field-group']) }, [ VM.h('label', { labelFor: `${fieldId}-transform-origin-x` }, 'Transform Origin X'), transformOriginXInputEl, ]), VM.h('div', { className: cssClass(['field-group']) }, [ VM.h('label', { labelFor: `${fieldId}-transform-origin-y` }, 'Transform Origin Y'), transformOriginYInputEl, ]), ]), ]); overlaySettingsContainerGroupEl.appendChild(overlaySettingEl); overlaySettingEl.addEventListener('paste', (event) => { const file = event.clipboardData.files[0]; if (!file) { return; } handleFileUpload(file); }); function updateDom() { previewEl.classList.toggle(cssClass('overlay-preview--no-image'), !overlaySetting.imageSrc); if (overlaySetting.imageSrc && previewImageEl.src !== overlaySetting.imageSrc) { previewImageEl.src = overlaySetting.imageSrc; previewImageLoaded = false; } flipToggleInputEl.checked = overlaySetting.flip; previewEl.classList.toggle(cssClass('overlay-preview--flipped'), overlaySetting.flip); if (overlaySetting.transformOrigin) { transformOriginXInputEl.value = overlaySetting.transformOrigin.x; transformOriginYInputEl.value = overlaySetting.transformOrigin.y; const normalizeTransformOriginValue = (value) => ({ 'center': '50%', 'top': '0%', 'left': '0%', 'right': '100%', 'bottom': '100%', }[value] || value); transformOriginCrosshairEl.style.display = ''; transformOriginCrosshairEl.style.setProperty('--transform-origin-x', normalizeTransformOriginValue(overlaySetting.transformOrigin.x)); transformOriginCrosshairEl.style.setProperty('--transform-origin-y', normalizeTransformOriginValue(overlaySetting.transformOrigin.y)); } else { transformOriginCrosshairEl.style.display = 'none'; } updateOverlays(); } previewImageEl.addEventListener('load', () => { updateDom(); }); previewImageEl.addEventListener('error', () => { updateDom(); }); updateDom(); } settingsTab.container.append( showVehicleOverlayFieldGroupEl, alwaysShowGameUiFieldGroupEl, overlaySettingsContainerGroupEl ); } function patch(vue) { const calculateOverridenHeadingAngle = (baseHeading) => (baseHeading + state.settings.lookingDirection * 90) % 360; function replaceHeadingInPanoUrl(urlStr, vanillaHeadingOverride = null) { if (!urlStr) { return urlStr; } const url = new URL(urlStr); if (vanillaHeadingOverride != null || url.searchParams.has('heading')) { const currentHeading = vanillaHeadingOverride ?? parseFloat(url.searchParams.get('heading')); if (!Number.isNaN(currentHeading)) { url.searchParams.set('heading', calculateOverridenHeadingAngle(currentHeading)); } } return url.toString(); } vue.state.getPanoUrl = new Proxy(vue.methods.getPanoUrl, { apply(ogGetPanoUrl, thisArg, args) { const urlStr = ogGetPanoUrl.apply(thisArg, args); return replaceHeadingInPanoUrl(urlStr); } }); const panoEls = Object.keys(vue.$refs).filter((name) => name.startsWith('pano')).map((key) => vue.$refs[key]); let isVanillaTransitioning = false; { /** * For reference, this is what the vanilla code more-or-less does: * * ```js * function changeStop(..., newPano, newHeading, ...) { * // ... * this.currFrame = this.currFrame === 0 ? 1 : 0; * this.currentPano = newPano; * // ... * setTimeout(() => { * this.switchFrameOrder(); * this.currentHeading = newHeading; * // ... * }, someDelay)); * } * ``` * * Note the heading is set with a delay, after switchFrameOrder is called. */ vue.state.changeStop = new Proxy(vue.methods.changeStop, { apply(ogChangeStop, thisArg, args) { isVanillaTransitioning = true; return ogChangeStop.apply(thisArg, args); } }); function isCurrentFrameFacingTheCorrectDirection() { const currPanoSrc = panoEls[vue.state.currFrame]?.src; const currPanoUrl = currPanoSrc && new URL(currPanoSrc); if (!currPanoUrl) { return false; } const urlHeading = parseFloat(currPanoUrl.searchParams.get('heading')); if (isNaN(urlHeading)) { return false; } const correctHeading = calculateOverridenHeadingAngle(state.vue.data.currentHeading); return Math.abs(urlHeading - correctHeading) < 1e-3; } vue.state.switchFrameOrder = new Proxy(vue.methods.switchFrameOrder, { apply(ogSwitchFrameOrder, thisArg, args) { isVanillaTransitioning = false; requestIdleCallback(() => { // run after currentHeading is updated (see reference method implementation above) if (!isCurrentFrameFacingTheCorrectDirection()) { attemptManualPanoTransition(/* animate: */ true); } }); return ogSwitchFrameOrder.apply(thisArg, args); } }); } let modTransitionTimeout = null; function attemptManualPanoTransition(animate = true) { const now = Date.now(); const currFrame = vue.state.currFrame; const nextFrame = (currFrame + 1) % panoEls.length; const activePanoEl = panoEls[currFrame]; const attemptManualPanoTransitionEl = panoEls[nextFrame]; if (!activePanoEl.src) { // The vanilla code hasn't set a src on the current pano iframe yet, meaning this ran too soon. // We'll let the vanilla code do the transition for us. clearTimeout(modTransitionTimeout); return; } if (isVanillaTransitioning) { // The page will do the transition for us clearTimeout(modTransitionTimeout); return; } const newPanoUrl = replaceHeadingInPanoUrl(activePanoEl.src, state.vue.data.currentHeading); if (animate) { if (modTransitionTimeout == null) { state.vue.state.currFrame = nextFrame; attemptManualPanoTransitionEl.src = newPanoUrl; } else { clearTimeout(modTransitionTimeout); activePanoEl.src = newPanoUrl; } modTransitionTimeout = setTimeout(() => { modTransitionTimeout = null; state.vue.methods.switchFrameOrder(); }, 500); } else { activePanoEl.src = newPanoUrl; } }; state.attemptManualPanoTransition = attemptManualPanoTransition; } function updateUiFromSettings() { state.dom.toggleVehicleOverlayInputEl.checked = state.settings.showVehicleUi; document.body.classList.toggle(cssClass('show-vehicle-ui'), state.settings.showVehicleUi); state.dom.alwaysShowGameUIInputEl.checked = state.settings.alwaysShowGameUi; document.body.classList.toggle(cssClass('always-show-game-ui'), state.settings.alwaysShowGameUi); } function updateOverlays() { const setCssVariable = (element, name, value) => value ? element.style.setProperty(`--${name}`, value) : element.style.removeProperty(`--${name}`); function setOverlayCssVariables(overlayName, overlaySetting) { const cssVariable = (name) => `${CSS_PREFIX}${overlayName}-overlay-${name}`; setCssVariable( state.dom.overlayEl, cssVariable('image-src'), overlaySetting.imageSrc ? `url("${overlaySetting.imageSrc}")` : null ); setCssVariable( state.dom.overlayEl, cssVariable('transform-origin'), overlaySetting.transformOrigin ? `${overlaySetting.transformOrigin.x} ${overlaySetting.transformOrigin.y}` : null ); } setOverlayCssVariables('front', state.settings.frontOverlay); setOverlayCssVariables('back', state.settings.backOverlay); setOverlayCssVariables('left', state.settings.leftOverlay); setOverlayCssVariables('right', state.settings.rightOverlay); const lookingDirectionOverlaySettings = { [Direction.FRONT]: state.settings.frontOverlay, [Direction.RIGHT]: state.settings.rightOverlay, [Direction.BACK]: state.settings.backOverlay, [Direction.LEFT]: state.settings.leftOverlay, }[state.settings.lookingDirection]; state.dom.overlayEl.classList.toggle(cssClass('overlay--flipped'), lookingDirectionOverlaySettings?.flip ?? false); } function updateLookAt(animate = true) { document.body.dataset.lookOutTheWindowDirection = state.settings.lookingDirection; updateOverlays(); state.attemptManualPanoTransition(animate); } function updateZoom() { for (const panoIframeEl of state.dom.panoIframeEls) { panoIframeEl.style.scale = (state.settings.zoom * 0.4 + 0.6 /* parallax */).toString(); } state.dom.overlayImageEl.style.scale = state.settings.zoom.toString(); } async function saveSettings() { await GM.setValues(state.settings); } state.vue = await IRF.vdom.container; patch(state.vue); setupDom(); saveSettings(); })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址