{"id":229,"date":"2026-04-13T21:48:43","date_gmt":"2026-04-13T21:48:43","guid":{"rendered":"https:\/\/beginnerprojects.com\/cms\/?p=229"},"modified":"2026-04-21T23:59:01","modified_gmt":"2026-04-21T23:59:01","slug":"never-forget-a-birthday-again-a-simple-free-ai-powered-celebration-tracker","status":"publish","type":"post","link":"https:\/\/beginnerprojects.com\/cms\/never-forget-a-birthday-again-a-simple-free-ai-powered-celebration-tracker\/","title":{"rendered":"Never Forget a Birthday Again: A Simple, Free AI-Powered Celebration Tracker"},"content":{"rendered":"\n<p><strong>Note:<\/strong> This is a complete &#8220;Single-File App.&#8221; All the code, instructions, and security steps required to successfully run this software are contained within this post. <a href=\"https:\/\/beginnerprojects.com\/apps\/celebrations-app.html\">You can <strong>preview<\/strong> the app here<\/a><\/p>\n\n\n\n<figure class=\"wp-block-image aligncenter size-full\"><a href=\"https:\/\/beginnerprojects.com\/apps\/celebrations-app.html\"><img loading=\"lazy\" decoding=\"async\" width=\"1200\" height=\"600\" src=\"https:\/\/beginnerprojects.com\/cms\/wp-content\/uploads\/2026\/04\/celebrations-app-preview.webp\" alt=\"Screenshot of the Celebrations app showing a list of birthdays and anniversaries with color-coded countdown tags\" class=\"wp-image-231\" srcset=\"https:\/\/beginnerprojects.com\/cms\/wp-content\/uploads\/2026\/04\/celebrations-app-preview.webp 1200w, https:\/\/beginnerprojects.com\/cms\/wp-content\/uploads\/2026\/04\/celebrations-app-preview-300x150.webp 300w, https:\/\/beginnerprojects.com\/cms\/wp-content\/uploads\/2026\/04\/celebrations-app-preview-1024x512.webp 1024w, https:\/\/beginnerprojects.com\/cms\/wp-content\/uploads\/2026\/04\/celebrations-app-preview-768x384.webp 768w\" sizes=\"auto, (max-width: 1200px) 100vw, 1200px\" \/><\/a><figcaption class=\"wp-element-caption\">Actual screenshot of the Celebrations app<\/figcaption><\/figure>\n\n\n\n<p class=\"has-text-align-center\"><a href=\"https:\/\/beginnerprojects.com\/apps\/celebrations-app.html\">Preview this app right now in your browser<\/a><\/p>\n\n\n\n<p>I have a strange memory. I can remember complex music compositions and navigate advanced software like Blender, FreeCAD, and ComfyUI with ease. But for some reason, remembering birthdays and anniversaries? I\u2019m terrible at it.<\/p>\n\n\n\n<p>If you\u2019re in the same boat as me and need a dependable way to stay on top of important dates, you\u2019ll love how simple this tool is.<\/p>\n\n\n\n<div class=\"wp-block-columns is-layout-flex wp-container-core-columns-is-layout-28f84493 wp-block-columns-is-layout-flex\">\n<div class=\"wp-block-column is-layout-flow wp-block-column-is-layout-flow\">\n<h2 class=\"wp-block-heading\">\u26a0\ufe0f IMPORTANT: Security First (The AI Safety Check)<\/h2>\n\n\n\n<p><strong>I want you to <mark style=\"background-color:rgba(0, 0, 0, 0);color:#cf2e2e\" class=\"has-inline-color\">verify this software before you run it. <\/mark><\/strong><br>Even though I have provided this for free, it is a best practice to always verify code from the internet.<\/p>\n\n\n\n<p>Before you save the code provided below, please do the following:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>Copy the code from the block at the bottom of this post.<\/li>\n\n\n\n<li>Open one of these AI tools:\n<ul class=\"wp-block-list\">\n<li><a href=\"https:\/\/copilot.microsoft.com\/\" target=\"_blank\" rel=\"noreferrer noopener\">Microsoft Copilot<\/a><\/li>\n\n\n\n<li><a href=\"https:\/\/chatgpt.com\/\" target=\"_blank\" rel=\"noreferrer noopener\">ChatGPT<\/a><\/li>\n\n\n\n<li><a href=\"https:\/\/www.perplexity.ai\/\" target=\"_blank\" rel=\"noreferrer noopener\">Perplexity AI<\/a><\/li>\n<\/ul>\n<\/li>\n\n\n\n<li>Paste the code and ask: <em>&#8220;Can you check this HTML\/JavaScript code for any security issues, vulnerabilities, or viruses?&#8221;<\/em><\/li>\n<\/ol>\n\n\n\n<p>In a few seconds, the AI will confirm that the code is safe. Once you have that peace of mind, you are ready to go!<\/p>\n<\/div>\n<\/div>\n\n\n\n<h2 class=\"wp-block-heading\">How to customize the app<\/h2>\n\n\n\n<p>If you are new to programming, then<a href=\"https:\/\/beginnerprojects.com\/cms\/learn-how-the-celebrations-app-actually-works\/\"> take a look at the <strong>app info page<\/strong><\/a> which will help you with changing the placeholder values to the actual names you want to use.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity is-style-wide\"\/>\n\n\n\n<div data-wp-context=\"{ &quot;autoclose&quot;: false, &quot;accordionItems&quot;: [] }\" data-wp-interactive=\"core\/accordion\" role=\"group\" class=\"wp-block-accordion is-layout-flow wp-block-accordion-is-layout-flow\">\n<div data-wp-class--is-open=\"state.isOpen\" data-wp-context=\"{ &quot;id&quot;: &quot;accordion-item-1&quot;, &quot;openByDefault&quot;: false }\" data-wp-init=\"callbacks.initAccordionItems\" data-wp-on-window--hashchange=\"callbacks.hashChange\" class=\"wp-block-accordion-item is-layout-flow wp-block-accordion-item-is-layout-flow\">\n<h3 class=\"wp-block-accordion-heading\"><button aria-expanded=\"false\" aria-controls=\"accordion-item-1-panel\" data-wp-bind--aria-expanded=\"state.isOpen\" data-wp-on--click=\"actions.toggle\" data-wp-on--keydown=\"actions.handleKeyDown\" id=\"accordion-item-1\" type=\"button\" class=\"wp-block-accordion-heading__toggle\"><span class=\"wp-block-accordion-heading__toggle-title\"><mark style=\"background-color:rgba(0, 0, 0, 0)\" class=\"has-inline-color has-palette-color-1-color\">See the Code<\/mark> (click to expand)<\/span><span class=\"wp-block-accordion-heading__toggle-icon\" aria-hidden=\"true\">+<\/span><\/button><\/h3>\n\n\n\n<div inert aria-labelledby=\"accordion-item-1\" data-wp-bind--inert=\"!state.isOpen\" id=\"accordion-item-1-panel\" role=\"region\" class=\"wp-block-accordion-panel is-layout-flow wp-block-accordion-panel-is-layout-flow\">\n<pre class=\"wp-block-code has-palette-color-11-background-color has-background\"><code>&lt;!DOCTYPE html&gt;\n&lt;html lang=\"en\"&gt;\n&lt;head&gt;\n  &lt;meta charset=\"UTF-8\" \/&gt;\n  &lt;title&gt;Celebrations&lt;\/title&gt;\n  &lt;meta name=\"viewport\" content=\"width=1280\" \/&gt;\n  &lt;style&gt;\n    :root {\n      --bg-main: #121212;\n      --bg-surface: #34373F;\n      --text-primary: #abb2ba;\n      --text-secondary: #abb2ba;\n      --accent-primary: #4169E1;\n      --accent-secondary: #45B52D;\n      --domain-green: #27338b;\n      --domain-green-text: #FFFFFF;\n      --domain-orange: #F7B42C;\n      --domain-orange-text: #2A2D34;\n      --domain-red: #F85149;\n      --domain-red-text: #FFFFFF;\n      --border-subtle: #40444C;\n      --border-radius: 7px;\n      --spacing-unit: 20px;\n      --font-sans: Inter, system-ui, sans-serif;\n      --font-mono: Fira Code, monospace;\n    }\n\n    body {\n      background-color: var(--bg-main);\n      color: var(--text-primary);\n      font-family: var(--font-sans);\n      margin: 0;\n      padding: 0;\n      font-size: 1.0625rem;\n      line-height: 1.6;\n    }\n\n    .dashboard-container {\n      max-width: 1140px;\n      margin: 0 auto;\n      padding: calc(var(--spacing-unit) * 1.5) var(--spacing-unit);\n    }\n\n    h1 {\n      font-size: 1.6rem; \n      font-weight: normal;\n      margin-bottom: 20px;\n      color: var(--text-primary);\n    }\n\n    footer p {\n      font-size: 1rem;\n      font-weight: 500;\n      color: var(--text-muted);\n      margin-top: 20px;\n      text-align: center;\n    }\n\n    .payments-table {\n      width: 100%;\n      border-collapse: collapse;\n      margin-bottom: 30px;\n    }\n\n    .payments-table th,\n    .payments-table td {\n      padding: 8px;\n      border-bottom: 1px solid var(--border-subtle);\n      text-align: left;\n    }\n\n    .payments-table th {\n      background-color: var(--bg-surface);\n      color: var(--text-secondary);\n      font-weight: 600;\n      font-size: 0.95rem;\n    }\n\n    .days-remaining-tag {\n      font-weight: 600;\n      padding: 6px 10px;\n      border-radius: var(--border-radius);\n      display: inline-block;\n      font-size: 0.9em;\n    }\n\n    .days-green {\n      background-color: var(--domain-green);\n      color: var(--domain-green-text);\n    }\n    .days-orange {\n      background-color: var(--domain-orange);\n      color: var(--domain-orange-text);\n    }\n    .days-red {\n      background-color: var(--domain-red);\n      color: var(--domain-red-text);\n    }\n\n    .notes-info {\n      font-style: italic;\n      color: var(--text-secondary);\n      white-space: pre-wrap;\n      word-break: break-word;\n    }\n\n    .full-cell {\n      display: block;\n      width: 60%;\n      text-align: center;\n      padding: 8px 20px;\n      border-radius: var(--border-radius);\n    }\n\n    th.sortable {\n      cursor: pointer;\n      position: relative;\n      user-select: none;\n    }\n    th.sortable::after {\n      content: '';\n      font-size: 0.8em;\n      margin-left: 6px;\n    }\n    th.sortable.asc::after {\n      content: '\u25b2';\n    }\n    th.sortable.desc::after {\n      content: '\u25bc';\n    }\n\n    .celebration-table th,\n    .celebration-table td {\n      padding: 8px 12px;\n    }\n  &lt;\/style&gt;\n&lt;\/head&gt;\n&lt;body&gt;\n  &lt;div class=\"dashboard-container\"&gt;\n    &lt;section id=\"celebration-section\"&gt;\n      &lt;h1&gt;Birthdays &amp; Anniversaries&lt;\/h1&gt;\n      &lt;div id=\"celebration-table-wrapper\"&gt;&lt;\/div&gt;\n    &lt;\/section&gt;\n\n    &lt;footer&gt;\n      &lt;p&gt;Today is &lt;span id=\"footer-date\"&gt;&lt;\/span&gt;&lt;\/p&gt;\n    &lt;\/footer&gt;\n  &lt;\/div&gt;\n\n  &lt;script&gt;\n    \/\/ Footer Date\n    document.addEventListener(\"DOMContentLoaded\", () =&gt; {\n      const today = new Date();\n      const dateSpan = document.getElementById(\"footer-date\");\n      if (dateSpan) {\n        dateSpan.textContent = today.toLocaleDateString(undefined, {\n          weekday: \"short\",\n          year: \"numeric\",\n          month: \"short\",\n          day: \"numeric\"\n        });\n      }\n      if (document.getElementById(\"celebration-table-wrapper\")) renderCelebrationTable();\n    });\n\n    \/\/ Celebrations Data &amp; Logic\n    const celebrations = &#91;\n      { name: \"Name_1\", type: \"Birthday\", date: \"1963-09-05T00:00:00\", notes: \"Don't Email. Call 000.000.1234 instead. \" },\n      { name: \"Name_2\", type: \"Birthday\", date: \"1961-12-19T00:00:00\", notes: \"Send flowers.\" },\n      { name: \"Name_3\", type: \"Birthday\", date: \"1961-11-03T00:00:00\", notes: \"Loves to video chat instead of calling\" },\n      { name: \"Name_4\", type: \"Birthday\", date: \"1989-02-25T00:00:00\", notes: \"Make sure to text his new cell number!\" },\n      { name: \"Name_5\", type: \"Birthday\", date: \"1988-06-23T00:00:00\", notes: \"Loved the givt last year\" },\n      { name: \"Name_6\", type: \"Birthday\", date: \"2021-11-16T00:00:00\", notes: \"Will be 75 this year!\" }\n    ];\n\n    let celebrationSort = { column: \"days\", direction: \"asc\" };\n\n    function getNextCelebrationCountdown(dateStr) {\n      const today = new Date();\n      const original = new Date(dateStr);\n      const thisYear = new Date(today.getFullYear(), original.getMonth(), original.getDate());\n      const nextYear = new Date(today.getFullYear() + 1, original.getMonth(), original.getDate());\n      const eventDate = thisYear &gt;= today ? thisYear : nextYear;\n\n      const daysUntil = Math.ceil((eventDate - today) \/ (1000 * 60 * 60 * 24));\n      let tag = \"days-green\";\n      if (daysUntil &lt;= 3) tag = \"days-red\";\n      else if (daysUntil &lt;= 7) tag = \"days-orange\";\n\n      return { daysUntil, tag, displayDate: eventDate.toLocaleDateString(\"en-CA\") };\n    }\n\n    function toggleCelebrationSort(key) {\n      if (celebrationSort.column === key) {\n        celebrationSort.direction = celebrationSort.direction === \"asc\" ? \"desc\" : \"asc\";\n      } else {\n        celebrationSort.column = key;\n        celebrationSort.direction = \"asc\";\n      }\n      renderCelebrationTable();\n    }\n\n    function renderCelebrationTable() {\n      let data = &#91;...celebrations];\n      if (celebrationSort.column) {\n        data.sort((a, b) =&gt; {\n          let x = a&#91;celebrationSort.column];\n          let y = b&#91;celebrationSort.column];\n          if (celebrationSort.column === \"days\") {\n            x = getNextCelebrationCountdown(a.date).daysUntil;\n            y = getNextCelebrationCountdown(b.date).daysUntil;\n            return celebrationSort.direction === \"asc\" ? x - y : y - x;\n          }\n          return celebrationSort.direction === \"asc\"\n            ? x.localeCompare(y)\n            : y.localeCompare(x);\n        });\n      }\n\n      let html = `&lt;table class=\"payments-table\"&gt;&lt;thead&gt;&lt;tr&gt;\n        &lt;th class=\"sortable ${celebrationSort.column === \"name\" ? celebrationSort.direction : \"\"}\" onclick=\"toggleCelebrationSort('name')\"&gt;Name&lt;\/th&gt;\n        &lt;th&gt;Type&lt;\/th&gt;\n        &lt;th&gt;Date&lt;\/th&gt;\n        &lt;th class=\"sortable ${celebrationSort.column === \"days\" ? celebrationSort.direction : \"\"}\" onclick=\"toggleCelebrationSort('days')\"&gt;Days Until&lt;\/th&gt;\n        &lt;th&gt;Notes&lt;\/th&gt;&lt;\/tr&gt;&lt;\/thead&gt;&lt;tbody&gt;`;\n\n      data.forEach(({ name, type, date, notes }) =&gt; {\n        const { daysUntil, tag, displayDate } = getNextCelebrationCountdown(date);\n        html += `&lt;tr&gt;\n          &lt;td&gt;${name}&lt;\/td&gt;\n          &lt;td&gt;${type}&lt;\/td&gt;\n          &lt;td&gt;${displayDate}&lt;\/td&gt;\n          &lt;td&gt;&lt;span class=\"days-remaining-tag ${tag} full-cell\"&gt;${daysUntil} Days&lt;\/span&gt;&lt;\/td&gt;\n          &lt;td class=\"notes-info\"&gt;${notes}&lt;\/td&gt;\n        &lt;\/tr&gt;`;\n      });\n\n      html += \"&lt;\/tbody&gt;&lt;\/table&gt;\";\n      document.getElementById(\"celebration-table-wrapper\").innerHTML = html;\n    }\n  &lt;\/script&gt;\n&lt;\/body&gt;\n&lt;\/html&gt;<\/code><\/pre>\n<\/div>\n<\/div>\n<\/div>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity is-style-wide\"\/>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p><em><a href=\"https:\/\/beginnerprojects.com\/cms\/learn-how-the-celebrations-app-actually-works\/\" data-type=\"post\" data-id=\"246\">Learn How The Celebrations App Actually Works<\/a><\/em><\/p>\n<\/blockquote>\n\n\n\n<h3 class=\"wp-block-heading\">You&#8217;ve Just Taken Your First Step into Software!<\/h3>\n\n\n\n<p>Congratulations! By following these steps, you didn&#8217;t just download a tool\u2014you hosted your own local application. That is a huge win for anyone starting their journey with AI and coding.<\/p>\n\n\n\n<p>If you run into any trouble, get stuck on a line of code, or need a hand adjusting the settings, please leave a comment below. I will do my absolute best to help you get it working perfectly.<\/p>\n\n\n\n<p>What&#8217;s coming next? Stay tuned for the next release: the Payments Manager. Similar to the Celebrations app, it&#8217;s a clean, local tool, but it&#8217;s specialized for financial tracking and organizing your online banking needs.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Note: This is a complete &#8220;Single-File App.&#8221; All the code, instructions, and security steps required to successfully run this software are contained within this post. You can preview the app here Preview this app right now in your browser I have a strange memory. I can remember complex music compositions and navigate advanced software like [&hellip;]<\/p>\n","protected":false},"author":2,"featured_media":231,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_ecmd_meta_description":"Stop forgetting birthdays! Download this free, simple AI-powered Celebrations app. A private, local tool to track anniversaries and birthdays with ease.","footnotes":""},"categories":[6,9],"tags":[],"class_list":["post-229","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-free-apps","category-javascript"],"blocksy_meta":{"page_structure_type":"type-4","styles_descriptor":{"styles":{"desktop":"","tablet":"","mobile":""},"google_fonts":[],"version":6}},"_links":{"self":[{"href":"https:\/\/beginnerprojects.com\/cms\/wp-json\/wp\/v2\/posts\/229","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/beginnerprojects.com\/cms\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/beginnerprojects.com\/cms\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/beginnerprojects.com\/cms\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/beginnerprojects.com\/cms\/wp-json\/wp\/v2\/comments?post=229"}],"version-history":[{"count":16,"href":"https:\/\/beginnerprojects.com\/cms\/wp-json\/wp\/v2\/posts\/229\/revisions"}],"predecessor-version":[{"id":467,"href":"https:\/\/beginnerprojects.com\/cms\/wp-json\/wp\/v2\/posts\/229\/revisions\/467"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/beginnerprojects.com\/cms\/wp-json\/wp\/v2\/media\/231"}],"wp:attachment":[{"href":"https:\/\/beginnerprojects.com\/cms\/wp-json\/wp\/v2\/media?parent=229"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/beginnerprojects.com\/cms\/wp-json\/wp\/v2\/categories?post=229"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/beginnerprojects.com\/cms\/wp-json\/wp\/v2\/tags?post=229"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}