Compare commits
5 Commits
bugfix/sql
...
970b0a07da
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
970b0a07da | ||
|
|
e2e5813b54 | ||
|
|
73567c185c | ||
| 0587762bbb | |||
| 4d2aad404b |
@@ -38,4 +38,4 @@ calendar.db
|
||||
**/tests/
|
||||
|
||||
# Migrations (not needed for builds)
|
||||
migrations/
|
||||
migrations/
|
||||
|
||||
2
backend/migrations/004_add_style_preference.sql
Normal file
2
backend/migrations/004_add_style_preference.sql
Normal file
@@ -0,0 +1,2 @@
|
||||
-- Add calendar style preference to user preferences
|
||||
ALTER TABLE user_preferences ADD COLUMN calendar_style TEXT DEFAULT 'default';
|
||||
@@ -91,6 +91,7 @@ impl AuthService {
|
||||
calendar_time_increment: preferences.calendar_time_increment,
|
||||
calendar_view_mode: preferences.calendar_view_mode,
|
||||
calendar_theme: preferences.calendar_theme,
|
||||
calendar_style: preferences.calendar_style,
|
||||
calendar_colors: preferences.calendar_colors,
|
||||
},
|
||||
})
|
||||
|
||||
@@ -93,6 +93,7 @@ pub struct UserPreferences {
|
||||
pub calendar_time_increment: Option<i32>,
|
||||
pub calendar_view_mode: Option<String>,
|
||||
pub calendar_theme: Option<String>,
|
||||
pub calendar_style: Option<String>,
|
||||
pub calendar_colors: Option<String>, // JSON string
|
||||
pub updated_at: DateTime<Utc>,
|
||||
}
|
||||
@@ -106,6 +107,7 @@ impl UserPreferences {
|
||||
calendar_time_increment: Some(15),
|
||||
calendar_view_mode: Some("month".to_string()),
|
||||
calendar_theme: Some("light".to_string()),
|
||||
calendar_style: Some("default".to_string()),
|
||||
calendar_colors: None,
|
||||
updated_at: Utc::now(),
|
||||
}
|
||||
@@ -264,14 +266,15 @@ impl<'a> PreferencesRepository<'a> {
|
||||
sqlx::query(
|
||||
"INSERT INTO user_preferences
|
||||
(user_id, calendar_selected_date, calendar_time_increment,
|
||||
calendar_view_mode, calendar_theme, calendar_colors, updated_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?)",
|
||||
calendar_view_mode, calendar_theme, calendar_style, calendar_colors, updated_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?)",
|
||||
)
|
||||
.bind(&prefs.user_id)
|
||||
.bind(&prefs.calendar_selected_date)
|
||||
.bind(&prefs.calendar_time_increment)
|
||||
.bind(&prefs.calendar_view_mode)
|
||||
.bind(&prefs.calendar_theme)
|
||||
.bind(&prefs.calendar_style)
|
||||
.bind(&prefs.calendar_colors)
|
||||
.bind(&prefs.updated_at)
|
||||
.execute(self.db.pool())
|
||||
@@ -286,7 +289,7 @@ impl<'a> PreferencesRepository<'a> {
|
||||
sqlx::query(
|
||||
"UPDATE user_preferences
|
||||
SET calendar_selected_date = ?, calendar_time_increment = ?,
|
||||
calendar_view_mode = ?, calendar_theme = ?,
|
||||
calendar_view_mode = ?, calendar_theme = ?, calendar_style = ?,
|
||||
calendar_colors = ?, updated_at = ?
|
||||
WHERE user_id = ?",
|
||||
)
|
||||
@@ -294,6 +297,7 @@ impl<'a> PreferencesRepository<'a> {
|
||||
.bind(&prefs.calendar_time_increment)
|
||||
.bind(&prefs.calendar_view_mode)
|
||||
.bind(&prefs.calendar_theme)
|
||||
.bind(&prefs.calendar_style)
|
||||
.bind(&prefs.calendar_colors)
|
||||
.bind(Utc::now())
|
||||
.bind(&prefs.user_id)
|
||||
|
||||
@@ -2,7 +2,6 @@ use axum::{extract::State, http::HeaderMap, response::Json};
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::calendar::CalDAVClient;
|
||||
use crate::config::CalDAVConfig;
|
||||
use crate::{
|
||||
models::{ApiError, AuthResponse, CalDAVLoginRequest, CalendarInfo, UserInfo},
|
||||
AppState,
|
||||
|
||||
@@ -38,6 +38,7 @@ pub async fn get_preferences(
|
||||
calendar_time_increment: preferences.calendar_time_increment,
|
||||
calendar_view_mode: preferences.calendar_view_mode,
|
||||
calendar_theme: preferences.calendar_theme,
|
||||
calendar_style: preferences.calendar_style,
|
||||
calendar_colors: preferences.calendar_colors,
|
||||
}))
|
||||
}
|
||||
@@ -78,6 +79,9 @@ pub async fn update_preferences(
|
||||
if request.calendar_theme.is_some() {
|
||||
preferences.calendar_theme = request.calendar_theme;
|
||||
}
|
||||
if request.calendar_style.is_some() {
|
||||
preferences.calendar_style = request.calendar_style;
|
||||
}
|
||||
if request.calendar_colors.is_some() {
|
||||
preferences.calendar_colors = request.calendar_colors;
|
||||
}
|
||||
@@ -94,6 +98,7 @@ pub async fn update_preferences(
|
||||
calendar_time_increment: preferences.calendar_time_increment,
|
||||
calendar_view_mode: preferences.calendar_view_mode,
|
||||
calendar_theme: preferences.calendar_theme,
|
||||
calendar_style: preferences.calendar_style,
|
||||
calendar_colors: preferences.calendar_colors,
|
||||
}),
|
||||
))
|
||||
|
||||
@@ -28,6 +28,7 @@ pub struct UserPreferencesResponse {
|
||||
pub calendar_time_increment: Option<i32>,
|
||||
pub calendar_view_mode: Option<String>,
|
||||
pub calendar_theme: Option<String>,
|
||||
pub calendar_style: Option<String>,
|
||||
pub calendar_colors: Option<String>,
|
||||
}
|
||||
|
||||
@@ -37,6 +38,7 @@ pub struct UpdatePreferencesRequest {
|
||||
pub calendar_time_increment: Option<i32>,
|
||||
pub calendar_view_mode: Option<String>,
|
||||
pub calendar_theme: Option<String>,
|
||||
pub calendar_style: Option<String>,
|
||||
pub calendar_colors: Option<String>,
|
||||
}
|
||||
|
||||
|
||||
BIN
calendar.db
BIN
calendar.db
Binary file not shown.
@@ -1,17 +1,16 @@
|
||||
services:
|
||||
calendar-backend:
|
||||
build: .
|
||||
env_file:
|
||||
- .env
|
||||
ports:
|
||||
- "3000:3000"
|
||||
volumes:
|
||||
- ./data/site_dist:/srv/www
|
||||
- ./data/db:/db
|
||||
|
||||
calendar-frontend:
|
||||
image: caddy
|
||||
env_file:
|
||||
- .env
|
||||
environment:
|
||||
- BACKEND_API_URL=http://localhost:3000/api
|
||||
ports:
|
||||
- "80:80"
|
||||
- "443:443"
|
||||
|
||||
@@ -13,6 +13,8 @@ web-sys = { version = "0.3", features = [
|
||||
"HtmlSelectElement",
|
||||
"HtmlInputElement",
|
||||
"HtmlTextAreaElement",
|
||||
"HtmlLinkElement",
|
||||
"HtmlHeadElement",
|
||||
"Event",
|
||||
"MouseEvent",
|
||||
"InputEvent",
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<base data-trunk-public-url />
|
||||
<link data-trunk rel="css" href="styles.css">
|
||||
<link data-trunk rel="copy-file" href="styles/google.css">
|
||||
</head>
|
||||
<body>
|
||||
<script>
|
||||
|
||||
@@ -3,10 +3,12 @@ use crate::components::{
|
||||
EditAction, EventClass, EventContextMenu, EventCreationData, EventStatus, RecurrenceType,
|
||||
ReminderType, RouteHandler, Sidebar, Theme, ViewMode,
|
||||
};
|
||||
use crate::components::sidebar::{Style};
|
||||
use crate::models::ical::VEvent;
|
||||
use crate::services::{calendar_service::UserInfo, CalendarService};
|
||||
use chrono::NaiveDate;
|
||||
use gloo_storage::{LocalStorage, Storage};
|
||||
use wasm_bindgen::JsCast;
|
||||
use web_sys::MouseEvent;
|
||||
use yew::prelude::*;
|
||||
use yew_router::prelude::*;
|
||||
@@ -96,6 +98,16 @@ pub fn App() -> Html {
|
||||
}
|
||||
});
|
||||
|
||||
// Style state - load from localStorage if available
|
||||
let current_style = use_state(|| {
|
||||
// Try to load saved style from localStorage
|
||||
if let Ok(saved_style) = LocalStorage::get::<String>("calendar_style") {
|
||||
Style::from_value(&saved_style)
|
||||
} else {
|
||||
Style::Default // Default style
|
||||
}
|
||||
});
|
||||
|
||||
let available_colors = use_state(|| get_theme_event_colors());
|
||||
|
||||
let on_login = {
|
||||
@@ -152,6 +164,42 @@ pub fn App() -> Html {
|
||||
})
|
||||
};
|
||||
|
||||
let on_style_change = {
|
||||
let current_style = current_style.clone();
|
||||
Callback::from(move |new_style: Style| {
|
||||
// Save style to localStorage
|
||||
let _ = LocalStorage::set("calendar_style", new_style.value());
|
||||
|
||||
// Hot-swap stylesheet
|
||||
if let Some(window) = web_sys::window() {
|
||||
if let Some(document) = window.document() {
|
||||
// Remove existing style link if it exists
|
||||
if let Some(existing_link) = document.get_element_by_id("dynamic-style") {
|
||||
existing_link.remove();
|
||||
}
|
||||
|
||||
// Create and append new stylesheet link only if style has a path
|
||||
if let Some(stylesheet_path) = new_style.stylesheet_path() {
|
||||
if let Ok(link) = document.create_element("link") {
|
||||
let link = link.dyn_into::<web_sys::HtmlLinkElement>().unwrap();
|
||||
link.set_id("dynamic-style");
|
||||
link.set_rel("stylesheet");
|
||||
link.set_href(stylesheet_path);
|
||||
|
||||
if let Some(head) = document.head() {
|
||||
let _ = head.append_child(&link);
|
||||
}
|
||||
}
|
||||
}
|
||||
// If stylesheet_path is None (Default style), just removing the dynamic link is enough
|
||||
}
|
||||
}
|
||||
|
||||
// Update state
|
||||
current_style.set(new_style);
|
||||
})
|
||||
};
|
||||
|
||||
// Apply initial theme on mount
|
||||
{
|
||||
let current_theme = current_theme.clone();
|
||||
@@ -165,6 +213,32 @@ pub fn App() -> Html {
|
||||
});
|
||||
}
|
||||
|
||||
// Apply initial style on mount
|
||||
{
|
||||
let current_style = current_style.clone();
|
||||
use_effect_with((), move |_| {
|
||||
let style = (*current_style).clone();
|
||||
if let Some(window) = web_sys::window() {
|
||||
if let Some(document) = window.document() {
|
||||
// Create and append stylesheet link for initial style only if it has a path
|
||||
if let Some(stylesheet_path) = style.stylesheet_path() {
|
||||
if let Ok(link) = document.create_element("link") {
|
||||
let link = link.dyn_into::<web_sys::HtmlLinkElement>().unwrap();
|
||||
link.set_id("dynamic-style");
|
||||
link.set_rel("stylesheet");
|
||||
link.set_href(stylesheet_path);
|
||||
|
||||
if let Some(head) = document.head() {
|
||||
let _ = head.append_child(&link);
|
||||
}
|
||||
}
|
||||
}
|
||||
// If initial style is Default (None), no additional stylesheet needed
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Fetch user info when token is available
|
||||
{
|
||||
let user_info = user_info.clone();
|
||||
@@ -718,6 +792,8 @@ pub fn App() -> Html {
|
||||
on_view_change={on_view_change}
|
||||
current_theme={(*current_theme).clone()}
|
||||
on_theme_change={on_theme_change}
|
||||
current_style={(*current_style).clone()}
|
||||
on_style_change={on_style_change}
|
||||
/>
|
||||
<main class="app-main">
|
||||
<RouteHandler
|
||||
|
||||
@@ -32,6 +32,12 @@ pub enum Theme {
|
||||
Mint,
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq)]
|
||||
pub enum Style {
|
||||
Default,
|
||||
Google,
|
||||
}
|
||||
|
||||
impl Theme {
|
||||
pub fn value(&self) -> &'static str {
|
||||
match self {
|
||||
@@ -60,6 +66,30 @@ impl Theme {
|
||||
}
|
||||
}
|
||||
|
||||
impl Style {
|
||||
pub fn value(&self) -> &'static str {
|
||||
match self {
|
||||
Style::Default => "default",
|
||||
Style::Google => "google",
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_value(value: &str) -> Self {
|
||||
match value {
|
||||
"google" => Style::Google,
|
||||
_ => Style::Default,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
pub fn stylesheet_path(&self) -> Option<&'static str> {
|
||||
match self {
|
||||
Style::Default => None, // No additional stylesheet needed - uses base styles.css
|
||||
Style::Google => Some("google.css"), // Trunk copies to root level
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for ViewMode {
|
||||
fn default() -> Self {
|
||||
ViewMode::Month
|
||||
@@ -80,6 +110,8 @@ pub struct SidebarProps {
|
||||
pub on_view_change: Callback<ViewMode>,
|
||||
pub current_theme: Theme,
|
||||
pub on_theme_change: Callback<Theme>,
|
||||
pub current_style: Style,
|
||||
pub on_style_change: Callback<Style>,
|
||||
}
|
||||
|
||||
#[function_component(Sidebar)]
|
||||
@@ -111,6 +143,18 @@ pub fn sidebar(props: &SidebarProps) -> Html {
|
||||
})
|
||||
};
|
||||
|
||||
let on_style_change = {
|
||||
let on_style_change = props.on_style_change.clone();
|
||||
Callback::from(move |e: Event| {
|
||||
let target = e.target_dyn_into::<HtmlSelectElement>();
|
||||
if let Some(select) = target {
|
||||
let value = select.value();
|
||||
let new_style = Style::from_value(&value);
|
||||
on_style_change.emit(new_style);
|
||||
}
|
||||
})
|
||||
};
|
||||
|
||||
html! {
|
||||
<aside class="app-sidebar">
|
||||
<div class="sidebar-header">
|
||||
@@ -175,6 +219,7 @@ pub fn sidebar(props: &SidebarProps) -> Html {
|
||||
</div>
|
||||
|
||||
<div class="theme-selector">
|
||||
<label>{"Theme:"}</label>
|
||||
<select class="theme-selector-dropdown" onchange={on_theme_change}>
|
||||
<option value="default" selected={matches!(props.current_theme, Theme::Default)}>{"Default"}</option>
|
||||
<option value="ocean" selected={matches!(props.current_theme, Theme::Ocean)}>{"Ocean"}</option>
|
||||
@@ -187,6 +232,14 @@ pub fn sidebar(props: &SidebarProps) -> Html {
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="style-selector">
|
||||
<label>{"Style:"}</label>
|
||||
<select class="style-selector-dropdown" onchange={on_style_change}>
|
||||
<option value="default" selected={matches!(props.current_style, Style::Default)}>{"Default"}</option>
|
||||
<option value="google" selected={matches!(props.current_style, Style::Google)}>{"Google Calendar"}</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<button onclick={props.on_logout.reform(|_| ())} class="logout-button">{"Logout"}</button>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
@@ -585,11 +585,6 @@ pub fn week_view(props: &WeekViewProps) -> Html {
|
||||
}
|
||||
}).collect::<Html>()
|
||||
}
|
||||
// Final boundary slot to complete the 24-hour visual grid - make it interactive like other slots
|
||||
<div class="time-slot boundary-slot">
|
||||
<div class="time-slot-half"></div>
|
||||
<div class="time-slot-half"></div>
|
||||
</div>
|
||||
|
||||
// Events positioned absolutely based on their actual times
|
||||
<div class="events-container">
|
||||
@@ -1029,8 +1024,13 @@ fn calculate_event_position(event: &VEvent, date: NaiveDate) -> (f32, f32, bool)
|
||||
let local_start = event.dtstart.with_timezone(&Local);
|
||||
let event_date = local_start.date_naive();
|
||||
|
||||
// Only position events that are on this specific date
|
||||
if event_date != date {
|
||||
// Position events based on when they appear in local time, not their original date
|
||||
// For timezone issues: an event created at 10 PM Sunday might be stored as Monday UTC
|
||||
// but should still display on Sunday's column since that's when the user sees it
|
||||
let should_display_here = event_date == date ||
|
||||
(event_date == date - chrono::Duration::days(1) && local_start.hour() >= 20);
|
||||
|
||||
if !should_display_here {
|
||||
return (0.0, 0.0, false); // Event not on this date
|
||||
}
|
||||
|
||||
|
||||
@@ -2,4 +2,3 @@ pub mod calendar_service;
|
||||
pub mod preferences;
|
||||
|
||||
pub use calendar_service::CalendarService;
|
||||
pub use preferences::PreferencesService;
|
||||
|
||||
@@ -15,6 +15,7 @@ pub struct UserPreferences {
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
#[allow(dead_code)]
|
||||
pub struct UpdatePreferencesRequest {
|
||||
pub calendar_selected_date: Option<String>,
|
||||
pub calendar_time_increment: Option<i32>,
|
||||
@@ -23,10 +24,12 @@ pub struct UpdatePreferencesRequest {
|
||||
pub calendar_colors: Option<String>,
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub struct PreferencesService {
|
||||
base_url: String,
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
impl PreferencesService {
|
||||
pub fn new() -> Self {
|
||||
let base_url = option_env!("BACKEND_API_URL")
|
||||
@@ -147,7 +150,7 @@ impl PreferencesService {
|
||||
let session_token = LocalStorage::get::<String>("session_token")
|
||||
.map_err(|_| "No session token found".to_string())?;
|
||||
|
||||
let mut request = UpdatePreferencesRequest {
|
||||
let request = UpdatePreferencesRequest {
|
||||
calendar_selected_date: LocalStorage::get::<String>("calendar_selected_date").ok(),
|
||||
calendar_time_increment: LocalStorage::get::<u32>("calendar_time_increment").ok().map(|i| i as i32),
|
||||
calendar_view_mode: LocalStorage::get::<String>("calendar_view_mode").ok(),
|
||||
|
||||
@@ -1,9 +1,120 @@
|
||||
/* Base Styles - Always Loaded */
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
:root {
|
||||
/* CSS Variables for Style System */
|
||||
--border-radius-small: 4px;
|
||||
--border-radius-medium: 8px;
|
||||
--border-radius-large: 12px;
|
||||
--spacing-xs: 4px;
|
||||
--spacing-sm: 8px;
|
||||
--spacing-md: 16px;
|
||||
--spacing-lg: 24px;
|
||||
--spacing-xl: 32px;
|
||||
--shadow-sm: 0 1px 3px rgba(0,0,0,0.1);
|
||||
--shadow-md: 0 4px 6px rgba(0,0,0,0.1);
|
||||
--shadow-lg: 0 8px 25px rgba(0,0,0,0.15);
|
||||
--border-light: 1px solid #e9ecef;
|
||||
--border-medium: 1px solid #dee2e6;
|
||||
--transition-fast: 0.15s ease;
|
||||
--transition-normal: 0.2s ease;
|
||||
--transition-slow: 0.3s ease;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||
background-color: #f8f9fa;
|
||||
color: #333;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.app {
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.login-layout {
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* Base Layout */
|
||||
.main-content {
|
||||
flex: 1;
|
||||
margin-left: 280px;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
/* Basic Form Elements */
|
||||
input, select, textarea, button {
|
||||
font-family: inherit;
|
||||
}
|
||||
|
||||
/* Utility Classes */
|
||||
.loading {
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.error {
|
||||
color: #dc3545;
|
||||
}
|
||||
|
||||
.success {
|
||||
color: #28a745;
|
||||
}
|
||||
|
||||
/* Theme Data Attributes for Color Schemes */
|
||||
[data-theme="default"] {
|
||||
--primary-color: #667eea;
|
||||
--primary-gradient: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
}
|
||||
|
||||
[data-theme="ocean"] {
|
||||
--primary-color: #006994;
|
||||
--primary-gradient: linear-gradient(135deg, #006994 0%, #0891b2 100%);
|
||||
}
|
||||
|
||||
[data-theme="forest"] {
|
||||
--primary-color: #065f46;
|
||||
--primary-gradient: linear-gradient(135deg, #065f46 0%, #047857 100%);
|
||||
}
|
||||
|
||||
[data-theme="sunset"] {
|
||||
--primary-color: #ea580c;
|
||||
--primary-gradient: linear-gradient(135deg, #ea580c 0%, #dc2626 100%);
|
||||
}
|
||||
|
||||
[data-theme="purple"] {
|
||||
--primary-color: #7c3aed;
|
||||
--primary-gradient: linear-gradient(135deg, #7c3aed 0%, #a855f7 100%);
|
||||
}
|
||||
|
||||
[data-theme="dark"] {
|
||||
--primary-color: #374151;
|
||||
--primary-gradient: linear-gradient(135deg, #374151 0%, #1f2937 100%);
|
||||
}
|
||||
|
||||
[data-theme="rose"] {
|
||||
--primary-color: #e11d48;
|
||||
--primary-gradient: linear-gradient(135deg, #e11d48 0%, #f43f5e 100%);
|
||||
}
|
||||
|
||||
[data-theme="mint"] {
|
||||
--primary-color: #10b981;
|
||||
--primary-gradient: linear-gradient(135deg, #10b981 0%, #059669 100%);
|
||||
}* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||
background-color: #f8f9fa;
|
||||
@@ -574,12 +685,13 @@ body {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
min-height: 0; /* Allow flex item to shrink below content size */
|
||||
}
|
||||
|
||||
.time-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 80px 1fr;
|
||||
min-height: 100%;
|
||||
min-height: 1530px;
|
||||
}
|
||||
|
||||
/* Time Labels */
|
||||
@@ -589,6 +701,7 @@ body {
|
||||
position: sticky;
|
||||
left: 0;
|
||||
z-index: 5;
|
||||
min-height: 1440px; /* Match the time slots height */
|
||||
}
|
||||
|
||||
.time-label {
|
||||
@@ -614,12 +727,13 @@ body {
|
||||
.week-days-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(7, 1fr);
|
||||
min-height: 1440px; /* Ensure grid is tall enough for 24 time slots */
|
||||
}
|
||||
|
||||
.week-day-column {
|
||||
position: relative;
|
||||
border-right: 1px solid var(--time-label-border, #e9ecef);
|
||||
min-height: 1500px; /* 25 time labels × 60px = 1500px total */
|
||||
min-height: 1440px; /* 24 time slots × 60px = 1440px total */
|
||||
}
|
||||
|
||||
.week-day-column:last-child {
|
||||
@@ -3014,6 +3128,50 @@ body {
|
||||
padding: 0.5rem;
|
||||
}
|
||||
|
||||
/* Style Selector Styles */
|
||||
.style-selector {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.style-selector label {
|
||||
display: block;
|
||||
margin-bottom: 0.5rem;
|
||||
font-size: 0.8rem;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
color: rgba(255, 255, 255, 0.8);
|
||||
}
|
||||
|
||||
.style-selector-dropdown {
|
||||
width: 100%;
|
||||
padding: 0.5rem;
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
border: 1px solid rgba(255, 255, 255, 0.2);
|
||||
border-radius: 6px;
|
||||
color: white;
|
||||
font-size: 0.85rem;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.style-selector-dropdown:hover {
|
||||
background: rgba(255, 255, 255, 0.15);
|
||||
border-color: rgba(255, 255, 255, 0.3);
|
||||
}
|
||||
|
||||
.style-selector-dropdown:focus {
|
||||
outline: none;
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
border-color: rgba(255, 255, 255, 0.4);
|
||||
}
|
||||
|
||||
.style-selector-dropdown option {
|
||||
background: #333;
|
||||
color: white;
|
||||
padding: 0.5rem;
|
||||
}
|
||||
|
||||
/* Theme Definitions */
|
||||
:root {
|
||||
/* Default Theme */
|
||||
|
||||
3501
frontend/styles.css.backup
Normal file
3501
frontend/styles.css.backup
Normal file
File diff suppressed because it is too large
Load Diff
51
frontend/styles/base.css
Normal file
51
frontend/styles/base.css
Normal file
@@ -0,0 +1,51 @@
|
||||
/* Base Styles - Always Loaded */
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||
background-color: #f8f9fa;
|
||||
color: #333;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.app {
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.login-layout {
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* Base Layout */
|
||||
.main-content {
|
||||
flex: 1;
|
||||
margin-left: 280px;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
/* Basic Form Elements */
|
||||
input, select, textarea, button {
|
||||
font-family: inherit;
|
||||
}
|
||||
|
||||
/* Utility Classes */
|
||||
.loading {
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.error {
|
||||
color: #dc3545;
|
||||
}
|
||||
|
||||
.success {
|
||||
color: #28a745;
|
||||
}
|
||||
3501
frontend/styles/default.css
Normal file
3501
frontend/styles/default.css
Normal file
File diff suppressed because it is too large
Load Diff
645
frontend/styles/google.css
Normal file
645
frontend/styles/google.css
Normal file
@@ -0,0 +1,645 @@
|
||||
/* Google Calendar-inspired styles */
|
||||
|
||||
/* Override CSS Variables for Google Calendar Style */
|
||||
:root {
|
||||
/* Google-style spacing */
|
||||
--spacing-xs: 2px;
|
||||
--spacing-sm: 4px;
|
||||
--spacing-md: 8px;
|
||||
--spacing-lg: 12px;
|
||||
--spacing-xl: 16px;
|
||||
|
||||
/* Google-style borders and radius */
|
||||
--border-radius-small: 2px;
|
||||
--border-radius-medium: 4px;
|
||||
--border-radius-large: 8px;
|
||||
--border-light: 1px solid #dadce0;
|
||||
--border-medium: 1px solid #dadce0;
|
||||
|
||||
/* Google-style shadows */
|
||||
--shadow-sm: 0 1px 2px 0 rgba(60,64,67,.3), 0 1px 3px 1px rgba(60,64,67,.15);
|
||||
--shadow-md: 0 1px 3px 0 rgba(60,64,67,.3), 0 4px 8px 3px rgba(60,64,67,.15);
|
||||
--shadow-lg: 0 4px 6px 0 rgba(60,64,67,.3), 0 8px 25px 5px rgba(60,64,67,.15);
|
||||
}
|
||||
|
||||
/* Google-style sidebar - override all theme variants */
|
||||
body .app-sidebar,
|
||||
[data-theme] .app-sidebar,
|
||||
[data-theme="default"] .app-sidebar,
|
||||
[data-theme="ocean"] .app-sidebar,
|
||||
[data-theme="forest"] .app-sidebar,
|
||||
[data-theme="sunset"] .app-sidebar,
|
||||
[data-theme="purple"] .app-sidebar,
|
||||
[data-theme="dark"] .app-sidebar,
|
||||
[data-theme="rose"] .app-sidebar,
|
||||
[data-theme="mint"] .app-sidebar {
|
||||
background: #ffffff !important;
|
||||
border-right: 1px solid #dadce0 !important;
|
||||
font-family: 'Google Sans', 'Roboto', sans-serif !important;
|
||||
box-shadow: 2px 0 8px rgba(60,64,67,.1) !important;
|
||||
}
|
||||
|
||||
body .sidebar-header,
|
||||
[data-theme] .sidebar-header {
|
||||
background: transparent !important;
|
||||
border-bottom: 1px solid #dadce0 !important;
|
||||
}
|
||||
|
||||
body .sidebar-header h1,
|
||||
[data-theme] .sidebar-header h1 {
|
||||
font-size: 20px !important;
|
||||
font-weight: 500 !important;
|
||||
color: #3c4043 !important;
|
||||
font-family: 'Google Sans', 'Roboto', sans-serif !important;
|
||||
}
|
||||
|
||||
body .user-info,
|
||||
[data-theme] .user-info {
|
||||
color: #3c4043 !important;
|
||||
}
|
||||
|
||||
body .user-info .username,
|
||||
[data-theme] .user-info .username {
|
||||
font-weight: 500 !important;
|
||||
color: #3c4043 !important;
|
||||
}
|
||||
|
||||
body .user-info .server-url,
|
||||
[data-theme] .user-info .server-url {
|
||||
color: #5f6368 !important;
|
||||
}
|
||||
|
||||
/* Google-style buttons */
|
||||
.create-calendar-button {
|
||||
background: #1a73e8 !important;
|
||||
color: white !important;
|
||||
border: none !important;
|
||||
border-radius: 4px !important;
|
||||
padding: 8px 16px !important;
|
||||
font-weight: 500 !important;
|
||||
font-size: 14px !important;
|
||||
cursor: pointer !important;
|
||||
box-shadow: 0 1px 2px 0 rgba(60,64,67,.3), 0 1px 3px 1px rgba(60,64,67,.15) !important;
|
||||
transition: box-shadow 0.2s ease !important;
|
||||
}
|
||||
|
||||
.create-calendar-button:hover {
|
||||
box-shadow: 0 1px 3px 0 rgba(60,64,67,.3), 0 4px 8px 3px rgba(60,64,67,.15) !important;
|
||||
}
|
||||
|
||||
.logout-button {
|
||||
background: transparent !important;
|
||||
color: #1a73e8 !important;
|
||||
border: 1px solid #dadce0 !important;
|
||||
border-radius: 4px !important;
|
||||
padding: 8px 16px !important;
|
||||
font-weight: 500 !important;
|
||||
font-size: 14px !important;
|
||||
cursor: pointer !important;
|
||||
transition: background-color 0.2s ease !important;
|
||||
}
|
||||
|
||||
.logout-button:hover {
|
||||
background: #f8f9fa !important;
|
||||
}
|
||||
|
||||
/* Google-style navigation and sidebar text */
|
||||
body .sidebar-nav .nav-link,
|
||||
[data-theme] .sidebar-nav .nav-link {
|
||||
color: #3c4043 !important;
|
||||
text-decoration: none !important;
|
||||
}
|
||||
|
||||
body .sidebar-nav .nav-link:hover,
|
||||
[data-theme] .sidebar-nav .nav-link:hover {
|
||||
color: #1a73e8 !important;
|
||||
background: #f1f3f4 !important;
|
||||
}
|
||||
|
||||
/* Calendar list styling */
|
||||
body .calendar-list h3,
|
||||
[data-theme] .calendar-list h3 {
|
||||
color: #3c4043 !important;
|
||||
}
|
||||
|
||||
body .calendar-list .calendar-name,
|
||||
[data-theme] .calendar-list .calendar-name {
|
||||
color: #3c4043 !important;
|
||||
}
|
||||
|
||||
body .no-calendars,
|
||||
[data-theme] .no-calendars {
|
||||
color: #5f6368 !important;
|
||||
}
|
||||
|
||||
/* Form labels and text */
|
||||
body .sidebar-footer label,
|
||||
[data-theme] .sidebar-footer label,
|
||||
body .view-selector label,
|
||||
[data-theme] .view-selector label,
|
||||
body .theme-selector label,
|
||||
[data-theme] .theme-selector label,
|
||||
body .style-selector label,
|
||||
[data-theme] .style-selector label {
|
||||
color: #3c4043 !important;
|
||||
}
|
||||
|
||||
/* Google-style selectors */
|
||||
body .view-selector-dropdown,
|
||||
body .theme-selector-dropdown,
|
||||
body .style-selector-dropdown,
|
||||
[data-theme] .view-selector-dropdown,
|
||||
[data-theme] .theme-selector-dropdown,
|
||||
[data-theme] .style-selector-dropdown {
|
||||
border: 1px solid #dadce0 !important;
|
||||
border-radius: 4px !important;
|
||||
padding: 8px !important;
|
||||
font-size: 14px !important;
|
||||
color: #3c4043 !important;
|
||||
background: white !important;
|
||||
font-family: inherit !important;
|
||||
}
|
||||
|
||||
.view-selector-dropdown:focus,
|
||||
.theme-selector-dropdown:focus,
|
||||
.style-selector-dropdown:focus {
|
||||
outline: none;
|
||||
border-color: #1a73e8;
|
||||
box-shadow: 0 0 0 2px rgba(26,115,232,.2);
|
||||
}
|
||||
|
||||
/* Google-style calendar list items */
|
||||
.calendar-list h3 {
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: #3c4043;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.calendar-list ul {
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.calendar-list .calendar-item {
|
||||
padding: 4px 0;
|
||||
border-radius: 4px;
|
||||
transition: background-color 0.15s ease;
|
||||
}
|
||||
|
||||
.calendar-list .calendar-item:hover {
|
||||
background-color: #f1f3f4;
|
||||
}
|
||||
|
||||
.calendar-list .calendar-name {
|
||||
color: #3c4043;
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
/* Google-style main content area */
|
||||
body .app-main,
|
||||
[data-theme] .app-main {
|
||||
background: #ffffff !important;
|
||||
font-family: 'Google Sans', 'Roboto', sans-serif !important;
|
||||
color: #3c4043 !important;
|
||||
}
|
||||
|
||||
/* Calendar header elements */
|
||||
body .calendar-header,
|
||||
[data-theme] .calendar-header {
|
||||
background: #f8f9fa !important;
|
||||
color: #3c4043 !important;
|
||||
}
|
||||
|
||||
body .calendar-header h2,
|
||||
body .calendar-header h3,
|
||||
body .month-header,
|
||||
body .week-header,
|
||||
[data-theme] .calendar-header h2,
|
||||
[data-theme] .calendar-header h3,
|
||||
[data-theme] .month-header,
|
||||
[data-theme] .week-header {
|
||||
color: #3c4043 !important;
|
||||
font-family: 'Google Sans', 'Roboto', sans-serif !important;
|
||||
}
|
||||
|
||||
/* Month name and title - aggressive override */
|
||||
body h1,
|
||||
body h2,
|
||||
body h3,
|
||||
body .month-title,
|
||||
body .calendar-title,
|
||||
body .current-month,
|
||||
body .month-year,
|
||||
body .header-title,
|
||||
[data-theme] h1,
|
||||
[data-theme] h2,
|
||||
[data-theme] h3,
|
||||
[data-theme] .month-title,
|
||||
[data-theme] .calendar-title,
|
||||
[data-theme] .current-month,
|
||||
[data-theme] .month-year,
|
||||
[data-theme] .header-title {
|
||||
color: #3c4043 !important;
|
||||
font-family: 'Google Sans', 'Roboto', sans-serif !important;
|
||||
}
|
||||
|
||||
/* Navigation arrows and buttons - aggressive override */
|
||||
body button,
|
||||
body .nav-button,
|
||||
body .calendar-nav-button,
|
||||
body .prev-button,
|
||||
body .next-button,
|
||||
body .arrow-button,
|
||||
body .navigation-arrow,
|
||||
body [class*="arrow"],
|
||||
body [class*="nav"],
|
||||
body [class*="button"],
|
||||
[data-theme] button,
|
||||
[data-theme] .nav-button,
|
||||
[data-theme] .calendar-nav-button,
|
||||
[data-theme] .prev-button,
|
||||
[data-theme] .next-button,
|
||||
[data-theme] .arrow-button,
|
||||
[data-theme] .navigation-arrow,
|
||||
[data-theme] [class*="arrow"],
|
||||
[data-theme] [class*="nav"],
|
||||
[data-theme] [class*="button"] {
|
||||
color: #3c4043 !important;
|
||||
background: #f8f9fa !important;
|
||||
border: 1px solid #dadce0 !important;
|
||||
border-radius: 4px !important;
|
||||
}
|
||||
|
||||
body button:hover,
|
||||
body .nav-button:hover,
|
||||
body .calendar-nav-button:hover,
|
||||
body .prev-button:hover,
|
||||
body .next-button:hover,
|
||||
body .arrow-button:hover,
|
||||
[data-theme] button:hover,
|
||||
[data-theme] .nav-button:hover,
|
||||
[data-theme] .calendar-nav-button:hover,
|
||||
[data-theme] .prev-button:hover,
|
||||
[data-theme] .next-button:hover,
|
||||
[data-theme] .arrow-button:hover {
|
||||
background: #e8f0fe !important;
|
||||
color: #1a73e8 !important;
|
||||
border-color: #1a73e8 !important;
|
||||
}
|
||||
|
||||
/* Calendar controls and date display */
|
||||
body .calendar-controls,
|
||||
body .current-date,
|
||||
body .date-display,
|
||||
[data-theme] .calendar-controls,
|
||||
[data-theme] .current-date,
|
||||
[data-theme] .date-display {
|
||||
color: #3c4043 !important;
|
||||
}
|
||||
|
||||
/* Ultimate nuclear approach - override EVERYTHING */
|
||||
html body .app-main,
|
||||
html body .app-main *,
|
||||
html body .main-content,
|
||||
html body .main-content *,
|
||||
html body .calendar-container,
|
||||
html body .calendar-container *,
|
||||
html [data-theme] .app-main,
|
||||
html [data-theme] .app-main *,
|
||||
html [data-theme] .main-content,
|
||||
html [data-theme] .main-content *,
|
||||
html [data-theme] .calendar-container,
|
||||
html [data-theme] .calendar-container *,
|
||||
html [data-theme="default"] .app-main *,
|
||||
html [data-theme="ocean"] .app-main *,
|
||||
html [data-theme="forest"] .app-main *,
|
||||
html [data-theme="sunset"] .app-main *,
|
||||
html [data-theme="purple"] .app-main *,
|
||||
html [data-theme="dark"] .app-main *,
|
||||
html [data-theme="rose"] .app-main *,
|
||||
html [data-theme="mint"] .app-main * {
|
||||
color: #3c4043 !important;
|
||||
text-shadow: none !important;
|
||||
}
|
||||
|
||||
/* Force all text elements */
|
||||
html body .app-main h1,
|
||||
html body .app-main h2,
|
||||
html body .app-main h3,
|
||||
html body .app-main h4,
|
||||
html body .app-main h5,
|
||||
html body .app-main h6,
|
||||
html body .app-main p,
|
||||
html body .app-main span,
|
||||
html body .app-main div,
|
||||
html body .app-main button,
|
||||
html [data-theme] .app-main h1,
|
||||
html [data-theme] .app-main h2,
|
||||
html [data-theme] .app-main h3,
|
||||
html [data-theme] .app-main h4,
|
||||
html [data-theme] .app-main h5,
|
||||
html [data-theme] .app-main h6,
|
||||
html [data-theme] .app-main p,
|
||||
html [data-theme] .app-main span,
|
||||
html [data-theme] .app-main div,
|
||||
html [data-theme] .app-main button {
|
||||
color: #3c4043 !important;
|
||||
}
|
||||
|
||||
/* Exception for buttons - make them stand out */
|
||||
body .app-main button,
|
||||
body .main-content button,
|
||||
[data-theme] .app-main button,
|
||||
[data-theme] .main-content button {
|
||||
color: #3c4043 !important;
|
||||
background: #f8f9fa !important;
|
||||
border: 1px solid #dadce0 !important;
|
||||
}
|
||||
|
||||
/* Google-style calendar grid - more aggressive styling */
|
||||
html body .calendar-grid,
|
||||
html [data-theme] .calendar-grid,
|
||||
body .calendar-container,
|
||||
[data-theme] .calendar-container {
|
||||
border: 1px solid #dadce0 !important;
|
||||
border-radius: 8px !important;
|
||||
overflow: hidden !important;
|
||||
background: white !important;
|
||||
box-shadow: 0 1px 3px 0 rgba(60,64,67,.3), 0 4px 8px 3px rgba(60,64,67,.15) !important;
|
||||
font-family: 'Google Sans', 'Roboto', sans-serif !important;
|
||||
}
|
||||
|
||||
html body .calendar-header,
|
||||
html [data-theme] .calendar-header {
|
||||
background: #f8f9fa !important;
|
||||
border-bottom: 1px solid #dadce0 !important;
|
||||
padding: 16px !important;
|
||||
}
|
||||
|
||||
html body .month-header,
|
||||
html body .week-header,
|
||||
html [data-theme] .month-header,
|
||||
html [data-theme] .week-header {
|
||||
font-size: 22px !important;
|
||||
font-weight: 400 !important;
|
||||
color: #3c4043 !important;
|
||||
font-family: 'Google Sans', 'Roboto', sans-serif !important;
|
||||
}
|
||||
|
||||
/* Google-style calendar cells - complete overhaul */
|
||||
html body .calendar-day,
|
||||
html [data-theme] .calendar-day,
|
||||
body .day-cell,
|
||||
[data-theme] .day-cell {
|
||||
border: 1px solid #e8eaed !important;
|
||||
background: white !important;
|
||||
transition: background-color 0.15s ease !important;
|
||||
padding: 8px !important;
|
||||
min-height: 120px !important;
|
||||
position: relative !important;
|
||||
}
|
||||
|
||||
html body .calendar-day:hover,
|
||||
html [data-theme] .calendar-day:hover,
|
||||
body .day-cell:hover,
|
||||
[data-theme] .day-cell:hover {
|
||||
background: #f8f9fa !important;
|
||||
box-shadow: inset 0 0 0 1px #dadce0 !important;
|
||||
}
|
||||
|
||||
html body .calendar-day.today,
|
||||
html [data-theme] .calendar-day.today,
|
||||
body .day-cell.today,
|
||||
[data-theme] .day-cell.today {
|
||||
background: #e8f0fe !important;
|
||||
border-color: #1a73e8 !important;
|
||||
}
|
||||
|
||||
html body .calendar-day.other-month,
|
||||
html [data-theme] .calendar-day.other-month,
|
||||
body .day-cell.other-month,
|
||||
[data-theme] .day-cell.other-month {
|
||||
background: #fafafa !important;
|
||||
color: #9aa0a6 !important;
|
||||
}
|
||||
|
||||
html body .day-number,
|
||||
html [data-theme] .day-number,
|
||||
body .date-number,
|
||||
[data-theme] .date-number {
|
||||
font-size: 14px !important;
|
||||
font-weight: 500 !important;
|
||||
color: #3c4043 !important;
|
||||
margin-bottom: 4px !important;
|
||||
font-family: 'Google Sans', 'Roboto', sans-serif !important;
|
||||
}
|
||||
|
||||
/* Day headers (Mon, Tue, Wed, etc.) */
|
||||
html body .day-header,
|
||||
html [data-theme] .day-header,
|
||||
body .weekday-header,
|
||||
[data-theme] .weekday-header {
|
||||
background: #f8f9fa !important;
|
||||
color: #5f6368 !important;
|
||||
font-size: 12px !important;
|
||||
font-weight: 500 !important;
|
||||
text-transform: uppercase !important;
|
||||
letter-spacing: 0.8px !important;
|
||||
padding: 8px !important;
|
||||
border-bottom: 1px solid #dadce0 !important;
|
||||
font-family: 'Google Sans', 'Roboto', sans-serif !important;
|
||||
}
|
||||
|
||||
/* Google Calendar-style events - complete redesign */
|
||||
html body .app-main .event,
|
||||
html [data-theme] .app-main .event,
|
||||
html body .calendar-container .event,
|
||||
html [data-theme] .calendar-container .event,
|
||||
body .event,
|
||||
[data-theme] .event {
|
||||
border-radius: 4px !important;
|
||||
padding: 2px 8px !important;
|
||||
font-size: 11px !important;
|
||||
font-weight: 400 !important;
|
||||
margin: 1px 0 2px 0 !important;
|
||||
cursor: pointer !important;
|
||||
border: none !important;
|
||||
color: white !important;
|
||||
font-family: 'Google Sans', 'Roboto', sans-serif !important;
|
||||
box-shadow: 0 1px 3px rgba(60,64,67,.3) !important;
|
||||
transition: transform 0.1s ease, box-shadow 0.1s ease !important;
|
||||
display: block !important;
|
||||
text-overflow: ellipsis !important;
|
||||
overflow: hidden !important;
|
||||
white-space: nowrap !important;
|
||||
line-height: 1.3 !important;
|
||||
}
|
||||
|
||||
html body .app-main .event *,
|
||||
html [data-theme] .app-main .event *,
|
||||
html body .calendar-container .event *,
|
||||
html [data-theme] .calendar-container .event *,
|
||||
body .event *,
|
||||
[data-theme] .event * {
|
||||
color: white !important;
|
||||
font-family: inherit !important;
|
||||
}
|
||||
|
||||
html body .app-main .event:hover,
|
||||
html [data-theme] .app-main .event:hover,
|
||||
body .event:hover,
|
||||
[data-theme] .event:hover {
|
||||
transform: translateY(-1px) !important;
|
||||
box-shadow: 0 2px 8px rgba(60,64,67,.4) !important;
|
||||
}
|
||||
|
||||
/* All-day events styling */
|
||||
html body .event.all-day,
|
||||
html [data-theme] .event.all-day {
|
||||
border-radius: 12px !important;
|
||||
padding: 4px 12px !important;
|
||||
font-weight: 500 !important;
|
||||
margin: 2px 0 !important;
|
||||
}
|
||||
|
||||
/* Event time display */
|
||||
html body .event-time,
|
||||
html [data-theme] .event-time {
|
||||
opacity: 0.9 !important;
|
||||
font-size: 10px !important;
|
||||
margin-right: 4px !important;
|
||||
}
|
||||
|
||||
/* Week view events */
|
||||
html body .week-view .event,
|
||||
html [data-theme] .week-view .event {
|
||||
border-left: 3px solid rgba(255,255,255,0.8) !important;
|
||||
border-radius: 0 4px 4px 0 !important;
|
||||
padding-left: 6px !important;
|
||||
}
|
||||
|
||||
/* Calendar table structure */
|
||||
html body .calendar-table,
|
||||
html [data-theme] .calendar-table,
|
||||
body table,
|
||||
[data-theme] table {
|
||||
border-collapse: separate !important;
|
||||
border-spacing: 0 !important;
|
||||
width: 100% !important;
|
||||
background: white !important;
|
||||
}
|
||||
|
||||
html body .calendar-table td,
|
||||
html [data-theme] .calendar-table td,
|
||||
body table td,
|
||||
[data-theme] table td {
|
||||
vertical-align: top !important;
|
||||
border: 1px solid #e8eaed !important;
|
||||
background: white !important;
|
||||
}
|
||||
|
||||
/* Month/Week view toggle */
|
||||
html body .view-toggle,
|
||||
html [data-theme] .view-toggle {
|
||||
display: flex !important;
|
||||
gap: 4px !important;
|
||||
background: #f1f3f4 !important;
|
||||
border-radius: 6px !important;
|
||||
padding: 2px !important;
|
||||
}
|
||||
|
||||
html body .view-toggle button,
|
||||
html [data-theme] .view-toggle button {
|
||||
padding: 6px 12px !important;
|
||||
border: none !important;
|
||||
background: transparent !important;
|
||||
color: #5f6368 !important;
|
||||
border-radius: 4px !important;
|
||||
font-size: 13px !important;
|
||||
font-weight: 500 !important;
|
||||
cursor: pointer !important;
|
||||
transition: all 0.15s ease !important;
|
||||
}
|
||||
|
||||
html body .view-toggle button.active,
|
||||
html [data-theme] .view-toggle button.active {
|
||||
background: white !important;
|
||||
color: #1a73e8 !important;
|
||||
box-shadow: 0 1px 3px rgba(60,64,67,.3) !important;
|
||||
}
|
||||
|
||||
/* Today button */
|
||||
html body .today-button,
|
||||
html [data-theme] .today-button {
|
||||
background: white !important;
|
||||
border: 1px solid #dadce0 !important;
|
||||
color: #1a73e8 !important;
|
||||
padding: 8px 16px !important;
|
||||
border-radius: 4px !important;
|
||||
font-weight: 500 !important;
|
||||
font-size: 14px !important;
|
||||
cursor: pointer !important;
|
||||
transition: all 0.15s ease !important;
|
||||
}
|
||||
|
||||
html body .today-button:hover,
|
||||
html [data-theme] .today-button:hover {
|
||||
background: #f8f9fa !important;
|
||||
border-color: #1a73e8 !important;
|
||||
}
|
||||
|
||||
/* Google-style modals */
|
||||
.modal-overlay {
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
box-shadow: var(--shadow-lg);
|
||||
border: none;
|
||||
}
|
||||
|
||||
.modal h2 {
|
||||
font-size: 20px;
|
||||
font-weight: 500;
|
||||
color: #3c4043;
|
||||
font-family: 'Google Sans', sans-serif;
|
||||
}
|
||||
|
||||
/* Google-style form inputs */
|
||||
input[type="text"],
|
||||
input[type="email"],
|
||||
input[type="password"],
|
||||
input[type="url"],
|
||||
input[type="date"],
|
||||
input[type="time"],
|
||||
textarea,
|
||||
select {
|
||||
border: 1px solid #dadce0;
|
||||
border-radius: 4px;
|
||||
padding: 8px 12px;
|
||||
font-size: 14px;
|
||||
color: #3c4043;
|
||||
background: white;
|
||||
font-family: inherit;
|
||||
transition: border-color 0.15s ease, box-shadow 0.15s ease;
|
||||
}
|
||||
|
||||
input:focus,
|
||||
textarea:focus,
|
||||
select:focus {
|
||||
outline: none;
|
||||
border-color: #1a73e8;
|
||||
box-shadow: 0 0 0 2px rgba(26,115,232,.2);
|
||||
}
|
||||
|
||||
/* Google-style labels */
|
||||
label {
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: #3c4043;
|
||||
margin-bottom: 4px;
|
||||
display: block;
|
||||
}
|
||||
Reference in New Issue
Block a user