Cool-light theme, Google event colors, photo frame contain+lower-right clock
- Main app swapped from warm cream/sage to cool slate light theme with blue accent; dark-* tokens and text-white/gray overrides remapped in tailwind.config.js and index.css - Calendar events now render in Google's 11-color palette (Tomato through Graphite), deterministically hashed from the event summary so the same event always gets the same color - PhotoFrame uses object-contain (whole photo shown, letterboxed) instead of object-cover; clock + date moved to lower-right, same white color, text-shadow for readability over any photo - EMAIL_UPLOAD.md / PHOTO_FRAME.md / iCloud sync script and systemd timer remain unchanged
This commit is contained in:
@@ -2,10 +2,15 @@ import { useState, useMemo, useCallback, useEffect } from 'react';
|
||||
import { format, startOfMonth, endOfMonth, startOfWeek, endOfWeek, addDays, isSameMonth, isSameDay, isToday } from 'date-fns';
|
||||
import { useCalendar, CalendarEvent, formatEventTime } from '@/hooks/useCalendar';
|
||||
import { VirtualKeyboard } from '@/components/keyboard';
|
||||
import { getEventColor } from './eventColors';
|
||||
|
||||
function EventItem({ event }: { event: CalendarEvent }) {
|
||||
const color = getEventColor(event.summary);
|
||||
return (
|
||||
<div className="text-[0.6rem] truncate px-1 py-0.5 rounded bg-accent/20 text-accent-light">
|
||||
<div
|
||||
className="text-[0.6rem] truncate px-1 py-0.5 rounded"
|
||||
style={{ backgroundColor: color.bg, color: color.text }}
|
||||
>
|
||||
{formatEventTime(event)} {event.summary}
|
||||
</div>
|
||||
);
|
||||
@@ -74,21 +79,28 @@ function EventDetails({ date, events, onAddEvent }: { date: Date; events: Calend
|
||||
<p className="text-xs text-gray-500">No events</p>
|
||||
) : (
|
||||
<div className="space-y-1.5 max-h-24 overflow-y-auto">
|
||||
{events.map((event) => (
|
||||
<div key={event.id} className="text-xs">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-accent font-medium">
|
||||
{formatEventTime(event)}
|
||||
</span>
|
||||
<span className="flex-1 truncate">{event.summary}</span>
|
||||
</div>
|
||||
{event.location && (
|
||||
<div className="text-[0.65rem] text-gray-500 truncate ml-12">
|
||||
{event.location}
|
||||
{events.map((event) => {
|
||||
const color = getEventColor(event.summary);
|
||||
return (
|
||||
<div key={event.id} className="text-xs">
|
||||
<div className="flex items-center gap-2">
|
||||
<span
|
||||
className="inline-block w-2 h-2 rounded-full shrink-0"
|
||||
style={{ backgroundColor: color.bg }}
|
||||
/>
|
||||
<span className="font-medium" style={{ color: color.bg }}>
|
||||
{formatEventTime(event)}
|
||||
</span>
|
||||
<span className="flex-1 truncate">{event.summary}</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
{event.location && (
|
||||
<div className="text-[0.65rem] text-gray-500 truncate ml-12">
|
||||
{event.location}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
32
src/components/calendar/eventColors.ts
Normal file
32
src/components/calendar/eventColors.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
// Google Calendar's 11 event colors, values lifted from the web client
|
||||
// color picker. Colors are assigned deterministically by hashing the event
|
||||
// summary so each recurring event always renders in the same color.
|
||||
const GOOGLE_EVENT_COLORS = [
|
||||
{ name: 'Tomato', bg: '#d50000', text: '#ffffff' },
|
||||
{ name: 'Flamingo', bg: '#e67c73', text: '#ffffff' },
|
||||
{ name: 'Tangerine', bg: '#f4511e', text: '#ffffff' },
|
||||
{ name: 'Banana', bg: '#f6bf26', text: '#202124' },
|
||||
{ name: 'Sage', bg: '#33b679', text: '#ffffff' },
|
||||
{ name: 'Basil', bg: '#0b8043', text: '#ffffff' },
|
||||
{ name: 'Peacock', bg: '#039be5', text: '#ffffff' },
|
||||
{ name: 'Blueberry', bg: '#3f51b5', text: '#ffffff' },
|
||||
{ name: 'Lavender', bg: '#7986cb', text: '#ffffff' },
|
||||
{ name: 'Grape', bg: '#8e24aa', text: '#ffffff' },
|
||||
{ name: 'Graphite', bg: '#616161', text: '#ffffff' },
|
||||
] as const;
|
||||
|
||||
export type EventColor = (typeof GOOGLE_EVENT_COLORS)[number];
|
||||
|
||||
function hash(str: string): number {
|
||||
let h = 2166136261;
|
||||
for (let i = 0; i < str.length; i++) {
|
||||
h ^= str.charCodeAt(i);
|
||||
h = Math.imul(h, 16777619);
|
||||
}
|
||||
return h >>> 0;
|
||||
}
|
||||
|
||||
export function getEventColor(key: string): EventColor {
|
||||
const idx = hash(key.toLowerCase().trim()) % GOOGLE_EVENT_COLORS.length;
|
||||
return GOOGLE_EVENT_COLORS[idx];
|
||||
}
|
||||
@@ -137,7 +137,7 @@ export function PhotoFrame({ intervalMs = 15_000, transitionMs = 1_200 }: PhotoF
|
||||
key={`prev-${prevSrc}`}
|
||||
src={prevSrc}
|
||||
alt=""
|
||||
className="absolute inset-0 w-full h-full object-cover animate-ken-burns"
|
||||
className="absolute inset-0 w-full h-full object-contain animate-ken-burns"
|
||||
style={{ opacity: 1 }}
|
||||
/>
|
||||
)}
|
||||
@@ -146,20 +146,18 @@ export function PhotoFrame({ intervalMs = 15_000, transitionMs = 1_200 }: PhotoF
|
||||
key={`cur-${index}`}
|
||||
src={currentSrc}
|
||||
alt=""
|
||||
className="absolute inset-0 w-full h-full object-cover animate-ken-burns"
|
||||
className="absolute inset-0 w-full h-full object-contain animate-ken-burns"
|
||||
style={{
|
||||
opacity: 0,
|
||||
animation: `photo-fade-in ${transitionMs}ms ease-out forwards, ken-burns 20s ease-in-out infinite alternate`,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{/* Gradient + centered clock/date overlay (Skylight style) */}
|
||||
<div className="pointer-events-none absolute inset-x-0 bottom-0 h-56 bg-gradient-to-t from-black/75 via-black/30 to-transparent" />
|
||||
<div className="pointer-events-none absolute inset-x-0 bottom-14 flex justify-center">
|
||||
<div className="px-10 py-5 rounded-3xl bg-black/25 backdrop-blur-md border border-white/10 text-white text-center drop-shadow-2xl">
|
||||
<div className="text-7xl md:text-8xl font-light tracking-tight leading-none">{timeStr}</div>
|
||||
<div className="text-2xl md:text-3xl mt-3 capitalize font-light opacity-95">{dateStr}</div>
|
||||
</div>
|
||||
{/* Lower-right clock + date overlay */}
|
||||
<div className="pointer-events-none absolute bottom-8 right-10 text-right text-white leading-tight"
|
||||
style={{ textShadow: '0 2px 12px rgba(0,0,0,0.9), 0 0 4px rgba(0,0,0,0.7)' }}>
|
||||
<div className="text-6xl md:text-7xl font-light tracking-tight">{timeStr}</div>
|
||||
<div className="text-xl md:text-2xl mt-1 capitalize font-light">{dateStr}</div>
|
||||
</div>
|
||||
<style>{`
|
||||
@keyframes photo-fade-in {
|
||||
|
||||
@@ -266,23 +266,21 @@
|
||||
}
|
||||
|
||||
/*
|
||||
* Theme remap: existing components use text-white and text-gray-* for
|
||||
* dark-theme text. For the Skylight light theme, rebind those utilities
|
||||
* to the warm ink scale so every component becomes readable on cream bg
|
||||
* without touching hundreds of component files.
|
||||
* Theme remap (cool light): existing components use text-white and
|
||||
* text-gray-* for dark-theme text. Rebind to cool slate ink so the UI
|
||||
* is readable on the slate-100 bg without touching every component.
|
||||
*/
|
||||
.text-white { color: #3a322a; }
|
||||
.text-gray-300 { color: #4a4239; }
|
||||
.text-gray-400 { color: #6b5f51; }
|
||||
.text-gray-500 { color: #8a7d6e; }
|
||||
.text-gray-600 { color: #a0937f; }
|
||||
.text-white { color: #0f172a; }
|
||||
.text-gray-300 { color: #1e293b; }
|
||||
.text-gray-400 { color: #475569; }
|
||||
.text-gray-500 { color: #64748b; }
|
||||
.text-gray-600 { color: #94a3b8; }
|
||||
|
||||
/* Dark backgrounds that were nearly-black in the old theme */
|
||||
.bg-imperial-black { background-color: #faf6f0; }
|
||||
.bg-black { background-color: #2a2520; }
|
||||
.bg-imperial-black { background-color: #f1f5f9; }
|
||||
.bg-black { background-color: #0f172a; }
|
||||
|
||||
.text-ink { color: #3a322a; }
|
||||
.text-ink-muted { color: #6b5f51; }
|
||||
.text-ink-subtle { color: #8a7d6e; }
|
||||
.text-ink-faint { color: #b0a494; }
|
||||
.text-ink { color: #0f172a; }
|
||||
.text-ink-muted { color: #475569; }
|
||||
.text-ink-subtle { color: #64748b; }
|
||||
.text-ink-faint { color: #94a3b8; }
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user