diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 9bd82503..ae63af07 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -192,6 +192,7 @@
             
         
         
+        
 
         (FragmentOriginalTabBinding::inflate) {
 
     private val viewModel: OriginalWorkViewModel by inject()
+    private val myPageViewModel: MyPageViewModel by inject()
 
     private lateinit var adapter: OriginalWorkListAdapter
 
+    private lateinit var loadingDialog: LoadingDialog
+
     override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
         super.onViewCreated(view, savedInstanceState)
+
+        loadingDialog = LoadingDialog(requireActivity(), layoutInflater)
+
         setupRecycler()
         bind()
         viewModel.loadMore()
@@ -28,7 +51,18 @@ class OriginalTabFragment :
     private fun setupRecycler() {
         val spanCount = 3
         val spacingPx = 16f.dpToPx().toInt()
-        adapter = OriginalWorkListAdapter { /* TODO: 상세 페이지 이동 정의 시 연결 */ }
+        adapter = OriginalWorkListAdapter { id ->
+            ensureLoginAndAuth {
+                startActivity(
+                    Intent(
+                        requireContext(),
+                        OriginalWorkDetailActivity::class.java
+                    ).apply {
+                        this.putExtra(OriginalWorkDetailActivity.EXTRA_ORIGINAL_ID, id)
+                    }
+                )
+            }
+        }
         binding.rvOriginal.layoutManager = GridLayoutManager(requireContext(), spanCount)
         binding.rvOriginal.addItemDecoration(
             GridSpacingItemDecoration(
@@ -56,6 +90,68 @@ class OriginalTabFragment :
             // 누적 리스트를 어댑터에 추가
             adapter.addItems(list.drop(adapter.itemCount))
         }
-        // 필요 시 로딩/토스트 처리 추가
+
+        viewModel.isLoading.observe(viewLifecycleOwner) {
+            if (it) {
+                loadingDialog.show(screenWidth)
+            } else {
+                loadingDialog.dismiss()
+            }
+        }
+
+        viewModel.toast.observe(viewLifecycleOwner) {
+            it?.let { Toast.makeText(requireActivity(), it, Toast.LENGTH_LONG).show() }
+        }
+    }
+
+    private fun ensureLoginAndAuth(onAuthed: () -> Unit) {
+        if (SharedPreferenceManager.token.isBlank()) {
+            (requireActivity() as MainActivity).showLoginActivity()
+            return
+        }
+
+        if (!SharedPreferenceManager.isAuth) {
+            SodaDialog(
+                activity = requireActivity(),
+                layoutInflater = layoutInflater,
+                title = "본인인증",
+                desc = "보이스온의 오픈월드 캐릭터톡은\n청소년 보호를 위해 본인인증한\n성인만 이용이 가능합니다.\n" +
+                    "캐릭터톡 서비스를 이용하시려면\n본인인증을 하고 이용해주세요.",
+                confirmButtonTitle = "본인인증 하러가기",
+                confirmButtonClick = { startAuthFlow() },
+                cancelButtonTitle = "취소",
+                cancelButtonClick = {},
+                descGravity = Gravity.CENTER
+            ).show(screenWidth)
+            return
+        }
+
+        onAuthed()
+    }
+
+    private fun startAuthFlow() {
+        Auth.auth(requireActivity(), requireContext()) { json ->
+            val bootpayResponse = Gson().fromJson(
+                json,
+                BootpayResponse::class.java
+            )
+            val request = AuthVerifyRequest(receiptId = bootpayResponse.data.receiptId)
+            requireActivity().runOnUiThread {
+                myPageViewModel.authVerify(request) {
+                    startActivity(
+                        Intent(
+                            requireContext(),
+                            SplashActivity::class.java
+                        ).apply {
+                            addFlags(
+                                Intent.FLAG_ACTIVITY_CLEAR_TASK or
+                                    Intent.FLAG_ACTIVITY_NEW_TASK
+                            )
+                        }
+                    )
+                    requireActivity().finish()
+                }
+            }
+        }
     }
 }
diff --git a/app/src/main/java/kr/co/vividnext/sodalive/chat/original/OriginalWorkApi.kt b/app/src/main/java/kr/co/vividnext/sodalive/chat/original/OriginalWorkApi.kt
index 3b2ba0b0..d17277ff 100644
--- a/app/src/main/java/kr/co/vividnext/sodalive/chat/original/OriginalWorkApi.kt
+++ b/app/src/main/java/kr/co/vividnext/sodalive/chat/original/OriginalWorkApi.kt
@@ -4,6 +4,7 @@ import io.reactivex.rxjava3.core.Single
 import kr.co.vividnext.sodalive.common.ApiResponse
 import retrofit2.http.GET
 import retrofit2.http.Header
+import retrofit2.http.Path
 import retrofit2.http.Query
 
 interface OriginalWorkApi {
@@ -13,4 +14,18 @@ interface OriginalWorkApi {
         @Query("page") page: Int,
         @Query("size") size: Int
     ): Single>
+
+    @GET("/api/chat/original/{id}")
+    fun getOriginalWorkDetail(
+        @Header("Authorization") authHeader: String,
+        @Path("id") id: Long
+    ): Single>
+
+    @GET("/api/chat/original/{id}/characters")
+    fun getOriginalWorkCharacters(
+        @Header("Authorization") authHeader: String,
+        @Path("id") id: Long,
+        @Query("page") page: Int,
+        @Query("size") size: Int
+    ): Single>
 }
diff --git a/app/src/main/java/kr/co/vividnext/sodalive/chat/original/OriginalWorkCharactersPageResponse.kt b/app/src/main/java/kr/co/vividnext/sodalive/chat/original/OriginalWorkCharactersPageResponse.kt
new file mode 100644
index 00000000..889a9644
--- /dev/null
+++ b/app/src/main/java/kr/co/vividnext/sodalive/chat/original/OriginalWorkCharactersPageResponse.kt
@@ -0,0 +1,11 @@
+package kr.co.vividnext.sodalive.chat.original
+
+import androidx.annotation.Keep
+import com.google.gson.annotations.SerializedName
+import kr.co.vividnext.sodalive.chat.character.Character
+
+@Keep
+data class OriginalWorkCharactersPageResponse(
+    @SerializedName("totalCount") val totalCount: Long,
+    @SerializedName("content") val content: List
+)
diff --git a/app/src/main/java/kr/co/vividnext/sodalive/chat/original/OriginalWorkDetailResponse.kt b/app/src/main/java/kr/co/vividnext/sodalive/chat/original/OriginalWorkDetailResponse.kt
new file mode 100644
index 00000000..bc033e22
--- /dev/null
+++ b/app/src/main/java/kr/co/vividnext/sodalive/chat/original/OriginalWorkDetailResponse.kt
@@ -0,0 +1,17 @@
+package kr.co.vividnext.sodalive.chat.original
+
+import androidx.annotation.Keep
+import com.google.gson.annotations.SerializedName
+import kr.co.vividnext.sodalive.chat.character.Character
+
+@Keep
+data class OriginalWorkDetailResponse(
+    @SerializedName("imageUrl") val imageUrl: String?,
+    @SerializedName("title") val title: String,
+    @SerializedName("contentType") val contentType: String,
+    @SerializedName("category") val category: String,
+    @SerializedName("isAdult") val isAdult: Boolean,
+    @SerializedName("description") val description: String,
+    @SerializedName("originalLink") val originalLink: String?,
+    @SerializedName("characters") val characters: List
+)
diff --git a/app/src/main/java/kr/co/vividnext/sodalive/chat/original/OriginalWorkRepository.kt b/app/src/main/java/kr/co/vividnext/sodalive/chat/original/OriginalWorkRepository.kt
index fbdede9a..e085e48c 100644
--- a/app/src/main/java/kr/co/vividnext/sodalive/chat/original/OriginalWorkRepository.kt
+++ b/app/src/main/java/kr/co/vividnext/sodalive/chat/original/OriginalWorkRepository.kt
@@ -6,7 +6,27 @@ import kr.co.vividnext.sodalive.common.ApiResponse
 class OriginalWorkRepository(
     private val api: OriginalWorkApi
 ) {
-    fun getOriginalWorks(token: String, page: Int, size: Int): Single> {
+    fun getOriginalWorks(
+        token: String,
+        page: Int,
+        size: Int
+    ): Single> {
         return api.getOriginalWorkList(token, page, size)
     }
+
+    fun getOriginalDetail(
+        token: String,
+        id: Long
+    ): Single> {
+        return api.getOriginalWorkDetail(token, id)
+    }
+
+    fun getOriginalCharacters(
+        token: String,
+        id: Long,
+        page: Int,
+        size: Int
+    ): Single> {
+        return api.getOriginalWorkCharacters(token, id, page, size)
+    }
 }
diff --git a/app/src/main/java/kr/co/vividnext/sodalive/chat/original/detail/OriginalWorkDetailActivity.kt b/app/src/main/java/kr/co/vividnext/sodalive/chat/original/detail/OriginalWorkDetailActivity.kt
new file mode 100644
index 00000000..7c903f85
--- /dev/null
+++ b/app/src/main/java/kr/co/vividnext/sodalive/chat/original/detail/OriginalWorkDetailActivity.kt
@@ -0,0 +1,117 @@
+package kr.co.vividnext.sodalive.chat.original.detail
+
+import android.content.Intent
+import android.os.Bundle
+import androidx.core.net.toUri
+import androidx.recyclerview.widget.GridLayoutManager
+import androidx.recyclerview.widget.RecyclerView
+import coil.load
+import coil.size.Scale
+import coil.transform.BlurTransformation
+import kr.co.vividnext.sodalive.R
+import kr.co.vividnext.sodalive.base.BaseActivity
+import kr.co.vividnext.sodalive.chat.character.detail.CharacterDetailActivity
+import kr.co.vividnext.sodalive.chat.character.detail.CharacterDetailActivity.Companion.EXTRA_CHARACTER_ID
+import kr.co.vividnext.sodalive.common.GridSpacingItemDecoration
+import kr.co.vividnext.sodalive.databinding.ActivityOriginalWorkDetailBinding
+import kr.co.vividnext.sodalive.extensions.dpToPx
+import org.koin.android.ext.android.inject
+
+class OriginalWorkDetailActivity : BaseActivity(
+    ActivityOriginalWorkDetailBinding::inflate
+) {
+
+    companion object {
+        const val EXTRA_ORIGINAL_ID = "extra_original_id"
+    }
+
+    private val viewModel: OriginalWorkDetailViewModel by inject()
+
+    private lateinit var adapter: OriginalWorkDetailAdapter
+
+    private var originalId: Long = -1
+
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+        originalId = intent.getLongExtra(EXTRA_ORIGINAL_ID, -1)
+
+        setupRecycler()
+        bind()
+
+        if (originalId > 0) viewModel.loadDetail(originalId)
+    }
+
+    override fun setupView() {
+        // 배경 이미지 높이를 화면 너비 비율에 맞게 설정(306:432)
+        binding.ivBg.post {
+            val width = binding.ivBg.width.takeIf { it > 0 } ?: resources.displayMetrics.widthPixels
+            val height = width * 432 / 306
+            val lp = binding.ivBg.layoutParams
+            lp.height = height
+            binding.ivBg.layoutParams = lp
+        }
+
+        // Toolbar back
+        binding.ivBack.setOnClickListener { finish() }
+    }
+
+    private fun setupRecycler() {
+        adapter = OriginalWorkDetailAdapter(
+            onClickOpenLink = { url ->
+                startActivity(Intent(Intent.ACTION_VIEW, url.toUri()))
+            },
+            onClickCharacter = { characterId ->
+                startActivity(
+                    Intent(this, CharacterDetailActivity::class.java).apply {
+                        putExtra(EXTRA_CHARACTER_ID, characterId)
+                    }
+                )
+            }
+        )
+        val spanCount = 2
+        val spacingPx = 16f.dpToPx().toInt()
+        val layoutManager = GridLayoutManager(this, spanCount)
+        layoutManager.spanSizeLookup = object : GridLayoutManager.SpanSizeLookup() {
+            override fun getSpanSize(position: Int): Int {
+                return if (adapter.getItemViewType(position) == 0) spanCount else 1
+            }
+        }
+        binding.rvDetail.layoutManager = layoutManager
+        binding.rvDetail.addItemDecoration(GridSpacingItemDecoration(spanCount, spacingPx, true, headerCount = 1))
+        binding.rvDetail.adapter = adapter
+
+        binding.rvDetail.addOnScrollListener(object : RecyclerView.OnScrollListener() {
+            override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
+                super.onScrolled(recyclerView, dx, dy)
+                if (!recyclerView.canScrollVertically(1)) {
+                    if (originalId > 0) viewModel.loadMoreCharacters(originalId)
+                }
+            }
+        })
+    }
+
+    private fun bind() {
+        viewModel.detail.observe(this) { data ->
+            adapter.setHeader(data)
+            // 배경 이미지 Blur 처리 및 채우기
+            val imageUrl = data?.imageUrl
+            if (!imageUrl.isNullOrBlank()) {
+                binding.ivBg.load(imageUrl) {
+                    transformations(
+                        BlurTransformation(
+                            this@OriginalWorkDetailActivity,
+                            25f,
+                            2.5f
+                        )
+                    )
+                    scale(Scale.FILL)
+                }
+            } else {
+                binding.ivBg.setImageResource(R.drawable.bg_placeholder)
+            }
+        }
+        viewModel.characters.observe(this) { list ->
+            adapter.setItems(list)
+        }
+    }
+}
diff --git a/app/src/main/java/kr/co/vividnext/sodalive/chat/original/detail/OriginalWorkDetailAdapter.kt b/app/src/main/java/kr/co/vividnext/sodalive/chat/original/detail/OriginalWorkDetailAdapter.kt
new file mode 100644
index 00000000..d3470861
--- /dev/null
+++ b/app/src/main/java/kr/co/vividnext/sodalive/chat/original/detail/OriginalWorkDetailAdapter.kt
@@ -0,0 +1,137 @@
+package kr.co.vividnext.sodalive.chat.original.detail
+
+import android.annotation.SuppressLint
+import android.text.TextUtils
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.recyclerview.widget.RecyclerView
+import coil.load
+import coil.transform.RoundedCornersTransformation
+import kr.co.vividnext.sodalive.R
+import kr.co.vividnext.sodalive.chat.character.Character
+import kr.co.vividnext.sodalive.chat.original.OriginalWorkDetailResponse
+import kr.co.vividnext.sodalive.databinding.ItemOriginalDetailCharacterBinding
+import kr.co.vividnext.sodalive.databinding.ItemOriginalDetailHeaderBinding
+import kr.co.vividnext.sodalive.extensions.dpToPx
+
+class OriginalWorkDetailAdapter(
+    private val onClickOpenLink: (String) -> Unit,
+    private val onClickCharacter: (Long) -> Unit
+) : RecyclerView.Adapter() {
+
+    // 작품소개 확장 상태 (헤더 1개이므로 어댑터 레벨에서 유지)
+    private var isDescriptionExpanded: Boolean = false
+
+    companion object {
+        private const val TYPE_HEADER = 0
+        private const val TYPE_ITEM = 1
+    }
+
+    private var header: OriginalWorkDetailResponse? = null
+    private val items = mutableListOf()
+
+    fun setHeader(data: OriginalWorkDetailResponse?) {
+        header = data
+        notifyItemChanged(0)
+    }
+
+    @SuppressLint("NotifyDataSetChanged")
+    fun setItems(chars: List) {
+        items.clear()
+        items.addAll(chars)
+        notifyDataSetChanged()
+    }
+
+    override fun getItemViewType(position: Int): Int = if (position == 0) TYPE_HEADER else TYPE_ITEM
+
+    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
+        return if (viewType == TYPE_HEADER) {
+            val binding = ItemOriginalDetailHeaderBinding.inflate(
+                LayoutInflater.from(parent.context),
+                parent,
+                false
+            )
+            HeaderVH(binding)
+        } else {
+            val binding = ItemOriginalDetailCharacterBinding.inflate(
+                LayoutInflater.from(parent.context),
+                parent,
+                false
+            )
+            ItemVH(binding)
+        }
+    }
+
+    override fun getItemCount(): Int = 1 + items.size
+
+    override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
+        if (holder is HeaderVH) {
+            holder.bind(header)
+        } else if (holder is ItemVH) {
+            holder.bind(items[position - 1])
+        }
+    }
+
+    inner class HeaderVH(private val binding: ItemOriginalDetailHeaderBinding) :
+        RecyclerView.ViewHolder(binding.root) {
+        private fun applyDescriptionState() {
+            if (isDescriptionExpanded) {
+                binding.tvDescription.maxLines = Int.MAX_VALUE
+                binding.tvDescription.ellipsize = null
+            } else {
+                binding.tvDescription.maxLines = 2
+                binding.tvDescription.ellipsize = TextUtils.TruncateAt.END
+            }
+        }
+
+        fun bind(data: OriginalWorkDetailResponse?) {
+            if (data == null) return
+
+            // Cover small card
+            binding.ivCover.load(data.imageUrl) {
+                crossfade(true)
+                placeholder(R.drawable.bg_placeholder)
+                transformations(RoundedCornersTransformation(16f.dpToPx()))
+            }
+
+            binding.tvTitle.text = data.title
+            binding.tvContentType.text = data.contentType
+            binding.tvCategory.text = data.category
+            binding.tvDescription.text = data.description
+
+            binding.tvAdult.visibility = if (data.isAdult) {
+                View.VISIBLE
+            } else {
+                View.GONE
+            }
+
+            // 설명 토글 (2줄/전체)
+            applyDescriptionState()
+            binding.tvDescription.setOnClickListener {
+                isDescriptionExpanded = !isDescriptionExpanded
+                applyDescriptionState()
+            }
+
+            binding.tvOpenOriginal.isEnabled = !data.originalLink.isNullOrBlank()
+            binding.tvOpenOriginal.alpha = if (data.originalLink.isNullOrBlank()) 0.5f else 1f
+            binding.tvOpenOriginal.setOnClickListener {
+                data.originalLink?.let { onClickOpenLink(it) }
+            }
+        }
+    }
+
+    inner class ItemVH(private val binding: ItemOriginalDetailCharacterBinding) :
+        RecyclerView.ViewHolder(binding.root) {
+        fun bind(item: Character) {
+            binding.tvCharacterName.text = item.name
+            binding.tvCharacterDescription.text = item.description
+            binding.ivCharacter.load(item.imageUrl) {
+                crossfade(true)
+                placeholder(R.drawable.ic_logo_service_center)
+                transformations(RoundedCornersTransformation(16f.dpToPx()))
+            }
+            binding.root.setOnClickListener { onClickCharacter(item.characterId) }
+        }
+    }
+}
diff --git a/app/src/main/java/kr/co/vividnext/sodalive/chat/original/detail/OriginalWorkDetailViewModel.kt b/app/src/main/java/kr/co/vividnext/sodalive/chat/original/detail/OriginalWorkDetailViewModel.kt
new file mode 100644
index 00000000..22cc8b5f
--- /dev/null
+++ b/app/src/main/java/kr/co/vividnext/sodalive/chat/original/detail/OriginalWorkDetailViewModel.kt
@@ -0,0 +1,96 @@
+package kr.co.vividnext.sodalive.chat.original.detail
+
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.MutableLiveData
+import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
+import io.reactivex.rxjava3.schedulers.Schedulers
+import kr.co.vividnext.sodalive.base.BaseViewModel
+import kr.co.vividnext.sodalive.chat.character.Character
+import kr.co.vividnext.sodalive.chat.original.OriginalWorkDetailResponse
+import kr.co.vividnext.sodalive.chat.original.OriginalWorkRepository
+import kr.co.vividnext.sodalive.common.SharedPreferenceManager
+
+class OriginalWorkDetailViewModel(
+    private val repository: OriginalWorkRepository
+) : BaseViewModel() {
+
+    private val _isLoading = MutableLiveData(false)
+    val isLoading: LiveData get() = _isLoading
+
+    private val _toast = MutableLiveData(null)
+    val toast: LiveData get() = _toast
+
+    private val _detail = MutableLiveData(null)
+    val detail: LiveData get() = _detail
+
+    private val _characters = MutableLiveData>(emptyList())
+    val characters: LiveData> get() = _characters
+
+    private val size = 20
+    private var page = 1 // 초기 로딩 이후부터 사용하므로 1부터 시작
+    private var isLast = false
+
+    fun loadDetail(id: Long) {
+        if (_isLoading.value == true) return
+        _isLoading.value = true
+        compositeDisposable.add(
+            repository.getOriginalDetail(
+                token = "Bearer ${SharedPreferenceManager.token}",
+                id = id
+            )
+                .subscribeOn(Schedulers.io())
+                .observeOn(AndroidSchedulers.mainThread())
+                .subscribe({ response ->
+                    val data = response.data
+                    if (response.success && data != null) {
+                        _detail.value = data
+                        // 상세 응답 내 캐릭터를 초기 세팅
+                        _characters.value = data.characters
+                        // 초기 캐릭터가 없으면 다음 로딩에서 page=1 그대로 시도
+                        page = 1
+                        isLast = false
+                    } else {
+                        _toast.value = response.message ?: "알 수 없는 오류가 발생했습니다."
+                    }
+                    _isLoading.value = false
+                }, { e ->
+                    _isLoading.value = false
+                    _toast.value = e.message ?: "알 수 없는 오류가 발생했습니다."
+                })
+        )
+    }
+
+    fun loadMoreCharacters(id: Long) {
+        if (_isLoading.value == true || isLast) return
+        _isLoading.value = true
+        compositeDisposable.add(
+            repository.getOriginalCharacters(
+                token = "Bearer ${SharedPreferenceManager.token}",
+                id = id,
+                page = page,
+                size = size
+            )
+                .subscribeOn(Schedulers.io())
+                .observeOn(AndroidSchedulers.mainThread())
+                .subscribe({ response ->
+                    val data = response.data
+                    if (response.success && data != null) {
+                        val current = _characters.value ?: emptyList()
+                        val next = current + data.content
+                        _characters.value = next
+                        if (data.content.isNotEmpty()) {
+                            page += 1
+                        } else {
+                            isLast = true
+                        }
+                    } else {
+                        _toast.value = response.message ?: "알 수 없는 오류가 발생했습니다."
+                    }
+                    _isLoading.value = false
+                }, { e ->
+                    _isLoading.value = false
+                    _toast.value = e.message ?: "알 수 없는 오류가 발생했습니다."
+                })
+        )
+    }
+}
diff --git a/app/src/main/java/kr/co/vividnext/sodalive/common/GridSpacingItemDecoration.kt b/app/src/main/java/kr/co/vividnext/sodalive/common/GridSpacingItemDecoration.kt
index 771764c8..6a1803c8 100644
--- a/app/src/main/java/kr/co/vividnext/sodalive/common/GridSpacingItemDecoration.kt
+++ b/app/src/main/java/kr/co/vividnext/sodalive/common/GridSpacingItemDecoration.kt
@@ -5,11 +5,14 @@ import android.view.View
 import androidx.recyclerview.widget.RecyclerView
 import androidx.recyclerview.widget.RecyclerView.ItemDecoration
 
-
+/**
+ * Grid 간격 데코레이션. 헤더가 있는 경우 headerCount로 보정하여 첫 행 판단 및 컬럼 계산을 정확히 수행한다.
+ */
 class GridSpacingItemDecoration(
     private val spanCount: Int,
     private val spacing: Int,
-    private val includeEdge: Boolean
+    private val includeEdge: Boolean,
+    private val headerCount: Int = 0
 ) : ItemDecoration() {
     override fun getItemOffsets(
         outRect: Rect,
@@ -17,19 +20,26 @@ class GridSpacingItemDecoration(
         parent: RecyclerView,
         state: RecyclerView.State
     ) {
-        val position = parent.getChildAdapterPosition(view) // Item position
-        val column = position % spanCount // Current column
+        val position = parent.getChildAdapterPosition(view)
+        // 헤더 범위는 간격을 적용하지 않음
+        val adjustedPosition = position - headerCount
+        if (adjustedPosition < 0) {
+            outRect.set(0, 0, 0, 0)
+            return
+        }
+
+        val column = adjustedPosition % spanCount
         if (includeEdge) {
             outRect.left = spacing - column * spacing / spanCount
             outRect.right = (column + 1) * spacing / spanCount
-            if (position < spanCount) { // Top edge
+            if (adjustedPosition < spanCount) { // Top edge (헤더 제외 첫 행)
                 outRect.top = spacing
             }
             outRect.bottom = spacing // Item bottom
         } else {
             outRect.left = column * spacing / spanCount
             outRect.right = spacing - (column + 1) * spacing / spanCount
-            if (position >= spanCount) {
+            if (adjustedPosition >= spanCount) {
                 outRect.top = spacing // Item top
             }
         }
diff --git a/app/src/main/java/kr/co/vividnext/sodalive/di/AppDI.kt b/app/src/main/java/kr/co/vividnext/sodalive/di/AppDI.kt
index 9e13fe3f..9dcc79d4 100644
--- a/app/src/main/java/kr/co/vividnext/sodalive/di/AppDI.kt
+++ b/app/src/main/java/kr/co/vividnext/sodalive/di/AppDI.kt
@@ -372,6 +372,7 @@ class AppDI(private val context: Context, isDebugMode: Boolean) {
         viewModel { kr.co.vividnext.sodalive.chat.character.comment.CharacterCommentReplyViewModel(get()) }
         viewModel { NewCharactersAllViewModel(get()) }
         viewModel { OriginalWorkViewModel(get()) }
+        viewModel { kr.co.vividnext.sodalive.chat.original.detail.OriginalWorkDetailViewModel(get()) }
     }
 
     private val repositoryModule = module {
diff --git a/app/src/main/res/drawable/bg_round_corner_4_263238_3bb9f1.xml b/app/src/main/res/drawable/bg_round_corner_4_263238_3bb9f1.xml
new file mode 100644
index 00000000..111ac987
--- /dev/null
+++ b/app/src/main/res/drawable/bg_round_corner_4_263238_3bb9f1.xml
@@ -0,0 +1,9 @@
+
+
+    
+    
+    
+
diff --git a/app/src/main/res/drawable/bg_round_corner_4_263238_ff5c49.xml b/app/src/main/res/drawable/bg_round_corner_4_263238_ff5c49.xml
new file mode 100644
index 00000000..db5a29f6
--- /dev/null
+++ b/app/src/main/res/drawable/bg_round_corner_4_263238_ff5c49.xml
@@ -0,0 +1,9 @@
+
+
+    
+    
+    
+
diff --git a/app/src/main/res/drawable/bg_round_corner_4_263238_ffffff.xml b/app/src/main/res/drawable/bg_round_corner_4_263238_ffffff.xml
new file mode 100644
index 00000000..88c39131
--- /dev/null
+++ b/app/src/main/res/drawable/bg_round_corner_4_263238_ffffff.xml
@@ -0,0 +1,9 @@
+
+
+    
+    
+    
+
diff --git a/app/src/main/res/layout/activity_original_work_detail.xml b/app/src/main/res/layout/activity_original_work_detail.xml
new file mode 100644
index 00000000..8802a5fe
--- /dev/null
+++ b/app/src/main/res/layout/activity_original_work_detail.xml
@@ -0,0 +1,60 @@
+
+
+
+    
+    
+
+    
+
+    
+
+        
+
+    
+
+    
+
diff --git a/app/src/main/res/layout/item_original_detail_character.xml b/app/src/main/res/layout/item_original_detail_character.xml
new file mode 100644
index 00000000..b4d55c97
--- /dev/null
+++ b/app/src/main/res/layout/item_original_detail_character.xml
@@ -0,0 +1,50 @@
+
+
+
+    
+
+    
+
+    
+
+
diff --git a/app/src/main/res/layout/item_original_detail_header.xml b/app/src/main/res/layout/item_original_detail_header.xml
new file mode 100644
index 00000000..0fa6e840
--- /dev/null
+++ b/app/src/main/res/layout/item_original_detail_header.xml
@@ -0,0 +1,113 @@
+
+
+
+    
+        
+        
+    
+
+    
+
+    
+
+        
+
+        
+
+        
+    
+
+    
+
+    
+
diff --git a/app/src/main/res/layout/item_original_work.xml b/app/src/main/res/layout/item_original_work.xml
index eb4fa623..83f5cc12 100644
--- a/app/src/main/res/layout/item_original_work.xml
+++ b/app/src/main/res/layout/item_original_work.xml
@@ -9,40 +9,44 @@
         android:id="@+id/iv_cover"
         android:layout_width="0dp"
         android:layout_height="0dp"
-        android:scaleType="centerCrop"
         android:contentDescription="@null"
-        app:layout_constraintTop_toTopOf="parent"
-        app:layout_constraintStart_toStartOf="parent"
-        app:layout_constraintEnd_toEndOf="parent"
+        android:scaleType="centerCrop"
         app:layout_constraintDimensionRatio="306:432"
+        app:layout_constraintEnd_toEndOf="parent"
+        app:layout_constraintStart_toStartOf="parent"
+        app:layout_constraintTop_toTopOf="parent"
         tools:src="@drawable/ic_logo_service_center" />