import SwiftUI struct AiCharacterCard: View { static let baseDeviceWidth: CGFloat = 402 static let maxCardWidth: CGFloat = 185 static let maxCardHeight: CGFloat = 278 let name: String let description: String let profileImageUrl: String? let chatCount: Int? let originalTitle: String? var body: some View { let cardSize = Self.responsiveSize() ZStack(alignment: .top) { Color.gray900 DownsampledKFImage( url: URL(string: profileImageUrl ?? ""), size: CGSize(width: cardSize.width, height: cardSize.width) ) .frame(width: cardSize.width, height: cardSize.width) .background(Color.gray800) LinearGradient( colors: [Color.gray900.opacity(0), Color.gray900], startPoint: .top, endPoint: .bottom ) .frame(width: cardSize.width, height: cardSize.width) VStack(alignment: .leading, spacing: SodaSpacing.s6) { Spacer() Text(name) .appFont(.heading3) .foregroundColor(.white) .lineLimit(1) .truncationMode(.tail) Text(description) .appFont(.body5) .foregroundColor(.white) .lineLimit(2) .truncationMode(.tail) if let originalTitle, !originalTitle.isEmpty { Text(originalTitle) .appFont(.caption2) .foregroundColor(Color.soda300) .lineLimit(1) .truncationMode(.tail) } } .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .bottomLeading) .padding(.horizontal, SodaSpacing.s12) .padding(.bottom, SodaSpacing.s20) if let chatCount { HStack(spacing: 2) { Image(systemName: "bubble.fill") .font(.system(size: 14, weight: .medium)) .foregroundColor(.white) Text(I18n.HomeRecommendation.compactChatCount(chatCount)) .appFont(.body5) .foregroundColor(Color.gray100) } .padding(.horizontal, SodaSpacing.s4) .padding(.vertical, 2) .background(Color.black.opacity(0.6)) .clipShape(RoundedRectangle(cornerRadius: SodaSpacing.s4, style: .continuous)) .frame(maxWidth: .infinity, alignment: .trailing) .padding(.top, SodaSpacing.s6) .padding(.trailing, SodaSpacing.s6) } } .frame(width: cardSize.width, height: cardSize.height) .clipShape(RoundedRectangle(cornerRadius: SodaSpacing.s14, style: .continuous)) } static func responsiveSize(deviceWidth: CGFloat = UIScreen.main.bounds.width) -> CGSize { let scale = min(deviceWidth / baseDeviceWidth, 1) return CGSize(width: maxCardWidth * scale, height: maxCardHeight * scale) } } struct AiCharacterCard_Previews: PreviewProvider { static var previews: some View { VStack(spacing: SodaSpacing.s20) { AiCharacterCard( name: "캐릭터 이름", description: "캐릭터 소개가 들어가는 부분입니다 넘어가는 경우 ...처리", profileImageUrl: "https://picsum.photos/500/500", chatCount: 14_000, originalTitle: nil ) AiCharacterCard( name: "캐릭터 이름", description: "캐릭터 소개가 들어가는 부분입니다 넘어가는 경우 ...처리", profileImageUrl: nil, chatCount: 128, originalTitle: "작품 이름" ) } .padding(SodaSpacing.s20) .background(Color.black) .previewLayout(.sizeThatFits) } }