diff --git a/answer.md b/answer.md
new file mode 100644
index 0000000..9e232a9
--- /dev/null
+++ b/answer.md
@@ -0,0 +1,272 @@
+# SQL 실습 정답
+
+## DDL 실습
+
+### 문제 1: 테이블 생성하기 (CREATE TABLE)
+
+1. attendance 테이블은 중복된 데이터가 쌓이는 구조이다. 중복된 데이터는 어떤 컬럼인가?
+
+`crew_id`와 `nickname` 컬럼이다. 같은 크루가 출석할 때마다 동일한 `crew_id`와 `nickname` 쌍이 반복해서 삽입된다. 예를 들어, 검프(crew_id=1)가 7일 출석하면 `(1, '검프')` 쌍이 7번 중복 저장된다.
+
+2. attendance 테이블에서 중복을 제거하기 위해 crew 테이블을 만들려고 한다. 어떻게 구성해 볼 수 있을까?
+
+크루의 고유 식별자인 `crew_id`(PK)와 `nickname` 두 컬럼으로 구성한다.
+
+3. crew 테이블에 들어가야 할 크루들의 정보는 어떻게 추출할까? (hint: DISTINCT)
+
+```sql
+-- 1. 크루 정보 추출
+SELECT DISTINCT crew_id, nickname
+FROM attendance
+ORDER BY crew_id;
+
+-- 2. crew 테이블 생성
+CREATE TABLE crew (
+ crew_id INT NOT NULL,
+ nickname VARCHAR(50) NOT NULL,
+ PRIMARY KEY (crew_id)
+);
+
+-- 3. attendance에서 크루 정보를 추출해서 crew 테이블에 삽입
+INSERT INTO crew (crew_id, nickname)
+SELECT DISTINCT crew_id, nickname
+FROM attendance
+ORDER BY crew_id;
+```
+
+---
+
+### 문제 2: 테이블 컬럼 삭제하기 (ALTER TABLE)
+
+1. crew 테이블을 만들고 중복을 제거했다. attendance에서 불필요해지는 컬럼은?
+
+`nickname` 컬럼이다. 크루 정보는 crew 테이블에서 `crew_id`를 통해 조회할 수 있으므로 attendance 테이블에서 nickname을 별도로 저장할 필요가 없다.
+
+2. 컬럼을 삭제하려면 어떻게 해야 하는가?
+
+```sql
+ALTER TABLE attendance
+DROP COLUMN nickname;
+```
+
+---
+
+### 문제 3: 외래키 설정하기
+
+- 만약에 crew 테이블에는 crew_id가 12번인 크루가 존재하지 않지만, attendance 테이블에는 여전히 crew_id가 12번인 크루가 존재한다면?
+
+attendance 테이블에 crew 테이블에 존재하지 않는 `crew_id`가 들어올 수 있다. 외래키(FK) 제약을 걸면 crew 테이블에 없는 `crew_id`는 attendance에 삽입되지 않고, crew에서 레코드가 삭제될 때도 참조 무결성이 보호된다.
+
+```sql
+ALTER TABLE attendance
+ADD CONSTRAINT fk_attendance_crew
+ FOREIGN KEY (crew_id)
+ REFERENCES crew (crew_id);
+```
+
+---
+
+### 문제 4: 유니크 키 설정
+
+- 우아한테크코스에서는 닉네임의 '중복'이 엄연히 금지된다. 그런데 현재 테이블에는 중복된 닉네임이 담길 수 있다. crew 테이블의 결함을 어떻게 해결할 수 있을까?
+
+
+현재 crew 테이블에는 동일한 nickname이 여러 번 삽입될 수 있다. 우아한테크코스에서 닉네임 중복이 금지되므로, UNIQUE 제약으로 DB 레벨에서도 중복을 방지해야 한다.
+
+```sql
+ALTER TABLE crew
+ADD CONSTRAINT uq_crew_nickname
+ UNIQUE (nickname);
+```
+
+---
+
+## DML 실습
+
+### 문제 5: 크루 닉네임 검색하기 (LIKE)
+
+3월 4일에 등교한 크루 중 닉네임 첫 글자가 '디'인 크루를 찾는다.
+
+```sql
+-- crew 테이블 분리 전
+SELECT *
+FROM attendance
+WHERE attendance_date = '2025-03-04'
+ AND nickname LIKE '디%';
+
+-- crew 테이블 분리 후
+SELECT c.nickname, a.attendance_date, a.start_time, a.end_time
+FROM attendance a
+JOIN crew c ON a.crew_id = c.crew_id
+WHERE a.attendance_date = '2025-03-04'
+ AND c.nickname LIKE '디%';
+```
+
+> 결과: **디노** (crew_id=11, 2025-03-04 09:59 등교)
+
+---
+
+### 문제 6: 출석 기록 확인하기 (SELECT + WHERE)
+
+어셔의 3월 6일 출석 기록이 있는지 확인한다.
+
+```sql
+-- crew 테이블 분리 전
+SELECT *
+FROM attendance
+WHERE nickname = '어셔'
+ AND attendance_date = '2025-03-06';
+
+-- crew 테이블 분리 후
+SELECT *
+FROM attendance
+WHERE crew_id = (SELECT crew_id FROM crew WHERE nickname = '어셔')
+ AND attendance_date = '2025-03-06';
+```
+
+> 결과: 레코드가 없음 → 출석 기록이 누락된 것이 확인된다.
+
+---
+
+### 문제 7: 누락된 출석 기록 추가 (INSERT)
+
+어셔의 crew 정보를 먼저 추가한 뒤 출석 기록을 삽입한다.
+
+```sql
+-- crew 테이블에 어셔 추가 (crew_id는 기존 최댓값 이후로 배정)
+INSERT INTO crew (crew_id, nickname)
+VALUES (13, '어셔');
+
+-- 출석 기록 추가
+INSERT INTO attendance (crew_id, attendance_date, start_time, end_time)
+VALUES (13, '2025-03-06', '09:31', '18:01');
+```
+
+---
+
+### 문제 8: 잘못된 출석 기록 수정 (UPDATE)
+
+주니의 3월 12일 start_time을 10:05 -> 10:00으로 수정한다.
+
+```sql
+UPDATE attendance
+SET start_time = '10:00'
+WHERE crew_id = (SELECT crew_id FROM crew WHERE nickname = '주니')
+ AND attendance_date = '2025-03-12';
+```
+
+---
+
+### 문제 9: 허위 출석 기록 삭제 (DELETE)
+
+아론의 3월 12일 출석 기록을 삭제한다.
+
+```sql
+DELETE FROM attendance
+WHERE crew_id = (SELECT crew_id FROM crew WHERE nickname = '아론')
+ AND attendance_date = '2025-03-12';
+```
+
+---
+
+### 문제 10: 출석 정보 조회하기 (JOIN)
+
+crew 테이블과 JOIN하여 nickname과 함께 출석 기록을 조회한다.
+
+```sql
+SELECT c.nickname, a.attendance_date, a.start_time, a.end_time
+FROM attendance a
+JOIN crew c ON a.crew_id = c.crew_id
+WHERE c.nickname = '검프'
+ORDER BY a.attendance_date;
+```
+
+---
+
+### 문제 11: nickname으로 쿼리 처리하기 (서브 쿼리)
+
+서브쿼리로 nickname → crew_id를 변환해서 조회한다.
+
+```sql
+SELECT *
+FROM attendance
+WHERE crew_id = (
+ SELECT crew_id
+ FROM crew
+ WHERE nickname = '검프'
+)
+ORDER BY attendance_date;
+```
+
+---
+
+### 문제 12: 가장 늦게 하교한 크루 찾기
+
+3월 5일에 end_time이 가장 늦은 크루의 nickname과 하교 시각을 찾는다.
+
+```sql
+SELECT c.nickname, a.end_time
+FROM attendance a
+JOIN crew c ON a.crew_id = c.crew_id
+WHERE a.attendance_date = '2025-03-05'
+ AND a.end_time = (
+ SELECT MAX(end_time)
+ FROM attendance
+ WHERE attendance_date = '2025-03-05'
+ );
+```
+
+> 결과: **네오**, 18:15
+
+---
+
+## 집계 함수 실습
+
+### 문제 13: 크루별로 '기록된' 날짜 수 조회
+
+```sql
+SELECT c.nickname, COUNT(*) AS attendance_count
+FROM attendance a
+JOIN crew c ON a.crew_id = c.crew_id
+GROUP BY a.crew_id, c.nickname
+ORDER BY a.crew_id;
+```
+
+---
+
+### 문제 14: 크루별로 등교 기록이 있는(start_time IS NOT NULL) 날짜 수 조회
+
+`COUNT(컬럼명)`은 NULL을 제외하고 카운트한다.
+
+```sql
+SELECT c.nickname, COUNT(a.start_time) AS checkin_count
+FROM attendance a
+JOIN crew c ON a.crew_id = c.crew_id
+GROUP BY a.crew_id, c.nickname
+ORDER BY a.crew_id;
+```
+
+---
+
+### 문제 15: 날짜별로 등교한 크루 수 조회
+
+```sql
+SELECT attendance_date, COUNT(*) AS crew_count
+FROM attendance
+GROUP BY attendance_date
+ORDER BY attendance_date;
+```
+
+---
+
+### 문제 16: 크루별 가장 빠른 등교 시각(MIN)과 가장 늦은 등교 시각(MAX)
+
+```sql
+SELECT c.nickname,
+ MIN(a.start_time) AS earliest_checkin,
+ MAX(a.start_time) AS latest_checkin
+FROM attendance a
+JOIN crew c ON a.crew_id = c.crew_id
+GROUP BY a.crew_id, c.nickname
+ORDER BY a.crew_id;
+```
\ No newline at end of file
diff --git a/attendance.sql b/attendance.sql
new file mode 100644
index 0000000..c4d15c6
--- /dev/null
+++ b/attendance.sql
@@ -0,0 +1,115 @@
+CREATE TABLE attendance (
+ attendance_id INT NOT NULL AUTO_INCREMENT,
+ crew_id INT NOT NULL,
+ nickname VARCHAR(50) NOT NULL,
+ attendance_date DATE NOT NULL,
+ start_time TIME,
+ end_time TIME,
+ PRIMARY KEY (attendance_id)
+);
+
+-- 정규화 이전 상태: attendance(crew_id, nickname, attendance_date, start_time, end_time)
+-- crew_id와 nickname 매핑:
+-- 1=검프, 2=구구, 3=네오, 4=브라운, 5=브리, 6=포비,
+-- 7=워니, 8=리사, 9=제임스, 10=류시, 11=디노, 12=시지프
+
+INSERT INTO attendance (crew_id, nickname, attendance_date, start_time, end_time) VALUES
+
+ -- 검프(crew_id=1)
+ (1, '검프', '2025-03-04', '09:45', '18:10'),
+ (1, '검프', '2025-03-05', '09:50', '18:05'),
+ (1, '검프', '2025-03-06', '09:59', '18:02'),
+ (1, '검프', '2025-03-07', '10:00', '18:05'),
+ (1, '검프', '2025-03-10', '12:55', '18:10'),
+ (1, '검프', '2025-03-11', '09:58', '18:03'),
+ (1, '검프', '2025-03-12', '09:55', '18:05'),
+
+ -- 구구(crew_id=2)
+ (2, '구구', '2025-03-04', '10:01', '18:01'),
+ (2, '구구', '2025-03-05', '09:59', '18:00'),
+ (2, '구구', '2025-03-06', '09:58', '17:30'),
+ (2, '구구', '2025-03-07', '10:10', '18:00'),
+ (2, '구구', '2025-03-11', '09:59', '18:01'),
+ (2, '구구', '2025-03-12', '10:02', '18:10'),
+
+ -- 네오(crew_id=3)
+ (3, '네오', '2025-03-04', '09:59', '18:00'),
+ (3, '네오', '2025-03-05', '10:03', '18:15'),
+ (3, '네오', '2025-03-07', '10:00', '17:50'),
+ (3, '네오', '2025-03-10', '13:05', '18:10'),
+ (3, '네오', '2025-03-12', '09:55', '18:00'),
+
+ -- 브라운(crew_id=4)
+ (4, '브라운', '2025-03-04', '09:59', '18:00'),
+ (4, '브라운', '2025-03-05', '09:59', '18:00'),
+ (4, '브라운', '2025-03-06', '10:00', '18:00'),
+ (4, '브라운', '2025-03-07', '10:00', '18:00'),
+ (4, '브라운', '2025-03-10', '13:00', '18:00'),
+ (4, '브라운', '2025-03-11', '09:59', '18:00'),
+ (4, '브라운', '2025-03-12', '09:59', '18:00'),
+
+ -- 브리(crew_id=5)
+ (5, '브리', '2025-03-04', '10:20', '18:10'),
+ (5, '브리', '2025-03-05', '09:58', '18:02'),
+ (5, '브리', '2025-03-06', '09:59', '18:00'),
+ (5, '브리', '2025-03-07', '10:02', '18:00'),
+ (5, '브리', '2025-03-11', '09:55', '18:00'),
+ (5, '브리', '2025-03-12', '09:57', '18:05'),
+
+ -- 포비(crew_id=6)
+ (6, '포비', '2025-03-04', '10:15', '17:58'),
+ (6, '포비', '2025-03-05', '10:05', '18:05'),
+ (6, '포비', '2025-03-10', '13:10', '18:10'),
+ (6, '포비', '2025-03-11', '09:52', '18:01'),
+ (6, '포비', '2025-03-12', '09:59', '18:00'),
+
+ -- 워니(crew_id=7)
+ (7, '워니', '2025-03-04', '10:10', '18:10'),
+ (7, '워니', '2025-03-05', '09:50', '18:02'),
+ (7, '워니', '2025-03-10', '12:59', '18:05'),
+ (7, '워니', '2025-03-12', '10:05', '17:00'),
+
+ -- 리사(crew_id=8)
+ (8, '리사', '2025-03-04', '09:55', '18:00'),
+ (8, '리사', '2025-03-05', '10:01', '18:03'),
+ (8, '리사', '2025-03-06', '10:10', '17:40'),
+ (8, '리사', '2025-03-07', '10:02', '18:05'),
+ (8, '리사', '2025-03-10', '13:02', '18:00'),
+ (8, '리사', '2025-03-11', '10:05', '18:10'),
+ (8, '리사', '2025-03-12', '10:03', '18:00'),
+
+ -- 제임스(crew_id=9)
+ (9, '제임스', '2025-03-04', '09:55', '18:00'),
+ (9, '제임스', '2025-03-05', '09:59', '18:00'),
+ (9, '제임스', '2025-03-06', '09:59', '18:10'),
+ (9, '제임스', '2025-03-07', '10:05', '18:00'),
+ (9, '제임스', '2025-03-10', '12:59', '17:50'),
+ (9, '제임스', '2025-03-11', '09:55', '18:00'),
+ (9, '제임스', '2025-03-12', '10:01', '18:00'),
+
+ -- 류시(crew_id=10)
+ (10, '류시', '2025-03-04', '10:04', '18:00'),
+ (10, '류시', '2025-03-05', '10:02', '18:02'),
+ (10, '류시', '2025-03-06', '09:45', '18:05'),
+ (10, '류시', '2025-03-07', '10:10', '18:00'),
+ (10, '류시', '2025-03-10', '13:03', '17:40'),
+ (10, '류시', '2025-03-11', '09:57', '18:10'),
+ (10, '류시', '2025-03-12', '09:59', '17:30'),
+
+ -- 디노(crew_id=11)
+ (11, '디노', '2025-03-04', '09:59', '18:00'),
+ (11, '디노', '2025-03-05', '10:10', '18:00'),
+ (11, '디노', '2025-03-06', '09:57', '18:05'),
+ (11, '디노', '2025-03-07', '10:00', '18:03'),
+ (11, '디노', '2025-03-10', '12:57', '18:00'),
+ (11, '디노', '2025-03-11', '09:55', '18:00'),
+ (11, '디노', '2025-03-12', '10:03', '18:05'),
+
+ -- 시지프(crew_id=12)
+ (12, '시지프', '2025-03-04', '09:52', '18:05'),
+ (12, '시지프', '2025-03-05', '09:55', '18:00'),
+ (12, '시지프', '2025-03-06', '10:15', '18:00'),
+ (12, '시지프', '2025-03-07', '10:03', '17:59'),
+ (12, '시지프', '2025-03-10', '12:58', '18:10'),
+ (12, '시지프', '2025-03-11', '09:55', '18:00'),
+ (12, '시지프', '2025-03-12', '10:10', '18:10');
diff --git a/css/common.css b/css/common.css
index 78a113f..40eea4a 100644
--- a/css/common.css
+++ b/css/common.css
@@ -1,16 +1,62 @@
-@import url("https://fonts.googleapis.com/css2?family=Jua&family=Noto+Sans+KR:wght@400;700&display=swap");
+@import url("https://fonts.googleapis.com/css2?family=Jua&display=swap");
+@import url("https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/dist/web/static/pretendard.css");
+
+:root {
+ /* GitHub Primer Inspired Colors */
+ --color-canvas-default: #ffffff;
+ --color-canvas-subtle: #f6f8fa;
+ --color-border-default: #d0d7de;
+ --color-border-muted: hsla(210, 18%, 87%, 1);
+ --color-fg-default: #1f2328;
+ --color-fg-muted: #656d76;
+ --color-accent-fg: #0969da;
+ --color-neutral-muted: rgba(175, 184, 193, 0.2);
+ --color-btn-bg: #f6f8fa;
+ --color-btn-hover-bg: #f3f4f6;
+ --color-btn-active-bg: #ebecf0;
+
+ --primary-color: var(--color-accent-fg);
+ --nav-bg-color: var(--color-canvas-default);
+ --hero-bg-color: var(--color-canvas-subtle);
+ --card-bg-color: var(--color-canvas-default);
+ --divider-color: var(--color-border-muted);
+ --font-default-color: var(--color-fg-default);
+
+ /* Fonts: Cute Headings, Basic Body */
+ --font-family-base: "Pretendard", -apple-system, BlinkMacSystemFont, "Segoe UI",
+ Roboto, "Helvetica Neue", Arial, sans-serif;
+ --font-family-heading: "Jua", sans-serif;
+}
+
+html {
+ scroll-behavior: smooth;
+ scroll-padding-top: 80px;
+}
body {
- font-family: "Noto Sans KR", system-ui, -apple-system, BlinkMacSystemFont,
- "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Open Sans",
- "Helvetica Neue", sans-serif;
+ font-family: var(--font-family-base);
font-size: 16px;
+ line-height: 1.6;
color: var(--font-default-color);
+ background-color: var(--color-canvas-default);
+ -webkit-font-smoothing: antialiased;
}
h1,
h2,
-h3 {
- font-family: "Jua", serif;
+h3,
+h4 {
+ font-family: var(--font-family-heading);
font-weight: 400;
+ color: var(--color-fg-default);
+ margin-top: 0;
+}
+
+a {
+ color: var(--color-accent-fg);
+ text-decoration: none;
+}
+
+a:hover {
+ text-decoration: underline;
}
diff --git a/css/wiki-layout.css b/css/wiki-layout.css
index c813d17..bfaa0b0 100644
--- a/css/wiki-layout.css
+++ b/css/wiki-layout.css
@@ -1,62 +1,72 @@
-/* 상단 내비게이션 */
+/* Layout Components */
+
+/* Sticky Navigation */
.top-nav {
position: sticky;
top: 0;
display: flex;
align-items: center;
justify-content: space-between;
- padding: 0 32px;
- height: 56px;
- background-color: var(--nav-bg-color);
+ padding: 0 24px;
+ height: 64px;
+ background-color: rgba(255, 255, 255, 0.8);
+ backdrop-filter: blur(8px);
+ -webkit-backdrop-filter: blur(8px);
border-bottom: 1px solid var(--divider-color);
- z-index: 10;
+ z-index: 100;
}
.nav-logo {
- font-family: "Jua", serif;
- font-size: 1.4rem;
- color: var(--primary-color);
+ font-weight: 700;
+ font-size: 1.25rem;
+ color: var(--color-fg-default);
+ letter-spacing: -0.02em;
}
.nav-links {
display: flex;
- gap: 24px;
+ gap: 20px;
list-style: none;
+ margin: 0;
+ padding: 0;
}
.nav-links a {
text-decoration: none;
- color: var(--font-default-color);
+ color: var(--color-fg-muted);
font-size: 0.9rem;
+ font-weight: 500;
transition: color 0.2s;
}
.nav-links a:hover {
- color: var(--primary-color);
+ color: var(--color-fg-default);
}
-/* 프로필 히어로 */
+/* Hero Section / Profile */
.hero {
- display: flex;
- justify-content: center;
- padding: 64px 32px;
+ padding: 48px 24px;
background-color: var(--hero-bg-color);
+ border-bottom: 1px solid var(--divider-color);
}
.profile-card {
display: flex;
align-items: center;
gap: 32px;
- max-width: 600px;
+ max-width: 1000px;
+ margin: 0 auto;
}
.profile-image {
- width: 160px;
- height: 160px;
+ width: 120px;
+ height: 120px;
border-radius: 50%;
- background-color: #ddd;
+ border: 1px solid var(--color-border-default);
+ background-color: var(--color-canvas-default);
flex-shrink: 0;
overflow: hidden;
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
}
.profile-image img {
@@ -67,305 +77,284 @@
.profile-info h1 {
font-size: 2rem;
- margin-bottom: 8px;
+ margin-bottom: 4px;
+ letter-spacing: -0.01em;
}
.profile-info .tagline {
- color: #777;
- line-height: 1.6;
+ font-size: 1.1rem;
+ color: var(--color-fg-muted);
+ font-weight: 400;
}
-/* 메인 콘텐츠 */
+/* Container */
main {
- max-width: 780px;
+ max-width: 1000px;
margin: 0 auto;
- padding: 0 24px;
+ padding: 40px 24px;
}
.content-section {
- padding: 48px 0;
- border-bottom: 1px solid var(--divider-color);
-}
-
-.content-section:last-child {
- border-bottom: none;
+ margin-bottom: 48px;
}
.content-section h2 {
- font-size: 1.6rem;
+ font-size: 1.5rem;
margin-bottom: 16px;
+ padding-bottom: 8px;
+ border-bottom: 1px solid var(--divider-color);
}
-.content-section > p,
.section-desc {
- color: #666;
- line-height: 1.8;
+ color: var(--color-fg-muted);
margin-bottom: 24px;
}
-/* 갤러리 그리드 */
+/* Gallery Grid */
.gallery-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
- gap: 8px;
+ gap: 16px;
}
.gallery-grid img {
width: 100%;
- aspect-ratio: 1 / 1;
+ aspect-ratio: 1;
object-fit: cover;
- border-radius: 4px;
+ border-radius: 8px;
+ border: 1px solid var(--color-border-muted);
+ transition:
+ transform 0.3s cubic-bezier(0.4, 0, 0.2, 1),
+ box-shadow 0.3s ease;
}
-/* 인생 작품 카드 */
-.works-list {
+.gallery-grid img:hover {
+ transform: translateY(-4px);
+ box-shadow: 0 8px 24px rgba(140, 149, 159, 0.2);
+ border-color: #d0d7de;
+}
+
+/* Cards (Works, TIL) */
+.card-grid {
display: flex;
flex-direction: column;
- gap: 24px;
+ gap: 16px;
}
-.work-card {
+.work-card,
+.til-item {
display: flex;
- gap: 20px;
- padding: 20px;
- border-radius: 8px;
- background-color: var(--card-bg-color);
+ padding: 24px;
+ border: 1px solid var(--color-border-default);
+ border-radius: 12px;
+ background-color: var(--color-canvas-default);
+ transition:
+ border-color 0.2s,
+ box-shadow 0.2s;
+}
+
+.work-card:hover,
+.til-item:hover {
+ border-color: #d0d7de;
+ background: linear-gradient(145deg, #ffffff 0%, #f6f8fa 100%);
+ box-shadow: 0 8px 24px rgba(140, 149, 159, 0.15);
+ transform: translateY(-2px);
+}
+
+.work-card {
+ gap: 40px;
+ align-items: center;
}
.work-thumbnail {
- width: 140px;
- height: 180px;
- background-color: #ddd;
- border-radius: 4px;
- flex-shrink: 0;
+ width: 440px;
+ aspect-ratio: 16 / 9;
+ border-radius: 12px;
overflow: hidden;
+ border: 1px solid var(--color-border-muted);
+ flex-shrink: 0;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ background-color: #000;
}
-.work-thumbnail img {
+.work-thumbnail img,
+.work-thumbnail iframe {
width: 100%;
height: 100%;
object-fit: cover;
+ border: none;
}
-.work-info h3 {
- font-size: 1.2rem;
- margin-bottom: 8px;
+.work-info h3,
+.til-item h3 {
+ font-size: 1.25rem;
+ margin-bottom: 12px;
}
-.work-info p {
- color: #666;
+.work-info p,
+.til-item p {
+ color: var(--color-fg-muted);
+ font-size: 0.95rem;
line-height: 1.6;
}
-/* TIL */
+/* TIL List */
.til-list {
display: flex;
flex-direction: column;
- gap: 20px;
+ gap: 16px;
}
.til-item {
- padding: 20px;
- border-left: 3px solid var(--primary-color);
- background-color: var(--card-bg-color);
- border-radius: 0 8px 8px 0;
+ flex-direction: column;
}
.til-item time {
+ display: inline-block;
font-size: 0.85rem;
- color: #999;
+ color: var(--color-accent-fg);
+ font-weight: 600;
+ background-color: rgba(9, 105, 218, 0.1);
+ padding: 4px 10px;
+ border-radius: 20px;
+ margin-bottom: 12px;
+ width: fit-content;
}
-.til-item h3 {
- font-size: 1.1rem;
- margin: 8px 0;
+/* Form Styles */
+.til-form {
+ margin-bottom: 40px;
+ padding: 32px;
+ border: 1px solid var(--color-border-default);
+ border-radius: 12px;
+ background-color: var(--color-canvas-subtle);
}
-.til-item p {
- color: #666;
- line-height: 1.6;
+.form-group {
+ margin-bottom: 20px;
}
-/* 방명록 */
-.comment-form-container form {
- display: flex;
- flex-direction: column;
- gap: 12px;
- margin-bottom: 32px;
+.form-group label {
+ display: block;
+ font-size: 0.9rem;
+ font-weight: 600;
+ margin-bottom: 8px;
+ color: var(--color-fg-default);
}
-.comment-form-container input,
-.comment-form-container textarea {
- padding: 12px;
- border: 1px solid var(--divider-color);
+.form-group input,
+.form-group textarea {
+ width: 100%;
+ padding: 10px 14px;
+ border: 1px solid var(--color-border-default);
border-radius: 6px;
font-family: inherit;
font-size: 0.95rem;
+ background-color: var(--color-canvas-default);
+ transition:
+ border-color 0.2s,
+ box-shadow 0.2s;
+ box-sizing: border-box;
}
-.comment-form-container textarea {
- min-height: 80px;
- resize: vertical;
+.form-group input:focus,
+.form-group textarea:focus {
+ outline: none;
+ border-color: var(--color-accent-fg);
+ box-shadow: 0 0 0 3px rgba(9, 105, 218, 0.15);
}
-.comment-buttons {
+.form-actions {
display: flex;
- gap: 8px;
- justify-content: flex-end;
+ gap: 12px;
}
-.comment-buttons button {
- padding: 8px 20px;
- border: none;
+.btn {
+ padding: 8px 16px;
+ font-size: 0.9rem;
+ font-weight: 600;
border-radius: 6px;
cursor: pointer;
- font-size: 0.9rem;
+ transition:
+ background-color 0.2s,
+ border-color 0.2s;
+ border: 1px solid transparent;
}
-.comment-buttons button[type="button"] {
- background-color: var(--primary-color);
+.btn-primary {
+ background-color: var(--color-accent-fg);
color: #fff;
}
-.comment-buttons button[type="reset"] {
- background-color: #eee;
- color: #666;
+.btn-primary:hover {
+ background-color: var(--color-fg-default);
}
-.comment-list {
- list-style: none;
- display: flex;
- flex-direction: column;
- gap: 16px;
+.btn-secondary {
+ background-color: var(--color-btn-bg);
+ border-color: var(--color-border-default);
+ color: var(--color-fg-default);
}
-.comment-item {
- padding: 16px;
- background-color: var(--card-bg-color);
- border-radius: 8px;
-}
-
-.comment-item strong {
- font-size: 0.9rem;
- color: var(--primary-color);
+.btn-secondary:hover {
+ background-color: var(--color-btn-hover-bg);
}
-.comment-item p {
- margin-top: 6px;
- color: #555;
- line-height: 1.6;
-}
-
-/* 푸터 */
-footer {
- text-align: center;
- padding: 32px;
- color: #aaa;
- font-size: 0.85rem;
-}
-
-/* 기본 정보 테이블 */
+/* Table */
.info-table {
width: 100%;
border-collapse: collapse;
-}
-
-.info-table th,
-.info-table td {
- padding: 12px 16px;
- border-bottom: 1px solid var(--divider-color);
- text-align: left;
-}
-
-.info-table th {
- width: 140px;
- color: #555;
- font-weight: 700;
- background-color: var(--card-bg-color);
-}
-
-.info-table td {
- color: #333;
-}
-
-/* TIL 작성 폼 */
-.til-form {
- display: flex;
- flex-direction: column;
- gap: 14px;
- padding: 24px;
- margin-bottom: 32px;
border: 1px solid var(--divider-color);
border-radius: 8px;
- background-color: var(--card-bg-color);
-}
-
-.til-form .form-group {
- display: flex;
- flex-direction: column;
- gap: 6px;
-}
-
-.til-form label {
- font-size: 0.85rem;
- font-weight: 700;
- color: #555;
+ overflow: hidden;
}
-.til-form input,
-.til-form textarea {
- padding: 10px 12px;
- border: 1px solid var(--divider-color);
- border-radius: 6px;
- font-family: inherit;
- font-size: 0.95rem;
+.info-table th {
+ width: 160px;
+ text-align: left;
+ padding: 16px;
+ background-color: var(--color-canvas-subtle);
+ border-bottom: 1px solid var(--divider-color);
+ font-weight: 600;
+ color: var(--color-fg-muted);
}
-.til-form textarea {
- min-height: 80px;
- resize: vertical;
+.info-table td {
+ padding: 16px;
+ border-bottom: 1px solid var(--divider-color);
}
-.til-form .form-actions {
- display: flex;
- gap: 8px;
- justify-content: flex-end;
+.info-table tr:last-child th,
+.info-table tr:last-child td {
+ border-bottom: none;
}
-.til-form button {
- padding: 8px 20px;
- border: none;
- border-radius: 6px;
- cursor: pointer;
+/* Footer */
+footer {
+ text-align: center;
+ padding: 64px 24px;
+ color: var(--color-fg-muted);
+ border-top: 1px solid var(--divider-color);
font-size: 0.9rem;
}
-.til-form button[type="submit"] {
- background-color: var(--primary-color);
- color: #fff;
-}
-
-.til-form button[type="reset"] {
- background-color: #eee;
- color: #666;
-}
+/* Responsive */
+@media (max-width: 768px) {
+ .profile-card {
+ flex-direction: column;
+ text-align: center;
+ gap: 20px;
+ }
-/* 영상 삽입 */
-.work-card iframe,
-.work-card video {
- width: 100%;
- max-width: 480px;
- aspect-ratio: 16 / 9;
- border: none;
- border-radius: 4px;
-}
+ .work-card {
+ flex-direction: column;
+ }
-/* 변수 */
-:root {
- --primary-color: #4a7c59;
- --nav-bg-color: #fff;
- --hero-bg-color: #f8faf8;
- --card-bg-color: #fafafa;
- --divider-color: #e8e8e8;
- --font-default-color: #333;
+ .work-thumbnail {
+ width: 100%;
+ height: auto;
+ aspect-ratio: 4/5;
+ }
}
diff --git a/images/image.png b/images/image.png
new file mode 100644
index 0000000..de41f69
Binary files /dev/null and b/images/image.png differ
diff --git a/images/image1.png b/images/image1.png
new file mode 100644
index 0000000..c72c1be
Binary files /dev/null and b/images/image1.png differ
diff --git a/images/image10.png b/images/image10.png
new file mode 100644
index 0000000..4c01fb0
Binary files /dev/null and b/images/image10.png differ
diff --git a/images/image2.png b/images/image2.png
new file mode 100644
index 0000000..8d45244
Binary files /dev/null and b/images/image2.png differ
diff --git a/images/image3.png b/images/image3.png
new file mode 100644
index 0000000..6b42bb0
Binary files /dev/null and b/images/image3.png differ
diff --git a/images/image4.png b/images/image4.png
new file mode 100644
index 0000000..c639825
Binary files /dev/null and b/images/image4.png differ
diff --git a/images/image5.png b/images/image5.png
new file mode 100644
index 0000000..e29b620
Binary files /dev/null and b/images/image5.png differ
diff --git a/images/image6.png b/images/image6.png
new file mode 100644
index 0000000..5d65452
Binary files /dev/null and b/images/image6.png differ
diff --git a/images/image7.png b/images/image7.png
new file mode 100644
index 0000000..9bbb199
Binary files /dev/null and b/images/image7.png differ
diff --git a/images/image8.png b/images/image8.png
new file mode 100644
index 0000000..47ec54a
Binary files /dev/null and b/images/image8.png differ
diff --git a/images/image9.png b/images/image9.png
new file mode 100644
index 0000000..8bdb1ed
Binary files /dev/null and b/images/image9.png differ
diff --git a/index.html b/index.html
index e6338cf..b010051 100644
--- a/index.html
+++ b/index.html
@@ -1,148 +1,220 @@
-
+
-
+
-
-
-
-
+ Henny's Dev Blog
+
+
+
+
- Today I Learn
-
+
- My Page
+ Henny.log
-
-
+
+
-
-
+
-
닉네임
-
한 줄 소개를 작성해주세요.
+
해니
+
우테코 프론트엔드 8기입니다. 아자잣 화이팅!!
+
-
+
-
-
- 자기소개
- 자기소개를 작성해주세요.
-
-
-
-
+
- 기본 정보
-
-
-
- 이름
-
-
-
- MBTI
-
-
-
- 취미
-
-
-
- 좋아하는 음식
-
-
-
+ 자기소개
+
+
+
+ Name
+ 장혜원
+
+
+ MBTI
+ INFJ
+
+
+ Hobbies
+ 뜨개질, 잠자기, 드라마 정주행 사랑합니다, 팟캐스트 듣기
+
+
+ Favorites
+
+ 떡볶이, 소금빵 킬러..빵이라면 다 킬러입니다, 면 킬러입니다, 한식
+ 킬러입니다
+
+
+
+
-
+
- 나만의 갤러리
- 갤러리 부제를 작성해주세요. (예: 기억에 남는 여행지)
+ 갤러리
+
+ 일상의 작은 기록들과 좋아하는 순간들을 모았습니다.
+
-
+
나를 울린 인생 작품
-
-
-
+
-
+ VIDEO
-
작품 제목
-
작품에 대한 설명을 작성해주세요.
+
도시남녀 사랑법
+
+ 이걸 안보시면 진정한 드라마 킬러가 아니십니다. 지창욱 이 사람은
+ 상처받은 저런 눈빛으로 매달리는 연기를 너무 잘해여..보는 내가
+ 미안하고..속상하구..안아주고 싶고..제가 진짜 드라마 광인데
+ 그래서 별의별 다정하구 착하구 설레는 남주들 다 봤지만 ㄹㅇ 이
+ 드라마 남주가 그냥 원탑임. 어떻게 설레는 연기를 자연스럽게 할 수
+ 있고 한 사람 하나만 바라보고 매달리고 집착하는 순정남 연기를
+ 어떻게 저렇게 잘함. 진짜 사랑해요. 아 암튼 이 드라마가 무슨
+ 내용이냐면요. 휴가를 갈려고 양양을 찾은 남주가 거기서 여주를
+ 만나 처음부터 서로한테 반해가지구 사랑에 빠지게 되는데요 .그렇게
+ 두 달동안 행복하게 나날을 보내고 있던 도중에 남주가 서울로 먼저
+ 올라가게 되면서 잠깐 헤어지게 됐는데 여주가 그 이후로 자취를
+ 감춰요. 그 이후에 미친듯이 남주는 여주를 찾아다니다가 1년 뒤에
+ 만나게 됐는데, 근데!! 알고보니까 여주가 양양에서는 다른 이름을
+ 가지구 다른 사람으로 살아갔다는 걸 알게 되고...그렇게 남주가
+ 배신감 느끼고 여주랑 사이가 나빠졌다가 여주 사연듣구 막 다시
+ 몽글몽글해지는 사이가 되는 암튼 그런 내용인데 글만 읽으면 그런
+ 감정이 1도 안느껴지니까 당장 보러가시길..
+
-
+
- TIL
+ Today I Learned
오늘 새롭게 배운 것들을 기록합니다.
-
+
-
+
-
2026-03-15
HTML 시멘틱 태그
- header, nav, main, section, article, footer 등 시멘틱 태그의 역할과 사용법을 배웠다.
+
+ header, nav, main, section, article, footer 등 시멘틱 태그의
+ 역할과 사용법을 배웠다. 웹 접근성과 SEO 향상에 큰 도움이 된다.
+
+
+
+ 2026-03-15
+ 실행 컨텍스트 (Execution Context)
+
+ 자바스크립트 엔진이 코드를 실행하기 위해 필요한 환경 정보를
+ 모아놓은 객체의 생성 과정을 깊이 있게 학습했다.
+
-
diff --git a/js/index.js b/js/index.js
index da9ea6a..8167e16 100644
--- a/js/index.js
+++ b/js/index.js
@@ -1,12 +1,45 @@
-// TODO: TIL 폼 등록 기능을 구현하세요
-// 1. 폼 요소와 목록 요소를 querySelector로 선택합니다.
-// 2. 폼의 submit 이벤트를 감지하여 새 TIL 항목을 목록에 추가합니다.
-
+// TIL List registration functionality
const tilForm = document.querySelector("#til-form");
const tilList = document.querySelector("#til-list");
-tilForm.addEventListener("submit", function (event) {
- event.preventDefault();
+if (tilForm && tilList) {
+ tilForm.addEventListener("submit", function (event) {
+ event.preventDefault();
+
+ // 1. Get input values
+ const dateInput = document.querySelector("#til-date");
+ const titleInput = document.querySelector("#til-title");
+ const contentInput = document.querySelector("#til-content");
+
+ const date = dateInput.value;
+ const title = titleInput.value;
+ const content = contentInput.value;
+
+ // 2. Create new TIL item structure
+ const article = document.createElement("article");
+ article.classList.add("til-item");
+
+ const time = document.createElement("time");
+ time.textContent = date;
+
+ const h3 = document.createElement("h3");
+ h3.textContent = title;
+
+ const p = document.createElement("p");
+ p.textContent = content;
+
+ // 3. Assemble and add to list
+ article.appendChild(time);
+ article.appendChild(h3);
+ article.appendChild(p);
+
+ // Add to the top of the list
+ tilList.prepend(article);
- // TODO: 입력값을 가져와서 새 TIL 항목을 만들어 목록에 추가하세요
-});
+ // 4. Reset form
+ tilForm.reset();
+
+ // Optional: Scroll to the new item
+ article.scrollIntoView({ behavior: 'smooth', block: 'center' });
+ });
+}