├── styles ├── system-fonts.css ├── flexbox-center-container.css └── style.css ├── .prettierrc.js ├── index.html ├── readme.md └── scripts ├── confetti.min.js └── index.js /styles/system-fonts.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: -apple-system, 3 | BlinkMacSystemFont, 4 | "Segoe UI", 5 | Roboto, 6 | Oxygen-Sans, 7 | Ubuntu, 8 | Cantarell, 9 | "Helvetica Neue", 10 | sans-serif; 11 | } -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | tabWidth: 2, 3 | printWidth: 80, 4 | endOfLine: 'lf', 5 | arrowParens: 'avoid', 6 | trailingComma: 'none', 7 | semi: true, 8 | useTabs: false, 9 | singleQuote: true, 10 | bracketSpacing: true 11 | }; 12 | -------------------------------------------------------------------------------- /styles/flexbox-center-container.css: -------------------------------------------------------------------------------- 1 | html, body { 2 | height: 100%; 3 | margin: 0; 4 | } 5 | 6 | body { 7 | -webkit-box-align: center; 8 | -ms-flex-align: center; 9 | align-items: center; 10 | display: -webkit-box; 11 | display: -ms-flexbox; 12 | display: flex; 13 | } 14 | 15 | .container { 16 | margin: 0 auto; 17 | } -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Happy Birthday! 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 |

Countdown to your birthday:

19 | 25 |
26 |
27 | 28 |
29 |
30 |
31 |
32 |
33 |
34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | #### birthday counter 2 | 3 | A really simple and elegant looking birthday counter 4 | 5 |   6 | 7 | #### demo (youtube) 8 | 9 | [![Birthday Countdown + Sweet Birthday Greeting with HTML, CSS & JS](http://img.youtube.com/vi/B-f1bxYaayc/0.jpg)](https://youtu.be/B-f1bxYaayc?t=53 'Birthday Countdown + Sweet Birthday Greeting with HTML, CSS & JS') 10 | 11 |   12 | 13 | #### installation 14 | 15 | ```sh 16 | git clone https://github.com/pavanjadhaw/birthday-counter 17 | ``` 18 | 19 |   20 | 21 | #### usage 22 | 23 | Edit config object in `scripts/index.js` 24 | replace name and birthdate 25 | 26 | ```js 27 | 6 const config = { 28 | 7 birthdate: 'Jan 29, 2020', 29 | 8 name: 'Darlene' 30 | 9 }; 31 | ``` 32 | 33 |   34 | 35 | #### deploying 36 | 37 | You can deploy it to many free hosting sites 38 | 39 | Deploying to [now.sh](https://zeit.co/home) 40 | 41 | ```sh 42 | $ cd birthday-counter 43 | $ now 44 | ``` 45 | 46 |   47 | 48 | Deploying to [surge.sh](https://surge.sh/) 49 | 50 | ```sh 51 | $ cd birthday-counter 52 | $ surge 53 | ``` 54 | 55 |   56 | 57 | #### acknowledgements 58 | 59 | Feel free to use any part of this! Contributions are welcome, 60 | I managed to put this together year ago when I didnt know any js or anything about programming/webdev in general. 61 | So thanks to all copepen which I copied, use few parts of. 62 | 63 |   64 | -------------------------------------------------------------------------------- /scripts/confetti.min.js: -------------------------------------------------------------------------------- 1 | window.ConfettiGenerator=function(e){function t(e,t){e||(e=1);var i=Math.random()*e;return t?Math.floor(i):i}function i(){return{prop:a.props[t(a.props.length,!0)],x:t(a.width),y:t(a.height),radius:t(4)+1,line:Math.floor(t(65)-30),angles:[t(10,!0)+2,t(10,!0)+2,t(10,!0)+2,t(10,!0)+2],color:a.colors[t(a.colors.length,!0)],rotation:t(360,!0)*Math.PI/180,speed:t(a.clock/7)+a.clock/30}}function r(e){var t=e.radius<=3?.4:.8;switch(n.fillStyle=n.strokeStyle="rgba("+e.color+", "+t+")",n.beginPath(),e.prop){case"circle":n.moveTo(e.x,e.y),n.arc(e.x,e.y,e.radius*a.size,0,2*Math.PI,!0),n.fill();break;case"triangle":n.moveTo(e.x,e.y),n.lineTo(e.x+e.angles[0]*a.size,e.y+e.angles[1]*a.size),n.lineTo(e.x+e.angles[2]*a.size,e.y+e.angles[3]*a.size),n.closePath(),n.fill();break;case"line":n.moveTo(e.x,e.y),n.lineTo(e.x+e.line*a.size,e.y+5*e.radius),n.lineWidth=2*a.size,n.stroke();break;case"square":n.save(),n.translate(e.x+15,e.y+5),n.rotate(e.rotation),n.fillRect(-15*a.size,-5*a.size,15*a.size,5*a.size),n.restore()}}var a={target:"confetti-holder",max:80,size:1,animate:!0,props:["circle","square","triangle","line"],colors:[[165,104,246],[230,61,135],[0,199,228],[253,214,126]],clock:25,interval:null,width:window.innerWidth,height:window.innerHeight};e&&(e.target&&(a.target=e.target),e.max&&(a.max=e.max),e.size&&(a.size=e.size),void 0!==e.animate&&null!==e.animate&&(a.animate=e.animate),e.props&&(a.props=e.props),e.colors&&(a.colors=e.colors),e.clock&&(a.clock=e.clock),e.width&&(a.width=e.width),e.height&&(a.height=e.height));var o=document.getElementById(a.target),n=o.getContext("2d"),l=[];return{render:function(){function e(){n.clearRect(0,0,a.width,a.height);for(var e in l)r(l[e]);s()}function s(){for(var e=0;ea.height&&(l[e]=i,l[e].x=t(a.width,!0),l[e].y=-10)}}o.width=a.width,o.height=a.height,l=[];for(var c=0;c div { 53 | background: #F64747; 54 | position: absolute; 55 | box-shadow: 5px 10px 18px rgba(0, 0, 0, 0.4); 56 | } 57 | .giftbox > div:after, .giftbox > div:before { 58 | position: absolute; 59 | content: ""; 60 | top: 0; 61 | } 62 | .giftbox:after { 63 | position: absolute; 64 | color: #fff; 65 | width: 100%; 66 | content: "Click Me!"; 67 | left: 0; 68 | bottom: 0; 69 | font-size: 24px; 70 | text-align: center; 71 | transform: rotate(-20deg); 72 | transform-origin: 0 0; 73 | } 74 | .giftbox .cover { 75 | width: 100%; 76 | top: 0; 77 | left: 0; 78 | height: 25%; 79 | z-index: 2; 80 | } 81 | .giftbox .cover:before { 82 | position: absolute; 83 | height: 100%; 84 | left: 50%; 85 | width: 50px; 86 | transform: translateX(-50%); 87 | background: #2C3E50; 88 | } 89 | .giftbox .cover > div { 90 | position: absolute; 91 | width: 50px; 92 | height: 50px; 93 | left: 50%; 94 | top: -50px; 95 | transform: translateX(-50%); 96 | } 97 | .giftbox .cover > div:before, .giftbox .cover > div:after { 98 | position: absolute; 99 | left: 0; 100 | top: 0; 101 | width: 100%; 102 | height: 100%; 103 | content: ""; 104 | box-shadow: inset 0 0 0 15px #2C3E50; 105 | border-radius: 30px; 106 | transform-origin: 50% 100%; 107 | } 108 | .giftbox .cover > div:before { 109 | transform: translateX(-45%) skewY(40deg); 110 | } 111 | .giftbox .cover > div:after { 112 | transform: translateX(45%) skewY(-40deg); 113 | } 114 | .giftbox .box { 115 | right: 5%; 116 | left: 5%; 117 | height: 80%; 118 | bottom: 0; 119 | } 120 | .giftbox .box:before { 121 | width: 50px; 122 | height: 100%; 123 | left: 50%; 124 | transform: translateX(-50%); 125 | background: #2C3E50; 126 | } 127 | .giftbox .box:after { 128 | width: 100%; 129 | height: 30px; 130 | background: rgba(0, 0, 0, 0); 131 | } 132 | .step-1 .giftbox { 133 | animation: wobble 0.5s linear infinite forwards; 134 | } 135 | .step-1 .cover { 136 | animation: wobble 0.5s linear infinite 5s forwards; 137 | } 138 | .step-1 .icons .row span { 139 | opacity: 1; 140 | } 141 | .step-2 .giftbox:after { 142 | opacity: 0; 143 | } 144 | .step-3 .giftbox, .step-4 .giftbox { 145 | opacity: 0; 146 | z-index: 1; 147 | } 148 | .step-3 .giftbox:after, .step-4 .giftbox:after { 149 | opacity: 0; 150 | } 151 | .step-2 .giftbox .cover { 152 | animation: flyUp 2s ease-in forwards; 153 | } 154 | .step-2 .giftbox .box { 155 | animation: flyDown 2s ease-in 0.05s forwards; 156 | } 157 | @keyframes wobble { 158 | 25% { 159 | transform: rotate(4deg); 160 | } 161 | 75% { 162 | transform: rotate(-2deg); 163 | } 164 | } 165 | @keyframes flyUp { 166 | 75% { 167 | opacity: 1; 168 | } 169 | 100% { 170 | transform: translateY(-1000px) rotate(20deg); 171 | opacity: 0; 172 | } 173 | } 174 | @keyframes flyDown { 175 | 75% { 176 | opacity: 1; 177 | } 178 | 100% { 179 | transform: translateY(100%); 180 | opacity: 0; 181 | } 182 | } 183 | -------------------------------------------------------------------------------- /scripts/index.js: -------------------------------------------------------------------------------- 1 | const count = document.getElementById('count'); 2 | const head = document.getElementById('head'); 3 | const giftbox = document.getElementById('merrywrap'); 4 | const canvasC = document.getElementById('c'); 5 | 6 | const config = { 7 | birthdate: 'Jan 29, 2020', 8 | name: 'Darlene' 9 | }; 10 | 11 | function hideEverything() { 12 | head.style.display = 'none'; 13 | count.style.display = 'none'; 14 | giftbox.style.display = 'none'; 15 | canvasC.style.display = 'none'; 16 | } 17 | 18 | hideEverything(); 19 | 20 | const confettiSettings = { target: 'confetti' }; 21 | const confetti = new window.ConfettiGenerator(confettiSettings); 22 | confetti.render(); 23 | 24 | const second = 1000, 25 | minute = second * 60, 26 | hour = minute * 60, 27 | day = hour * 24; 28 | 29 | let countDown = new Date(`${config.birthdate} 00:00:00`).getTime(); 30 | x = setInterval(function() { 31 | let now = new Date().getTime(), 32 | distance = countDown - now; 33 | 34 | document.getElementById('day').innerText = Math.floor(distance / day); 35 | document.getElementById('hour').innerText = Math.floor( 36 | (distance % day) / hour 37 | ); 38 | document.getElementById('minute').innerText = Math.floor( 39 | (distance % hour) / minute 40 | ); 41 | document.getElementById('second').innerText = Math.floor( 42 | (distance % minute) / second 43 | ); 44 | 45 | let w = (c.width = window.innerWidth), 46 | h = (c.height = window.innerHeight), 47 | ctx = c.getContext('2d'), 48 | hw = w / 2, // half-width 49 | hh = h / 2, 50 | opts = { 51 | strings: ['HAPPY', 'BIRTHDAY!', config.name], 52 | charSize: 30, 53 | charSpacing: 35, 54 | lineHeight: 40, 55 | 56 | cx: w / 2, 57 | cy: h / 2, 58 | 59 | fireworkPrevPoints: 10, 60 | fireworkBaseLineWidth: 5, 61 | fireworkAddedLineWidth: 8, 62 | fireworkSpawnTime: 200, 63 | fireworkBaseReachTime: 30, 64 | fireworkAddedReachTime: 30, 65 | fireworkCircleBaseSize: 20, 66 | fireworkCircleAddedSize: 10, 67 | fireworkCircleBaseTime: 30, 68 | fireworkCircleAddedTime: 30, 69 | fireworkCircleFadeBaseTime: 10, 70 | fireworkCircleFadeAddedTime: 5, 71 | fireworkBaseShards: 5, 72 | fireworkAddedShards: 5, 73 | fireworkShardPrevPoints: 3, 74 | fireworkShardBaseVel: 4, 75 | fireworkShardAddedVel: 2, 76 | fireworkShardBaseSize: 3, 77 | fireworkShardAddedSize: 3, 78 | gravity: 0.1, 79 | upFlow: -0.1, 80 | letterContemplatingWaitTime: 360, 81 | balloonSpawnTime: 20, 82 | balloonBaseInflateTime: 10, 83 | balloonAddedInflateTime: 10, 84 | balloonBaseSize: 20, 85 | balloonAddedSize: 20, 86 | balloonBaseVel: 0.4, 87 | balloonAddedVel: 0.4, 88 | balloonBaseRadian: -(Math.PI / 2 - 0.5), 89 | balloonAddedRadian: -1 90 | }, 91 | calc = { 92 | totalWidth: 93 | opts.charSpacing * 94 | Math.max(opts.strings[0].length, opts.strings[1].length) 95 | }, 96 | Tau = Math.PI * 2, 97 | TauQuarter = Tau / 4, 98 | letters = []; 99 | 100 | ctx.font = opts.charSize + 'px Verdana'; 101 | 102 | function Letter(char, x, y) { 103 | this.char = char; 104 | this.x = x; 105 | this.y = y; 106 | 107 | this.dx = -ctx.measureText(char).width / 2; 108 | this.dy = +opts.charSize / 2; 109 | 110 | this.fireworkDy = this.y - hh; 111 | 112 | let hue = (x / calc.totalWidth) * 360; 113 | 114 | this.color = 'hsl(hue,80%,50%)'.replace('hue', hue); 115 | this.lightAlphaColor = 'hsla(hue,80%,light%,alp)'.replace('hue', hue); 116 | this.lightColor = 'hsl(hue,80%,light%)'.replace('hue', hue); 117 | this.alphaColor = 'hsla(hue,80%,50%,alp)'.replace('hue', hue); 118 | 119 | this.reset(); 120 | } 121 | Letter.prototype.reset = function() { 122 | this.phase = 'firework'; 123 | this.tick = 0; 124 | this.spawned = false; 125 | this.spawningTime = (opts.fireworkSpawnTime * Math.random()) | 0; 126 | this.reachTime = 127 | (opts.fireworkBaseReachTime + 128 | opts.fireworkAddedReachTime * Math.random()) | 129 | 0; 130 | this.lineWidth = 131 | opts.fireworkBaseLineWidth + opts.fireworkAddedLineWidth * Math.random(); 132 | this.prevPoints = [[0, hh, 0]]; 133 | }; 134 | Letter.prototype.step = function() { 135 | if (this.phase === 'firework') { 136 | if (!this.spawned) { 137 | ++this.tick; 138 | if (this.tick >= this.spawningTime) { 139 | this.tick = 0; 140 | this.spawned = true; 141 | } 142 | } else { 143 | ++this.tick; 144 | 145 | let linearProportion = this.tick / this.reachTime, 146 | armonicProportion = Math.sin(linearProportion * TauQuarter), 147 | x = linearProportion * this.x, 148 | y = hh + armonicProportion * this.fireworkDy; 149 | 150 | if (this.prevPoints.length > opts.fireworkPrevPoints) 151 | this.prevPoints.shift(); 152 | 153 | this.prevPoints.push([x, y, linearProportion * this.lineWidth]); 154 | 155 | let lineWidthProportion = 1 / (this.prevPoints.length - 1); 156 | 157 | for (let i = 1; i < this.prevPoints.length; ++i) { 158 | let point = this.prevPoints[i], 159 | point2 = this.prevPoints[i - 1]; 160 | 161 | ctx.strokeStyle = this.alphaColor.replace( 162 | 'alp', 163 | i / this.prevPoints.length 164 | ); 165 | ctx.lineWidth = point[2] * lineWidthProportion * i; 166 | ctx.beginPath(); 167 | ctx.moveTo(point[0], point[1]); 168 | ctx.lineTo(point2[0], point2[1]); 169 | ctx.stroke(); 170 | } 171 | 172 | if (this.tick >= this.reachTime) { 173 | this.phase = 'contemplate'; 174 | 175 | this.circleFinalSize = 176 | opts.fireworkCircleBaseSize + 177 | opts.fireworkCircleAddedSize * Math.random(); 178 | this.circleCompleteTime = 179 | (opts.fireworkCircleBaseTime + 180 | opts.fireworkCircleAddedTime * Math.random()) | 181 | 0; 182 | this.circleCreating = true; 183 | this.circleFading = false; 184 | 185 | this.circleFadeTime = 186 | (opts.fireworkCircleFadeBaseTime + 187 | opts.fireworkCircleFadeAddedTime * Math.random()) | 188 | 0; 189 | this.tick = 0; 190 | this.tick2 = 0; 191 | 192 | this.shards = []; 193 | 194 | let shardCount = 195 | (opts.fireworkBaseShards + 196 | opts.fireworkAddedShards * Math.random()) | 197 | 0, 198 | angle = Tau / shardCount, 199 | cos = Math.cos(angle), 200 | sin = Math.sin(angle), 201 | x = 1, 202 | y = 0; 203 | 204 | for (let i = 0; i < shardCount; ++i) { 205 | let x1 = x; 206 | x = x * cos - y * sin; 207 | y = y * cos + x1 * sin; 208 | 209 | this.shards.push(new Shard(this.x, this.y, x, y, this.alphaColor)); 210 | } 211 | } 212 | } 213 | } else if (this.phase === 'contemplate') { 214 | ++this.tick; 215 | 216 | if (this.circleCreating) { 217 | ++this.tick2; 218 | let proportion = this.tick2 / this.circleCompleteTime, 219 | armonic = -Math.cos(proportion * Math.PI) / 2 + 0.5; 220 | 221 | ctx.beginPath(); 222 | ctx.fillStyle = this.lightAlphaColor 223 | .replace('light', 50 + 50 * proportion) 224 | .replace('alp', proportion); 225 | ctx.beginPath(); 226 | ctx.arc(this.x, this.y, armonic * this.circleFinalSize, 0, Tau); 227 | ctx.fill(); 228 | 229 | if (this.tick2 > this.circleCompleteTime) { 230 | this.tick2 = 0; 231 | this.circleCreating = false; 232 | this.circleFading = true; 233 | } 234 | } else if (this.circleFading) { 235 | ctx.fillStyle = this.lightColor.replace('light', 70); 236 | ctx.fillText(this.char, this.x + this.dx, this.y + this.dy); 237 | 238 | ++this.tick2; 239 | let proportion = this.tick2 / this.circleFadeTime, 240 | armonic = -Math.cos(proportion * Math.PI) / 2 + 0.5; 241 | 242 | ctx.beginPath(); 243 | ctx.fillStyle = this.lightAlphaColor 244 | .replace('light', 100) 245 | .replace('alp', 1 - armonic); 246 | ctx.arc(this.x, this.y, this.circleFinalSize, 0, Tau); 247 | ctx.fill(); 248 | 249 | if (this.tick2 >= this.circleFadeTime) this.circleFading = false; 250 | } else { 251 | ctx.fillStyle = this.lightColor.replace('light', 70); 252 | ctx.fillText(this.char, this.x + this.dx, this.y + this.dy); 253 | } 254 | 255 | for (let i = 0; i < this.shards.length; ++i) { 256 | this.shards[i].step(); 257 | 258 | if (!this.shards[i].alive) { 259 | this.shards.splice(i, 1); 260 | --i; 261 | } 262 | } 263 | 264 | if (this.tick > opts.letterContemplatingWaitTime) { 265 | this.phase = 'balloon'; 266 | 267 | this.tick = 0; 268 | this.spawning = true; 269 | this.spawnTime = (opts.balloonSpawnTime * Math.random()) | 0; 270 | this.inflating = false; 271 | this.inflateTime = 272 | (opts.balloonBaseInflateTime + 273 | opts.balloonAddedInflateTime * Math.random()) | 274 | 0; 275 | this.size = 276 | (opts.balloonBaseSize + opts.balloonAddedSize * Math.random()) | 0; 277 | 278 | let rad = 279 | opts.balloonBaseRadian + opts.balloonAddedRadian * Math.random(), 280 | vel = opts.balloonBaseVel + opts.balloonAddedVel * Math.random(); 281 | 282 | this.vx = Math.cos(rad) * vel; 283 | this.vy = Math.sin(rad) * vel; 284 | } 285 | } else if (this.phase === 'balloon') { 286 | ctx.strokeStyle = this.lightColor.replace('light', 80); 287 | 288 | if (this.spawning) { 289 | ++this.tick; 290 | ctx.fillStyle = this.lightColor.replace('light', 70); 291 | ctx.fillText(this.char, this.x + this.dx, this.y + this.dy); 292 | 293 | if (this.tick >= this.spawnTime) { 294 | this.tick = 0; 295 | this.spawning = false; 296 | this.inflating = true; 297 | } 298 | } else if (this.inflating) { 299 | ++this.tick; 300 | 301 | let proportion = this.tick / this.inflateTime, 302 | x = (this.cx = this.x), 303 | y = (this.cy = this.y - this.size * proportion); 304 | 305 | ctx.fillStyle = this.alphaColor.replace('alp', proportion); 306 | ctx.beginPath(); 307 | generateBalloonPath(x, y, this.size * proportion); 308 | ctx.fill(); 309 | 310 | ctx.beginPath(); 311 | ctx.moveTo(x, y); 312 | ctx.lineTo(x, this.y); 313 | ctx.stroke(); 314 | 315 | ctx.fillStyle = this.lightColor.replace('light', 70); 316 | ctx.fillText(this.char, this.x + this.dx, this.y + this.dy); 317 | 318 | if (this.tick >= this.inflateTime) { 319 | this.tick = 0; 320 | this.inflating = false; 321 | } 322 | } else { 323 | this.cx += this.vx; 324 | this.cy += this.vy += opts.upFlow; 325 | 326 | ctx.fillStyle = this.color; 327 | ctx.beginPath(); 328 | generateBalloonPath(this.cx, this.cy, this.size); 329 | ctx.fill(); 330 | 331 | ctx.beginPath(); 332 | ctx.moveTo(this.cx, this.cy); 333 | ctx.lineTo(this.cx, this.cy + this.size); 334 | ctx.stroke(); 335 | 336 | ctx.fillStyle = this.lightColor.replace('light', 70); 337 | ctx.fillText( 338 | this.char, 339 | this.cx + this.dx, 340 | this.cy + this.dy + this.size 341 | ); 342 | 343 | if (this.cy + this.size < -hh || this.cx < -hw || this.cy > hw) 344 | this.phase = 'done'; 345 | } 346 | } 347 | }; 348 | 349 | function Shard(x, y, vx, vy, color) { 350 | let vel = 351 | opts.fireworkShardBaseVel + opts.fireworkShardAddedVel * Math.random(); 352 | 353 | this.vx = vx * vel; 354 | this.vy = vy * vel; 355 | 356 | this.x = x; 357 | this.y = y; 358 | 359 | this.prevPoints = [[x, y]]; 360 | this.color = color; 361 | 362 | this.alive = true; 363 | 364 | this.size = 365 | opts.fireworkShardBaseSize + opts.fireworkShardAddedSize * Math.random(); 366 | } 367 | Shard.prototype.step = function() { 368 | this.x += this.vx; 369 | this.y += this.vy += opts.gravity; 370 | 371 | if (this.prevPoints.length > opts.fireworkShardPrevPoints) 372 | this.prevPoints.shift(); 373 | 374 | this.prevPoints.push([this.x, this.y]); 375 | 376 | let lineWidthProportion = this.size / this.prevPoints.length; 377 | 378 | for (let k = 0; k < this.prevPoints.length - 1; ++k) { 379 | let point = this.prevPoints[k], 380 | point2 = this.prevPoints[k + 1]; 381 | 382 | ctx.strokeStyle = this.color.replace('alp', k / this.prevPoints.length); 383 | ctx.lineWidth = k * lineWidthProportion; 384 | ctx.beginPath(); 385 | ctx.moveTo(point[0], point[1]); 386 | ctx.lineTo(point2[0], point2[1]); 387 | ctx.stroke(); 388 | } 389 | 390 | if (this.prevPoints[0][1] > hh) this.alive = false; 391 | }; 392 | 393 | function generateBalloonPath(x, y, size) { 394 | ctx.moveTo(x, y); 395 | ctx.bezierCurveTo( 396 | x - size / 2, 397 | y - size / 2, 398 | x - size / 4, 399 | y - size, 400 | x, 401 | y - size 402 | ); 403 | ctx.bezierCurveTo(x + size / 4, y - size, x + size / 2, y - size / 2, x, y); 404 | } 405 | 406 | function anim() { 407 | window.requestAnimationFrame(anim); 408 | 409 | ctx.fillStyle = '#fff'; 410 | ctx.fillRect(0, 0, w, h); 411 | 412 | ctx.translate(hw, hh); 413 | 414 | let done = true; 415 | for (let l = 0; l < letters.length; ++l) { 416 | letters[l].step(); 417 | if (letters[l].phase !== 'done') done = false; 418 | } 419 | 420 | ctx.translate(-hw, -hh); 421 | 422 | if (done) for (let l = 0; l < letters.length; ++l) letters[l].reset(); 423 | } 424 | 425 | for (let i = 0; i < opts.strings.length; ++i) { 426 | for (let j = 0; j < opts.strings[i].length; ++j) { 427 | letters.push( 428 | new Letter( 429 | opts.strings[i][j], 430 | j * opts.charSpacing + 431 | opts.charSpacing / 2 - 432 | (opts.strings[i].length * opts.charSize) / 2, 433 | i * opts.lineHeight + 434 | opts.lineHeight / 2 - 435 | (opts.strings.length * opts.lineHeight) / 2 436 | ) 437 | ); 438 | } 439 | } 440 | 441 | window.addEventListener('resize', function() { 442 | w = c.width = window.innerWidth; 443 | h = c.height = window.innerHeight; 444 | 445 | hw = w / 2; 446 | hh = h / 2; 447 | 448 | ctx.font = opts.charSize + 'px Verdana'; 449 | }); 450 | 451 | if (distance > 0) { 452 | head.style.display = 'initial'; 453 | count.style.display = 'initial'; 454 | } else { 455 | head.style.display = 'none'; 456 | count.style.display = 'none'; 457 | giftbox.style.display = 'initial'; 458 | clearInterval(x); 459 | let merrywrap = document.getElementById('merrywrap'); 460 | let box = merrywrap.getElementsByClassName('giftbox')[0]; 461 | let step = 1; 462 | let stepMinutes = [2000, 2000, 1000, 1000]; 463 | 464 | function init() { 465 | box.addEventListener('click', openBox, false); 466 | box.addEventListener('click', showfireworks, false); 467 | } 468 | 469 | function stepClass(step) { 470 | merrywrap.className = 'merrywrap'; 471 | merrywrap.className = 'merrywrap step-' + step; 472 | } 473 | 474 | function openBox() { 475 | if (step === 1) { 476 | box.removeEventListener('click', openBox, false); 477 | } 478 | stepClass(step); 479 | if (step === 3) { 480 | } 481 | if (step === 4) { 482 | return; 483 | } 484 | setTimeout(openBox, stepMinutes[step - 1]); 485 | step++; 486 | // setTimeout(anim, 1900); 487 | } 488 | 489 | function showfireworks() { 490 | canvasC.style.display = 'initial'; 491 | setTimeout(anim, 1500); 492 | } 493 | 494 | init(); 495 | } 496 | 497 | // if (distance < 0) { 498 | // clearInterval(x); 499 | // console.log("happy birthday"); 500 | // } 501 | }, second); 502 | --------------------------------------------------------------------------------