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 - + - -
+ +
- - + Haeny Profile
-

닉네임

-

한 줄 소개를 작성해주세요.

+

해니

+

우테코 프론트엔드 8기입니다. 아자잣 화이팅!!

+
+ GitHub + Blog +
-
+
- -
-

자기소개

-

자기소개를 작성해주세요.

- -
- - +
-

기본 정보

- - - - - - - - - - - - - - - - - - -
이름
MBTI
취미
좋아하는 음식
+

자기소개

+
+ + + + + + + + + + + + + + + + + +
Name장혜원
MBTIINFJ
Hobbies뜨개질, 잠자기, 드라마 정주행 사랑합니다, 팟캐스트 듣기
Favorites + 떡볶이, 소금빵 킬러..빵이라면 다 킬러입니다, 면 킬러입니다, 한식 + 킬러입니다 +
+
- + - +

나를 울린 인생 작품

-
- - +
- +
-

작품 제목

-

작품에 대한 설명을 작성해주세요.

+

도시남녀 사랑법

+

+ 이걸 안보시면 진정한 드라마 킬러가 아니십니다. 지창욱 이 사람은 + 상처받은 저런 눈빛으로 매달리는 연기를 너무 잘해여..보는 내가 + 미안하고..속상하구..안아주고 싶고..제가 진짜 드라마 광인데 + 그래서 별의별 다정하구 착하구 설레는 남주들 다 봤지만 ㄹㅇ 이 + 드라마 남주가 그냥 원탑임. 어떻게 설레는 연기를 자연스럽게 할 수 + 있고 한 사람 하나만 바라보고 매달리고 집착하는 순정남 연기를 + 어떻게 저렇게 잘함. 진짜 사랑해요. 아 암튼 이 드라마가 무슨 + 내용이냐면요. 휴가를 갈려고 양양을 찾은 남주가 거기서 여주를 + 만나 처음부터 서로한테 반해가지구 사랑에 빠지게 되는데요 .그렇게 + 두 달동안 행복하게 나날을 보내고 있던 도중에 남주가 서울로 먼저 + 올라가게 되면서 잠깐 헤어지게 됐는데 여주가 그 이후로 자취를 + 감춰요. 그 이후에 미친듯이 남주는 여주를 찾아다니다가 1년 뒤에 + 만나게 됐는데, 근데!! 알고보니까 여주가 양양에서는 다른 이름을 + 가지구 다른 사람으로 살아갔다는 걸 알게 되고...그렇게 남주가 + 배신감 느끼고 여주랑 사이가 나빠졌다가 여주 사연듣구 막 다시 + 몽글몽글해지는 사이가 되는 암튼 그런 내용인데 글만 읽으면 그런 + 감정이 1도 안느껴지니까 당장 보러가시길.. +

- +
-

TIL

+

Today I Learned

오늘 새롭게 배운 것들을 기록합니다.

- +
- +
- - + +
- - + +
- - + +
- +
-

HTML 시멘틱 태그

-

header, nav, main, section, article, footer 등 시멘틱 태그의 역할과 사용법을 배웠다.

+

+ header, nav, main, section, article, footer 등 시멘틱 태그의 + 역할과 사용법을 배웠다. 웹 접근성과 SEO 향상에 큰 도움이 된다. +

+
+
+ +

실행 컨텍스트 (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' }); + }); +}