This commit is contained in:
		| @@ -1,110 +1,147 @@ | ||||
| --- | ||||
| import BaseLayout from '../../layouts/BaseLayout.astro'; | ||||
|  | ||||
| import directus from "../../../lib/directus" | ||||
| import { readItems } from "@directus/sdk"; | ||||
| import directus from '../../../lib/directus'; | ||||
| import { readItems } from '@directus/sdk'; | ||||
|  | ||||
| const posts = await directus.request( | ||||
|   readItems("posts", { | ||||
|   readItems('posts', { | ||||
|     fields: ['*'], | ||||
|     sort: ["-published_date"], | ||||
|     sort: ['-published_date'], | ||||
|   }) | ||||
| ); | ||||
|  | ||||
| const tags = [...new Set(posts.flatMap(post => post.tags || []))].sort(); | ||||
| 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; | ||||
| 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,  | ||||
|   return { | ||||
|     name: tag, | ||||
|     count, | ||||
|     size: Math.max(1, Math.min(3, Math.floor(count / 2) + 1)), // Size 1-3 based on count | ||||
|     hue | ||||
|     hue, | ||||
|   }; | ||||
| }); | ||||
|  | ||||
| const sortedTags = [...tagObjects].sort((a, b) => b.count - a.count); | ||||
|  | ||||
| --- | ||||
|  | ||||
| <BaseLayout title="Explore Tags"> | ||||
|   <div class="w-full mx-auto px-3 sm:px-6 py-6 sm:py-12 md:py-16 theme-transition-all"> | ||||
|   <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="relative mb-8 sm:mb-12 md:mb-16 text-center theme-transition-element"> | ||||
|       <div class="absolute -top-16 -left-16 w-36 sm:w-48 md:w-72 h-36 sm:h-48 md:h-72 bg-zinc-100 dark:bg-zinc-800/50 rounded-full blur-3xl opacity-50 animate-blob theme-transition-bg"></div> | ||||
|       <div class="absolute -bottom-16 -right-16 w-36 sm:w-48 md:w-72 h-36 sm:h-48 md:h-72 bg-zinc-200 dark:bg-zinc-800/30 rounded-full blur-3xl opacity-30 animate-blob animation-delay-2000 theme-transition-bg"></div> | ||||
|       <div class="absolute top-8 right-8 w-24 sm:w-32 md:w-40 h-24 sm:h-32 md:h-40 bg-zinc-100/30 dark:bg-zinc-700/20 rounded-full blur-2xl opacity-40 animate-blob animation-delay-4000 theme-transition-bg"></div> | ||||
|        | ||||
|       <h1 class="relative text-3xl sm:text-4xl md:text-5xl lg:text-6xl font-bold tracking-tight text-zinc-900 dark:text-zinc-100 mb-3 sm:mb-4 md:mb-6 theme-transition-color"> | ||||
|         <span class="inline-block relative"> | ||||
|     <div class="theme-transition-element relative mb-8 text-center sm:mb-12 md:mb-16"> | ||||
|       <div | ||||
|         class="animate-blob theme-transition-bg absolute -left-16 -top-16 h-36 w-36 rounded-full bg-zinc-100 opacity-50 blur-3xl dark:bg-zinc-800/50 sm:h-48 sm:w-48 md:h-72 md:w-72" | ||||
|       > | ||||
|       </div> | ||||
|       <div | ||||
|         class="animate-blob animation-delay-2000 theme-transition-bg absolute -bottom-16 -right-16 h-36 w-36 rounded-full bg-zinc-200 opacity-30 blur-3xl dark:bg-zinc-800/30 sm:h-48 sm:w-48 md:h-72 md:w-72" | ||||
|       > | ||||
|       </div> | ||||
|       <div | ||||
|         class="animate-blob animation-delay-4000 theme-transition-bg absolute right-8 top-8 h-24 w-24 rounded-full bg-zinc-100/30 opacity-40 blur-2xl dark:bg-zinc-700/20 sm:h-32 sm:w-32 md:h-40 md:w-40" | ||||
|       > | ||||
|       </div> | ||||
|  | ||||
|       <h1 | ||||
|         class="theme-transition-color relative mb-3 text-3xl font-bold tracking-tight text-zinc-900 dark:text-zinc-100 sm:mb-4 sm:text-4xl md:mb-6 md:text-5xl lg:text-6xl" | ||||
|       > | ||||
|         <span class="relative inline-block"> | ||||
|           <span class="relative inline-block"> | ||||
|             <span class="absolute -inset-1 rounded-lg bg-gradient-to-r from-zinc-200/50 to-zinc-300/50 dark:from-zinc-800/50 dark:to-zinc-700/50 blur-sm theme-transition-bg"></span> | ||||
|             <span | ||||
|               class="theme-transition-bg absolute -inset-1 rounded-lg bg-gradient-to-r from-zinc-200/50 to-zinc-300/50 blur-sm 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="absolute -bottom-1 sm:-bottom-2 left-0 w-full h-0.5 sm:h-1 bg-gradient-to-r from-zinc-400 to-zinc-600 dark:from-zinc-600 dark:to-zinc-400 transform origin-left animate-underline theme-transition-bg"></span> | ||||
|             <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 dark:from-zinc-600 dark:to-zinc-400 sm:-bottom-2 sm:h-1" | ||||
|             ></span> | ||||
|           </span> | ||||
|         </span> | ||||
|       </h1> | ||||
|       <p class="relative text-sm sm:text-base md:text-lg lg:text-xl text-zinc-600 dark:text-zinc-400 max-w-2xl mx-auto theme-transition-color"> | ||||
|       <p | ||||
|         class="theme-transition-color relative mx-auto max-w-2xl text-sm text-zinc-600 dark:text-zinc-400 sm:text-base md:text-lg lg:text-xl" | ||||
|       > | ||||
|         Discover content organized by your interests | ||||
|       </p> | ||||
|     </div> | ||||
|      | ||||
|     {tags.length === 0 ? ( | ||||
|       <div class="text-center py-8 sm:py-12 md:py-16 theme-transition-element"> | ||||
|         <div class="inline-flex items-center justify-center w-16 sm:w-20 md:w-24 h-16 sm:h-20 md:h-24 rounded-full bg-zinc-100 dark:bg-zinc-800 mb-3 sm:mb-4 md:mb-6 shadow-inner theme-transition-bg"> | ||||
|           <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-8 sm:w-10 md:w-12 h-8 sm:h-10 md:h-12 text-zinc-500 dark:text-zinc-400 theme-transition-color"> | ||||
|             <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> | ||||
|  | ||||
|     { | ||||
|       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 dark:bg-zinc-800 sm:mb-4 sm:h-20 sm:w-20 md:mb-6 md:h-24 md:w-24"> | ||||
|             <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 dark:text-zinc-400 sm:h-10 sm:w-10 md:h-12 md:w-12" | ||||
|             > | ||||
|               <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 dark:text-zinc-200 sm:text-xl md:text-2xl"> | ||||
|             No tags found yet. | ||||
|           </p> | ||||
|           <p class="theme-transition-color mt-2 text-xs text-zinc-500 dark:text-zinc-500 sm:text-sm md:text-base"> | ||||
|             Check back later for categorized content. | ||||
|           </p> | ||||
|         </div> | ||||
|         <p class="text-lg sm:text-xl md:text-2xl font-medium text-zinc-800 dark:text-zinc-200 theme-transition-color">No tags found yet.</p> | ||||
|         <p class="mt-2 text-xs sm:text-sm md:text-base text-zinc-500 dark:text-zinc-500 theme-transition-color">Check back later for categorized content.</p> | ||||
|       </div> | ||||
|     ) : ( | ||||
|       <div class="flex justify-center w-full"> | ||||
|         <!-- Featured Tags Section - ultra-responsive design --> | ||||
|         <div class="tag-cloud relative p-3 sm:p-4 md:p-6 lg:p-8 rounded-lg sm:rounded-xl md:rounded-2xl lg:rounded-3xl border border-zinc-100 dark:border-zinc-800 bg-white/50 dark:bg-zinc-900/50 backdrop-blur-sm hover-3d glass theme-transition-all w-full"> | ||||
|           <div class="absolute inset-0 bg-grid-pattern opacity-5 dark:opacity-10 theme-transition-bg"></div> | ||||
|           <div class="absolute -top-8 -right-8 w-20 sm:w-24 md:w-32 lg:w-40 h-20 sm:h-24 md:h-32 lg:h-40 bg-gradient-to-br from-zinc-200/30 to-zinc-300/20 dark:from-zinc-700/20 dark:to-zinc-800/10 rounded-full blur-xl theme-transition-bg"></div> | ||||
|           <div class="absolute -bottom-8 -left-8 w-20 sm:w-24 md:w-32 lg:w-40 h-20 sm:h-24 md:h-32 lg:h-40 bg-gradient-to-tl from-zinc-200/30 to-zinc-300/20 dark:from-zinc-700/20 dark:to-zinc-800/10 rounded-full blur-xl theme-transition-bg"></div> | ||||
|            | ||||
|           <h2 class="text-lg sm:text-xl md:text-2xl lg:text-3xl font-bold text-zinc-900 dark:text-zinc-100 mb-3 sm:mb-4 md:mb-6 lg:mb-8 text-center theme-transition-color">Popular Topics</h2> | ||||
|            | ||||
|           <!-- Ultra-responsive grid layout with fallbacks --> | ||||
|           <div class="grid grid-cols-2 xxxs:grid-cols-2 xxs:grid-cols-2 xs:grid-cols-3 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 gap-1.5 xxxs:gap-2 xxs:gap-2 xs:gap-2 sm:gap-3 md:gap-4 w-full"> | ||||
|             {sortedTags.map((tag) => ( | ||||
|               <a  | ||||
|                 href={`/topics/${tag.name}`} | ||||
|                 class="group relative overflow-hidden rounded-md sm:rounded-lg md:rounded-xl border border-zinc-200 dark:border-zinc-800 transition-all duration-300 hover:shadow-md sm:hover:shadow-lg hover:scale-[1.03] hover:border-zinc-300 dark:hover:border-zinc-700 active:scale-95 theme-transition-element theme-ripple flex-grow min-w-0" | ||||
|                 style={`--tag-hue: ${tag.hue};`} | ||||
|               > | ||||
|                 <div class="absolute inset-0 bg-gradient-to-br from-zinc-50/90 to-zinc-100/90 dark:from-zinc-800/90 dark:to-zinc-900/90 opacity-100 group-hover:opacity-95 transition-opacity theme-transition-bg"></div> | ||||
|                  | ||||
|                 <div class="relative px-1.5 xxxs:px-2 xxs:px-2 xs:px-2 sm:px-3 md:px-4 py-1.5 xxxs:py-2 xxs:py-2 xs:py-2 sm:py-3 md:py-4 flex items-center gap-1.5 xxs:gap-2 w-full"> | ||||
|                   <div class="flex-shrink-0 flex items-center justify-center w-5 h-5 xxxs:w-6 xxxs:h-6 xxs:w-6 xxs:h-6 xs:w-7 xs:h-7 sm:w-8 sm:h-8 md:w-10 md:h-10 rounded-full bg-zinc-100 dark:bg-zinc-800 text-zinc-700 dark:text-zinc-300 group-hover:bg-accent/20 dark:group-hover:bg-accent/20 group-hover:text-accent-dark dark:group-hover:text-accent-light transition-all duration-300 shadow-sm theme-transition-all"> | ||||
|                     <span class="text-xs xxxs:text-xs xxs:text-xs xs:text-sm sm:text-base md:text-lg font-semibold">#</span> | ||||
|       ) : ( | ||||
|         <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-sm dark:border-zinc-800 dark:bg-zinc-900/50 sm:rounded-xl sm:p-4 md:rounded-2xl md:p-6 lg:rounded-3xl lg:p-8"> | ||||
|             <div class="bg-grid-pattern theme-transition-bg absolute inset-0 opacity-5 dark:opacity-10" /> | ||||
|             <div class="theme-transition-bg absolute -right-8 -top-8 h-20 w-20 rounded-full bg-gradient-to-br from-zinc-200/30 to-zinc-300/20 blur-xl dark:from-zinc-700/20 dark:to-zinc-800/10 sm:h-24 sm:w-24 md:h-32 md:w-32 lg:h-40 lg:w-40" /> | ||||
|             <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 dark:from-zinc-700/20 dark:to-zinc-800/10 sm:h-24 sm:w-24 md:h-32 md:w-32 lg:h-40 lg:w-40" /> | ||||
|  | ||||
|             <h2 class="theme-transition-color mb-3 text-center text-lg font-bold text-zinc-900 dark:text-zinc-100 sm:mb-4 sm:text-xl md:mb-6 md:text-2xl lg:mb-8 lg:text-3xl"> | ||||
|               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 flex-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 dark:border-zinc-800 dark:hover:border-zinc-700 sm:rounded-lg sm:hover:shadow-lg md:rounded-xl" | ||||
|                   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 flex-shrink-0 items-center justify-center rounded-full bg-zinc-100 text-zinc-700 shadow-sm transition-all duration-300 dark:bg-zinc-800 dark:text-zinc-300 sm:h-8 sm:w-8 md:h-10 md:w-10"> | ||||
|                       <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 hyphens-auto break-words text-[10px] font-bold text-zinc-900 transition-colors group-hover:text-zinc-700 dark:text-zinc-100 dark:group-hover:text-zinc-300 sm:text-sm md:text-base"> | ||||
|                         {tag.name} | ||||
|                       </h3> | ||||
|                       <p class="xxxs:text-[9px] xxs:text-[9px] xs:text-[10px] theme-transition-color truncate text-[8px] text-zinc-500 dark:text-zinc-400 sm:text-xs md:text-xs"> | ||||
|                         {tag.count} article{tag.count !== 1 ? 's' : ''} | ||||
|                       </p> | ||||
|                     </div> | ||||
|                   </div> | ||||
|                    | ||||
|                   <div class="flex-1 min-w-0 overflow-hidden"> | ||||
|                     <h3 class="text-[10px] xxxs:text-xs xxs:text-xs xs:text-xs sm:text-sm md:text-base font-bold text-zinc-900 dark:text-zinc-100 group-hover:text-zinc-700 dark:group-hover:text-zinc-300 transition-colors theme-transition-color break-words hyphens-auto truncate"> | ||||
|                       {tag.name} | ||||
|                     </h3> | ||||
|                     <p class="text-[8px] xxxs:text-[9px] xxs:text-[9px] xs:text-[10px] sm:text-xs md:text-xs text-zinc-500 dark:text-zinc-400 theme-transition-color truncate">{tag.count} article{tag.count !== 1 ? 's' : ''}</p> | ||||
|                   </div> | ||||
|                 </div> | ||||
|               </a> | ||||
|             ))} | ||||
|                 </a> | ||||
|               ))} | ||||
|             </div> | ||||
|           </div> | ||||
|         </div> | ||||
|       </div> | ||||
|     )} | ||||
|       ) | ||||
|     } | ||||
|   </div> | ||||
| </BaseLayout> | ||||
|  | ||||
| @@ -121,46 +158,50 @@ const sortedTags = [...tagObjects].sort((a, b) => b.count - a.count); | ||||
|         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'); | ||||
|         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 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 => { | ||||
|  | ||||
|       tagItems.forEach((item) => { | ||||
|         // Set appropriate classes based on screen size | ||||
|         if (isMicroScreen) { | ||||
|           item.classList.add('micro-screen'); | ||||
| @@ -174,15 +215,15 @@ const sortedTags = [...tagObjects].sort((a, b) => b.count - a.count); | ||||
|         } 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; | ||||
| @@ -198,32 +239,32 @@ const sortedTags = [...tagObjects].sort((a, b) => b.count - a.count); | ||||
|             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(); | ||||
| @@ -234,17 +275,20 @@ const sortedTags = [...tagObjects].sort((a, b) => b.count - a.count); | ||||
|         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)'); | ||||
|        | ||||
|       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', () => { | ||||
| @@ -255,39 +299,51 @@ const sortedTags = [...tagObjects].sort((a, b) => b.count - a.count); | ||||
|         }, 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 }); | ||||
|          | ||||
|  | ||||
|       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 }); | ||||
|         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> | ||||
| @@ -295,9 +351,10 @@ const sortedTags = [...tagObjects].sort((a, b) => b.count - a.count); | ||||
| <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); | ||||
|     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); | ||||
| @@ -307,19 +364,20 @@ const sortedTags = [...tagObjects].sort((a, b) => b.count - a.count); | ||||
|     margin-left: 0 !important; | ||||
|     margin-right: 0 !important; | ||||
|   } | ||||
|    | ||||
|  | ||||
|   /* Fix for horizontal overflow */ | ||||
|   :global(html), :global(body) { | ||||
|   :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) { | ||||
| @@ -329,146 +387,146 @@ const sortedTags = [...tagObjects].sort((a, b) => b.count - a.count); | ||||
|       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; | ||||
| @@ -476,7 +534,7 @@ const sortedTags = [...tagObjects].sort((a, b) => b.count - a.count); | ||||
|     white-space: nowrap; | ||||
|     max-width: 100%; | ||||
|   } | ||||
|    | ||||
|  | ||||
|   /* Ensure proper word breaking for long tag names */ | ||||
|   .break-words { | ||||
|     word-break: break-word; | ||||
| @@ -486,38 +544,42 @@ const sortedTags = [...tagObjects].sort((a, b) => b.count - a.count); | ||||
|   .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); | ||||
|     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 */ | ||||
|   .flex-grow { | ||||
|     flex-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; | ||||
|     transition: | ||||
|       transform 0.15s ease-in-out, | ||||
|       opacity 0.15s ease-in-out !important; | ||||
|   } | ||||
|    | ||||
|  | ||||
|   /* Animation for blob */ | ||||
|   @keyframes blob { | ||||
|     0%, 100% { | ||||
|     0%, | ||||
|     100% { | ||||
|       transform: translate(0, 0) scale(1); | ||||
|     } | ||||
|     25% { | ||||
| @@ -530,19 +592,19 @@ const sortedTags = [...tagObjects].sort((a, b) => b.count - a.count); | ||||
|       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% { | ||||
| @@ -552,11 +614,11 @@ const sortedTags = [...tagObjects].sort((a, b) => b.count - a.count); | ||||
|       transform: scaleX(1); | ||||
|     } | ||||
|   } | ||||
|    | ||||
|  | ||||
|   .animate-underline { | ||||
|     animation: underline 1.5s ease-out forwards; | ||||
|   } | ||||
|    | ||||
|  | ||||
|   /* Fix for iOS Safari notch */ | ||||
|   @supports (padding: max(0px)) { | ||||
|     .tag-cloud { | ||||
| @@ -571,32 +633,34 @@ const sortedTags = [...tagObjects].sort((a, b) => b.count - a.count); | ||||
|   // Handle SPA transitions for tags index page | ||||
|   function setupSPATransitions() { | ||||
|     // Handle all internal links for SPA transitions | ||||
|     document.querySelectorAll('a[href^="/"]').forEach(link => { | ||||
|     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')) { | ||||
|       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; | ||||
| @@ -607,18 +671,18 @@ const sortedTags = [...tagObjects].sort((a, b) => b.count - a.count); | ||||
|         } | ||||
|       }); | ||||
|     }); | ||||
|      | ||||
|  | ||||
|     // 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 => { | ||||
|  | ||||
|       tagCards.forEach((card) => { | ||||
|         card.addEventListener('touchstart', () => { | ||||
|           card.classList.add('is-touched'); | ||||
|         }); | ||||
|          | ||||
|  | ||||
|         card.addEventListener('touchend', () => { | ||||
|           setTimeout(() => { | ||||
|             card.classList.remove('is-touched'); | ||||
| @@ -626,23 +690,25 @@ const sortedTags = [...tagObjects].sort((a, b) => b.count - a.count); | ||||
|         }); | ||||
|       }); | ||||
|     } | ||||
|      | ||||
|  | ||||
|     // 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)); | ||||
|       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> | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user