From f1f7b48b223fe53d2a4a72903421d157b112a31e Mon Sep 17 00:00:00 2001 From: thepaperpilot <thepaperpilot@gmail.com> Date: Mon, 28 Feb 2022 21:30:17 -0600 Subject: [PATCH] Added logo to homepage --- .vitepress/theme/Layout.vue | 36 +++++ .vitepress/theme/Profectus.vue | 195 +++++++++++++++++++++++++ .vitepress/theme/home/HomeFeatures.vue | 143 ++++++++++++++++++ .vitepress/theme/home/HomeFooter.vue | 50 +++++++ .vitepress/theme/home/HomeHero.vue | 161 ++++++++++++++++++++ .vitepress/theme/home/NavLink.vue | 100 +++++++++++++ .vitepress/theme/index.js | 10 +- 7 files changed, 691 insertions(+), 4 deletions(-) create mode 100644 .vitepress/theme/Layout.vue create mode 100644 .vitepress/theme/Profectus.vue create mode 100644 .vitepress/theme/home/HomeFeatures.vue create mode 100644 .vitepress/theme/home/HomeFooter.vue create mode 100644 .vitepress/theme/home/HomeHero.vue create mode 100644 .vitepress/theme/home/NavLink.vue diff --git a/.vitepress/theme/Layout.vue b/.vitepress/theme/Layout.vue new file mode 100644 index 00000000..6c83ff18 --- /dev/null +++ b/.vitepress/theme/Layout.vue @@ -0,0 +1,36 @@ +<template> + <Layout> + <template #home> + <main class="home" aria-labelledby="main-title"> + <Profectus style="height: 30vmin; margin: auto; display: block" /> + <HomeHero /> + <HomeFeatures /> + <div class="home-content"> + <Content /> + </div> + <HomeFooter /> + </main> + </template> + </Layout> +</template> + +<script setup> +import DefaultTheme from 'vitepress/theme'; +import Profectus from './Profectus.vue'; +// I want Profectus above the hero text, so I effectively need to recreate the Home class now +import HomeHero from './home/HomeHero.vue'; +import HomeFeatures from './home/HomeFeatures.vue'; +import HomeFooter from './home/HomeFooter.vue'; +const { Layout } = DefaultTheme; +</script> + +<style scoped> +.home { + padding-top: var(--header-height); +} +.home-content { + max-width: 960px; + margin: 0px auto; + padding: 0 1.5rem; +} +</style> diff --git a/.vitepress/theme/Profectus.vue b/.vitepress/theme/Profectus.vue new file mode 100644 index 00000000..3a7700bd --- /dev/null +++ b/.vitepress/theme/Profectus.vue @@ -0,0 +1,195 @@ +<template> + <transition appear> + <svg + id="eaRe02fYmMp1" + xmlns="http://www.w3.org/2000/svg" + xmlns:xlink="http://www.w3.org/1999/xlink" + viewBox="0 0 228 521" + shape-rendering="geometricPrecision" + text-rendering="geometricPrecision" + > + <g id="P"> + <path + d="m 101,512.877 c -17.547386,-5.3519 -50.794681,-10.26296 -80,0 10.737201,-217.43031 5.7244,-300.999 0,-464.9995 0,0 46.6144,-37.1164 80,-42.00002 33.386,-4.883633 86.025,10.45942 120,50.00002 5,30 -4.353,106.6565 -44,156.0005 -34.149,42.5 -130,38.48 -130,92.999 0,102 54,208 54,208 z" + style=" + display: inline; + fill: none; + stroke: rgb(163, 190, 140); + stroke-width: 10; + stroke-linecap: round; + stroke-miterlimit: 16; + " + id="trunk" + class="svg-elem-1" + ></path> + <path + d="M 221,55.8775 C 209.023,126.453 185.39,166.835 158.997,191.5 93.783098,252.444 11.718998,217.436 46.999998,304.877" + style=" + display: inline; + fill: none; + stroke: rgb(163, 190, 140); + stroke-width: 5; + stroke-linecap: round; + stroke-miterlimit: 16; + " + id="vine2" + class="svg-elem-2" + ></path> + <path + d="m 194.5,188 c -11.225,4.447 -19.066,5.134 -35.503,3.5" + style=" + display: inline; + fill: none; + stroke: rgb(163, 190, 140); + stroke-width: 5; + stroke-linecap: round; + stroke-miterlimit: 16; + " + id="short_vine4" + class="svg-elem-3" + ></path> + <path + d="M 73.499996,246.5 C 111.145,245.626 127.011,238.775 156.5,228" + style=" + display: inline; + fill: none; + stroke: rgb(163, 190, 140); + stroke-width: 5; + stroke-linecap: round; + stroke-miterlimit: 16; + " + id="short_vine3" + class="svg-elem-4" + ></path> + <path + d="M 221,55.8775 C 169.5,17.8262 86.0943,44.9468 47,107 c -4.743,7.528 -7.1041,15.373 -8.326,24 -3.5282,24.91 2.4426,56.34 -12.0011,105.5" + style=" + display: inline; + fill: none; + stroke: rgb(163, 190, 140); + stroke-width: 5; + stroke-linecap: round; + stroke-miterlimit: 16; + " + id="vine1" + class="svg-elem-5" + ></path> + <path + d="M 21,47.8775 38.674,131" + style=" + display: inline; + fill: none; + stroke: rgb(163, 190, 140); + stroke-width: 5; + stroke-linecap: round; + stroke-miterlimit: 16; + " + id="short_vine2" + class="svg-elem-6" + ></path> + <path + d="m 3,326.5 c 13.1783,22.208 16.4863,42.834 21.6997,81" + style=" + display: inline; + fill: none; + stroke: rgb(163, 190, 140); + stroke-width: 5; + stroke-linecap: round; + stroke-miterlimit: 16; + " + id="short_vine1" + class="svg-elem-7" + ></path> + </g> + </svg> + </transition> +</template> + +<style scoped> +svg { + background: #2e3440; +} + +/*************************************************** + * Generated by SVG Artista on 1/7/2022, 4:39:47 PM + * MIT license (https://opensource.org/licenses/MIT) + * W. https://svgartista.net + **************************************************/ + +svg .svg-elem-1 { + stroke-dashoffset: 2648.758056640625px; + stroke-dasharray: 1324.3790283203125px; + transition: stroke-dashoffset 1s cubic-bezier(0.47, 0, 0.745, 0.715) 0s; +} + +svg.v-enter-from .svg-elem-1, +svg.v-leave-to .svg-elem-1 { + stroke-dashoffset: 1324.3790283203125px; +} + +svg .svg-elem-2 { + stroke-dashoffset: 680.4000854492188px; + stroke-dasharray: 340.2000427246094px; + transition: stroke-dashoffset 1s ease-out 0.4s; +} + +svg.v-enter-from .svg-elem-2, +svg.v-leave-to .svg-elem-2 { + stroke-dashoffset: 340.2000427246094px; +} + +svg .svg-elem-3 { + stroke-dashoffset: 76.21031951904297px; + stroke-dasharray: 38.105159759521484px; + transition: stroke-dashoffset 1s ease-out 0.8s; +} + +svg.v-enter-from .svg-elem-3, +svg.v-leave-to .svg-elem-3 { + stroke-dashoffset: 38.105159759521484px; +} + +svg .svg-elem-4 { + stroke-dashoffset: 175.18072509765625px; + stroke-dasharray: 87.59036254882812px; + transition: stroke-dashoffset 1s cubic-bezier(0.47, 0, 0.745, 0.715) 0.36s; +} + +svg.v-enter-from .svg-elem-4, +svg.v-leave-to .svg-elem-4 { + stroke-dashoffset: 87.59036254882812px; +} + +svg .svg-elem-5 { + stroke-dashoffset: 671.9447021484375px; + stroke-dasharray: 335.97235107421875px; + transition: stroke-dashoffset 1s ease-out 0.8s; +} + +svg.v-enter-from .svg-elem-5, +svg.v-leave-to .svg-elem-5 { + stroke-dashoffset: 335.97235107421875px; +} + +svg .svg-elem-6 { + stroke-dashoffset: 173.96141052246094px; + stroke-dasharray: 86.98070526123047px; + transition: stroke-dashoffset 1s ease-out 1s; +} + +svg.v-enter-from .svg-elem-6, +svg.v-leave-to .svg-elem-6 { + stroke-dashoffset: 86.98070526123047px; +} + +svg .svg-elem-7 { + stroke-dashoffset: 172.99151611328125px; + stroke-dasharray: 86.49575805664062px; + transition: stroke-dashoffset 1s ease-out 0.85s; +} + +svg.v-enter-from .svg-elem-7, +svg.v-leave-to .svg-elem-7 { + stroke-dashoffset: 86.49575805664062px; +} +</style> diff --git a/.vitepress/theme/home/HomeFeatures.vue b/.vitepress/theme/home/HomeFeatures.vue new file mode 100644 index 00000000..d33b772f --- /dev/null +++ b/.vitepress/theme/home/HomeFeatures.vue @@ -0,0 +1,143 @@ +<script setup lang="ts"> +import { computed } from 'vue' +import { useData } from 'vitepress' + +const { frontmatter } = useData() + +const hasFeatures = computed(() => { + return frontmatter.value.features && frontmatter.value.features.length > 0 +}) + +interface Feature { + title?: string + details?: string +} + +const features = computed<Feature[]>(() => { + return frontmatter.value.features ? frontmatter.value.features : [] +}) +</script> + +<template> + <div v-if="hasFeatures" class="home-features"> + <div class="wrapper"> + <div class="container"> + <div class="features"> + <section + v-for="(feature, index) in features" + :key="index" + class="feature" + > + <h2 class="title" v-if="feature.title">{{ feature.title }}</h2> + <p class="details" v-if="feature.details">{{ feature.details }}</p> + </section> + </div> + </div> + </div> + </div> +</template> + +<style scoped> +.home-features { + margin: 0 auto; + padding: 2.5rem 0 2.75rem; + max-width: 960px; +} + +.home-hero + .home-features { + padding-top: 0; +} + +@media (min-width: 420px) { + .home-features { + padding: 3.25rem 0 3.5rem; + } + + .home-hero + .home-features { + padding-top: 0; + } +} + +@media (min-width: 720px) { + .home-features { + padding-right: 1.5rem; + padding-left: 1.5rem; + } +} + +.wrapper { + padding: 0 1.5rem; +} + +.home-hero + .home-features .wrapper { + border-top: 1px solid var(--c-divider); + padding-top: 2.5rem; +} + +@media (min-width: 420px) { + .home-hero + .home-features .wrapper { + padding-top: 3.25rem; + } +} + +@media (min-width: 720px) { + .wrapper { + padding-right: 0; + padding-left: 0; + } +} + +.container { + margin: 0 auto; + max-width: 392px; +} + +@media (min-width: 720px) { + .container { + max-width: 960px; + } +} + +.features { + display: flex; + flex-wrap: wrap; + margin: -20px -24px; +} + +.feature { + flex-shrink: 0; + padding: 20px 24px; + width: 100%; +} + +@media (min-width: 720px) { + .feature { + width: calc(100% / 3); + } +} + +.title { + margin: 0; + border-bottom: 0; + line-height: 1.4; + font-size: 1.25rem; + font-weight: 500; +} + +@media (min-width: 420px) { + .title { + font-size: 1.4rem; + } +} + +.details { + margin: 0; + line-height: 1.6; + font-size: 1rem; + color: var(--c-text-light); +} + +.title + .details { + padding-top: 0.25rem; +} +</style> \ No newline at end of file diff --git a/.vitepress/theme/home/HomeFooter.vue b/.vitepress/theme/home/HomeFooter.vue new file mode 100644 index 00000000..8492e59a --- /dev/null +++ b/.vitepress/theme/home/HomeFooter.vue @@ -0,0 +1,50 @@ +<script setup lang="ts"> +import { useData } from 'vitepress' + +const { frontmatter } = useData() +</script> + +<template> + <footer v-if="frontmatter.footer" class="footer"> + <div class="container"> + <p class="text">{{ frontmatter.footer }}</p> + </div> + </footer> +</template> + +<style scoped> +.footer { + margin: 0 auto; + max-width: 960px; +} + +@media (min-width: 720px) { + .footer { + padding: 0 1.5rem; + } +} + +.container { + padding: 2rem 1.5rem 2.25rem; +} + +.home-hero + .footer .container, +.home-features + .footer .container, +.home-content + .footer .container { + border-top: 1px solid var(--c-divider); +} + +@media (min-width: 420px) { + .container { + padding: 3rem 1.5rem 3.25rem; + } +} + +.text { + margin: 0; + text-align: center; + line-height: 1.4; + font-size: 0.9rem; + color: var(--c-text-light); +} +</style> \ No newline at end of file diff --git a/.vitepress/theme/home/HomeHero.vue b/.vitepress/theme/home/HomeHero.vue new file mode 100644 index 00000000..b404165d --- /dev/null +++ b/.vitepress/theme/home/HomeHero.vue @@ -0,0 +1,161 @@ +<script setup lang="ts"> +import { computed } from 'vue' +import { useData, withBase } from 'vitepress' +import NavLink from './NavLink.vue' + +const { site, frontmatter } = useData() + +const showHero = computed(() => { + const { heroImage, heroText, tagline, actionLink, actionText } = + frontmatter.value + return heroImage || heroText || tagline || (actionLink && actionText) +}) + +const heroText = computed(() => frontmatter.value.heroText || site.value.title) +const tagline = computed( + () => frontmatter.value.tagline || site.value.description +) +</script> + +<template> + <header v-if="showHero" class="home-hero"> + <figure v-if="frontmatter.heroImage" class="figure"> + <img + class="image" + :src="withBase(frontmatter.heroImage)" + :alt="frontmatter.heroAlt" + /> + </figure> + + <h1 v-if="heroText" id="main-title" class="title">{{ heroText }}</h1> + <p v-if="tagline" class="tagline">{{ tagline }}</p> + + <NavLink + v-if="frontmatter.actionLink && frontmatter.actionText" + :item="{ link: frontmatter.actionLink, text: frontmatter.actionText }" + class="action" + /> + + <NavLink + v-if="frontmatter.altActionLink && frontmatter.altActionText" + :item="{ + link: frontmatter.altActionLink, + text: frontmatter.altActionText + }" + class="action alt" + /> + </header> +</template> + +<style scoped> +.home-hero { + margin: 2.5rem 0 2.75rem; + padding: 0 1.5rem; + text-align: center; +} + +@media (min-width: 420px) { + .home-hero { + margin: 3.5rem 0; + } +} + +@media (min-width: 720px) { + .home-hero { + margin: 4rem 0 4.25rem; + } +} + +.figure { + padding: 0 1.5rem; +} + +.image { + display: block; + margin: 0 auto; + width: auto; + max-width: 100%; + max-height: 280px; +} + +.title { + margin-top: 1.5rem; + font-size: 2rem; +} + +@media (min-width: 420px) { + .title { + font-size: 3rem; + } +} + +@media (min-width: 720px) { + .title { + margin-top: 2rem; + } +} + +.tagline { + margin: 0; + margin-top: 0.25rem; + line-height: 1.3; + font-size: 1.2rem; + color: var(--c-text-light); +} + +@media (min-width: 420px) { + .tagline { + line-height: 1.2; + font-size: 1.6rem; + } +} + +.action { + margin-top: 1.5rem; + display: inline-block; +} + +.action.alt { + margin-left: 1.5rem; +} + +@media (min-width: 420px) { + .action { + margin-top: 2rem; + display: inline-block; + } +} + +.action :deep(.item) { + display: inline-block; + border-radius: 6px; + padding: 0 20px; + line-height: 44px; + font-size: 1rem; + font-weight: 500; + color: var(--c-bg); + background-color: var(--c-brand); + border: 2px solid var(--c-brand); + transition: background-color 0.1s ease; +} + +.action.alt :deep(.item) { + background-color: var(--c-bg); + color: var(--c-brand); +} + +.action :deep(.item:hover) { + text-decoration: none; + color: var(--c-bg); + background-color: var(--c-brand-light); +} + +@media (min-width: 420px) { + .action :deep(.item) { + padding: 0 24px; + line-height: 52px; + font-size: 1.2rem; + font-weight: 500; + } +} +</style> \ No newline at end of file diff --git a/.vitepress/theme/home/NavLink.vue b/.vitepress/theme/home/NavLink.vue new file mode 100644 index 00000000..ff265974 --- /dev/null +++ b/.vitepress/theme/home/NavLink.vue @@ -0,0 +1,100 @@ +<script setup lang="ts"> +import { computed, toRefs } from 'vue' +import { useRoute, withBase } from 'vitepress' +import { useNavLink } from '../composables/navLink' +import OutboundLink from './icons/OutboundLink.vue' + +const props = defineProps<{ + item: { + text: string + target?: string + rel?: string + ariaLabel?: string + activeMatch?: string + link: string + } +}>() + +const propsRefs = toRefs(props) + +const route = useRoute() +const linkProps = computed(() => { + const routePath = normalizePath(`/${route.data.relativePath}`) + + let active = false + if (propsRefs.item.value.activeMatch) { + active = new RegExp(propsRefs.item.value.activeMatch).test(routePath) + } else { + const itemPath = normalizePath(propsRefs.item.value.link) + active = + itemPath === '/' + ? itemPath === routePath + : routePath.startsWith(itemPath) + } + + return { + class: { + active + }, + href: withBase(propsRefs.item.value.link), + target: propsRefs.item.value.target || null, + rel: propsRefs.item.value.rel || null, + 'aria-label': propsRefs.item.value.ariaLabel + } +}) + +function normalizePath(path: string): string { + return path + .replace(/#.*$/, '') + .replace(/\?.*$/, '') + .replace(/\.(html|md)$/, '') + .replace(/\/index$/, '/') +} +</script> + +<template> + <div class="nav-link"> + <a class="item" v-bind="linkProps"> + {{ item.text }} + </a> + </div> +</template> + +<style scoped> +.item { + display: block; + padding: 0 1.5rem; + line-height: 36px; + font-size: 1rem; + font-weight: 600; + color: var(--c-text); + white-space: nowrap; +} + +.item:hover, +.item.active { + text-decoration: none; + color: var(--c-brand); +} + +.item.external:hover { + border-bottom-color: transparent; + color: var(--c-text); +} + +@media (min-width: 720px) { + .item { + border-bottom: 2px solid transparent; + padding: 0; + line-height: 24px; + font-size: 0.9rem; + font-weight: 500; + } + + .item:hover, + .item.active { + border-bottom-color: var(--c-brand); + color: var(--c-text); + } +} +</style> \ No newline at end of file diff --git a/.vitepress/theme/index.js b/.vitepress/theme/index.js index 42a715a8..7e1b50a8 100644 --- a/.vitepress/theme/index.js +++ b/.vitepress/theme/index.js @@ -1,6 +1,8 @@ -import DefaultTheme from 'vitepress/theme' -import './vars.css' +import DefaultTheme from 'vitepress/theme'; +import Layout from './Layout.vue'; +import './vars.css'; export default { - ...DefaultTheme -} + ...DefaultTheme, + Layout +};