/** * 어나운스 바 배경색 프리셋 * @type {ReadonlyArray<{ id: string, label: string, value: string, textColor: string }>} */ export const ANNOUNCEMENT_BACKGROUND_PRESETS = [ { id: 'black', label: '검정', value: '#15171a', textColor: '#ffffff' }, { id: 'white', label: '흰색', value: '#ffffff', textColor: '#15171a' }, { id: 'accent', label: '브랜드', value: '#ff4f2e', textColor: '#ffffff' } ] /** @type {string} 기본 어나운스 바 배경색 */ export const DEFAULT_ANNOUNCEMENT_BACKGROUND_COLOR = '#15171a' /** @type {string} 기본 어나운스 바 정렬 */ export const DEFAULT_ANNOUNCEMENT_ALIGNMENT = 'center' /** * 어나운스 바 정렬 옵션 * @type {ReadonlyArray<{ value: string, label: string }>} */ export const ANNOUNCEMENT_ALIGNMENT_OPTIONS = [ { value: 'center', label: '중앙' }, { value: 'left', label: '왼쪽' } ] /** * 어나운스 바 배경색 형식이 올바른지 확인한다. * @param {unknown} value - hex 색상 * @returns {boolean} 유효 여부 */ export const isValidAnnouncementBackgroundColor = (value) => /^#(?:[0-9a-f]{3}|[0-9a-f]{6})$/i.test(String(value || '').trim()) /** * 어나운스 바 배경색을 6자리 hex 값으로 정규화한다. * @param {unknown} value - 입력 색상 * @returns {string} 정규화된 색상 */ export const normalizeAnnouncementBackgroundColor = (value) => { const color = String(value || '').trim().toLowerCase() if (!isValidAnnouncementBackgroundColor(color)) { return DEFAULT_ANNOUNCEMENT_BACKGROUND_COLOR } if (color.length === 4) { return `#${color[1]}${color[1]}${color[2]}${color[2]}${color[3]}${color[3]}` } return color } /** * 어나운스 바 정렬 값을 정리한다. * @param {unknown} value - 입력 정렬 * @returns {string} 정리된 정렬 */ export const normalizeAnnouncementAlignment = (value) => { const alignment = String(value || '').trim().toLowerCase() return ANNOUNCEMENT_ALIGNMENT_OPTIONS.some((option) => option.value === alignment) ? alignment : DEFAULT_ANNOUNCEMENT_ALIGNMENT } /** * 어나운스 바 배경색에 맞는 전경색을 반환한다. * @param {string} backgroundColor - hex 배경색 * @returns {string} 전경 hex 색상 */ export const getAnnouncementBarTextColor = (backgroundColor) => { const normalized = normalizeAnnouncementBackgroundColor(backgroundColor) const preset = ANNOUNCEMENT_BACKGROUND_PRESETS.find((item) => item.value.toLowerCase() === normalized) if (preset) { return preset.textColor } const hex = normalized.slice(1) const red = parseInt(hex.slice(0, 2), 16) const green = parseInt(hex.slice(2, 4), 16) const blue = parseInt(hex.slice(4, 6), 16) const luminance = (red * 299 + green * 587 + blue * 114) / 1000 return luminance > 150 ? '#15171a' : '#ffffff' } /** * 어나운스 링크를 정리한다. 빈 값은 링크 미사용. * @param {string} url - 입력 URL * @returns {string} 정리된 URL 또는 빈 문자열 */ export const normalizeAnnouncementUrl = (url) => { const trimmed = (url || '').trim() if (!trimmed) { return '' } if (trimmed.startsWith('/')) { return trimmed } try { const parsed = new URL(trimmed) if (parsed.protocol === 'http:' || parsed.protocol === 'https:') { return parsed.toString() } } catch { return '' } return '' } /** @type {string} 어나운스 바 7일 숨김 localStorage 키 */ export const ANNOUNCEMENT_DISMISS_STORAGE_KEY = 'SITE_ANNOUNCEMENT_DISMISS' /** @type {string} 어나운스 바 이번 방문(세션) 숨김 sessionStorage 키 */ export const ANNOUNCEMENT_SESSION_DISMISS_KEY = 'SITE_ANNOUNCEMENT_SESSION_DISMISS' /** @type {number} 기본 숨김 일수 */ export const ANNOUNCEMENT_SNOOZE_DAYS = 7 /** * 어나운스 바 닫기 식별 키(설정 저장 시각) * @param {{ updatedAt?: string | null }} settings - 사이트 설정 * @returns {string} 식별 키 */ export const getAnnouncementDismissKey = (settings) => settings?.updatedAt || '' /** * 어나운스 바가 숨김 상태인지 확인한다. * @param {{ updatedAt?: string | null }} settings - 사이트 설정 * @returns {boolean} 숨김 여부 */ export const isAnnouncementDismissed = (settings) => { if (!import.meta.client) { return false } const key = getAnnouncementDismissKey(settings) if (!key) { return false } const sessionDismissed = sessionStorage.getItem(ANNOUNCEMENT_SESSION_DISMISS_KEY) if (sessionDismissed === key) { return true } const raw = localStorage.getItem(ANNOUNCEMENT_DISMISS_STORAGE_KEY) if (!raw) { return false } try { const parsed = JSON.parse(raw) if (parsed?.key !== key) { return false } if (typeof parsed.until === 'number' && Date.now() < parsed.until) { return true } if (parsed.until == null && parsed.key === key) { return true } } catch { return false } return false } /** * 이번 브라우저 방문(세션) 동안만 어나운스 바를 숨긴다. * @param {{ updatedAt?: string | null }} settings - 사이트 설정 * @returns {void} */ export const dismissAnnouncementForSession = (settings) => { if (!import.meta.client) { return } const key = getAnnouncementDismissKey(settings) if (!key) { return } sessionStorage.setItem(ANNOUNCEMENT_SESSION_DISMISS_KEY, key) } /** * 지정 일수 동안 어나운스 바를 숨긴다. * @param {{ updatedAt?: string | null }} settings - 사이트 설정 * @param {number} [days=ANNOUNCEMENT_SNOOZE_DAYS] - 숨김 일수 * @returns {void} */ export const dismissAnnouncementForDays = (settings, days = ANNOUNCEMENT_SNOOZE_DAYS) => { if (!import.meta.client) { return } const key = getAnnouncementDismissKey(settings) if (!key) { return } const until = Date.now() + days * 24 * 60 * 60 * 1000 localStorage.setItem(ANNOUNCEMENT_DISMISS_STORAGE_KEY, JSON.stringify({ key, until })) sessionStorage.setItem(ANNOUNCEMENT_SESSION_DISMISS_KEY, key) }