Files
mini-test-projects-2-release/module-a/module-a-kr.md
2026-04-12 18:58:03 +09:00

23 KiB
Raw Blame History

Test Project: Module A — Speed Test

1. 과제 개요 (Project Overview)

본 과제는 웹 기술 전반에 걸친 실무 숙련도를 빠르고 정확하게 측정하는 Speed Test 형식의 평가입니다. 선수는 A(Design), B(Layout), C(Frontend), D(Backend) 4개 분류에 걸쳐 총 20개의 독립 Task를 제한 시간 내에 완성해야 합니다. 각 Task는 서로 독립적이며, 순서에 상관없이 자유롭게 풀이할 수 있습니다.


2. 기술 스택 및 제약 조건 (Tech Stack & Constraints)

분류 허용 기술 제약 사항
A. Design GIMP Photoshop 등 타 편집 프로그램 사용 불가
B. Layout HTML, CSS JavaScript 사용 불가
C. Frontend JavaScript (Vanilla) 외부 라이브러리/프레임워크 사용 불가
D. Backend PHP, MySQL PDO 방식으로 DB 연결
  • 모든 Task의 결과물은 데스크톱 Chrome 브라우저 기준으로 평가합니다.
  • 각 Task의 결과물은 /module_a/{task_id}/ 경로에 저장하십시오. (예: /module_a/a1/, /module_a/b1/)
  • Design Task 제출 시에는 GIMP 원본 파일(.xcf)도 함께 포함해야 합니다.

3. Task 목록 및 요구사항 (Tasks)

🎨 A. Design — GIMP 활용 능력

제공 파일: A1(source.png), A2(base.jpg), A3(photo.jpg, mask.png) Task의 작업 디렉토리에 미리 배치되어 있습니다. A4, A5는 제공 파일이 없으며 선수가 직접 생성합니다.

A1. 레이어 합성 및 텍스트 배치 Easy

GIMP에서 레이어 구조를 활용한 이미지를 제작하십시오.

  • 캔버스 크기: 800×400px, 해상도: 72dpi
  • 레이어 1: #2C3E50 단색 배경을 채우십시오.
  • 레이어 2: 제공된 이미지(source.png)를 불러와 캔버스 중앙에 배치하고 불투명도를 **70%**로 설정하십시오.
  • 레이어 3: 텍스트 "Hello, GIMP" 를 흰색(#FFFFFF), 36pt로 추가하고 캔버스 좌측 상단 (x: 20px, y: 20px) 에 배치하십시오.
  • 모든 레이어를 병합(Flatten Image)하기 전에 .xcf 원본 파일로 먼저 저장하십시오. 이후 레이어를 병합하여 result.png로 내보내십시오.

A2. 선택 영역 및 레이어 합성 Easy

GIMP의 선택 도구와 레이어를 활용하여 이미지에 도형 효과를 적용하십시오.

  • 제공된 이미지(base.jpg)를 배경 레이어로 여십시오.
  • 효과 1: 새 레이어를 추가하고 이미지 좌측 상단 (0, 0) ~ (200, 200) 영역을 사각형 선택 도구로 선택한 뒤 #E74C3C으로 채우십시오. 이 레이어의 불투명도를 **60%**로 설정하십시오.
  • 효과 2: 또 다른 새 레이어를 추가하고 이미지 중앙에 지름 150px 타원 선택 영역을 만드십시오. Selection → Border (한국어: 선택 → 경계선)에서 경계 두께를 3px로 설정한 뒤 #FFFFFF로 채우십시오.
  • .xcf 원본 파일로 먼저 저장한 뒤, 레이어를 병합(Flatten Image)하여 result.jpg로 내보내십시오.

A3. 색상 보정 및 레이어 마스크 Normal

제공된 이미지(photo.jpg)를 GIMP에서 색상 보정하고 레이어 마스크를 적용하십시오.

  • 색상 보정: 색조-채도(Hue-Saturation) 도구로 채도(Saturation)를 +40 올리십시오.
  • 곡선 조정: 곡선(Curves) 도구에서 밝은 영역 출력값을 +20 이상, 어두운 영역 출력값을 -20 이하로 조정하여 대비를 높이십시오.
  • 레이어 마스크: 이미지 레이어에 레이어 마스크를 추가하고, 제공된 마스크 이미지(mask.png)를 해당 마스크 레이어에 붙여넣어 피사체 외 배경이 제거되도록 하십시오.
  • 완성본을 result.png로 내보내기 전에 .xcf 원본 파일로 먼저 저장하십시오. 내보낼 때 투명 배경이 유지되도록 PNG 형식을 사용하십시오.

A4. 텍스트 이펙트 및 발광 효과 Normal

GIMP에서 텍스트에 발광(glow) 시각 효과를 적용하십시오.

  • 캔버스 크기: 600×200px, 배경색: #1A1A2E
  • 텍스트 "SPEED TEST" 를 흰색(#FFFFFF), Bold, 60pt로 캔버스 중앙에 배치하십시오.
  • 텍스트 레이어를 복사하여 복사본 레이어를 텍스트 레이어 아래에 배치하십시오.
  • 복사본 레이어를 래스터화(Layer → Rasterize)한 뒤, 레이어 패널에서 해당 레이어의 모드(Mode)를 **Color**로 먼저 변경하십시오. 그 다음 전경색을 #4A90D9로 설정하고 Edit → Fill with Foreground Color를 실행하여 파란색이 적용되도록 하십시오.
  • 해당 복사본 레이어에 가우시안 흐림(Gaussian Blur) 효과를 반경 8px로 적용하여 외곽 발광 효과를 완성하십시오.
  • .xcf 원본 파일로 먼저 저장한 뒤 result.png로 내보내십시오.

A5. 텍스트 워프 및 필터 효과 Hard

GIMP에서 텍스트를 래스터화하고 Distorts 필터를 조합하여 시각적 변형 효과를 구현하십시오.

  • 캔버스 크기: 800×300px, 배경색: #0D0D0D
  • 텍스트 "DISTORTION" 을 흰색(#FFFFFF), Bold, 72pt로 캔버스 중앙에 배치하십시오.
  • 텍스트 레이어를 래스터화(Layer → Rasterize)하십시오.
  • 래스터화된 텍스트 레이어에 다음 두 가지 Distorts 필터를 순서대로 적용하십시오.
    • Ripple: Amplitude 8, Wavelength 40, 방향 Horizontal
    • Whirl and Pinch: Whirl angle 20도, Pinch 0, Radius 1.0
  • 필터 적용이 완료된 텍스트 레이어를 복사하여 복사본에 가우시안 흐림(Gaussian Blur) 반경 3px를 적용하고, 복사본을 원본 텍스트 레이어 아래에 배치하여 잔상 효과를 구현하십시오.
  • .xcf 원본 파일로 먼저 저장한 뒤 result.png로 내보내십시오.

📐 B. Layout — HTML / CSS 전용

B1. Flexbox 카드 레이아웃 Easy

HTML과 CSS만을 사용하여 카드 목록 레이아웃을 구현하십시오.

  • Flexbox를 사용하여 카드를 한 줄에 4개씩 배치하십시오. (flex-wrap: wrap 사용)
  • 각 카드는 이미지 영역(<div>, 높이 160px, 배경색 지정), 제목(<h3>), 본문 텍스트(<p>), 버튼(<button>)을 포함해야 합니다.
  • 카드 간 간격은 24px로 설정하십시오.
  • 더미 카드를 8개 작성하십시오. 각 카드의 이미지 영역 배경색은 서로 달라야 합니다.

B2. Sticky 헤더 및 CSS 인터랙션 Easy

HTML과 CSS만을 사용하여 헤더와 CSS 전용 인터랙션을 구현하십시오.

  • Flexbox를 사용하여 좌측에 로고 텍스트, 중앙에 메뉴 링크(4개), 우측에 버튼(2개)을 배치하십시오.
  • 메뉴 링크 호버 시 하단에 2px 실선 언더라인transition과 함께 나타나는 CSS 전용 인터랙션을 구현하십시오. (border-bottom 또는 ::after pseudo-element 활용)
  • position: sticky; top: 0;을 사용하여 스크롤 시 헤더가 상단에 고정되도록 하십시오.
  • 헤더 아래에 최소 높이 3000px 이상의 더미 콘텐츠를 배치하여 스크롤이 가능하도록 하십시오.

B3. CSS Grid 2단 레이아웃 Normal

HTML과 CSS만을 사용하여 CSS Grid 기반의 2단 페이지 레이아웃을 구현하십시오.

  • CSS Grid를 사용하여 좌측 사이드바(250px 고정)와 우측 메인 콘텐츠 영역(1fr)으로 구성된 2단 레이아웃을 구현하십시오.
  • 좌측 사이드바에는 세로 내비게이션 메뉴(5개 항목)를 배치하십시오. 첫 번째 항목은 배경색과 텍스트 색상으로 활성 상태를 표시하십시오.
  • 우측 메인 영역에는 제목, 본문 텍스트, 3열 카드 그리드(CSS Grid 사용, 카드 6개)를 순서대로 배치하십시오.
  • height: 100vh를 전체 레이아웃 래퍼에 적용하고 overflow: hidden을 설정하십시오. 사이드바와 메인 영역 각각에 height: 100vhoverflow-y: auto를 적용하여 각 영역이 독립적으로 스크롤되도록 하십시오.

B4. CSS 애니메이션 및 트랜지션 Normal

HTML과 CSS만을 사용하여 다양한 CSS 애니메이션과 트랜지션 효과를 하나의 페이지에 구현하십시오.

  • 카드 호버 효과: 카드 4개를 Flexbox로 가로 배치하고, 각 카드에 호버 시 위로 8px 이동하고 그림자가 강해지는 트랜지션 효과를 적용하십시오. (transform: translateY(-8px), box-shadow, transition 활용)
  • 로딩 스피너: @keyframes를 사용하여 원형 로딩 스피너를 구현하십시오. 스피너는 지름 48px, 테두리 두께 4px이며, 상단 테두리(border-top)만 #3498DB 색상으로, 나머지 테두리는 #e0e0e0으로 표시하고 무한 회전합니다.
  • 페이드인 텍스트: 페이지 로드 시 제목 텍스트(<h1>)가 translateY(20px)에서 translateY(0)으로 이동하며 opacity: 0에서 opacity: 1로 나타나는 @keyframes 애니메이션을 구현하십시오. (animation-duration: 0.8s, animation-fill-mode: both)

B5. CSS Grid 및 고급 선택자 활용 Hard

HTML과 CSS만을 사용하여 2단 콘텐츠 레이아웃과 CSS 전용 탭 인터랙션을 구현하십시오.

  • CSS Grid를 사용하여 좌측(65%)과 우측(35%)으로 분할된 2단 레이아웃을 구현하십시오.
  • 좌측 영역: 높이 300px의 메인 이미지 영역(<div>, 배경색 지정)과 하단 썸네일 목록(Flexbox, overflow-x: auto 가로 스크롤)을 배치하십시오. 썸네일은 5개이며 각각 80×80px <div>로 구현하고 서로 다른 배경색을 지정하십시오.
  • 우측 영역 상단: 제목, 부제목, 본문 텍스트를 배치하십시오.
  • 우측 영역 하단: 탭 3개(개요 / 상세 / 리뷰)와 각 탭의 콘텐츠 영역을 구현하십시오.
    • JavaScript 없이 <input type="radio" name="tab">과 CSS :checked 선택자를 활용하여 선택된 탭의 콘텐츠만 표시되도록 구현하십시오.
    • <input type="radio"> 요소는 display: none으로 숨기고, <label> 요소를 탭 버튼처럼 스타일링하십시오.
  • 우측 영역 최하단에 Primary 버튼과 Secondary 버튼 2개를 나란히 배치하십시오. Primary 버튼에는 @keyframes를 활용하여 호버 시 배경색이 자연스럽게 전환되는 애니메이션을 적용하십시오.

C. Frontend — JavaScript (Vanilla)

C1. localStorage 기반 메모 앱 Easy

Vanilla JavaScript와 localStorage를 사용하여 간단한 메모 앱을 구현하십시오.

  • 텍스트 입력창(<textarea>)과 "저장" 버튼이 있어야 합니다.
  • "저장" 버튼 클릭 시 메모가 localStoragememos 키에 JSON 배열로 누적 저장되어야 합니다. 빈 내용은 저장되지 않아야 합니다.
  • 저장된 메모 목록을 페이지 하단에 렌더링하십시오. 각 항목에는 메모 내용과 "삭제" 버튼이 포함되어야 합니다.
  • "삭제" 버튼 클릭 시 해당 항목이 localStorage에서 제거되고 목록이 즉시 갱신되어야 합니다.
  • 페이지 새로고침 후에도 메모 목록이 유지되어야 합니다.

C2. DOM 조작 및 이벤트 처리 Easy

Vanilla JavaScript를 사용하여 동적 리스트 조작 기능을 구현하십시오.

  • 텍스트 입력창(<input type="text">)과 "추가" 버튼이 있어야 합니다.
  • "추가" 버튼 클릭 또는 Enter 키 입력 시 입력값이 리스트에 항목으로 추가됩니다. 빈 값은 추가되지 않으며, 추가 후 입력창은 초기화됩니다.
  • 각 항목에는 "완료" 버튼과 "삭제" 버튼이 포함됩니다.
    • "완료" 버튼 클릭 시 해당 항목의 텍스트에 취소선(text-decoration: line-through)이 적용됩니다. 다시 클릭 시 원래 상태로 돌아옵니다.
    • "삭제" 버튼 클릭 시 해당 항목이 즉시 제거됩니다.
  • 리스트 상단에 현재 전체 항목 수완료된 항목 수를 실시간으로 표시하십시오.

C3. JSON 데이터 동적 렌더링 및 필터링 Normal

Vanilla JavaScript와 Fetch API를 사용하여 제공된 로컬 JSON 파일을 받아 동적으로 렌더링하는 페이지를 구현하십시오.

  • 제공된 posts.json 파일(/module_a/c3/posts.json)을 Fetch API로 불러오십시오. 파일에는 id, title, body, category 키를 가진 객체 배열이 포함되어 있습니다. (총 20개)
  • 불러오는 동안 로딩 스피너(CSS 애니메이션)를 표시하고, 완료 후 제거하십시오.
  • 데이터 전체를 카드 형태로 렌더링하십시오. 각 카드에는 id, title, body, category가 표시되어야 합니다.
  • 제목(title)을 기준으로 실시간 필터링이 가능한 검색창을 구현하십시오. (대소문자 구분 없음, 입력할 때마다 즉시 반영)
  • Fetch 요청 실패 시 "Failed to load data." 오류 메시지를 화면에 표시하십시오.

제공 파일: posts.json (선수 작업 디렉토리 /module_a/c3/에 미리 배치됨)

C4. 폼 유효성 검사 Normal

Vanilla JavaScript를 사용하여 실시간 폼 유효성 검사를 구현하십시오.

  • 다음 필드를 포함한 회원 가입 폼을 작성하십시오: 이름, 이메일, 비밀번호, 비밀번호 확인
  • 각 필드의 유효성 규칙은 다음과 같습니다.
    • 이름: 필수, 2자 이상
    • 이메일: 필수, 이메일 형식 (@. 포함 여부로 검사)
    • 비밀번호: 필수, 8자 이상, 영문자와 숫자를 모두 포함
    • 비밀번호 확인: 필수, 비밀번호 필드와 동일한 값
  • 각 필드에서 포커스가 벗어날 때(blur 이벤트) 해당 필드 아래에 오류 메시지를 표시하십시오. 조건을 충족하면 오류 메시지를 즉시 제거하십시오.
  • "제출" 버튼은 모든 필드가 유효할 때만 활성화됩니다. 클릭 시 alert("Registration complete.")를 표시하십시오.

C5. IntersectionObserver와 지연 렌더링 Hard

IntersectionObserver를 활용하여 무한 스크롤과 지연 렌더링을 구현하십시오.

  • 더미 데이터 50개를 JavaScript 배열로 미리 정의하십시오. 각 항목은 id, title, color를 포함하십시오. (color#RRGGBB 형식의 임의 색상값)
  • 초기 로드 시 아이템을 10개 렌더링하십시오. 각 아이템은 높이 200px의 색상 <div>title 텍스트로 구성합니다. 초기 10개는 color 값을 배경색으로 바로 표시합니다.
  • 목록 맨 아래에 sentinel 요소(<div id="sentinel">)를 두고, 해당 요소가 뷰포트에 진입하면 자동으로 다음 10개를 추가 렌더링하십시오.
  • 아이템이 추가되는 동안 sentinel 바로 앞에 로딩 스피너 요소를 삽입하여 화면에 표시하고, 렌더링 완료 후 제거하십시오. (비동기 시뮬레이션: setTimeout 600ms 사용)
  • 50개에 도달하면 sentinel 감지를 중단(observer.unobserve)하고 "All items loaded." 메시지를 표시하십시오.
  • 새로 추가되는 아이템의 색상 <div>는 배경색을 #cccccc(회색)로 설정한 채로 DOM에 추가하십시오. 별도의 IntersectionObserver 인스턴스를 사용하여 해당 <div>가 뷰포트에 진입하는 순간 data-color 속성에 저장된 실제 색상값을 배경색으로 교체하십시오.

🛢️ D. Backend — PHP / MySQL

제공 파일: D4의 posts_dump.sql과 D5의 data_dump.sql은 해당 Task의 작업 디렉토리에 미리 배치되어 있습니다.

D1. PDO 기반 CRUD API Easy

PHP와 MySQL(PDO)을 사용하여 데이터를 처리하는 REST API를 작성하십시오.

  • 아래 테이블 구조로 items 테이블을 생성하십시오. 테이블 생성 SQL을 table.sql로 함께 제출하십시오.
    • id (INT, PK, AUTO_INCREMENT), title (VARCHAR 100, NOT NULL), content (TEXT), created_at (TIMESTAMP DEFAULT CURRENT_TIMESTAMP)
  • 다음 4개의 엔드포인트를 단일 파일 api.php에 구현하십시오. HTTP 메서드($_SERVER['REQUEST_METHOD'])로 분기하십시오.
    • GET /module_a/d1/api.php → 전체 목록을 JSON 배열로 반환
    • POST /module_a/d1/api.php → 항목 추가. Body(JSON): {"title": "...", "content": "..."}. 요청 본문은 php://input으로 읽어 json_decode()로 파싱하십시오. 성공 시 삽입된 행을 JSON으로 반환
    • PUT /module_a/d1/api.php?id={id}title, content 수정. Body(JSON): {"title": "...", "content": "..."}. 요청 본문은 php://input으로 읽어 json_decode()로 파싱하십시오. 성공 시 수정된 행을 JSON으로 반환
    • DELETE /module_a/d1/api.php?id={id} → 항목 삭제. 성공 시 {"message": "deleted"} 반환
  • 존재하지 않는 id에 대한 PUT/DELETE 요청 시 HTTP 상태코드 404{"error": "Not found"} 를 반환하십시오.
  • 모든 응답은 Content-Type: application/json 헤더와 함께 반환하십시오.

D2. 파일 업로드 처리 Easy

PHP를 사용하여 이미지 파일 업로드 기능을 구현하십시오.

  • upload.php에 파일 업로드 HTML 폼(GET)과 업로드 처리(POST)를 구현하십시오. 폼의 enctypemultipart/form-data로 설정하십시오.
  • 업로드 허용 조건: 확장자는 jpg, jpeg, png, gif만 허용, 파일 크기는 2MB 이하.
  • 조건을 통과한 파일은 uploads/ 디렉토리에 {time()}_{원본파일명} 형식으로 저장하십시오. uploads/ 디렉토리가 없으면 직접 생성하십시오.
  • 저장 성공 시 업로드된 이미지를 <img> 태그로 페이지에 표시하십시오.
  • 조건 위반 시 오류 메시지를 표시하십시오.
    • 확장자 불일치: "File type not allowed."
    • 용량 초과: "File size exceeds 2MB."

D3. 세션 기반 인증 시스템 Normal

PHP 세션과 MySQL을 사용하여 회원 가입, 로그인, 로그아웃 기능을 구현하십시오.

  • users 테이블: id (INT, PK, AUTO_INCREMENT), email (VARCHAR 100, UNIQUE), password (VARCHAR 255), name (VARCHAR 50), created_at (TIMESTAMP DEFAULT CURRENT_TIMESTAMP)
  • register.php: GET 요청 시 회원 가입 HTML 폼을 출력합니다. POST 요청 시 이메일 중복을 확인하고, password_hash()로 비밀번호를 해시하여 DB에 저장한 후 별도 메시지 없이 즉시 login.php로 리다이렉트합니다. 이메일 중복 시 폼 위에 "This email is already registered." 오류 메시지를 표시합니다.
  • login.php: GET 요청 시 로그인 HTML 폼을 출력합니다. POST 요청 시 password_verify()로 검증하고, 성공 시 세션에 user_id, name, email을 저장한 후 mypage.php로 리다이렉트합니다. 실패 시 폼 위에 "Incorrect email or password." 오류 메시지를 표시합니다.
  • logout.php: GET 요청 시 session_destroy()로 세션을 파기하고 login.php로 리다이렉트합니다.
  • mypage.php: 세션에 user_id가 없으면 login.php로 리다이렉트합니다. 로그인 상태에서는 "Welcome, {name}!" 메시지와 로그아웃 링크를 화면에 출력합니다.

D4. 페이지네이션 구현 Normal

PHP와 MySQL(PDO)을 사용하여 서버 사이드 페이지네이션을 구현하십시오.

  • posts 테이블을 직접 생성한 뒤, 제공된 SQL 덤프(posts_dump.sql)를 임포트하십시오. posts 테이블 구조: id, title, content, created_at
  • index.php에서 게시글 목록을 한 페이지에 10개씩 표시하십시오.
  • 현재 페이지는 URL 쿼리스트링 ?page={n}으로 결정되며, 기본값은 1입니다. LIMITOFFSET을 사용한 SQL 쿼리로 해당 페이지의 데이터만 조회하십시오. page 값이 1 미만이거나 전체 페이지 수를 초과하면 1페이지로 리다이렉트하십시오.
  • 페이지 하단에 페이지네이션 링크를 표시하십시오. 전체 페이지 번호를 모두 표시하며, 현재 페이지 번호는 활성 스타일로 구분하고, 이전/다음 버튼을 포함하십시오. 첫 페이지에서 이전 버튼, 마지막 페이지에서 다음 버튼은 비활성화하십시오.
  • 각 게시글 항목에는 제목과 등록일이 표시되며, 제목 클릭 시 view.php?id={id}로 이동하여 해당 게시글의 전체 내용을 표시하십시오.

D5. 다중 테이블 집계 쿼리 Hard

복수 테이블 JOIN과 집계 쿼리를 활용한 통계 API를 구현하십시오.

  • 아래 구조에 맞춰 테이블 3개를 직접 생성한 뒤, 제공된 SQL 덤프(data_dump.sql)를 임포트하십시오. 테이블 생성 순서는 userspostscomments 순서를 지켜야 합니다. (외래 키 참조 순서)
    • users (id, name, created_at)
    • posts (id, user_id — users.id 참조, title, category, view_count, created_at)
    • comments (id, post_id — posts.id 참조, created_at)
  • 단일 파일 stats.php에서 type 쿼리스트링 값으로 분기하여 다음 3개의 응답을 반환하십시오.
    • GET /module_a/d5/stats.php?type=daily → 최근 7일간 날짜별 신규 게시글 수를 날짜 오름차순으로 반환. 응답 형식: [{"date": "2026-04-06", "count": 5}, ...]
    • GET /module_a/d5/stats.php?type=category카테고리별 총 게시글 수와 평균 조회수를 게시글 수 내림차순으로 반환. 응답 형식: [{"category": "tech", "post_count": 12, "avg_views": 340}, ...]
    • GET /module_a/d5/stats.php?type=top → 댓글 수 기준 상위 5개 게시글을 댓글 수 내림차순으로 반환. 응답 형식: [{"post_id": 3, "title": "...", "author": "Alice Johnson", "comment_count": 18}, ...]
  • 모든 쿼리는 PDO Prepared Statement로 작성하십시오.
  • 알 수 없는 type 값에 대해 HTTP 400{"error": "Invalid type"} 을 반환하십시오.

4. 제출 안내 (Submission Guidelines)

선수는 작업 결과물을 서버 디렉토리 /module_a/에 업로드하십시오. 각 Task 결과물은 Task ID를 이름으로 한 하위 디렉토리에 저장해야 합니다. (예: /module_a/a1/, /module_a/b1/, /module_a/c1/, /module_a/d1/)


5. 채점 기준 요약 (Marking Scheme — Total 25점)

분류 Task 배점
A. Design A1. 레이어 합성 및 텍스트 배치 0.5
A2. 선택 영역 및 레이어 합성 0.5
A3. 색상 보정 및 레이어 마스크 1.5
A4. 텍스트 이펙트 및 발광 효과 1.5
A5. 텍스트 워프 및 필터 효과 2.0
B. Layout B1. Flexbox 카드 레이아웃 0.5
B2. Sticky 헤더 및 CSS 인터랙션 0.5
B3. CSS Grid 2단 레이아웃 1.5
B4. CSS 애니메이션 및 트랜지션 1.5
B5. CSS Grid 및 고급 선택자 활용 2.0
C. Frontend C1. localStorage 기반 메모 앱 0.5
C2. DOM 조작 및 이벤트 처리 0.5
C3. JSON 데이터 동적 렌더링 및 필터링 1.5
C4. 폼 유효성 검사 1.5
C5. IntersectionObserver와 지연 렌더링 2.0
D. Backend D1. PDO 기반 CRUD API 0.5
D2. 파일 업로드 처리 0.5
D3. 세션 기반 인증 시스템 1.5
D4. 페이지네이션 구현 1.5
D5. 다중 테이블 집계 쿼리 3.0
합계 25