slidev-theme-gtlabo 1.1.1 → 2.0.0
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.
- package/components/ChapterItem.vue +93 -0
- package/components/SectionDivider.vue +265 -11
- package/components/TableOfContents.vue +1 -0
- package/layouts/cover.vue +128 -213
- package/layouts/end.vue +76 -100
- package/package.json +1 -1
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
<!-- components/ChapterItem.vue -->
|
|
2
|
+
<template>
|
|
3
|
+
<div
|
|
4
|
+
class="flex flex-col"
|
|
5
|
+
:class="isNext ? 'border-sky-500 border-l-8 pl-6' : 'border-slate-200 border-l-4 pl-7'"
|
|
6
|
+
>
|
|
7
|
+
<!-- 次の章インジケーター - sky-500アクセント -->
|
|
8
|
+
<div
|
|
9
|
+
v-if="isNext"
|
|
10
|
+
:class="nextBadgeClasses"
|
|
11
|
+
class="bg-sky-500 text-white font-bold uppercase tracking-widest leading-none"
|
|
12
|
+
>
|
|
13
|
+
NEXT
|
|
14
|
+
</div>
|
|
15
|
+
|
|
16
|
+
<!-- 章番号とタイトル - 左揃え、明確なヒエラルキー -->
|
|
17
|
+
<div :class="chapterHeaderClasses">
|
|
18
|
+
<div class="flex items-end space-x-3 w-full">
|
|
19
|
+
<div :class="chapterNumberClasses" class="flex text-slate-900 font-bold tabular-nums leading-none">
|
|
20
|
+
{{ chapterNumber }}
|
|
21
|
+
</div>
|
|
22
|
+
<div class="flex-1">
|
|
23
|
+
<div class="border-b border-slate-900 mb-2">
|
|
24
|
+
<span :class="chapterTitleClasses" class="text-slate-900 font-bold leading-tight">
|
|
25
|
+
{{ chapter.title }}
|
|
26
|
+
</span>
|
|
27
|
+
</div>
|
|
28
|
+
</div>
|
|
29
|
+
</div>
|
|
30
|
+
</div>
|
|
31
|
+
|
|
32
|
+
<!-- セクション一覧 - 機能的な情報構造 -->
|
|
33
|
+
<div :class="sectionListClasses">
|
|
34
|
+
<div
|
|
35
|
+
v-for="(sectionData, sectionIndex) in chapter.uniqueSections"
|
|
36
|
+
:key="`${chapter.key}-${sectionIndex}`"
|
|
37
|
+
class="flex items-start"
|
|
38
|
+
>
|
|
39
|
+
<span class="w-1 h-1 bg-slate-400 mr-2 mt-1.5 flex-shrink-0 rounded-full"></span>
|
|
40
|
+
<span :class="sectionTextClasses" class="text-slate-600 leading-snug">
|
|
41
|
+
{{ sectionData.title }}
|
|
42
|
+
</span>
|
|
43
|
+
</div>
|
|
44
|
+
</div>
|
|
45
|
+
</div>
|
|
46
|
+
</template>
|
|
47
|
+
|
|
48
|
+
<script setup>
|
|
49
|
+
import { computed } from 'vue'
|
|
50
|
+
|
|
51
|
+
const props = defineProps({
|
|
52
|
+
chapter: {
|
|
53
|
+
type: Object,
|
|
54
|
+
required: true
|
|
55
|
+
},
|
|
56
|
+
index: {
|
|
57
|
+
type: Number,
|
|
58
|
+
required: true
|
|
59
|
+
},
|
|
60
|
+
isNext: {
|
|
61
|
+
type: Boolean,
|
|
62
|
+
default: false
|
|
63
|
+
},
|
|
64
|
+
isAppendix: {
|
|
65
|
+
type: Boolean,
|
|
66
|
+
default: false
|
|
67
|
+
},
|
|
68
|
+
size: {
|
|
69
|
+
type: String,
|
|
70
|
+
default: 'md'
|
|
71
|
+
},
|
|
72
|
+
config: {
|
|
73
|
+
type: Object,
|
|
74
|
+
required: true
|
|
75
|
+
}
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
// 章番号の生成(通常章は "01", "02"、付録は "A1", "A2")
|
|
79
|
+
const chapterNumber = computed(() => {
|
|
80
|
+
if (props.isAppendix) {
|
|
81
|
+
return `A${props.index + 1}`
|
|
82
|
+
}
|
|
83
|
+
return String(props.index + 1).padStart(2, '0')
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
// 動的クラス
|
|
87
|
+
const chapterHeaderClasses = computed(() => props.config.chapterHeader)
|
|
88
|
+
const chapterNumberClasses = computed(() => props.config.chapterNumber)
|
|
89
|
+
const chapterTitleClasses = computed(() => props.config.chapterTitle)
|
|
90
|
+
const sectionListClasses = computed(() => props.config.sectionList)
|
|
91
|
+
const sectionTextClasses = computed(() => props.config.sectionText)
|
|
92
|
+
const nextBadgeClasses = computed(() => props.config.nextBadge)
|
|
93
|
+
</script>
|
|
@@ -1,24 +1,278 @@
|
|
|
1
|
+
<!-- components/SectionDivider.vue -->
|
|
1
2
|
<template>
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
3
|
+
<div class="table-of-contents-container flex flex-col justify-start bg-white overflow-hidden relative">
|
|
4
|
+
<!-- 厳密な背景グリッド - Swiss Styleの基礎 -->
|
|
5
|
+
<div class="absolute inset-0 z-0 pointer-events-none opacity-10"
|
|
6
|
+
style="background-image: linear-gradient(#0f172a 1px, transparent 1px), linear-gradient(to right, #0f172a 1px, transparent 1px); background-size: 50px 50px;">
|
|
7
|
+
</div>
|
|
8
|
+
|
|
9
|
+
<!-- 非対称の幾何学的構成要素 - 左上の強調 -->
|
|
10
|
+
<div class="absolute top-0 left-0 w-2 h-32 bg-slate-900 z-5"></div>
|
|
11
|
+
<div class="absolute top-0 left-0 w-32 h-2 bg-slate-900 z-5"></div>
|
|
12
|
+
|
|
13
|
+
<!-- 非対称の幾何学的構成要素 - 右下のアクセント -->
|
|
14
|
+
<div class="absolute bottom-0 right-0 w-48 h-2 bg-slate-900 z-5"></div>
|
|
15
|
+
<div class="absolute bottom-12 right-0 w-1 h-48 bg-slate-900 opacity-30 z-5"></div>
|
|
16
|
+
|
|
17
|
+
<!-- 右側の装飾バー群 - cover.vueと同じスタイル -->
|
|
18
|
+
<div class="absolute right-0 top-0 w-1/4 h-full z-0 pointer-events-none">
|
|
19
|
+
<div class="absolute right-0 top-0 w-full h-full bg-gradient-to-l from-slate-200 to-slate-200/10" />
|
|
20
|
+
<div class="absolute right-[15%] top-0 w-32 h-[35%] bg-sky-600/10" />
|
|
21
|
+
<div class="absolute right-0 bottom-0 w-2 h-[40%] bg-slate-900" />
|
|
22
|
+
</div>
|
|
23
|
+
|
|
24
|
+
<!-- 機能的なsky-500のアクセントブロック -->
|
|
25
|
+
<div class="absolute right-0 top-1/4 w-[12%] h-24 bg-sky-500/30 z-5"></div>
|
|
26
|
+
|
|
27
|
+
<!-- ヘッダー - 付録セクション用 -->
|
|
28
|
+
<div
|
|
29
|
+
v-if="isAppendixSection"
|
|
30
|
+
:class="headerClasses"
|
|
31
|
+
class="relative z-10"
|
|
32
|
+
>
|
|
33
|
+
<div class="flex items-start space-x-6">
|
|
34
|
+
<div :class="headerTitleClasses" class="font-bold uppercase tracking-widest text-slate-800 leading-none">
|
|
35
|
+
APPENDIX
|
|
36
|
+
</div>
|
|
37
|
+
</div>
|
|
38
|
+
</div>
|
|
39
|
+
|
|
40
|
+
<!-- メインコンテンツ - 厳密なグリッドベース -->
|
|
41
|
+
<div :class="mainContentClasses" class="relative z-10">
|
|
42
|
+
<div class="max-w-full w-full">
|
|
43
|
+
<!-- 共通の目次表示 -->
|
|
44
|
+
<div :class="gridClasses">
|
|
45
|
+
<ChapterItem
|
|
46
|
+
v-for="(chapter, index) in displayChapters"
|
|
47
|
+
:key="chapter.key"
|
|
48
|
+
:chapter="chapter"
|
|
49
|
+
:index="index"
|
|
50
|
+
:is-next="isNextChapter(chapter.key)"
|
|
51
|
+
:is-appendix="isAppendixSection"
|
|
52
|
+
:size="size"
|
|
53
|
+
:config="currentConfig"
|
|
54
|
+
/>
|
|
55
|
+
</div>
|
|
56
|
+
</div>
|
|
57
|
+
</div>
|
|
58
|
+
|
|
59
|
+
<!-- タイポグラフィックな署名 - 客観性の表明 -->
|
|
60
|
+
<div class="absolute bottom-4 left-6 text-[10px] font-mono text-slate-800 uppercase tracking-widest z-20">
|
|
61
|
+
CONTENTS
|
|
62
|
+
</div>
|
|
63
|
+
</div>
|
|
7
64
|
</template>
|
|
8
|
-
|
|
65
|
+
|
|
9
66
|
<script setup>
|
|
10
|
-
import
|
|
67
|
+
import { computed } from 'vue'
|
|
68
|
+
import { useSlideContext } from '@slidev/client'
|
|
69
|
+
import ChapterItem from './ChapterItem.vue'
|
|
70
|
+
|
|
71
|
+
// Slidevコンテキストからconfigsにアクセス
|
|
72
|
+
const { $slidev } = useSlideContext()
|
|
73
|
+
const chapters = $slidev.configs.chapters || {}
|
|
11
74
|
|
|
12
75
|
const props = defineProps({
|
|
13
|
-
nextChapter: {
|
|
76
|
+
nextChapter: {
|
|
14
77
|
type: String,
|
|
15
78
|
required: true,
|
|
16
79
|
description: '次に始まる章のキー(例: "c2", "c3", "ap1")'
|
|
17
|
-
},
|
|
18
|
-
currentChapter: {
|
|
80
|
+
},
|
|
81
|
+
currentChapter: {
|
|
19
82
|
type: String,
|
|
20
83
|
required: false,
|
|
21
84
|
description: '現在の章のキー(進捗表示用)'
|
|
85
|
+
},
|
|
86
|
+
size: {
|
|
87
|
+
type: String,
|
|
88
|
+
default: 'md',
|
|
89
|
+
validator: (value) => ['xs', 'sm', 'md', 'lg', 'xl'].includes(value),
|
|
90
|
+
description: '目次のサイズ(xs, sm, md, lg, xl)'
|
|
91
|
+
}
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
// セクションの重複を除去する関数
|
|
95
|
+
const getUniqueSections = (chapterKey) => {
|
|
96
|
+
const chapter = chapters[chapterKey]
|
|
97
|
+
if (!chapter?.sections) return []
|
|
98
|
+
|
|
99
|
+
const sections = chapter.sections
|
|
100
|
+
const sectionKeys = Object.keys(sections)
|
|
101
|
+
const uniqueSections = []
|
|
102
|
+
let previousTitle = null
|
|
103
|
+
|
|
104
|
+
for (const sectionKey of sectionKeys) {
|
|
105
|
+
const sectionData = sections[sectionKey]
|
|
106
|
+
const currentTitle = sectionData?.title || `セクション ${sectionKey}`
|
|
107
|
+
|
|
108
|
+
// 前のセクションと同じタイトルでない場合のみ追加
|
|
109
|
+
if (currentTitle !== previousTitle) {
|
|
110
|
+
uniqueSections.push({
|
|
111
|
+
key: sectionKey,
|
|
112
|
+
title: currentTitle,
|
|
113
|
+
originalData: sectionData
|
|
114
|
+
})
|
|
115
|
+
previousTitle = currentTitle
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return uniqueSections
|
|
22
120
|
}
|
|
121
|
+
|
|
122
|
+
// 次の章が付録かどうかを判定
|
|
123
|
+
const isAppendixSection = computed(() => {
|
|
124
|
+
return props.nextChapter.startsWith('ap')
|
|
23
125
|
})
|
|
24
|
-
|
|
126
|
+
|
|
127
|
+
// 章リストを取得する共通関数
|
|
128
|
+
const getChapterList = (filterFn) => {
|
|
129
|
+
if (!chapters || typeof chapters !== 'object') {
|
|
130
|
+
return []
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return Object.keys(chapters)
|
|
134
|
+
.filter(filterFn)
|
|
135
|
+
.map(chapterKey => {
|
|
136
|
+
const chapter = chapters[chapterKey]
|
|
137
|
+
const sections = chapter?.sections || {}
|
|
138
|
+
const sectionKeys = Object.keys(sections)
|
|
139
|
+
|
|
140
|
+
return {
|
|
141
|
+
key: chapterKey,
|
|
142
|
+
title: chapter?.title || `章 ${chapterKey}`,
|
|
143
|
+
sections: sectionKeys,
|
|
144
|
+
sectionData: sections,
|
|
145
|
+
uniqueSections: getUniqueSections(chapterKey)
|
|
146
|
+
}
|
|
147
|
+
})
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// 通常の章リスト(refと付録を除外)
|
|
151
|
+
const regularChapters = computed(() =>
|
|
152
|
+
getChapterList(chapterKey => chapterKey !== 'ref' && !chapterKey.startsWith('ap'))
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
// 付録の章リスト
|
|
156
|
+
const appendixChapters = computed(() =>
|
|
157
|
+
getChapterList(chapterKey => chapterKey.startsWith('ap'))
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
// 表示する章リスト(条件に応じて切り替え)
|
|
161
|
+
const displayChapters = computed(() =>
|
|
162
|
+
isAppendixSection.value ? appendixChapters.value : regularChapters.value
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
// 次の章かどうかを判定
|
|
166
|
+
const isNextChapter = (chapterKey) => {
|
|
167
|
+
return props.nextChapter === chapterKey
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// サイズ設定
|
|
171
|
+
const sizeConfigs = {
|
|
172
|
+
xs: {
|
|
173
|
+
columns: 4,
|
|
174
|
+
headerPadding: 'pt-3 pb-1 px-6',
|
|
175
|
+
headerTitle: 'text-lg',
|
|
176
|
+
mainPadding: 'px-6 py-6',
|
|
177
|
+
gridGap: 'gap-x-4 gap-y-4',
|
|
178
|
+
chapterNumber: 'text-2xl',
|
|
179
|
+
chapterTitle: 'text-[11px]',
|
|
180
|
+
chapterHeader: 'mb-2',
|
|
181
|
+
sectionList: 'mt-2 space-y-1',
|
|
182
|
+
sectionText: 'text-[9px]',
|
|
183
|
+
appendixNumber: 'text-xl',
|
|
184
|
+
nextBadge: 'mb-2 px-1.5 py-0.5 text-[8px]'
|
|
185
|
+
},
|
|
186
|
+
sm: {
|
|
187
|
+
columns: 3,
|
|
188
|
+
headerPadding: 'pt-6 pb-2 px-8',
|
|
189
|
+
headerTitle: 'text-xl',
|
|
190
|
+
mainPadding: 'px-8 py-8',
|
|
191
|
+
gridGap: 'gap-x-6 gap-y-5',
|
|
192
|
+
chapterNumber: 'text-3xl',
|
|
193
|
+
chapterTitle: 'text-xs',
|
|
194
|
+
chapterHeader: 'mb-2.5',
|
|
195
|
+
sectionList: 'mt-2.5 space-y-1',
|
|
196
|
+
sectionText: 'text-[10px]',
|
|
197
|
+
appendixNumber: 'text-2xl',
|
|
198
|
+
nextBadge: 'mb-2.5 px-2 py-0.5 text-[9px]'
|
|
199
|
+
},
|
|
200
|
+
md: {
|
|
201
|
+
columns: 2,
|
|
202
|
+
headerPadding: 'pt-12 px-12',
|
|
203
|
+
headerTitle: 'text-3xl',
|
|
204
|
+
mainPadding: 'px-12 py-14',
|
|
205
|
+
gridGap: 'gap-x-12 gap-y-6',
|
|
206
|
+
chapterNumber: 'text-6xl',
|
|
207
|
+
chapterTitle: 'text-2xl',
|
|
208
|
+
chapterHeader: 'mb-3',
|
|
209
|
+
sectionList: 'mt-3 space-y-1.5',
|
|
210
|
+
sectionText: 'text-lg',
|
|
211
|
+
appendixNumber: 'text-4xl',
|
|
212
|
+
nextBadge: 'mb-3 px-2.5 py-1 text-md'
|
|
213
|
+
},
|
|
214
|
+
lg: {
|
|
215
|
+
columns: 2,
|
|
216
|
+
headerPadding: 'pt-8 pb-4 px-16',
|
|
217
|
+
headerTitle: 'text-4xl',
|
|
218
|
+
mainPadding: 'px-16 py-18',
|
|
219
|
+
gridGap: 'gap-x-16 gap-y-8',
|
|
220
|
+
chapterNumber: 'text-7xl',
|
|
221
|
+
chapterTitle: 'text-xl',
|
|
222
|
+
chapterHeader: 'mb-4',
|
|
223
|
+
sectionList: 'mt-4 space-y-2',
|
|
224
|
+
sectionText: 'text-base',
|
|
225
|
+
appendixNumber: 'text-5xl',
|
|
226
|
+
nextBadge: 'mb-4 px-3 py-1.5 text-xs'
|
|
227
|
+
},
|
|
228
|
+
xl: {
|
|
229
|
+
columns: 1,
|
|
230
|
+
headerPadding: 'pt-10 pb-5 px-20',
|
|
231
|
+
headerTitle: 'text-6xl',
|
|
232
|
+
mainPadding: 'px-20 py-24',
|
|
233
|
+
gridGap: 'gap-x-20 gap-y-12',
|
|
234
|
+
chapterNumber: 'text-8xl',
|
|
235
|
+
chapterTitle: 'text-3xl',
|
|
236
|
+
chapterHeader: 'mb-6',
|
|
237
|
+
sectionList: 'mt-6 space-y-3',
|
|
238
|
+
sectionText: 'text-xl',
|
|
239
|
+
appendixNumber: 'text-7xl',
|
|
240
|
+
nextBadge: 'mb-6 px-4 py-2 text-sm'
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
const currentConfig = computed(() => sizeConfigs[props.size])
|
|
245
|
+
|
|
246
|
+
// 動的クラス
|
|
247
|
+
const headerClasses = computed(() => `text-left ${currentConfig.value.headerPadding}`)
|
|
248
|
+
const headerTitleClasses = computed(() => currentConfig.value.headerTitle)
|
|
249
|
+
const mainContentClasses = computed(() => `flex-1 ${currentConfig.value.mainPadding}`)
|
|
250
|
+
const gridClasses = computed(() => `grid grid-cols-${currentConfig.value.columns} ${currentConfig.value.gridGap}`)
|
|
251
|
+
</script>
|
|
252
|
+
|
|
253
|
+
<style scoped>
|
|
254
|
+
/* このコンポーネント専用のスタイル */
|
|
255
|
+
.table-of-contents-container {
|
|
256
|
+
position: absolute !important;
|
|
257
|
+
top: 0 !important;
|
|
258
|
+
left: 50% !important;
|
|
259
|
+
transform: translateX(-50%) !important;
|
|
260
|
+
width: 100% !important;
|
|
261
|
+
height: 100% !important;
|
|
262
|
+
margin: 0 !important;
|
|
263
|
+
box-sizing: border-box !important;
|
|
264
|
+
z-index: 0 !important;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/* タイポグラフィの最適化 - Swiss Styleの核心 */
|
|
268
|
+
div, span, h2, p {
|
|
269
|
+
font-feature-settings: "palt", "kern";
|
|
270
|
+
-webkit-font-smoothing: antialiased;
|
|
271
|
+
-moz-osx-font-smoothing: grayscale;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/* タブラーナンバーの最適化 */
|
|
275
|
+
.tabular-nums {
|
|
276
|
+
font-variant-numeric: tabular-nums;
|
|
277
|
+
}
|
|
278
|
+
</style>
|
package/layouts/cover.vue
CHANGED
|
@@ -1,279 +1,194 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<div class="slidev-layout cover relative overflow-hidden
|
|
3
|
-
<!-- ランダムに動く大きな円形要素(右上) -->
|
|
4
|
-
<div
|
|
5
|
-
ref="circle1"
|
|
6
|
-
class="absolute -right-32 -top-32 w-96 h-96 bg-sky-300/70 rounded-full transition-all duration-[3000ms] ease-in-out"
|
|
7
|
-
:style="circleStyles[0]"
|
|
8
|
-
/>
|
|
9
|
-
<div
|
|
10
|
-
ref="circle2"
|
|
11
|
-
class="absolute right-8 top-24 w-64 h-64 bg-sky-400/60 rounded-full transition-all duration-[4000ms] ease-in-out"
|
|
12
|
-
:style="circleStyles[1]"
|
|
13
|
-
/>
|
|
2
|
+
<div class="slidev-layout cover relative w-full h-full !p-0 bg-white overflow-hidden font-sans text-slate-900 selection:bg-sky-200 selection:text-sky-900">
|
|
14
3
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
class="absolute
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
4
|
+
<div class="absolute inset-0 z-0 pointer-events-none opacity-10"
|
|
5
|
+
style="background-image: linear-gradient(#0f172a 1px, transparent 1px), linear-gradient(to right, #0f172a 1px, transparent 1px); background-size: 50px 50px;">
|
|
6
|
+
</div>
|
|
7
|
+
|
|
8
|
+
<div class="absolute -top-[20%] -right-[10%] w-[80vh] h-[80vh] z-0 pointer-events-none opacity-20">
|
|
9
|
+
<div class="absolute inset-0 border-3 border-dashed border-slate-700 rounded-full animate-spin-super-slow"></div>
|
|
10
|
+
<div class="absolute inset-[15%] border-3 border-dotted border-sky-700 rounded-full animate-reverse-spin-slow"></div>
|
|
11
|
+
|
|
12
|
+
</div>
|
|
13
|
+
|
|
14
|
+
<div class="absolute bottom-[10%] left-[5%] w-32 h-32 z-0 pointer-events-none opacity-30">
|
|
15
|
+
<svg class="w-full h-full animate-spin-slow text-sky-800" viewBox="0 0 100 100" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
16
|
+
<circle cx="50" cy="50" r="48" stroke="currentColor" stroke-width="0.8" stroke-dasharray="4 4" />
|
|
17
|
+
<rect x="49.5" y="10" width="1" height="80" fill="currentColor" />
|
|
18
|
+
<rect x="10" y="49.5" width="80" height="1" fill="currentColor" />
|
|
19
|
+
</svg>
|
|
20
|
+
</div>
|
|
21
|
+
<div class="absolute right-0 top-0 w-1/3 h-full z-0 pointer-events-none">
|
|
30
22
|
<div
|
|
31
|
-
|
|
32
|
-
:
|
|
33
|
-
|
|
34
|
-
|
|
23
|
+
class="absolute right-0 top-0 w-full h-full bg-slate-50 transform origin-right transition-transform duration-[1500ms] ease-expo-out"
|
|
24
|
+
:class="mounted ? 'scale-x-100' : 'scale-x-0'"
|
|
25
|
+
/>
|
|
26
|
+
<div
|
|
27
|
+
class="absolute right-[15%] top-0 w-32 h-full bg-sky-600/10 transform origin-top transition-transform duration-[2000ms] ease-expo-out delay-100"
|
|
28
|
+
:class="mounted ? 'scale-y-100' : 'scale-y-0'"
|
|
35
29
|
/>
|
|
36
|
-
</div>
|
|
37
|
-
|
|
38
|
-
<!-- ランダムに動く左上の円形グリッド -->
|
|
39
|
-
<div
|
|
40
|
-
class="absolute left-16 top-16 grid grid-cols-3 gap-3 opacity-60 rotating-grid"
|
|
41
|
-
:style="gridStyle"
|
|
42
|
-
>
|
|
43
30
|
<div
|
|
44
|
-
|
|
45
|
-
:
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
:style="dot.style"
|
|
31
|
+
class="absolute right-[25%] bottom-0 w-4 h-2/3 bg-slate-900 transform origin-bottom transition-transform duration-[2000ms] ease-expo-out delay-300"
|
|
32
|
+
:class="mounted ? 'scale-y-100' : 'scale-y-0'"
|
|
33
|
+
/>
|
|
34
|
+
<div
|
|
35
|
+
class="absolute right-0 top-1/4 w-1/2 h-24 bg-sky-500 transform origin-right transition-all duration-[2500ms] ease-in-out delay-500"
|
|
36
|
+
:class="mounted ? 'translate-x-0 opacity-100' : 'translate-x-full opacity-0'"
|
|
51
37
|
/>
|
|
52
38
|
</div>
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
<div class="
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
39
|
+
|
|
40
|
+
<div class="relative z-10 flex flex-col justify-between h-full pl-16 pr-12 py-16 w-full">
|
|
41
|
+
|
|
42
|
+
<div class="flex flex-col items-start space-y-4">
|
|
43
|
+
<div
|
|
44
|
+
class="w-24 h-2 bg-slate-900 transition-all duration-1000 ease-out"
|
|
45
|
+
:class="mounted ? 'w-24 opacity-100' : 'w-0 opacity-0'"
|
|
46
|
+
/>
|
|
61
47
|
|
|
62
|
-
|
|
48
|
+
<div class="space-y-2 ml-4">
|
|
49
|
+
<div class="overflow-hidden">
|
|
50
|
+
<div
|
|
51
|
+
class="text-lg font-bold tracking-widest uppercase text-slate-800 transition-transform duration-1000 ease-out delay-300"
|
|
52
|
+
:class="mounted ? 'translate-y-0' : 'translate-y-full'"
|
|
53
|
+
>
|
|
54
|
+
{{ $slidev.configs.meetingName }}
|
|
55
|
+
</div>
|
|
56
|
+
</div>
|
|
57
|
+
<div class="overflow-hidden">
|
|
58
|
+
<div
|
|
59
|
+
class="text-base font-mono text-sky-800 transition-transform duration-1000 ease-out delay-400"
|
|
60
|
+
:class="mounted ? 'translate-y-0' : 'translate-y-full'"
|
|
61
|
+
>
|
|
62
|
+
{{ $slidev.configs.date }}
|
|
63
|
+
</div>
|
|
64
|
+
</div>
|
|
65
|
+
</div>
|
|
66
|
+
</div>
|
|
67
|
+
|
|
68
|
+
<div class="space-y-6 py-8 w-full">
|
|
63
69
|
<div
|
|
64
70
|
:class="[
|
|
65
|
-
'font-bold text-slate-900 leading-tight ',
|
|
66
|
-
getTitleSizeClass($slidev.configs.titleSize || 'large')
|
|
71
|
+
'font-bold text-slate-900 leading-[0.9] tracking-tight transition-all duration-1000 ease-out delay-500 w-full !max-w-none break-words space-y-6',
|
|
72
|
+
getTitleSizeClass($slidev.configs.titleSize || 'large'),
|
|
73
|
+
mounted ? 'opacity-100 translate-x-0' : 'opacity-0 -translate-x-8'
|
|
67
74
|
]"
|
|
68
75
|
>
|
|
69
76
|
<template v-if="Array.isArray($slidev.configs.coverTitle)">
|
|
70
|
-
<span v-for="(line, index) in $slidev.configs.coverTitle" :key="index">
|
|
77
|
+
<span v-for="(line, index) in $slidev.configs.coverTitle" :key="index" class="block">
|
|
71
78
|
{{ line }}
|
|
72
|
-
<br v-if="index < $slidev.configs.coverTitle.length - 1">
|
|
73
79
|
</span>
|
|
74
80
|
</template>
|
|
75
81
|
<template v-else>
|
|
76
|
-
{{ $slidev.configs.coverTitle.first }}
|
|
77
|
-
<
|
|
78
|
-
{{ $slidev.configs.coverTitle.second }}
|
|
82
|
+
<span class="block">{{ $slidev.configs.coverTitle.first }}</span>
|
|
83
|
+
<span class="block text-slate-500">{{ $slidev.configs.coverTitle.second }}</span>
|
|
79
84
|
</template>
|
|
80
85
|
</div>
|
|
86
|
+
</div>
|
|
87
|
+
|
|
88
|
+
<div class="flex flex-col space-y-6 pt-8 transition-opacity duration-1000 delay-700 w-full"
|
|
89
|
+
:class="mounted ? 'opacity-100' : 'opacity-0'">
|
|
81
90
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
<div class="text-2xl font-
|
|
86
|
-
{{ $slidev.configs.author.
|
|
91
|
+
<div class="grid grid-cols-2 gap-12">
|
|
92
|
+
<div>
|
|
93
|
+
<div class="text-xs font-bold text-slate-400 uppercase tracking-wider mb-1">Presenter</div>
|
|
94
|
+
<div class="text-2xl font-bold text-slate-900 whitespace-nowrap">
|
|
95
|
+
{{ $slidev.configs.author.name }}
|
|
96
|
+
</div>
|
|
97
|
+
<div class="text-sm font-medium text-sky-700 mt-1">
|
|
98
|
+
{{ $slidev.configs.author.affiliation }}
|
|
87
99
|
</div>
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
100
|
+
</div>
|
|
101
|
+
|
|
102
|
+
<div v-if="coAuthors.length > 0">
|
|
103
|
+
<div class="text-xs font-bold text-slate-400 uppercase tracking-wider mb-1">Co-Authors</div>
|
|
104
|
+
<div class="space-y-2">
|
|
91
105
|
<div
|
|
92
106
|
v-for="(coAuthor, index) in coAuthors"
|
|
93
107
|
:key="index"
|
|
94
|
-
class="
|
|
108
|
+
class="flex flex-col"
|
|
95
109
|
>
|
|
96
|
-
<span class="
|
|
110
|
+
<span class="text-lg font-semibold text-slate-800 whitespace-nowrap">{{ coAuthor.name }}</span>
|
|
111
|
+
<span class="text-xs text-slate-500">{{ coAuthor.affiliation }}</span>
|
|
97
112
|
</div>
|
|
98
113
|
</div>
|
|
99
114
|
</div>
|
|
100
|
-
|
|
101
|
-
<div class="text-xl font-semibold text-slate-700">
|
|
102
|
-
{{ $slidev.configs.date }}
|
|
103
|
-
</div>
|
|
104
115
|
</div>
|
|
105
116
|
</div>
|
|
106
117
|
</div>
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
{{ new Date().getFullYear() }} {{ $slidev.configs.author.affiliation }}
|
|
118
|
+
|
|
119
|
+
<div class="absolute bottom-4 right-4 text-[10px] font-mono text-slate-800 z-20 writing-vertical-rl">
|
|
120
|
+
© {{ new Date().getFullYear() }} {{ $slidev.configs.author.affiliation }}
|
|
111
121
|
</div>
|
|
122
|
+
|
|
112
123
|
</div>
|
|
113
124
|
</template>
|
|
114
125
|
|
|
115
126
|
<script setup>
|
|
116
|
-
import { ref, computed, onMounted
|
|
127
|
+
import { ref, computed, onMounted } from 'vue'
|
|
117
128
|
import { useSlideContext } from '@slidev/client'
|
|
118
129
|
|
|
119
130
|
const { $slidev } = useSlideContext()
|
|
131
|
+
const mounted = ref(false)
|
|
132
|
+
|
|
133
|
+
onMounted(() => {
|
|
134
|
+
setTimeout(() => {
|
|
135
|
+
mounted.value = true
|
|
136
|
+
}, 100)
|
|
137
|
+
})
|
|
120
138
|
|
|
121
|
-
// タイトルサイズのクラスを返す関数
|
|
122
139
|
function getTitleSizeClass(size) {
|
|
123
140
|
const sizeMap = {
|
|
124
141
|
'small': 'text-4xl',
|
|
125
142
|
'medium': 'text-5xl',
|
|
126
|
-
'large': 'text-
|
|
127
|
-
'xlarge': 'text-
|
|
128
|
-
'xxlarge': 'text-
|
|
143
|
+
'large': 'text-8xl',
|
|
144
|
+
'xlarge': 'text-9xl',
|
|
145
|
+
'xxlarge': 'text-[10rem]'
|
|
129
146
|
}
|
|
130
|
-
|
|
131
147
|
return sizeMap[size] || sizeMap['large']
|
|
132
148
|
}
|
|
133
149
|
|
|
134
|
-
// 共著者リストを取得
|
|
135
150
|
const coAuthors = computed(() => {
|
|
136
151
|
const configs = $slidev.configs
|
|
137
152
|
if (!configs || !configs.coAuthors || !Array.isArray(configs.coAuthors)) {
|
|
138
153
|
return []
|
|
139
154
|
}
|
|
140
|
-
console.log(configs.coAuthors)
|
|
141
|
-
// 最大4人まで
|
|
142
155
|
return configs.coAuthors.slice(0, 4)
|
|
143
156
|
})
|
|
144
|
-
|
|
145
|
-
// ランダムな値を生成する関数
|
|
146
|
-
const randomRange = (min, max) => Math.random() * (max - min) + min
|
|
147
|
-
|
|
148
|
-
// 初期状態でランダムな位置を設定
|
|
149
|
-
const circleStyles = ref([
|
|
150
|
-
{ transform: `translate(${randomRange(-15, 15)}px, ${randomRange(-15, 15)}px) rotate(${randomRange(-5, 5)}deg)` },
|
|
151
|
-
{ transform: `translate(${randomRange(-20, 20)}px, ${randomRange(-20, 20)}px) rotate(${randomRange(-8, 8)}deg)` }
|
|
152
|
-
])
|
|
153
|
-
|
|
154
|
-
const triangleStyles = ref([
|
|
155
|
-
{ transform: `translate(${randomRange(-10, 10)}px, ${randomRange(-10, 10)}px) rotate(${randomRange(-3, 3)}deg)` },
|
|
156
|
-
{ transform: `translate(${randomRange(-12, 12)}px, ${randomRange(-12, 12)}px) rotate(${randomRange(-4, 4)}deg)` }
|
|
157
|
-
])
|
|
158
|
-
|
|
159
|
-
const linesStyle = ref({
|
|
160
|
-
transform: `translate(${randomRange(-15, 15)}px, ${randomRange(-15, 15)}px) rotate(${randomRange(55, 65)}deg)`
|
|
161
|
-
})
|
|
162
|
-
|
|
163
|
-
const lineWidths = ref([
|
|
164
|
-
randomRange(180, 204),
|
|
165
|
-
randomRange(130, 158),
|
|
166
|
-
randomRange(180, 204),
|
|
167
|
-
randomRange(85, 107)
|
|
168
|
-
])
|
|
169
|
-
|
|
170
|
-
const gridStyle = ref({})
|
|
171
|
-
|
|
172
|
-
const dots = ref([
|
|
173
|
-
{ filled: true, style: { transform: `scale(${randomRange(0.85, 1.15)})` } },
|
|
174
|
-
{ filled: false, style: { transform: `scale(${randomRange(0.85, 1.15)})` } },
|
|
175
|
-
{ filled: true, style: { transform: `scale(${randomRange(0.85, 1.15)})` } },
|
|
176
|
-
{ filled: true, style: { transform: `scale(${randomRange(0.85, 1.15)})` } },
|
|
177
|
-
{ filled: true, style: { transform: `scale(${randomRange(0.85, 1.15)})` } },
|
|
178
|
-
{ filled: false, style: { transform: `scale(${randomRange(0.85, 1.15)})` } },
|
|
179
|
-
{ filled: false, style: { transform: `scale(${randomRange(0.85, 1.15)})` } },
|
|
180
|
-
{ filled: true, style: { transform: `scale(${randomRange(0.85, 1.15)})` } },
|
|
181
|
-
{ filled: true, style: { transform: `scale(${randomRange(0.85, 1.15)})` } }
|
|
182
|
-
])
|
|
183
|
-
|
|
184
|
-
// アニメーション更新関数
|
|
185
|
-
const updateAnimations = () => {
|
|
186
|
-
// 円形要素をランダムに動かす
|
|
187
|
-
circleStyles.value[0] = {
|
|
188
|
-
transform: `translate(${randomRange(-15, 15)}px, ${randomRange(-15, 15)}px) rotate(${randomRange(-5, 5)}deg)`
|
|
189
|
-
}
|
|
190
|
-
circleStyles.value[1] = {
|
|
191
|
-
transform: `translate(${randomRange(-20, 20)}px, ${randomRange(-20, 20)}px) rotate(${randomRange(-8, 8)}deg)`
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
// 三角形要素をランダムに動かす
|
|
195
|
-
triangleStyles.value[0] = {
|
|
196
|
-
transform: `translate(${randomRange(-10, 10)}px, ${randomRange(-10, 10)}px) rotate(${randomRange(-3, 3)}deg)`
|
|
197
|
-
}
|
|
198
|
-
triangleStyles.value[1] = {
|
|
199
|
-
transform: `translate(${randomRange(-12, 12)}px, ${randomRange(-12, 12)}px) rotate(${randomRange(-4, 4)}deg)`
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
// 斜線要素をランダムに動かす
|
|
203
|
-
linesStyle.value = {
|
|
204
|
-
transform: `translate(${randomRange(-15, 15)}px, ${randomRange(-15, 15)}px) rotate(${randomRange(55, 65)}deg)`
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
// 斜線の幅をランダムに変更
|
|
208
|
-
lineWidths.value = [
|
|
209
|
-
randomRange(180, 204),
|
|
210
|
-
randomRange(130, 158),
|
|
211
|
-
randomRange(180, 204),
|
|
212
|
-
randomRange(85, 107)
|
|
213
|
-
]
|
|
214
|
-
|
|
215
|
-
// ドットをランダムにスケール変更
|
|
216
|
-
dots.value = dots.value.map(dot => ({
|
|
217
|
-
...dot,
|
|
218
|
-
style: {
|
|
219
|
-
transform: `scale(${randomRange(0.85, 1.15)})`
|
|
220
|
-
}
|
|
221
|
-
}))
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
let intervalId = null
|
|
225
|
-
|
|
226
|
-
onMounted(() => {
|
|
227
|
-
// 即座に最初のアニメーションを開始
|
|
228
|
-
setTimeout(() => {
|
|
229
|
-
updateAnimations()
|
|
230
|
-
}, 100)
|
|
231
|
-
|
|
232
|
-
// 定期的にアニメーションを更新(3秒ごと)
|
|
233
|
-
intervalId = setInterval(() => {
|
|
234
|
-
updateAnimations()
|
|
235
|
-
}, 3000)
|
|
236
|
-
})
|
|
237
|
-
|
|
238
|
-
onUnmounted(() => {
|
|
239
|
-
if (intervalId) {
|
|
240
|
-
clearInterval(intervalId)
|
|
241
|
-
}
|
|
242
|
-
})
|
|
243
157
|
</script>
|
|
244
158
|
|
|
245
159
|
<style scoped>
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
0% {
|
|
249
|
-
background: linear-gradient(to bottom right, white, #f3f4f6, #e0f2fe);
|
|
250
|
-
}
|
|
251
|
-
33% {
|
|
252
|
-
background: linear-gradient(to bottom right, white, #eff6ff, #dbeafe);
|
|
253
|
-
}
|
|
254
|
-
66% {
|
|
255
|
-
background: linear-gradient(to bottom right, white, #f3f4f6, #dbeafe);
|
|
256
|
-
}
|
|
257
|
-
100% {
|
|
258
|
-
background: linear-gradient(to bottom right, white, #f3f4f6, #e0f2fe);
|
|
259
|
-
}
|
|
160
|
+
.slidev-layout {
|
|
161
|
+
padding: 0 !important;
|
|
260
162
|
}
|
|
261
163
|
|
|
262
|
-
.
|
|
263
|
-
|
|
164
|
+
.ease-expo-out {
|
|
165
|
+
transition-timing-function: cubic-bezier(0.19, 1, 0.22, 1);
|
|
166
|
+
}
|
|
167
|
+
.writing-vertical-rl {
|
|
168
|
+
writing-mode: vertical-rl;
|
|
169
|
+
}
|
|
170
|
+
div {
|
|
171
|
+
font-feature-settings: "palt", "kern";
|
|
264
172
|
}
|
|
265
173
|
|
|
266
|
-
/*
|
|
267
|
-
@keyframes
|
|
268
|
-
from {
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
}
|
|
174
|
+
/* === カスタムアニメーション定義 === */
|
|
175
|
+
@keyframes spin-slow {
|
|
176
|
+
from { transform: rotate(0deg); }
|
|
177
|
+
to { transform: rotate(360deg); }
|
|
178
|
+
}
|
|
179
|
+
@keyframes reverse-spin-slow {
|
|
180
|
+
from { transform: rotate(360deg); }
|
|
181
|
+
to { transform: rotate(0deg); }
|
|
274
182
|
}
|
|
275
183
|
|
|
276
|
-
|
|
277
|
-
|
|
184
|
+
/* Tailwindのデフォルトspinより遥かに遅く設定し、上品さを出す */
|
|
185
|
+
.animate-spin-super-slow {
|
|
186
|
+
animation: spin-slow 80s linear infinite;
|
|
187
|
+
}
|
|
188
|
+
.animate-reverse-spin-slow {
|
|
189
|
+
animation: reverse-spin-slow 40s linear infinite;
|
|
190
|
+
}
|
|
191
|
+
.animate-spin-slow {
|
|
192
|
+
animation: spin-slow 30s linear infinite;
|
|
278
193
|
}
|
|
279
194
|
</style>
|
package/layouts/end.vue
CHANGED
|
@@ -1,104 +1,106 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<div class="slidev-layout
|
|
3
|
-
|
|
2
|
+
<div class="slidev-layout end relative w-full h-full !p-0 bg-white overflow-hidden font-sans text-slate-900 selection:bg-sky-200 selection:text-sky-900">
|
|
3
|
+
|
|
4
|
+
<div class="absolute inset-0 z-0 pointer-events-none opacity-10"
|
|
5
|
+
style="background-image: linear-gradient(#0f172a 1px, transparent 1px), linear-gradient(to right, #0f172a 1px, transparent 1px); background-size: 50px 50px;">
|
|
6
|
+
</div>
|
|
7
|
+
|
|
4
8
|
<div
|
|
5
|
-
class="absolute left-1/2 top-1/2 transform -translate-x-1/2 -translate-y-1/2 w-
|
|
9
|
+
class="absolute left-1/2 top-1/2 transform -translate-x-1/2 -translate-y-1/2 w-[60vh] h-[60vh] z-0 pointer-events-none transition-all duration-[3000ms] ease-in-out"
|
|
6
10
|
:style="centerCircleStyles[0]"
|
|
7
|
-
|
|
11
|
+
>
|
|
12
|
+
<div class="w-full h-full border-2 border-dashed border-slate-300 rounded-full opacity-50"></div>
|
|
13
|
+
</div>
|
|
8
14
|
<div
|
|
9
|
-
class="absolute left-1/2 top-1/2 transform -translate-x-1/2 -translate-y-1/2 w-
|
|
15
|
+
class="absolute left-1/2 top-1/2 transform -translate-x-1/2 -translate-y-1/2 w-[45vh] h-[45vh] z-0 pointer-events-none transition-all duration-[3500ms] ease-in-out"
|
|
10
16
|
:style="centerCircleStyles[1]"
|
|
11
|
-
|
|
17
|
+
>
|
|
18
|
+
<div class="w-full h-full border-2 border-dotted border-sky-700 rounded-full opacity-30"></div>
|
|
19
|
+
</div>
|
|
12
20
|
|
|
13
|
-
|
|
14
|
-
<div class="absolute left-0 top-0 w-96 h-96 overflow-hidden">
|
|
21
|
+
<div class="absolute left-0 top-0 w-96 h-96 overflow-hidden pointer-events-none z-0">
|
|
15
22
|
<div
|
|
16
|
-
class="absolute -left-
|
|
23
|
+
class="absolute -left-12 -top-12 w-64 h-64 border border-slate-200 transition-all duration-[4000ms] ease-in-out"
|
|
17
24
|
:style="topLeftCircleStyles[0]"
|
|
18
25
|
/>
|
|
19
26
|
<div
|
|
20
|
-
class="absolute left-
|
|
27
|
+
class="absolute left-12 top-12 w-32 h-32 bg-sky-200/80 transition-all duration-[3500ms] ease-in-out"
|
|
21
28
|
:style="topLeftCircleStyles[1]"
|
|
22
29
|
/>
|
|
23
30
|
</div>
|
|
24
31
|
|
|
25
|
-
|
|
26
|
-
<div class="absolute right-0 bottom-0 w-80 h-80 overflow-hidden">
|
|
32
|
+
<div class="absolute right-0 bottom-0 w-80 h-80 overflow-hidden pointer-events-none z-0">
|
|
27
33
|
<div
|
|
28
|
-
class="absolute right-16 bottom-16 w-32 h-32 bg-
|
|
34
|
+
class="absolute right-16 bottom-16 w-32 h-32 bg-slate-900/5 transform rotate-45 transition-all duration-[3000ms] ease-in-out"
|
|
29
35
|
:style="bottomRightSquareStyles[0]"
|
|
30
36
|
/>
|
|
31
37
|
<div
|
|
32
|
-
class="absolute right-32 bottom-32 w-24 h-24
|
|
38
|
+
class="absolute right-32 bottom-32 w-24 h-24 border border-sky-500/30 transform rotate-45 transition-all duration-[3500ms] ease-in-out"
|
|
33
39
|
:style="bottomRightSquareStyles[1]"
|
|
34
40
|
/>
|
|
35
41
|
<div
|
|
36
|
-
class="absolute right-8 bottom-8 w-16 h-16 bg-sky-
|
|
42
|
+
class="absolute right-8 bottom-8 w-16 h-16 bg-sky-800/10 transform rotate-45 transition-all duration-[4000ms] ease-in-out"
|
|
37
43
|
:style="bottomRightSquareStyles[2]"
|
|
38
44
|
/>
|
|
39
45
|
</div>
|
|
40
46
|
|
|
41
|
-
<!-- 左下のドット要素 -->
|
|
42
47
|
<div
|
|
43
|
-
class="absolute left-
|
|
48
|
+
class="absolute left-12 bottom-12 grid grid-cols-4 gap-3 opacity-60 transition-all duration-[2500ms] ease-in-out z-0"
|
|
44
49
|
:style="dotsGridStyle"
|
|
45
50
|
>
|
|
46
51
|
<div
|
|
47
52
|
v-for="(dot, index) in dots"
|
|
48
53
|
:key="index"
|
|
49
|
-
:class="['w-
|
|
54
|
+
:class="['w-1.5 h-1.5 transition-all duration-[2500ms] ease-in-out', dot.colorClass]"
|
|
50
55
|
:style="dot.style"
|
|
51
56
|
/>
|
|
52
57
|
</div>
|
|
53
58
|
|
|
54
|
-
<!-- 右上の線形要素 -->
|
|
55
59
|
<div
|
|
56
|
-
class="absolute right-
|
|
60
|
+
class="absolute right-12 top-12 transform rotate-0 space-y-2 opacity-60 transition-all duration-[4500ms] ease-in-out z-0"
|
|
57
61
|
:style="topRightLinesStyle"
|
|
58
62
|
>
|
|
59
63
|
<div
|
|
60
64
|
v-for="(width, index) in lineWidths"
|
|
61
65
|
:key="index"
|
|
62
|
-
:class="['h-
|
|
66
|
+
:class="['h-1 transition-all duration-[2000ms] ease-in-out', index % 2 === 0 ? 'bg-slate-900' : 'bg-sky-600']"
|
|
63
67
|
:style="{ width: `${width}px` }"
|
|
64
68
|
/>
|
|
65
69
|
</div>
|
|
66
70
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
<div class="text-center space-y-8">
|
|
70
|
-
<!-- メインメッセージ -->
|
|
71
|
-
<div class="text-5xl font-bold text-slate-800 leading-tight tracking-wide">
|
|
72
|
-
ご清聴ありがとうございました
|
|
73
|
-
</div>
|
|
71
|
+
<div class="relative z-10 flex flex-col justify-center items-center h-full px-16 w-full">
|
|
72
|
+
<div class="flex flex-col items-start space-y-8 max-w-4xl">
|
|
74
73
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
74
|
+
<div class="w-24 h-2 bg-slate-900"></div>
|
|
75
|
+
|
|
76
|
+
<div class="text-7xl font-bold text-slate-900 leading-[0.9] tracking-tight">
|
|
77
|
+
<span class="block">THANK YOU</span>
|
|
78
|
+
<span class="block text-slate-400">FOR LISTENING</span>
|
|
78
79
|
</div>
|
|
79
80
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
81
|
+
<p class="pt-4 text-xl font-mono text-sky-800 tracking-wide leading-normal">
|
|
82
|
+
{{ $slidev.configs.thankYouMessage || 'ご清聴ありがとうございました' }}
|
|
83
|
+
</p>
|
|
84
|
+
|
|
85
|
+
<div class="pt-8 grid grid-cols-1 gap-4 text-base text-slate-600" v-if="$slidev.configs.contact">
|
|
86
|
+
<div v-if="$slidev.configs.contact.email" class="flex items-center space-x-3">
|
|
87
|
+
<span class="w-8 h-[1px] bg-slate-400"></span>
|
|
88
|
+
<span class="font-semibold text-slate-900">EMAIL</span>
|
|
89
|
+
<span>{{ $slidev.configs.contact.email }}</span>
|
|
90
|
+
</div>
|
|
91
|
+
<div v-if="$slidev.configs.contact.twitter" class="flex items-center space-x-3">
|
|
92
|
+
<span class="w-8 h-[1px] bg-slate-400"></span>
|
|
93
|
+
<span class="font-semibold text-slate-900">X / TWITTER</span>
|
|
94
|
+
<span>{{ $slidev.configs.contact.twitter }}</span>
|
|
85
95
|
</div>
|
|
86
96
|
</div>
|
|
87
97
|
</div>
|
|
88
98
|
</div>
|
|
89
99
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
<div class="text-lg font-semibold">
|
|
93
|
-
{{ $slidev.configs.author.name }}
|
|
94
|
-
</div>
|
|
95
|
-
<div class="text-base font-medium">
|
|
96
|
-
{{ $slidev.configs.author.affiliation }}
|
|
97
|
-
</div>
|
|
98
|
-
<div class="text-sm">
|
|
99
|
-
{{ $slidev.configs.date }}
|
|
100
|
-
</div>
|
|
100
|
+
<div class="absolute bottom-4 right-4 text-[10px] font-mono text-slate-800 z-20 writing-vertical-rl">
|
|
101
|
+
{{ $slidev.configs.author.name }} / {{ $slidev.configs.author.affiliation }}
|
|
101
102
|
</div>
|
|
103
|
+
|
|
102
104
|
</div>
|
|
103
105
|
</template>
|
|
104
106
|
|
|
@@ -108,47 +110,41 @@ import { useSlideContext } from '@slidev/client'
|
|
|
108
110
|
|
|
109
111
|
const { $slidev } = useSlideContext()
|
|
110
112
|
|
|
111
|
-
// ランダムな値を生成する関数
|
|
112
113
|
const randomRange = (min, max) => Math.random() * (max - min) + min
|
|
113
114
|
|
|
114
|
-
// 中央の円形要素のスタイル
|
|
115
115
|
const centerCircleStyles = ref([
|
|
116
|
-
{ transform: `translate(${randomRange(-20, 20)}px, ${randomRange(-20, 20)}px) scale(${randomRange(0.
|
|
117
|
-
{ transform: `translate(${randomRange(-15, 15)}px, ${randomRange(-15, 15)}px) scale(${randomRange(0.
|
|
116
|
+
{ transform: `translate(${randomRange(-20, 20)}px, ${randomRange(-20, 20)}px) scale(${randomRange(0.98, 1.02)})` },
|
|
117
|
+
{ transform: `translate(${randomRange(-15, 15)}px, ${randomRange(-15, 15)}px) scale(${randomRange(0.98, 1.02)})` }
|
|
118
118
|
])
|
|
119
119
|
|
|
120
|
-
// 左上の円形要素のスタイル
|
|
121
120
|
const topLeftCircleStyles = ref([
|
|
122
|
-
{ transform: `translate(${randomRange(-15, 15)}px, ${randomRange(-15, 15)}px)
|
|
123
|
-
{ transform: `translate(${randomRange(-10, 10)}px, ${randomRange(-10, 10)}px)
|
|
121
|
+
{ transform: `translate(${randomRange(-15, 15)}px, ${randomRange(-15, 15)}px)` },
|
|
122
|
+
{ transform: `translate(${randomRange(-10, 10)}px, ${randomRange(-10, 10)}px)` }
|
|
124
123
|
])
|
|
125
124
|
|
|
126
|
-
// 右下の正方形要素のスタイル
|
|
127
125
|
const bottomRightSquareStyles = ref([
|
|
128
126
|
{ transform: `translate(${randomRange(-10, 10)}px, ${randomRange(-10, 10)}px) rotate(${randomRange(40, 50)}deg)` },
|
|
129
127
|
{ transform: `translate(${randomRange(-12, 12)}px, ${randomRange(-12, 12)}px) rotate(${randomRange(40, 50)}deg)` },
|
|
130
128
|
{ transform: `translate(${randomRange(-8, 8)}px, ${randomRange(-8, 8)}px) rotate(${randomRange(40, 50)}deg)` }
|
|
131
129
|
])
|
|
132
130
|
|
|
133
|
-
// 左下のドットグリッドのスタイル
|
|
134
131
|
const dotsGridStyle = ref({
|
|
135
|
-
transform: `translate(${randomRange(-8, 8)}px, ${randomRange(-8, 8)}px)
|
|
132
|
+
transform: `translate(${randomRange(-8, 8)}px, ${randomRange(-8, 8)}px)`
|
|
136
133
|
})
|
|
137
134
|
|
|
138
135
|
const dots = ref([
|
|
139
|
-
{
|
|
140
|
-
{
|
|
141
|
-
{
|
|
142
|
-
{
|
|
143
|
-
{
|
|
144
|
-
{
|
|
145
|
-
{
|
|
146
|
-
{
|
|
136
|
+
{ colorClass: 'bg-slate-900', style: { transform: `scale(${randomRange(0.85, 1.15)})` } },
|
|
137
|
+
{ colorClass: 'bg-sky-600', style: { transform: `scale(${randomRange(0.85, 1.15)})` } },
|
|
138
|
+
{ colorClass: 'bg-slate-900', style: { transform: `scale(${randomRange(0.85, 1.15)})` } },
|
|
139
|
+
{ colorClass: 'bg-sky-600', style: { transform: `scale(${randomRange(0.85, 1.15)})` } },
|
|
140
|
+
{ colorClass: 'bg-sky-600', style: { transform: `scale(${randomRange(0.85, 1.15)})` } },
|
|
141
|
+
{ colorClass: 'bg-slate-900', style: { transform: `scale(${randomRange(0.85, 1.15)})` } },
|
|
142
|
+
{ colorClass: 'bg-sky-600', style: { transform: `scale(${randomRange(0.85, 1.15)})` } },
|
|
143
|
+
{ colorClass: 'bg-slate-900', style: { transform: `scale(${randomRange(0.85, 1.15)})` } }
|
|
147
144
|
])
|
|
148
145
|
|
|
149
|
-
// 右上の線形要素のスタイル
|
|
150
146
|
const topRightLinesStyle = ref({
|
|
151
|
-
transform: `translate(${randomRange(-10, 10)}px, ${randomRange(-10, 10)}px)
|
|
147
|
+
transform: `translate(${randomRange(-10, 10)}px, ${randomRange(-10, 10)}px)`
|
|
152
148
|
})
|
|
153
149
|
|
|
154
150
|
const lineWidths = ref([
|
|
@@ -158,25 +154,21 @@ const lineWidths = ref([
|
|
|
158
154
|
randomRange(105, 125)
|
|
159
155
|
])
|
|
160
156
|
|
|
161
|
-
// アニメーション更新関数
|
|
162
157
|
const updateAnimations = () => {
|
|
163
|
-
// 中央の円形要素
|
|
164
158
|
centerCircleStyles.value[0] = {
|
|
165
|
-
transform: `translate(${randomRange(-20, 20)}px, ${randomRange(-20, 20)}px) scale(${randomRange(0.
|
|
159
|
+
transform: `translate(${randomRange(-20, 20)}px, ${randomRange(-20, 20)}px) scale(${randomRange(0.98, 1.02)})`
|
|
166
160
|
}
|
|
167
161
|
centerCircleStyles.value[1] = {
|
|
168
|
-
transform: `translate(${randomRange(-15, 15)}px, ${randomRange(-15, 15)}px) scale(${randomRange(0.
|
|
162
|
+
transform: `translate(${randomRange(-15, 15)}px, ${randomRange(-15, 15)}px) scale(${randomRange(0.98, 1.02)})`
|
|
169
163
|
}
|
|
170
164
|
|
|
171
|
-
// 左上の円形要素
|
|
172
165
|
topLeftCircleStyles.value[0] = {
|
|
173
|
-
transform: `translate(${randomRange(-15, 15)}px, ${randomRange(-15, 15)}px)
|
|
166
|
+
transform: `translate(${randomRange(-15, 15)}px, ${randomRange(-15, 15)}px)`
|
|
174
167
|
}
|
|
175
168
|
topLeftCircleStyles.value[1] = {
|
|
176
|
-
transform: `translate(${randomRange(-10, 10)}px, ${randomRange(-10, 10)}px)
|
|
169
|
+
transform: `translate(${randomRange(-10, 10)}px, ${randomRange(-10, 10)}px)`
|
|
177
170
|
}
|
|
178
171
|
|
|
179
|
-
// 右下の正方形要素
|
|
180
172
|
bottomRightSquareStyles.value[0] = {
|
|
181
173
|
transform: `translate(${randomRange(-10, 10)}px, ${randomRange(-10, 10)}px) rotate(${randomRange(40, 50)}deg)`
|
|
182
174
|
}
|
|
@@ -187,9 +179,8 @@ const updateAnimations = () => {
|
|
|
187
179
|
transform: `translate(${randomRange(-8, 8)}px, ${randomRange(-8, 8)}px) rotate(${randomRange(40, 50)}deg)`
|
|
188
180
|
}
|
|
189
181
|
|
|
190
|
-
// 左下のドットグリッド
|
|
191
182
|
dotsGridStyle.value = {
|
|
192
|
-
transform: `translate(${randomRange(-8, 8)}px, ${randomRange(-8, 8)}px)
|
|
183
|
+
transform: `translate(${randomRange(-8, 8)}px, ${randomRange(-8, 8)}px)`
|
|
193
184
|
}
|
|
194
185
|
|
|
195
186
|
dots.value = dots.value.map(dot => ({
|
|
@@ -199,9 +190,8 @@ const updateAnimations = () => {
|
|
|
199
190
|
}
|
|
200
191
|
}))
|
|
201
192
|
|
|
202
|
-
// 右上の線形要素
|
|
203
193
|
topRightLinesStyle.value = {
|
|
204
|
-
transform: `translate(${randomRange(-10, 10)}px, ${randomRange(-10, 10)}px)
|
|
194
|
+
transform: `translate(${randomRange(-10, 10)}px, ${randomRange(-10, 10)}px)`
|
|
205
195
|
}
|
|
206
196
|
|
|
207
197
|
lineWidths.value = [
|
|
@@ -215,12 +205,10 @@ const updateAnimations = () => {
|
|
|
215
205
|
let intervalId = null
|
|
216
206
|
|
|
217
207
|
onMounted(() => {
|
|
218
|
-
// 即座に最初のアニメーションを開始
|
|
219
208
|
setTimeout(() => {
|
|
220
209
|
updateAnimations()
|
|
221
210
|
}, 100)
|
|
222
211
|
|
|
223
|
-
// 定期的にアニメーションを更新(3秒ごと)
|
|
224
212
|
intervalId = setInterval(() => {
|
|
225
213
|
updateAnimations()
|
|
226
214
|
}, 3000)
|
|
@@ -234,27 +222,15 @@ onUnmounted(() => {
|
|
|
234
222
|
</script>
|
|
235
223
|
|
|
236
224
|
<style scoped>
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
0% {
|
|
240
|
-
background: linear-gradient(to top right, #e0f2fe, white, #f0f9ff);
|
|
241
|
-
}
|
|
242
|
-
33% {
|
|
243
|
-
background: linear-gradient(to top right, #dbeafe, white, #eff6ff);
|
|
244
|
-
}
|
|
245
|
-
66% {
|
|
246
|
-
background: linear-gradient(to top right, #e0f2fe, #f3f4f6, white);
|
|
247
|
-
}
|
|
248
|
-
100% {
|
|
249
|
-
background: linear-gradient(to top right, #e0f2fe, white, #f0f9ff);
|
|
250
|
-
}
|
|
225
|
+
.slidev-layout {
|
|
226
|
+
padding: 0 !important;
|
|
251
227
|
}
|
|
252
228
|
|
|
253
|
-
.
|
|
254
|
-
|
|
229
|
+
.writing-vertical-rl {
|
|
230
|
+
writing-mode: vertical-rl;
|
|
255
231
|
}
|
|
256
232
|
|
|
257
|
-
|
|
258
|
-
font-
|
|
233
|
+
div, p {
|
|
234
|
+
font-feature-settings: "palt", "kern";
|
|
259
235
|
}
|
|
260
|
-
</style>
|
|
236
|
+
</style>
|