25 Staff Celebration Confetti Shared by Jeff Richmond, The Well Community Church 2 years ago 11.0 General Intermediate Description This recipe will check if today is the logged in person's birthday, anniversary or workiversary, and if so, it displays a celebration message on the page and showers the screen with confetti for a few seconds. Once the confetti settles, the user can click the message to experience the celebration all over again. The confetti colors are different, depending on the occasion. Currently birthdays are multicolored, anniversaries are shades of red and yellow, and workiversaries are shades of blue. If the person has multiple occasions on the same day, then the confetti will be a mix of each occasion's colors. I also included 28 other color combos that I thought were interesting in the JavaScript, so if you don't like the default ones, there are plenty of others to pick from. The intended purpose of this recipe was for staff members on the internal Rock site, but it should also work on the public website for non-staff members since the workiversary portions would just be ignored for anyone who doesn't have a staff hire date attribute value. A Note on Widowed Staff Members This recipe will still display an anniversary message and confetti for staff members whose spouse has passed away unless they change their marital status to something other than "Married" and/or they remove the anniversary date from their profile. Because some people prefer to keep their marital status and anniversary date even after losing a spouse, we have also added a "Widowed" tag to our own Rock instance, and then added an additional filter to the staff celebration code to make sure confetti isn't triggered on their anniversary. This functionality is not included in this recipe, but please don't hesitate to let me know if you're interested in more info. Credit The actual confetti part of this recipe was adapted from this CodePen. It's not clear who the original author was, but that's where I got it from. Google shows samples of the exact same code all over the internet, so we'll probably never know. How-To If you're not already keeping track of hire dates somewhere in Rock, create a new person attribute for the Staff Hire Date Name: Staff Hire Date Key: StaffHireDate Required: No Field Type: Date See if your HR department can backfill hire dates for existing staff members Add a new HTML block to the page that you'd like to display the celebration on. Ours is on the Internal Home Page. Add the following Lava to the content of the HTML block: {%- assign marriedStatusID = 143 -%} {%- assign btnIconCSSClass = 'far fa-star' -%} {%- assign today = 'Now' | Date:'M/d' -%} {%- assign anniversaryDate = CurrentPerson.AnniversaryDate | AsDateTime -%} {%- assign birthDate = CurrentPerson.BirthDate | AsDateTime -%} {%- assign workiversaryDate = CurrentPerson | Attribute:'StaffHireDate' | AsDateTime -%} {%- assign anniversaryDay = anniversaryDate | Date:'M/d' -%} {%- assign birthDay = birthDate | Date:'M/d' -%} {%- assign workiversaryDay = workiversaryDate | Date:'M/d' -%} {%- assign isAnniversary = false -%} {%- assign isBirthday = false -%} {%- assign isWorkiversary = false -%} {%- if anniversaryDay == today and Person.MaritalStatusValueId == marriedStatusID %}{% assign isAnniversary = true %}{% endif -%} {%- if birthDay == today %}{% assign isBirthday = true %}{% endif -%} {%- if workiversaryDay == today %}{% assign isWorkiversary = true %}{% endif -%} {%- if isAnniversary == true or isBirthday == true or isWorkiversary == true -%} {%- assign thisYear = 'Now' | Date:'yyyy' | AsInteger -%} {%- assign anniversaryYear = anniversaryDate | Date:'yyyy' | Default:'0' | AsInteger -%} {%- assign workiversaryYear = workiversaryDate | Date:'yyyy' | Default:'0' | AsInteger -%} {%- assign anniversaryNum = 0 -%} {%- assign workiversaryNum = 0 -%} {%- if isAnniversary == true and isBirthday == true and isWorkiversary == true -%} {%- assign theme = 31 -%} {%- assign anniversaryNum = thisYear | Minus:anniversaryYear %} {%- assign workiversaryNum = thisYear | Minus:workiversaryYear %} {%- if workiversaryNum == 0 -%} {%- capture occasion %}Welcome to The Team{% endcapture -%} {%- else -%} {%- capture occasion %}{{ workiversaryNum | AsString | NumberToOrdinal }} Workiversary{% endcapture -%} {%- endif -%} {%- if anniversaryNum == 0 -%} {%- capture occasion %}{{ occasion }} and Congratuations on Getting Married{% endcapture -%} {%- else -%} {%- capture occasion %}{{ anniversaryNum | AsString | NumberToOrdinal }} Anniversary and {{ occasion }}{% endcapture -%} {%- endif -%} {%- capture occasion %}Happy Birthday, {{ occasion }}{% endcapture -%} {%- elseif isBirthday == true and isAnniversary == true -%} {%- assign theme = 28 -%} {%- assign anniversaryNum = thisYear | Minus:anniversaryYear %} {%- if anniversaryNum == 0 -%} {%- capture occasion %}Happy Birthday and Congratulations on Getting Married{% endcapture -%} {%- else -%} {%- capture occasion %}Happy Birthday and {{ anniversaryNum | AsString | NumberToOrdinal }} Anniversary{% endcapture -%} {%- endif -%} {%- elseif isBirthday == true and isWorkiversary == true -%} {%- assign theme = 29 -%} {%- assign workiversaryNum = thisYear | Minus:workiversaryYear %} {%- if workiversaryNum == 0 -%} {%- assign occasion = 'Happy Birthday and Welcome to The Team' -%} {%- else -%} {%- capture occasion %}Happy Birthday and {{ workiversaryNum | AsString | NumberToOrdinal }} Workiversary{% endcapture -%} {%- endif -%} {%- elseif isAnniversary == true and isWorkiversary == true -%} {%- assign theme = 30 -%} {%- assign anniversaryNum = thisYear | Minus:anniversaryYear %} {%- assign workiversaryNum = thisYear | Minus:workiversaryYear %} {%- if anniversaryNum == 0 -%} {%- assign occasion = 'Congratuations on Getting Married' -%} {%- else -%} {%- capture occasion %}Happy {{ anniversaryNum | AsString | NumberToOrdinal }} Anniversary{% endcapture -%} {%- endif -%} {%- if workiversaryNum == 0 -%} {%- capture occasion %}{{ occasion }} and Welcome to The Team{% endcapture -%} {%- else -%} {%- capture occasion %}{{ occasion }} and {% if anniversaryNum == 0 %}Happy {% endif %}{{ workiversaryNum | AsString | NumberToOrdinal }} Workiversary{% endcapture -%} {%- endif -%} {%- elseif isAnniversary == true -%} {%- assign theme = 19 -%} {%- assign anniversaryNum = thisYear | Minus:anniversaryYear %} {%- if anniversaryNum == 0 -%} {%- assign occasion = 'Congratuations on Getting Married' -%} {%- else -%} {%- capture occasion %}Happy {{ anniversaryNum | AsString | NumberToOrdinal }} Anniversary{% endcapture -%} {%- endif -%} {%- elseif isWorkiversary == true -%} {%- assign theme = 7 -%} {%- assign workiversaryNum = thisYear | Minus:workiversaryYear %} {%- if workiversaryNum == 0 -%} {%- assign occasion = 'Welcome to The Team' -%} {%- else -%} {%- capture occasion %}Happy {{ workiversaryNum | AsString | NumberToOrdinal }} Workiversary{% endcapture -%} {%- endif -%} {%- else -%} {%- assign theme = 1 -%} {%- assign occasion = 'Happy Birthday' -%} {%- endif -%} {%- stylesheet compile:'less' -%} .btn-celebrate { margin: 0 auto 35px auto; padding: 10px 30px; border-radius: 40px; border: 5px solid #fff; font-size: 24px; font-weight: bold; white-space: normal; color: #fff; background: #f7921a; box-shadow: 0 4px 10px rgba(0,0,0,.1); transition: transform .25s cubic-bezier(0, 0, 0.35, 2.5); i:before { display: inline-block; transition: transform .4s cubic-bezier(.5,-2,.5,6); } &:hover, &:focus { border-color: #fff; color: #fff; background: #de7e0b; box-shadow: 0 2px 8px rgba(0,0,0,.15); } &:active, &:active:focus { border-color: #fff; color: #fff; background: #e08b27; box-shadow: 0 0 6px rgba(0,0,0,.15); } &:active { transform: scale(.95); transition: transform 50ms linear; i:before { transform: rotate(10deg); transition: transform 50ms linear; } } } {%- endstylesheet -%} <div class="text-center"><a href="#" class="btn btn-default btn-celebrate" data-theme="{{ theme }}"><i class="{{ btnIconCSSClass }} fa-lg fa-flip-horizontal margin-r-sm"></i> Happy {{ occasion }}, {{ CurrentPerson.NickName }}! <i class="{{ btnIconCSSClass }} fa-lg margin-l-sm"></i></a></div> {%- javascript disableanonymousfunction:'true' -%} // Adapted from: https://codepen.io/Pillowfication/pen/PNEJbY // If set to true, the user must press the button to trigger // the confetti. Otherwise the confetti triggers on page load as well. var onlyOnClick = false; $(document).ready(function () { // Globals var $window = $(window) , random = Math.random , cos = Math.cos , sin = Math.sin , PI = Math.PI , PI2 = PI * 2 , timer = undefined , frame = undefined , confetti = []; var pointer = 0; var particles = 250 , spread = 40 , sizeMin = 5 , sizeMax = 15 - sizeMin , eccentricity = 12 , deviation = 100 , dxThetaMin = -.1 , dxThetaMax = -dxThetaMin - dxThetaMin , dyMin = .13 , dyMax = .18 , dThetaMin = .4 , dThetaMax = .7 - dThetaMin; var colorThemes = [ //multicolor function () { return color(200 * random() | 0, 200 * random() | 0, 200 * random() | 0); } //0 multicolor , function () { var i = [2, 3, 4, 5, 6, 7, 8]; var p = 7 * random() | 0; return colorThemes[i[p]](); } //1 alt multicolor (birthday) //single colors , function () { var b = 200 * random() | 0; return color(200, b, b); } //2 reds , function () { var p = random(); return color(240 - (40 * p | 0), 120 + (80 * p | 0), 200 * p | 0); } //3 oranges , function () { var p = random(); return color(240 - (40 * p | 0), 240 - (40 * p | 0), 200 * p | 0); } //4 yellows , function () { var b = 200 * random() | 0; return color(b, 200, b); } //5 greens , function () { return color(200 * random() | 0, 200, 200); } //6 teals , function () { var b = 150 * random() | 0; return color(b + 30, b + 50, 200); } //7 blues (workiversary) , function () { var p = random(); return color(120 + (80 * p | 0), 40 + (140 * p | 0), 240 - (40 * p | 0)); } //8 purples , function () { var b = 256 * random() | 0; return color(b, b, b); } //9 black/white , function () { var g = 100 * random() | 0 + 100; return color(g, g, g); } //10 grays , function () { var w = 56 * random() | 0 + 200; return color(w, w, w); } //11 white //fun color combos , function () { return mixThemes(2, 5); } //12 red/green (Christmas) , function () { return mixThemes(2, 7); } //13 red/blue (July 4th) , function () { return mixThemes(3, 8); } //14 orange/purple (Halloween) , function () { return mixThemes(3, 8, 9, 1 / 3, 2 / 3); } //15 orange/purple/gray (Halloween) , function () { return mixThemes(2, 11); } //16 red/white (Valentines) , function () { return mixThemes(7, 6); } //17 blue/teal , function () { return mixThemes(2, 6); } //18 red/white/teal , function () { return mixThemes(2, 4, 0, .8); } //19 red/yellow (Anniversary) , function () { return mixThemes(3, 4); } //20 orange/yellow , function () { return mixThemes(8, 4); } //21 purple/yellow , function () { return mixThemes(8, 20); } //22 purple/orange/yellow , function () { return mixThemes(8, 6); } //23 purple/teal , function () { return mixThemes(6, 7, 8, 1 / 3, 2 / 3); } //24 blue/purple/teal , function () { return mixThemes(2, 7, 8, 1 / 3, 2 / 3); } //25 red/blue/purple , function () { return mixThemes(2, 5, 7, 1 / 3, 2 / 3); } //26 red/green/blue , function () { var i = [2, 5, 7, 4, 8]; var p = 5 * random() | 0; return colorThemes[i[p]](); } //27 red/green/blue/yellow/purple //celebration combos , function () { return mixThemes(1, 19); } //28 birthday/anniversary , function () { return mixThemes(1, 7); } //29 birthday/workiversary , function () { return mixThemes(19, 7); } //30 anniversary/workiversary , function () { return mixThemes(1, 19, 7, 1 / 3, 2 / 3); } //31 birthday/anniversary/workiversary ]; //color helper functions function color(r, g, b) { return 'rgb(' + r + ',' + g + ',' + b + ')'; } function mixThemes(c1, c2, c3 = 0, p1 = .5, p2 = 1.0) { var r = random(); return colorThemes[r < p1 ? c1 : r < p2 ? c2 : c3](); } // Cosine interpolation function interpolation(a, b, t) { return (1 - cos(PI * t)) / 2 * (b - a) + a; } // Create a 1D Maximal Poisson Disc over [0, 1] var radius = 1 / eccentricity, radius2 = radius + radius; function createPoisson() { // domain is the set of points which are still available to pick from // D = union{ [d_i, d_i+1] | i is even } var domain = [radius, 1 - radius], measure = 1 - radius2, spline = [0, 1]; while (measure) { var dart = measure * random(), i, l, interval, a, b, c, d; // Find where dart lies for (i = 0, l = domain.length, measure = 0; i < l; i += 2) { a = domain[i], b = domain[i + 1], interval = b - a; if (dart < measure + interval) { spline.push(dart += a - measure); break; } measure += interval; } c = dart - radius, d = dart + radius; // Update the domain for (i = domain.length - 1; i > 0; i -= 2) { l = i - 1, a = domain[l], b = domain[i]; // c---d c---d Do nothing // c-----d c-----d Move interior // c--------------d Delete interval // c--d Split interval // a------b if (a >= c && a < d) if (b > d) domain[l] = d; // Move interior (Left case) else domain.splice(l, 2); // Delete interval else if (a < c && b > c) if (b <= d) domain[i] = c; // Move interior (Right case) else domain.splice(i, 0, c, d); // Split interval } // Re-measure the domain for (i = 0, l = domain.length, measure = 0; i < l; i += 2) measure += domain[i + 1] - domain[i]; } return spline.sort(); } // Create the overarching container var container = document.createElement('div'); container.style.position = 'fixed'; container.style.top = '0'; container.style.left = '0'; container.style.width = '100%'; container.style.height = '0'; container.style.overflow = 'visible'; container.style.zIndex = '9999'; // Confetto constructor function Confetto(theme) { this.frame = 0; this.outer = document.createElement('div'); this.inner = document.createElement('div'); this.outer.appendChild(this.inner); var outerStyle = this.outer.style, innerStyle = this.inner.style; outerStyle.position = 'absolute'; outerStyle.width = (sizeMin + sizeMax * random()) + 'px'; outerStyle.height = (sizeMin + sizeMax * random()) + 'px'; innerStyle.width = '100%'; innerStyle.height = '100%'; innerStyle.backgroundColor = theme(); outerStyle.perspective = '50px'; outerStyle.transform = 'rotate(' + (360 * random()) + 'deg)'; this.axis = 'rotate3D(' + cos(360 * random()) + ',' + cos(360 * random()) + ',0,'; this.theta = 360 * random(); this.dTheta = dThetaMin + dThetaMax * random(); innerStyle.transform = this.axis + this.theta + 'deg)'; this.x = $window.width() * random(); this.y = -deviation; this.dx = sin(dxThetaMin + dxThetaMax * random()); this.dy = dyMin + dyMax * random(); outerStyle.left = this.x + 'px'; outerStyle.top = this.y + 'px'; // Create the periodic spline this.splineX = createPoisson(); this.splineY = []; for (var i = 1, l = this.splineX.length - 1; i < l; ++i) this.splineY[i] = deviation * random(); this.splineY[0] = this.splineY[l] = deviation * random(); this.update = function (height, delta) { this.frame += delta; this.x += this.dx * delta; this.y += this.dy * delta; this.theta += this.dTheta * delta; // Compute spline and convert to polar var phi = this.frame % 7777 / 7777, i = 0, j = 1; while (phi >= this.splineX[j]) i = j++; var rho = interpolation( this.splineY[i], this.splineY[j], (phi - this.splineX[i]) / (this.splineX[j] - this.splineX[i]) ); phi *= PI2; outerStyle.left = this.x + rho * cos(phi) + 'px'; outerStyle.top = this.y + rho * sin(phi) + 'px'; innerStyle.transform = this.axis + this.theta + 'deg)'; return this.y > height + deviation; }; } function poof() { if (!frame) { // Append the container document.body.appendChild(container); // Add confetti var theme = colorThemes[0], count = 0; if ($('a.btn-celebrate').data('theme') != '') { theme = colorThemes[$('a.btn-celebrate').data('theme')]; } (function addConfetto() { if (++count > particles) return timer = undefined; var confetto = new Confetto(theme); confetti.push(confetto); container.appendChild(confetto.outer); timer = setTimeout(addConfetto, spread * random()); })(0); // Start the loop var prev = undefined; requestAnimationFrame(function loop(timestamp) { var delta = prev ? timestamp - prev : 0; prev = timestamp; var height = $window.height(); for (var i = confetti.length - 1; i >= 0; --i) { if (confetti[i].update(height, delta)) { container.removeChild(confetti[i].outer); confetti.splice(i, 1); } } if (timer || confetti.length) return frame = requestAnimationFrame(loop); // Cleanup document.body.removeChild(container); frame = undefined; }); } } // trigger confetti on page load if (!onlyOnClick) poof(); // trigger confetti on button click $('a.btn-celebrate').on('click', function (e) { e.preventDefault(); poof(); return false; }) }); {%- endjavascript -%} {%- endif -%} Wait for your next birthday, anniversary or workiversary and enjoy the show! Bonus Font Awesome doesn't currently include the "party popper" emoji, so I created one myself and added it to our own custom icon font. You can see an example of the icon in the screenshot below. I attached the .svg file for my party popper icon to this recipe for anyone who's interested and knows how to add their own custom font to Rock. Use the Download File button to get it and feel free to use or modify the icon however you want. By default, the recipe uses the star icon instead: Follow Up Please don't hesitate to leave a comment or hit me up on Rock Chat (@JeffRichmond) if you have questions or find any issues with this recipe. Change Log 2023-04-12 - Added marital status filter for anniversaries and added a note about widowed staff members. 2022-09-02 - Handle 0th anniversaries and workiversaries 2021-07-09 - Initial Version Download File