valaxy-theme-hairy 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (102) hide show
  1. package/@types/markdown-it.d.ts +1 -0
  2. package/@types/markdown-toc.d.ts +1 -0
  3. package/@types/types.d.ts +1 -0
  4. package/@types/valaxy.d.ts +10 -0
  5. package/LICENSE +21 -0
  6. package/components/HairyAlgoliaSearchBox.vue +118 -0
  7. package/components/HairyArticleImage.vue +17 -0
  8. package/components/HairyArticleImageDefault.vue +127 -0
  9. package/components/HairyArticleImageSmall.vue +49 -0
  10. package/components/HairyArticleSeries.vue +73 -0
  11. package/components/HairyArticleText.vue +38 -0
  12. package/components/HairyArticleTop.vue +0 -0
  13. package/components/HairyBackToTop.vue +72 -0
  14. package/components/HairyBody.vue +55 -0
  15. package/components/HairyBreadcrumb.vue +51 -0
  16. package/components/HairyBreadcrumbItem.vue +14 -0
  17. package/components/HairyCarousel.vue +45 -0
  18. package/components/HairyDivider.vue +0 -0
  19. package/components/HairyHeader.vue +31 -0
  20. package/components/HairyImage.vue +14 -0
  21. package/components/HairyImageGroup.vue +69 -0
  22. package/components/HairyImageViewer.vue +22 -0
  23. package/components/HairyLayout.vue +29 -0
  24. package/components/HairyLink.vue +10 -0
  25. package/components/HairyMenu.vue +15 -0
  26. package/components/HairyMenuItem.vue +40 -0
  27. package/components/HairyMeting.vue +19 -0
  28. package/components/HairyNav.vue +39 -0
  29. package/components/HairyNavBackground.vue +7 -0
  30. package/components/HairyNavSearch.vue +265 -0
  31. package/components/HairyNavTitle.vue +13 -0
  32. package/components/HairyNavToggleDark.vue +16 -0
  33. package/components/HairyPostImageList.vue +28 -0
  34. package/components/HairyPostList.vue +24 -0
  35. package/components/HairyPostTitle.vue +33 -0
  36. package/components/HairySocialLinks.vue +27 -0
  37. package/components/HairyTimelinePostItem.vue +40 -0
  38. package/components/HairyToc.vue +135 -0
  39. package/components/HairyUserCard.vue +30 -0
  40. package/components/HairyUserNav.vue +68 -0
  41. package/components/HairyUserTab.vue +40 -0
  42. package/components/HairyWaline.vue +25 -0
  43. package/components/HairyWaves.vue +67 -0
  44. package/components/ValaxyMain.vue +45 -0
  45. package/hooks/useCategory.ts +18 -0
  46. package/hooks/useCategoryPost.ts +21 -0
  47. package/hooks/useContext.ts +16 -0
  48. package/hooks/useHeaderHeight.ts +9 -0
  49. package/hooks/usePostLayout.ts +9 -0
  50. package/hooks/useYearArchives.ts +43 -0
  51. package/images.json +101 -0
  52. package/index.d.ts +54 -0
  53. package/layouts/archives.vue +11 -0
  54. package/layouts/categories.vue +6 -0
  55. package/layouts/default.vue +9 -0
  56. package/layouts/home.vue +23 -0
  57. package/layouts/month.vue +6 -0
  58. package/layouts/post.vue +26 -0
  59. package/layouts/tag.vue +6 -0
  60. package/layouts/tags.vue +6 -0
  61. package/layouts/year.vue +6 -0
  62. package/locales/en.yml +1 -0
  63. package/locales/zh-CN.yml +3 -0
  64. package/modules/context.ts +5 -0
  65. package/modules/loading.scss +531 -0
  66. package/modules/loading.ts +42 -0
  67. package/modules/scroll.ts +21 -0
  68. package/node/addon-hairy.ts +18 -0
  69. package/node/addon-images.ts +61 -0
  70. package/node/addon-meting.ts +13 -0
  71. package/node/addon-statistics.ts +19 -0
  72. package/node/addon-toc.ts +20 -0
  73. package/node/utils.ts +20 -0
  74. package/package.json +47 -0
  75. package/pages/archives/[year]/[month]/index.vue +52 -0
  76. package/pages/archives/[year]/index.vue +73 -0
  77. package/pages/archives/index.vue +53 -0
  78. package/pages/categories/[...categories].vue +115 -0
  79. package/pages/index.vue +14 -0
  80. package/pages/tags/[tag].vue +40 -0
  81. package/pages/tags/index.vue +31 -0
  82. package/setup/main.ts +11 -0
  83. package/shims.d.ts +8 -0
  84. package/styles/css-vars.scss +161 -0
  85. package/styles/element-plus/index.scss +2 -0
  86. package/styles/element-plus/tabs.scss +26 -0
  87. package/styles/element-plus/timeline.scss +19 -0
  88. package/styles/font-face.scss +20 -0
  89. package/styles/fonts/FrederickatheGreat.ttf +0 -0
  90. package/styles/fonts/Modesty.ttf +0 -0
  91. package/styles/fonts/MountainsofChristmas-Bold.ttf +0 -0
  92. package/styles/fonts/MountainsofChristmas-Regular.ttf +0 -0
  93. package/styles/fonts/Seto.ttf +0 -0
  94. package/styles/index.scss +65 -0
  95. package/styles/markdown.scss +60 -0
  96. package/styles/scrollbar.scss +26 -0
  97. package/unocss.config.ts +29 -0
  98. package/utils/createContext.ts +40 -0
  99. package/utils/index.ts +28 -0
  100. package/utils/loading.scss +531 -0
  101. package/utils/loading.ts +30 -0
  102. package/valaxy.config.ts +26 -0
@@ -0,0 +1 @@
1
+ declare module "*-markdown-it"
@@ -0,0 +1 @@
1
+ declare module "markdown-toc"
@@ -0,0 +1 @@
1
+ declare const __ALGOLIA__: boolean
@@ -0,0 +1,10 @@
1
+ declare module 'valaxy' {
2
+ export interface Post {
3
+ image: string
4
+ durations: Omit<import('reading-time').ReadTimeResults, 'text'>
5
+ length: number
6
+ }
7
+ }
8
+
9
+
10
+ export {}
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2022 云游君
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,118 @@
1
+ <script setup lang="ts">
2
+ import docsearch from '@docsearch/js'
3
+ import type { DocSearchHit } from '@docsearch/react/dist/esm/types'
4
+ import { onMounted } from 'vue'
5
+ import type { AlgoliaSearchOptions } from 'valaxy'
6
+ import { useConfig } from 'valaxy'
7
+ import { useRoute, useRouter } from 'vue-router'
8
+
9
+ const router = useRouter()
10
+ const route = useRoute()
11
+ const config = useConfig()
12
+
13
+ onMounted(() => {
14
+ initialize(config.value.search.algolia)
15
+ setTimeout(poll, 16)
16
+ })
17
+
18
+ /**
19
+ * poll until open
20
+ */
21
+ function poll() {
22
+ // programmatically open the search box after initialize
23
+ const e = new Event('keydown') as any
24
+
25
+ e.key = 'k'
26
+ e.metaKey = true
27
+
28
+ window.dispatchEvent(e)
29
+
30
+ setTimeout(() => {
31
+ if (!document.querySelector('.DocSearch-Modal'))
32
+ poll()
33
+ }, 16)
34
+ }
35
+
36
+ function initialize(userOptions: AlgoliaSearchOptions) {
37
+ // note: multi-lang search support is removed since the theme
38
+ // doesn't support multiple locales as of now.
39
+ const options = Object.assign({}, userOptions, {
40
+ container: '#docsearch',
41
+ navigator: {
42
+ navigate({ itemUrl }: { itemUrl: string }) {
43
+ const { pathname: hitPathname } = new URL(
44
+ window.location.origin + itemUrl,
45
+ )
46
+ // router doesn't handle same-page navigation so we use the native
47
+ // browser location API for anchor navigation
48
+ if (route.path === hitPathname)
49
+ window.location.assign(window.location.origin + itemUrl)
50
+
51
+ else
52
+ router.push(itemUrl)
53
+ },
54
+ },
55
+ transformItems(items: DocSearchHit[]) {
56
+ return items.map((item) => {
57
+ return Object.assign({}, item, {
58
+ url: getRelativePath(item.url),
59
+ })
60
+ })
61
+ },
62
+ hitComponent({ hit, children }: { hit: DocSearchHit; children: any }) {
63
+ const relativeHit = hit.url.startsWith('http')
64
+ ? getRelativePath(hit.url as string)
65
+ : hit.url
66
+ return {
67
+ __v: null,
68
+ type: 'a',
69
+ ref: undefined,
70
+ constructor: undefined,
71
+ key: undefined,
72
+ props: {
73
+ href: hit.url,
74
+ onClick(event: MouseEvent) {
75
+ if (isSpecialClick(event))
76
+ return
77
+
78
+ // we rely on the native link scrolling when user is already on
79
+ // the right anchor because Router doesn't support duplicated
80
+ // history entries.
81
+ if (route.path === relativeHit)
82
+ return
83
+
84
+ // if the hits goes to another page, we prevent the native link
85
+ // behavior to leverage the Router loading feature.
86
+ if (route.path !== relativeHit)
87
+ event.preventDefault()
88
+
89
+ router.push(relativeHit)
90
+ },
91
+ children,
92
+ },
93
+ }
94
+ },
95
+ })
96
+ docsearch(options as any)
97
+ }
98
+
99
+ function isSpecialClick(event: MouseEvent) {
100
+ return (
101
+ event.button === 1
102
+ || event.altKey
103
+ || event.ctrlKey
104
+ || event.metaKey
105
+ || event.shiftKey
106
+ )
107
+ }
108
+
109
+ function getRelativePath(absoluteUrl: string) {
110
+ const { pathname, hash } = new URL(absoluteUrl)
111
+ return pathname + hash
112
+ }
113
+ </script>
114
+
115
+ <template>
116
+ <div id="docsearch" />
117
+ </template>
118
+
@@ -0,0 +1,17 @@
1
+ <script lang="ts" setup>
2
+ import type { Post } from 'valaxy'
3
+
4
+ defineProps<{
5
+ post: Post
6
+ reverse?: boolean
7
+ }>()
8
+ </script>
9
+
10
+ <template>
11
+ <HairyArticleImageDefault :post="post" :reverse="reverse" class="hidden sm:block" />
12
+ <HairyArticleImageSmall :post="post" :reverse="reverse" class="hidden lt-sm:block" />
13
+ </template>
14
+
15
+ <style lang="scss">
16
+
17
+ </style>
@@ -0,0 +1,127 @@
1
+ <script lang="ts" setup>
2
+ import type { Post } from 'valaxy'
3
+ import { computed, defineProps } from 'vue'
4
+ import dayjs from 'dayjs'
5
+ import { useRouter } from 'vue-router'
6
+ import { useI18n } from 'vue-i18n'
7
+ import last from 'lodash/last'
8
+ import { usePostLayout } from '../hooks/usePostLayout'
9
+ import { toArr } from '../utils'
10
+
11
+ const props = defineProps<{
12
+ post: Post
13
+ reverse?: boolean
14
+ }>()
15
+ const router = useRouter()
16
+ const layout = usePostLayout()
17
+ const slice = computed(() => layout.value.includes('slice'))
18
+ const image = computed(() => props.post.image)
19
+
20
+ const i18n = useI18n()
21
+
22
+ const onReadMore = () => {
23
+ if (props.post.path)
24
+ router.push(props.post.path)
25
+ }
26
+
27
+ const displayCategory = (keys: string | string[] = []) => {
28
+ router.push({ path: `/categories/${toArr(keys).join('/')}` })
29
+ }
30
+ </script>
31
+
32
+ <template>
33
+ <li class="HairyArticleImage my-10 py-2" :class="[slice && 'slice', reverse && 'reverse']">
34
+ <article>
35
+ <div class="flex justify-between items-center">
36
+ <a class="text-size-2xl font-bold truncate cursor-pointer" :class="[reverse ? 'order-first' : 'order-last']" @click="onReadMore">{{ post.title }}</a>
37
+ <div class="flex justify-end gap-2 text-size-sm">
38
+ <span>{{ dayjs(post.date).format('YYYY-MM-DD') }}</span>
39
+ <span>{{ (post.length / 1000).toFixed(1) }}k字</span>
40
+ <span>{{ post.durations.minutes.toFixed(2) }}分钟</span>
41
+ </div>
42
+ </div>
43
+ <div class="h-200px flex">
44
+ <div class="flex-1 post-image-content" :class="[reverse ? 'order-last' : 'order-first']">
45
+ <img
46
+ class="post-image rounded-1 w-full h-full object-cover cursor-pointer" :src="image"
47
+ @click="onReadMore"
48
+ />
49
+ </div>
50
+ <div class="flex flex-col justify-between flex-1">
51
+ <div class="flex-1 text-size-sm">
52
+ <div class="line-clamp-text">
53
+ {{ post.text }}
54
+ </div>
55
+ </div>
56
+ <div class="flex justify-between items-center">
57
+ <a class="cursor-pointer" :class="[reverse && 'order-1']">
58
+ <span v-if="post.categories?.length" @click="displayCategory(post.categories)">
59
+ {{ i18n.t(last(toArr(post.categories)) || '') }}
60
+ </span>
61
+ </a>
62
+ <div class="text-base leading-6 font-medium">
63
+ <a class="link cursor-pointer" aria-label="read more" @click="onReadMore">
64
+ <span v-if="reverse">←</span>
65
+ Read more
66
+ <span v-if="!reverse">→</span>
67
+ </a>
68
+ </div>
69
+ </div>
70
+ </div>
71
+ </div>
72
+ </article>
73
+ </li>
74
+ </template>
75
+
76
+ <style lang="scss" scoped>
77
+ .line-clamp-text {
78
+ word-break: break-all;
79
+ overflow: hidden;
80
+ text-overflow: ellipsis;
81
+ display: -webkit-box;
82
+ text-overflow: ellipsis;
83
+ -webkit-box-orient: vertical;
84
+ -webkit-line-clamp: 5;
85
+ }
86
+
87
+ .dark {
88
+ .post-image {
89
+ @apply opacity-75 hover: opacity-90 duration-200;
90
+ }
91
+ }
92
+
93
+ .post-image-content {
94
+ margin-right: 1rem;
95
+ }
96
+
97
+ .slice {
98
+ .post-image-content {
99
+ webkit-clip-path: polygon(0 0, 92% 0, 100% 100%, 0 100%);
100
+ clip-path: polygon(0 0, 92% 0, 100% 100%, 0 100%);
101
+ border-radius: 0.625rem 0 0 0.625rem;
102
+ overflow: hidden;
103
+ }
104
+ }
105
+
106
+ .reverse {
107
+ .post-image-content {
108
+ margin-right: 0;
109
+ margin-left: 1rem;
110
+ clip-path: polygon(0 0,100% 0,100% 100%,8% 100%);
111
+ border-radius: 0 0.625rem 0.625rem 0;
112
+ overflow: hidden;
113
+ }
114
+ }
115
+
116
+ .HairyArticleImage.slice {
117
+ .post-image {
118
+ @apply transition-all;
119
+ }
120
+
121
+ &:hover {
122
+ .post-image {
123
+ transform: scale(1.05) rotate(1deg);
124
+ }
125
+ }
126
+ }
127
+ </style>
@@ -0,0 +1,49 @@
1
+ <script lang="ts" setup>
2
+ import type { Post } from 'valaxy'
3
+ import { computed, defineProps } from 'vue'
4
+ import dayjs from 'dayjs'
5
+ import { useRouter } from 'vue-router'
6
+ import { useI18n } from 'vue-i18n'
7
+ import last from 'lodash/last'
8
+ import { usePostLayout } from '../hooks/usePostLayout'
9
+ import { toArr } from '../utils'
10
+
11
+ const props = defineProps<{
12
+ post: Post
13
+ reverse?: boolean
14
+ }>()
15
+ const router = useRouter()
16
+ const layout = usePostLayout()
17
+ const slice = computed(() => layout.value.includes('slice'))
18
+ const image = computed(() => props.post.image)
19
+
20
+ const i18n = useI18n()
21
+
22
+ const onReadMore = () => {
23
+ if (props.post.path)
24
+ router.push(props.post.path)
25
+ }
26
+
27
+ const displayCategory = (keys: string | string[] = []) => {
28
+ router.push({ path: `/categories/${toArr(keys).join('/')}` })
29
+ }
30
+ </script>
31
+
32
+ <template>
33
+ <li class="HairyArticleImage my-10 py-2" :class="[slice && 'slice', reverse && 'reverse']">
34
+ <article>
35
+ <div class="flex justify-between items-center">
36
+ <a class="text-size-2xl font-bold truncate cursor-pointer" :class="[reverse ? 'order-first' : 'order-last']" @click="onReadMore">{{ post.title }}</a>
37
+ <div class="flex justify-end gap-2 text-size-sm">
38
+ <span>{{ dayjs(post.date).format('YYYY-MM-DD') }}</span>
39
+ <span>{{ (post.length / 1000).toFixed(1) }}k字</span>
40
+ <span>{{ post.durations.minutes.toFixed(2) }}分钟</span>
41
+ </div>
42
+ </div>
43
+ </article>
44
+ </li>
45
+ </template>
46
+
47
+ <style lang="scss">
48
+
49
+ </style>
@@ -0,0 +1,73 @@
1
+ <script lang="ts" setup>
2
+ import { useFrontmatter } from 'valaxy'
3
+ import { computed } from 'vue'
4
+ import { toArr } from '../utils'
5
+ import { useCurrentCategory } from '../hooks/useCategory'
6
+
7
+ const frontmatter = useFrontmatter()
8
+ const paths = computed(() => toArr(frontmatter.value.categories).filter(Boolean) as string[])
9
+ const category = useCurrentCategory(paths)
10
+ const posts = computed(() => category.value.posts || [])
11
+
12
+ function isCurrent(title = '') {
13
+ return frontmatter.value.title === title
14
+ }
15
+ </script>
16
+
17
+ <template>
18
+ <div class="pl-16px text-14px relative overflow-hidden">
19
+ <div class="outline-title">
20
+ On this Series
21
+ </div>
22
+ <ul class="va-toc relative z-1">
23
+ <a v-for="(item, index) of posts" :key="index" class="va-toc-item" @click="$router.push(item.path || '')">
24
+ <a class="outline-link" :class="[isCurrent(item.title) && 'active']">{{ index + 1 }}.{{ item.title }}</a>
25
+ </a>
26
+ </ul>
27
+ </div>
28
+ </template>
29
+
30
+ <style lang="scss" scoped>
31
+ .outline-title {
32
+ letter-spacing: 0.4px;
33
+ line-height: 28px;
34
+ font-size: 14px;
35
+ font-weight: 600;
36
+ }
37
+
38
+ .outline-link {
39
+ display: block;
40
+ position: relative;
41
+ line-height: 28px;
42
+ color: var(--va-c-text-light);
43
+ white-space: nowrap;
44
+ text-overflow: ellipsis;
45
+ transition: color 0.5s;
46
+ cursor: pointer;
47
+
48
+ &:hover {
49
+ color: var(--va-c-brand);
50
+ transition: color 0.25s;
51
+ }
52
+
53
+ &.active {
54
+ color: var(--va-c-brand);
55
+ transition: color .25s;
56
+
57
+ &::after {
58
+ position: absolute;
59
+ content: '';
60
+ left: -1.12rem;
61
+ top: 0;
62
+ bottom: 0;
63
+ margin: auto;
64
+ width: 4px;
65
+ height: 18px;
66
+ background-color: var(--va-c-brand);
67
+ transition: top 0.25s cubic-bezier(0, 1, 0.5, 1), background-color 0.5s, opacity 0.25s;
68
+ border-top-right-radius: 2px;
69
+ border-bottom-right-radius: 2px;
70
+ }
71
+ }
72
+ }
73
+ </style>
@@ -0,0 +1,38 @@
1
+ <script lang="ts" setup>
2
+ import { useThemeConfig } from 'valaxy'
3
+ import type { Post } from 'valaxy'
4
+ import { computed, defineProps } from 'vue'
5
+ import type { HairyTheme } from '..'
6
+ const props = defineProps<{
7
+ post: Post
8
+ }>()
9
+ const themeConfig = useThemeConfig<HairyTheme>()
10
+ const layout = computed(() => themeConfig.value.post?.layout || 'text')
11
+ const text = computed(() => {
12
+ if (layout.value === 'text')
13
+ return props.post.text
14
+ return props.post.excerpt
15
+ })
16
+ </script>
17
+
18
+ <template>
19
+ <li class="py-12">
20
+ <article class="space-y-2 xl:grid xl:grid-cols-4 xl:space-y-0 xl:items-baseline">
21
+ <div class="space-y-5 xl:col-span-4">
22
+ <div class="space-y-6">
23
+ <h2 class="text-2xl leading-8 font-bold tracking-tight">
24
+ <a class="st-text" :href="post.path">{{ post.title }}</a>
25
+ </h2>
26
+ <div
27
+ v-if="text"
28
+ class="prose max-w-none text-gray-500"
29
+ v-html="text"
30
+ />
31
+ </div>
32
+ <div class="text-base leading-6 font-medium">
33
+ <a class="link" aria-label="read more" :href="post.path">Read more →</a>
34
+ </div>
35
+ </div>
36
+ </article>
37
+ </li>
38
+ </template>
File without changes
@@ -0,0 +1,72 @@
1
+ <script lang="ts" setup>
2
+ import { computed } from 'vue'
3
+ import { useBackToTop } from 'valaxy'
4
+
5
+ const { show, percentage } = useBackToTop({ offset: 100 })
6
+
7
+ const radius = 48
8
+ const circumference = 2 * radius * Math.PI
9
+
10
+ const strokeOffset = computed(() => {
11
+ // 周长
12
+ const val = (1 - percentage.value) * circumference
13
+ return val < 0 ? 0 : val
14
+ })
15
+
16
+ function toTop() {
17
+ window.scrollTo({ top: 0, behavior: 'smooth' })
18
+ }
19
+ </script>
20
+
21
+ <template>
22
+ <a class="back-to-top cursor-pointer" :class="show && 'show'" @click="toTop">
23
+ <div w="8" h="8" i-ri-arrow-up-s-line />
24
+ <svg class="progress-circle-container" viewBox="0 0 100 100">
25
+ <circle :stroke-dasharray="`${circumference} ${circumference}`" :stroke-dashoffset="strokeOffset" class="progress-circle" cx="50" cy="50" :r="radius" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" />
26
+ </svg>
27
+ </a>
28
+ </template>
29
+
30
+ <style lang="scss">
31
+ @use "sass:map";
32
+
33
+ .back-to-top {
34
+ position: fixed;
35
+ width: 32px;
36
+ height: 32px;
37
+ right: 0rem;
38
+ bottom: 1.5rem;
39
+ z-index: 10;
40
+ opacity: 0;
41
+ pointer-events: none;
42
+
43
+ color: var(--hy-c-primary);
44
+ transform: translateX(0) rotate(270deg);
45
+ // override yun-icon-btn transition
46
+ transition: transform var(--va-transition-duration), opacity var(--va-transition-duration-fast) !important;
47
+
48
+ &.show {
49
+ transform: translateX(-32px) rotate(360deg);
50
+ opacity: 1;
51
+ pointer-events: fill;
52
+ }
53
+
54
+ .icon {
55
+ width: 2.5rem;
56
+ height: 2.5rem;
57
+ }
58
+ > div {
59
+ margin-bottom: -2em;
60
+ }
61
+ }
62
+
63
+ .progress-circle {
64
+ transition: 0.3s stroke-dashoffset;
65
+ transform: rotate(-90deg);
66
+ transform-origin: 50% 50%;
67
+
68
+ &-container {
69
+ position: absolute;
70
+ }
71
+ }
72
+ </style>
@@ -0,0 +1,55 @@
1
+ <script lang="ts" setup>
2
+ import { useFrontmatter } from 'valaxy'
3
+ import { computed } from 'vue'
4
+ import { useRoute } from 'vue-router'
5
+ const fr = useFrontmatter()
6
+ const route = useRoute()
7
+
8
+ const showWaline = computed(() => route.path.includes('/posts/') || fr.value.waline)
9
+ </script>
10
+
11
+ <template>
12
+ <div class="HairyBody min-h-50vh relative">
13
+ <div class="mx-auto breakpoint flex z-1 relative">
14
+ <div class="relative flex-1 pt-2">
15
+ <slot />
16
+ <HairyWaline v-if="showWaline" />
17
+ </div>
18
+ <div class="ml-4 w-60 lg:block hidden">
19
+ <div class="sticky top-3.125rem z-1">
20
+ <slot v-if="$slots.slide" name="slide" />
21
+ <HairyUserCard v-else />
22
+ </div>
23
+ </div>
24
+ </div>
25
+ <div class="HairyBodyBackground" />
26
+ </div>
27
+ </template>
28
+
29
+ <style lang="scss">
30
+ .a {
31
+ overflow: hidden;
32
+ }
33
+
34
+ .HairyBodyBackground {
35
+ @apply transition-all duration-200;
36
+ @apply absolute top-0 max-h-150vh top-5 bottom-0 w-full transition-opacity;
37
+ opacity: 0;
38
+ }
39
+
40
+ .dark {
41
+ .HairyBodyBackground {
42
+ transition-delay: 200ms;
43
+ transition-delay: 0;
44
+ opacity: 1;
45
+ background-image:
46
+ linear-gradient(to bottom, var(--hy-c-waves-dimm) 0%, transparent 60%, var(--hy-c-waves-dimm) 100%),
47
+ url(https://tva2.sinaimg.cn/large/008ugSUaly8h4mt4lbuc0j31hc0u040c.jpg);
48
+ background-position: center;
49
+ opacity: 0.4;
50
+ background-repeat: no-repeat;
51
+ filter: blur(0px);
52
+ background-size: cover;
53
+ }
54
+ }
55
+ </style>
@@ -0,0 +1,51 @@
1
+ <script lang="ts" setup>
2
+ import { ElBreadcrumb, breadcrumbProps } from 'element-plus/es/components/breadcrumb/index'
3
+ import 'element-plus/es/components/breadcrumb/style/index'
4
+ import type { PropType } from 'vue'
5
+ import { computed } from 'vue'
6
+
7
+ const props = defineProps({
8
+ ...breadcrumbProps,
9
+ size: String as PropType<'default' | 'large'>,
10
+ after: String,
11
+ })
12
+ const text = computed(() => `"${props.after || ''}"`)
13
+ </script>
14
+
15
+ <template>
16
+ <ElBreadcrumb :class="[size]" :style="{ '--after-text': text }" v-bind="$props">
17
+ <slot />
18
+ </ElBreadcrumb>
19
+ </template>
20
+
21
+ <style lang="scss">
22
+ .el-breadcrumb {
23
+ overflow: hidden;
24
+ display: inline-block;
25
+ position: relative;
26
+ }
27
+ .el-breadcrumb .el-breadcrumb__inner,
28
+ .el-breadcrumb .el-breadcrumb__item:last-child .el-breadcrumb__inner {
29
+ color: initial;
30
+ }
31
+ .el-breadcrumb .el-breadcrumb__inner.is-link {
32
+ @apply border-b border-dashed hover:border-primary hover:text-primary transition-all cursor-pointer;
33
+ font-weight: normal;
34
+ }
35
+
36
+ .el-breadcrumb::after {
37
+ @apply text-gray-5 text-size-5;
38
+ content: var(--after-text);
39
+ clear: none;
40
+ transform: translateX(2px);
41
+ }
42
+ .el-breadcrumb.large {
43
+ margin-top: 3rem;
44
+ font-size: 2.5em;
45
+ @apply lt-md:text-3xl lt-sm:text-xl;
46
+ .el-breadcrumb__separator {
47
+ @apply text-size-5 text-gray-5;
48
+ font-weight: normal;
49
+ }
50
+ }
51
+ </style>
@@ -0,0 +1,14 @@
1
+ <script lang="ts" setup>
2
+ import { ElBreadcrumbItem, breadcrumbItemProps } from 'element-plus/es/components/breadcrumb/index'
3
+ defineProps(breadcrumbItemProps)
4
+ </script>
5
+
6
+ <template>
7
+ <ElBreadcrumbItem v-bind="$props">
8
+ <slot />
9
+ </ElBreadcrumbItem>
10
+ </template>
11
+
12
+ <style lang="scss" scoped>
13
+
14
+ </style>