아이콘 버튼의 렌더링/위치 스타일을 보정해 원래 레이아웃 감각을 복원하고, 저장 제한 환경에서도 메뉴 스크립트가 중단되지 않도록 예외 처리를 추가했습니다. Made-with: Cursor
512 lines
31 KiB
HTML
512 lines
31 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="ko">
|
|
<head>
|
|
<meta charset="UTF-8" />
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
<title>zenn.clock</title>
|
|
<link rel="icon" href="./favicon/favicon-16x16.png" sizes="16x16" type="image/png">
|
|
<link rel="icon" href="./favicon/favicon-32x32.png" sizes="32x32" type="image/png">
|
|
<link rel="icon" href="./favicon/favicon-96x96.png" sizes="96x96" type="image/png">
|
|
<link rel="icon" href="./favicon/favicon-128x128.png" sizes="128x128" type="image/png">
|
|
<link rel="icon" href="./favicon/favicon-196x196.png" sizes="196x196" type="image/png">
|
|
<link href="https://spoqa.github.io/spoqa-han-sans/css/SpoqaHanSansNeo.css" rel="stylesheet" type="text/css" />
|
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
<link href="https://fonts.googleapis.com/css2?family=Poetsen+One&display=swap" rel="stylesheet">
|
|
</head>
|
|
<style>
|
|
/* Theme */
|
|
:root { --bg-color: #262626; --main-text-color: #fff; --sub-text-color: #aaa; }
|
|
.YORUNOSEIJAKU { --bg-color: #000; --main-text-color: #fff; --sub-text-color: #aaa; }
|
|
.MEIRYONASENGEN { --bg-color: #fff; --main-text-color: #000; --sub-text-color: #262626; }
|
|
.OUKARANMAN { --bg-color: #fce9ea; --main-text-color: #f6bdc5; --sub-text-color: #ea5e75; }
|
|
.HANAZUKIYO { --bg-color: #172d5d; --main-text-color: #fef5f3; --sub-text-color: #fde2a8; }
|
|
.TOUGENKYOU { --bg-color: #fff6e4; --main-text-color: #d64565; --sub-text-color: #f19db8; }
|
|
.SAYOSHIGURE { --bg-color: #53456b; --main-text-color: #003840; --sub-text-color: #6097b0; }
|
|
.AKATSUKI { --bg-color: #342438; --main-text-color: #aa1f32; --sub-text-color: #ea8c21; }
|
|
.YAMASHITATARU { --bg-color: #00512e; --main-text-color: #c2da75; --sub-text-color: #018573; }
|
|
.TSUKINOSHIZUKU { --bg-color: #e7ebf6; --main-text-color: #82a0b3; --sub-text-color: #3c6f98; }
|
|
.GINPA { --bg-color: #c6ceda; --main-text-color: #375578; --sub-text-color: #5a7f8e; }
|
|
.GEKKABIJIN { --bg-color: #2c2f3d; --main-text-color: #fdfce2; --sub-text-color: #e3cb66; }
|
|
.MONAKANOTSUKI { --bg-color: #212121; --main-text-color: #fee59f; --sub-text-color: #fcfdf3; }
|
|
|
|
body { background-color: var(--bg-color); color: var(--main-text-color); cursor: crosshair; display: flex; justify-content: center; align-items: center; width: 100vw; height: 100vh; margin: 0; font-family: 'Spoqa Han Sans Neo', monospace; font-weight: 900; transition: 0.9s; }
|
|
.sub-text { color: var(--sub-text-color); }
|
|
|
|
.show-hide { transition: opacity 0.5s ease, visibility 0.5s ease; opacity: 1; visibility: visible; }
|
|
.show-hide.hidden { opacity: 0; visibility: hidden; }
|
|
|
|
.clock-container { position: relative; text-align: center; user-select: none; }
|
|
.date { margin: 0 auto; font-size: 12px; margin-bottom: 10px; letter-spacing: 2px; display: flex; flex-direction: column; justify-content: center; width: 92px; }
|
|
.time-area { display: flex; flex-direction: column; gap: 2rem; position: relative; }
|
|
.time-counter { width: 100%; text-align: center; letter-spacing: 0.75rem; font-size: 6rem; }
|
|
.section { position: relative; }
|
|
.time-unit { width: 2rem; font-size: 12px; margin-top: 5px; text-align: left; position: absolute; right: -2rem; bottom: 1rem; }
|
|
.am-pm { font-size: 12px; position: absolute; left: -2rem; bottom: 92%; letter-spacing: 2px; }
|
|
.section { position: relative; }
|
|
.app-title { position: fixed; left: 1rem; top: 1rem; user-select: none; }
|
|
.app-copyright { display: none; position: fixed; right: 1rem; bottom: 1rem; user-select: none; color: var(--sub-text-color); }
|
|
.app-theme { position: fixed; right: 1rem; top: 1rem; user-select: none; color: var(--sub-text-color); transition: 0.9s; }
|
|
.app-icons-container { position: fixed; left: 1rem; bottom: 1rem; user-select: none; cursor: pointer; color: var(--main-text-color); }
|
|
.app-buttons { display: flex; flex-direction: column; gap: 0.5rem}
|
|
.app-icon-button { background: none; border: none; color: inherit; cursor: pointer; display: inline-flex; align-items: center; justify-content: center; }
|
|
.app-icon-button:focus-visible { outline: 2px solid var(--sub-text-color); outline-offset: 2px; border-radius: 8px; }
|
|
.app-icon-button svg { width: 24px; height: 24px; display: block; flex-shrink: 0; }
|
|
.app-full { width: 24px; height: 24px; padding: 1rem; }
|
|
.app-book { width: 24px; height: 24px; padding: 1rem; }
|
|
.app-cog { width: 24px; height: 24px; padding: 1rem; }
|
|
.app-next { width: 24px; height: 24px; padding: 1rem; }
|
|
.link { text-decoration: none; color: var(--main-text-color); }
|
|
|
|
.theme-text { position: absolute; right: 0; transition: opacity 0.9s ease; opacity: 1; }
|
|
.theme-text.hidden { opacity: 0; }
|
|
/* 모달 */
|
|
.modal-content { display: flex; flex-direction: column; justify-content: center; }
|
|
.modal-guide-line { font-size: 14px; font-weight: bold; letter-spacing: 1.5px; color: var(--main-text-color); }
|
|
.modal-heading { margin-top: 24px; font-size: 28px; font-weight: bold; color: var(--sub-text-color); }
|
|
.modal-close-button { background-color: var(--sub-text-color); color: var(--bg-color); font-size: 14px; font-weight: 600; width: 100%; max-width: 320px; min-height: 44px; margin: 0 auto; display: grid; place-items: center; border-radius: 2rem; text-align: center; cursor: pointer; margin-top: 2rem; border: none; }
|
|
|
|
.modal-close-button:hover { background-color: var(--main-text-color); }
|
|
.modal { position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); display: flex; flex-direction: column; gap: 1rem; padding: 2rem 3rem; overflow-y: auto; border-radius: 20px; box-shadow: 0px 10px 30px rgba(0, 0, 0, 0.1); width: 60%; max-width: 600px; max-height: 80%; margin: 0 auto; background-color: var(--bg-color); border: 1px solid var(--sub-text-color); opacity: 1; transition: 0.9s; z-index: 50; }
|
|
.modal.hidden { opacity: 0; z-index: -50; }
|
|
|
|
.modal-overlay { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background-color: rgba(0, 0, 0, 0.6); z-index: 40; }
|
|
.modal-overlay.hidden { opacity: 0; z-index: -10; }
|
|
.red { color: red; }
|
|
|
|
.font-style-1 { font-family: 'Arial', sans-serif; }
|
|
.font-style-2 { font-family: 'Georgia', serif; }
|
|
.font-style-3 { font-family: 'Courier New', monospace; }
|
|
.font-style-4 { font-family: 'Verdana', sans-serif; }
|
|
.font-style-5 { font-family: 'Times New Roman', serif; }
|
|
.font-style-6 { font-family: "Poetsen One", serif; }
|
|
|
|
.font-selector, .style-selector, .fontSize-selector { margin: 20px 0; font-size: 14px;}
|
|
#fontDropdown, #styleDropdown, #fontSizeDropdown { margin-left: 4px; padding: 8px 12px; font-size: 16px; border-radius: 5px; border: 1px solid #ccc; }
|
|
|
|
.toggle-container { display: flex; align-items: center; gap: 10px; margin: 20px 0; }
|
|
/* #hideInfo { padding: 10px 20px; background-color: #4CAF50; color: white; border: none; border-radius: 5px; cursor: pointer; font-size: 16px; } */
|
|
|
|
|
|
#timeSeparator { display: none; }
|
|
/* 개선된 Options 디자인 */
|
|
.options {
|
|
display: none;
|
|
position: fixed;
|
|
bottom: -100%; /* 시작 위치 */
|
|
left: 50%;
|
|
transform: translateX(-50%);
|
|
width: 90%;
|
|
max-width: 500px;
|
|
background: rgba(255, 255, 255, 0.1); /* 유리 효과 배경 */
|
|
backdrop-filter: blur(20px);
|
|
-webkit-backdrop-filter: blur(20px);
|
|
border: 1px solid rgba(255, 255, 255, 0.2);
|
|
border-radius: 30px 30px 0 0;
|
|
padding: 2rem;
|
|
box-sizing: border-box;
|
|
transition: bottom 0.5s cubic-bezier(0.4, 0, 0.2, 1);
|
|
z-index: 100;
|
|
}
|
|
|
|
.options.show {
|
|
display: block;
|
|
bottom: 0;
|
|
}
|
|
|
|
.optionsContianer {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 1.5rem;
|
|
}
|
|
|
|
/* min-width: 600 */
|
|
@media screen and (min-width: 600px) {
|
|
.date { font-size: 1.5vw; flex-direction: row; gap: 0.5rem; width: initial;}
|
|
.time-area { flex-direction: row; gap: 5rem; }
|
|
.time-counter { width: 20vw; font-size: 15vw; }
|
|
.time-unit { font-size: 1vw; position: initial; text-align: center; margin: 0 auto; }
|
|
.am-pm { font-size: 1vw; left: -3vw; bottom: 5vw; }
|
|
.app-copyright { display: block; }
|
|
.modal-content { align-items: center; }
|
|
#timeSeparator.show { display: block; }
|
|
.options { bottom: 2rem; border-radius: 24px; width: 400px; }
|
|
.options.show { bottom: 2rem; }
|
|
.app-buttons { flex-direction: row; }
|
|
}
|
|
|
|
.button { width: 80px; display: inline-block; background-color: #007bff; color: #fff; font-size: 1rem; font-weight: bold; border: none; border-radius: 5px; padding: 0.5rem 1rem; cursor: pointer; transition: background-color 0.3s, transform 0.2s; text-align: center; }
|
|
.button:hover { background-color: #0056b3; transform: scale(1.05); }
|
|
.button:active { background-color: #004085; transform: scale(0.95) }
|
|
.toggle-button { background-color: #28a745; }
|
|
.toggle-button:hover { background-color: #218838; }
|
|
.toggle-button:active { background-color: #1e7e34; }
|
|
.close-button { background-color: #dc3545; grid-area: 2 / 3 / 3 / 4; margin: 20px 0; }
|
|
.close-button:hover { background-color: #c82333; }
|
|
.close-button:active { background-color: #bd2130; }
|
|
#hideInfo.off { background-color: #f44336; }
|
|
#separatorPosition.off { background-color: #f44336; }
|
|
.label { font-size: 16px; font-weight: bold; }
|
|
|
|
/* 드롭다운 스타일 커스텀 */
|
|
select {
|
|
appearance: none;
|
|
background: rgba(255, 255, 255, 0.1);
|
|
border: 1px solid var(--sub-text-color);
|
|
color: var(--main-text-color);
|
|
padding: 8px 16px;
|
|
border-radius: 12px;
|
|
font-family: inherit;
|
|
cursor: pointer;
|
|
outline: none;
|
|
}
|
|
|
|
.selector-group {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
}
|
|
|
|
/* 토글 버튼 세련되게 변경 */
|
|
.button.toggle-button {
|
|
width: 60px;
|
|
height: 30px;
|
|
border-radius: 20px;
|
|
font-size: 11px;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
transition: 0.3s;
|
|
background: rgba(255, 255, 255, 0.1);
|
|
border: 1px solid var(--sub-text-color);
|
|
color: var(--main-text-color);
|
|
}
|
|
|
|
.button.toggle-button.on {
|
|
background: var(--main-text-color);
|
|
color: var(--bg-color);
|
|
}
|
|
|
|
/* 닫기 버튼 */
|
|
.close-button {
|
|
width: 100%;
|
|
background: var(--sub-text-color);
|
|
color: var(--bg-color);
|
|
border-radius: 15px;
|
|
margin-top: 10px;
|
|
height: 50px;
|
|
}
|
|
|
|
</style>
|
|
|
|
|
|
|
|
<body>
|
|
<div class="clock-container">
|
|
<div class="date sub-text not-info" id="date">
|
|
<div id="date-md"></div>
|
|
<div id="date-wd"></div>
|
|
</div>
|
|
<div class="time-area">
|
|
<div class="section hours-section">
|
|
<div id="hours" class="time-counter main-text">00</div>
|
|
<div class="time-unit sub-text not-info">HOUR</div>
|
|
</div>
|
|
<div id="timeSeparator" class="time-counter main-text">:</div>
|
|
<div class="section minutes-section">
|
|
<div id="minutes" class="time-counter main-text">00</div>
|
|
<div class="time-unit sub-text not-info">MIN</div>
|
|
</div>
|
|
<div class="section seconds-section">
|
|
<div id="seconds" class="time-counter main-text">00</div>
|
|
<div class="time-unit sub-text not-info">SEC</div>
|
|
</div>
|
|
<div id="ampm" class="am-pm sub-text">AM</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="options">
|
|
<div class="optionsContianer">
|
|
<h3 style="margin: 0 0 10px 0; font-size: 18px; opacity: 0.8;">Settings</h3>
|
|
|
|
<div class="selector-group">
|
|
<label class="label">폰트</label>
|
|
<select id="fontDropdown" onchange="changeFont(this.value)">
|
|
<option value="6">Poetsen One</option>
|
|
<option value="1">Arial</option>
|
|
<option value="3">Courier New</option>
|
|
</select>
|
|
</div>
|
|
|
|
<div class="selector-group">
|
|
<label class="label">스타일</label>
|
|
<select id="styleDropdown" onchange="changeStyle(this.value)">
|
|
<option value="hms12">12H (SEC)</option>
|
|
<option value="hms24">24H (SEC)</option>
|
|
<option value="hm12">12H</option>
|
|
<option value="hm24">24H</option>
|
|
</select>
|
|
</div>
|
|
|
|
<div class="selector-group">
|
|
<label class="label">부가정보</label>
|
|
<button id="hideInfo" class="button toggle-button on">ON</button>
|
|
</div>
|
|
|
|
<div class="selector-group">
|
|
<label class="label">콜론 위치</label>
|
|
<button id="separatorPosition" class="button toggle-button off">OFF</button>
|
|
</div>
|
|
|
|
<button class="button close-button" onclick="closeOptions()">DONE</button>
|
|
</div>
|
|
</div>
|
|
<div class="show-hide app-title sub-text">zenn.clock</div>
|
|
<div class="show-hide app-copyright sub-text">copyrights <a href="https://x.com/zennbox" target="_blank" class="link">zenn</a>. all rights reserved.</div>
|
|
<div class="show-hide app-theme sub-text">
|
|
<div class="theme-text" id="current-theme"></div>
|
|
<div class="theme-text hidden" id="next-theme"></div>
|
|
</div>
|
|
<div class="show-hide app-icons-container">
|
|
<div class="app-buttons">
|
|
<button type="button" class="app-icon-button app-full" aria-label="전체 화면 전환"><svg id="enter-fullscreen-icon" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-6"><path stroke-linecap="round" stroke-linejoin="round" d="M3.75 3.75v4.5m0-4.5h4.5m-4.5 0L9 9M3.75 20.25v-4.5m0 4.5h4.5m-4.5 0L9 15M20.25 3.75h-4.5m4.5 0v4.5m0-4.5L15 9m5.25 11.25h-4.5m4.5 0v-4.5m0 4.5L15 15" /></svg><svg id="exit-fullscreen-icon" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-6"><path stroke-linecap="round" stroke-linejoin="round" d="M9 9V4.5M9 9H4.5M9 9 3.75 3.75M9 15v4.5M9 15H4.5M9 15l-5.25 5.25M15 9h4.5M15 9V4.5M15 9l5.25-5.25M15 15h4.5M15 15v4.5m0-4.5 5.25 5.25" /></svg></button>
|
|
<button type="button" class="app-icon-button app-book" aria-label="사용 방법 열기"><svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-6"><path stroke-linecap="round" stroke-linejoin="round" d="M12 6.042A8.967 8.967 0 0 0 6 3.75c-1.052 0-2.062.18-3 .512v14.25A8.987 8.987 0 0 1 6 18c2.305 0 4.408.867 6 2.292m0-14.25a8.966 8.966 0 0 1 6-2.292c1.052 0 2.062.18 3 .512v14.25A8.987 8.987 0 0 0 18 18a8.967 8.967 0 0 0-6 2.292m0-14.25v14.25" /></svg></button>
|
|
<button type="button" class="app-icon-button app-cog" aria-label="설정 열기"><svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-6"><path stroke-linecap="round" stroke-linejoin="round" d="M10.5311 5.04576C10.9571 3.28976 13.4551 3.28976 13.8811 5.04576C13.945 5.30956 14.0703 5.55454 14.2468 5.76076C14.4233 5.96698 14.6459 6.12861 14.8967 6.23251C15.1475 6.3364 15.4192 6.37961 15.6898 6.35863C15.9605 6.33765 16.2223 6.25306 16.4541 6.11176C17.9971 5.17176 19.7641 6.93776 18.8241 8.48176C18.683 8.71342 18.5985 8.9751 18.5776 9.24553C18.5566 9.51597 18.5998 9.78753 18.7036 10.0381C18.8074 10.2887 18.9688 10.5113 19.1748 10.6878C19.3808 10.8643 19.6255 10.9897 19.8891 11.0538C21.6451 11.4798 21.6451 13.9778 19.8891 14.4038C19.6253 14.4677 19.3803 14.593 19.1741 14.7695C18.9678 14.946 18.8062 15.1686 18.7023 15.4194C18.5984 15.6702 18.5552 15.9419 18.5762 16.2125C18.5972 16.4832 18.6818 16.745 18.8231 16.9768C19.7631 18.5198 17.9971 20.2868 16.4531 19.3468C16.2214 19.2057 15.9597 19.1212 15.6893 19.1003C15.4188 19.0793 15.1473 19.1225 14.8967 19.2263C14.6461 19.3301 14.4235 19.4915 14.247 19.6975C14.0705 19.9035 13.9451 20.1482 13.8811 20.4118C13.4551 22.1678 10.9571 22.1678 10.5311 20.4118C10.4671 20.148 10.3418 19.903 10.1653 19.6968C9.98885 19.4905 9.76617 19.3289 9.5154 19.225C9.26464 19.1211 8.99289 19.0779 8.72227 19.0989C8.45165 19.1199 8.18981 19.2045 7.95805 19.3458C6.41505 20.2858 4.64805 18.5198 5.58805 16.9758C5.72916 16.7441 5.81361 16.4824 5.83454 16.212C5.85547 15.9416 5.8123 15.67 5.70853 15.4194C5.60476 15.1688 5.44332 14.9462 5.23733 14.7697C5.03134 14.5932 4.78662 14.4679 4.52305 14.4038C2.76705 13.9778 2.76705 11.4798 4.52305 11.0538C4.78685 10.9898 5.03183 10.8645 5.23805 10.688C5.44427 10.5116 5.60591 10.2889 5.7098 10.0381C5.81369 9.78734 5.85691 9.51559 5.83592 9.24497C5.81494 8.97435 5.73036 8.71251 5.58905 8.48076C4.64905 6.93776 6.41505 5.17076 7.95905 6.11076C8.95505 6.71876 10.2551 6.18076 10.5311 5.04576Z" stroke-linecap="round" stroke-linejoin="round"/><path d="M15.2061 12.7288C15.2061 13.5244 14.89 14.2875 14.3274 14.8501C13.7648 15.4127 13.0017 15.7288 12.2061 15.7288C11.4104 15.7288 10.6473 15.4127 10.0847 14.8501C9.52212 14.2875 9.20605 13.5244 9.20605 12.7288C9.20605 11.9331 9.52212 11.17 10.0847 10.6074C10.6473 10.0448 11.4104 9.72876 12.2061 9.72876C13.0017 9.72876 13.7648 10.0448 14.3274 10.6074C14.89 11.17 15.2061 11.9331 15.2061 12.7288Z" /></svg></button>
|
|
<button type="button" class="app-icon-button app-next" aria-label="다음 테마"><svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-6"><path stroke-linecap="round" stroke-linejoin="round" d="m8.25 4.5 7.5 7.5-7.5 7.5" /></svg></button>
|
|
</div>
|
|
</div>
|
|
<div class="modal hidden">
|
|
<div class="modal-content">
|
|
<h2 class="modal-heading">사용방법</h2>
|
|
<p class="modal-guide-line">
|
|
1. 마우스 [클릭] 또는 [SPACE] 버튼으로 메뉴를 보이게 할 수 있습니다.
|
|
</p>
|
|
<p class="modal-guide-line">2. [F] 키를 눌러 전체 화면모드로 변경할 수 있습니다.</p>
|
|
<p class="modal-guide-line">
|
|
3. '[' 키 또는 ']'키를 눌러 테마를 변경할 수 있습니다.
|
|
</p>
|
|
<p class="modal-guide-line">
|
|
<span class="red">*</span>
|
|
zenn.clock은 800px 이상의 화면을 대상으로 합니다.
|
|
</p>
|
|
<button type="button" class="modal-close-button" onclick="closeModal()">닫기</button>
|
|
</div>
|
|
</div>
|
|
<div class="modal-overlay hidden" onclick="closeModal()"></div>
|
|
|
|
|
|
|
|
<script>
|
|
|
|
const styles = ['hms12', 'hms24', 'hm12', 'hm24'];
|
|
const STORAGE_KEY = 'zenn.clock.settings.v2';
|
|
const DEFAULT_SETTINGS = {
|
|
themeIndex: 0,
|
|
fontNumber: '6',
|
|
style: 'hms12',
|
|
hideInfo: false,
|
|
separatorCompact: false,
|
|
};
|
|
let currentSettings = { ...DEFAULT_SETTINGS };
|
|
let selectedStyle = DEFAULT_SETTINGS.style;
|
|
const dateMonthDay = document.getElementById('date-md');
|
|
const dateWeekday = document.getElementById('date-wd');
|
|
const fontDropdown = document.getElementById('fontDropdown');
|
|
const styleDropdown = document.getElementById('styleDropdown');
|
|
const hideInfoButton = document.getElementById('hideInfo');
|
|
const separatorPosition = document.getElementById('separatorPosition');
|
|
const subTextElements = document.querySelectorAll('.sub-text.not-info');
|
|
|
|
function saveSettings() {
|
|
try {
|
|
localStorage.setItem(STORAGE_KEY, JSON.stringify(currentSettings));
|
|
} catch (error) {
|
|
// Storage가 막힌 환경(사파리 프라이빗 모드 등)에서는 저장만 건너뛴다.
|
|
}
|
|
}
|
|
|
|
function loadSettings() {
|
|
try {
|
|
const saved = JSON.parse(localStorage.getItem(STORAGE_KEY));
|
|
if (!saved) {
|
|
return;
|
|
}
|
|
currentSettings = { ...DEFAULT_SETTINGS, ...saved };
|
|
if (!styles.includes(currentSettings.style)) {
|
|
currentSettings.style = DEFAULT_SETTINGS.style;
|
|
}
|
|
} catch (error) {
|
|
currentSettings = { ...DEFAULT_SETTINGS };
|
|
}
|
|
}
|
|
|
|
function updateClock() {
|
|
const now = new Date();
|
|
let hours = now.getHours();
|
|
let minutes = now.getMinutes();
|
|
let seconds = now.getSeconds();
|
|
const ampm = hours >= 12 ? 'PM' : 'AM';
|
|
if (selectedStyle === 'hms12' || selectedStyle === 'hm12') {
|
|
hours = hours % 12; // 12시간 형식으로 변경
|
|
hours = hours ? hours : 12; // 0시를 12시로 표시
|
|
}
|
|
minutes = minutes < 10 ? '0' + minutes : minutes;
|
|
seconds = seconds < 10 ? '0' + seconds : seconds;
|
|
document.getElementById('hours').textContent = hours < 10 ? '0' + hours : hours;
|
|
document.getElementById('minutes').textContent = minutes;
|
|
document.getElementById('seconds').textContent = seconds;
|
|
document.getElementById('ampm').textContent = ampm;
|
|
const day = String(now.getDate()).padStart(2, '0');
|
|
const month = String(now.getMonth() + 1).padStart(2, '0');
|
|
const weekdays = [ 'SUNDAY', 'MONDAY', 'TUESDAY', 'WEDNESDAY', 'THURSDAY', 'FRIDAY', 'SATURDAY', ];
|
|
const weekday = weekdays[now.getDay()];
|
|
dateMonthDay.textContent = `${month}.${day}`;
|
|
dateWeekday.textContent = weekday;
|
|
}
|
|
|
|
function checkSeparator() {
|
|
const timeSeparator = document.getElementById('timeSeparator');
|
|
const timeArea = document.querySelector('.time-area');
|
|
if (selectedStyle === 'hm12' || selectedStyle === 'hm24') {
|
|
timeSeparator.classList.add('show');
|
|
timeArea.style.gap = '0';
|
|
} else {
|
|
timeSeparator.classList.remove('show');
|
|
timeArea.style.gap = '5rem';
|
|
}
|
|
}
|
|
|
|
function startClock() {
|
|
updateClock();
|
|
const nextTickDelay = 1000 - new Date().getMilliseconds();
|
|
setTimeout(startClock, nextTickDelay);
|
|
}
|
|
|
|
startClock();
|
|
changeStyle('hms12');
|
|
checkSeparator();
|
|
|
|
const exitFullscreenIcon = document.getElementById('exit-fullscreen-icon');
|
|
const enterFullscreenIcon = document.getElementById('enter-fullscreen-icon');
|
|
enterFullscreenIcon.style.display = 'block';
|
|
exitFullscreenIcon.style.display = 'none';
|
|
function toggleFullScreen() { if (!document.fullscreenElement) { document.documentElement.requestFullscreen(); enterFullscreenIcon.style.display = 'none'; exitFullscreenIcon.style.display = 'block'; } else { if (document.exitFullscreen) { document.exitFullscreen(); enterFullscreenIcon.style.display = 'block'; exitFullscreenIcon.style.display = 'none'; } } }
|
|
document.addEventListener('keydown', function (event) { if (event.code === 'KeyF') { toggleFullScreen(); } else if (event.code === 'Space') { event.preventDefault(); const showHideElements = document.querySelectorAll('.show-hide'); showHideElements.forEach(element => { if (element.classList.contains('hidden')) { element.classList.remove('hidden'); } else { element.classList.add('hidden'); } }); } });
|
|
const fullScreenButton = document.querySelector('.app-full');
|
|
fullScreenButton.addEventListener('click', function (event) { event.stopPropagation(); toggleFullScreen(); });
|
|
|
|
document.body.addEventListener('click', function () {
|
|
const showHideElements = document.querySelectorAll('.show-hide');
|
|
showHideElements.forEach(element => {
|
|
if (element.classList.contains('hidden')) {
|
|
element.classList.remove('hidden');
|
|
} else {
|
|
element.classList.add('hidden');
|
|
}
|
|
});
|
|
});
|
|
|
|
// MENU: BOOK
|
|
const showHideOverlay = document.querySelector('.modal-overlay');
|
|
let theBook = document.querySelector('.app-book');
|
|
theBook.addEventListener('click', (event) => { event.stopPropagation(); const showHideGuideBook = document.querySelector('.modal'); if (showHideGuideBook.classList.contains('hidden')) { showHideGuideBook.classList.remove('hidden'); } if (showHideOverlay.classList.contains('hidden')) { showHideOverlay.classList.remove('hidden'); } });
|
|
function closeModal() { const showHideGuideBook = document.querySelector('.modal'); if (!showHideGuideBook.classList.contains('hidden')) { showHideGuideBook.classList.add('hidden'); } if (!showHideOverlay.classList.contains('hidden')) { showHideOverlay.classList.add('hidden'); } }
|
|
|
|
// MENU: THEME
|
|
const themes = [ '밤하늘', '명료한 선언', '앵화난만', '화월야', '도원향', '소야시우', '밤의 고요함', '적월', '녹음이 우거진 산', '달의 물방울', '은파', '월하미인', '음력 보름밤의 달' ];
|
|
let currentThemeIndex = 0;
|
|
function setTheme(theme) { const body = document.body; const themeName = document.querySelector('.app-theme'); const currentThemeElement = document.getElementById('current-theme'); const nextThemeElement = document.getElementById('next-theme'); body.className = ''; switch (theme) { case '밤의 고요함': body.classList.add('YORUNOSEIJAKU'); break; case '명료한 선언': body.classList.add('MEIRYONASENGEN'); break; case '앵화난만': body.classList.add('OUKARANMAN'); break; case '화월야': body.classList.add('HANAZUKIYO'); break; case '도원향': body.classList.add('TOUGENKYOU'); break; case '소야시우': body.classList.add('SAYOSHIGURE'); break; case '적월': body.classList.add('AKATSUKI'); break; case '녹음이 우거진 산': body.classList.add('YAMASHITATARU'); break; case '달의 물방울': body.classList.add('TSUKINOSHIZUKU'); break; case '은파': body.classList.add('GINPA'); break; case '월하미인': body.classList.add('GEKKABIJIN'); break; case '음력 보름밤의 달': body.classList.add('MONAKANOTSUKI'); break; default: break; } nextThemeElement.textContent = theme.replace(/-/g, ' ').toUpperCase(); nextThemeElement.classList.remove('hidden'); setTimeout(() => { currentThemeElement.classList.add('hidden'); setTimeout(() => { currentThemeElement.textContent = nextThemeElement.textContent; currentThemeElement.classList.remove('hidden'); nextThemeElement.classList.add('hidden'); }, 900); }, 0); }
|
|
function changeThemeIndex(direction) { if (direction === 'next') { currentThemeIndex = (currentThemeIndex + 1) % themes.length; } else if (direction === 'prev') { currentThemeIndex = (currentThemeIndex - 1 + themes.length) % themes.length; } const nextTheme = themes[currentThemeIndex]; setTheme(nextTheme); currentSettings.themeIndex = currentThemeIndex; saveSettings(); }
|
|
document.addEventListener('keydown', function (event) { if (event.key === '[') { changeThemeIndex('prev'); } else if (event.key === ']') { changeThemeIndex('next'); } });
|
|
const nextThemeButton = document.querySelector('.app-next');
|
|
nextThemeButton.addEventListener('click', function (event) { event.stopPropagation(); changeThemeIndex('next'); });
|
|
|
|
// MENU: COG
|
|
const cog = document.querySelector('.app-cog');
|
|
const options = document.querySelector('.options');
|
|
options.addEventListener('click', (event) => { event.stopPropagation(); });
|
|
cog.addEventListener('click', (event) => { event.stopPropagation(); options.classList.toggle('show'); });
|
|
function closeOptions() { options.classList.remove('show'); }
|
|
|
|
window.onload = function () {
|
|
const showHideElements = document.querySelectorAll('.show-hide'); showHideElements.forEach(element => { element.style.display = 'block'; });
|
|
loadSettings();
|
|
currentThemeIndex = Number.isInteger(currentSettings.themeIndex) ? currentSettings.themeIndex : 0;
|
|
if (currentThemeIndex < 0 || currentThemeIndex >= themes.length) {
|
|
currentThemeIndex = 0;
|
|
}
|
|
setTheme(themes[currentThemeIndex]);
|
|
changeFont(currentSettings.fontNumber);
|
|
changeStyle(currentSettings.style);
|
|
applyHideInfo(Boolean(currentSettings.hideInfo));
|
|
applySeparatorPosition(Boolean(currentSettings.separatorCompact));
|
|
saveSettings();
|
|
};
|
|
|
|
function changeFont(fontNumber) {
|
|
const clockContainer = document.querySelector('.clock-container');
|
|
clockContainer.classList.remove( 'font-style-1', 'font-style-2', 'font-style-3', 'font-style-4', 'font-style-5', 'font-style-6' );
|
|
clockContainer.classList.add(`font-style-${fontNumber}`);
|
|
fontDropdown.value = String(fontNumber);
|
|
currentSettings.fontNumber = String(fontNumber);
|
|
saveSettings();
|
|
}
|
|
|
|
function changeStyle(value) {
|
|
selectedStyle = value;
|
|
const ampm = document.getElementById('ampm')
|
|
const seconds = document.querySelector('.seconds-section')
|
|
switch (selectedStyle) { case 'hms12': seconds.style.display = 'block'; ampm.style.display = 'block'; break; case 'hms24': seconds.style.display = 'block'; ampm.style.display = 'none'; break; case 'hm12': seconds.style.display = 'none'; ampm.style.display = 'block'; break; case 'hm24': seconds.style.display = 'none'; ampm.style.display = 'none'; break; default: break; }
|
|
checkSeparator();
|
|
styleDropdown.value = selectedStyle;
|
|
currentSettings.style = selectedStyle;
|
|
saveSettings();
|
|
}
|
|
|
|
function changefontSize(value) {
|
|
const timeCounters = document.querySelectorAll('.time-counter');
|
|
timeCounters.forEach(timeCounter => {
|
|
if (value == 6) {
|
|
timeCounter.style.width = 'initial';
|
|
timeCounter.style.fontSize = '20vw';
|
|
} else if (value == 8) {
|
|
timeCounter.style.width = 'initial';
|
|
timeCounter.style.fontSize = '24vw';
|
|
} else if (value == 10) {
|
|
timeCounter.style.width = 'initial';
|
|
timeCounter.style.fontSize = '28vw';
|
|
}
|
|
});
|
|
}
|
|
|
|
function applyHideInfo(hidden) {
|
|
hideInfoButton.classList.toggle('on', !hidden);
|
|
hideInfoButton.classList.toggle('off', hidden);
|
|
hideInfoButton.textContent = hidden ? 'OFF' : 'ON';
|
|
subTextElements.forEach(el => {
|
|
el.style.display = hidden ? 'none' : 'block';
|
|
});
|
|
currentSettings.hideInfo = hidden;
|
|
saveSettings();
|
|
}
|
|
|
|
hideInfoButton.addEventListener('click', () => {
|
|
applyHideInfo(!currentSettings.hideInfo);
|
|
});
|
|
|
|
function applySeparatorPosition(compact) {
|
|
separatorPosition.classList.toggle('on', compact);
|
|
separatorPosition.classList.toggle('off', !compact);
|
|
separatorPosition.textContent = compact ? 'ON' : 'OFF';
|
|
document.getElementById('timeSeparator').style.lineHeight = compact ? '1' : 'initial';
|
|
currentSettings.separatorCompact = compact;
|
|
saveSettings();
|
|
}
|
|
|
|
separatorPosition.addEventListener('click', () => {
|
|
applySeparatorPosition(!currentSettings.separatorCompact);
|
|
});
|
|
</script>
|
|
</body>
|
|
</html>
|