feat: improve agent responses and workspace UI

Add live agent progress, elapsed timing, verified code navigation and rich Markdown rendering. Preserve NanumSquare styling with missing glyph fallback, fix platform availability filtering and unify workspace scrollbar and visualization controls.
This commit is contained in:
2026-10-19 17:06:18 +09:00
parent af21fa0510
commit 8b501266ff
33 changed files with 2231 additions and 98 deletions
+50 -34
View File
@@ -24,11 +24,12 @@
#miniMapToggle {
position:absolute;
right:16px;
bottom:240px;
bottom:calc(16px + 220px + 2px);
z-index:11;
min-height:28px;
padding:0 10px;
border:1px solid rgba(255,255,255,.22);
border-radius:6px 6px 0 0;
background:rgba(17,24,39,.42);
color:rgba(255,255,255,.88);
font:12px sans-serif;
@@ -109,6 +110,7 @@
display:none;
}
#keyGuide {
display:none;
position:absolute;
left:16px;
bottom:16px;
@@ -127,7 +129,7 @@
#rosTopicStatusWrap {
position:absolute;
left:16px;
bottom:54px;
bottom:16px;
z-index:10;
width:178px;
}
@@ -252,6 +254,7 @@
#cameraPresetSelect {
min-height:32px;
border:1px solid rgba(255,255,255,.22);
border-radius:6px;
background:rgba(17,24,39,.42);
color:rgba(255,255,255,.88);
font:12px sans-serif;
@@ -261,29 +264,43 @@
box-shadow:0 10px 26px rgba(0,0,0,.16);
transition:background .15s ease, border-color .15s ease, color .15s ease;
}
#downloadMap,
#pathToggle,
#sensorToggle,
#poseToggle,
#goalToggle,
#obstacleToggle {
padding:0 11px;
}
#downloadMap {
display:inline-flex;
align-items:center;
gap:6px;
}
.viewer-icon-button {
display:grid;
place-items:center;
width:34px;
height:32px;
flex:0 0 34px;
padding:0;
}
.camera-preset-wrap {
position:relative;
width:34px;
height:32px;
flex:0 0 34px;
}
.camera-preset-wrap > svg {
position:absolute;
inset:0;
margin:auto;
color:rgba(255,255,255,.88);
pointer-events:none;
}
#cameraPresetSelect {
min-width:86px;
padding:0 24px 0 9px;
outline:none;
width:100%;
height:100%;
padding:0;
color:transparent;
appearance:none;
background-image:linear-gradient(45deg, transparent 50%, rgba(255,255,255,.7) 50%), linear-gradient(135deg, rgba(255,255,255,.7) 50%, transparent 50%);
background-position:calc(100% - 13px) 14px, calc(100% - 8px) 14px;
background-size:5px 5px, 5px 5px;
background-repeat:no-repeat;
}
#cameraPresetSelect option {
color:rgba(255,255,255,.88);
background:#202020;
}
.viewer-icon-button:focus-visible,
#cameraPresetSelect:focus-visible {
outline:2px solid #93c5fd;
outline-offset:2px;
}
#downloadMap:hover,
#pathToggle:hover,
#sensorToggle:hover,
@@ -296,6 +313,7 @@
border-color:rgba(255,255,255,.34);
color:#fff;
}
#cameraPresetSelect:hover { color:transparent; }
#downloadMap:disabled {
opacity:.45;
cursor:not-allowed;
@@ -374,35 +392,33 @@
.nav2-slam #goalToggle, .nav2-slam #poseToggle { display:none; }
.nav2-navigation #downloadWrap { display:none; }
.nav2-navigation #topLeftControls { display:block; }
.nav2-navigation #keyGuide { display:none; }
.nav2-navigation #rosTopicStatusWrap { bottom:16px; }
.real-runtime #topLeftControls { display:none; }
.real-runtime #keyGuide { display:none; }
.real-teleop:not(.editor-mode) #keyGuide { display:block; }
.real-teleop #rosTopicStatusWrap { bottom:50px; }
</style>
</head>
<body>
<div id="topLeftControls">
<button id="obstacleToggle" title="벽 배치" data-i18n-title="벽 배치" aria-label="벽 배치" data-i18n-aria-label="벽 배치" aria-pressed="false">Wall</button>
<button id="obstacleToggle" class="viewer-icon-button" title="벽 배치" data-i18n-title="벽 배치" aria-label="벽 배치" data-i18n-aria-label="벽 배치" aria-pressed="false"><svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-brick-wall" aria-hidden="true"><rect width="18" height="18" x="3" y="3" rx="2"/><path d="M12 9v6M16 15v6M16 3v6M3 15h18M3 9h18M8 15v6M8 3v6"/></svg></button>
</div>
<div id="topRightControls">
<div id="downloadWrap">
<button id="downloadMap" title="맵 저장" data-i18n-title="맵 저장" aria-label="맵 저장" data-i18n-aria-label="맵 저장" disabled><span>SAVE</span></button>
<button id="downloadMap" class="viewer-icon-button" title="맵 저장" data-i18n-title="맵 저장" aria-label="맵 저장" data-i18n-aria-label="맵 저장" disabled><svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-save" aria-hidden="true"><path d="M15.2 3a2 2 0 0 1 1.4.6l3.8 3.8a2 2 0 0 1 .6 1.4V19a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2z"/><path d="M17 21v-7a1 1 0 0 0-1-1H8a1 1 0 0 0-1 1v7M7 3v4a1 1 0 0 0 1 1h7"/></svg></button>
</div>
<button id="pathToggle" title="로봇 경로 표시" data-i18n-title="로봇 경로 표시" aria-label="로봇 경로 표시" data-i18n-aria-label="로봇 경로 표시">Path</button>
<button id="sensorToggle" title="센서 표시" data-i18n-title="센서 표시" aria-label="센서 표시" data-i18n-aria-label="센서 표시" aria-pressed="false">Sensor</button>
<button id="cameraToggle" type="button" title="카메라 영상 표시" data-i18n-title="카메라 영상 표시" aria-label="카메라 영상 표시" data-i18n-aria-label="카메라 영상 표시" aria-pressed="true" aria-controls="cameraPreviewWrap">
<button id="pathToggle" class="viewer-icon-button" title="로봇 경로 표시" data-i18n-title="로봇 경로 표시" aria-label="로봇 경로 표시" data-i18n-aria-label="로봇 경로 표시"><svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-route" aria-hidden="true"><circle cx="6" cy="19" r="3"/><path d="M9 19h8.5a3.5 3.5 0 0 0 0-7h-11a3.5 3.5 0 0 1 0-7H15"/><circle cx="18" cy="5" r="3"/></svg></button>
<button id="sensorToggle" class="viewer-icon-button" title="센서 표시" data-i18n-title="센서 표시" aria-label="센서 표시" data-i18n-aria-label="센서 표시" aria-pressed="false"><svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-radar" aria-hidden="true"><path d="M19.07 4.93A10 10 0 0 0 6.99 3.34M4 6h.01M2.29 9.62A10 10 0 1 0 21.31 8.35M16.24 7.76A6 6 0 1 0 8.23 16.67M12 18h.01M17.99 11.66A6 6 0 0 1 15.77 16.67"/><circle cx="12" cy="12" r="2"/><path d="m13.41 10.59 5.66-5.66"/></svg></button>
<button id="cameraToggle" class="viewer-icon-button" type="button" title="카메라 영상 표시" data-i18n-title="카메라 영상 표시" aria-label="카메라 영상 표시" data-i18n-aria-label="카메라 영상 표시" aria-pressed="true" aria-controls="cameraPreviewWrap">
<!-- Lucide Camera icon. -->
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M13.997 4a2 2 0 0 1 1.76 1.05l.486.9A2 2 0 0 0 18.003 7H20a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V9a2 2 0 0 1 2-2h1.997a2 2 0 0 0 1.759-1.048l.489-.904A2 2 0 0 1 10.004 4z"/><circle cx="12" cy="13" r="3"/></svg>
</button>
<button id="poseToggle" title="초기 위치 지정" data-i18n-title="초기 위치 지정" aria-label="초기 위치 지정" data-i18n-aria-label="초기 위치 지정">Pose</button>
<button id="goalToggle" title="목표 위치 지정" data-i18n-title="목표 위치 지정" aria-label="목표 위치 지정" data-i18n-aria-label="목표 위치 지정">Goal</button>
<button id="poseToggle" class="viewer-icon-button" title="초기 위치 지정" data-i18n-title="초기 위치 지정" aria-label="초기 위치 지정" data-i18n-aria-label="초기 위치 지정"><svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-locate-fixed" aria-hidden="true"><path d="M2 12h3M19 12h3M12 2v3M12 19v3"/><circle cx="12" cy="12" r="7"/><circle cx="12" cy="12" r="3"/></svg></button>
<button id="goalToggle" class="viewer-icon-button" title="목표 위치 지정" data-i18n-title="목표 위치 지정" aria-label="목표 위치 지정" data-i18n-aria-label="목표 위치 지정"><svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-flag" aria-hidden="true"><path d="M4 22V4a1 1 0 0 1 .4-.8A6 6 0 0 1 8 2c3 0 5 2 7.333 2q2 0 3.067-.8A1 1 0 0 1 20 4v10a1 1 0 0 1-.4.8A6 6 0 0 1 16 16c-3 0-5-2-8-2a6 6 0 0 0-4 1.528"/></svg></button>
<div class="camera-preset-wrap">
<select id="cameraPresetSelect" title="카메라 시점" data-i18n-title="카메라 시점" aria-label="카메라 시점" data-i18n-aria-label="카메라 시점">
<option value="default">Default</option>
<option value="top">Top</option>
<option value="follow">Follow</option>
</select>
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-panels-top-left" aria-hidden="true"><rect width="18" height="18" x="3" y="3" rx="2"/><path d="M3 9h18M9 21V9"/></svg>
</div>
</div>
<canvas id="miniMap" width="260" height="220"></canvas>
<button id="miniMapToggle" title="미니맵 접기/펼치기" data-i18n-title="미니맵 접기/펼치기" aria-label="미니맵 접기" data-i18n-aria-label="미니맵 접기">▾</button>
+46 -5
View File
@@ -60,7 +60,8 @@ import { useQaChat } from './features/workspace/useQaChat'
import { useAiAgent } from './features/workspace/useAiAgent'
import { validateAgentOperation, validateAgentTool, type AgentToolRequest, type AgentToolResult } from '../../shared/agentTools'
import { AGENT_PRACTICE_PROFILES, inspectAgentPractice, prepareAgentPractice } from './features/workspace/agentPractice'
import type { AgentCommand, AgentFileChange, AgentPlan, AgentRuntimeLog, AgentWorkspaceModule } from './features/workspace/useAiAgent'
import type { AgentCodeReference, AgentCommand, AgentFileChange, AgentPlan, AgentRuntimeLog, AgentWorkspaceModule } from './features/workspace/useAiAgent'
import { revealAgentCodeReference, validateCodeReference } from './features/workspace/agentCodeNavigation'
import type { ChatAttachment } from './components/chatAttachments'
import { useRemoteWorkspaceRuntime } from './features/workspace/useRemoteWorkspaceRuntime'
import { useSourceControl } from './features/workspace/useSourceControl'
@@ -632,12 +633,16 @@ function LocalPcWorkspaceMockup({
statusLoading: agentStatusLoading,
pending: agentPending,
activity: agentActivity,
progressSteps: agentProgressSteps,
requestStartedAt: agentRequestStartedAt,
messages: agentMessages,
persistenceError: agentPersistenceError,
requestPlan: requestAgentPlan,
applyPlan: applyAgentPlan,
cancelPlan: cancelAgentPlan,
updateCommandExecution: updateAgentCommandExecution,
openCodeReference: openAgentCodeReference,
cancelCodeReference: cancelAgentCodeReference,
stop: stopAgent,
clear: clearAgentConversation
} = useAiAgent({
@@ -855,6 +860,7 @@ function LocalPcWorkspaceMockup({
const editorPaneRef = useRef<HTMLDivElement>(null)
const editorRef = useRef<Parameters<OnMount>[0] | null>(null)
const decorationIdsRef = useRef<string[]>([])
const agentCodeDecorationsRef = useRef<ReturnType<Parameters<OnMount>[0]['createDecorationsCollection']> | null>(null)
const [leftPanelWidth, setLeftPanelWidth] = useState(310)
const [rightPanelWidth, setRightPanelWidth] = useState(310)
const [projectTreeWidth, setProjectTreeWidth] = useState(280)
@@ -2446,6 +2452,10 @@ function LocalPcWorkspaceMockup({
setActiveEditorFile(file)
setOpenEditorTabs((tabs) => (tabs.includes(file) ? tabs : [...tabs, file]))
}
const agentNavigationScope = JSON.stringify([studentEmail, courseKey, weekNumber, platformId, clonePathPreview, remoteWorkspace, remoteWorkspaceTargetKey])
const agentNavigationRef = useRef({ scope: agentNavigationScope, file: currentActiveEditorFile, ready: false, visible: workspaceVisible })
agentNavigationRef.current = { scope: agentNavigationScope, file: currentActiveEditorFile, ready: activeIdeActivity === 'workspace' && !activeAgentDiffPreview && !remoteEditorFile.loading && !remoteEditorFile.error, visible: workspaceVisible }
useEffect(() => () => { agentCodeDecorationsRef.current?.clear(); agentCodeDecorationsRef.current = null }, [agentNavigationScope, currentActiveEditorFile, activeIdeActivity, activeAgentDiffPreview?.id, workspaceVisible])
const loadExampleCodeFiles = async (): Promise<void> => {
try {
const token = accessToken ? await getFreshAccessToken() : ''
@@ -3680,6 +3690,33 @@ function LocalPcWorkspaceMockup({
onTitlebarNotice(error instanceof Error ? error.message : '모듈 파일을 읽지 못했습니다.', 'error')
}
}
const openWorkspaceAgentCodeReference = async (reference: AgentCodeReference, conversationCurrent: () => boolean): Promise<void> => {
if (agentActionControllerRef.current || agentApplying) throw new Error('진행 중인 에이전트 작업이 끝난 뒤 열어주세요.')
validateCodeReference(reference)
const scope = agentNavigationScope
const isCurrent = (): boolean => conversationCurrent() && agentNavigationRef.current.scope === scope && agentNavigationRef.current.visible
const module = availableModules.find(item => item.name === reference.moduleName)
if (!module) throw new Error('코드 위치의 모듈을 찾을 수 없습니다. 다시 분석을 요청해주세요.')
const file = `modules/${module.name}/${reference.path}`
if (!isCurrent()) throw new Error('코드 위치 열기가 중단되었습니다.')
const snapshot = agentEditorFilesRef.current[file]
const result = remoteWorkspace
? await window.remoteWorkspace.moduleFiles.content(getRemoteWorkspaceTarget(), module.name, file, module.folderName)
: { content: (await window.localWorkspace.moduleFiles.read(module.name, clonePathPreview, module.folderName)).files[file] }
if (!isCurrent()) throw new Error('코드 위치 열기가 중단되었습니다.')
if (typeof result.content !== 'string') throw new Error('코드 파일을 읽지 못했습니다. 다시 분석을 요청해주세요.')
setEditorFiles(files => {
// Preserve any editor buffer already open, including edits made during the read.
if (files[file] !== undefined || files[file] !== snapshot) return files
return { ...files, [file]: result.content }
})
setSavedEditorFiles(files => ({ ...files, [file]: result.content }))
if (selectedModule?.name !== module.name) openModuleSource(module)
else setActiveIdeActivity('workspace')
openEditorFile(file)
agentCodeDecorationsRef.current?.clear()
agentCodeDecorationsRef.current = await revealAgentCodeReference({ file, reference, isCurrent, getEditor: () => editorRef.current, getActiveFile: () => agentNavigationRef.current.file, isReady: () => agentNavigationRef.current.ready })
}
const openAgentChangePreview = async (messageId: string, change: AgentFileChange): Promise<void> => {
const module = availableModules.find((item) => item.name === change.moduleName)
?? (selectedModule?.name === change.moduleName ? selectedModule : null)
@@ -4719,6 +4756,8 @@ function LocalPcWorkspaceMockup({
agentStatusLoading={agentStatusLoading}
agentPending={agentPending}
agentActivity={agentActivity}
agentProgressSteps={agentProgressSteps}
agentRequestStartedAt={agentRequestStartedAt}
agentActionBusy={agentActionBusy || agentApplying}
agentPersistenceError={agentPersistenceError}
aiPending={aiPending}
@@ -4752,6 +4791,8 @@ function LocalPcWorkspaceMockup({
if (agentActionControllerRef.current || agentPending) return
if (cancelAgentPlan(messageId)) closeAgentDiffPreviewsForMessage(messageId)
}}
onOpenAgentCodeReference={(messageId, referenceId) => void openAgentCodeReference(messageId, referenceId, openWorkspaceAgentCodeReference)}
onCancelAgentCodeReference={cancelAgentCodeReference}
onApplyAgentPlan={(messageId, plan) => void applyAgentPlan(messageId, async () => {
await applyWorkspaceAgentPlan(plan)
closeAgentDiffPreviewsForMessage(messageId)
@@ -5545,14 +5586,14 @@ export default function App(): React.JSX.Element {
)
const filteredPlatformOptions = platformOptions.filter((platform) => {
const pending = isRemotePlatformStatusPending(platform)
if (platformFilter === 'available') return platform.state === 'ready' && !pending
if (platformFilter === 'unavailable') return platform.state !== 'ready' && !pending
if (platformFilter === 'available') return platform.state === 'ready' && !isPlatformUnavailable(platform) && !pending
if (platformFilter === 'unavailable') return (platform.state !== 'ready' || isPlatformUnavailable(platform)) && !pending
return true
})
const platformFilterCounts = {
all: platformOptions.length,
available: platformOptions.filter((platform) => platform.state === 'ready' && !isRemotePlatformStatusPending(platform)).length,
unavailable: platformOptions.filter((platform) => platform.state !== 'ready' && !isRemotePlatformStatusPending(platform)).length
available: platformOptions.filter((platform) => platform.state === 'ready' && !isPlatformUnavailable(platform) && !isRemotePlatformStatusPending(platform)).length,
unavailable: platformOptions.filter((platform) => (platform.state !== 'ready' || isPlatformUnavailable(platform)) && !isRemotePlatformStatusPending(platform)).length
}
const localPcPlatform = platformOptions.find((platform) => platform.platformKey === 'futuredrive-local-pc')
const openPlatformSettings = (platform: PlatformOption): void => {
@@ -0,0 +1,38 @@
import { FileCode2, Loader2, X } from 'lucide-react'
import { t, useLocaleRender } from '../i18n'
import type { AgentCodeReference, AgentCodeReferenceState } from '../features/workspace/useAiAgent'
export function AgentCodeReferences({ references, states, dark, busy, onOpen, onCancel }: {
references: AgentCodeReference[]
states?: Record<string, AgentCodeReferenceState>
dark: boolean
busy?: boolean
onOpen?: (id: string) => void
onCancel?: (id: string) => void
}): React.JSX.Element {
useLocaleRender()
return <div className="grid min-w-0 gap-3">
{references.map(reference => {
const state = states?.[reference.id]
const opening = state?.status === 'opening'
const cancelled = state?.status === 'cancelled'
return <div key={reference.id} data-agent-code-reference={reference.id} className={['min-w-0 border-t pt-2', dark ? 'border-white/10' : 'border-[#ddd6fe]'].join(' ')}>
<div className="flex min-w-0 items-start gap-2">
<FileCode2 size={14} className="mt-0.5 shrink-0 text-[#8b5cf6]" />
<div className="min-w-0">
<p className="break-all font-bold">{reference.moduleName}/{reference.path}</p>
<p className={dark ? 'text-white/48' : 'text-[#64748b]'}>{t('{{start}}~{{end}}줄', { start: reference.startLine, end: reference.endLine })}</p>
<p className="mt-1 break-words">{reference.reason}</p>
</div>
</div>
{state?.error && <p role="alert" className="mt-2 break-words text-[11px] text-[#ef4444]">{t(state.error)}</p>}
<div className="mt-2 flex min-w-0 items-center justify-end gap-2">
{cancelled ? <span className={dark ? 'text-white/40' : 'text-[#64748b]'}>{t('취소됨')}</span> : <>
{state?.status !== 'opened' && <button type="button" disabled={busy || opening || !onCancel} onClick={() => onCancel?.(reference.id)} className={['inline-flex h-8 shrink-0 cursor-pointer items-center justify-center gap-1.5 rounded-md border px-3 text-[11px] font-bold disabled:cursor-not-allowed disabled:opacity-40', dark ? 'border-white/15 hover:bg-white/8' : 'border-[#d8dee9] hover:bg-[#e2e8f0]'].join(' ')}><X size={13} />{t('취소')}</button>}
<button type="button" disabled={busy || opening || !onOpen} onClick={() => onOpen?.(reference.id)} className="inline-flex h-8 min-w-0 cursor-pointer items-center justify-center gap-1.5 rounded-md bg-[#7c3aed] px-3 text-[11px] font-bold text-white hover:bg-[#6d28d9] disabled:cursor-not-allowed disabled:opacity-40">{opening ? <Loader2 size={13} className="shrink-0 animate-spin" /> : <FileCode2 size={13} className="shrink-0" />}<span>{opening ? t('파일 여는 중...') : t('코드에서 보기')}</span></button>
</>}
</div>
</div>
})}
</div>
}
@@ -0,0 +1,58 @@
import { CheckCircle2, CircleAlert, XCircle } from 'lucide-react'
import { useEffect, useRef } from 'react'
import { t, useLocaleRender } from '../i18n'
import type { AgentProgressStep } from '../features/workspace/agentContext'
import { AgentWorkDuration } from './AgentWorkDuration'
function stepLabel(step: AgentProgressStep): string {
if (step.kind === 'analysis') return [
t('{{round}}차 분석 완료', { round: step.round ?? 1 }),
step.requests ? t('추가 조회 {{count}}건', { count: step.requests }) : ''
].filter(Boolean).join(' · ')
if (step.kind === 'image') return `${t('이미지 업로드 완료')} · ${step.name}`
const tools: Record<string, string> = {
'modules.list': t('모듈 목록'),
'files.list': t('파일 목록'),
'files.search': t('파일 검색'),
'files.read': t('파일 읽기'),
'logs.read': t('로그 조회'),
'network.inspect': t('네트워크 조회'),
'ros.inspect': t('ROS 상태 조회'),
'practice.inspect': t('실습 구성 조회')
}
const status = step.status === 'error' ? t('조회 실패') : step.status === 'partial' ? t('일부 결과') : step.status === 'empty' ? t('조회 결과 없음') : step.status === 'missing' ? t('대상 없음') : t('조회 완료')
return [
step.moduleName,
step.kind === 'files' ? t('소스 파일') : step.kind === 'logs' ? t('로그 조회') : tools[step.name ?? ''] ?? step.name,
step.path,
status,
step.count !== undefined ? t('{{count}}건', { count: step.count }) : ''
].filter(Boolean).join(' · ')
}
export function AgentProgressBubble({ dark, steps, activity, startedAt }: { dark: boolean; steps: AgentProgressStep[]; activity?: string; startedAt?: number | null }): React.JSX.Element {
useLocaleRender()
const listRef = useRef<HTMLOListElement>(null)
useEffect(() => {
const list = listRef.current
if (list) list.scrollTop = list.scrollHeight
}, [steps.length])
return (
<div data-agent-progress="true" className={['min-w-0 flex-1 overflow-hidden rounded-lg border px-3 py-3 text-[12px] leading-relaxed', dark ? 'border-white/10 bg-[#303030] text-white/75' : 'border-[#c4b5fd] bg-[#faf7ff] text-[#334155]'].join(' ')}>
<AgentWorkDuration dark={dark} startedAt={startedAt} />
<p className="font-bold">{t('진행 상황')}</p>
{steps.length > 0 && <ol ref={listRef} role="log" aria-label={t('진행 상황')} aria-live="polite" aria-relevant="additions" className="thin-scrollbar mt-2 grid max-h-64 gap-2 overflow-y-auto">
{steps.map(step => <li key={step.id} data-agent-progress-step={step.kind} className="agent-response-fade flex min-w-0 items-start gap-2">
{step.status === 'error' ? <XCircle size={13} className="mt-0.5 shrink-0 text-[#ef4444]" /> : step.status !== 'ok' ? <CircleAlert size={13} className="mt-0.5 shrink-0 text-[#d97706]" /> : <CheckCircle2 size={13} className="mt-0.5 shrink-0 text-[#22c55e]" />}
<span className="min-w-0 whitespace-pre-wrap break-words [overflow-wrap:anywhere]">{stepLabel(step)}</span>
</li>)}
</ol>}
<p role="status" className={['mt-2 break-words text-[11px]', dark ? 'text-white/50' : 'text-[#64748b]'].join(' ')}>{activity ? t(activity) : t('요청 분석 중')}</p>
<div className="mt-3 grid w-[220px] max-w-full animate-pulse gap-2 motion-reduce:animate-none" aria-hidden="true">
<span className={['h-2 w-full rounded-sm', dark ? 'bg-white/18' : 'bg-[#c4b5fd]/65'].join(' ')} />
<span className={['h-2 w-[76%] rounded-sm', dark ? 'bg-white/14' : 'bg-[#c4b5fd]/50'].join(' ')} />
<span className={['h-2 w-[52%] rounded-sm', dark ? 'bg-white/10' : 'bg-[#c4b5fd]/40'].join(' ')} />
</div>
</div>
)
}
@@ -0,0 +1,27 @@
import { useEffect, useState } from 'react'
import { t, useLocaleRender } from '../i18n'
export function AgentWorkDuration({ startedAt, durationMs, dark }: { startedAt?: number | null; durationMs?: number; dark: boolean }): React.JSX.Element | null {
useLocaleRender()
const [now, setNow] = useState(Date.now)
const completed = typeof durationMs === 'number' && Number.isFinite(durationMs) && durationMs >= 0
const running = !completed && typeof startedAt === 'number' && Number.isFinite(startedAt)
useEffect(() => {
if (!running) return
const update = (): void => setNow(Date.now())
update()
const timer = window.setInterval(update, 1000)
document.addEventListener('visibilitychange', update)
window.addEventListener('focus', update)
return () => {
window.clearInterval(timer)
document.removeEventListener('visibilitychange', update)
window.removeEventListener('focus', update)
}
}, [running, startedAt])
if (!running && !completed) return null
const seconds = Math.floor(Math.max(0, completed ? durationMs : now - startedAt!) / 1000)
return <p data-agent-work-duration={completed ? 'completed' : 'running'} aria-live="off" className={['mb-2 text-left text-[11px] font-normal leading-relaxed tabular-nums', dark ? 'text-white/45' : 'text-[#64748b]'].join(' ')}>
{completed ? t('{{seconds}}초 동안 작업함', { seconds }) : t('{{seconds}}초 동안 작업 중입니다', { seconds })}
</p>
}
@@ -0,0 +1,63 @@
import { Check, Copy, FileCode2, X } from 'lucide-react'
import { common, createLowlight } from 'lowlight'
import type React from 'react'
import { useEffect, useMemo, useRef, useState } from 'react'
import { t, useLocaleRender } from '../i18n'
const highlighter = createLowlight(common)
const languageLabels: Record<string, string> = {
python: 'Python', py: 'Python', yaml: 'YAML', yml: 'YAML', json: 'JSON',
cpp: 'C++', 'c++': 'C++', c: 'C', javascript: 'JavaScript', js: 'JavaScript',
typescript: 'TypeScript', ts: 'TypeScript', bash: 'Bash', sh: 'Bash', shell: 'Shell',
xml: 'XML', html: 'HTML', css: 'CSS', sql: 'SQL', markdown: 'Markdown', md: 'Markdown',
}
type HighlightNode = ReturnType<typeof highlighter.highlight>['children'][number]
function renderToken(node: HighlightNode, key: string): React.ReactNode {
if (node.type === 'text') return node.value
if (node.type !== 'element') return null
const classes = node.properties.className
return <span key={key} className={Array.isArray(classes) ? classes.join(' ') : undefined}>
{node.children.map((child, index) => renderToken(child, `${key}-${index}`))}
</span>
}
export function ChatCodeBlock({ source, language = '', dark, fadeClass = '' }: {
source: string
language?: string
dark: boolean
fadeClass?: string
}): React.JSX.Element {
useLocaleRender()
const [copyState, setCopyState] = useState<'idle' | 'copied' | 'error'>('idle')
const timer = useRef<ReturnType<typeof setTimeout> | undefined>(undefined)
useEffect(() => () => clearTimeout(timer.current), [])
useEffect(() => { setCopyState('idle'); clearTimeout(timer.current) }, [source])
const normalizedLanguage = language.toLowerCase()
const tokens = useMemo(() => {
if (source.length > 50_000 || !highlighter.registered(normalizedLanguage)) return source
try { return highlighter.highlight(normalizedLanguage, source).children.map((node, index) => renderToken(node, `${index}`)) }
catch { return source }
}, [source, normalizedLanguage])
const copy = async (): Promise<void> => {
try {
await navigator.clipboard.writeText(source)
setCopyState('copied')
} catch {
setCopyState('error')
}
clearTimeout(timer.current)
timer.current = setTimeout(() => setCopyState('idle'), 1800)
}
const copyLabel = copyState === 'copied' ? t('복사됨') : copyState === 'error' ? t('코드를 복사하지 못했습니다.') : t('코드 복사')
return <div data-chat-code-block data-code-theme={dark ? 'dark' : 'light'} className={['chat-code-block my-2 min-w-0 max-w-full overflow-hidden rounded-md border', dark ? 'border-white/12 bg-[#191919] text-[#dedede]' : 'border-[#d7dfeb] bg-[#f8fafc] text-[#253247]', fadeClass].join(' ')}>
<div className={['flex h-9 min-w-0 items-center justify-between gap-2 border-b px-2.5', dark ? 'border-white/10 bg-[#262626]' : 'border-[#e2e8f0] bg-[#eef2f7]'].join(' ')}>
<span title={language || undefined} className="flex min-w-0 items-center gap-1.5 text-[11px] font-bold"><FileCode2 size={13} className="shrink-0" /><span className="truncate">{languageLabels[normalizedLanguage] ?? (language ? language.slice(0, 40) : t('일반 텍스트'))}</span></span>
<button type="button" title={copyLabel} aria-label={copyLabel} onClick={event => { event.stopPropagation(); void copy() }} className={['grid h-7 w-7 shrink-0 cursor-pointer place-items-center rounded transition focus-visible:outline-2 focus-visible:outline-[#60a5fa]', copyState === 'error' ? 'text-[#ef4444]' : dark ? 'text-white/65 hover:bg-white/10 hover:text-white' : 'text-[#526079] hover:bg-[#dbe3ee] hover:text-[#182337]'].join(' ')}>{copyState === 'copied' ? <Check size={14} /> : copyState === 'error' ? <X size={14} /> : <Copy size={14} />}</button>
<span role="status" className="sr-only">{copyState === 'idle' ? '' : copyLabel}</span>
</div>
<pre tabIndex={0} aria-label={t('코드')} className="thin-scrollbar m-0 max-h-80 max-w-full overflow-auto p-3 text-[11px] leading-relaxed outline-offset-[-2px] [overflow-wrap:normal] [tab-size:4] [word-break:normal]">
<code className="block w-max min-w-full whitespace-pre font-mono font-normal [overflow-wrap:normal] [word-break:normal]">{tokens}</code>
</pre>
</div>
}
+17 -10
View File
@@ -4,6 +4,7 @@ import type React from 'react'
import { memo, useEffect, useLayoutEffect, useRef, useState } from 'react'
import { CHAT_ATTACHMENT_ACCEPT, CHAT_IMAGE_ACCEPT, readChatAttachments, type ChatAttachment } from './chatAttachments'
import { ChatImageAttachments } from './ChatImageAttachments'
import { ChatCodeBlock } from './ChatCodeBlock'
import ReactMarkdown from 'react-markdown'
import remarkGfm from 'remark-gfm'
import { ChatTextContextMenu, type ChatTextContextMenuState } from './ChatTextContextMenu'
@@ -66,7 +67,6 @@ export function MarkdownMessage({ content, dark, fadeFrom }: { content: string;
const textColor = dark ? 'text-white/76' : 'text-[#4f596b]'
const headingColor = dark ? 'text-white/92' : 'text-[#111827]'
const mutedBorder = dark ? 'border-white/10' : 'border-[#d7dfeb]'
const codeBg = dark ? 'bg-[#171717] text-white/82' : 'bg-[#f1f5f9] text-[#334155]'
const fadeClass = (offset?: number): string => fadeFrom !== undefined && offset !== undefined && offset >= fadeFrom ? ' agent-response-fade' : ''
return (
@@ -92,30 +92,37 @@ export function MarkdownMessage({ content, dark, fadeFrom }: { content: string;
{children}
</a>
),
ul: ({ children }) => <ul className="my-2 grid gap-1 pl-4">{children}</ul>,
ol: ({ children }) => <ol className="my-2 grid gap-1 pl-4">{children}</ol>,
li: ({ children, node }) => <li className={`max-w-full break-words pl-1 leading-relaxed marker:font-bold [overflow-wrap:anywhere] [word-break:break-word]${fadeClass(node?.position?.start.offset)}`}>{children}</li>,
ul: ({ children, className }) => <ul className={`my-2 grid gap-1 pl-5 ${className?.includes('contains-task-list') ? 'contains-task-list list-none' : 'list-disc [&_ul]:list-[circle]'}`}>{children}</ul>,
ol: ({ children, start }) => <ol start={start} className="my-2 grid list-decimal gap-1 pl-5">{children}</ol>,
li: ({ children, node, className }) => <li className={`max-w-full break-words pl-1 leading-relaxed marker:font-bold [overflow-wrap:anywhere] [word-break:break-word] ${className?.includes('task-list-item') ? 'task-list-item list-none' : ''}${fadeClass(node?.position?.start.offset)}`}>{children}</li>,
blockquote: ({ children, node }) => (
<blockquote className={`my-2 border-l-2 pl-3 ${mutedBorder} ${dark ? 'text-white/58' : 'text-[#64748b]'}${fadeClass(node?.position?.start.offset)}`}>
{children}
</blockquote>
),
hr: ({ node }) => <hr className={`my-3 ${mutedBorder}${fadeClass(node?.position?.start.offset)}`} />,
pre: ({ children, node }) => <pre className={`my-2 max-w-full overflow-hidden whitespace-pre-wrap break-words rounded-md p-2 font-mono text-[11px] font-medium leading-relaxed [overflow-wrap:anywhere] ${codeBg}${fadeClass(node?.position?.start.offset)}`}>{children}</pre>,
pre: ({ children, node }) => {
const code = node?.children.find(child => child.type === 'element' && child.tagName === 'code')
if (code?.type !== 'element') return <pre className="my-2 max-w-full overflow-auto whitespace-pre font-mono text-[11px]">{children}</pre>
const source = code.children.map(child => child.type === 'text' ? child.value : '').join('')
const classes = code.properties.className
const language = Array.isArray(classes) ? String(classes.find(value => String(value).startsWith('language-')) ?? '').slice(9) : ''
return <ChatCodeBlock source={source} language={language} dark={dark} fadeClass={fadeClass(node?.position?.start.offset)} />
},
code: ({ children, className }) => {
if (className) {
return <code className="whitespace-pre-wrap break-words font-mono [overflow-wrap:anywhere]">{children}</code>
}
return <code className={`rounded px-1 py-0.5 font-mono text-[11px] ${dark ? 'bg-white/10 text-[#dbeafe]' : 'bg-[#eef2ff] text-[#1d4ed8]'}`}>{children}</code>
return <code className={`chat-inline-code rounded px-1 py-0.5 font-mono text-[11px] ${dark ? 'bg-white/10 text-[#dbeafe]' : 'bg-[#eef2ff] text-[#1d4ed8]'}`}>{children}</code>
},
table: ({ children, node }) => (
<div className={`my-2 max-w-full overflow-hidden${fadeClass(node?.position?.start.offset)}`}>
<table className={`w-full table-fixed border-collapse text-left text-[11px] ${dark ? 'text-white/72' : 'text-[#475569]'}`}>{children}</table>
<div data-chat-table tabIndex={0} role="region" aria-label={t('표')} className={`thin-x-scrollbar my-2 max-w-full overflow-x-auto rounded-md${fadeClass(node?.position?.start.offset)}`}>
<table className={`w-full table-auto border-collapse text-left text-[11px] ${dark ? 'text-white/72' : 'text-[#475569]'}`}>{children}</table>
</div>
),
thead: ({ children }) => <thead className={dark ? 'bg-white/8 text-white/86' : 'bg-[#eef2f7] text-[#1e293b]'}>{children}</thead>,
th: ({ children }) => <th className={`break-words border px-2 py-1.5 font-black [overflow-wrap:anywhere] ${mutedBorder}`}>{children}</th>,
td: ({ children }) => <td className={`break-words border px-2 py-1.5 align-top [overflow-wrap:anywhere] ${mutedBorder}`}>{children}</td>
th: ({ children, style }) => <th style={style} className={`min-w-[96px] max-w-[320px] break-words border px-2 py-1.5 font-black [overflow-wrap:anywhere] ${mutedBorder}`}>{children}</th>,
td: ({ children, style }) => <td style={style} className={`min-w-[96px] max-w-[320px] break-words border px-2 py-1.5 align-top [overflow-wrap:anywhere] ${mutedBorder}`}>{children}</td>
}}
>
{content}
@@ -49,14 +49,14 @@ export function ProgressiveChatResponse({ message, dark, children, footerAction,
}, [frame.duration, message.revealStartedAt, reducedMotion])
return (
<div className="grid gap-3" aria-busy={!frame.complete}>
<div>
<div className="grid min-w-0 max-w-full gap-3" aria-busy={!frame.complete}>
<div className="min-w-0">
{message.plan && (
<div className={['font-black', frame.fadeFrom === 0 ? 'agent-response-fade' : ''].join(' ')}>
<MarkdownMessage content={message.plan.summary} dark={dark} />
</div>
)}
<div className={message.plan ? 'mt-1' : ''}>
<div className={message.plan ? 'mt-1 min-w-0' : 'min-w-0'}>
{frame.complete && frame.fadeFrom === undefined ? (
<ExpandableMarkdownMessage
content={content}
@@ -275,7 +275,7 @@ export default function ModuleListView({
)}
</div>
<div className="-mt-2 flex min-h-0 flex-1 overflow-x-auto overflow-y-hidden">
<div className="thin-x-scrollbar -mt-2 flex min-h-0 flex-1 overflow-x-auto overflow-y-hidden">
<div className={['flex h-full min-w-[760px] flex-1 flex-col overflow-hidden border-b', dark ? 'border-white/10 bg-[#242424]' : 'border-[#d8dee9] bg-white'].join(' ')}>
<ModuleListToolbar
dark={dark}
@@ -7,6 +7,10 @@ import { ConfirmStatusModal } from '../../components/AppStatusModals'
import { ProgressiveChatResponse } from '../../components/ProgressiveChatResponse'
import { ChatTextContextMenu, type ChatTextContextMenuState } from '../../components/ChatTextContextMenu'
import { ChatImageAttachments } from '../../components/ChatImageAttachments'
import { AgentProgressBubble } from '../../components/AgentProgressBubble'
import { AgentWorkDuration } from '../../components/AgentWorkDuration'
import { AgentCodeReferences } from '../../components/AgentCodeReferences'
import type { AgentProgressStep } from './agentContext'
import { ChatAvatar, ChatEmptyState, ChatResponseSkeleton, ExpandablePlainMessage, MarkdownMessage, QaInputBox } from '../../components/ChatWidgets'
import type { AgentCommand, AgentConversationMessage, AgentFileChange, AgentPlan } from './useAiAgent'
import type { ChatAttachment } from '../../components/chatAttachments'
@@ -46,6 +50,8 @@ type WorkspaceQaPanelProps = {
agentStatusLoading: boolean
agentPending: boolean
agentActivity?: string
agentProgressSteps?: AgentProgressStep[]
agentRequestStartedAt?: number | null
agentActionBusy?: boolean
agentPersistenceError?: string | null
aiPending: boolean
@@ -64,6 +70,8 @@ type WorkspaceQaPanelProps = {
onOpenAgentChange: (messageId: string, change: AgentFileChange) => void
onRunAgentCommand: (messageId: string, command: AgentCommand) => void
onRejectAgentCommand: (messageId: string, command: AgentCommand) => void
onOpenAgentCodeReference?: (messageId: string, referenceId: string) => void
onCancelAgentCodeReference?: (messageId: string, referenceId: string) => void
}
function MessageCopyButton({ content, dark }: { content: string; dark: boolean }): React.JSX.Element {
@@ -120,6 +128,8 @@ export default function WorkspaceQaPanel({
agentStatusLoading,
agentPending,
agentActivity,
agentProgressSteps = [],
agentRequestStartedAt,
agentActionBusy,
agentPersistenceError,
aiPending,
@@ -135,6 +145,8 @@ export default function WorkspaceQaPanel({
onClearConversation,
onApplyAgentPlan,
onCancelAgentPlan,
onOpenAgentCodeReference,
onCancelAgentCodeReference,
onOpenAgentChange,
onRunAgentCommand,
onRejectAgentCommand
@@ -197,6 +209,8 @@ export default function WorkspaceQaPanel({
}
}, [
agentMessages.length,
agentProgressSteps.length,
agentActivity,
agentPending,
aiPending,
lastMessage?.content,
@@ -304,10 +318,14 @@ export default function WorkspaceQaPanel({
statusLoading={agentStatusLoading}
pending={agentPending}
activity={agentActivity}
progressSteps={agentProgressSteps}
requestStartedAt={agentRequestStartedAt}
busy={agentPending || agentActionBusy}
messages={agentMessages}
onApply={onApplyAgentPlan}
onCancel={onCancelAgentPlan}
onOpenCodeReference={onOpenAgentCodeReference}
onCancelCodeReference={onCancelAgentCodeReference}
onOpenChange={onOpenAgentChange}
onRunCommand={onRunAgentCommand}
onRejectCommand={onRejectAgentCommand}
@@ -361,19 +379,25 @@ function AgentMessages({
statusLoading,
pending,
activity,
progressSteps = [],
requestStartedAt,
busy,
messages,
onApply,
onCancel,
onOpenChange,
onRunCommand,
onRejectCommand
onRejectCommand,
onOpenCodeReference,
onCancelCodeReference
}: {
dark: boolean
enabled: boolean
statusLoading: boolean
pending: boolean
activity?: string
progressSteps?: AgentProgressStep[]
requestStartedAt?: number | null
busy?: boolean
messages: AgentConversationMessage[]
onApply: (messageId: string, plan: AgentPlan) => void
@@ -381,6 +405,8 @@ function AgentMessages({
onOpenChange: (messageId: string, change: AgentFileChange) => void
onRunCommand: (messageId: string, command: AgentCommand) => void
onRejectCommand: (messageId: string, command: AgentCommand) => void
onOpenCodeReference?: (messageId: string, referenceId: string) => void
onCancelCodeReference?: (messageId: string, referenceId: string) => void
}): React.JSX.Element {
useLocaleRender()
if (statusLoading) {
@@ -422,9 +448,11 @@ function AgentMessages({
<span className="grid h-7 w-7 shrink-0 place-items-center rounded-full bg-[#7c3aed] text-white"><WandSparkles size={14} /></span>
<div className="group relative min-w-0 max-w-[calc(100%-38px)]">
<div className={['overflow-hidden rounded-lg border px-3 py-2 text-[12px] leading-relaxed', dark ? 'border-white/10 bg-[#303030] text-white/75' : 'border-[#c4b5fd] bg-[#faf7ff] text-[#334155]'].join(' ')}>
<AgentWorkDuration dark={dark} durationMs={message.durationMs} />
<ProgressiveChatResponse message={message} dark={dark} collapseOnSignal={index < messages.length - 1} collapseSignal={messages.length} footerAction={<MessageCopyButton content={agentMessageCopyText(message)} dark={dark} />}>
{message.plan && (
<>
{!!message.plan.codeReferences?.length && <AgentCodeReferences references={message.plan.codeReferences} states={message.codeReferenceStates} dark={dark} busy={busy} onOpen={onOpenCodeReference ? id => onOpenCodeReference(message.id, id) : undefined} onCancel={onCancelCodeReference ? id => onCancelCodeReference(message.id, id) : undefined} />}
{!!message.plan.evidence?.length && (
<details className={['border-t pt-2 text-[11px]', dark ? 'border-white/10 text-white/55' : 'border-[#ddd6fe] text-[#64748b]'].join(' ')}>
<summary className="cursor-pointer select-none">{t("조회 근거")} · {message.plan.evidence.length}</summary>
@@ -584,10 +612,7 @@ function AgentMessages({
{pending ? (
<div data-qa-message="true" className="flex gap-2.5">
<span className="grid h-7 w-7 shrink-0 place-items-center rounded-full bg-[#7c3aed] text-white"><WandSparkles size={14} /></span>
<div className="min-w-0 flex-1">
{activity ? <p role="status" className={['mb-2 break-words text-[11px]', dark ? 'text-white/45' : 'text-[#64748b]'].join(' ')}>{activity}</p> : null}
<ChatResponseSkeleton dark={dark} label={t("코드 변경안을 만드는 중입니다.")} />
</div>
<AgentProgressBubble dark={dark} steps={progressSteps} activity={activity} startedAt={requestStartedAt} />
</div>
) : null}
</div>
@@ -0,0 +1,63 @@
import type { editor } from 'monaco-editor'
import { safeAgentPath } from '../../../../shared/agentTools'
import type { AgentCodeReference } from './useAiAgent'
export function validateCodeReference(reference: AgentCodeReference): void {
safeAgentPath(reference.path)
if (!Number.isSafeInteger(reference.startLine) || !Number.isSafeInteger(reference.endLine) || reference.startLine < 1 || reference.endLine < reference.startLine || reference.endLine > 100_000 || reference.endLine - reference.startLine >= 200
|| !reference.expectedHash || !/^[a-f0-9]{64}$/.test(reference.expectedHash) || !reference.snippet?.trim() || reference.snippet.length > 12_000 || reference.snippet.includes('[REDACTED]')) {
throw new Error('코드 위치의 조회 근거가 없습니다. 다시 분석을 요청해주세요.')
}
}
export async function resolveCodeReferenceRange(content: string, reference: AgentCodeReference): Promise<{ startLine: number; endLine: number }> {
validateCodeReference(reference)
const lines = content.replace(/\r\n/g, '\n').split('\n')
const snippet = reference.snippet!.replace(/\r\n/g, '\n')
const expectedLines = reference.endLine - reference.startLine + 1
if (snippet.split('\n').length !== expectedLines) throw new Error('코드 위치의 조회 근거가 없습니다. 다시 분석을 요청해주세요.')
const bytes = globalThis.crypto?.subtle ? await globalThis.crypto.subtle.digest('SHA-256', new TextEncoder().encode(content)) : null
const hash = bytes ? Array.from(new Uint8Array(bytes), value => value.toString(16).padStart(2, '0')).join('') : null
if (hash === reference.expectedHash && lines.slice(reference.startLine - 1, reference.endLine).join('\n') === snippet) return { startLine: reference.startLine, endLine: reference.endLine }
// Unsaved edits can shift lines. Only a unique, exact match is safe to reveal.
const matches: number[] = []
for (let index = 0; index <= lines.length - expectedLines; index++) {
if (lines.slice(index, index + expectedLines).join('\n') === snippet) matches.push(index + 1)
if (matches.length > 1) break
}
if (matches.length !== 1) throw new Error('코드가 변경되어 위치를 확정할 수 없습니다. 다시 분석을 요청해주세요.')
return { startLine: matches[0], endLine: matches[0] + expectedLines - 1 }
}
export async function revealAgentCodeReference(options: {
file: string
reference: AgentCodeReference
isCurrent: () => boolean
getEditor: () => editor.IStandaloneCodeEditor | null
getActiveFile: () => string | null
isReady: () => boolean
attempts?: number
}): Promise<editor.IEditorDecorationsCollection> {
for (let attempt = 0; attempt < (options.attempts ?? 100); attempt++) {
if (!options.isCurrent()) throw new Error('코드 위치 열기가 중단되었습니다.')
const instance = options.getEditor()
const model = instance?.getModel()
const element = instance?.getDomNode()
if (options.isReady() && options.getActiveFile() === options.file && instance && element && element.clientWidth > 0 && element.clientHeight > 0 && model && !model.isDisposed() && model.uri.path === `/${options.file}`) {
const content = model.getValue()
const range = await resolveCodeReferenceRange(content, options.reference)
if (!options.isCurrent()) throw new Error('코드 위치 열기가 중단되었습니다.')
if (instance !== options.getEditor() || model !== instance.getModel() || model.getValue() !== content || !options.isReady() || options.getActiveFile() !== options.file) continue
instance.layout()
const selection = { startLineNumber: range.startLine, startColumn: 1, endLineNumber: range.endLine, endColumn: model.getLineMaxColumn(range.endLine) }
const decorations = instance.createDecorationsCollection([{ range: selection, options: { isWholeLine: true, className: 'monaco-agent-code-highlight' } }])
instance.setPosition({ lineNumber: range.startLine, column: 1 })
instance.revealRangeInCenter(selection)
instance.focus()
return decorations
}
await new Promise(resolve => window.setTimeout(resolve, 100))
}
throw new Error('코드 편집기를 준비하지 못했습니다. 다시 시도해주세요.')
}
@@ -3,6 +3,30 @@ import type { AgentToolRequest, AgentToolResult } from '../../../../shared/agent
export type AgentContext = { modules: AgentWorkspaceModule[]; runtimeLogs: AgentRuntimeLog[]; toolResults?: AgentToolResult[] }
export type AgentContextRequest = { modules: string[]; logs: string[] }
export type AgentProgressStep = {
id: string
kind: 'analysis' | 'tool' | 'files' | 'logs' | 'image'
status: 'ok' | 'error' | 'partial' | 'empty' | 'missing'
moduleName?: string
name?: string
path?: string
count?: number
round?: number
requests?: number
}
function toolProgressStep(tool: AgentToolRequest, result: AgentToolResult, id: string): AgentProgressStep {
const step: AgentProgressStep = { id, kind: 'tool', name: tool.name, moduleName: tool.moduleName, path: tool.args?.path, status: result.status === 'error' ? 'error' : result.truncated ? 'partial' : 'ok' }
if (result.status !== 'ok') return step
try {
const data = JSON.parse(result.data)
if (tool.name === 'files.read' && data?.exists === false) step.status = 'missing'
else if (tool.name === 'files.read' && data?.complete === false) step.status = 'partial'
const items = tool.name === 'files.list' ? data?.paths : tool.name === 'files.search' ? data?.matches : undefined
if (Array.isArray(items)) step.count = items.length
} catch { /* Progress never exposes raw tool output or treats it as instructions. */ }
return step
}
export type AgentPlanResponse = {
provider: string
model: string
@@ -14,10 +38,11 @@ export function recoverInterruptedAgentMessages(messages: AgentConversationMessa
let changed = false
const recovered = messages.map(message => {
const interrupted = Object.entries(message.commandExecutions ?? {}).some(([, execution]) => execution.status === 'running')
if (!interrupted && message.applyState !== 'applying') return message
const interruptedNavigation = Object.values(message.codeReferenceStates ?? {}).some(state => state.status === 'opening')
if (!interrupted && !interruptedNavigation && message.applyState !== 'applying') return message
changed = true
const error = '이전 작업의 완료 결과를 확인할 수 없습니다. 현재 모듈·파일 상태를 다시 조회한 뒤 진행해주세요.'
return { ...message, ...(message.applyState === 'applying' ? { applyState: 'error' as const, applyError: error } : {}), commandExecutions: Object.fromEntries(Object.entries(message.commandExecutions ?? {}).map(([id, execution]) => [id, execution.status === 'running' ? { ...execution, status: 'failed' as const, error } : execution])) }
return { ...message, ...(message.applyState === 'applying' ? { applyState: 'error' as const, applyError: error } : {}), ...(interruptedNavigation ? { codeReferenceStates: Object.fromEntries(Object.entries(message.codeReferenceStates ?? {}).map(([id, state]) => [id, state.status === 'opening' ? { status: 'error' as const, error: '코드 위치를 열지 못했습니다. 다시 분석을 요청해주세요.' } : state])) } : {}), commandExecutions: Object.fromEntries(Object.entries(message.commandExecutions ?? {}).map(([id, execution]) => [id, execution.status === 'running' ? { ...execution, status: 'failed' as const, error } : execution])) }
})
return changed ? recovered : messages
}
@@ -100,6 +125,7 @@ export async function resolveAgentContext({
loadRequestedContext,
executeTool,
onProgress,
onStep,
signal
}: {
initialContext: AgentContext
@@ -107,6 +133,7 @@ export async function resolveAgentContext({
loadRequestedContext?: (request: AgentContextRequest) => Promise<AgentContext>
executeTool?: (request: AgentToolRequest) => Promise<AgentToolResult>
onProgress?: (label: string) => void
onStep?: (step: AgentProgressStep) => void
signal: AbortSignal
}): Promise<AgentPlanResponse> {
let cachedContext = initialContext
@@ -127,6 +154,7 @@ export async function resolveAgentContext({
}
const finalize = async (): Promise<AgentPlanResponse> => {
checkAborted()
onProgress?.('최종 답변 정리 중')
const response = await requestPlan(context, true)
checkAborted()
addUsage(response)
@@ -154,12 +182,14 @@ export async function resolveAgentContext({
const maxRounds = executeTool ? 8 : 5
for (let round = 0; round <= maxRounds; round += 1) {
checkAborted()
onProgress?.(round === 0 ? '요청 분석 중' : '조회 결과 분석 중')
const response = await requestPlan(context)
checkAborted()
addUsage(response)
const modules = [...new Set(response.plan.requestedModules ?? [])]
const logs = [...new Set(response.plan.requestedLogs ?? [])]
const tools = response.plan.toolRequests ?? []
onStep?.({ id: `analysis-${round}`, kind: 'analysis', status: 'ok', round: round + 1, requests: modules.length + logs.length + tools.length })
if (!modules.length && !logs.length && !tools.length) return { ...response, usage, plan: { ...response.plan, ...(executeTool ? { evidence: response.plan.evidence ?? context.toolResults ?? [] } : {}) } }
if (round === maxRounds) return finalize()
if (tools.length) {
@@ -175,6 +205,7 @@ export async function resolveAgentContext({
onProgress?.(`${tool.moduleName ? `${tool.moduleName} · ` : ''}${tool.name} 조회 중`)
const result = await executeTool({ ...tool, id: `query-${round}-${index}-${tool.id}`.slice(0, 80) })
checkAborted()
onStep?.(toolProgressStep(tool, result, `tool-${round}-${index}`))
toolCount++
results.push(result)
}
@@ -195,8 +226,19 @@ export async function resolveAgentContext({
}
nextRequest.modules.forEach((name) => requestedModules.add(name))
nextRequest.logs.forEach((name) => requestedLogs.add(name))
onProgress?.('추가 자료 조회 중')
const loaded = await loadRequestedContext(nextRequest)
checkAborted()
for (const name of nextRequest.modules) {
const module = loaded.modules.find(module => module.name === name)
const state = module?.fileContext?.status
const status = state === 'error' ? 'error' : state === 'uninstalled' ? 'missing' : state === 'limited' ? 'partial' : module?.files.length ? 'ok' : 'empty'
onStep?.({ id: `files-${round}-${name}`, kind: 'files', moduleName: name, status, count: module?.files.length ?? 0 })
}
for (const name of nextRequest.logs) {
const log = loaded.runtimeLogs.find(log => log.moduleName === name)
onStep?.({ id: `logs-${round}-${name}`, kind: 'logs', moduleName: name, status: log?.content ? 'ok' : 'empty' })
}
const loadedModules = new Map(loaded.modules.map((module) => [module.name, module]))
const mergedModules = cachedContext.modules.map((module) => {
const updated = loadedModules.get(module.name)
@@ -1,6 +1,6 @@
import { useCallback, useEffect, useMemo, useRef, useState, useSyncExternalStore } from 'react'
import { createAgentSessionSync } from './agentSessionSync'
import { agentPlanRequestText, recoverInterruptedAgentMessages, resolveAgentContext, type AgentContext, type AgentContextRequest, type AgentPlanResponse } from './agentContext'
import { agentPlanRequestText, recoverInterruptedAgentMessages, resolveAgentContext, type AgentContext, type AgentContextRequest, type AgentPlanResponse, type AgentProgressStep } from './agentContext'
import type { AgentImageReference, ChatAttachment } from '../../components/chatAttachments'
import type { AgentOperation, AgentToolRequest, AgentToolResult } from '../../../../shared/agentTools'
@@ -12,6 +12,22 @@ export type AgentFileChange = {
reason: string
}
export type AgentCodeReference = {
id: string
moduleName: string
path: string
startLine: number
endLine: number
reason: string
expectedHash?: string
snippet?: string
}
export type AgentCodeReferenceState = {
status: 'pending' | 'opening' | 'opened' | 'cancelled' | 'error'
error?: string
}
export type AgentModulePlan = {
name: string
category: string
@@ -37,6 +53,7 @@ export type AgentCommandExecution = {
}
export type AgentPlan = {
codeReferences?: AgentCodeReference[]
toolRequests?: AgentToolRequest[]
evidence?: AgentToolResult[]
summary: string
@@ -76,11 +93,13 @@ export type AgentConversationMessage = {
provider?: string
model?: string
revealStartedAt?: number
durationMs?: number
imageAttachments?: AgentImageReference[]
plan?: AgentPlan
applyState?: 'ready' | 'applying' | 'applied' | 'error' | 'cancelled'
applyError?: string
commandExecutions?: Record<string, AgentCommandExecution>
codeReferenceStates?: Record<string, AgentCodeReferenceState>
usage?: {
promptTokens?: number
completionTokens?: number
@@ -119,9 +138,12 @@ export function useAiAgent({ accessToken, studentEmail, courseId, weekNumber, ap
const [historyError, setHistoryError] = useState<string | null>(null)
const [pending, setPending] = useState(false)
const [activity, setActivity] = useState('')
const [progressSteps, setProgressSteps] = useState<AgentProgressStep[]>([])
const [requestStartedAt, setRequestStartedAt] = useState<number | null>(null)
const [messages, setMessages] = useState<AgentConversationMessage[]>([])
const abortControllerRef = useRef<AbortController | null>(null)
const applyingRef = useRef(false)
const navigatingRef = useRef(false)
const tokenReaderRef = useRef(getFreshAccessToken)
tokenReaderRef.current = getFreshAccessToken
const onResponseRef = useRef(onResponse)
@@ -209,6 +231,9 @@ export function useAiAgent({ accessToken, studentEmail, courseId, weekNumber, ap
// Request cancellation follows the conversation lifetime, not its visibility.
useEffect(() => {
setPending(false)
setRequestStartedAt(null)
setProgressSteps([])
setActivity('')
return () => {
abortControllerRef.current?.abort()
abortControllerRef.current = null
@@ -273,6 +298,7 @@ export function useAiAgent({ accessToken, studentEmail, courseId, weekNumber, ap
): Promise<void> => {
const request = rawRequest.trim()
if (!request || pending || historyLoading || abortControllerRef.current || !status.enabled) return
const startedAt = Date.now()
const userMessage: AgentConversationMessage = {
id: `agent-user-${Date.now()}`,
role: 'user',
@@ -280,9 +306,15 @@ export function useAiAgent({ accessToken, studentEmail, courseId, weekNumber, ap
}
if (options.showUserMessage !== false) updateMessages((current) => [...current, userMessage])
setPending(true)
setRequestStartedAt(startedAt)
setProgressSteps([])
setActivity('요청 분석 중')
const abortController = new AbortController()
abortControllerRef.current = abortController
const currentRequest = (): boolean => !abortController.signal.aborted && abortControllerRef.current === abortController && messageStateRef.current.session === session
const appendStep = (step: AgentProgressStep): void => {
if (currentRequest()) setProgressSteps(current => [...current, step])
}
try {
const textAttachments = (options.attachments ?? []).flatMap(file => file.kind === 'image' ? [] : [{ name: file.name, content: file.content }])
@@ -299,6 +331,7 @@ export function useAiAgent({ accessToken, studentEmail, courseId, weekNumber, ap
imageAttachments.push(uploaded)
if (options.showUserMessage !== false) updateMessages(current => current.map(item => item.id === userMessage.id ? { ...item, imageAttachments: [...imageAttachments] } : item))
if (abortController.signal.aborted) return
appendStep({ id: `image-${imageAttachments.length}`, kind: 'image', status: 'ok', name: file.name })
}
const imageTokens = imageAttachments.map(image => image.token)
setActivity('요청 분석 중')
@@ -307,7 +340,8 @@ export function useAiAgent({ accessToken, studentEmail, courseId, weekNumber, ap
signal: abortController.signal,
loadRequestedContext: options.loadRequestedContext,
executeTool: options.executeTool,
onProgress: setActivity,
onProgress: label => { if (currentRequest()) setActivity(label) },
onStep: appendStep,
requestPlan: (context, finalize) => requestApi<AgentPlanResponse>('/chat/agent/plan', {
method: 'POST',
signal: abortController.signal,
@@ -327,6 +361,7 @@ export function useAiAgent({ accessToken, studentEmail, courseId, weekNumber, ap
id: `agent-plan-${Date.now()}`,
role: 'assistant',
revealStartedAt: Date.now(),
durationMs: Math.max(0, Date.now() - startedAt),
content: plan.summary,
provider: response.provider,
model: response.model,
@@ -342,6 +377,7 @@ export function useAiAgent({ accessToken, studentEmail, courseId, weekNumber, ap
id: `agent-error-${Date.now()}`,
role: 'assistant',
revealStartedAt: Date.now(),
durationMs: Math.max(0, Date.now() - startedAt),
content: error instanceof Error ? error.message : 'AI 변경안을 만들지 못했습니다.'
}])
onResponseRef.current?.()
@@ -349,7 +385,9 @@ export function useAiAgent({ accessToken, studentEmail, courseId, weekNumber, ap
if (abortControllerRef.current === abortController) {
abortControllerRef.current = null
setPending(false)
setRequestStartedAt(null)
setActivity('')
setProgressSteps([])
}
}
}
@@ -404,6 +442,9 @@ export function useAiAgent({ accessToken, studentEmail, courseId, weekNumber, ap
abortControllerRef.current?.abort()
abortControllerRef.current = null
setPending(false)
setRequestStartedAt(null)
setActivity('')
setProgressSteps([])
}
const updateCommandExecution = (messageId: string, commandId: string, execution: AgentCommandExecution): void => {
@@ -418,6 +459,34 @@ export function useAiAgent({ accessToken, studentEmail, courseId, weekNumber, ap
: message))
}
const cancelCodeReference = (messageId: string, referenceId: string): void => {
updateMessages(current => current.map(message => message.id === messageId && message.plan?.codeReferences?.some(reference => reference.id === referenceId) && message.codeReferenceStates?.[referenceId]?.status !== 'opening'
? { ...message, codeReferenceStates: { ...message.codeReferenceStates, [referenceId]: { status: 'cancelled' } } }
: message))
}
const openCodeReference = async (messageId: string, referenceId: string, open: (reference: AgentCodeReference, isCurrent: () => boolean) => Promise<void>): Promise<void> => {
if (navigatingRef.current || applyingRef.current || abortControllerRef.current || messageStateRef.current.session !== session) return
const message = messageStateRef.current.messages.find(message => message.id === messageId)
const reference = message?.plan?.codeReferences?.find(reference => reference.id === referenceId)
if (!reference || message?.codeReferenceStates?.[referenceId]?.status === 'cancelled') return
const latestMessageId = messageStateRef.current.messages.at(-1)?.id
const isCurrent = (): boolean => messageStateRef.current.session === session && messageStateRef.current.messages.some(message => message.id === messageId)
const update = (state: AgentCodeReferenceState): void => {
if (isCurrent()) updateMessages(current => current.map(message => message.id === messageId ? { ...message, codeReferenceStates: { ...message.codeReferenceStates, [referenceId]: state } } : message))
}
navigatingRef.current = true
update({ status: 'opening' })
try {
await open(reference, () => isCurrent() && messageStateRef.current.messages.at(-1)?.id === latestMessageId && !abortControllerRef.current)
update({ status: 'opened' })
} catch (error) {
update({ status: 'error', error: error instanceof Error ? error.message : '코드 위치를 열지 못했습니다. 다시 분석을 요청해주세요.' })
} finally {
navigatingRef.current = false
}
}
const clear = (): void => {
if (!authenticated || historyLoading) return
stop()
@@ -444,12 +513,16 @@ export function useAiAgent({ accessToken, studentEmail, courseId, weekNumber, ap
statusLoading: statusLoading || historyLoading,
pending,
activity,
progressSteps,
requestStartedAt,
messages,
persistenceError: persistenceError ?? historyError,
requestPlan,
applyPlan,
cancelPlan,
updateCommandExecution,
openCodeReference,
cancelCodeReference,
stop,
clear
}
+39 -1
View File
@@ -719,5 +719,43 @@
"일부 결과": "Partial results",
"SLAM 실습 준비": "Prepare SLAM practice",
"승인하고 준비": "Approve and prepare",
"내 PC에서 직접 개발 환경을 실행합니다.": "Run the development environment directly on this PC."
"내 PC에서 직접 개발 환경을 실행합니다.": "Run the development environment directly on this PC.",
"진행 상황": "Progress",
"{{seconds}}초 동안 작업 중입니다": "Working for {{seconds}} seconds",
"{{seconds}}초 동안 작업함": "Worked for {{seconds}} seconds",
"모듈 목록": "Module list",
"{{round}}차 분석 완료": "Analysis round {{round}} completed",
"추가 조회 {{count}}건": "{{count}} additional lookups requested",
"이미지 업로드 완료": "Image uploaded",
"파일 목록": "File list",
"파일 검색": "File search",
"파일 읽기": "File read",
"로그 조회": "Log lookup",
"네트워크 조회": "Network inspection",
"ROS 상태 조회": "ROS inspection",
"실습 구성 조회": "Practice configuration",
"조회 결과 없음": "No results",
"대상 없음": "Not found",
"소스 파일": "Source files",
"{{count}}건": "{{count}} items",
"요청 분석 중": "Analyzing request",
"조회 결과 분석 중": "Analyzing retrieved results",
"추가 자료 조회 중": "Retrieving additional context",
"최종 답변 정리 중": "Preparing the final answer",
"코드에서 보기": "View in editor",
"파일 여는 중...": "Opening file...",
"{{start}}~{{end}}줄": "Lines {{start}}-{{end}}",
"코드 위치를 열지 못했습니다. 다시 분석을 요청해주세요.": "Could not open the code location. Ask the agent to analyze it again.",
"코드 위치의 조회 근거가 없습니다. 다시 분석을 요청해주세요.": "The code location has no verified source. Ask the agent to analyze it again.",
"코드가 변경되어 위치를 확정할 수 없습니다. 다시 분석을 요청해주세요.": "The code has changed and its location is no longer certain. Ask the agent to analyze it again.",
"코드 위치 열기가 중단되었습니다.": "Opening the code location was cancelled.",
"코드 편집기를 준비하지 못했습니다. 다시 시도해주세요.": "The editor could not be prepared. Please try again.",
"진행 중인 에이전트 작업이 끝난 뒤 열어주세요.": "Wait for the current agent task before opening the file.",
"코드 위치의 모듈을 찾을 수 없습니다. 다시 분석을 요청해주세요.": "The referenced module was not found. Ask the agent to analyze it again.",
"코드 파일을 읽지 못했습니다. 다시 분석을 요청해주세요.": "The code file could not be read. Ask the agent to analyze it again.",
"코드 복사": "Copy code",
"코드를 복사하지 못했습니다.": "Could not copy the code.",
"일반 텍스트": "Plain text",
"코드": "Code",
"표": "Table"
}
+5 -30
View File
@@ -1,36 +1,11 @@
@import "tailwindcss";
@import "driver.js/dist/driver.css";
@import "./styles/nanumSquare.css";
@import "./styles/chatCode.css";
@font-face {
font-family: 'NanumSquare';
src: url('/assets/fonts/NanumSquareL.woff2') format('woff2');
font-weight: 300;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: 'NanumSquare';
src: url('/assets/fonts/NanumSquare.woff2') format('woff2');
font-weight: 400;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: 'NanumSquare';
src: url('/assets/fonts/NanumSquareB.woff2') format('woff2');
font-weight: 700;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: 'NanumSquare';
src: url('/assets/fonts/NanumSquareEB.woff2') format('woff2');
font-weight: 800;
font-style: normal;
font-display: swap;
.monaco-agent-code-highlight {
background: rgba(124, 58, 237, 0.16);
border-left: 2px solid #8b5cf6;
}
@layer base {
+48
View File
@@ -0,0 +1,48 @@
.chat-code-block[data-code-theme='dark'] {
--code-keyword: #c5a5ee;
--code-string: #b5d69b;
--code-number: #efbc82;
--code-title: #8bc7ed;
--code-comment: #939da8;
--code-meta: #e9d38c;
--code-attribute: #92d6cc;
}
.chat-code-block[data-code-theme='light'] {
--code-keyword: #833ba3;
--code-string: #247041;
--code-number: #a34d11;
--code-title: #1765a4;
--code-comment: #64748b;
--code-meta: #896418;
--code-attribute: #14766e;
}
.chat-code-block :is(.hljs-keyword, .hljs-selector-tag, .hljs-literal, .hljs-doctag) {
color: var(--code-keyword);
}
.chat-code-block :is(.hljs-string, .hljs-regexp, .hljs-addition) {
color: var(--code-string);
}
.chat-code-block :is(.hljs-number, .hljs-symbol, .hljs-bullet) {
color: var(--code-number);
}
.chat-code-block :is(.hljs-title, .hljs-built_in, .hljs-section, .hljs-selector-class) {
color: var(--code-title);
}
.chat-code-block :is(.hljs-comment, .hljs-quote) {
color: var(--code-comment);
font-style: italic;
}
.chat-code-block :is(.hljs-meta, .hljs-deletion) {
color: var(--code-meta);
}
.chat-code-block :is(.hljs-attr, .hljs-attribute, .hljs-variable, .hljs-type) {
color: var(--code-attribute);
}
+776
View File
@@ -0,0 +1,776 @@
/* Generated by scripts/generate-nanumsquare-css.py. Do not edit manually.
* Empty glyphs are excluded so the existing system-font fallback can render them.
* Font binaries and visible glyphs are unchanged.
*/
/* NanumSquareL.woff2 sha256: 310d868fb3afa240a7e8190c83f6eb29ef3ddd9764c3bdf62bfd15e7007f6c36 */
@font-face {
font-family: 'NanumSquare';
src: url('/assets/fonts/NanumSquareL.woff2') format('woff2');
font-weight: 300;
font-style: normal;
font-display: swap;
unicode-range:
U+20-7E, U+A1, U+A4, U+A7-AB, U+B0-B4, U+B6, U+B8-BF, U+C6, U+D0, U+D7-D8,
U+DE-DF, U+E6, U+F0, U+F7-F8, U+FE, U+111, U+126-127, U+131-133, U+138, U+13F-142,
U+149-14B, U+152-153, U+166-167, U+2C7, U+2D8-2DD, U+391-3A1, U+3A3-3A9, U+3B1-3C1, U+3C3-3C9, U+401,
U+410-44F, U+451, U+2010, U+2013-2014, U+2016, U+2018-2019, U+201C-201D, U+2020-2021, U+2025-2026, U+2030,
U+2032-2033, U+203B, U+2074, U+207F, U+2081-2084, U+20A9, U+2103, U+2109, U+2113, U+2116,
U+2121-2122, U+2126, U+212B, U+2153-2154, U+215B-215E, U+2160-2169, U+2170-2179, U+2190-2199, U+21D2, U+21D4,
U+2200, U+2202-2203, U+2207-2208, U+220B, U+220F, U+2211, U+221A, U+221D-221E, U+2220, U+2227-222C,
U+222E, U+2234-2236, U+223D, U+2252, U+2260-2261, U+2264-2265, U+2282-2283, U+2286-2287, U+22A5, U+22EF,
U+2312, U+2460-246E, U+2474-2482, U+249C-24B5, U+24D0-24E9, U+2500-2503, U+250C-254B, U+2592, U+25A0-25A1, U+25A3-25A9,
U+25B2-25B3, U+25B6-25B7, U+25BC-25BD, U+25C0-25C1, U+25C6-25C9, U+25CB, U+25CE-25D1, U+2605-2606, U+260E-260F, U+261C,
U+261E, U+2640, U+2642, U+2660-2661, U+2663-2665, U+2667-266A, U+266C-266D, U+3000-3003, U+3008-3011, U+3013-3015,
U+3041-3093, U+30A1-30F6, U+30FB, U+3131-318E, U+3200-321C, U+3260-327B, U+327F, U+3380-3384, U+3388-33CA, U+33CF-33D0,
U+33D3, U+33D6, U+33D8, U+33DB-33DD, U+AC00-AC01, U+AC04, U+AC07-AC0A, U+AC10-AC17, U+AC19-AC1D, U+AC20,
U+AC24, U+AC2C-AC2D, U+AC2F-AC31, U+AC38-AC39, U+AC3C, U+AC40, U+AC4B, U+AC4D, U+AC54, U+AC58,
U+AC5C, U+AC70-AC71, U+AC74, U+AC77-AC78, U+AC7A, U+AC80-AC81, U+AC83-AC86, U+AC89-AC8C, U+AC90, U+AC94,
U+AC9C-AC9D, U+AC9F-ACA1, U+ACA8-ACAA, U+ACAC, U+ACAF-ACB0, U+ACB8-ACB9, U+ACBB-ACBD, U+ACC1, U+ACC4, U+ACC8,
U+ACCC, U+ACD5, U+ACD7, U+ACE0-ACE1, U+ACE4, U+ACE7-ACE8, U+ACEA, U+ACEC, U+ACEF-ACF1, U+ACF3,
U+ACF5-ACF6, U+ACFC-ACFD, U+AD00, U+AD04, U+AD06, U+AD0C-AD0D, U+AD0F, U+AD11, U+AD18, U+AD1C,
U+AD1E, U+AD20, U+AD29, U+AD2C-AD2D, U+AD34-AD35, U+AD38, U+AD3C, U+AD44-AD45, U+AD47, U+AD49,
U+AD50, U+AD54, U+AD58, U+AD61, U+AD63, U+AD65, U+AD6C-AD6D, U+AD70, U+AD73-AD76, U+AD7B-AD7D,
U+AD7F, U+AD81-AD82, U+AD88-AD89, U+AD8C, U+AD90, U+AD9C-AD9D, U+ADA4, U+ADB4, U+ADB7, U+ADC0-ADC1,
U+ADC4, U+ADC8, U+ADD0-ADD1, U+ADD3, U+ADDC, U+ADE0, U+ADE4, U+ADF8-ADF9, U+ADFC, U+ADFF-AE01,
U+AE08-AE09, U+AE0B, U+AE0D, U+AE14, U+AE1C, U+AE30-AE31, U+AE34, U+AE37-AE38, U+AE3A, U+AE40-AE41,
U+AE43, U+AE45-AE46, U+AE4A, U+AE4C-AE4E, U+AE50, U+AE54, U+AE56, U+AE5C-AE5D, U+AE5F-AE61, U+AE65,
U+AE68-AE69, U+AE6C, U+AE70, U+AE78-AE79, U+AE7B-AE7D, U+AE84-AE86, U+AE8C-AE8D, U+AEA0, U+AEBC-AEBE, U+AEC0,
U+AEC4, U+AECC-AECD, U+AECF-AED1, U+AED8-AED9, U+AEDC, U+AEE8, U+AEEB, U+AEED, U+AEF4, U+AEF8,
U+AEFC, U+AF07-AF08, U+AF0D, U+AF10, U+AF2C-AF2D, U+AF30, U+AF32, U+AF34, U+AF3C-AF3D, U+AF3F,
U+AF41-AF43, U+AF48-AF49, U+AF50, U+AF5C-AF5D, U+AF64-AF65, U+AF79, U+AF80, U+AF84, U+AF88, U+AF90-AF91,
U+AF95, U+AF9C, U+AFA4, U+AFB8-AFBA, U+AFBC, U+AFC0, U+AFC7-AFC9, U+AFCB, U+AFCD-AFCE, U+AFD4,
U+AFDC, U+AFE8-AFE9, U+AFF0-AFF1, U+AFF4, U+AFF8-AFF9, U+AFFF-B001, U+B004, U+B00C, U+B010, U+B014,
U+B01C-B01D, U+B028, U+B044-B045, U+B048, U+B04A, U+B04C, U+B04E, U+B053-B055, U+B057, U+B059,
U+B05D, U+B068, U+B07C-B07D, U+B080, U+B084, U+B08C-B08D, U+B08F, U+B091, U+B098-B09A, U+B09C,
U+B09F-B0A2, U+B0A8-B0A9, U+B0AB-B0AF, U+B0B1, U+B0B3-B0B5, U+B0B8, U+B0BC, U+B0C4-B0C5, U+B0C7-B0C9, U+B0D0-B0D1,
U+B0D4, U+B0D8, U+B0E0, U+B0E5, U+B108-B109, U+B10B-B10C, U+B110, U+B112-B113, U+B118-B119, U+B11B-B11D,
U+B123-B125, U+B128, U+B12C, U+B134-B135, U+B137-B139, U+B140-B141, U+B144, U+B148, U+B150-B151, U+B154-B155,
U+B158, U+B15C, U+B160, U+B178-B179, U+B17C, U+B180, U+B182, U+B188-B189, U+B18B, U+B18D,
U+B192-B194, U+B198, U+B19C, U+B1A8, U+B1CC, U+B1D0, U+B1D4, U+B1DC-B1DD, U+B1DF, U+B1E8-B1E9,
U+B1EC, U+B1F0, U+B1F9, U+B1FB, U+B1FD, U+B204-B205, U+B208, U+B20B-B20C, U+B214-B215, U+B217,
U+B219, U+B220, U+B234, U+B23C, U+B24C-B24D, U+B251, U+B258, U+B25C, U+B260, U+B268-B269,
U+B274-B275, U+B27C, U+B284-B285, U+B289, U+B290-B291, U+B294, U+B298-B29A, U+B2A0-B2A1, U+B2A3, U+B2A5-B2A6,
U+B2AA, U+B2AC, U+B2B0, U+B2B4, U+B2C8-B2C9, U+B2CC, U+B2D0, U+B2D2, U+B2D8-B2D9, U+B2DB,
U+B2DD, U+B2E2, U+B2E4-B2E6, U+B2E8, U+B2EB-B2EF, U+B2F3-B2F5, U+B2F7-B2FB, U+B2FF-B301, U+B304, U+B308,
U+B310-B311, U+B313-B315, U+B31C, U+B324, U+B32C, U+B331, U+B354-B356, U+B358, U+B35B-B35C, U+B35E-B35F,
U+B364-B365, U+B367, U+B369, U+B36B, U+B36E, U+B370-B371, U+B374, U+B378, U+B380-B381, U+B383-B385,
U+B38C, U+B390, U+B394, U+B3A0-B3A1, U+B3A8, U+B3AC, U+B3C4-B3C5, U+B3C8, U+B3CB-B3CC, U+B3CE,
U+B3D0, U+B3D4-B3D5, U+B3D7, U+B3D9, U+B3DB, U+B3DD, U+B3E0, U+B3E4, U+B3E8, U+B3FC,
U+B400, U+B40F-B411, U+B418, U+B41C, U+B420, U+B428-B429, U+B42B-B42D, U+B434, U+B450-B451, U+B454,
U+B458, U+B460-B461, U+B463, U+B465, U+B46C, U+B480, U+B488, U+B49D, U+B4A4, U+B4A8,
U+B4AC, U+B4B5, U+B4B7, U+B4B9, U+B4C0, U+B4C4, U+B4C8, U+B4D0, U+B4D5, U+B4DC-B4DD,
U+B4E0, U+B4E3-B4E4, U+B4E6, U+B4EC-B4ED, U+B4EF, U+B4F1, U+B4F8, U+B514-B515, U+B518, U+B51B-B51C,
U+B524-B525, U+B527-B52A, U+B530-B531, U+B534, U+B538, U+B540-B541, U+B543-B545, U+B54B-B54D, U+B550, U+B554,
U+B55C-B55D, U+B55F-B561, U+B568, U+B584, U+B599, U+B5A0-B5A1, U+B5A4, U+B5A8, U+B5AA-B5AB, U+B5B0-B5B1,
U+B5B3-B5B5, U+B5BB-B5BD, U+B5C0, U+B5C4, U+B5CC-B5CD, U+B5CF-B5D1, U+B5D8, U+B5EC, U+B610-B611, U+B614,
U+B618, U+B620, U+B625, U+B62C, U+B634, U+B648, U+B65D, U+B664, U+B668, U+B69C-B69D,
U+B6A0, U+B6A4, U+B6AB-B6AC, U+B6B1, U+B6D4, U+B6F0, U+B6F4, U+B6F8, U+B700-B701, U+B705,
U+B71B, U+B728-B729, U+B72C, U+B72F-B730, U+B738-B739, U+B73B, U+B73D, U+B744, U+B748, U+B74C,
U+B754-B755, U+B760-B761, U+B764, U+B768, U+B770-B771, U+B773, U+B775, U+B77C-B77D, U+B780, U+B784,
U+B78C-B78D, U+B78F-B792, U+B796-B799, U+B79C, U+B7A0, U+B7A8-B7A9, U+B7AB-B7AD, U+B7B4-B7B5, U+B7B8, U+B7BC,
U+B7C7, U+B7C9, U+B7EC-B7ED, U+B7F0, U+B7F4, U+B7FC-B7FD, U+B7FF-B801, U+B807-B809, U+B80C, U+B810,
U+B818-B819, U+B81B, U+B81D, U+B824-B825, U+B828, U+B82C, U+B834-B835, U+B837-B839, U+B840, U+B844,
U+B851, U+B853, U+B85C-B85D, U+B860, U+B864, U+B86C-B86D, U+B86F, U+B871, U+B878-B879, U+B87C,
U+B880, U+B88D, U+B894, U+B8A8, U+B8B0, U+B8B4, U+B8B8, U+B8C0-B8C1, U+B8C3, U+B8C5,
U+B8CC, U+B8D0, U+B8D4, U+B8DD, U+B8DF, U+B8E1, U+B8E8-B8E9, U+B8EC, U+B8F0, U+B8F8-B8F9,
U+B8FB, U+B8FD, U+B904, U+B918, U+B920, U+B93C-B93D, U+B940, U+B944, U+B94C, U+B94F,
U+B951, U+B958-B959, U+B95C, U+B960, U+B968-B969, U+B96B, U+B96D, U+B974-B975, U+B978, U+B97C,
U+B984-B985, U+B987, U+B989-B98A, U+B98D-B98E, U+B9AC-B9AD, U+B9B0, U+B9B4, U+B9BC-B9BD, U+B9BF, U+B9C1,
U+B9C8-B9C9, U+B9CC, U+B9CE-B9D2, U+B9D8-B9D9, U+B9DB, U+B9DD-B9DE, U+B9E1, U+B9E3-B9E5, U+B9E8, U+B9EC,
U+B9F4-B9F5, U+B9F7-B9FA, U+BA00-BA01, U+BA04, U+BA08, U+BA15, U+BA38-BA39, U+BA3C, U+BA40, U+BA42,
U+BA48-BA49, U+BA4B, U+BA4D-BA4E, U+BA53-BA55, U+BA58, U+BA5C, U+BA64-BA65, U+BA67-BA69, U+BA70-BA71, U+BA74,
U+BA78, U+BA83-BA85, U+BA87, U+BA8C, U+BAA8-BAA9, U+BAAB-BAAC, U+BAB0, U+BAB2, U+BAB8-BAB9, U+BABB,
U+BABD, U+BAC4, U+BAC8, U+BAD8-BAD9, U+BAFC, U+BB00, U+BB04, U+BB0D, U+BB0F, U+BB11,
U+BB18, U+BB1C, U+BB20, U+BB29, U+BB2B, U+BB34-BB36, U+BB38, U+BB3B-BB3E, U+BB44-BB45, U+BB47,
U+BB49, U+BB4D, U+BB4F-BB50, U+BB54, U+BB58, U+BB61, U+BB63, U+BB65, U+BB6C, U+BB88,
U+BB8C, U+BB90, U+BBA4, U+BBA8, U+BBAC, U+BBB4, U+BBB7, U+BBC0, U+BBC4, U+BBC8,
U+BBD0, U+BBD3, U+BBDC, U+BBF8-BBF9, U+BBFC, U+BBFF-BC00, U+BC02, U+BC08-BC09, U+BC0B-BC0D, U+BC0F,
U+BC11, U+BC14-BC18, U+BC1B-BC1F, U+BC24-BC25, U+BC27, U+BC29, U+BC2D, U+BC30-BC31, U+BC34, U+BC38,
U+BC40-BC41, U+BC43-BC45, U+BC49, U+BC4C-BC4D, U+BC50, U+BC5D, U+BC84-BC85, U+BC88, U+BC8B-BC8C, U+BC8E,
U+BC94-BC95, U+BC97, U+BC99-BC9A, U+BCA0-BCA1, U+BCA4, U+BCA7-BCA9, U+BCB0-BCB1, U+BCB3-BCB5, U+BCBC-BCBD, U+BCC0,
U+BCC4, U+BCCD, U+BCCF-BCD1, U+BCD5, U+BCD8, U+BCDC, U+BCF4-BCF6, U+BCF8, U+BCFC, U+BD04-BD05,
U+BD07, U+BD09, U+BD10, U+BD14, U+BD23-BD25, U+BD2C, U+BD35, U+BD40, U+BD48-BD49, U+BD4C,
U+BD50, U+BD58-BD59, U+BD64, U+BD68, U+BD79, U+BD80-BD81, U+BD84, U+BD87-BD8A, U+BD90-BD91, U+BD93,
U+BD95, U+BD99-BD9A, U+BD9C, U+BDA4, U+BDB0, U+BDB8-BDB9, U+BDC1, U+BDD4-BDD5, U+BDD8, U+BDDC,
U+BDE9, U+BDF0, U+BDF4, U+BDF8, U+BE00, U+BE03, U+BE05, U+BE0C-BE0D, U+BE10, U+BE14,
U+BE1C-BE1D, U+BE1F, U+BE44-BE45, U+BE48, U+BE4C, U+BE4E, U+BE54-BE55, U+BE57, U+BE59-BE5B, U+BE60-BE61,
U+BE64, U+BE68, U+BE6A, U+BE70-BE71, U+BE73-BE75, U+BE7B-BE7D, U+BE80, U+BE84, U+BE8C-BE8D, U+BE8F-BE91,
U+BE98-BE99, U+BEA8, U+BEAD, U+BEB4, U+BED0-BED1, U+BED4, U+BED7-BED8, U+BEE0, U+BEE3-BEE5, U+BEEC,
U+BEF5, U+BF01, U+BF08-BF09, U+BF18-BF19, U+BF1B-BF1D, U+BF40-BF41, U+BF44, U+BF48, U+BF50-BF51, U+BF55,
U+BF5C, U+BF71, U+BF94, U+BFB0, U+BFB8, U+BFC5, U+BFCC-BFCD, U+BFD0, U+BFD4, U+BFDC,
U+BFDF, U+BFE1, U+C00D, U+C03C, U+C04C, U+C051, U+C058, U+C05C, U+C060, U+C068-C069,
U+C090-C091, U+C094, U+C098, U+C0A0-C0A1, U+C0A3, U+C0A5, U+C0AC-C0AD, U+C0AF-C0B0, U+C0B3-C0B6, U+C0BC-C0BD,
U+C0BF-C0C1, U+C0C5, U+C0C8-C0C9, U+C0CC, U+C0D0, U+C0D8-C0D9, U+C0DB-C0DD, U+C0E4-C0E5, U+C0E8, U+C0EC,
U+C0F4-C0F5, U+C0F7, U+C0F9, U+C0FE, U+C100, U+C104, U+C108, U+C110, U+C115, U+C11C-C120,
U+C123-C124, U+C126-C127, U+C12C-C12D, U+C12F-C131, U+C136, U+C138-C139, U+C13C, U+C140, U+C142, U+C148-C149,
U+C14B-C14D, U+C154-C155, U+C158, U+C15C, U+C164-C165, U+C167-C169, U+C170, U+C174, U+C178, U+C185,
U+C18C-C18E, U+C190, U+C194, U+C196, U+C19C-C19D, U+C19F, U+C1A1, U+C1A5, U+C1A8-C1A9, U+C1AC,
U+C1B0, U+C1BD, U+C1C4, U+C1C8, U+C1CC, U+C1D4, U+C1D7-C1D8, U+C1E0, U+C1E4, U+C1E8,
U+C1F0-C1F1, U+C1F3, U+C1FC-C1FD, U+C200, U+C204, U+C20C-C20D, U+C20F, U+C211, U+C216, U+C218-C219,
U+C21C, U+C21F-C220, U+C228-C229, U+C22B, U+C22D, U+C22F, U+C231-C232, U+C234, U+C245, U+C248,
U+C250-C251, U+C254, U+C258, U+C25F-C260, U+C265, U+C26C-C26D, U+C270, U+C274, U+C27C-C27D, U+C27F,
U+C281, U+C288-C289, U+C28C, U+C290, U+C298-C299, U+C29B, U+C29D, U+C2A0, U+C2A4-C2A5, U+C2A8,
U+C2AC-C2AD, U+C2B4-C2B5, U+C2B7, U+C2B9, U+C2DC-C2DD, U+C2E0, U+C2E3-C2E4, U+C2EB-C2ED, U+C2EF, U+C2F1,
U+C2F6, U+C2F8-C2F9, U+C2FB-C2FC, U+C300, U+C308-C309, U+C30C-C30D, U+C313-C315, U+C318, U+C31C, U+C324-C325,
U+C328-C329, U+C330, U+C338, U+C345, U+C34C, U+C368-C369, U+C36C, U+C370, U+C372, U+C378-C379,
U+C37B-C37D, U+C384, U+C388, U+C38C, U+C395, U+C399, U+C3B5, U+C3BC, U+C3C0, U+C3D8-C3D9,
U+C3DC, U+C3DF-C3E0, U+C3E2, U+C3E8-C3E9, U+C3ED, U+C3F4-C3F5, U+C3F8, U+C3FC, U+C408, U+C410,
U+C424, U+C42C, U+C430, U+C434, U+C43C-C43D, U+C448, U+C450, U+C45D, U+C464-C465, U+C468,
U+C46C, U+C474-C475, U+C479, U+C480, U+C494, U+C49C-C49D, U+C4B8, U+C4BC, U+C4CD, U+C4D4,
U+C4D8, U+C4E9, U+C4F0-C4F1, U+C4F4, U+C4F8, U+C4FA, U+C4FF-C501, U+C50C, U+C510, U+C514,
U+C51C, U+C521, U+C528-C529, U+C52C, U+C530, U+C538-C539, U+C53B, U+C53D, U+C544-C545, U+C548-C54A,
U+C54C-C54E, U+C553-C555, U+C557-C559, U+C55C-C55E, U+C560-C561, U+C564, U+C568, U+C570-C571, U+C573-C575, U+C57B-C57D,
U+C580, U+C584, U+C587, U+C58C-C58D, U+C58F, U+C591, U+C595, U+C597-C598, U+C59C, U+C5A0,
U+C5A9, U+C5B4-C5B5, U+C5B8-C5B9, U+C5BB-C5BE, U+C5C4-C5CA, U+C5CC, U+C5CE, U+C5D0-C5D1, U+C5D4, U+C5D8,
U+C5E0-C5E1, U+C5E3, U+C5E5, U+C5EC-C5EE, U+C5F0, U+C5F4, U+C5F6-C5F7, U+C5FC-C601, U+C605-C608, U+C60C,
U+C610, U+C618-C619, U+C61B-C61C, U+C624-C625, U+C628, U+C62C-C62E, U+C630, U+C633-C635, U+C637, U+C639,
U+C63B, U+C640-C641, U+C644, U+C648, U+C650-C651, U+C653-C655, U+C65C-C65D, U+C660, U+C664, U+C66C,
U+C66F, U+C671, U+C674, U+C678-C679, U+C67C, U+C680, U+C688-C689, U+C68B, U+C68D, U+C694-C695,
U+C698, U+C69C, U+C6A4-C6A5, U+C6A7, U+C6A9, U+C6B0-C6B1, U+C6B4, U+C6B8-C6BA, U+C6C0-C6C1, U+C6C3,
U+C6C5, U+C6CC-C6CD, U+C6D0, U+C6D4, U+C6DC-C6DD, U+C6DF-C6E1, U+C6E8-C6E9, U+C6EC, U+C6F0-C6F1, U+C6F8-C6F9,
U+C6FD, U+C704-C705, U+C708, U+C70C, U+C714-C715, U+C717, U+C719, U+C720-C721, U+C724, U+C728,
U+C730-C731, U+C733, U+C735, U+C737, U+C73C-C73E, U+C740, U+C743-C744, U+C74A, U+C74C-C74F, U+C751-C758,
U+C75C, U+C760, U+C768, U+C76B, U+C774-C776, U+C778, U+C77C-C77E, U+C783-C785, U+C787-C78A, U+C78C,
U+C78E, U+C790-C791, U+C794, U+C796-C798, U+C79A, U+C7A0-C7A1, U+C7A3-C7A6, U+C7AC-C7AD, U+C7B0, U+C7B4,
U+C7BC-C7BD, U+C7BF-C7C1, U+C7C8-C7C9, U+C7CC, U+C7CE, U+C7D0, U+C7D8-C7D9, U+C7DD, U+C7E4, U+C7E8,
U+C7EC, U+C800-C801, U+C804, U+C808, U+C80A, U+C810-C811, U+C813, U+C815-C816, U+C81C-C81D, U+C820,
U+C824, U+C82C-C82D, U+C82F, U+C831, U+C838, U+C83C, U+C840, U+C848-C849, U+C84C-C84D, U+C854,
U+C870-C871, U+C874, U+C878, U+C87A, U+C880-C881, U+C883, U+C885-C887, U+C88B-C88D, U+C890, U+C894,
U+C89D, U+C89F, U+C8A1, U+C8A8, U+C8BC-C8BD, U+C8C4, U+C8C8, U+C8CC, U+C8D4-C8D5, U+C8D7,
U+C8D9, U+C8E0-C8E1, U+C8E4, U+C8E8, U+C8F5, U+C8FC-C8FD, U+C900, U+C904-C906, U+C90C-C90D, U+C90F,
U+C911, U+C918, U+C92C, U+C934-C935, U+C938, U+C950-C951, U+C954, U+C958, U+C960-C961, U+C963,
U+C965, U+C96C, U+C970, U+C974, U+C97C, U+C981, U+C988-C989, U+C98C, U+C990, U+C998-C999,
U+C99B, U+C99D, U+C9A4, U+C9A8, U+C9C0-C9C1, U+C9C4, U+C9C7-C9C8, U+C9CA, U+C9D0-C9D1, U+C9D3,
U+C9D5-C9D6, U+C9D9-C9DA, U+C9DC-C9DD, U+C9E0, U+C9E2, U+C9E4, U+C9E7, U+C9EC-C9ED, U+C9EF-C9F1, U+C9F8-C9F9,
U+C9FC, U+CA00, U+CA08-CA09, U+CA0B-CA0D, U+CA14, U+CA18, U+CA1C, U+CA29, U+CA30, U+CA4C-CA4D,
U+CA50, U+CA54, U+CA5C-CA5D, U+CA5F-CA61, U+CA68, U+CA7D, U+CA84, U+CA98, U+CABC-CABD, U+CAC0,
U+CAC4, U+CACC-CACD, U+CACF, U+CAD1, U+CAD3, U+CAD8-CAD9, U+CAE0, U+CAEC, U+CAF4, U+CB08,
U+CB10, U+CB14, U+CB18, U+CB20-CB21, U+CB2C, U+CB41, U+CB48-CB49, U+CB4C, U+CB50, U+CB58-CB59,
U+CB5D, U+CB64, U+CB78-CB79, U+CB9C, U+CBB8, U+CBD4, U+CBE4-CBE5, U+CBE7, U+CBE9, U+CC0C-CC0D,
U+CC10, U+CC14, U+CC1C-CC1D, U+CC21-CC22, U+CC27-CC29, U+CC2C, U+CC2E, U+CC30, U+CC38-CC39, U+CC3B-CC3E,
U+CC44-CC45, U+CC48, U+CC4C, U+CC54-CC55, U+CC57-CC59, U+CC60, U+CC64, U+CC66, U+CC68, U+CC70,
U+CC75, U+CC98-CC99, U+CC9C, U+CCA0, U+CCA8-CCA9, U+CCAB-CCAD, U+CCB4-CCB5, U+CCB8, U+CCBC, U+CCC4-CCC5,
U+CCC7, U+CCC9, U+CCD0, U+CCD4, U+CCE1, U+CCE4, U+CCEC, U+CCF0, U+CD01, U+CD08-CD09,
U+CD0C, U+CD10, U+CD18-CD19, U+CD1B, U+CD1D, U+CD24, U+CD28, U+CD2C, U+CD39, U+CD5C-CD5D,
U+CD60, U+CD64, U+CD6C-CD6D, U+CD6F, U+CD71, U+CD78, U+CD88, U+CD94-CD95, U+CD98, U+CD9C,
U+CDA4-CDA5, U+CDA7, U+CDA9, U+CDB0, U+CDC4, U+CDCC-CDCD, U+CDD0, U+CDDD, U+CDDF, U+CDE8,
U+CDEC, U+CDF0, U+CDF8-CDF9, U+CDFB, U+CDFD, U+CE04, U+CE08, U+CE0C, U+CE14-CE15, U+CE19,
U+CE20-CE21, U+CE24, U+CE28, U+CE30-CE31, U+CE33, U+CE35, U+CE58-CE59, U+CE5C, U+CE5F-CE61, U+CE68-CE69,
U+CE6B, U+CE6D, U+CE74-CE75, U+CE78, U+CE7C, U+CE84-CE85, U+CE87, U+CE89, U+CE8C, U+CE90-CE91,
U+CE94, U+CE98, U+CEA0-CEA1, U+CEA3-CEA5, U+CEAC-CEAE, U+CEB4, U+CEC1, U+CEC4, U+CEE4-CEE5, U+CEE8,
U+CEEB-CEEC, U+CEF4-CEF5, U+CEF7-CEF9, U+CF00-CF01, U+CF04, U+CF08, U+CF10-CF11, U+CF13, U+CF15, U+CF18,
U+CF1C, U+CF20, U+CF24, U+CF2C-CF2D, U+CF2F-CF31, U+CF38, U+CF54-CF55, U+CF58, U+CF5C, U+CF64-CF65,
U+CF67, U+CF69, U+CF70-CF71, U+CF74, U+CF78, U+CF80, U+CF85, U+CF8C, U+CFA1, U+CFA8,
U+CFB0, U+CFC4, U+CFDC, U+CFE0-CFE1, U+CFE4, U+CFE8, U+CFF0-CFF1, U+CFF3, U+CFF5, U+CFFC-CFFD,
U+D000, U+D004, U+D011, U+D018, U+D02D, U+D034-D035, U+D038, U+D03C, U+D044-D045, U+D047,
U+D049, U+D050, U+D054, U+D058, U+D060, U+D06C-D06E, U+D070, U+D074, U+D07C-D07D, U+D07F,
U+D081, U+D084, U+D0A4-D0A5, U+D0A8, U+D0AC, U+D0B4-D0B5, U+D0B7, U+D0B9, U+D0C0-D0C1, U+D0C4,
U+D0C8-D0C9, U+D0D0-D0D1, U+D0D3-D0D5, U+D0DC-D0DD, U+D0E0, U+D0E4, U+D0EC-D0ED, U+D0EF-D0F1, U+D0F8, U+D10D,
U+D130-D131, U+D134, U+D138, U+D13A, U+D140-D141, U+D143-D145, U+D14C-D14D, U+D150, U+D154, U+D15C-D15D,
U+D15F, U+D161, U+D168, U+D16C, U+D17C, U+D184, U+D188, U+D1A0-D1A1, U+D1A4, U+D1A8,
U+D1B0-D1B1, U+D1B3, U+D1B5, U+D1BA, U+D1BC, U+D1C0, U+D1D8, U+D1F4, U+D1F8, U+D207,
U+D209, U+D210, U+D22C-D22D, U+D230, U+D234, U+D23C-D23D, U+D23F, U+D241, U+D248, U+D25C,
U+D264, U+D277, U+D280-D281, U+D284, U+D288, U+D290-D291, U+D295, U+D29C, U+D2A0, U+D2A4,
U+D2AC, U+D2B1, U+D2B8-D2B9, U+D2BC, U+D2BF-D2C0, U+D2C2, U+D2C8-D2C9, U+D2CB, U+D2D4, U+D2D8,
U+D2DC, U+D2E4-D2E5, U+D2F0-D2F1, U+D2F4, U+D2F8, U+D300-D301, U+D303, U+D305, U+D30C-D30E, U+D310,
U+D314, U+D316, U+D31C-D31D, U+D31F-D321, U+D325, U+D328-D329, U+D32C, U+D330, U+D338-D339, U+D33B-D33D,
U+D344-D345, U+D37C-D37D, U+D380, U+D384, U+D38C-D38D, U+D38F-D391, U+D398-D399, U+D39C, U+D3A0, U+D3A8-D3A9,
U+D3AB, U+D3AD, U+D3B4, U+D3B8, U+D3BC, U+D3C4-D3C5, U+D3C8-D3C9, U+D3D0, U+D3D8, U+D3E1,
U+D3E3, U+D3EC-D3ED, U+D3F0, U+D3F4, U+D3FC-D3FD, U+D3FF, U+D401, U+D408, U+D41D, U+D440,
U+D444, U+D45C, U+D460, U+D464, U+D46D, U+D46F, U+D478-D479, U+D47C, U+D47F-D480, U+D482,
U+D488-D489, U+D48B, U+D48D, U+D494, U+D4A9, U+D4B0, U+D4CC, U+D4D0, U+D4D4, U+D4DC,
U+D4DF, U+D4E8, U+D4EC, U+D4F0, U+D4F8, U+D4FB, U+D4FD, U+D504, U+D508, U+D50C,
U+D514-D515, U+D517, U+D53C-D53D, U+D540, U+D544, U+D54C-D54D, U+D54F, U+D551, U+D558-D559, U+D55C,
U+D560, U+D565, U+D568-D569, U+D56B, U+D56D, U+D571, U+D573-D575, U+D578, U+D57C, U+D584-D585,
U+D587-D589, U+D58F-D590, U+D59D, U+D5A5, U+D5C8-D5C9, U+D5CC, U+D5D0, U+D5D2, U+D5D8-D5D9, U+D5DB,
U+D5DD, U+D5E4-D5E5, U+D5E8, U+D5EC, U+D5F3-D5F5, U+D5F7, U+D5F9, U+D5FF-D601, U+D604, U+D608,
U+D610-D611, U+D613-D615, U+D61C, U+D620, U+D624, U+D62D, U+D638-D639, U+D63C, U+D640, U+D645,
U+D648-D649, U+D64B, U+D64D, U+D651, U+D654-D655, U+D658, U+D65C, U+D664, U+D667, U+D669,
U+D670-D671, U+D674, U+D683, U+D685, U+D68C-D68D, U+D690, U+D694, U+D69D, U+D69F, U+D6A1,
U+D6A8, U+D6AC, U+D6B0, U+D6B9, U+D6BB, U+D6BD, U+D6C3-D6C5, U+D6C8, U+D6CC, U+D6D1,
U+D6D4-D6D5, U+D6D7, U+D6D9, U+D6E0, U+D6E4, U+D6E8, U+D6F0, U+D6F5, U+D6FC-D6FD, U+D700,
U+D704, U+D711, U+D718-D719, U+D71C, U+D720, U+D728-D729, U+D72B, U+D72D, U+D734-D735, U+D738,
U+D73C, U+D744, U+D747, U+D749, U+D750-D751, U+D754, U+D756-D759, U+D760-D761, U+D763, U+D765,
U+D769, U+D76C, U+D770, U+D774, U+D77C-D77D, U+D781, U+D788-D789, U+D78C, U+D790, U+D798-D799,
U+D79B, U+D79D, U+FF01-FF5E, U+FFE0-FFE3, U+FFE5-FFE6;
}
/* NanumSquare.woff2 sha256: 2e9ab2497693544b0dfeebbc409ac64a1da2f946748f360f34fc71cd2475185e */
@font-face {
font-family: 'NanumSquare';
src: url('/assets/fonts/NanumSquare.woff2') format('woff2');
font-weight: 400;
font-style: normal;
font-display: swap;
unicode-range:
U+20-7E, U+A1, U+A4, U+A7-AB, U+B0-B4, U+B6, U+B8-BF, U+C6, U+D0, U+D7-D8,
U+DE-DF, U+E6, U+F0, U+F7-F8, U+FE, U+111, U+126-127, U+131-133, U+138, U+13F-142,
U+149-14B, U+152-153, U+166-167, U+2C7, U+2D8-2DD, U+391-3A1, U+3A3-3A9, U+3B1-3C1, U+3C3-3C9, U+401,
U+410-44F, U+451, U+2010, U+2013-2014, U+2016, U+2018-2019, U+201C-201D, U+2020-2021, U+2025-2026, U+2030,
U+2032-2033, U+203B, U+2074, U+207F, U+2081-2084, U+20A9, U+2103, U+2109, U+2113, U+2116,
U+2121-2122, U+2126, U+212B, U+2153-2154, U+215B-215E, U+2160-2169, U+2170-2179, U+2190-2199, U+21D2, U+21D4,
U+2200, U+2202-2203, U+2207-2208, U+220B, U+220F, U+2211, U+221A, U+221D-221E, U+2220, U+2227-222C,
U+222E, U+2234-2236, U+223D, U+2252, U+2260-2261, U+2264-2265, U+2282-2283, U+2286-2287, U+22A5, U+22EF,
U+2312, U+2460-246E, U+2474-2482, U+249C-24B5, U+24D0-24E9, U+2500-2503, U+250C-254B, U+2592, U+25A0-25A1, U+25A3-25A9,
U+25B2-25B3, U+25B6-25B7, U+25BC-25BD, U+25C0-25C1, U+25C6-25C9, U+25CB, U+25CE-25D1, U+2605-2606, U+260E-260F, U+261C,
U+261E, U+2640, U+2642, U+2660-2661, U+2663-2665, U+2667-266A, U+266C-266D, U+3000-3003, U+3008-3011, U+3013-3015,
U+3041-3093, U+30A1-30F6, U+30FB, U+3131-318E, U+3200-321C, U+3260-327B, U+327F, U+3380-3384, U+3388-33CA, U+33CF-33D0,
U+33D3, U+33D6, U+33D8, U+33DB-33DD, U+AC00-AC01, U+AC04, U+AC07-AC0A, U+AC10-AC17, U+AC19-AC1D, U+AC20,
U+AC24, U+AC2C-AC2D, U+AC2F-AC31, U+AC38-AC39, U+AC3C, U+AC40, U+AC4B, U+AC4D, U+AC54, U+AC58,
U+AC5C, U+AC70-AC71, U+AC74, U+AC77-AC78, U+AC7A, U+AC80-AC81, U+AC83-AC86, U+AC89-AC8C, U+AC90, U+AC94,
U+AC9C-AC9D, U+AC9F-ACA1, U+ACA8-ACAA, U+ACAC, U+ACAF-ACB0, U+ACB8-ACB9, U+ACBB-ACBD, U+ACC1, U+ACC4, U+ACC8,
U+ACCC, U+ACD5, U+ACD7, U+ACE0-ACE1, U+ACE4, U+ACE7-ACE8, U+ACEA, U+ACEC, U+ACEF-ACF1, U+ACF3,
U+ACF5-ACF6, U+ACFC-ACFD, U+AD00, U+AD04, U+AD06, U+AD0C-AD0D, U+AD0F, U+AD11, U+AD18, U+AD1C,
U+AD1E, U+AD20, U+AD29, U+AD2C-AD2D, U+AD34-AD35, U+AD38, U+AD3C, U+AD44-AD45, U+AD47, U+AD49,
U+AD50, U+AD54, U+AD58, U+AD61, U+AD63, U+AD65, U+AD6C-AD6D, U+AD70, U+AD73-AD76, U+AD7B-AD7D,
U+AD7F, U+AD81-AD82, U+AD88-AD89, U+AD8C, U+AD90, U+AD9C-AD9D, U+ADA4, U+ADB4, U+ADB7, U+ADC0-ADC1,
U+ADC4, U+ADC8, U+ADD0-ADD1, U+ADD3, U+ADDC, U+ADE0, U+ADE4, U+ADF8-ADF9, U+ADFC, U+ADFF-AE01,
U+AE08-AE09, U+AE0B, U+AE0D, U+AE14, U+AE1C, U+AE30-AE31, U+AE34, U+AE37-AE38, U+AE3A, U+AE40-AE41,
U+AE43, U+AE45-AE46, U+AE4A, U+AE4C-AE4E, U+AE50, U+AE54, U+AE56, U+AE5C-AE5D, U+AE5F-AE61, U+AE65,
U+AE68-AE69, U+AE6C, U+AE70, U+AE78-AE79, U+AE7B-AE7D, U+AE84-AE86, U+AE8C-AE8D, U+AEA0, U+AEBC-AEBE, U+AEC0,
U+AEC4, U+AECC-AECD, U+AECF-AED1, U+AED8-AED9, U+AEDC, U+AEE8, U+AEEB, U+AEED, U+AEF4, U+AEF8,
U+AEFC, U+AF07-AF08, U+AF0D, U+AF10, U+AF2C-AF2D, U+AF30, U+AF32, U+AF34, U+AF3C-AF3D, U+AF3F,
U+AF41-AF43, U+AF48-AF49, U+AF50, U+AF5C-AF5D, U+AF64-AF65, U+AF79, U+AF80, U+AF84, U+AF88, U+AF90-AF91,
U+AF95, U+AF9C, U+AFA4, U+AFB8-AFBA, U+AFBC, U+AFC0, U+AFC7-AFC9, U+AFCB, U+AFCD-AFCE, U+AFD4,
U+AFDC, U+AFE8-AFE9, U+AFF0-AFF1, U+AFF4, U+AFF8-AFF9, U+AFFF-B001, U+B004, U+B00C, U+B010, U+B014,
U+B01C-B01D, U+B028, U+B044-B045, U+B048, U+B04A, U+B04C, U+B04E, U+B053-B055, U+B057, U+B059,
U+B05D, U+B068, U+B07C-B07D, U+B080, U+B084, U+B08C-B08D, U+B08F, U+B091, U+B098-B09A, U+B09C,
U+B09F-B0A2, U+B0A8-B0A9, U+B0AB-B0AF, U+B0B1, U+B0B3-B0B5, U+B0B8, U+B0BC, U+B0C4-B0C5, U+B0C7-B0C9, U+B0D0-B0D1,
U+B0D4, U+B0D8, U+B0E0, U+B0E5, U+B108-B109, U+B10B-B10C, U+B110, U+B112-B113, U+B118-B119, U+B11B-B11D,
U+B123-B125, U+B128, U+B12C, U+B134-B135, U+B137-B139, U+B140-B141, U+B144, U+B148, U+B150-B151, U+B154-B155,
U+B158, U+B15C, U+B160, U+B178-B179, U+B17C, U+B180, U+B182, U+B188-B189, U+B18B, U+B18D,
U+B192-B194, U+B198, U+B19C, U+B1A8, U+B1CC, U+B1D0, U+B1D4, U+B1DC-B1DD, U+B1DF, U+B1E8-B1E9,
U+B1EC, U+B1F0, U+B1F9, U+B1FB, U+B1FD, U+B204-B205, U+B208, U+B20B-B20C, U+B214-B215, U+B217,
U+B219, U+B220, U+B234, U+B23C, U+B24C-B24D, U+B251, U+B258, U+B25C, U+B260, U+B268-B269,
U+B274-B275, U+B27C, U+B284-B285, U+B289, U+B290-B291, U+B294, U+B298-B29A, U+B2A0-B2A1, U+B2A3, U+B2A5-B2A6,
U+B2AA, U+B2AC, U+B2B0, U+B2B4, U+B2C8-B2C9, U+B2CC, U+B2D0, U+B2D2, U+B2D8-B2D9, U+B2DB,
U+B2DD, U+B2E2, U+B2E4-B2E6, U+B2E8, U+B2EB-B2EF, U+B2F3-B2F5, U+B2F7-B2FB, U+B2FF-B301, U+B304, U+B308,
U+B310-B311, U+B313-B315, U+B31C, U+B324, U+B32C, U+B331, U+B354-B356, U+B358, U+B35B-B35C, U+B35E-B35F,
U+B364-B365, U+B367, U+B369, U+B36B, U+B36E, U+B370-B371, U+B374, U+B378, U+B380-B381, U+B383-B385,
U+B38C, U+B390, U+B394, U+B3A0-B3A1, U+B3A8, U+B3AC, U+B3C4-B3C5, U+B3C8, U+B3CB-B3CC, U+B3CE,
U+B3D0, U+B3D4-B3D5, U+B3D7, U+B3D9, U+B3DB, U+B3DD, U+B3E0, U+B3E4, U+B3E8, U+B3FC,
U+B400, U+B40F-B411, U+B418, U+B41C, U+B420, U+B428-B429, U+B42B-B42D, U+B434, U+B450-B451, U+B454,
U+B458, U+B460-B461, U+B463, U+B465, U+B46C, U+B480, U+B488, U+B49D, U+B4A4, U+B4A8,
U+B4AC, U+B4B5, U+B4B7, U+B4B9, U+B4C0, U+B4C4, U+B4C8, U+B4D0, U+B4D5, U+B4DC-B4DD,
U+B4E0, U+B4E3-B4E4, U+B4E6, U+B4EC-B4ED, U+B4EF, U+B4F1, U+B4F8, U+B514-B515, U+B518, U+B51B-B51C,
U+B524-B525, U+B527-B52A, U+B530-B531, U+B534, U+B538, U+B540-B541, U+B543-B545, U+B54B-B54D, U+B550, U+B554,
U+B55C-B55D, U+B55F-B561, U+B568, U+B584, U+B599, U+B5A0-B5A1, U+B5A4, U+B5A8, U+B5AA-B5AB, U+B5B0-B5B1,
U+B5B3-B5B5, U+B5BB-B5BD, U+B5C0, U+B5C4, U+B5CC-B5CD, U+B5CF-B5D1, U+B5D8, U+B5EC, U+B610-B611, U+B614,
U+B618, U+B620, U+B625, U+B62C, U+B634, U+B648, U+B65D, U+B664, U+B668, U+B69C-B69D,
U+B6A0, U+B6A4, U+B6AB-B6AC, U+B6B1, U+B6D4, U+B6F0, U+B6F4, U+B6F8, U+B700-B701, U+B705,
U+B71B, U+B728-B729, U+B72C, U+B72F-B730, U+B738-B739, U+B73B, U+B73D, U+B744, U+B748, U+B74C,
U+B754-B755, U+B760-B761, U+B764, U+B768, U+B770-B771, U+B773, U+B775, U+B77C-B77D, U+B780, U+B784,
U+B78C-B78D, U+B78F-B792, U+B796-B799, U+B79C, U+B7A0, U+B7A8-B7A9, U+B7AB-B7AD, U+B7B4-B7B5, U+B7B8, U+B7BC,
U+B7C7, U+B7C9, U+B7EC-B7ED, U+B7F0, U+B7F4, U+B7FC-B7FD, U+B7FF-B801, U+B807-B809, U+B80C, U+B810,
U+B818-B819, U+B81B, U+B81D, U+B824-B825, U+B828, U+B82C, U+B834-B835, U+B837-B839, U+B840, U+B844,
U+B851, U+B853, U+B85C-B85D, U+B860, U+B864, U+B86C-B86D, U+B86F, U+B871, U+B878-B879, U+B87C,
U+B880, U+B88D, U+B894, U+B8A8, U+B8B0, U+B8B4, U+B8B8, U+B8C0-B8C1, U+B8C3, U+B8C5,
U+B8CC, U+B8D0, U+B8D4, U+B8DD, U+B8DF, U+B8E1, U+B8E8-B8E9, U+B8EC, U+B8F0, U+B8F8-B8F9,
U+B8FB, U+B8FD, U+B904, U+B918, U+B920, U+B93C-B93D, U+B940, U+B944, U+B94C, U+B94F,
U+B951, U+B958-B959, U+B95C, U+B960, U+B968-B969, U+B96B, U+B96D, U+B974-B975, U+B978, U+B97C,
U+B984-B985, U+B987, U+B989-B98A, U+B98D-B98E, U+B9AC-B9AD, U+B9B0, U+B9B4, U+B9BC-B9BD, U+B9BF, U+B9C1,
U+B9C8-B9C9, U+B9CC, U+B9CE-B9D2, U+B9D8-B9D9, U+B9DB, U+B9DD-B9DE, U+B9E1, U+B9E3-B9E5, U+B9E8, U+B9EC,
U+B9F4-B9F5, U+B9F7-B9FA, U+BA00-BA01, U+BA04, U+BA08, U+BA15, U+BA38-BA39, U+BA3C, U+BA40, U+BA42,
U+BA48-BA49, U+BA4B, U+BA4D-BA4E, U+BA53-BA55, U+BA58, U+BA5C, U+BA64-BA65, U+BA67-BA69, U+BA70-BA71, U+BA74,
U+BA78, U+BA83-BA85, U+BA87, U+BA8C, U+BAA8-BAA9, U+BAAB-BAAC, U+BAB0, U+BAB2, U+BAB8-BAB9, U+BABB,
U+BABD, U+BAC4, U+BAC8, U+BAD8-BAD9, U+BAFC, U+BB00, U+BB04, U+BB0D, U+BB0F, U+BB11,
U+BB18, U+BB1C, U+BB20, U+BB29, U+BB2B, U+BB34-BB36, U+BB38, U+BB3B-BB3E, U+BB44-BB45, U+BB47,
U+BB49, U+BB4D, U+BB4F-BB50, U+BB54, U+BB58, U+BB61, U+BB63, U+BB65, U+BB6C, U+BB88,
U+BB8C, U+BB90, U+BBA4, U+BBA8, U+BBAC, U+BBB4, U+BBB7, U+BBC0, U+BBC4, U+BBC8,
U+BBD0, U+BBD3, U+BBDC, U+BBF8-BBF9, U+BBFC, U+BBFF-BC00, U+BC02, U+BC08-BC09, U+BC0B-BC0D, U+BC0F,
U+BC11, U+BC14-BC18, U+BC1B-BC1F, U+BC24-BC25, U+BC27, U+BC29, U+BC2D, U+BC30-BC31, U+BC34, U+BC38,
U+BC40-BC41, U+BC43-BC45, U+BC49, U+BC4C-BC4D, U+BC50, U+BC5D, U+BC84-BC85, U+BC88, U+BC8B-BC8C, U+BC8E,
U+BC94-BC95, U+BC97, U+BC99-BC9A, U+BCA0-BCA1, U+BCA4, U+BCA7-BCA9, U+BCB0-BCB1, U+BCB3-BCB5, U+BCBC-BCBD, U+BCC0,
U+BCC4, U+BCCD, U+BCCF-BCD1, U+BCD5, U+BCD8, U+BCDC, U+BCF4-BCF6, U+BCF8, U+BCFC, U+BD04-BD05,
U+BD07, U+BD09, U+BD10, U+BD14, U+BD23-BD25, U+BD2C, U+BD35, U+BD40, U+BD48-BD49, U+BD4C,
U+BD50, U+BD58-BD59, U+BD64, U+BD68, U+BD79, U+BD80-BD81, U+BD84, U+BD87-BD8A, U+BD90-BD91, U+BD93,
U+BD95, U+BD99-BD9A, U+BD9C, U+BDA4, U+BDB0, U+BDB8-BDB9, U+BDC1, U+BDD4-BDD5, U+BDD8, U+BDDC,
U+BDE9, U+BDF0, U+BDF4, U+BDF8, U+BE00, U+BE03, U+BE05, U+BE0C-BE0D, U+BE10, U+BE14,
U+BE1C-BE1D, U+BE1F, U+BE44-BE45, U+BE48, U+BE4C, U+BE4E, U+BE54-BE55, U+BE57, U+BE59-BE5B, U+BE60-BE61,
U+BE64, U+BE68, U+BE6A, U+BE70-BE71, U+BE73-BE75, U+BE7B-BE7D, U+BE80, U+BE84, U+BE8C-BE8D, U+BE8F-BE91,
U+BE98-BE99, U+BEA8, U+BEAD, U+BEB4, U+BED0-BED1, U+BED4, U+BED7-BED8, U+BEE0, U+BEE3-BEE5, U+BEEC,
U+BEF5, U+BF01, U+BF08-BF09, U+BF18-BF19, U+BF1B-BF1D, U+BF40-BF41, U+BF44, U+BF48, U+BF50-BF51, U+BF55,
U+BF5C, U+BF71, U+BF94, U+BFB0, U+BFB8, U+BFC5, U+BFCC-BFCD, U+BFD0, U+BFD4, U+BFDC,
U+BFDF, U+BFE1, U+C00D, U+C03C, U+C04C, U+C051, U+C058, U+C05C, U+C060, U+C068-C069,
U+C090-C091, U+C094, U+C098, U+C0A0-C0A1, U+C0A3, U+C0A5, U+C0AC-C0AD, U+C0AF-C0B0, U+C0B3-C0B6, U+C0BC-C0BD,
U+C0BF-C0C1, U+C0C5, U+C0C8-C0C9, U+C0CC, U+C0D0, U+C0D8-C0D9, U+C0DB-C0DD, U+C0E4-C0E5, U+C0E8, U+C0EC,
U+C0F4-C0F5, U+C0F7, U+C0F9, U+C0FE, U+C100, U+C104, U+C108, U+C110, U+C115, U+C11C-C120,
U+C123-C124, U+C126-C127, U+C12C-C12D, U+C12F-C131, U+C136, U+C138-C139, U+C13C, U+C140, U+C142, U+C148-C149,
U+C14B-C14D, U+C154-C155, U+C158, U+C15C, U+C164-C165, U+C167-C169, U+C170, U+C174, U+C178, U+C185,
U+C18C-C18E, U+C190, U+C194, U+C196, U+C19C-C19D, U+C19F, U+C1A1, U+C1A5, U+C1A8-C1A9, U+C1AC,
U+C1B0, U+C1BD, U+C1C4, U+C1C8, U+C1CC, U+C1D4, U+C1D7-C1D8, U+C1E0, U+C1E4, U+C1E8,
U+C1F0-C1F1, U+C1F3, U+C1FC-C1FD, U+C200, U+C204, U+C20C-C20D, U+C20F, U+C211, U+C216, U+C218-C219,
U+C21C, U+C21F-C220, U+C228-C229, U+C22B, U+C22D, U+C22F, U+C231-C232, U+C234, U+C245, U+C248,
U+C250-C251, U+C254, U+C258, U+C25F-C260, U+C265, U+C26C-C26D, U+C270, U+C274, U+C27C-C27D, U+C27F,
U+C281, U+C288-C289, U+C28C, U+C290, U+C298-C299, U+C29B, U+C29D, U+C2A0, U+C2A4-C2A5, U+C2A8,
U+C2AC-C2AD, U+C2B4-C2B5, U+C2B7, U+C2B9, U+C2DC-C2DD, U+C2E0, U+C2E3-C2E4, U+C2EB-C2ED, U+C2EF, U+C2F1,
U+C2F6, U+C2F8-C2F9, U+C2FB-C2FC, U+C300, U+C308-C309, U+C30C-C30D, U+C313-C315, U+C318, U+C31C, U+C324-C325,
U+C328-C329, U+C330, U+C338, U+C345, U+C34C, U+C368-C369, U+C36C, U+C370, U+C372, U+C378-C379,
U+C37B-C37D, U+C384, U+C388, U+C38C, U+C395, U+C399, U+C3B5, U+C3BC, U+C3C0, U+C3D8-C3D9,
U+C3DC, U+C3DF-C3E0, U+C3E2, U+C3E8-C3E9, U+C3ED, U+C3F4-C3F5, U+C3F8, U+C3FC, U+C408, U+C410,
U+C424, U+C42C, U+C430, U+C434, U+C43C-C43D, U+C448, U+C450, U+C45D, U+C464-C465, U+C468,
U+C46C, U+C474-C475, U+C479, U+C480, U+C494, U+C49C-C49D, U+C4B8, U+C4BC, U+C4CD, U+C4D4,
U+C4D8, U+C4E9, U+C4F0-C4F1, U+C4F4, U+C4F8, U+C4FA, U+C4FF-C501, U+C50C, U+C510, U+C514,
U+C51C, U+C521, U+C528-C529, U+C52C, U+C530, U+C538-C539, U+C53B, U+C53D, U+C544-C545, U+C548-C54A,
U+C54C-C54E, U+C553-C555, U+C557-C559, U+C55C-C55E, U+C560-C561, U+C564, U+C568, U+C570-C571, U+C573-C575, U+C57B-C57D,
U+C580, U+C584, U+C587, U+C58C-C58D, U+C58F, U+C591, U+C595, U+C597-C598, U+C59C, U+C5A0,
U+C5A9, U+C5B4-C5B5, U+C5B8-C5B9, U+C5BB-C5BE, U+C5C4-C5CA, U+C5CC, U+C5CE, U+C5D0-C5D1, U+C5D4, U+C5D8,
U+C5E0-C5E1, U+C5E3, U+C5E5, U+C5EC-C5EE, U+C5F0, U+C5F4, U+C5F6-C5F7, U+C5FC-C601, U+C605-C608, U+C60C,
U+C610, U+C618-C619, U+C61B-C61C, U+C624-C625, U+C628, U+C62C-C62E, U+C630, U+C633-C635, U+C637, U+C639,
U+C63B, U+C640-C641, U+C644, U+C648, U+C650-C651, U+C653-C655, U+C65C-C65D, U+C660, U+C664, U+C66C,
U+C66F, U+C671, U+C674, U+C678-C679, U+C67C, U+C680, U+C688-C689, U+C68B, U+C68D, U+C694-C695,
U+C698, U+C69C, U+C6A4-C6A5, U+C6A7, U+C6A9, U+C6B0-C6B1, U+C6B4, U+C6B8-C6BA, U+C6C0-C6C1, U+C6C3,
U+C6C5, U+C6CC-C6CD, U+C6D0, U+C6D4, U+C6DC-C6DD, U+C6DF-C6E1, U+C6E8-C6E9, U+C6EC, U+C6F0-C6F1, U+C6F8-C6F9,
U+C6FD, U+C704-C705, U+C708, U+C70C, U+C714-C715, U+C717, U+C719, U+C720-C721, U+C724, U+C728,
U+C730-C731, U+C733, U+C735, U+C737, U+C73C-C73E, U+C740, U+C743-C744, U+C74A, U+C74C-C74F, U+C751-C758,
U+C75C, U+C760, U+C768, U+C76B, U+C774-C776, U+C778, U+C77C-C77E, U+C783-C785, U+C787-C78A, U+C78C,
U+C78E, U+C790-C791, U+C794, U+C796-C798, U+C79A, U+C7A0-C7A1, U+C7A3-C7A6, U+C7AC-C7AD, U+C7B0, U+C7B4,
U+C7BC-C7BD, U+C7BF-C7C1, U+C7C8-C7C9, U+C7CC, U+C7CE, U+C7D0, U+C7D8-C7D9, U+C7DD, U+C7E4, U+C7E8,
U+C7EC, U+C800-C801, U+C804, U+C808, U+C80A, U+C810-C811, U+C813, U+C815-C816, U+C81C-C81D, U+C820,
U+C824, U+C82C-C82D, U+C82F, U+C831, U+C838, U+C83C, U+C840, U+C848-C849, U+C84C-C84D, U+C854,
U+C870-C871, U+C874, U+C878, U+C87A, U+C880-C881, U+C883, U+C885-C887, U+C88B-C88D, U+C890, U+C894,
U+C89D, U+C89F, U+C8A1, U+C8A8, U+C8BC-C8BD, U+C8C4, U+C8C8, U+C8CC, U+C8D4-C8D5, U+C8D7,
U+C8D9, U+C8E0-C8E1, U+C8E4, U+C8E8, U+C8F5, U+C8FC-C8FD, U+C900, U+C904-C906, U+C90C-C90D, U+C90F,
U+C911, U+C918, U+C92C, U+C934-C935, U+C938, U+C950-C951, U+C954, U+C958, U+C960-C961, U+C963,
U+C965, U+C96C, U+C970, U+C974, U+C97C, U+C981, U+C988-C989, U+C98C, U+C990, U+C998-C999,
U+C99B, U+C99D, U+C9A4, U+C9A8, U+C9C0-C9C1, U+C9C4, U+C9C7-C9C8, U+C9CA, U+C9D0-C9D1, U+C9D3,
U+C9D5-C9D6, U+C9D9-C9DA, U+C9DC-C9DD, U+C9E0, U+C9E2, U+C9E4, U+C9E7, U+C9EC-C9ED, U+C9EF-C9F1, U+C9F8-C9F9,
U+C9FC, U+CA00, U+CA08-CA09, U+CA0B-CA0D, U+CA14, U+CA18, U+CA1C, U+CA29, U+CA30, U+CA4C-CA4D,
U+CA50, U+CA54, U+CA5C-CA5D, U+CA5F-CA61, U+CA68, U+CA7D, U+CA84, U+CA98, U+CABC-CABD, U+CAC0,
U+CAC4, U+CACC-CACD, U+CACF, U+CAD1, U+CAD3, U+CAD8-CAD9, U+CAE0, U+CAEC, U+CAF4, U+CB08,
U+CB10, U+CB14, U+CB18, U+CB20-CB21, U+CB2C, U+CB41, U+CB48-CB49, U+CB4C, U+CB50, U+CB58-CB59,
U+CB5D, U+CB64, U+CB78-CB79, U+CB9C, U+CBB8, U+CBD4, U+CBE4-CBE5, U+CBE7, U+CBE9, U+CC0C-CC0D,
U+CC10, U+CC14, U+CC1C-CC1D, U+CC21-CC22, U+CC27-CC29, U+CC2C, U+CC2E, U+CC30, U+CC38-CC39, U+CC3B-CC3E,
U+CC44-CC45, U+CC48, U+CC4C, U+CC54-CC55, U+CC57-CC59, U+CC60, U+CC64, U+CC66, U+CC68, U+CC70,
U+CC75, U+CC98-CC99, U+CC9C, U+CCA0, U+CCA8-CCA9, U+CCAB-CCAD, U+CCB4-CCB5, U+CCB8, U+CCBC, U+CCC4-CCC5,
U+CCC7, U+CCC9, U+CCD0, U+CCD4, U+CCE1, U+CCE4, U+CCEC, U+CCF0, U+CD01, U+CD08-CD09,
U+CD0C, U+CD10, U+CD18-CD19, U+CD1B, U+CD1D, U+CD24, U+CD28, U+CD2C, U+CD39, U+CD5C-CD5D,
U+CD60, U+CD64, U+CD6C-CD6D, U+CD6F, U+CD71, U+CD78, U+CD88, U+CD94-CD95, U+CD98, U+CD9C,
U+CDA4-CDA5, U+CDA7, U+CDA9, U+CDB0, U+CDC4, U+CDCC-CDCD, U+CDD0, U+CDDD, U+CDDF, U+CDE8,
U+CDEC, U+CDF0, U+CDF8-CDF9, U+CDFB, U+CDFD, U+CE04, U+CE08, U+CE0C, U+CE14-CE15, U+CE19,
U+CE20-CE21, U+CE24, U+CE28, U+CE30-CE31, U+CE33, U+CE35, U+CE58-CE59, U+CE5C, U+CE5F-CE61, U+CE68-CE69,
U+CE6B, U+CE6D, U+CE74-CE75, U+CE78, U+CE7C, U+CE84-CE85, U+CE87, U+CE89, U+CE8C, U+CE90-CE91,
U+CE94, U+CE98, U+CEA0-CEA1, U+CEA3-CEA5, U+CEAC-CEAE, U+CEB4, U+CEC1, U+CEC4, U+CEE4-CEE5, U+CEE8,
U+CEEB-CEEC, U+CEF4-CEF5, U+CEF7-CEF9, U+CF00-CF01, U+CF04, U+CF08, U+CF10-CF11, U+CF13, U+CF15, U+CF18,
U+CF1C, U+CF20, U+CF24, U+CF2C-CF2D, U+CF2F-CF31, U+CF38, U+CF54-CF55, U+CF58, U+CF5C, U+CF64-CF65,
U+CF67, U+CF69, U+CF70-CF71, U+CF74, U+CF78, U+CF80, U+CF85, U+CF8C, U+CFA1, U+CFA8,
U+CFB0, U+CFC4, U+CFDC, U+CFE0-CFE1, U+CFE4, U+CFE8, U+CFF0-CFF1, U+CFF3, U+CFF5, U+CFFC-CFFD,
U+D000, U+D004, U+D011, U+D018, U+D02D, U+D034-D035, U+D038, U+D03C, U+D044-D045, U+D047,
U+D049, U+D050, U+D054, U+D058, U+D060, U+D06C-D06E, U+D070, U+D074, U+D07C-D07D, U+D07F,
U+D081, U+D084, U+D0A4-D0A5, U+D0A8, U+D0AC, U+D0B4-D0B5, U+D0B7, U+D0B9, U+D0C0-D0C1, U+D0C4,
U+D0C8-D0C9, U+D0D0-D0D1, U+D0D3-D0D5, U+D0DC-D0DD, U+D0E0, U+D0E4, U+D0EC-D0ED, U+D0EF-D0F1, U+D0F8, U+D10D,
U+D130-D131, U+D134, U+D138, U+D13A, U+D140-D141, U+D143-D145, U+D14C-D14D, U+D150, U+D154, U+D15C-D15D,
U+D15F, U+D161, U+D168, U+D16C, U+D17C, U+D184, U+D188, U+D1A0-D1A1, U+D1A4, U+D1A8,
U+D1B0-D1B1, U+D1B3, U+D1B5, U+D1BA, U+D1BC, U+D1C0, U+D1D8, U+D1F4, U+D1F8, U+D207,
U+D209, U+D210, U+D22C-D22D, U+D230, U+D234, U+D23C-D23D, U+D23F, U+D241, U+D248, U+D25C,
U+D264, U+D277, U+D280-D281, U+D284, U+D288, U+D290-D291, U+D295, U+D29C, U+D2A0, U+D2A4,
U+D2AC, U+D2B1, U+D2B8-D2B9, U+D2BC, U+D2BF-D2C0, U+D2C2, U+D2C8-D2C9, U+D2CB, U+D2D4, U+D2D8,
U+D2DC, U+D2E4-D2E5, U+D2F0-D2F1, U+D2F4, U+D2F8, U+D300-D301, U+D303, U+D305, U+D30C-D30E, U+D310,
U+D314, U+D316, U+D31C-D31D, U+D31F-D321, U+D325, U+D328-D329, U+D32C, U+D330, U+D338-D339, U+D33B-D33D,
U+D344-D345, U+D37C-D37D, U+D380, U+D384, U+D38C-D38D, U+D38F-D391, U+D398-D399, U+D39C, U+D3A0, U+D3A8-D3A9,
U+D3AB, U+D3AD, U+D3B4, U+D3B8, U+D3BC, U+D3C4-D3C5, U+D3C8-D3C9, U+D3D0, U+D3D8, U+D3E1,
U+D3E3, U+D3EC-D3ED, U+D3F0, U+D3F4, U+D3FC-D3FD, U+D3FF, U+D401, U+D408, U+D41D, U+D440,
U+D444, U+D45C, U+D460, U+D464, U+D46D, U+D46F, U+D478-D479, U+D47C, U+D47F-D480, U+D482,
U+D488-D489, U+D48B, U+D48D, U+D494, U+D4A9, U+D4B0, U+D4CC, U+D4D0, U+D4D4, U+D4DC,
U+D4DF, U+D4E8, U+D4EC, U+D4F0, U+D4F8, U+D4FB, U+D4FD, U+D504, U+D508, U+D50C,
U+D514-D515, U+D517, U+D53C-D53D, U+D540, U+D544, U+D54C-D54D, U+D54F, U+D551, U+D558-D559, U+D55C,
U+D560, U+D565, U+D568-D569, U+D56B, U+D56D, U+D571, U+D573-D575, U+D578, U+D57C, U+D584-D585,
U+D587-D589, U+D58F-D590, U+D59D, U+D5A5, U+D5C8-D5C9, U+D5CC, U+D5D0, U+D5D2, U+D5D8-D5D9, U+D5DB,
U+D5DD, U+D5E4-D5E5, U+D5E8, U+D5EC, U+D5F3-D5F5, U+D5F7, U+D5F9, U+D5FF-D601, U+D604, U+D608,
U+D610-D611, U+D613-D615, U+D61C, U+D620, U+D624, U+D62D, U+D638-D639, U+D63C, U+D640, U+D645,
U+D648-D649, U+D64B, U+D64D, U+D651, U+D654-D655, U+D658, U+D65C, U+D664, U+D667, U+D669,
U+D670-D671, U+D674, U+D683, U+D685, U+D68C-D68D, U+D690, U+D694, U+D69D, U+D69F, U+D6A1,
U+D6A8, U+D6AC, U+D6B0, U+D6B9, U+D6BB, U+D6BD, U+D6C3-D6C5, U+D6C8, U+D6CC, U+D6D1,
U+D6D4-D6D5, U+D6D7, U+D6D9, U+D6E0, U+D6E4, U+D6E8, U+D6F0, U+D6F5, U+D6FC-D6FD, U+D700,
U+D704, U+D711, U+D718-D719, U+D71C, U+D720, U+D728-D729, U+D72B, U+D72D, U+D734-D735, U+D738,
U+D73C, U+D744, U+D747, U+D749, U+D750-D751, U+D754, U+D756-D759, U+D760-D761, U+D763, U+D765,
U+D769, U+D76C, U+D770, U+D774, U+D77C-D77D, U+D781, U+D788-D789, U+D78C, U+D790, U+D798-D799,
U+D79B, U+D79D, U+FF01-FF5E, U+FFE0-FFE3, U+FFE5-FFE6;
}
/* NanumSquareB.woff2 sha256: c6baca42985691111d4faa6b056277e11f65dd6ea4ed5693b1ea2cd0b7b43f25 */
@font-face {
font-family: 'NanumSquare';
src: url('/assets/fonts/NanumSquareB.woff2') format('woff2');
font-weight: 700;
font-style: normal;
font-display: swap;
unicode-range:
U+20-7E, U+A1, U+A4, U+A7-AB, U+B0-B4, U+B6, U+B8-BF, U+C6, U+D0, U+D7-D8,
U+DE-DF, U+E6, U+F0, U+F7-F8, U+FE, U+111, U+126-127, U+131-133, U+138, U+13F-142,
U+149-14B, U+152-153, U+166-167, U+2C7, U+2D8-2DD, U+391-3A1, U+3A3-3A9, U+3B1-3C1, U+3C3-3C9, U+401,
U+410-44F, U+451, U+2010, U+2013-2014, U+2016, U+2018-2019, U+201C-201D, U+2020-2021, U+2025-2026, U+2030,
U+2032-2033, U+203B, U+2074, U+207F, U+2081-2084, U+20A9, U+2103, U+2109, U+2113, U+2116,
U+2121-2122, U+2126, U+212B, U+2153-2154, U+215B-215E, U+2160-2169, U+2170-2179, U+2190-2199, U+21D2, U+21D4,
U+2200, U+2202-2203, U+2207-2208, U+220B, U+220F, U+2211, U+221A, U+221D-221E, U+2220, U+2227-222C,
U+222E, U+2234-2236, U+223D, U+2252, U+2260-2261, U+2264-2265, U+2282-2283, U+2286-2287, U+22A5, U+22EF,
U+2312, U+2460-246E, U+2474-2482, U+249C-24B5, U+24D0-24E9, U+2500-2503, U+250C-254B, U+2592, U+25A0-25A1, U+25A3-25A9,
U+25B2-25B3, U+25B6-25B7, U+25BC-25BD, U+25C0-25C1, U+25C6-25C9, U+25CB, U+25CE-25D1, U+2605-2606, U+260E-260F, U+261C,
U+261E, U+2640, U+2642, U+2660-2661, U+2663-2665, U+2667-266A, U+266C-266D, U+3000-3003, U+3008-3011, U+3013-3015,
U+3041-3093, U+30A1-30F6, U+30FB, U+3131-318E, U+3200-321C, U+3260-327B, U+327F, U+3380-3384, U+3388-33CA, U+33CF-33D0,
U+33D3, U+33D6, U+33D8, U+33DB-33DD, U+AC00-AC01, U+AC04, U+AC07-AC0A, U+AC10-AC17, U+AC19-AC1D, U+AC20,
U+AC24, U+AC2C-AC2D, U+AC2F-AC31, U+AC38-AC39, U+AC3C, U+AC40, U+AC4B, U+AC4D, U+AC54, U+AC58,
U+AC5C, U+AC70-AC71, U+AC74, U+AC77-AC78, U+AC7A, U+AC80-AC81, U+AC83-AC86, U+AC89-AC8C, U+AC90, U+AC94,
U+AC9C-AC9D, U+AC9F-ACA1, U+ACA8-ACAA, U+ACAC, U+ACAF-ACB0, U+ACB8-ACB9, U+ACBB-ACBD, U+ACC1, U+ACC4, U+ACC8,
U+ACCC, U+ACD5, U+ACD7, U+ACE0-ACE1, U+ACE4, U+ACE7-ACE8, U+ACEA, U+ACEC, U+ACEF-ACF1, U+ACF3,
U+ACF5-ACF6, U+ACFC-ACFD, U+AD00, U+AD04, U+AD06, U+AD0C-AD0D, U+AD0F, U+AD11, U+AD18, U+AD1C,
U+AD1E, U+AD20, U+AD29, U+AD2C-AD2D, U+AD34-AD35, U+AD38, U+AD3C, U+AD44-AD45, U+AD47, U+AD49,
U+AD50, U+AD54, U+AD58, U+AD61, U+AD63, U+AD65, U+AD6C-AD6D, U+AD70, U+AD73-AD76, U+AD7B-AD7D,
U+AD7F, U+AD81-AD82, U+AD88-AD89, U+AD8C, U+AD90, U+AD9C-AD9D, U+ADA4, U+ADB4, U+ADB7, U+ADC0-ADC1,
U+ADC4, U+ADC8, U+ADD0-ADD1, U+ADD3, U+ADDC, U+ADE0, U+ADE4, U+ADF8-ADF9, U+ADFC, U+ADFF-AE01,
U+AE08-AE09, U+AE0B, U+AE0D, U+AE14, U+AE1C, U+AE30-AE31, U+AE34, U+AE37-AE38, U+AE3A, U+AE40-AE41,
U+AE43, U+AE45-AE46, U+AE4A, U+AE4C-AE4E, U+AE50, U+AE54, U+AE56, U+AE5C-AE5D, U+AE5F-AE61, U+AE65,
U+AE68-AE69, U+AE6C, U+AE70, U+AE78-AE79, U+AE7B-AE7D, U+AE84-AE86, U+AE8C-AE8D, U+AEA0, U+AEBC-AEBE, U+AEC0,
U+AEC4, U+AECC-AECD, U+AECF-AED1, U+AED8-AED9, U+AEDC, U+AEE8, U+AEEB, U+AEED, U+AEF4, U+AEF8,
U+AEFC, U+AF07-AF08, U+AF0D, U+AF10, U+AF2C-AF2D, U+AF30, U+AF32, U+AF34, U+AF3C-AF3D, U+AF3F,
U+AF41-AF43, U+AF48-AF49, U+AF50, U+AF5C-AF5D, U+AF64-AF65, U+AF79, U+AF80, U+AF84, U+AF88, U+AF90-AF91,
U+AF95, U+AF9C, U+AFA4, U+AFB8-AFBA, U+AFBC, U+AFC0, U+AFC7-AFC9, U+AFCB, U+AFCD-AFCE, U+AFD4,
U+AFDC, U+AFE8-AFE9, U+AFF0-AFF1, U+AFF4, U+AFF8-AFF9, U+AFFF-B001, U+B004, U+B00C, U+B010, U+B014,
U+B01C-B01D, U+B028, U+B044-B045, U+B048, U+B04A, U+B04C, U+B04E, U+B053-B055, U+B057, U+B059,
U+B05D, U+B068, U+B07C-B07D, U+B080, U+B084, U+B08C-B08D, U+B08F, U+B091, U+B098-B09A, U+B09C,
U+B09F-B0A2, U+B0A8-B0A9, U+B0AB-B0AF, U+B0B1, U+B0B3-B0B5, U+B0B8, U+B0BC, U+B0C4-B0C5, U+B0C7-B0C9, U+B0D0-B0D1,
U+B0D4, U+B0D8, U+B0E0, U+B0E5, U+B108-B109, U+B10B-B10C, U+B110, U+B112-B113, U+B118-B119, U+B11B-B11D,
U+B123-B125, U+B128, U+B12C, U+B134-B135, U+B137-B139, U+B140-B141, U+B144, U+B148, U+B150-B151, U+B154-B155,
U+B158, U+B15C, U+B160, U+B178-B179, U+B17C, U+B180, U+B182, U+B188-B189, U+B18B, U+B18D,
U+B192-B194, U+B198, U+B19C, U+B1A8, U+B1CC, U+B1D0, U+B1D4, U+B1DC-B1DD, U+B1DF, U+B1E8-B1E9,
U+B1EC, U+B1F0, U+B1F9, U+B1FB, U+B1FD, U+B204-B205, U+B208, U+B20B-B20C, U+B214-B215, U+B217,
U+B219, U+B220, U+B234, U+B23C, U+B24C-B24D, U+B251, U+B258, U+B25C, U+B260, U+B268-B269,
U+B274-B275, U+B27C, U+B284-B285, U+B289, U+B290-B291, U+B294, U+B298-B29A, U+B2A0-B2A1, U+B2A3, U+B2A5-B2A6,
U+B2AA, U+B2AC, U+B2B0, U+B2B4, U+B2C8-B2C9, U+B2CC, U+B2D0, U+B2D2, U+B2D8-B2D9, U+B2DB,
U+B2DD, U+B2E2, U+B2E4-B2E6, U+B2E8, U+B2EB-B2EF, U+B2F3-B2F5, U+B2F7-B2FB, U+B2FF-B301, U+B304, U+B308,
U+B310-B311, U+B313-B315, U+B31C, U+B324, U+B32C, U+B331, U+B354-B356, U+B358, U+B35B-B35C, U+B35E-B35F,
U+B364-B365, U+B367, U+B369, U+B36B, U+B36E, U+B370-B371, U+B374, U+B378, U+B380-B381, U+B383-B385,
U+B38C, U+B390, U+B394, U+B3A0-B3A1, U+B3A8, U+B3AC, U+B3C4-B3C5, U+B3C8, U+B3CB-B3CC, U+B3CE,
U+B3D0, U+B3D4-B3D5, U+B3D7, U+B3D9, U+B3DB, U+B3DD, U+B3E0, U+B3E4, U+B3E8, U+B3FC,
U+B400, U+B40F-B411, U+B418, U+B41C, U+B420, U+B428-B429, U+B42B-B42D, U+B434, U+B450-B451, U+B454,
U+B458, U+B460-B461, U+B463, U+B465, U+B46C, U+B480, U+B488, U+B49D, U+B4A4, U+B4A8,
U+B4AC, U+B4B5, U+B4B7, U+B4B9, U+B4C0, U+B4C4, U+B4C8, U+B4D0, U+B4D5, U+B4DC-B4DD,
U+B4E0, U+B4E3-B4E4, U+B4E6, U+B4EC-B4ED, U+B4EF, U+B4F1, U+B4F8, U+B514-B515, U+B518, U+B51B-B51C,
U+B524-B525, U+B527-B52A, U+B530-B531, U+B534, U+B538, U+B540-B541, U+B543-B545, U+B54B-B54D, U+B550, U+B554,
U+B55C-B55D, U+B55F-B561, U+B568, U+B584, U+B599, U+B5A0-B5A1, U+B5A4, U+B5A8, U+B5AA-B5AB, U+B5B0-B5B1,
U+B5B3-B5B5, U+B5BB-B5BD, U+B5C0, U+B5C4, U+B5CC-B5CD, U+B5CF-B5D1, U+B5D8, U+B5EC, U+B610-B611, U+B614,
U+B618, U+B620, U+B625, U+B62C, U+B634, U+B648, U+B65D, U+B664, U+B668, U+B69C-B69D,
U+B6A0, U+B6A4, U+B6AB-B6AC, U+B6B1, U+B6D4, U+B6F0, U+B6F4, U+B6F8, U+B700-B701, U+B705,
U+B71B, U+B728-B729, U+B72C, U+B72F-B730, U+B738-B739, U+B73B, U+B73D, U+B744, U+B748, U+B74C,
U+B754-B755, U+B760-B761, U+B764, U+B768, U+B770-B771, U+B773, U+B775, U+B77C-B77D, U+B780, U+B784,
U+B78C-B78D, U+B78F-B792, U+B796-B799, U+B79C, U+B7A0, U+B7A8-B7A9, U+B7AB-B7AD, U+B7B4-B7B5, U+B7B8, U+B7BC,
U+B7C7, U+B7C9, U+B7EC-B7ED, U+B7F0, U+B7F4, U+B7FC-B7FD, U+B7FF-B801, U+B807-B809, U+B80C, U+B810,
U+B818-B819, U+B81B, U+B81D, U+B824-B825, U+B828, U+B82C, U+B834-B835, U+B837-B839, U+B840, U+B844,
U+B851, U+B853, U+B85C-B85D, U+B860, U+B864, U+B86C-B86D, U+B86F, U+B871, U+B878-B879, U+B87C,
U+B880, U+B88D, U+B894, U+B8A8, U+B8B0, U+B8B4, U+B8B8, U+B8C0-B8C1, U+B8C3, U+B8C5,
U+B8CC, U+B8D0, U+B8D4, U+B8DD, U+B8DF, U+B8E1, U+B8E8-B8E9, U+B8EC, U+B8F0, U+B8F8-B8F9,
U+B8FB, U+B8FD, U+B904, U+B918, U+B920, U+B93C-B93D, U+B940, U+B944, U+B94C, U+B94F,
U+B951, U+B958-B959, U+B95C, U+B960, U+B968-B969, U+B96B, U+B96D, U+B974-B975, U+B978, U+B97C,
U+B984-B985, U+B987, U+B989-B98A, U+B98D-B98E, U+B9AC-B9AD, U+B9B0, U+B9B4, U+B9BC-B9BD, U+B9BF, U+B9C1,
U+B9C8-B9C9, U+B9CC, U+B9CE-B9D2, U+B9D8-B9D9, U+B9DB, U+B9DD-B9DE, U+B9E1, U+B9E3-B9E5, U+B9E8, U+B9EC,
U+B9F4-B9F5, U+B9F7-B9FA, U+BA00-BA01, U+BA04, U+BA08, U+BA15, U+BA38-BA39, U+BA3C, U+BA40, U+BA42,
U+BA48-BA49, U+BA4B, U+BA4D-BA4E, U+BA53-BA55, U+BA58, U+BA5C, U+BA64-BA65, U+BA67-BA69, U+BA70-BA71, U+BA74,
U+BA78, U+BA83-BA85, U+BA87, U+BA8C, U+BAA8-BAA9, U+BAAB-BAAC, U+BAB0, U+BAB2, U+BAB8-BAB9, U+BABB,
U+BABD, U+BAC4, U+BAC8, U+BAD8-BAD9, U+BAFC, U+BB00, U+BB04, U+BB0D, U+BB0F, U+BB11,
U+BB18, U+BB1C, U+BB20, U+BB29, U+BB2B, U+BB34-BB36, U+BB38, U+BB3B-BB3E, U+BB44-BB45, U+BB47,
U+BB49, U+BB4D, U+BB4F-BB50, U+BB54, U+BB58, U+BB61, U+BB63, U+BB65, U+BB6C, U+BB88,
U+BB8C, U+BB90, U+BBA4, U+BBA8, U+BBAC, U+BBB4, U+BBB7, U+BBC0, U+BBC4, U+BBC8,
U+BBD0, U+BBD3, U+BBDC, U+BBF8-BBF9, U+BBFC, U+BBFF-BC00, U+BC02, U+BC08-BC09, U+BC0B-BC0D, U+BC0F,
U+BC11, U+BC14-BC18, U+BC1B-BC1F, U+BC24-BC25, U+BC27, U+BC29, U+BC2D, U+BC30-BC31, U+BC34, U+BC38,
U+BC40-BC41, U+BC43-BC45, U+BC49, U+BC4C-BC4D, U+BC50, U+BC5D, U+BC84-BC85, U+BC88, U+BC8B-BC8C, U+BC8E,
U+BC94-BC95, U+BC97, U+BC99-BC9A, U+BCA0-BCA1, U+BCA4, U+BCA7-BCA9, U+BCB0-BCB1, U+BCB3-BCB5, U+BCBC-BCBD, U+BCC0,
U+BCC4, U+BCCD, U+BCCF-BCD1, U+BCD5, U+BCD8, U+BCDC, U+BCF4-BCF6, U+BCF8, U+BCFC, U+BD04-BD05,
U+BD07, U+BD09, U+BD10, U+BD14, U+BD23-BD25, U+BD2C, U+BD35, U+BD40, U+BD48-BD49, U+BD4C,
U+BD50, U+BD58-BD59, U+BD64, U+BD68, U+BD79, U+BD80-BD81, U+BD84, U+BD87-BD8A, U+BD90-BD91, U+BD93,
U+BD95, U+BD99-BD9A, U+BD9C, U+BDA4, U+BDB0, U+BDB8-BDB9, U+BDC1, U+BDD4-BDD5, U+BDD8, U+BDDC,
U+BDE9, U+BDF0, U+BDF4, U+BDF8, U+BE00, U+BE03, U+BE05, U+BE0C-BE0D, U+BE10, U+BE14,
U+BE1C-BE1D, U+BE1F, U+BE44-BE45, U+BE48, U+BE4C, U+BE4E, U+BE54-BE55, U+BE57, U+BE59-BE5B, U+BE60-BE61,
U+BE64, U+BE68, U+BE6A, U+BE70-BE71, U+BE73-BE75, U+BE7B-BE7D, U+BE80, U+BE84, U+BE8C-BE8D, U+BE8F-BE91,
U+BE98-BE99, U+BEA8, U+BEAD, U+BEB4, U+BED0-BED1, U+BED4, U+BED7-BED8, U+BEE0, U+BEE3-BEE5, U+BEEC,
U+BEF5, U+BF01, U+BF08-BF09, U+BF18-BF19, U+BF1B-BF1D, U+BF40-BF41, U+BF44, U+BF48, U+BF50-BF51, U+BF55,
U+BF5C, U+BF71, U+BF94, U+BFB0, U+BFB8, U+BFC5, U+BFCC-BFCD, U+BFD0, U+BFD4, U+BFDC,
U+BFDF, U+BFE1, U+C00D, U+C03C, U+C04C, U+C051, U+C058, U+C05C, U+C060, U+C068-C069,
U+C090-C091, U+C094, U+C098, U+C0A0-C0A1, U+C0A3, U+C0A5, U+C0AC-C0AD, U+C0AF-C0B0, U+C0B3-C0B6, U+C0BC-C0BD,
U+C0BF-C0C1, U+C0C5, U+C0C8-C0C9, U+C0CC, U+C0D0, U+C0D8-C0D9, U+C0DB-C0DD, U+C0E4-C0E5, U+C0E8, U+C0EC,
U+C0F4-C0F5, U+C0F7, U+C0F9, U+C0FE, U+C100, U+C104, U+C108, U+C110, U+C115, U+C11C-C120,
U+C123-C124, U+C126-C127, U+C12C-C12D, U+C12F-C131, U+C136, U+C138-C139, U+C13C, U+C140, U+C142, U+C148-C149,
U+C14B-C14D, U+C154-C155, U+C158, U+C15C, U+C164-C165, U+C167-C169, U+C170, U+C174, U+C178, U+C185,
U+C18C-C18E, U+C190, U+C194, U+C196, U+C19C-C19D, U+C19F, U+C1A1, U+C1A5, U+C1A8-C1A9, U+C1AC,
U+C1B0, U+C1BD, U+C1C4, U+C1C8, U+C1CC, U+C1D4, U+C1D7-C1D8, U+C1E0, U+C1E4, U+C1E8,
U+C1F0-C1F1, U+C1F3, U+C1FC-C1FD, U+C200, U+C204, U+C20C-C20D, U+C20F, U+C211, U+C216, U+C218-C219,
U+C21C, U+C21F-C220, U+C228-C229, U+C22B, U+C22D, U+C22F, U+C231-C232, U+C234, U+C245, U+C248,
U+C250-C251, U+C254, U+C258, U+C25F-C260, U+C265, U+C26C-C26D, U+C270, U+C274, U+C27C-C27D, U+C27F,
U+C281, U+C288-C289, U+C28C, U+C290, U+C298-C299, U+C29B, U+C29D, U+C2A0, U+C2A4-C2A5, U+C2A8,
U+C2AC-C2AD, U+C2B4-C2B5, U+C2B7, U+C2B9, U+C2DC-C2DD, U+C2E0, U+C2E3-C2E4, U+C2EB-C2ED, U+C2EF, U+C2F1,
U+C2F6, U+C2F8-C2F9, U+C2FB-C2FC, U+C300, U+C308-C309, U+C30C-C30D, U+C313-C315, U+C318, U+C31C, U+C324-C325,
U+C328-C329, U+C330, U+C338, U+C345, U+C34C, U+C368-C369, U+C36C, U+C370, U+C372, U+C378-C379,
U+C37B-C37D, U+C384, U+C388, U+C38C, U+C395, U+C399, U+C3B5, U+C3BC, U+C3C0, U+C3D8-C3D9,
U+C3DC, U+C3DF-C3E0, U+C3E2, U+C3E8-C3E9, U+C3ED, U+C3F4-C3F5, U+C3F8, U+C3FC, U+C408, U+C410,
U+C424, U+C42C, U+C430, U+C434, U+C43C-C43D, U+C448, U+C450, U+C45D, U+C464-C465, U+C468,
U+C46C, U+C474-C475, U+C479, U+C480, U+C494, U+C49C-C49D, U+C4B8, U+C4BC, U+C4CD, U+C4D4,
U+C4D8, U+C4E9, U+C4F0-C4F1, U+C4F4, U+C4F8, U+C4FA, U+C4FF-C501, U+C50C, U+C510, U+C514,
U+C51C, U+C521, U+C528-C529, U+C52C, U+C530, U+C538-C539, U+C53B, U+C53D, U+C544-C545, U+C548-C54A,
U+C54C-C54E, U+C553-C555, U+C557-C559, U+C55C-C55E, U+C560-C561, U+C564, U+C568, U+C570-C571, U+C573-C575, U+C57B-C57D,
U+C580, U+C584, U+C587, U+C58C-C58D, U+C58F, U+C591, U+C595, U+C597-C598, U+C59C, U+C5A0,
U+C5A9, U+C5B4-C5B5, U+C5B8-C5B9, U+C5BB-C5BE, U+C5C4-C5CA, U+C5CC, U+C5CE, U+C5D0-C5D1, U+C5D4, U+C5D8,
U+C5E0-C5E1, U+C5E3, U+C5E5, U+C5EC-C5EE, U+C5F0, U+C5F4, U+C5F6-C5F7, U+C5FC-C601, U+C605-C608, U+C60C,
U+C610, U+C618-C619, U+C61B-C61C, U+C624-C625, U+C628, U+C62C-C62E, U+C630, U+C633-C635, U+C637, U+C639,
U+C63B, U+C640-C641, U+C644, U+C648, U+C650-C651, U+C653-C655, U+C65C-C65D, U+C660, U+C664, U+C66C,
U+C66F, U+C671, U+C674, U+C678-C679, U+C67C, U+C680, U+C688-C689, U+C68B, U+C68D, U+C694-C695,
U+C698, U+C69C, U+C6A4-C6A5, U+C6A7, U+C6A9, U+C6B0-C6B1, U+C6B4, U+C6B8-C6BA, U+C6C0-C6C1, U+C6C3,
U+C6C5, U+C6CC-C6CD, U+C6D0, U+C6D4, U+C6DC-C6DD, U+C6DF-C6E1, U+C6E8-C6E9, U+C6EC, U+C6F0-C6F1, U+C6F8-C6F9,
U+C6FD, U+C704-C705, U+C708, U+C70C, U+C714-C715, U+C717, U+C719, U+C720-C721, U+C724, U+C728,
U+C730-C731, U+C733, U+C735, U+C737, U+C73C-C73E, U+C740, U+C743-C744, U+C74A, U+C74C-C74F, U+C751-C758,
U+C75C, U+C760, U+C768, U+C76B, U+C774-C776, U+C778, U+C77C-C77E, U+C783-C785, U+C787-C78A, U+C78C,
U+C78E, U+C790-C791, U+C794, U+C796-C798, U+C79A, U+C7A0-C7A1, U+C7A3-C7A6, U+C7AC-C7AD, U+C7B0, U+C7B4,
U+C7BC-C7BD, U+C7BF-C7C1, U+C7C8-C7C9, U+C7CC, U+C7CE, U+C7D0, U+C7D8-C7D9, U+C7DD, U+C7E4, U+C7E8,
U+C7EC, U+C800-C801, U+C804, U+C808, U+C80A, U+C810-C811, U+C813, U+C815-C816, U+C81C-C81D, U+C820,
U+C824, U+C82C-C82D, U+C82F, U+C831, U+C838, U+C83C, U+C840, U+C848-C849, U+C84C-C84D, U+C854,
U+C870-C871, U+C874, U+C878, U+C87A, U+C880-C881, U+C883, U+C885-C887, U+C88B-C88D, U+C890, U+C894,
U+C89D, U+C89F, U+C8A1, U+C8A8, U+C8BC-C8BD, U+C8C4, U+C8C8, U+C8CC, U+C8D4-C8D5, U+C8D7,
U+C8D9, U+C8E0-C8E1, U+C8E4, U+C8E8, U+C8F5, U+C8FC-C8FD, U+C900, U+C904-C906, U+C90C-C90D, U+C90F,
U+C911, U+C918, U+C92C, U+C934-C935, U+C938, U+C950-C951, U+C954, U+C958, U+C960-C961, U+C963,
U+C965, U+C96C, U+C970, U+C974, U+C97C, U+C981, U+C988-C989, U+C98C, U+C990, U+C998-C999,
U+C99B, U+C99D, U+C9A4, U+C9A8, U+C9C0-C9C1, U+C9C4, U+C9C7-C9C8, U+C9CA, U+C9D0-C9D1, U+C9D3,
U+C9D5-C9D6, U+C9D9-C9DA, U+C9DC-C9DD, U+C9E0, U+C9E2, U+C9E4, U+C9E7, U+C9EC-C9ED, U+C9EF-C9F1, U+C9F8-C9F9,
U+C9FC, U+CA00, U+CA08-CA09, U+CA0B-CA0D, U+CA14, U+CA18, U+CA1C, U+CA29, U+CA30, U+CA4C-CA4D,
U+CA50, U+CA54, U+CA5C-CA5D, U+CA5F-CA61, U+CA68, U+CA7D, U+CA84, U+CA98, U+CABC-CABD, U+CAC0,
U+CAC4, U+CACC-CACD, U+CACF, U+CAD1, U+CAD3, U+CAD8-CAD9, U+CAE0, U+CAEC, U+CAF4, U+CB08,
U+CB10, U+CB14, U+CB18, U+CB20-CB21, U+CB2C, U+CB41, U+CB48-CB49, U+CB4C, U+CB50, U+CB58-CB59,
U+CB5D, U+CB64, U+CB78-CB79, U+CB9C, U+CBB8, U+CBD4, U+CBE4-CBE5, U+CBE7, U+CBE9, U+CC0C-CC0D,
U+CC10, U+CC14, U+CC1C-CC1D, U+CC21-CC22, U+CC27-CC29, U+CC2C, U+CC2E, U+CC30, U+CC38-CC39, U+CC3B-CC3E,
U+CC44-CC45, U+CC48, U+CC4C, U+CC54-CC55, U+CC57-CC59, U+CC60, U+CC64, U+CC66, U+CC68, U+CC70,
U+CC75, U+CC98-CC99, U+CC9C, U+CCA0, U+CCA8-CCA9, U+CCAB-CCAD, U+CCB4-CCB5, U+CCB8, U+CCBC, U+CCC4-CCC5,
U+CCC7, U+CCC9, U+CCD0, U+CCD4, U+CCE1, U+CCE4, U+CCEC, U+CCF0, U+CD01, U+CD08-CD09,
U+CD0C, U+CD10, U+CD18-CD19, U+CD1B, U+CD1D, U+CD24, U+CD28, U+CD2C, U+CD39, U+CD5C-CD5D,
U+CD60, U+CD64, U+CD6C-CD6D, U+CD6F, U+CD71, U+CD78, U+CD88, U+CD94-CD95, U+CD98, U+CD9C,
U+CDA4-CDA5, U+CDA7, U+CDA9, U+CDB0, U+CDC4, U+CDCC-CDCD, U+CDD0, U+CDDD, U+CDDF, U+CDE8,
U+CDEC, U+CDF0, U+CDF8-CDF9, U+CDFB, U+CDFD, U+CE04, U+CE08, U+CE0C, U+CE14-CE15, U+CE19,
U+CE20-CE21, U+CE24, U+CE28, U+CE30-CE31, U+CE33, U+CE35, U+CE58-CE59, U+CE5C, U+CE5F-CE61, U+CE68-CE69,
U+CE6B, U+CE6D, U+CE74-CE75, U+CE78, U+CE7C, U+CE84-CE85, U+CE87, U+CE89, U+CE8C, U+CE90-CE91,
U+CE94, U+CE98, U+CEA0-CEA1, U+CEA3-CEA5, U+CEAC-CEAE, U+CEB4, U+CEC1, U+CEC4, U+CEE4-CEE5, U+CEE8,
U+CEEB-CEEC, U+CEF4-CEF5, U+CEF7-CEF9, U+CF00-CF01, U+CF04, U+CF08, U+CF10-CF11, U+CF13, U+CF15, U+CF18,
U+CF1C, U+CF20, U+CF24, U+CF2C-CF2D, U+CF2F-CF31, U+CF38, U+CF54-CF55, U+CF58, U+CF5C, U+CF64-CF65,
U+CF67, U+CF69, U+CF70-CF71, U+CF74, U+CF78, U+CF80, U+CF85, U+CF8C, U+CFA1, U+CFA8,
U+CFB0, U+CFC4, U+CFDC, U+CFE0-CFE1, U+CFE4, U+CFE8, U+CFF0-CFF1, U+CFF3, U+CFF5, U+CFFC-CFFD,
U+D000, U+D004, U+D011, U+D018, U+D02D, U+D034-D035, U+D038, U+D03C, U+D044-D045, U+D047,
U+D049, U+D050, U+D054, U+D058, U+D060, U+D06C-D06E, U+D070, U+D074, U+D07C-D07D, U+D07F,
U+D081, U+D084, U+D0A4-D0A5, U+D0A8, U+D0AC, U+D0B4-D0B5, U+D0B7, U+D0B9, U+D0C0-D0C1, U+D0C4,
U+D0C8-D0C9, U+D0D0-D0D1, U+D0D3-D0D5, U+D0DC-D0DD, U+D0E0, U+D0E4, U+D0EC-D0ED, U+D0EF-D0F1, U+D0F8, U+D10D,
U+D130-D131, U+D134, U+D138, U+D13A, U+D140-D141, U+D143-D145, U+D14C-D14D, U+D150, U+D154, U+D15C-D15D,
U+D15F, U+D161, U+D168, U+D16C, U+D17C, U+D184, U+D188, U+D1A0-D1A1, U+D1A4, U+D1A8,
U+D1B0-D1B1, U+D1B3, U+D1B5, U+D1BA, U+D1BC, U+D1C0, U+D1D8, U+D1F4, U+D1F8, U+D207,
U+D209, U+D210, U+D22C-D22D, U+D230, U+D234, U+D23C-D23D, U+D23F, U+D241, U+D248, U+D25C,
U+D264, U+D277, U+D280-D281, U+D284, U+D288, U+D290-D291, U+D295, U+D29C, U+D2A0, U+D2A4,
U+D2AC, U+D2B1, U+D2B8-D2B9, U+D2BC, U+D2BF-D2C0, U+D2C2, U+D2C8-D2C9, U+D2CB, U+D2D4, U+D2D8,
U+D2DC, U+D2E4-D2E5, U+D2F0-D2F1, U+D2F4, U+D2F8, U+D300-D301, U+D303, U+D305, U+D30C-D30E, U+D310,
U+D314, U+D316, U+D31C-D31D, U+D31F-D321, U+D325, U+D328-D329, U+D32C, U+D330, U+D338-D339, U+D33B-D33D,
U+D344-D345, U+D37C-D37D, U+D380, U+D384, U+D38C-D38D, U+D38F-D391, U+D398-D399, U+D39C, U+D3A0, U+D3A8-D3A9,
U+D3AB, U+D3AD, U+D3B4, U+D3B8, U+D3BC, U+D3C4-D3C5, U+D3C8-D3C9, U+D3D0, U+D3D8, U+D3E1,
U+D3E3, U+D3EC-D3ED, U+D3F0, U+D3F4, U+D3FC-D3FD, U+D3FF, U+D401, U+D408, U+D41D, U+D440,
U+D444, U+D45C, U+D460, U+D464, U+D46D, U+D46F, U+D478-D479, U+D47C, U+D47F-D480, U+D482,
U+D488-D489, U+D48B, U+D48D, U+D494, U+D4A9, U+D4B0, U+D4CC, U+D4D0, U+D4D4, U+D4DC,
U+D4DF, U+D4E8, U+D4EC, U+D4F0, U+D4F8, U+D4FB, U+D4FD, U+D504, U+D508, U+D50C,
U+D514-D515, U+D517, U+D53C-D53D, U+D540, U+D544, U+D54C-D54D, U+D54F, U+D551, U+D558-D559, U+D55C,
U+D560, U+D565, U+D568-D569, U+D56B, U+D56D, U+D571, U+D573-D575, U+D578, U+D57C, U+D584-D585,
U+D587-D589, U+D58F-D590, U+D59D, U+D5A5, U+D5C8-D5C9, U+D5CC, U+D5D0, U+D5D2, U+D5D8-D5D9, U+D5DB,
U+D5DD, U+D5E4-D5E5, U+D5E8, U+D5EC, U+D5F3-D5F5, U+D5F7, U+D5F9, U+D5FF-D601, U+D604, U+D608,
U+D610-D611, U+D613-D615, U+D61C, U+D620, U+D624, U+D62D, U+D638-D639, U+D63C, U+D640, U+D645,
U+D648-D649, U+D64B, U+D64D, U+D651, U+D654-D655, U+D658, U+D65C, U+D664, U+D667, U+D669,
U+D670-D671, U+D674, U+D683, U+D685, U+D68C-D68D, U+D690, U+D694, U+D69D, U+D69F, U+D6A1,
U+D6A8, U+D6AC, U+D6B0, U+D6B9, U+D6BB, U+D6BD, U+D6C3-D6C5, U+D6C8, U+D6CC, U+D6D1,
U+D6D4-D6D5, U+D6D7, U+D6D9, U+D6E0, U+D6E4, U+D6E8, U+D6F0, U+D6F5, U+D6FC-D6FD, U+D700,
U+D704, U+D711, U+D718-D719, U+D71C, U+D720, U+D728-D729, U+D72B, U+D72D, U+D734-D735, U+D738,
U+D73C, U+D744, U+D747, U+D749, U+D750-D751, U+D754, U+D756-D759, U+D760-D761, U+D763, U+D765,
U+D769, U+D76C, U+D770, U+D774, U+D77C-D77D, U+D781, U+D788-D789, U+D78C, U+D790, U+D798-D799,
U+D79B, U+D79D, U+FF01-FF5E, U+FFE0-FFE3, U+FFE5-FFE6;
}
/* NanumSquareEB.woff2 sha256: 8b8bda555af380ee62435880cb302af570a4c0364a3842fdb79b847ee220fb33 */
@font-face {
font-family: 'NanumSquare';
src: url('/assets/fonts/NanumSquareEB.woff2') format('woff2');
font-weight: 800;
font-style: normal;
font-display: swap;
unicode-range:
U+20-7E, U+A1, U+A4, U+A7-AB, U+B0-B4, U+B6, U+B8-BF, U+C6, U+D0, U+D7-D8,
U+DE-DF, U+E6, U+F0, U+F7-F8, U+FE, U+111, U+126-127, U+131-133, U+138, U+13F-142,
U+149-14B, U+152-153, U+166-167, U+2C7, U+2D8-2DD, U+391-3A1, U+3A3-3A9, U+3B1-3C1, U+3C3-3C9, U+401,
U+410-44F, U+451, U+2010, U+2013-2014, U+2016, U+2018-2019, U+201C-201D, U+2020-2021, U+2025-2026, U+2030,
U+2032-2033, U+203B, U+2074, U+207F, U+2081-2084, U+20A9, U+2103, U+2109, U+2113, U+2116,
U+2121-2122, U+2126, U+212B, U+2153-2154, U+215B-215E, U+2160-2169, U+2170-2179, U+2190-2199, U+21D2, U+21D4,
U+2200, U+2202-2203, U+2207-2208, U+220B, U+220F, U+2211, U+221A, U+221D-221E, U+2220, U+2227-222C,
U+222E, U+2234-2236, U+223D, U+2252, U+2260-2261, U+2264-2265, U+2282-2283, U+2286-2287, U+22A5, U+22EF,
U+2312, U+2460-246E, U+2474-2482, U+249C-24B5, U+24D0-24E9, U+2500-2503, U+250C-254B, U+2592, U+25A0-25A1, U+25A3-25A9,
U+25B2-25B3, U+25B6-25B7, U+25BC-25BD, U+25C0-25C1, U+25C6-25C9, U+25CB, U+25CE-25D1, U+2605-2606, U+260E-260F, U+261C,
U+261E, U+2640, U+2642, U+2660-2661, U+2663-2665, U+2667-266A, U+266C-266D, U+3000-3003, U+3008-3011, U+3013-3015,
U+3041-3093, U+30A1-30F6, U+30FB, U+3131-318E, U+3200-321C, U+3260-327B, U+327F, U+3380-3384, U+3388-33CA, U+33CF-33D0,
U+33D3, U+33D6, U+33D8, U+33DB-33DD, U+AC00-AC01, U+AC04, U+AC07-AC0A, U+AC10-AC17, U+AC19-AC1D, U+AC20,
U+AC24, U+AC2C-AC2D, U+AC2F-AC31, U+AC38-AC39, U+AC3C, U+AC40, U+AC4B, U+AC4D, U+AC54, U+AC58,
U+AC5C, U+AC70-AC71, U+AC74, U+AC77-AC78, U+AC7A, U+AC80-AC81, U+AC83-AC86, U+AC89-AC8C, U+AC90, U+AC94,
U+AC9C-AC9D, U+AC9F-ACA1, U+ACA8-ACAA, U+ACAC, U+ACAF-ACB0, U+ACB8-ACB9, U+ACBB-ACBD, U+ACC1, U+ACC4, U+ACC8,
U+ACCC, U+ACD5, U+ACD7, U+ACE0-ACE1, U+ACE4, U+ACE7-ACE8, U+ACEA, U+ACEC, U+ACEF-ACF1, U+ACF3,
U+ACF5-ACF6, U+ACFC-ACFD, U+AD00, U+AD04, U+AD06, U+AD0C-AD0D, U+AD0F, U+AD11, U+AD18, U+AD1C,
U+AD1E, U+AD20, U+AD29, U+AD2C-AD2D, U+AD34-AD35, U+AD38, U+AD3C, U+AD44-AD45, U+AD47, U+AD49,
U+AD50, U+AD54, U+AD58, U+AD61, U+AD63, U+AD65, U+AD6C-AD6D, U+AD70, U+AD73-AD76, U+AD7B-AD7D,
U+AD7F, U+AD81-AD82, U+AD88-AD89, U+AD8C, U+AD90, U+AD9C-AD9D, U+ADA4, U+ADB4, U+ADB7, U+ADC0-ADC1,
U+ADC4, U+ADC8, U+ADD0-ADD1, U+ADD3, U+ADDC, U+ADE0, U+ADE4, U+ADF8-ADF9, U+ADFC, U+ADFF-AE01,
U+AE08-AE09, U+AE0B, U+AE0D, U+AE14, U+AE1C, U+AE30-AE31, U+AE34, U+AE37-AE38, U+AE3A, U+AE40-AE41,
U+AE43, U+AE45-AE46, U+AE4A, U+AE4C-AE4E, U+AE50, U+AE54, U+AE56, U+AE5C-AE5D, U+AE5F-AE61, U+AE65,
U+AE68-AE69, U+AE6C, U+AE70, U+AE78-AE79, U+AE7B-AE7D, U+AE84-AE86, U+AE8C-AE8D, U+AEA0, U+AEBC-AEBE, U+AEC0,
U+AEC4, U+AECC-AECD, U+AECF-AED1, U+AED8-AED9, U+AEDC, U+AEE8, U+AEEB, U+AEED, U+AEF4, U+AEF8,
U+AEFC, U+AF07-AF08, U+AF0D, U+AF10, U+AF2C-AF2D, U+AF30, U+AF32, U+AF34, U+AF3C-AF3D, U+AF3F,
U+AF41-AF43, U+AF48-AF49, U+AF50, U+AF5C-AF5D, U+AF64-AF65, U+AF79, U+AF80, U+AF84, U+AF88, U+AF90-AF91,
U+AF95, U+AF9C, U+AFA4, U+AFB8-AFBA, U+AFBC, U+AFC0, U+AFC7-AFC9, U+AFCB, U+AFCD-AFCE, U+AFD4,
U+AFDC, U+AFE8-AFE9, U+AFF0-AFF1, U+AFF4, U+AFF8-AFF9, U+AFFF-B001, U+B004, U+B00C, U+B010, U+B014,
U+B01C-B01D, U+B028, U+B044-B045, U+B048, U+B04A, U+B04C, U+B04E, U+B053-B055, U+B057, U+B059,
U+B05D, U+B068, U+B07C-B07D, U+B080, U+B084, U+B08C-B08D, U+B08F, U+B091, U+B098-B09A, U+B09C,
U+B09F-B0A2, U+B0A8-B0A9, U+B0AB-B0AF, U+B0B1, U+B0B3-B0B5, U+B0B8, U+B0BC, U+B0C4-B0C5, U+B0C7-B0C9, U+B0D0-B0D1,
U+B0D4, U+B0D8, U+B0E0, U+B0E5, U+B108-B109, U+B10B-B10C, U+B110, U+B112-B113, U+B118-B119, U+B11B-B11D,
U+B123-B125, U+B128, U+B12C, U+B134-B135, U+B137-B139, U+B140-B141, U+B144, U+B148, U+B150-B151, U+B154-B155,
U+B158, U+B15C, U+B160, U+B178-B179, U+B17C, U+B180, U+B182, U+B188-B189, U+B18B, U+B18D,
U+B192-B194, U+B198, U+B19C, U+B1A8, U+B1CC, U+B1D0, U+B1D4, U+B1DC-B1DD, U+B1DF, U+B1E8-B1E9,
U+B1EC, U+B1F0, U+B1F9, U+B1FB, U+B1FD, U+B204-B205, U+B208, U+B20B-B20C, U+B214-B215, U+B217,
U+B219, U+B220, U+B234, U+B23C, U+B24C-B24D, U+B251, U+B258, U+B25C, U+B260, U+B268-B269,
U+B274-B275, U+B27C, U+B284-B285, U+B289, U+B290-B291, U+B294, U+B298-B29A, U+B2A0-B2A1, U+B2A3, U+B2A5-B2A6,
U+B2AA, U+B2AC, U+B2B0, U+B2B4, U+B2C8-B2C9, U+B2CC, U+B2D0, U+B2D2, U+B2D8-B2D9, U+B2DB,
U+B2DD, U+B2E2, U+B2E4-B2E6, U+B2E8, U+B2EB-B2EF, U+B2F3-B2F5, U+B2F7-B2FB, U+B2FF-B301, U+B304, U+B308,
U+B310-B311, U+B313-B315, U+B31C, U+B324, U+B32C, U+B331, U+B354-B356, U+B358, U+B35B-B35C, U+B35E-B35F,
U+B364-B365, U+B367, U+B369, U+B36B, U+B36E, U+B370-B371, U+B374, U+B378, U+B380-B381, U+B383-B385,
U+B38C, U+B390, U+B394, U+B3A0-B3A1, U+B3A8, U+B3AC, U+B3C4-B3C5, U+B3C8, U+B3CB-B3CC, U+B3CE,
U+B3D0, U+B3D4-B3D5, U+B3D7, U+B3D9, U+B3DB, U+B3DD, U+B3E0, U+B3E4, U+B3E8, U+B3FC,
U+B400, U+B40F-B411, U+B418, U+B41C, U+B420, U+B428-B429, U+B42B-B42D, U+B434, U+B450-B451, U+B454,
U+B458, U+B460-B461, U+B463, U+B465, U+B46C, U+B480, U+B488, U+B49D, U+B4A4, U+B4A8,
U+B4AC, U+B4B5, U+B4B7, U+B4B9, U+B4C0, U+B4C4, U+B4C8, U+B4D0, U+B4D5, U+B4DC-B4DD,
U+B4E0, U+B4E3-B4E4, U+B4E6, U+B4EC-B4ED, U+B4EF, U+B4F1, U+B4F8, U+B514-B515, U+B518, U+B51B-B51C,
U+B524-B525, U+B527-B52A, U+B530-B531, U+B534, U+B538, U+B540-B541, U+B543-B545, U+B54B-B54D, U+B550, U+B554,
U+B55C-B55D, U+B55F-B561, U+B568, U+B584, U+B599, U+B5A0-B5A1, U+B5A4, U+B5A8, U+B5AA-B5AB, U+B5B0-B5B1,
U+B5B3-B5B5, U+B5BB-B5BD, U+B5C0, U+B5C4, U+B5CC-B5CD, U+B5CF-B5D1, U+B5D8, U+B5EC, U+B610-B611, U+B614,
U+B618, U+B620, U+B625, U+B62C, U+B634, U+B648, U+B65D, U+B664, U+B668, U+B69C-B69D,
U+B6A0, U+B6A4, U+B6AB-B6AC, U+B6B1, U+B6D4, U+B6F0, U+B6F4, U+B6F8, U+B700-B701, U+B705,
U+B71B, U+B728-B729, U+B72C, U+B72F-B730, U+B738-B739, U+B73B, U+B73D, U+B744, U+B748, U+B74C,
U+B754-B755, U+B760-B761, U+B764, U+B768, U+B770-B771, U+B773, U+B775, U+B77C-B77D, U+B780, U+B784,
U+B78C-B78D, U+B78F-B792, U+B796-B799, U+B79C, U+B7A0, U+B7A8-B7A9, U+B7AB-B7AD, U+B7B4-B7B5, U+B7B8, U+B7BC,
U+B7C7, U+B7C9, U+B7EC-B7ED, U+B7F0, U+B7F4, U+B7FC-B7FD, U+B7FF-B801, U+B807-B809, U+B80C, U+B810,
U+B818-B819, U+B81B, U+B81D, U+B824-B825, U+B828, U+B82C, U+B834-B835, U+B837-B839, U+B840, U+B844,
U+B851, U+B853, U+B85C-B85D, U+B860, U+B864, U+B86C-B86D, U+B86F, U+B871, U+B878-B879, U+B87C,
U+B880, U+B88D, U+B894, U+B8A8, U+B8B0, U+B8B4, U+B8B8, U+B8C0-B8C1, U+B8C3, U+B8C5,
U+B8CC, U+B8D0, U+B8D4, U+B8DD, U+B8DF, U+B8E1, U+B8E8-B8E9, U+B8EC, U+B8F0, U+B8F8-B8F9,
U+B8FB, U+B8FD, U+B904, U+B918, U+B920, U+B93C-B93D, U+B940, U+B944, U+B94C, U+B94F,
U+B951, U+B958-B959, U+B95C, U+B960, U+B968-B969, U+B96B, U+B96D, U+B974-B975, U+B978, U+B97C,
U+B984-B985, U+B987, U+B989-B98A, U+B98D-B98E, U+B9AC-B9AD, U+B9B0, U+B9B4, U+B9BC-B9BD, U+B9BF, U+B9C1,
U+B9C8-B9C9, U+B9CC, U+B9CE-B9D2, U+B9D8-B9D9, U+B9DB, U+B9DD-B9DE, U+B9E1, U+B9E3-B9E5, U+B9E8, U+B9EC,
U+B9F4-B9F5, U+B9F7-B9FA, U+BA00-BA01, U+BA04, U+BA08, U+BA15, U+BA38-BA39, U+BA3C, U+BA40, U+BA42,
U+BA48-BA49, U+BA4B, U+BA4D-BA4E, U+BA53-BA55, U+BA58, U+BA5C, U+BA64-BA65, U+BA67-BA69, U+BA70-BA71, U+BA74,
U+BA78, U+BA83-BA85, U+BA87, U+BA8C, U+BAA8-BAA9, U+BAAB-BAAC, U+BAB0, U+BAB2, U+BAB8-BAB9, U+BABB,
U+BABD, U+BAC4, U+BAC8, U+BAD8-BAD9, U+BAFC, U+BB00, U+BB04, U+BB0D, U+BB0F, U+BB11,
U+BB18, U+BB1C, U+BB20, U+BB29, U+BB2B, U+BB34-BB36, U+BB38, U+BB3B-BB3E, U+BB44-BB45, U+BB47,
U+BB49, U+BB4D, U+BB4F-BB50, U+BB54, U+BB58, U+BB61, U+BB63, U+BB65, U+BB6C, U+BB88,
U+BB8C, U+BB90, U+BBA4, U+BBA8, U+BBAC, U+BBB4, U+BBB7, U+BBC0, U+BBC4, U+BBC8,
U+BBD0, U+BBD3, U+BBDC, U+BBF8-BBF9, U+BBFC, U+BBFF-BC00, U+BC02, U+BC08-BC09, U+BC0B-BC0D, U+BC0F,
U+BC11, U+BC14-BC18, U+BC1B-BC1F, U+BC24-BC25, U+BC27, U+BC29, U+BC2D, U+BC30-BC31, U+BC34, U+BC38,
U+BC40-BC41, U+BC43-BC45, U+BC49, U+BC4C-BC4D, U+BC50, U+BC5D, U+BC84-BC85, U+BC88, U+BC8B-BC8C, U+BC8E,
U+BC94-BC95, U+BC97, U+BC99-BC9A, U+BCA0-BCA1, U+BCA4, U+BCA7-BCA9, U+BCB0-BCB1, U+BCB3-BCB5, U+BCBC-BCBD, U+BCC0,
U+BCC4, U+BCCD, U+BCCF-BCD1, U+BCD5, U+BCD8, U+BCDC, U+BCF4-BCF6, U+BCF8, U+BCFC, U+BD04-BD05,
U+BD07, U+BD09, U+BD10, U+BD14, U+BD23-BD25, U+BD2C, U+BD35, U+BD40, U+BD48-BD49, U+BD4C,
U+BD50, U+BD58-BD59, U+BD64, U+BD68, U+BD79, U+BD80-BD81, U+BD84, U+BD87-BD8A, U+BD90-BD91, U+BD93,
U+BD95, U+BD99-BD9A, U+BD9C, U+BDA4, U+BDB0, U+BDB8-BDB9, U+BDC1, U+BDD4-BDD5, U+BDD8, U+BDDC,
U+BDE9, U+BDF0, U+BDF4, U+BDF8, U+BE00, U+BE03, U+BE05, U+BE0C-BE0D, U+BE10, U+BE14,
U+BE1C-BE1D, U+BE1F, U+BE44-BE45, U+BE48, U+BE4C, U+BE4E, U+BE54-BE55, U+BE57, U+BE59-BE5B, U+BE60-BE61,
U+BE64, U+BE68, U+BE6A, U+BE70-BE71, U+BE73-BE75, U+BE7B-BE7D, U+BE80, U+BE84, U+BE8C-BE8D, U+BE8F-BE91,
U+BE98-BE99, U+BEA8, U+BEAD, U+BEB4, U+BED0-BED1, U+BED4, U+BED7-BED8, U+BEE0, U+BEE3-BEE5, U+BEEC,
U+BEF5, U+BF01, U+BF08-BF09, U+BF18-BF19, U+BF1B-BF1D, U+BF40-BF41, U+BF44, U+BF48, U+BF50-BF51, U+BF55,
U+BF5C, U+BF71, U+BF94, U+BFB0, U+BFB8, U+BFC5, U+BFCC-BFCD, U+BFD0, U+BFD4, U+BFDC,
U+BFDF, U+BFE1, U+C00D, U+C03C, U+C04C, U+C051, U+C058, U+C05C, U+C060, U+C068-C069,
U+C090-C091, U+C094, U+C098, U+C0A0-C0A1, U+C0A3, U+C0A5, U+C0AC-C0AD, U+C0AF-C0B0, U+C0B3-C0B6, U+C0BC-C0BD,
U+C0BF-C0C1, U+C0C5, U+C0C8-C0C9, U+C0CC, U+C0D0, U+C0D8-C0D9, U+C0DB-C0DD, U+C0E4-C0E5, U+C0E8, U+C0EC,
U+C0F4-C0F5, U+C0F7, U+C0F9, U+C0FE, U+C100, U+C104, U+C108, U+C110, U+C115, U+C11C-C120,
U+C123-C124, U+C126-C127, U+C12C-C12D, U+C12F-C131, U+C136, U+C138-C139, U+C13C, U+C140, U+C142, U+C148-C149,
U+C14B-C14D, U+C154-C155, U+C158, U+C15C, U+C164-C165, U+C167-C169, U+C170, U+C174, U+C178, U+C185,
U+C18C-C18E, U+C190, U+C194, U+C196, U+C19C-C19D, U+C19F, U+C1A1, U+C1A5, U+C1A8-C1A9, U+C1AC,
U+C1B0, U+C1BD, U+C1C4, U+C1C8, U+C1CC, U+C1D4, U+C1D7-C1D8, U+C1E0, U+C1E4, U+C1E8,
U+C1F0-C1F1, U+C1F3, U+C1FC-C1FD, U+C200, U+C204, U+C20C-C20D, U+C20F, U+C211, U+C216, U+C218-C219,
U+C21C, U+C21F-C220, U+C228-C229, U+C22B, U+C22D, U+C22F, U+C231-C232, U+C234, U+C245, U+C248,
U+C250-C251, U+C254, U+C258, U+C25F-C260, U+C265, U+C26C-C26D, U+C270, U+C274, U+C27C-C27D, U+C27F,
U+C281, U+C288-C289, U+C28C, U+C290, U+C298-C299, U+C29B, U+C29D, U+C2A0, U+C2A4-C2A5, U+C2A8,
U+C2AC-C2AD, U+C2B4-C2B5, U+C2B7, U+C2B9, U+C2DC-C2DD, U+C2E0, U+C2E3-C2E4, U+C2EB-C2ED, U+C2EF, U+C2F1,
U+C2F6, U+C2F8-C2F9, U+C2FB-C2FC, U+C300, U+C308-C309, U+C30C-C30D, U+C313-C315, U+C318, U+C31C, U+C324-C325,
U+C328-C329, U+C330, U+C338, U+C345, U+C34C, U+C368-C369, U+C36C, U+C370, U+C372, U+C378-C379,
U+C37B-C37D, U+C384, U+C388, U+C38C, U+C395, U+C399, U+C3B5, U+C3BC, U+C3C0, U+C3D8-C3D9,
U+C3DC, U+C3DF-C3E0, U+C3E2, U+C3E8-C3E9, U+C3ED, U+C3F4-C3F5, U+C3F8, U+C3FC, U+C408, U+C410,
U+C424, U+C42C, U+C430, U+C434, U+C43C-C43D, U+C448, U+C450, U+C45D, U+C464-C465, U+C468,
U+C46C, U+C474-C475, U+C479, U+C480, U+C494, U+C49C-C49D, U+C4B8, U+C4BC, U+C4CD, U+C4D4,
U+C4D8, U+C4E9, U+C4F0-C4F1, U+C4F4, U+C4F8, U+C4FA, U+C4FF-C501, U+C50C, U+C510, U+C514,
U+C51C, U+C521, U+C528-C529, U+C52C, U+C530, U+C538-C539, U+C53B, U+C53D, U+C544-C545, U+C548-C54A,
U+C54C-C54E, U+C553-C555, U+C557-C559, U+C55C-C55E, U+C560-C561, U+C564, U+C568, U+C570-C571, U+C573-C575, U+C57B-C57D,
U+C580, U+C584, U+C587, U+C58C-C58D, U+C58F, U+C591, U+C595, U+C597-C598, U+C59C, U+C5A0,
U+C5A9, U+C5B4-C5B5, U+C5B8-C5B9, U+C5BB-C5BE, U+C5C4-C5CA, U+C5CC, U+C5CE, U+C5D0-C5D1, U+C5D4, U+C5D8,
U+C5E0-C5E1, U+C5E3, U+C5E5, U+C5EC-C5EE, U+C5F0, U+C5F4, U+C5F6-C5F7, U+C5FC-C601, U+C605-C608, U+C60C,
U+C610, U+C618-C619, U+C61B-C61C, U+C624-C625, U+C628, U+C62C-C62E, U+C630, U+C633-C635, U+C637, U+C639,
U+C63B, U+C640-C641, U+C644, U+C648, U+C650-C651, U+C653-C655, U+C65C-C65D, U+C660, U+C664, U+C66C,
U+C66F, U+C671, U+C674, U+C678-C679, U+C67C, U+C680, U+C688-C689, U+C68B, U+C68D, U+C694-C695,
U+C698, U+C69C, U+C6A4-C6A5, U+C6A7, U+C6A9, U+C6B0-C6B1, U+C6B4, U+C6B8-C6BA, U+C6C0-C6C1, U+C6C3,
U+C6C5, U+C6CC-C6CD, U+C6D0, U+C6D4, U+C6DC-C6DD, U+C6DF-C6E1, U+C6E8-C6E9, U+C6EC, U+C6F0-C6F1, U+C6F8-C6F9,
U+C6FD, U+C704-C705, U+C708, U+C70C, U+C714-C715, U+C717, U+C719, U+C720-C721, U+C724, U+C728,
U+C730-C731, U+C733, U+C735, U+C737, U+C73C-C73E, U+C740, U+C743-C744, U+C74A, U+C74C-C74F, U+C751-C758,
U+C75C, U+C760, U+C768, U+C76B, U+C774-C776, U+C778, U+C77C-C77E, U+C783-C785, U+C787-C78A, U+C78C,
U+C78E, U+C790-C791, U+C794, U+C796-C798, U+C79A, U+C7A0-C7A1, U+C7A3-C7A6, U+C7AC-C7AD, U+C7B0, U+C7B4,
U+C7BC-C7BD, U+C7BF-C7C1, U+C7C8-C7C9, U+C7CC, U+C7CE, U+C7D0, U+C7D8-C7D9, U+C7DD, U+C7E4, U+C7E8,
U+C7EC, U+C800-C801, U+C804, U+C808, U+C80A, U+C810-C811, U+C813, U+C815-C816, U+C81C-C81D, U+C820,
U+C824, U+C82C-C82D, U+C82F, U+C831, U+C838, U+C83C, U+C840, U+C848-C849, U+C84C-C84D, U+C854,
U+C870-C871, U+C874, U+C878, U+C87A, U+C880-C881, U+C883, U+C885-C887, U+C88B-C88D, U+C890, U+C894,
U+C89D, U+C89F, U+C8A1, U+C8A8, U+C8BC-C8BD, U+C8C4, U+C8C8, U+C8CC, U+C8D4-C8D5, U+C8D7,
U+C8D9, U+C8E0-C8E1, U+C8E4, U+C8E8, U+C8F5, U+C8FC-C8FD, U+C900, U+C904-C906, U+C90C-C90D, U+C90F,
U+C911, U+C918, U+C92C, U+C934-C935, U+C938, U+C950-C951, U+C954, U+C958, U+C960-C961, U+C963,
U+C965, U+C96C, U+C970, U+C974, U+C97C, U+C981, U+C988-C989, U+C98C, U+C990, U+C998-C999,
U+C99B, U+C99D, U+C9A4, U+C9A8, U+C9C0-C9C1, U+C9C4, U+C9C7-C9C8, U+C9CA, U+C9D0-C9D1, U+C9D3,
U+C9D5-C9D6, U+C9D9-C9DA, U+C9DC-C9DD, U+C9E0, U+C9E2, U+C9E4, U+C9E7, U+C9EC-C9ED, U+C9EF-C9F1, U+C9F8-C9F9,
U+C9FC, U+CA00, U+CA08-CA09, U+CA0B-CA0D, U+CA14, U+CA18, U+CA1C, U+CA29, U+CA30, U+CA4C-CA4D,
U+CA50, U+CA54, U+CA5C-CA5D, U+CA5F-CA61, U+CA68, U+CA7D, U+CA84, U+CA98, U+CABC-CABD, U+CAC0,
U+CAC4, U+CACC-CACD, U+CACF, U+CAD1, U+CAD3, U+CAD8-CAD9, U+CAE0, U+CAEC, U+CAF4, U+CB08,
U+CB10, U+CB14, U+CB18, U+CB20-CB21, U+CB2C, U+CB41, U+CB48-CB49, U+CB4C, U+CB50, U+CB58-CB59,
U+CB5D, U+CB64, U+CB78-CB79, U+CB9C, U+CBB8, U+CBD4, U+CBE4-CBE5, U+CBE7, U+CBE9, U+CC0C-CC0D,
U+CC10, U+CC14, U+CC1C-CC1D, U+CC21-CC22, U+CC27-CC29, U+CC2C, U+CC2E, U+CC30, U+CC38-CC39, U+CC3B-CC3E,
U+CC44-CC45, U+CC48, U+CC4C, U+CC54-CC55, U+CC57-CC59, U+CC60, U+CC64, U+CC66, U+CC68, U+CC70,
U+CC75, U+CC98-CC99, U+CC9C, U+CCA0, U+CCA8-CCA9, U+CCAB-CCAD, U+CCB4-CCB5, U+CCB8, U+CCBC, U+CCC4-CCC5,
U+CCC7, U+CCC9, U+CCD0, U+CCD4, U+CCE1, U+CCE4, U+CCEC, U+CCF0, U+CD01, U+CD08-CD09,
U+CD0C, U+CD10, U+CD18-CD19, U+CD1B, U+CD1D, U+CD24, U+CD28, U+CD2C, U+CD39, U+CD5C-CD5D,
U+CD60, U+CD64, U+CD6C-CD6D, U+CD6F, U+CD71, U+CD78, U+CD88, U+CD94-CD95, U+CD98, U+CD9C,
U+CDA4-CDA5, U+CDA7, U+CDA9, U+CDB0, U+CDC4, U+CDCC-CDCD, U+CDD0, U+CDDD, U+CDDF, U+CDE8,
U+CDEC, U+CDF0, U+CDF8-CDF9, U+CDFB, U+CDFD, U+CE04, U+CE08, U+CE0C, U+CE14-CE15, U+CE19,
U+CE20-CE21, U+CE24, U+CE28, U+CE30-CE31, U+CE33, U+CE35, U+CE58-CE59, U+CE5C, U+CE5F-CE61, U+CE68-CE69,
U+CE6B, U+CE6D, U+CE74-CE75, U+CE78, U+CE7C, U+CE84-CE85, U+CE87, U+CE89, U+CE8C, U+CE90-CE91,
U+CE94, U+CE98, U+CEA0-CEA1, U+CEA3-CEA5, U+CEAC-CEAE, U+CEB4, U+CEC1, U+CEC4, U+CEE4-CEE5, U+CEE8,
U+CEEB-CEEC, U+CEF4-CEF5, U+CEF7-CEF9, U+CF00-CF01, U+CF04, U+CF08, U+CF10-CF11, U+CF13, U+CF15, U+CF18,
U+CF1C, U+CF20, U+CF24, U+CF2C-CF2D, U+CF2F-CF31, U+CF38, U+CF54-CF55, U+CF58, U+CF5C, U+CF64-CF65,
U+CF67, U+CF69, U+CF70-CF71, U+CF74, U+CF78, U+CF80, U+CF85, U+CF8C, U+CFA1, U+CFA8,
U+CFB0, U+CFC4, U+CFDC, U+CFE0-CFE1, U+CFE4, U+CFE8, U+CFF0-CFF1, U+CFF3, U+CFF5, U+CFFC-CFFD,
U+D000, U+D004, U+D011, U+D018, U+D02D, U+D034-D035, U+D038, U+D03C, U+D044-D045, U+D047,
U+D049, U+D050, U+D054, U+D058, U+D060, U+D06C-D06E, U+D070, U+D074, U+D07C-D07D, U+D07F,
U+D081, U+D084, U+D0A4-D0A5, U+D0A8, U+D0AC, U+D0B4-D0B5, U+D0B7, U+D0B9, U+D0C0-D0C1, U+D0C4,
U+D0C8-D0C9, U+D0D0-D0D1, U+D0D3-D0D5, U+D0DC-D0DD, U+D0E0, U+D0E4, U+D0EC-D0ED, U+D0EF-D0F1, U+D0F8, U+D10D,
U+D130-D131, U+D134, U+D138, U+D13A, U+D140-D141, U+D143-D145, U+D14C-D14D, U+D150, U+D154, U+D15C-D15D,
U+D15F, U+D161, U+D168, U+D16C, U+D17C, U+D184, U+D188, U+D1A0-D1A1, U+D1A4, U+D1A8,
U+D1B0-D1B1, U+D1B3, U+D1B5, U+D1BA, U+D1BC, U+D1C0, U+D1D8, U+D1F4, U+D1F8, U+D207,
U+D209, U+D210, U+D22C-D22D, U+D230, U+D234, U+D23C-D23D, U+D23F, U+D241, U+D248, U+D25C,
U+D264, U+D277, U+D280-D281, U+D284, U+D288, U+D290-D291, U+D295, U+D29C, U+D2A0, U+D2A4,
U+D2AC, U+D2B1, U+D2B8-D2B9, U+D2BC, U+D2BF-D2C0, U+D2C2, U+D2C8-D2C9, U+D2CB, U+D2D4, U+D2D8,
U+D2DC, U+D2E4-D2E5, U+D2F0-D2F1, U+D2F4, U+D2F8, U+D300-D301, U+D303, U+D305, U+D30C-D30E, U+D310,
U+D314, U+D316, U+D31C-D31D, U+D31F-D321, U+D325, U+D328-D329, U+D32C, U+D330, U+D338-D339, U+D33B-D33D,
U+D344-D345, U+D37C-D37D, U+D380, U+D384, U+D38C-D38D, U+D38F-D391, U+D398-D399, U+D39C, U+D3A0, U+D3A8-D3A9,
U+D3AB, U+D3AD, U+D3B4, U+D3B8, U+D3BC, U+D3C4-D3C5, U+D3C8-D3C9, U+D3D0, U+D3D8, U+D3E1,
U+D3E3, U+D3EC-D3ED, U+D3F0, U+D3F4, U+D3FC-D3FD, U+D3FF, U+D401, U+D408, U+D41D, U+D440,
U+D444, U+D45C, U+D460, U+D464, U+D46D, U+D46F, U+D478-D479, U+D47C, U+D47F-D480, U+D482,
U+D488-D489, U+D48B, U+D48D, U+D494, U+D4A9, U+D4B0, U+D4CC, U+D4D0, U+D4D4, U+D4DC,
U+D4DF, U+D4E8, U+D4EC, U+D4F0, U+D4F8, U+D4FB, U+D4FD, U+D504, U+D508, U+D50C,
U+D514-D515, U+D517, U+D53C-D53D, U+D540, U+D544, U+D54C-D54D, U+D54F, U+D551, U+D558-D559, U+D55C,
U+D560, U+D565, U+D568-D569, U+D56B, U+D56D, U+D571, U+D573-D575, U+D578, U+D57C, U+D584-D585,
U+D587-D589, U+D58F-D590, U+D59D, U+D5A5, U+D5C8-D5C9, U+D5CC, U+D5D0, U+D5D2, U+D5D8-D5D9, U+D5DB,
U+D5DD, U+D5E4-D5E5, U+D5E8, U+D5EC, U+D5F3-D5F5, U+D5F7, U+D5F9, U+D5FF-D601, U+D604, U+D608,
U+D610-D611, U+D613-D615, U+D61C, U+D620, U+D624, U+D62D, U+D638-D639, U+D63C, U+D640, U+D645,
U+D648-D649, U+D64B, U+D64D, U+D651, U+D654-D655, U+D658, U+D65C, U+D664, U+D667, U+D669,
U+D670-D671, U+D674, U+D683, U+D685, U+D68C-D68D, U+D690, U+D694, U+D69D, U+D69F, U+D6A1,
U+D6A8, U+D6AC, U+D6B0, U+D6B9, U+D6BB, U+D6BD, U+D6C3-D6C5, U+D6C8, U+D6CC, U+D6D1,
U+D6D4-D6D5, U+D6D7, U+D6D9, U+D6E0, U+D6E4, U+D6E8, U+D6F0, U+D6F5, U+D6FC-D6FD, U+D700,
U+D704, U+D711, U+D718-D719, U+D71C, U+D720, U+D728-D729, U+D72B, U+D72D, U+D734-D735, U+D738,
U+D73C, U+D744, U+D747, U+D749, U+D750-D751, U+D754, U+D756-D759, U+D760-D761, U+D763, U+D765,
U+D769, U+D76C, U+D770, U+D774, U+D77C-D77D, U+D781, U+D788-D789, U+D78C, U+D790, U+D798-D799,
U+D79B, U+D79D, U+FF01-FF5E, U+FFE0-FFE3, U+FFE5-FFE6;
}