Blendet Suchergebnisse ohne "Vollen Zugriff" aus und zeigt ein Steuerfeld
// ==UserScript==
// @name SpringerLink – Zugriffsfilter
// @namespace https://link.springer.com/
// @version 1.3
// @description Blendet Suchergebnisse ohne "Vollen Zugriff" aus und zeigt ein Steuerfeld
// @author Nader Rupiani
// @match https://link.springer.com/*
// @grant GM_addStyle
// @license MIT
// ==/UserScript==
(function () {
'use strict';
// ── Styles ────────────────────────────────────────────────────────────────
GM_addStyle(`
#sl-access-panel {
position: fixed;
bottom: 24px;
right: 24px;
z-index: 99999;
background: #fff;
border: 1.5px solid #006EAF;
border-radius: 12px;
box-shadow: 0 4px 20px rgba(0,0,0,0.15);
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
font-size: 13px;
color: #1a1a1a;
min-width: 220px;
user-select: none;
overflow: hidden;
}
#sl-panel-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 11px 14px;
cursor: pointer;
background: #f5f9fd;
border-bottom: 1.5px solid #d0e4f0;
gap: 8px;
}
#sl-panel-header:hover { background: #e8f1f9; }
#sl-panel-title {
font-size: 13px;
font-weight: 700;
color: #006EAF;
letter-spacing: 0.3px;
display: flex;
align-items: center;
gap: 6px;
margin: 0;
}
#sl-panel-title::before { content: "🔒"; font-size: 14px; }
#sl-collapse-icon {
font-size: 11px;
color: #006EAF;
transition: transform 0.2s;
flex-shrink: 0;
}
#sl-collapse-icon.collapsed { transform: rotate(-90deg); }
#sl-panel-body {
padding: 14px 18px;
transition: max-height 0.25s ease, opacity 0.2s ease, padding 0.2s ease;
max-height: 300px;
opacity: 1;
overflow: hidden;
}
#sl-panel-body.collapsed {
max-height: 0;
opacity: 0;
padding-top: 0;
padding-bottom: 0;
}
#sl-stats {
margin-bottom: 12px;
line-height: 1.6;
}
#sl-stats .stat-row {
display: flex;
justify-content: space-between;
gap: 12px;
}
#sl-stats .stat-value {
font-weight: 700;
}
#sl-stats .stat-value.full { color: #2e7d32; }
#sl-stats .stat-value.no { color: #c62828; }
.sl-toggle-btn {
width: 100%;
padding: 7px 10px;
border: none;
border-radius: 7px;
cursor: pointer;
font-size: 12.5px;
font-weight: 600;
transition: background 0.2s, opacity 0.2s;
}
#sl-btn-hide {
background: #006EAF;
color: #fff;
margin-bottom: 6px;
}
#sl-btn-hide:hover { background: #005a8e; }
#sl-btn-dim {
background: #f0f4f8;
color: #444;
margin-bottom: 6px;
}
#sl-btn-dim:hover { background: #dce6ef; }
#sl-btn-show {
background: #f0f4f8;
color: #444;
}
#sl-btn-show:hover { background: #dce6ef; }
/* States applied to result cards */
.sl-no-access--hidden {
display: none !important;
}
.sl-no-access--dimmed {
opacity: 0.25 !important;
filter: grayscale(60%) !important;
pointer-events: none;
}
.sl-no-access-badge {
display: inline-block;
background: #c62828;
color: #fff;
font-size: 10px;
font-weight: 700;
padding: 2px 6px;
border-radius: 4px;
margin-left: 6px;
vertical-align: middle;
letter-spacing: 0.5px;
}
`);
// ── State ─────────────────────────────────────────────────────────────────
let mode = 'badge'; // 'badge' | 'dim' | 'hide'
// ── Panel ─────────────────────────────────────────────────────────────────
function buildPanel() {
if (document.getElementById('sl-access-panel')) return;
const panel = document.createElement('div');
panel.id = 'sl-access-panel';
panel.innerHTML = `
<div id="sl-panel-header">
<h4 id="sl-panel-title">Zugriffsfilter</h4>
<span id="sl-collapse-icon">▼</span>
</div>
<div id="sl-panel-body">
<div id="sl-stats">
<div class="stat-row">
<span>✅ Voller Zugriff</span>
<span class="stat-value full" id="sl-count-full">–</span>
</div>
<div class="stat-row">
<span>🔒 Kein Zugriff</span>
<span class="stat-value no" id="sl-count-no">–</span>
</div>
</div>
<button class="sl-toggle-btn" id="sl-btn-hide">Ausblenden</button>
<button class="sl-toggle-btn" id="sl-btn-dim">Abdunkeln</button>
<button class="sl-toggle-btn" id="sl-btn-show">Alle einblenden</button>
</div>
`;
document.body.appendChild(panel);
// Collapse toggle
let collapsed = false;
document.getElementById('sl-panel-header').addEventListener('click', () => {
collapsed = !collapsed;
document.getElementById('sl-panel-body').classList.toggle('collapsed', collapsed);
document.getElementById('sl-collapse-icon').classList.toggle('collapsed', collapsed);
});
document.getElementById('sl-btn-hide').addEventListener('click', () => setMode('hide'));
document.getElementById('sl-btn-dim').addEventListener('click', () => setMode('dim'));
document.getElementById('sl-btn-show').addEventListener('click', () => setMode('badge'));
}
function setMode(newMode) {
mode = newMode;
applyMode();
}
// ── Core logic ────────────────────────────────────────────────────────────
function scanAndTag() {
const cards = document.querySelectorAll('li.app-card-open');
let countFull = 0;
let countNo = 0;
cards.forEach(card => {
// Remove previous badges to avoid duplicates on re-scan
card.querySelectorAll('.sl-no-access-badge').forEach(b => b.remove());
card.classList.remove('sl-no-access', 'sl-no-access--hidden', 'sl-no-access--dimmed');
const hasFullAccess = !!card.querySelector(
'.app-entitlement__icon--full-access, [data-test="entitlements"]'
) && card.querySelector('[data-test="entitlements"]')?.textContent.trim().toLowerCase().includes('full access');
if (hasFullAccess) {
countFull++;
} else {
countNo++;
card.classList.add('sl-no-access');
// Add badge next to title
const title = card.querySelector('.app-card-open__heading, h3');
if (title && !title.querySelector('.sl-no-access-badge')) {
const badge = document.createElement('span');
badge.className = 'sl-no-access-badge';
badge.textContent = 'KEIN ZUGRIFF';
title.appendChild(badge);
}
}
});
// Update counters
const elFull = document.getElementById('sl-count-full');
const elNo = document.getElementById('sl-count-no');
if (elFull) elFull.textContent = countFull;
if (elNo) elNo.textContent = countNo;
applyMode();
}
function applyMode() {
const noAccessCards = document.querySelectorAll('.sl-no-access');
noAccessCards.forEach(card => {
card.classList.remove('sl-no-access--hidden', 'sl-no-access--dimmed');
if (mode === 'hide') {
card.classList.add('sl-no-access--hidden');
} else if (mode === 'dim') {
card.classList.add('sl-no-access--dimmed');
}
// 'badge' mode: leave visible, badge already attached
});
// Update button active state
['sl-btn-hide', 'sl-btn-dim', 'sl-btn-show'].forEach(id => {
const btn = document.getElementById(id);
if (btn) btn.style.outline = '';
});
const activeMap = { hide: 'sl-btn-hide', dim: 'sl-btn-dim', badge: 'sl-btn-show' };
const activeBtn = document.getElementById(activeMap[mode]);
if (activeBtn) activeBtn.style.outline = '2px solid #006EAF';
}
// ── MutationObserver – handles pagination / dynamic content ───────────────
let debounceTimer = null;
const observer = new MutationObserver(() => {
clearTimeout(debounceTimer);
debounceTimer = setTimeout(() => {
buildPanel();
scanAndTag();
}, 400);
});
function init() {
buildPanel();
scanAndTag();
// Watch the search results container (or body as fallback)
const target = document.querySelector('#results-list, [data-test="results"], main') || document.body;
observer.observe(target, { childList: true, subtree: true });
}
// Wait for DOM ready
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init);
} else {
init();
}
})();