#sleepcalc{–sc-bg:#ffffff;–sc-card:#fafafa;–sc-text:#111827;–sc-sub:#6b7280;–sc-primary:#3b82f6;–sc-accent:#10b981;–sc-border:#e5e7eb;–sc-danger:#ef4444;–sc-shadow:0 8px 30px rgba(0,0,0,.06);font-family:system-ui,-apple-system,Segoe UI,Roboto,Pretendard,Apple SD Gothic Neo,Noto Sans,Arial,sans-serif;color:var(–sc-text);}
#sleepcalc *{box-sizing:border-box}
#sleepcalc .sc-wrap{max-width:860px;margin:0 auto;background:var(–sc-bg);padding:16px}
#sleepcalc .sc-card{background:var(–sc-card);border:1px solid var(–sc-border);border-radius:16px;box-shadow:var(–sc-shadow);overflow:hidden}
#sleepcalc header{display:flex;gap:12px;align-items:center;padding:18px 20px;border-bottom:1px solid var(–sc-border);background:#fff}
#sleepcalc .sc-title{font-weight:800;font-size:1.15rem;letter-spacing:-.015em}
#sleepcalc .sc-sub{color:var(–sc-sub);font-size:.9rem}
#sleepcalc .sc-body{padding:20px}
#sleepcalc .grid{display:grid;gap:14px}
@media(min-width:720px){#sleepcalc .grid{grid-template-columns:1fr 1fr}}
#sleepcalc label{display:block;font-weight:700;margin:2px 0 8px}
#sleepcalc select,#sleepcalc input[type=time],#sleepcalc input[type=number]{width:100%;padding:11px 12px;border:1px solid var(–sc-border);border-radius:12px;background:#fff;outline:none;transition:border .15s}
#sleepcalc select:focus,#sleepcalc input:focus{border-color:var(–sc-primary)}
#sleepcalc .radio-row{display:flex;flex-wrap:wrap;gap:8px}
#sleepcalc .radio{display:flex;align-items:center;gap:8px;padding:10px 12px;border:1px solid var(–sc-border);border-radius:999px;background:#fff;cursor:pointer}
#sleepcalc .radio input{accent-color:var(–sc-primary)}
#sleepcalc .muted{color:var(–sc-sub);font-size:.88rem}
#sleepcalc .row{display:grid;gap:8px}
#sleepcalc .actions{display:flex;gap:10px;flex-wrap:wrap;margin-top:10px}
#sleepcalc button{border:0;border-radius:12px;padding:11px 14px;font-weight:800;cursor:pointer}
#sleepcalc .btn-primary{background:var(–sc-primary);color:#fff}
#sleepcalc .btn-ghost{background:#fff;border:1px solid var(–sc-border)}
#sleepcalc .btn-link{background:transparent;color:var(–sc-sub);text-decoration:underline}
#sleepcalc .result{margin-top:14px;border-top:1px dashed var(–sc-border);padding-top:16px}
#sleepcalc .pill{display:inline-flex;align-items:center;gap:6px;padding:6px 10px;border-radius:999px;border:1px solid var(–sc-border);background:#fff;font-weight:700}
#sleepcalc .pill.primary{border-color:#bfdbfe;background:#eff6ff;color:#1d4ed8}
#sleepcalc .pill.accent{border-color:#bbf7d0;background:#ecfdf5;color:#047857}
#sleepcalc .pill.warn{border-color:#fecaca;background:#fff1f2;color:#b91c1c}
#sleepcalc .times{display:flex;flex-direction:column;gap:10px;margin-top:12px}
#sleepcalc .time-item{display:flex;align-items:center;gap:10px;justify-content:space-between;border:1px solid var(–sc-border);background:#fff;border-radius:12px;padding:10px 12px}
#sleepcalc .time-left{display:flex;align-items:center;gap:10px}
#sleepcalc .badge{font-size:.72rem;padding:4px 8px;border-radius:999px;border:1px solid var(–sc-border);color:var(–sc-sub)}
#sleepcalc .best{border-color:#bfdbfe;background:#f0f9ff}
#sleepcalc .note{font-size:.85rem;color:var(–sc-sub);margin-top:8px}
#sleepcalc .foot{padding:14px 20px;border-top:1px solid var(–sc-border);background:#fff;color:var(–sc-sub);font-size:.86rem}
#sleepcalc .icon{width:22px;height:22px}
선택하세요
0–3개월 (신생아) · 14–17시간
4–11개월 (영아) · 12–15시간
1–2세 (유아) · 11–14시간
3–5세 (유치) · 10–13시간
6–13세 (학령기) · 9–11시간
14–17세 (청소년) · 8–10시간
18–25세 (청년) · 7–9시간
26–64세 (성인) · 7–9시간
65세 이상 (노년) · 7–8시간
이 계산기는 JavaScript가 필요합니다. 브라우저 설정에서 JavaScript를 활성화해 주세요.
(function(){
const root = document.querySelector(‘#sleepcalc’);
const form = root.querySelector(‘#sc-form’);
const modeRadios = form.querySelectorAll(‘input[name=”mode”]’);
const wakeRow = form.querySelector(‘#wakeRow’);
const bedRow = form.querySelector(‘#bedRow’);
const wakeEl = form.querySelector(‘#wake’);
const bedEl = form.querySelector(‘#bed’);
const ageEl = form.querySelector(‘#age’);
const latencyEl = form.querySelector(‘#latency’);
const resultEl = form.querySelector(‘#result’);
// 모드 토글
modeRadios.forEach(r=>r.addEventListener(‘change’,()=>{
const v = getMode();
if(v===’wake2bed’){wakeRow.style.display=’block’;bedRow.style.display=’none’;wakeEl.required=true;bedEl.required=false}
else{wakeRow.style.display=’none’;bedRow.style.display=’block’;wakeEl.required=false;bedEl.required=true}
resultEl.innerHTML=”;
}));
function getMode(){return form.querySelector(‘input[name=”mode”]:checked’).value}
function parseTimeToMin(hhmm){const [h,m]=hhmm.split(‘:’).map(Number);return h*60+m}
function minToHHMM(min){min=((min%1440)+1440)%1440;const h=Math.floor(min/60),m=min%60;return `${String(h).padStart(2,’0′)}:${String(m).padStart(2,’0′)}`}
function compute(){
const ageOpt = ageEl.options[ageEl.selectedIndex];
if(!ageOpt || !ageOpt.dataset.min){resultEl.innerHTML=warn(‘연령대를 먼저 선택해 주세요.’);return}
const minH = Number(ageOpt.dataset.min); // 권장 최소 (시간)
const maxH = Number(ageOpt.dataset.max); // 권장 최대 (시간)
const latency = Math.max(0, Math.min(60, Number(latencyEl.value||15))); // 분
const mode = getMode();
const CYCLE=90; // 분
const pickCyclesInRange = ()=>{
const picks=[];
const minTotal = minH*60 + latency;
const maxTotal = maxH*60 + latency;
for(let c=3;c=minH*60+latency && total{
const pref=(x)=>Math.abs(x-5.5);return pref(a)-pref(b)||a-b;
});
return picks;
};
let baseMin;
try{
baseMin = mode===’wake2bed’ ? parseTimeToMin(wakeEl.value) : parseTimeToMin(bedEl.value);
}catch(e){resultEl.innerHTML=warn(‘시간을 올바르게 입력해 주세요.’);return}
if(Number.isNaN(baseMin)){resultEl.innerHTML=warn(‘시간을 올바르게 입력해 주세요.’);return}
const minTotal = minH*60 + latency;
const maxTotal = maxH*60 + latency;
const window = (mode===’wake2bed’)
? [baseMin – maxTotal, baseMin – minTotal]
: [baseMin + minTotal, baseMin + maxTotal];
// 사이클 추천
const cycles = pickCyclesInRange();
const suggestions = cycles.map(c=>{
const total = c*CYCLE + latency;
const when = (mode===’wake2bed’) ? baseMin – total : baseMin + total;
return {cycles:c, total, ts:when, hhmm:minToHHMM(when), rel:relDay(when, baseMin, mode)}
});
// 최고의 두 가지를 선별: 균형(5사이클 근처), 깊은 휴식(6사이클 근처)
const bestBalanced = suggestions.slice().sort((a,b)=>Math.abs(a.cycles-5)-Math.abs(b.cycles-5) || a.cycles-b.cycles)[0];
const bestDeep = suggestions.slice().sort((a,b)=>Math.abs(a.cycles-6)-Math.abs(b.cycles-6) || a.cycles-b.cycles)[0];
// 결과 렌더링
const windowFrom = minToHHMM(window[0]);
const windowTo = minToHHMM(window[1]);
const windowRelL = relDay(window[0], baseMin, mode);
const windowRelR = relDay(window[1], baseMin, mode);
let html = ”;
html += `
`;
html += `
`;
if(suggestions.length){
html += ‘
const printed = new Set();
const pushItem=(s,label,isBest)=>{
if(!s || printed.has(s.cycles)) return; printed.add(s.cycles);
const dur = fmtDur(s.total);
html += `
+ `
+ `
`
+ `
`
+ ``
+ `
`;
};
pushItem(bestBalanced, ‘균형 추천’, true);
pushItem(bestDeep, ‘깊은 휴식’, true);
suggestions.filter(s=>s!==bestBalanced && s!==bestDeep).forEach(s=>pushItem(s));
html += ‘
‘;
} else {
html += `
`
}
resultEl.innerHTML = html;
// 복사 버튼
resultEl.querySelectorAll(‘button[data-copy]’).forEach(btn=>{
btn.addEventListener(‘click’,()=>{
const v=btn.getAttribute(‘data-copy’);
navigator.clipboard?.writeText(v).then(()=>{
btn.textContent=’복사됨’;
setTimeout(()=>btn.textContent=’복사’,1200);
});
});
})
}
function warn(msg){return `
`}
function relDay(ts, base, mode){
const diff = ts – base;
if(mode===’wake2bed’){ // 과거로 이동
return diff<=-1440? '이틀 전': diff=1440? ‘모레’: diff>=1440? ‘모레’: diff>=0? (diff>=1440?’모레’: (diff>=0 && diff<1440?'다음날':'')) : '';
}
}
function badge(rel){return rel?`${rel}`:”}
function fmtDur(min){const h=Math.floor(min/60),m=min%60;return `${h}시간 ${m?m+’분’:”}`.trim()}
form.addEventListener(‘submit’,e=>{e.preventDefault();compute()});
root.querySelector(‘#resetBtn’).addEventListener(‘click’,()=>{form.reset();wakeRow.style.display=’block’;bedRow.style.display=’none’;wakeEl.value=’08:00′;bedEl.value=’23:00′;latencyEl.value=15;resultEl.innerHTML=”});
// 초기 계산 예시: 성인, 08:00 기상 → 22:45 / 00:15 제시
compute();
})();
Leave a Reply