Skip to content

Commit be3e42e

Browse files
committed
Add Notification Header component and integrate into layout
1 parent 1bb07b5 commit be3e42e

3 files changed

Lines changed: 316 additions & 0 deletions

File tree

Lines changed: 294 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,294 @@
1+
---
2+
// Notification Header Component
3+
---
4+
5+
<div id="notification-header" class="notification-header hidden">
6+
<div class="notification-content">
7+
<div class="notification-text">
8+
<div class="notification-icon">
9+
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-5 h-5">
10+
<path stroke-linecap="round" stroke-linejoin="round" d="M14.857 17.082a23.848 23.848 0 005.454-1.31A8.967 8.967 0 0118 9.75v-.7V9A6 6 0 006 9v.75a8.967 8.967 0 01-2.312 6.022c1.733.64 3.56 1.085 5.455 1.31m5.714 0a24.255 24.255 0 01-5.714 0m5.714 0a3 3 0 11-5.714 0" />
11+
</svg>
12+
</div>
13+
<div class="notification-content-text">
14+
<span class="notification-title">Important Update</span>
15+
<span class="notification-message">
16+
TimeKeeper has moved to a new domain: <a href="https://timekeeper.edbn.me" target="_blank" class="notification-link">timekeeper.edbn.me</a>
17+
</span>
18+
</div>
19+
</div>
20+
<button id="close-notification" class="close-button" aria-label="Close notification">
21+
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-4 h-4">
22+
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12" />
23+
</svg>
24+
</button>
25+
</div>
26+
</div>
27+
28+
<style>
29+
.notification-header {
30+
position: fixed;
31+
top: 0;
32+
left: 0;
33+
right: 0;
34+
background: linear-gradient(135deg, #667eea 0%, #764ba2 50%, #8b5fbf 100%);
35+
color: white;
36+
padding: 16px 24px;
37+
z-index: 99999;
38+
box-shadow: 0 4px 20px rgba(102, 126, 234, 0.25);
39+
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
40+
backdrop-filter: blur(10px);
41+
transform: translateY(-100%);
42+
transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
43+
}
44+
45+
.notification-header.show {
46+
transform: translateY(0);
47+
}
48+
49+
.notification-header.hidden {
50+
display: none;
51+
}
52+
53+
.notification-content {
54+
display: flex;
55+
justify-content: space-between;
56+
align-items: center;
57+
max-width: 1200px;
58+
margin: 0 auto;
59+
gap: 16px;
60+
}
61+
62+
.notification-text {
63+
display: flex;
64+
align-items: flex-start;
65+
gap: 12px;
66+
flex: 1;
67+
}
68+
69+
.notification-icon {
70+
color: #ffd700;
71+
margin-top: 2px;
72+
flex-shrink: 0;
73+
}
74+
75+
.notification-content-text {
76+
display: flex;
77+
flex-direction: column;
78+
gap: 4px;
79+
}
80+
81+
.notification-title {
82+
font-size: 14px;
83+
font-weight: 700;
84+
color: #ffd700;
85+
text-transform: uppercase;
86+
letter-spacing: 0.5px;
87+
}
88+
89+
.notification-message {
90+
font-size: 15px;
91+
font-weight: 400;
92+
line-height: 1.5;
93+
color: rgba(255, 255, 255, 0.95);
94+
}
95+
96+
.notification-link {
97+
color: #ffd700;
98+
text-decoration: none;
99+
font-weight: 600;
100+
border-bottom: 1px solid transparent;
101+
transition: all 0.2s ease;
102+
padding: 1px 2px;
103+
border-radius: 2px;
104+
}
105+
106+
.notification-link:hover {
107+
color: #fff;
108+
border-bottom-color: #ffd700;
109+
background-color: rgba(255, 215, 0, 0.1);
110+
}
111+
112+
.close-button {
113+
background: rgba(255, 255, 255, 0.1);
114+
border: 1px solid rgba(255, 255, 255, 0.2);
115+
color: white;
116+
cursor: pointer;
117+
padding: 8px;
118+
border-radius: 8px;
119+
transition: all 0.3s ease;
120+
display: flex;
121+
align-items: center;
122+
justify-content: center;
123+
flex-shrink: 0;
124+
backdrop-filter: blur(5px);
125+
}
126+
127+
.close-button:hover {
128+
background-color: rgba(255, 255, 255, 0.2);
129+
border-color: rgba(255, 255, 255, 0.3);
130+
transform: scale(1.05);
131+
}
132+
133+
.close-button:focus {
134+
outline: 2px solid #ffd700;
135+
outline-offset: 2px;
136+
}
137+
138+
.close-button:active {
139+
transform: scale(0.95);
140+
}
141+
142+
/* Mobile responsiveness */
143+
@media (max-width: 768px) {
144+
.notification-header {
145+
padding: 14px 20px;
146+
}
147+
148+
.notification-content {
149+
gap: 12px;
150+
}
151+
152+
.notification-text {
153+
gap: 10px;
154+
}
155+
156+
.notification-title {
157+
font-size: 13px;
158+
}
159+
160+
.notification-message {
161+
font-size: 14px;
162+
}
163+
}
164+
165+
@media (max-width: 480px) {
166+
.notification-header {
167+
padding: 12px 16px;
168+
}
169+
170+
.notification-content {
171+
gap: 8px;
172+
}
173+
174+
.notification-text {
175+
gap: 8px;
176+
}
177+
178+
.notification-content-text {
179+
gap: 2px;
180+
}
181+
182+
.notification-title {
183+
font-size: 12px;
184+
}
185+
186+
.notification-message {
187+
font-size: 13px;
188+
line-height: 1.4;
189+
}
190+
191+
.close-button {
192+
padding: 6px;
193+
}
194+
}
195+
196+
/* Animation enhancement */
197+
@keyframes slideInDown {
198+
from {
199+
transform: translateY(-100%);
200+
opacity: 0;
201+
}
202+
to {
203+
transform: translateY(0);
204+
opacity: 1;
205+
}
206+
}
207+
208+
.notification-header.show {
209+
animation: slideInDown 0.4s cubic-bezier(0.4, 0, 0.2, 1);
210+
}
211+
212+
/* Global body adjustment when notification is active */
213+
:global(body.notification-active) {
214+
padding-top: 80px;
215+
transition: padding-top 0.4s cubic-bezier(0.4, 0, 0.2, 1);
216+
}
217+
218+
@media (max-width: 768px) {
219+
:global(body.notification-active) {
220+
padding-top: 75px;
221+
}
222+
}
223+
224+
@media (max-width: 480px) {
225+
:global(body.notification-active) {
226+
padding-top: 70px;
227+
}
228+
}
229+
</style>
230+
231+
<script>
232+
// Notification management
233+
const NOTIFICATION_KEY = 'timekeeper-site-update-notification';
234+
const NOTIFICATION_DURATION = 24 * 60 * 60 * 1000; // 24 hours in milliseconds
235+
236+
function shouldShowNotification() {
237+
const lastDismissed = localStorage.getItem(NOTIFICATION_KEY);
238+
if (!lastDismissed) return true;
239+
240+
const now = Date.now();
241+
const dismissedTime = parseInt(lastDismissed, 10);
242+
243+
return (now - dismissedTime) >= NOTIFICATION_DURATION;
244+
}
245+
246+
function dismissNotification() {
247+
const notification = document.getElementById('notification-header');
248+
if (notification) {
249+
notification.classList.remove('show');
250+
document.body.classList.remove('notification-active');
251+
setTimeout(() => {
252+
notification.classList.add('hidden');
253+
}, 300);
254+
255+
// Store dismissal time
256+
localStorage.setItem(NOTIFICATION_KEY, Date.now().toString());
257+
}
258+
}
259+
260+
function showNotification() {
261+
const notification = document.getElementById('notification-header');
262+
if (notification) {
263+
notification.classList.remove('hidden');
264+
// Small delay to ensure the element is rendered before animation
265+
setTimeout(() => {
266+
notification.classList.add('show');
267+
document.body.classList.add('notification-active');
268+
}, 10);
269+
}
270+
}
271+
272+
// Initialize notification on page load
273+
document.addEventListener('DOMContentLoaded', () => {
274+
if (shouldShowNotification()) {
275+
showNotification();
276+
}
277+
278+
// Add close button event listener
279+
const closeButton = document.getElementById('close-notification');
280+
if (closeButton) {
281+
closeButton.addEventListener('click', dismissNotification);
282+
}
283+
284+
// Also allow dismissal by pressing Escape key when notification is focused
285+
document.addEventListener('keydown', (event) => {
286+
if (event.key === 'Escape') {
287+
const notification = document.getElementById('notification-header');
288+
if (notification && notification.classList.contains('show')) {
289+
dismissNotification();
290+
}
291+
}
292+
});
293+
});
294+
</script>

src/env.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
1+
/// <reference path="../.astro/types.d.ts" />
12
/// <reference types="astro/client" />

src/layouts/Layout.astro

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ const { title } = Astro.props;
77
const { description } = Astro.props;
88
import Head from "../components/Head.astro";
99
import Footer from "../components/Footer.astro";
10+
import NotificationHeader from "../components/NotificationHeader.astro";
1011
---
1112

1213
<!DOCTYPE html>
@@ -26,6 +27,7 @@ import Footer from "../components/Footer.astro";
2627
/>
2728
</head>
2829
<body>
30+
<NotificationHeader />
2931
<Head />
3032
<slot />
3133
<Footer />
@@ -45,6 +47,25 @@ import Footer from "../components/Footer.astro";
4547
font-family: system-ui, sans-serif;
4648
background-color: #f6f6f6;
4749
}
50+
51+
body {
52+
margin: 0;
53+
padding: 0;
54+
transition: padding-top 0.3s ease-in-out;
55+
}
56+
57+
/* Adjust body padding when notification is visible */
58+
body:has(.notification-header.show) {
59+
padding-top: 50px;
60+
}
61+
62+
/* Fallback for browsers that don't support :has() */
63+
@supports not selector(:has(*)) {
64+
.notification-active {
65+
padding-top: 50px !important;
66+
}
67+
}
68+
4869
code {
4970
font-family: Menlo, Monaco, Lucida Console, Liberation Mono,
5071
DejaVu Sans Mono, Bitstream Vera Sans Mono, Courier New, monospace;

0 commit comments

Comments
 (0)