Sooplive Calendar Viewer

Sooplive 즐겨찾기 그룹의 통합 일정을 캘린더 형태로 표시하는 스크립트

// ==UserScript==
// @name         Sooplive Calendar Viewer
// @namespace    https://sooplive-calendar-viewer.local
// @version      2.7
// @description  Sooplive 즐겨찾기 그룹의 통합 일정을 캘린더 형태로 표시하는 스크립트
// @author       지창연구소
// @match        https://www.sooplive.co.kr/*
// @grant        GM_xmlhttpRequest
// @connect      api-channel.sooplive.co.kr
// @connect      myapi.sooplive.co.kr
// ==/UserScript==

(function() {
    'use strict';

    // 전역 변수
    let anchorDate = new Date();
    let currentUrl = window.location.href;
    let fixedStreamer = null; // 고정된 멤버 정보 {id, nickname}
    let clickedDay = null; // 클릭한 날짜 컬럼
    
    // 상수 정의 (하드코딩 제거)
    const TIMING = {
        SCROLL_COMPLETE_WAIT: 600,
        INDICATOR_SHOW_DELAY: 700,
        INDICATOR_FADE_OUT: 200,
        INDICATOR_FADE_IN: 10,
        INIT_RETRY_DELAY: 2000,
        URL_CHECK_INTERVAL: 1000,
        SPA_NAVIGATION_DELAY: 100
    };
    
        const LAYOUT = {
            SCROLL_MARGIN: 10,
            INDICATOR_BOTTOM_OFFSET: 15,
            SCROLL_POSITION_OFFSET: 5,
            VISIBLE_AREA_PADDING: 50, // 보이는 영역의 패딩 (헤더 높이 고려)
            OPTIMAL_POSITION_OFFSET: 30 // 최적 위치 오프셋
        };
    
    // 화살표 관련 유틸리티 함수들
    function createScrollIndicator(position, titleText) {
        const indicator = document.createElement('div');
        indicator.className = `scroll-indicator scroll-indicator-${position}`;
        
        // 위쪽/아래쪽 화살표 표시
        indicator.innerHTML = position === 'top' ? '↑' : '↓';
        
        indicator.title = titleText;
        indicator.style.opacity = '0';
        
        return indicator;
    }
    
    // 모든 강조 효과 및 화살표 제거
    function clearAllHighlights() {
        // 모든 강조 효과 제거
        const allEventItems = document.querySelectorAll('.event-item');
        allEventItems.forEach(eventItem => {
            eventItem.classList.remove('highlight', 'fade');
        });
        
        // 빈 날짜 강조 효과 제거
        const allDays = document.querySelectorAll('.calendar-day');
        allDays.forEach(day => {
            day.classList.remove('empty-day');
            
            // 스크롤 인디케이터 제거 (더 확실하게)
            const indicators = day.querySelectorAll('.scroll-indicator');
            indicators.forEach(indicator => {
                indicator.remove();
            });
        });
    }
    
    function removeScrollIndicator(container) {
        const existingIndicator = container.querySelector('.scroll-indicator');
        if (existingIndicator) {
            existingIndicator.style.opacity = '0';
            setTimeout(() => {
                if (existingIndicator.parentNode) {
                    existingIndicator.remove();
                }
            }, TIMING.INDICATOR_FADE_OUT);
        }
    }
    
    function showScrollIndicator(indicator, container, position) {
        container.style.position = 'relative';
        container.appendChild(indicator);
        
        // 부드럽게 나타나기
        setTimeout(() => {
            indicator.classList.add('show');
        }, 10);
    }

    // 테마 감지 함수 (Sooplive 실제 클래스 기반)
    function detectTheme() {
        const urlParams = new URLSearchParams(window.location.search);
        const themeColor = urlParams.get('theme_color');
        const htmlElement = document.documentElement;
        const bodyElement = document.body;
        
        // Sooplive의 실제 다크 테마 감지
        const isDarkTheme = 
            themeColor === 'dark' ||
            bodyElement.classList.contains('thema_dark') ||
            htmlElement.getAttribute('dark') === 'true' ||
            htmlElement.classList.contains('dark') ||
            bodyElement.classList.contains('dark') ||
            htmlElement.getAttribute('data-theme') === 'dark' ||
            window.matchMedia('(prefers-color-scheme: dark)').matches;
            
        return isDarkTheme ? 'dark' : 'light';
    }

    // CSS 스타일 추가 (테마 자동 감지)
    const style = document.createElement('style');
    style.textContent = `
        #sooplive-calendar {
            font-family: inherit;
            border-radius: 12px;
            transition: all 0.3s ease;
            margin: 24px 0;
            padding: 24px;
        }
        
        /* 라이트 테마 */
        #sooplive-calendar {
            background: #fff;
            border: 1px solid #e1e5e9;
            box-shadow: 0 2px 8px rgba(0,0,0,0.06);
        }
        
        .calendar-header {
            display: flex;
            justify-content: space-between;
            align-items: center;
            margin-bottom: 20px;
            padding-bottom: 15px;
            border-bottom: 1px solid #e1e5e9;
        }
        
        .calendar-header h3 {
            margin: 0;
            color: #1a1a1a;
            font-size: 20px;
            font-weight: 600;
        }
        
        .week-navigation {
            display: flex;
            align-items: center;
            gap: 12px;
        }
        
        
        .week-navigation button {
            background: #6366f1;
            color: white;
            border: none;
            padding: 8px 16px;
            border-radius: 8px;
            cursor: pointer;
            font-size: 14px;
            font-weight: 500;
            transition: all 0.2s ease;
        }
        
        .week-navigation button:hover {
            background: #4f46e5;
            transform: translateY(-1px);
        }
        
        .week-range {
            font-weight: 500;
            color: #374151;
            font-size: 15px;
        }
        
        .calendar-grid {
            display: grid;
            grid-template-columns: repeat(7, 1fr);
            gap: 1px;
            background: #f3f4f6;
            border: 1px solid #e1e5e9;
            border-radius: 12px;
            overflow: hidden;
        }
        
        .calendar-day {
            background: #fff;
            min-height: 140px;
        }
        
        .day-header {
            background: #f9fafb;
            padding: 12px 8px;
            text-align: center;
            border-bottom: 1px solid #e1e5e9;
        }
        
        .day-name {
            font-size: 12px;
            color: #6b7280;
            font-weight: 600;
            text-transform: uppercase;
            letter-spacing: 0.5px;
        }
        
        .day-number {
            font-size: 17px;
            color: #1a1a1a;
            font-weight: 600;
            margin-top: 4px;
        }
        
        .event-count {
            font-size: 10px;
            color: #6366f1;
            font-weight: 600;
            margin-top: 2px;
            background: #f0f9ff;
            border-radius: 8px;
            padding: 2px 6px;
            display: inline-block;
        }
        
        .calendar-credit {
            text-align: right;
            margin-top: 12px;
            padding-top: 8px;
            border-top: 1px solid #e1e5e9;
            font-size: 11px;
            color: #9ca3af;
        }
        
        .credit-name {
            color: #6366f1;
            font-weight: 600;
        }
        
        .day-events {
            padding: 12px 8px;
            min-height: 100px;
            max-height: 500px;
            overflow-y: auto;
            scrollbar-width: thin;
            scrollbar-color: #cbd5e0 #f7fafc;
        }
        
        .day-events::-webkit-scrollbar {
            width: 4px;
        }
        
        .day-events::-webkit-scrollbar-track {
            background: #f7fafc;
            border-radius: 2px;
        }
        
        .day-events::-webkit-scrollbar-thumb {
            background: #cbd5e0;
            border-radius: 2px;
        }
        
        .day-events::-webkit-scrollbar-thumb:hover {
            background: #a0aec0;
        }
        
        .no-events {
            color: #9ca3af;
            font-size: 13px;
            text-align: center;
            margin-top: 30px;
            font-style: italic;
        }
        
        .loading-message {
            text-align: center;
            padding: 20px;
            color: #374151;
            font-size: 15px;
            font-weight: 500;
        }
        
        .event-item {
            background: linear-gradient(135deg, #f0f9ff 0%, #e0f2fe 100%);
            border: 1px solid #bae6fd;
            border-radius: 8px;
            padding: 8px;
            margin-bottom: 6px;
            font-size: 12px;
            transition: all 0.2s ease;
            cursor: pointer;
            pointer-events: auto;
            user-select: none;
            position: relative;
            display: flex;
            justify-content: space-between;
            align-items: flex-start;
        }
        
        .event-item:hover {
            transform: translateY(-1px);
            box-shadow: 0 2px 8px rgba(0,0,0,0.1);
            background: linear-gradient(135deg, #e0f2fe 0%, #bae6fd 100%);
            border-color: #7dd3fc;
        }
        
        .event-content {
            flex: 1;
            min-width: 0;
        }
        
        .event-actions {
            margin-left: 8px;
            flex-shrink: 0;
        }
        
        .station-link-btn {
            background: rgba(99, 102, 241, 0.1);
            border: 1px solid rgba(99, 102, 241, 0.3);
            border-radius: 3px;
            padding: 3px;
            cursor: pointer;
            transition: all 0.2s ease;
            opacity: 0.7;
            display: flex;
            align-items: center;
            justify-content: center;
            color: #6366f1;
            width: 16px;
            height: 16px;
        }
        
        .station-link-btn:hover {
            background: rgba(99, 102, 241, 0.2);
            border-color: rgba(99, 102, 241, 0.5);
            opacity: 1;
            transform: scale(1.1);
            color: #4f46e5;
        }
        
        /* 특정 멤버 강조 효과 */
        .event-item.highlight {
            background: linear-gradient(135deg, #fef3c7 0%, #fde68a 100%) !important;
            border: 2px solid #f59e0b !important;
            box-shadow: 0 4px 12px rgba(245, 158, 11, 0.3) !important;
            transform: scale(1.02) !important;
            z-index: 10;
            position: relative;
        }
        
        .event-item.fade {
            opacity: 0.3;
            filter: grayscale(50%);
        }
        
        .calendar-day.empty-day {
            background: linear-gradient(135deg, #fef2f2 0%, #fee2e2 100%) !important;
            border: 2px solid #fca5a5 !important;
            box-shadow: 0 0 0 1px #f87171 !important;
        }
        
        .calendar-day.empty-day .day-header {
            background: linear-gradient(135deg, #fecaca 0%, #fca5a5 100%) !important;
            border-bottom: 2px solid #f87171 !important;
        }
        
        .calendar-day.empty-day .day-number {
            color: #dc2626 !important;
            font-weight: 700 !important;
        }
        
        .calendar-day .scroll-indicator {
            position: absolute;
            left: 50%;
            transform: translateX(-50%);
            background: #6366f1;
            color: white;
            border-radius: 50%;
            width: 20px;
            height: 20px;
            display: flex;
            align-items: center;
            justify-content: center;
            font-size: 12px;
            font-weight: bold;
            z-index: 10;
            animation: pulse 1.5s infinite;
            opacity: 0;
            transition: all 0.3s ease-in-out;
            box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
        }
        
        .calendar-day .scroll-indicator-top {
            top: 15px;
        }
        
        .calendar-day .scroll-indicator-bottom {
            bottom: 15px;
        }
        
        .calendar-day .scroll-indicator.show {
            opacity: 1;
            transform: translateX(-50%) scale(1);
        }
        
        @keyframes pulse {
            0%, 100% { transform: translateX(-50%) scale(1); opacity: 1; }
            50% { transform: translateX(-50%) scale(1.1); opacity: 0.8; }
        }
        
        
        
        .event-time {
            color: #0369a1;
            font-weight: 600;
            margin-bottom: 4px;
            font-size: 11px;
        }
        
        .event-type-badge {
            display: inline-block;
            background: #3b82f6;
            color: white;
            font-size: 9px;
            font-weight: 600;
            padding: 2px 6px;
            border-radius: 4px;
            margin-left: 6px;
            text-transform: uppercase;
            letter-spacing: 0.5px;
        }
        
        .event-type-badge.방송 {
            background: #3b82f6;
        }
        
        .event-type-badge.방송예정 {
            background: #06b6d4;
        }
        
        .event-type-badge.합방 {
            background: #10b981;
        }
        
        .event-type-badge.휴방 {
            background: #f59e0b;
        }
        
        .event-type-badge.기타 {
            background: #6b7280;
        }
        
        
        .event-title {
            color: #1e293b;
            font-size: 12px;
            line-height: 1.3;
            margin-bottom: 3px;
            font-weight: 500;
        }
        
        /* 주말 스타일 - 라이트 */
        .calendar-day:nth-child(7) {
            background: #fafbfc;
        }
        
        .calendar-day:nth-child(7) .day-header {
            background: #f3f4f6;
        }
        
        /* 주말 날짜 숫자 색상 - 라이트 */
        .calendar-day:nth-child(7) .day-number {
            color: #dc2626; /* 일요일 - 빨간색 */
        }
        
        /* 오늘 날짜 강조 - 라이트 */
        .calendar-day.today {
            background: #f8fafc;
            border: 2px solid #e2e8f0;
        }
        
        .calendar-day.today .day-header {
            background: #f1f5f9;
        }
        
        /* 다크 테마 스타일 - Sooplive 실제 클래스 */
        body.thema_dark #sooplive-calendar,
        html[dark=true] #sooplive-calendar,
        .dark #sooplive-calendar,
        html.dark #sooplive-calendar,
        body.dark #sooplive-calendar,
        [data-theme="dark"] #sooplive-calendar {
            background: #1f2937;
            border: 1px solid #374151;
            box-shadow: 0 2px 8px rgba(0,0,0,0.3);
        }
        
        body.thema_dark .calendar-header,
        html[dark=true] .calendar-header,
        .dark .calendar-header,
        html.dark .calendar-header,
        body.dark .calendar-header,
        [data-theme="dark"] .calendar-header {
            border-bottom: 1px solid #374151;
        }
        
        body.thema_dark .calendar-header h3,
        html[dark=true] .calendar-header h3,
        .dark .calendar-header h3,
        html.dark .calendar-header h3,
        body.dark .calendar-header h3,
        [data-theme="dark"] .calendar-header h3 {
            color: #f9fafb;
        }
        
        body.thema_dark .week-range,
        html[dark=true] .week-range,
        .dark .week-range,
        html.dark .week-range,
        body.dark .week-range,
        [data-theme="dark"] .week-range {
            color: #d1d5db;
        }
        
        body.thema_dark .calendar-grid,
        html[dark=true] .calendar-grid,
        .dark .calendar-grid,
        html.dark .calendar-grid,
        body.dark .calendar-grid,
        [data-theme="dark"] .calendar-grid {
            background: #374151;
            border: 1px solid #4b5563;
        }
        
        body.thema_dark .calendar-day,
        html[dark=true] .calendar-day,
        .dark .calendar-day,
        html.dark .calendar-day,
        body.dark .calendar-day,
        [data-theme="dark"] .calendar-day {
            background: #1f2937;
        }
        
        body.thema_dark .day-header,
        html[dark=true] .day-header,
        .dark .day-header,
        html.dark .day-header,
        body.dark .day-header,
        [data-theme="dark"] .day-header {
            background: #374151;
            border-bottom: 1px solid #4b5563;
        }
        
        body.thema_dark .day-name,
        html[dark=true] .day-name,
        .dark .day-name,
        html.dark .day-name,
        body.dark .day-name,
        [data-theme="dark"] .day-name {
            color: #9ca3af;
        }
        
        body.thema_dark .day-number,
        html[dark=true] .day-number,
        .dark .day-number,
        html.dark .day-number,
        body.dark .day-number,
        [data-theme="dark"] .day-number {
            color: #f9fafb;
        }
        
        body.thema_dark .event-count,
        html[dark=true] .event-count,
        .dark .event-count,
        html.dark .event-count,
        body.dark .event-count,
        [data-theme="dark"] .event-count {
            color: #93c5fd;
            background: #1e3a8a;
        }
        
        body.thema_dark .no-events,
        html[dark=true] .no-events,
        .dark .no-events,
        html.dark .no-events,
        body.dark .no-events,
        [data-theme="dark"] .no-events {
            color: #6b7280;
        }
        
        body.thema_dark .loading-message,
        html[dark=true] .loading-message,
        .dark .loading-message,
        html.dark .loading-message,
        body.dark .loading-message,
        [data-theme="dark"] .loading-message {
            color: #d1d5db;
        }
        
        body.thema_dark .event-item,
        html[dark=true] .event-item,
        .dark .event-item,
        html.dark .event-item,
        body.dark .event-item,
        [data-theme="dark"] .event-item {
            background: linear-gradient(135deg, #1e3a8a 0%, #1e40af 100%);
            border: 1px solid #3b82f6;
        }
        
        body.thema_dark .event-item:hover,
        html[dark=true] .event-item:hover,
        .dark .event-item:hover,
        html.dark .event-item:hover,
        body.dark .event-item:hover,
        [data-theme="dark"] .event-item:hover {
            background: linear-gradient(135deg, #1e40af 0%, #2563eb 100%);
            border-color: #60a5fa;
        }
        
        body.thema_dark .station-link-btn,
        html[dark=true] .station-link-btn,
        .dark .station-link-btn,
        html.dark .station-link-btn,
        body.dark .station-link-btn,
        [data-theme="dark"] .station-link-btn {
            background: rgba(99, 102, 241, 0.2);
            border-color: rgba(99, 102, 241, 0.4);
            color: #a5b4fc;
        }
        
        body.thema_dark .station-link-btn:hover,
        html[dark=true] .station-link-btn:hover,
        .dark .station-link-btn:hover,
        html.dark .station-link-btn:hover,
        body.dark .station-link-btn:hover,
        [data-theme="dark"] .station-link-btn:hover {
            background: rgba(99, 102, 241, 0.3);
            border-color: rgba(99, 102, 241, 0.6);
            color: #c7d2fe;
        }
        
        
        /* 다크 테마 특정 멤버 강조 효과 */
        body.thema_dark .event-item.highlight,
        html[dark=true] .event-item.highlight,
        .dark .event-item.highlight,
        html.dark .event-item.highlight,
        body.dark .event-item.highlight,
        [data-theme="dark"] .event-item.highlight {
            background: linear-gradient(135deg, #451a03 0%, #78350f 100%) !important;
            border: 2px solid #f59e0b !important;
            box-shadow: 0 4px 12px rgba(245, 158, 11, 0.4) !important;
        }
        
        body.thema_dark .event-item.fade,
        html[dark=true] .event-item.fade,
        .dark .event-item.fade,
        html.dark .event-item.fade,
        body.dark .event-item.fade,
        [data-theme="dark"] .event-item.fade {
            opacity: 0.2;
            filter: grayscale(70%);
        }
        
        body.thema_dark .calendar-day.empty-day,
        html[dark=true] .calendar-day.empty-day,
        .dark .calendar-day.empty-day,
        html.dark .calendar-day.empty-day,
        body.dark .calendar-day.empty-day,
        [data-theme="dark"] .calendar-day.empty-day {
            background: linear-gradient(135deg, #3d1f1f 0%, #4d2626 100%) !important;
            border: 2px solid #ef4444 !important;
            box-shadow: 0 0 0 1px #fca5a5 !important;
        }
        
        body.thema_dark .calendar-day.empty-day .day-header,
        html[dark=true] .calendar-day.empty-day .day-header,
        .dark .calendar-day.empty-day .day-header,
        html.dark .calendar-day.empty-day .day-header,
        body.dark .calendar-day.empty-day .day-header,
        [data-theme="dark"] .calendar-day.empty-day .day-header {
            background: linear-gradient(135deg, #4d2626 0%, #5d2d2d 100%) !important;
            border-bottom: 2px solid #ef4444 !important;
        }
        
        body.thema_dark .calendar-day.empty-day .day-number,
        html[dark=true] .calendar-day.empty-day .day-number,
        .dark .calendar-day.empty-day .day-number,
        html.dark .calendar-day.empty-day .day-number,
        body.dark .calendar-day.empty-day .day-number,
        [data-theme="dark"] .calendar-day.empty-day .day-number {
            color: #f87171 !important;
            font-weight: 700 !important;
        }
        
        body.thema_dark .calendar-day .scroll-indicator,
        html[dark=true] .calendar-day .scroll-indicator,
        .dark .calendar-day .scroll-indicator,
        html.dark .calendar-day .scroll-indicator,
        body.dark .calendar-day .scroll-indicator,
        [data-theme="dark"] .calendar-day .scroll-indicator {
            background: #4f46e5;
            color: #e5e7eb;
        }
        
        
        
        body.thema_dark .event-time,
        html[dark=true] .event-time,
        .dark .event-time,
        html.dark .event-time,
        body.dark .event-time,
        [data-theme="dark"] .event-time {
            color: #93c5fd;
        }
        
        body.thema_dark .event-type-badge,
        html[dark=true] .event-type-badge,
        .dark .event-type-badge,
        html.dark .event-type-badge,
        body.dark .event-type-badge,
        [data-theme="dark"] .event-type-badge {
            color: white;
            font-weight: 700;
        }
        
        body.thema_dark .event-type-badge.방송,
        html[dark=true] .event-type-badge.방송,
        .dark .event-type-badge.방송,
        html.dark .event-type-badge.방송,
        body.dark .event-type-badge.방송,
        [data-theme="dark"] .event-type-badge.방송 {
            background: #2563eb;
        }
        
        body.thema_dark .event-type-badge.방송예정,
        html[dark=true] .event-type-badge.방송예정,
        .dark .event-type-badge.방송예정,
        html.dark .event-type-badge.방송예정,
        body.dark .event-type-badge.방송예정,
        [data-theme="dark"] .event-type-badge.방송예정 {
            background: #0891b2;
        }
        
        body.thema_dark .event-type-badge.합방,
        html[dark=true] .event-type-badge.합방,
        .dark .event-type-badge.합방,
        html.dark .event-type-badge.합방,
        body.dark .event-type-badge.합방,
        [data-theme="dark"] .event-type-badge.합방 {
            background: #059669;
        }
        
        body.thema_dark .event-type-badge.휴방,
        html[dark=true] .event-type-badge.휴방,
        .dark .event-type-badge.휴방,
        html.dark .event-type-badge.휴방,
        body.dark .event-type-badge.휴방,
        [data-theme="dark"] .event-type-badge.휴방 {
            background: #d97706;
        }
        
        body.thema_dark .event-type-badge.기타,
        html[dark=true] .event-type-badge.기타,
        .dark .event-type-badge.기타,
        html.dark .event-type-badge.기타,
        body.dark .event-type-badge.기타,
        [data-theme="dark"] .event-type-badge.기타 {
            background: #4b5563;
        }
        
        
        body.thema_dark .event-title,
        html[dark=true] .event-title,
        .dark .event-title,
        html.dark .event-title,
        body.dark .event-title,
        [data-theme="dark"] .event-title {
            color: #e5e7eb;
        }
        
        /* 다크 테마 스크롤바 */
        body.thema_dark .day-events,
        html[dark=true] .day-events,
        .dark .day-events,
        html.dark .day-events,
        body.dark .day-events,
        [data-theme="dark"] .day-events {
            scrollbar-color: #4a5568 #2d3748;
        }
        
        body.thema_dark .day-events::-webkit-scrollbar-track,
        html[dark=true] .day-events::-webkit-scrollbar-track,
        .dark .day-events::-webkit-scrollbar-track,
        html.dark .day-events::-webkit-scrollbar-track,
        body.dark .day-events::-webkit-scrollbar-track,
        [data-theme="dark"] .day-events::-webkit-scrollbar-track {
            background: #2d3748;
        }
        
        body.thema_dark .day-events::-webkit-scrollbar-thumb,
        html[dark=true] .day-events::-webkit-scrollbar-thumb,
        .dark .day-events::-webkit-scrollbar-thumb,
        html.dark .day-events::-webkit-scrollbar-thumb,
        body.dark .day-events::-webkit-scrollbar-thumb,
        [data-theme="dark"] .day-events::-webkit-scrollbar-thumb {
            background: #4a5568;
        }
        
        body.thema_dark .day-events::-webkit-scrollbar-thumb:hover,
        html[dark=true] .day-events::-webkit-scrollbar-thumb:hover,
        .dark .day-events::-webkit-scrollbar-thumb:hover,
        html.dark .day-events::-webkit-scrollbar-thumb:hover,
        body.dark .day-events::-webkit-scrollbar-thumb:hover,
        [data-theme="dark"] .day-events::-webkit-scrollbar-thumb:hover {
            background: #718096;
        }
        
        /* 다크 테마 크레딧 */
        body.thema_dark .calendar-credit,
        html[dark=true] .calendar-credit,
        .dark .calendar-credit,
        html.dark .calendar-credit,
        body.dark .calendar-credit,
        [data-theme="dark"] .calendar-credit {
            color: #6b7280;
            border-top: 1px solid #374151;
            text-align: right;
        }
        
        body.thema_dark .credit-name,
        html[dark=true] .credit-name,
        .dark .credit-name,
        html.dark .credit-name,
        body.dark .credit-name,
        [data-theme="dark"] .credit-name {
            color: #93c5fd;
        }
        
        
        /* 주말 스타일 - 다크 */
        body.thema_dark .calendar-day:nth-child(7),
        html[dark=true] .calendar-day:nth-child(7),
        .dark .calendar-day:nth-child(7),
        html.dark .calendar-day:nth-child(7),
        body.dark .calendar-day:nth-child(7),
        [data-theme="dark"] .calendar-day:nth-child(7) {
            background: #111827;
        }
        
        body.thema_dark .calendar-day:nth-child(7) .day-header,
        html[dark=true] .calendar-day:nth-child(7) .day-header,
        .dark .calendar-day:nth-child(7) .day-header,
        html.dark .calendar-day:nth-child(7) .day-header,
        body.dark .calendar-day:nth-child(7) .day-header,
        [data-theme="dark"] .calendar-day:nth-child(7) .day-header {
            background: #374151;
        }
        
        /* 주말 날짜 숫자 색상 - 다크 */
        body.thema_dark .calendar-day:nth-child(7) .day-number,
        html[dark=true] .calendar-day:nth-child(7) .day-number,
        .dark .calendar-day:nth-child(7) .day-number,
        html.dark .calendar-day:nth-child(7) .day-number,
        body.dark .calendar-day:nth-child(7) .day-number,
        [data-theme="dark"] .calendar-day:nth-child(7) .day-number {
            color: #ef4444; /* 일요일 - 빨간색 */
        }
        
        /* 휴일 날짜 숫자 색상 - 라이트 */
        .calendar-day.holiday .day-number {
            color: #dc2626 !important; /* 휴일 - 빨간색 */
        }
        
        /* 휴일 날짜 숫자 색상 - 다크 */
        body.thema_dark .calendar-day.holiday .day-number,
        html[dark=true] .calendar-day.holiday .day-number,
        .dark .calendar-day.holiday .day-number,
        html.dark .calendar-day.holiday .day-number,
        body.dark .calendar-day.holiday .day-number,
        [data-theme="dark"] .calendar-day.holiday .day-number {
            color: #ef4444 !important; /* 휴일 - 빨간색 */
        }
        
        /* 오늘 날짜 강조 - 다크 */
        body.thema_dark .calendar-day.today,
        html[dark=true] .calendar-day.today,
        .dark .calendar-day.today,
        html.dark .calendar-day.today,
        body.dark .calendar-day.today,
        [data-theme="dark"] .calendar-day.today {
            background: #0f172a;
            border: 2px solid #475569;
            box-shadow: 0 0 0 1px #64748b;
        }
        
        body.thema_dark .calendar-day.today .day-header,
        html[dark=true] .calendar-day.today .day-header,
        .dark .calendar-day.today .day-header,
        html.dark .calendar-day.today .day-header,
        body.dark .calendar-day.today .day-header,
        [data-theme="dark"] .calendar-day.today .day-header {
            background: #1e293b;
        }
        
        /* 반응형 디자인 */
        @media (max-width: 768px) {
            .calendar-grid {
                grid-template-columns: repeat(7, 1fr);
                gap: 0;
            }
            
            .calendar-day {
                min-height: 100px;
            }
            
            .day-events {
                padding: 8px 4px;
                min-height: 70px;
            }
            
            .week-navigation {
                gap: 8px;
            }
            
            .week-navigation button {
                padding: 6px 12px;
                font-size: 13px;
            }
        }
    `;
    document.head.appendChild(style);

    // URL에서 groupId 추출
    function getGroupId() {
        const urlParams = new URLSearchParams(window.location.search);
        return urlParams.get('groupId') || '';
    }

    // 주간 시작일 계산 (월요일 기준)
    function startOfWeek(date) {
        const d = new Date(date);
        const day = d.getDay();
        const diff = day === 0 ? -6 : 1 - day;
        d.setDate(d.getDate() + diff);
        d.setHours(0, 0, 0, 0);
        return d;
    }

    // 휴일 체크 함수 (주말만 체크)
    function isHoliday(date) {
        return date.getDay() === 0;
    }

    // 날짜 문자열 포맷팅 함수
    function formatDateString(date) {
        return date.getFullYear() + '-' + 
               String(date.getMonth() + 1).padStart(2, '0') + '-' + 
               String(date.getDate()).padStart(2, '0');
    }
    
    // 오늘 날짜 문자열
    function getTodayString() {
        const today = new Date();
        return formatDateString(today);
    }

    // 일정 아이템 HTML 생성 함수
    function createEventItemHTML(event) {
        const streamerId = event.userId || event.streamerId;
        const streamerUrl = streamerId ? `https://www.sooplive.co.kr/station/${streamerId}` : '#';
        const streamerNickname = event.streamerNickname || '';
        
        return `
            <div class="event-item" 
                 data-streamer-id="${streamerId || ''}" 
                 data-streamer-nickname="${streamerNickname}">
                <div class="event-content">
                    <div class="event-time">
                        ${event.eventTime}
                        <span class="event-type-badge ${event.calendarTypeName || '기타'}">${event.calendarTypeName || '기타'}</span>
                    </div>
                    <div class="event-title">${event.title}</div>
                </div>
                <div class="event-actions">
                    <button class="station-link-btn" 
                            onclick="event.stopPropagation(); window.open('${streamerUrl}', '_blank')" 
                            title="방송국으로 이동">
                        <svg width="8" height="8" viewBox="0 0 8 8" fill="currentColor">
                            <path d="M4 0L0 3v5h2V4h4v4h2V3L4 0z"/>
                        </svg>
                    </button>
                </div>
            </div>
        `;
    }

    // 현재 페이지의 쿠키와 인증 헤더 가져오기
    function getAuthHeaders() {
        const headers = {
            'Accept': 'application/json',
            'User-Agent': navigator.userAgent,
            'Referer': window.location.href,
            'Origin': window.location.origin
        };
        
        if (document.cookie) {
            headers['Cookie'] = document.cookie;
        }
        
        const csrfToken = document.querySelector('meta[name="csrf-token"]')?.content ||
                         document.querySelector('input[name="_token"]')?.value ||
                         document.querySelector('meta[name="csrf"]')?.content;
        
        if (csrfToken) {
            headers['X-CSRF-TOKEN'] = csrfToken;
            headers['X-Requested-With'] = 'XMLHttpRequest';
        }
        
        return headers;
    }

    // API 호출
    function fetchJSON(url) {
        return new Promise((resolve, reject) => {
            const headers = getAuthHeaders();
            
            GM_xmlhttpRequest({
                method: 'GET',
                url: url,
                headers: headers,
                onload: function(response) {
                    if (response.status === 403) {
                        resolve({
                            ok: false,
                            data: response.responseText,
                            status: response.status,
                            statusText: response.statusText,
                            error: '인증이 필요합니다. sooplive.co.kr에 로그인되어 있는지 확인해주세요.'
                        });
                        return;
                    }
                    
                    try {
                        const data = JSON.parse(response.responseText);
                        resolve({ 
                            ok: response.status >= 200 && response.status < 300, 
                            data,
                            status: response.status,
                            statusText: response.statusText
                        });
                    } catch (e) {
                        resolve({ 
                            ok: false, 
                            data: response.responseText,
                            status: response.status,
                            statusText: response.statusText,
                            parseError: e.message
                        });
                    }
                },
                onerror: function(error) {
                    reject(new Error(`네트워크 오류: ${error.error || 'Unknown error'}`));
                },
                ontimeout: function() {
                    reject(new Error('요청 시간 초과'));
                },
                timeout: 10000
            });
        });
    }

    // 즐겨찾기 스트리머 목록
    async function getStreamers(groupId) {
        try {
            const response = await fetchJSON(`https://myapi.sooplive.co.kr/api/favorite/${groupId}`);
            
            if (!response.ok) {
                if (response.status === 403) {
                    throw new Error(`인증 오류 (403): sooplive.co.kr에 로그인되어 있는지 확인해주세요.`);
                }
                throw new Error(`HTTP ${response.status}: ${response.data || response.error || 'Unknown error'}`);
            }
            
            const items = Array.isArray(response.data) ? response.data : (response.data?.data || []);
            
            if (items.length === 0) {
                return [];
            }
            
            const streamers = items.map(item => {
                const userId = item?.user_id;
                const nickname = item?.user_nick;
                const isLive = item?.is_live || false;
                return { userId, nickname, isLive };
            }).filter(streamer => streamer.userId && streamer.nickname);
            
            return streamers;
        } catch (error) {
            throw error;
        }
    }

    // 캘린더 이벤트
    async function getEvents(userId, weekStart) {
        try {
            // 이번주 전체를 검색 (weekStart 기준)
            const year = weekStart.getFullYear();
            const month = weekStart.getMonth() + 1;
            const day = weekStart.getDate();
            
            const response = await fetchJSON(`https://api-channel.sooplive.co.kr/v1.1/channel/${userId}/calendar?view=week&year=${year}&month=${month}&day=${day}&userId=${userId}`);
            if (!response.ok) throw new Error(`HTTP ${response.status}`);

            const events = [];
            if (response.data.days) {
                for (const day of response.data.days) {
                    if (day.events) {
                        for (const event of day.events) {
                            if (event.eventDate && event.eventTime) {
                                // 이벤트 날짜는 그대로 유지
                                events.push({
                                    title: event.title || '제목없음',
                                    eventDate: event.eventDate,
                                    eventTime: event.eventTime,
                                    calendarTypeName: event.calendarTypeName || '일정'
                                });
                            }
                        }
                    }
                }
            }
            return events;
        } catch (error) {
            return [];
        }
    }

    // 캘린더 HTML 생성
    function createCalendarHTML(events, weekStart) {
        const weekEnd = new Date(weekStart);
        weekEnd.setDate(weekEnd.getDate() + 6);

        const eventsByDate = {};
        for (const event of events) {
            if (!eventsByDate[event.eventDate]) {
                eventsByDate[event.eventDate] = [];
            }
            eventsByDate[event.eventDate].push(event);
        }

        const weekDates = [];
        for (let i = 0; i < 7; i++) {
            const date = new Date(weekStart);
            date.setDate(date.getDate() + i);
            weekDates.push(date);
        }

        let html = `
            <div class="calendar-container">
                <div class="calendar-header">
                    <h3>통합 일정</h3>
                    <div class="week-navigation">
                        <button id="prev-week-btn">← 이전 주</button>
                        <span class="week-range">${weekStart.toLocaleDateString('ko-KR')} ~ ${weekEnd.toLocaleDateString('ko-KR')}</span>
                        <button id="next-week-btn">다음 주 →</button>
                    </div>
                </div>
                <div class="calendar-grid">
        `;

        // 오늘 날짜
        const todayStr = getTodayString();

        for (const date of weekDates) {
            // 이벤트 날짜는 그대로 사용
            const dateStr = formatDateString(date);
            const dayEvents = eventsByDate[dateStr] || [];
            dayEvents.sort((a, b) => a.eventTime.localeCompare(b.eventTime));

            // 오늘 날짜인지 확인
            const isToday = dateStr === todayStr;
            const todayClass = isToday ? ' today' : '';
            
            // 휴일인지 확인
            const isHolidayDate = isHoliday(date);
            const holidayClass = isHolidayDate ? ' holiday' : '';

            html += `
                <div class="calendar-day${todayClass}${holidayClass}">
                    <div class="day-header">
                        <div class="day-name">${date.toLocaleDateString('ko-KR', { weekday: 'short' })}</div>
                        <div class="day-number">${date.getDate()}</div>
                        <div class="event-count">${dayEvents.length}개</div>
                    </div>
                    <div class="day-events">
            `;

            if (dayEvents.length === 0) {
                html += '<div class="no-events">일정 없음</div>';
            } else {
                // 모든 일정 표시 (스크롤로 확인)
                for (const event of dayEvents) {
                    html += createEventItemHTML(event);
                }
            }

            html += `
                    </div>
                </div>
            `;
        }

        html += `
                </div>
                <div class="calendar-credit">
                    made by <span class="credit-name">지창연구소</span>
                </div>
            </div>
        `;

        return html;
    }

    // 마우스 오버 이벤트 처리 함수
    function addHoverEvents() {
        const eventItems = document.querySelectorAll('.event-item');
        
        eventItems.forEach(item => {
            item.addEventListener('mouseenter', function() {
                // 고정된 멤버가 있으면 마우스 오버 무시
                if (fixedStreamer) return;
                
                const streamerId = this.getAttribute('data-streamer-id');
                const streamerNickname = this.getAttribute('data-streamer-nickname');
                
                // 마우스 오버한 날 찾기
                const hoveredDay = this.closest('.calendar-day');
                
                // 같은 스트리머의 모든 일정 강조
                const allEventItems = document.querySelectorAll('.event-item');
                allEventItems.forEach(eventItem => {
                    const itemStreamerId = eventItem.getAttribute('data-streamer-id');
                    const itemStreamerNickname = eventItem.getAttribute('data-streamer-nickname');
                    
                    if (itemStreamerId === streamerId && itemStreamerNickname === streamerNickname) {
                        eventItem.classList.add('highlight');
                    } else {
                        eventItem.classList.add('fade');
                    }
                });
                
                // 해당 스트리머의 일정이 없는 날들을 빨갛게 강조
                highlightEmptyDays(streamerId, streamerNickname);
                
                // 화살표 표시 및 스크롤 실행
                showScrollIndicatorsForStreamer(streamerId, streamerNickname);
                
                // 마우스 오버한 날짜를 제외한 다른 날짜들의 해당 멤버 첫 번째 일정을 가운데로 스크롤
                scrollToFirstEventInOtherDays(streamerId, streamerNickname, hoveredDay);
            });
            
            item.addEventListener('mouseleave', function() {
                // 고정된 멤버가 없을 때만 강조 효과 제거
                if (!fixedStreamer) {
                    clearAllHighlights();
                }
            });
            
            // 클릭 이벤트 추가 (멤버 고정)
            item.addEventListener('click', function(e) {
                // 방송국 버튼 클릭이 아닐 때만 멤버 고정
                if (!e.target.classList.contains('station-link-btn')) {
                    const streamerId = this.getAttribute('data-streamer-id');
                    const streamerNickname = this.getAttribute('data-streamer-nickname');
                    
                    // 클릭한 날짜 저장
                    clickedDay = this.closest('.calendar-day');
                    
                    // 이미 고정된 멤버와 같으면 고정 해제
                    if (fixedStreamer && fixedStreamer.id === streamerId && fixedStreamer.nickname === streamerNickname) {
                        fixedStreamer = null;
                        clearAllHighlights();
                    } else {
                        // 다른 멤버 클릭 시 기존 고정 해제 후 새 멤버로 고정 변경
                        clearAllHighlights();
                        fixedStreamer = { id: streamerId, nickname: streamerNickname };
                        applyFixedStreamerHighlight();
                    }
                }
            });
        });
    }


    // 고정된 멤버 강조 적용 함수
    function applyFixedStreamerHighlight() {
        if (!fixedStreamer) return;
        
        const allEventItems = document.querySelectorAll('.event-item');
        allEventItems.forEach(eventItem => {
            const itemStreamerId = eventItem.getAttribute('data-streamer-id');
            const itemStreamerNickname = eventItem.getAttribute('data-streamer-nickname');
            
            if (itemStreamerId === fixedStreamer.id && itemStreamerNickname === fixedStreamer.nickname) {
                eventItem.classList.add('highlight');
            } else {
                eventItem.classList.add('fade');
            }
        });
        
        // 해당 스트리머의 일정이 없는 날들을 빨갛게 강조
        highlightEmptyDays(fixedStreamer.id, fixedStreamer.nickname);
        
        // 화살표 표시
        showScrollIndicatorsForStreamer(fixedStreamer.id, fixedStreamer.nickname);
        
        // 다른 날짜들의 해당 멤버 첫 번째 일정을 맨 위로 스크롤
        scrollToFirstEventInOtherDays(fixedStreamer.id, fixedStreamer.nickname);
    }

    // 스크롤 완료 대기 함수
    function waitForScrollComplete(container, callback) {
        const startTime = Date.now();
        let lastScrollTop = container.scrollTop;
        let isScrolling = false;
        
        const checkScroll = () => {
            const currentScrollTop = container.scrollTop;
            const currentTime = Date.now();
            
            if (Math.abs(currentScrollTop - lastScrollTop) > 1) {
                isScrolling = true;
                lastScrollTop = currentScrollTop;
            } else if (isScrolling && (currentTime - startTime) > 200) {
                // 스크롤이 멈췄고 충분한 시간이 지났으면 완료
                callback();
                return;
            }
            
            requestAnimationFrame(checkScroll);
        };
        
        requestAnimationFrame(checkScroll);
    }

    // 스크롤 완료 후 화살표 인디케이터 업데이트
    function updateScrollIndicatorsAfterScroll(streamerId, streamerNickname, excludeDay = null) {
        const allDays = document.querySelectorAll('.calendar-day');
        
        allDays.forEach(day => {
            if (excludeDay === day) return;
            
            const dayEventsContainer = day.querySelector('.day-events');
            if (!dayEventsContainer) return;
            
            // 해당 스트리머의 일정들 찾기
            const streamerEvents = Array.from(day.querySelectorAll('.event-item')).filter(event => {
                const eventStreamerId = event.getAttribute('data-streamer-id');
                const eventStreamerNickname = event.getAttribute('data-streamer-nickname');
                return eventStreamerId === streamerId && eventStreamerNickname === streamerNickname;
            });
            
            if (streamerEvents.length === 0) return;
            
            // 현재 보이는 영역 확인
            const containerRect = dayEventsContainer.getBoundingClientRect();
            const containerTop = containerRect.top;
            const containerBottom = containerRect.bottom;
            
            // 위쪽/아래쪽에 숨겨진 일정이 있는지 확인
            let hasHiddenEventsAbove = false;
            let hasHiddenEventsBelow = false;
            
            streamerEvents.forEach(event => {
                const eventRect = event.getBoundingClientRect();
                if (eventRect.bottom < containerTop) {
                    hasHiddenEventsAbove = true;
                } else if (eventRect.top > containerBottom) {
                    hasHiddenEventsBelow = true;
                }
            });
            
            // 기존 화살표 제거
            removeScrollIndicator(day);
            
            // 새로운 화살표 표시
            if (hasHiddenEventsAbove) {
                const topIndicator = createScrollIndicator('top', '위쪽에 더 많은 일정이 있습니다');
                showScrollIndicator(topIndicator, day, 'top');
            }
            
            if (hasHiddenEventsBelow) {
                const bottomIndicator = createScrollIndicator('bottom', '아래쪽에 더 많은 일정이 있습니다');
                showScrollIndicator(bottomIndicator, day, 'bottom');
            }
        });
    }

    // 개선된 스크롤 함수 - 다른 날짜들의 해당 멤버 첫 번째 일정을 가운데로 스크롤
    function scrollToFirstEventInOtherDays(streamerId, streamerNickname, excludeDay = null) {
        // 강조 효과 적용 후 레이아웃 안정화를 위해 지연
        setTimeout(() => {
            const allDays = document.querySelectorAll('.calendar-day');
            let scrollCount = 0;
            let totalScrolls = 0;
            
            allDays.forEach(day => {
                const dayEventsContainer = day.querySelector('.day-events');
                if (!dayEventsContainer) return;
                
                // 제외할 날짜 체크 (클릭한 날짜 또는 마우스 오버한 날짜)
                if (clickedDay === day || excludeDay === day) return;
                
                // 해당 스트리머의 첫 번째 일정 찾기
                const streamerEvents = Array.from(day.querySelectorAll('.event-item')).filter(event => {
                    const eventStreamerId = event.getAttribute('data-streamer-id');
                    const eventStreamerNickname = event.getAttribute('data-streamer-nickname');
                    return eventStreamerId === streamerId && eventStreamerNickname === streamerNickname;
                });
                
                if (streamerEvents.length === 0) return;
                
                // 시간순 정렬 후 첫 번째 일정
                const firstEvent = streamerEvents.sort((a, b) => {
                    const timeA = a.querySelector('.event-time')?.textContent || '';
                    const timeB = b.querySelector('.event-time')?.textContent || '';
                    return timeA.localeCompare(timeB);
                })[0];
                
                if (firstEvent) {
                    // 스크롤이 필요한지 확인
                    if (dayEventsContainer.scrollHeight > dayEventsContainer.clientHeight) {
                        totalScrolls++;
                        
                        // 더 정확한 스크롤 위치 계산
                        const containerHeight = dayEventsContainer.clientHeight;
                        const eventHeight = firstEvent.offsetHeight;
                        
                        // getBoundingClientRect를 사용한 정확한 위치 계산
                        const containerRect = dayEventsContainer.getBoundingClientRect();
                        const eventRect = firstEvent.getBoundingClientRect();
                        const relativeTop = eventRect.top - containerRect.top + dayEventsContainer.scrollTop;
                        
                        // 위쪽에 하나 정도 더 보일 정도로 스크롤하는 위치 계산
                        const scrollPosition = relativeTop - (eventHeight * 1.5) + (eventHeight / 2);
                        
                        // 부드러운 스크롤 적용
                        dayEventsContainer.scrollTo({
                            top: Math.max(0, scrollPosition),
                            behavior: 'smooth'
                        });
                        
                        // 스크롤 완료 후 화살표 업데이트
                        waitForScrollComplete(dayEventsContainer, () => {
                            scrollCount++;
                            if (scrollCount === totalScrolls) {
                                // 모든 스크롤이 완료되면 화살표 업데이트
                                updateScrollIndicatorsAfterScroll(streamerId, streamerNickname, excludeDay);
                            }
                        });
                    }
                }
            });
            
            // 스크롤이 필요한 컨테이너가 없으면 즉시 화살표 업데이트
            if (totalScrolls === 0) {
                updateScrollIndicatorsAfterScroll(streamerId, streamerNickname, excludeDay);
            }
        }, 150); // 150ms 지연으로 레이아웃 안정화
    }

    // 빈 날짜 강조 함수 (휴방일 포함)
    function highlightEmptyDays(streamerId, streamerNickname) {
        const allDays = document.querySelectorAll('.calendar-day');
        
        allDays.forEach(day => {
            const dayEvents = day.querySelectorAll('.event-item');
            let hasStreamerNormalEvent = false;
            
            // 해당 스트리머의 일반 일정(휴방 제외)이 있는지 확인
            dayEvents.forEach(event => {
                const eventStreamerId = event.getAttribute('data-streamer-id');
                const eventStreamerNickname = event.getAttribute('data-streamer-nickname');
                const eventType = event.querySelector('.event-type-badge')?.textContent;
                
                if (eventStreamerId === streamerId && eventStreamerNickname === streamerNickname) {
                    // 휴방이 아닌 일반 일정이 있는지 확인
                    if (eventType !== '휴방') {
                        hasStreamerNormalEvent = true;
                    }
                }
            });
            
            // 해당 스트리머의 일반 일정이 없는 날은 빨갛게 강조
            if (!hasStreamerNormalEvent) {
                day.classList.add('empty-day');
            }
        });
    }



    // 해당 멤버의 스크롤 영역 밖에 일정이 있으면 화살표 표시 (스크롤 전 초기 표시용)
    function showScrollIndicatorsForStreamer(streamerId, streamerNickname) {
        const allDays = document.querySelectorAll('.calendar-day');
        
        allDays.forEach(day => {
            const dayEvents = day.querySelectorAll('.event-item');
            const dayEventsContainer = day.querySelector('.day-events');
            
            if (!dayEventsContainer) return;
            
            // 해당 스트리머의 일정들 찾기
            const streamerEvents = Array.from(dayEvents).filter(event => {
                const eventStreamerId = event.getAttribute('data-streamer-id');
                const eventStreamerNickname = event.getAttribute('data-streamer-nickname');
                return eventStreamerId === streamerId && eventStreamerNickname === streamerNickname;
            });
            
            if (streamerEvents.length === 0) return;
            
            // 스크롤 영역의 높이와 스크롤 위치 확인
            const containerHeight = dayEventsContainer.clientHeight;
            const scrollHeight = dayEventsContainer.scrollHeight;
            
            // 스크롤이 가능한지 확인 (내용이 컨테이너보다 높을 때)
            if (scrollHeight > containerHeight) {
                const containerRect = dayEventsContainer.getBoundingClientRect();
                
                // 위쪽/아래쪽에 숨겨진 일정이 있는지 확인
                let hasHiddenEventsAbove = false;
                let hasHiddenEventsBelow = false;
                
                // 해당 멤버의 일정들을 시간순으로 정렬
                const sortedStreamerEvents = streamerEvents.sort((a, b) => {
                    const timeA = a.querySelector('.event-time')?.textContent || '';
                    const timeB = b.querySelector('.event-time')?.textContent || '';
                    return timeA.localeCompare(timeB);
                });
                
                // 첫 번째와 마지막 일정 확인
                const firstEvent = sortedStreamerEvents[0];
                const lastEvent = sortedStreamerEvents[sortedStreamerEvents.length - 1];
                
                if (firstEvent && lastEvent) {
                    const firstEventRect = firstEvent.getBoundingClientRect();
                    const lastEventRect = lastEvent.getBoundingClientRect();
                    
                    // 첫 번째 일정이 위쪽에 숨겨져 있는지 확인
                    hasHiddenEventsAbove = firstEventRect.bottom < containerRect.top;
                    
                    // 마지막 일정이 아래쪽에 숨겨져 있는지 확인
                    hasHiddenEventsBelow = lastEventRect.top > containerRect.bottom;
                }
                
                // 기존 화살표 제거
                removeScrollIndicator(day);
                
                // 화살표 표시
                if (hasHiddenEventsAbove) {
                    const topIndicator = createScrollIndicator('top', '위쪽에 더 많은 일정이 있습니다');
                    showScrollIndicator(topIndicator, day, 'top');
                }
                
                if (hasHiddenEventsBelow) {
                    const bottomIndicator = createScrollIndicator('bottom', '아래쪽에 더 많은 일정이 있습니다');
                    showScrollIndicator(bottomIndicator, day, 'bottom');
                }
            }
        });
    }



    // 주간 변경
    window.changeWeek = function(days) {
        anchorDate.setDate(anchorDate.getDate() + days);
        loadCalendar();
    };


    // 캘린더 로드
    window.loadCalendar = async function() {
        const container = document.getElementById('sooplive-calendar');
        if (!container) return;

        container.innerHTML = '<div class="loading-message">일정을 불러오는 중...</div>';

        try {
            const groupId = getGroupId();
            if (!groupId) {
                container.innerHTML = '<div style="color: red; padding: 20px;">groupId를 찾을 수 없습니다.</div>';
                return;
            }

            const streamers = await getStreamers(groupId);
            if (streamers.length === 0) {
                container.innerHTML = '<div style="text-align: center; padding: 20px;">즐겨찾기 스트리머가 없습니다.</div>';
                return;
            }

            // 현재 날짜를 기준으로 주 계산
            const weekStart = startOfWeek(anchorDate);
            const allEvents = [];
            
            for (const streamer of streamers) {
                const { userId, nickname } = streamer;
                
                const events = await getEvents(userId, weekStart);
                
                for (const event of events) {
                    allEvents.push({
                        ...event,
                        streamerNickname: nickname,
                        userId: userId,
                        streamerId: userId,
                        title: `${nickname}:${event.title}`
                    });
                }
            }
            container.innerHTML = createCalendarHTML(allEvents, weekStart);

            // 이벤트 리스너 추가
            const prevBtn = document.getElementById('prev-week-btn');
            const nextBtn = document.getElementById('next-week-btn');
            
            if (prevBtn) {
                prevBtn.addEventListener('click', () => changeWeek(-7));
            }
            
            if (nextBtn) {
                nextBtn.addEventListener('click', () => changeWeek(7));
            }
            
            
            // 마우스 오버 이벤트 추가
            addHoverEvents();
            
            // 고정된 멤버가 있으면 강조 적용
            if (fixedStreamer) {
                applyFixedStreamerHighlight();
            }
        } catch (error) {
            let errorMessage = `오류: ${error.message}`;
            let troubleshooting = '';
            
            if (error.message.includes('Failed to fetch') || error.message.includes('네트워크 오류')) {
                errorMessage = '네트워크 연결 오류가 발생했습니다.';
                troubleshooting = `
                    <div style="margin-top: 10px; padding: 10px; background: #fff3cd; border-radius: 4px; font-size: 12px;">
                        <strong>해결 방법:</strong><br>
                        1. 인터넷 연결을 확인해주세요<br>
                        2. sooplive.co.kr에 로그인되어 있는지 확인해주세요<br>
                        3. 페이지를 새로고침해보세요
                    </div>
                `;
            } else if (error.message.includes('403') || error.message.includes('인증 오류')) {
                errorMessage = '인증이 필요합니다. 로그인 상태를 확인해주세요.';
                troubleshooting = `
                    <div style="margin-top: 10px; padding: 10px; background: #f8d7da; border-radius: 4px; font-size: 12px;">
                        <strong>403 Forbidden 오류 해결 방법:</strong><br>
                        1. <strong>sooplive.co.kr에 로그인</strong>되어 있는지 확인<br>
                        2. 로그인 세션이 만료되었다면 <strong>다시 로그인</strong><br>
                        3. 브라우저 쿠키가 활성화되어 있는지 확인
                    </div>
                `;
            }
            
            container.innerHTML = `
                <div style="color: red; padding: 20px;">
                    ${errorMessage}
                    ${troubleshooting}
                </div>
            `;
        }
    };

    // 캘린더 컨테이너 추가
    function addCalendarContainer() {
        if (document.getElementById('sooplive-calendar')) {
            return;
        }
        
        // groupId가 없으면 캘린더를 표시하지 않음
        const groupId = getGroupId();
        if (!groupId) {
            return;
        }
        
        let strmArea = document.querySelector('.strm_area');
        
        if (!strmArea) {
            setTimeout(addCalendarContainer, TIMING.INIT_RETRY_DELAY);
            return;
        }

        const calendarContainer = document.createElement('div');
        calendarContainer.id = 'sooplive-calendar';

        strmArea.parentNode.insertBefore(calendarContainer, strmArea.nextSibling);
        
        // 캘린더 전체 영역에서 마우스가 벗어났을 때 모든 효과 제거
        calendarContainer.addEventListener('mouseleave', function() {
            clearAllHighlights();
        });
        
        // 문서 전체에서 마우스가 캘린더 영역을 벗어났을 때도 효과 제거
        document.addEventListener('mouseleave', function(e) {
            const calendar = document.getElementById('sooplive-calendar');
            if (calendar && !calendar.contains(e.relatedTarget)) {
                clearAllHighlights();
            }
        });
        
        loadCalendar();
    }

    // URL 변경 감지
    function checkUrlChange() {
        if (window.location.href !== currentUrl) {
            currentUrl = window.location.href;
            
            // 기존 캘린더 제거
            const existingCalendar = document.getElementById('sooplive-calendar');
            if (existingCalendar) {
                existingCalendar.remove();
            }
            
            // 새 캘린더 추가
            setTimeout(addCalendarContainer, TIMING.SPA_NAVIGATION_DELAY * 5);
        }
    }

    // 초기화
    function init() {
        addCalendarContainer();
    }

    // 실행
    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', init);
    } else {
        init();
    }

    setTimeout(init, TIMING.INIT_RETRY_DELAY);

    // URL 변경 감지 시작 (SPA 대응)
    setInterval(checkUrlChange, TIMING.URL_CHECK_INTERVAL);
    
    // popstate 이벤트 리스너 (뒤로가기/앞으로가기 대응)
    window.addEventListener('popstate', () => {
        setTimeout(() => {
            checkUrlChange();
        }, TIMING.SPA_NAVIGATION_DELAY);
    });

})();