├── .gitattributes ├── local ├── sample-pic.jpeg ├── readme.md └── sample-scroll.txt ├── src ├── resources │ ├── img │ │ ├── bulb.png │ │ ├── cake.jpg │ │ ├── door.png │ │ ├── gift.png │ │ ├── Asset.png │ │ ├── nature.jpg │ │ ├── nature.png │ │ ├── stars.png │ │ ├── Confetti.png │ │ ├── Hallway.png │ │ ├── bday-cap.png │ │ ├── bedroom.png │ │ ├── flag-left.png │ │ ├── flag-right.png │ │ ├── balloon-left.png │ │ ├── balloon-right.png │ │ ├── flag-left.svg │ │ ├── flag-right.svg │ │ ├── bulb.svg │ │ ├── gift.svg │ │ ├── door.svg │ │ ├── balloon-right.svg │ │ ├── balloon-left.svg │ │ ├── stars.svg │ │ ├── cake.svg │ │ └── confetti.svg │ ├── sfx │ │ ├── door.mp3 │ │ ├── hbd.mp3 │ │ ├── blast.mp3 │ │ ├── switch.mp3 │ │ └── haunted-bgm.mp3 │ └── favicon │ │ ├── favicon.ico │ │ ├── favicon-16x16.png │ │ ├── favicon-32x32.png │ │ ├── apple-touch-icon.png │ │ ├── android-chrome-192x192.png │ │ ├── android-chrome-512x512.png │ │ └── site.webmanifest ├── scss │ ├── main.scss │ ├── _mixins.scss │ ├── _base.scss │ ├── _layout.scss │ ├── _animations.scss │ └── _components.scss ├── js │ ├── ext │ │ ├── setPage.js │ │ └── openDate.js │ ├── config.js │ ├── index.js │ ├── pages.js │ └── animation.js └── template.html ├── .gitignore ├── netlify.toml ├── example.env ├── docs ├── attributions.md ├── customizations.md └── variables.md ├── package.json └── README.md /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /local/sample-pic.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SilentDemonSD/happy-birthday-vercel/main/local/sample-pic.jpeg -------------------------------------------------------------------------------- /src/resources/img/bulb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SilentDemonSD/happy-birthday-vercel/main/src/resources/img/bulb.png -------------------------------------------------------------------------------- /src/resources/img/cake.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SilentDemonSD/happy-birthday-vercel/main/src/resources/img/cake.jpg -------------------------------------------------------------------------------- /src/resources/img/door.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SilentDemonSD/happy-birthday-vercel/main/src/resources/img/door.png -------------------------------------------------------------------------------- /src/resources/img/gift.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SilentDemonSD/happy-birthday-vercel/main/src/resources/img/gift.png -------------------------------------------------------------------------------- /src/resources/sfx/door.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SilentDemonSD/happy-birthday-vercel/main/src/resources/sfx/door.mp3 -------------------------------------------------------------------------------- /src/resources/sfx/hbd.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SilentDemonSD/happy-birthday-vercel/main/src/resources/sfx/hbd.mp3 -------------------------------------------------------------------------------- /src/resources/img/Asset.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SilentDemonSD/happy-birthday-vercel/main/src/resources/img/Asset.png -------------------------------------------------------------------------------- /src/resources/img/nature.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SilentDemonSD/happy-birthday-vercel/main/src/resources/img/nature.jpg -------------------------------------------------------------------------------- /src/resources/img/nature.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SilentDemonSD/happy-birthday-vercel/main/src/resources/img/nature.png -------------------------------------------------------------------------------- /src/resources/img/stars.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SilentDemonSD/happy-birthday-vercel/main/src/resources/img/stars.png -------------------------------------------------------------------------------- /src/resources/sfx/blast.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SilentDemonSD/happy-birthday-vercel/main/src/resources/sfx/blast.mp3 -------------------------------------------------------------------------------- /src/resources/sfx/switch.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SilentDemonSD/happy-birthday-vercel/main/src/resources/sfx/switch.mp3 -------------------------------------------------------------------------------- /src/resources/img/Confetti.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SilentDemonSD/happy-birthday-vercel/main/src/resources/img/Confetti.png -------------------------------------------------------------------------------- /src/resources/img/Hallway.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SilentDemonSD/happy-birthday-vercel/main/src/resources/img/Hallway.png -------------------------------------------------------------------------------- /src/resources/img/bday-cap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SilentDemonSD/happy-birthday-vercel/main/src/resources/img/bday-cap.png -------------------------------------------------------------------------------- /src/resources/img/bedroom.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SilentDemonSD/happy-birthday-vercel/main/src/resources/img/bedroom.png -------------------------------------------------------------------------------- /src/resources/img/flag-left.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SilentDemonSD/happy-birthday-vercel/main/src/resources/img/flag-left.png -------------------------------------------------------------------------------- /src/resources/favicon/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SilentDemonSD/happy-birthday-vercel/main/src/resources/favicon/favicon.ico -------------------------------------------------------------------------------- /src/resources/img/flag-right.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SilentDemonSD/happy-birthday-vercel/main/src/resources/img/flag-right.png -------------------------------------------------------------------------------- /src/resources/sfx/haunted-bgm.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SilentDemonSD/happy-birthday-vercel/main/src/resources/sfx/haunted-bgm.mp3 -------------------------------------------------------------------------------- /src/resources/img/balloon-left.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SilentDemonSD/happy-birthday-vercel/main/src/resources/img/balloon-left.png -------------------------------------------------------------------------------- /src/resources/img/balloon-right.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SilentDemonSD/happy-birthday-vercel/main/src/resources/img/balloon-right.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | .parcel-cache/ 3 | dist/ 4 | .env 5 | *-safe.* 6 | temp/ 7 | local/ 8 | src/index.html 9 | src/pic.jpeg 10 | -------------------------------------------------------------------------------- /src/scss/main.scss: -------------------------------------------------------------------------------- 1 | @import './mixins'; 2 | @import './layout'; 3 | @import './base'; 4 | @import './components'; 5 | @import './animations'; 6 | -------------------------------------------------------------------------------- /src/resources/favicon/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SilentDemonSD/happy-birthday-vercel/main/src/resources/favicon/favicon-16x16.png -------------------------------------------------------------------------------- /src/resources/favicon/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SilentDemonSD/happy-birthday-vercel/main/src/resources/favicon/favicon-32x32.png -------------------------------------------------------------------------------- /src/resources/favicon/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SilentDemonSD/happy-birthday-vercel/main/src/resources/favicon/apple-touch-icon.png -------------------------------------------------------------------------------- /src/resources/favicon/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SilentDemonSD/happy-birthday-vercel/main/src/resources/favicon/android-chrome-192x192.png -------------------------------------------------------------------------------- /src/resources/favicon/android-chrome-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SilentDemonSD/happy-birthday-vercel/main/src/resources/favicon/android-chrome-512x512.png -------------------------------------------------------------------------------- /src/js/ext/setPage.js: -------------------------------------------------------------------------------- 1 | export default function (page) { 2 | document.title = page.title; 3 | document.body.innerHTML = page.body; 4 | document.body.classList.add("page-404"); 5 | } 6 | -------------------------------------------------------------------------------- /src/scss/_mixins.scss: -------------------------------------------------------------------------------- 1 | @mixin position($top: 50%,$left: 50%,$transX: -50%,$transY: -50%) { 2 | position: absolute; 3 | top: $top; 4 | left: $left; 5 | transform: translate($transX,$transY); 6 | } -------------------------------------------------------------------------------- /netlify.toml: -------------------------------------------------------------------------------- 1 | [build] 2 | base = "./" 3 | publish = "./dist/" 4 | command = "npm run build" 5 | 6 | [template.environment] 7 | NAME = "enter Name of the receiver" 8 | PIC = "enter web url of the receiver's image" 9 | -------------------------------------------------------------------------------- /local/readme.md: -------------------------------------------------------------------------------- 1 | # Local Deployement File Storage 2 | 3 | This folder stores the data files required for building local deployement. 4 | 5 | ## File List 6 | 7 | - Image for the card. (.jpeg/.png) 8 | - Text for the scroll message. (.txt) 9 | -------------------------------------------------------------------------------- /src/js/ext/openDate.js: -------------------------------------------------------------------------------- 1 | export const isBDay = function () { 2 | const startTime = new Date(process.env.OPEN_DATE + "T00:00").getTime(); 3 | const endTime = startTime + 24 * 60 * 60 * 1000; 4 | const localTime = Date.now(); 5 | if (localTime < startTime) return "IS_EARLY"; 6 | if (localTime > endTime) return "IS_LATE"; 7 | return "ON_TIME"; 8 | }; 9 | -------------------------------------------------------------------------------- /src/scss/_base.scss: -------------------------------------------------------------------------------- 1 | *, 2 | *::before, 3 | *::after { 4 | box-sizing: inherit; 5 | margin: 0; 6 | padding: 0; 7 | } 8 | 9 | html { 10 | font-size: 62.5%; 11 | box-sizing: border-box; 12 | 13 | @media (min-width: 1200px) { 14 | font-size: 70%; 15 | } 16 | } 17 | 18 | body { 19 | color: #fff; 20 | font-family: "Courgette", cursive; 21 | // background-color: black; 22 | } 23 | -------------------------------------------------------------------------------- /src/js/config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | name: process.env.NAME, // actual name of the recipient (Mandatory) 3 | 4 | nickname: process.env.NICKNAME, // nickname(optional) 5 | 6 | pic: process.env.PIC, // image url of recipients (Mandatory) 7 | 8 | showScrollMsg: process.env.SCROLL_MSG, // set to false if you do not want the scrolling message 9 | 10 | birthDate: process.env.BIRTH_DATE, // Mention birthday date in YYYY-MM-DD format 11 | }; 12 | -------------------------------------------------------------------------------- /src/resources/favicon/site.webmanifest: -------------------------------------------------------------------------------- 1 | { 2 | "name": "", 3 | "short_name": "", 4 | "icons": [ 5 | { 6 | "src": "./android-chrome-192x192.png", 7 | "sizes": "192x192", 8 | "type": "image/png" 9 | }, 10 | { 11 | "src": "./android-chrome-512x512.png", 12 | "sizes": "512x512", 13 | "type": "image/png" 14 | } 15 | ], 16 | "theme_color": "#ffffff", 17 | "background_color": "#ffffff", 18 | "display": "standalone" 19 | } 20 | -------------------------------------------------------------------------------- /example.env: -------------------------------------------------------------------------------- 1 | ## for Local Build 2 | 3 | # Mandatory Vars 4 | 5 | NAME='Friend' 6 | ## Name of receiver 7 | 8 | PIC='sample-pic.jpeg' 9 | ## Name of image file 10 | 11 | # Optional Vars -- Do not add these vars to your env if you dont want to use them 12 | 13 | NICKNAME='Buddy' 14 | ## A Nickname for more personalization 15 | 16 | HBD_MSG='May your soul rest in peace' 17 | ## Custom HBD Greeting message 18 | 19 | SCROLL_MSG='sample-scroll.txt' 20 | ## Name of the Scroll Message txt file 21 | 22 | OPEN_DATE='2022-08-09' 23 | ## Active Date in YYYY-MM-DD format 24 | -------------------------------------------------------------------------------- /src/js/index.js: -------------------------------------------------------------------------------- 1 | //jshint esversion:8 2 | 3 | import { isBDay } from "./ext/openDate.js"; 4 | import setPage from "./ext/setPage.js"; 5 | import { late, soon } from "./pages.js"; 6 | import { animate } from "./animation.js"; 7 | 8 | /******************************************************* SETUP ************************************************************/ 9 | 10 | if (process.env.OPEN_DATE) { 11 | const status = isBDay(); 12 | if (status === "IS_EARLY") setPage(soon); 13 | if (status === "IS_LATE") setPage(late); 14 | if (status === "ON_TIME") animate(); 15 | } else { 16 | animate(); 17 | } 18 | -------------------------------------------------------------------------------- /src/js/pages.js: -------------------------------------------------------------------------------- 1 | export const soon = { 2 | title: "Come Back Later...", 3 | body: `
4 |

Hi, you come to early

5 |


6 |

7 | I know this page is very interesting for you, especially for your special day but.
8 | You need to be patience until the time has come, right ? 9 |

`, 10 | }; 11 | 12 | export const late = { 13 | title: "See you next time...", 14 | body: `
15 |

The party was over

16 |


17 |

18 | Yes, my gift for you is kinda simple, cheap, and weird ? 😖
19 | B-but. It's only for you. 💖 20 |

21 | `, 22 | }; 23 | -------------------------------------------------------------------------------- /docs/attributions.md: -------------------------------------------------------------------------------- 1 | # Atributions 2 | 3 | - Pattern vector created by mokoland - www.freepik.com 4 | - Christmas vector created by freepik - www.freepik.com 5 | - Background vector created by brgfx - www.freepik.com 6 | - Halloween vector created by vectorpouch - www.freepik.com 7 | 8 | - The favicon was generated using the following graphics from Twitter Twemoji: 9 | 10 | - Graphics Title: 1f382.svg 11 | - [Graphics Author: Copyright 2020 Twitter, Inc and other contributors](https://github.com/twitter/twemoji) 12 | - Graphics Source: [https://github.com/twitter/twemoji/blob/master/assets/svg/1f382.svg](https://github.com/twitter/twemoji/blob/master/assets/svg/1f382.svg) 13 | - [Graphics License: CC-BY 4.0](https://creativecommons.org/licenses/by/4.0/) 14 | 15 | --- 16 | 17 |
Made with 💖 by Anshuman Mahato
18 | -------------------------------------------------------------------------------- /src/scss/_layout.scss: -------------------------------------------------------------------------------- 1 | .content { 2 | height: 100vh; 3 | overflow: hidden; 4 | position: absolute; 5 | 6 | display: grid; 7 | grid-template-columns: 8 | [flag-cl-start balloon-l-start] 1fr 9 | [flag-cl-end balloon-l-end flag-l-start frame-start] 1fr 10 | [flag-l-end flag-r-start] 1fr 11 | [flag-r-end flag-cr-start balloon-r-start frame-end] 1fr [flag-cr-end balloon-r-end]; 12 | grid-template-rows: 30vh 70vh; 13 | 14 | & > * { 15 | width: 100%; 16 | } 17 | 18 | @media only screen and (max-width: 800px) { 19 | grid-template-columns: 20 | [flag-l-start balloon-l-start frame-start] 1fr 21 | [balloon-l-end flag-l-end flag-r-start balloon-r-start ] 1fr [flag-r-end balloon-r-end frame-end]; 22 | } 23 | } 24 | 25 | .frame { 26 | grid-row: 2/3; 27 | grid-column: frame-start / frame-end; 28 | 29 | display: none; 30 | opacity: 0; 31 | flex-direction: column; 32 | justify-content: space-evenly; 33 | align-items: center; 34 | text-align: center; 35 | } 36 | 37 | .page-404 { 38 | background-color: #413f42; 39 | text-align: center; 40 | } 41 | -------------------------------------------------------------------------------- /local/sample-scroll.txt: -------------------------------------------------------------------------------- 1 | Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum. 2 | 3 | 4 | It is a long established fact that a reader will be distracted by the readable content of a page when looking at its layout. The point of using Lorem Ipsum is that it has a more-or-less normal distribution of letters, as opposed to using 'Content here, content here', making it look like readable English. Many desktop publishing packages and web page editors now use Lorem Ipsum as their default model text, and a search for 'lorem ipsum' will uncover many web sites still in their infancy. Various versions have evolved over the years, sometimes by accident, sometimes on purpose (injected humour and the like). 5 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "happy-birthday-card", 3 | "version": "3.0.2", 4 | "description": "A birthday card template", 5 | "main": "index.js", 6 | "default": "index.html", 7 | "targets": { 8 | "main": false 9 | }, 10 | "browserslist": "last 10 versions, not dead", 11 | "scripts": { 12 | "watch:parcel": "parcel src/index.html", 13 | "build:parcel": "parcel build src/index.html", 14 | "init-index-local": "node builder/init.js --local", 15 | "init-index-remote": "node builder/init.js --remote", 16 | "watch": "npm-run-all init-index-local watch:parcel", 17 | "build": "npm-run-all init-index-remote build:parcel" 18 | }, 19 | "repository": { 20 | "type": "git", 21 | "url": "git+https://github.com/AnshumanMahato/Happy-Birthday-Card.git" 22 | }, 23 | "keywords": [ 24 | "happy-birthday", 25 | "birthday-card" 26 | ], 27 | "author": "Anshuman Mahato", 28 | "license": "ISC", 29 | "bugs": { 30 | "url": "https://github.com/AnshumanMahato/Happy-Birthday-Card/issues" 31 | }, 32 | "homepage": "https://github.com/AnshumanMahato/Happy-Birthday-Card#readme", 33 | "devDependencies": { 34 | "@parcel/packager-raw-url": "^2.6.2", 35 | "@parcel/transformer-sass": "^2.6.2", 36 | "@parcel/transformer-webmanifest": "^2.6.2", 37 | "parcel": "^2.6.2", 38 | "sass": "^1.53.0" 39 | }, 40 | "dependencies": { 41 | "axios": "^0.27.2", 42 | "dotenv": "^7.0.0", 43 | "npm-run-all": "^4.1.5", 44 | "sharp": "^0.30.7" 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/scss/_animations.scss: -------------------------------------------------------------------------------- 1 | @keyframes fade { 2 | 0% { 3 | opacity: 1; 4 | } 5 | 100% { 6 | opacity: 0; 7 | } 8 | } 9 | 10 | @keyframes read { 11 | 0% { 12 | opacity: 0; 13 | } 14 | 15 | 25% { 16 | opacity: 1; 17 | } 18 | 19 | 75% { 20 | opacity: 1; 21 | } 22 | 23 | 100% { 24 | opacity: 0; 25 | } 26 | } 27 | 28 | @keyframes heart-beat { 29 | 0% { 30 | transform: translate(-50%, -50%) scale(1, 1); 31 | } 32 | 33 | 50% { 34 | transform: translate(-50%, -50%) scale(1.25, 1.25); 35 | } 36 | 37 | 100% { 38 | transform: translate(-50%, -50%) scale(1, 1); 39 | } 40 | } 41 | 42 | @keyframes balloon-move { 43 | 0% { 44 | transform: translateY(0) rotate(0); 45 | } 46 | 25% { 47 | transform: translateY(5%) rotate(3deg); 48 | } 49 | 50% { 50 | transform: translateY(0) rotate(0); 51 | } 52 | 75% { 53 | transform: translateY(-5%) rotate(-3deg); 54 | } 55 | 100% { 56 | transform: translateY(0) rotate(0); 57 | } 58 | } 59 | 60 | @keyframes move-up { 61 | 0% { 62 | transform: translateY(0); 63 | } 64 | 65 | 100% { 66 | transform: translateY(-100%); 67 | } 68 | } 69 | 70 | // Dynamic Classes 71 | 72 | .hidden { 73 | display: none; 74 | } 75 | .fade-in { 76 | animation: fade 2s ease-in 0s; 77 | } 78 | 79 | .appear { 80 | animation: fade 2s linear 0s 1 reverse; 81 | } 82 | 83 | .read { 84 | animation: read 4s linear 0s; 85 | } 86 | 87 | .move-up { 88 | animation: move-up var(--readTime) linear 3s; 89 | } 90 | -------------------------------------------------------------------------------- /src/resources/img/flag-left.svg: -------------------------------------------------------------------------------- 1 | Asset 2 -------------------------------------------------------------------------------- /src/resources/img/flag-right.svg: -------------------------------------------------------------------------------- 1 | Asset 5 -------------------------------------------------------------------------------- /docs/customizations.md: -------------------------------------------------------------------------------- 1 | # Customizations 2 | 3 | Following are some of the customizations that can be added to the card. 4 | 5 | ## The Scrolling Message 6 | 7 | The scrolling message is a customization that you can enable in the card. Once enabled, a custom message can be displayed to the user before rendering the actual card. 8 | 9 | Check it out in this [sample deployment](https://hbd-card.netlify.app/) to see this feature. 10 | 11 | ### in Remote Deployment 12 | 13 | 1. For this, we create a telegraph Article with our message. Go to [Telegraph](https://telegra.ph) and publish an article with your message in it. 14 | 15 | Here is a sample for you - https://telegra.ph/Bday-07-24. 16 | 17 | 2. Copy the url of the published article. In my case, it is this `https://telegra.ph/Bday-07-24`. 18 | 19 | 3. Add `SCROLL_MSG` environment variable to your deployed site with the above url as its value. 20 | 21 | ### in Local Build 22 | 23 | 1. Create a 'filename.txt' file in the `./local` directory with your message in it. 24 | 25 | 2. Add environment variable `SCROLL_MSG=filename.txt`. 26 | 27 | ## Custom Happy BDay Text 28 | 29 | Instead of the predefined 'Wish you a very happy birthday' text in the card, one can add a custom hbd message by specifying it in the `HBD_MSG` environment variable. 30 | 31 | ## Live only for a specific day 32 | 33 | The card can be set to be visible only for a specific day. On other days, it will show a different page depending upon whether it is accessed before or after the specified date. 34 | 35 | To set this, specify the date in `YYYY-MM-DD` format as `OPEN_DATE` environment variable. 36 | 37 | --- 38 | 39 | ## Note 40 | 41 | For the customizations to take effect, one must redeploy/rebuild the project. 42 | 43 | --- 44 | 45 |
Made with 💖 by Anshuman Mahato
46 | -------------------------------------------------------------------------------- /docs/variables.md: -------------------------------------------------------------------------------- 1 | # Environment Variables 2 | 3 | Following is the list of all the environment variables that are used in this project. 4 | 5 | ```sh 6 | # Mandatory Variables 7 | NAME 8 | PIC 9 | 10 | # Optional Variables 11 | NICKNAME 12 | HBD_MSG 13 | SCROLL_MSG 14 | OPEN_DATE 15 | ``` 16 | 17 | - The `NAME` and `NICKNAME` are self explainatory. `NICKNAME` is optional. If no nickname is provided, then value of name will be used in its place. 18 | 19 | - `PIC` is the image that will appear on the card. 20 | 21 | - For local building, one needs to add the image file to the `./local` directory and add the name of the file as its value. 22 | 23 | - For remote deployment, it is the web address of image file. If don't have the image hosted somewhere, you may publish a [telegra.ph article](https://telegra.ph) with your image and copy the image address from there. 24 | 25 | NOTE: Picture must be in 1:1 ratio or it will get cropped. 26 | 27 | - `HBD_MSG` is an optional variable that adds a custom HBD greeting. It takes the custom message as value. Check [Customizations](./customizations.md#custom-happy-bday-text) to know how to set up. 28 | 29 | - `SCROLL_MSG` is an optional variable to specify a custom scrolling message that appears before the card. 30 | 31 | - For local building, one needs to add a `.txt` file with the message, to the `./local` directory and add the name of the file as its value. 32 | 33 | - For remote deployment, it is the url of the telegra.ph article. 34 | 35 | Check [Customizations](./customizations.md#the-scrolling-message) to know how to set up. 36 | 37 | - `OPEN_DATE` is an optional variable to specify the active date for the webpage. It the takes the date in `YYYY-MM-DD` format as value. Used to setup the Live Date feature. Check [Customizations](./customizations.md#live-only-for-a-specific-day) to know how to set up. 38 | 39 | --- 40 | 41 |
Made with 💖 by Anshuman Mahato
42 | -------------------------------------------------------------------------------- /src/resources/img/bulb.svg: -------------------------------------------------------------------------------- 1 | Asset 10 -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Happy Birthday Card 2 | 3 | A Customizable Web based birthday card to wish your friends and family in a unique way. 4 | 5 | Check out the Previews - 6 | 7 | - [Without Scroll Message](https://happy-birthday-card.vercel.app/) 8 | - [With Scroll Message](https://hbd-card.netlify.app/) 9 | 10 | If you liked it, please consider giving it star a 🤩⭐. 11 | 12 | --- 13 | 14 | ## How to setup 15 | 16 | Here are the methods to set it up for yourself. 17 | 18 | ### Remote Deployment 19 | 20 | - Vercel Deploy 21 | 22 | [![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2FAnshumanMahato%2FHappy-Birthday-Card&env=NAME,PIC&envDescription=NAME%20-%3E%20Name%20of%20the%20Receiver%20%7C%20PIC%20-%3E%20web%20url%20of%20a%20picture%20of%20the%20receiver&envLink=https%3A%2F%2Fgithub.com%2FAnshumanMahato%2FHappy-Birthday-Card%2Fblob%2Fmain%2Fdocs%2Fvariables.md&project-name=happy-birthday-card&repo-name=happy-birthday-card&demo-title=Happy%20Birthday%20Card&demo-description=This%20is%20a%20web%20based%20interactive%20birthday%20card.&demo-url=https%3A%2F%2Fhappy-birthday-card.vercel.app%2F&demo-image=https%3A%2F%2Ftelegra.ph%2Ffile%2Fac886529ccc3843552f81.png) 23 | 24 | - Netlify Deploy 25 | 26 | [![Deploy with NEtlify](https://www.netlify.com/img/deploy/button.svg)](https://app.netlify.com/start/deploy?repository=https://github.com/AnshumanMahato/Happy-Birthday-Card) 27 | 28 | Remote deployment will require you to specify some mandatory Evironment Variables, 29 | 30 | - NAME: Name of the receiver. 31 | 32 | - PIC: Url of the pic to be loaded in the card. If don't have the image hosted somewhere, you may publish a [telegra.ph article](https://telegra.ph) with your image and copy the image address from there. 33 | 34 | To know more about the environment variables, check [References](#references). 35 | 36 | ### For Local Building 37 | 38 | 1. Clone the repository 39 | 40 | ```sh 41 | git clone https://github.com/AnshumanMahato/Happy-Birthday-Card 42 | ``` 43 | 44 | 2. Install dependencies 45 | 46 | ```sh 47 | npm install 48 | ``` 49 | 50 | 3. Add a pic of the receiver, in the `./local` directory. Ensure that the image is of 1:1 ratio or it might get cropped and squished. 51 | 52 | 4. Create a `.env` file in root directory, and add the following lines. 53 | 54 | ```env 55 | NAME='Name of the Receiever' 56 | PIC='name-of-image.extension' 57 | ``` 58 | 59 | 5. Execute the following commands in order. 60 | 61 | ```sh 62 | npm run init-index-local 63 | npm run build:parcel 64 | ``` 65 | 66 | 6. Upon Successful execution, your built files will be ready in the `./dist` directory. Open `./dist/index.html` to see the card. 67 | 68 | For further customization, checkout [here](./docs/customizations.md). 69 | 70 | --- 71 | 72 | ## References 73 | 74 | - [Environment Variables](./docs/variables.md) 75 | 76 | - [Attributions](./docs/attributions.md) 77 | 78 | --- 79 | 80 | ## Support 81 | 82 | If you have any queries or need some help in deployment, you may contact me here 83 | 84 |
85 | 86 | 87 | 88 | 89 | Made with 💖 by Anshuman Mahato 90 | 91 |
92 | -------------------------------------------------------------------------------- /src/template.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | Happy Birthday to You!!! 16 | 17 | 18 | 19 | 20 | 21 | 25 | 29 | 30 | 34 | 35 | 36 | {{^READ_TIME}} 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 58 | 62 | 66 | 70 | 74 | 75 |
76 | flags 81 | flags 86 | flags 91 | flags 96 | balloon-left 101 | balloon-right 106 |
107 |
108 |
109 | 110 | 111 | 112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |

{{^HBD_MSG}}

122 |

{{^NICKNAME}}

123 |
124 |
125 | 126 |
127 |
128 |
{{^SCROLL_MSG}}
129 |
130 |
131 | 132 |
133 | 134 |
135 |

Wow! That was something weird.

136 |

Hey look! There's a gift for you..

137 |

C'mon, let's open it and see what's in there!

138 |
139 | 140 |
141 |

Hey!

142 |

Maybe it's a bit late to move in the hallway,

143 |

Things are creepy here...

144 |

I think we should go back inside.

145 |
146 | 147 |
148 |

Ok! Why is it so empty here?

149 |

You know what, let's move outside.

150 |

Let's see if anyone's over there...

151 |
152 | 153 |
154 |

Hey!

155 |

Why is it so dark here?

156 |

157 | {{^NAME}}! Can you please switch on the lights? 158 |

159 |
160 | 161 |
162 |
163 | 164 | 165 | -------------------------------------------------------------------------------- /src/js/animation.js: -------------------------------------------------------------------------------- 1 | //jshint esversion:6 2 | 3 | const button = document.querySelector(".btn"), 4 | darkroom = document.querySelector(".darkroom"), 5 | giftroom = document.querySelector(".giftroom"), 6 | hallway = document.querySelector(".hallway"), 7 | room = document.querySelector(".empty-room"), 8 | flash = document.querySelector(".flash"); 9 | 10 | // These are the text elements that hold messages to be displayed in the respective screes 11 | 12 | const blackText = document.querySelectorAll(".bb-text"), // msgs in the dark room scene 13 | giftText = document.querySelectorAll(".gift-text"), // msgs in the gift scene 14 | hallText = document.querySelectorAll(".hall-text"), // msgs in the hallway scene 15 | roomText = document.querySelectorAll(".room-text"), // msgs in empty room scene 16 | CTAtext = document.querySelector(".btn-ref"); 17 | 18 | //Elements in the card page 19 | 20 | const frames = document.querySelectorAll(".frame"), 21 | msgWindow = document.querySelector(".scroll"), // this one has the message frame in [0] and card fram in [1] 22 | msg = document.querySelector(".text"); // the Message para 23 | 24 | //Sfx files 25 | 26 | const light = document.querySelector(".switch-aud"), 27 | blast = document.querySelector(".blast-aud"), 28 | door = document.querySelector(".door-aud"), 29 | haunt = document.querySelector(".haunt-aud"), 30 | music = document.querySelector(".hbd-aud"); 31 | 32 | // readMsg() displays the paras in each scene successively. It takes an array of the para elements as input. 33 | 34 | const readMsg = (text) => { 35 | for (let i = 0; i < text.length; i++) { 36 | // this loop goes through all the text msg paras 37 | setTimeout(() => { 38 | // A timeout of 5s ia applied to all text elements so that appear successively one after the other 39 | text[i].classList.add("read"); // this adds a fadeIn-fadeOut animation to elements 40 | if (i === text.length - 1) { 41 | // this ensures that the button appears only after the last text is displayed. 42 | button.style.display = "inline-block"; 43 | CTAtext.style.display = "block"; 44 | } 45 | }, 5000 * i); 46 | } 47 | }; 48 | 49 | // transition() is animation for change from one scene to another. It takes the current scene div element as input. 50 | 51 | const transition = (currentScene) => { 52 | currentScene.classList.add("fade-in"); 53 | currentScene.style.opacity = "0"; 54 | button.style.display = "none"; 55 | CTAtext.style.display = "none"; 56 | }; 57 | 58 | //Animation Code 59 | 60 | /* 61 | In the beginning, the black page appears signifying a dark room and after displaying the msg paras 62 | one by one, a button(bulb) appears and the user is asked to click the button to swith on the lights. 63 | */ 64 | 65 | export const animate = function () { 66 | CTAtext.innerHTML = "Click the Light Bulb."; 67 | 68 | readMsg(blackText); 69 | 70 | button.addEventListener("click", function () { 71 | if (button.classList.contains("switch")) { 72 | /* 73 | When the switch is pressed, the black div will wipe out and the backgroung scene with no 74 | elements will appear, signifying that the lights are turned on and the room is empty. Then 75 | the msg will be displayed after which, the user will be asked to move out and the button with 76 | door icon will appear. 77 | */ 78 | 79 | light.play(); 80 | transition(darkroom); 81 | CTAtext.innerHTML = "Click the Door"; 82 | setTimeout(function () { 83 | button.classList.add("door-out"); 84 | button.classList.remove("switch"); 85 | darkroom.style.display = "none"; 86 | readMsg(roomText); 87 | }, 4000); 88 | } else if (button.classList.contains("door-out")) { 89 | /* 90 | when the door is pressed, scene changes to cemetry. Again, the msg will be displayed, after 91 | which, the user will be asked to come inside and the button with door will appear again. 92 | */ 93 | 94 | door.play(); 95 | transition(room); 96 | setTimeout(function () { 97 | haunt.play(); 98 | haunt.loop = true; 99 | button.classList.add("door-in"); 100 | button.classList.remove("door-out"); 101 | room.style.display = "none"; 102 | readMsg(hallText); 103 | }, 4000); 104 | } else if (button.classList.contains("door-in")) { 105 | /* 106 | when the door is pressed, scene changes to the gift room. Again, the msg will be displayed, after 107 | which, the user will be asked to open the gift and the button with gift will appear. 108 | */ 109 | 110 | door.play(); 111 | transition(hallway); 112 | CTAtext.innerHTML = "Click the Gift"; 113 | setTimeout(function () { 114 | button.classList.add("gift"); 115 | button.classList.remove("door-in"); 116 | hallway.style.display = "none"; 117 | readMsg(giftText); 118 | }, 4000); 119 | } else if (button.classList.contains("gift")) { 120 | /* 121 | when the gift is pressed, the gift scene vanishes and the white div fades slowly giving a sense 122 | of explosion. After that, the message frame appears and moves up until the message completes. Then, 123 | the message frame fades away and the card appears. 124 | */ 125 | 126 | haunt.pause(); 127 | blast.play(); 128 | giftroom.style.display = "none"; 129 | transition(flash); 130 | 131 | music.loop = true; 132 | music.play(); 133 | 134 | if (!process.env.SCROLL_MSG) { 135 | frames[0].style.display = "flex"; 136 | setTimeout(() => { 137 | frames[0].classList.add("appear"); 138 | frames[0].style.opacity = "1"; 139 | }, 1500); 140 | return; 141 | } 142 | 143 | //This value is stored in the --readTime css variable of root element and is calculated dynamically at build time. 144 | const readTime = 145 | parseInt( 146 | getComputedStyle(document.documentElement).getPropertyValue( 147 | "--readTime" 148 | ) 149 | ) + 5; 150 | 151 | frames[1].style.display = "flex"; 152 | 153 | setTimeout(() => { 154 | frames[1].classList.add("appear"); 155 | frames[1].style.opacity = "1"; 156 | msg.classList.add("move-up"); 157 | }, 1500); 158 | 159 | setTimeout(() => { 160 | msg.style.transform = "translateY(-100%)"; 161 | flash.style.display = "none"; 162 | }, 5000); 163 | 164 | setTimeout(() => { 165 | msgWindow.classList.add("fade-in"); 166 | msgWindow.style.opacity = "0"; 167 | }, readTime * 1000); 168 | 169 | setTimeout(() => { 170 | frames[1].style.display = "none"; 171 | frames[0].style.display = "flex"; 172 | frames[0].classList.add("appear"); 173 | frames[0].style.opacity = "1"; 174 | }, (readTime + 3) * 1000); 175 | } 176 | }); 177 | }; 178 | -------------------------------------------------------------------------------- /src/scss/_components.scss: -------------------------------------------------------------------------------- 1 | // DECORATIONS 2 | 3 | .balloon { 4 | grid-row: 1 / -1; 5 | width: 130%; 6 | 7 | &--left { 8 | grid-column: balloon-l-start / balloon-l-end; 9 | animation: balloon-move 4s linear 0s infinite; 10 | @media (max-width: 800px) { 11 | margin-left: -25%; 12 | } 13 | 14 | @media (max-width: 500px) { 15 | margin-left: -40%; 16 | } 17 | } 18 | 19 | &--right { 20 | grid-column: balloon-r-start / balloon-r-end; 21 | margin-left: -20%; 22 | animation: balloon-move 4s linear 0s infinite reverse; 23 | @media (max-width: 800px) { 24 | margin-left: 25%; 25 | } 26 | } 27 | 28 | @media (max-width: 800px) { 29 | width: 100%; 30 | } 31 | 32 | @media (max-width: 500px) { 33 | width: 120%; 34 | } 35 | } 36 | 37 | .flag { 38 | grid-row: 1/2; 39 | z-index: 0; 40 | 41 | &--c-left { 42 | grid-column: flag-cl-start / flag-cl-end; 43 | @media (max-width: 800px) { 44 | display: none; 45 | } 46 | } 47 | 48 | &--c-right { 49 | grid-column: flag-cr-start / flag-cr-end; 50 | @media (max-width: 800px) { 51 | display: none; 52 | } 53 | } 54 | 55 | &--left { 56 | grid-column: flag-l-start / flag-l-end; 57 | } 58 | 59 | &--right { 60 | grid-column: flag-r-start / flag-r-end; 61 | } 62 | 63 | @media (max-width: 800px) { 64 | transform: scale(1.2); 65 | margin-top: 20%; 66 | } 67 | } 68 | 69 | // BIRTHDAY CARD 70 | 71 | .img-back { 72 | position: relative; 73 | background-color: #ffa8dc; 74 | height: 20rem; 75 | width: 20rem; 76 | border-radius: 50%; 77 | 78 | @media (max-width: 800px) { 79 | height: 35rem; 80 | width: 35rem; 81 | } 82 | 83 | @media (max-width: 500px) { 84 | height: 20rem; 85 | width: 20rem; 86 | } 87 | } 88 | 89 | .bd-pic { 90 | @include position; 91 | 92 | height: 83.33333333%; 93 | width: 83.33333333%; 94 | overflow: hidden; 95 | 96 | border-color: #fff; 97 | border-style: dotted; 98 | border-radius: 50%; 99 | 100 | background-image: url(../pic.jpeg); 101 | background-position: top; 102 | background-repeat: no-repeat; 103 | background-size: cover; 104 | } 105 | 106 | .cap { 107 | @include position(-15%, 50%, 0, 0); 108 | 109 | height: 33.33333333%; 110 | } 111 | 112 | .confetti { 113 | @include position; 114 | 115 | height: 150%; 116 | width: 150%; 117 | } 118 | 119 | .cake { 120 | @include position(90%); 121 | 122 | height: 70%; 123 | width: 70%; 124 | } 125 | 126 | .HBD-text { 127 | width: 90%; 128 | } 129 | 130 | .HBD { 131 | font-size: 2.4rem; 132 | padding: 0; 133 | 134 | @media (max-width: 800px) { 135 | font-size: 3.4rem; 136 | } 137 | 138 | @media (max-width: 500px) { 139 | font-size: 3rem; 140 | } 141 | } 142 | 143 | .nickname { 144 | font-size: 3.2rem; 145 | padding: 0; 146 | 147 | @media (max-width: 800px) { 148 | font-size: 4.2rem; 149 | } 150 | @media (max-width: 500px) { 151 | font-size: 3.8rem; 152 | } 153 | } 154 | 155 | // SCROLL 156 | 157 | .scroll { 158 | position: relative; 159 | overflow-y: hidden; 160 | height: 70%; 161 | width: 80%; 162 | font-size: 2.4rem; 163 | 164 | @media (max-width: 800px) { 165 | // width: 60%; 166 | height: 60%; 167 | } 168 | 169 | @media (max-width: 500px) { 170 | height: 80%; 171 | font-size: 2.2rem; 172 | } 173 | } 174 | 175 | .text { 176 | @include position(50%, 0, 0, 0); 177 | text-align: center; 178 | } 179 | 180 | // BUTTONS 181 | 182 | .btn { 183 | @include position(60%, 50%, 0, -50%); 184 | 185 | display: none; 186 | overflow: hidden; 187 | height: 15rem; 188 | width: 15rem; 189 | 190 | background-repeat: no-repeat; 191 | background-size: contain; 192 | background-position: center; 193 | animation: heart-beat 2s linear 0s infinite alternate; 194 | } 195 | 196 | .btn-ref { 197 | @include position(90%, 50%, -50%, 0); 198 | 199 | text-align: center; 200 | 201 | color: #fff; 202 | display: none; 203 | font-size: 1.8rem; 204 | } 205 | 206 | .switch { 207 | background-image: url("../resources/img/bulb.svg"); 208 | } 209 | 210 | .gift { 211 | background-image: url("../resources/img/gift.svg"); 212 | } 213 | 214 | .door-in, 215 | .door-out { 216 | background-image: url("../resources/img/door.svg"); 217 | } 218 | 219 | //ROOMS 220 | 221 | .darkroom, 222 | .flash, 223 | .giftroom, 224 | .empty-room, 225 | .hallway { 226 | text-align: center; 227 | position: absolute; 228 | height: 100vh; 229 | width: 100vw; 230 | } 231 | 232 | .darkroom { 233 | background-color: #000; 234 | } 235 | 236 | .flash { 237 | background-color: #fff; 238 | } 239 | 240 | .giftroom, 241 | .empty-room { 242 | background-color: #141852; 243 | } 244 | 245 | .hallway { 246 | background-image: radial-gradient(transparent, #000), 247 | url(../resources/img/Hallway.svg); 248 | background-position: center; 249 | background-repeat: no-repeat; 250 | background-size: cover; 251 | 252 | overflow: hidden; 253 | } 254 | 255 | .empty-room, 256 | .giftroom, 257 | .content { 258 | background-image: radial-gradient(transparent, rgba(0, 0, 0, 0.65)), 259 | url(../resources/img/bedroom.svg); 260 | background-position: 70%; 261 | background-repeat: no-repeat; 262 | background-size: cover; 263 | } 264 | 265 | // TEXT CONTAINERS 266 | 267 | .bb-text, 268 | .gift-text, 269 | .hall-text, 270 | .room-text { 271 | @include position(20%, 50%, -50%, -50%); 272 | 273 | display: inline-block; 274 | text-align: center; 275 | width: 80vw; 276 | opacity: 0; 277 | font-size: 3.2rem; 278 | } 279 | 280 | // CANDLE -> https://codepen.io/rizqyhi/pen/BsAFn 281 | 282 | .velas { 283 | background: #ffffff; 284 | border-radius: 0.625rem; 285 | position: absolute; 286 | top: 70%; 287 | left: 50%; 288 | width: 1.5%; 289 | height: 12%; 290 | backface-visibility: hidden; 291 | } 292 | .velas:after, 293 | .velas:before { 294 | background: rgba(255, 0, 0, 0.4); 295 | content: ""; 296 | position: absolute; 297 | width: 100%; 298 | height: 5%; 299 | } 300 | .velas:after { 301 | top: 25%; 302 | left: 0; 303 | } 304 | .velas:before { 305 | top: 45%; 306 | left: 0; 307 | } 308 | 309 | /* Fire */ 310 | 311 | .fuego { 312 | border-radius: 100%; 313 | position: absolute; 314 | top: -50%; 315 | left: 60%; 316 | margin-left: -2.6px; 317 | width: 100%; 318 | height: 40%; 319 | } 320 | .fuego:nth-child(1) { 321 | animation: fuego 2s 6.5s infinite; 322 | } 323 | .fuego:nth-child(2) { 324 | animation: fuego 1.5s 6.5s infinite; 325 | } 326 | .fuego:nth-child(3) { 327 | animation: fuego 1s 6.5s infinite; 328 | } 329 | .fuego:nth-child(4) { 330 | animation: fuego 0.5s 6.5s infinite; 331 | } 332 | .fuego:nth-child(5) { 333 | animation: fuego 0.2s 6.5s infinite; 334 | } 335 | 336 | /* Animation Fire */ 337 | 338 | @keyframes fuego { 339 | 0%, 340 | 100% { 341 | background: rgba(254, 248, 97, 0.5); 342 | box-shadow: 0 0 250% 62.5% rgba(248, 233, 209, 0.2); 343 | transform: scale(1); 344 | } 345 | 50% { 346 | background: rgba(255, 50, 0, 0.1); 347 | box-shadow: 0 0 250% 125% rgba(248, 233, 209, 0.2); 348 | transform: scale(0); 349 | } 350 | } 351 | 352 | @keyframes in { 353 | to { 354 | transform: translateY(0); 355 | } 356 | } 357 | -------------------------------------------------------------------------------- /src/resources/img/gift.svg: -------------------------------------------------------------------------------- 1 | Asset 9 -------------------------------------------------------------------------------- /src/resources/img/door.svg: -------------------------------------------------------------------------------- 1 | Asset 11 -------------------------------------------------------------------------------- /src/resources/img/balloon-right.svg: -------------------------------------------------------------------------------- 1 | Asset 6 -------------------------------------------------------------------------------- /src/resources/img/balloon-left.svg: -------------------------------------------------------------------------------- 1 | Asset 4 -------------------------------------------------------------------------------- /src/resources/img/stars.svg: -------------------------------------------------------------------------------- 1 | Asset 11 -------------------------------------------------------------------------------- /src/resources/img/cake.svg: -------------------------------------------------------------------------------- 1 | Asset 1 -------------------------------------------------------------------------------- /src/resources/img/confetti.svg: -------------------------------------------------------------------------------- 1 | Asset 1 --------------------------------------------------------------------------------