valaxy-theme-yun 0.28.3 → 0.28.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,6 @@
1
1
  <script lang="ts" setup>
2
2
  import type { GirlType } from '../types'
3
+ import { computed } from 'vue'
3
4
  import { useRandomData } from '../composables'
4
5
 
5
6
  const props = defineProps<{
@@ -8,12 +9,18 @@ const props = defineProps<{
8
9
  }>()
9
10
 
10
11
  const { data } = useRandomData(props.girls, props.random)
12
+ const isUrlSource = computed(() => typeof props.girls === 'string')
11
13
  </script>
12
14
 
13
15
  <template>
14
16
  <div class="girls">
15
- <ul class="girl-items">
16
- <YunGirlItem v-for="girl, i in data" :key="i" :i="i" :girl="girl" />
17
+ <ClientOnly v-if="isUrlSource">
18
+ <ul class="girl-items">
19
+ <YunGirlItem v-for="girl, i in data" :key="girl.url" :i="i" :girl="girl" />
20
+ </ul>
21
+ </ClientOnly>
22
+ <ul v-else class="girl-items">
23
+ <YunGirlItem v-for="girl, i in data" :key="girl.url" :i="i" :girl="girl" />
17
24
  </ul>
18
25
  </div>
19
26
  </template>
@@ -1,5 +1,6 @@
1
1
  <script lang="ts" setup>
2
2
  import type { LinkType } from '../types'
3
+ import { computed } from 'vue'
3
4
  import { useRandomData } from '../composables'
4
5
 
5
6
  const props = defineProps<{
@@ -12,14 +13,40 @@ const props = defineProps<{
12
13
  }>()
13
14
 
14
15
  const { data } = useRandomData(props.links, props.random)
16
+
17
+ /**
18
+ * When links source is a URL (string), data is fetched asynchronously on the
19
+ * client side only. During SSG the list is empty, but after hydration the
20
+ * fetched items are injected into the DOM. Wrapping the list in
21
+ * `<ClientOnly>` avoids a hydration-mismatch between the static (empty) HTML
22
+ * and the client-rendered (populated) list, which could cause `replaceChild`
23
+ * errors or the list not appearing at all.
24
+ *
25
+ * For static array data the mismatch risk is lower (SSR and client render
26
+ * the same initial order), so `<ClientOnly>` is not applied.
27
+ */
28
+ const isUrlSource = computed(() => typeof props.links === 'string')
15
29
  </script>
16
30
 
17
31
  <template>
18
32
  <div class="yun-links">
19
- <ul class="yun-link-items" flex="center wrap">
33
+ <!--
34
+ Use ClientOnly when links are fetched from a URL to prevent
35
+ hydration mismatch (SSG renders empty list, client fills it).
36
+ -->
37
+ <ClientOnly v-if="isUrlSource">
38
+ <ul class="yun-link-items" flex="center wrap">
39
+ <YunLinkItem
40
+ v-for="link, i in data"
41
+ :key="link.url"
42
+ :i="i" :link="link" :error-img="errorImg"
43
+ />
44
+ </ul>
45
+ </ClientOnly>
46
+ <ul v-else class="yun-link-items" flex="center wrap">
20
47
  <YunLinkItem
21
48
  v-for="link, i in data"
22
- :key="i"
49
+ :key="link.url"
23
50
  :i="i" :link="link" :error-img="errorImg"
24
51
  />
25
52
  </ul>
@@ -23,89 +23,82 @@ const postTitleClass = computed(() => {
23
23
  }
24
24
  return props.post.postTitleClass || gradientClasses.value
25
25
  })
26
-
27
- /**
28
- * Guard navigation so clicks on nested interactive elements (e.g. external links)
29
- * don't also trigger the card's route navigation.
30
- */
31
- function guardedNavigate(e: Event, navigate: () => void) {
32
- if ((e.target as HTMLElement)?.closest('a'))
33
- return
34
- navigate()
35
- }
36
26
  </script>
37
27
 
38
28
  <template>
39
- <RouterLink v-slot="{ navigate }" :to="post.path || ''" custom>
40
- <div class="post-card-link flex-center w-full" role="link" tabindex="0" @click="(e) => guardedNavigate(e, navigate)" @keydown.enter="(e) => guardedNavigate(e, navigate)" @keydown.space.prevent="(e) => guardedNavigate(e, navigate)">
41
- <YunCard
42
- class="post-card-wrapper w-full hover:scale-102 hover:z-1"
43
- mx="4"
44
- :class="post.cover ? 'post-card-image' : 'post-card'"
45
- overflow="hidden" v-bind="styles ? { style: styles } : {}"
46
- >
47
- <div class="flex flex-1 of-hidden justify-start items-start post-card-info" w="full">
48
- <img
49
- v-if="post.cover" :src="post.cover" :alt="t('post.cover')" width="320" height="180"
50
- class="post-card-cover cover object-cover object-center md:shadow" loading="lazy"
51
- >
52
-
53
- <div class="post-card-body flex flex-col items-center relative" :class="post.cover && 'post-card-body-with-cover'" w="full">
54
- <AppLink class="post-title-link cursor-pointer" :to="post.path || ''" m="t-3" :class="postTitleClass">
55
- <div class="post-card-title flex-center title text-2xl" text="center" font="serif black">
56
- <div v-if="post.type" class="inline-flex" m="r-1" :class="icon" />
57
- <span>{{ $tO(post.title) }}</span>
58
- </div>
59
- </AppLink>
60
-
61
- <YunPostMeta :frontmatter="post" />
62
-
63
- <div class="post-card-excerpt" flex="~ grow col" w="full" justify="center" items="center">
64
- <div v-if="post.excerpt_type === 'text'" py="1" />
65
- <template v-if="post.excerpt">
66
- <div
67
- v-if="post.excerpt_type === 'html'"
68
- class="post-card-excerpt-content markdown-body" op="90" text="left" w="full" p="x-6 y-2"
69
- >
70
- <ValaxyDynamicComponent :template-str="post.excerpt" />
71
- </div>
72
- <div
73
- v-else
74
- class="post-card-excerpt-content markdown-body" op="90" text="left" w="full" p="x-6 y-2"
75
- v-html="post.excerpt"
76
- />
77
- </template>
78
- <div v-else m="b-5" />
29
+ <div class="post-card-link flex-center w-full">
30
+ <YunCard
31
+ class="post-card-wrapper w-full hover:scale-102 hover:z-1"
32
+ mx="4"
33
+ :class="post.cover ? 'post-card-image' : 'post-card'"
34
+ overflow="hidden" v-bind="styles ? { style: styles } : {}"
35
+ >
36
+ <!-- Overlay link covers the entire card -->
37
+ <AppLink class="post-card-overlay" :to="post.path || ''" :aria-label="$tO(post.title)" tabindex="0" />
38
+
39
+ <div class="flex flex-1 of-hidden justify-start items-start post-card-info" w="full">
40
+ <img
41
+ v-if="post.cover" :src="post.cover" :alt="t('post.cover')" width="320" height="180"
42
+ class="post-card-cover cover object-cover object-center md:shadow" loading="lazy"
43
+ >
44
+
45
+ <div class="post-card-body flex flex-col items-center relative" :class="post.cover && 'post-card-body-with-cover'" w="full">
46
+ <AppLink class="post-title-link" :to="post.path || ''" m="t-3" :class="postTitleClass" tabindex="-1">
47
+ <div class="post-card-title flex-center title text-2xl" text="center" font="serif black">
48
+ <div v-if="post.type" class="inline-flex" m="r-1" :class="icon" />
49
+ <span>{{ $tO(post.title) }}</span>
79
50
  </div>
51
+ </AppLink>
52
+
53
+ <YunPostMeta :frontmatter="post" />
54
+
55
+ <div class="post-card-excerpt" flex="~ grow col" w="full" justify="center" items="center">
56
+ <div v-if="post.excerpt_type === 'text'" py="1" />
57
+ <template v-if="post.excerpt">
58
+ <div
59
+ v-if="post.excerpt_type === 'html'"
60
+ class="post-card-excerpt-content markdown-body" op="90" text="left" w="full" p="x-6 y-2"
61
+ >
62
+ <ValaxyDynamicComponent :template-str="post.excerpt" />
63
+ </div>
64
+ <div
65
+ v-else
66
+ class="post-card-excerpt-content markdown-body" op="90" text="left" w="full" p="x-6 y-2"
67
+ v-html="post.excerpt"
68
+ />
69
+ </template>
70
+ <div v-else m="b-5" />
71
+ </div>
80
72
 
81
- <YunExcerptBottomGradient v-if="post.excerpt" />
73
+ <YunExcerptBottomGradient v-if="post.excerpt" />
82
74
 
83
- <a
84
- v-if="post.url" :href="post.url" class="post-link-btn shadow hover:shadow-md z-2" rounded target="_blank"
85
- rel="noopener noreferrer"
86
- m="b-4"
87
- >
88
- {{ t('post.view_link') }}
89
- </a>
90
- </div>
75
+ <a
76
+ v-if="post.url" :href="post.url" class="post-link-btn shadow hover:shadow-md" rounded target="_blank"
77
+ rel="noopener noreferrer"
78
+ m="b-4"
79
+ >
80
+ {{ t('post.view_link') }}
81
+ </a>
91
82
  </div>
83
+ </div>
92
84
 
93
- <!-- always show -->
94
- <div w="full" class="yun-card-actions flex items-center justify-between" p="x-4 y-2" text="sm">
95
- <div class="post-categories inline-flex gap-2" flex="wrap 1" items="center">
96
- <YunPostCategories :categories="post.categories" />
97
- <YunPostCollectionBadge v-if="postCollections.length" :collections="postCollections" />
98
- </div>
99
-
100
- <YunPostTags v-if="post.tags" m="l-2" :tags="post.tags" />
85
+ <!-- always show -->
86
+ <div w="full" class="yun-card-actions flex items-center justify-between" p="x-4 y-2" text="sm">
87
+ <div class="post-categories inline-flex gap-2" flex="wrap 1" items="center">
88
+ <YunPostCategories :categories="post.categories" />
89
+ <YunPostCollectionBadge v-if="postCollections.length" :collections="postCollections" />
101
90
  </div>
102
- </YunCard>
103
- </div>
104
- </RouterLink>
91
+
92
+ <YunPostTags v-if="post.tags" m="l-2" :tags="post.tags" />
93
+ </div>
94
+ </YunCard>
95
+ </div>
105
96
  </template>
106
97
 
107
98
  <style lang="scss">
108
99
  .post-card-wrapper {
100
+ // Stacking context for the overlay
101
+ position: relative;
109
102
  transition: box-shadow var(--va-transition-duration), scale var(--va-transition-duration);
110
103
 
111
104
  &:hover {
@@ -117,32 +110,17 @@ function guardedNavigate(e: Event, navigate: () => void) {
117
110
  gap: 0.15rem;
118
111
  }
119
112
 
120
- .post-card {
121
- // safari not support
122
- // animation: card-appear 0.6s ease-in-out forwards, card-appear 0.6s ease-in-out forwards reverse;
123
- // animation-timeline: view();
124
- // animation-range: entry, exit;
125
- }
126
-
127
113
  .post-card-link {
128
- text-decoration: none;
129
- color: inherit;
130
-
131
114
  // max-w-$yun-post-card-max-width
132
115
  max-width: calc(var(--yun-post-card-max-width) + 2rem);
116
+ }
133
117
 
134
- &:hover {
135
- text-decoration: none;
136
- color: inherit;
137
- }
138
-
139
- &:visited {
140
- color: inherit;
141
- }
142
-
143
- &:focus {
144
- outline: none;
145
- }
118
+ // Overlay link covers the entire card as the click target
119
+ // Sits above normal content but below interactive elements
120
+ .post-card-overlay {
121
+ position: absolute;
122
+ inset: 0;
123
+ z-index: 1;
146
124
 
147
125
  &:focus-visible {
148
126
  outline: 2px solid var(--va-c-primary);
@@ -151,6 +129,14 @@ function guardedNavigate(e: Event, navigate: () => void) {
151
129
  }
152
130
  }
153
131
 
132
+ // Interactive elements (title, tags, categories, external link) float above the overlay
133
+ .post-title-link,
134
+ .yun-card-actions,
135
+ .post-link-btn {
136
+ position: relative;
137
+ z-index: 2;
138
+ }
139
+
154
140
  .post-card-link :hover {
155
141
  cursor: var(--cursor-pointer), pointer;
156
142
  }
@@ -160,18 +146,6 @@ function guardedNavigate(e: Event, navigate: () => void) {
160
146
  line-height: 1.7;
161
147
  }
162
148
 
163
- @keyframes card-appear {
164
- 0% {
165
- opacity: 0;
166
- transform: scale(0.8) translateY(20px);
167
- }
168
-
169
- 100% {
170
- opacity: 1;
171
- transform: scale(1) translateY(0);
172
- }
173
- }
174
-
175
149
  .yun-card-actions {
176
150
  border-top: 1px solid rgb(122 122 122 / 0.08);
177
151
  }
@@ -47,7 +47,7 @@ const app = useAppStore()
47
47
  v-if="showMenu"
48
48
  class="yun-nav-menu z-$yun-z-nav-menu fixed bg-transparent"
49
49
  :class="{
50
- play: playAnimation,
50
+ 'shadow play': playAnimation,
51
51
  }"
52
52
  >
53
53
  <!-- -->
@@ -113,7 +113,6 @@ const app = useAppStore()
113
113
  // safari not support
114
114
  // animation-timeline: scroll();
115
115
  // animation-range: 0 calc(30vh), exit;
116
- box-shadow: none;
117
116
  display: flex;
118
117
 
119
118
  // top: var(--rect-margin);
@@ -126,19 +125,14 @@ const app = useAppStore()
126
125
  align-items: center;
127
126
  justify-content: space-between;
128
127
  height: var(--yun-nav-height, 50px);
129
- transition: all var(--va-transition-duration-moderate) map.get($cubic-bezier, 'ease-in');
128
+ transition: background-color var(--va-transition-duration-moderate) map.get($cubic-bezier, 'ease-in'),
129
+ backdrop-filter var(--va-transition-duration-moderate) map.get($cubic-bezier, 'ease-in'),
130
+ box-shadow var(--va-transition-duration-moderate) map.get($cubic-bezier, 'ease-in');
130
131
 
131
132
  &.play {
132
- top: 0;
133
- left: 0;
134
- right: 0;
135
- background-color: var(--va-c-bg);
136
- border-color: rgb(0 0 0 / 0.1);
137
-
138
- --un-shadow: var(--un-shadow-inset) 0 20px 25px -5px var(--un-shadow-color, rgb(0 0 0 / 0.1)), var(--un-shadow-inset) 0 8px 10px -6px var(--un-shadow-color, rgb(0 0 0 / 0.1));
139
-
140
- box-shadow: var(--un-ring-offset-shadow), var(--un-ring-shadow),
141
- var(--un-shadow);
133
+ background-color: var(--yun-nav-bg-color);
134
+ backdrop-filter: blur(var(--yun-nav-blur));
135
+ -webkit-backdrop-filter: blur(var(--yun-nav-blur));
142
136
  }
143
137
 
144
138
  .vt-hamburger-top, .vt-hamburger-middle, .vt-hamburger-bottom {
@@ -87,3 +87,19 @@ const isHomePage = computed(() => route.meta.layout === 'home')
87
87
  </div>
88
88
  </Transition>
89
89
  </template>
90
+
91
+ <style lang="scss" scoped>
92
+ @use 'sass:map';
93
+ @use 'valaxy-theme-yun/styles/vars.scss' as *;
94
+
95
+ .yun-nav-menu {
96
+ height: var(--yun-nav-height, 50px);
97
+ transition: all var(--va-transition-duration-moderate) map.get($cubic-bezier, 'ease-in');
98
+
99
+ &.play {
100
+ background-color: var(--yun-nav-bg-color);
101
+ backdrop-filter: blur(var(--yun-nav-blur));
102
+ -webkit-backdrop-filter: blur(var(--yun-nav-blur));
103
+ }
104
+ }
105
+ </style>
package/layouts/home.vue CHANGED
@@ -19,7 +19,7 @@ const showNotice = computed(() => {
19
19
 
20
20
  <template>
21
21
  <YunLayoutWrapper :no-margin="!isPage">
22
- <div class="w-full flex flex-col items-center pb-8">
22
+ <div class="w-full flex flex-col items-center pb-4">
23
23
  <template v-if="themeConfig.banner?.enable">
24
24
  <template v-if="!isPage">
25
25
  <div class="w-full">
@@ -62,6 +62,7 @@ const showNotice = computed(() => {
62
62
  }"
63
63
  :content="themeConfig.notice?.content"
64
64
  />
65
+ <div v-else-if="!isPage" class="mt-4" />
65
66
 
66
67
  <slot name="board" />
67
68
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "valaxy-theme-yun",
3
3
  "type": "module",
4
- "version": "0.28.3",
4
+ "version": "0.28.5",
5
5
  "author": {
6
6
  "email": "me@yunyoujun.cn",
7
7
  "name": "YunYouJun",
@@ -25,16 +25,16 @@
25
25
  "@ctrl/tinycolor": "^4.2.0",
26
26
  "@explosions/fireworks": "^0.2.0",
27
27
  "@iconify-json/ant-design": "^1.2.5",
28
- "@iconify-json/simple-icons": "^1.2.75",
28
+ "@iconify-json/simple-icons": "^1.2.78",
29
29
  "@vueuse/motion": "^3.0.3",
30
30
  "animejs": "^4.3.6",
31
- "gsap": "^3.14.2",
32
- "reka-ui": "^2.9.2"
31
+ "gsap": "^3.15.0",
32
+ "reka-ui": "^2.9.6"
33
33
  },
34
34
  "devDependencies": {
35
35
  "@types/animejs": "^3.1.13",
36
36
  "valaxy-addon-waline": "0.2.1",
37
- "valaxy": "0.28.3"
37
+ "valaxy": "0.28.5"
38
38
  },
39
39
  "scripts": {
40
40
  "release": "bumpp && pnpm publish"
@@ -9,6 +9,8 @@
9
9
  --yun-home-hero-name-background: linear-gradient(120deg, var(--va-c-text) 30%, black);
10
10
  --yun-home-hero-image-background-image: linear-gradient(-45deg, #bd34fe 50%, #47caff 50%);
11
11
  --yun-nav-height: 50px;
12
+ --yun-nav-bg-color: rgb(255 255 255 / 0.8);
13
+ --yun-nav-blur: 12px;
12
14
  }
13
15
 
14
16
  :root {
@@ -31,6 +33,7 @@ html.dark {
31
33
  --va-c-bg: #1a1a1d;
32
34
  --va-c-bg-soft: #121215;
33
35
  --yun-home-hero-name-background: linear-gradient(120deg, var(--va-c-primary) 30%, #41d1ff);
36
+ --yun-nav-bg-color: rgb(26 26 29 / 0.8);
34
37
  }
35
38
 
36
39
  // animation
package/types/index.d.ts CHANGED
@@ -269,7 +269,7 @@ export interface ThemeConfig extends DefaultTheme.Config {
269
269
  * 苏公网安备xxxxxxxx号
270
270
  * https://beian.mps.gov.cn/
271
271
  */
272
- police: string
272
+ police?: string
273
273
  }
274
274
  }>
275
275