root 4 年 前
コミット
07f50a5251
共有9 個のファイルを変更した1216 個の追加0 個の削除を含む
  1. 1 0
      assets/css/style.css
  2. 561 0
      assets/css/style.scss
  3. 371 0
      assets/js/main.js
  4. 174 0
      assets/js/util.js
  5. 3 0
      event-abs-circuit.html
  6. 3 0
      event-restorative-yoga.html
  7. 3 0
      event-rowing-workout.html
  8. 3 0
      event-yoga-1.html
  9. 97 0
      index.html

File diff suppressed because it is too large
+ 1 - 0
assets/css/style.css


+ 561 - 0
assets/css/style.scss ファイルの表示

@@ -0,0 +1,561 @@
1
+@import '../../../../../codyhouse-framework/main/assets/css/style.scss'; // ⚠️ make sure to import the CodyHouse framework
2
+@import url('https://fonts.googleapis.com/css?family=Source+Sans+Pro:400,600'); // custom font
3
+
4
+// --------------------------------
5
+
6
+// Schedule Template - by CodyHouse.co
7
+
8
+// --------------------------------
9
+
10
+:root {
11
+  // colors
12
+  @include defineColorHSL(--cd-color-event-1, 199, 25%, 46%);   // Smalt Blue
13
+  @include defineColorHSL(--cd-color-event-2, 271, 23%, 26%);   // Martinique
14
+  @include defineColorHSL(--cd-color-event-3, 162, 14%, 68%);   // Edward
15
+  @include defineColorHSL(--cd-color-event-4, 31, 89%, 68%);    // Rajah
16
+  @include defineColorHSL(--cd-color-text, 0, 0%, 13%);         // Black
17
+  @include defineColorHSL(--cd-color-border, 0, 0%, 92%);       // Grey
18
+
19
+  // font
20
+  --font-primary: 'Source Sans Pro', sans-serif;
21
+
22
+  //schedule template
23
+  --schedule-rows-number: 19;
24
+  --schedule-rows-height: 50px;
25
+}
26
+
27
+body {
28
+	color: var(--cd-color-text);
29
+}
30
+
31
+a {
32
+	color: var(--cd-color-event-3);
33
+}
34
+
35
+.cd-schedule {
36
+	position: relative;
37
+
38
+	&::before { // never visible - this is used in js to check the current MQ
39
+		content: 'mobile';
40
+		display: none;
41
+	}
42
+  
43
+  .js & {
44
+  	@include breakpoint(md) {
45
+      width: calc(100% - 2*var(--component-padding));
46
+      margin-left: auto;
47
+      margin-right: auto;
48
+      max-width: var(--max-width-xl);
49
+
50
+  		&::before {
51
+  			content: 'desktop';
52
+  		}
53
+  	}
54
+  }
55
+}
56
+
57
+.cd-schedule__timeline { // events time
58
+	display: none;
59
+
60
+  .js & {
61
+  	@include breakpoint(md) {
62
+  		display: block;
63
+  		position: absolute;
64
+  		top: 0;
65
+  		left: 0;
66
+  		height: 100%;
67
+  		width: 100%;
68
+  		padding-top: var(--schedule-rows-height);
69
+
70
+  		li {
71
+  			position: relative;
72
+  			height: var(--schedule-rows-height);
73
+
74
+  			&::after { // this is used to create the table horizontal lines
75
+  				content: '';
76
+  				position: absolute;
77
+  				bottom: 0;
78
+  				left: 0;
79
+  				width: 100%;
80
+  				height: 1px;
81
+  				background: var(--cd-color-border);
82
+  			}
83
+
84
+  			&:last-of-type::after {
85
+  				display: none;
86
+  			}
87
+
88
+  			span {
89
+  				display: none;
90
+  			}
91
+  		}
92
+  	}
93
+
94
+  	@include breakpoint(lg) {
95
+  		li {
96
+
97
+  			&::after {
98
+  				width: calc(100% - 60px);
99
+  				left: 60px;
100
+  			}
101
+
102
+  			span {
103
+  				display: inline-block;
104
+  				transform: translateY(-50%);
105
+          font-size: var(--text-sm);
106
+  			}
107
+
108
+  			&:nth-of-type(2n) span {
109
+  				display: none;
110
+  			}
111
+  		}
112
+  	}
113
+  }
114
+}
115
+
116
+.cd-schedule__events {
117
+	position: relative;
118
+	z-index: 1;
119
+  
120
+  .js & {
121
+  	@include breakpoint(md) {
122
+  		width: 100%;
123
+
124
+      > ul {
125
+        display: flex;
126
+        flex-wrap: nowrap;
127
+      }
128
+  	}
129
+
130
+  	@include breakpoint(lg) {
131
+  		width: calc(100% - 60px); // 60px is the .cd-schedule__timeline > li::after element left
132
+  		margin-left: 60px;
133
+  	}
134
+  }
135
+}
136
+
137
+.cd-schedule__group { // group of same day events
138
+  margin-bottom: var(--space-lg);
139
+  
140
+  .js & {
141
+    @include breakpoint(md) {
142
+      flex-basis: 0;
143
+      flex-grow: 1;
144
+      border: 1px solid var(--cd-color-border);
145
+      margin-bottom: 0; // reset style
146
+
147
+      &:not(:first-of-type) {
148
+        border-left-width: 0;
149
+      }
150
+    }
151
+  }
152
+}
153
+
154
+.cd-schedule__group > ul {
155
+  position: relative;
156
+  padding: 0 var(--component-padding);
157
+  display: flex;
158
+  overflow-x: scroll;
159
+  -webkit-overflow-scrolling: touch;
160
+
161
+  &::after { // never visible - used to add a right padding to .cd-schedule__group > ul
162
+    display: inline-block;
163
+    content: '-';
164
+    width: 1px;
165
+    height: 100%;
166
+    opacity: 0;
167
+    color: transparent;
168
+  }
169
+  
170
+  .js & {
171
+    @include breakpoint(md) {
172
+      height: calc(var(--schedule-rows-height)*var(--schedule-rows-number));
173
+      overflow: visible;
174
+      padding: 0;
175
+
176
+      &::after { // reset style
177
+        display: none;
178
+      }
179
+    }
180
+  }
181
+}
182
+
183
+.cd-schedule__top-info { // day label
184
+  width: 100%;
185
+  padding: 0 var(--component-padding);
186
+
187
+  > span {
188
+    display: inline-block;
189
+    margin-bottom: var(--space-sm);
190
+    font-weight: bold;
191
+  }
192
+  
193
+  .js & {
194
+    @include breakpoint(md) {
195
+      // vertically center its content
196
+      display: flex;
197
+      align-items: center;
198
+      justify-content: center;
199
+      height: var(--schedule-rows-height);
200
+      border-bottom: 1px solid var(--cd-color-border);
201
+      padding: 0; // reset style
202
+
203
+      > span {
204
+        font-weight: normal;
205
+        font-size: var(--text-sm);
206
+        margin-bottom: 0;
207
+      } 
208
+    }
209
+  }
210
+}
211
+
212
+.cd-schedule__event {
213
+  flex-shrink: 0; // force them to stay on one line
214
+  float: left; // flex fallback
215
+  height: 150px;
216
+  width: 70%;
217
+  max-width: 300px;
218
+  margin-right: var(--space-md);
219
+  transition: opacity .2s, background .2s;
220
+  
221
+  a {
222
+    display: block;
223
+    height: 100%;
224
+    padding: var(--space-sm);
225
+    box-shadow: inset 0 -3px 0 rgba(#000, .2);
226
+    text-decoration: none;
227
+  }
228
+
229
+  a::before { // event start/end date
230
+    content: attr(data-start)' - 'attr(data-end);
231
+  }
232
+  
233
+  .js & {
234
+    @include breakpoint(sm) {
235
+      width: 40%;
236
+    }
237
+
238
+    @include breakpoint(md) {
239
+      position: absolute;
240
+      z-index: 3;
241
+      width: calc(100% + 2px); // top position and height will be set using js
242
+      left: -1px;
243
+      max-width: none; // reset style
244
+      margin-right: 0;
245
+
246
+      a {
247
+        padding: var(--space-sm);
248
+        box-shadow: 0 10px 20px rgba(#000, .1), inset 0 -3px 0 rgba(#000, .2);
249
+      }
250
+    }
251
+  }
252
+}
253
+
254
+.js {
255
+  @include breakpoint(md) {
256
+    .cd-schedule__event--selected { // classes added when an user select the event
257
+      visibility: hidden;
258
+    }
259
+
260
+    .cd-schedule--loading .cd-schedule__event {
261
+    	// the class .cd-schedule--loading is added by default to the .cd-schedule element
262
+    	// it is removed as soon as the single events are placed in the schedule plan (using javascript)
263
+    	opacity: 0;
264
+    }
265
+  }
266
+}
267
+
268
+.cd-schedule__name, // event name in the schedule template
269
+.cd-schedule__event a::before, // event date in the schedule template
270
+.cd-schedule-modal__name, // event name in the modal element
271
+.cd-schedule-modal__date { // event date in the modal element
272
+	display: block;
273
+	color: var(--color-white);
274
+	font-weight: bold;
275
+	@include fontSmooth;
276
+}
277
+
278
+.cd-schedule__name,
279
+.cd-schedule-modal__name {
280
+	font-size: var(--text-lg);
281
+
282
+  @include breakpoint(md) {
283
+    font-size: calc(var(--text-sm)*1.2);
284
+  }
285
+}
286
+
287
+.cd-schedule-modal__date, // not included in the the HTML but added using JavScript
288
+.cd-schedule__event a::before { 
289
+	opacity: .7;
290
+	margin-bottom: var(--space-xxxs);
291
+
292
+  @include breakpoint(md) {
293
+    font-size: calc(var(--text-xs)*1.05);
294
+    margin-bottom: var(--space-xxxxs);
295
+  }
296
+}
297
+
298
+.cd-schedule__event [data-event="event-1"],
299
+.cd-schedule-modal[data-event="event-1"] .cd-schedule-modal__header-bg {
300
+	// this is used to set a background color for the event and the modal window
301
+	background: var(--cd-color-event-1);
302
+}
303
+
304
+.cd-schedule__event [data-event="event-2"],
305
+.cd-schedule-modal[data-event="event-2"] .cd-schedule-modal__header-bg {
306
+	background: var(--cd-color-event-2);
307
+}
308
+
309
+.cd-schedule__event [data-event="event-3"],
310
+.cd-schedule-modal[data-event="event-3"] .cd-schedule-modal__header-bg {
311
+	background: var(--cd-color-event-3);
312
+}
313
+
314
+.cd-schedule__event [data-event="event-4"],
315
+.cd-schedule-modal[data-event="event-4"] .cd-schedule-modal__header-bg {
316
+	background: var(--cd-color-event-4);
317
+}
318
+
319
+.cd-schedule-modal {
320
+	position: fixed;
321
+	z-index: 3;
322
+	top: 0;
323
+	right: 0;
324
+	height: 100%;
325
+	width: 100%;
326
+	visibility: hidden;
327
+	transform: translateZ(0); // Force Hardware acceleration
328
+
329
+	transform: translateX(100%);
330
+	transition: transform .4s, visibility .4s;
331
+	transition-timing-function: cubic-bezier(.5,0,.1,1);
332
+
333
+	@include breakpoint(md) {
334
+		// reset style
335
+		right: auto;
336
+		width: auto;
337
+		height: auto;
338
+		transform: translateX(0);
339
+		will-change: transform, width, height;
340
+		transition: height .4s, width .4s, transform .4s, visibility .4s;
341
+		transition-timing-function: cubic-bezier(.5,0,.1,1);
342
+	}
343
+}
344
+
345
+.cd-schedule-modal__header {
346
+  position: relative;
347
+  height: 70px;
348
+  display: flex;
349
+  align-content: center;
350
+  width: 100%;
351
+
352
+  @include breakpoint(md) {
353
+    position: absolute;
354
+    display: block;
355
+    top: 0;
356
+    left: 0;
357
+    height: 100%;
358
+  }
359
+}
360
+
361
+.cd-schedule-modal__content {
362
+  position: relative;
363
+  z-index: 3;
364
+  display: flex;
365
+  align-items: center;
366
+  padding: var(--space-sm) var(--component-padding);
367
+
368
+  @include breakpoint(md) {
369
+    // reset style
370
+    display: block;
371
+    padding: var(--space-sm);
372
+  }
373
+}
374
+
375
+.cd-schedule-modal__body {
376
+  position: relative;
377
+  width: 100%;
378
+  height: calc(100% - 70px); // 70px is the .cd-schedule-modal__header height
379
+
380
+  @include breakpoint(md) {
381
+    height: 100%;
382
+    width: auto;
383
+  }
384
+}
385
+
386
+.cd-schedule-modal__event-info {
387
+  position: relative;
388
+  z-index: 2;
389
+  line-height: var(--body-line-height);
390
+  height: 100%;
391
+  overflow: hidden;
392
+  font-size: calc(var(--text-sm) * 1.2);
393
+
394
+  > div {
395
+    overflow: auto;
396
+    height: 100%;
397
+    padding: var(--space-md) var(--component-padding);
398
+  }
399
+
400
+  @include breakpoint(md) {
401
+    opacity: 0;
402
+    font-size: var(--text-sm);
403
+
404
+    > div {
405
+      padding: calc(var(--space-md)*1.3) calc(var(--space-lg)*1.2) calc(var(--space-md)*1.3) calc(var(--space-md)*1.3);
406
+    }
407
+  }
408
+}
409
+
410
+.cd-schedule-modal__header-bg, 
411
+.cd-schedule-modal__body-bg { // these are the morphing backgrounds - visible on desktop only
412
+  position: absolute;
413
+  top: 0;
414
+  left: 0;
415
+  height: 100%;
416
+  width: 100%;
417
+  
418
+  @include breakpoint(md) {
419
+    // Force Hardware acceleration
420
+    transform: translateZ(0);
421
+    will-change: transform;
422
+    backface-visibility: hidden;
423
+  }
424
+}
425
+
426
+.cd-schedule-modal__header-bg {
427
+  z-index: 2;
428
+  transform-origin: top center;
429
+  
430
+  @include breakpoint(md) {
431
+    transition: transform .4s;
432
+    transition-timing-function: cubic-bezier(.5,0,.1,1);
433
+  }
434
+}
435
+
436
+.cd-schedule-modal__body-bg {
437
+  z-index: 1;
438
+  background: var(--color-white);
439
+  transform-origin: top left;
440
+
441
+  @include breakpoint(md) {
442
+    opacity: 0;
443
+    transform: none;
444
+  }
445
+}
446
+
447
+.cd-schedule-modal--no-transition {
448
+  transition: none;
449
+
450
+  .cd-schedule-modal__header-bg, 
451
+  .cd-schedule-modal__body-bg {
452
+    transition: none !important;
453
+  }
454
+}
455
+
456
+.cd-schedule-modal__date {
457
+  display: none;
458
+
459
+  @include breakpoint(md) {
460
+    display: block;
461
+  }
462
+}
463
+
464
+.cd-schedule-modal__close { // close modal icon
465
+  position: absolute;
466
+  z-index: 3;
467
+  top: 0;
468
+  right: 0;
469
+  height: 70px;
470
+  width: 70px;
471
+  background: alpha(var(--color-black), .1);
472
+
473
+  &::before, &::after { // these are the two lines of the 'X' icon
474
+    content: '';
475
+    position: absolute;
476
+    top: 50%;
477
+    left: 50%;
478
+    width: 2px;
479
+    height: 22px;
480
+    background: var(--color-white);
481
+    backface-visibility: hidden;
482
+  }
483
+
484
+  &::before {
485
+    transform: translateX(-50%) translateY(-50%) rotate(45deg);
486
+  }
487
+
488
+  &::after {
489
+    transform: translateX(-50%) translateY(-50%) rotate(-45deg);
490
+  }
491
+
492
+  @include breakpoint(md) {
493
+    width: 40px;
494
+    height: 40px;
495
+    background: transparent;
496
+    opacity: 0;
497
+
498
+    &::after, &::before {
499
+      background: var(--cd-color-text);
500
+      height: 16px;
501
+    }
502
+  }
503
+}
504
+
505
+.cd-schedule-modal--open { // this class is added as soon as an event is selected
506
+	transform: translateX(0);
507
+	visibility: visible;
508
+
509
+	.cd-schedule-modal__event-info > div { // smooth scroll on iOS touch deviceS
510
+		-webkit-overflow-scrolling: touch;
511
+	}
512
+}
513
+
514
+@include breakpoint(md) {
515
+	.cd-schedule-modal--animation-completed  .cd-schedule-modal__close,
516
+	.cd-schedule-modal--content-loaded.cd-schedule-modal--animation-completed  .cd-schedule-modal__event-info {
517
+		// 	the .cd-schedule-modal--animation-completed class is added when the modal animation is completed
518
+		//	the .cd-schedule-modal--content-loaded class is added when the modal content has been loaded (using ajax)
519
+		opacity: 1;
520
+		transition: opacity .2s;
521
+	}
522
+
523
+	.cd-schedule-modal--open .cd-schedule-modal__body-bg {
524
+		opacity: 1;
525
+		transition: transform .4s;
526
+		transition-timing-function: cubic-bezier(.5,0,.1,1);
527
+	}
528
+}
529
+
530
+.cd-schedule__cover-layer { // layer between the content and the modal window
531
+	position: fixed;
532
+	z-index: 2;
533
+	top: 0;
534
+	left: 0;
535
+	height: 100%;
536
+	width: 100%;
537
+	background: alpha(var(--color-black), 0.8);
538
+	opacity: 0;
539
+	visibility: hidden;
540
+	transition: opacity .4s, visibility .4s;
541
+}
542
+
543
+.cd-schedule-modal--open + .cd-schedule__cover-layer {
544
+	opacity: 1;
545
+	visibility: visible;
546
+}
547
+
548
+//demo style
549
+.cd-main-header h1 {
550
+  color: var(--cd-color-text);
551
+  font-weight: 700;
552
+}
553
+
554
+.cd-article-link {
555
+  font-size: var(--text-sm);
556
+  transition: opacity .2s;
557
+
558
+  &:hover {
559
+    opacity: 0.8;
560
+  }
561
+}

+ 371 - 0
assets/js/main.js ファイルの表示

@@ -0,0 +1,371 @@
1
+(function() {
2
+	// Schedule Template - by CodyHouse.co
3
+	function ScheduleTemplate( element ) {
4
+		this.element = element;
5
+		this.timelineItems = this.element.getElementsByClassName('cd-schedule__timeline')[0].getElementsByTagName('li');
6
+		this.timelineStart = getScheduleTimestamp(this.timelineItems[0].textContent);
7
+		this.timelineUnitDuration = getScheduleTimestamp(this.timelineItems[1].textContent) - getScheduleTimestamp(this.timelineItems[0].textContent);
8
+		
9
+		this.topInfoElement = this.element.getElementsByClassName('cd-schedule__top-info')[0];
10
+		this.singleEvents = this.element.getElementsByClassName('cd-schedule__event');
11
+		
12
+		this.modal = this.element.getElementsByClassName('cd-schedule-modal')[0];
13
+		this.modalHeader = this.element.getElementsByClassName('cd-schedule-modal__header')[0];
14
+		this.modalHeaderBg = this.element.getElementsByClassName('cd-schedule-modal__header-bg')[0];
15
+		this.modalBody = this.element.getElementsByClassName('cd-schedule-modal__body')[0];
16
+		this.modalBodyBg = this.element.getElementsByClassName('cd-schedule-modal__body-bg')[0];
17
+		this.modalClose = this.modal.getElementsByClassName('cd-schedule-modal__close')[0];
18
+		this.modalDate = this.modal.getElementsByClassName('cd-schedule-modal__date')[0];
19
+		this.modalEventName = this.modal.getElementsByClassName('cd-schedule-modal__name')[0];
20
+		this.coverLayer = this.element.getElementsByClassName('cd-schedule__cover-layer')[0];
21
+
22
+		this.modalMaxWidth = 800;
23
+		this.modalMaxHeight = 480;
24
+
25
+		this.animating = false;
26
+		this.supportAnimation = Util.cssSupports('transition');
27
+
28
+		this.initSchedule();
29
+	};
30
+
31
+	ScheduleTemplate.prototype.initSchedule = function() {
32
+		this.scheduleReset();
33
+		this.initEvents();
34
+	};
35
+
36
+	ScheduleTemplate.prototype.scheduleReset = function() {
37
+		// according to the mq value, init the style of the template
38
+		var mq = this.mq(),
39
+			loaded = Util.hasClass(this.element, 'js-schedule-loaded'),
40
+			modalOpen = Util.hasClass(this.modal, 'cd-schedule-modal--open');
41
+		if( mq == 'desktop' && !loaded ) {
42
+			Util.addClass(this.element, 'js-schedule-loaded');
43
+			this.placeEvents();
44
+			modalOpen && this.checkEventModal(modalOpen);
45
+		} else if( mq == 'mobile' && loaded) {
46
+			//in this case you are on a mobile version (first load or resize from desktop)
47
+			Util.removeClass(this.element, 'cd-schedule--loading js-schedule-loaded');
48
+			this.resetEventsStyle();
49
+			modalOpen && this.checkEventModal();
50
+		} else if( mq == 'desktop' && modalOpen ) {
51
+			//on a mobile version with modal open - need to resize/move modal window
52
+			this.checkEventModal(modalOpen);
53
+			Util.removeClass(this.element, 'cd-schedule--loading');
54
+		} else {
55
+			Util.removeClass(this.element, 'cd-schedule--loading');
56
+		}
57
+	};
58
+
59
+	ScheduleTemplate.prototype.resetEventsStyle = function() {
60
+		// remove js style applied to the single events
61
+		for(var i = 0; i < this.singleEvents.length; i++) {
62
+			this.singleEvents[i].removeAttribute('style');
63
+		}
64
+	};
65
+
66
+	ScheduleTemplate.prototype.placeEvents = function() {
67
+		// on big devices - place events in the template according to their time/day
68
+		var self = this,
69
+			slotHeight = this.topInfoElement.offsetHeight;
70
+		for(var i = 0; i < this.singleEvents.length; i++) {
71
+			var anchor = this.singleEvents[i].getElementsByTagName('a')[0];
72
+			var start = getScheduleTimestamp(anchor.getAttribute('data-start')),
73
+				duration = getScheduleTimestamp(anchor.getAttribute('data-end')) - start;
74
+
75
+			var eventTop = slotHeight*(start - self.timelineStart)/self.timelineUnitDuration,
76
+				eventHeight = slotHeight*duration/self.timelineUnitDuration;
77
+
78
+			this.singleEvents[i].setAttribute('style', 'top: '+(eventTop-1)+'px; height: '+(eventHeight +1)+'px');
79
+		}
80
+
81
+		Util.removeClass(this.element, 'cd-schedule--loading');
82
+	};
83
+
84
+	ScheduleTemplate.prototype.initEvents = function() {
85
+		var self = this;
86
+		for(var i = 0; i < this.singleEvents.length; i++) {
87
+			// open modal when user selects an event
88
+			this.singleEvents[i].addEventListener('click', function(event){
89
+				event.preventDefault();
90
+				if(!self.animating) self.openModal(this.getElementsByTagName('a')[0]);
91
+			});
92
+		}
93
+		//close modal window
94
+		this.modalClose.addEventListener('click', function(event){
95
+			event.preventDefault();
96
+			if( !self.animating ) self.closeModal();
97
+		});
98
+		this.coverLayer.addEventListener('click', function(event){
99
+			event.preventDefault();
100
+			if( !self.animating ) self.closeModal();
101
+		});
102
+	};
103
+
104
+	ScheduleTemplate.prototype.openModal = function(target) {
105
+		var self = this;
106
+		var mq = self.mq();
107
+		this.animating = true;
108
+
109
+		//update event name and time
110
+		this.modalEventName.textContent = target.getElementsByTagName('em')[0].textContent;
111
+		this.modalDate.textContent = target.getAttribute('data-start')+' - '+target.getAttribute('data-end');
112
+		this.modal.setAttribute('data-event', target.getAttribute('data-event'));
113
+
114
+		//update event content
115
+		this.loadEventContent(target.getAttribute('data-content'));
116
+
117
+		Util.addClass(this.modal, 'cd-schedule-modal--open');
118
+		
119
+		setTimeout(function(){
120
+			//fixes a flash when an event is selected - desktop version only
121
+			Util.addClass(target.closest('li'), 'cd-schedule__event--selected');
122
+		}, 10);
123
+
124
+		if( mq == 'mobile' ) {
125
+			self.modal.addEventListener('transitionend', function cb(){
126
+				self.animating = false;
127
+				self.modal.removeEventListener('transitionend', cb);
128
+			});
129
+		} else {
130
+			var eventPosition = target.getBoundingClientRect(),
131
+				eventTop = eventPosition.top,
132
+				eventLeft = eventPosition.left,
133
+				eventHeight = target.offsetHeight,
134
+				eventWidth = target.offsetWidth;
135
+
136
+			var windowWidth = window.innerWidth,
137
+				windowHeight = window.innerHeight;
138
+
139
+			var modalWidth = ( windowWidth*.8 > self.modalMaxWidth ) ? self.modalMaxWidth : windowWidth*.8,
140
+				modalHeight = ( windowHeight*.8 > self.modalMaxHeight ) ? self.modalMaxHeight : windowHeight*.8;
141
+
142
+			var modalTranslateX = parseInt((windowWidth - modalWidth)/2 - eventLeft),
143
+				modalTranslateY = parseInt((windowHeight - modalHeight)/2 - eventTop);
144
+			
145
+			var HeaderBgScaleY = modalHeight/eventHeight,
146
+				BodyBgScaleX = (modalWidth - eventWidth);
147
+
148
+			//change modal height/width and translate it
149
+			self.modal.setAttribute('style', 'top:'+eventTop+'px;left:'+eventLeft+'px;height:'+modalHeight+'px;width:'+modalWidth+'px;transform: translateY('+modalTranslateY+'px) translateX('+modalTranslateX+'px)');
150
+			//set modalHeader width
151
+			self.modalHeader.setAttribute('style', 'width:'+eventWidth+'px');
152
+			//set modalBody left margin
153
+			self.modalBody.setAttribute('style', 'margin-left:'+eventWidth+'px');
154
+			//change modalBodyBg height/width ans scale it
155
+			self.modalBodyBg.setAttribute('style', 'height:'+eventHeight+'px; width: 1px; transform: scaleY('+HeaderBgScaleY+') scaleX('+BodyBgScaleX+')');
156
+			//change modal modalHeaderBg height/width and scale it
157
+			self.modalHeaderBg.setAttribute('style', 'height: '+eventHeight+'px; width: '+eventWidth+'px; transform: scaleY('+HeaderBgScaleY+')');
158
+			
159
+			self.modalHeaderBg.addEventListener('transitionend', function cb(){
160
+				//wait for the  end of the modalHeaderBg transformation and show the modal content
161
+				self.animating = false;
162
+				Util.addClass(self.modal, 'cd-schedule-modal--animation-completed');
163
+				self.modalHeaderBg.removeEventListener('transitionend', cb);
164
+			});
165
+		}
166
+
167
+		//if browser do not support transitions -> no need to wait for the end of it
168
+		this.animationFallback();
169
+	};
170
+
171
+	ScheduleTemplate.prototype.closeModal = function() {
172
+		var self = this;
173
+		var mq = self.mq();
174
+
175
+		var item = self.element.getElementsByClassName('cd-schedule__event--selected')[0],
176
+			target = item.getElementsByTagName('a')[0];
177
+
178
+		this.animating = true;
179
+
180
+		if( mq == 'mobile' ) {
181
+			Util.removeClass(this.modal, 'cd-schedule-modal--open');
182
+			self.modal.addEventListener('transitionend', function cb(){
183
+				Util.removeClass(self.modal, 'cd-schedule-modal--content-loaded');
184
+				Util.removeClass(item, 'cd-schedule__event--selected');
185
+				self.animating = false;
186
+				self.modal.removeEventListener('transitionend', cb);
187
+			});
188
+		} else {
189
+			var eventPosition = target.getBoundingClientRect(),
190
+				eventTop = eventPosition.top,
191
+				eventLeft = eventPosition.left,
192
+				eventHeight = target.offsetHeight,
193
+				eventWidth = target.offsetWidth;
194
+
195
+			var modalStyle = window.getComputedStyle(self.modal),
196
+				modalTop = Number(modalStyle.getPropertyValue('top').replace('px', '')),
197
+				modalLeft = Number(modalStyle.getPropertyValue('left').replace('px', ''));
198
+
199
+			var modalTranslateX = eventLeft - modalLeft,
200
+				modalTranslateY = eventTop - modalTop;
201
+
202
+			Util.removeClass(this.modal, 'cd-schedule-modal--open cd-schedule-modal--animation-completed');
203
+
204
+			//change modal width/height and translate it
205
+			self.modal.style.width = eventWidth+'px';self.modal.style.height = eventHeight+'px';self.modal.style.transform = 'translateX('+modalTranslateX+'px) translateY('+modalTranslateY+'px)';
206
+			//scale down modalBodyBg element
207
+			self.modalBodyBg.style.transform = 'scaleX(0) scaleY(1)';
208
+			//scale down modalHeaderBg element
209
+			// self.modalHeaderBg.setAttribute('style', 'transform: scaleY(1)');
210
+			self.modalHeaderBg.style.transform = 'scaleY(1)';
211
+
212
+			self.modalHeaderBg.addEventListener('transitionend', function cb(){
213
+				//wait for the  end of the modalHeaderBg transformation and reset modal style
214
+				Util.addClass(self.modal, 'cd-schedule-modal--no-transition');
215
+				setTimeout(function(){
216
+					self.modal.removeAttribute('style');
217
+					self.modalBody.removeAttribute('style');
218
+					self.modalHeader.removeAttribute('style');
219
+					self.modalHeaderBg.removeAttribute('style');
220
+					self.modalBodyBg.removeAttribute('style');
221
+				}, 10);
222
+				setTimeout(function(){
223
+					Util.removeClass(self.modal, 'cd-schedule-modal--no-transition');
224
+				}, 20);
225
+				self.animating = false;
226
+				Util.removeClass(self.modal, 'cd-schedule-modal--content-loaded');
227
+				Util.removeClass(item, 'cd-schedule__event--selected');
228
+				self.modalHeaderBg.removeEventListener('transitionend', cb);
229
+			});
230
+		}
231
+
232
+		//if browser do not support transitions -> no need to wait for the end of it
233
+		this.animationFallback();
234
+	};
235
+
236
+	ScheduleTemplate.prototype.checkEventModal = function(modalOpen) {
237
+		// this function is used on resize to reset events/modal style
238
+		this.animating = true;
239
+		var self = this;
240
+		var mq = this.mq();
241
+		if( mq == 'mobile' ) {
242
+			//reset modal style on mobile
243
+			self.modal.removeAttribute('style');
244
+			self.modalBody.removeAttribute('style');
245
+			self.modalHeader.removeAttribute('style');
246
+			self.modalHeaderBg.removeAttribute('style');
247
+			self.modalBodyBg.removeAttribute('style');
248
+			Util.removeClass(self.modal, 'cd-schedule-modal--no-transition');
249
+			self.animating = false;	
250
+		} else if( mq == 'desktop' && modalOpen) {
251
+			Util.addClass(self.modal, 'cd-schedule-modal--no-transition cd-schedule-modal--animation-completed');
252
+			var item = self.element.getElementsByClassName('cd-schedule__event--selected')[0],
253
+				target = item.getElementsByTagName('a')[0];
254
+
255
+			var eventPosition = target.getBoundingClientRect(),
256
+				eventTop = eventPosition.top,
257
+				eventLeft = eventPosition.left,
258
+				eventHeight = target.offsetHeight,
259
+				eventWidth = target.offsetWidth;
260
+
261
+			var windowWidth = window.innerWidth,
262
+				windowHeight = window.innerHeight;
263
+
264
+			var modalWidth = ( windowWidth*.8 > self.modalMaxWidth ) ? self.modalMaxWidth : windowWidth*.8,
265
+				modalHeight = ( windowHeight*.8 > self.modalMaxHeight ) ? self.modalMaxHeight : windowHeight*.8;
266
+
267
+			var HeaderBgScaleY = modalHeight/eventHeight,
268
+				BodyBgScaleX = (modalWidth - eventWidth);
269
+
270
+
271
+			setTimeout(function(){
272
+				self.modal.setAttribute('style', 'top:'+(windowHeight/2 - modalHeight/2)+'px;left:'+(windowWidth/2 - modalWidth/2)+'px;height:'+modalHeight+'px;width:'+modalWidth+'px;transform: translateY(0) translateX(0)');
273
+				//change modal modalBodyBg height/width
274
+				self.modalBodyBg.style.height = modalHeight+'px';self.modalBodyBg.style.transform = 'scaleY(1) scaleX('+BodyBgScaleX+')';self.modalBodyBg.style.width = '1px';
275
+				//set modalHeader width
276
+				self.modalHeader.setAttribute('style', 'width:'+eventWidth+'px');
277
+				//set modalBody left margin
278
+				self.modalBody.setAttribute('style', 'margin-left:'+eventWidth+'px');
279
+				//change modal modalHeaderBg height/width and scale it
280
+				self.modalHeaderBg.setAttribute('style', 'height: '+eventHeight+'px;width:'+eventWidth+'px; transform:scaleY('+HeaderBgScaleY+');');
281
+			}, 10);
282
+
283
+			setTimeout(function(){
284
+				Util.removeClass(self.modal, 'cd-schedule-modal--no-transition');
285
+				self.animating = false;	
286
+			}, 20);
287
+
288
+		}
289
+	};
290
+
291
+	ScheduleTemplate.prototype.loadEventContent = function(content) {
292
+		// load the content of an event when user selects it
293
+		var self = this;
294
+
295
+		httpRequest = new XMLHttpRequest();
296
+		httpRequest.onreadystatechange = function() {
297
+			if (httpRequest.readyState === XMLHttpRequest.DONE) {
298
+	      if (httpRequest.status === 200) {
299
+	      	self.modal.getElementsByClassName('cd-schedule-modal__event-info')[0].innerHTML = self.getEventContent(httpRequest.responseText); 
300
+	      	Util.addClass(self.modal, 'cd-schedule-modal--content-loaded');
301
+	      }
302
+	    }
303
+		};
304
+		httpRequest.open('GET', content+'.html');
305
+    httpRequest.send();
306
+	};
307
+
308
+	ScheduleTemplate.prototype.getEventContent = function(string) {
309
+		// reset the loaded event content so that it can be inserted in the modal
310
+		var div = document.createElement('div');
311
+		div.innerHTML = string.trim();
312
+		return div.getElementsByClassName('cd-schedule-modal__event-info')[0].innerHTML;
313
+	};
314
+
315
+	ScheduleTemplate.prototype.animationFallback = function() {
316
+		if( !this.supportAnimation ) { // fallback for browsers not supporting transitions
317
+			var event = new CustomEvent('transitionend');
318
+			self.modal.dispatchEvent(event);
319
+			self.modalHeaderBg.dispatchEvent(event);
320
+		}
321
+	};
322
+
323
+	ScheduleTemplate.prototype.mq = function(){
324
+		//get MQ value ('desktop' or 'mobile') 
325
+		var self = this;
326
+		return window.getComputedStyle(this.element, '::before').getPropertyValue('content').replace(/'|"/g, "");
327
+	};
328
+
329
+	function getScheduleTimestamp(time) {
330
+		//accepts hh:mm format - convert hh:mm to timestamp
331
+		time = time.replace(/ /g,'');
332
+		var timeArray = time.split(':');
333
+		var timeStamp = parseInt(timeArray[0])*60 + parseInt(timeArray[1]);
334
+		return timeStamp;
335
+	};
336
+
337
+	var scheduleTemplate = document.getElementsByClassName('js-cd-schedule'),	
338
+		scheduleTemplateArray = [],
339
+		resizing = false;
340
+	if( scheduleTemplate.length > 0 ) { // init ScheduleTemplate objects
341
+		for( var i = 0; i < scheduleTemplate.length; i++) {
342
+			(function(i){
343
+				scheduleTemplateArray.push(new ScheduleTemplate(scheduleTemplate[i]));
344
+			})(i);
345
+		}
346
+
347
+		window.addEventListener('resize', function(event) { 
348
+			// on resize - update events position and modal position (if open)
349
+			if( !resizing ) {
350
+				resizing = true;
351
+				(!window.requestAnimationFrame) ? setTimeout(checkResize, 250) : window.requestAnimationFrame(checkResize);
352
+			}
353
+		});
354
+
355
+		window.addEventListener('keyup', function(event){
356
+			// close event modal when pressing escape key
357
+			if( event.keyCode && event.keyCode == 27 || event.key && event.key.toLowerCase() == 'escape' ) {
358
+				for(var i = 0; i < scheduleTemplateArray.length; i++) {
359
+					scheduleTemplateArray[i].closeModal();
360
+				}
361
+			}
362
+		});
363
+
364
+		function checkResize(){
365
+			for(var i = 0; i < scheduleTemplateArray.length; i++) {
366
+				scheduleTemplateArray[i].scheduleReset();
367
+			}
368
+			resizing = false;
369
+		};
370
+	}
371
+}());

+ 174 - 0
assets/js/util.js ファイルの表示

@@ -0,0 +1,174 @@
1
+// Utility function
2
+function Util () {};
3
+
4
+/* 
5
+	class manipulation functions
6
+*/
7
+Util.hasClass = function(el, className) {
8
+	if (el.classList) return el.classList.contains(className);
9
+	else return !!el.className.match(new RegExp('(\\s|^)' + className + '(\\s|$)'));
10
+};
11
+
12
+Util.addClass = function(el, className) {
13
+	var classList = className.split(' ');
14
+ 	if (el.classList) el.classList.add(classList[0]);
15
+ 	else if (!Util.hasClass(el, classList[0])) el.className += " " + classList[0];
16
+ 	if (classList.length > 1) Util.addClass(el, classList.slice(1).join(' '));
17
+};
18
+
19
+Util.removeClass = function(el, className) {
20
+	var classList = className.split(' ');
21
+	if (el.classList) el.classList.remove(classList[0]);	
22
+	else if(Util.hasClass(el, classList[0])) {
23
+		var reg = new RegExp('(\\s|^)' + classList[0] + '(\\s|$)');
24
+		el.className=el.className.replace(reg, ' ');
25
+	}
26
+	if (classList.length > 1) Util.removeClass(el, classList.slice(1).join(' '));
27
+};
28
+
29
+Util.toggleClass = function(el, className, bool) {
30
+	if(bool) Util.addClass(el, className);
31
+	else Util.removeClass(el, className);
32
+};
33
+
34
+Util.setAttributes = function(el, attrs) {
35
+  for(var key in attrs) {
36
+    el.setAttribute(key, attrs[key]);
37
+  }
38
+};
39
+
40
+/* 
41
+  DOM manipulation
42
+*/
43
+Util.getChildrenByClassName = function(el, className) {
44
+  var children = el.children,
45
+    childrenByClass = [];
46
+  for (var i = 0; i < el.children.length; i++) {
47
+    if (Util.hasClass(el.children[i], className)) childrenByClass.push(el.children[i]);
48
+  }
49
+  return childrenByClass;
50
+};
51
+
52
+/* 
53
+	Animate height of an element
54
+*/
55
+Util.setHeight = function(start, to, element, duration, cb) {
56
+	var change = to - start,
57
+	    currentTime = null;
58
+
59
+  var animateHeight = function(timestamp){  
60
+    if (!currentTime) currentTime = timestamp;         
61
+    var progress = timestamp - currentTime;
62
+    var val = parseInt((progress/duration)*change + start);
63
+    element.setAttribute("style", "height:"+val+"px;");
64
+    if(progress < duration) {
65
+        window.requestAnimationFrame(animateHeight);
66
+    } else {
67
+    	cb();
68
+    }
69
+  };
70
+  
71
+  //set the height of the element before starting animation -> fix bug on Safari
72
+  element.setAttribute("style", "height:"+start+"px;");
73
+  window.requestAnimationFrame(animateHeight);
74
+};
75
+
76
+/* 
77
+	Smooth Scroll
78
+*/
79
+
80
+Util.scrollTo = function(final, duration, cb) {
81
+  var start = window.scrollY || document.documentElement.scrollTop,
82
+      currentTime = null;
83
+      
84
+  var animateScroll = function(timestamp){
85
+  	if (!currentTime) currentTime = timestamp;        
86
+    var progress = timestamp - currentTime;
87
+    if(progress > duration) progress = duration;
88
+    var val = Math.easeInOutQuad(progress, start, final-start, duration);
89
+    window.scrollTo(0, val);
90
+    if(progress < duration) {
91
+        window.requestAnimationFrame(animateScroll);
92
+    } else {
93
+      cb && cb();
94
+    }
95
+  };
96
+
97
+  window.requestAnimationFrame(animateScroll);
98
+};
99
+
100
+/* 
101
+  Focus utility classes
102
+*/
103
+
104
+//Move focus to an element
105
+Util.moveFocus = function (element) {
106
+  if( !element ) element = document.getElementsByTagName("body")[0];
107
+  element.focus();
108
+  if (document.activeElement !== element) {
109
+    element.setAttribute('tabindex','-1');
110
+    element.focus();
111
+  }
112
+};
113
+
114
+/* 
115
+  Misc
116
+*/
117
+
118
+Util.getIndexInArray = function(array, el) {
119
+  return Array.prototype.indexOf.call(array, el);
120
+};
121
+
122
+Util.cssSupports = function(property, value) {
123
+  if('CSS' in window) {
124
+    return CSS.supports(property, value);
125
+  } else {
126
+    var jsProperty = property.replace(/-([a-z])/g, function (g) { return g[1].toUpperCase();});
127
+    return jsProperty in document.body.style;
128
+  }
129
+};
130
+
131
+/* 
132
+	Polyfills
133
+*/
134
+//Closest() method
135
+if (!Element.prototype.matches) {
136
+	Element.prototype.matches = Element.prototype.msMatchesSelector || Element.prototype.webkitMatchesSelector;
137
+}
138
+
139
+if (!Element.prototype.closest) {
140
+	Element.prototype.closest = function(s) {
141
+		var el = this;
142
+		if (!document.documentElement.contains(el)) return null;
143
+		do {
144
+			if (el.matches(s)) return el;
145
+			el = el.parentElement || el.parentNode;
146
+		} while (el !== null && el.nodeType === 1); 
147
+		return null;
148
+	};
149
+}
150
+
151
+//Custom Event() constructor
152
+if ( typeof window.CustomEvent !== "function" ) {
153
+
154
+  function CustomEvent ( event, params ) {
155
+    params = params || { bubbles: false, cancelable: false, detail: undefined };
156
+    var evt = document.createEvent( 'CustomEvent' );
157
+    evt.initCustomEvent( event, params.bubbles, params.cancelable, params.detail );
158
+    return evt;
159
+   }
160
+
161
+  CustomEvent.prototype = window.Event.prototype;
162
+
163
+  window.CustomEvent = CustomEvent;
164
+}
165
+
166
+/* 
167
+	Animation curves
168
+*/
169
+Math.easeInOutQuad = function (t, b, c, d) {
170
+	t /= d/2;
171
+	if (t < 1) return c/2*t*t + b;
172
+	t--;
173
+	return -c/2 * (t*(t-2) - 1) + b;
174
+};

+ 3 - 0
event-abs-circuit.html ファイルの表示

@@ -0,0 +1,3 @@
1
+<div class="cd-schedule-modal__event-info">
2
+	<div>Abs Circuit. Lorem ipsum dolor sit amet, consectetur adipisicing elit. Velit, unde, nulla. Vel unde deleniti, distinctio inventore quis molestiae perferendis, eum quo harum dolorum reiciendis sunt dicta maiores similique! Officiis repellat iure odio debitis enim eius commodi quae deserunt quam assumenda, ab asperiores reiciendis minima maxime odit laborum, libero veniam non? </div>
3
+</div>

+ 3 - 0
event-restorative-yoga.html ファイルの表示

@@ -0,0 +1,3 @@
1
+<div class="cd-schedule-modal__event-info">
2
+	<div>Restorative Yoga. Lorem ipsum dolor sit amet, consectetur adipisicing elit. Velit, unde, nulla. Vel unde deleniti, distinctio inventore quis molestiae perferendis, eum quo harum dolorum reiciendis sunt dicta maiores similique! Officiis repellat iure odio debitis enim eius commodi quae deserunt quam assumenda, ab asperiores reiciendis minima maxime odit laborum, libero veniam non?</div>
3
+</div>

+ 3 - 0
event-rowing-workout.html ファイルの表示

@@ -0,0 +1,3 @@
1
+<div class="cd-schedule-modal__event-info">
2
+	<div>Rowing Workout. Lorem ipsum dolor sit amet, consectetur adipisicing elit. Velit, unde, nulla. Vel unde deleniti, distinctio inventore quis molestiae perferendis, eum quo harum dolorum reiciendis sunt dicta maiores similique! Officiis repellat iure odio debitis enim eius commodi quae deserunt quam assumenda, ab asperiores reiciendis minima maxime odit laborum, libero veniam non?</div>
3
+</div>

+ 3 - 0
event-yoga-1.html ファイルの表示

@@ -0,0 +1,3 @@
1
+<div class="cd-schedule-modal__event-info">
2
+	<div>Yoga 1. Lorem ipsum dolor sit amet, consectetur adipisicing elit. Velit, unde, nulla. Vel unde deleniti, distinctio inventore quis molestiae perferendis, eum quo harum dolorum reiciendis sunt dicta maiores similique! Officiis repellat iure odio debitis enim eius commodi quae deserunt quam assumenda, ab asperiores reiciendis minima maxime odit laborum, libero veniam non?</div>
3
+</div>

+ 97 - 0
index.html ファイルの表示

@@ -0,0 +1,97 @@
1
+<!doctype html>
2
+<html lang="en">
3
+<head>
4
+  <meta charset="UTF-8">
5
+  <meta name="viewport" content="width=device-width, initial-scale=1">
6
+  <script>document.getElementsByTagName("html")[0].className += " js";</script>
7
+  <link rel="stylesheet" href="assets/css/style.css">
8
+  <title>Schedule Template | CodyHouse</title>
9
+</head>
10
+<body>
11
+  <header class="cd-main-header text-center flex flex-column flex-center">
12
+
13
+    <h1 class="text-xl">Schedule Template</h1>
14
+  </header>
15
+
16
+  <div class="cd-schedule cd-schedule--loading margin-top-lg margin-bottom-lg js-cd-schedule">
17
+    <div class="cd-schedule__timeline">
18
+      <ul>
19
+        <li><span>19:30</span></li>
20
+        <li><span>20:00</span></li>
21
+        <li><span>20:30</span></li>
22
+        <li><span>21:00</span></li>
23
+	<li><span>21:30</span></li>
24
+        <li><span>22:00</span></li>
25
+        <li><span>22:30</span></li>
26
+	<li><span>23:00</span></li>
27
+	<li><span>23:30</span></li>
28
+        <li><span>00:00</span></li>
29
+        <li><span>00:30</span></li>
30
+      </ul>
31
+    </div> <!-- .cd-schedule__timeline -->
32
+  
33
+    <div class="cd-schedule__events">
34
+      <ul>
35
+        <li class="cd-schedule__group">
36
+          <div class="cd-schedule__top-info"><span>Monday</span></div>
37
+  
38
+          <ul>
39
+
40
+            <li class="cd-schedule__event">
41
+              <a data-start="21:00" data-end="22:15"  data-content="event-yoga-1" data-event="event-1" href="#0">
42
+                <em class="cd-schedule__name">Matt</em>
43
+		A genius
44
+              </a>
45
+            </li>
46
+          </ul>
47
+        </li>
48
+  
49
+        <li class="cd-schedule__group">
50
+          <div class="cd-schedule__top-info"><span>Tuesday</span></div>
51
+  
52
+          <ul>
53
+	    <li class="cd-schedule__event">
54
+              <a data-start="21:00" data-end="22:15"  data-content="event-yoga-1" data-event="event-2" href="#0">
55
+                <em class="cd-schedule__name">Matt</em>
56
+              </a>
57
+            </li>
58
+          </ul>
59
+        </li>
60
+  
61
+        <li class="cd-schedule__group">
62
+          <div class="cd-schedule__top-info"><span>Wednesday</span></div>
63
+  
64
+          <ul>
65
+           <li class="cd-schedule__event">
66
+              <a data-start="21:00" data-end="22:15"  data-content="event-yoga-1" data-event="event-3" href="#0">
67
+                <em class="cd-schedule__name">Matt</em>
68
+              </a>
69
+            </li>
70
+      </ul>
71
+    </div>
72
+  
73
+    <div class="cd-schedule-modal">
74
+      <header class="cd-schedule-modal__header">
75
+        <div class="cd-schedule-modal__content">
76
+          <span class="cd-schedule-modal__date"></span>
77
+          <h3 class="cd-schedule-modal__name"></h3>
78
+        </div>
79
+  
80
+        <div class="cd-schedule-modal__header-bg"></div>
81
+      </header>
82
+  
83
+      <div class="cd-schedule-modal__body">
84
+        <div class="cd-schedule-modal__event-info"></div>
85
+        <div class="cd-schedule-modal__body-bg"></div>
86
+      </div>
87
+  
88
+      <a href="#0" class="cd-schedule-modal__close text-replace">Close</a>
89
+    </div>
90
+  
91
+    <div class="cd-schedule__cover-layer"></div>
92
+  </div> <!-- .cd-schedule -->
93
+
94
+  <script src="assets/js/util.js"></script> <!-- util functions included in the CodyHouse framework -->
95
+  <script src="assets/js/main.js"></script>
96
+</body>
97
+</html>