/
TheHeadSection.vue
505 lines (438 loc) · 16.4 KB
/
TheHeadSection.vue
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
<template>
<BaseSection class="the-head-section">
<div class="main-visual-wrapper">
<no-ssr>
<svg
class="main-visual"
:viewBox="viewBox"
:width="width"
:height="height"
>
<g transform="translate(-6, -6)">
<transition-group
tag="g"
mode="out-in"
@enter="enter"
@leave="leave"
>
<g v-for="item in itemsFlatten" :key="item.key">
<component :is="item.type" :item="item" />
</g>
</transition-group>
</g>
</svg>
</no-ssr>
</div>
<h1 class="title">
Vue Fes Japan 2019
</h1>
<div class="date-place">
<time datetime="2019-10-12">2019.10.12</time>
<span class="date-place__day">SAT</span>
<span class="date-place__place">TOC GOTANDA MESSE</span>
</div>
<div class="notification">
<h3>
チケット返金について追加のお知らせ
</h3>
<!-- prettier-ignore -->
<p>
2019/11/13 現在、販売元である Universe 側のトラブルにより、一部の方々に対して返金が完了していないことが判明いたしました。これまでの経緯は <a href="https://note.mu/ryamakuchi/n/ncdd3950d7580" target="_blank" rel="noopener noreferrer">note の記事</a> にまとめています。お待たせして申し訳ございませんが、現在対応中とのことです。返金されるまで今しばらくお待ちくださいますようお願い申し上げます。
</p>
<!-- prettier-ignore -->
<p>
2019年11月16日 Vue.js 日本ユーザーグループ 代表 川口 和也
</p>
</div>
<div class="notification">
<h3>
Vue Fes Japan 2019 チケット返金のお知らせ
</h3>
<!-- prettier-ignore -->
<p>
2019年10月12日(土)TOC 五反田メッセにて開催を予定しておりました「Vue Fes Japan 2019」は、台風19号の接近に伴い開催中止を決定しました。皆様には多大なご迷惑をおかけしますことを、心より深くお詫び申し上げます。
</p>
<!-- prettier-ignore -->
<p>
チケットをご購入いただいた皆様には、チケット代金を全額返金させていただきます。払い戻し処理は、10月12日(土)中に実施を予定しております。処理が完了後、決済プラットフォームの Universe からチケット購入時に入力したメールアドレスにキャンセルの旨を記したメールが送信されます。実際のご返金タイミングは利用されたクレジットカード会社様により異なります。返金確認は、クレジットカードの明細にてご確認いただけます。返金日については、カード会社様にお問い合わせいただくようお願いいたします。
</p>
<!-- prettier-ignore -->
<p>
返金に際して、チケットご購入者側でのお手続きは不要です。
</p>
<!-- prettier-ignore -->
<p>
なお、個人間でのチケット売買や譲渡に関しては、主催者側は責任を負いかねますのでご注意ください。また、Vue Fes Japan 2019 参加に伴って参加者ご自身が手配された交通機関・宿泊施設などの保証は致しかねますのであらかじめご了承ください。
</p>
<!-- prettier-ignore -->
<p>
2019年10月11日 Vue.js 日本ユーザーグループ 代表 川口 和也
</p>
</div>
<div class="notification">
<h3>
Vue Fes Japan 2019 開催中止のお知らせ
</h3>
<!-- prettier-ignore -->
<p>
2019年10月12日(土)TOC 五反田メッセにて開催を予定しておりました「Vue Fes Japan 2019」は、台風19号の接近に伴い、参加者、スポンサー、スピーカー、スタッフ、すべての人の安全を最優先に考慮し、また JR 東日本の他、主要交通機関が計画運休を発表したことを受け、開催中止を決定しました。
</p>
<p>
開催を楽しみにお待ちいただいた皆様には多大なご迷惑をおかけしますことを、心より深くお詫び申し上げます。また中止の決定が開催前日となってしまったことにより、特に遠方からお越しいただく予定だった皆様には多大なご負担をおかけしましたことを重ねてお詫び申し上げます。
</p>
<!-- prettier-ignore -->
<p>
ここで、今回の中止決定の経緯について改めてご説明させていただきます。主催者である我々 Vue.js 日本ユーザーグループは有志による非営利のコミュニティであり、今回のような大規模カンファレンスを開催するための費用は、スポンサー様による協賛金と、ご参加いただく皆様によるチケット売上のみに依存しております。
</p>
<!-- prettier-ignore -->
<p>
そこで万が一の開催中止に備えて、Vue Fes Japan 2019 では <a href="https://note.mu/448jp/n/n36cbb8d2e91f" target="_blank" rel="noopener noreferrer">先日の発表</a> のとおり興行中止保険に加入しています。具体的には、開催当日に開催地より半径 100km 以内に開催に影響を与える自然現象(今回の場合は台風にあたります)がある場合、または開催地への交通機関が運休などで機能しなくなった場合、この 2つの条件のいずれかを満たした場合に一部費用を補償する契約内容です。
</p>
<!-- prettier-ignore -->
<p>
同契約では、最終的に費用が補償されるかどうかの判断は開催当日になされることになります。つまり、計画運休の発表や台風の予想進路が確定的であっても、仮に当日に運休が取り消しになり交通機関が通常通り運行され、台風が開催に影響を与えないと判断された場合は、補償が行われない可能性があります。我々 Vue.js 日本ユーザーグループには、補償が行われなかった場合に多額の金銭的損失を補填できるだけの余力は残念ながらありません。このリスクを最小限に抑えるためには、開催中止のタイミングを当日早朝、または前日夜にすることが最善と考え先日の発表に至りました。
</p>
<p>
しかし連日連夜スタッフ内でも協議を重ねた結果、金銭面などのリスクよりも、最終的にはすべての人の安全が何よりも最優先に確保されるべきと考え、当初の予定よりも早く開催中止を決定することになりました。
</p>
<!-- prettier-ignore -->
<p>
チケット代金の返金方法やスケジュールについては、決定次第、本 Web サイトおよび <a href="https://twitter.com/vuefes" target="_blank" rel="noopener noreferrer">公式 Twitter</a> でお知らせいたします。また、興行中止保険が適用となった場合、保険の規約上、Vue.js 日本ユーザーグループとして Vue Fes Japan 2019 の代替イベントを企画・開催することが禁止されております。こちらも併せてお知らせさせていただきます。
</p>
<p>
改めて、開催を楽しみにしてくださった皆様に多大なご迷惑、ご心配をおかけしましたことを心より深くお詫び申し上げます。何卒、ご理解いただけますようよろしくお願い申し上げます。
</p>
<!-- prettier-ignore -->
<p>
2019年10月11日 Vue.js 日本ユーザーグループ 代表 川口 和也
</p>
</div>
<LinkToTwitter />
</BaseSection>
</template>
<script lang="ts">
import { setInterval } from 'timers'
import { Component, Vue } from 'nuxt-property-decorator'
import BaseSection from '~/components/BaseSection.vue'
import HeadCircle from '~/components/HeadCircle.vue'
import HeadHorizontal from '~/components/HeadHorizontal.vue'
import HeadSquare from '~/components/HeadSquare.vue'
import HeadSlash from '~/components/HeadSlash.vue'
import HeadTriangle from '~/components/HeadTriangle.vue'
import HeadCross from '~/components/HeadCross.vue'
import HeadPhoto from '~/components/HeadPhoto.vue'
import LinkToTwitter from '~/components/LinkToTwitter.vue'
type PartsType =
| 'head-circle'
| 'head-horizontal'
| 'head-square'
| 'head-triangle'
| 'head-cross'
| 'head-photo'
| 'head-slash'
const gap = 12
const grid = 120
export interface Parts {
type: PartsType
x: number
y: number
rotate: number
src: string
key: string
}
export const partsLeaveTime = 0.2
export const partsCreateTime = 0.6
let timer
type WindowMode = 'sm' | 'md' | 'lg'
/**
* 現在の画面サイズ名を返す
*/
function getWindowMode(): WindowMode {
if (window.innerWidth > 980) {
return 'lg'
}
if (window.innerWidth > 768) {
return 'md'
}
return 'sm'
}
@Component({
components: {
BaseSection,
HeadCircle,
HeadHorizontal,
HeadSquare,
HeadSlash,
HeadTriangle,
HeadCross,
HeadPhoto,
LinkToTwitter
}
})
export default class TheHeadSection extends Vue {
get viewBox(): string {
return `0 0 ${this.width} ${this.height}`
}
get itemsFlatten(): Parts[] {
if (!this.visible) {
return []
}
return Array.prototype.concat.apply([], this.items)
}
get items(): Parts[][] {
return this.pattern[this.patternIndex].map((line, row) =>
Array.from(line)
.slice(0, this.t)
.map(
(p, col): Parts => {
let type: PartsType = 'head-circle'
let rotate = 0
let src = ''
switch (p) {
case '⮽':
type = 'head-cross'
break
case '⧅':
type = 'head-slash'
rotate = 90
break
case '⧄':
type = 'head-slash'
break
case '■':
type = 'head-square'
break
case '|':
type = 'head-horizontal'
rotate = 90
break
case 'o':
type = 'head-circle'
break
case '-':
type = 'head-horizontal'
break
case '◢':
type = 'head-triangle'
break
case '◥':
type = 'head-triangle'
rotate = 270
break
case '◣':
type = 'head-triangle'
rotate = 90
break
case '◤':
type = 'head-triangle'
rotate = 180
break
case '1':
type = 'head-photo'
src = 'image01.png'
break
case '2':
type = 'head-photo'
src = 'image02.png'
break
case '3':
type = 'head-photo'
src = 'image03.png'
break
case '4':
type = 'head-photo'
src = 'image04.png'
break
case '5':
type = 'head-photo'
src = 'image05.png'
break
case '6':
type = 'head-photo'
src = 'image06.png'
break
}
return {
type,
x: (gap + grid) / 2 + col * (gap + grid),
y: (gap + grid) / 2 + row * (gap + grid),
rotate,
src,
key: `${row}-${col}-${type}-${rotate}`
}
}
)
)
}
private width = 0
private height = 384
private pattern = [
['-⧄|⧅⧄⧅⮽⧄o', 'o⮽⧄◢-⧄⧅◥|', '⧅◣⧅⧄⧅|⧄⧅⧄'],
['1⧄|⧅o⧅⮽⧄◥', 'o⮽⧄◢⧄-⧅2|', '◥◣o⧄⧅|⧄⧅⧄'],
['1⧄|3◤⧅o⧄◥', '-⮽■◣⧄4⧅2|', '⧅⧅o⧄⧅◤⧄■⧄'],
['◣⧄⧅3◤⧅o⧄6', '-⮽1⧅⧄4⧅|⧄', '⧅◣o⧄2-⧄⧄⧄'],
['5⧄◣3◤⧅o⧄6', '-⮽1⧅⧄4⮽|⧄', '⧄◣o⧅2-⧄⧄⧄'],
['o⧄⧅3⧄⧅o⧄6', '-⮽1⧅⧄4⧄|⧄', '⧄◣o⧄2⧅⧄◥⮽'],
['3⧄4⧅⧄⧅⮽5o', 'o⮽⧄◤2⧄6◥|', '⧅◢⧅1⧅⧅⧄-⧄']
]
private patternIndex = 0
private t = 0
private tMax = 0
private visible = true
private windowMode: WindowMode = 'sm'
adjustSvg(mode: WindowMode) {
// SSR 時には SVG の表示を確定することができないため、
// 初期表示時にちらついてしまう
this.t = 0
this.width = (grid + gap) * 5 - gap
this.tMax = 15
if (mode === 'md') {
this.width = (grid + gap) * 6 - gap
this.tMax = 18
}
if (mode === 'lg') {
this.width = (grid + gap) * 9 - gap
this.tMax = 27
}
}
leave(el, done) {
setTimeout(() => {
done()
}, partsLeaveTime * 1000)
}
enter(el, done) {
setTimeout(() => {
done()
}, partsCreateTime * 1000)
}
mounted() {
this.windowMode = getWindowMode()
this.adjustSvg(this.windowMode)
setInterval(() => {
if (this.t < this.tMax) {
this.t++
}
}, 90)
setInterval(() => {
this.patternIndex = (this.patternIndex + 1) % this.pattern.length
}, 2000)
window.addEventListener('resize', () => {
if (timer > 0) {
clearTimeout(timer)
}
timer = setTimeout(() => {
// 画面サイズ名が変更になったときのみリドローをかける
const preWindowMode = this.windowMode
this.windowMode = getWindowMode()
if (preWindowMode !== this.windowMode) {
this.visible = false
setTimeout(() => {
this.visible = true
this.adjustSvg(this.windowMode)
}, 400)
}
}, 100)
})
}
}
</script>
<style lang="scss" scoped>
$svg-gap: 12px;
$svg-grid: 120px;
.the-head-section {
color: $primary-text-color--invert;
background: linear-gradient(to right bottom, $hiwamoegi, $asagi);
}
.main-visual-wrapper {
text-align: center;
margin: 1vw 0 5vw;
// grid の 1辺 x 3 + gap x 2
height: calc(
((100vw - 7.8vw * 2 - #{$svg-gap} * 4) / 5) * 3 + #{$svg-gap} * 2
);
line-height: 0;
}
.title {
font-size: 8.3vw;
font-weight: bold;
line-height: 1;
}
.date-place {
color: $primary-text-color--invert;
font-size: 0;
margin-bottom: 7.8vw;
time,
&__place {
font-size: 4.1vw;
}
&__day {
font-size: 2.3vw;
margin: 0 15px 0 5px;
}
}
.notification {
color: $primary-text-color;
background-color: $white;
padding: 5vw;
margin-bottom: 8vw;
@media screen and (min-width: $layout-breakpoint--is-small-up) {
margin: 0 auto 60px;
padding: calc(
#{$layout-column-width--is-small-up} + #{$layout-gutter-width--is-small-up}
);
// prettier-ignore
width: calc(
#{$layout-column-width--is-small-up} * 16 + #{$layout-gutter-width--is-small-up} * 15
);
min-width: $content-min-width--is-small-up;
}
h3 {
font-size: 5vw;
font-weight: bold;
@media screen and (min-width: $layout-breakpoint--is-small-up) {
font-size: 30px;
}
}
p {
margin-top: 2em;
}
}
circle {
fill: $vue-dark-blue;
}
svg.main-visual {
max-width: 100%;
height: auto;
}
@media screen and (min-width: $layout-breakpoint--is-small-up) {
.main-visual-wrapper {
margin: 0 0 40px;
height: calc(#{$svg-grid} * 3 + #{$svg-gap} * 2);
@media screen and (max-width: 860px) {
// grid の 1辺が 120px 未満のときの調整
height: calc(
((100vw - 70px * 2 - #{$svg-gap} * 5) / 6) * 3 + #{$svg-gap} * 2
);
}
@media screen and (min-width: $layout-breakpoint--is-medium-up) and (max-width: 1316px) {
// grid の 1辺が 120px 未満のときの調整
height: calc(
((100vw - 70px * 2 - #{$svg-gap} * 8) / 9) * 3 + #{$svg-gap} * 2
);
}
}
.title {
font-size: 64px;
}
.date-place {
margin-bottom: 60px;
time,
&__place {
font-size: 32px;
}
&__day {
font-size: 18px;
}
}
}
</style>