Skip to content

Instantly share code, notes, and snippets.

@VarunBatraIT
Created January 29, 2026 07:55
Show Gist options
  • Select an option

  • Save VarunBatraIT/e82dfc7d3e966ca4ca76fa17362e0688 to your computer and use it in GitHub Desktop.

Select an option

Save VarunBatraIT/e82dfc7d3e966ca4ca76fa17362e0688 to your computer and use it in GitHub Desktop.
// ==UserScript==
// @name Lichess Game Play with Questions and Timer Quotes
// @namespace vm-lichess-game-play
// @version 1.1
// @description Interactive questions + timer quotes for Lichess
// @match https://lichess.org/*
// @run-at document-start
// @grant none
// ==/UserScript==
(function () {
'use strict';
// ======== OPENING PRINCIPLES ========
const openingPrinciples = [
"🎯 Control the Center",
"🐴 Develop Minor Pieces Early",
"👸 Don't Bring the Queen Out Too Early",
"🚫 Avoid Unnecessary Pawn Moves",
"🏰 Castle Early",
"👑 King Safety First",
"⭕ Once Developed, break the center",
"🔄 Avoid Moving Same Piece Twice in Opening",
"🛡️ Keep Your Pieces Protected",
"♟️ Watch for f pawn",
"🏁 Finish Development Before Attacking",
"🔚 Pawn structures will define the end-game"
];
// ======== MIDGAME PRINCIPLES ========
const midgamePrinciples = [
"🔗 Connect the Rooks",
"⚠️ To Take is a Mistake if Position Worsens",
"↗️ Put Bishops on Long Diagonals",
"📂 Put Rooks on Open/Half-Open Files",
"🎪 Put Knights in the Center",
"♞ Put knights on outposts",
"🤝 Coordinate Your Pieces",
"🌟 Rooks on the Seventh Rank",
"🐴 Keep Your Pieces Active",
"🧱 Maintain Pawn Structure",
"🏰 Don't Move Pawns in Front of Castled King",
"⬅️ Capture Pawns Towards Center",
"👁️ Watch Your Opponent's Threats",
"🎯 Control Key Squares",
"⚖️ Trade Pieces When Ahead in Material",
"💎 Avoid Trading Your Strong Pieces",
"🚀 Create Passed Pawns",
"🛑 Avoid Creating Weaknesses",
"🔍 Look for Tactical Opportunities",
"⚖️ When Behind in Material, Avoid Unnecessary Trades",
"🔁 Improve Your Worst-Placed Piece",
"📏 Use Space Advantage Wisely",
"🧮 Evaluate Exchanges: Material vs Activity vs Structure",
"🧱 Fix Opponent Weaknesses Before Creating Your Own",
"🪤 Prophylaxis: Ask 'What Does My Opponent Want?'",
"⚔️ Avoid Attacking Without Development Advantage"
];
// ======== ENDGAME PRINCIPLES ========
const endgamePrinciples = [
"👑 In end-game King is a piece",
"📉 Simplify Into Winning Endgames, Not Just Any Endgame",
"🚶 Activate the King Early in the Endgame",
"♟️ Passed Pawns Must Be Pushed",
"📂 Rook Activity Over Pawn Grabbing",
"🎯 Centralize Pieces in the Endgame"
];
// ======== GENERAL PRINCIPLES ========
const generalPrinciples = [
"🧠 Think Ahead and Plan Your Moves",
"⏰ Use All Your Time Wisely",
"😌 Stay Calm Under Time Pressure",
"🔍 Calculate Forcing Moves First (Checks, Captures, Threats)",
"🧠 Improve Position Before Looking for Tactics"
];
// ======== MERGED PRINCIPLES ========
const principles = [
...openingPrinciples,
...midgamePrinciples,
...endgamePrinciples,
...generalPrinciples
];
// ======== MOTIVATIONAL MESSAGES ========
const motivationalMessages = [
'Good Luck! 🍀',
'World is Yours! 🌍',
'You Got This! 💪',
'Checkmate Time! ♟️',
'Think Like a Champion! 🏆',
'Play Your Best! ⭐',
'Trust Your Skills! 🎯',
'Time to Shine! ✨',
'Victory Awaits! 👑',
'Make Your Move! 🚀',
'Stay Focused! 🔥',
'Believe in Yourself! 💫',
'Game On! 🎮',
'You\'re Ready! 💯'
];
// ======== CONFIG ========
const questions = [
{ text: "⚠️ Spot <span style='color:#fef08a; font-weight:bold; font-size:20px; text-shadow: 2px 2px 3px rgba(0,0,0,0.8);'>Immediate Threats</span>" },
{ text: "🎯 Count <span style='color:#fef08a; font-weight:bold; font-size:20px; text-shadow: 2px 2px 3px rgba(0,0,0,0.8);'>Undefended Materials</span>" },
{ text: "👁️ See all <span style='color:#fef08a; font-weight:bold; font-size:20px; text-shadow: 2px 2px 3px rgba(0,0,0,0.8);'>Check Angles</span>" },
{ text: "💀 Who are <span style='color:#fef08a; font-weight:bold; font-size:20px; text-shadow: 2px 2px 3px rgba(0,0,0,0.8);'>Hanging</span>?" },
{ text: "📌 Find <span style='color:#fef08a; font-weight:bold; font-size:20px; text-shadow: 2px 2px 3px rgba(0,0,0,0.8);'>Pins</span>" },
{ text: "🍴 Look for <span style='color:#fef08a; font-weight:bold; font-size:20px; text-shadow: 2px 2px 3px rgba(0,0,0,0.8);'>Forks</span>" },
{ text: "🗡️ Are there <span style='color:#fef08a; font-weight:bold; font-size:20px; text-shadow: 2px 2px 3px rgba(0,0,0,0.8);'>Skewers</span>" },
{ text: "💥 Watch-out for <span style='color:#fef08a; font-weight:bold; font-size:20px; text-shadow: 2px 2px 3px rgba(0,0,0,0.8);'>Discovered Attacks</span>" },
{ text: "🔍 Careful for <span style='color:#fef08a; font-weight:bold; font-size:20px; text-shadow: 2px 2px 3px rgba(0,0,0,0.8);'>Nearby Pieces</span>" },
{ text: "🤝 Ensure <span style='color:#fef08a; font-weight:bold; font-size:20px; text-shadow: 2px 2px 3px rgba(0,0,0,0.8);'>Pieces Coordination</span>" },
{ text: "👑 Remember <span style='color:#fef08a; font-weight:bold; font-size:20px; text-shadow: 2px 2px 3px rgba(0,0,0,0.8);'>Kings Safety</span>" },
{ text: "♟️ Check <span style='color:#fef08a; font-weight:bold; font-size:20px; text-shadow: 2px 2px 3px rgba(0,0,0,0.8);'>Pawn Chains</span>!!" },
{ text: "💡 Aha <span style='color:#fef08a; font-weight:bold; font-size:20px; text-shadow: 2px 2px 3px rgba(0,0,0,0.8);'>Pieces Duties</span>!!" },
{ text: "👼 Always <span style='color:#fef08a; font-weight:bold; font-size:20px; text-shadow: 2px 2px 3px rgba(0,0,0,0.8);'>Improve Position</span>!!" },
{ text: "🏠 Most importantly <span style='color:#fef08a; font-weight:bold; font-size:20px; text-shadow: 2px 2px 3px rgba(0,0,0,0.8);'>Safe Place</span>" }
];
const timerQuotes = [
"Best Moves take Time",
"Move soon, Lose soon",
"Patience pays off",
"Think first, act smart",
"Stay calm, play on",
"Chess is a marathon, not a sprint",
"Plan, then execute",
"Focus, then move"
];
// ======== GLOBAL STATE ========
let currentQuestion = 0;
let currentPrinciple = 0;
let timerInterval = null;
let timerElapsed = 0;
// Principles auto-advance state
let principleInterval = null;
const PRINCIPLE_AUTO_SECONDS = 10;
let principleSecondsLeft = PRINCIPLE_AUTO_SECONDS;
let lastActiveMove = '';
// ======== FONT SIZE CONSTANTS ========
const FONT_SIZE_QUESTION = '22px';
const FONT_SIZE_PRINCIPLE = '18px';
const FONT_SIZE_PRINCIPLE_TIMER = '16px';
const FONT_SIZE_BUTTON_LARGE = '14px';
const FONT_SIZE_BUTTON_SMALL = '10px';
const FONT_SIZE_TIMER = '20px';
const FONT_SIZE_QUOTE = '15px';
// ======== DRAWING BRUSH COLORS ========
// These match Lichess/Chessground brush colors
const DRAWING_BRUSHES = [
{ name: 'Green', color: '#15781B', key: 'green' },
{ name: 'Red', color: '#882020', key: 'red' },
{ name: 'Blue', color: '#003088', key: 'blue' },
{ name: 'Yellow', color: '#e68f00', key: 'yellow' }
];
// Currently selected brush for drawing
let selectedBrush = DRAWING_BRUSHES[0];
let drawingMode = false;
let drawStartPos = null;
// ======== UTILITY FUNCTIONS ========
function randomQuote() {
return timerQuotes[Math.floor(Math.random() * timerQuotes.length)];
}
// ======== DRAWING VIA SIMULATED MOUSE EVENTS ========
// Lichess/Chessground uses right-click with modifier keys to draw:
// - Green: Right-click (no modifiers)
// - Red: Shift+Right-click OR Ctrl+Right-click
// - Blue: Alt+Right-click OR Meta+Right-click
// - Yellow: (Shift OR Ctrl) + (Alt OR Meta) + Right-click
function getModifiersForBrush(brushKey) {
// Returns { shiftKey, ctrlKey, altKey, metaKey } based on brush color
// From chessground draw.ts eventBrush():
// brushes = ['green', 'red', 'blue', 'yellow']
// modA = (shift || ctrl) && rightClick -> index += 1
// modB = (alt || meta) -> index += 2
switch (brushKey) {
case 'green': // index 0: no modifiers
return { shiftKey: false, ctrlKey: false, altKey: false, metaKey: false };
case 'red': // index 1: need modA (shift or ctrl)
return { shiftKey: true, ctrlKey: false, altKey: false, metaKey: false };
case 'blue': // index 2: need modB (alt or meta)
return { shiftKey: false, ctrlKey: false, altKey: true, metaKey: false };
case 'yellow': // index 3: need modA + modB
return { shiftKey: true, ctrlKey: false, altKey: true, metaKey: false };
default:
return { shiftKey: false, ctrlKey: false, altKey: false, metaKey: false };
}
}
function getSquareCenterPos(squareKey) {
// Get the center position of a square in client coordinates
const board = document.querySelector('cg-board');
if (!board) return null;
const rect = board.getBoundingClientRect();
const squareSize = rect.width / 8;
const file = squareKey.charCodeAt(0) - 97; // a=0, h=7
const rank = parseInt(squareKey[1]) - 1; // 1=0, 8=7
// Check board orientation
const isFlipped = document.querySelector('.cg-wrap.orientation-black') !== null;
let col, row;
if (isFlipped) {
col = 7 - file;
row = rank;
} else {
col = file;
row = 7 - rank;
}
const x = rect.left + (col + 0.5) * squareSize;
const y = rect.top + (row + 0.5) * squareSize;
return { x: x, y: y };
}
function simulateDrawing(origSquare, destSquare, brushKey) {
// Simulate right-click drawing with appropriate modifier keys
const board = document.querySelector('cg-board');
if (!board) {
return;
}
const origPos = getSquareCenterPos(origSquare);
const destPos = destSquare && destSquare !== origSquare ? getSquareCenterPos(destSquare) : origPos;
if (!origPos || !destPos) {
return;
}
const modifiers = getModifiersForBrush(brushKey);
// Create and dispatch mousedown event (right-click = button 2)
const mousedownEvent = new MouseEvent('mousedown', {
bubbles: true,
cancelable: true,
view: window,
button: 2, // Right mouse button
buttons: 2, // Right button is pressed
clientX: origPos.x,
clientY: origPos.y,
shiftKey: modifiers.shiftKey,
ctrlKey: modifiers.ctrlKey,
altKey: modifiers.altKey,
metaKey: modifiers.metaKey
});
board.dispatchEvent(mousedownEvent);
// Small delay then dispatch mousemove if drawing arrow
setTimeout(function() {
if (destSquare && destSquare !== origSquare) {
const mousemoveEvent = new MouseEvent('mousemove', {
bubbles: true,
cancelable: true,
view: window,
button: 2,
buttons: 2,
clientX: destPos.x,
clientY: destPos.y,
shiftKey: modifiers.shiftKey,
ctrlKey: modifiers.ctrlKey,
altKey: modifiers.altKey,
metaKey: modifiers.metaKey
});
board.dispatchEvent(mousemoveEvent);
}
// Dispatch mouseup to complete the drawing
setTimeout(function() {
const mouseupEvent = new MouseEvent('mouseup', {
bubbles: true,
cancelable: true,
view: window,
button: 2,
buttons: 0,
clientX: destPos.x,
clientY: destPos.y,
shiftKey: modifiers.shiftKey,
ctrlKey: modifiers.ctrlKey,
altKey: modifiers.altKey,
metaKey: modifiers.metaKey
});
board.dispatchEvent(mouseupEvent);
}, 20);
}, 20);
}
function getSquareFromPos(x, y) {
// Calculate square from position
const board = document.querySelector('cg-board');
if (!board) return null;
const rect = board.getBoundingClientRect();
const squareSize = rect.width / 8;
const col = Math.floor((x - rect.left) / squareSize);
const row = Math.floor((y - rect.top) / squareSize);
if (col < 0 || col > 7 || row < 0 || row > 7) return null;
// Check board orientation
const isFlipped = document.querySelector('.cg-wrap.orientation-black') !== null;
const file = isFlipped ? String.fromCharCode(104 - col) : String.fromCharCode(97 + col);
const rank = isFlipped ? (row + 1) : (8 - row);
return file + rank;
}
function clearAllDrawings() {
// Simulate pressing Escape or use the native clear
// First try to access chessground
if (window.lichess && window.lichess.round && window.lichess.round.chessground) {
const cg = window.lichess.round.chessground;
if (cg.state && cg.state.drawable) {
cg.state.drawable.shapes = [];
if (cg.state.drawable.onChange) {
cg.state.drawable.onChange([]);
}
if (cg.redrawAll) {
cg.redrawAll();
}
return;
}
}
// Fallback: Clear by clicking on empty square (this clears drawings in Lichess)
const board = document.querySelector('cg-board');
if (board) {
const rect = board.getBoundingClientRect();
// Click on center of board
const clickEvent = new MouseEvent('mousedown', {
bubbles: true,
cancelable: true,
view: window,
button: 0,
clientX: rect.left + rect.width / 2,
clientY: rect.top + rect.height / 2
});
board.dispatchEvent(clickEvent);
const upEvent = new MouseEvent('mouseup', {
bubbles: true,
cancelable: true,
view: window,
button: 0,
clientX: rect.left + rect.width / 2,
clientY: rect.top + rect.height / 2
});
board.dispatchEvent(upEvent);
}
}
// ======== DRAWING TOOLBAR ========
function createDrawingToolbar() {
const toolbar = document.createElement('div');
toolbar.id = 'vm-drawing-toolbar';
toolbar.style.cssText = 'width: 100%; margin-top: 8px; padding: 8px; background: rgba(100, 116, 139, 0.15); border: 1px solid #475569; border-radius: 6px; box-sizing: border-box;';
// Color swatches left, action buttons (draw/clear) on the right
const swatchRow = document.createElement('div');
swatchRow.style.cssText = 'display: flex; align-items: center; justify-content: space-between; gap: 8px;';
// Left: swatches container
const swatchesLeft = document.createElement('div');
swatchesLeft.style.cssText = 'display: flex; flex-wrap: wrap; gap: 6px; align-items: center;';
DRAWING_BRUSHES.forEach(function(brush, index) {
const swatch = document.createElement('div');
swatch.className = 'vm-color-swatch';
swatch.setAttribute('data-brush', brush.key);
swatch.style.cssText = 'width: 28px; height: 28px; border-radius: 4px; cursor: pointer; border: 2px solid transparent; transition: all 0.2s; box-shadow: 0 1px 3px rgba(0,0,0,0.3);';
swatch.style.backgroundColor = brush.color;
// Highlight selected brush
if (brush.key === selectedBrush.key) {
swatch.style.borderColor = '#ffffff';
swatch.style.transform = 'scale(1.15)';
}
swatch.title = brush.name;
swatch.onmouseover = function() {
if (brush.key !== selectedBrush.key) {
swatch.style.transform = 'scale(1.1)';
swatch.style.borderColor = 'rgba(255,255,255,0.5)';
}
};
swatch.onmouseout = function() {
if (brush.key !== selectedBrush.key) {
swatch.style.transform = 'scale(1)';
swatch.style.borderColor = 'transparent';
}
};
swatch.onclick = function() {
selectBrush(brush, swatchesLeft);
};
swatchesLeft.appendChild(swatch);
});
// Right: action buttons container
const actionsRight = document.createElement('div');
actionsRight.style.cssText = 'display: flex; gap: 6px; align-items: center;';
// Drawing mode toggle button (icon only)
const drawBtn = document.createElement('button');
drawBtn.id = 'vm-draw-mode-btn';
drawBtn.textContent = '✏️';
drawBtn.title = 'Enable Drawing';
drawBtn.style.cssText = 'width: 32px; height: 28px; background: #475569; border: none; color: #e2e8f0; font-size: 16px; border-radius: 4px; cursor: pointer; transition: all 0.2s; display: flex; align-items: center; justify-content: center;';
drawBtn.onmouseover = function() { if (!drawingMode) drawBtn.style.background = '#334155'; };
drawBtn.onmouseout = function() { if (!drawingMode) drawBtn.style.background = '#475569'; };
drawBtn.onclick = function() { toggleDrawingMode(drawBtn); };
actionsRight.appendChild(drawBtn);
// Clear button (icon only)
const clearBtn = document.createElement('button');
clearBtn.textContent = '🗑️';
clearBtn.title = 'Clear Drawings';
clearBtn.style.cssText = 'width: 32px; height: 28px; background: #475569; border: none; color: #e2e8f0; font-size: 16px; border-radius: 4px; cursor: pointer; transition: all 0.2s; display: flex; align-items: center; justify-content: center;';
clearBtn.onmouseover = function() { clearBtn.style.background = '#334155'; };
clearBtn.onmouseout = function() { clearBtn.style.background = '#475569'; };
clearBtn.onclick = function() { clearAllDrawings(); };
actionsRight.appendChild(clearBtn);
swatchRow.appendChild(swatchesLeft);
swatchRow.appendChild(actionsRight);
toolbar.appendChild(swatchRow);
return toolbar;
}
function toggleDrawingMode(btn) {
drawingMode = !drawingMode;
if (drawingMode) {
btn.textContent = '✏️';
btn.title = 'Drawing ON - Click board!';
btn.style.background = '#16a34a';
btn.style.boxShadow = '0 0 8px #16a34a';
setupBoardDrawing();
} else {
btn.textContent = '✏️';
btn.title = 'Enable Drawing';
btn.style.background = '#475569';
btn.style.boxShadow = 'none';
removeBoardDrawing();
}
}
function setupBoardDrawing() {
const board = document.querySelector('cg-board');
if (!board) {
return;
}
// Use capture phase to intercept before Lichess handlers
board.addEventListener('mousedown', handleDrawStart, true);
board.addEventListener('mouseup', handleDrawEnd, true);
board.addEventListener('mouseleave', handleDrawCancel, true);
// Also handle touch for tablets
board.addEventListener('touchstart', handleTouchStart, true);
board.addEventListener('touchend', handleTouchEnd, true);
board.style.cursor = 'crosshair';
}
function removeBoardDrawing() {
const board = document.querySelector('cg-board');
if (!board) return;
board.removeEventListener('mousedown', handleDrawStart, true);
board.removeEventListener('mouseup', handleDrawEnd, true);
board.removeEventListener('mouseleave', handleDrawCancel, true);
board.removeEventListener('touchstart', handleTouchStart, true);
board.removeEventListener('touchend', handleTouchEnd, true);
board.style.cursor = '';
drawStartPos = null;
}
function handleTouchStart(e) {
if (!drawingMode) return;
e.preventDefault();
e.stopPropagation();
e.stopImmediatePropagation();
const touch = e.touches[0];
drawStartPos = {
square: getSquareFromPos(touch.clientX, touch.clientY),
x: touch.clientX,
y: touch.clientY
};
}
function handleTouchEnd(e) {
if (!drawingMode || !drawStartPos) return;
e.preventDefault();
e.stopPropagation();
e.stopImmediatePropagation();
const touch = e.changedTouches[0];
const endSquare = getSquareFromPos(touch.clientX, touch.clientY);
if (drawStartPos.square && endSquare) {
simulateDrawing(drawStartPos.square, endSquare, selectedBrush.key);
}
drawStartPos = null;
}
function handleDrawStart(e) {
if (!drawingMode) return;
if (e.button !== 0) return; // Left click only
e.preventDefault();
e.stopPropagation();
e.stopImmediatePropagation();
drawStartPos = {
square: getSquareFromPos(e.clientX, e.clientY),
x: e.clientX,
y: e.clientY
};
}
function handleDrawEnd(e) {
if (!drawingMode || !drawStartPos) return;
e.preventDefault();
e.stopPropagation();
e.stopImmediatePropagation();
const endSquare = getSquareFromPos(e.clientX, e.clientY);
if (drawStartPos.square && endSquare) {
simulateDrawing(drawStartPos.square, endSquare, selectedBrush.key);
}
drawStartPos = null;
}
function handleDrawCancel(e) {
// Cancel drawing if mouse leaves board
drawStartPos = null;
}
function selectBrush(brush, container) {
selectedBrush = brush;
// Update all swatches
const swatches = container.querySelectorAll('.vm-color-swatch');
swatches.forEach(function(swatch) {
if (swatch.getAttribute('data-brush') === brush.key) {
swatch.style.borderColor = '#ffffff';
swatch.style.transform = 'scale(1.15)';
} else {
swatch.style.borderColor = 'transparent';
swatch.style.transform = 'scale(1)';
}
});
}
// ======== PRINCIPLES AUTO-ADVANCE ========
function startPrincipleAutoAdvance() {
// clear existing
if (principleInterval) clearInterval(principleInterval);
principleSecondsLeft = PRINCIPLE_AUTO_SECONDS;
const timerEl = document.getElementById('vm-principle-timer');
const principleTextEl = document.getElementById('vm-principle-text');
if (timerEl) timerEl.textContent = 'Next in: ' + principleSecondsLeft + 's';
principleInterval = setInterval(() => {
principleSecondsLeft--;
if (timerEl) timerEl.textContent = 'Next in: ' + principleSecondsLeft + 's';
if (principleSecondsLeft <= 0) {
currentPrinciple = (currentPrinciple + 1) % principles.length;
if (principleTextEl) principleTextEl.textContent = principles[currentPrinciple];
principleSecondsLeft = PRINCIPLE_AUTO_SECONDS;
if (timerEl) timerEl.textContent = 'Next in: ' + principleSecondsLeft + 's';
}
}, 1000);
}
function createTopBar() {
// Remove existing bar if present
const existing = document.getElementById('vm-top-bar');
if (existing) existing.remove();
const bar = document.createElement('div');
bar.id = 'vm-top-bar';
bar.style.cssText = 'position: fixed; top: 0; left: 0; right: 0; background: #1e1e2f; color: #f0f0f0; font-family: system-ui, sans-serif; font-size: 14px; display: flex; justify-content: space-between; align-items: center; padding: 6px 12px; z-index: 9999; box-shadow: 0 2px 6px rgba(0,0,0,0.3);';
// ---- Question section ----
const qDiv = document.createElement('div');
qDiv.id = 'vm-question';
qDiv.innerHTML = questions[currentQuestion].text;
const doneBtn = document.createElement('button');
doneBtn.textContent = 'Done';
doneBtn.style.cssText = 'margin-left: 8px; background: #4ade80; border: none; color: #064e3b; font-weight: bold; padding: 2px 8px; border-radius: 4px; cursor: pointer;';
doneBtn.onclick = () => {
currentQuestion = (currentQuestion + 1) % questions.length;
qDiv.innerHTML = questions[currentQuestion].text;
qDiv.appendChild(doneBtn); // Reattach button
};
qDiv.appendChild(doneBtn);
// ---- Timer section ----
const tDiv = document.createElement('div');
tDiv.id = 'vm-timer';
tDiv.textContent = timerRemaining + 's ⏳';
// ---- Quote section ----
const quoteDiv = document.createElement('div');
quoteDiv.id = 'vm-quote';
quoteDiv.textContent = randomQuote();
quoteDiv.style.fontStyle = 'italic';
quoteDiv.style.opacity = 0.8;
bar.appendChild(qDiv);
bar.appendChild(tDiv);
bar.appendChild(quoteDiv);
if (document.body) {
document.body.appendChild(bar);
startTimer(tDiv);
updateStreak(streakDiv);
}
}
// ======== TIMER LOGIC ========
function startTimer(tDiv) {
if (timerInterval) clearInterval(timerInterval);
timerElapsed = 0;
tDiv.textContent = '0s ⏳';
tDiv.style.color = '#ef4444'; // Start red
timerInterval = setInterval(() => {
timerElapsed++;
tDiv.textContent = timerElapsed + 's ⏳';
// Color coding: red (0-10s), yellow (10-15s), green (15s+)
if (timerElapsed < 10) {
tDiv.style.color = '#ef4444'; // Red
} else if (timerElapsed < 15) {
tDiv.style.color = '#fde047'; // Yellow
} else {
tDiv.style.color = '#10b981'; // Green
}
}, 1000);
}
// ======== STREAK LOGIC (REMOVED) ========
// Streak tracking has been removed from this version
// ======== DETECT MOVE CHANGE & RESET ========
function checkMoveChange() {
// Check for active move element (game page: kwdb.a1t)
const activeMove = document.querySelector('kwdb.a1t');
if (!activeMove) return false;
// Use the 'p' attribute or class as the unique identifier for each move
const moveAttr = activeMove.getAttribute('p') || activeMove.className || '';
const moveText = activeMove.textContent.trim();
const currentMove = moveAttr + moveText;
if (currentMove && currentMove !== lastActiveMove) {
lastActiveMove = currentMove;
return true;
}
return false;
}
function resetQuizState() {
currentQuestion = 0;
// Reset question text and buttons
const qText = document.querySelector('#vm-question span');
const backBtn = document.getElementById('vm-back-btn');
const doneBtn = document.getElementById('vm-done-btn');
if (qText) {
qText.innerHTML = questions[currentQuestion].text;
}
if (backBtn) {
backBtn.textContent = '← Back';
}
if (doneBtn) {
doneBtn.style.display = '';
doneBtn.textContent = 'Got it';
}
// Reset timer
const tDiv = document.getElementById('vm-timer');
if (tDiv) {
tDiv.style.color = '#ef4444'; // Red at start
startTimer(tDiv);
}
// Reset quote
const quoteDiv = document.getElementById('vm-quote');
if (quoteDiv) {
quoteDiv.textContent = randomQuote();
}
}
// ======== INJECT CUSTOM SIDEBAR DIV ========
function injectCustomSidebarDiv() {
// Check if already injected
if (document.getElementById('vm-custom-sidebar')) {
// Check for move change and reset if needed
if (checkMoveChange()) {
resetQuizState();
}
return;
}
// Try to find an anchor element for game page
const gameMeta = document.querySelector('.game__meta');
if (!gameMeta) {
return;
}
// Create custom container with unique class
const customDiv = document.createElement('div');
customDiv.id = 'vm-custom-sidebar';
customDiv.className = 'vm-game-helper';
customDiv.style.cssText = 'width: 100%; margin-top: 16px; padding: 14px; background: linear-gradient(135deg, #0f172a 0%, #1e293b 100%); border-radius: 8px; box-shadow: 0 4px 12px rgba(0,0,0,0.4); border: 1px solid #334155; box-sizing: border-box; position: relative; overflow: hidden;';
// Question section with custom styling - PROMINENT DESIGN
const qDiv = document.createElement('div');
qDiv.id = 'vm-question';
qDiv.style.cssText = 'width: 100%; margin-bottom: 16px; padding: 18px; background: rgba(100, 116, 139, 0.15); border-radius: 10px; box-shadow: 0 6px 20px rgba(148, 163, 184, 0.3); border: 3px solid #94a3b8; box-sizing: border-box;';
// Question text div
const qTextDiv = document.createElement('div');
qTextDiv.style.cssText = 'width: 100%; font-weight: bold; font-size: ' + FONT_SIZE_QUESTION + '; line-height: 1.8; min-height: 80px; margin-bottom: 16px; color: #f1f5f9; text-shadow: 2px 2px 4px rgba(0,0,0,0.7);';
const qText = document.createElement('span');
qText.innerHTML = questions[currentQuestion].text;
qTextDiv.appendChild(qText);
// Buttons div
const btnContainer = document.createElement('div');
btnContainer.style.cssText = 'width: 100%; display: flex; justify-content: space-between; gap: 12px; height: 40px;';
const backBtn = document.createElement('button');
backBtn.id = 'vm-back-btn';
backBtn.textContent = '← Back';
backBtn.style.cssText = 'background: #1e293b; border: 2px solid #ffffff; color: #ffffff; font-weight: bold; padding: 8px 16px; border-radius: 8px; cursor: pointer; font-size: ' + FONT_SIZE_BUTTON_LARGE + '; transition: all 0.2s; box-shadow: 0 3px 8px rgba(0,0,0,0.3);';
// Hide back button on first question
if (currentQuestion === 0) {
backBtn.style.visibility = 'hidden';
}
backBtn.onmouseover = () => { backBtn.style.background = '#334155'; backBtn.style.transform = 'scale(1.08)'; };
backBtn.onmouseout = () => { backBtn.style.background = '#1e293b'; backBtn.style.transform = 'scale(1)'; };
backBtn.onclick = () => {
if (currentQuestion > 0) {
currentQuestion--;
qText.innerHTML = questions[currentQuestion].text;
doneBtn.style.display = '';
// Hide back button if we're at first question
if (currentQuestion === 0) {
backBtn.style.visibility = 'hidden';
}
}
};
const doneBtn = document.createElement('button');
doneBtn.id = 'vm-done-btn';
doneBtn.textContent = 'Got it ✓';
doneBtn.style.cssText = 'background: #16a34a; border: 2px solid #ffffff; color: #ffffff; font-weight: bold; padding: 8px 20px; border-radius: 8px; cursor: pointer; font-size: ' + FONT_SIZE_BUTTON_LARGE + '; transition: all 0.2s; box-shadow: 0 3px 8px rgba(0,0,0,0.3);';
doneBtn.onmouseover = () => { doneBtn.style.background = '#15803d'; doneBtn.style.transform = 'scale(1.08)'; };
doneBtn.onmouseout = () => { doneBtn.style.background = '#16a34a'; doneBtn.style.transform = 'scale(1)'; };
doneBtn.onclick = () => {
currentQuestion++;
// Show back button since we're past first question
backBtn.style.visibility = 'visible';
if (currentQuestion >= questions.length) {
qText.innerHTML = '<strong style="color: #fef3c7; font-size: 24px; text-shadow: 3px 3px 6px rgba(0,0,0,0.6);">' + motivationalMessages[Math.floor(Math.random() * motivationalMessages.length)] + '</strong>';
doneBtn.style.display = 'none';
} else {
qText.innerHTML = questions[currentQuestion].text;
}
};
btnContainer.appendChild(backBtn);
btnContainer.appendChild(doneBtn);
qDiv.appendChild(qTextDiv);
qDiv.appendChild(btnContainer);
// Principles section with Back/Next controls - SUBTLE DESIGN
const principlesDiv = document.createElement('div');
principlesDiv.id = 'vm-principles';
principlesDiv.style.cssText = 'width: 100%; margin-bottom: 10px; padding: 8px; background: rgba(100, 116, 139, 0.15); border: 1px solid #475569; border-radius: 5px; color: #94a3b8; box-sizing: border-box; opacity: 0.85;';
const principleTextDiv = document.createElement('div');
principleTextDiv.style.cssText = 'width: 100%; font-weight: normal; font-size: ' + FONT_SIZE_PRINCIPLE + '; line-height: 1.4; min-height: 50px; margin-bottom: 6px; text-align: center;';
const principleText = document.createElement('span');
principleText.id = 'vm-principle-text';
principleText.textContent = principles[currentPrinciple];
principleTextDiv.appendChild(principleText);
const principleBtnContainer = document.createElement('div');
principleBtnContainer.style.cssText = 'width: 100%; display: flex; justify-content: space-between; gap: 8px; height: 24px;';
const principleBackBtn = document.createElement('button');
principleBackBtn.id = 'vm-principle-back-btn';
principleBackBtn.textContent = '← Back';
principleBackBtn.style.cssText = 'background: #475569; border: none; color: #e2e8f0; font-weight: normal; padding: 3px 8px; border-radius: 4px; cursor: pointer; font-size: ' + FONT_SIZE_BUTTON_SMALL + '; transition: all 0.2s; box-shadow: 0 1px 2px rgba(0,0,0,0.2);';
principleBackBtn.onmouseover = () => { principleBackBtn.style.background = '#334155'; principleBackBtn.style.transform = 'scale(1.03)'; };
principleBackBtn.onmouseout = () => { principleBackBtn.style.background = '#475569'; principleBackBtn.style.transform = 'scale(1)'; };
principleBackBtn.onclick = () => {
// move back (wrap around)
currentPrinciple = (currentPrinciple - 1 + principles.length) % principles.length;
principleText.textContent = principles[currentPrinciple];
// reset auto-advance timer
startPrincipleAutoAdvance();
};
// Timer for principles (visible under principle text)
const principleTimerDiv = document.createElement('div');
principleTimerDiv.id = 'vm-principle-timer';
principleTimerDiv.style.cssText = 'width: 100%; text-align: center; font-size: ' + FONT_SIZE_PRINCIPLE_TIMER + '; margin-bottom: 6px; color: #64748b; opacity: 0.7;';
principleTimerDiv.textContent = 'Next in: ' + PRINCIPLE_AUTO_SECONDS + 's';
principlesDiv.appendChild(principleTimerDiv);
const principleNextBtn = document.createElement('button');
principleNextBtn.id = 'vm-principle-next-btn';
principleNextBtn.textContent = 'Next →';
principleNextBtn.style.cssText = 'background: #475569; border: none; color: #e2e8f0; font-weight: normal; padding: 3px 8px; border-radius: 4px; cursor: pointer; font-size: ' + FONT_SIZE_BUTTON_SMALL + '; transition: all 0.2s; box-shadow: 0 1px 2px rgba(0,0,0,0.2);';
principleNextBtn.onmouseover = () => { principleNextBtn.style.background = '#334155'; principleNextBtn.style.transform = 'scale(1.03)'; };
principleNextBtn.onmouseout = () => { principleNextBtn.style.background = '#475569'; principleNextBtn.style.transform = 'scale(1)'; };
principleNextBtn.onclick = () => {
// move next (wrap around)
currentPrinciple = (currentPrinciple + 1) % principles.length;
principleText.textContent = principles[currentPrinciple];
// reset auto-advance timer
startPrincipleAutoAdvance();
};
principleBtnContainer.appendChild(principleBackBtn);
principleBtnContainer.appendChild(principleNextBtn);
principlesDiv.appendChild(principleTextDiv);
principlesDiv.appendChild(principleBtnContainer);
// Timer section
const tDiv = document.createElement('div');
tDiv.id = 'vm-timer';
tDiv.textContent = '0s ⏳';
tDiv.style.cssText = 'width: 100%; margin-bottom: 10px; font-size: ' + FONT_SIZE_TIMER + '; font-weight: bold; color: #ef4444; text-align: center; padding: 10px; background: rgba(0,0,0,0.3); border-radius: 6px; box-sizing: border-box; text-shadow: 0 2px 4px rgba(0,0,0,0.3);';
// Quote section
const quoteDiv = document.createElement('div');
quoteDiv.id = 'vm-quote';
quoteDiv.textContent = randomQuote();
quoteDiv.style.cssText = 'width: 100%; font-style: italic; margin-bottom: 10px; font-size: ' + FONT_SIZE_QUOTE + '; font-weight: bold; color: #ffffff; text-align: center; box-sizing: border-box; text-shadow: 0 1px 3px rgba(0,0,0,0.5);';
customDiv.appendChild(tDiv);
customDiv.appendChild(qDiv);
customDiv.appendChild(principlesDiv);
customDiv.appendChild(quoteDiv);
// ======== DRAWING TOOLBAR ========
const drawingToolbar = createDrawingToolbar();
customDiv.appendChild(drawingToolbar);
// Insert into the game page container
if (gameMeta) {
// Game page: append after game__meta
gameMeta.parentNode.insertBefore(customDiv, gameMeta.nextSibling);
}
// Start timer
// Start principles auto-advance and widget timer
startPrincipleAutoAdvance();
startTimer(tDiv);
}
// ======== OBSERVER ========
const observer = new MutationObserver(() => {
// Only inject sidebar, don't create top bar
injectCustomSidebarDiv();
});
observer.observe(document.documentElement, { childList: true, subtree: true });
// Retry mechanism - check periodically for 10 seconds
let retryCount = 0;
const maxRetries = 50; // 50 * 200ms = 10 seconds
const retryInterval = setInterval(() => {
retryCount++;
injectCustomSidebarDiv();
if (document.getElementById('vm-custom-sidebar') || retryCount >= maxRetries) {
clearInterval(retryInterval);
}
}, 200);
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment