', { class: ID + '-highlights ' + ID + '-content' });
63 |
64 | this.$backdrop = $('
', { class: ID + '-backdrop' })
65 | .append(this.$highlights);
66 |
67 | this.$container = $('
', { class: ID + '-container' })
68 | .insertAfter(this.$el)
69 | .append(this.$backdrop, this.$el) // moves $el into $container
70 | .on('scroll', this.blockContainerScroll.bind(this));
71 |
72 | this.browser = this.detectBrowser();
73 | switch (this.browser) {
74 | case 'firefox':
75 | this.fixFirefox();
76 | break;
77 | case 'ios':
78 | this.fixIOS();
79 | break;
80 | }
81 |
82 | // plugin function checks this for success
83 | this.isGenerated = true;
84 |
85 | // trigger input event to highlight any existing input
86 | this.handleInput();
87 | },
88 |
89 | // browser sniffing sucks, but there are browser-specific quirks to handle
90 | // that are not a matter of feature detection
91 | detectBrowser: function() {
92 | let ua = window.navigator.userAgent.toLowerCase();
93 | if (ua.indexOf('firefox') !== -1) {
94 | return 'firefox';
95 | } else if (!!ua.match(/msie|trident\/7|edge/)) {
96 | return 'ie';
97 | } else if (!!ua.match(/ipad|iphone|ipod/) && ua.indexOf('windows phone') === -1) {
98 | // Windows Phone flags itself as "like iPhone", thus the extra check
99 | return 'ios';
100 | } else {
101 | return 'other';
102 | }
103 | },
104 |
105 | // Firefox doesn't show text that scrolls into the padding of a textarea, so
106 | // rearrange a couple box models to make highlights behave the same way
107 | fixFirefox: function() {
108 | // take padding and border pixels from highlights div
109 | let padding = this.$highlights.css([
110 | 'padding-top', 'padding-right', 'padding-bottom', 'padding-left'
111 | ]);
112 | let border = this.$highlights.css([
113 | 'border-top-width', 'border-right-width', 'border-bottom-width', 'border-left-width'
114 | ]);
115 | this.$highlights.css({
116 | 'padding': '0',
117 | 'border-width': '0'
118 | });
119 |
120 | this.$backdrop
121 | .css({
122 | // give padding pixels to backdrop div
123 | 'margin-top': '+=' + padding['padding-top'],
124 | 'margin-right': '+=' + padding['padding-right'],
125 | 'margin-bottom': '+=' + padding['padding-bottom'],
126 | 'margin-left': '+=' + padding['padding-left'],
127 | })
128 | .css({
129 | // give border pixels to backdrop div
130 | 'margin-top': '+=' + border['border-top-width'],
131 | 'margin-right': '+=' + border['border-right-width'],
132 | 'margin-bottom': '+=' + border['border-bottom-width'],
133 | 'margin-left': '+=' + border['border-left-width'],
134 | });
135 | },
136 |
137 | // iOS adds 3px of (unremovable) padding to the left and right of a textarea,
138 | // so adjust highlights div to match
139 | fixIOS: function() {
140 | this.$highlights.css({
141 | 'padding-left': '+=3px',
142 | 'padding-right': '+=3px'
143 | });
144 | },
145 |
146 | handleInput: function() {
147 | let input = this.$el.val();
148 | let ranges = this.getRanges(input, this.highlight);
149 | let unstaggeredRanges = this.removeStaggeredRanges(ranges);
150 | let boundaries = this.getBoundaries(unstaggeredRanges);
151 | this.renderMarks(boundaries);
152 | },
153 |
154 | getRanges: function(input, highlight) {
155 | let type = this.getType(highlight);
156 | switch (type) {
157 | case 'array':
158 | return this.getArrayRanges(input, highlight);
159 | case 'function':
160 | return this.getFunctionRanges(input, highlight);
161 | case 'regexp':
162 | return this.getRegExpRanges(input, highlight);
163 | case 'string':
164 | return this.getStringRanges(input, highlight);
165 | case 'range':
166 | return this.getRangeRanges(input, highlight);
167 | case 'custom':
168 | return this.getCustomRanges(input, highlight);
169 | default:
170 | if (!highlight) {
171 | // do nothing for falsey values
172 | return [];
173 | } else {
174 | console.error('unrecognized highlight type');
175 | }
176 | }
177 | },
178 |
179 | getArrayRanges: function(input, arr) {
180 | let ranges = arr.map(this.getRanges.bind(this, input));
181 | return Array.prototype.concat.apply([], ranges);
182 | },
183 |
184 | getFunctionRanges: function(input, func) {
185 | return this.getRanges(input, func(input));
186 | },
187 |
188 | getRegExpRanges: function(input, regex) {
189 | let ranges = [];
190 | let match;
191 | while (match = regex.exec(input), match !== null) {
192 | ranges.push([match.index, match.index + match[0].length]);
193 | if (!regex.global) {
194 | // non-global regexes do not increase lastIndex, causing an infinite loop,
195 | // but we can just break manually after the first match
196 | break;
197 | }
198 | }
199 | return ranges;
200 | },
201 |
202 | getStringRanges: function(input, str) {
203 | let ranges = [];
204 | let inputLower = input.toLowerCase();
205 | let strLower = str.toLowerCase();
206 | let index = 0;
207 | while (index = inputLower.indexOf(strLower, index), index !== -1) {
208 | ranges.push([index, index + strLower.length]);
209 | index += strLower.length;
210 | }
211 | return ranges;
212 | },
213 |
214 | getRangeRanges: function(input, range) {
215 | return [range];
216 | },
217 |
218 | getCustomRanges: function(input, custom) {
219 | let ranges = this.getRanges(input, custom.highlight);
220 | if (custom.className) {
221 | ranges.forEach(function(range) {
222 | // persist class name as a property of the array
223 | if (range.className) {
224 | range.className = custom.className + ' ' + range.className;
225 | } else {
226 | range.className = custom.className;
227 | }
228 | });
229 | }
230 | return ranges;
231 | },
232 |
233 | // prevent staggered overlaps (clean nesting is fine)
234 | removeStaggeredRanges: function(ranges) {
235 | let unstaggeredRanges = [];
236 | ranges.forEach(function(range) {
237 | let isStaggered = unstaggeredRanges.some(function(unstaggeredRange) {
238 | let isStartInside = range[0] > unstaggeredRange[0] && range[0] < unstaggeredRange[1];
239 | let isStopInside = range[1] > unstaggeredRange[0] && range[1] < unstaggeredRange[1];
240 | return isStartInside !== isStopInside; // xor
241 | });
242 | if (!isStaggered) {
243 | unstaggeredRanges.push(range);
244 | }
245 | });
246 | return unstaggeredRanges;
247 | },
248 |
249 | getBoundaries: function(ranges) {
250 | let boundaries = [];
251 | ranges.forEach(function(range) {
252 | boundaries.push({
253 | type: 'start',
254 | index: range[0],
255 | className: range.className
256 | });
257 | boundaries.push({
258 | type: 'stop',
259 | index: range[1]
260 | });
261 | });
262 |
263 | this.sortBoundaries(boundaries);
264 | return boundaries;
265 | },
266 |
267 | sortBoundaries: function(boundaries) {
268 | // backwards sort (since marks are inserted right to left)
269 | boundaries.sort(function(a, b) {
270 | if (a.index !== b.index) {
271 | return b.index - a.index;
272 | } else if (a.type === 'stop' && b.type === 'start') {
273 | return 1;
274 | } else if (a.type === 'start' && b.type === 'stop') {
275 | return -1;
276 | } else {
277 | return 0;
278 | }
279 | });
280 | },
281 |
282 | renderMarks: function(boundaries) {
283 | let input = this.$el.val();
284 | boundaries.forEach(function(boundary, index) {
285 | let markup;
286 | if (boundary.type === 'start') {
287 | markup = '{{hwt-mark-start|' + index + '}}';
288 | } else {
289 | markup = '{{hwt-mark-stop}}';
290 | }
291 | input = input.slice(0, boundary.index) + markup + input.slice(boundary.index);
292 | });
293 |
294 | // this keeps scrolling aligned when input ends with a newline
295 | input = input.replace(/\n(\{\{hwt-mark-stop\}\})?$/, '\n\n$1');
296 |
297 | // encode HTML entities
298 | input = input.replace(//g, '>');
299 |
300 | if (this.browser === 'ie') {
301 | // IE/Edge wraps whitespace differently in a div vs textarea, this fixes it
302 | input = input.replace(/ /g, ' ');
303 | }
304 |
305 | // replace start tokens with opening tags with class name
306 | input = input.replace(/\{\{hwt-mark-start\|(\d+)\}\}/g, function(match, submatch) {
307 | var className = boundaries[+submatch].className;
308 | if (className) {
309 | return '';
310 | } else {
311 | return '';
312 | }
313 | });
314 |
315 | // replace stop tokens with closing tags
316 | input = input.replace(/\{\{hwt-mark-stop\}\}/g, '');
317 |
318 | this.$highlights.html(input);
319 | },
320 |
321 | handleScroll: function() {
322 | let scrollTop = this.$el.scrollTop();
323 | this.$backdrop.scrollTop(scrollTop);
324 |
325 | // Chrome and Safari won't break long strings of spaces, which can cause
326 | // horizontal scrolling, this compensates by shifting highlights by the
327 | // horizontally scrolled amount to keep things aligned
328 | let scrollLeft = this.$el.scrollLeft();
329 | this.$backdrop.css('transform', (scrollLeft > 0) ? 'translateX(' + -scrollLeft + 'px)' : '');
330 | },
331 |
332 | // in Chrome, page up/down in the textarea will shift stuff within the
333 | // container (despite the CSS), this immediately reverts the shift
334 | blockContainerScroll: function() {
335 | this.$container.scrollLeft(0);
336 | },
337 |
338 | destroy: function() {
339 | this.$backdrop.remove();
340 | this.$el
341 | .unwrap()
342 | .removeClass(ID + '-text ' + ID + '-input')
343 | .off(ID)
344 | .removeData(ID);
345 | },
346 | };
347 |
348 | // register the jQuery plugin
349 | $.fn.highlightWithinTextarea = function(options) {
350 | return this.each(function() {
351 | let $this = $(this);
352 | let plugin = $this.data(ID);
353 |
354 | if (typeof options === 'string') {
355 | if (plugin) {
356 | switch (options) {
357 | case 'update':
358 | plugin.handleInput();
359 | break;
360 | case 'destroy':
361 | plugin.destroy();
362 | break;
363 | default:
364 | console.error('unrecognized method string');
365 | }
366 | } else {
367 | console.error('plugin must be instantiated first');
368 | }
369 | } else {
370 | if (plugin) {
371 | plugin.destroy();
372 | }
373 | plugin = new HighlightWithinTextarea($this, options);
374 | if (plugin.isGenerated) {
375 | $this.data(ID, plugin);
376 | }
377 | }
378 | });
379 | };
380 | })(jQuery);
--------------------------------------------------------------------------------
/script.js:
--------------------------------------------------------------------------------
1 | const sTwo = new Howl({
2 | src: [
3 | "./sounds/do.wav"
4 | ]
5 | });
6 |
7 | const sFour = new Howl({
8 | src: [
9 | "./sounds/re.wav"
10 | ]
11 | });
12 |
13 | const sSix = new Howl({
14 | src: [
15 | "./sounds/mi.wav"
16 | ]
17 | });
18 |
19 | const sTwelve = new Howl({
20 | src: [
21 | "./sounds/sol.wav"
22 | ]
23 | });
24 |
25 | const sElse = new Howl({
26 | src: [
27 | "./sounds/la.wav"
28 | ]
29 | });
30 |
31 | const soundsArray = [sTwo, sFour, sSix, sTwelve, sElse];
32 |
33 | const markToSound = {
34 | "two": 0,
35 | "four": 1,
36 | "six": 2,
37 | "twelve": 3,
38 | "else": 4
39 | };
40 |
41 | let textBox = $(".text");
42 | let playButton = $('.play-button');
43 | let viewButton = $('.view-button');
44 |
45 | var viewHidden = false;
46 |
47 |
48 | // Wrapper to setTimeout that allows the easy removable of all timeouts set
49 | // isolated layer wrapper (for the local variables)
50 | (function (_W) {
51 | let cache = [], // will store all timeouts IDs
52 | _set = _W.setTimeout, // save original reference
53 | _clear = _W.clearTimeout; // save original reference
54 |
55 | // Wrap original setTimeout with a function
56 | _W.setTimeout = function (CB, duration) {
57 | // also, wrap the callback, so the cache reference will be removed
58 | // when the timeout has reached (fired the callback)
59 | const id = _set(function () {
60 | CB();
61 | removeCacheItem(id);
62 | }, duration || 0);
63 |
64 | cache.push(id); // store reference in the cache array
65 |
66 | // id must be returned to the user could save it and clear it if they choose to
67 | return id;
68 | };
69 |
70 | // Wrap original clearTimeout with a function
71 | _W.clearTimeout = function (id) {
72 | _clear(id);
73 | removeCacheItem(id);
74 | };
75 |
76 | // Add a custom function named "clearTimeouts" to the "window" object
77 | _W.clearTimeouts = function () {
78 | cache.forEach(n => _clear(n));
79 | cache.length = [];
80 | };
81 |
82 | // removes a specific id from the cache array
83 | function removeCacheItem(id) {
84 | const idx = cache.indexOf(id);
85 |
86 | if (idx > -1) cache = cache.filter(n => n !== id);
87 | }
88 | })(window);
89 |
90 | function arrayRemove(arr, value) {
91 | return arr.filter(function (ele) {
92 | return ele !== value;
93 | });
94 | }
95 |
96 |
97 | function whatShouldWeHighlight(input) {
98 | // Only god himself knows what happens here
99 | const inputArray = input.match(/(\p{L}?[^\n.!?]+[\n.!?]+)|(.+[^.!?]?$)/gu);
100 | const highlightResult = [];
101 |
102 | let last_index = 0;
103 |
104 | // Iterates through the sentences matched by the regex
105 | if (inputArray) {
106 | let sentence;
107 | for (sentence of inputArray) {
108 | let color;
109 | if (sentence.replace(" ", "").replace("\n", "").trim() !== "") {
110 | // Replacement for regex \b, for better non-ascii chars support.
111 | const splitSentence = arrayRemove(sentence.split(" "), "");
112 | const splitSentenceLen = splitSentence.length;
113 |
114 | // set the color based on length
115 | if (splitSentenceLen <= 2) {
116 | color = "two";
117 | } else if (splitSentenceLen <= 4) {
118 | color = "four";
119 | } else if (splitSentenceLen <= 6) {
120 | color = "six";
121 | } else if (splitSentenceLen <= 12) {
122 | color = "twelve";
123 | } else {
124 | color = "else";
125 | }
126 | // Gets the starting and ending indexes of the sentence to avoid highlighting
127 | // similar sentences or words multiple times.
128 | const start_index = input.indexOf(sentence.trim(), last_index);
129 | last_index = start_index + (sentence.trim().length - 1);
130 |
131 | // Adds the sentence and its respective highlight color to the wrapper
132 | highlightResult.push({
133 | highlight: [start_index, last_index + 1],
134 | className: color
135 | });
136 | }
137 | }
138 | }
139 | return highlightResult;
140 | }
141 |
142 | // Highlights the text. Plugin @ https://github.com/lonekorean/highlight-within-textarea/
143 | textBox.highlightWithinTextarea({
144 | highlight: whatShouldWeHighlight
145 | });
146 |
147 |
148 | function playAudio(soundType, highlight, i) {
149 | const input = textBox.val();
150 | const arrayHighlight = whatShouldWeHighlight(input);
151 |
152 | window.setTimeout(function () {
153 | textBox.highlightWithinTextarea({highlight: [highlight]});
154 |
155 | // Scrolls the textarea by setting the cursor to the current highlighted word and enabling and focusing
156 | // the textarea. Hacky.
157 | textBox.attr("disabled", false);
158 | textBox.prop('selectionStart', highlight.highlight[1]);
159 | textBox.prop('selectionEnd', highlight.highlight[1]);
160 |
161 | textBox.blur();
162 | textBox.focus();
163 | textBox.blur();
164 |
165 | textBox.attr("disabled", true);
166 |
167 | playButton.removeClass();
168 | playButton.addClass('play-button ' + highlight.className);
169 |
170 | soundsArray[soundType].play();
171 | if (i >= arrayHighlight.length - 1) { // resets the view if all the sounds have played.
172 | window.setTimeout(function () { // waits before resetting for a better visual experience.
173 | playButton.removeClass();
174 | playButton.addClass('play-button twelve');
175 | playButton.html('');
176 |
177 | textBox.attr("disabled", false);
178 | textBox.highlightWithinTextarea({highlight: whatShouldWeHighlight});
179 | textBox.focus();
180 | if (viewHidden) {
181 | $('.hwt-backdrop').addClass('hidden');
182 | }
183 | }, 800);
184 | }
185 | }, 500 * i); // set the timeout time the current sentence iteration, so it doesn't fire all at once.
186 |
187 | }
188 |
189 | playButton.click(function () {
190 | // Reset the view if the stop button is pressed
191 | if (playButton.html() === '') {
192 | playButton.removeClass();
193 | playButton.addClass('play-button twelve');
194 | playButton.html('');
195 |
196 | window.clearTimeouts(); // Stop and clear the sound timeouts
197 | textBox.attr("disabled", false);
198 | textBox.highlightWithinTextarea({highlight: whatShouldWeHighlight});
199 | textBox.focus();
200 | return;
201 | }
202 |
203 | const input = textBox.val();
204 | const arrayHighlight = whatShouldWeHighlight(input);
205 | let soundType = 0;
206 | let i = 0;
207 |
208 | if (arrayHighlight.length >= 1) {
209 | playButton.html('');
210 | textBox.attr("disabled", true);
211 | } else {
212 | return;
213 | }
214 |
215 | let mark;
216 | for (mark of arrayHighlight) {
217 | const note = mark.className;
218 | // set the appropriate sound based on the current highlight.
219 | soundType = markToSound[note];
220 |
221 | playAudio(soundType, mark, i);
222 | i++;
223 | }
224 | });
225 |
226 | viewButton.click(function () {
227 | if (viewHidden) {
228 | viewButton.html('')
229 | $('.hwt-backdrop').removeClass('hidden');
230 | viewHidden = false;
231 | } else {
232 | viewButton.html('')
233 | $('.hwt-backdrop').addClass('hidden');
234 | viewHidden = true;
235 | }
236 | })
237 |
--------------------------------------------------------------------------------
/sounds/do.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/eitchtee/write-music/bdf5d832b86a4d41647a99c9ec46dc28d34c8f9f/sounds/do.wav
--------------------------------------------------------------------------------
/sounds/la.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/eitchtee/write-music/bdf5d832b86a4d41647a99c9ec46dc28d34c8f9f/sounds/la.wav
--------------------------------------------------------------------------------
/sounds/mi.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/eitchtee/write-music/bdf5d832b86a4d41647a99c9ec46dc28d34c8f9f/sounds/mi.wav
--------------------------------------------------------------------------------
/sounds/re.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/eitchtee/write-music/bdf5d832b86a4d41647a99c9ec46dc28d34c8f9f/sounds/re.wav
--------------------------------------------------------------------------------
/sounds/sol.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/eitchtee/write-music/bdf5d832b86a4d41647a99c9ec46dc28d34c8f9f/sounds/sol.wav
--------------------------------------------------------------------------------
/style.css:
--------------------------------------------------------------------------------
1 | :root {
2 | --color-green: rgba(102, 204, 102, 0.8);
3 | --color-yellow: rgba(253, 253, 131, 0.6);
4 | --color-red: rgba(138, 40, 40, 0.8);
5 | --color-purple: rgba(204, 153, 204, 0.8);
6 | --color-blue: rgba(153, 204, 204, 0.8);
7 |
8 | font-size: 60%;
9 | }
10 |
11 | body {
12 | margin: 0;
13 | height: 100%;
14 | width: 100%;
15 | background-color: #121212;
16 | }
17 |
18 | .hidden {
19 | display: none;
20 | visibility: hidden;
21 | }
22 |
23 | .hwt-container {
24 | background-color: #121212;
25 | display: grid;
26 | top: 1vh;
27 | bottom: 5vh;
28 | left: 2vh;
29 | right: 1vh;
30 | position: absolute;
31 | }
32 |
33 | .hwt-content {
34 | line-height: 4vh;
35 | font-size: 3vh;
36 | resize: none;
37 | overflow: scroll;
38 | padding: 0.6vh;
39 | border: 0;
40 | color: #fff;
41 | font-family: monospace;
42 | height: auto !important;
43 | }
44 |
45 | .hwt-input:focus {
46 | outline-color: #f8f9fa;
47 | }
48 |
49 | .hwt-content mark {
50 | border-radius: 5px;
51 | background-color: #d0bfff;
52 | }
53 |
54 | .hwt-content mark.two {
55 | background-color: var(--color-yellow);
56 | }
57 |
58 | .hwt-content mark.four {
59 | background-color: var(--color-purple);
60 | }
61 |
62 | .hwt-content mark.six {
63 | background-color: var(--color-red);
64 | }
65 |
66 | .hwt-content mark.twelve {
67 | background-color: var(--color-green);
68 | }
69 |
70 | .hwt-content mark.else {
71 | background-color: var(--color-blue);
72 | }
73 |
74 |
75 | .credits {
76 | font-family: monospace;
77 | font-size: 1.8vh;
78 | bottom: 1.5vh;
79 | left: 0.4vh;
80 | /*top: 80vh;*/
81 | position: absolute;
82 | display: grid;
83 | grid-template-columns: 14vh 16vh;
84 | text-align: center;
85 | align-content: center;
86 | grid-column-gap: 1vh;
87 | width: 1vw;
88 | }
89 |
90 | .by {
91 | color: white;
92 | text-decoration: none;
93 | border-radius: 5px;
94 | background-color: var(--color-yellow);
95 | }
96 |
97 | .check-code {
98 | color: white;
99 | text-decoration: none;
100 | border-radius: 5px;
101 | background-color: var(--color-purple);
102 |
103 | }
104 |
105 | .by:hover {
106 | color: papayawhip;
107 | text-decoration:underline;
108 | text-decoration-style: dotted;
109 | }
110 |
111 | .check-code:hover {
112 | color: papayawhip;
113 | text-decoration:underline;
114 | text-decoration-style: dotted;
115 | }
116 |
117 |
118 | .play-button {
119 | width: 8vh;
120 | height: 8vh;
121 |
122 | background-color: rgba(102, 204, 102, 0.8);
123 |
124 | border-radius: 50%;
125 | /*box-shadow: 0 4px 10px 0 #666;*/
126 |
127 | -webkit-transition: all 0.1s ease-in-out;
128 | -moz-transition: all 0.1s ease-in-out;
129 | -ms-transition: all 0.1s ease-in-out;
130 | -o-transition: all 0.1s ease-in-out;
131 | transition: all 0.1s ease-in-out;
132 |
133 | font-size: 4.4vh;
134 | color: white;
135 | text-align: center;
136 | line-height: 8vh;
137 |
138 | position: absolute;
139 | /*left: 50%;*/
140 | bottom: 0.8vh;
141 | right: 0.8vh;
142 | z-index: 1;
143 |
144 | border: none;
145 | padding: 0;
146 | outline: none;
147 | }
148 |
149 | .play-button:hover {
150 | /* box-shadow: 0 6px 14px 0 #666; */
151 | -webkit-transform: scale(1.1);
152 | -moz-transform: scale(1.1);
153 | -ms-transform: scale(1.1);
154 | -o-transform: scale(1.1);
155 | transform: scale(1.1);
156 | }
157 |
158 | .play-button:focus {
159 | /* box-shadow: 0 6px 14px 0 #666; */
160 | -webkit-transform: scale(1.1);
161 | -moz-transform: scale(1.1);
162 | -ms-transform: scale(1.1);
163 | -o-transform: scale(1.1);
164 | transform: scale(1.1);
165 | }
166 |
167 | .play-button:active {
168 | /*box-shadow: 0 4px 8px 0 #666;;*/
169 | -webkit-transform: scale(0.9);
170 | -moz-transform: scale(0.9);
171 | -ms-transform: scale(0.9);
172 | -o-transform: scale(0.9);
173 | transform: scale(0.9);
174 | }
175 |
176 | .play-button.two {
177 | -webkit-transition: background-color 350ms linear;
178 | -moz-transition: background-color 350ms linear;
179 | -ms-transition: background-color 350ms linear;
180 | -o-transition: background-color 350ms linear;
181 | transition: background-color 350ms linear;
182 | background-color: rgba(253, 253, 131, 0.6);
183 | }
184 |
185 | .play-button.four {
186 | -webkit-transition: background-color 350ms linear;
187 | -moz-transition: background-color 350ms linear;
188 | -ms-transition: background-color 350ms linear;
189 | -o-transition: background-color 350ms linear;
190 | transition: background-color 350ms linear;
191 | background-color: rgba(204, 153, 204, 0.8);
192 | }
193 |
194 | .play-button.six {
195 | -webkit-transition: background-color 350ms linear;
196 | -moz-transition: background-color 350ms linear;
197 | -ms-transition: background-color 350ms linear;
198 | -o-transition: background-color 350ms linear;
199 | transition: background-color 350ms linear;
200 | background-color: rgba(255, 102, 102, 0.8);
201 | }
202 |
203 | .play-button.twelve {
204 | -webkit-transition: background-color 350ms linear;
205 | -moz-transition: background-color 350ms linear;
206 | -ms-transition: background-color 350ms linear;
207 | -o-transition: background-color 350ms linear;
208 | transition: background-color 350ms linear;
209 | background-color: rgba(102, 204, 102, 0.8);
210 | }
211 |
212 | .play-button.else {
213 | -webkit-transition: background-color 350ms linear;
214 | -moz-transition: background-color 350ms linear;
215 | -ms-transition: background-color 350ms linear;
216 | -o-transition: background-color 350ms linear;
217 | transition: background-color 350ms linear;
218 | background-color: rgba(153, 204, 204, 0.8);
219 | }
220 |
221 |
222 | /*Custom Scrollbar*/
223 | /* width */
224 | ::-webkit-scrollbar {
225 | width: 5px;
226 | }
227 |
228 | /* Track */
229 | ::-webkit-scrollbar-track {
230 | background: transparent;
231 | }
232 |
233 | /* Handle */
234 | ::-webkit-scrollbar-thumb {
235 | background: #e0e0e0;
236 | border-radius: 10px;
237 | }
238 |
239 | /* Handle on hover */
240 | ::-webkit-scrollbar-thumb:hover {
241 | background: #9e9e9e;
242 | }
243 |
244 |
245 | /*Removes focus border*/
246 | *:focus {
247 | outline: none;
248 | }
249 |
250 | .view-button {
251 | width: 4vh;
252 | height: 4vh;
253 |
254 | background-color: rgba(102, 204, 102, 0.8);
255 |
256 | border-radius: 50%;
257 |
258 | transition: all 0.1s ease-in-out;
259 |
260 | font-size: 2.4vh;
261 | color: white;
262 | text-align: center;
263 |
264 | position: absolute;
265 | bottom: 0.8vh;
266 | right: 7.5rem;
267 | z-index: 1;
268 |
269 | border: none;
270 | padding: 0;
271 | outline: none;
272 | }
273 |
274 | .view-button:hover {
275 | transform: scale(1.1);
276 | }
277 |
278 | .view-button:focus {
279 | transform: scale(1.1);
280 | }
281 |
282 | .view-button:active {
283 | transform: scale(0.9);
284 | }
--------------------------------------------------------------------------------