diff --git a/app/views/common/_calendar.html.erb b/app/views/common/_calendar.html.erb
index 704f865..393a09a 100644
--- a/app/views/common/_calendar.html.erb
+++ b/app/views/common/_calendar.html.erb
@@ -1,3 +1,5 @@
+<%= javascript_include_tag('dynamic_favicon', plugin: :redmine_qbo_calendar) %>
+
<%
# Check if toggles are active via query params
only_appointments = params[:only_appointments] == '1'
diff --git a/app/views/customer_appointments/edit.html.erb b/app/views/customer_appointments/edit.html.erb
index 8db5cd9..0084ae4 100644
--- a/app/views/customer_appointments/edit.html.erb
+++ b/app/views/customer_appointments/edit.html.erb
@@ -1,3 +1,5 @@
+<%= javascript_include_tag('dynamic_favicon', plugin: :redmine_qbo_calendar) %>
+
Edit Appointment
<%= render "form" %>
\ No newline at end of file
diff --git a/app/views/customer_appointments/new.html.erb b/app/views/customer_appointments/new.html.erb
index 852510f..b440001 100644
--- a/app/views/customer_appointments/new.html.erb
+++ b/app/views/customer_appointments/new.html.erb
@@ -1,3 +1,5 @@
+<%= javascript_include_tag('dynamic_favicon', plugin: :redmine_qbo_calendar) %>
+
New Appointment
<%= render "form" %>
\ No newline at end of file
diff --git a/app/views/customer_appointments/show.html.erb b/app/views/customer_appointments/show.html.erb
index 6ad3250..57950a9 100644
--- a/app/views/customer_appointments/show.html.erb
+++ b/app/views/customer_appointments/show.html.erb
@@ -1,3 +1,5 @@
+<%= javascript_include_tag('dynamic_favicon', plugin: :redmine_qbo_calendar) %>
+
<%= link_to issues_calendar_path do %>
<%= l(:button_calendar) %>
diff --git a/assets/javascripts/dynamic_favicon.js b/assets/javascripts/dynamic_favicon.js
new file mode 100644
index 0000000..ffb57b1
--- /dev/null
+++ b/assets/javascripts/dynamic_favicon.js
@@ -0,0 +1,140 @@
+(function() {
+ 'use strict';
+
+ console.log('[Redmine QBO Calendar] dynamic_favicon.js loaded (Simplified Canvas PNG)');
+
+ const FAVICON_ID = 'redmine-qbo-calendar-favicon';
+
+ function generatePngFaviconUrl(day, callback) {
+ const svg = `
+
+ `;
+
+ const cleanSvg = svg.replace(/\n/g, '').replace(/\s+/g, ' ').trim();
+ const svgDataUrl = 'data:image/svg+xml;base64,' + btoa(unescape(encodeURIComponent(cleanSvg)));
+
+ const canvas = document.createElement('canvas');
+ canvas.width = 64;
+ canvas.height = 64;
+ const ctx = canvas.getContext('2d');
+
+ const img = new Image();
+
+ img.onload = function() {
+ ctx.drawImage(img, 0, 0, 64, 64);
+ const pngDataUrl = canvas.toDataURL('image/png');
+ callback(pngDataUrl);
+ };
+
+ img.src = svgDataUrl;
+ }
+
+ function setFavicon() {
+ const day = new Date().getDate();
+
+ generatePngFaviconUrl(day, function(newFaviconUrl) {
+ const existingIcons = document.querySelectorAll('link[rel*="icon"]');
+
+ if (existingIcons.length > 0) {
+ existingIcons.forEach(function(icon) {
+ icon.type = 'image/png';
+ icon.rel = 'icon';
+ icon.href = newFaviconUrl;
+ });
+ } else {
+ const favicon = document.createElement('link');
+ favicon.id = FAVICON_ID;
+ favicon.rel = 'icon';
+ favicon.type = 'image/png';
+ favicon.href = newFaviconUrl;
+ document.head.appendChild(favicon);
+ }
+
+ console.log(
+ '[OSOR Calendar] favicon updated (Simplified PNG)',
+ { day: day }
+ );
+ });
+ }
+
+ function initialize() {
+ console.log('[OSOR Calendar] initializing favicon');
+ setTimeout(setFavicon, 150);
+ }
+
+ if (document.readyState === 'loading') {
+ document.addEventListener(
+ 'DOMContentLoaded',
+ initialize
+ );
+ } else {
+ initialize();
+ }
+
+})();
\ No newline at end of file