diff --git a/app/src/main/java/kr/co/vividnext/sodalive/v2/widget/SeriesContentCardSize.kt b/app/src/main/java/kr/co/vividnext/sodalive/v2/widget/SeriesContentCardSize.kt
new file mode 100644
index 00000000..26f43c9f
--- /dev/null
+++ b/app/src/main/java/kr/co/vividnext/sodalive/v2/widget/SeriesContentCardSize.kt
@@ -0,0 +1,34 @@
+package kr.co.vividnext.sodalive.v2.widget
+
+import androidx.annotation.StyleRes
+import kr.co.vividnext.sodalive.R
+
+sealed class SeriesContentCardSize(
+ val cardWidthDp: Int,
+ val thumbnailWidthDp: Int,
+ val thumbnailHeightDp: Int,
+ val labelWidthDp: Int,
+ val thumbnailLabelGapDp: Int,
+ @get:StyleRes val titleStyleRes: Int,
+ @get:StyleRes val creatorStyleRes: Int
+) {
+ data object Large : SeriesContentCardSize(
+ cardWidthDp = 163,
+ thumbnailWidthDp = 163,
+ thumbnailHeightDp = 230,
+ labelWidthDp = 151,
+ thumbnailLabelGapDp = 8,
+ titleStyleRes = R.style.Typography_Heading4,
+ creatorStyleRes = R.style.Typography_Body5
+ )
+
+ data object Small : SeriesContentCardSize(
+ cardWidthDp = 122,
+ thumbnailWidthDp = 122,
+ thumbnailHeightDp = 172,
+ labelWidthDp = 114,
+ thumbnailLabelGapDp = 8,
+ titleStyleRes = R.style.Typography_Body1,
+ creatorStyleRes = R.style.Typography_Caption2
+ )
+}
diff --git a/app/src/main/java/kr/co/vividnext/sodalive/v2/widget/SeriesContentCardView.kt b/app/src/main/java/kr/co/vividnext/sodalive/v2/widget/SeriesContentCardView.kt
new file mode 100644
index 00000000..a84a7b6e
--- /dev/null
+++ b/app/src/main/java/kr/co/vividnext/sodalive/v2/widget/SeriesContentCardView.kt
@@ -0,0 +1,115 @@
+package kr.co.vividnext.sodalive.v2.widget
+
+import android.content.Context
+import android.graphics.Outline
+import android.util.AttributeSet
+import android.view.View
+import android.view.ViewGroup
+import android.view.ViewOutlineProvider
+import android.widget.FrameLayout
+import android.widget.ImageView
+import android.widget.LinearLayout
+import android.widget.TextView
+import kr.co.vividnext.sodalive.R
+import kotlin.math.roundToInt
+
+class SeriesContentCardView @JvmOverloads constructor(
+ context: Context,
+ attrs: AttributeSet? = null,
+ defStyleAttr: Int = 0
+) : LinearLayout(context, attrs, defStyleAttr) {
+
+ private var thumbnailContainer: FrameLayout? = null
+ private var thumbnail: ImageView? = null
+ private var originalTag: View? = null
+ private var labelContainer: LinearLayout? = null
+ private var titleText: TextView? = null
+ private var creatorText: TextView? = null
+
+ init {
+ orientation = VERTICAL
+ }
+
+ override fun onFinishInflate() {
+ super.onFinishInflate()
+ thumbnailContainer = findViewById(R.id.fl_series_thumbnail_container)
+ thumbnail = findViewById(R.id.iv_series_content_thumbnail)
+ originalTag = findViewById(R.id.include_series_original_tag)
+ labelContainer = findViewById(R.id.ll_series_content_label)
+ titleText = findViewById(R.id.tv_series_content_title)
+ creatorText = findViewById(R.id.tv_series_content_creator)
+
+ setThumbnailOutline()
+ setSize(SeriesContentCardSize.Large)
+ }
+
+ fun setSize(size: SeriesContentCardSize) {
+ updateRootWidth(size.cardWidthDp.dpToPx())
+
+ requireNotNull(thumbnailContainer).layoutParams = LayoutParams(
+ size.thumbnailWidthDp.dpToPx(),
+ size.thumbnailHeightDp.dpToPx()
+ )
+
+ requireNotNull(labelContainer).layoutParams = LayoutParams(
+ size.labelWidthDp.dpToPx(),
+ ViewGroup.LayoutParams.WRAP_CONTENT
+ ).apply {
+ topMargin = size.thumbnailLabelGapDp.dpToPx()
+ }
+
+ requireNotNull(titleText).setTextAppearance(size.titleStyleRes)
+ requireNotNull(creatorText).apply {
+ setTextAppearance(size.creatorStyleRes)
+ layoutParams = LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.WRAP_CONTENT
+ ).apply {
+ topMargin = TITLE_CREATOR_GAP_DP.dpToPx()
+ }
+ }
+ }
+
+ fun setContent(
+ title: String,
+ creatorName: String
+ ) {
+ requireNotNull(titleText).text = title
+ requireNotNull(creatorText).text = creatorName
+ }
+
+ fun setOriginalVisible(isVisible: Boolean) {
+ requireNotNull(originalTag).visibility = if (isVisible) VISIBLE else GONE
+ }
+
+ fun thumbnailView(): ImageView = requireNotNull(thumbnail)
+
+ private fun setThumbnailOutline() {
+ requireNotNull(thumbnailContainer).apply {
+ clipToOutline = true
+ outlineProvider = object : ViewOutlineProvider() {
+ override fun getOutline(view: View, outline: Outline) {
+ outline.setRoundRect(0, 0, view.width, view.height, resources.getDimension(R.dimen.radius_14))
+ }
+ }
+ }
+ }
+
+ private fun updateRootWidth(width: Int) {
+ val currentLayoutParams = layoutParams
+ layoutParams = if (currentLayoutParams == null) {
+ ViewGroup.LayoutParams(width, ViewGroup.LayoutParams.WRAP_CONTENT)
+ } else {
+ currentLayoutParams.apply {
+ this.width = width
+ this.height = ViewGroup.LayoutParams.WRAP_CONTENT
+ }
+ }
+ }
+
+ private fun Int.dpToPx(): Int = (this * resources.displayMetrics.density).roundToInt()
+
+ private companion object {
+ const val TITLE_CREATOR_GAP_DP = 2
+ }
+}
diff --git a/app/src/main/res/drawable-mdpi/ic_series_original.png b/app/src/main/res/drawable-mdpi/ic_series_original.png
new file mode 100644
index 00000000..29708072
Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_series_original.png differ
diff --git a/app/src/main/res/drawable/bg_series_content_thumbnail.xml b/app/src/main/res/drawable/bg_series_content_thumbnail.xml
new file mode 100644
index 00000000..c410cc4e
--- /dev/null
+++ b/app/src/main/res/drawable/bg_series_content_thumbnail.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/app/src/main/res/drawable/bg_series_original_tag.xml b/app/src/main/res/drawable/bg_series_original_tag.xml
new file mode 100644
index 00000000..68617a54
--- /dev/null
+++ b/app/src/main/res/drawable/bg_series_original_tag.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/app/src/main/res/layout/view_series_content_card.xml b/app/src/main/res/layout/view_series_content_card.xml
new file mode 100644
index 00000000..f9af1fb8
--- /dev/null
+++ b/app/src/main/res/layout/view_series_content_card.xml
@@ -0,0 +1,55 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/view_series_original_tag.xml b/app/src/main/res/layout/view_series_original_tag.xml
new file mode 100644
index 00000000..a6678316
--- /dev/null
+++ b/app/src/main/res/layout/view_series_original_tag.xml
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+
diff --git a/app/src/test/java/kr/co/vividnext/sodalive/v2/widget/SeriesContentCardSizeTest.kt b/app/src/test/java/kr/co/vividnext/sodalive/v2/widget/SeriesContentCardSizeTest.kt
new file mode 100644
index 00000000..e6b1726f
--- /dev/null
+++ b/app/src/test/java/kr/co/vividnext/sodalive/v2/widget/SeriesContentCardSizeTest.kt
@@ -0,0 +1,30 @@
+package kr.co.vividnext.sodalive.v2.widget
+
+import kr.co.vividnext.sodalive.R
+import org.junit.Assert.assertEquals
+import org.junit.Test
+
+class SeriesContentCardSizeTest {
+
+ @Test
+ fun `large size matches figma contract`() {
+ assertEquals(163, SeriesContentCardSize.Large.cardWidthDp)
+ assertEquals(163, SeriesContentCardSize.Large.thumbnailWidthDp)
+ assertEquals(230, SeriesContentCardSize.Large.thumbnailHeightDp)
+ assertEquals(151, SeriesContentCardSize.Large.labelWidthDp)
+ assertEquals(8, SeriesContentCardSize.Large.thumbnailLabelGapDp)
+ assertEquals(R.style.Typography_Heading4, SeriesContentCardSize.Large.titleStyleRes)
+ assertEquals(R.style.Typography_Body5, SeriesContentCardSize.Large.creatorStyleRes)
+ }
+
+ @Test
+ fun `small size matches figma contract`() {
+ assertEquals(122, SeriesContentCardSize.Small.cardWidthDp)
+ assertEquals(122, SeriesContentCardSize.Small.thumbnailWidthDp)
+ assertEquals(172, SeriesContentCardSize.Small.thumbnailHeightDp)
+ assertEquals(114, SeriesContentCardSize.Small.labelWidthDp)
+ assertEquals(8, SeriesContentCardSize.Small.thumbnailLabelGapDp)
+ assertEquals(R.style.Typography_Body1, SeriesContentCardSize.Small.titleStyleRes)
+ assertEquals(R.style.Typography_Caption2, SeriesContentCardSize.Small.creatorStyleRes)
+ }
+}
diff --git a/docs/plan-task/20260520_시리즈컴포넌트.md b/docs/plan-task/20260520_시리즈컴포넌트.md
new file mode 100644
index 00000000..9a142856
--- /dev/null
+++ b/docs/plan-task/20260520_시리즈컴포넌트.md
@@ -0,0 +1,451 @@
+# 시리즈 컴포넌트 Implementation Plan
+
+> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
+
+**Goal:** Figma `20:3875`, `20:3887`, `20:3906`, `20:3914` 기준으로 세로형 poster ratio와 ORIGINAL 태그를 지원하는 재사용 가능한 Series Content Card Component를 추가한다.
+
+**Architecture:** 기존 `AudioContentCardView`는 정사각형 오디오 카드 전용으로 유지하고, 시리즈 카드는 별도 `SeriesContentCardView`와 `SeriesContentCardSize`로 분리한다. XML 레이아웃은 thumbnail overlay 영역, ORIGINAL tag include, label contents의 공통 구조만 제공하고 Kotlin custom view가 size, tag visibility, 텍스트 바인딩, thumbnail 접근 API를 적용한다.
+
+**Tech Stack:** Android XML Views, Kotlin custom View, ViewBinding/resource merge, JUnit4 local unit test.
+
+---
+
+## 작업 목표
+- `large` 시리즈 카드는 width `163dp`, thumbnail `163dp x 230dp`, label width `151dp`를 사용한다.
+- `small` 시리즈 카드는 width `122dp`, thumbnail `122dp x 172dp`, label width `114dp`를 사용한다.
+- thumbnail은 `radius_14`, `centerCrop`을 사용한다.
+- title/creator는 한 줄 말줄임 처리하고 size별 typography를 적용한다.
+- ORIGINAL 태그는 `ic_series_original` 아이콘과 `ORIGINAL` 텍스트를 포함하고, thumbnail 좌상단 overlay로 표시된다.
+- 이미지 로딩은 컴포넌트 내부에 고정하지 않고 호출부가 처리한다.
+- 기존 오디오 콘텐츠 카드와 기존 화면 파일은 변경하지 않는다.
+
+## 파일 구조
+- Create: `app/src/main/java/kr/co/vividnext/sodalive/v2/widget/SeriesContentCardSize.kt`
+ - `large`, `small` size별 card width, thumbnail width/height, label width, typography contract를 정의한다.
+- Create: `app/src/test/java/kr/co/vividnext/sodalive/v2/widget/SeriesContentCardSizeTest.kt`
+ - size별 dimension/style contract를 검증한다.
+- Create: `app/src/main/res/drawable/bg_series_content_thumbnail.xml`
+ - 14dp corner radius thumbnail 배경을 정의한다.
+- Create: `app/src/main/res/drawable/bg_series_original_tag.xml`
+ - `gray_900` 배경과 bottom end `8dp` radius를 가진 ORIGINAL tag 배경을 정의한다.
+- Create: `app/src/main/res/layout/view_series_original_tag.xml`
+ - `ic_series_original` icon과 `ORIGINAL` 텍스트를 포함하는 101dp x 24dp 태그 layout을 정의한다.
+- Create: `app/src/main/res/layout/view_series_content_card.xml`
+ - `SeriesContentCardView` 루트, thumbnail `ImageView`, ORIGINAL tag include, label container, title `TextView`, creator `TextView`를 정의한다.
+- Create: `app/src/main/java/kr/co/vividnext/sodalive/v2/widget/SeriesContentCardView.kt`
+ - size 적용, ORIGINAL tag 표시 여부, 텍스트 바인딩, 썸네일 view 접근 API를 제공한다.
+- Modify if missing: `app/src/main/res/drawable-mdpi/ic_series_original.png`
+ - 현재 작업트리에 미추적 파일로 존재하므로 구현 전 상태를 확인하고, 없을 때만 디자인 에셋을 추가한다.
+- Modify: `docs/plan-task/20260520_시리즈컴포넌트.md`
+ - 구현 중 체크박스와 검증 기록을 누적한다.
+
+## 구현 계획
+
+### Task 1: 기존 리소스 및 유사 UI 확인
+
+**Files:**
+- Read: `docs/prd/20260520_시리즈컴포넌트_prd.md`
+- Read: `app/src/main/java/kr/co/vividnext/sodalive/v2/widget/AudioContentCardSize.kt`
+- Read: `app/src/main/java/kr/co/vividnext/sodalive/v2/widget/AudioContentCardView.kt`
+- Read: `app/src/main/res/layout/view_audio_content_card.xml`
+- Read: `app/src/main/res/values/colors.xml`
+- Read: `app/src/main/res/values/dimens.xml`
+- Read: `app/src/main/res/values/typography.xml`
+
+- [x] **Step 1: 기존 custom view와 리소스 패턴 확인**
+
+Run: `rg -n "AudioContentCard|CapsuleTabBar|setTextAppearance|resources.displayMetrics|radius_14|gray_500|gray_900|Typography_Heading4|Typography_Body1|Typography_Body5|Typography_Caption2" app/src/main docs`
+
+Expected: 기존 `AudioContentCardView`/`AudioContentCardSize` 패턴, 디자인 토큰, typography 리소스를 확인한다.
+
+- [x] **Step 2: ORIGINAL tag icon 상태 확인**
+
+Run: `rg --files app/src/main/res | rg "ic_series_original"`
+
+Expected: `app/src/main/res/drawable-mdpi/ic_series_original.png`가 출력된다. 출력되지 않으면 Figma asset 또는 제공된 디자인 에셋에서 `ic_series_original`을 추가한다.
+
+- [x] **Step 3: Phosphate font 보유 여부 확인**
+
+Run: `rg --files app/src/main/res/font | rg -i "phosphate|bold|medium|regular"`
+
+Expected: `phosphate` font가 있으면 ORIGINAL 텍스트에 사용한다. 없으면 기존 `@font/bold`를 fallback으로 사용하고 검증 기록에 남긴다.
+
+### Task 2: SeriesContentCardSize TDD
+
+**Files:**
+- Create: `app/src/test/java/kr/co/vividnext/sodalive/v2/widget/SeriesContentCardSizeTest.kt`
+- Create: `app/src/main/java/kr/co/vividnext/sodalive/v2/widget/SeriesContentCardSize.kt`
+
+- [x] **Step 1: RED - size contract 테스트 추가**
+
+```kotlin
+package kr.co.vividnext.sodalive.v2.widget
+
+import kr.co.vividnext.sodalive.R
+import org.junit.Assert.assertEquals
+import org.junit.Test
+
+class SeriesContentCardSizeTest {
+
+ @Test
+ fun `large size matches figma contract`() {
+ assertEquals(163, SeriesContentCardSize.Large.cardWidthDp)
+ assertEquals(163, SeriesContentCardSize.Large.thumbnailWidthDp)
+ assertEquals(230, SeriesContentCardSize.Large.thumbnailHeightDp)
+ assertEquals(151, SeriesContentCardSize.Large.labelWidthDp)
+ assertEquals(8, SeriesContentCardSize.Large.thumbnailLabelGapDp)
+ assertEquals(R.style.Typography_Heading4, SeriesContentCardSize.Large.titleStyleRes)
+ assertEquals(R.style.Typography_Body5, SeriesContentCardSize.Large.creatorStyleRes)
+ }
+
+ @Test
+ fun `small size matches figma contract`() {
+ assertEquals(122, SeriesContentCardSize.Small.cardWidthDp)
+ assertEquals(122, SeriesContentCardSize.Small.thumbnailWidthDp)
+ assertEquals(172, SeriesContentCardSize.Small.thumbnailHeightDp)
+ assertEquals(114, SeriesContentCardSize.Small.labelWidthDp)
+ assertEquals(8, SeriesContentCardSize.Small.thumbnailLabelGapDp)
+ assertEquals(R.style.Typography_Body1, SeriesContentCardSize.Small.titleStyleRes)
+ assertEquals(R.style.Typography_Caption2, SeriesContentCardSize.Small.creatorStyleRes)
+ }
+}
+```
+
+- [x] **Step 2: RED 실행**
+
+Run: `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.widget.SeriesContentCardSizeTest"`
+
+Expected: `Unresolved reference 'SeriesContentCardSize'`로 실패한다.
+
+- [x] **Step 3: GREEN - 최소 size contract 추가**
+
+```kotlin
+package kr.co.vividnext.sodalive.v2.widget
+
+import androidx.annotation.StyleRes
+import kr.co.vividnext.sodalive.R
+
+sealed class SeriesContentCardSize(
+ val cardWidthDp: Int,
+ val thumbnailWidthDp: Int,
+ val thumbnailHeightDp: Int,
+ val labelWidthDp: Int,
+ val thumbnailLabelGapDp: Int,
+ @get:StyleRes val titleStyleRes: Int,
+ @get:StyleRes val creatorStyleRes: Int
+) {
+ data object Large : SeriesContentCardSize(
+ cardWidthDp = 163,
+ thumbnailWidthDp = 163,
+ thumbnailHeightDp = 230,
+ labelWidthDp = 151,
+ thumbnailLabelGapDp = 8,
+ titleStyleRes = R.style.Typography_Heading4,
+ creatorStyleRes = R.style.Typography_Body5
+ )
+
+ data object Small : SeriesContentCardSize(
+ cardWidthDp = 122,
+ thumbnailWidthDp = 122,
+ thumbnailHeightDp = 172,
+ labelWidthDp = 114,
+ thumbnailLabelGapDp = 8,
+ titleStyleRes = R.style.Typography_Body1,
+ creatorStyleRes = R.style.Typography_Caption2
+ )
+}
+```
+
+- [x] **Step 4: GREEN 실행**
+
+Run: `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.widget.SeriesContentCardSizeTest"`
+
+Expected: `BUILD SUCCESSFUL`
+
+### Task 3: Series content card XML 리소스 추가
+
+**Files:**
+- Create: `app/src/main/res/drawable/bg_series_content_thumbnail.xml`
+- Create: `app/src/main/res/drawable/bg_series_original_tag.xml`
+- Create: `app/src/main/res/layout/view_series_original_tag.xml`
+- Create: `app/src/main/res/layout/view_series_content_card.xml`
+
+- [x] **Step 1: thumbnail radius drawable 추가**
+
+`app/src/main/res/drawable/bg_series_content_thumbnail.xml`
+
+```xml
+
+
+
+
+
+```
+
+- [x] **Step 2: ORIGINAL tag background 추가**
+
+`app/src/main/res/drawable/bg_series_original_tag.xml`
+
+```xml
+
+
+
+
+
+```
+
+- [x] **Step 3: ORIGINAL tag layout 추가**
+
+`app/src/main/res/layout/view_series_original_tag.xml`
+
+```xml
+
+
+
+
+
+
+
+```
+
+Task 1에서 Phosphate font 리소스가 확인되면 `android:fontFamily="@font/bold"`를 해당 리소스로 교체한다.
+
+- [x] **Step 4: series content card layout 추가**
+
+`app/src/main/res/layout/view_series_content_card.xml`
+
+```xml
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+```
+
+### Task 4: SeriesContentCardView 구현
+
+**Files:**
+- Create: `app/src/main/java/kr/co/vividnext/sodalive/v2/widget/SeriesContentCardView.kt`
+
+- [x] **Step 1: custom view 추가**
+
+`SeriesContentCardView`는 `LinearLayout`을 상속하고 `@JvmOverloads constructor` 패턴을 따른다.
+
+Required API:
+- `fun setSize(size: SeriesContentCardSize)`
+- `fun setContent(title: String, creatorName: String)`
+- `fun setOriginalVisible(isVisible: Boolean)`
+- `fun thumbnailView(): ImageView`
+
+Implementation requirements:
+- 기본 size는 `SeriesContentCardSize.Large`를 사용한다.
+- `orientation = VERTICAL`을 보장한다.
+- root layout width를 size별 `cardWidthDp`로 적용한다.
+- thumbnail container width/height를 size별 `thumbnailWidthDp`/`thumbnailHeightDp`로 적용한다.
+- label container width를 size별 `labelWidthDp`로 적용한다.
+- label top margin을 size별 `thumbnailLabelGapDp`로 적용한다.
+- title/creator typography는 size별 style resource를 적용한다.
+- title과 creator 사이 gap은 2dp로 적용한다.
+- ORIGINAL tag는 `setOriginalVisible(false)`일 때 `GONE`, true일 때 `VISIBLE`로 표시한다.
+- thumbnail radius clipping은 `clipToOutline` 또는 기존 프로젝트에서 사용하는 방식으로 14dp radius를 보장한다.
+
+- [x] **Step 2: 텍스트 바인딩 구현**
+
+`setContent(title, creatorName)`은 title TextView와 creator TextView에 값을 그대로 바인딩한다. 빈 문자열 보정은 호출부 책임으로 둔다.
+
+- [x] **Step 3: 썸네일 바인딩 확장 지점 제공**
+
+이미지 로딩 라이브러리를 컴포넌트 내부에 고정하지 않도록 `thumbnailView()`로 `ImageView`를 노출한다.
+
+### Task 5: 검증 및 문서 기록
+
+**Files:**
+- Modify: `docs/plan-task/20260520_시리즈컴포넌트.md`
+
+- [x] **Step 1: 단일 테스트 실행**
+
+Run: `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.widget.SeriesContentCardSizeTest"`
+
+Expected: `BUILD SUCCESSFUL`
+
+- [x] **Step 2: LSP 진단 실행**
+
+Run: `lsp_diagnostics` on modified Kotlin/XML files
+
+Expected: 새 오류가 없다. Kotlin/XML LSP가 환경에 없으면 그 사실을 검증 기록에 남긴다.
+
+- [x] **Step 3: 리소스/레이아웃 참조 확인**
+
+Run: `rg -n "SeriesContentCardView|iv_series_content_thumbnail|include_series_original_tag|ic_series_original|clipToOutline=\"true\"|scaleType=\"centerCrop\"|tv_series_content_title|tv_series_content_creator|gray_500|bg_series_content_thumbnail|bg_series_original_tag" app/src/main/res app/src/main/java/kr/co/vividnext/sodalive/v2/widget`
+
+Expected: 신규 view, layout id, drawable, icon, text color, thumbnail 속성이 출력된다.
+
+- [x] **Step 4: 디버그 빌드 실행**
+
+Run: `./gradlew :app:assembleDebug`
+
+Expected: `BUILD SUCCESSFUL`
+
+- [x] **Step 5: ViewBinding 생성 확인**
+
+Run: `rg --files app/build/generated/data_binding_base_class_source_out/debug/out | rg "ViewSeriesContentCardBinding|ViewSeriesOriginalTagBinding"`
+
+Expected: `ViewSeriesContentCardBinding`과 `ViewSeriesOriginalTagBinding` 생성 파일이 출력된다.
+
+- [x] **Step 6: 검증 기록 누적**
+
+문서 하단 `검증 기록`에 실행한 명령, 결과, 빌드 성공 여부를 한국어로 기록한다.
+
+## 체크리스트
+- [x] AC1: `large` 카드는 width `163dp`, thumbnail `163dp x 230dp`, label width `151dp`를 사용한다.
+ - QA: `SeriesContentCardSizeTest`, custom view size 적용 확인
+- [x] AC2: `small` 카드는 width `122dp`, thumbnail `122dp x 172dp`, label width `114dp`를 사용한다.
+ - QA: `SeriesContentCardSizeTest`, custom view size 적용 확인
+- [x] AC3: 모든 thumbnail은 radius `14dp`, `centerCrop`을 사용한다.
+ - QA: drawable/custom view clipping, XML `scaleType` 확인
+- [x] AC4: title은 white, creator name은 `gray_500`이며 둘 다 한 줄 말줄임 처리된다.
+ - QA: XML `textColor`, `maxLines`, `ellipsize` 확인
+- [x] AC5: size별 typography는 large title `Typography.Heading4`, creator `Typography.Body5`, small title `Typography.Body1`, creator `Typography.Caption2`를 사용한다.
+ - QA: `SeriesContentCardSizeTest`, custom view style 적용 확인
+- [x] AC6: ORIGINAL 태그는 `ic_series_original`, `ORIGINAL` 텍스트, `gray_900` 배경, 101dp x 24dp 크기를 사용한다.
+ - QA: `view_series_original_tag.xml`, resource reference 확인
+- [x] AC7: ORIGINAL 태그 표시 여부를 API로 제어할 수 있다.
+ - QA: `setOriginalVisible(Boolean)` 구현 확인
+- [x] AC8: 이미지 로딩 라이브러리를 컴포넌트 내부에 고정하지 않는다.
+ - QA: `thumbnailView()` API 및 의존성 변경 없음 확인
+- [x] AC9: 기존 오디오 콘텐츠 카드와 기존 화면 파일은 변경하지 않는다.
+ - QA: `git status --short` 변경 파일 확인
+- [x] AC10: 리소스 병합 및 디버그 빌드가 성공한다.
+ - QA: `./gradlew :app:assembleDebug`
+
+## 검증 기록
+- 2026-05-20
+ - 무엇/왜/어떻게: 사용자 요청에 따라 구현 전 PRD와 구현 계획/TASK 문서만 작성했다. Figma `20:3875`, `20:3887`, `20:3906`, `20:3914`는 시리즈 콘텐츠 카드와 ORIGINAL 태그 기준으로 문서화했다.
+ - 실행 명령/도구:
+ - `Figma_get_design_context(20:3875)`
+ - `Figma_get_design_context(20:3887)`
+ - `Figma_get_design_context(20:3906)`
+ - `Figma_get_design_context(20:3914)`
+ - `Figma_get_screenshot(20:3875)`
+ - `Figma_get_screenshot(20:3887)`
+ - `Figma_get_screenshot(20:3906)`
+ - `Figma_get_screenshot(20:3914)`
+ - `read(docs/agent-guides/workflow-docs-commits.md)`
+ - `read(docs/prd/sample-prd.md)`
+ - `read(docs/prd/20260519_오디오콘텐츠카드컴포넌트_prd.md)`
+ - `read(docs/plan-task/20260519_오디오콘텐츠카드컴포넌트.md)`
+ - `read(app/src/main/java/kr/co/vividnext/sodalive/v2/widget/AudioContentCardSize.kt)`
+ - `read(app/src/main/java/kr/co/vividnext/sodalive/v2/widget/AudioContentCardView.kt)`
+ - `read(app/src/main/res/layout/view_audio_content_card.xml)`
+ - `read(app/src/main/res/values/typography.xml)`
+ - `read(app/src/main/res/values/dimens.xml)`
+ - `rg -n "AudioContentCard|CapsuleTabBar|radius_14|gray_500|soda_900|spacing_8|ic_series_original|Typography_Heading|Typography_Body|Typography_Caption" "app/src/main" "docs"`
+ - `git status --short`
+ - 결과:
+ - PRD 문서는 `docs/prd/20260520_시리즈컴포넌트_prd.md`에 작성했다.
+ - 계획/TASK 문서는 `docs/plan-task/20260520_시리즈컴포넌트.md`에 작성했다.
+ - Figma `20:3875`는 large 시리즈 카드, `20:3887`은 small 시리즈 카드, `20:3906`은 ORIGINAL 태그, `20:3914`는 ORIGINAL 태그 사용 예시로 정리했다.
+ - 사용자가 `20:3096`은 오타이고 `20:3906`이 맞다고 정정했으므로, 구현 기준은 `20:3906`으로 확정했다.
+ - `app/src/main/res/drawable-mdpi/ic_series_original.png`는 작업트리에 미추적 파일로 존재함을 확인했으며, 이번 문서 작성 작업에서는 수정하지 않았다.
+ - 코드, 리소스, 레이아웃 구현 파일은 변경하지 않았다.
+ - 실제 구현과 빌드 검증은 사용자 승인 후 계획 문서 체크리스트에 따라 진행한다.
+- 2026-05-20
+ - 무엇/왜/어떻게: 사용자 정정에 따라 `20:3096`은 오타로 보고 `20:3906`을 ORIGINAL 태그 기준으로 확정한 뒤, 계획 문서에 따라 시리즈 컴포넌트를 구현했다. `SeriesContentCardSize`로 size contract를 분리하고, `SeriesContentCardView`에서 카드 폭/썸네일/label/typography/tag visibility를 size별로 적용하도록 했다.
+ - 실행 명령/도구:
+ - `rg -n "AudioContentCard|CapsuleTabBar|setTextAppearance|resources.displayMetrics|radius_14|gray_500|gray_900|Typography_Heading4|Typography_Body1|Typography_Body5|Typography_Caption2" app/src/main docs`
+ - `rg --files app/src/main/res | rg "ic_series_original"`
+ - `rg --files app/src/main/res/font | rg -i "phosphate|bold|medium|regular"`
+ - `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.widget.SeriesContentCardSizeTest"` (RED)
+ - `./gradlew :app:testDebugUnitTest --tests "kr.co.vividnext.sodalive.v2.widget.SeriesContentCardSizeTest"` (GREEN)
+ - `lsp_diagnostics` on modified Kotlin/XML files
+ - `rg -n "SeriesContentCardView|iv_series_content_thumbnail|include_series_original_tag|ic_series_original|clipToOutline=\"true\"|scaleType=\"centerCrop\"|tv_series_content_title|tv_series_content_creator|gray_500|bg_series_content_thumbnail|bg_series_original_tag|setOriginalVisible|thumbnailView|phosphate_solid" app/src/main/res app/src/main/java/kr/co/vividnext/sodalive/v2/widget`
+ - `./gradlew :app:assembleDebug`
+ - `rg --files app/build/generated/data_binding_base_class_source_out/debug/out | rg "ViewSeriesContentCardBinding|ViewSeriesOriginalTagBinding"`
+ - `git status --short`
+ - 결과:
+ - `app/src/main/java/kr/co/vividnext/sodalive/v2/widget/SeriesContentCardSize.kt`를 추가했다.
+ - `app/src/main/java/kr/co/vividnext/sodalive/v2/widget/SeriesContentCardView.kt`를 추가했다.
+ - `app/src/main/res/drawable/bg_series_content_thumbnail.xml`을 추가했다.
+ - `app/src/main/res/drawable/bg_series_original_tag.xml`을 추가했다.
+ - `app/src/main/res/layout/view_series_content_card.xml`을 추가했다.
+ - `app/src/main/res/layout/view_series_original_tag.xml`을 추가했다.
+ - `app/src/test/java/kr/co/vividnext/sodalive/v2/widget/SeriesContentCardSizeTest.kt`를 추가했다.
+ - `app/src/main/res/drawable-mdpi/ic_series_original.png`를 ORIGINAL 태그 아이콘으로 사용했다.
+ - RED 실행은 `Unresolved reference 'SeriesContentCardSize'`로 실패해 테스트가 신규 contract 부재를 검증함을 확인했다.
+ - GREEN 실행은 `BUILD SUCCESSFUL`로 완료됐다.
+ - 현재 환경에는 Kotlin/XML LSP 서버가 설정되어 있지 않아 `lsp_diagnostics`는 실행 불가했다.
+ - 리소스 참조 확인에서 `SeriesContentCardView`, thumbnail id, ORIGINAL tag include, `ic_series_original`, `clipToOutline`, `centerCrop`, title/creator id, `gray_500`, series drawable, `setOriginalVisible`, `thumbnailView`, `phosphate_solid` 참조를 확인했다.
+ - `:app:assembleDebug`는 `BUILD SUCCESSFUL`로 완료됐다.
+ - `ViewSeriesContentCardBinding.java`와 `ViewSeriesOriginalTagBinding.java` 생성 파일을 확인했다.
diff --git a/docs/prd/20260520_시리즈컴포넌트_prd.md b/docs/prd/20260520_시리즈컴포넌트_prd.md
new file mode 100644
index 00000000..29e1cda3
--- /dev/null
+++ b/docs/prd/20260520_시리즈컴포넌트_prd.md
@@ -0,0 +1,138 @@
+# PRD: 시리즈 컴포넌트
+
+## 1. Overview
+Figma `20:3875`, `20:3887`, `20:3906`, `20:3914` 디자인을 기준으로 Android XML Views 기반 화면에서 재사용할 수 있는 Series Content Card Component와 ORIGINAL series tag를 개발한다.
+
+---
+
+## 2. Problem
+- 시리즈 콘텐츠 카드는 기존 오디오 콘텐츠 카드와 텍스트 구조는 유사하지만 썸네일이 정사각형이 아니라 세로형 poster ratio다.
+- 시리즈 카드가 화면마다 개별 구현되면 poster 크기, radius, label 폭, typography, ORIGINAL 태그 위치가 달라질 수 있다.
+- ORIGINAL 태그는 시리즈 카드 위에 overlay로 쓰이고 단독 태그 컴포넌트로도 관리되어야 하므로, 카드와 태그의 계약을 분리해야 한다.
+- ORIGINAL tag node는 `20:3906`이다.
+
+---
+
+## 3. Goals
+- 동일한 구조를 갖는 시리즈 콘텐츠 카드의 `large`, `small` 크기 변형을 제공한다.
+- 썸네일은 Figma 기준 세로형 poster ratio를 유지하고, corner radius `14dp`, `centerCrop` 기준으로 표시한다.
+- 제목과 크리에이터명은 한 줄 말줄임 처리하고, 크기별 Figma typography에 맞춘다.
+- ORIGINAL 태그는 `ic_series_original` 아이콘, `ORIGINAL` 텍스트, `gray_900` 배경, `24dp` 높이를 가진 재사용 가능한 tag view 또는 include layout으로 제공한다.
+- 시리즈 카드에는 ORIGINAL 태그 overlay 표시 여부를 선택할 수 있는 API를 제공한다.
+- 기존 오디오 콘텐츠 카드와 기존 화면 일괄 적용은 변경하지 않고, 컴포넌트 추가와 사용 계약 문서화에 한정한다.
+
+---
+
+## 4. Non-Goals
+- 이번 범위에서는 기존 `AudioContentCardView`를 시리즈 카드로 리팩터링하거나 통합하지 않는다.
+- 기존 RecyclerView adapter나 기존 콘텐츠 목록 화면에 신규 시리즈 카드를 일괄 적용하지 않는다.
+- 신규 Activity, Fragment, ViewModel을 만들지 않는다.
+- Compose 컴포넌트 또는 Compose Theme를 추가하지 않는다.
+- Figma에 표시된 하단 유료/무료 태그는 이번 범위에서 구현하지 않는다. 사용자 요청은 ORIGINAL 태그에 한정한다.
+- Figma에 없는 그림자, dim overlay 색상, pressed animation, skeleton loading, placeholder 정책은 추가하지 않는다.
+- 이미지 로딩 라이브러리 선택이나 실제 URL 로딩 정책을 컴포넌트 내부에 고정하지 않는다.
+
+---
+
+## 5. Target Users
+- XML 레이아웃을 작성하거나 유지보수하는 Android 개발자.
+- v2 화면에서 시리즈 콘텐츠 카드 UI를 리스트/캐러셀/그리드에 재사용하려는 개발자.
+
+---
+
+## 6. User Stories
+- 개발자는 같은 시리즈 카드 형태를 크기만 바꿔 재사용하고 싶다.
+- 개발자는 시리즈 콘텐츠가 ORIGINAL인지 여부에 따라 상단 태그를 표시하고 싶다.
+- 개발자는 썸네일, 콘텐츠 제목, 크리에이터명을 ViewBinding 또는 custom view API로 바인딩하고 싶다.
+- 개발자는 긴 제목과 크리에이터명이 레이아웃을 밀어내지 않고 한 줄 말줄임되기를 원한다.
+
+---
+
+## 7. Core Features
+
+### Series Content Card Component
+Figma 2개 카드 크기와 ORIGINAL tag 사용 예시를 하나의 XML + Kotlin custom view 컴포넌트로 제공한다.
+
+#### Figma References
+- Series card large: https://www.figma.com/design/HmN1yNdJ3EIpqknFL0Hkab/-%EA%B3%B5%EC%9C%A0%EC%9A%A9-%EB%B3%B4%EC%9D%B4%EC%8A%A4%EC%98%A8-UI-UX-%EA%B8%B0%ED%9A%8D%EB%AC%B8%EC%84%9C?node-id=20-3875&m=dev
+- Series card small: https://www.figma.com/design/HmN1yNdJ3EIpqknFL0Hkab/-%EA%B3%B5%EC%9C%A0%EC%9A%A9-%EB%B3%B4%EC%9D%B4%EC%8A%A4%EC%98%A8-UI-UX-%EA%B8%B0%ED%9A%8D%EB%AC%B8%EC%84%9C?node-id=20-3887&m=dev
+- ORIGINAL tag: https://www.figma.com/design/HmN1yNdJ3EIpqknFL0Hkab/-%EA%B3%B5%EC%9C%A0%EC%9A%A9-%EB%B3%B4%EC%9D%B4%EC%8A%A4%EC%98%A8-UI-UX-%EA%B8%B0%ED%9A%8D%EB%AC%B8%EC%84%9C?node-id=20-3906&m=dev
+- ORIGINAL tag usage example: https://www.figma.com/design/HmN1yNdJ3EIpqknFL0Hkab/-%EA%B3%B5%EC%9C%A0%EC%9A%A9-%EB%B3%B4%EC%9D%B4%EC%8A%A4%EC%98%A8-UI-UX-%EA%B8%B0%ED%9A%8D%EB%AC%B8%EC%84%9C?node-id=20-3914&m=dev
+
+#### Requirements
+- 공통 구조: 세로형 thumbnail + 하단 label contents 영역.
+- Thumbnail corner radius: `radius_14`.
+- Thumbnail scale type: `centerCrop`.
+- Thumbnail과 label 영역 사이 gap은 `spacing_8` 기준으로 맞춘다.
+- Title: `maxLines=1`, `ellipsize=end`, text color white.
+- Creator name: `maxLines=1`, `ellipsize=end`, text color `gray_500`.
+- Title과 creator name 사이 gap은 `2dp`로 맞춘다. 기존 `spacing_2` 토큰이 없으므로 구현 단계에서 XML 직접값 또는 컴포넌트 내부 상수로 처리한다.
+- ORIGINAL 태그는 thumbnail 좌상단에 붙고, 태그 container는 thumbnail radius와 충돌하지 않도록 우하단 corner radius `8dp`를 가진다.
+- ORIGINAL 태그는 `ic_series_original` 아이콘을 사용한다.
+
+#### Size Variants
+| Size | Figma node | Card width | Thumbnail | Label width | Title style | Creator style | Thumbnail-label gap |
+| --- | --- | --- | --- | --- | --- | --- | --- |
+| `large` | `20:3875` | `163dp` | `163dp x 230dp` | `151dp` | `Typography.Heading4` | `Typography.Body5` | `spacing_8` |
+| `small` | `20:3887` | `122dp` | `122dp x 172dp` | `114dp` | `Typography.Body1` | `Typography.Caption2` | `spacing_8` |
+
+#### ORIGINAL Tag Contract
+| Property | Value |
+| --- | --- |
+| Figma node | `20:3906` |
+| Width | `101dp` |
+| Height | `24dp` |
+| Background | `gray_900` (`#202020`) |
+| Icon | `ic_series_original`, `14dp x 14dp`, left `8dp`, vertically centered |
+| Text | `ORIGINAL`, white, Phosphate Solid style if available |
+| Text fallback | 프로젝트에 Phosphate font가 없으면 이미지/폰트 자산 추가 여부를 확인하고, 없을 때는 기존 font 리소스 중 가장 가까운 bold 스타일을 사용한다 |
+| Text position | left `26dp`, top `2dp` 기준 |
+| Overlay position | thumbnail top-left |
+| Overlay corner | `bottomEnd` radius `8dp` |
+
+#### Edge Cases
+- 제목이 길면 한 줄 말줄임 처리한다.
+- 크리에이터명이 길면 한 줄 말줄임 처리한다.
+- 제목 또는 크리에이터명이 비어 있으면 호출부 데이터 문제로 간주하고 컴포넌트는 전달된 값을 그대로 표시한다.
+- 썸네일 이미지가 없거나 로딩 실패한 경우의 placeholder 정책은 호출부 또는 이미지 로딩 계층에서 결정한다.
+- size가 지정되지 않으면 `large`를 기본값으로 사용한다.
+- ORIGINAL 여부가 false이면 태그 영역은 `gone` 처리되어 thumbnail만 표시된다.
+
+---
+
+## 8. UX / UI Expectations
+- 두 크기 모두 같은 카드 구조와 세로형 poster thumbnail 형태를 유지한다.
+- 썸네일 radius는 모든 크기에서 14dp로 동일하다.
+- `large` 카드는 카드 폭 163dp, thumbnail 163dp x 230dp, label 폭 151dp로 사용한다.
+- `small` 카드는 카드 폭 122dp, thumbnail 122dp x 172dp, label 폭 114dp로 사용한다.
+- 텍스트는 어두운 배경 위 사용을 전제로 white/`gray_500` 색상 대비를 유지한다.
+- ORIGINAL 태그는 thumbnail 좌상단에 고정되어 Figma `20:3914`처럼 이미지 위에 overlay된다.
+
+---
+
+## 9. Technical Constraints
+- 현재 프로젝트는 XML Views + ViewBinding 기반이므로 XML 레이아웃과 Kotlin custom view 패턴을 우선한다.
+- 신규 Kotlin 코드는 `kr.co.vividnext.sodalive.v2.widget` 패키지 하위에 둔다.
+- 재사용 레이아웃은 `app/src/main/res/layout` 아래에 둔다.
+- 색상, spacing, radius, typography는 기존 `colors.xml`, `dimens.xml`, `typography.xml` 토큰을 우선 사용한다.
+- 기존 `AudioContentCardSize`처럼 size contract는 순수 Kotlin 객체 또는 enum으로 분리해 단위 테스트 가능하게 한다.
+- `ic_series_original.png` 리소스가 이미 존재하면 그대로 사용하고, 없으면 구현 전 디자인 에셋을 추가한다.
+- 기존 화면의 동작이나 레이아웃을 요청 없이 변경하지 않는다.
+
+---
+
+## 10. Metrics
+- `large`, `small` 2개 size variant의 카드 폭, 썸네일 크기, label 폭이 문서와 구현에서 일치한다.
+- 제목과 크리에이터명은 한 줄 말줄임 처리된다.
+- ORIGINAL 태그는 `ic_series_original` 아이콘과 `ORIGINAL` 텍스트를 표시한다.
+- ORIGINAL 태그 표시 여부를 호출부에서 제어할 수 있다.
+- size contract 단위 테스트가 통과한다.
+- Android 리소스 병합 및 디버그 빌드가 성공한다.
+- 기존 오디오 콘텐츠 카드와 기존 화면 파일은 변경되지 않는다.
+
+---
+
+## 11. Open Questions
+- 사용자가 `20:3096`은 오타이고 `20:3906`이 맞다고 정정했으므로, 이 문서는 `20:3906`을 ORIGINAL 태그 구현 기준으로 삼는다.
+- Figma의 ORIGINAL 텍스트는 Phosphate Solid이며, 프로젝트의 `@font/phosphate_solid` 리소스를 사용한다.
+- 하단 유료/무료 태그는 Figma generated code에 포함되지만 사용자 요청에 없으므로 제외한다.