// ==UserScript==
// @name Google Plus & Bing Plus
// @version 7.5
// @description Add Gemini response, improve speed to search results(Bing), add Google/Bing search buttons, Gemini On/Off toggle
// @author monit8280
// @match https://www.bing.com/search*
// @match https://www.google.com/search*
// @grant GM_addStyle
// @grant GM_xmlhttpRequest
// @connect generativelanguage.googleapis.com
// @connect api.cdnjs.com
// @require https://cdnjs.cloudflare.com/ajax/libs/marked/16.3.0/lib/marked.umd.js
// @license MIT
// @namespace http://tampermonkey.net/
// @downloadURL https://update.greasyfork.icu/scripts/530097/Google%20Plus%20%20Bing%20Plus.user.js
// @updateURL https://update.greasyfork.icu/scripts/530097/Google%20Plus%20%20Bing%20Plus.meta.js
// ==/UserScript==
(function () {
'use strict';
// ---------------------- Config ----------------------
const Config = {
API: {
GEMINI_MODEL: 'gemini-2.5-flash',
GEMINI_URL: 'https://generativelanguage.googleapis.com/v1beta/models/',
MARKED_CDN_URL: 'https://api.cdnjs.com/libraries/marked'
},
VERSIONS: {
MARKED_VERSION: '16.3.0'
},
CACHE: {
PREFIX: 'gemini_cache_'
},
STORAGE_KEYS: {
CURRENT_VERSION: 'markedCurrentVersion',
LATEST_VERSION: 'markedLatestVersion',
LAST_NOTIFIED: 'markedLastNotifiedVersion',
THEME_MODE: 'themeMode', // 테마 모드 저장
GEMINI_ENABLED: 'geminiEnabled' // Gemini On/Off 상태 저장
},
UI: {
DEFAULT_MARGIN: 8,
DEFAULT_PADDING: 16,
Z_INDEX: 9999
},
STYLES: {
COLORS: {
BACKGROUND_LIGHT: '#fff',
BACKGROUND_DARK: '#282c34',
BORDER_LIGHT: '#e0e0e0',
BORDER_DARK: '#444',
TEXT_LIGHT: '#000',
TEXT_DARK: '#e0e0e0',
TITLE_LIGHT: '#000',
TITLE_DARK: '#ffffff',
BUTTON_BG_LIGHT: '#f0f3ff',
BUTTON_BG_DARK: '#3a3f4b',
BUTTON_BORDER_LIGHT: '#ccc',
BUTTON_BORDER_DARK: '#555',
CODE_BLOCK_BG_LIGHT: '#f0f0f0',
CODE_BLOCK_BG_DARK: '#3b3b3b',
},
BORDER_RADIUS: '4px',
FONT_SIZE: {
TEXT: '14px',
TITLE: '18px'
},
ICON_SIZE: '20px',
LOGO_SIZE: '24px',
SMALL_ICON_SIZE: '16px'
},
ASSETS: {
GOOGLE_LOGO: 'https://www.gstatic.com/images/branding/searchlogo/ico/favicon.ico',
BING_LOGO: 'https://account.microsoft.com/favicon.ico',
GEMINI_LOGO: 'https://www.gstatic.com/lamda/images/gemini_sparkle_aurora_33f86dc0c0257da337c63.svg',
REFRESH_ICON: 'data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz48IS0tIFVwbG9hZGVkIHRvOiBTVkcgUmVwbywgd3d3LnN2Z3JlcG8uY29tLCBHZW5lcmF0b3I6IFNWRyBSZXBvIE1peGVyIFRvb2xzIC0tPg0KPHN2ZyB3aWR0aD0iODAwcHgiIGhlaWdodD0iODAwcHgiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4NCjxwYXRoIGQ9Ik00LjA2MTg5IDEzQzQuMDIxMDQgMTIuNjcyNCA0IDEyLjMzODcgNCAxMkM0IDcuNTgxNzIgNy41ODE3MiA0IDEyIDRDMTQuNTAwNiA0IDE2LjczMzIgNS4xNDcyNyAxOC4yMDAyIDYuOTQ0MTZNMTkuOTM4MSAxMUMxOS45NzkgMTEuMzI3NiAyMCAxMS42NjEzIDIwIDEyQzIwIDE2LjQxODMgMTYuNDE4MyAyMCAxMiAyMEM5LjYxMDYxIDIwIDcuNDY1ODkgMTguOTUyNSA2IDE3LjI5MTZNOSAxN0g2VjE3LjI5MTZNMTguMjAwMiA0VjYuOTQ0MTZNMTguMjAwMiA2Ljk0NDE2VjYuOTk5OTNMMTUuMjAwMiA3TTYgMjBWMTcuMjkxNiIgc3Ryb2tlPSIjMDAwMDAwIiBzdHJva2Utd2lkdGg9IjIiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIvPg0KPC9zdmc+',
LIGHT_MODE_ICON: 'data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4NCjwhLS0gVXBsb2FkZWQgdG86IFNWRyBSZXBvLCB3d3cuc3ZncmVwby5jb20sIEdlbmVyYXRvcjogU1ZHIFJlcG8gTWl4ZXIgVG9vbHMgLS0+DQo8c3ZnIHdpZHRoPSI4MDBweCIgaGVpZ2h0PSI4MDBweCIgdmlld0JveD0iMCAwIDEwMjQgMTAyNCIgY2xhc3M9Imljb24iICB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0iTTg2MSA2NTYuN2wxNDQuNi0xNDQuNkw4NjEgMzY3LjZWMTYzLjFINjU2LjZMNTEyIDE4LjYgMzY3LjQgMTYzLjFIMTYzdjIwNC41TDE4LjQgNTEyLjEgMTYzIDY1Ni43djIwNC40aDIwNC40TDUxMiAxMDA1LjdsMTQ0LjYtMTQ0LjZIODYxeiIgZmlsbD0iI0ZDRDE3MCIgLz48cGF0aCBkPSJNNTEyIDEwMTUuN2MtMi42IDAtNS4xLTEtNy4xLTIuOUwzNjMuMyA4NzEuMUgxNjNjLTUuNSAwLTEwLTQuNS0xMC0xMFY2NjAuOEwxMS40IDUxOS4yYy0xLjktMS45LTIuOS00LjQtMi45LTcuMSAwLTIuNyAxLjEtNS4yIDIuOS03LjFMMTUzIDM2My40VjE2My4xYzAtNS41IDQuNS0xMCAxMC0xMGgyMDAuM0w1MDQuOSAxMS41YzEuOS0xLjkgNC40LTIuOSA3LjEtMi45czUuMiAxLjEgNy4xIDIuOWwxNDEuNiAxNDEuNkg4NjFjNS41IDAgMTAgNC41IDEwIDEwdjIwMC4zTDEwMTIuNiA1MDVjMS45IDEuOSAyLjkgNC40IDIuOSA3LjEgMCAyLjctMS4xIDUuMi0yLjkgNy4xTDg3MSA2NjAuOHYyMDAuM2MwIDUuNS00LjUgMTAtMTAgMTBINjYwLjdsLTE0MS42IDE0MS42Yy0yIDItNC41IDMtNy4xIDN6TTE3MyA4NTEuMWgxOTQuNGMyLjcgMCA1LjIgMS4xIDcuMSAyLjlMNTEyIDk5MS42bDEzNy41LTEzNy41YzEuOS0xLjkgNC40LTIuOSA3LjEtMi45SDg1MVY2NTYuN2MwLTIuNyAxLjEtNS4yIDIuOS03LjFsMTM3LjUtMTM3LjUtMTM3LjUtMTM3LjVjLTEuOS0xLjktMi45LTQuNC0yLjktNy4xVjE3My4xSDY1Ni42Yy0yLjcgMC01LjItMS4xLTcuMS0yLjlMNTEyIDMyLjcgMzc0LjUgMTcwLjJjLTEuOSAxLjktNC40IDIuOS03LjEgMi45SDE3M3YxOTQuNGMwIDIuNy0xLjEgNS4yLTIuOSA3LjFMMzIuNiA1MTIuMWwxMzcuNSAxMzcuNWMxLjkgMS45IDIuOSA0LjQgMi45IDcuMXYxOTQuNHoiIGZpbGw9IiIgLz48cGF0aCBkPSJNNTEyIDUxMi4xbS0yNTcuOCAwYTI1Ny44IDI1Ny44IDAgMSAwIDUxNS42IDAgMjU3LjggMjU3LjggMCAxIDAtNTE1LjYgMFoiIGZpbGw9IiNGN0REQUQiIC8+PHBhdGggZD0iTTUxMiA3NzkuOWMtNzEuNSAwLTEzOC44LTI3LjktMTg5LjQtNzguNC01MC42LTUwLjYtNzguNC0xMTcuOC03OC40LTE4OS40czI3LjktMTM4LjggNzguNC0xODkuNGM1MC42LTUwLjYgMTE3LjgtNzguNCAxODkuNC03OC40IDcxLjUgMCAxMzguOCAyNy45IDE4OS40IDc4LjQgNTAuNiA1MC42IDc4LjQgMTE3LjggNzguNCAxODkuNFM3NTIgNjUwLjkgNzAxLjQgNzAxLjUgNTgzLjUgNzc5LjkgNTEyIDc3OS45eiBtMC01MTUuNmMtNjYuMiAwLTEyOC40IDI1LjgtMTc1LjIgNzIuNi00Ni44IDQ2LjgtNzIuNiAxMDktNzIuNiAxNzUuMnMyNS44IDEyOC40IDcyLjYgMTc1LjJjNDYuOCA0Ni44IDEwOSA3Mi42IDE3NS4yIDcyLjYgNjYuMiAwIDEyOC40LTI1LjggMTc1LjItNzIuNiA0Ni44LTQ2LjggNzIuNi0xMDkgNzIuNi0xNzUuMlM3MzQgMzgzLjcgNjg3LjIgMzM2LjljLTQ2LjgtNDYuOC0xMDktNzIuNi0xNzUuMi03Mi42eiIgZmlsbD0iIiAvPjwvc3ZnPg==', // 라이트 모드 아이콘
DARK_MODE_ICON: 'data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz48IS0tIFVwbG9hZGVkIHRvOiBTVkcgUmVwbywgd3d3LnN2Z3JlcG8uY29tLCBHZW5lcmF0b3I6IFNWRyBSZXBvIE1peGVyIFRvb2xzIC0tPg0KPHN2ZyB3aWR0aD0iODAwcHgiIGhlaWdodD0iODAwcHgiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4NCjxwYXRoIGQ9Ik0xOS45MDAxIDIuMzA3MTlDMTkuNzM5MiAxLjg5NzYgMTkuMTYxNiAxLjg5NzYgMTkuMDAwNyAyLjMwNzE5TDE4LjU3MDMgMy40MDI0N0MxOC41MjEyIDMuNTI3NTIgMTguNDIyNiAzLjYyNjUxIDE4LjI5OCAzLjY3NTgzTDE3LjIwNjcgNC4xMDc4QzE2Ljc5ODYgNC4yNjkzNCAxNi43OTg2IDQuODQ5IDE3LjIwNjcgNS4wMTA1NEwxOC4yOTggNS40NDI1MkMxOC40MjI2IDUuNDkxODQgMTguNTIxMiA1LjU5MDgyIDE4LjU3MDMgNS43MTU4N0wxOS4wMDA3IDYuODExMTVDMTkuMTYxNiA3LjIyMDc0IDE5LjczOTIgNy4yMjA3NCAxOS45MDAxIDYuODExMTZMMjAuMzMwNSA1LjcxNTg3QzIwLjM3OTYgNS41OTA4MiAyMC40NzgyIDUuNDkxODQgMjAuNjAyOCA1LjQ0MjUyTDIxLjY5NDEgNS4wMTA1NEMyMi4xMDIyIDQuODQ5IDIyLjEwMjIgNC4yNjkzNCAyMS42OTQxIDQuMTA3OEwyMC42MDI4IDMuNjc1ODNDMjAuNDc4MiAzLjYyNjUxIDIwLjM3OTYgMy41Mjc1MiAyMC4zMzA1IDMuNDAyNDdMMTkuOTAwMSAyLjMwNzE5WiIgZmlsbD0iIzFDMjc0QyIvPg0KPHBhdGggZD0iTTE2LjAzMjggOC4xMjk2N0MxNS44NzE4IDcuNzIwMDkgMTUuMjk0MyA3LjcyMDA5IDE1LjEzMzMgOC4xMjk2N0wxNC45NzY0IDguNTI5MDJDMTQuOTI3MyA4LjY1NDA3IDE0LjgyODcgOC43NTMwNSAxNC43MDQxIDguODAyMzdMMTQuMzA2MiA4Ljk1OTg3QzEzLjg5ODEgOS4xMjE0MSAxMy44OTgxIDkuNzAxMDcgMTQuMzA2MiA5Ljg2MjYxTDE0LjcwNDEgMTAuMDIwMUMxNC44Mjg3IDEwLjA2OTQgMTQuOTI3MyAxMC4xNjg0IDE0Ljk3NjQgMTAuMjkzNUwxNS4xMzMzIDEwLjY5MjhDMTUuMjk0MyAxMS4xMDI0IDE1Ljg3MTggMTEuMTAyNCAxNi4wMzI4IDEwLjY5MjhMMTYuMTg5NyAxMC4yOTM1QzE2LjIzODggMTAuMTY4NCAxNi4zMzc0IDEwLjA2OTQgMTYuNDYyIDEwLjAyMDFMMTYuODU5OSA5Ljg2MjYxQzE3LjI2OCA5LjcwMTA3IDE3LjI2OCA5LjEyMTQxIDE2Ljg1OTkgOC45NTk4N0wxNi40NjIgOC44MDIzN0MxNi4zMzc0IDguNzUzMDUgMTYuMjM4OCA4LjY1NDA3IDE2LjE4OTcgOC41MjkwMkwxNi4wMzI4IDguMTI5NjdaIiBmaWxsPSIjMUMyNzRDIi8+DQo8cGF0aCBkPSJNMTIgMjJDMTcuNTIyOCAyMiAyMiAxNy41MjI4IDIyIDEyQzIyIDExLjUzNzMgMjEuMzA2NSAxMS40NjA4IDIxLjA2NzIgMTEuODU2OEMxOS45Mjg5IDEzLjc0MDYgMTcuODYxNSAxNSAxNS41IDE1QzExLjkxMDEgMTUgOSAxMi4wODk5IDkgOC41QzkgNi4xMzg0NSAxMC4yNTk0IDQuMDcxMDUgMTIuMTQzMiAyLjkzMjc2QzEyLjUzOTIgMi42OTM0NyAxMi40NjI3IDIgMTIgMkM2LjQ3NzE1IDIgMiA2LjQ3NzE1IDIgMTJDMiAxNy41MjI4IDYuNDc3MTUgMjIgMTIgMjJaIiBmaWxsPSIjMUMyNzRDIi8+DQo8L3N2Zz4=' // 다크 모드 아이콘
},
MESSAGE_KEYS: {
PROMPT: 'prompt',
ENTER_API_KEY: 'enterApiKey',
GEMINI_EMPTY: 'geminiEmpty',
PARSE_ERROR: 'parseError',
NETWORK_ERROR: 'networkError',
TIMEOUT: 'timeout',
LOADING: 'loading',
UPDATE_TITLE: 'updateTitle',
UPDATE_NOW: 'updateNow',
SEARCH_ON_GOOGLE: 'searchongoogle',
SEARCH_ON_BING: 'searchonbing',
GEMINI_OFF: 'geminiOff'
}
};
// ---------------------- Localization ----------------------
const Localization = {
MESSAGES: {
[Config.MESSAGE_KEYS.PROMPT]: {
ko: `"${'${query}'}"에 대한 정보를 찾아줘`,
zh: `请以标记格式填写有关\"${'${query}'}\"的信息。`,
default: `Please write information about \"${'${query}'}\" in markdown format`
},
[Config.MESSAGE_KEYS.ENTER_API_KEY]: {
ko: 'Gemini API 키를 입력하세요:',
zh: '请输入 Gemini API 密钥:',
default: 'Please enter your Gemini API key:'
},
[Config.MESSAGE_KEYS.GEMINI_EMPTY]: {
ko: '⚠️ Gemini 응답이 비어있습니다.',
zh: '⚠️ Gemini 返回为空。',
default: '⚠️ Gemini response is empty.'
},
[Config.MESSAGE_KEYS.PARSE_ERROR]: {
ko: '❌ 파싱 오류:',
zh: '❌ 解析错误:',
default: '❌ Parsing error:'
},
[Config.MESSAGE_KEYS.NETWORK_ERROR]: {
ko: '❌ 네트워크 오류:',
zh: '❌ 网络错误:',
default: '❌ Network error:'
},
[Config.MESSAGE_KEYS.TIMEOUT]: {
ko: '❌ 요청 시간이 초과되었습니다.',
zh: '❌ 请求超时。',
default: '❌ Request timeout'
},
[Config.MESSAGE_KEYS.LOADING]: {
ko: '불러오는 중...',
zh: '加载中...',
default: 'Loading...'
},
[Config.MESSAGE_KEYS.UPDATE_TITLE]: {
ko: 'marked.min.js 업데이트 필요',
zh: '需要更新 marked.min.js',
default: 'marked.min.js update required'
},
[Config.MESSAGE_KEYS.UPDATE_NOW]: {
ko: '확인',
zh: '确认',
default: 'OK'
},
[Config.MESSAGE_KEYS.SEARCH_ON_GOOGLE]: {
ko: 'Google 에서 검색하기',
zh: '在 Google 上搜索',
default: 'Search on Google'
},
[Config.MESSAGE_KEYS.SEARCH_ON_BING]: {
ko: 'Bing 에서 검색하기',
zh: '在 Bing 上搜索',
default: 'Search on Bing'
},
[Config.MESSAGE_KEYS.GEMINI_OFF]: {
ko: '현재 Gemini 옵션이 OFF 상태입니다.',
zh: '当前 Gemini 选项为关闭状态。',
default: 'Gemini option is currently OFF.'
}
},
getMessage(key, vars = {}) {
const lang = navigator.language;
const langKey = lang.includes('ko') ? 'ko' : lang.includes('zh') ? 'zh' : 'default';
const template = this.MESSAGES[key]?.[langKey] || this.MESSAGES[key]?.default || '';
return template.replace(/\$\{(.*?)\}/g, (_, k) => vars[k] || '');
}
};
// ---------------------- Device Detector ----------------------
const DeviceDetector = {
_cache: {
deviceType: null,
isGeminiAvailable: null
},
getDeviceType() {
if (this._cache.deviceType !== null) return this._cache.deviceType;
const userAgent = navigator.userAgent;
const isTouchDevice = 'ontouchstart' in window || navigator.maxTouchPoints > 0;
const width = window.innerWidth;
let deviceType;
const isAndroid = /Android/i.test(userAgent);
const isIPhone = /iPhone/i.test(userAgent);
const hasMobileKeyword = /Mobile/i.test(userAgent);
const isWindows = /Windows NT/i.test(userAgent);
if (isWindows && !isTouchDevice && width > 1024) deviceType = 'desktop';
else if ((isAndroid || isIPhone) && hasMobileKeyword) deviceType = 'mobile';
else if (isAndroid && !hasMobileKeyword && width >= 768) deviceType = 'tablet';
else if (isTouchDevice && width <= 1024) deviceType = 'mobile';
else deviceType = 'desktop';
this._cache.deviceType = deviceType;
return deviceType;
},
isDesktop() { return this.getDeviceType() === 'desktop'; },
isMobile() { return this.getDeviceType() === 'mobile'; },
isTablet() { return this.getDeviceType() === 'tablet'; },
isGeminiAvailable() {
if (this._cache.isGeminiAvailable === null) {
const hasRHS = !!document.getElementById('rhs') || !!document.getElementById('b_context') || !!document.querySelector('.b_right');
this._cache.isGeminiAvailable = this.isDesktop() && hasRHS;
}
return this._cache.isGeminiAvailable;
},
resetCache() {
this._cache = { deviceType: null, isGeminiAvailable: null };
},
isGoogle() { return window.location.hostname.includes('google.com'); },
isBing() { return window.location.hostname.includes('bing.com'); }
};
// ---------------------- Styles ----------------------
const StyleGenerator = {
commonStyles: {
'#b_results > li.b_ad a': { 'color': 'green !important' },
'#b_context, .b_context, .b_right': {
'color': 'initial !important',
'border': 'none !important',
'border-width': '0 !important',
'border-style': 'none !important',
'border-collapse': 'separate !important',
'background': 'transparent !important'
},
'#rhs': {
'float': 'right',
'padding-left': '16px',
'width': '432px',
'margin-top': '20px'
},
'#rhs #gemini-wrapper': { 'margin-bottom': '20px' },
'.mobile-useragent #gsr': { 'background-color': '#ffffff !important' }
},
geminiBoxStyles: {
'#gemini-box': {
'width': '100%',
'max-width': '100%',
'border-width': '1px',
'border-style': 'solid',
'border-radius': Config.STYLES.BORDER_RADIUS,
'padding': `${Config.UI.DEFAULT_PADDING}px`,
'margin-bottom': `${Config.UI.DEFAULT_MARGIN * 2.5}px`,
'font-family': 'sans-serif',
'overflow-x': 'auto',
'position': 'relative',
'box-sizing': 'border-box',
'color': 'initial !important'
}
},
themeStyles: {
'#gemini-box': {
'background': `var(--gemini-background-color) !important`,
'border-color': `var(--gemini-border-color) !important`
},
'#gemini-box h3': { 'color': `var(--gemini-title-color) !important` },
'#gemini-content, #gemini-content *': {
'color': `var(--gemini-text-color) !important`,
'background': 'transparent !important'
},
'#gemini-divider': { 'background': `var(--gemini-border-color) !important` },
'#gemini-content pre': {
'background': `var(--gemini-code-block-bg) !important`,
'padding': `${Config.UI.DEFAULT_MARGIN + 2}px`,
'border-radius': Config.STYLES.BORDER_RADIUS,
'overflow-x': 'auto'
},
'#google-search-btn, #bing-search-btn': {
'border-color': `var(--gemini-button-border)`,
'background-color': `var(--gemini-button-bg)`,
'color': `var(--gemini-title-color)`,
},
'#marked-update-popup': {
'background': `var(--gemini-background-color)`,
'border-color': `var(--gemini-button-border)`,
},
'#marked-update-popup button': {
'border-color': `var(--gemini-button-border)`,
'background-color': `var(--gemini-button-bg)`,
'color': `var(--gemini-title-color)`,
}
},
contentStyles: {
'#gemini-content': {
'font-size': Config.STYLES.FONT_SIZE.TEXT,
'line-height': '1.6',
'white-space': 'pre-wrap',
'word-wrap': 'break-word',
'overflow-wrap': 'break-word',
'background': 'transparent !important'
},
'#gemini-content ul, #gemini-content ol': { 'list-style-type': 'none' }
},
headerStyles: {
'#gemini-header': {
'display': 'flex',
'align-items': 'center',
'justify-content': 'space-between',
'margin-bottom': `${Config.UI.DEFAULT_MARGIN}px`
},
'#gemini-title-wrap': {
'display': 'flex',
'align-items': 'center'
},
'#gemini-logo': {
'width': Config.STYLES.LOGO_SIZE,
'height': Config.STYLES.LOGO_SIZE,
'margin-right': `${Config.UI.DEFAULT_MARGIN}px`
},
'#gemini-box h3': {
'margin': '0',
'font-size': Config.STYLES.FONT_SIZE.TITLE,
'font-weight': 'bold'
},
'#gemini-toggle-switch': {
'margin-right': `${Config.UI.DEFAULT_MARGIN}px`,
'display': 'flex',
'align-items': 'center'
},
'#gemini-refresh-btn': {
'width': Config.STYLES.ICON_SIZE,
'height': Config.STYLES.ICON_SIZE,
'cursor': 'pointer',
'opacity': '0.6',
'transition': 'transform 0.5s ease',
'margin-left': `${Config.UI.DEFAULT_MARGIN}px`,
'filter': 'var(--gemini-icon-filter)'
},
'#gemini-theme-toggle-btn': {
'width': Config.STYLES.ICON_SIZE,
'height': Config.STYLES.ICON_SIZE,
'cursor': 'pointer',
'opacity': '0.6',
'transition': 'transform 0.5s ease'
},
'#gemini-refresh-btn:hover, #gemini-theme-toggle-btn:hover': {
'opacity': '1',
'transform': 'rotate(360deg)'
},
'#gemini-divider': {
'height': '1px',
'margin': `${Config.UI.DEFAULT_MARGIN}px 0`
}
},
searchButtonStyles: {
'#google-search-btn, #bing-search-btn': {
'width': '100%',
'max-width': '100%',
'font-size': Config.STYLES.FONT_SIZE.TEXT,
'padding': `${Config.UI.DEFAULT_MARGIN}px`,
'margin-bottom': `${Config.UI.DEFAULT_MARGIN * 1.25}px`,
'cursor': 'pointer',
'border-width': '1px',
'border-style': 'solid',
'border-radius': Config.STYLES.BORDER_RADIUS,
'font-family': 'sans-serif',
'display': 'flex',
'align-items': 'center',
'justify-content': 'center',
'gap': `${Config.UI.DEFAULT_MARGIN}px`,
'transition': 'transform 0.2s ease'
},
'#google-search-btn img, #bing-search-btn img': {
'width': Config.STYLES.SMALL_ICON_SIZE,
'height': Config.STYLES.SMALL_ICON_SIZE,
'vertical-align': 'middle',
'transition': 'transform 0.2s ease'
},
'.desktop-useragent #google-search-btn:hover, .desktop-useragent #bing-search-btn:hover': {
'transform': 'scale(1.1)'
},
'.desktop-useragent #google-search-btn:hover img, .desktop-useragent #bing-search-btn:hover img': {
'transform': 'scale(1.1)'
}
},
popupStyles: {
'#marked-update-popup': {
'position': 'fixed',
'top': '30%',
'left': '50%',
'transform': 'translate(-50%, -50%)',
'padding': `${Config.UI.DEFAULT_PADDING * 1.25}px`,
'z-index': Config.UI.Z_INDEX,
'border-width': '1px',
'border-style': 'solid',
'box-shadow': '0 2px 10px rgba(0,0,0,0.1)',
'text-align': 'center'
},
'#marked-update-popup button': {
'margin-top': `${Config.UI.DEFAULT_MARGIN * 1.25}px`,
'padding': `${Config.UI.DEFAULT_PADDING}px ${Config.UI.DEFAULT_PADDING}px`,
'cursor': 'pointer',
'border-width': '1px',
'border-style': 'solid',
'border-radius': Config.STYLES.BORDER_RADIUS,
'font-family': 'sans-serif'
}
},
mobileStyles: {
'.mobile-useragent #google-search-btn, .mobile-useragent #bing-search-btn': {
'max-width': '100%',
'width': 'calc(100% - 16px)',
'margin-left': `${Config.UI.DEFAULT_MARGIN}px !important`,
'margin-right': `${Config.UI.DEFAULT_MARGIN}px !important`,
'margin-top': `${Config.UI.DEFAULT_MARGIN}px`,
'margin-bottom': `${Config.UI.DEFAULT_MARGIN}px`,
'padding': `${Config.UI.DEFAULT_PADDING * 0.75}px`,
'border-radius': '16px',
'box-sizing': 'border-box'
},
'.mobile-useragent #gemini-box': {
'padding': `${Config.UI.DEFAULT_PADDING * 0.75}px`,
'border-radius': '16px'
},
'.mobile-useragent #b_content': {
'overflow': 'visible !important',
'position': 'relative'
}
},
generateStyles() {
const styles = [
this.commonStyles,
this.geminiBoxStyles,
this.themeStyles,
this.contentStyles,
this.headerStyles,
this.searchButtonStyles,
this.popupStyles,
this.mobileStyles
];
const cssVariables = `
:root {
--gemini-background-color: ${Config.STYLES.COLORS.BACKGROUND_LIGHT};
--gemini-border-color: ${Config.STYLES.COLORS.BORDER_LIGHT};
--gemini-text-color: ${Config.STYLES.COLORS.TEXT_LIGHT};
--gemini-title-color: ${Config.STYLES.COLORS.TITLE_LIGHT};
--gemini-button-bg: ${Config.STYLES.COLORS.BUTTON_BG_LIGHT};
--gemini-button-border: ${Config.STYLES.COLORS.BUTTON_BORDER_LIGHT};
--gemini-code-block-bg: ${Config.STYLES.CODE_BLOCK_BG_LIGHT};
--gemini-icon-filter: none;
}
.dark-mode {
--gemini-background-color: ${Config.STYLES.COLORS.BACKGROUND_DARK};
--gemini-border-color: ${Config.STYLES.COLORS.BORDER_DARK};
--gemini-text-color: ${Config.STYLES.COLORS.TEXT_DARK};
--gemini-title-color: ${Config.STYLES.COLORS.TITLE_DARK};
--gemini-button-bg: ${Config.STYLES.COLORS.BUTTON_BG_DARK};
--gemini-button-border: ${Config.STYLES.COLORS.BUTTON_BORDER_DARK};
--gemini-code-block-bg: ${Config.STYLES.CODE_BLOCK_BG_DARK};
--gemini-icon-filter: invert(1);
}
`;
return cssVariables + styles.reduce((css, styleObj) => {
for (const [selector, props] of Object.entries(styleObj)) {
css += `${selector} {`;
for (const [prop, value] of Object.entries(props)) {
css += `${prop}: ${value};`;
}
css += '}';
}
return css;
}, '');
}
};
const Styles = {
initStyles() {
const styleElement = document.createElement('style');
styleElement.id = 'bing-plus-styles';
styleElement.textContent = StyleGenerator.generateStyles();
document.head.appendChild(styleElement);
this.applyMobileStyles();
},
applyMobileStyles() {
if (DeviceDetector.isMobile()) document.documentElement.classList.add('mobile-useragent');
else if (DeviceDetector.isDesktop()) document.documentElement.classList.add('desktop-useragent');
}
};
// ---------------------- Theme Manager ----------------------
const ThemeManager = {
currentTheme: 'light',
init() {
const savedTheme = localStorage.getItem(Config.STORAGE_KEYS.THEME_MODE);
if (savedTheme) this.currentTheme = savedTheme;
else if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) this.currentTheme = 'dark';
this.applyTheme();
},
applyTheme() {
if (this.currentTheme === 'dark') document.documentElement.classList.add('dark-mode');
else document.documentElement.classList.remove('dark-mode');
},
toggleTheme() {
this.currentTheme = this.currentTheme === 'light' ? 'dark' : 'light';
localStorage.setItem(Config.STORAGE_KEYS.THEME_MODE, this.currentTheme);
this.applyTheme();
this.updateThemeToggleButtonIcon();
},
getThemeToggleButtonIcon() {
return this.currentTheme === 'light' ? Config.ASSETS.DARK_MODE_ICON : Config.ASSETS.LIGHT_MODE_ICON;
},
updateThemeToggleButtonIcon() {
const themeToggleButton = document.getElementById('gemini-theme-toggle-btn');
if (themeToggleButton) {
themeToggleButton.src = this.getThemeToggleButtonIcon();
themeToggleButton.title = this.currentTheme === 'light' ? 'Dark Mode' : 'Light Mode';
}
},
observeThemeChange() {
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', (e) => {
const newTheme = e.matches ? 'dark' : 'light';
if (this.currentTheme !== newTheme) {
this.currentTheme = newTheme;
localStorage.setItem(Config.STORAGE_KEYS.THEME_MODE, this.currentTheme);
this.applyTheme();
this.updateThemeToggleButtonIcon();
}
});
}
};
// ---------------------- Utils ----------------------
const Utils = {
getQuery() {
return new URLSearchParams(location.search).get('q');
},
getApiKey() {
let key = localStorage.getItem('geminiApiKey');
if (!key) {
key = prompt(Localization.getMessage(Config.MESSAGE_KEYS.ENTER_API_KEY));
if (key) localStorage.setItem('geminiApiKey', key);
}
return key;
},
getGeminiEnabled() {
const val = localStorage.getItem(Config.STORAGE_KEYS.GEMINI_ENABLED);
return val === null ? true : val === 'true';
},
setGeminiEnabled(enabled) {
localStorage.setItem(Config.STORAGE_KEYS.GEMINI_ENABLED, enabled ? 'true' : 'false');
}
};
// ---------------------- UI ----------------------
const UI = {
createSearchButton(query) {
const btn = document.createElement('button');
if (DeviceDetector.isGoogle()) {
btn.id = 'bing-search-btn';
btn.innerHTML = `
${Localization.getMessage(Config.MESSAGE_KEYS.SEARCH_ON_BING)}
`;
btn.onclick = () => window.open(`https://www.bing.com/search?q=${encodeURIComponent(query)}`, '_blank');
} else {
btn.id = 'google-search-btn';
btn.innerHTML = `
${Localization.getMessage(Config.MESSAGE_KEYS.SEARCH_ON_GOOGLE)}
`;
btn.onclick = () => window.open(`https://www.google.com/search?q=${encodeURIComponent(query)}`, '_blank');
}
return btn;
},
createGeminiToggleSwitch(enabled, onToggle) {
// 첨부 이미지와 유사하게 구현
const wrapper = document.createElement('div');
wrapper.style.display = 'flex';
wrapper.style.alignItems = 'center';
wrapper.style.gap = '6px';
wrapper.style.height = '28px';
wrapper.style.marginRight = '10px';
// Toggle bg
const toggle = document.createElement('div');
toggle.style.width = '44px';
toggle.style.height = '24px';
toggle.style.borderRadius = '12px';
toggle.style.position = 'relative';
toggle.style.cursor = 'pointer';
toggle.style.background = enabled ? '#d1d5db' : '#353535';
toggle.style.transition = 'background 0.2s';
// Knob
const knob = document.createElement('div');
knob.style.width = '24px';
knob.style.height = '24px';
knob.style.borderRadius = '50%';
knob.style.background = enabled ? '#fff' : '#777';
knob.style.position = 'absolute';
knob.style.top = '0';
knob.style.left = enabled ? '20px' : '0';
knob.style.boxShadow = '0 1px 3px rgba(0,0,0,0.10)';
knob.style.transition = 'left 0.2s, background 0.2s';
toggle.appendChild(knob);
toggle.onclick = () => {
const newState = !enabled;
onToggle(newState);
};
// ON/OFF text
const stateText = document.createElement('span');
stateText.textContent = enabled ? 'ON' : 'OFF';
stateText.style.color = enabled ? 'var(--gemini-title-color)' : '#bbb';
stateText.style.fontWeight = 'bold';
stateText.style.fontSize = '14px';
stateText.style.width = '32px';
wrapper.appendChild(toggle);
wrapper.appendChild(stateText);
wrapper.update = (en) => {
toggle.style.background = en ? '#d1d5db' : '#353535';
knob.style.left = en ? '20px' : '0';
knob.style.background = en ? '#fff' : '#777';
stateText.textContent = en ? 'ON' : 'OFF';
stateText.style.color = en ? 'var(--gemini-title-color)' : '#bbb';
};
return wrapper;
},
createGeminiBox(query, apiKey) {
const box = document.createElement('div');
box.id = 'gemini-box';
// Gemini On/Off 상태
const enabled = Utils.getGeminiEnabled();
box.innerHTML = `
${Localization.getMessage(Config.MESSAGE_KEYS.UPDATE_TITLE)}
Current: ${Config.VERSIONS.MARKED_VERSION}
Latest: ${latest}