sharing mechanic with eye-watering animation

This commit is contained in:
overcuriousity
2025-07-21 22:31:43 +02:00
parent 6ae7f36660
commit a6b51187b7
6 changed files with 983 additions and 273 deletions

View File

@@ -21,7 +21,6 @@ export interface Props {
const { tool } = Astro.props;
// Check types
const isMethod = tool.type === 'method';
const isConcept = tool.type === 'concept';

View File

@@ -1,5 +1,7 @@
---
import { getToolsData } from '../utils/dataService.js';
import ShareButton from './ShareButton.astro';
// Load tools data
@@ -146,12 +148,17 @@ domains.forEach((domain: any) => {
<div class="tool-details" id="tool-details-primary">
<div style="display: flex; justify-content: space-between; align-items: start; margin-bottom: 1rem;">
<h2 id="tool-name-primary" style="margin: 0;">Tool Name</h2>
<button class="btn-icon" onclick="window.hideToolDetails('primary')">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<line x1="18" y1="6" x2="6" y2="18"></line>
<line x1="6" y1="6" x2="18" y2="18"></line>
</svg>
</button>
<div style="display: flex; align-items: center; gap: 0.5rem;">
<div id="share-button-primary" style="display: none;">
<!-- Share button will be populated by JavaScript -->
</div>
<button class="btn-icon" onclick="window.hideToolDetails('primary')">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<line x1="18" y1="6" x2="6" y2="18"></line>
<line x1="6" y1="6" x2="18" y2="18"></line>
</svg>
</button>
</div>
</div>
<p id="tool-description-primary" class="text-muted"></p>
@@ -169,12 +176,17 @@ domains.forEach((domain: any) => {
<div class="tool-details" id="tool-details-secondary">
<div style="display: flex; justify-content: space-between; align-items: start; margin-bottom: 1rem;">
<h2 id="tool-name-secondary" style="margin: 0;">Tool Name</h2>
<button class="btn-icon" onclick="window.hideToolDetails('secondary')">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<line x1="18" y1="6" x2="6" y2="18"></line>
<line x1="6" y1="6" x2="18" y2="18"></line>
</svg>
</button>
<div style="display: flex; align-items: center; gap: 0.5rem;">
<div id="share-button-secondary" style="display: none;">
<!-- Share button will be populated by JavaScript -->
</div>
<button class="btn-icon" onclick="window.hideToolDetails('secondary')">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<line x1="18" y1="6" x2="6" y2="18"></line>
<line x1="6" y1="6" x2="18" y2="18"></line>
</svg>
</button>
</div>
</div>
<p id="tool-description-secondary" class="text-muted"></p>
@@ -266,6 +278,201 @@ domains.forEach((domain: any) => {
}
}
// ===== SHARING FUNCTIONALITY =====
// Create tool slug from name (same logic as ShareButton.astro)
function createToolSlug(toolName) {
return toolName.toLowerCase()
.replace(/[^a-z0-9\s-]/g, '') // Remove special characters
.replace(/\s+/g, '-') // Replace spaces with hyphens
.replace(/-+/g, '-') // Remove duplicate hyphens
.replace(/^-|-$/g, ''); // Remove leading/trailing hyphens
}
// Find tool by name or slug
function findTool(identifier) {
return toolsData.find(tool =>
tool.name === identifier ||
createToolSlug(tool.name) === identifier.toLowerCase()
);
}
// Generate share URLs
function generateShareURL(toolName, view, modal = null) {
const toolSlug = createToolSlug(toolName);
const baseUrl = window.location.origin + window.location.pathname;
const params = new URLSearchParams();
params.set('tool', toolSlug);
params.set('view', view);
if (modal) {
params.set('modal', modal);
}
return `${baseUrl}?${params.toString()}`;
}
// Copy to clipboard with feedback
async function copyToClipboard(text, button) {
try {
await navigator.clipboard.writeText(text);
// Show feedback
const originalHTML = button.innerHTML;
button.innerHTML = '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="20,6 9,17 4,12"/></svg> Kopiert!';
button.style.color = 'var(--color-accent)';
setTimeout(() => {
button.innerHTML = originalHTML;
button.style.color = '';
}, 2000);
} catch (err) {
// Fallback for older browsers
const textArea = document.createElement('textarea');
textArea.value = text;
document.body.appendChild(textArea);
textArea.select();
document.execCommand('copy');
document.body.removeChild(textArea);
// Show feedback
const originalHTML = button.innerHTML;
button.innerHTML = 'Kopiert!';
setTimeout(() => {
button.innerHTML = originalHTML;
}, 2000);
}
}
// Show share dialog
window.showShareDialog = function(shareButton) {
const toolName = shareButton.getAttribute('data-tool-name');
const context = shareButton.getAttribute('data-context');
// Create modal backdrop
let backdrop = document.getElementById('share-modal-backdrop');
if (!backdrop) {
backdrop = document.createElement('div');
backdrop.id = 'share-modal-backdrop';
backdrop.style.cssText = `
position: fixed; top: 0; left: 0; right: 0; bottom: 0;
background: rgba(0, 0, 0, 0.5); z-index: 9999;
display: flex; align-items: center; justify-content: center;
opacity: 0; transition: opacity 0.2s ease;
`;
document.body.appendChild(backdrop);
}
// Create share dialog
const dialog = document.createElement('div');
dialog.style.cssText = `
background: var(--color-bg); border: 1px solid var(--color-border);
border-radius: 0.75rem; padding: 1.5rem; max-width: 400px; width: 90%;
box-shadow: var(--shadow-lg); transform: scale(0.9); transition: transform 0.2s ease;
`;
dialog.innerHTML = `
<div style="display: flex; align-items: center; justify-content: space-between; margin-bottom: 1rem;">
<h3 style="margin: 0; color: var(--color-primary);">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="margin-right: 0.5rem; vertical-align: middle;">
<circle cx="18" cy="5" r="3"/><circle cx="6" cy="12" r="3"/><circle cx="18" cy="19" r="3"/>
<line x1="8.59" y1="13.51" x2="15.42" y2="17.49"/><line x1="15.41" y1="6.51" x2="8.59" y2="10.49"/>
</svg>
${toolName} teilen
</h3>
<button id="close-share-dialog" style="background: none; border: none; cursor: pointer; padding: 0.25rem;color: var(--color-text-secondary)">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/>
</svg>
</button>
</div>
<div style="display: flex; flex-direction: column; gap: 0.75rem;">
<button class="share-option-btn" data-url="${generateShareURL(toolName, 'grid')}"
style="display: flex; align-items: center; gap: 0.75rem; padding: 0.75rem; border: 1px solid var(--color-border); border-radius: 0.5rem; background: var(--color-bg); cursor: pointer; transition: var(--transition-fast); text-align: left; width: 100%;">
<div style="width: 32px; height: 32px; background: var(--color-primary); border-radius: 0.25rem; display: flex; align-items: center; justify-content: center;">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="white" stroke-width="2">
<rect x="3" y="3" width="7" height="7"/><rect x="14" y="3" width="7" height="7"/>
<rect x="14" y="14" width="7" height="7"/><rect x="3" y="14" width="7" height="7"/>
</svg>
</div>
<div>
<div style="font-weight: 500; margin-bottom: 0.25rem;color: var(--color-text-secondary)">Kachelansicht</div>
<div style="font-size: 0.8125rem; color: var(--color-text-secondary);">Scrollt zur Karte in der Übersicht</div>
</div>
</button>
<button class="share-option-btn" data-url="${generateShareURL(toolName, 'matrix')}"
style="display: flex; align-items: center; gap: 0.75rem; padding: 0.75rem; border: 1px solid var(--color-border); border-radius: 0.5rem; background: var(--color-bg); cursor: pointer; transition: var(--transition-fast); text-align: left; width: 100%;">
<div style="width: 32px; height: 32px; background: var(--color-accent); border-radius: 0.25rem; display: flex; align-items: center; justify-content: center;">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="white" stroke-width="2">
<path d="M3 3h7v7H3zM14 3h7v7h-7zM14 14h7v7h-7zM3 14h7v7H3z"/>
</svg>
</div>
<div>
<div style="font-weight: 500; margin-bottom: 0.25rem;color: var(--color-text-secondary)">Matrix-Ansicht</div>
<div style="font-size: 0.8125rem; color: var(--color-text-secondary);">Zeigt Tool-Position in der Matrix</div>
</div>
</button>
<button class="share-option-btn" data-url="${generateShareURL(toolName, 'modal', 'primary')}"
style="display: flex; align-items: center; gap: 0.75rem; padding: 0.75rem; border: 1px solid var(--color-border); border-radius: 0.5rem; background: var(--color-bg); cursor: pointer; transition: var(--transition-fast); text-align: left; width: 100%;">
<div style="width: 32px; height: 32px; background: var(--color-warning); border-radius: 0.25rem; display: flex; align-items: center; justify-content: center;">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="white" stroke-width="2">
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/>
<polyline points="14 2 14 8 20 8"/>
</svg>
</div>
<div>
<div style="font-weight: 500; margin-bottom: 0.25rem;color: var(--color-text-secondary)">Tool-Details</div>
<div style="font-size: 0.8125rem; color: var(--color-text-secondary);">Öffnet Detail-Fenster direkt</div>
</div>
</button>
</div>
`;
backdrop.appendChild(dialog);
// Show with animation
requestAnimationFrame(() => {
backdrop.style.opacity = '1';
dialog.style.transform = 'scale(1)';
});
// Event handlers
const closeDialog = () => {
backdrop.style.opacity = '0';
dialog.style.transform = 'scale(0.9)';
setTimeout(() => {
if (backdrop.parentNode) {
document.body.removeChild(backdrop);
}
}, 200);
};
backdrop.addEventListener('click', (e) => {
if (e.target === backdrop) closeDialog();
});
document.getElementById('close-share-dialog').addEventListener('click', closeDialog);
// Share option handlers
dialog.querySelectorAll('.share-option-btn').forEach(btn => {
btn.addEventListener('mouseover', () => {
btn.style.backgroundColor = 'var(--color-bg-secondary)';
btn.style.borderColor = 'var(--color-primary)';
});
btn.addEventListener('mouseout', () => {
btn.style.backgroundColor = 'var(--color-bg)';
btn.style.borderColor = 'var(--color-border)';
});
btn.addEventListener('click', () => {
const url = btn.getAttribute('data-url');
copyToClipboard(url, btn);
});
});
};
// Make functions globally available
window.toggleDomainAgnosticSection = toggleDomainAgnosticSection;
@@ -372,10 +579,23 @@ domains.forEach((domain: any) => {
return `<span class="tag" style="background-color: var(--color-bg-tertiary); color: var(--color-text-secondary); margin: 0.125rem;">${conceptName}</span>`;
}).join('');
// Check if mobile device
const isMobile = window.innerWidth <= 768;
const collapseOnMobile = isMobile && relatedConcepts.length > 2;
tagsHTML += `
<div style="margin-top: 1rem;">
<strong style="display: block; margin-bottom: 0.5rem; color: var(--color-text);">Verwandte Konzepte:</strong>
<div style="display: flex; flex-wrap: wrap; gap: 0.25rem;">
<div style="display: flex; align-items: center; gap: 0.5rem; margin-bottom: 0.5rem;">
<strong style="color: var(--color-text);">Verwandte Konzepte:</strong>
${collapseOnMobile ? `
<button id="concepts-toggle-${modalType}"
onclick="this.nextElementSibling.style.display = this.nextElementSibling.style.display === 'none' ? 'block' : 'none'; this.textContent = this.textContent === '▼' ? '▲' : '▼';"
style="background: none; border: 1px solid var(--color-border); border-radius: 0.25rem; padding: 0.25rem 0.5rem; cursor: pointer; font-size: 0.75rem;">
</button>
` : ''}
</div>
<div ${collapseOnMobile ? 'style="display: none;"' : ''} style="display: flex; flex-wrap: wrap; gap: 0.25rem;">
${conceptLinks}
</div>
</div>
@@ -436,6 +656,30 @@ domains.forEach((domain: any) => {
elements.links.innerHTML = linksHTML;
// ===== POPULATE SHARE BUTTON =====
const shareButtonContainer = document.getElementById(`share-button-${modalType}`);
if (shareButtonContainer) {
const toolSlug = createToolSlug(tool.name);
shareButtonContainer.innerHTML = `
<button class="share-btn share-btn--medium"
data-tool-name="${tool.name}"
data-tool-slug="${toolSlug}"
data-context="modal-${modalType}"
onclick="event.stopPropagation(); window.showShareDialog(this)"
title="${tool.name} teilen"
aria-label="${tool.name} teilen">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<circle cx="18" cy="5" r="3"/>
<circle cx="6" cy="12" r="3"/>
<circle cx="18" cy="19" r="3"/>
<line x1="8.59" y1="13.51" x2="15.42" y2="17.49"/>
<line x1="15.41" y1="6.51" x2="8.59" y2="10.49"/>
</svg>
</button>
`;
shareButtonContainer.style.display = 'block';
}
// Show modals and update layout
const overlay = document.getElementById('modal-overlay');
const primaryModal = document.getElementById('tool-details-primary');
@@ -460,14 +704,28 @@ domains.forEach((domain: any) => {
const secondaryModal = document.getElementById('tool-details-secondary');
if (modalType === 'both' || modalType === 'all') {
if (primaryModal) primaryModal.classList.remove('active');
if (secondaryModal) secondaryModal.classList.remove('active');
if (primaryModal) {
primaryModal.classList.remove('active');
// Hide share button
const shareButtonPrimary = document.getElementById('share-button-primary');
if (shareButtonPrimary) shareButtonPrimary.style.display = 'none';
}
if (secondaryModal) {
secondaryModal.classList.remove('active');
// Hide share button
const shareButtonSecondary = document.getElementById('share-button-secondary');
if (shareButtonSecondary) shareButtonSecondary.style.display = 'none';
}
if (overlay) overlay.classList.remove('active');
document.body.classList.remove('modals-side-by-side');
} else if (modalType === 'primary' && primaryModal) {
primaryModal.classList.remove('active');
const shareButtonPrimary = document.getElementById('share-button-primary');
if (shareButtonPrimary) shareButtonPrimary.style.display = 'none';
} else if (modalType === 'secondary' && secondaryModal) {
secondaryModal.classList.remove('active');
const shareButtonSecondary = document.getElementById('share-button-secondary');
if (shareButtonSecondary) shareButtonSecondary.style.display = 'none';
}
// Check if any modal is still active