diff --git a/css/style.css b/css/style.css
new file mode 100644
index 0000000..1258a3b
--- /dev/null
+++ b/css/style.css
@@ -0,0 +1,368 @@
+* {
+ box-sizing: border-box;
+}
+
+body {
+ margin: 0;
+ font-family: "Pretendard", -apple-system, BlinkMacSystemFont, "Segoe UI",
+ Roboto, "Helvetica Neue", Arial, sans-serif;
+ background-color: #ffd6e7;
+ color: #2e2a26;
+}
+
+button,
+input {
+ font: inherit;
+}
+
+button {
+ border: none;
+ cursor: pointer;
+ transition: background-color 0.2s ease, transform 0.15s ease,
+ opacity 0.2s ease;
+}
+
+.app {
+ min-height: 100vh;
+ display: flex;
+ justify-content: center;
+ padding: 24px;
+}
+
+.todoCard {
+ width: 100%;
+ max-width: 520px;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: 24px;
+}
+
+.todoHeader {
+ width: 100%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ position: relative;
+ padding-top: 8px;
+}
+
+.menuButton {
+ position: absolute;
+ left: 0;
+ top: 0;
+ background: transparent;
+ font-size: 28px;
+ color: #2e2a26;
+ padding: 4px 8px;
+ z-index: 20;
+}
+
+.menuButton:hover {
+ opacity: 0.7;
+}
+
+.viewMenu {
+ position: absolute;
+ left: 0;
+ top: 42px;
+ display: flex;
+ flex-direction: column;
+ gap: 8px;
+ background-color: #ffe6f0;
+ padding: 10px;
+ border-radius: 14px;
+ box-shadow: 0 8px 18px rgba(90, 60, 70, 0.14);
+ z-index: 15;
+}
+
+.viewMenuButton {
+ min-width: 110px;
+ background-color: white;
+ color: #2e2a26;
+ border-radius: 10px;
+ padding: 8px 12px;
+ font-weight: 600;
+ text-align: left;
+}
+
+.viewMenuButton:hover {
+ background-color: #ffcade;
+ transform: translateY(-1px);
+}
+
+.hidden {
+ display: none;
+}
+
+.appTitle {
+ margin: 0;
+ font-size: 28px;
+ font-weight: 700;
+}
+
+.dateSection {
+ display: flex;
+ align-items: center;
+ gap: 24px;
+}
+
+.dateButton {
+ background: transparent;
+ color: #2e2a26;
+ font-size: 18px;
+ padding: 4px 8px;
+}
+
+.dateButton:hover {
+ opacity: 0.7;
+ transform: scale(1.05);
+}
+
+.selectedDate {
+ margin: 0;
+ font-size: 16px;
+ font-weight: 600;
+}
+
+.todoInputSection,
+.todoListSection,
+.todoSummarySection {
+ width: 100%;
+}
+
+.todoForm {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ background-color: #ffe6f0;
+ border-radius: 20px;
+ padding: 10px 12px;
+}
+
+.todoInput {
+ flex: 1;
+ border: none;
+ outline: none;
+ background: transparent;
+ font-size: 15px;
+ color: #2e2a26;
+}
+
+.todoInput::placeholder {
+ color: #8c7a73;
+}
+
+.todoInput:disabled {
+ opacity: 0.7;
+ cursor: not-allowed;
+}
+
+.addButton {
+ background-color: #ff7aa2;
+ color: white;
+ font-weight: 700;
+ border-radius: 12px;
+ padding: 10px 16px;
+}
+
+.addButton:hover {
+ background-color: #f0628f;
+ transform: translateY(-1px);
+}
+
+.addButton:disabled {
+ background-color: #d9a7b9;
+ cursor: not-allowed;
+ transform: none;
+}
+
+.todoList {
+ list-style: none;
+ margin: 0;
+ padding: 0;
+ display: flex;
+ flex-direction: column;
+ gap: 12px;
+}
+
+.todoItem {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ gap: 12px;
+ background-color: #ffb3cc;
+ border-radius: 20px;
+ padding: 12px 14px;
+ transition: transform 0.15s ease, box-shadow 0.2s ease;
+}
+
+.todoItem:hover {
+ transform: translateY(-2px);
+ box-shadow: 0 8px 18px rgba(120, 72, 88, 0.14);
+}
+
+.todoLeft {
+ display: flex;
+ align-items: center;
+ gap: 12px;
+ flex: 1;
+ min-width: 0;
+}
+
+.todoCheckbox {
+ width: 22px;
+ height: 22px;
+ appearance: none;
+ -webkit-appearance: none;
+ background-color: white;
+ border: 2px solid #efb2c8;
+ border-radius: 6px;
+ position: relative;
+ cursor: pointer;
+ flex-shrink: 0;
+}
+
+.todoCheckbox:hover {
+ border-color: #ff8fb2;
+}
+
+.todoCheckbox:checked {
+ background-color: white;
+ border-color: #efb2c8;
+}
+
+.todoCheckbox:checked::after {
+ content: "✔";
+ position: absolute;
+ color: #ff5c8a;
+ font-size: 16px;
+ font-weight: 900;
+ left: 3px;
+ top: -1px;
+}
+
+.todoText {
+ font-size: 15px;
+ font-weight: 400;
+ word-break: break-word;
+ color: #2e2a26;
+}
+
+.completedTodo {
+ text-decoration: line-through;
+ opacity: 0.7;
+}
+
+.deleteButton {
+ background-color: #4a403b;
+ color: white;
+ border-radius: 12px;
+ padding: 8px 14px;
+ font-weight: 700;
+ flex-shrink: 0;
+}
+
+.deleteButton:hover {
+ background-color: #6f645e;
+ transform: translateY(-1px);
+}
+
+.summaryCardList {
+ display: flex;
+ gap: 12px;
+ flex-wrap: wrap;
+}
+
+.summaryCard {
+ flex: 1 1 150px;
+ background-color: #ffe6f0;
+ border-radius: 18px;
+ padding: 16px;
+ text-align: center;
+ transition: transform 0.15s ease, box-shadow 0.2s ease;
+}
+
+.summaryCard:hover {
+ transform: translateY(-2px);
+ box-shadow: 0 8px 18px rgba(120, 72, 88, 0.1);
+}
+
+.summaryTitle {
+ margin: 0 0 10px;
+ font-size: 15px;
+ color: #5c514b;
+}
+
+.summaryValue {
+ margin: 0;
+ font-size: 22px;
+ font-weight: 700;
+ color: #d94b77;
+}
+
+/* Weekly view */
+.weeklyTodoGroup {
+ display: flex;
+ flex-direction: column;
+ gap: 10px;
+ background-color: #ffdbe8;
+ border-radius: 18px;
+ padding: 14px;
+}
+
+.weeklyTodoTitle {
+ margin: 0;
+ font-size: 16px;
+ font-weight: 700;
+ color: #4a403b;
+}
+
+.weeklyEmptyText {
+ margin: 0;
+ font-size: 14px;
+ color: #6f645e;
+ padding-left: 4px;
+}
+
+.emptyTodoText {
+ margin: 0;
+ font-size: 15px;
+ font-weight: 500;
+ color: #5c514b;
+ text-align: center;
+}
+
+@media (max-width: 600px) {
+ .app {
+ padding: 16px;
+ }
+
+ .todoCard {
+ gap: 20px;
+ }
+
+ .dateSection {
+ gap: 14px;
+ }
+
+ .todoForm {
+ padding: 10px;
+ }
+
+ .addButton {
+ padding: 10px 14px;
+ }
+
+ .todoItem {
+ padding: 12px;
+ }
+
+ .summaryCardList {
+ flex-direction: column;
+ }
+
+ .viewMenu {
+ top: 40px;
+ }
+}
\ No newline at end of file
diff --git a/index.html b/index.html
new file mode 100644
index 0000000..3bd6f8a
--- /dev/null
+++ b/index.html
@@ -0,0 +1,104 @@
+
+
+
+
+
+ vanilla-todo
+
+
+
+
+
+
+
+
+
+
+
+ 2026년 3월 14일
+
+
+
+
+
+
+
+
+
+
+
+ 전체 투두
+ 0개
+
+
+
+ 완료한 투두
+ 0개
+
+
+
+ 달성률
+ 0%
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/js/script.js b/js/script.js
new file mode 100644
index 0000000..b521ddc
--- /dev/null
+++ b/js/script.js
@@ -0,0 +1,311 @@
+const selectedDate = document.querySelector("#selectedDate");
+const prevDateButton = document.querySelector(".prevDateButton");
+const nextDateButton = document.querySelector(".nextDateButton");
+const todoForm = document.querySelector("#todoForm");
+const todoInput = document.querySelector("#todoInput");
+const todoList = document.querySelector("#todoList");
+
+const totalTodoCount = document.querySelector("#totalTodoCount");
+const completedTodoCount = document.querySelector("#completedTodoCount");
+const achievementRate = document.querySelector("#achievementRate");
+
+const menuButton = document.querySelector("#menuButton");
+const viewMenu = document.querySelector("#viewMenu");
+const viewMenuButtons = document.querySelectorAll(".viewMenuButton");
+
+let currentDate = new Date();
+let viewMode = "daily";
+
+const todoData = {};
+
+function formatDateKey(date) {
+ const year = date.getFullYear();
+ const month = String(date.getMonth() + 1).padStart(2, "0");
+ const day = String(date.getDate()).padStart(2, "0");
+ return `${year}-${month}-${day}`;
+}
+
+function formatDisplayDate(date) {
+ const year = date.getFullYear();
+ const month = date.getMonth() + 1;
+ const day = date.getDate();
+ return `${year}년 ${month}월 ${day}일`;
+}
+
+function getWeekOfMonth(date) {
+ const firstDay = new Date(date.getFullYear(), date.getMonth(), 1);
+ const day = date.getDate();
+ const offset = firstDay.getDay();
+ return Math.ceil((day + offset) / 7);
+}
+
+function formatWeeklyDisplay(date) {
+ const year = date.getFullYear();
+ const month = date.getMonth() + 1;
+ const week = getWeekOfMonth(date);
+ return `${year}년 ${month}월 ${week}째주`;
+}
+
+function renderSelectedDate() {
+ if (viewMode === "daily") {
+ selectedDate.textContent = formatDisplayDate(currentDate);
+ } else {
+ selectedDate.textContent = formatWeeklyDisplay(currentDate);
+ }
+}
+
+function getCurrentTodos() {
+ const dateKey = formatDateKey(currentDate);
+ if (!todoData[dateKey]) {
+ todoData[dateKey] = [];
+ }
+ return todoData[dateKey];
+}
+
+function getStartOfWeek(date) {
+ const start = new Date(date);
+ const day = start.getDay();
+ start.setDate(start.getDate() - day);
+ start.setHours(0, 0, 0, 0);
+ return start;
+}
+
+function getWeeklyTodos() {
+ const start = getStartOfWeek(currentDate);
+ const result = [];
+
+ for (let i = 0; i < 7; i++) {
+ const date = new Date(start);
+ date.setDate(start.getDate() + i);
+
+ const key = formatDateKey(date);
+ const todos = todoData[key] || [];
+
+ result.push({
+ date,
+ todos
+ });
+ }
+
+ return result;
+}
+
+function createTodoItemElement(todo) {
+ const todoItem = document.createElement("li");
+ todoItem.className = "todoItem";
+
+ const todoLeft = document.createElement("div");
+ todoLeft.className = "todoLeft";
+
+ const todoCheckbox = document.createElement("input");
+ todoCheckbox.type = "checkbox";
+ todoCheckbox.className = "todoCheckbox";
+ todoCheckbox.id = `todo-${todo.id}`;
+ todoCheckbox.checked = todo.isCompleted;
+
+ const todoLabel = document.createElement("label");
+ todoLabel.className = "todoText";
+ todoLabel.setAttribute("for", `todo-${todo.id}`);
+ todoLabel.textContent = todo.text;
+
+ if (todo.isCompleted) {
+ todoLabel.classList.add("completedTodo");
+ }
+
+ const deleteButton = document.createElement("button");
+ deleteButton.type = "button";
+ deleteButton.className = "deleteButton";
+ deleteButton.textContent = "삭제";
+
+ todoCheckbox.addEventListener("change", function () {
+ todo.isCompleted = todoCheckbox.checked;
+
+ if (todo.isCompleted) {
+ todoLabel.classList.add("completedTodo");
+ } else {
+ todoLabel.classList.remove("completedTodo");
+ }
+
+ renderSummary();
+ });
+
+ deleteButton.addEventListener("click", function () {
+ deleteTodo(todo.id);
+ });
+
+ todoLeft.append(todoCheckbox, todoLabel);
+ todoItem.append(todoLeft, deleteButton);
+
+ return todoItem;
+}
+
+function renderDailyTodoList() {
+ const todos = getCurrentTodos();
+
+ if (todos.length === 0) {
+ const emptyItem = document.createElement("li");
+ emptyItem.className = "todoItem";
+
+ const emptyText = document.createElement("p");
+ emptyText.className = "emptyTodoText";
+ emptyText.textContent = "아직 등록된 할 일이 없어요.";
+
+ emptyItem.append(emptyText);
+ todoList.append(emptyItem);
+ return;
+ }
+
+ todos.forEach(todo => {
+ const element = createTodoItemElement(todo);
+ todoList.append(element);
+ });
+}
+
+function renderWeeklyTodoList() {
+ const weekly = getWeeklyTodos();
+
+ weekly.forEach(day => {
+ const group = document.createElement("li");
+ group.className = "weeklyTodoGroup";
+
+ const title = document.createElement("h3");
+ title.className = "weeklyTodoTitle";
+ title.textContent = formatDisplayDate(day.date);
+
+ group.append(title);
+
+ if (day.todos.length === 0) {
+ const empty = document.createElement("p");
+ empty.className = "weeklyEmptyText";
+ empty.textContent = "등록된 할 일이 없어요.";
+ group.append(empty);
+ } else {
+ day.todos.forEach(todo => {
+ const element = createTodoItemElement(todo);
+ group.append(element);
+ });
+ }
+
+ todoList.append(group);
+ });
+}
+
+function renderTodoList() {
+ todoList.innerHTML = "";
+
+ if (viewMode === "daily") {
+ renderDailyTodoList();
+ } else {
+ renderWeeklyTodoList();
+ }
+}
+
+function renderSummary() {
+ let todos = [];
+
+ if (viewMode === "daily") {
+ todos = getCurrentTodos();
+ } else {
+ const weekly = getWeeklyTodos();
+ todos = weekly.flatMap(d => d.todos);
+ }
+
+ const totalCount = todos.length;
+ const completedCount = todos.filter(t => t.isCompleted).length;
+
+ let rate = 0;
+ if (totalCount > 0) {
+ rate = Math.round((completedCount / totalCount) * 100);
+ }
+
+ totalTodoCount.textContent = `${totalCount}개`;
+ completedTodoCount.textContent = `${completedCount}개`;
+ achievementRate.textContent = `${rate}%`;
+}
+
+function renderInputState() {
+ if (viewMode === "daily") {
+ todoInput.disabled = false;
+ todoInput.placeholder = "오늘의 할 일을 적어주세요!";
+ } else {
+ todoInput.disabled = true;
+ todoInput.placeholder = "주간 보기에서는 추가할 수 없어요.";
+ }
+}
+
+function renderApp() {
+ renderSelectedDate();
+ renderInputState();
+ renderTodoList();
+ renderSummary();
+}
+
+function addTodo(text) {
+ const todos = getCurrentTodos();
+
+ const newTodo = {
+ id: Date.now(),
+ text,
+ isCompleted: false
+ };
+
+ todos.push(newTodo);
+ renderApp();
+}
+
+function deleteTodo(id) {
+ const todos = getCurrentTodos();
+ const updated = todos.filter(todo => todo.id !== id);
+
+ const key = formatDateKey(currentDate);
+ todoData[key] = updated;
+
+ renderApp();
+}
+
+function changePeriod(offset) {
+ const newDate = new Date(currentDate);
+
+ if (viewMode === "daily") {
+ newDate.setDate(newDate.getDate() + offset);
+ } else {
+ newDate.setDate(newDate.getDate() + offset * 7);
+ }
+
+ currentDate = newDate;
+ renderApp();
+}
+
+menuButton.addEventListener("click", () => {
+ viewMenu.classList.toggle("hidden");
+});
+
+viewMenuButtons.forEach(button => {
+ button.addEventListener("click", () => {
+ viewMode = button.dataset.view;
+ viewMenu.classList.add("hidden");
+ renderApp();
+ });
+});
+
+todoForm.addEventListener("submit", event => {
+ event.preventDefault();
+
+ if (viewMode !== "daily") return;
+
+ const text = todoInput.value.trim();
+ if (text === "") return;
+
+ addTodo(text);
+ todoInput.value = "";
+});
+
+prevDateButton.addEventListener("click", () => {
+ changePeriod(-1);
+});
+
+nextDateButton.addEventListener("click", () => {
+ changePeriod(1);
+});
+
+renderApp();
\ No newline at end of file