├── .gitignore ├── LICENSE ├── README.md ├── algorithms.html ├── ambilight.html ├── background.html ├── background_async.html ├── background_async_dominant.html ├── border.html ├── box-shadow-4-sides.html ├── box-shadow.html ├── canvas.html ├── define.html ├── dist ├── algorithms.css ├── algorithms.js ├── ambilight.css ├── ambilight.js ├── background.css ├── background.js ├── background_async.css ├── background_async.js ├── background_async_dominant.css ├── background_async_dominant.js ├── border.css ├── border.js ├── box-shadow-4-sides.css ├── box-shadow-4-sides.js ├── box-shadow.css ├── box-shadow.js ├── canvas.css ├── canvas.js ├── define.css ├── define.js ├── gallery.css ├── gallery.js ├── gallery_vertical.css ├── gallery_vertical.js ├── gradient.css ├── gradient.js ├── gradient_stripes.css ├── gradient_stripes.js ├── text-photo.css ├── text-photo.js ├── timeline.css └── timeline.js ├── gallery.html ├── gallery_vertical.html ├── gradient.html ├── gradient_stripes.html ├── images ├── 1.jpg ├── 10.jpg ├── 11.jpg ├── 12.jpg ├── 13.jpg ├── 14.jpg ├── 15.jpg ├── 16.jpg ├── 17.jpg ├── 18.jpg ├── 19.jpg ├── 2.jpg ├── 20.jpg ├── 21.jpg ├── 22.jpg ├── 23.jpg ├── 24.jpg ├── 25.jpg ├── 26.jpg ├── 27.jpg ├── 3.jpg ├── 4.jpg ├── 5.jpg ├── 6.jpg ├── 7.jpg ├── 8.jpg ├── 9.jpg ├── chrome.jpg ├── firefox.jpg ├── ie.jpg ├── opera.jpg └── transparent.png ├── package-lock.json ├── package.json ├── rollup.config.mjs ├── src ├── algorithms │ ├── build.js │ ├── index.css │ ├── index.ts │ └── template.html ├── ambilight │ ├── ambilight.ts │ ├── index.css │ ├── index.ts │ └── types │ │ ├── 4sides.ts │ │ ├── base.ts │ │ ├── helpers │ │ └── ua.ts │ │ └── many-points.ts ├── background │ ├── index.css │ └── index.ts ├── background_async │ ├── index.css │ └── index.ts ├── background_async_dominant │ ├── index.css │ └── index.ts ├── border │ ├── index.css │ └── index.ts ├── box-shadow-4-sides │ ├── index.css │ └── index.ts ├── box-shadow │ ├── index.css │ └── index.ts ├── canvas │ ├── helpers.ts │ ├── index.css │ └── index.ts ├── common.css ├── common.ts ├── define │ ├── index.css │ └── index.ts ├── gallery │ ├── index.css │ └── index.ts ├── gallery_vertical │ ├── getColors.ts │ ├── index.css │ └── index.ts ├── gradient │ ├── index.css │ └── index.ts ├── gradient_stripes │ ├── index.css │ └── index.ts ├── text-photo │ ├── index.css │ └── index.ts └── timeline │ ├── index.css │ ├── index.ts │ └── utils.ts ├── text-photo.html ├── timeline.html ├── tsconfig.json ├── videos └── 1.mp4 └── webgl.html /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Denis Seleznev 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # [Examples](https://fast-average-color.github.io/examples/background.html) for [Fast Average Color](https://github.com/fast-average-color/fast-average-color) 2 | 3 | - [Background](https://fast-average-color.github.io/examples/background.html) 4 | - [Background async](https://fast-average-color.github.io/examples/background_async.html) 5 | - [Box shadow](https://fast-average-color.github.io/examples/box-shadow.html) 6 | - [Box shadow, 4 sides](https://fast-average-color.github.io/examples/box-shadow-4-sides.html) 7 | - [Border](https://fast-average-color.github.io/examples/border.html) 8 | - [Gallery](https://fast-average-color.github.io/examples/gallery.html) 9 | - [Vertical and horizontal](https://fast-average-color.github.io/examples/gallery_vertical.html) 10 | - [Gradient](https://fast-average-color.github.io/examples/gradient.html) 11 | - [Gradient as stripes](https://fast-average-color.github.io/examples/gradient_stripes.html) 12 | - [Canvas](https://fast-average-color.github.io/examples/canvas.html) 13 | - [Text photo](https://fast-average-color.github.io/examples/text-photo.html) 14 | - [Ambilight](https://fast-average-color.github.io/examples/ambilight.html) 15 | - [Algorithms](https://fast-average-color.github.io/examples/algorithms.html) 16 | - [Define the average color for your images](https://fast-average-color.github.io/examples/define.html) 17 | - [Timeline](https://fast-average-color.github.io/examples/timeline.html) 18 | - [WebGL](https://fast-average-color.github.io/examples/webgl.html) 19 | -------------------------------------------------------------------------------- /algorithms.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Average color with algorithms 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 |
14 |
Photo
15 |
simple algorithm
16 |
sqrt algorithm
17 |
dominant algorithm
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 | -------------------------------------------------------------------------------- /ambilight.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Get average color for ambilight 5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 |

Ambilight

13 | 14 | 15 |
16 |
17 | 18 |
19 |
Press “Play”
20 | Altair - Fluke | Demoscene 21 | 22 | 23 | -------------------------------------------------------------------------------- /background.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | get average color for background 5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 |
14 | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. 15 |
16 |
17 | 18 |
19 | 20 |
21 | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. 22 |
23 |
24 | 25 |
26 | 27 |
28 | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. 29 |
30 |
31 | 32 |
33 | 34 |
35 | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. 36 |
37 |
38 | 39 |
40 | 41 |
42 | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. 43 |
44 |
45 | 46 |
47 | 48 |
49 | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. 50 |
51 |
52 | 53 | 54 | -------------------------------------------------------------------------------- /background_async.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Get average color for background, async 5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 |
14 | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. 15 |
16 |
17 | 18 |
19 | 20 |
21 | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. 22 |
23 |
24 | 25 |
26 | 27 |
28 | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. 29 |
30 |
31 | 32 |
33 | 34 |
35 | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. 36 |
37 |
38 | 39 |
40 | 41 |
42 | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. 43 |
44 |
45 | 46 |
47 | 48 |
49 | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. 50 |
51 |
52 | 53 | 54 | -------------------------------------------------------------------------------- /background_async_dominant.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Get average color for background, async 5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 |
14 | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. 15 |
16 |
17 | 18 |
19 | 20 |
21 | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. 22 |
23 |
24 | 25 |
26 | 27 |
28 | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. 29 |
30 |
31 | 32 |
33 | 34 |
35 | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. 36 |
37 |
38 | 39 |
40 | 41 |
42 | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. 43 |
44 |
45 | 46 |
47 | 48 |
49 | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. 50 |
51 |
52 | 53 | 54 | -------------------------------------------------------------------------------- /border.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Get average color for border 5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 |
13 |
14 | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. 15 |
16 |
17 | 18 |
19 |
20 |
21 | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. 22 |
23 |
24 | 25 |
26 |
27 |
28 | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. 29 |
30 |
31 | 32 |
33 |
34 |
35 | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. 36 |
37 |
38 | 39 |
40 |
41 |
42 | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. 43 |
44 |
45 | 46 |
47 |
48 |
49 | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. 50 |
51 |
52 | 53 | 54 | -------------------------------------------------------------------------------- /box-shadow-4-sides.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Get average color for box shadow, 4 sides 5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 |
14 | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. 15 |
16 |
17 | 18 |
19 | 20 |
21 | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. 22 |
23 |
24 | 25 |
26 | 27 |
28 | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. 29 |
30 |
31 | 32 |
33 | 34 |
35 | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. 36 |
37 |
38 | 39 |
40 | 41 |
42 | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. 43 |
44 |
45 | 46 |
47 | 48 |
49 | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. 50 |
51 |
52 | 53 | 54 | -------------------------------------------------------------------------------- /box-shadow.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Get average color for box shadow 5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 |
14 | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. 15 |
16 |
17 | 18 |
19 | 20 |
21 | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. 22 |
23 |
24 | 25 |
26 | 27 |
28 | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. 29 |
30 |
31 | 32 |
33 | 34 |
35 | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. 36 |
37 |
38 | 39 |
40 | 41 |
42 | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. 43 |
44 |
45 | 46 |
47 | 48 |
49 | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. 50 |
51 |
52 | 53 | 54 | -------------------------------------------------------------------------------- /canvas.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Get Average Color for canvas 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |
Average color: 14 |
15 |
16 | Algorithm: 17 | 18 | 19 | 20 |
21 | 22 |
23 | Mode: 24 | 25 | 26 |
27 | 28 |
29 | Step: 30 |
31 | 32 |
33 | Size: 34 | 35 | 36 | 37 |
38 | 39 | 40 |
41 | 42 | 43 | -------------------------------------------------------------------------------- /define.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Get average color of image 5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | Select a image to define the average color:
13 | Or a photo from the camera. 14 |
15 | 16 |
17 | 18 | 19 | -------------------------------------------------------------------------------- /dist/algorithms.css: -------------------------------------------------------------------------------- 1 | html, 2 | body 3 | { 4 | font-family: Arial; 5 | font-size: 16px; 6 | 7 | margin: 0; 8 | padding: 0; 9 | 10 | border: 0; 11 | background: black; 12 | } 13 | 14 | .nav 15 | { 16 | position: fixed; 17 | z-index: 10000; 18 | top: 5px; 19 | left: 5px; 20 | padding: 10px; 21 | 22 | background: rgba(0, 0, 0, .5); 23 | 24 | border-radius: 5px; 25 | } 26 | 27 | .back 28 | { 29 | margin-right: 15px; 30 | } 31 | 32 | .prev 33 | { 34 | margin-right: 3px; 35 | } 36 | 37 | .button 38 | { 39 | padding: 5px 15px; 40 | 41 | transition: opacity .3s ease; 42 | text-decoration: none; 43 | 44 | color: #fff; 45 | border-radius: 5px; 46 | background: rgba(255, 255, 255, .3); 47 | 48 | font-size: 12px; 49 | 50 | filter: grayscale(100%); 51 | } 52 | 53 | .button:hover 54 | { 55 | opacity: .5; 56 | } 57 | 58 | .rows { 59 | margin: 50px 0 0 20px; 60 | color: #fff; 61 | } 62 | 63 | .row 64 | { 65 | display: flex; 66 | } 67 | 68 | .item 69 | { 70 | transition: all .3s ease; 71 | flex-basis: 300px; 72 | margin: 20px 20px 0 0; 73 | text-align: center; 74 | } 75 | 76 | .item_image { 77 | max-width: 300px; 78 | width: 25%; 79 | } 80 | 81 | -------------------------------------------------------------------------------- /dist/ambilight.css: -------------------------------------------------------------------------------- 1 | html, 2 | body 3 | { 4 | font-family: Arial; 5 | font-size: 16px; 6 | 7 | margin: 0; 8 | padding: 0; 9 | 10 | border: 0; 11 | background: black; 12 | } 13 | 14 | .nav 15 | { 16 | position: fixed; 17 | z-index: 10000; 18 | top: 5px; 19 | left: 5px; 20 | padding: 10px; 21 | 22 | background: rgba(0, 0, 0, .5); 23 | 24 | border-radius: 5px; 25 | } 26 | 27 | .back 28 | { 29 | margin-right: 15px; 30 | } 31 | 32 | .prev 33 | { 34 | margin-right: 3px; 35 | } 36 | 37 | .button 38 | { 39 | padding: 5px 15px; 40 | 41 | transition: opacity .3s ease; 42 | text-decoration: none; 43 | 44 | color: #fff; 45 | border-radius: 5px; 46 | background: rgba(255, 255, 255, .3); 47 | 48 | font-size: 12px; 49 | 50 | filter: grayscale(100%); 51 | } 52 | 53 | .button:hover 54 | { 55 | opacity: .5; 56 | } 57 | 58 | .ambilight-control 59 | { 60 | font-size: 12px; 61 | line-height: 12px; 62 | 63 | position: fixed; 64 | z-index: 100; 65 | top: 21px; 66 | left: 50%; 67 | 68 | transform: translateX(-50%); 69 | text-align: center; 70 | white-space: nowrap; 71 | 72 | color: #999; 73 | } 74 | 75 | .ambilight-control h1 76 | { 77 | font-weight: normal; 78 | 79 | margin: 0 0 15px 0; 80 | padding: 0; 81 | 82 | color: #aaa; 83 | } 84 | 85 | .ambilight-control label 86 | { 87 | margin: 0; 88 | margin-right: 8px; 89 | padding: 0; 90 | } 91 | 92 | .ambilight-control input 93 | { 94 | font-size: 12px; 95 | line-height: 12px; 96 | 97 | margin: 0 2px 0 0; 98 | 99 | vertical-align: top; 100 | } 101 | 102 | .video-container 103 | { 104 | position: absolute; 105 | top: 50%; 106 | left: 50%; 107 | 108 | transform: translate(-50%, -50%); 109 | } 110 | 111 | video 112 | { 113 | position: relative; 114 | z-index: 100; 115 | 116 | display: block; 117 | 118 | margin: 0; 119 | padding: 0; 120 | 121 | border: 10px solid black; 122 | outline: 1px solid rgba(255, 255, 255, .1); 123 | background: black; 124 | } 125 | 126 | .video-shadow 127 | { 128 | position: absolute; 129 | } 130 | 131 | .video-shadow_left 132 | { 133 | left: 0; 134 | 135 | border-radius: 100% 0 0 100%; 136 | } 137 | 138 | .video-shadow_right 139 | { 140 | right: 0; 141 | 142 | border-radius: 0 100% 100% 0; 143 | } 144 | 145 | .video-shadow_top 146 | { 147 | top: 0; 148 | 149 | border-radius: 100% 100% 0 0; 150 | } 151 | 152 | .video-shadow_bottom 153 | { 154 | bottom: 0; 155 | 156 | border-radius: 0 0 100% 100%; 157 | } 158 | 159 | .copyright 160 | { 161 | font-family: Arial; 162 | font-size: 12px; 163 | 164 | position: fixed; 165 | bottom: 5px; 166 | left: 50%; 167 | 168 | margin-left: -5em; 169 | } 170 | 171 | .hint 172 | { 173 | font-size: 16px; 174 | 175 | position: absolute; 176 | top: 50%; 177 | left: 50%; 178 | 179 | margin-top: 200px; 180 | margin-left: -320px; 181 | 182 | color: #aaa; 183 | } 184 | 185 | .copyright:link, 186 | .copyright:visited, 187 | .copyright:active, 188 | .copyright:hover 189 | { 190 | color: #555; 191 | } 192 | 193 | -------------------------------------------------------------------------------- /dist/background.css: -------------------------------------------------------------------------------- 1 | html, 2 | body 3 | { 4 | font-family: Arial; 5 | font-size: 16px; 6 | 7 | margin: 0; 8 | padding: 0; 9 | 10 | border: 0; 11 | background: black; 12 | } 13 | 14 | .nav 15 | { 16 | position: fixed; 17 | z-index: 10000; 18 | top: 5px; 19 | left: 5px; 20 | padding: 10px; 21 | 22 | background: rgba(0, 0, 0, .5); 23 | 24 | border-radius: 5px; 25 | } 26 | 27 | .back 28 | { 29 | margin-right: 15px; 30 | } 31 | 32 | .prev 33 | { 34 | margin-right: 3px; 35 | } 36 | 37 | .button 38 | { 39 | padding: 5px 15px; 40 | 41 | transition: opacity .3s ease; 42 | text-decoration: none; 43 | 44 | color: #fff; 45 | border-radius: 5px; 46 | background: rgba(255, 255, 255, .3); 47 | 48 | font-size: 12px; 49 | 50 | filter: grayscale(100%); 51 | } 52 | 53 | .button:hover 54 | { 55 | opacity: .5; 56 | } 57 | 58 | img 59 | { 60 | width: 100%; 61 | } 62 | 63 | .item 64 | { 65 | float: left; 66 | 67 | box-sizing: border-box; 68 | width: 500px; 69 | max-width: 100%; 70 | padding: 50px 50px 45px 50px; 71 | 72 | transition: background-color .5s ease; 73 | } 74 | 75 | .item__text 76 | { 77 | padding-top: 10px; 78 | } 79 | 80 | -------------------------------------------------------------------------------- /dist/background.js: -------------------------------------------------------------------------------- 1 | (function (factory) { 2 | typeof define === 'function' && define.amd ? define(factory) : 3 | factory(); 4 | })((function () { 'use strict'; 5 | 6 | /*! Fast Average Color | © 2025 Denis Seleznev | MIT License | https://github.com/fast-average-color/fast-average-color */ 7 | function toHex(num) { 8 | var str = num.toString(16); 9 | return str.length === 1 ? '0' + str : str; 10 | } 11 | function arrayToHex(arr) { 12 | return '#' + arr.map(toHex).join(''); 13 | } 14 | function isDark(color) { 15 | // http://www.w3.org/TR/AERT#color-contrast 16 | var result = (color[0] * 299 + color[1] * 587 + color[2] * 114) / 1000; 17 | return result < 128; 18 | } 19 | function prepareIgnoredColor(color) { 20 | if (!color) { 21 | return []; 22 | } 23 | return isRGBArray(color) ? color : [color]; 24 | } 25 | function isRGBArray(value) { 26 | return Array.isArray(value[0]); 27 | } 28 | function isIgnoredColor(data, index, ignoredColor) { 29 | for (var i = 0; i < ignoredColor.length; i++) { 30 | if (isIgnoredColorAsNumbers(data, index, ignoredColor[i])) { 31 | return true; 32 | } 33 | } 34 | return false; 35 | } 36 | function isIgnoredColorAsNumbers(data, index, ignoredColor) { 37 | switch (ignoredColor.length) { 38 | case 3: 39 | // [red, green, blue] 40 | if (isIgnoredRGBColor(data, index, ignoredColor)) { 41 | return true; 42 | } 43 | break; 44 | case 4: 45 | // [red, green, blue, alpha] 46 | if (isIgnoredRGBAColor(data, index, ignoredColor)) { 47 | return true; 48 | } 49 | break; 50 | case 5: 51 | // [red, green, blue, alpha, threshold] 52 | if (isIgnoredRGBAColorWithThreshold(data, index, ignoredColor)) { 53 | return true; 54 | } 55 | break; 56 | default: 57 | return false; 58 | } 59 | } 60 | function isIgnoredRGBColor(data, index, ignoredColor) { 61 | // Ignore if the pixel are transparent. 62 | if (data[index + 3] !== 255) { 63 | return true; 64 | } 65 | if (data[index] === ignoredColor[0] && 66 | data[index + 1] === ignoredColor[1] && 67 | data[index + 2] === ignoredColor[2]) { 68 | return true; 69 | } 70 | return false; 71 | } 72 | function isIgnoredRGBAColor(data, index, ignoredColor) { 73 | if (data[index + 3] && ignoredColor[3]) { 74 | return data[index] === ignoredColor[0] && 75 | data[index + 1] === ignoredColor[1] && 76 | data[index + 2] === ignoredColor[2] && 77 | data[index + 3] === ignoredColor[3]; 78 | } 79 | // Ignore rgb components if the pixel are fully transparent. 80 | return data[index + 3] === ignoredColor[3]; 81 | } 82 | function inRange(colorComponent, ignoredColorComponent, value) { 83 | return colorComponent >= (ignoredColorComponent - value) && 84 | colorComponent <= (ignoredColorComponent + value); 85 | } 86 | function isIgnoredRGBAColorWithThreshold(data, index, ignoredColor) { 87 | var redIgnored = ignoredColor[0]; 88 | var greenIgnored = ignoredColor[1]; 89 | var blueIgnored = ignoredColor[2]; 90 | var alphaIgnored = ignoredColor[3]; 91 | var threshold = ignoredColor[4]; 92 | var alphaData = data[index + 3]; 93 | var alphaInRange = inRange(alphaData, alphaIgnored, threshold); 94 | if (!alphaIgnored) { 95 | return alphaInRange; 96 | } 97 | if (!alphaData && alphaInRange) { 98 | return true; 99 | } 100 | if (inRange(data[index], redIgnored, threshold) && 101 | inRange(data[index + 1], greenIgnored, threshold) && 102 | inRange(data[index + 2], blueIgnored, threshold) && 103 | alphaInRange) { 104 | return true; 105 | } 106 | return false; 107 | } 108 | 109 | var DEFAULT_DOMINANT_DIVIDER = 24; 110 | function dominantAlgorithm(arr, len, options) { 111 | var colorHash = {}; 112 | var divider = options.dominantDivider || DEFAULT_DOMINANT_DIVIDER; 113 | var ignoredColor = options.ignoredColor; 114 | var step = options.step; 115 | var max = [0, 0, 0, 0, 0]; 116 | for (var i = 0; i < len; i += step) { 117 | var red = arr[i]; 118 | var green = arr[i + 1]; 119 | var blue = arr[i + 2]; 120 | var alpha = arr[i + 3]; 121 | if (ignoredColor && isIgnoredColor(arr, i, ignoredColor)) { 122 | continue; 123 | } 124 | var key = Math.round(red / divider) + ',' + 125 | Math.round(green / divider) + ',' + 126 | Math.round(blue / divider); 127 | if (colorHash[key]) { 128 | colorHash[key] = [ 129 | colorHash[key][0] + red * alpha, 130 | colorHash[key][1] + green * alpha, 131 | colorHash[key][2] + blue * alpha, 132 | colorHash[key][3] + alpha, 133 | colorHash[key][4] + 1 134 | ]; 135 | } 136 | else { 137 | colorHash[key] = [red * alpha, green * alpha, blue * alpha, alpha, 1]; 138 | } 139 | if (max[4] < colorHash[key][4]) { 140 | max = colorHash[key]; 141 | } 142 | } 143 | var redTotal = max[0]; 144 | var greenTotal = max[1]; 145 | var blueTotal = max[2]; 146 | var alphaTotal = max[3]; 147 | var count = max[4]; 148 | return alphaTotal ? [ 149 | Math.round(redTotal / alphaTotal), 150 | Math.round(greenTotal / alphaTotal), 151 | Math.round(blueTotal / alphaTotal), 152 | Math.round(alphaTotal / count) 153 | ] : options.defaultColor; 154 | } 155 | 156 | function simpleAlgorithm(arr, len, options) { 157 | var redTotal = 0; 158 | var greenTotal = 0; 159 | var blueTotal = 0; 160 | var alphaTotal = 0; 161 | var count = 0; 162 | var ignoredColor = options.ignoredColor; 163 | var step = options.step; 164 | for (var i = 0; i < len; i += step) { 165 | var alpha = arr[i + 3]; 166 | var red = arr[i] * alpha; 167 | var green = arr[i + 1] * alpha; 168 | var blue = arr[i + 2] * alpha; 169 | if (ignoredColor && isIgnoredColor(arr, i, ignoredColor)) { 170 | continue; 171 | } 172 | redTotal += red; 173 | greenTotal += green; 174 | blueTotal += blue; 175 | alphaTotal += alpha; 176 | count++; 177 | } 178 | return alphaTotal ? [ 179 | Math.round(redTotal / alphaTotal), 180 | Math.round(greenTotal / alphaTotal), 181 | Math.round(blueTotal / alphaTotal), 182 | Math.round(alphaTotal / count) 183 | ] : options.defaultColor; 184 | } 185 | 186 | function sqrtAlgorithm(arr, len, options) { 187 | var redTotal = 0; 188 | var greenTotal = 0; 189 | var blueTotal = 0; 190 | var alphaTotal = 0; 191 | var count = 0; 192 | var ignoredColor = options.ignoredColor; 193 | var step = options.step; 194 | for (var i = 0; i < len; i += step) { 195 | var red = arr[i]; 196 | var green = arr[i + 1]; 197 | var blue = arr[i + 2]; 198 | var alpha = arr[i + 3]; 199 | if (ignoredColor && isIgnoredColor(arr, i, ignoredColor)) { 200 | continue; 201 | } 202 | redTotal += red * red * alpha; 203 | greenTotal += green * green * alpha; 204 | blueTotal += blue * blue * alpha; 205 | alphaTotal += alpha; 206 | count++; 207 | } 208 | return alphaTotal ? [ 209 | Math.round(Math.sqrt(redTotal / alphaTotal)), 210 | Math.round(Math.sqrt(greenTotal / alphaTotal)), 211 | Math.round(Math.sqrt(blueTotal / alphaTotal)), 212 | Math.round(alphaTotal / count) 213 | ] : options.defaultColor; 214 | } 215 | 216 | function getDefaultColor(options) { 217 | return getOption(options, 'defaultColor', [0, 0, 0, 0]); 218 | } 219 | function getOption(options, name, defaultValue) { 220 | return (options[name] === undefined ? defaultValue : options[name]); 221 | } 222 | 223 | var MIN_SIZE = 10; 224 | var MAX_SIZE = 100; 225 | function isSvg(filename) { 226 | return filename.search(/\.svg(\?|$)/i) !== -1; 227 | } 228 | function getOriginalSize(resource) { 229 | if (isInstanceOfHTMLImageElement(resource)) { 230 | var width = resource.naturalWidth; 231 | var height = resource.naturalHeight; 232 | // For SVG images with only viewBox attribute 233 | if (!resource.naturalWidth && isSvg(resource.src)) { 234 | width = height = MAX_SIZE; 235 | } 236 | return { 237 | width: width, 238 | height: height, 239 | }; 240 | } 241 | if (isInstanceOfHTMLVideoElement(resource)) { 242 | return { 243 | width: resource.videoWidth, 244 | height: resource.videoHeight 245 | }; 246 | } 247 | if (isInstanceOfVideoFrame(resource)) { 248 | return { 249 | width: resource.codedWidth, 250 | height: resource.codedHeight, 251 | }; 252 | } 253 | return { 254 | width: resource.width, 255 | height: resource.height 256 | }; 257 | } 258 | function getSrc(resource) { 259 | if (isInstanceOfHTMLCanvasElement(resource)) { 260 | return 'canvas'; 261 | } 262 | if (isInstanceOfOffscreenCanvas(resource)) { 263 | return 'offscreencanvas'; 264 | } 265 | if (isInstanceOfVideoFrame(resource)) { 266 | return 'videoframe'; 267 | } 268 | if (isInstanceOfImageBitmap(resource)) { 269 | return 'imagebitmap'; 270 | } 271 | return resource.src; 272 | } 273 | function isInstanceOfHTMLImageElement(resource) { 274 | return typeof HTMLImageElement !== 'undefined' && resource instanceof HTMLImageElement; 275 | } 276 | var hasOffscreenCanvas = typeof OffscreenCanvas !== 'undefined'; 277 | function isInstanceOfOffscreenCanvas(resource) { 278 | return hasOffscreenCanvas && resource instanceof OffscreenCanvas; 279 | } 280 | function isInstanceOfHTMLVideoElement(resource) { 281 | return typeof HTMLVideoElement !== 'undefined' && resource instanceof HTMLVideoElement; 282 | } 283 | function isInstanceOfVideoFrame(resource) { 284 | return typeof VideoFrame !== 'undefined' && resource instanceof VideoFrame; 285 | } 286 | function isInstanceOfHTMLCanvasElement(resource) { 287 | return typeof HTMLCanvasElement !== 'undefined' && resource instanceof HTMLCanvasElement; 288 | } 289 | function isInstanceOfImageBitmap(resource) { 290 | return typeof ImageBitmap !== 'undefined' && resource instanceof ImageBitmap; 291 | } 292 | function prepareSizeAndPosition(originalSize, options) { 293 | var srcLeft = getOption(options, 'left', 0); 294 | var srcTop = getOption(options, 'top', 0); 295 | var srcWidth = getOption(options, 'width', originalSize.width); 296 | var srcHeight = getOption(options, 'height', originalSize.height); 297 | var destWidth = srcWidth; 298 | var destHeight = srcHeight; 299 | if (options.mode === 'precision') { 300 | return { 301 | srcLeft: srcLeft, 302 | srcTop: srcTop, 303 | srcWidth: srcWidth, 304 | srcHeight: srcHeight, 305 | destWidth: destWidth, 306 | destHeight: destHeight 307 | }; 308 | } 309 | var factor; 310 | if (srcWidth > srcHeight) { 311 | factor = srcWidth / srcHeight; 312 | destWidth = MAX_SIZE; 313 | destHeight = Math.round(destWidth / factor); 314 | } 315 | else { 316 | factor = srcHeight / srcWidth; 317 | destHeight = MAX_SIZE; 318 | destWidth = Math.round(destHeight / factor); 319 | } 320 | if (destWidth > srcWidth || destHeight > srcHeight || 321 | destWidth < MIN_SIZE || destHeight < MIN_SIZE) { 322 | destWidth = srcWidth; 323 | destHeight = srcHeight; 324 | } 325 | return { 326 | srcLeft: srcLeft, 327 | srcTop: srcTop, 328 | srcWidth: srcWidth, 329 | srcHeight: srcHeight, 330 | destWidth: destWidth, 331 | destHeight: destHeight 332 | }; 333 | } 334 | var isWebWorkers = typeof window === 'undefined'; 335 | function makeCanvas() { 336 | if (isWebWorkers) { 337 | return hasOffscreenCanvas ? new OffscreenCanvas(1, 1) : null; 338 | } 339 | return document.createElement('canvas'); 340 | } 341 | 342 | var ERROR_PREFIX = 'FastAverageColor: '; 343 | function getError(message) { 344 | return Error(ERROR_PREFIX + message); 345 | } 346 | function outputError(error, silent) { 347 | if (!silent) { 348 | console.error(error); 349 | } 350 | } 351 | 352 | var FastAverageColor = /** @class */ (function () { 353 | function FastAverageColor() { 354 | this.canvas = null; 355 | this.ctx = null; 356 | } 357 | FastAverageColor.prototype.getColorAsync = function (resource, options) { 358 | if (!resource) { 359 | return Promise.reject(getError('call .getColorAsync() without resource')); 360 | } 361 | if (typeof resource === 'string') { 362 | // Web workers 363 | if (typeof Image === 'undefined') { 364 | return Promise.reject(getError('resource as string is not supported in this environment')); 365 | } 366 | var img = new Image(); 367 | img.crossOrigin = options && options.crossOrigin || ''; 368 | img.src = resource; 369 | return this.bindImageEvents(img, options); 370 | } 371 | else if (isInstanceOfHTMLImageElement(resource) && !resource.complete) { 372 | return this.bindImageEvents(resource, options); 373 | } 374 | else { 375 | var result = this.getColor(resource, options); 376 | return result.error ? Promise.reject(result.error) : Promise.resolve(result); 377 | } 378 | }; 379 | /** 380 | * Get the average color from images, videos and canvas. 381 | */ 382 | FastAverageColor.prototype.getColor = function (resource, options) { 383 | options = options || {}; 384 | var defaultColor = getDefaultColor(options); 385 | if (!resource) { 386 | var error = getError('call .getColor() without resource'); 387 | outputError(error, options.silent); 388 | return this.prepareResult(defaultColor, error); 389 | } 390 | var originalSize = getOriginalSize(resource); 391 | var size = prepareSizeAndPosition(originalSize, options); 392 | if (!size.srcWidth || !size.srcHeight || !size.destWidth || !size.destHeight) { 393 | var error = getError("incorrect sizes for resource \"".concat(getSrc(resource), "\"")); 394 | outputError(error, options.silent); 395 | return this.prepareResult(defaultColor, error); 396 | } 397 | if (!this.canvas) { 398 | this.canvas = makeCanvas(); 399 | if (!this.canvas) { 400 | var error = getError('OffscreenCanvas is not supported in this browser'); 401 | outputError(error, options.silent); 402 | return this.prepareResult(defaultColor, error); 403 | } 404 | } 405 | if (!this.ctx) { 406 | this.ctx = this.canvas.getContext('2d', { willReadFrequently: true }); 407 | if (!this.ctx) { 408 | var error = getError('Canvas Context 2D is not supported in this browser'); 409 | outputError(error, options.silent); 410 | return this.prepareResult(defaultColor); 411 | } 412 | this.ctx.imageSmoothingEnabled = false; 413 | } 414 | this.canvas.width = size.destWidth; 415 | this.canvas.height = size.destHeight; 416 | try { 417 | this.ctx.clearRect(0, 0, size.destWidth, size.destHeight); 418 | this.ctx.drawImage(resource, size.srcLeft, size.srcTop, size.srcWidth, size.srcHeight, 0, 0, size.destWidth, size.destHeight); 419 | var bitmapData = this.ctx.getImageData(0, 0, size.destWidth, size.destHeight).data; 420 | return this.prepareResult(this.getColorFromArray4(bitmapData, options)); 421 | } 422 | catch (originalError) { 423 | var error = getError("security error (CORS) for resource ".concat(getSrc(resource), ".\nDetails: https://developer.mozilla.org/en/docs/Web/HTML/CORS_enabled_image")); 424 | outputError(error, options.silent); 425 | if (!options.silent) { 426 | console.error(originalError); 427 | } 428 | return this.prepareResult(defaultColor, error); 429 | } 430 | }; 431 | /** 432 | * Get the average color from a array when 1 pixel is 4 bytes. 433 | */ 434 | FastAverageColor.prototype.getColorFromArray4 = function (arr, options) { 435 | options = options || {}; 436 | var bytesPerPixel = 4; 437 | var arrLength = arr.length; 438 | var defaultColor = getDefaultColor(options); 439 | if (arrLength < bytesPerPixel) { 440 | return defaultColor; 441 | } 442 | var len = arrLength - arrLength % bytesPerPixel; 443 | var step = (options.step || 1) * bytesPerPixel; 444 | var algorithm; 445 | switch (options.algorithm || 'sqrt') { 446 | case 'simple': 447 | algorithm = simpleAlgorithm; 448 | break; 449 | case 'sqrt': 450 | algorithm = sqrtAlgorithm; 451 | break; 452 | case 'dominant': 453 | algorithm = dominantAlgorithm; 454 | break; 455 | default: 456 | throw getError("".concat(options.algorithm, " is unknown algorithm")); 457 | } 458 | return algorithm(arr, len, { 459 | defaultColor: defaultColor, 460 | ignoredColor: prepareIgnoredColor(options.ignoredColor), 461 | step: step, 462 | dominantDivider: options.dominantDivider, 463 | }); 464 | }; 465 | /** 466 | * Get color data from value ([r, g, b, a]). 467 | */ 468 | FastAverageColor.prototype.prepareResult = function (value, error) { 469 | var rgb = value.slice(0, 3); 470 | var rgba = [value[0], value[1], value[2], value[3] / 255]; 471 | var isDarkColor = isDark(value); 472 | return { 473 | value: [value[0], value[1], value[2], value[3]], 474 | rgb: 'rgb(' + rgb.join(',') + ')', 475 | rgba: 'rgba(' + rgba.join(',') + ')', 476 | hex: arrayToHex(rgb), 477 | hexa: arrayToHex(value), 478 | isDark: isDarkColor, 479 | isLight: !isDarkColor, 480 | error: error, 481 | }; 482 | }; 483 | /** 484 | * Destroy the instance. 485 | */ 486 | FastAverageColor.prototype.destroy = function () { 487 | if (this.canvas) { 488 | this.canvas.width = 1; 489 | this.canvas.height = 1; 490 | this.canvas = null; 491 | } 492 | this.ctx = null; 493 | }; 494 | FastAverageColor.prototype.bindImageEvents = function (resource, options) { 495 | var _this = this; 496 | return new Promise(function (resolve, reject) { 497 | var onload = function () { 498 | unbindEvents(); 499 | var result = _this.getColor(resource, options); 500 | if (result.error) { 501 | reject(result.error); 502 | } 503 | else { 504 | resolve(result); 505 | } 506 | }; 507 | var onerror = function () { 508 | unbindEvents(); 509 | reject(getError("Error loading image \"".concat(resource.src, "\""))); 510 | }; 511 | var onabort = function () { 512 | unbindEvents(); 513 | reject(getError("Image \"".concat(resource.src, "\" loading aborted"))); 514 | }; 515 | var unbindEvents = function () { 516 | resource.removeEventListener('load', onload); 517 | resource.removeEventListener('error', onerror); 518 | resource.removeEventListener('abort', onabort); 519 | }; 520 | resource.addEventListener('load', onload); 521 | resource.addEventListener('error', onerror); 522 | resource.addEventListener('abort', onabort); 523 | }); 524 | }; 525 | return FastAverageColor; 526 | }()); 527 | 528 | var hasDocument = typeof document !== 'undefined'; 529 | var hasWindow = typeof window !== 'undefined'; 530 | var hasNavigator = typeof navigator != 'undefined'; 531 | var hasScreen = typeof screen != 'undefined'; 532 | function getCharset() { 533 | return hasDocument && typeof document.charset === 'string' ? 534 | document.charset.toLowerCase() : 535 | ''; 536 | } 537 | function getPageUrl() { 538 | return hasWindow && window.location ? window.location.href : ''; 539 | } 540 | function getReferrer() { 541 | return hasDocument ? document.referrer : ''; 542 | } 543 | function getTitle() { 544 | return hasDocument ? document.title : ''; 545 | } 546 | function cookieEnabled() { 547 | return hasNavigator ? navigator.cookieEnabled : false; 548 | } 549 | function getScreenSize() { 550 | return hasScreen ? [ 551 | screen.width, 552 | screen.height, 553 | screen.colorDepth 554 | ].join('x') : ''; 555 | } 556 | var DEFAULT_DEVICE_PIXEL_RATIO = 1; 557 | function getDevicePixelRatio() { 558 | return hasWindow ? 559 | (window.devicePixelRatio || DEFAULT_DEVICE_PIXEL_RATIO) : 560 | DEFAULT_DEVICE_PIXEL_RATIO; 561 | } 562 | function getClientSize() { 563 | return hasWindow ? [ 564 | window.innerWidth, 565 | window.innerHeight 566 | ].join('x') : ''; 567 | } 568 | 569 | function truncate(str, len) { 570 | return (str || '').slice(0, len); 571 | } 572 | 573 | function getRandom() { 574 | return Math.floor(Math.random() * (1 << 31 - 1)); 575 | } 576 | 577 | function getSeconds() { 578 | return Math.round(Date.now() / 1000); 579 | } 580 | 581 | var MAX_TITLE_LEN = 512; 582 | function addParam(result, name, value) { 583 | if (value || value === 0) { 584 | result.push(name + ':' + (value === true ? '1' : value)); 585 | } 586 | } 587 | function getBrowserInfo(params, title) { 588 | var result = []; 589 | if (params) { 590 | Object.keys(params).forEach(function (key) { return addParam(result, key, params[key]); }); 591 | } 592 | addParam(result, 'rn', getRandom()); 593 | addParam(result, 'c', cookieEnabled()); 594 | addParam(result, 's', getScreenSize()); 595 | addParam(result, 'sk', getDevicePixelRatio()); 596 | addParam(result, 'w', getClientSize()); 597 | addParam(result, 'en', getCharset()); 598 | var time = getSeconds(); 599 | addParam(result, 'et', time); 600 | addParam(result, 'st', time); 601 | addParam(result, 't', truncate(title, MAX_TITLE_LEN)); 602 | return result.join(':'); 603 | } 604 | 605 | function queryStringify(params) { 606 | return Object.keys(params) 607 | .filter(function (key) { return params[key] || params[key] === 0; }) 608 | .map(function (key) { return encodeURIComponent(key) + '=' + encodeURIComponent(params[key]); }) 609 | .join('&'); 610 | } 611 | var MAX_URL_LEN = 1024; 612 | function prepareUrl(url) { 613 | return truncate(url, MAX_URL_LEN); 614 | } 615 | 616 | var metrikaOrigin = 'https://mc.yandex.ru'; 617 | function sendData(counterId, queryParams) { 618 | var url = metrikaOrigin + '/watch/' + counterId + '?' + queryStringify(queryParams); 619 | var hasBeacon = typeof navigator !== 'undefined' && navigator.sendBeacon; 620 | if (!hasBeacon || !navigator.sendBeacon(url, ' ')) { 621 | if (typeof fetch !== 'undefined') { 622 | fetch(url, { credentials: 'include' }).catch(function () { }); 623 | } 624 | else if (typeof Image !== 'undefined') { 625 | new Image().src = url; 626 | } 627 | } 628 | } 629 | 630 | function hitExt(hitExtParams) { 631 | var browserInfo = hitExtParams.browserInfo, counterId = hitExtParams.counterId, pageParams = hitExtParams.pageParams; 632 | var data = { 633 | 'browser-info': getBrowserInfo(browserInfo, pageParams.title), 634 | rn: getRandom(), 635 | ut: pageParams.ut 636 | }; 637 | if (pageParams.url) { 638 | data['page-url'] = prepareUrl(pageParams.url); 639 | } 640 | if (pageParams.referrer) { 641 | data['page-ref'] = prepareUrl(pageParams.referrer); 642 | } 643 | sendData(counterId, data); 644 | } 645 | /** 646 | * Отправка хита. 647 | * 648 | * @param counterId - Номер счётчика. 649 | * @param hitParams - Параметры страницы. 650 | * @param userVars - Параметры визитов. 651 | * 652 | * @example 653 | * hit('123456'); 654 | * 655 | * hit('123456', { 656 | * referer: document.referer, 657 | * title: document.title, 658 | * url: window.location.href 659 | * }, { 660 | * myParam: 'value' 661 | * }); 662 | */ 663 | function hit(counterId, hitParams, params) { 664 | var referrer = getReferrer(); 665 | var title = getTitle(); 666 | var url = getPageUrl(); 667 | hitExt({ 668 | browserInfo: { pv: true, ar: true }, 669 | counterId: counterId, 670 | pageParams: { 671 | referrer: referrer, 672 | title: title, 673 | url: url 674 | }}); 675 | } 676 | 677 | window.addEventListener('load', function () { 678 | var pages = [ 679 | 'background', 680 | 'timeline', 681 | 'gradient', 682 | 'gradient_stripes', 683 | 'border', 684 | 'gallery', 685 | 'gallery_vertical', 686 | 'box-shadow', 687 | 'box-shadow-4-sides', 688 | 'ambilight', 689 | 'text-photo', 690 | 'canvas', 691 | 'define' 692 | ]; 693 | var prev = pages[pages.length - 1]; 694 | var next = pages[1]; 695 | pages.some(function (item, i) { 696 | prev = pages[i - 1] || pages[pages.length - 1]; 697 | next = pages[i + 1] || pages[0]; 698 | return location.pathname.search('/' + item + '\\.') > -1; 699 | }); 700 | var nav = document.createElement('div'); 701 | nav.innerHTML = ''; 706 | document.body.appendChild(nav); 707 | hit('49603183'); 708 | }, false); 709 | 710 | var fac = new FastAverageColor(); 711 | window.addEventListener('load', function () { 712 | Array.from(document.querySelectorAll('.item')).forEach(function (item) { 713 | var color = fac.getColor(item.querySelector('img')); 714 | item.style.backgroundColor = color.rgb; 715 | item.style.color = color.isDark ? 'white' : 'black'; 716 | }); 717 | }, false); 718 | 719 | })); 720 | -------------------------------------------------------------------------------- /dist/background_async.css: -------------------------------------------------------------------------------- 1 | html, 2 | body 3 | { 4 | font-family: Arial; 5 | font-size: 16px; 6 | 7 | margin: 0; 8 | padding: 0; 9 | 10 | border: 0; 11 | background: black; 12 | } 13 | 14 | .nav 15 | { 16 | position: fixed; 17 | z-index: 10000; 18 | top: 5px; 19 | left: 5px; 20 | padding: 10px; 21 | 22 | background: rgba(0, 0, 0, .5); 23 | 24 | border-radius: 5px; 25 | } 26 | 27 | .back 28 | { 29 | margin-right: 15px; 30 | } 31 | 32 | .prev 33 | { 34 | margin-right: 3px; 35 | } 36 | 37 | .button 38 | { 39 | padding: 5px 15px; 40 | 41 | transition: opacity .3s ease; 42 | text-decoration: none; 43 | 44 | color: #fff; 45 | border-radius: 5px; 46 | background: rgba(255, 255, 255, .3); 47 | 48 | font-size: 12px; 49 | 50 | filter: grayscale(100%); 51 | } 52 | 53 | .button:hover 54 | { 55 | opacity: .5; 56 | } 57 | 58 | img 59 | { 60 | width: 100%; 61 | } 62 | 63 | .item 64 | { 65 | float: left; 66 | 67 | box-sizing: border-box; 68 | width: 500px; 69 | max-width: 100%; 70 | padding: 50px 50px 45px 50px; 71 | 72 | transition: background-color .5s ease; 73 | } 74 | 75 | .item__text 76 | { 77 | padding-top: 10px; 78 | } 79 | 80 | -------------------------------------------------------------------------------- /dist/background_async_dominant.css: -------------------------------------------------------------------------------- 1 | html, 2 | body 3 | { 4 | font-family: Arial; 5 | font-size: 16px; 6 | 7 | margin: 0; 8 | padding: 0; 9 | 10 | border: 0; 11 | background: black; 12 | } 13 | 14 | .nav 15 | { 16 | position: fixed; 17 | z-index: 10000; 18 | top: 5px; 19 | left: 5px; 20 | padding: 10px; 21 | 22 | background: rgba(0, 0, 0, .5); 23 | 24 | border-radius: 5px; 25 | } 26 | 27 | .back 28 | { 29 | margin-right: 15px; 30 | } 31 | 32 | .prev 33 | { 34 | margin-right: 3px; 35 | } 36 | 37 | .button 38 | { 39 | padding: 5px 15px; 40 | 41 | transition: opacity .3s ease; 42 | text-decoration: none; 43 | 44 | color: #fff; 45 | border-radius: 5px; 46 | background: rgba(255, 255, 255, .3); 47 | 48 | font-size: 12px; 49 | 50 | filter: grayscale(100%); 51 | } 52 | 53 | .button:hover 54 | { 55 | opacity: .5; 56 | } 57 | 58 | img 59 | { 60 | width: 100%; 61 | } 62 | 63 | .item 64 | { 65 | float: left; 66 | 67 | box-sizing: border-box; 68 | width: 500px; 69 | max-width: 100%; 70 | padding: 50px 50px 45px 50px; 71 | 72 | transition: background-color .5s ease; 73 | } 74 | 75 | .item__text 76 | { 77 | padding-top: 10px; 78 | } 79 | 80 | -------------------------------------------------------------------------------- /dist/border.css: -------------------------------------------------------------------------------- 1 | html, 2 | body 3 | { 4 | font-family: Arial; 5 | font-size: 16px; 6 | 7 | margin: 0; 8 | padding: 0; 9 | 10 | border: 0; 11 | background: black; 12 | } 13 | 14 | .nav 15 | { 16 | position: fixed; 17 | z-index: 10000; 18 | top: 5px; 19 | left: 5px; 20 | padding: 10px; 21 | 22 | background: rgba(0, 0, 0, .5); 23 | 24 | border-radius: 5px; 25 | } 26 | 27 | .back 28 | { 29 | margin-right: 15px; 30 | } 31 | 32 | .prev 33 | { 34 | margin-right: 3px; 35 | } 36 | 37 | .button 38 | { 39 | padding: 5px 15px; 40 | 41 | transition: opacity .3s ease; 42 | text-decoration: none; 43 | 44 | color: #fff; 45 | border-radius: 5px; 46 | background: rgba(255, 255, 255, .3); 47 | 48 | font-size: 12px; 49 | 50 | filter: grayscale(100%); 51 | } 52 | 53 | .button:hover 54 | { 55 | opacity: .5; 56 | } 57 | 58 | .item 59 | { 60 | float: left; 61 | 62 | box-sizing: border-box; 63 | width: 400px; 64 | max-width: 100%; 65 | padding: 30px; 66 | 67 | transition: border-color .5s ease; 68 | } 69 | 70 | .item__image-container 71 | { 72 | transition: all .3s ease; 73 | 74 | border: 40px solid #000; 75 | outline: 1px solid rgba(255, 255, 255, .08); 76 | } 77 | 78 | .item__image 79 | { 80 | display: block; 81 | 82 | width: 100%; 83 | 84 | outline: 1px solid rgba(255, 255, 255, .08); 85 | } 86 | 87 | .item__text 88 | { 89 | position: relative; 90 | z-index: 10; 91 | 92 | padding: 10px; 93 | 94 | color: #fff; 95 | } 96 | 97 | -------------------------------------------------------------------------------- /dist/box-shadow-4-sides.css: -------------------------------------------------------------------------------- 1 | html, 2 | body 3 | { 4 | font-family: Arial; 5 | font-size: 16px; 6 | 7 | margin: 0; 8 | padding: 0; 9 | 10 | border: 0; 11 | background: black; 12 | } 13 | 14 | .nav 15 | { 16 | position: fixed; 17 | z-index: 10000; 18 | top: 5px; 19 | left: 5px; 20 | padding: 10px; 21 | 22 | background: rgba(0, 0, 0, .5); 23 | 24 | border-radius: 5px; 25 | } 26 | 27 | .back 28 | { 29 | margin-right: 15px; 30 | } 31 | 32 | .prev 33 | { 34 | margin-right: 3px; 35 | } 36 | 37 | .button 38 | { 39 | padding: 5px 15px; 40 | 41 | transition: opacity .3s ease; 42 | text-decoration: none; 43 | 44 | color: #fff; 45 | border-radius: 5px; 46 | background: rgba(255, 255, 255, .3); 47 | 48 | font-size: 12px; 49 | 50 | filter: grayscale(100%); 51 | } 52 | 53 | .button:hover 54 | { 55 | opacity: .5; 56 | } 57 | 58 | img 59 | { 60 | width: 100%; 61 | 62 | transition: all .3s ease; 63 | 64 | border-radius: 50px; 65 | } 66 | 67 | .item 68 | { 69 | float: left; 70 | 71 | box-sizing: border-box; 72 | width: 540px; 73 | max-width: 100%; 74 | padding: 70px; 75 | 76 | transition: background-color .5s ease; 77 | } 78 | 79 | .item__text 80 | { 81 | position: relative; 82 | z-index: 10; 83 | 84 | padding: 10px; 85 | } 86 | 87 | -------------------------------------------------------------------------------- /dist/box-shadow.css: -------------------------------------------------------------------------------- 1 | html, 2 | body 3 | { 4 | font-family: Arial; 5 | font-size: 16px; 6 | 7 | margin: 0; 8 | padding: 0; 9 | 10 | border: 0; 11 | background: black; 12 | } 13 | 14 | .nav 15 | { 16 | position: fixed; 17 | z-index: 10000; 18 | top: 5px; 19 | left: 5px; 20 | padding: 10px; 21 | 22 | background: rgba(0, 0, 0, .5); 23 | 24 | border-radius: 5px; 25 | } 26 | 27 | .back 28 | { 29 | margin-right: 15px; 30 | } 31 | 32 | .prev 33 | { 34 | margin-right: 3px; 35 | } 36 | 37 | .button 38 | { 39 | padding: 5px 15px; 40 | 41 | transition: opacity .3s ease; 42 | text-decoration: none; 43 | 44 | color: #fff; 45 | border-radius: 5px; 46 | background: rgba(255, 255, 255, .3); 47 | 48 | font-size: 12px; 49 | 50 | filter: grayscale(100%); 51 | } 52 | 53 | .button:hover 54 | { 55 | opacity: .5; 56 | } 57 | 58 | img 59 | { 60 | width: 100%; 61 | 62 | transition: all .3s ease; 63 | 64 | border-radius: 50px; 65 | } 66 | 67 | img:hover 68 | { 69 | transform: translateY(10px); 70 | 71 | opacity: .8; 72 | } 73 | 74 | .item 75 | { 76 | float: left; 77 | 78 | box-sizing: border-box; 79 | width: 540px; 80 | max-width: 100%; 81 | padding: 30px 50px; 82 | 83 | transition: background-color .5s ease; 84 | } 85 | 86 | .item__text 87 | { 88 | position: relative; 89 | z-index: 10; 90 | 91 | padding: 10px; 92 | } 93 | 94 | -------------------------------------------------------------------------------- /dist/canvas.css: -------------------------------------------------------------------------------- 1 | html, 2 | body 3 | { 4 | font-family: Arial; 5 | font-size: 16px; 6 | 7 | margin: 0; 8 | padding: 0; 9 | 10 | border: 0; 11 | background: black; 12 | } 13 | 14 | .nav 15 | { 16 | position: fixed; 17 | z-index: 10000; 18 | top: 5px; 19 | left: 5px; 20 | padding: 10px; 21 | 22 | background: rgba(0, 0, 0, .5); 23 | 24 | border-radius: 5px; 25 | } 26 | 27 | .back 28 | { 29 | margin-right: 15px; 30 | } 31 | 32 | .prev 33 | { 34 | margin-right: 3px; 35 | } 36 | 37 | .button 38 | { 39 | padding: 5px 15px; 40 | 41 | transition: opacity .3s ease; 42 | text-decoration: none; 43 | 44 | color: #fff; 45 | border-radius: 5px; 46 | background: rgba(255, 255, 255, .3); 47 | 48 | font-size: 12px; 49 | 50 | filter: grayscale(100%); 51 | } 52 | 53 | .button:hover 54 | { 55 | opacity: .5; 56 | } 57 | 58 | canvas 59 | { 60 | position: fixed; 61 | top: 50%; 62 | left: 50%; 63 | 64 | transform: translate(-50%, -50%); 65 | 66 | border: 1px solid rgba(100, 100, 100, .5); 67 | } 68 | 69 | .info 70 | { 71 | font-family: Arial; 72 | font-size: 16px; 73 | 74 | position: fixed; 75 | z-index: 10; 76 | top: 50px; 77 | left: 50px; 78 | 79 | padding: 20px; 80 | 81 | color: white; 82 | border-radius: 10px; 83 | background: rgba(255, 255, 255, .1); 84 | text-shadow: 1px 1px 2px black; 85 | } 86 | 87 | .info__item 88 | { 89 | overflow-x: hidden; 90 | 91 | width: 250px; 92 | 93 | white-space: nowrap; 94 | text-overflow: ellipsis; 95 | } 96 | 97 | .info__item, .option { 98 | padding: 3px; 99 | } 100 | 101 | .option { 102 | margin: 5px 0 5px 0; 103 | } 104 | 105 | #start { 106 | font-size: 14px; 107 | padding: 10px; 108 | width: 100%; 109 | box-sizing: border-box; 110 | } 111 | 112 | -------------------------------------------------------------------------------- /dist/define.css: -------------------------------------------------------------------------------- 1 | html, 2 | body 3 | { 4 | font-family: Arial; 5 | font-size: 16px; 6 | 7 | margin: 0; 8 | padding: 0; 9 | 10 | border: 0; 11 | background: black; 12 | } 13 | 14 | .nav 15 | { 16 | position: fixed; 17 | z-index: 10000; 18 | top: 5px; 19 | left: 5px; 20 | padding: 10px; 21 | 22 | background: rgba(0, 0, 0, .5); 23 | 24 | border-radius: 5px; 25 | } 26 | 27 | .back 28 | { 29 | margin-right: 15px; 30 | } 31 | 32 | .prev 33 | { 34 | margin-right: 3px; 35 | } 36 | 37 | .button 38 | { 39 | padding: 5px 15px; 40 | 41 | transition: opacity .3s ease; 42 | text-decoration: none; 43 | 44 | color: #fff; 45 | border-radius: 5px; 46 | background: rgba(255, 255, 255, .3); 47 | 48 | font-size: 12px; 49 | 50 | filter: grayscale(100%); 51 | } 52 | 53 | .button:hover 54 | { 55 | opacity: .5; 56 | } 57 | 58 | .select-file-container 59 | { 60 | margin-top: 50px; 61 | padding: 10px; 62 | 63 | text-align: center; 64 | white-space: nowrap; 65 | 66 | color: white; 67 | background: rgba(0, 0, 0, .5); 68 | } 69 | 70 | .images 71 | { 72 | margin-top: 50px; 73 | } 74 | 75 | .images__item 76 | { 77 | margin: 0 25px 25px 25px; 78 | 79 | transition: all .3s linear; 80 | display: inline-block; 81 | 82 | border-radius: 5px; 83 | } 84 | 85 | .images__img-container 86 | { 87 | padding: 25px; 88 | } 89 | 90 | .images__img 91 | { 92 | max-width: 100%; 93 | 94 | box-sizing: border-box; 95 | border: 1px solid gray; 96 | background: url('../images/transparent.png') 0 0 repeat; 97 | } 98 | 99 | .images__title 100 | { 101 | width: 100%; 102 | padding: 10px; 103 | border-bottom: 1px solid rgba(255, 255, 255, .1); 104 | box-sizing: border-box; 105 | 106 | text-overflow: ellipsis; 107 | overflow-x: hidden; 108 | } 109 | 110 | .images__body 111 | { 112 | padding: 10px; 113 | border-bottom: 1px solid rgba(255, 255, 255, .1); 114 | box-sizing: border-box; 115 | } 116 | 117 | .images__algorithm { 118 | padding: 5px; 119 | display: block; 120 | background: url('../images/transparent.png') 0 0 repeat; 121 | border-radius: 5px; 122 | } 123 | 124 | -------------------------------------------------------------------------------- /dist/gallery.css: -------------------------------------------------------------------------------- 1 | html, 2 | body 3 | { 4 | font-family: Arial; 5 | font-size: 16px; 6 | 7 | margin: 0; 8 | padding: 0; 9 | 10 | border: 0; 11 | background: black; 12 | } 13 | 14 | .nav 15 | { 16 | position: fixed; 17 | z-index: 10000; 18 | top: 5px; 19 | left: 5px; 20 | padding: 10px; 21 | 22 | background: rgba(0, 0, 0, .5); 23 | 24 | border-radius: 5px; 25 | } 26 | 27 | .back 28 | { 29 | margin-right: 15px; 30 | } 31 | 32 | .prev 33 | { 34 | margin-right: 3px; 35 | } 36 | 37 | .button 38 | { 39 | padding: 5px 15px; 40 | 41 | transition: opacity .3s ease; 42 | text-decoration: none; 43 | 44 | color: #fff; 45 | border-radius: 5px; 46 | background: rgba(255, 255, 255, .3); 47 | 48 | font-size: 12px; 49 | 50 | filter: grayscale(100%); 51 | } 52 | 53 | .button:hover 54 | { 55 | opacity: .5; 56 | } 57 | 58 | .big-image { 59 | position: fixed; 60 | 61 | left: 50%; 62 | top: 50%; 63 | max-width: 60%; 64 | width: 640px; 65 | height: auto; 66 | 67 | transform: translate(-50%, -50%); 68 | 69 | transition: opacity 1s ease; 70 | opacity: 1; 71 | } 72 | 73 | .big-image_hidden { 74 | opacity: 0; 75 | } 76 | 77 | .big-image-border { 78 | --width: calc(100vw / 2 - 640px / 2 + .5px); 79 | --height: calc(100vh / 2 - 480px / 2 + .5px); 80 | 81 | position: fixed; 82 | 83 | left: 0; 84 | top: 0; 85 | width: 100%; 86 | height: 100%; 87 | box-sizing: border-box; 88 | 89 | border-left: var(--width) solid transparent; 90 | border-right: var(--width) solid transparent; 91 | border-top: var(--height) solid transparent; 92 | border-bottom: var(--height) solid transparent; 93 | 94 | transition: border-color .5s ease; 95 | } 96 | 97 | @media (max-width: 1066px) { 98 | .big-image-border { 99 | outline: 10px solid red; 100 | --width: calc(100vw / 2 - 60vw / 2 + .5px); 101 | --height: calc(100vh / 2 - 45vw / 2 + .5px); 102 | } 103 | } 104 | 105 | .slider { 106 | position: fixed; 107 | 108 | left: 50%; 109 | bottom: 0; 110 | 111 | transform: translateX(-50%); 112 | 113 | white-space: nowrap; 114 | 115 | background: rgba(255, 255, 255, .2); 116 | 117 | border-radius: 5px 5px 0 0; 118 | 119 | line-height: 0; 120 | } 121 | 122 | .slider__item { 123 | border: 0; 124 | 125 | margin: 5px 5px 5px 0; 126 | width: 80px; 127 | height: 60px; 128 | 129 | cursor: pointer; 130 | 131 | transition: opacity .3s ease; 132 | } 133 | 134 | .slider__item_active { 135 | outline: 5px solid orange; 136 | } 137 | 138 | .slider__item_first { 139 | margin-left: 5px; 140 | } 141 | 142 | .slider__item_last { 143 | border-radius: 0 5px 0 0; 144 | } 145 | 146 | .slider__item:hover { 147 | opacity: .6; 148 | } 149 | 150 | .slider__item_active:hover { 151 | opacity: 1; 152 | } 153 | 154 | -------------------------------------------------------------------------------- /dist/gallery_vertical.css: -------------------------------------------------------------------------------- 1 | html, 2 | body 3 | { 4 | font-family: Arial; 5 | font-size: 16px; 6 | 7 | margin: 0; 8 | padding: 0; 9 | 10 | border: 0; 11 | background: black; 12 | } 13 | 14 | .nav 15 | { 16 | position: fixed; 17 | z-index: 10000; 18 | top: 5px; 19 | left: 5px; 20 | padding: 10px; 21 | 22 | background: rgba(0, 0, 0, .5); 23 | 24 | border-radius: 5px; 25 | } 26 | 27 | .back 28 | { 29 | margin-right: 15px; 30 | } 31 | 32 | .prev 33 | { 34 | margin-right: 3px; 35 | } 36 | 37 | .button 38 | { 39 | padding: 5px 15px; 40 | 41 | transition: opacity .3s ease; 42 | text-decoration: none; 43 | 44 | color: #fff; 45 | border-radius: 5px; 46 | background: rgba(255, 255, 255, .3); 47 | 48 | font-size: 12px; 49 | 50 | filter: grayscale(100%); 51 | } 52 | 53 | .button:hover 54 | { 55 | opacity: .5; 56 | } 57 | 58 | .big-image-container { 59 | position: fixed; 60 | 61 | left: 50%; 62 | top: 50%; 63 | width: 640px; 64 | height: auto; 65 | 66 | transform: translate(-50%, -50%); 67 | 68 | transition: opacity 1s ease; 69 | opacity: 1; 70 | } 71 | 72 | .big-image { 73 | display: block; 74 | width: 100%; 75 | height: auto; 76 | } 77 | 78 | .vertical-gradient { 79 | position: absolute; 80 | 81 | left: 0; 82 | top: 0; 83 | width: 50px; 84 | height: 100%; 85 | margin-left: -60px; 86 | } 87 | 88 | .horizontal-gradient { 89 | position: absolute; 90 | 91 | left: 0; 92 | top: 0; 93 | width: 100%; 94 | height: 50px; 95 | margin-top: -60px; 96 | } 97 | 98 | .big-image_hidden { 99 | opacity: 0; 100 | } 101 | 102 | .slider { 103 | position: fixed; 104 | 105 | left: 50%; 106 | bottom: 0; 107 | 108 | transform: translateX(-50%); 109 | 110 | white-space: nowrap; 111 | 112 | background: rgba(255, 255, 255, .2); 113 | 114 | border-radius: 5px 5px 0 0; 115 | 116 | line-height: 0; 117 | } 118 | 119 | .slider__item { 120 | border: 0; 121 | 122 | margin: 5px 5px 5px 0; 123 | width: 80px; 124 | height: 60px; 125 | 126 | cursor: pointer; 127 | 128 | transition: opacity .3s ease; 129 | } 130 | 131 | .slider__item_active { 132 | outline: 5px solid orange; 133 | } 134 | 135 | .slider__item_first { 136 | margin-left: 5px; 137 | } 138 | 139 | .slider__item_last { 140 | border-radius: 0 5px 0 0; 141 | } 142 | 143 | .slider__item:hover { 144 | opacity: .6; 145 | } 146 | 147 | .slider__item_active:hover { 148 | opacity: 1; 149 | } 150 | 151 | -------------------------------------------------------------------------------- /dist/gradient.css: -------------------------------------------------------------------------------- 1 | html, 2 | body 3 | { 4 | font-family: Arial; 5 | font-size: 16px; 6 | 7 | margin: 0; 8 | padding: 0; 9 | 10 | border: 0; 11 | background: black; 12 | } 13 | 14 | .nav 15 | { 16 | position: fixed; 17 | z-index: 10000; 18 | top: 5px; 19 | left: 5px; 20 | padding: 10px; 21 | 22 | background: rgba(0, 0, 0, .5); 23 | 24 | border-radius: 5px; 25 | } 26 | 27 | .back 28 | { 29 | margin-right: 15px; 30 | } 31 | 32 | .prev 33 | { 34 | margin-right: 3px; 35 | } 36 | 37 | .button 38 | { 39 | padding: 5px 15px; 40 | 41 | transition: opacity .3s ease; 42 | text-decoration: none; 43 | 44 | color: #fff; 45 | border-radius: 5px; 46 | background: rgba(255, 255, 255, .3); 47 | 48 | font-size: 12px; 49 | 50 | filter: grayscale(100%); 51 | } 52 | 53 | .button:hover 54 | { 55 | opacity: .5; 56 | } 57 | 58 | .item 59 | { 60 | position: relative; 61 | 62 | float: left; 63 | 64 | box-sizing: border-box; 65 | width: 400px; 66 | max-width: 100%; 67 | margin: 1px 1px 0 0; 68 | 69 | transition: all .5s ease; 70 | } 71 | 72 | .item__image 73 | { 74 | position: relative; 75 | } 76 | 77 | img 78 | { 79 | display: block; 80 | 81 | width: 100%; 82 | } 83 | 84 | .item__gradient 85 | { 86 | position: absolute; 87 | z-index: 1; 88 | top: 0; 89 | right: 0; 90 | bottom: 50%; 91 | left: 0; 92 | 93 | transition: all .5s ease; 94 | } 95 | 96 | .item_bottom .item__gradient 97 | { 98 | top: 50%; 99 | bottom: 0; 100 | } 101 | 102 | 103 | .item__text 104 | { 105 | padding: 20px; 106 | } 107 | 108 | -------------------------------------------------------------------------------- /dist/gradient_stripes.css: -------------------------------------------------------------------------------- 1 | html, 2 | body 3 | { 4 | font-family: Arial; 5 | font-size: 16px; 6 | 7 | margin: 0; 8 | padding: 0; 9 | 10 | border: 0; 11 | background: black; 12 | } 13 | 14 | .nav 15 | { 16 | position: fixed; 17 | z-index: 10000; 18 | top: 5px; 19 | left: 5px; 20 | padding: 10px; 21 | 22 | background: rgba(0, 0, 0, .5); 23 | 24 | border-radius: 5px; 25 | } 26 | 27 | .back 28 | { 29 | margin-right: 15px; 30 | } 31 | 32 | .prev 33 | { 34 | margin-right: 3px; 35 | } 36 | 37 | .button 38 | { 39 | padding: 5px 15px; 40 | 41 | transition: opacity .3s ease; 42 | text-decoration: none; 43 | 44 | color: #fff; 45 | border-radius: 5px; 46 | background: rgba(255, 255, 255, .3); 47 | 48 | font-size: 12px; 49 | 50 | filter: grayscale(100%); 51 | } 52 | 53 | .button:hover 54 | { 55 | opacity: .5; 56 | } 57 | 58 | .items 59 | { 60 | box-sizing: border-box; 61 | padding: 20px; 62 | } 63 | 64 | .item 65 | { 66 | position: relative; 67 | 68 | float: left; 69 | 70 | box-sizing: border-box; 71 | width: 460px; 72 | max-width: 100%; 73 | margin: 0 20px 20px 0; 74 | padding: 30px; 75 | 76 | transition: all .5s ease; 77 | } 78 | 79 | img 80 | { 81 | display: block; 82 | 83 | width: 100%; 84 | } 85 | 86 | .item__text 87 | { 88 | padding: 10px 0 10px 0; 89 | } 90 | 91 | -------------------------------------------------------------------------------- /dist/text-photo.css: -------------------------------------------------------------------------------- 1 | html, 2 | body 3 | { 4 | font-family: Arial; 5 | font-size: 16px; 6 | 7 | margin: 0; 8 | padding: 0; 9 | 10 | border: 0; 11 | background: black; 12 | } 13 | 14 | .nav 15 | { 16 | position: fixed; 17 | z-index: 10000; 18 | top: 5px; 19 | left: 5px; 20 | padding: 10px; 21 | 22 | background: rgba(0, 0, 0, .5); 23 | 24 | border-radius: 5px; 25 | } 26 | 27 | .back 28 | { 29 | margin-right: 15px; 30 | } 31 | 32 | .prev 33 | { 34 | margin-right: 3px; 35 | } 36 | 37 | .button 38 | { 39 | padding: 5px 15px; 40 | 41 | transition: opacity .3s ease; 42 | text-decoration: none; 43 | 44 | color: #fff; 45 | border-radius: 5px; 46 | background: rgba(255, 255, 255, .3); 47 | 48 | font-size: 12px; 49 | 50 | filter: grayscale(100%); 51 | } 52 | 53 | .button:hover 54 | { 55 | opacity: .5; 56 | } 57 | 58 | .controls 59 | { 60 | font-size: 11px; 61 | line-height: 14px; 62 | 63 | position: fixed; 64 | z-index: 100; 65 | top: 70px; 66 | left: 10px; 67 | 68 | margin: 0; 69 | padding: 0; 70 | 71 | color: #999; 72 | } 73 | 74 | .controls label 75 | { 76 | display: block; 77 | } 78 | 79 | .controls input 80 | { 81 | font-size: 11px; 82 | line-height: 14px; 83 | } 84 | 85 | .big-photo, 86 | .text-photo 87 | { 88 | position: fixed; 89 | top: 50%; 90 | left: 50%; 91 | 92 | transform: translate(-50%, -50%); 93 | } 94 | 95 | @media (max-width: 900px) and (min-width: 800px) 96 | { 97 | .big-photo, 98 | .text-photo 99 | { 100 | transform: translate(-50%, -50%) scale(.9); 101 | } 102 | } 103 | 104 | @media (max-width: 800px) and (min-width: 700px) 105 | { 106 | .big-photo, 107 | .text-photo 108 | { 109 | transform: translate(-50%, -50%) scale(.8); 110 | } 111 | } 112 | 113 | @media (max-width: 700px) and (min-width: 600px) 114 | { 115 | .big-photo, 116 | .text-photo 117 | { 118 | transform: translate(-50%, -50%) scale(.7); 119 | } 120 | } 121 | 122 | @media (max-width: 600px) 123 | { 124 | .big-photo, 125 | .text-photo 126 | { 127 | transform: translate(-50%, -50%) scale(.6); 128 | } 129 | } 130 | 131 | .text-photo 132 | { 133 | visibility: visible; 134 | 135 | opacity: 0; 136 | } 137 | 138 | .big-photo_fade 139 | { 140 | transition: opacity 2s ease; 141 | 142 | opacity: 0; 143 | } 144 | 145 | .big-photo_load 146 | { 147 | visibility: hidden; 148 | } 149 | 150 | .text-photo_fade 151 | { 152 | transition: opacity 2s ease; 153 | 154 | opacity: 1; 155 | } 156 | 157 | -------------------------------------------------------------------------------- /dist/timeline.css: -------------------------------------------------------------------------------- 1 | html, 2 | body 3 | { 4 | font-family: Arial; 5 | font-size: 16px; 6 | 7 | margin: 0; 8 | padding: 0; 9 | 10 | border: 0; 11 | background: black; 12 | } 13 | 14 | .nav 15 | { 16 | position: fixed; 17 | z-index: 10000; 18 | top: 5px; 19 | left: 5px; 20 | padding: 10px; 21 | 22 | background: rgba(0, 0, 0, .5); 23 | 24 | border-radius: 5px; 25 | } 26 | 27 | .back 28 | { 29 | margin-right: 15px; 30 | } 31 | 32 | .prev 33 | { 34 | margin-right: 3px; 35 | } 36 | 37 | .button 38 | { 39 | padding: 5px 15px; 40 | 41 | transition: opacity .3s ease; 42 | text-decoration: none; 43 | 44 | color: #fff; 45 | border-radius: 5px; 46 | background: rgba(255, 255, 255, .3); 47 | 48 | font-size: 12px; 49 | 50 | filter: grayscale(100%); 51 | } 52 | 53 | .button:hover 54 | { 55 | opacity: .5; 56 | } 57 | 58 | body { 59 | background: #000; 60 | color: #fff; 61 | 62 | padding: 10px; 63 | } 64 | 65 | .title-container { 66 | position: absolute; 67 | left: 0; 68 | right: 0; 69 | top: 10px; 70 | text-align: center; 71 | } 72 | 73 | .title { 74 | display: inline-block; 75 | margin: 0 auto; 76 | padding: 0; 77 | color: #fff; 78 | font-size: 30px; 79 | font-weight: normal; 80 | line-height: 30px; 81 | background: linear-gradient(90deg, #555, #fff); 82 | background-clip: text; 83 | -webkit-background-clip: text; 84 | -webkit-text-fill-color: transparent; 85 | white-space: nowrap; 86 | } 87 | 88 | .spinner, 89 | .spinner:after { 90 | border-radius: 50%; 91 | width: 50px; 92 | height: 50px; 93 | } 94 | 95 | .spinner { 96 | position: fixed; 97 | left: 50%; 98 | top: 50%; 99 | transform: translate(-50%, -50%); 100 | border-top: 1.1em solid rgba(255, 255, 255, 0.2); 101 | border-right: 1.1em solid rgba(255, 255, 255, 0.2); 102 | border-bottom: 1.1em solid rgba(255, 255, 255, 0.2); 103 | border-left: 1.1em solid #ffffff; 104 | transform: translateZ(0); 105 | animation: spinner 1.1s infinite linear; 106 | } 107 | 108 | @keyframes spinner { 109 | 0% { 110 | transform: rotate(0deg); 111 | } 112 | 100% { 113 | transform: rotate(360deg); 114 | } 115 | } 116 | 117 | .timeline { 118 | margin: 0 0 10px 0; 119 | } 120 | 121 | .timeline__label { 122 | font-size: 12px; 123 | color: #ccc; 124 | } 125 | 126 | .timeline__colors { 127 | width: auto; 128 | line-height: 0; 129 | font-size: 0; 130 | border: 2px solid rgba(255,255,255,0.1); 131 | } 132 | 133 | .timeline__color { 134 | display: inline-block; 135 | width: 1px; 136 | height: 50px; 137 | } 138 | 139 | .progress { 140 | position: fixed; 141 | top: 10px; 142 | right: 10px; 143 | text-align: right; 144 | } 145 | 146 | .demo { 147 | text-align: center; 148 | } 149 | 150 | .radial-demo, 151 | .conic-demo { 152 | display: inline-block; 153 | width: 48vw; 154 | height: 48vw; 155 | margin-right: 10px; 156 | } 157 | 158 | video { 159 | position: fixed; 160 | right: 10px; 161 | bottom: 10px; 162 | border-radius: 10px; 163 | max-width: 400px; 164 | } 165 | 166 | .movie-selector { 167 | margin-top: 50px; 168 | margin-bottom: 10px; 169 | } 170 | 171 | -------------------------------------------------------------------------------- /gallery.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Get average color for gallery 5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 |
36 | 37 | 38 | -------------------------------------------------------------------------------- /gallery_vertical.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Get vertical or horizontal average color for gallery 5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 |
13 |
14 | 15 |
16 |
39 | 40 | 41 | -------------------------------------------------------------------------------- /gradient.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Average color: Gradient 5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 |
13 | 14 |
15 |
16 |
17 | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. 18 |
19 |
20 | 21 |
22 |
23 | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. 24 |
25 |
26 | 27 |
28 |
29 |
30 | 31 |
32 |
33 | 34 |
35 |
36 |
37 | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. 38 |
39 |
40 | 41 |
42 |
43 | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. 44 |
45 |
46 | 47 |
48 |
49 |
50 | 51 |
52 |
53 | 54 |
55 |
56 |
57 | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. 58 |
59 |
60 | 61 |
62 |
63 | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. 64 |
65 |
66 | 67 |
68 |
69 |
70 | 71 | 72 | -------------------------------------------------------------------------------- /gradient_stripes.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Get average color for Minecraft gradient 5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 |
13 | 14 |
15 | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. 16 |
17 |
18 | 19 |
20 | 21 |
22 | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. 23 |
24 |
25 | 26 |
27 | 28 |
29 | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. 30 |
31 |
32 | 33 |
34 | 35 |
36 | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. 37 |
38 |
39 | 40 |
41 | 42 |
43 | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. 44 |
45 |
46 | 47 |
48 | 49 |
50 | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. 51 |
52 |
53 |
54 | 55 | 56 | -------------------------------------------------------------------------------- /images/1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fast-average-color/examples/96b6a7d45fa4d1bc335011f1e4cedad0457c65ac/images/1.jpg -------------------------------------------------------------------------------- /images/10.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fast-average-color/examples/96b6a7d45fa4d1bc335011f1e4cedad0457c65ac/images/10.jpg -------------------------------------------------------------------------------- /images/11.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fast-average-color/examples/96b6a7d45fa4d1bc335011f1e4cedad0457c65ac/images/11.jpg -------------------------------------------------------------------------------- /images/12.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fast-average-color/examples/96b6a7d45fa4d1bc335011f1e4cedad0457c65ac/images/12.jpg -------------------------------------------------------------------------------- /images/13.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fast-average-color/examples/96b6a7d45fa4d1bc335011f1e4cedad0457c65ac/images/13.jpg -------------------------------------------------------------------------------- /images/14.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fast-average-color/examples/96b6a7d45fa4d1bc335011f1e4cedad0457c65ac/images/14.jpg -------------------------------------------------------------------------------- /images/15.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fast-average-color/examples/96b6a7d45fa4d1bc335011f1e4cedad0457c65ac/images/15.jpg -------------------------------------------------------------------------------- /images/16.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fast-average-color/examples/96b6a7d45fa4d1bc335011f1e4cedad0457c65ac/images/16.jpg -------------------------------------------------------------------------------- /images/17.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fast-average-color/examples/96b6a7d45fa4d1bc335011f1e4cedad0457c65ac/images/17.jpg -------------------------------------------------------------------------------- /images/18.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fast-average-color/examples/96b6a7d45fa4d1bc335011f1e4cedad0457c65ac/images/18.jpg -------------------------------------------------------------------------------- /images/19.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fast-average-color/examples/96b6a7d45fa4d1bc335011f1e4cedad0457c65ac/images/19.jpg -------------------------------------------------------------------------------- /images/2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fast-average-color/examples/96b6a7d45fa4d1bc335011f1e4cedad0457c65ac/images/2.jpg -------------------------------------------------------------------------------- /images/20.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fast-average-color/examples/96b6a7d45fa4d1bc335011f1e4cedad0457c65ac/images/20.jpg -------------------------------------------------------------------------------- /images/21.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fast-average-color/examples/96b6a7d45fa4d1bc335011f1e4cedad0457c65ac/images/21.jpg -------------------------------------------------------------------------------- /images/22.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fast-average-color/examples/96b6a7d45fa4d1bc335011f1e4cedad0457c65ac/images/22.jpg -------------------------------------------------------------------------------- /images/23.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fast-average-color/examples/96b6a7d45fa4d1bc335011f1e4cedad0457c65ac/images/23.jpg -------------------------------------------------------------------------------- /images/24.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fast-average-color/examples/96b6a7d45fa4d1bc335011f1e4cedad0457c65ac/images/24.jpg -------------------------------------------------------------------------------- /images/25.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fast-average-color/examples/96b6a7d45fa4d1bc335011f1e4cedad0457c65ac/images/25.jpg -------------------------------------------------------------------------------- /images/26.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fast-average-color/examples/96b6a7d45fa4d1bc335011f1e4cedad0457c65ac/images/26.jpg -------------------------------------------------------------------------------- /images/27.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fast-average-color/examples/96b6a7d45fa4d1bc335011f1e4cedad0457c65ac/images/27.jpg -------------------------------------------------------------------------------- /images/3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fast-average-color/examples/96b6a7d45fa4d1bc335011f1e4cedad0457c65ac/images/3.jpg -------------------------------------------------------------------------------- /images/4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fast-average-color/examples/96b6a7d45fa4d1bc335011f1e4cedad0457c65ac/images/4.jpg -------------------------------------------------------------------------------- /images/5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fast-average-color/examples/96b6a7d45fa4d1bc335011f1e4cedad0457c65ac/images/5.jpg -------------------------------------------------------------------------------- /images/6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fast-average-color/examples/96b6a7d45fa4d1bc335011f1e4cedad0457c65ac/images/6.jpg -------------------------------------------------------------------------------- /images/7.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fast-average-color/examples/96b6a7d45fa4d1bc335011f1e4cedad0457c65ac/images/7.jpg -------------------------------------------------------------------------------- /images/8.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fast-average-color/examples/96b6a7d45fa4d1bc335011f1e4cedad0457c65ac/images/8.jpg -------------------------------------------------------------------------------- /images/9.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fast-average-color/examples/96b6a7d45fa4d1bc335011f1e4cedad0457c65ac/images/9.jpg -------------------------------------------------------------------------------- /images/chrome.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fast-average-color/examples/96b6a7d45fa4d1bc335011f1e4cedad0457c65ac/images/chrome.jpg -------------------------------------------------------------------------------- /images/firefox.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fast-average-color/examples/96b6a7d45fa4d1bc335011f1e4cedad0457c65ac/images/firefox.jpg -------------------------------------------------------------------------------- /images/ie.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fast-average-color/examples/96b6a7d45fa4d1bc335011f1e4cedad0457c65ac/images/ie.jpg -------------------------------------------------------------------------------- /images/opera.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fast-average-color/examples/96b6a7d45fa4d1bc335011f1e4cedad0457c65ac/images/opera.jpg -------------------------------------------------------------------------------- /images/transparent.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fast-average-color/examples/96b6a7d45fa4d1bc335011f1e4cedad0457c65ac/images/transparent.png -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "fast-average-color-examples", 3 | "version": "1.0.0", 4 | "lockfileVersion": 3, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "fast-average-color-examples", 9 | "version": "1.0.0", 10 | "license": "MIT", 11 | "devDependencies": { 12 | "@rollup/plugin-node-resolve": "^16.0.1", 13 | "@rollup/plugin-typescript": "^12.1.2", 14 | "fast-average-color": "^9.5.0", 15 | "lyam": "^3.2.1", 16 | "rollup": "^4.36.0", 17 | "rollup-plugin-css-only": "^4.5.2", 18 | "tslib": "^2.8.1", 19 | "typescript": "^5.8.2" 20 | } 21 | }, 22 | "node_modules/@rollup/plugin-node-resolve": { 23 | "version": "16.0.1", 24 | "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-16.0.1.tgz", 25 | "integrity": "sha512-tk5YCxJWIG81umIvNkSod2qK5KyQW19qcBF/B78n1bjtOON6gzKoVeSzAE8yHCZEDmqkHKkxplExA8KzdJLJpA==", 26 | "dev": true, 27 | "license": "MIT", 28 | "dependencies": { 29 | "@rollup/pluginutils": "^5.0.1", 30 | "@types/resolve": "1.20.2", 31 | "deepmerge": "^4.2.2", 32 | "is-module": "^1.0.0", 33 | "resolve": "^1.22.1" 34 | }, 35 | "engines": { 36 | "node": ">=14.0.0" 37 | }, 38 | "peerDependencies": { 39 | "rollup": "^2.78.0||^3.0.0||^4.0.0" 40 | }, 41 | "peerDependenciesMeta": { 42 | "rollup": { 43 | "optional": true 44 | } 45 | } 46 | }, 47 | "node_modules/@rollup/plugin-typescript": { 48 | "version": "12.1.2", 49 | "resolved": "https://registry.npmjs.org/@rollup/plugin-typescript/-/plugin-typescript-12.1.2.tgz", 50 | "integrity": "sha512-cdtSp154H5sv637uMr1a8OTWB0L1SWDSm1rDGiyfcGcvQ6cuTs4MDk2BVEBGysUWago4OJN4EQZqOTl/QY3Jgg==", 51 | "dev": true, 52 | "license": "MIT", 53 | "dependencies": { 54 | "@rollup/pluginutils": "^5.1.0", 55 | "resolve": "^1.22.1" 56 | }, 57 | "engines": { 58 | "node": ">=14.0.0" 59 | }, 60 | "peerDependencies": { 61 | "rollup": "^2.14.0||^3.0.0||^4.0.0", 62 | "tslib": "*", 63 | "typescript": ">=3.7.0" 64 | }, 65 | "peerDependenciesMeta": { 66 | "rollup": { 67 | "optional": true 68 | }, 69 | "tslib": { 70 | "optional": true 71 | } 72 | } 73 | }, 74 | "node_modules/@rollup/pluginutils": { 75 | "version": "5.1.4", 76 | "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.1.4.tgz", 77 | "integrity": "sha512-USm05zrsFxYLPdWWq+K3STlWiT/3ELn3RcV5hJMghpeAIhxfsUIg6mt12CBJBInWMV4VneoV7SfGv8xIwo2qNQ==", 78 | "dev": true, 79 | "license": "MIT", 80 | "dependencies": { 81 | "@types/estree": "^1.0.0", 82 | "estree-walker": "^2.0.2", 83 | "picomatch": "^4.0.2" 84 | }, 85 | "engines": { 86 | "node": ">=14.0.0" 87 | }, 88 | "peerDependencies": { 89 | "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" 90 | }, 91 | "peerDependenciesMeta": { 92 | "rollup": { 93 | "optional": true 94 | } 95 | } 96 | }, 97 | "node_modules/@rollup/rollup-android-arm-eabi": { 98 | "version": "4.36.0", 99 | "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.36.0.tgz", 100 | "integrity": "sha512-jgrXjjcEwN6XpZXL0HUeOVGfjXhPyxAbbhD0BlXUB+abTOpbPiN5Wb3kOT7yb+uEtATNYF5x5gIfwutmuBA26w==", 101 | "cpu": [ 102 | "arm" 103 | ], 104 | "dev": true, 105 | "license": "MIT", 106 | "optional": true, 107 | "os": [ 108 | "android" 109 | ] 110 | }, 111 | "node_modules/@rollup/rollup-android-arm64": { 112 | "version": "4.36.0", 113 | "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.36.0.tgz", 114 | "integrity": "sha512-NyfuLvdPdNUfUNeYKUwPwKsE5SXa2J6bCt2LdB/N+AxShnkpiczi3tcLJrm5mA+eqpy0HmaIY9F6XCa32N5yzg==", 115 | "cpu": [ 116 | "arm64" 117 | ], 118 | "dev": true, 119 | "license": "MIT", 120 | "optional": true, 121 | "os": [ 122 | "android" 123 | ] 124 | }, 125 | "node_modules/@rollup/rollup-darwin-arm64": { 126 | "version": "4.36.0", 127 | "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.36.0.tgz", 128 | "integrity": "sha512-JQ1Jk5G4bGrD4pWJQzWsD8I1n1mgPXq33+/vP4sk8j/z/C2siRuxZtaUA7yMTf71TCZTZl/4e1bfzwUmFb3+rw==", 129 | "cpu": [ 130 | "arm64" 131 | ], 132 | "dev": true, 133 | "license": "MIT", 134 | "optional": true, 135 | "os": [ 136 | "darwin" 137 | ] 138 | }, 139 | "node_modules/@rollup/rollup-darwin-x64": { 140 | "version": "4.36.0", 141 | "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.36.0.tgz", 142 | "integrity": "sha512-6c6wMZa1lrtiRsbDziCmjE53YbTkxMYhhnWnSW8R/yqsM7a6mSJ3uAVT0t8Y/DGt7gxUWYuFM4bwWk9XCJrFKA==", 143 | "cpu": [ 144 | "x64" 145 | ], 146 | "dev": true, 147 | "license": "MIT", 148 | "optional": true, 149 | "os": [ 150 | "darwin" 151 | ] 152 | }, 153 | "node_modules/@rollup/rollup-freebsd-arm64": { 154 | "version": "4.36.0", 155 | "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.36.0.tgz", 156 | "integrity": "sha512-KXVsijKeJXOl8QzXTsA+sHVDsFOmMCdBRgFmBb+mfEb/7geR7+C8ypAml4fquUt14ZyVXaw2o1FWhqAfOvA4sg==", 157 | "cpu": [ 158 | "arm64" 159 | ], 160 | "dev": true, 161 | "license": "MIT", 162 | "optional": true, 163 | "os": [ 164 | "freebsd" 165 | ] 166 | }, 167 | "node_modules/@rollup/rollup-freebsd-x64": { 168 | "version": "4.36.0", 169 | "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.36.0.tgz", 170 | "integrity": "sha512-dVeWq1ebbvByI+ndz4IJcD4a09RJgRYmLccwlQ8bPd4olz3Y213uf1iwvc7ZaxNn2ab7bjc08PrtBgMu6nb4pQ==", 171 | "cpu": [ 172 | "x64" 173 | ], 174 | "dev": true, 175 | "license": "MIT", 176 | "optional": true, 177 | "os": [ 178 | "freebsd" 179 | ] 180 | }, 181 | "node_modules/@rollup/rollup-linux-arm-gnueabihf": { 182 | "version": "4.36.0", 183 | "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.36.0.tgz", 184 | "integrity": "sha512-bvXVU42mOVcF4le6XSjscdXjqx8okv4n5vmwgzcmtvFdifQ5U4dXFYaCB87namDRKlUL9ybVtLQ9ztnawaSzvg==", 185 | "cpu": [ 186 | "arm" 187 | ], 188 | "dev": true, 189 | "license": "MIT", 190 | "optional": true, 191 | "os": [ 192 | "linux" 193 | ] 194 | }, 195 | "node_modules/@rollup/rollup-linux-arm-musleabihf": { 196 | "version": "4.36.0", 197 | "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.36.0.tgz", 198 | "integrity": "sha512-JFIQrDJYrxOnyDQGYkqnNBtjDwTgbasdbUiQvcU8JmGDfValfH1lNpng+4FWlhaVIR4KPkeddYjsVVbmJYvDcg==", 199 | "cpu": [ 200 | "arm" 201 | ], 202 | "dev": true, 203 | "license": "MIT", 204 | "optional": true, 205 | "os": [ 206 | "linux" 207 | ] 208 | }, 209 | "node_modules/@rollup/rollup-linux-arm64-gnu": { 210 | "version": "4.36.0", 211 | "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.36.0.tgz", 212 | "integrity": "sha512-KqjYVh3oM1bj//5X7k79PSCZ6CvaVzb7Qs7VMWS+SlWB5M8p3FqufLP9VNp4CazJ0CsPDLwVD9r3vX7Ci4J56A==", 213 | "cpu": [ 214 | "arm64" 215 | ], 216 | "dev": true, 217 | "license": "MIT", 218 | "optional": true, 219 | "os": [ 220 | "linux" 221 | ] 222 | }, 223 | "node_modules/@rollup/rollup-linux-arm64-musl": { 224 | "version": "4.36.0", 225 | "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.36.0.tgz", 226 | "integrity": "sha512-QiGnhScND+mAAtfHqeT+cB1S9yFnNQ/EwCg5yE3MzoaZZnIV0RV9O5alJAoJKX/sBONVKeZdMfO8QSaWEygMhw==", 227 | "cpu": [ 228 | "arm64" 229 | ], 230 | "dev": true, 231 | "license": "MIT", 232 | "optional": true, 233 | "os": [ 234 | "linux" 235 | ] 236 | }, 237 | "node_modules/@rollup/rollup-linux-loongarch64-gnu": { 238 | "version": "4.36.0", 239 | "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.36.0.tgz", 240 | "integrity": "sha512-1ZPyEDWF8phd4FQtTzMh8FQwqzvIjLsl6/84gzUxnMNFBtExBtpL51H67mV9xipuxl1AEAerRBgBwFNpkw8+Lg==", 241 | "cpu": [ 242 | "loong64" 243 | ], 244 | "dev": true, 245 | "license": "MIT", 246 | "optional": true, 247 | "os": [ 248 | "linux" 249 | ] 250 | }, 251 | "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { 252 | "version": "4.36.0", 253 | "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.36.0.tgz", 254 | "integrity": "sha512-VMPMEIUpPFKpPI9GZMhJrtu8rxnp6mJR3ZzQPykq4xc2GmdHj3Q4cA+7avMyegXy4n1v+Qynr9fR88BmyO74tg==", 255 | "cpu": [ 256 | "ppc64" 257 | ], 258 | "dev": true, 259 | "license": "MIT", 260 | "optional": true, 261 | "os": [ 262 | "linux" 263 | ] 264 | }, 265 | "node_modules/@rollup/rollup-linux-riscv64-gnu": { 266 | "version": "4.36.0", 267 | "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.36.0.tgz", 268 | "integrity": "sha512-ttE6ayb/kHwNRJGYLpuAvB7SMtOeQnVXEIpMtAvx3kepFQeowVED0n1K9nAdraHUPJ5hydEMxBpIR7o4nrm8uA==", 269 | "cpu": [ 270 | "riscv64" 271 | ], 272 | "dev": true, 273 | "license": "MIT", 274 | "optional": true, 275 | "os": [ 276 | "linux" 277 | ] 278 | }, 279 | "node_modules/@rollup/rollup-linux-s390x-gnu": { 280 | "version": "4.36.0", 281 | "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.36.0.tgz", 282 | "integrity": "sha512-4a5gf2jpS0AIe7uBjxDeUMNcFmaRTbNv7NxI5xOCs4lhzsVyGR/0qBXduPnoWf6dGC365saTiwag8hP1imTgag==", 283 | "cpu": [ 284 | "s390x" 285 | ], 286 | "dev": true, 287 | "license": "MIT", 288 | "optional": true, 289 | "os": [ 290 | "linux" 291 | ] 292 | }, 293 | "node_modules/@rollup/rollup-linux-x64-gnu": { 294 | "version": "4.36.0", 295 | "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.36.0.tgz", 296 | "integrity": "sha512-5KtoW8UWmwFKQ96aQL3LlRXX16IMwyzMq/jSSVIIyAANiE1doaQsx/KRyhAvpHlPjPiSU/AYX/8m+lQ9VToxFQ==", 297 | "cpu": [ 298 | "x64" 299 | ], 300 | "dev": true, 301 | "license": "MIT", 302 | "optional": true, 303 | "os": [ 304 | "linux" 305 | ] 306 | }, 307 | "node_modules/@rollup/rollup-linux-x64-musl": { 308 | "version": "4.36.0", 309 | "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.36.0.tgz", 310 | "integrity": "sha512-sycrYZPrv2ag4OCvaN5js+f01eoZ2U+RmT5as8vhxiFz+kxwlHrsxOwKPSA8WyS+Wc6Epid9QeI/IkQ9NkgYyQ==", 311 | "cpu": [ 312 | "x64" 313 | ], 314 | "dev": true, 315 | "license": "MIT", 316 | "optional": true, 317 | "os": [ 318 | "linux" 319 | ] 320 | }, 321 | "node_modules/@rollup/rollup-win32-arm64-msvc": { 322 | "version": "4.36.0", 323 | "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.36.0.tgz", 324 | "integrity": "sha512-qbqt4N7tokFwwSVlWDsjfoHgviS3n/vZ8LK0h1uLG9TYIRuUTJC88E1xb3LM2iqZ/WTqNQjYrtmtGmrmmawB6A==", 325 | "cpu": [ 326 | "arm64" 327 | ], 328 | "dev": true, 329 | "license": "MIT", 330 | "optional": true, 331 | "os": [ 332 | "win32" 333 | ] 334 | }, 335 | "node_modules/@rollup/rollup-win32-ia32-msvc": { 336 | "version": "4.36.0", 337 | "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.36.0.tgz", 338 | "integrity": "sha512-t+RY0JuRamIocMuQcfwYSOkmdX9dtkr1PbhKW42AMvaDQa+jOdpUYysroTF/nuPpAaQMWp7ye+ndlmmthieJrQ==", 339 | "cpu": [ 340 | "ia32" 341 | ], 342 | "dev": true, 343 | "license": "MIT", 344 | "optional": true, 345 | "os": [ 346 | "win32" 347 | ] 348 | }, 349 | "node_modules/@rollup/rollup-win32-x64-msvc": { 350 | "version": "4.36.0", 351 | "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.36.0.tgz", 352 | "integrity": "sha512-aRXd7tRZkWLqGbChgcMMDEHjOKudo1kChb1Jt1IfR8cY/KIpgNviLeJy5FUb9IpSuQj8dU2fAYNMPW/hLKOSTw==", 353 | "cpu": [ 354 | "x64" 355 | ], 356 | "dev": true, 357 | "license": "MIT", 358 | "optional": true, 359 | "os": [ 360 | "win32" 361 | ] 362 | }, 363 | "node_modules/@types/estree": { 364 | "version": "1.0.6", 365 | "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", 366 | "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", 367 | "dev": true, 368 | "license": "MIT" 369 | }, 370 | "node_modules/@types/resolve": { 371 | "version": "1.20.2", 372 | "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.20.2.tgz", 373 | "integrity": "sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==", 374 | "dev": true, 375 | "license": "MIT" 376 | }, 377 | "node_modules/deepmerge": { 378 | "version": "4.3.1", 379 | "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", 380 | "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", 381 | "dev": true, 382 | "license": "MIT", 383 | "engines": { 384 | "node": ">=0.10.0" 385 | } 386 | }, 387 | "node_modules/estree-walker": { 388 | "version": "2.0.2", 389 | "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", 390 | "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", 391 | "dev": true, 392 | "license": "MIT" 393 | }, 394 | "node_modules/fast-average-color": { 395 | "version": "9.5.0", 396 | "resolved": "https://registry.npmjs.org/fast-average-color/-/fast-average-color-9.5.0.tgz", 397 | "integrity": "sha512-nC6x2YIlJ9xxgkMFMd1BNoM1ctMjNoRKfRliPmiEWW3S6rLTHiQcy9g3pt/xiKv/D0NAAkhb9VyV+WJFvTqMGg==", 398 | "dev": true, 399 | "license": "MIT", 400 | "engines": { 401 | "node": ">= 12" 402 | } 403 | }, 404 | "node_modules/fsevents": { 405 | "version": "2.3.3", 406 | "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", 407 | "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", 408 | "dev": true, 409 | "hasInstallScript": true, 410 | "license": "MIT", 411 | "optional": true, 412 | "os": [ 413 | "darwin" 414 | ], 415 | "engines": { 416 | "node": "^8.16.0 || ^10.6.0 || >=11.0.0" 417 | } 418 | }, 419 | "node_modules/function-bind": { 420 | "version": "1.1.2", 421 | "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", 422 | "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", 423 | "dev": true, 424 | "license": "MIT", 425 | "funding": { 426 | "url": "https://github.com/sponsors/ljharb" 427 | } 428 | }, 429 | "node_modules/hasown": { 430 | "version": "2.0.2", 431 | "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", 432 | "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", 433 | "dev": true, 434 | "license": "MIT", 435 | "dependencies": { 436 | "function-bind": "^1.1.2" 437 | }, 438 | "engines": { 439 | "node": ">= 0.4" 440 | } 441 | }, 442 | "node_modules/is-core-module": { 443 | "version": "2.16.1", 444 | "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", 445 | "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", 446 | "dev": true, 447 | "license": "MIT", 448 | "dependencies": { 449 | "hasown": "^2.0.2" 450 | }, 451 | "engines": { 452 | "node": ">= 0.4" 453 | }, 454 | "funding": { 455 | "url": "https://github.com/sponsors/ljharb" 456 | } 457 | }, 458 | "node_modules/is-module": { 459 | "version": "1.0.0", 460 | "resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz", 461 | "integrity": "sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==", 462 | "dev": true, 463 | "license": "MIT" 464 | }, 465 | "node_modules/lyam": { 466 | "version": "3.2.1", 467 | "resolved": "https://registry.npmjs.org/lyam/-/lyam-3.2.1.tgz", 468 | "integrity": "sha512-yUsFFaCcHGE9tBAWQFUI5KdK8Cwv8l9yCPCZ5jxVONtRuBI78UKT9t9Wy0DAwXDfOuaOl5ymapPacY/mZZxJuA==", 469 | "dev": true, 470 | "license": "MIT", 471 | "engines": { 472 | "node": ">= 12" 473 | } 474 | }, 475 | "node_modules/path-parse": { 476 | "version": "1.0.7", 477 | "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", 478 | "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", 479 | "dev": true, 480 | "license": "MIT" 481 | }, 482 | "node_modules/picomatch": { 483 | "version": "4.0.2", 484 | "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", 485 | "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", 486 | "dev": true, 487 | "license": "MIT", 488 | "engines": { 489 | "node": ">=12" 490 | }, 491 | "funding": { 492 | "url": "https://github.com/sponsors/jonschlinkert" 493 | } 494 | }, 495 | "node_modules/resolve": { 496 | "version": "1.22.10", 497 | "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", 498 | "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", 499 | "dev": true, 500 | "license": "MIT", 501 | "dependencies": { 502 | "is-core-module": "^2.16.0", 503 | "path-parse": "^1.0.7", 504 | "supports-preserve-symlinks-flag": "^1.0.0" 505 | }, 506 | "bin": { 507 | "resolve": "bin/resolve" 508 | }, 509 | "engines": { 510 | "node": ">= 0.4" 511 | }, 512 | "funding": { 513 | "url": "https://github.com/sponsors/ljharb" 514 | } 515 | }, 516 | "node_modules/rollup": { 517 | "version": "4.36.0", 518 | "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.36.0.tgz", 519 | "integrity": "sha512-zwATAXNQxUcd40zgtQG0ZafcRK4g004WtEl7kbuhTWPvf07PsfohXl39jVUvPF7jvNAIkKPQ2XrsDlWuxBd++Q==", 520 | "dev": true, 521 | "license": "MIT", 522 | "dependencies": { 523 | "@types/estree": "1.0.6" 524 | }, 525 | "bin": { 526 | "rollup": "dist/bin/rollup" 527 | }, 528 | "engines": { 529 | "node": ">=18.0.0", 530 | "npm": ">=8.0.0" 531 | }, 532 | "optionalDependencies": { 533 | "@rollup/rollup-android-arm-eabi": "4.36.0", 534 | "@rollup/rollup-android-arm64": "4.36.0", 535 | "@rollup/rollup-darwin-arm64": "4.36.0", 536 | "@rollup/rollup-darwin-x64": "4.36.0", 537 | "@rollup/rollup-freebsd-arm64": "4.36.0", 538 | "@rollup/rollup-freebsd-x64": "4.36.0", 539 | "@rollup/rollup-linux-arm-gnueabihf": "4.36.0", 540 | "@rollup/rollup-linux-arm-musleabihf": "4.36.0", 541 | "@rollup/rollup-linux-arm64-gnu": "4.36.0", 542 | "@rollup/rollup-linux-arm64-musl": "4.36.0", 543 | "@rollup/rollup-linux-loongarch64-gnu": "4.36.0", 544 | "@rollup/rollup-linux-powerpc64le-gnu": "4.36.0", 545 | "@rollup/rollup-linux-riscv64-gnu": "4.36.0", 546 | "@rollup/rollup-linux-s390x-gnu": "4.36.0", 547 | "@rollup/rollup-linux-x64-gnu": "4.36.0", 548 | "@rollup/rollup-linux-x64-musl": "4.36.0", 549 | "@rollup/rollup-win32-arm64-msvc": "4.36.0", 550 | "@rollup/rollup-win32-ia32-msvc": "4.36.0", 551 | "@rollup/rollup-win32-x64-msvc": "4.36.0", 552 | "fsevents": "~2.3.2" 553 | } 554 | }, 555 | "node_modules/rollup-plugin-css-only": { 556 | "version": "4.5.2", 557 | "resolved": "https://registry.npmjs.org/rollup-plugin-css-only/-/rollup-plugin-css-only-4.5.2.tgz", 558 | "integrity": "sha512-7rj9+jB17Pz8LNcPgtMUb16JcgD8lxQMK9HcGfAVhMK3na/WXes3oGIo5QsrQQVqtgAU6q6KnQNXJrYunaUIQQ==", 559 | "dev": true, 560 | "license": "MIT", 561 | "dependencies": { 562 | "@rollup/pluginutils": "5" 563 | }, 564 | "engines": { 565 | "node": ">=14" 566 | }, 567 | "peerDependencies": { 568 | "rollup": "<5" 569 | } 570 | }, 571 | "node_modules/supports-preserve-symlinks-flag": { 572 | "version": "1.0.0", 573 | "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", 574 | "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", 575 | "dev": true, 576 | "license": "MIT", 577 | "engines": { 578 | "node": ">= 0.4" 579 | }, 580 | "funding": { 581 | "url": "https://github.com/sponsors/ljharb" 582 | } 583 | }, 584 | "node_modules/tslib": { 585 | "version": "2.8.1", 586 | "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", 587 | "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", 588 | "dev": true, 589 | "license": "0BSD" 590 | }, 591 | "node_modules/typescript": { 592 | "version": "5.8.2", 593 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.2.tgz", 594 | "integrity": "sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ==", 595 | "dev": true, 596 | "license": "Apache-2.0", 597 | "bin": { 598 | "tsc": "bin/tsc", 599 | "tsserver": "bin/tsserver" 600 | }, 601 | "engines": { 602 | "node": ">=14.17" 603 | } 604 | } 605 | } 606 | } 607 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "fast-average-color-examples", 3 | "version": "1.0.0", 4 | "description": "Examples for Fast Average Color", 5 | "scripts": { 6 | "build": "rollup -c", 7 | "typecheck": "tsc --noEmit", 8 | "test": "npm run typecheck" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "git+https://github.com/fast-average-color/examples.git" 13 | }, 14 | "keywords": [ 15 | "examples", 16 | "average color" 17 | ], 18 | "author": "Denis Seleznev", 19 | "license": "MIT", 20 | "bugs": { 21 | "url": "https://github.com/fast-average-color/examples/issues" 22 | }, 23 | "homepage": "https://github.com/fast-average-color/examples#readme", 24 | "private": true, 25 | "devDependencies": { 26 | "@rollup/plugin-node-resolve": "^16.0.1", 27 | "@rollup/plugin-typescript": "^12.1.2", 28 | "fast-average-color": "^9.5.0", 29 | "lyam": "^3.2.1", 30 | "rollup": "^4.36.0", 31 | "rollup-plugin-css-only": "^4.5.2", 32 | "tslib": "^2.8.1", 33 | "typescript": "^5.8.2" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /rollup.config.mjs: -------------------------------------------------------------------------------- 1 | import typescript from '@rollup/plugin-typescript'; 2 | import { nodeResolve } from '@rollup/plugin-node-resolve'; 3 | import css from 'rollup-plugin-css-only'; 4 | 5 | const createConfig = name => ({ 6 | input: `src/${name}/index.ts`, 7 | output: [ 8 | { 9 | file: `dist/${name}.js`, 10 | format: 'umd', 11 | }, 12 | ] , 13 | plugins: [ 14 | typescript(), 15 | nodeResolve(), 16 | css({ 17 | output: `${name}.css`, 18 | }), 19 | ] 20 | }); 21 | 22 | const configs = [ 23 | 'algorithms', 24 | 'ambilight', 25 | 'background', 26 | 'background_async', 27 | 'background_async_dominant', 28 | 'box-shadow', 29 | 'box-shadow-4-sides', 30 | 'canvas', 31 | 'border', 32 | 'text-photo', 33 | 'gradient', 34 | 'gradient_stripes', 35 | 'gallery', 36 | 'gallery_vertical', 37 | 'define', 38 | 'timeline', 39 | ].map(name => createConfig(name)); 40 | 41 | export default configs; 42 | -------------------------------------------------------------------------------- /src/algorithms/build.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const fs = require('fs'); 4 | const template = fs.readFileSync('./template.html', 'utf8'); 5 | const MAX_PHOTO = 27; 6 | 7 | let rows = ''; 8 | for (let i = 1; i <= MAX_PHOTO; i++) { 9 | rows += `
10 | 11 |
12 |
13 |
14 |
` 15 | } 16 | 17 | fs.writeFileSync('../../algorithms.html', template.replace(/\{ROWS\}/, rows)); 18 | -------------------------------------------------------------------------------- /src/algorithms/index.css: -------------------------------------------------------------------------------- 1 | .rows { 2 | margin: 50px 0 0 20px; 3 | color: #fff; 4 | } 5 | 6 | .row 7 | { 8 | display: flex; 9 | } 10 | 11 | .item 12 | { 13 | transition: all .3s ease; 14 | flex-basis: 300px; 15 | margin: 20px 20px 0 0; 16 | text-align: center; 17 | } 18 | 19 | .item_image { 20 | max-width: 300px; 21 | width: 25%; 22 | } 23 | -------------------------------------------------------------------------------- /src/algorithms/index.ts: -------------------------------------------------------------------------------- 1 | import { FastAverageColor } from 'fast-average-color'; 2 | import '../common'; 3 | import './index.css'; 4 | 5 | const fac = new FastAverageColor(); 6 | 7 | window.addEventListener('load', () => { 8 | Array.from(document.querySelectorAll('.row')).forEach(row => { 9 | (['simple', 'sqrt', 'dominant'] as const).forEach(algorithm => { 10 | fac.getColorAsync(row.querySelector('.item_image'), { algorithm }) 11 | .then(color => { 12 | const item = row.querySelector('.item_' + algorithm) as HTMLDivElement; 13 | 14 | item.style.backgroundColor = color.rgb; 15 | item.style.color = color.isDark ? '#fff' : '#000'; 16 | item.innerText = color.hex; 17 | }) 18 | .catch(e => console.log(e)); 19 | }); 20 | }); 21 | }, false); 22 | -------------------------------------------------------------------------------- /src/algorithms/template.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Average color: Algorithms 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 |
14 |
Photo
15 |
simple algorithm
16 |
sqrt algorithm
17 |
dominant algorithm
18 |
19 | {ROWS} 20 |
21 | 22 | 23 | -------------------------------------------------------------------------------- /src/ambilight/ambilight.ts: -------------------------------------------------------------------------------- 1 | import '../common'; 2 | import './index.css'; 3 | 4 | import { Ambilight4Sides } from './types/4sides'; 5 | import { AmbilightManyPoints } from './types/many-points'; 6 | import { AmbilightBase } from './types/base'; 7 | 8 | interface AmbilightOptions { 9 | onPlay?: () => void; 10 | } 11 | 12 | interface AmbilightTypeItem { 13 | name: string; 14 | klass: typeof AmbilightManyPoints | typeof Ambilight4Sides, 15 | options: any; 16 | } 17 | 18 | export class Ambilight { 19 | requestId = 0; 20 | 21 | plugin: AmbilightBase | null; 22 | 23 | video: HTMLVideoElement | null; 24 | container: HTMLElement | null; 25 | options: AmbilightOptions; 26 | 27 | types: AmbilightTypeItem[] = [ 28 | { name: 'ManyPoints', klass: AmbilightManyPoints, options: { countByWidth: 10, countByHeight: 5, size: 70 }}, 29 | { name: '4Sides', klass: Ambilight4Sides, options: { radius: 200, delta: 200, size: 70 }}, 30 | ]; 31 | 32 | constructor(video: HTMLVideoElement, container: HTMLElement, options?: AmbilightOptions) { 33 | this.onPlay = this.onPlay.bind(this); 34 | this.onPause = this.onPause.bind(this); 35 | this.onUpdate = this.onUpdate.bind(this) 36 | 37 | this.video = video; 38 | this.video.addEventListener('play', this.onPlay.bind(this), false); 39 | this.video.addEventListener('pause', this.onPause, false); 40 | 41 | this.container = container; 42 | this.options = options || {}; 43 | 44 | this.plugin = new this.types[0].klass(video, container, this.types[0].options); 45 | !video.paused && this.onPlay(); 46 | } 47 | 48 | setType(name: string) { 49 | const plugin = this.types.find(item => item.name === name); 50 | if (plugin && this.video && this.container) { 51 | this.plugin?.destroy(); 52 | this.plugin = new plugin.klass(this.video, this.container, plugin.options); 53 | } 54 | } 55 | 56 | private onPlay() { 57 | this.options.onPlay?.(); 58 | 59 | this.requestId = window.requestAnimationFrame(this.onUpdate); 60 | } 61 | 62 | private onPause() { 63 | window.cancelAnimationFrame(this.requestId); 64 | } 65 | 66 | private onUpdate() { 67 | this.plugin?.onUpdate(); 68 | this.requestId = window.requestAnimationFrame(this.onUpdate); 69 | } 70 | 71 | destroy() { 72 | if (this.plugin) { 73 | this.plugin.destroy(); 74 | this.plugin = null; 75 | } 76 | 77 | if (this.video) { 78 | this.video.removeEventListener('play', this.onPlay, false); 79 | this.video.removeEventListener('pause', this.onPause, false); 80 | } 81 | 82 | window.cancelAnimationFrame(this.requestId); 83 | } 84 | }; 85 | -------------------------------------------------------------------------------- /src/ambilight/index.css: -------------------------------------------------------------------------------- 1 | .ambilight-control 2 | { 3 | font-size: 12px; 4 | line-height: 12px; 5 | 6 | position: fixed; 7 | z-index: 100; 8 | top: 21px; 9 | left: 50%; 10 | 11 | transform: translateX(-50%); 12 | text-align: center; 13 | white-space: nowrap; 14 | 15 | color: #999; 16 | } 17 | 18 | .ambilight-control h1 19 | { 20 | font-weight: normal; 21 | 22 | margin: 0 0 15px 0; 23 | padding: 0; 24 | 25 | color: #aaa; 26 | } 27 | 28 | .ambilight-control label 29 | { 30 | margin: 0; 31 | margin-right: 8px; 32 | padding: 0; 33 | } 34 | 35 | .ambilight-control input 36 | { 37 | font-size: 12px; 38 | line-height: 12px; 39 | 40 | margin: 0 2px 0 0; 41 | 42 | vertical-align: top; 43 | } 44 | 45 | .video-container 46 | { 47 | position: absolute; 48 | top: 50%; 49 | left: 50%; 50 | 51 | transform: translate(-50%, -50%); 52 | } 53 | 54 | video 55 | { 56 | position: relative; 57 | z-index: 100; 58 | 59 | display: block; 60 | 61 | margin: 0; 62 | padding: 0; 63 | 64 | border: 10px solid black; 65 | outline: 1px solid rgba(255, 255, 255, .1); 66 | background: black; 67 | } 68 | 69 | .video-shadow 70 | { 71 | position: absolute; 72 | } 73 | 74 | .video-shadow_left 75 | { 76 | left: 0; 77 | 78 | border-radius: 100% 0 0 100%; 79 | } 80 | 81 | .video-shadow_right 82 | { 83 | right: 0; 84 | 85 | border-radius: 0 100% 100% 0; 86 | } 87 | 88 | .video-shadow_top 89 | { 90 | top: 0; 91 | 92 | border-radius: 100% 100% 0 0; 93 | } 94 | 95 | .video-shadow_bottom 96 | { 97 | bottom: 0; 98 | 99 | border-radius: 0 0 100% 100%; 100 | } 101 | 102 | .copyright 103 | { 104 | font-family: Arial; 105 | font-size: 12px; 106 | 107 | position: fixed; 108 | bottom: 5px; 109 | left: 50%; 110 | 111 | margin-left: -5em; 112 | } 113 | 114 | .hint 115 | { 116 | font-size: 16px; 117 | 118 | position: absolute; 119 | top: 50%; 120 | left: 50%; 121 | 122 | margin-top: 200px; 123 | margin-left: -320px; 124 | 125 | color: #aaa; 126 | } 127 | 128 | .copyright:link, 129 | .copyright:visited, 130 | .copyright:active, 131 | .copyright:hover 132 | { 133 | color: #555; 134 | } 135 | -------------------------------------------------------------------------------- /src/ambilight/index.ts: -------------------------------------------------------------------------------- 1 | import { Ambilight } from './ambilight'; 2 | import '../common'; 3 | import './index.css'; 4 | 5 | window.addEventListener('load', function() { 6 | const video = document.querySelector('video') as HTMLVideoElement; 7 | const container = document.querySelector('.video-container') as HTMLElement; 8 | const ambi = new Ambilight(video, container, { 9 | onPlay() { 10 | const hint = document.querySelector('.hint') as HTMLElement; 11 | hint.style.display = 'none'; 12 | }, 13 | }); 14 | 15 | video.muted = true; 16 | video.play(); 17 | 18 | const inputs = document.querySelectorAll('input[type="radio"]'); 19 | inputs[location.hash === '#4Sides' ? 1 : 0].checked = true; 20 | 21 | inputs[0].onchange = inputs[1].onchange = (event: Event) => { 22 | const target = event.target as HTMLInputElement; 23 | ambi.setType(target.value); 24 | location.hash = target.value; 25 | }; 26 | }, false); 27 | -------------------------------------------------------------------------------- /src/ambilight/types/4sides.ts: -------------------------------------------------------------------------------- 1 | import { FastAverageColor } from 'fast-average-color'; 2 | import { AmbilightBase } from './base'; 3 | 4 | const fac = new FastAverageColor(); 5 | 6 | interface Ambilight4SideOptions { 7 | radius: number; 8 | delta: number; 9 | size: number; 10 | } 11 | 12 | export class Ambilight4Sides extends AmbilightBase { 13 | video: HTMLVideoElement | null; 14 | container: HTMLElement | null; 15 | 16 | options: Ambilight4SideOptions; 17 | 18 | constructor(video: HTMLVideoElement, container: HTMLElement, options: Ambilight4SideOptions) { 19 | super(video, container, options); 20 | 21 | this.video = video; 22 | this.container = container; 23 | this.options = options; 24 | } 25 | 26 | destroy() { 27 | if (this.container) { 28 | this.container.style.boxShadow = 'none'; 29 | } 30 | 31 | this.video = null; 32 | this.container = null; 33 | } 34 | 35 | onUpdate() { 36 | if (!this.video || !this.container) { 37 | return; 38 | } 39 | 40 | const width = this.video.videoWidth; 41 | const height = this.video.videoHeight; 42 | const { size } = this.options; 43 | const video = this.video; 44 | 45 | 46 | const colorTop = fac.getColor(video, { 47 | left: 0, 48 | top: 0, 49 | height: size, 50 | width: width 51 | }); 52 | 53 | const colorRight = fac.getColor(video, { 54 | left: width - size, 55 | top: 0, 56 | width: size, 57 | height: height 58 | }); 59 | const colorLeft = fac.getColor(video, { 60 | left: 0, 61 | top: 0, 62 | width: size, 63 | height: height 64 | }); 65 | const colorBottom = fac.getColor(video, { 66 | left: 0, 67 | top: height - size, 68 | width: width, 69 | height: size 70 | }); 71 | 72 | const radius = this.options.radius + 'px'; 73 | const delta = this.options.delta + 'px'; 74 | 75 | this.container.style.boxShadow = [ 76 | `0 -${delta} ${radius} ${colorTop.rgb}`, 77 | `${delta} 0 ${radius} ${colorRight.rgb}`, 78 | `0 ${delta} ${radius} ${colorBottom.rgb}`, 79 | `-${delta} 0 ${radius} ${colorLeft.rgb}` 80 | ].join(', '); 81 | } 82 | }; 83 | -------------------------------------------------------------------------------- /src/ambilight/types/base.ts: -------------------------------------------------------------------------------- 1 | export abstract class AmbilightBase { 2 | constructor(_video: HTMLVideoElement, _container: HTMLElement, _options: any) {}; 3 | 4 | abstract onUpdate(): void; 5 | abstract destroy(): void; 6 | } 7 | -------------------------------------------------------------------------------- /src/ambilight/types/helpers/ua.ts: -------------------------------------------------------------------------------- 1 | const ua = navigator.userAgent.toLowerCase(); 2 | export const isFirefox = ua.indexOf('firefox') > -1; 3 | export const isSafari = (ua.search('safari') > -1 && ua.search('chrome') === -1); 4 | -------------------------------------------------------------------------------- /src/ambilight/types/many-points.ts: -------------------------------------------------------------------------------- 1 | import { FastAverageColor } from 'fast-average-color'; 2 | import { AmbilightBase } from './base'; 3 | 4 | import { isSafari, isFirefox } from './helpers/ua'; 5 | 6 | const fac = new FastAverageColor(); 7 | 8 | interface AmbilightManyPointsOptions { 9 | countByWidth: number; 10 | countByHeight: number; 11 | size: number; 12 | } 13 | 14 | export class AmbilightManyPoints extends AmbilightBase { 15 | hasDoubleBlur = false; 16 | 17 | topElems: HTMLDivElement[] = []; 18 | bottomElems: HTMLDivElement[] = []; 19 | leftElems: HTMLDivElement[] = []; 20 | rightElems: HTMLDivElement[] = []; 21 | 22 | video: HTMLVideoElement | null; 23 | container: HTMLElement | null; 24 | 25 | options: AmbilightManyPointsOptions; 26 | 27 | constructor(video: HTMLVideoElement, container: HTMLElement, options: AmbilightManyPointsOptions) { 28 | super(video, container, options); 29 | 30 | this.video = video; 31 | this.container = container; 32 | this.options = options; 33 | 34 | this.hasDoubleBlur = isFirefox || isSafari ? false : true; 35 | 36 | this.createShadows(); 37 | 38 | this.updateShadows = this.updateShadows.bind(this); 39 | 40 | console.log('init plugin'); 41 | } 42 | 43 | destroy() { 44 | if (this.container) { 45 | for (let i = 0; i < this.topElems.length; i++) { 46 | this.container.removeChild(this.topElems[i]); 47 | this.container.removeChild(this.bottomElems[i]); 48 | } 49 | 50 | for (let i = 0; i < this.leftElems.length; i++) { 51 | this.container.removeChild(this.leftElems[i]); 52 | this.container.removeChild(this.rightElems[i]); 53 | } 54 | } 55 | 56 | this.video = null; 57 | this.container = null; 58 | } 59 | 60 | createShadows() { 61 | const { countByWidth, countByHeight } = this.options; 62 | 63 | const width = this.video?.videoWidth || 0; 64 | const height = this.video?.videoHeight || 0; 65 | 66 | this.topElems = []; 67 | this.bottomElems = []; 68 | this.leftElems = []; 69 | this.rightElems = []; 70 | 71 | for (let i = 0; i < countByWidth; i++) { 72 | const size = width / countByWidth; 73 | const topElem = this.createShadow('top'); 74 | const left = (i * size) + 'px'; 75 | topElem.style.left = left; 76 | this.topElems.push(topElem); 77 | this.container?.appendChild(topElem); 78 | 79 | const bottomElem = this.createShadow('bottom'); 80 | bottomElem.style.left = left; 81 | this.bottomElems.push(bottomElem); 82 | this.container?.appendChild(bottomElem); 83 | } 84 | 85 | for (let i = 0; i < countByHeight; i++) { 86 | const leftElem = this.createShadow('left'); 87 | const size = height / countByHeight; 88 | 89 | const top = (i * size) + 'px'; 90 | leftElem.style.top = top; 91 | this.leftElems.push(leftElem); 92 | this.container?.appendChild(leftElem); 93 | 94 | const rightElem = this.createShadow('right'); 95 | rightElem.style.top = top; 96 | this.rightElems.push(rightElem); 97 | this.container?.appendChild(rightElem); 98 | } 99 | } 100 | 101 | createShadow(position: string) { 102 | const { size } = this.options; 103 | const elem = document.createElement('div'); 104 | 105 | elem.className = 'video-shadow video-shadow_' + position; 106 | elem.style.width = `${size}px`; 107 | elem.style.height = `${size}px`; 108 | 109 | return elem; 110 | } 111 | 112 | updateShadows() { 113 | console.log('updateShadows plugin'); 114 | 115 | const width = this.video?.videoWidth || 0; 116 | const height = this.video?.videoHeight || 0; 117 | 118 | const { video } = this; 119 | const { countByHeight, countByWidth } = this.options; 120 | 121 | for (let i = 0; i < this.leftElems.length; i++) { 122 | const size = Math.floor(height / countByHeight); 123 | 124 | const leftColor = fac.getColor(video, { 125 | left: 0, 126 | top: size * i, 127 | width: size, 128 | height: size 129 | }); 130 | 131 | const rightColor = fac.getColor(video, { 132 | left: width - size, 133 | top: size * i, 134 | width: size, 135 | height: size 136 | }); 137 | 138 | const offset = size; 139 | const blur = this.hasDoubleBlur ? size * 2 : size; 140 | 141 | this.leftElems[i].style.boxShadow = '-' + offset + 'px 0 ' + blur + 'px ' + leftColor.rgb; 142 | this.rightElems[i].style.boxShadow = offset + 'px 0 ' + blur + 'px ' + rightColor.rgb; 143 | } 144 | 145 | for (let i = 0; i < this.topElems.length; i++) { 146 | const size = Math.floor(width / countByWidth); 147 | 148 | const topColor = fac.getColor(video, { 149 | left: size * i, 150 | top: 0, 151 | width: size, 152 | height: size 153 | }); 154 | 155 | const bottomColor = fac.getColor(video, { 156 | left: size * i, 157 | top: height - size, 158 | width: size, 159 | height: size 160 | }); 161 | 162 | const offset = size; 163 | const blur = this.hasDoubleBlur ? size * 2 : size; 164 | 165 | this.topElems[i].style.boxShadow = '0 -' + offset + 'px ' + blur + 'px ' + topColor.rgb; 166 | this.bottomElems[i].style.boxShadow = '0 ' + offset + 'px ' + blur + 'px ' + bottomColor.rgb; 167 | } 168 | } 169 | 170 | onUpdate() { 171 | this.updateShadows(); 172 | } 173 | } 174 | -------------------------------------------------------------------------------- /src/background/index.css: -------------------------------------------------------------------------------- 1 | img 2 | { 3 | width: 100%; 4 | } 5 | 6 | .item 7 | { 8 | float: left; 9 | 10 | box-sizing: border-box; 11 | width: 500px; 12 | max-width: 100%; 13 | padding: 50px 50px 45px 50px; 14 | 15 | transition: background-color .5s ease; 16 | } 17 | 18 | .item__text 19 | { 20 | padding-top: 10px; 21 | } 22 | -------------------------------------------------------------------------------- /src/background/index.ts: -------------------------------------------------------------------------------- 1 | import { FastAverageColor } from 'fast-average-color'; 2 | 3 | import '../common'; 4 | import './index.css'; 5 | 6 | const fac = new FastAverageColor(); 7 | 8 | window.addEventListener('load', function() { 9 | Array.from(document.querySelectorAll('.item')).forEach(item => { 10 | const color = fac.getColor(item.querySelector('img')); 11 | 12 | item.style.backgroundColor = color.rgb; 13 | item.style.color = color.isDark ? 'white' : 'black'; 14 | }); 15 | }, false); 16 | -------------------------------------------------------------------------------- /src/background_async/index.css: -------------------------------------------------------------------------------- 1 | img 2 | { 3 | width: 100%; 4 | } 5 | 6 | .item 7 | { 8 | float: left; 9 | 10 | box-sizing: border-box; 11 | width: 500px; 12 | max-width: 100%; 13 | padding: 50px 50px 45px 50px; 14 | 15 | transition: background-color .5s ease; 16 | } 17 | 18 | .item__text 19 | { 20 | padding-top: 10px; 21 | } 22 | -------------------------------------------------------------------------------- /src/background_async/index.ts: -------------------------------------------------------------------------------- 1 | import { FastAverageColor } from 'fast-average-color'; 2 | 3 | import '../common'; 4 | import './index.css'; 5 | 6 | const fac = new FastAverageColor(); 7 | 8 | Array.from(document.querySelectorAll('.item')).forEach(item => { 9 | fac.getColorAsync(item.querySelector('img')) 10 | .then(color => { 11 | item.style.backgroundColor = color.rgb; 12 | item.style.color = color.isDark ? 'white' : 'black'; 13 | }) 14 | .catch(e => { 15 | console.log(e); 16 | }); 17 | }); 18 | -------------------------------------------------------------------------------- /src/background_async_dominant/index.css: -------------------------------------------------------------------------------- 1 | img 2 | { 3 | width: 100%; 4 | } 5 | 6 | .item 7 | { 8 | float: left; 9 | 10 | box-sizing: border-box; 11 | width: 500px; 12 | max-width: 100%; 13 | padding: 50px 50px 45px 50px; 14 | 15 | transition: background-color .5s ease; 16 | } 17 | 18 | .item__text 19 | { 20 | padding-top: 10px; 21 | } 22 | -------------------------------------------------------------------------------- /src/background_async_dominant/index.ts: -------------------------------------------------------------------------------- 1 | import { FastAverageColor } from 'fast-average-color'; 2 | 3 | import '../common'; 4 | import './index.css'; 5 | 6 | const fac = new FastAverageColor(); 7 | 8 | Array.from(document.querySelectorAll('.item')).forEach(item => { 9 | fac.getColorAsync(item.querySelector('img'), { algorithm: 'dominant' }) 10 | .then(color => { 11 | item.style.backgroundColor = color.rgb; 12 | item.style.color = color.isDark ? 'white' : 'black'; 13 | }) 14 | .catch(e => { 15 | console.log(e); 16 | }); 17 | }); 18 | -------------------------------------------------------------------------------- /src/border/index.css: -------------------------------------------------------------------------------- 1 | .item 2 | { 3 | float: left; 4 | 5 | box-sizing: border-box; 6 | width: 400px; 7 | max-width: 100%; 8 | padding: 30px; 9 | 10 | transition: border-color .5s ease; 11 | } 12 | 13 | .item__image-container 14 | { 15 | transition: all .3s ease; 16 | 17 | border: 40px solid #000; 18 | outline: 1px solid rgba(255, 255, 255, .08); 19 | } 20 | 21 | .item__image 22 | { 23 | display: block; 24 | 25 | width: 100%; 26 | 27 | outline: 1px solid rgba(255, 255, 255, .08); 28 | } 29 | 30 | .item__text 31 | { 32 | position: relative; 33 | z-index: 10; 34 | 35 | padding: 10px; 36 | 37 | color: #fff; 38 | } 39 | -------------------------------------------------------------------------------- /src/border/index.ts: -------------------------------------------------------------------------------- 1 | import { FastAverageColor } from 'fast-average-color'; 2 | 3 | import '../common'; 4 | import './index.css'; 5 | 6 | const fac = new FastAverageColor(); 7 | 8 | window.addEventListener('load', function() { 9 | Array.from(document.querySelectorAll('.item')).forEach(item => { 10 | const imageContainer = item.querySelector('.item__image-container') as HTMLDivElement; 11 | const image = item.querySelector('.item__image') as HTMLImageElement; 12 | 13 | const size = 20; 14 | 15 | const width = image.naturalWidth; 16 | const height = image.naturalHeight; 17 | 18 | const colorTop = fac.getColor(image, {height: size}); 19 | const colorRight = fac.getColor(image, {left: width - size, width: size}); 20 | const colorLeft = fac.getColor(image, {width: size}); 21 | const colorBottom = fac.getColor(image, {top: height - size, height: size}); 22 | 23 | imageContainer.style.borderColor = [ 24 | colorTop.rgb, 25 | colorRight.rgb, 26 | colorBottom.rgb, 27 | colorLeft.rgb 28 | ].join(' '); 29 | }); 30 | }, false); 31 | -------------------------------------------------------------------------------- /src/box-shadow-4-sides/index.css: -------------------------------------------------------------------------------- 1 | img 2 | { 3 | width: 100%; 4 | 5 | transition: all .3s ease; 6 | 7 | border-radius: 50px; 8 | } 9 | 10 | .item 11 | { 12 | float: left; 13 | 14 | box-sizing: border-box; 15 | width: 540px; 16 | max-width: 100%; 17 | padding: 70px; 18 | 19 | transition: background-color .5s ease; 20 | } 21 | 22 | .item__text 23 | { 24 | position: relative; 25 | z-index: 10; 26 | 27 | padding: 10px; 28 | } 29 | -------------------------------------------------------------------------------- /src/box-shadow-4-sides/index.ts: -------------------------------------------------------------------------------- 1 | import { FastAverageColor } from 'fast-average-color'; 2 | 3 | import '../common'; 4 | import './index.css'; 5 | 6 | const fac = new FastAverageColor(); 7 | 8 | window.addEventListener('load', () => { 9 | Array.from(document.querySelectorAll('.item')).forEach(item => { 10 | const image = item.querySelector('img') as HTMLImageElement; 11 | const size = 50; 12 | 13 | const width = image.naturalWidth; 14 | const height = image.naturalHeight; 15 | 16 | const colorTop = fac.getColor(image, {height: size}); 17 | const colorRight = fac.getColor(image, {left: width - size, width: size}); 18 | const colorLeft = fac.getColor(image, {width: size}); 19 | const colorBottom = fac.getColor(image, {top: height - size, height: size}); 20 | const radius = ' 90px '; 21 | const delta = '70px'; 22 | 23 | image.style.boxShadow = [ 24 | `0 -${delta} ${radius} ${colorTop.rgb}`, 25 | `${delta} 0 ${radius} ${colorRight.rgb}`, 26 | `0 ${delta} ${radius} ${colorBottom.rgb}`, 27 | `-${delta} 0 ${radius} ${colorLeft.rgb}`, 28 | ].join(', '); 29 | 30 | item.style.color = colorBottom.isDark ? 'white' : 'black'; 31 | }); 32 | }, false); 33 | -------------------------------------------------------------------------------- /src/box-shadow/index.css: -------------------------------------------------------------------------------- 1 | img 2 | { 3 | width: 100%; 4 | 5 | transition: all .3s ease; 6 | 7 | border-radius: 50px; 8 | } 9 | 10 | img:hover 11 | { 12 | transform: translateY(10px); 13 | 14 | opacity: .8; 15 | } 16 | 17 | .item 18 | { 19 | float: left; 20 | 21 | box-sizing: border-box; 22 | width: 540px; 23 | max-width: 100%; 24 | padding: 30px 50px; 25 | 26 | transition: background-color .5s ease; 27 | } 28 | 29 | .item__text 30 | { 31 | position: relative; 32 | z-index: 10; 33 | 34 | padding: 10px; 35 | } 36 | -------------------------------------------------------------------------------- /src/box-shadow/index.ts: -------------------------------------------------------------------------------- 1 | import { FastAverageColor } from 'fast-average-color'; 2 | 3 | import '../common'; 4 | import './index.css'; 5 | 6 | const fac = new FastAverageColor(); 7 | 8 | window.addEventListener('load', function() { 9 | Array.from(document.querySelectorAll('.item')).forEach(item => { 10 | const image = item.querySelector('img'); 11 | const color = fac.getColor(image); 12 | 13 | if (image) { 14 | image.style.boxShadow = '0 70px 90px ' + color.rgb; 15 | item.style.color = color.isDark ? 'white' : 'black'; 16 | } 17 | }); 18 | }, false); 19 | -------------------------------------------------------------------------------- /src/canvas/helpers.ts: -------------------------------------------------------------------------------- 1 | export const rnd = (max: number) => { 2 | return Math.random() * max; 3 | }; 4 | 5 | export const rndFloor = (max: number) => { 6 | return Math.floor(Math.random() * max); 7 | }; 8 | -------------------------------------------------------------------------------- /src/canvas/index.css: -------------------------------------------------------------------------------- 1 | canvas 2 | { 3 | position: fixed; 4 | top: 50%; 5 | left: 50%; 6 | 7 | transform: translate(-50%, -50%); 8 | 9 | border: 1px solid rgba(100, 100, 100, .5); 10 | } 11 | 12 | .info 13 | { 14 | font-family: Arial; 15 | font-size: 16px; 16 | 17 | position: fixed; 18 | z-index: 10; 19 | top: 50px; 20 | left: 50px; 21 | 22 | padding: 20px; 23 | 24 | color: white; 25 | border-radius: 10px; 26 | background: rgba(255, 255, 255, .1); 27 | text-shadow: 1px 1px 2px black; 28 | } 29 | 30 | .info__item 31 | { 32 | overflow-x: hidden; 33 | 34 | width: 250px; 35 | 36 | white-space: nowrap; 37 | text-overflow: ellipsis; 38 | } 39 | 40 | .info__item, .option { 41 | padding: 3px; 42 | } 43 | 44 | .option { 45 | margin: 5px 0 5px 0; 46 | } 47 | 48 | #start { 49 | font-size: 14px; 50 | padding: 10px; 51 | width: 100%; 52 | box-sizing: border-box; 53 | } 54 | -------------------------------------------------------------------------------- /src/canvas/index.ts: -------------------------------------------------------------------------------- 1 | import { FastAverageColor } from 'fast-average-color'; 2 | import { rnd, rndFloor } from './helpers'; 3 | 4 | import '../common'; 5 | import './index.css'; 6 | 7 | const fac = new FastAverageColor(); 8 | 9 | class App { 10 | canvas = document.querySelector('canvas') as HTMLCanvasElement; 11 | ctx = this.canvas.getContext('2d') as CanvasRenderingContext2D; 12 | 13 | infoElement = document.querySelector('.info') as HTMLElement; 14 | startElement = document.querySelector('#start') as HTMLButtonElement; 15 | infoColorElement = document.querySelector('.info__color') as HTMLElement; 16 | precisionElement = document.querySelector('#mode-precision') as HTMLButtonElement; 17 | speedElement = document.querySelector('#mode-speed') as HTMLInputElement; 18 | stepElement = document.querySelector('#step') as HTMLInputElement; 19 | algorithmSimpleElement = document.querySelector('#algorithm-simple') as HTMLInputElement; 20 | algorithmSqrtElement = document.querySelector('#algorithm-sqrt') as HTMLInputElement; 21 | algorithmDominantElement = document.querySelector('#algorithm-dominant') as HTMLInputElement; 22 | width640Element = document.querySelector('#width-640') as HTMLInputElement; 23 | width1280Element = document.querySelector('#width-1280') as HTMLInputElement; 24 | width2560Element = document.querySelector('#width-2560') as HTMLInputElement; 25 | 26 | stoped = false; 27 | isPrecision = true; 28 | timer?: number; 29 | step = 1; 30 | algorithm: 'simple' | 'sqrt' | 'dominant' = 'simple'; 31 | 32 | constructor() { 33 | this.bindEvents(); 34 | this.start(); 35 | } 36 | 37 | bindEvents() { 38 | this.startElement.onclick = () => { 39 | this.stoped = !this.stoped; 40 | if (this.stoped) { 41 | this.startElement.innerText = 'Start'; 42 | this.stop(); 43 | } else { 44 | this.startElement.innerText = 'Stop'; 45 | this.start(); 46 | } 47 | }; 48 | 49 | this.precisionElement.onclick = () => { 50 | this.isPrecision = true; 51 | this.getColor(); 52 | }; 53 | 54 | this.speedElement.onclick = () => { 55 | this.isPrecision = false; 56 | this.getColor(); 57 | }; 58 | 59 | this.stepElement.oninput = () => { 60 | this.step = Number(this.stepElement.value); 61 | this.getColor(); 62 | }; 63 | 64 | this.algorithmSimpleElement.onclick = () => { 65 | this.algorithm = 'simple'; 66 | }; 67 | 68 | this.algorithmSqrtElement.onclick = () => { 69 | this.algorithm = 'sqrt'; 70 | }; 71 | 72 | this.algorithmDominantElement.onclick = () => { 73 | this.algorithm = 'dominant'; 74 | }; 75 | 76 | this.width640Element.onclick = () => { 77 | this.canvas.width = 640; 78 | this.canvas.height = 480; 79 | }; 80 | 81 | this.width1280Element.onclick = () => { 82 | this.canvas.width = 1280; 83 | this.canvas.height = 1024; 84 | }; 85 | 86 | this.width2560Element.onclick = () => { 87 | this.canvas.width = 2560; 88 | this.canvas.height = 2048; 89 | }; 90 | } 91 | 92 | start() { 93 | this.timer = window.setInterval(this.nextStep.bind(this), 100); 94 | } 95 | 96 | stop() { 97 | clearInterval(this.timer); 98 | } 99 | 100 | nextStep() { 101 | const { width, height } = this.canvas; 102 | 103 | this.ctx.fillStyle = 'rgba(' + [ 104 | rndFloor(255), 105 | rndFloor(255), 106 | rndFloor(255), 107 | rnd(1) 108 | ].join(',') + ')'; 109 | 110 | this.ctx.fillRect( 111 | rnd(width), 112 | rnd(height), 113 | rnd(width), 114 | rnd(height) 115 | ); 116 | 117 | this.getColor(); 118 | } 119 | 120 | getColor() { 121 | const timeBefore = Date.now(); 122 | const color = fac.getColor(this.canvas, { 123 | algorithm: this.algorithm, 124 | mode: this.isPrecision ? 'precision' : 'speed', 125 | step: this.step, 126 | }); 127 | 128 | const time = Date.now() - timeBefore; 129 | 130 | this.infoElement.style.backgroundColor = color.rgba; 131 | this.infoColorElement.innerHTML = [ 132 | 'rgb: ' + color.rgb, 133 | 'rgba: ' + color.rgba, 134 | 'hex: ' + color.hex, 135 | 'hexa: ' + color.hexa, 136 | `time: ${time} ms`, 137 | ].map(item => `
${item}
`).join(''); 138 | } 139 | }; 140 | 141 | window.addEventListener('load', () => { 142 | new App(); 143 | }, false); 144 | -------------------------------------------------------------------------------- /src/common.css: -------------------------------------------------------------------------------- 1 | html, 2 | body 3 | { 4 | font-family: Arial; 5 | font-size: 16px; 6 | 7 | margin: 0; 8 | padding: 0; 9 | 10 | border: 0; 11 | background: black; 12 | } 13 | 14 | .nav 15 | { 16 | position: fixed; 17 | z-index: 10000; 18 | top: 5px; 19 | left: 5px; 20 | padding: 10px; 21 | 22 | background: rgba(0, 0, 0, .5); 23 | 24 | border-radius: 5px; 25 | } 26 | 27 | .back 28 | { 29 | margin-right: 15px; 30 | } 31 | 32 | .prev 33 | { 34 | margin-right: 3px; 35 | } 36 | 37 | .button 38 | { 39 | padding: 5px 15px; 40 | 41 | transition: opacity .3s ease; 42 | text-decoration: none; 43 | 44 | color: #fff; 45 | border-radius: 5px; 46 | background: rgba(255, 255, 255, .3); 47 | 48 | font-size: 12px; 49 | 50 | filter: grayscale(100%); 51 | } 52 | 53 | .button:hover 54 | { 55 | opacity: .5; 56 | } 57 | -------------------------------------------------------------------------------- /src/common.ts: -------------------------------------------------------------------------------- 1 | import { hit } from 'lyam'; 2 | 3 | import './common.css'; 4 | 5 | window.addEventListener('load', () => { 6 | const pages = [ 7 | 'background', 8 | 'timeline', 9 | 'gradient', 10 | 'gradient_stripes', 11 | 'border', 12 | 'gallery', 13 | 'gallery_vertical', 14 | 'box-shadow', 15 | 'box-shadow-4-sides', 16 | 'ambilight', 17 | 'text-photo', 18 | 'canvas', 19 | 'define' 20 | ]; 21 | 22 | let prev = pages[pages.length - 1]; 23 | let next = pages[1]; 24 | 25 | pages.some((item, i) => { 26 | prev = pages[i - 1] || pages[pages.length - 1]; 27 | next = pages[i + 1] || pages[0]; 28 | 29 | return location.pathname.search('/' + item + '\\.') > -1; 30 | }); 31 | 32 | const nav = document.createElement('div'); 33 | nav.innerHTML = ''; 38 | 39 | document.body.appendChild(nav); 40 | 41 | hit('49603183'); 42 | }, false); 43 | -------------------------------------------------------------------------------- /src/define/index.css: -------------------------------------------------------------------------------- 1 | .select-file-container 2 | { 3 | margin-top: 50px; 4 | padding: 10px; 5 | 6 | text-align: center; 7 | white-space: nowrap; 8 | 9 | color: white; 10 | background: rgba(0, 0, 0, .5); 11 | } 12 | 13 | .images 14 | { 15 | margin-top: 50px; 16 | } 17 | 18 | .images__item 19 | { 20 | margin: 0 25px 25px 25px; 21 | 22 | transition: all .3s linear; 23 | display: inline-block; 24 | 25 | border-radius: 5px; 26 | } 27 | 28 | .images__img-container 29 | { 30 | padding: 25px; 31 | } 32 | 33 | .images__img 34 | { 35 | max-width: 100%; 36 | 37 | box-sizing: border-box; 38 | border: 1px solid gray; 39 | background: url('../images/transparent.png') 0 0 repeat; 40 | } 41 | 42 | .images__title 43 | { 44 | width: 100%; 45 | padding: 10px; 46 | border-bottom: 1px solid rgba(255, 255, 255, .1); 47 | box-sizing: border-box; 48 | 49 | text-overflow: ellipsis; 50 | overflow-x: hidden; 51 | } 52 | 53 | .images__body 54 | { 55 | padding: 10px; 56 | border-bottom: 1px solid rgba(255, 255, 255, .1); 57 | box-sizing: border-box; 58 | } 59 | 60 | .images__algorithm { 61 | padding: 5px; 62 | display: block; 63 | background: url('../images/transparent.png') 0 0 repeat; 64 | border-radius: 5px; 65 | } 66 | -------------------------------------------------------------------------------- /src/define/index.ts: -------------------------------------------------------------------------------- 1 | import { FastAverageColor, FastAverageColorResult } from 'fast-average-color'; 2 | 3 | import '../common'; 4 | import './index.css'; 5 | 6 | const fac = new FastAverageColor(); 7 | 8 | class App { 9 | imageCounter = 0; 10 | 11 | constructor() { 12 | const input = document.querySelector('.select-file') as HTMLInputElement; 13 | const captureButton = document.querySelector('.capture-photo') as HTMLButtonElement; 14 | 15 | input.onchange = e => { 16 | const file = (e.target as HTMLInputElement).files?.[0]; 17 | const reader = new FileReader(); 18 | 19 | reader.onloadend = () => { 20 | const image = new Image(); 21 | image.src = reader.result as string; 22 | 23 | this.getColors(image).then(colors => { 24 | this.addImage(image, file?.name || '...', colors); 25 | }); 26 | }; 27 | 28 | if (file) { 29 | reader.readAsDataURL(file); 30 | } 31 | }; 32 | 33 | captureButton.onclick = () => { 34 | this.capture(); 35 | }; 36 | } 37 | 38 | setImageColor(elem: HTMLLabelElement, backgroundColor: string, isDark: boolean) { 39 | const container = elem.parentNode!.parentNode as HTMLDivElement; 40 | container.style.backgroundColor = backgroundColor; 41 | container.style.color = this.getTextColor(isDark); 42 | } 43 | 44 | getColors(image: HTMLImageElement) { 45 | return Promise.all([ 46 | fac.getColorAsync(image, { algorithm: 'simple', mode: 'precision' }), 47 | fac.getColorAsync(image, { algorithm: 'sqrt', mode: 'precision' }), 48 | fac.getColorAsync(image, { algorithm: 'dominant', mode: 'precision' }) 49 | ]); 50 | } 51 | 52 | addImage(resource: HTMLImageElement, name: string, colors: FastAverageColorResult[]) { 53 | const images = document.querySelector('.images') as HTMLDivElement; 54 | const item = document.createElement('div'); 55 | item.className = 'images__item'; 56 | const firstColor = colors[0]; 57 | item.style.background = firstColor.rgb; 58 | item.style.color = this.getTextColor(firstColor.isDark); 59 | images.insertBefore(item, images.firstChild); 60 | 61 | const title = document.createElement('div'); 62 | title.className = 'images__title'; 63 | title.innerText = name; 64 | item.appendChild(title); 65 | 66 | const body = document.createElement('div'); 67 | body.className = 'images__body'; 68 | body.innerHTML = 'Algorithms:
'; 69 | body.appendChild(this.getColorInfo(colors[0], 'simple', true)); 70 | body.appendChild(this.getColorInfo(colors[1], 'sqrt', false)); 71 | body.appendChild(this.getColorInfo(colors[2], 'dominant', false)); 72 | 73 | item.appendChild(body); 74 | 75 | resource.className = 'images__img'; 76 | 77 | const container = document.createElement('div'); 78 | container.className = 'images__img-container'; 79 | container.appendChild(resource); 80 | item.appendChild(container); 81 | 82 | this.imageCounter++; 83 | } 84 | 85 | getTextColor(isDark: boolean) { 86 | return isDark ? 'white' : 'black'; 87 | } 88 | 89 | getColorInfo(color: FastAverageColorResult, algorithm: string, checked: boolean) { 90 | const text = [ 91 | color.rgb, 92 | color.rgba, 93 | color.hex, 94 | color.hexa 95 | ].join(', '); 96 | 97 | const label = document.createElement('label') 98 | label.className = 'images__algorithm'; 99 | label.style.background = color.rgb; 100 | label.style.color = this.getTextColor(color.isDark); 101 | 102 | const input = document.createElement('input'); 103 | input.name = 'radio' + this.imageCounter; 104 | input.type = 'radio'; 105 | input.checked = checked; 106 | input.onclick = () => { 107 | this.setImageColor(label, color.rgb, color.isDark); 108 | }; 109 | label.appendChild(input); 110 | label.appendChild(document.createTextNode(algorithm + ': ' + text)) 111 | 112 | return label; 113 | } 114 | 115 | capture() { 116 | // @ts-ignore 117 | navigator.getUserMedia({ video: true, audio: false }, mediaStream => { 118 | // Firefox 119 | if (!('readyState' in mediaStream)) { 120 | // @ts-ignore 121 | mediaStream.readyState = 'live'; 122 | } 123 | 124 | const video = document.createElement('video'); 125 | const previewStream = new MediaStream(mediaStream); 126 | 127 | if (window.HTMLMediaElement) { 128 | video.srcObject = previewStream; // Safari 11 doesn't allow use of createObjectURL for MediaStream 129 | } else { 130 | // @ts-ignore 131 | video.src = URL.createObjectURL(previewStream); 132 | } 133 | 134 | video.muted = true; 135 | // Required by Safari on iOS 11. See https://webkit.org/blog/6784 136 | video.setAttribute('playsinline', ''); 137 | video.play(); 138 | video.addEventListener('playing', () => { 139 | setTimeout(() => { 140 | const canvas = document.createElement('canvas'); 141 | canvas.width = video.videoWidth; 142 | canvas.height = video.videoHeight; 143 | 144 | const ctx = canvas.getContext('2d'); 145 | if (ctx) { 146 | ctx.drawImage(video, 0, 0); 147 | } 148 | 149 | const image = new Image(); 150 | image.src = canvas.toDataURL('image/png'); 151 | 152 | this.getColors(image).then(colors => { 153 | this.addImage(image, 'Camera', colors); 154 | // @ts-ignore 155 | mediaStream.stop(); 156 | }); 157 | }, 500); 158 | }); 159 | }, () => { 160 | // console.log('failure to get media'); 161 | }); 162 | } 163 | }; 164 | 165 | // @ts-ignore 166 | navigator.getUserMedia = navigator.getUserMedia || 167 | // @ts-ignore 168 | navigator.webkitGetUserMedia || 169 | // @ts-ignore 170 | navigator.mozGetUserMedia; 171 | 172 | window.addEventListener('load', () => { 173 | new App(); 174 | }, false); 175 | -------------------------------------------------------------------------------- /src/gallery/index.css: -------------------------------------------------------------------------------- 1 | .big-image { 2 | position: fixed; 3 | 4 | left: 50%; 5 | top: 50%; 6 | max-width: 60%; 7 | width: 640px; 8 | height: auto; 9 | 10 | transform: translate(-50%, -50%); 11 | 12 | transition: opacity 1s ease; 13 | opacity: 1; 14 | } 15 | 16 | .big-image_hidden { 17 | opacity: 0; 18 | } 19 | 20 | .big-image-border { 21 | --width: calc(100vw / 2 - 640px / 2 + .5px); 22 | --height: calc(100vh / 2 - 480px / 2 + .5px); 23 | 24 | position: fixed; 25 | 26 | left: 0; 27 | top: 0; 28 | width: 100%; 29 | height: 100%; 30 | box-sizing: border-box; 31 | 32 | border-left: var(--width) solid transparent; 33 | border-right: var(--width) solid transparent; 34 | border-top: var(--height) solid transparent; 35 | border-bottom: var(--height) solid transparent; 36 | 37 | transition: border-color .5s ease; 38 | } 39 | 40 | @media (max-width: 1066px) { 41 | .big-image-border { 42 | outline: 10px solid red; 43 | --width: calc(100vw / 2 - 60vw / 2 + .5px); 44 | --height: calc(100vh / 2 - 45vw / 2 + .5px); 45 | } 46 | } 47 | 48 | .slider { 49 | position: fixed; 50 | 51 | left: 50%; 52 | bottom: 0; 53 | 54 | transform: translateX(-50%); 55 | 56 | white-space: nowrap; 57 | 58 | background: rgba(255, 255, 255, .2); 59 | 60 | border-radius: 5px 5px 0 0; 61 | 62 | line-height: 0; 63 | } 64 | 65 | .slider__item { 66 | border: 0; 67 | 68 | margin: 5px 5px 5px 0; 69 | width: 80px; 70 | height: 60px; 71 | 72 | cursor: pointer; 73 | 74 | transition: opacity .3s ease; 75 | } 76 | 77 | .slider__item_active { 78 | outline: 5px solid orange; 79 | } 80 | 81 | .slider__item_first { 82 | margin-left: 5px; 83 | } 84 | 85 | .slider__item_last { 86 | border-radius: 0 5px 0 0; 87 | } 88 | 89 | .slider__item:hover { 90 | opacity: .6; 91 | } 92 | 93 | .slider__item_active:hover { 94 | opacity: 1; 95 | } 96 | -------------------------------------------------------------------------------- /src/gallery/index.ts: -------------------------------------------------------------------------------- 1 | import { FastAverageColor } from 'fast-average-color'; 2 | 3 | import '../common'; 4 | import './index.css'; 5 | 6 | const fac = new FastAverageColor(); 7 | 8 | window.addEventListener('load', () => { 9 | const items = Array.from(document.querySelectorAll('.slider__item')); 10 | const border = document.querySelector('.big-image-border') as HTMLDivElement; 11 | const bigImage = document.querySelector('.big-image') as HTMLImageElement; 12 | 13 | bigImage.classList.remove('big-image_hidden'); 14 | 15 | function onClick(elem: HTMLImageElement) { 16 | for (let item of items) { 17 | item.classList.remove('slider__item_active'); 18 | } 19 | 20 | elem.classList.add('slider__item_active'); 21 | 22 | bigImage.src = elem.src; 23 | 24 | const width = bigImage.naturalWidth; 25 | const height = bigImage.naturalHeight; 26 | const size = 30; 27 | 28 | const top = fac.getColor(elem, {left: 0, top: 0, width: width, height: size}); 29 | const bottom = fac.getColor(elem, {left: 0, top: height - size, width: width, height: size}); 30 | const left = fac.getColor(elem, {left: 0, top: 0, width: size, height: height}); 31 | const right = fac.getColor(elem, {left: width - size, top: 0, width: size, height: height}); 32 | 33 | border.style.borderTopColor = top.rgb; 34 | border.style.borderRightColor = right.rgb; 35 | border.style.borderBottomColor = bottom.rgb; 36 | border.style.borderLeftColor = left.rgb; 37 | } 38 | 39 | onClick(items[0]); 40 | 41 | for (let item of items) { 42 | item.onclick = function() { 43 | onClick(this as HTMLImageElement); 44 | }; 45 | } 46 | }); 47 | -------------------------------------------------------------------------------- /src/gallery_vertical/getColors.ts: -------------------------------------------------------------------------------- 1 | import { FastAverageColor } from 'fast-average-color'; 2 | 3 | const fac = new FastAverageColor(); 4 | 5 | export function getVerticalColors(img: HTMLImageElement) { 6 | const result: Array = []; 7 | 8 | for (let i = 0; i < img.height; i++) { 9 | const color = fac.getColor(img, { 10 | left: 0, 11 | width: img.width, 12 | top: i, 13 | height: 1, 14 | mode: 'precision', 15 | }); 16 | 17 | result.push(color.rgba); 18 | } 19 | 20 | return result; 21 | } 22 | 23 | export function getHorizontalColors(img: HTMLImageElement) { 24 | const result: Array = []; 25 | 26 | for (let i = 0; i < img.width; i++) { 27 | const color = fac.getColor(img, { 28 | left: i, 29 | top: 0, 30 | width: 1, 31 | height: img.height, 32 | mode: 'precision', 33 | }); 34 | 35 | result.push(color.rgba); 36 | } 37 | 38 | return result; 39 | } 40 | 41 | export function getVerticalGradient(data: Array) { 42 | return `linear-gradient(180deg, ${data.join(', ')})`; 43 | } 44 | 45 | export function getHorizontalGradient(data: Array) { 46 | return `linear-gradient(90deg, ${data.join(', ')})`; 47 | } 48 | -------------------------------------------------------------------------------- /src/gallery_vertical/index.css: -------------------------------------------------------------------------------- 1 | .big-image-container { 2 | position: fixed; 3 | 4 | left: 50%; 5 | top: 50%; 6 | width: 640px; 7 | height: auto; 8 | 9 | transform: translate(-50%, -50%); 10 | 11 | transition: opacity 1s ease; 12 | opacity: 1; 13 | } 14 | 15 | .big-image { 16 | display: block; 17 | width: 100%; 18 | height: auto; 19 | } 20 | 21 | .vertical-gradient { 22 | position: absolute; 23 | 24 | left: 0; 25 | top: 0; 26 | width: 50px; 27 | height: 100%; 28 | margin-left: -60px; 29 | } 30 | 31 | .horizontal-gradient { 32 | position: absolute; 33 | 34 | left: 0; 35 | top: 0; 36 | width: 100%; 37 | height: 50px; 38 | margin-top: -60px; 39 | } 40 | 41 | .big-image_hidden { 42 | opacity: 0; 43 | } 44 | 45 | .slider { 46 | position: fixed; 47 | 48 | left: 50%; 49 | bottom: 0; 50 | 51 | transform: translateX(-50%); 52 | 53 | white-space: nowrap; 54 | 55 | background: rgba(255, 255, 255, .2); 56 | 57 | border-radius: 5px 5px 0 0; 58 | 59 | line-height: 0; 60 | } 61 | 62 | .slider__item { 63 | border: 0; 64 | 65 | margin: 5px 5px 5px 0; 66 | width: 80px; 67 | height: 60px; 68 | 69 | cursor: pointer; 70 | 71 | transition: opacity .3s ease; 72 | } 73 | 74 | .slider__item_active { 75 | outline: 5px solid orange; 76 | } 77 | 78 | .slider__item_first { 79 | margin-left: 5px; 80 | } 81 | 82 | .slider__item_last { 83 | border-radius: 0 5px 0 0; 84 | } 85 | 86 | .slider__item:hover { 87 | opacity: .6; 88 | } 89 | 90 | .slider__item_active:hover { 91 | opacity: 1; 92 | } 93 | -------------------------------------------------------------------------------- /src/gallery_vertical/index.ts: -------------------------------------------------------------------------------- 1 | import '../common'; 2 | import { getVerticalColors, getVerticalGradient, getHorizontalGradient, getHorizontalColors } from './getColors'; 3 | import './index.css'; 4 | 5 | window.addEventListener('load', () => { 6 | const items = Array.from(document.querySelectorAll('.slider__item')); 7 | const bigImage = document.querySelector('.big-image') as HTMLImageElement; 8 | const verticalGradient = document.querySelector('.vertical-gradient') as HTMLDivElement; 9 | const horizontalGradient = document.querySelector('.horizontal-gradient') as HTMLDivElement; 10 | 11 | bigImage.classList.remove('big-image_hidden'); 12 | 13 | function onClick(elem: HTMLImageElement) { 14 | for (let item of items) { 15 | item.classList.remove('slider__item_active'); 16 | } 17 | 18 | elem.classList.add('slider__item_active'); 19 | 20 | bigImage.onload = function() { 21 | const verticalColors = getVerticalColors(bigImage); 22 | const horizontalColors = getHorizontalColors(bigImage); 23 | verticalGradient.style.background = getVerticalGradient(verticalColors); 24 | horizontalGradient.style.background = getHorizontalGradient(horizontalColors); 25 | }; 26 | 27 | bigImage.src = elem.src; 28 | 29 | } 30 | 31 | onClick(items[0]); 32 | 33 | for (let item of items) { 34 | item.onclick = function() { 35 | onClick(this as HTMLImageElement); 36 | }; 37 | } 38 | }); 39 | -------------------------------------------------------------------------------- /src/gradient/index.css: -------------------------------------------------------------------------------- 1 | .item 2 | { 3 | position: relative; 4 | 5 | float: left; 6 | 7 | box-sizing: border-box; 8 | width: 400px; 9 | max-width: 100%; 10 | margin: 1px 1px 0 0; 11 | 12 | transition: all .5s ease; 13 | } 14 | 15 | .item__image 16 | { 17 | position: relative; 18 | } 19 | 20 | img 21 | { 22 | display: block; 23 | 24 | width: 100%; 25 | } 26 | 27 | .item__gradient 28 | { 29 | position: absolute; 30 | z-index: 1; 31 | top: 0; 32 | right: 0; 33 | bottom: 50%; 34 | left: 0; 35 | 36 | transition: all .5s ease; 37 | } 38 | 39 | .item_bottom .item__gradient 40 | { 41 | top: 50%; 42 | bottom: 0; 43 | } 44 | 45 | 46 | .item__text 47 | { 48 | padding: 20px; 49 | } 50 | -------------------------------------------------------------------------------- /src/gradient/index.ts: -------------------------------------------------------------------------------- 1 | import { FastAverageColor } from 'fast-average-color'; 2 | 3 | import '../common'; 4 | import './index.css'; 5 | 6 | const fac = new FastAverageColor(); 7 | 8 | window.addEventListener('load', () => { 9 | Array.from(document.querySelectorAll('.item')).forEach(item => { 10 | const image = item.querySelector('img') as HTMLImageElement; 11 | const isBottom = item.classList.contains('item_bottom'); 12 | const gradient = item.querySelector('.item__gradient') as HTMLDivElement; 13 | 14 | const height = image.naturalHeight; 15 | const size = 50; 16 | 17 | const color = fac.getColor( 18 | image, 19 | isBottom ? 20 | { top: height - size, height: size } : 21 | { height: size } 22 | ); 23 | const colorEnd = [...color.value.slice(0, 3), 0].join(','); 24 | 25 | item.style.background = color.rgb; 26 | item.style.color = color.isDark ? 'white' : 'black'; 27 | 28 | if (isBottom) { 29 | gradient.style.background = `linear-gradient(to bottom, rgba(${colorEnd}) 0%, ${color.rgba} 100%)`; 30 | } else { 31 | gradient.style.background = `linear-gradient(to top, rgba(${colorEnd}) 0%, ${color.rgba} 100%)`; 32 | } 33 | }); 34 | }, false); 35 | -------------------------------------------------------------------------------- /src/gradient_stripes/index.css: -------------------------------------------------------------------------------- 1 | .items 2 | { 3 | box-sizing: border-box; 4 | padding: 20px; 5 | } 6 | 7 | .item 8 | { 9 | position: relative; 10 | 11 | float: left; 12 | 13 | box-sizing: border-box; 14 | width: 460px; 15 | max-width: 100%; 16 | margin: 0 20px 20px 0; 17 | padding: 30px; 18 | 19 | transition: all .5s ease; 20 | } 21 | 22 | img 23 | { 24 | display: block; 25 | 26 | width: 100%; 27 | } 28 | 29 | .item__text 30 | { 31 | padding: 10px 0 10px 0; 32 | } 33 | -------------------------------------------------------------------------------- /src/gradient_stripes/index.ts: -------------------------------------------------------------------------------- 1 | import { FastAverageColor, FastAverageColorResult } from 'fast-average-color'; 2 | 3 | import '../common'; 4 | import './index.css'; 5 | 6 | const fac = new FastAverageColor(); 7 | 8 | function getGradient(image: HTMLImageElement, padding = 30, count = 10) { 9 | const naturalHeight = image.naturalHeight; 10 | const height = image.height; 11 | 12 | const naturalHeightPart = Math.floor(naturalHeight / count); 13 | const heightPart = Math.floor(height / count); 14 | 15 | const parts: string[] = []; 16 | 17 | const colors: FastAverageColorResult[] = []; 18 | let value = 'linear-gradient(to bottom, '; 19 | 20 | for (let i = 0; i < count; i++) { 21 | const color = fac.getColor(image, { 22 | left: 0, 23 | top: i * naturalHeightPart, 24 | height: naturalHeightPart 25 | }); 26 | 27 | const top = i ? (i * heightPart) + padding : 0; 28 | const bottom = ((i + 1) * heightPart - 1) + padding; 29 | 30 | parts.push(`${color.rgb} ${top}px, ${color.rgb} ${bottom}px`); 31 | colors.push(color); 32 | } 33 | 34 | value += parts.join(', '); 35 | value += ')'; 36 | 37 | return { 38 | value, 39 | lastColor: colors[colors.length - 1], 40 | }; 41 | } 42 | 43 | function updateStripes(items: HTMLElement[]) { 44 | items.forEach(item => { 45 | const image = item.querySelector('img') as HTMLImageElement; 46 | const { value, lastColor } = getGradient(image); 47 | 48 | item.style.background = value; 49 | item.style.color = lastColor.isDark ? 'white' : 'black'; 50 | }); 51 | } 52 | 53 | window.addEventListener('load', () => { 54 | const items = Array.from(document.querySelectorAll('.item')); 55 | 56 | window.addEventListener('resize', () => updateStripes(items), false); 57 | updateStripes(items); 58 | }, false); 59 | -------------------------------------------------------------------------------- /src/text-photo/index.css: -------------------------------------------------------------------------------- 1 | .controls 2 | { 3 | font-size: 11px; 4 | line-height: 14px; 5 | 6 | position: fixed; 7 | z-index: 100; 8 | top: 70px; 9 | left: 10px; 10 | 11 | margin: 0; 12 | padding: 0; 13 | 14 | color: #999; 15 | } 16 | 17 | .controls label 18 | { 19 | display: block; 20 | } 21 | 22 | .controls input 23 | { 24 | font-size: 11px; 25 | line-height: 14px; 26 | } 27 | 28 | .big-photo, 29 | .text-photo 30 | { 31 | position: fixed; 32 | top: 50%; 33 | left: 50%; 34 | 35 | transform: translate(-50%, -50%); 36 | } 37 | 38 | @media (max-width: 900px) and (min-width: 800px) 39 | { 40 | .big-photo, 41 | .text-photo 42 | { 43 | transform: translate(-50%, -50%) scale(.9); 44 | } 45 | } 46 | 47 | @media (max-width: 800px) and (min-width: 700px) 48 | { 49 | .big-photo, 50 | .text-photo 51 | { 52 | transform: translate(-50%, -50%) scale(.8); 53 | } 54 | } 55 | 56 | @media (max-width: 700px) and (min-width: 600px) 57 | { 58 | .big-photo, 59 | .text-photo 60 | { 61 | transform: translate(-50%, -50%) scale(.7); 62 | } 63 | } 64 | 65 | @media (max-width: 600px) 66 | { 67 | .big-photo, 68 | .text-photo 69 | { 70 | transform: translate(-50%, -50%) scale(.6); 71 | } 72 | } 73 | 74 | .text-photo 75 | { 76 | visibility: visible; 77 | 78 | opacity: 0; 79 | } 80 | 81 | .big-photo_fade 82 | { 83 | transition: opacity 2s ease; 84 | 85 | opacity: 0; 86 | } 87 | 88 | .big-photo_load 89 | { 90 | visibility: hidden; 91 | } 92 | 93 | .text-photo_fade 94 | { 95 | transition: opacity 2s ease; 96 | 97 | opacity: 1; 98 | } 99 | -------------------------------------------------------------------------------- /src/text-photo/index.ts: -------------------------------------------------------------------------------- 1 | import { FastAverageColor } from 'fast-average-color'; 2 | 3 | import '../common'; 4 | import './index.css'; 5 | 6 | type Browser = 'firefox' | 'ie' | 'chrome' | 'opera'; 7 | 8 | const fac = new FastAverageColor(); 9 | 10 | class App { 11 | image = document.querySelector('.big-photo') as HTMLImageElement; 12 | canvas = document.querySelector('.text-photo') as HTMLCanvasElement; 13 | ctx = this.canvas.getContext('2d') as CanvasRenderingContext2D; 14 | browser?: Browser; 15 | 16 | constructor() { 17 | Array.from(document.querySelectorAll('input')).forEach(input => { 18 | const that = this; 19 | input.onclick = () => { 20 | that.setImage(input.value as Browser); 21 | }; 22 | }); 23 | 24 | this.setImage('firefox'); 25 | } 26 | 27 | setImage(browser: Browser) { 28 | if (this.browser === browser) { return; } 29 | 30 | this.browser = browser; 31 | this.image.classList.remove('big-photo_fade'); 32 | this.image.classList.add('big-photo_load'); 33 | this.canvas.classList.remove('text-photo_fade'); 34 | 35 | this.image.onload = () => { 36 | const { width, height } = this.image; 37 | 38 | this.canvas.width = width; 39 | this.canvas.height = height; 40 | this.ctx.clearRect(0, 0, width, height); 41 | 42 | setTimeout(() => { 43 | this.generate(this.getBrowserTitle(browser)); 44 | 45 | this.image.classList.add('big-photo_fade'); 46 | this.image.classList.remove('big-photo_load'); 47 | this.canvas.classList.add('text-photo_fade'); 48 | }, 50); 49 | }; 50 | 51 | this.image.src = `./images/${browser}.jpg`; 52 | } 53 | 54 | getBrowserTitle(browser: Browser) { 55 | return { 56 | firefox: 'Mozilla Firefox', 57 | chrome: 'Google Chrome', 58 | ie: 'Internet Explorer', 59 | opera: 'Opera', 60 | }[browser]; 61 | } 62 | 63 | generate(browserTitle: string) { 64 | const { width, height } = this.image; 65 | const x0 = width / 2; 66 | const y0 = height / 2; 67 | 68 | const n = 40; 69 | const a = 12; 70 | const pi = Math.PI; 71 | 72 | let step; 73 | let fs = 6; 74 | let pos = 0; 75 | 76 | for (let angle = pi; angle < 2 * pi * n; angle += step) { 77 | const r = a * angle / 2 / pi; 78 | step = Math.asin((fs - 2) / r) * 2; 79 | const x = x0 + r * Math.cos(angle); 80 | const y = y0 + r * Math.sin(angle); 81 | 82 | if (x < 0 || y < 0 || x > width || y > height) { 83 | continue; 84 | } 85 | 86 | const color = fac.getColor(this.image, { 87 | left: x, 88 | top: y, 89 | width: Math.floor(fs), 90 | height: Math.floor(fs) * 0.6 91 | }); 92 | 93 | this.ctx.save(); 94 | 95 | this.ctx.font = Math.floor(fs) + 'px Arial'; 96 | this.ctx.fillStyle = color.rgb; 97 | if (!browserTitle[pos]) { 98 | pos = 0; 99 | } 100 | 101 | this.ctx.translate(x, y); 102 | this.ctx.rotate(angle); 103 | this.ctx.fillText(browserTitle[pos], 0, 0); 104 | pos++; 105 | 106 | this.ctx.restore(); 107 | 108 | fs += 0.005; 109 | } 110 | } 111 | }; 112 | 113 | window.addEventListener('load', () => { 114 | new App(); 115 | }, false); 116 | 117 | -------------------------------------------------------------------------------- /src/timeline/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | background: #000; 3 | color: #fff; 4 | 5 | padding: 10px; 6 | } 7 | 8 | .title-container { 9 | position: absolute; 10 | left: 0; 11 | right: 0; 12 | top: 10px; 13 | text-align: center; 14 | } 15 | 16 | .title { 17 | display: inline-block; 18 | margin: 0 auto; 19 | padding: 0; 20 | color: #fff; 21 | font-size: 30px; 22 | font-weight: normal; 23 | line-height: 30px; 24 | background: linear-gradient(90deg, #555, #fff); 25 | background-clip: text; 26 | -webkit-background-clip: text; 27 | -webkit-text-fill-color: transparent; 28 | white-space: nowrap; 29 | } 30 | 31 | .spinner, 32 | .spinner:after { 33 | border-radius: 50%; 34 | width: 50px; 35 | height: 50px; 36 | } 37 | 38 | .spinner { 39 | position: fixed; 40 | left: 50%; 41 | top: 50%; 42 | transform: translate(-50%, -50%); 43 | border-top: 1.1em solid rgba(255, 255, 255, 0.2); 44 | border-right: 1.1em solid rgba(255, 255, 255, 0.2); 45 | border-bottom: 1.1em solid rgba(255, 255, 255, 0.2); 46 | border-left: 1.1em solid #ffffff; 47 | transform: translateZ(0); 48 | animation: spinner 1.1s infinite linear; 49 | } 50 | 51 | @keyframes spinner { 52 | 0% { 53 | transform: rotate(0deg); 54 | } 55 | 100% { 56 | transform: rotate(360deg); 57 | } 58 | } 59 | 60 | .timeline { 61 | margin: 0 0 10px 0; 62 | } 63 | 64 | .timeline__label { 65 | font-size: 12px; 66 | color: #ccc; 67 | } 68 | 69 | .timeline__colors { 70 | width: auto; 71 | line-height: 0; 72 | font-size: 0; 73 | border: 2px solid rgba(255,255,255,0.1); 74 | } 75 | 76 | .timeline__color { 77 | display: inline-block; 78 | width: 1px; 79 | height: 50px; 80 | } 81 | 82 | .progress { 83 | position: fixed; 84 | top: 10px; 85 | right: 10px; 86 | text-align: right; 87 | } 88 | 89 | .demo { 90 | text-align: center; 91 | } 92 | 93 | .radial-demo, 94 | .conic-demo { 95 | display: inline-block; 96 | width: 48vw; 97 | height: 48vw; 98 | margin-right: 10px; 99 | } 100 | 101 | video { 102 | position: fixed; 103 | right: 10px; 104 | bottom: 10px; 105 | border-radius: 10px; 106 | max-width: 400px; 107 | } 108 | 109 | .movie-selector { 110 | margin-top: 50px; 111 | margin-bottom: 10px; 112 | } 113 | -------------------------------------------------------------------------------- /src/timeline/index.ts: -------------------------------------------------------------------------------- 1 | import { FastAverageColor, FastAverageColorResult } from 'fast-average-color'; 2 | import { secsToHMS } from './utils'; 3 | 4 | import '../common'; 5 | import './index.css'; 6 | 7 | const fac = new FastAverageColor(); 8 | 9 | const TIMELINE_HEIGHT = 50; 10 | 11 | interface ColorsOfMoviesData { 12 | step: number; 13 | count: number; 14 | duration: number, 15 | averageColors: string[], 16 | averageSqrtColors: string[], 17 | dominantColors: string[], 18 | palette: number[][]; 19 | } 20 | 21 | class ColorsOfMovies { 22 | private video: HTMLVideoElement; 23 | private currentSrc: string = ''; 24 | private averageTimeline: HTMLCanvasElement; 25 | private dominantTimeline: HTMLCanvasElement; 26 | private radialDemo: HTMLDivElement; 27 | private conicDemo: HTMLDivElement; 28 | private spinner: HTMLDivElement; 29 | private title: HTMLDivElement; 30 | private progress: HTMLDivElement; 31 | private selectMovie: HTMLSelectElement; 32 | private uploadFile: HTMLInputElement; 33 | private originalDocumentTitle = document.title; 34 | 35 | constructor() { 36 | this.video = document.querySelector('video')!; 37 | this.averageTimeline = document.querySelector('.timeline_type_average .timeline__colors')!; 38 | this.dominantTimeline = document.querySelector('.timeline_type_dominant .timeline__colors')!; 39 | this.spinner = document.querySelector('.spinner')!; 40 | 41 | this.radialDemo = document.querySelector('.radial-demo')!; 42 | this.conicDemo = document.querySelector('.conic-demo')!; 43 | this.title = document.querySelector('.title')!; 44 | 45 | this.progress = document.querySelector('.progress')!; 46 | 47 | this.selectMovie = document.querySelector('.movies')!; 48 | this.selectMovie.addEventListener('change', () => { 49 | this.currentSrc = this.selectMovie.selectedOptions[0].value; 50 | this.start(this.currentSrc); 51 | }); 52 | 53 | this.uploadFile = document.querySelector('.upload-file')!; 54 | 55 | const that = this; 56 | this.uploadFile.addEventListener('change', function() { 57 | const files = this.files; 58 | 59 | if (files) { 60 | const file = files.item(0); 61 | if (file) { 62 | const src = URL.createObjectURL(file); 63 | that.currentSrc = src; 64 | 65 | that.start(src); 66 | } 67 | } 68 | }); 69 | 70 | this.start(this.selectMovie.selectedOptions[0].value); 71 | } 72 | 73 | public start(src: string) { 74 | this.reset(); 75 | this.showSpinner(); 76 | 77 | if (src) { 78 | this.video.src = src; 79 | this.video.addEventListener('canplay', this.handleCanPlay); 80 | } 81 | } 82 | 83 | private getStep(duration: number) { 84 | let step = Math.ceil(duration / document.documentElement.clientWidth); 85 | 86 | if (step < 1) { 87 | step = 1; 88 | } 89 | 90 | if (step > 10) { 91 | step = 10; 92 | } 93 | 94 | return step; 95 | } 96 | 97 | private handleCanPlay = () => { 98 | this.getColorsFromMovie(this.currentSrc); 99 | this.hideSpinner(); 100 | this.video.removeEventListener('canplay', this.handleCanPlay); 101 | } 102 | 103 | private hideSpinner() { 104 | this.spinner.style.display = 'none'; 105 | } 106 | 107 | private showSpinner() { 108 | this.spinner.style.display = 'block'; 109 | } 110 | 111 | async getColorsFromMovie(src: string) { 112 | const duration = this.video.duration; 113 | const startTime = Date.now(); 114 | const step = this.getStep(duration); 115 | const data: ColorsOfMoviesData = { 116 | step, 117 | duration: duration, 118 | count: 0, 119 | averageColors: [], 120 | averageSqrtColors: [], 121 | dominantColors: [], 122 | palette: [], 123 | }; 124 | 125 | const width = Math.ceil(duration / step); 126 | this.averageTimeline.width = width; 127 | this.averageTimeline.height = TIMELINE_HEIGHT; 128 | this.dominantTimeline.width = width; 129 | this.dominantTimeline.height = TIMELINE_HEIGHT; 130 | 131 | let x = 0; 132 | for (let i = 0; i < duration; i += step) { 133 | this.video.currentTime = i; 134 | await this.waitForSeek(); 135 | 136 | if (src !== this.currentSrc) { 137 | return; 138 | } 139 | 140 | const averageColor = fac.getColor(this.video, { algorithm: 'simple'}); 141 | const averageSqrtColor = fac.getColor(this.video, { algorithm: 'sqrt'}); 142 | const dominantColor = fac.getColor(this.video, { algorithm: 'dominant' }); 143 | 144 | data.averageColors.push(averageColor.rgb); 145 | data.averageSqrtColors.push(averageSqrtColor.rgb); 146 | data.dominantColors.push(dominantColor.rgb); 147 | 148 | this.addColor(this.averageTimeline, averageColor, x); 149 | this.addColor(this.dominantTimeline, dominantColor, x); 150 | 151 | const percents = Math.floor(i / duration * 100) + '%'; 152 | this.progress.innerHTML = [ 153 | percents + ', step: ' + step + ' s', 154 | secsToHMS(i) + ' / ' + secsToHMS(duration), 155 | Math.floor((Date.now() - startTime) / 1000) + ' s' 156 | ].join('
'); 157 | 158 | document.title = percents + ' — ' + this.originalDocumentTitle; 159 | 160 | this.radialDemo.style.background = 'radial-gradient(' + data.dominantColors.join(',') + ')'; 161 | this.conicDemo.style.background = 'conic-gradient(' + data.dominantColors.join(',') + ')'; 162 | 163 | x++; 164 | } 165 | 166 | this.title.style.background = 'linear-gradient(90deg,' + data.dominantColors.join(',') + ')'; 167 | this.title.style.backgroundClip = 'text'; 168 | // Fix for Chrome 169 | this.title.style.webkitBackgroundClip = 'text'; 170 | this.progress.innerHTML = ''; 171 | document.title = this.originalDocumentTitle; 172 | 173 | this.hideVideo(); 174 | } 175 | 176 | private reset() { 177 | this.title.style.background = ''; 178 | document.title = this.originalDocumentTitle; 179 | 180 | this.radialDemo.style.background = ''; 181 | this.conicDemo.style.background = ''; 182 | 183 | this.resetCanvas(this.averageTimeline); 184 | this.resetCanvas(this.dominantTimeline); 185 | 186 | this.progress.innerHTML = ''; 187 | 188 | this.video.removeEventListener('canplay', this.handleCanPlay); 189 | 190 | this.showVideo(); 191 | } 192 | 193 | private resetCanvas(canvas: HTMLCanvasElement) { 194 | const ctx = canvas.getContext('2d'); 195 | if (ctx) { 196 | ctx.clearRect(0, 0, canvas.width, canvas.height); 197 | } 198 | } 199 | 200 | private showVideo() { 201 | this.video.style.display = 'block'; 202 | } 203 | 204 | private hideVideo() { 205 | this.video.style.display = 'none'; 206 | } 207 | 208 | private addColor( 209 | canvas: HTMLCanvasElement, 210 | color: FastAverageColorResult, 211 | x: number, 212 | ) { 213 | const ctx = canvas.getContext('2d'); 214 | if (!ctx) return; 215 | 216 | ctx.fillStyle = color.rgba; 217 | ctx.fillRect(x, 0, 1, TIMELINE_HEIGHT); 218 | } 219 | 220 | private waitForSeek() { 221 | return new Promise(resolve => { 222 | this.video.onseeked = () => { 223 | this.video.onseeked = null; 224 | resolve(); 225 | } 226 | }); 227 | } 228 | } 229 | 230 | new ColorsOfMovies(); 231 | -------------------------------------------------------------------------------- /src/timeline/utils.ts: -------------------------------------------------------------------------------- 1 | function leadZero(num: number) { 2 | return num > 9 ? num : '0' + num; 3 | } 4 | 5 | export function secsToHMS(value: number) { 6 | const hours = Math.floor(value / 3600); 7 | const mins = Math.floor((value - hours * 3600) / 60); 8 | const secs = Math.floor(value - hours * 3600 - mins * 60); 9 | 10 | return [hours, leadZero(mins), leadZero(secs)].join(':'); 11 | } 12 | -------------------------------------------------------------------------------- /text-photo.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Get average color for text photo 5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /timeline.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 🎨 Colors of movies 🎬 5 | 6 | 7 | 8 | 9 | 10 |
11 |

Colors of movies

12 |
Select or upload a movie
18 | 19 |
20 | 21 |
22 |
Average:
23 | 24 |
25 | 29 |
30 |
Dominant:
31 | 32 |
33 | 34 |
35 |
36 |
37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Basic Options */ 4 | // "incremental": true, /* Enable incremental compilation */ 5 | "target": "es5", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */ 6 | "module": "ES2015", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */ 7 | "lib": [ 8 | "DOM", "ES6" 9 | ], /* Specify library files to be included in the compilation. */ 10 | // "allowJs": true, /* Allow javascript files to be compiled. */ 11 | // "checkJs": true, /* Report errors in .js files. */ 12 | // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ 13 | // "declaration": true, /* Generates corresponding '.d.ts' file. */ 14 | // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ 15 | // "sourceMap": true, /* Generates corresponding '.map' file. */ 16 | // "outFile": "./", /* Concatenate and emit output to single file. */ 17 | "outDir": "./dist", /* Redirect output structure to the directory. */ 18 | "rootDir": "./src", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ 19 | // "composite": true, /* Enable project compilation */ 20 | // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */ 21 | // "removeComments": true, /* Do not emit comments to output. */ 22 | // "noEmit": true, /* Do not emit outputs. */ 23 | // "importHelpers": true, /* Import emit helpers from 'tslib'. */ 24 | // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ 25 | // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ 26 | 27 | /* Strict Type-Checking Options */ 28 | "strict": true, /* Enable all strict type-checking options. */ 29 | "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ 30 | "strictNullChecks": true, /* Enable strict null checks. */ 31 | "strictFunctionTypes": true, /* Enable strict checking of function types. */ 32 | "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ 33 | "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ 34 | "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ 35 | "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ 36 | 37 | /* Additional Checks */ 38 | "noUnusedLocals": true, /* Report errors on unused locals. */ 39 | "noUnusedParameters": true, /* Report errors on unused parameters. */ 40 | "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ 41 | "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ 42 | 43 | /* Module Resolution Options */ 44 | "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ 45 | // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ 46 | // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ 47 | // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ 48 | // "typeRoots": [], /* List of folders to include type definitions from. */ 49 | // "types": [], /* Type declaration files to be included in compilation. */ 50 | // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ 51 | "esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ 52 | // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ 53 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ 54 | 55 | /* Source Map Options */ 56 | // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ 57 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 58 | // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ 59 | // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ 60 | 61 | /* Experimental Options */ 62 | // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ 63 | // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ 64 | 65 | /* Advanced Options */ 66 | "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */ 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /videos/1.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fast-average-color/examples/96b6a7d45fa4d1bc335011f1e4cedad0457c65ac/videos/1.mp4 -------------------------------------------------------------------------------- /webgl.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | WebGL Cube with Fast Average Color 5 | 14 | 15 | 16 | 17 | 18 | 19 | 236 | 237 | --------------------------------------------------------------------------------