715 lines
22 KiB
Plaintext
715 lines
22 KiB
Plaintext
---
|
|
import BaseLayout from '../../layouts/BaseLayout.astro';
|
|
|
|
import directus from '../../../lib/directus';
|
|
import { readItems } from '@directus/sdk';
|
|
|
|
const posts = await directus.request(
|
|
readItems('posts', {
|
|
fields: ['*'],
|
|
sort: ['-published_date'],
|
|
})
|
|
);
|
|
|
|
const tags = [...new Set(posts.flatMap((post) => post.tags || []))].sort();
|
|
|
|
// Count posts for each tag and create tag objects with additional data
|
|
const tagObjects = tags.map((tag) => {
|
|
const count = posts.filter((post) => post.tags?.includes(tag)).length;
|
|
// Generate a consistent but random-looking hue for each tag
|
|
const hue = Math.abs(tag.split('').reduce((acc, char) => acc + char.charCodeAt(0), 0) % 360);
|
|
return {
|
|
name: tag,
|
|
count,
|
|
size: Math.max(1, Math.min(3, Math.floor(count / 2) + 1)), // Size 1-3 based on count
|
|
hue,
|
|
};
|
|
});
|
|
|
|
const sortedTags = [...tagObjects].sort((a, b) => b.count - a.count);
|
|
---
|
|
|
|
<BaseLayout title="Explore Tags">
|
|
<div class="theme-transition-all mx-auto w-full px-3 py-6 sm:px-6 sm:py-12 md:py-16">
|
|
<!-- Enhanced header section with animated elements - improved for mobile -->
|
|
<div class="theme-transition-element relative mb-8 text-center sm:mb-12 md:mb-16">
|
|
<div
|
|
class="animate-blob theme-transition-bg absolute -top-16 -left-16 h-36 w-36 rounded-full bg-zinc-100 opacity-50 blur-3xl sm:h-48 sm:w-48 md:h-72 md:w-72 dark:bg-zinc-800/50"
|
|
>
|
|
</div>
|
|
<div
|
|
class="animate-blob animation-delay-2000 theme-transition-bg absolute -right-16 -bottom-16 h-36 w-36 rounded-full bg-zinc-200 opacity-30 blur-3xl sm:h-48 sm:w-48 md:h-72 md:w-72 dark:bg-zinc-800/30"
|
|
>
|
|
</div>
|
|
<div
|
|
class="animate-blob animation-delay-4000 theme-transition-bg absolute top-8 right-8 h-24 w-24 rounded-full bg-zinc-100/30 opacity-40 blur-2xl sm:h-32 sm:w-32 md:h-40 md:w-40 dark:bg-zinc-700/20"
|
|
>
|
|
</div>
|
|
|
|
<h1
|
|
class="theme-transition-color relative mb-3 text-3xl font-bold tracking-tight text-zinc-900 sm:mb-4 sm:text-4xl md:mb-6 md:text-5xl lg:text-6xl dark:text-zinc-100"
|
|
>
|
|
<span class="relative inline-block">
|
|
<span class="relative inline-block">
|
|
<span
|
|
class="theme-transition-bg absolute -inset-1 rounded-lg bg-gradient-to-r from-zinc-200/50 to-zinc-300/50 blur-xs dark:from-zinc-800/50 dark:to-zinc-700/50"
|
|
></span>
|
|
<span class="relative">Explore</span>
|
|
</span>
|
|
{' '}
|
|
<span class="relative inline-block">
|
|
Topics
|
|
<span
|
|
class="animate-underline theme-transition-bg absolute -bottom-1 left-0 h-0.5 w-full origin-left transform bg-gradient-to-r from-zinc-400 to-zinc-600 sm:-bottom-2 sm:h-1 dark:from-zinc-600 dark:to-zinc-400"
|
|
></span>
|
|
</span>
|
|
</span>
|
|
</h1>
|
|
<p
|
|
class="theme-transition-color relative mx-auto max-w-2xl text-sm text-zinc-600 sm:text-base md:text-lg lg:text-xl dark:text-zinc-400"
|
|
>
|
|
Discover content organized by your interests
|
|
</p>
|
|
</div>
|
|
|
|
{
|
|
tags.length === 0 ? (
|
|
<div class="theme-transition-element py-8 text-center sm:py-12 md:py-16">
|
|
<div class="theme-transition-bg mb-3 inline-flex h-16 w-16 items-center justify-center rounded-full bg-zinc-100 shadow-inner sm:mb-4 sm:h-20 sm:w-20 md:mb-6 md:h-24 md:w-24 dark:bg-zinc-800">
|
|
<svg
|
|
xmlns="http://www.w3.org/2000/svg"
|
|
fill="none"
|
|
viewBox="0 0 24 24"
|
|
stroke-width="1.5"
|
|
stroke="currentColor"
|
|
class="theme-transition-color h-8 w-8 text-zinc-500 sm:h-10 sm:w-10 md:h-12 md:w-12 dark:text-zinc-400"
|
|
>
|
|
<path
|
|
stroke-linecap="round"
|
|
stroke-linejoin="round"
|
|
d="M9.568 3H5.25A2.25 2.25 0 003 5.25v4.318c0 .597.237 1.17.659 1.591l9.581 9.581c.699.699 1.78.872 2.607.33a18.095 18.095 0 005.223-5.223c.542-.827.369-1.908-.33-2.607L11.16 3.66A2.25 2.25 0 009.568 3z"
|
|
/>
|
|
<path stroke-linecap="round" stroke-linejoin="round" d="M6 6h.008v.008H6V6z" />
|
|
</svg>
|
|
</div>
|
|
<p class="theme-transition-color text-lg font-medium text-zinc-800 sm:text-xl md:text-2xl dark:text-zinc-200">
|
|
No tags found yet.
|
|
</p>
|
|
<p class="theme-transition-color mt-2 text-xs text-zinc-500 sm:text-sm md:text-base dark:text-zinc-500">
|
|
Check back later for categorized content.
|
|
</p>
|
|
</div>
|
|
) : (
|
|
<div class="flex w-full justify-center">
|
|
<div class="tag-cloud hover-3d glass theme-transition-all relative w-full rounded-lg border border-zinc-100 bg-white/50 p-3 backdrop-blur-xs sm:rounded-xl sm:p-4 md:rounded-2xl md:p-6 lg:rounded-3xl lg:p-8 dark:border-zinc-800 dark:bg-zinc-900/50">
|
|
<div class="bg-grid-pattern theme-transition-bg absolute inset-0 opacity-5 dark:opacity-10" />
|
|
<div class="theme-transition-bg absolute -top-8 -right-8 h-20 w-20 rounded-full bg-gradient-to-br from-zinc-200/30 to-zinc-300/20 blur-xl sm:h-24 sm:w-24 md:h-32 md:w-32 lg:h-40 lg:w-40 dark:from-zinc-700/20 dark:to-zinc-800/10" />
|
|
<div class="theme-transition-bg absolute -bottom-8 -left-8 h-20 w-20 rounded-full bg-gradient-to-tl from-zinc-200/30 to-zinc-300/20 blur-xl sm:h-24 sm:w-24 md:h-32 md:w-32 lg:h-40 lg:w-40 dark:from-zinc-700/20 dark:to-zinc-800/10" />
|
|
|
|
<h2 class="theme-transition-color mb-3 text-center text-lg font-bold text-zinc-900 sm:mb-4 sm:text-xl md:mb-6 md:text-2xl lg:mb-8 lg:text-3xl dark:text-zinc-100">
|
|
Popular Topics
|
|
</h2>
|
|
|
|
<div class="xxxs:grid-cols-2 xxs:grid-cols-2 xs:grid-cols-3 xxxs:gap-2 xxs:gap-2 xs:gap-2 grid w-full grid-cols-2 gap-1.5 sm:grid-cols-3 sm:gap-3 md:grid-cols-4 md:gap-4 lg:grid-cols-5">
|
|
{sortedTags.map((tag) => (
|
|
<a
|
|
href={`/topics/${tag.name}`}
|
|
class="theme-transition-element theme-ripple group relative min-w-0 grow overflow-hidden rounded-md border border-zinc-200 transition-all duration-300 hover:scale-[1.03] hover:border-zinc-300 hover:shadow-md active:scale-95 sm:rounded-lg sm:hover:shadow-lg md:rounded-xl dark:border-zinc-800 dark:hover:border-zinc-700"
|
|
style={`--tag-hue: ${tag.hue};`}
|
|
>
|
|
<div class="theme-transition-bg absolute inset-0 bg-gradient-to-br from-zinc-50/90 to-zinc-100/90 opacity-100 transition-opacity group-hover:opacity-95 dark:from-zinc-800/90 dark:to-zinc-900/90" />
|
|
|
|
<div class="xxxs:px-2 xxs:px-2 xs:px-2 xxxs:py-2 xxs:py-2 xs:py-2 xxs:gap-2 relative flex w-full items-center gap-1.5 px-1.5 py-1.5 sm:px-3 sm:py-3 md:px-4 md:py-4">
|
|
<div class="xxxs:w-6 xxxs:h-6 xxs:w-6 xxs:h-6 xs:w-7 xs:h-7 group-hover:bg-accent/20 dark:group-hover:bg-accent/20 group-hover:text-accent-dark dark:group-hover:text-accent-light theme-transition-all flex h-5 w-5 shrink-0 items-center justify-center rounded-full bg-zinc-100 text-zinc-700 shadow-xs transition-all duration-300 sm:h-8 sm:w-8 md:h-10 md:w-10 dark:bg-zinc-800 dark:text-zinc-300">
|
|
<span class="xxxs:text-xs xxs:text-xs xs:text-sm text-xs font-semibold sm:text-base md:text-lg">
|
|
#
|
|
</span>
|
|
</div>
|
|
|
|
<div class="min-w-0 flex-1 overflow-hidden">
|
|
<h3 class="xxxs:text-xs xxs:text-xs xs:text-xs theme-transition-color truncate text-[10px] font-bold break-words hyphens-auto text-zinc-900 transition-colors group-hover:text-zinc-700 sm:text-sm md:text-base dark:text-zinc-100 dark:group-hover:text-zinc-300">
|
|
{tag.name}
|
|
</h3>
|
|
<p class="xxxs:text-[9px] xxs:text-[9px] xs:text-[10px] theme-transition-color truncate text-[8px] text-zinc-500 sm:text-xs md:text-xs dark:text-zinc-400">
|
|
{tag.count} article{tag.count !== 1 ? 's' : ''}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</a>
|
|
))}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|
|
</div>
|
|
</BaseLayout>
|
|
|
|
<script>
|
|
// Ultra-reliable responsiveness handling
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
// Fix viewport width issues on mobile
|
|
const fixViewportWidth = () => {
|
|
// Force the viewport to be exactly the width of the device
|
|
const viewport = document.querySelector('meta[name="viewport"]');
|
|
if (!viewport) {
|
|
const meta = document.createElement('meta');
|
|
meta.name = 'viewport';
|
|
meta.content = 'width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no';
|
|
document.getElementsByTagName('head')[0].appendChild(meta);
|
|
} else {
|
|
viewport.setAttribute(
|
|
'content',
|
|
'width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no'
|
|
);
|
|
}
|
|
|
|
// Fix for horizontal overflow
|
|
document.body.style.overflowX = 'hidden';
|
|
document.documentElement.style.overflowX = 'hidden';
|
|
document.documentElement.style.width = '100%';
|
|
document.body.style.width = '100%';
|
|
};
|
|
|
|
fixViewportWidth();
|
|
|
|
// Adjust tag items based on screen size with extreme precision
|
|
const adjustTagItems = () => {
|
|
const tagItems = document.querySelectorAll('.theme-ripple');
|
|
const width =
|
|
window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth;
|
|
const isVerySmall = width < 360;
|
|
const isExtremelySmall = width < 280;
|
|
const isMicroScreen = width < 240;
|
|
|
|
// Fix container width to match viewport exactly
|
|
const container = document.querySelector('.tag-cloud');
|
|
if (container) {
|
|
container.style.maxWidth = '100vw';
|
|
container.style.width = '100%';
|
|
container.style.boxSizing = 'border-box';
|
|
|
|
// Remove any margins that might cause overflow
|
|
container.style.marginLeft = '0';
|
|
container.style.marginRight = '0';
|
|
}
|
|
|
|
// Fix grid width
|
|
const grid = document.querySelector('.grid');
|
|
if (grid) {
|
|
grid.style.width = '100%';
|
|
grid.style.maxWidth = '100%';
|
|
}
|
|
|
|
tagItems.forEach((item) => {
|
|
// Set appropriate classes based on screen size
|
|
if (isMicroScreen) {
|
|
item.classList.add('micro-screen');
|
|
item.classList.remove('extremely-small-screen', 'very-small-screen');
|
|
} else if (isExtremelySmall) {
|
|
item.classList.add('extremely-small-screen');
|
|
item.classList.remove('very-small-screen', 'micro-screen');
|
|
} else if (isVerySmall) {
|
|
item.classList.add('very-small-screen');
|
|
item.classList.remove('extremely-small-screen', 'micro-screen');
|
|
} else {
|
|
item.classList.remove('very-small-screen', 'extremely-small-screen', 'micro-screen');
|
|
}
|
|
|
|
// Ensure text doesn't overflow on small screens
|
|
const tagName = item.querySelector('h3');
|
|
const tagCount = item.querySelector('p');
|
|
|
|
if (tagName) {
|
|
// Set title for accessibility
|
|
tagName.title = tagName.textContent.trim();
|
|
|
|
// Adjust text length based on screen size
|
|
if (isMicroScreen && tagName.textContent.length > 6) {
|
|
tagName.dataset.fullText = tagName.textContent;
|
|
tagName.textContent = tagName.textContent.substring(0, 6) + '...';
|
|
} else if (isExtremelySmall && tagName.textContent.length > 8) {
|
|
tagName.dataset.fullText = tagName.textContent;
|
|
tagName.textContent = tagName.textContent.substring(0, 8) + '...';
|
|
} else if (isVerySmall && tagName.textContent.length > 12) {
|
|
tagName.dataset.fullText = tagName.textContent;
|
|
tagName.textContent = tagName.textContent.substring(0, 12) + '...';
|
|
} else if (tagName.dataset.fullText) {
|
|
tagName.textContent = tagName.dataset.fullText;
|
|
delete tagName.dataset.fullText;
|
|
}
|
|
}
|
|
|
|
// Set the tag hue for hover effects
|
|
const hue = item.style.getPropertyValue('--tag-hue');
|
|
item.style.setProperty('--hover-hue', hue);
|
|
});
|
|
};
|
|
|
|
// Run on load
|
|
adjustTagItems();
|
|
|
|
// Run on resize with optimized debounce
|
|
let resizeTimer;
|
|
const handleResize = () => {
|
|
if (resizeTimer) {
|
|
window.cancelAnimationFrame(resizeTimer);
|
|
}
|
|
|
|
resizeTimer = window.requestAnimationFrame(() => {
|
|
fixViewportWidth();
|
|
adjustTagItems();
|
|
});
|
|
};
|
|
|
|
window.addEventListener('resize', handleResize);
|
|
window.addEventListener('orientationchange', handleResize);
|
|
|
|
// Ensure layout is recalculated after page is fully loaded
|
|
window.addEventListener('load', () => {
|
|
fixViewportWidth();
|
|
adjustTagItems();
|
|
// Force recalculation after images and fonts are loaded
|
|
setTimeout(() => {
|
|
fixViewportWidth();
|
|
adjustTagItems();
|
|
}, 500);
|
|
});
|
|
|
|
// Fix for iOS Safari and other mobile browsers
|
|
if (/iPhone|iPad|iPod|Android/.test(navigator.userAgent)) {
|
|
document.documentElement.style.setProperty(
|
|
'--safe-area-inset-bottom',
|
|
'env(safe-area-inset-bottom)'
|
|
);
|
|
|
|
// Fix for mobile viewport height issues
|
|
const setVh = () => {
|
|
const vh = window.innerHeight * 0.01;
|
|
document.documentElement.style.setProperty('--vh', `${vh}px`);
|
|
};
|
|
|
|
setVh();
|
|
window.addEventListener('resize', setVh);
|
|
window.addEventListener('orientationchange', () => {
|
|
// Wait for orientation change to complete
|
|
setTimeout(() => {
|
|
setVh();
|
|
fixViewportWidth();
|
|
}, 100);
|
|
});
|
|
}
|
|
|
|
// Add touch support for mobile devices
|
|
const addTouchSupport = () => {
|
|
const tagItems = document.querySelectorAll('.theme-ripple');
|
|
|
|
tagItems.forEach((item) => {
|
|
item.addEventListener(
|
|
'touchstart',
|
|
() => {
|
|
item.classList.add('touch-active');
|
|
},
|
|
{ passive: true }
|
|
);
|
|
|
|
item.addEventListener(
|
|
'touchend',
|
|
() => {
|
|
setTimeout(() => {
|
|
item.classList.remove('touch-active');
|
|
}, 150);
|
|
},
|
|
{ passive: true }
|
|
);
|
|
|
|
// Cancel active state if touch moves away
|
|
item.addEventListener(
|
|
'touchmove',
|
|
(e) => {
|
|
const touch = e.touches[0];
|
|
const rect = item.getBoundingClientRect();
|
|
|
|
if (
|
|
touch.clientX < rect.left ||
|
|
touch.clientX > rect.right ||
|
|
touch.clientY < rect.top ||
|
|
touch.clientY > rect.bottom
|
|
) {
|
|
item.classList.remove('touch-active');
|
|
}
|
|
},
|
|
{ passive: true }
|
|
);
|
|
});
|
|
};
|
|
|
|
addTouchSupport();
|
|
});
|
|
</script>
|
|
|
|
<style>
|
|
/* Base styles */
|
|
.tag-cloud {
|
|
box-shadow:
|
|
0 0 0 1px rgba(0, 0, 0, 0.03),
|
|
0 2px 4px rgba(0, 0, 0, 0.03),
|
|
0 4px 8px rgba(0, 0, 0, 0.05);
|
|
transform-style: preserve-3d;
|
|
perspective: 1000px;
|
|
transition: all var(--theme-transition);
|
|
width: 100% !important;
|
|
max-width: 100% !important;
|
|
box-sizing: border-box;
|
|
margin-left: 0 !important;
|
|
margin-right: 0 !important;
|
|
}
|
|
|
|
/* Fix for horizontal overflow */
|
|
:global(html),
|
|
:global(body) {
|
|
overflow-x: hidden;
|
|
width: 100%;
|
|
max-width: 100%;
|
|
}
|
|
|
|
:global(.max-w-6xl) {
|
|
max-width: 100% !important;
|
|
width: 100% !important;
|
|
}
|
|
|
|
/* Ultra-responsive breakpoints for extreme reliability */
|
|
/* Micro screens (below 240px) */
|
|
@media (max-width: 239px) {
|
|
.tag-cloud {
|
|
padding: 0.5rem !important;
|
|
margin: 0 !important;
|
|
border-radius: 0.25rem !important;
|
|
width: 100% !important;
|
|
}
|
|
|
|
.tag-cloud h2 {
|
|
font-size: 0.875rem !important;
|
|
margin-bottom: 0.375rem !important;
|
|
}
|
|
|
|
.grid {
|
|
grid-template-columns: repeat(1, minmax(0, 1fr)) !important;
|
|
gap: 0.375rem !important;
|
|
width: 100% !important;
|
|
}
|
|
|
|
.micro-screen .flex {
|
|
padding: 0.25rem !important;
|
|
}
|
|
|
|
.micro-screen h3 {
|
|
font-size: 0.625rem !important;
|
|
}
|
|
|
|
.micro-screen p {
|
|
font-size: 0.5rem !important;
|
|
}
|
|
}
|
|
|
|
/* Extra extra extra small screens (240px-279px) */
|
|
@media (min-width: 240px) and (max-width: 279px) {
|
|
.xxxs\:grid-cols-2 {
|
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
}
|
|
|
|
.xxxs\:px-2 {
|
|
padding-left: 0.5rem;
|
|
padding-right: 0.5rem;
|
|
}
|
|
|
|
.xxxs\:py-2 {
|
|
padding-top: 0.5rem;
|
|
padding-bottom: 0.5rem;
|
|
}
|
|
|
|
.xxxs\:w-6 {
|
|
width: 1.5rem;
|
|
}
|
|
|
|
.xxxs\:h-6 {
|
|
height: 1.5rem;
|
|
}
|
|
|
|
.xxxs\:text-xs {
|
|
font-size: 0.75rem;
|
|
}
|
|
|
|
.xxxs\:gap-2 {
|
|
gap: 0.5rem;
|
|
}
|
|
|
|
.xxxs\:text-\[9px\] {
|
|
font-size: 9px;
|
|
}
|
|
}
|
|
|
|
/* Extra extra small screens (280px-359px) */
|
|
@media (min-width: 280px) {
|
|
.xxs\:grid-cols-2 {
|
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
}
|
|
|
|
.xxs\:px-2 {
|
|
padding-left: 0.5rem;
|
|
padding-right: 0.5rem;
|
|
}
|
|
|
|
.xxs\:py-2 {
|
|
padding-top: 0.5rem;
|
|
padding-bottom: 0.5rem;
|
|
}
|
|
|
|
.xxs\:w-6 {
|
|
width: 1.5rem;
|
|
}
|
|
|
|
.xxs\:h-6 {
|
|
height: 1.5rem;
|
|
}
|
|
|
|
.xxs\:text-xs {
|
|
font-size: 0.75rem;
|
|
}
|
|
|
|
.xxs\:gap-2 {
|
|
gap: 0.5rem;
|
|
}
|
|
|
|
.xxs\:text-\[9px\] {
|
|
font-size: 9px;
|
|
}
|
|
}
|
|
|
|
/* Extra small screens (360px-639px) */
|
|
@media (min-width: 360px) {
|
|
.xs\:grid-cols-3 {
|
|
grid-template-columns: repeat(3, minmax(0, 1fr));
|
|
}
|
|
|
|
.xs\:px-2 {
|
|
padding-left: 0.5rem;
|
|
padding-right: 0.5rem;
|
|
}
|
|
|
|
.xs\:py-2 {
|
|
padding-top: 0.5rem;
|
|
padding-bottom: 0.5rem;
|
|
}
|
|
|
|
.xs\:w-7 {
|
|
width: 1.75rem;
|
|
}
|
|
|
|
.xs\:h-7 {
|
|
height: 1.75rem;
|
|
}
|
|
|
|
.xs\:text-xs {
|
|
font-size: 0.75rem;
|
|
}
|
|
|
|
.xs\:text-sm {
|
|
font-size: 0.875rem;
|
|
}
|
|
|
|
.xs\:gap-2 {
|
|
gap: 0.5rem;
|
|
}
|
|
|
|
.xs\:text-\[10px\] {
|
|
font-size: 10px;
|
|
}
|
|
}
|
|
|
|
/* Ensure text doesn't overflow on small screens */
|
|
.truncate {
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
white-space: nowrap;
|
|
max-width: 100%;
|
|
}
|
|
|
|
/* Ensure proper word breaking for long tag names */
|
|
.break-words {
|
|
word-break: break-word;
|
|
overflow-wrap: break-word;
|
|
}
|
|
|
|
.hyphens-auto {
|
|
hyphens: auto;
|
|
}
|
|
|
|
/* Improved shadow for dark mode */
|
|
:global(.dark) .tag-cloud {
|
|
box-shadow:
|
|
0 0 0 1px rgba(255, 255, 255, 0.05),
|
|
0 2px 4px rgba(0, 0, 0, 0.1),
|
|
0 4px 8px rgba(0, 0, 0, 0.15);
|
|
}
|
|
|
|
/* Prevent layout shifts */
|
|
.grow {
|
|
grow: 1;
|
|
}
|
|
|
|
.min-w-0 {
|
|
min-width: 0;
|
|
}
|
|
|
|
/* Ensure container doesn't overflow */
|
|
.overflow-hidden {
|
|
overflow: hidden;
|
|
}
|
|
|
|
/* Touch support for mobile */
|
|
.touch-active {
|
|
transform: scale(0.97) !important;
|
|
opacity: 0.9;
|
|
transition:
|
|
transform 0.15s ease-in-out,
|
|
opacity 0.15s ease-in-out !important;
|
|
}
|
|
|
|
/* Animation for blob */
|
|
@keyframes blob {
|
|
0%,
|
|
100% {
|
|
transform: translate(0, 0) scale(1);
|
|
}
|
|
25% {
|
|
transform: translate(10px, -10px) scale(1.05);
|
|
}
|
|
50% {
|
|
transform: translate(0, 20px) scale(0.95);
|
|
}
|
|
75% {
|
|
transform: translate(-10px, -10px) scale(1.05);
|
|
}
|
|
}
|
|
|
|
.animate-blob {
|
|
animation: blob 20s infinite ease-in-out;
|
|
}
|
|
|
|
.animation-delay-2000 {
|
|
animation-delay: 2s;
|
|
}
|
|
|
|
.animation-delay-4000 {
|
|
animation-delay: 4s;
|
|
}
|
|
|
|
/* Animation for underline */
|
|
@keyframes underline {
|
|
0% {
|
|
transform: scaleX(0);
|
|
}
|
|
100% {
|
|
transform: scaleX(1);
|
|
}
|
|
}
|
|
|
|
.animate-underline {
|
|
animation: underline 1.5s ease-out forwards;
|
|
}
|
|
|
|
/* Fix for iOS Safari notch */
|
|
@supports (padding: max(0px)) {
|
|
.tag-cloud {
|
|
padding-left: max(0.75rem, env(safe-area-inset-left));
|
|
padding-right: max(0.75rem, env(safe-area-inset-right));
|
|
padding-bottom: max(0.75rem, env(safe-area-inset-bottom));
|
|
}
|
|
}
|
|
</style>
|
|
|
|
<script>
|
|
// Handle SPA transitions for tags index page
|
|
function setupSPATransitions() {
|
|
// Handle all internal links for SPA transitions
|
|
document.querySelectorAll('a[href^="/"]').forEach((link) => {
|
|
// Skip links that are anchor links, external links, or already processed
|
|
if (
|
|
link.getAttribute('href').includes('#') ||
|
|
link.getAttribute('target') === '_blank' ||
|
|
link.hasAttribute('data-spa-handled')
|
|
) {
|
|
return;
|
|
}
|
|
|
|
// Mark as handled to avoid duplicate listeners
|
|
link.setAttribute('data-spa-handled', 'true');
|
|
|
|
link.addEventListener('click', (e) => {
|
|
// Don't handle if modifier keys are pressed (for opening in new tab, etc.)
|
|
if (e.metaKey || e.ctrlKey || e.shiftKey || e.altKey) {
|
|
return;
|
|
}
|
|
|
|
e.preventDefault();
|
|
const targetHref = link.getAttribute('href');
|
|
|
|
// Trigger page transition animation
|
|
const pageTransition = document.getElementById('page-transition');
|
|
if (pageTransition) {
|
|
pageTransition.classList.remove('opacity-0');
|
|
pageTransition.classList.add('opacity-100');
|
|
|
|
// Navigate after transition effect
|
|
setTimeout(() => {
|
|
window.location.href = targetHref;
|
|
}, 300);
|
|
} else {
|
|
// Fallback if transition element doesn't exist
|
|
window.location.href = targetHref;
|
|
}
|
|
});
|
|
});
|
|
|
|
// Add hover effect for tag cards on touch devices
|
|
const isTouchDevice = 'ontouchstart' in window || navigator.maxTouchPoints > 0;
|
|
|
|
if (isTouchDevice) {
|
|
const tagCards = document.querySelectorAll('.tag-cloud a');
|
|
|
|
tagCards.forEach((card) => {
|
|
card.addEventListener('touchstart', () => {
|
|
card.classList.add('is-touched');
|
|
});
|
|
|
|
card.addEventListener('touchend', () => {
|
|
setTimeout(() => {
|
|
card.classList.remove('is-touched');
|
|
}, 300);
|
|
});
|
|
});
|
|
}
|
|
|
|
// Animate tag cards with staggered delay
|
|
const tagCards = document.querySelectorAll('.tag-cloud a');
|
|
tagCards.forEach((card, index) => {
|
|
setTimeout(
|
|
() => {
|
|
card.classList.add('animate-reveal');
|
|
},
|
|
100 + index * 50
|
|
);
|
|
});
|
|
}
|
|
|
|
// Initialize on first load
|
|
document.addEventListener('DOMContentLoaded', setupSPATransitions);
|
|
|
|
// Re-initialize when content changes via Astro's view transitions
|
|
document.addEventListener('astro:page-load', setupSPATransitions);
|
|
|
|
// For compatibility with custom transition system
|
|
document.addEventListener('page-transition-complete', setupSPATransitions);
|
|
</script>
|