at main 6.5 kB view raw
1<script setup> 2import { useDark, useToggle } from "@vueuse/core"; 3 4const config = useRuntimeConfig().public; 5 6const pageBackground = ref("bg-stone-100 dark:bg-neutral-900"); 7 8const isDark = useDark(); 9const toggleDark = useToggle(isDark); 10 11useHead({ 12 title: config.title, 13 meta: [ 14 { 15 name: "viewport", 16 content: "width=device-width, initial-scale=1" 17 }, 18 ...config.meta 19 ], 20 bodyAttrs: { 21 class: pageBackground 22 } 23}); 24 25const { data } = await useAsyncData("navigation", () => { 26 return queryCollectionNavigation("pages", ["path"]); 27}); 28 29const pages = data.value ? data.value[0]?.children : []; 30 31const links = ref( 32 [ 33 { 34 icon: "ant-design:github-filled", 35 title: "GitHub", 36 href: config.links.github 37 }, 38 { 39 icon: "ri:mastodon-fill", 40 title: "Mastodon", 41 href: config.links.mastodon 42 }, 43 { 44 icon: "ri:bluesky-fill", 45 title: "BlueSky", 46 href: config.links.bluesky 47 } 48 ].reverse() 49); 50 51const date = new Date(); 52 53function scrollToTop() { 54 window.scrollTo({ 55 top: 0, 56 behavior: "smooth" 57 }); 58} 59</script> 60 61<template> 62 <div :class="[ 63 'page-container', 64 pageBackground, 65 'text-gray-800 dark:text-gray-300', 66 'min-h-screen max-w-4xl', 67 'grid grid-cols-1 grid-rows-[auto_1fr_auto]', 68 'mx-auto px-6', 69 ]"> 70 <header :class="[ 71 'border-b-2 border-stone-200 dark:border-stone-800', 72 'py-6 md:py-8', 73 'flex justify-between align-center', 74 ]"> 75 <div class="flex items-center gap-4 sm:gap-6"> 76 <img src="/logo.png" alt="Logo" class="hidden sm:block h-8" /> 77 <NuxtLink to="/" class="text-xl leading-5 sm:text-2xl font-medium font-serif-bold"> 78 {{ config.title }} 79 </NuxtLink> 80 </div> 81 82 <div class="flex items-center gap-4 sm:gap-8"> 83 <Country /> 84 <div 85 class="cursor-pointer" 86 @click="toggleDark()" 87 > 88 <Icon v-if="isDark" name="ri:sun-fill" size="1.5rem" mode="svg" /> 89 <Icon v-else name="ri:moon-fill" size="1.5rem" mode="svg" /> 90 </div> 91 <nav :class="['flex items-start gap-4', 'text-gray-800 dark:text-gray-200', 'font-semibold']"> 92 <template v-for="item in pages" :key="item.path"> 93 <NuxtLink v-if="item.title" :to="item.path.replace('/pages', '')"> 94 {{ item.title }} 95 </NuxtLink> 96 </template> 97 </nav> 98 99 <div class="flex items-center gap-2"> 100 <template v-for="link in links" :key="link.title"> 101 <NuxtLink v-if="link.href" :to="link.href" target="_blank" :aria-label="link.title"> 102 <Icon :name="link.icon" size="2rem" :title="link.title" /> 103 </NuxtLink> 104 </template> 105 </div> 106 </div> 107 </header> 108 <main class=".content-grid"> 109 <NuxtPage /> 110 </main> 111 <footer :class="[ 112 'border-t-2 border-stone-200 dark:border-stone-800', 113 'p-4', 114 'flex justify-between', 115 ]"> 116 <p> 117 &copy; 118 {{ date.getFullYear() }} 119 {{ config.author }} 120 121 <span v-if="config.author !== 'finxol'" class="text-sm text-gray-500"> 122 <span class="mx-3"> 123 124 </span> 125 Theme by <a class="underline" href="https://github.com/finxol/nuxt-blog-template" target="_blank" rel="noopener noreferrer">finxol</a> 126 </span> 127 </p> 128 <button :class="['text-gray-600 dark:text-gray-300', 'font-light']" @click="scrollToTop"> 129 Back to top 130 </button> 131 </footer> 132 </div> 133</template> 134 135<style> 136@import "assets/fonts/orkney/orkney.css"; 137@import "assets/fonts/recoleta/recoleta.css"; 138@import "assets/fonts/ibm-plex-mono/css/ibm-plex-mono-all.min.css"; 139 140body { 141 font-family: 'OrkneyRegular', sans-serif; 142} 143 144h1, h2, h3, h4, h5, h6 { 145 font-family: 'recoleta-regular', serif; 146} 147 148code, pre { 149 font-family: 'IBM Plex Mono', 'Courier New', monospace; 150} 151 152html:not(.dark) .prose pre { 153 background-color: oklch(0.95 0.0013 106.42); 154 color: oklch(0.15 0.0013 106.42); 155} 156 157html.dark .schema { 158 filter: invert(1); 159} 160 161details.minor-callout summary { 162 @apply text-stone-500 px-4 text-base; 163} 164 165@media print { 166 @page { 167 size: A4 portrait; 168 } 169 170 .page-container > *:not(main) { 171 display: none; 172 } 173 174 :not(h1, h2, h3, h4, h5, h6, li) > a[href]:after { 175 content: " (" attr(href) ")"; 176 font-family: 'IBM Plex Mono', 'Courier New', monospace; 177 } 178 179 p { 180 break-inside: avoid-page; 181 orphans: 3; 182 widows: 3; 183 } 184 185 /* Force all backgrounds to be white and text to be black for print */ 186 html.dark * { 187 background-color: white !important; 188 color: black !important; 189 } 190 191 /* Preserve some specific text colors for readability */ 192 html.dark a { 193 color: #1f2937 !important; 194 } 195 196 html.dark .text-sm { 197 color: #6b7280 !important; 198 } 199} 200 201.content-grid { 202 --padding-inline: min(2%, 1.5rem); 203 204 display: grid; 205 grid-template-columns: 206 [full-width-start] var(--padding-inline) 207 [breakout-start] var(--padding-inline) 208 [content-start] 1fr 209 [content-end] 210 var(--padding-inline) [breakout-end] 211 var(--padding-inline) [full-width-end]; 212 justify-content: start; 213 align-content: start; 214 row-gap: calc(var(--spacing) * 4); 215} 216 217.content-grid > :not(.breakout, .full-width), 218.full-width > :not(.breakout, .full-width) { 219 grid-column: content; 220} 221 222.content-grid > .breakout { 223 grid-column: breakout; 224} 225 226.content-grid > .full-width { 227 grid-column: full-width; 228} 229</style>