// ==UserScript==
// @name Flickr Always Visible Stats
// @namespace http://tampermonkey.net/
// @version 1.0
// @description Makes like and comment counts always visible on Flickr photo thumbnails in profile pages
// @author fapek GPT
// @match https://*.flickr.com/people/*
// @match https://*.flickr.com/photos/*
// @match *.flickr.com/photos/*
// @license MIT
// @grant none
// ==/UserScript==
(function() {
'use strict';
// Function to create and inject custom CSS
function injectCustomCSS() {
const css = `
/* Force interaction bar to be always visible */
.photo-list-photo-view .interaction-bar,
.overlay-target .interaction-bar {
display: none !important; /* Hide the original interaction bar completely */
opacity: 0 !important;
visibility: hidden !important;
}
/* Style for our custom stats bar */
.always-visible-stats {
display: flex !important;
opacity: 1 !important;
visibility: visible !important;
position: absolute !important;
bottom: 0 !important;
left: 0 !important;
right: 0 !important;
background: rgb(0, 0, 0) !important; /* Fully opaque background */
padding: 5px !important;
transition: background 0.2s ease !important;
z-index: 99 !important; /* Higher z-index to ensure it's on top */
pointer-events: auto !important;
}
/* Hide the title and attribution to save space */
.photo-list-photo-view .interaction-bar .text {
display: none;
}
/* Make the engagement section take full width */
.photo-list-photo-view .interaction-bar .engagement {
display: flex;
width: 100%;
justify-content: space-around;
}
/* Style for engagement items */
.photo-list-photo-view .interaction-bar .engagement-item {
display: flex;
align-items: center;
color: white;
padding: 2px 5px;
font-weight: bold;
}
/* Hover effect on the bar */
.photo-list-photo-view:hover .interaction-bar {
background: rgba(0, 0, 0, 0.8);
}
/* Make engagement count more visible */
.photo-list-photo-view .interaction-bar .engagement-count {
margin-left: 3px;
font-size: 14px;
text-shadow: 1px 1px 1px rgba(0, 0, 0, 0.8);
}
/* Special styling for favorite counts */
.photo-list-photo-view .interaction-bar .fave .engagement-count {
color: #fffc00;
}
/* Special styling for comment counts */
.photo-list-photo-view .interaction-bar .comment .engagement-count {
color: #00ffff;
}
/* Hide the curate button to save space */
.photo-list-photo-view .interaction-bar .curate {
display: none;
}
/* Ensure icons are visible */
.photo-list-photo-view .interaction-bar svg,
.photo-list-photo-view .interaction-bar i {
filter: drop-shadow(1px 1px 1px rgba(0, 0, 0, 0.8));
}
`;
const styleElement = document.createElement('style');
styleElement.textContent = css;
document.head.appendChild(styleElement);
}
// Function to parse like counts including K format (1K, 1.5K etc.)
function parseCount(text) {
text = text.trim();
// Handle K format (e.g., "1K", "1.3K")
if (text.endsWith('K')) {
const numPart = parseFloat(text.replace('K', ''));
return Math.round(numPart * 1000);
}
return parseInt(text);
}
// Function to stylize like counts with the same levels as your other script
function stylizeLikeCounts() {
// Find all engagement count elements for favorites
const likeElements = document.querySelectorAll('.fave .engagement-count');
likeElements.forEach(function(likeElement) {
const originalText = likeElement.innerText.trim();
const likeCount = parseCount(originalText);
if (!isNaN(likeCount)) {
// Reset existing custom styles
likeElement.style.fontSize = '';
likeElement.style.textShadow = '';
// Style based on number of likes
if (likeCount >= 1 && likeCount <= 5) {
likeElement.style.fontSize = '14px';
} else if (likeCount >= 6 && likeCount <= 15) {
likeElement.style.fontSize = '16px';
likeElement.style.textShadow = '0 0 3px #fffc00';
} else if (likeCount >= 16 && likeCount <= 30) {
likeElement.style.fontSize = '18px';
likeElement.style.textShadow = '0 0 4px #fffc00';
} else if (likeCount >= 31 && likeCount <= 60) {
likeElement.style.fontSize = '20px';
likeElement.style.textShadow = '0 0 5px #fffc00';
} else if (likeCount >= 61 && likeCount <= 100) {
likeElement.style.fontSize = '22px';
likeElement.style.textShadow = '0 0 6px #fffc00';
} else if (likeCount >= 101 && likeCount <= 500) {
likeElement.style.fontSize = '24px';
likeElement.style.textShadow = '0 0 8px #fffc00, 0 0 12px #fffc00';
} else if (likeCount >= 501 && likeCount <= 1000) {
likeElement.style.fontSize = '26px';
likeElement.style.textShadow = '0 0 10px #fffc00, 0 0 15px #fffc00';
} else if (likeCount > 1000) {
likeElement.style.fontSize = '28px';
likeElement.style.textShadow = '0 0 10px #fffc00, 0 0 15px #fffc00, 0 0 20px #fffc00';
}
}
});
// Also style comment counts
const commentElements = document.querySelectorAll('.comment .engagement-count');
commentElements.forEach(function(commentElement) {
const originalText = commentElement.innerText.trim();
const commentCount = parseCount(originalText);
if (!isNaN(commentCount)) {
// Reset existing custom styles
commentElement.style.fontSize = '';
commentElement.style.textShadow = '';
// Style based on number of comments
if (commentCount >= 1 && commentCount <= 2) {
commentElement.style.fontSize = '14px';
} else if (commentCount >= 3 && commentCount <= 5) {
commentElement.style.fontSize = '16px';
commentElement.style.textShadow = '0 0 3px #00ffff';
} else if (commentCount >= 6 && commentCount <= 10) {
commentElement.style.fontSize = '18px';
commentElement.style.textShadow = '0 0 4px #00ffff';
} else if (commentCount >= 11 && commentCount <= 30) {
commentElement.style.fontSize = '20px';
commentElement.style.textShadow = '0 0 6px #00ffff, 0 0 10px #00ffff';
} else if (commentCount > 30) {
commentElement.style.fontSize = '22px';
commentElement.style.textShadow = '0 0 8px #00ffff, 0 0 12px #00ffff, 0 0 16px #00ffff';
}
}
});
}
// Function to make interaction bars visible by manipulating the DOM
function forceShowInteractionBars() {
// Find all photo containers
const photoContainers = document.querySelectorAll('.photo-list-photo-view');
photoContainers.forEach(container => {
// Find the interaction bar in this container
const interactionBar = container.querySelector('.interaction-bar');
if (interactionBar) {
// Completely hide the original interaction bar
interactionBar.style.display = 'none';
interactionBar.style.opacity = '0';
interactionBar.style.visibility = 'hidden';
interactionBar.style.pointerEvents = 'none';
// Remove any padding we might have added before
container.style.paddingBottom = '';
container.style.marginBottom = '';
// Create a new div for displaying stats if it doesn't exist yet
let statsDiv = container.querySelector('.always-visible-stats');
if (!statsDiv) {
statsDiv = document.createElement('div');
statsDiv.className = 'always-visible-stats';
// Apply explicit styles directly to the element
statsDiv.style.position = 'absolute';
statsDiv.style.bottom = '0';
statsDiv.style.left = '0';
statsDiv.style.right = '0';
statsDiv.style.height = '25px';
statsDiv.style.backgroundColor = 'rgba(0, 0, 0, 0.75)';
statsDiv.style.color = '#ffffff';
statsDiv.style.display = 'flex';
statsDiv.style.alignItems = 'center';
statsDiv.style.justifyContent = 'space-around';
statsDiv.style.zIndex = '9999';
statsDiv.style.padding = '0';
// Get original elements
const faveItem = interactionBar.querySelector('.engagement-item.fave');
const commentItem = interactionBar.querySelector('.engagement-item.comment');
if (faveItem && commentItem) {
// Create custom fave element with working interaction
const customFaveItem = document.createElement('div');
customFaveItem.className = 'custom-fave-item';
customFaveItem.style.display = 'flex';
customFaveItem.style.alignItems = 'center';
customFaveItem.style.cursor = 'pointer';
customFaveItem.style.padding = '0 10px';
// Create star icon
const starIcon = document.createElement('span');
starIcon.innerHTML = '★'; // Unicode star
starIcon.style.color = '#fffc00'; // Yellow color
starIcon.style.fontSize = '16px';
starIcon.style.marginRight = '4px';
starIcon.style.lineHeight = '1';
// Get original count
const countSpan = faveItem.querySelector('.engagement-count');
const customCountSpan = document.createElement('span');
customCountSpan.className = 'custom-engagement-count';
customCountSpan.textContent = countSpan ? countSpan.textContent : '0';
customCountSpan.style.color = '#fffc00'; // Yellow color
// Add click event that triggers the original fave button
customFaveItem.addEventListener('click', function(e) {
e.stopPropagation(); // Prevent triggering photo click
// Click the original fave button
faveItem.click();
// Update our custom count after a short delay
setTimeout(() => {
const updatedCount = faveItem.querySelector('.engagement-count');
if (updatedCount) {
customCountSpan.textContent = updatedCount.textContent;
// Re-stylize after update
stylizeCustomCounts();
}
}, 1000);
});
// Assemble fave item
customFaveItem.appendChild(starIcon);
customFaveItem.appendChild(customCountSpan);
// Create custom comment element (non-interactive, just display)
const customCommentItem = document.createElement('a');
customCommentItem.className = 'custom-comment-item';
customCommentItem.style.display = 'flex';
customCommentItem.style.alignItems = 'center';
customCommentItem.style.padding = '0 10px';
customCommentItem.style.textDecoration = 'none';
// Set the href to the original comment link if available
if (commentItem.hasAttribute('href')) {
customCommentItem.href = commentItem.getAttribute('href');
}
// Comment icon
const commentIcon = document.createElement('span');
commentIcon.innerHTML = '💬'; // Speech bubble emoji
commentIcon.style.fontSize = '14px';
commentIcon.style.marginRight = '4px';
commentIcon.style.lineHeight = '1';
// Comment count
const commentCountSpan = commentItem.querySelector('.engagement-count');
const customCommentCountSpan = document.createElement('span');
customCommentCountSpan.className = 'custom-comment-count';
customCommentCountSpan.textContent = commentCountSpan ? commentCountSpan.textContent : '0';
customCommentCountSpan.style.color = '#00ffff'; // Cyan color
// Assemble comment item
customCommentItem.appendChild(commentIcon);
customCommentItem.appendChild(customCommentCountSpan);
// Add both items to our custom stats bar
statsDiv.appendChild(customFaveItem);
statsDiv.appendChild(customCommentItem);
// Find the appropriate place to add our element
const photoElement = container.querySelector('.photo-list-photo-container');
if (photoElement) {
photoElement.appendChild(statsDiv);
} else {
container.appendChild(statsDiv);
}
}
}
}
});
// Apply styling to our custom counts
stylizeCustomCounts();
}
// Function to stylize our custom counts
function stylizeCustomCounts() {
// Style fave counts
const customFaveCounts = document.querySelectorAll('.custom-engagement-count');
customFaveCounts.forEach(function(countElement) {
const originalText = countElement.textContent.trim();
const likeCount = parseCount(originalText);
if (!isNaN(likeCount)) {
// Reset styles
countElement.style.fontSize = '';
countElement.style.textShadow = '';
// Apply styling based on count
if (likeCount >= 1 && likeCount <= 5) {
countElement.style.fontSize = '14px';
} else if (likeCount >= 6 && likeCount <= 15) {
countElement.style.fontSize = '16px';
countElement.style.textShadow = '0 0 3px #fffc00';
} else if (likeCount >= 16 && likeCount <= 30) {
countElement.style.fontSize = '18px';
countElement.style.textShadow = '0 0 4px #fffc00';
} else if (likeCount >= 31 && likeCount <= 60) {
countElement.style.fontSize = '20px';
countElement.style.textShadow = '0 0 5px #fffc00';
} else if (likeCount >= 61 && likeCount <= 100) {
countElement.style.fontSize = '22px';
countElement.style.textShadow = '0 0 6px #fffc00';
} else if (likeCount >= 101 && likeCount <= 500) {
countElement.style.fontSize = '24px';
countElement.style.textShadow = '0 0 8px #fffc00, 0 0 12px #fffc00';
} else if (likeCount >= 501 && likeCount <= 1000) {
countElement.style.fontSize = '26px';
countElement.style.textShadow = '0 0 10px #fffc00, 0 0 15px #fffc00';
} else if (likeCount > 1000) {
countElement.style.fontSize = '28px';
countElement.style.textShadow = '0 0 10px #fffc00, 0 0 15px #fffc00, 0 0 20px #fffc00';
}
}
});
// Style comment counts
const customCommentCounts = document.querySelectorAll('.custom-comment-count');
customCommentCounts.forEach(function(countElement) {
const originalText = countElement.textContent.trim();
const commentCount = parseCount(originalText);
if (!isNaN(commentCount)) {
// Reset styles
countElement.style.fontSize = '';
countElement.style.textShadow = '';
// Apply styling based on count
if (commentCount >= 1 && commentCount <= 2) {
countElement.style.fontSize = '14px';
} else if (commentCount >= 3 && commentCount <= 5) {
countElement.style.fontSize = '16px';
countElement.style.textShadow = '0 0 3px #00ffff';
} else if (commentCount >= 6 && commentCount <= 10) {
countElement.style.fontSize = '18px';
countElement.style.textShadow = '0 0 4px #00ffff';
} else if (commentCount >= 11 && commentCount <= 30) {
countElement.style.fontSize = '20px';
countElement.style.textShadow = '0 0 6px #00ffff, 0 0 10px #00ffff';
} else if (commentCount > 30) {
countElement.style.fontSize = '22px';
countElement.style.textShadow = '0 0 8px #00ffff, 0 0 12px #00ffff, 0 0 16px #00ffff';
}
}
});
}
// Main function to run on page load and after content changes
function initialize() {
// First, inject custom CSS to make bars visible
injectCustomCSS();
// Also force show by manipulating DOM directly
forceShowInteractionBars();
// No longer need this as we now use stylizeCustomCounts
// stylizeLikeCounts();
}
// Set up observer to watch for dynamically loaded content
function setupObserver() {
const observer = new MutationObserver(function(mutations) {
mutations.forEach(function(mutation) {
if (mutation.addedNodes.length) {
// Wait a moment to let any dynamic content fully render
setTimeout(stylizeLikeCounts, 300);
}
});
});
// Start observing the document body for changes
observer.observe(document.body, {
childList: true,
subtree: true
});
}
// Initial run
document.addEventListener('DOMContentLoaded', function() {
initialize();
setupObserver();
});
// Run immediately for already loaded content
initialize();
setupObserver();
// Run multiple times with delays to ensure we catch all dynamic content
setTimeout(initialize, 500);
setTimeout(initialize, 1500);
setTimeout(initialize, 3000);
// Set up interval to periodically check and update
setInterval(initialize, 5000);
// Additional force refresh on page interactions
document.addEventListener('click', function() {
setTimeout(initialize, 100);
});
// Target specific events that might trigger content changes
window.addEventListener('scroll', function() {
// Debounce the scroll event
clearTimeout(window.flickrStatsScrollTimer);
window.flickrStatsScrollTimer = setTimeout(initialize, 300);
});
})();