Spaces:
Running
Running
| <html lang="ko"> | |
| <head> | |
| <meta charset="utf-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1"> | |
| <meta name="theme-color" content="#050608"> | |
| <meta name="description" content="내용과 고유명사의 영향을 줄이고 한국어 소설의 문체를 비교하는 768차원 임베딩 모델입니다."> | |
| <title>Munche-768 | Korean Fiction Style Embedding</title> | |
| <link rel="preconnect" href="https://cdn.jsdelivr.net" crossorigin> | |
| <link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/dist/web/variable/pretendardvariable-dynamic-subset.css"> | |
| <style> | |
| :root { | |
| color-scheme: dark; | |
| --bg: #050608; | |
| --bg-soft: #0a0c10; | |
| --panel: #0d1015; | |
| --panel-hi: #12161d; | |
| --line: rgba(255, 255, 255, 0.12); | |
| --line-strong: rgba(255, 255, 255, 0.24); | |
| --text: #f7f7f4; | |
| --text-2: #d3d8de; | |
| --muted: var(--text-2); | |
| --dim: var(--text-2); | |
| --accent: #00efb3; | |
| --accent-blue: #5b8cff; | |
| --warning: #ffb020; | |
| --accent-warm: #ff704d; | |
| --radius: 22px; | |
| --max: 1480px; | |
| --fs-display: clamp(18px, 4vw, 56px); | |
| --fs-lead: 24px; | |
| --fs-body: 17px; | |
| --fs-meta: 14px; | |
| } | |
| * { box-sizing: border-box; } | |
| html { scroll-behavior: smooth; background: var(--bg); } | |
| body { | |
| margin: 0; | |
| background: | |
| radial-gradient(circle at 50% -15%, rgba(0, 239, 179, 0.08), transparent 31rem), | |
| var(--bg); | |
| color: var(--text); | |
| font-family: "Pretendard Variable", Pretendard, system-ui, -apple-system, "Segoe UI", sans-serif; | |
| font-size: var(--fs-body); | |
| font-weight: 450; | |
| line-height: 1.55; | |
| word-break: keep-all; | |
| overflow-wrap: break-word; | |
| overflow-x: hidden; | |
| } | |
| button, a { font: inherit; } | |
| a { color: inherit; text-decoration: none; } | |
| button { color: inherit; } | |
| ::selection { color: #020806; background: var(--accent); } | |
| .shell { width: min(calc(100% - 40px), var(--max)); margin-inline: auto; } | |
| .nav { | |
| position: fixed; | |
| z-index: 50; | |
| top: 18px; | |
| left: 50%; | |
| transform: translateX(-50%); | |
| width: min(calc(100% - 32px), 920px); | |
| display: grid; | |
| grid-template-columns: 1fr auto 1fr; | |
| align-items: center; | |
| gap: 20px; | |
| min-height: 58px; | |
| padding: 0 18px; | |
| border: 1px solid rgba(255, 255, 255, 0.12); | |
| border-radius: 18px; | |
| background: rgba(6, 8, 11, 0.76); | |
| backdrop-filter: blur(22px) saturate(135%); | |
| box-shadow: 0 12px 42px rgba(0, 0, 0, 0.34); | |
| } | |
| .brand { display: flex; align-items: center; gap: 10px; white-space: nowrap; } | |
| .brand img { | |
| width: 34px; | |
| height: 34px; | |
| border-radius: 9px; | |
| object-fit: cover; | |
| } | |
| .brand-copy { display: grid; gap: 1px; line-height: 1; } | |
| .brand-copy small { color: var(--text-2); font-size: 10px; font-weight: 650; letter-spacing: 0.05em; } | |
| .brand-copy strong { font-size: var(--fs-meta); font-weight: 780; letter-spacing: 0.01em; } | |
| .brand-copy strong span { color: var(--accent); } | |
| .nav-links { display: flex; gap: 24px; color: var(--text-2); font-size: var(--fs-meta); font-weight: 600; } | |
| .nav-links a:hover, .nav-links a:focus-visible { color: var(--text); } | |
| .nav-status { justify-self: end; display: flex; align-items: center; gap: 8px; font-size: var(--fs-meta); color: var(--text-2); font-weight: 600; } | |
| .status-dot { width: 7px; height: 7px; border-radius: 50%; background: var(--accent); box-shadow: 0 0 16px var(--accent); } | |
| .hero { | |
| min-height: 92svh; | |
| display: grid; | |
| place-items: center; | |
| padding: 150px 0 84px; | |
| text-align: center; | |
| } | |
| .hero-inner { width: min(100%, 1180px); } | |
| .model-name { | |
| margin: 0 0 22px; | |
| color: var(--text); | |
| font-size: clamp(54px, 9vw, 132px); | |
| font-weight: 800; | |
| line-height: .9; | |
| letter-spacing: -0.055em; | |
| } | |
| h1 { | |
| margin: 0; | |
| font-size: var(--fs-display); | |
| font-weight: 780; | |
| line-height: 1.04; | |
| letter-spacing: -0.01em; | |
| text-wrap: balance; | |
| } | |
| h1 .line { display: block; white-space: nowrap; } | |
| h1 .quiet { color: var(--text-2); } | |
| .hero-copy { | |
| width: min(100%, 720px); | |
| margin: 38px auto 0; | |
| color: var(--text-2); | |
| font-size: var(--fs-body); | |
| text-wrap: pretty; | |
| } | |
| .hero-meta { display: flex; justify-content: center; flex-wrap: wrap; gap: 10px; margin-top: 34px; } | |
| .chip { | |
| display: inline-flex; | |
| align-items: center; | |
| min-height: 34px; | |
| padding: 0 13px; | |
| border: 1px solid var(--line); | |
| border-radius: 999px; | |
| background: rgba(255, 255, 255, 0.025); | |
| color: var(--text-2); | |
| font-size: var(--fs-meta); | |
| } | |
| .scroll-cue { margin-top: 80px; color: var(--text-2); font-size: var(--fs-meta); letter-spacing: .08em; } | |
| .scroll-cue::after { content: ""; display: block; width: 1px; height: 54px; margin: 13px auto 0; background: linear-gradient(var(--line-strong), transparent); } | |
| .section { padding: 150px 0; } | |
| .section-head { display: block; margin-bottom: 54px; } | |
| .kicker { margin: 0 0 14px; color: var(--accent); font-size: var(--fs-meta); font-weight: 700; letter-spacing: .08em; text-transform: uppercase; } | |
| h2 { margin: 0; max-width: 980px; font-size: var(--fs-display); line-height: 1.04; letter-spacing: -0.01em; text-wrap: balance; } | |
| h2 .line { display: block; } | |
| h3 { font-size: var(--fs-lead); line-height: 1.25; letter-spacing: -0.01em; } | |
| .section-note { max-width: 760px; margin: 24px 0 0; color: var(--text-2); font-size: var(--fs-body); text-wrap: pretty; } | |
| .overview-grid { | |
| display: grid; | |
| grid-template-columns: minmax(0, 1.05fr) minmax(420px, .95fr); | |
| gap: 90px; | |
| align-items: start; | |
| } | |
| .overview-copy p { max-width: 62ch; margin: 28px 0 0; color: var(--text-2); font-size: var(--fs-body); } | |
| .spec-list { margin: 0; border-top: 1px solid var(--line); } | |
| .spec-row { | |
| display: grid; | |
| grid-template-columns: minmax(150px, .7fr) minmax(0, 1.3fr); | |
| gap: 24px; | |
| padding: 20px 0; | |
| border-bottom: 1px solid var(--line); | |
| } | |
| .spec-row dt { color: var(--text-2); font-size: var(--fs-meta); font-weight: 600; } | |
| .spec-row dd { margin: 0; color: var(--text); font-size: var(--fs-body); } | |
| .training-layout { | |
| display: grid; | |
| grid-template-columns: minmax(0, .8fr) minmax(0, 1.2fr); | |
| gap: 80px; | |
| align-items: start; | |
| } | |
| .training-copy { position: sticky; top: 120px; } | |
| .training-copy .section-note { max-width: 54ch; margin-top: 28px; } | |
| .training-details { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); border: 1px solid var(--line); border-radius: 24px; overflow: hidden; } | |
| .training-item { min-height: 250px; padding: 30px; background: var(--panel); box-shadow: inset -1px -1px 0 var(--line); } | |
| .training-item strong { display: block; margin-bottom: 18px; font-size: var(--fs-lead); } | |
| .training-item p { margin: 0; color: var(--text-2); font-size: var(--fs-body); } | |
| .training-item .stat { display: block; margin: 0 0 12px; color: var(--accent); font-size: var(--fs-meta); font-weight: 700; line-height: 1; letter-spacing: 0; } | |
| .use-grid { display: grid; grid-template-columns: repeat(5, minmax(0, 1fr)); border: 1px solid var(--line); border-radius: 24px; overflow: hidden; } | |
| .use-item { min-height: 270px; padding: 28px; background: var(--panel); box-shadow: inset -1px -1px 0 var(--line); } | |
| .use-item b { display: block; margin-bottom: 64px; color: var(--accent); font-size: var(--fs-meta); } | |
| .use-item h3 { margin: 0 0 16px; font-size: var(--fs-lead); line-height: 1.2; letter-spacing: -0.01em; } | |
| .use-item p { margin: 0; color: var(--text-2); font-size: var(--fs-body); } | |
| .stage-wrap { | |
| position: relative; | |
| min-height: 780px; | |
| overflow: hidden; | |
| border: 1px solid var(--line); | |
| border-radius: 30px; | |
| background: | |
| radial-gradient(circle at 50% 45%, rgba(15, 28, 28, .52), transparent 42%), | |
| #030405; | |
| box-shadow: 0 40px 100px rgba(0, 0, 0, .46); | |
| isolation: isolate; | |
| } | |
| .plot-viewport { position: absolute; inset: 0; overflow: hidden; } | |
| .stage-wrap::before { | |
| content: ""; | |
| position: absolute; | |
| inset: 0; | |
| z-index: -1; | |
| opacity: .38; | |
| background-image: | |
| linear-gradient(rgba(255,255,255,.025) 1px, transparent 1px), | |
| linear-gradient(90deg, rgba(255,255,255,.025) 1px, transparent 1px); | |
| background-size: 64px 64px; | |
| mask-image: radial-gradient(circle at center, #000 22%, transparent 76%); | |
| } | |
| #plot { position: absolute; inset: 0; width: 100%; height: 100%; touch-action: none; cursor: grab; } | |
| #plot:active { cursor: grabbing; } | |
| .stage-top { | |
| position: absolute; | |
| z-index: 5; | |
| top: 22px; | |
| left: 22px; | |
| right: 22px; | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: flex-start; | |
| gap: 24px; | |
| pointer-events: none; | |
| } | |
| .stage-label { font-size: var(--fs-meta); color: var(--text-2); } | |
| .stage-label strong { display: block; color: var(--text); font-size: var(--fs-lead); font-weight: 700; } | |
| .plot-help { display: block; margin-top: 5px; color: var(--text-2); font-size: 12px; font-weight: 550; } | |
| .stage-actions { display: flex; gap: 8px; pointer-events: auto; } | |
| .layer-switch { display: flex; gap: 4px; padding: 4px; border: 1px solid var(--line); border-radius: 14px; background: rgba(7, 9, 12, .72); backdrop-filter: blur(14px); } | |
| .icon-button, .legend-button, .filter-button { | |
| border: 1px solid var(--line); | |
| background: rgba(7, 9, 12, .72); | |
| backdrop-filter: blur(14px); | |
| cursor: pointer; | |
| transition: border-color .2s ease, background .2s ease, color .2s ease; | |
| } | |
| .icon-button:hover, .icon-button:focus-visible, .legend-button:hover, .filter-button:hover { border-color: var(--line-strong); background: rgba(20, 24, 30, .9); } | |
| .icon-button { min-height: 40px; padding: 0 13px; border-radius: 12px; color: var(--text); font-size: var(--fs-meta); font-weight: 600; } | |
| .icon-button[aria-pressed="true"] { color: #02100c; border-color: var(--accent); background: var(--accent); } | |
| .layer-switch .icon-button { min-height: 32px; border-color: transparent; background: transparent; } | |
| .layer-switch .icon-button[aria-pressed="true"] { color: #080a0d; background: #f3f5f7; } | |
| .legend, .filters { transition: opacity .2s ease; } | |
| .layer-controls-muted { visibility: hidden; opacity: 0; pointer-events: none; } | |
| .legend { | |
| position: absolute; | |
| z-index: 6; | |
| left: 22px; | |
| bottom: 22px; | |
| display: grid; | |
| grid-template-columns: repeat(2, minmax(0, 1fr)); | |
| gap: 6px; | |
| width: min(440px, calc(100% - 44px)); | |
| padding: 10px; | |
| border: 1px solid var(--line); | |
| border-radius: 16px; | |
| background: rgba(5, 7, 9, .78); | |
| backdrop-filter: blur(18px); | |
| } | |
| .legend-button { display: flex; align-items: center; gap: 9px; min-width: 0; padding: 8px 9px; border-radius: 9px; color: var(--text); font-size: var(--fs-meta); font-weight: 600; text-align: left; } | |
| .legend-button.off { text-decoration: line-through; } | |
| .legend-button.off .swatch { background: transparent; border: 1px solid var(--swatch); box-shadow: none; } | |
| .swatch { flex: 0 0 auto; width: 9px; height: 9px; border-radius: 50%; background: var(--swatch); box-shadow: 0 0 11px color-mix(in srgb, var(--swatch), transparent 45%); } | |
| .legend-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } | |
| .legend-count { margin-left: auto; color: var(--dim); font-variant-numeric: tabular-nums; } | |
| .filters { | |
| position: absolute; | |
| z-index: 6; | |
| right: 22px; | |
| bottom: 22px; | |
| display: flex; | |
| max-width: calc(100% - 44px); | |
| gap: 6px; | |
| padding: 7px; | |
| overflow-x: auto; | |
| border: 1px solid var(--line); | |
| border-radius: 15px; | |
| background: rgba(5, 7, 9, .78); | |
| backdrop-filter: blur(18px); | |
| } | |
| .filter-button { flex: 0 0 auto; min-height: 34px; padding: 0 11px; border-radius: 9px; color: var(--text-2); font-size: var(--fs-meta); font-weight: 600; } | |
| .filter-button.active { color: #020907; border-color: var(--accent); background: var(--accent); } | |
| .tooltip { | |
| position: absolute; | |
| z-index: 8; | |
| display: none; | |
| min-width: 168px; | |
| padding: 11px 13px; | |
| border: 1px solid var(--line-strong); | |
| border-radius: 12px; | |
| background: rgba(4, 6, 8, .92); | |
| box-shadow: 0 18px 50px rgba(0,0,0,.45); | |
| pointer-events: none; | |
| font-size: var(--fs-meta); | |
| } | |
| .tooltip b { display: block; margin-bottom: 3px; } | |
| .tooltip span { color: var(--muted); } | |
| .privacy-badge { | |
| position: absolute; | |
| z-index: 4; | |
| right: 24px; | |
| top: 88px; | |
| display: flex; | |
| align-items: center; | |
| gap: 8px; | |
| color: #8f969f; | |
| font-size: var(--fs-meta); | |
| pointer-events: none; | |
| } | |
| .privacy-badge::before { content: ""; width: 6px; height: 6px; border-radius: 50%; background: #fff; } | |
| .bento { | |
| display: grid; | |
| grid-template-columns: repeat(12, minmax(0, 1fr)); | |
| gap: 0; | |
| overflow: hidden; | |
| border: 1px solid var(--line); | |
| border-radius: 26px; | |
| background: var(--line); | |
| } | |
| .metric-card { | |
| min-height: 320px; | |
| padding: 34px; | |
| background: var(--panel); | |
| box-shadow: inset -1px -1px 0 var(--line); | |
| } | |
| .metric-card:nth-child(1) { grid-column: span 5; } | |
| .metric-card:nth-child(2) { grid-column: span 3; } | |
| .metric-card:nth-child(3) { grid-column: span 4; } | |
| .metric-card:nth-child(4) { grid-column: span 4; } | |
| .metric-card:nth-child(5) { grid-column: span 8; } | |
| .metric-label { color: var(--text-2); font-size: var(--fs-body); font-weight: 700; } | |
| .metric-index { margin-bottom: 18px; color: var(--accent); font-size: var(--fs-meta); font-weight: 700; letter-spacing: .08em; } | |
| .metric-value { margin-top: 42px; font-size: var(--fs-display); font-weight: 780; line-height: 1; letter-spacing: -0.01em; font-variant-numeric: tabular-nums; } | |
| .metric-value small { font-size: var(--fs-meta); letter-spacing: 0; color: var(--text-2); } | |
| .metric-copy { max-width: 43ch; margin: 24px 0 0; color: var(--text-2); font-size: var(--fs-body); } | |
| .metric-card.accent { background: var(--accent); color: #001a12; } | |
| .metric-card.accent .metric-index, .metric-card.accent .metric-label, .metric-card.accent .metric-copy { color: #08312a; } | |
| .metric-card.warning { background: #17130b; } | |
| .metric-card.warning .metric-value { color: var(--warning); } | |
| .metric-card.wide { display: grid; grid-template-columns: minmax(0, .8fr) minmax(320px, 1.2fr); gap: 42px; align-items: center; } | |
| .baseline-card { | |
| display: grid; | |
| grid-template-columns: minmax(280px, .75fr) minmax(0, 1.25fr); | |
| gap: 64px; | |
| align-items: center; | |
| margin-top: 24px; | |
| padding: 38px; | |
| border: 1px solid var(--line); | |
| border-radius: 22px; | |
| background: var(--panel); | |
| } | |
| .baseline-card h3 { margin: 0; } | |
| .comparison-list { display: grid; gap: 16px; } | |
| .comparison-row { display: grid; grid-template-columns: 220px minmax(0, 1fr) 160px; gap: 16px; align-items: center; color: var(--text); font-size: var(--fs-meta); } | |
| .bar { height: 8px; border-radius: 999px; background: rgba(255,255,255,.08); overflow: hidden; } | |
| .bar i { display: block; width: calc(var(--value) * 100%); height: 100%; border-radius: inherit; background: linear-gradient(90deg, #59616c, #f4f6f4); } | |
| .comparison-row strong { text-align: right; font-variant-numeric: tabular-nums; } | |
| .benchmark-chart { | |
| padding: 38px; | |
| border: 1px solid var(--line); | |
| border-radius: 26px; | |
| background: var(--panel); | |
| } | |
| .chart-legend { display: flex; justify-content: center; gap: 28px; margin-bottom: 34px; font-size: var(--fs-body); font-weight: 650; } | |
| .chart-legend span { display: flex; align-items: center; gap: 10px; } | |
| .chart-legend i { width: 16px; height: 16px; border-radius: 3px; background: #59616c; } | |
| .chart-legend .tuned { background: var(--accent); } | |
| .chart-plot { | |
| display: grid; | |
| grid-template-columns: repeat(4, minmax(0, 1fr)); | |
| gap: 26px; | |
| min-height: 430px; | |
| padding: 26px 18px 0; | |
| background: repeating-linear-gradient(to top, var(--line) 0 1px, transparent 1px 25%); | |
| border-bottom: 1px solid var(--line-strong); | |
| } | |
| .chart-group { display: grid; grid-template-rows: 340px auto; gap: 18px; min-width: 0; } | |
| .chart-bars { display: flex; justify-content: center; align-items: end; gap: 8px; height: 340px; } | |
| .chart-bar { position: relative; width: min(62px, 42%); height: var(--score); min-height: 2px; border-radius: 7px 7px 0 0; background: #59616c; } | |
| .chart-bar.tuned { background: var(--accent); } | |
| .chart-bar strong { position: absolute; left: 50%; bottom: calc(100% + 10px); transform: translateX(-50%); color: var(--text); font-size: var(--fs-body); font-variant-numeric: tabular-nums; white-space: nowrap; } | |
| .chart-label { min-height: 54px; color: var(--text); font-size: var(--fs-meta); font-weight: 650; text-align: center; } | |
| .metric-definitions { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 1px; margin-top: 22px; overflow: hidden; border: 1px solid var(--line); border-radius: 20px; background: var(--line); } | |
| .metric-definition { padding: 24px; background: var(--panel); } | |
| .metric-definition b { display: block; margin-bottom: 10px; font-size: var(--fs-body); } | |
| .metric-definition p { margin: 0; color: var(--text-2); font-size: var(--fs-meta); } | |
| .length-claim { margin: 34px 0 0; text-align: center; font-size: var(--fs-lead); font-weight: 700; } | |
| .additional-metrics { margin-top: 28px; } | |
| .additional-metrics table { table-layout: fixed; } | |
| .performance { display: grid; grid-template-columns: minmax(0, 1.05fr) minmax(0, .95fr); gap: 64px; align-items: start; } | |
| .performance-copy { position: sticky; top: 120px; } | |
| .inline-cloud { display: inline-grid; grid-template-columns: repeat(5, 5px); gap: 4px; vertical-align: .17em; margin: 0 .1em; } | |
| .inline-cloud i { width: 5px; height: 5px; border-radius: 50%; background: var(--accent); opacity: calc(.25 + var(--o) * .13); } | |
| .prose { color: var(--text-2); font-size: var(--fs-body); } | |
| .prose strong { color: var(--text); font-weight: 650; } | |
| .table-wrap { overflow: hidden; border: 1px solid var(--line); border-radius: 22px; } | |
| #evaluation table { min-width: 0; } | |
| table { width: 100%; border-collapse: collapse; background: var(--panel); } | |
| th, td { padding: 20px 22px; border-bottom: 1px solid var(--line); text-align: left; } | |
| th { color: var(--text); font-size: var(--fs-meta); font-weight: 700; letter-spacing: 0; } | |
| td { color: var(--text-2); font-size: var(--fs-body); } | |
| td:nth-child(2), td:nth-child(3), td:nth-child(4), td:nth-child(5) { font-variant-numeric: tabular-nums; } | |
| tr:last-child td { border-bottom: 0; } | |
| .table-note { margin: 18px 0 0; color: var(--text-2); font-size: var(--fs-meta); } | |
| .auroc-strip { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); margin-top: 22px; overflow: hidden; border: 1px solid var(--line); border-radius: 18px; } | |
| .auroc-item { padding: 24px; background: var(--panel); box-shadow: inset -1px 0 0 var(--line); } | |
| .auroc-item span { display: block; color: var(--text-2); font-size: var(--fs-meta); } | |
| .auroc-item strong { display: block; margin-top: 12px; font-size: var(--fs-display); line-height: 1; font-variant-numeric: tabular-nums; } | |
| .auroc-item small { color: var(--text-2); font-size: var(--fs-meta); } | |
| .probe-grid { | |
| display: grid; | |
| grid-template-columns: minmax(0, .8fr) minmax(420px, 1.2fr); | |
| gap: 54px; | |
| align-items: center; | |
| margin-top: 34px; | |
| padding: 42px; | |
| border: 1px solid var(--line); | |
| border-radius: 24px; | |
| background: var(--panel); | |
| } | |
| .probe-grid h3 { margin: 0; } | |
| .probe-summary { margin: 18px 0 0; color: var(--text); font-size: var(--fs-body); font-weight: 700; } | |
| .probe-score { display: flex; gap: 40px; margin-top: 30px; } | |
| .probe-score span { color: var(--text); font-size: var(--fs-body); font-weight: 700; } | |
| .probe-score strong { display: block; margin-top: 6px; color: var(--text); font-size: var(--fs-display); line-height: 1; } | |
| .probe-score small { display: block; max-width: 28ch; margin-top: 10px; color: var(--text-2); font-size: var(--fs-meta); font-weight: 600; } | |
| .confusion { width: 100%; border-collapse: separate; border-spacing: 8px; background: transparent; } | |
| .confusion th { color: var(--text); font-size: var(--fs-body); font-weight: 700; text-align: center; } | |
| .confusion th[scope="row"] { text-align: left; } | |
| .confusion .cell { | |
| height: 112px; | |
| text-align: center; | |
| vertical-align: middle; | |
| border: 1px solid var(--line); | |
| border-radius: 14px; | |
| background: var(--panel-hi); | |
| color: var(--text); | |
| font-size: var(--fs-lead); | |
| font-weight: 780; | |
| font-variant-numeric: tabular-nums; | |
| } | |
| .confusion .cell.hit { border-color: color-mix(in srgb, var(--accent), transparent 45%); background: color-mix(in srgb, var(--accent), var(--panel-hi) 88%); } | |
| .confusion .cell.miss { border-color: color-mix(in srgb, var(--warning), transparent 48%); background: color-mix(in srgb, var(--warning), var(--panel-hi) 91%); } | |
| .confusion .cell small { display: block; color: var(--text-2); font-size: var(--fs-meta); font-weight: 600; } | |
| .method-grid { display: grid; grid-template-columns: minmax(300px, .7fr) minmax(0, 1.3fr); gap: 80px; align-items: start; } | |
| .method-intro { position: sticky; top: 120px; } | |
| .stack { position: relative; display: grid; gap: 24px; } | |
| .method-card { | |
| position: sticky; | |
| top: calc(118px + var(--i) * 18px); | |
| min-height: 440px; | |
| padding: 38px; | |
| border: 1px solid var(--line); | |
| border-radius: 26px; | |
| background: color-mix(in srgb, var(--panel-hi) calc(88% + var(--i) * 2%), #050608); | |
| box-shadow: 0 32px 80px rgba(0,0,0,.36); | |
| } | |
| .method-index { display: flex; justify-content: space-between; color: var(--text-2); font-size: var(--fs-meta); } | |
| .method-card h3 { margin: 86px 0 18px; font-size: var(--fs-lead); line-height: 1.2; letter-spacing: -0.01em; } | |
| .method-card p { max-width: 54ch; margin: 0; color: var(--text-2); font-size: var(--fs-body); } | |
| .method-spec { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 34px; } | |
| .principles { | |
| display: grid; | |
| grid-template-columns: repeat(3, minmax(0, 1fr)); | |
| overflow: hidden; | |
| border: 1px solid var(--line); | |
| border-radius: 24px; | |
| background: var(--line); | |
| gap: 1px; | |
| } | |
| .principle { min-height: 260px; padding: 32px; background: var(--panel); } | |
| .principle b { display: block; margin-bottom: 28px; font-size: var(--fs-body); } | |
| .principle p { margin: 0; color: var(--text-2); font-size: var(--fs-body); } | |
| .training-centered { width: min(820px, 100%); margin-inline: auto; } | |
| .training-centered .kicker { text-align: center; } | |
| .training-note { margin: 34px auto 0; color: var(--text-2); font-size: var(--fs-body); } | |
| .footer { padding: 40px 0 44px; border-top: 1px solid var(--line); } | |
| .footer-cta { | |
| padding: 70px; | |
| border-radius: 28px; | |
| background: var(--text); | |
| color: #080a0c; | |
| } | |
| .footer-cta h2 { max-width: 1120px; } | |
| .footer-cta p { max-width: 720px; margin: 28px 0 0; color: #283138; font-size: var(--fs-body); } | |
| .cta-row { display: flex; flex-wrap: wrap; gap: 10px; margin-top: 34px; } | |
| .cta { | |
| display: inline-flex; | |
| align-items: center; | |
| justify-content: center; | |
| min-height: 46px; | |
| padding: 0 17px; | |
| border: 1px solid #111; | |
| border-radius: 12px; | |
| font-size: var(--fs-meta); | |
| font-weight: 700; | |
| } | |
| .cta.primary { background: #080a0c; color: #fff; } | |
| .cta.secondary { background: transparent; color: #080a0c; } | |
| .footer-bottom { display: flex; justify-content: space-between; gap: 30px; color: var(--text-2); font-size: var(--fs-meta); } | |
| .footer-links { display: flex; gap: 22px; } | |
| .footer-links a:hover, .footer-links a:focus-visible { color: var(--text); } | |
| @media (max-width: 1040px) { | |
| .stage-wrap { min-height: 690px; } | |
| .section-head, .performance, .method-grid, .overview-grid, .training-layout, .baseline-card { grid-template-columns: 1fr; } | |
| .performance-copy, .method-intro, .training-copy { position: static; } | |
| .metric-card:nth-child(n) { grid-column: span 6; } | |
| .metric-card.wide { grid-template-columns: 1fr; } | |
| .principles { grid-template-columns: 1fr; } | |
| .use-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } | |
| .probe-grid { grid-template-columns: 1fr; } | |
| .chart-plot { gap: 16px; } | |
| } | |
| @media (max-width: 720px) { | |
| .shell { width: min(calc(100% - 24px), var(--max)); } | |
| .nav { | |
| top: 0; | |
| left: 0; | |
| transform: none; | |
| width: 100%; | |
| min-height: 52px; | |
| grid-template-columns: 1fr auto; | |
| gap: 10px; | |
| padding: 0 12px; | |
| border-width: 0 0 1px; | |
| border-radius: 0; | |
| background: rgba(6, 8, 11, .94); | |
| } | |
| .brand { gap: 8px; } | |
| .brand img { width: 28px; height: 28px; border-radius: 7px; } | |
| .brand-copy { display: flex; align-items: baseline; gap: 5px; } | |
| .brand-copy small { font-size: 9px; } | |
| .brand-copy strong { font-size: 12px; } | |
| .nav-status { gap: 5px; font-size: 10px; } | |
| .status-dot { width: 5px; height: 5px; } | |
| .nav-links { display: none; } | |
| h2 { font-size: var(--fs-lead); } | |
| .hero { min-height: 0; padding: 104px 0 58px; } | |
| .model-name { margin-bottom: 16px; font-size: clamp(42px, 14vw, 56px); } | |
| .hero-copy { margin-top: 24px; } | |
| .hero-meta { margin-top: 24px; } | |
| .scroll-cue { display: none; } | |
| .section { padding: 68px 0; } | |
| .section-head { margin-bottom: 28px; } | |
| .section-note { margin-top: 18px; } | |
| .stage-wrap { | |
| min-height: 0; | |
| display: grid; | |
| grid-template-rows: 460px auto auto; | |
| overflow: hidden; | |
| border-radius: 20px; | |
| } | |
| .plot-viewport { position: relative; inset: auto; min-height: 460px; } | |
| .stage-top { top: 14px; left: 14px; right: 14px; flex-direction: column; gap: 10px; } | |
| .stage-actions { flex-wrap: wrap; } | |
| .privacy-badge { top: 78px; right: 16px; } | |
| .legend { | |
| position: relative; | |
| left: auto; | |
| right: auto; | |
| bottom: auto; | |
| display: flex; | |
| width: 100%; | |
| padding: 8px; | |
| overflow-x: auto; | |
| border-width: 1px 0 0; | |
| border-radius: 0; | |
| background: rgba(5, 7, 9, .96); | |
| backdrop-filter: none; | |
| scrollbar-width: thin; | |
| } | |
| .legend-button { flex: 0 0 auto; min-height: 38px; padding-inline: 10px; } | |
| .legend-count { display: none; } | |
| .filters { | |
| position: relative; | |
| left: auto; | |
| right: auto; | |
| bottom: auto; | |
| width: 100%; | |
| max-width: none; | |
| padding: 8px; | |
| border-width: 1px 0 0; | |
| border-radius: 0; | |
| background: rgba(5, 7, 9, .96); | |
| backdrop-filter: none; | |
| scrollbar-width: thin; | |
| } | |
| .metric-card:nth-child(n) { grid-column: 1 / -1; min-height: 190px; padding: 24px; } | |
| .metric-value { margin-top: 28px; } | |
| .comparison-row { grid-template-columns: minmax(0, 1fr) auto; gap: 10px 18px; } | |
| .comparison-row .bar { grid-column: 1 / -1; grid-row: 2; } | |
| .comparison-row strong { grid-column: 2; grid-row: 1; } | |
| th, td { padding: 16px 13px; } | |
| .benchmark-chart { padding: 22px 14px; } | |
| .chart-legend { gap: 16px; margin-bottom: 24px; font-size: var(--fs-meta); } | |
| .chart-plot { | |
| grid-template-columns: 1fr; | |
| gap: 18px; | |
| min-height: 0; | |
| padding: 0; | |
| border-bottom: 0; | |
| background: none; | |
| } | |
| .chart-group { | |
| grid-template-columns: minmax(94px, .8fr) minmax(0, 1.5fr); | |
| grid-template-rows: 58px; | |
| gap: 12px; | |
| align-items: center; | |
| } | |
| .chart-label { | |
| grid-column: 1; | |
| grid-row: 1; | |
| min-height: 0; | |
| text-align: left; | |
| } | |
| .chart-bars { | |
| grid-column: 2; | |
| grid-row: 1; | |
| display: grid; | |
| grid-template-columns: 1fr; | |
| grid-template-rows: repeat(2, 1fr); | |
| align-items: center; | |
| justify-items: start; | |
| gap: 7px; | |
| height: 58px; | |
| padding-right: 42px; | |
| } | |
| .chart-bar { | |
| width: var(--score); | |
| height: 100%; | |
| border-radius: 0 5px 5px 0; | |
| } | |
| .chart-bar strong { | |
| left: calc(100% + 7px); | |
| bottom: 50%; | |
| transform: translateY(50%); | |
| font-size: 12px; | |
| } | |
| .metric-definitions { grid-template-columns: 1fr; } | |
| .metric-definition { padding: 20px; } | |
| .length-claim { margin-top: 26px; } | |
| .method-card { min-height: 0; padding: 24px; } | |
| .training-details, .use-grid { grid-template-columns: 1fr; } | |
| .training-item, .use-item { min-height: 0; padding: 24px; } | |
| .use-item b { margin-bottom: 28px; } | |
| .footer-cta { padding: 38px 26px; } | |
| .footer-bottom { flex-direction: column; } | |
| .probe-grid { gap: 30px; margin-top: 24px; padding: 24px; } | |
| .probe-score { flex-direction: column; gap: 24px; } | |
| .confusion .cell { min-height: 86px; } | |
| } | |
| @media (prefers-reduced-motion: reduce) { | |
| html { scroll-behavior: auto; } | |
| *, *::before, *::after { animation-duration: .001ms ; animation-iteration-count: 1 ; transition-duration: .001ms ; } | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <nav class="nav" aria-label="주요 메뉴"> | |
| <a class="brand" href="#top" aria-label="Baragi AI Munche-768"> | |
| <img src="./assets/baragi-ai.png" alt=""> | |
| <span class="brand-copy"><small>Baragi AI</small><strong>Munche<span>-768</span></strong></span> | |
| </a> | |
| <div class="nav-links"> | |
| <a href="#performance">성능</a> | |
| <a href="#distribution">문체 공간</a> | |
| <a href="#training">학습</a> | |
| <a href="#limitations">한계</a> | |
| </div> | |
| <div class="nav-status"><i class="status-dot"></i> EmbeddingGemma 300M</div> | |
| </nav> | |
| <main id="top"> | |
| <header class="hero shell"> | |
| <div class="hero-inner"> | |
| <p class="model-name">Munche-768</p> | |
| <h1> | |
| <span class="line">최초의 한국어 소설 문체 임베딩 모델</span> | |
| </h1> | |
| <p class="hero-copy">각 작가의 고유한 문체를 768차원 벡터로 표현하다.</p> | |
| <div class="hero-meta"> | |
| <span class="chip">EmbeddingGemma 300M</span> | |
| <span class="chip">768 dimensions</span> | |
| </div> | |
| </div> | |
| </header> | |
| <section class="section shell" id="performance"> | |
| <div class="section-head"> | |
| <p class="kicker">Test results</p> | |
| <h2>작가 탐지 정확도 88.2%</h2> | |
| <p class="section-note">42개 test 작품에서 추출한 348개 질의 중, 동일 장르의 다른 작품을 후보로 두고 같은 작가의 작품을 1순위로 찾은 비율입니다.</p> | |
| </div> | |
| <div class="benchmark-chart" role="img" aria-label="원본 EmbeddingGemma와 Munche-768의 네 가지 평가 지표 비교"> | |
| <div class="chart-legend"> | |
| <span><i></i>원본 EmbeddingGemma</span> | |
| <span><i class="tuned"></i>Munche-768</span> | |
| </div> | |
| <div class="chart-plot"> | |
| <div class="chart-group"> | |
| <div class="chart-bars"><div class="chart-bar" style="--score:56.7%"><strong>56.7</strong></div><div class="chart-bar tuned" style="--score:84.0%"><strong>84.0</strong></div></div> | |
| <div class="chart-label">동일 작품 검색<br>mAP@R</div> | |
| </div> | |
| <div class="chart-group"> | |
| <div class="chart-bars"><div class="chart-bar" style="--score:59.6%"><strong>59.6</strong></div><div class="chart-bar tuned" style="--score:79.9%"><strong>79.9</strong></div></div> | |
| <div class="chart-label">동일 작가 검색<br>mAP@R</div> | |
| </div> | |
| <div class="chart-group"> | |
| <div class="chart-bars"><div class="chart-bar" style="--score:22.6%"><strong>22.6</strong></div><div class="chart-bar tuned" style="--score:83.1%"><strong>83.1</strong></div></div> | |
| <div class="chart-label">내용 강건성<br>정확도</div> | |
| </div> | |
| <div class="chart-group"> | |
| <div class="chart-bars"><div class="chart-bar" style="--score:0%"><strong>0.0</strong></div><div class="chart-bar tuned" style="--score:79.5%"><strong>79.5</strong></div></div> | |
| <div class="chart-label">동일 내용 분리<br>정확도</div> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="metric-definitions"> | |
| <div class="metric-definition"><b>동일 작품 검색</b><p>동일 장르 후보군에서 같은 작품의 비중첩 장면을 검색한 mAP@R입니다.</p></div> | |
| <div class="metric-definition"><b>동일 작가 검색</b><p>동일 장르의 다른 작품 중 같은 작가의 작품을 검색한 mAP@R입니다.</p></div> | |
| <div class="metric-definition"><b>내용 강건성</b><p>내용이 가장 유사한 타 작품 4개보다 같은 작품의 다른 장면을 가깝게 배치한 비율입니다.</p></div> | |
| <div class="metric-definition"><b>동일 내용 분리</b><p>동일 내용을 유지한 LLM 재작성본보다 같은 작품의 다른 장면을 가깝게 배치한 비율입니다.</p></div> | |
| </div> | |
| <p class="length-claim">최대 2,048토큰(약 3,600자)만으로 소설의 문체를 구분할 수 있습니다.</p> | |
| <div class="table-wrap additional-metrics"> | |
| <table> | |
| <thead><tr><th>추가 지표</th><th>결과</th><th>평가 조건</th></tr></thead> | |
| <tbody> | |
| <tr><td>동일 작품 Hit@1</td><td>95.6%</td><td>동일 장르 · 1,089 질의</td></tr> | |
| <tr><td>동일 작가 Hit@1</td><td>88.2%</td><td>동일 장르 · 다른 작품 · 348 질의</td></tr> | |
| <tr><td>가명 치환 질의 mAP@R</td><td>81.1%</td><td>가명 질의 · 원문 후보군</td></tr> | |
| <tr><td>문단·문장부호 정규화 mAP@R</td><td>68.3%</td><td>형식 신호 통제</td></tr> | |
| </tbody> | |
| </table> | |
| </div> | |
| </section> | |
| <section class="section shell" id="distribution"> | |
| <div class="section-head"> | |
| <div> | |
| <h2><span class="line">1,113가지 소설의 문체를</span><span class="line">3D 공간에서 확인해보세요.</span></h2> | |
| </div> | |
| <p class="section-note">AI와 인간, 그리고 장르별 분포를 확인해보세요. 전체 데이터는 6개 장르의 인간 소설 783편과 언어 모델 11종의 생성 소설 330편으로 구성됩니다.</p> | |
| </div> | |
| <div class="stage-wrap" id="plot-stage"> | |
| <div class="plot-viewport" id="plot-viewport"> | |
| <canvas id="plot" aria-label="한국어 소설 문체 임베딩 3차원 UMAP 산점도"></canvas> | |
| <div class="stage-top"> | |
| <div class="stage-label"> | |
| <strong>UMAP · 3D</strong> | |
| <span id="visible-count">1,113개 표본</span> | |
| <small class="plot-help">드래그 회전 · 두 손가락 이동 · 핀치 확대</small> | |
| </div> | |
| <div class="stage-actions"> | |
| <button class="icon-button" id="rotate-toggle" type="button" aria-pressed="true">자동 회전</button> | |
| <button class="icon-button" id="reset-view" type="button">시점 초기화</button> | |
| </div> | |
| </div> | |
| <div class="tooltip" id="tooltip"></div> | |
| </div> | |
| <div class="legend" id="legend" aria-label="모델 범례"></div> | |
| <div class="filters" id="genre-filters" aria-label="장르 필터"></div> | |
| </div> | |
| <div class="probe-grid" aria-label="인간 및 AI 변형본 선형 분류 결과"> | |
| <div> | |
| <p class="kicker">문체 판별</p> | |
| <h2>같은 내용, 다른 문체를 분리합니다.</h2> | |
| <p class="section-note">소설의 내용을 유지하고 AI로 새롭게 작성했을 때, 91.3%의 확률로 AI와 인간을 구분해냅니다.</p> | |
| <p class="section-note">단 10분의 추가 훈련만으로 같은 내용에서도 AI와 인간을 구분할 수 있습니다.</p> | |
| <p class="probe-summary">AUROC 0.967 · 균형 정확도 91.3%</p> | |
| <div class="probe-score"> | |
| <div><span>인간 recall</span><strong>95.8%</strong><small>인간 원문 190편</small></div> | |
| <div><span>LLM recall</span><strong>86.8%</strong><small>LLM 재작성본 190편</small></div> | |
| </div> | |
| </div> | |
| <table class="confusion" aria-label="인간 원문과 AI 재작성본 판별 결과"> | |
| <thead><tr><th>실제 원천</th><th scope="col">인간 예측</th><th scope="col">LLM 예측</th></tr></thead> | |
| <tbody> | |
| <tr><th scope="row">인간 원문 190편</th><td class="cell hit">182<small>정분류</small></td><td class="cell miss">8<small>오분류</small></td></tr> | |
| <tr><th scope="row">LLM 재작성본 190편</th><td class="cell miss">25<small>오분류</small></td><td class="cell hit">165<small>정분류</small></td></tr> | |
| </tbody> | |
| </table> | |
| </div> | |
| </section> | |
| <section class="section shell" id="evaluation"> | |
| <div class="performance"> | |
| <div class="performance-copy"> | |
| <p class="kicker">Detailed evaluation</p> | |
| <h2><span class="line">검색·강건성·분리 성능을</span><span class="line">독립 test split에서 검증했습니다.</span></h2> | |
| <div class="prose"> | |
| <p>작품 및 저자 단위로 train·validation·test를 분리했습니다. 검색 평가는 동일 장르 후보군에서 수행했으며, 아래 수치는 체크포인트 선택에 사용되지 않은 test split 결과입니다.</p> | |
| <p><strong>mAP@R</strong>은 정답 수만큼의 상위 결과에서 측정한 평균 정밀도입니다. 반사실 순위 정확도는 동일 내용의 재작성본보다 동일 작품의 다른 장면을 가깝게 배치한 비율입니다.</p> | |
| </div> | |
| </div> | |
| <div> | |
| <div class="table-wrap"> | |
| <table> | |
| <thead><tr><th>테스트 조건</th><th>지표</th><th>결과</th><th>표본</th></tr></thead> | |
| <tbody> | |
| <tr><td>동일 작품·동일 장르 검색</td><td>mAP@R</td><td>0.840</td><td>1,089 질의</td></tr> | |
| <tr><td>동일 저자·작품 간 검색</td><td>mAP@R</td><td>0.799</td><td>348 질의</td></tr> | |
| <tr><td>가명 치환 질의에서 원문 검색</td><td>mAP@R</td><td>0.811</td><td>1,089 질의</td></tr> | |
| <tr><td>내용이 유사한 타 작품 4개 분리</td><td>4개 전부 분리 정확도</td><td>83.1%</td><td>1,216 기준점</td></tr> | |
| <tr><td>동일 내용 LLM 재작성본 순위</td><td>Triplet accuracy</td><td>79.5%</td><td>190쌍</td></tr> | |
| <tr><td>문단·문장부호 정규화 후 검색</td><td>mAP@R</td><td>0.683</td><td>1,089 질의</td></tr> | |
| </tbody> | |
| </table> | |
| </div> | |
| <p class="table-note">작품과 저자 단위 분할을 사용했으며, test split은 체크포인트 선택에 사용하지 않았습니다.</p> | |
| </div> | |
| </div> | |
| </section> | |
| <section class="section shell" id="training"> | |
| <div class="training-centered"> | |
| <p class="kicker">Training configuration</p> | |
| <dl class="spec-list"> | |
| <div class="spec-row"><dt>인간 양성쌍</dt><dd>64,000쌍 · 동일 회차 8K, 근거리 20K, 원거리 24K, 동일 저자·타 작품 12K</dd></div> | |
| <div class="spec-row"><dt>내용 유사 음성</dt><dd>24,000 기준점 · Kiwi 형태소 TF-IDF 기반 타 작품 후보</dd></div> | |
| <div class="spec-row"><dt>반사실 쌍</dt><dd>1,306쌍 · train 913, validation 203, test 190</dd></div> | |
| <div class="spec-row"><dt>기반 모델</dt><dd>google/embeddinggemma-300m</dd></div> | |
| <div class="spec-row"><dt>적응 방식</dt><dd>Rank 32 LoRA · q/k/v/o 및 MLP projection</dd></div> | |
| <div class="spec-row"><dt>학습 파라미터</dt><dd>8,355,840 · 기반 모델, pooling, dense projection 고정</dd></div> | |
| <div class="spec-row"><dt>출력</dt><dd>Mean pooling · Dense projection · 768D · L2 normalization</dd></div> | |
| <div class="spec-row"><dt>입력 길이</dt><dd>최대 2,048 tokens</dd></div> | |
| </dl> | |
| <p class="training-note">동일 작품 및 동일 저자의 텍스트를 양성 표본으로, 내용 유사 타 작품과 동일 내용 LLM 재작성본을 명시적 음성 표본으로 구성했습니다. 한국어 입력은 원문 25%, 가명 치환 50%, 고유명사 마스킹 25% 비율로 노출했습니다.</p> | |
| </div> | |
| </section> | |
| <section class="section shell" id="uses"> | |
| <div class="section-head"> | |
| <div> | |
| <h2><span class="line">검색부터 코퍼스 진단까지,</span><span class="line">문체를 수치로 비교합니다.</span></h2> | |
| </div> | |
| <p class="section-note">문체 검색, 저자 간 일관성 분석, 코퍼스 분포 진단과 생성 모델 비교에 사용할 수 있습니다.</p> | |
| </div> | |
| <div class="use-grid"> | |
| <article class="use-item"><b>01</b><h3>문체 유사도 검색</h3><p>입력 텍스트와 문체적으로 유사한 텍스트를 검색합니다.</p></article> | |
| <article class="use-item"><b>02</b><h3>작가 간 비교</h3><p>서로 다른 작품 사이의 문체적 일관성을 분석합니다.</p></article> | |
| <article class="use-item"><b>03</b><h3>장르 조건부 군집 분석</h3><p>장르를 통제한 상태에서 작품 간 문체 거리와 군집 구조를 분석합니다.</p></article> | |
| <article class="use-item"><b>04</b><h3>코퍼스 분포 진단</h3><p>인간·생성 코퍼스의 문체 분포, 편중 및 이상치를 점검합니다.</p></article> | |
| <article class="use-item"><b>05</b><h3>생성 모델 비교</h3><p>언어 모델별 소설 출력의 문체 분포를 비교합니다.</p></article> | |
| </div> | |
| </section> | |
| <section class="section shell" id="limitations"> | |
| <div class="section-head"> | |
| <p class="kicker">Limitations</p> | |
| </div> | |
| <div class="principles"> | |
| <article class="principle"> | |
| <b>01 · 형식 신호 의존</b> | |
| <p>문단 경계와 문장부호를 통일하면 동일 작품 검색 mAP@R이 0.840에서 0.683으로 하락합니다. 줄바꿈과 부호 사용이 문체 임베딩의 일부를 차지합니다.</p> | |
| </article> | |
| <article class="principle"> | |
| <b>02 · 한국어 소설 중심 평가</b> | |
| <p>PAN21 저자 검증 AUROC는 0.895이나 PAN 삼중항은 학습 자료에 포함되었습니다. 미학습 영어 문체 속성을 평가한 SynthSTEL 정확도는 1.9%로 일반화 성능이 확인되지 않았습니다.</p> | |
| </article> | |
| <article class="principle"> | |
| <b>03 · LLM 원천 판별 범위</b> | |
| <p>AUROC 0.967은 지정된 4종의 생성 모델이 특정 절차로 재작성한 표본에 대한 결과이며, 새로운 모델이나 다른 생성 조건의 AI 텍스트 탐지 성능을 보장하지 않습니다.</p> | |
| </article> | |
| </div> | |
| </section> | |
| </main> | |
| <footer class="footer shell"> | |
| <div class="footer-bottom"> | |
| <span>Baragi AI | Munche-768</span> | |
| <span class="footer-links"> | |
| <a href="https://huggingface.co/Baragi-AI/Munche-768" target="_blank" rel="noreferrer">Hugging Face 모델</a> | |
| <a href="#top">맨 위로</a> | |
| </span> | |
| </div> | |
| </footer> | |
| <script src="./points-g2.js"></script> | |
| <script src="https://cdn.jsdelivr.net/npm/gsap@3.13.0/dist/gsap.min.js"></script> | |
| <script src="https://cdn.jsdelivr.net/npm/gsap@3.13.0/dist/ScrollTrigger.min.js"></script> | |
| <script> | |
| (() => { | |
| const dataset = window.STYLE_EMBEDDING_DATA; | |
| if (!dataset || !Array.isArray(dataset.points)) return; | |
| const MODEL_LABELS = { | |
| human: '인간 소설', | |
| 'google/gemini-3.5-flash': 'Gemini 3.5 Flash', | |
| 'unsloth/gemma-4-12b-it': 'Gemma 4 12B', | |
| 'google/gemma-4-31b-it': 'Gemma 4 31B', | |
| 'anthropic/claude-sonnet-5': 'Claude Sonnet 5', | |
| 'anthropic/claude-sonnet-5-low': 'Claude Sonnet 5 Low', | |
| 'deepseek/deepseek-v3.2': 'DeepSeek v3.2', | |
| 'deepseek/deepseek-v4-pro-cloud': 'DeepSeek V4 Pro', | |
| 'openai/gpt-5.4-mini': 'GPT-5.4 mini', | |
| 'openai/gpt-5.6-sol-high': 'GPT 5.6 Sol High', | |
| 'z-ai/glm-5.2-cloud': 'GLM 5.2', | |
| 'minimax/minimax-m3-cloud': 'MiniMax M3' | |
| }; | |
| const MODEL_COLORS = { | |
| human: '#ffffff', | |
| 'anthropic/claude-sonnet-5': '#ff704d', | |
| 'anthropic/claude-sonnet-5-low': '#ff9b7a', | |
| 'deepseek/deepseek-v3.2': '#5b8cff', | |
| 'deepseek/deepseek-v4-pro-cloud': '#88adff', | |
| 'google/gemini-3.5-flash': '#36cbb5', | |
| 'google/gemma-4-31b-it': '#00efb3', | |
| 'minimax/minimax-m3-cloud': '#3b75db', | |
| 'openai/gpt-5.4-mini': '#ff9a3d', | |
| 'openai/gpt-5.6-sol-high': '#ffc05a', | |
| 'unsloth/gemma-4-12b-it': '#78dbc8', | |
| 'z-ai/glm-5.2-cloud': '#7ca7ff' | |
| }; | |
| const GENRE_LABELS = { | |
| all: '전체', fantasy: '판타지', hyunpan: '현대판타지', muhyup: '무협', | |
| mystery: '미스터리', rofan: '로맨스판타지', sf_apoc: 'SF·아포칼립스', | |
| sports_ent: '스포츠·연예', game: '게임', drama: '드라마', apoc: '아포칼립스', | |
| althist: '대체역사', sf: 'SF', etc: '기타' | |
| }; | |
| const canvas = document.querySelector('#plot'); | |
| const stage = document.querySelector('#plot-stage'); | |
| const viewport = document.querySelector('#plot-viewport'); | |
| const ctx = canvas.getContext('2d', { alpha: true }); | |
| const tooltip = document.querySelector('#tooltip'); | |
| const visibleCount = document.querySelector('#visible-count'); | |
| const reduceMotion = matchMedia('(prefers-reduced-motion: reduce)').matches; | |
| const enabledModels = new Set(Object.keys(MODEL_LABELS)); | |
| let selectedGenre = 'all'; | |
| let autoRotate = !reduceMotion; | |
| let rotationX = -0.22; | |
| let rotationY = -0.56; | |
| let cameraDistance = 5.1; | |
| let panX = 0; | |
| let panY = 0; | |
| let dragging = false; | |
| let lastX = 0; | |
| let lastY = 0; | |
| let moved = false; | |
| let projected = []; | |
| let lastTime = performance.now(); | |
| let cssWidth = 0; | |
| let cssHeight = 0; | |
| const activePointers = new Map(); | |
| let gestureCenter = null; | |
| let gestureDistance = 0; | |
| const points = dataset.points.map((point, index) => ({ ...point, index })); | |
| const presentModels = new Set(points.map(point => point.source)); | |
| const models = Object.keys(MODEL_LABELS).filter(model => presentModels.has(model)); | |
| const genres = [...new Set(points.map(p => p.genre))].sort((a, b) => (GENRE_LABELS[a] || a).localeCompare(GENRE_LABELS[b] || b, 'ko')); | |
| function countBy(items, key) { | |
| return items.reduce((map, item) => map.set(item[key], (map.get(item[key]) || 0) + 1), new Map()); | |
| } | |
| function buildLegend() { | |
| const counts = countBy(points, 'source'); | |
| const legend = document.querySelector('#legend'); | |
| models.forEach(model => { | |
| const button = document.createElement('button'); | |
| button.type = 'button'; | |
| button.className = 'legend-button'; | |
| button.dataset.model = model; | |
| button.innerHTML = `<i class="swatch" style="--swatch:${MODEL_COLORS[model] || '#f2f2f2'}"></i><span class="legend-name">${MODEL_LABELS[model] || model}</span><span class="legend-count">${counts.get(model)}</span>`; | |
| button.addEventListener('click', () => { | |
| if (enabledModels.has(model)) enabledModels.delete(model); else enabledModels.add(model); | |
| button.classList.toggle('off', !enabledModels.has(model)); | |
| updateVisibleCount(); | |
| }); | |
| legend.appendChild(button); | |
| }); | |
| } | |
| function buildFilters() { | |
| const container = document.querySelector('#genre-filters'); | |
| ['all', ...genres].forEach(genre => { | |
| const button = document.createElement('button'); | |
| button.type = 'button'; | |
| button.className = `filter-button${genre === 'all' ? ' active' : ''}`; | |
| button.textContent = GENRE_LABELS[genre] || genre; | |
| button.addEventListener('click', () => { | |
| selectedGenre = genre; | |
| container.querySelectorAll('button').forEach(item => item.classList.toggle('active', item === button)); | |
| updateVisibleCount(); | |
| }); | |
| container.appendChild(button); | |
| }); | |
| } | |
| function isVisible(point) { | |
| return enabledModels.has(point.source) && (selectedGenre === 'all' || point.genre === selectedGenre); | |
| } | |
| function updateVisibleCount() { | |
| const count = points.filter(isVisible).length; | |
| visibleCount.textContent = `소설 분포 · ${count.toLocaleString('ko-KR')}개 표본`; | |
| } | |
| function resize() { | |
| const rect = viewport.getBoundingClientRect(); | |
| const dpr = Math.min(devicePixelRatio || 1, 2); | |
| cssWidth = Math.max(1, rect.width); | |
| cssHeight = Math.max(1, rect.height); | |
| canvas.width = Math.round(cssWidth * dpr); | |
| canvas.height = Math.round(cssHeight * dpr); | |
| canvas.style.width = `${cssWidth}px`; | |
| canvas.style.height = `${cssHeight}px`; | |
| ctx.setTransform(dpr, 0, 0, dpr, 0, 0); | |
| } | |
| function rotatePoint([x, y, z]) { | |
| const cosY = Math.cos(rotationY), sinY = Math.sin(rotationY); | |
| const x1 = x * cosY - z * sinY; | |
| const z1 = x * sinY + z * cosY; | |
| const cosX = Math.cos(rotationX), sinX = Math.sin(rotationX); | |
| return [x1, y * cosX - z1 * sinX, y * sinX + z1 * cosX]; | |
| } | |
| function project(coords) { | |
| const [x, y, z] = rotatePoint(coords); | |
| const depth = Math.max(1.7, cameraDistance - z); | |
| const focal = Math.min(cssWidth, cssHeight) * 1.55; | |
| const scale = focal / depth; | |
| return { x: cssWidth / 2 + panX + x * scale, y: cssHeight / 2 + panY - y * scale, z, depth, perspective: cameraDistance / depth }; | |
| } | |
| function drawFrame() { | |
| const bounds = 1.9; | |
| const corners = []; | |
| for (const x of [-bounds, bounds]) for (const y of [-bounds, bounds]) for (const z of [-bounds, bounds]) corners.push([x, y, z]); | |
| const projectedCorners = corners.map(project); | |
| const edges = []; | |
| for (let i = 0; i < corners.length; i++) { | |
| for (let j = i + 1; j < corners.length; j++) { | |
| const diff = [0, 1, 2].filter(axis => corners[i][axis] !== corners[j][axis]).length; | |
| if (diff === 1) edges.push([i, j]); | |
| } | |
| } | |
| ctx.save(); | |
| ctx.strokeStyle = 'rgba(255,255,255,.08)'; | |
| ctx.lineWidth = 1; | |
| edges.forEach(([a, b]) => { | |
| ctx.beginPath(); | |
| ctx.moveTo(projectedCorners[a].x, projectedCorners[a].y); | |
| ctx.lineTo(projectedCorners[b].x, projectedCorners[b].y); | |
| ctx.stroke(); | |
| }); | |
| ctx.restore(); | |
| } | |
| function render(now) { | |
| const delta = Math.min(40, now - lastTime); | |
| lastTime = now; | |
| if (autoRotate && !dragging) rotationY += delta * 0.000035; | |
| ctx.clearRect(0, 0, cssWidth, cssHeight); | |
| drawFrame(); | |
| const mainProjected = points.filter(isVisible).map(point => ({ point, ...project(point.xyz) })); | |
| projected = mainProjected.sort((a, b) => b.depth - a.depth); | |
| function drawLayer(items) { | |
| items.sort((a, b) => b.depth - a.depth).forEach(item => { | |
| const isHuman = item.point.source === 'human'; | |
| const radius = Math.max(isHuman ? 1.05 : 2.2, (isHuman ? 1.85 : 3.6) * item.perspective); | |
| const color = MODEL_COLORS[item.point.source] || '#f2f2f2'; | |
| ctx.save(); | |
| if (!isHuman) { | |
| const halo = ctx.createRadialGradient(item.x, item.y, 0, item.x, item.y, radius * 3.1); | |
| halo.addColorStop(0, color + '42'); | |
| halo.addColorStop(1, color + '00'); | |
| ctx.fillStyle = halo; | |
| ctx.beginPath(); | |
| ctx.arc(item.x, item.y, radius * 3.1, 0, Math.PI * 2); | |
| ctx.fill(); | |
| } | |
| ctx.globalAlpha = isHuman ? Math.min(.86, .42 + item.perspective * .26) : .96; | |
| ctx.fillStyle = color; | |
| ctx.beginPath(); | |
| ctx.arc(item.x, item.y, radius, 0, Math.PI * 2); | |
| ctx.fill(); | |
| if (!isHuman) { | |
| ctx.globalAlpha = .72; | |
| ctx.strokeStyle = '#ffffff'; | |
| ctx.lineWidth = .55; | |
| ctx.stroke(); | |
| } | |
| ctx.restore(); | |
| item.radius = radius; | |
| }); | |
| } | |
| drawLayer(mainProjected); | |
| requestAnimationFrame(render); | |
| } | |
| function pointerPosition(event) { | |
| const rect = canvas.getBoundingClientRect(); | |
| return { x: event.clientX - rect.left, y: event.clientY - rect.top }; | |
| } | |
| function updateTooltip(event) { | |
| if (dragging || moved) { tooltip.style.display = 'none'; return; } | |
| const pos = pointerPosition(event); | |
| let hit = null; | |
| let best = Infinity; | |
| for (let i = projected.length - 1; i >= 0; i--) { | |
| const item = projected[i]; | |
| const distance = Math.hypot(item.x - pos.x, item.y - pos.y); | |
| if (distance < Math.max(8, item.radius + 4) && distance < best) { hit = item; best = distance; } | |
| } | |
| if (!hit) { tooltip.style.display = 'none'; return; } | |
| const label = MODEL_LABELS[hit.point.source] || hit.point.source; | |
| const detail = GENRE_LABELS[hit.point.genre] || hit.point.genre; | |
| tooltip.innerHTML = `<b>${label}</b><span>${detail}</span>`; | |
| tooltip.style.display = 'block'; | |
| const left = Math.min(cssWidth - 190, Math.max(12, pos.x + 16)); | |
| const top = Math.min(cssHeight - 80, Math.max(12, pos.y + 16)); | |
| tooltip.style.transform = `translate(${left}px, ${top}px)`; | |
| } | |
| const clampDistance = value => Math.max(3.1, Math.min(8.2, value)); | |
| const clampPan = () => { | |
| panX = Math.max(-cssWidth * .75, Math.min(cssWidth * .75, panX)); | |
| panY = Math.max(-cssHeight * .75, Math.min(cssHeight * .75, panY)); | |
| }; | |
| const pointerGeometry = () => { | |
| const pointers = [...activePointers.values()]; | |
| if (pointers.length < 2) return null; | |
| const [a, b] = pointers; | |
| return { | |
| center: { x: (a.x + b.x) / 2, y: (a.y + b.y) / 2 }, | |
| distance: Math.hypot(a.x - b.x, a.y - b.y) | |
| }; | |
| }; | |
| const endPointer = event => { | |
| activePointers.delete(event.pointerId); | |
| dragging = activePointers.size > 0; | |
| const remaining = activePointers.values().next().value; | |
| if (remaining) { | |
| lastX = remaining.x; | |
| lastY = remaining.y; | |
| } | |
| gestureCenter = null; | |
| gestureDistance = 0; | |
| if (canvas.hasPointerCapture(event.pointerId)) canvas.releasePointerCapture(event.pointerId); | |
| }; | |
| canvas.addEventListener('pointerdown', event => { | |
| activePointers.set(event.pointerId, { x: event.clientX, y: event.clientY }); | |
| dragging = true; | |
| moved = false; | |
| lastX = event.clientX; | |
| lastY = event.clientY; | |
| const geometry = pointerGeometry(); | |
| if (geometry) { | |
| gestureCenter = geometry.center; | |
| gestureDistance = geometry.distance; | |
| } | |
| canvas.setPointerCapture(event.pointerId); | |
| tooltip.style.display = 'none'; | |
| }); | |
| canvas.addEventListener('pointermove', event => { | |
| if (!activePointers.has(event.pointerId)) { | |
| updateTooltip(event); | |
| return; | |
| } | |
| activePointers.set(event.pointerId, { x: event.clientX, y: event.clientY }); | |
| const geometry = pointerGeometry(); | |
| if (geometry) { | |
| if (gestureCenter) { | |
| panX += geometry.center.x - gestureCenter.x; | |
| panY += geometry.center.y - gestureCenter.y; | |
| cameraDistance = clampDistance(cameraDistance - (geometry.distance - gestureDistance) * .008); | |
| clampPan(); | |
| } | |
| gestureCenter = geometry.center; | |
| gestureDistance = geometry.distance; | |
| moved = true; | |
| return; | |
| } | |
| const dx = event.clientX - lastX, dy = event.clientY - lastY; | |
| if (Math.abs(dx) + Math.abs(dy) > 1) moved = true; | |
| if (event.shiftKey) { | |
| panX += dx; | |
| panY += dy; | |
| clampPan(); | |
| } else { | |
| rotationY -= dx * .006; | |
| rotationX += dy * .006; | |
| } | |
| lastX = event.clientX; | |
| lastY = event.clientY; | |
| }); | |
| canvas.addEventListener('pointerup', endPointer); | |
| canvas.addEventListener('pointercancel', endPointer); | |
| canvas.addEventListener('pointerleave', () => { if (!dragging) tooltip.style.display = 'none'; }); | |
| canvas.addEventListener('wheel', event => { | |
| event.preventDefault(); | |
| if (event.ctrlKey || event.metaKey) { | |
| cameraDistance = clampDistance(cameraDistance + event.deltaY * .003); | |
| } else { | |
| panX -= event.deltaX; | |
| panY -= event.deltaY; | |
| clampPan(); | |
| } | |
| }, { passive: false }); | |
| document.querySelector('#rotate-toggle').addEventListener('click', event => { | |
| autoRotate = !autoRotate; | |
| event.currentTarget.setAttribute('aria-pressed', String(autoRotate)); | |
| }); | |
| document.querySelector('#reset-view').addEventListener('click', () => { | |
| rotationX = -0.22; rotationY = -0.56; cameraDistance = 5.1; panX = 0; panY = 0; | |
| }); | |
| new ResizeObserver(resize).observe(viewport); | |
| buildLegend(); | |
| buildFilters(); | |
| updateVisibleCount(); | |
| resize(); | |
| requestAnimationFrame(render); | |
| if (window.gsap && window.ScrollTrigger && !reduceMotion) { | |
| gsap.registerPlugin(ScrollTrigger); | |
| gsap.from('.hero .model-name, .hero h1 .line, .hero-copy, .hero-meta', { | |
| y: 28, opacity: 0, duration: 1.05, stagger: .09, ease: 'power3.out' | |
| }); | |
| gsap.from('.stage-wrap', { | |
| scrollTrigger: { trigger: '.stage-wrap', start: 'top 86%', once: true }, | |
| scale: .965, opacity: 0, duration: 1.15, ease: 'power3.out' | |
| }); | |
| gsap.utils.toArray('.training-item, .use-item').forEach((card, index) => { | |
| gsap.from(card, { | |
| scrollTrigger: { trigger: card, start: 'top 90%', once: true }, | |
| y: 42 + index * 3, opacity: 0, duration: .8, ease: 'power2.out' | |
| }); | |
| }); | |
| } | |
| })(); | |
| </script> | |
| </body> | |
| </html> | |