/* DYNAMIC TAG SPHERE v2.0 - FULLSCREEN GLASS + MANY-ITEMS FIX */
/* ===== BACKDROP: Frosted Glass über Website ===== */
#tagSphereBackdrop {
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
background: rgba(10, 10, 30, 0.55);
backdrop-filter: blur(12px) saturate(1.4);
-webkit-backdrop-filter: blur(12px) saturate(1.4);
z-index: 2147483640;
opacity: 0;
visibility: hidden;
pointer-events: none;
transition: opacity 0.5s ease, visibility 0.5s ease;
}
#tagSphereBackdrop.visible {
opacity: 1;
visibility: visible;
pointer-events: auto;
}
/* ===== SPHERE CONTAINER ===== */
#tagSphere {
position: fixed;
top: 50%;
left: 20px;
transform: translateY(-50%);
width: 280px;
height: 280px;
z-index: 2147483645;
cursor: move;
cursor: -webkit-grab;
cursor: -moz-grab;
opacity: 0;
visibility: hidden;
transition: opacity 0.4s ease, visibility 0.4s ease,
width 0.7s cubic-bezier(0.68, -0.55, 0.265, 1.55),
height 0.7s cubic-bezier(0.68, -0.55, 0.265, 1.55),
top 0.7s ease, left 0.7s ease, transform 0.7s ease;
background: transparent;
border: none;
user-select: none;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
overflow: visible;
}
#tagSphere.visible {
opacity: 1;
visibility: visible;
}
/* EXPLODED: Voller Bildschirm */
#tagSphere.exploded {
width: 100vw !important;
height: 100vh !important;
max-width: none !important;
max-height: none !important;
top: 0 !important;
left: 0 !important;
transform: none !important;
z-index: 2147483647;
}
/* ===== MOBILE ===== */
@media (max-width: 768px) {
#tagSphere {
width: 250px;
height: 250px;
left: 50vw;
top: 50vh;
transform: translate(-50%, -50%);
}
#tagSphereBackdrop.visible {
background: rgba(10, 10, 30, 0.75);
}
}
/* ===== DRAGGING ===== */
#tagSphere.dragging {
cursor: -webkit-grabbing !important;
cursor: -moz-grabbing !important;
}
/* ===== CLOSE BUTTON ===== */
#tagSphere .close-btn {
position: fixed;
top: 24px;
right: 24px;
width: 60px;
height: 60px;
background: rgba(255, 255, 255, 0.12);
backdrop-filter: blur(20px);
-webkit-backdrop-filter: blur(20px);
color: #FFFFFF;
border: 1px solid rgba(255, 255, 255, 0.25);
border-radius: 50%;
font-size: 32px;
font-weight: 300;
line-height: 58px;
text-align: center;
cursor: pointer;
z-index: 2147483647;
opacity: 0;
visibility: hidden;
transition: all 0.3s ease;
user-select: none;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
}
#tagSphere.exploded .close-btn {
opacity: 1 !important;
visibility: visible !important;
}
#tagSphere .close-btn:hover {
background: rgba(220, 40, 40, 0.85);
border-color: rgba(255, 100, 100, 0.5);
transform: scale(1.1);
box-shadow: 0 8px 32px rgba(220, 40, 40, 0.5);
}
#tagSphere .close-btn:active {
transform: scale(0.92);
}
/* ===== BACK BUTTON ===== */
#tagSphere .back-btn {
position: fixed;
top: 24px;
left: 24px;
width: 60px;
height: 60px;
background: rgba(255, 255, 255, 0.12);
backdrop-filter: blur(20px);
-webkit-backdrop-filter: blur(20px);
color: #FFFFFF;
border: 1px solid rgba(255, 255, 255, 0.25);
border-radius: 50%;
font-size: 28px;
font-weight: 300;
line-height: 58px;
text-align: center;
cursor: pointer;
z-index: 2147483647;
opacity: 0;
visibility: hidden;
transition: all 0.3s ease;
user-select: none;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
}
#tagSphere.mode-pages.exploded .back-btn {
opacity: 1 !important;
visibility: visible !important;
}
#tagSphere .back-btn:hover {
background: rgba(40, 120, 220, 0.85);
border-color: rgba(100, 170, 255, 0.5);
transform: scale(1.1);
box-shadow: 0 8px 32px rgba(40, 120, 220, 0.5);
}
#tagSphere .back-btn:active {
transform: scale(0.92);
}
/* ===== ITEM COUNT INDICATOR ===== */
#tagSphere .item-count {
position: fixed;
bottom: 28px;
left: 50%;
transform: translateX(-50%);
padding: 10px 28px;
background: rgba(255, 255, 255, 0.08);
backdrop-filter: blur(16px);
-webkit-backdrop-filter: blur(16px);
border: 1px solid rgba(255, 255, 255, 0.15);
border-radius: 30px;
color: rgba(255, 255, 255, 0.7);
font-size: 14px;
font-weight: 500;
letter-spacing: 0.5px;
z-index: 2147483647;
opacity: 0;
visibility: hidden;
transition: all 0.3s ease;
user-select: none;
}
#tagSphere.exploded .item-count {
opacity: 1;
visibility: visible;
}
/* ===== MOBILE BUTTON SIZES ===== */
@media (max-width: 768px) {
#tagSphere .close-btn,
#tagSphere .back-btn {
width: 72px;
height: 72px;
font-size: 38px;
line-height: 70px;
}
#tagSphere .item-count {
bottom: 20px;
font-size: 13px;
}
}
/* ===== TAG ITEMS: Glass Pills ===== */
#tagSphere .tag-item {
position: absolute;
padding: 14px 26px;
white-space: nowrap;
font-size: 18px;
font-weight: 600;
text-decoration: none;
border: none;
border-radius: 30px;
cursor: pointer;
min-width: 60px;
text-align: center;
transition: transform 0.2s ease, box-shadow 0.2s ease, opacity 0.3s ease;
pointer-events: auto;
z-index: 100;
user-select: none;
letter-spacing: 0.3px;
}
/* Compact Mode: Kleine Items wenn viele */
#tagSphere.items-many .tag-item {
padding: 10px 18px;
font-size: 14px;
min-width: 40px;
border-radius: 20px;
}
@media (max-width: 768px) {
#tagSphere .tag-item {
padding: 16px 30px;
font-size: 20px;
min-width: 80px;
}
#tagSphere.items-many .tag-item {
padding: 12px 20px;
font-size: 16px;
min-width: 50px;
}
}
/* ===== COLLAPSED: Weisse Pills ===== */
#tagSphere:not(.exploded) .tag-item {
background: #FFFFFF;
color: #333333;
box-shadow: 0 3px 10px rgba(0, 0, 0, 0.15);
}
/* ===== EXPLODED TAGS: Glass auf dunklem Backdrop ===== */
#tagSphere.exploded.mode-tags .tag-item {
background: rgba(255, 255, 255, 0.1);
backdrop-filter: blur(8px);
-webkit-backdrop-filter: blur(8px);
border: 1px solid rgba(255, 255, 255, 0.2);
color: #FFFFFF;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.2);
text-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);
}
#tagSphere.exploded.mode-tags .tag-item:hover {
background: rgba(80, 160, 255, 0.3);
border-color: rgba(120, 180, 255, 0.5);
box-shadow: 0 8px 32px rgba(80, 160, 255, 0.5);
transform: scale(1.25) !important;
z-index: 9998 !important;
color: #FFFFFF;
}
/* ===== EXPLODED PAGES: Glass mit Rot-Abstufung ===== */
#tagSphere.exploded.mode-pages .tag-item {
background: rgba(255, 255, 255, 0.1);
backdrop-filter: blur(8px);
-webkit-backdrop-filter: blur(8px);
border: 1px solid rgba(255, 255, 255, 0.2);
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.2);
text-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);
}
#tagSphere.exploded.mode-pages .tag-item:hover {
background: rgba(220, 60, 60, 0.35);
border-color: rgba(255, 120, 120, 0.5);
box-shadow: 0 8px 32px rgba(220, 60, 60, 0.5);
transform: scale(1.25) !important;
z-index: 9998 !important;
}
/* ===== FLOAT ANIMATIONS ===== */
#tagSphere .tag-item.atom-0 { animation: float-0 4.2s ease-in-out infinite; }
#tagSphere .tag-item.atom-1 { animation: float-1 5.1s ease-in-out infinite; }
#tagSphere .tag-item.atom-2 { animation: float-2 4.7s ease-in-out infinite; }
#tagSphere .tag-item.atom-3 { animation: float-3 5.5s ease-in-out infinite; }
#tagSphere .tag-item.atom-4 { animation: float-4 4.9s ease-in-out infinite; }
#tagSphere .tag-item.atom-5 { animation: float-5 5.3s ease-in-out infinite; }
#tagSphere .tag-item.atom-6 { animation: float-6 4.5s ease-in-out infinite; }
#tagSphere .tag-item.atom-7 { animation: float-7 5.8s ease-in-out infinite; }
@keyframes float-0 {
0%, 100% { transform: translate(0, 0) rotate(0deg); }
25% { transform: translate(3px, -4px) rotate(1deg); }
50% { transform: translate(-3px, -5px) rotate(-1deg); }
75% { transform: translate(-4px, -2px) rotate(2deg); }
}
@keyframes float-1 {
0%, 100% { transform: translate(0, 0) rotate(0deg); }
25% { transform: translate(-3px, -5px) rotate(-1deg); }
50% { transform: translate(4px, -3px) rotate(1deg); }
75% { transform: translate(3px, -6px) rotate(-2deg); }
}
@keyframes float-2 {
0%, 100% { transform: translate(0, 0) rotate(0deg); }
25% { transform: translate(4px, -3px) rotate(2deg); }
50% { transform: translate(-3px, -6px) rotate(-1deg); }
75% { transform: translate(3px, -4px) rotate(1deg); }
}
@keyframes float-3 {
0%, 100% { transform: translate(0, 0) rotate(0deg); }
25% { transform: translate(-4px, -6px) rotate(-2deg); }
50% { transform: translate(3px, -4px) rotate(1deg); }
75% { transform: translate(-3px, -5px) rotate(-1deg); }
}
@keyframes float-4 {
0%, 100% { transform: translate(0, 0) rotate(0deg); }
25% { transform: translate(3px, -5px) rotate(1deg); }
50% { transform: translate(-4px, -4px) rotate(-2deg); }
75% { transform: translate(4px, -6px) rotate(2deg); }
}
@keyframes float-5 {
0%, 100% { transform: translate(0, 0) rotate(0deg); }
25% { transform: translate(-3px, -4px) rotate(-1deg); }
50% { transform: translate(4px, -6px) rotate(2deg); }
75% { transform: translate(-4px, -3px) rotate(-2deg); }
}
@keyframes float-6 {
0%, 100% { transform: translate(0, 0) rotate(0deg); }
25% { transform: translate(4px, -6px) rotate(2deg); }
50% { transform: translate(-3px, -5px) rotate(-1deg); }
75% { transform: translate(3px, -3px) rotate(1deg); }
}
@keyframes float-7 {
0%, 100% { transform: translate(0, 0) rotate(0deg); }
25% { transform: translate(-4px, -4px) rotate(-2deg); }
50% { transform: translate(3px, -6px) rotate(1deg); }
75% { transform: translate(-3px, -5px) rotate(-1deg); }
}
/* ===== LOADING PULSE ===== */
#tagSphere.loading::after {
content: '';
position: fixed;
top: 50%;
left: 50%;
width: 60px;
height: 60px;
margin: -30px 0 0 -30px;
border: 3px solid rgba(255, 255, 255, 0.15);
border-top-color: #FFFFFF;
border-radius: 50%;
z-index: 2147483647;
animation: sphere-spin 0.8s linear infinite;
}
@keyframes sphere-spin {
to { transform: rotate(360deg); }
}
(function(){
/* ===== CONFIG ===== */
var CFG = new Object();
CFG.baseRadius = 120;
CFG.smallSize = 280;
CFG.smallSizeMobile = 250;
CFG.baseFontSize = 18;
CFG.baseFontSizeMobile = 20;
CFG.minFontPages = 14;
CFG.maxFontPages = 34;
CFG.minFontPagesMobile = 16;
CFG.maxFontPagesMobile = 36;
CFG.inactiveOpacity = 0.4;
CFG.verticalGradient = 0.6;
CFG.autoExplodeTrigger = 0.3;
CFG.scrollThreshold = 1000;
CFG.mobileBreak = 768;
CFG.landscapeBreak = 1024;
CFG.dragSense = 0.008;
CFG.moveThreshold = 15;
CFG.manyItemsThreshold = 20;
/* Dynamischer Radius: base + sqrt(count) * factor */
CFG.radiusBase = 0.28;
CFG.radiusFactor = 0.012;
CFG.radiusMax = 0.45;
/* ===== STATE ===== */
var c = null;
var backdrop = null;
var closeBtn = null;
var backBtn = null;
var countLabel = null;
var items = [];
var angleX = 0;
var angleY = 0;
var exploded = false;
var drag = false;
var lastX = 0;
var lastY = 0;
var visible = false;
var loading = false;
var mode = 'tags';
var originalTags = [];
var isMobile = false;
var startX = 0;
var startY = 0;
var hasMoved = false;
var isActive = false;
var isLandscape = false;
var clickStartX = 0;
var clickStartY = 0;
var clickStartTime = 0;
/* ===== HELPERS ===== */
function detectMobile() {
isMobile = window.innerWidth <= CFG.mobileBreak;
}
function detectLandscape() {
isLandscape = (window.innerWidth > window.innerHeight) && (window.innerWidth >= CFG.landscapeBreak);
}
function getViewportSize() {
if (window.visualViewport) {
return { width: window.visualViewport.width, height: window.visualViewport.height };
}
return { width: window.innerWidth, height: window.innerHeight };
}
/* Dynamischer Radius basierend auf Item-Anzahl */
function calcExplodedRadius(count) {
var vp = getViewportSize();
var smaller = Math.min(vp.width, vp.height);
var factor = CFG.radiusBase + (Math.sqrt(count) * CFG.radiusFactor);
if (factor > CFG.radiusMax) factor = CFG.radiusMax;
return smaller * factor;
}
function updateItemCountClass() {
var cn = c.className;
cn = cn.replace(/\s*items-many/g, '');
if (items.length > CFG.manyItemsThreshold) {
cn += ' items-many';
}
c.className = cn;
}
function updateCountLabel() {
if (!countLabel) return;
if (mode === 'pages') {
countLabel.textContent = items.length + ' Seiten';
} else {
countLabel.textContent = items.length + ' Tags';
}
}
/* ===== CREATE DOM ===== */
function createContainer() {
backdrop = document.createElement('div');
backdrop.id = 'tagSphereBackdrop';
c = document.createElement('div');
c.id = 'tagSphere';
c.className = 'mode-tags';
closeBtn = document.createElement('button');
closeBtn.className = 'close-btn';
closeBtn.textContent = '\u00D7';
closeBtn.title = 'Schliessen';
backBtn = document.createElement('button');
backBtn.className = 'back-btn';
backBtn.textContent = '\u25C0';
backBtn.title = 'Zurueck zu Tags';
countLabel = document.createElement('div');
countLabel.className = 'item-count';
countLabel.textContent = '';
c.appendChild(closeBtn);
c.appendChild(backBtn);
c.appendChild(countLabel);
var body = document.body || document.getElementsByTagName('body')[0];
body.appendChild(backdrop);
body.appendChild(c);
}
/* ===== EXTRACT TAGS ===== */
function extractTags() {
var box = document.querySelector('.pageTags');
if (!box) return;
var links = box.getElementsByTagName('a');
for (var i = 0; i < links.length; i++) {
var link = links[i];
var txt = (link.textContent || link.innerText || '').replace(/^\s+|\s+$/g, '');
if (txt.indexOf('bearbeiten') !== -1 || txt.indexOf('edit') !== -1) continue;
if (txt.length > 0 && link.href) {
items.push({
text: txt,
url: link.href,
isTag: true,
views: 0,
phi: 0,
theta: 0
});
}
}
originalTags = items.slice();
distributeItems();
}
/* ===== FIBONACCI SPHERE DISTRIBUTION ===== */
function distributeItems() {
var count = items.length;
if (count === 0) return;
var goldenAngle = Math.PI * (3 - Math.sqrt(5));
for (var i = 0; i < count; i++) {
var y = 1 - (i / (count - 1)) * 2;
var theta = goldenAngle * i;
items[i].phi = theta;
items[i].theta = Math.acos(y);
}
}
/* ===== CLEAR / CREATE ELEMENTS ===== */
function clearItems() {
var elements = c.getElementsByClassName('tag-item');
while (elements.length > 0) {
c.removeChild(elements[0]);
}
items = [];
}
function createElements() {
for (var i = 0; i < items.length; i++) {
var item = items[i];
var span = document.createElement('span');
span.className = 'tag-item atom-' + (i % 8);
var icon = item.isTag ? '\uD83D\uDD17 ' : '\uD83D\uDCC4 ';
span.textContent = icon + item.text;
span.title = item.text + (item.views > 0 ? ' (' + item.views + ' views)' : '');
span.setAttribute('data-text', item.text);
span.setAttribute('data-url', item.url);
span.setAttribute('data-istag', item.isTag ? '1' : '0');
span.setAttribute('data-views', item.views || 0);
span.setAttribute('data-idx', i);
/* Farbe fuer Pages nach Views */
if (mode === 'pages') {
var maxV = 0;
var minV = 999999999;
var hasV = false;
for (var j = 0; j < items.length; j++) {
if (items[j].views > 0) {
hasV = true;
if (items[j].views > maxV) maxV = items[j].views;
if (items[j].views < minV) minV = items[j].views;
}
}
if (hasV && item.views > 0) {
var vRange = maxV - minV;
if (vRange > 0) {
var norm = (item.views - minV) / vRange;
/* Gradient: weiss -> orange -> rot */
var r = Math.floor(200 + (norm * 55));
var g = Math.floor(200 - (norm * 200));
var b = Math.floor(200 - (norm * 200));
span.style.color = 'rgb(' + r + ',' + g + ',' + b + ')';
} else {
span.style.color = '#FF6B6B';
}
} else {
span.style.color = 'rgba(255,255,255,0.85)';
}
}
c.appendChild(span);
}
updateItemCountClass();
updateCountLabel();
}
/* ===== POSITIONING ===== */
function position() {
var elements = c.getElementsByClassName('tag-item');
if (elements.length === 0) return;
var vp = getViewportSize();
var count = items.length;
var radius;
var centerX;
var centerY;
if (exploded) {
radius = calcExplodedRadius(count);
centerX = vp.width / 2;
centerY = vp.height / 2;
} else {
radius = CFG.baseRadius;
var smallS = isMobile ? CFG.smallSizeMobile : CFG.smallSize;
centerX = smallS / 2;
centerY = smallS / 2;
}
/* Spread-Faktoren: Landscape breiter, Portrait hoeher */
var xF, yF;
if (exploded) {
if (isLandscape) {
xF = (vp.width / vp.height) * 0.75;
yF = 0.9;
} else {
xF = 0.65;
yF = (vp.height / vp.width) * 0.6;
}
} else {
xF = 0.4;
yF = 1.5;
}
for (var i = 0; i < elements.length; i++) {
if (i >= items.length) break;
var elem = elements[i];
var item = items[i];
var phi = item.phi + angleX;
var theta = item.theta + (angleY * 0.3);
var x = radius * Math.sin(theta) * Math.cos(phi) * xF;
var y = radius * Math.sin(theta) * Math.sin(phi) * yF;
var z = radius * Math.cos(theta);
var scale = (z + radius) / (radius * 2);
scale = Math.max(0.6, Math.min(1.3, scale));
/* Font Size */
var baseFS = isMobile ? CFG.baseFontSizeMobile : CFG.baseFontSize;
var fs = baseFS;
/* Compact mode fuer viele Items */
if (count > CFG.manyItemsThreshold) {
var shrink = 1.0 - (Math.min(count, 80) - CFG.manyItemsThreshold) * 0.008;
if (shrink < 0.65) shrink = 0.65;
fs = Math.floor(fs * shrink);
}
/* Pages: Font nach Views skalieren */
if (mode === 'pages' && item.views > 0) {
var maxV2 = 0;
var minV2 = 999999999;
for (var j = 0; j < items.length; j++) {
if (items[j].views > 0) {
if (items[j].views > maxV2) maxV2 = items[j].views;
if (items[j].views < minV2) minV2 = items[j].views;
}
}
var vR = maxV2 - minV2;
if (vR > 0) {
var norm2 = (item.views - minV2) / vR;
var minF = isMobile ? CFG.minFontPagesMobile : CFG.minFontPages;
var maxF = isMobile ? CFG.maxFontPagesMobile : CFG.maxFontPages;
fs = Math.floor(minF + (norm2 * (maxF - minF)));
}
}
fs = Math.floor(fs * scale);
elem.style.fontSize = fs + 'px';
/* Approximate element size fuer Zentrierung */
var approxW = fs * item.text.length * 0.5 + 40;
var approxH = fs + 24;
if (exploded) {
elem.style.left = Math.floor(centerX + x - approxW / 2) + 'px';
elem.style.top = Math.floor(centerY + y - approxH / 2) + 'px';
} else {
elem.style.left = Math.floor(centerX + x - 40) + 'px';
elem.style.top = Math.floor(centerY + y - 20) + 'px';
}
/* Z-Index: Naeher = hoeher */
elem.style.zIndex = 100 + Math.floor(scale * 200);
/* Opacity */
var baseO = 0.6 + (scale * 0.4);
var posF = 1.0;
if (!exploded) {
var cH = isMobile ? CFG.smallSizeMobile : CFG.smallSize;
var yPos = parseFloat(elem.style.top);
var relY = yPos / cH;
posF = 1.0 - (Math.abs(relY - 0.5) * CFG.verticalGradient);
if (posF < 0.6) posF = 0.6;
if (posF > 1.0) posF = 1.0;
}
var finalO = baseO * posF;
if (!isActive && !exploded) {
finalO = finalO * CFG.inactiveOpacity;
}
elem.style.opacity = finalO;
}
}
/* ===== EXPLODE / COLLAPSE ===== */
function toggleExplode() {
exploded = !exploded;
var cn = c.className;
if (exploded) {
cn += ' exploded';
backdrop.className = 'visible';
} else {
cn = cn.replace(/\s*exploded/g, '');
backdrop.className = '';
if (mode === 'pages') {
backToTags();
}
}
c.className = cn;
position();
}
function backToTags() {
clearItems();
items = originalTags.slice();
distributeItems();
mode = 'tags';
var cn = c.className;
cn = cn.replace(/mode-\w+/g, '') + ' mode-tags';
if (exploded) cn += ' exploded';
if (visible) cn += ' visible';
c.className = cn;
createElements();
position();
}
/* ===== CLICK HANDLER (FIX: distance + time based) ===== */
function handleClick(e) {
e = e || window.event;
var target = e.target || e.srcElement;
if (loading) return;
/* Drag-Check: Distanz + Zeit */
if (hasMoved) return;
/* Close */
if (target === closeBtn) {
if (exploded) toggleExplode();
if (e.preventDefault) e.preventDefault();
return false;
}
/* Back */
if (target === backBtn) {
backToTags();
if (e.preventDefault) e.preventDefault();
return false;
}
/* Backdrop */
if (target === backdrop) {
if (exploded) toggleExplode();
if (e.preventDefault) e.preventDefault();
return false;
}
/* Tag Item suchen */
var clickedTag = false;
var tempT = target;
while (tempT && tempT !== c) {
if (tempT.className && tempT.className.indexOf('tag-item') !== -1) {
clickedTag = true;
var isTag = tempT.getAttribute('data-istag') === '1';
var text = tempT.getAttribute('data-text');
var url = tempT.getAttribute('data-url');
if (isTag) {
if (!exploded) toggleExplode();
loadPagesForTag(text);
} else {
/* PAGE CLICK: Direkt navigieren */
window.location.href = url;
}
if (e.preventDefault) e.preventDefault();
return false;
}
tempT = tempT.parentNode;
}
/* Leerer Bereich: Explode toggle */
if (!exploded && !clickedTag) {
var triggerPos = window.innerWidth * CFG.autoExplodeTrigger;
if (clickStartX < triggerPos) {
toggleExplode();
}
if (e.preventDefault) e.preventDefault();
return false;
}
}
/* ===== LOAD PAGES FOR TAG ===== */
function loadPagesForTag(tagName) {
loading = true;
var cn = c.className.replace(/mode-\w+/g, '');
c.className = cn + ' loading';
if (exploded) c.className += ' exploded';
if (visible) c.className += ' visible';
var url = '/Special:Tags?tag=' + encodeURIComponent(tagName);
var xhr = window.XMLHttpRequest ? new XMLHttpRequest() : new ActiveXObject('Microsoft.XMLHTTP');
xhr.onreadystatechange = function() {
if (xhr.readyState === 4) {
if (xhr.status === 200) {
parsePages(xhr.responseText);
} else {
loading = false;
c.className = c.className.replace(/loading/g, '') + ' mode-tags';
if (visible) c.className += ' visible';
}
}
};
try {
xhr.open('GET', url, true);
xhr.send();
} catch(err) {
loading = false;
}
}
function parsePages(html) {
var tempDiv = document.createElement('div');
tempDiv.innerHTML = html;
var specialT = tempDiv.querySelector('#SpecialTags');
if (!specialT) {
/* Fallback: versuche table oder ul */
specialT = tempDiv.querySelector('.wiki-directory') || tempDiv.querySelector('table') || tempDiv;
}
var newItems = [];
var links = specialT.getElementsByTagName('a');
var seen = {};
for (var i = 0; i < links.length; i++) {
var link = links[i];
var txt = (link.textContent || link.innerText || '').replace(/^\s+|\s+$/g, '');
var href = link.getAttribute('href');
if (txt.length > 0 && href && !seen[txt]) {
seen[txt] = true;
if (href.indexOf('http') !== 0) {
href = window.location.protocol + '//' + window.location.host + href;
}
newItems.push({
text: txt,
url: href,
isTag: false,
views: 0,
phi: 0,
theta: 0
});
}
}
clearItems();
items = newItems;
distributeItems();
mode = 'pages';
loadPopularity();
}
function loadPopularity() {
var xhr = window.XMLHttpRequest ? new XMLHttpRequest() : new ActiveXObject('Microsoft.XMLHTTP');
xhr.onreadystatechange = function() {
if (xhr.readyState === 4) {
if (xhr.status === 200) {
parsePopularity(xhr.responseText);
} else {
finishPageLoad();
}
}
};
try {
xhr.open('GET', '/Special:Popularpages', true);
xhr.send();
} catch(err) {
finishPageLoad();
}
}
function parsePopularity(html) {
var tempDiv = document.createElement('div');
tempDiv.innerHTML = html;
var rows = tempDiv.getElementsByTagName('tr');
var viewData = {};
for (var i = 0; i < rows.length; i++) {
var cells = rows[i].getElementsByTagName('td');
if (cells.length >= 2) {
var link = cells[0].getElementsByTagName('a')[0];
if (link) {
var pageName = (link.textContent || link.innerText || '').replace(/^\s+|\s+$/g, '');
var viewText = (cells[1].textContent || cells[1].innerText || '').replace(/^\s+|\s+$/g, '');
var views = parseInt(viewText.replace(/[^0-9]/g, ''), 10);
if (pageName && !isNaN(views)) {
viewData[pageName] = views;
}
}
}
}
for (var j = 0; j < items.length; j++) {
if (viewData[items[j].text]) {
items[j].views = viewData[items[j].text];
}
}
finishPageLoad();
}
function finishPageLoad() {
createElements();
position();
loading = false;
var cn = 'mode-pages exploded';
if (visible) cn += ' visible';
c.className = cn;
updateItemCountClass();
}
/* ===== SCROLL VISIBILITY ===== */
function updateVisibility() {
var scrollTop = window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop || 0;
if (scrollTop > CFG.scrollThreshold && !visible) {
c.className += ' visible';
visible = true;
} else if (scrollTop <= CFG.scrollThreshold && visible) {
c.className = c.className.replace(/\s*visible/g, '');
visible = false;
}
}
/* ===== MOUSE ENTER/LEAVE ===== */
function activate() {
isActive = true;
position();
}
function deactivate() {
isActive = false;
position();
}
/* ===== DRAG HANDLERS (FIX: Bessere Click-Erkennung) ===== */
function down(e) {
if (loading) return;
activate();
var touch = e.touches ? e.touches[0] : e;
startX = touch.clientX || touch.pageX || 0;
startY = touch.clientY || touch.pageY || 0;
clickStartX = startX;
clickStartY = startY;
clickStartTime = new Date().getTime();
lastX = startX;
lastY = startY;
hasMoved = false;
drag = true;
if (e.preventDefault) e.preventDefault();
return false;
}
function move(e) {
if (!drag) return;
var touch = e.touches ? e.touches[0] : e;
var x = touch.clientX || touch.pageX || 0;
var y = touch.clientY || touch.pageY || 0;
var deltaX = Math.abs(x - startX);
var deltaY = Math.abs(y - startY);
/* Hoehere Schwelle: Erst ab 15px ist es ein Drag */
if (deltaX > CFG.moveThreshold || deltaY > CFG.moveThreshold) {
hasMoved = true;
var cn = c.className;
if (cn.indexOf('dragging') === -1) {
c.className = cn + ' dragging';
}
angleX += (x - lastX) * CFG.dragSense;
angleY += (y - lastY) * CFG.dragSense;
position();
}
lastX = x;
lastY = y;
if (e.preventDefault) e.preventDefault();
return false;
}
function up(e) {
if (!drag) return;
drag = false;
c.className = c.className.replace(/\s*dragging/g, '');
/* Click nur wenn: wenig Bewegung UND kurze Zeit */
var elapsed = new Date().getTime() - clickStartTime;
if (!hasMoved && elapsed < 500) {
handleClick(e);
}
}
function handleMouseEnter() {
activate();
}
function handleMouseLeave() {
if (!drag) {
deactivate();
}
}
/* ===== RESIZE ===== */
function handleResize() {
detectMobile();
detectLandscape();
position();
}
/* ===== BIND EVENTS ===== */
function bind() {
if (backdrop.addEventListener) {
backdrop.addEventListener('click', handleClick, false);
} else if (backdrop.attachEvent) {
backdrop.attachEvent('onclick', handleClick);
}
if (c.addEventListener) {
c.addEventListener('mousedown', down, false);
c.addEventListener('touchstart', down, false);
c.addEventListener('mouseenter', handleMouseEnter, false);
c.addEventListener('mouseleave', handleMouseLeave, false);
document.addEventListener('mousemove', move, false);
document.addEventListener('touchmove', move, false);
document.addEventListener('mouseup', up, false);
document.addEventListener('touchend', up, false);
} else if (c.attachEvent) {
c.attachEvent('onmousedown', down);
c.attachEvent('onmouseenter', handleMouseEnter);
c.attachEvent('onmouseleave', handleMouseLeave);
document.attachEvent('onmousemove', move);
document.attachEvent('onmouseup', up);
}
if (window.addEventListener) {
window.addEventListener('scroll', updateVisibility, false);
window.addEventListener('resize', handleResize, false);
if (window.visualViewport) {
window.visualViewport.addEventListener('resize', handleResize, false);
}
} else if (window.attachEvent) {
window.attachEvent('onscroll', updateVisibility);
window.attachEvent('onresize', handleResize);
}
}
/* ===== INIT ===== */
function init() {
try {
detectMobile();
detectLandscape();
createContainer();
extractTags();
if (items.length === 0) {
c.style.display = 'none';
backdrop.style.display = 'none';
return;
}
createElements();
position();
bind();
updateVisibility();
} catch(err) {
if (window.console && console.error) {
console.error('TagSphere init error:', err);
}
}
}
if (document.readyState === 'complete') {
init();
} else if (document.addEventListener) {
document.addEventListener('DOMContentLoaded', init, false);
} else {
window.onload = init;
}
})();