<!doctype html>
<html lang="ko">
<head>
  <meta charset="utf-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1" />
  <title>PLAI Chain Source</title>
  <link rel="icon" type="image/png" href="/images/plai-symbol-mark-v1.png" />
  <link rel="apple-touch-icon" sizes="180x180" href="/images/plai-icon-180.png" />
  <link rel="manifest" href="/site.webmanifest" />
  <meta name="theme-color" content="#087b73" />
  <meta name="apple-mobile-web-app-capable" content="yes" />
  <meta name="apple-mobile-web-app-title" content="PLAI Chain" />
  <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
  <style>
    * { box-sizing: border-box; }
    body {
      margin: 0;
      color: #07111f;
      background: #edf5f8;
      font-family: Arial, "Noto Sans KR", sans-serif;
      letter-spacing: 0;
    }
    a { color: inherit; text-decoration: none; }
    .hero {
      color: #fff;
      background: linear-gradient(135deg, #087b73 0%, #1d64d8 72%, #2444b8 100%);
      padding: 34px 0 48px;
    }
    .wrap {
      width: min(1180px, calc(100% - 32px));
      margin: 0 auto;
    }
    .nav {
      display: flex;
      justify-content: space-between;
      gap: 12px;
      align-items: center;
      margin-bottom: 34px;
    }
    .brand {
      display: inline-flex;
      align-items: center;
      gap: 10px;
      font-size: 22px;
      font-weight: 900;
    }
    .brand-symbol {
      width: 42px;
      height: 42px;
      border-radius: 9px;
      border: 1px solid rgba(255,255,255,.3);
      object-fit: cover;
      flex: 0 0 auto;
    }
    .actions {
      display: flex;
      gap: 8px;
      flex-wrap: wrap;
      justify-content: flex-end;
    }
    .btn {
      display: inline-flex;
      align-items: center;
      justify-content: center;
      min-height: 42px;
      padding: 10px 15px;
      border-radius: 8px;
      border: 1px solid rgba(255,255,255,.28);
      background: rgba(255,255,255,.12);
      color: #fff;
      font-weight: 900;
    }
    .btn.primary {
      background: #fff;
      color: #07111f;
      border-color: #fff;
    }
    .btn.dark {
      background: #07111f;
      color: #fff;
      border-color: #07111f;
    }
    h1 {
      margin: 0;
      max-width: 980px;
      font-size: clamp(34px, 5vw, 58px);
      line-height: 1.08;
      font-weight: 950;
    }
    .lead {
      max-width: 810px;
      margin: 16px 0 0;
      color: #def5ff;
      font-size: 18px;
      line-height: 1.62;
      font-weight: 700;
    }
    .hero-stats {
      display: grid;
      grid-template-columns: repeat(4, minmax(0, 1fr));
      gap: 10px;
      margin-top: 26px;
    }
    .hero-stat {
      border: 1px solid rgba(255,255,255,.22);
      border-radius: 8px;
      padding: 14px;
      background: rgba(0,0,0,.16);
    }
    .hero-stat span {
      display: block;
      color: #bdeeff;
      font-size: 13px;
      font-weight: 800;
    }
    .hero-stat strong {
      display: block;
      margin-top: 6px;
      font-size: 24px;
      word-break: break-word;
    }
    main {
      padding: 26px 0 58px;
    }
    .grid {
      display: grid;
      grid-template-columns: minmax(0, 1.1fr) minmax(330px, .9fr);
      gap: 14px;
      align-items: start;
    }
    .panel {
      border: 1px solid #d8e3ee;
      background: #fff;
      border-radius: 8px;
      padding: 20px;
    }
    .panel h2 {
      margin: 0 0 12px;
      font-size: 24px;
      line-height: 1.2;
    }
    .panel h3 {
      margin: 0 0 10px;
      font-size: 17px;
    }
    .panel p,
    .panel li {
      color: #506178;
      line-height: 1.62;
      font-size: 15px;
    }
    .download {
      background: #071421;
      color: #fff;
      border-color: #071421;
      position: sticky;
      top: 16px;
    }
    .download p {
      color: #bfd0e2;
    }
    .download .btn {
      width: 100%;
      margin-top: 10px;
    }
    .meta {
      display: grid;
      gap: 8px;
      margin-top: 14px;
    }
    .meta div {
      display: flex;
      justify-content: space-between;
      gap: 12px;
      padding: 10px 0;
      border-bottom: 1px solid rgba(255,255,255,.14);
      color: #bfd0e2;
      font-size: 13px;
    }
    .meta strong {
      color: #fff;
      text-align: right;
      word-break: break-all;
    }
    .two-col {
      display: grid;
      grid-template-columns: repeat(2, minmax(0, 1fr));
      gap: 14px;
      margin-top: 14px;
    }
    .release {
      display: grid;
      gap: 10px;
      margin-top: 14px;
    }
    .release-row {
      display: grid;
      grid-template-columns: 120px 1fr;
      gap: 12px;
      border: 1px solid #d8e3ee;
      border-radius: 8px;
      padding: 14px;
      background: #fbfdff;
    }
    .tag {
      display: inline-flex;
      width: fit-content;
      align-items: center;
      border-radius: 999px;
      padding: 5px 9px;
      background: #dffaf0;
      color: #006b57;
      font-size: 12px;
      font-weight: 900;
    }
    code {
      background: #eef5f8;
      border: 1px solid #d8e3ee;
      border-radius: 6px;
      padding: 2px 6px;
      color: #0f513e;
      word-break: break-word;
    }
    .warning {
      border-color: #e4bd49;
      background: #fff8db;
    }
    .warning p,
    .warning li {
      color: #6f4e00;
      font-weight: 700;
    }
    .file-table {
      width: 100%;
      border-collapse: collapse;
      margin-top: 12px;
      table-layout: fixed;
    }
    .file-table th,
    .file-table td {
      padding: 11px 10px;
      border-bottom: 1px solid #e3ebf3;
      text-align: left;
      vertical-align: top;
      font-size: 13px;
    }
    .file-table th {
      color: #506178;
      background: #f3f8fb;
      font-weight: 900;
    }
    .file-table td:first-child {
      width: 72%;
      word-break: break-all;
    }
    .muted {
      color: #617188;
      font-size: 13px;
      line-height: 1.55;
    }
    .section {
      margin-top: 14px;
    }
    @media (max-width: 900px) {
      .nav { align-items: flex-start; flex-direction: column; }
      .actions { justify-content: flex-start; }
      .grid,
      .two-col,
      .hero-stats { grid-template-columns: 1fr; }
      .download { position: static; }
      .release-row { grid-template-columns: 1fr; }
    }
  </style>
</head>
<body>
  <%
    const manifest = sourceManifest || {};
    const hasSource = !!manifest.ok;
    const releases = Array.isArray(manifest.releases) ? manifest.releases.slice(0, 6) : [];
    const fileIndex = Array.isArray(manifest.fileIndex) ? manifest.fileIndex.slice(0, 80) : [];
    const notes = Array.isArray(manifest.releaseNotes) ? manifest.releaseNotes : [];
    const topDirs = Array.isArray(manifest.topDirectories) ? manifest.topDirectories : [];
    const excluded = manifest.excluded || {};
    const excludedDirs = Array.isArray(excluded.directories) ? excluded.directories : [];
    const excludedFiles = Array.isArray(excluded.files) ? excluded.files : [];
    const excludedPatterns = Array.isArray(excluded.patterns) ? excluded.patterns : [];
    const formatBytes = (value) => {
      const bytes = Number(value || 0);
      if (bytes >= 1024 * 1024) return `${(bytes / 1024 / 1024).toFixed(2)} MB`;
      if (bytes >= 1024) return `${Math.round(bytes / 1024)} KB`;
      return `${bytes} B`;
    };
    const formatDate = (value) => value ? new Date(value).toLocaleString('ko-KR', { timeZone: 'Asia/Seoul' }) : '-';
  %>
  <section class="hero">
    <div class="wrap">
      <div class="nav">
        <a class="brand" href="/source"><img class="brand-symbol" src="/images/plai-symbol-mark-v1.png" alt="PLAI Chain symbol" />PLAI Chain Source</a>
        <div class="actions">
          <a class="btn" href="/">Chain Home</a>
          <a class="btn" href="/explorer">Explorer</a>
          <a class="btn" href="/listing">Listing</a>
          <a class="btn" href="/whitepaper/ko">백서</a>
          <a class="btn" href="https://aisfor.kr/home">PLAIFOR 홈</a>
        </div>
      </div>
      <h1>GitHub 없이도 검증 가능한 PLAI Chain 공개 소스 포털입니다.</h1>
      <p class="lead">
        민감정보와 운영 데이터를 제외한 소스 패키지, 버전 기록, 변경 내역, 파일 인덱스, SHA256 검증값을 이 사이트에서 직접 제공합니다.
      </p>
      <div class="hero-stats">
        <div class="hero-stat"><span>최신 버전</span><strong><%= hasSource ? manifest.version : '-' %></strong></div>
        <div class="hero-stat"><span>공개 파일</span><strong><%= hasSource ? Number(manifest.fileCount || 0).toLocaleString('ko-KR') : '0' %></strong></div>
        <div class="hero-stat"><span>공개 폴더</span><strong><%= hasSource ? Number(manifest.directoryCount || 0).toLocaleString('ko-KR') : '0' %></strong></div>
        <div class="hero-stat"><span>패키지 크기</span><strong><%= hasSource ? formatBytes(manifest.sizeBytes) : '-' %></strong></div>
      </div>
    </div>
  </section>

  <main>
    <div class="wrap grid">
      <div>
        <section class="panel">
          <span class="tag">Latest Release</span>
          <h2 style="margin-top:12px;">공개 범위</h2>
          <p>
            PLAI Chain의 서버 코드, 라우트, 서비스, 모델, 화면, 계약/원장 관련 코드, 문서, 백서, 실행 예제를 공개합니다.
            운영 키, 지갑 private key, 사용자 데이터, DB, 로그, 업로드 파일, 의존성 폴더는 공개 패키지에서 제외합니다.
          </p>
          <div class="two-col">
            <div class="panel">
              <h3>포함</h3>
              <ul>
                <li><code>routes</code>, <code>services</code>, <code>models</code>, <code>middleware</code></li>
                <li><code>views</code>, <code>public</code>, <code>contracts</code>, <code>lib</code></li>
                <li><code>docs</code>, <code>README.md</code>, <code>.env.example</code></li>
                <li><code>package.json</code>, <code>package-lock.json</code></li>
              </ul>
            </div>
            <div class="panel warning">
              <h3>제외</h3>
              <ul>
                <li><code>.env</code>, API secret, 관리자 비밀번호</li>
                <li>private key, recovery material, wallet key</li>
                <li>DB 데이터, 로그, 사용자 업로드, <code>node_modules</code></li>
                <li><code>.git</code>, 백업 파일, 압축 파일</li>
              </ul>
            </div>
          </div>
        </section>

        <section class="panel section">
          <h2>릴리즈 노트</h2>
          <% if (notes.length) { %>
            <ul>
              <% notes.forEach((note) => { %>
                <li><%= note %></li>
              <% }) %>
            </ul>
          <% } else { %>
            <p>아직 릴리즈 노트가 없습니다.</p>
          <% } %>
        </section>

        <section class="panel section">
          <h2>버전 기록</h2>
          <% if (releases.length) { %>
            <div class="release">
              <% releases.forEach((release, index) => { %>
                <div class="release-row">
                  <div>
                    <span class="tag"><%= index === 0 ? 'latest' : 'version' %></span>
                    <p class="muted"><%= formatDate(release.generatedAt) %></p>
                  </div>
                  <div>
                    <h3><%= release.version %></h3>
                    <p class="muted"><%= release.package %> · <%= formatBytes(release.sizeBytes) %> · <%= Number(release.fileCount || 0).toLocaleString('ko-KR') %> files</p>
                    <code><%= release.sha256 %></code>
                  </div>
                </div>
              <% }) %>
            </div>
          <% } else { %>
            <p>공개된 버전 기록이 없습니다.</p>
          <% } %>
        </section>

        <section class="panel section">
          <h2>파일 인덱스</h2>
          <p>
            ZIP을 다운로드하기 전에 공개 패키지에 포함된 주요 파일을 확인할 수 있습니다.
            <% if (manifest.fileIndexTruncated) { %>화면에는 앞쪽 80개만 표시하고 전체 목록은 manifest에 포함됩니다.<% } %>
          </p>
          <% if (topDirs.length) { %>
            <p class="muted">상위 폴더: <%= topDirs.join(', ') %></p>
          <% } %>
          <% if (fileIndex.length) { %>
            <table class="file-table">
              <thead>
                <tr><th>파일</th><th>크기</th><th>수정 시간</th></tr>
              </thead>
              <tbody>
                <% fileIndex.forEach((file) => { %>
                  <tr>
                    <td><code><%= file.path %></code></td>
                    <td><%= formatBytes(file.sizeBytes) %></td>
                    <td><%= formatDate(file.modifiedAt) %></td>
                  </tr>
                <% }) %>
              </tbody>
            </table>
          <% } else { %>
            <p>아직 파일 인덱스가 없습니다.</p>
          <% } %>
        </section>

        <section class="panel section">
          <h2>라이선스와 검토 조건</h2>
          <p>
            <strong><%= manifest.license?.name || 'PLAI Chain Public Source Review License' %></strong>
          </p>
          <p><%= manifest.license?.summary || '공개 검토와 감사 목적의 열람을 허용하며, 무단 상업 복제와 민감정보 추출 시도는 허용하지 않습니다.' %></p>
        </section>
      </div>

      <aside class="panel download">
        <h2>다운로드</h2>
        <% if (hasSource) { %>
          <p>최신 공개 소스 패키지를 받을 수 있습니다. 다운로드 후 SHA256 값으로 위변조 여부를 확인할 수 있습니다.</p>
          <a class="btn primary" href="/source-release/plai-chain-source-latest.zip">최신 소스 ZIP 다운로드</a>
          <a class="btn" href="/source-release/<%= encodeURIComponent(manifest.package) %>">버전 ZIP 다운로드</a>
          <a class="btn" href="/source-release/manifest.json">manifest 보기</a>
          <div class="meta">
            <div><span>버전</span><strong><%= manifest.version %></strong></div>
            <div><span>파일</span><strong><%= manifest.package %></strong></div>
            <div><span>크기</span><strong><%= formatBytes(manifest.sizeBytes) %></strong></div>
            <div><span>파일 수</span><strong><%= Number(manifest.fileCount || 0).toLocaleString('ko-KR') %></strong></div>
            <div><span>SHA256</span><strong><%= manifest.sha256 %></strong></div>
            <div><span>생성</span><strong><%= formatDate(manifest.generatedAt) %></strong></div>
          </div>
        <% } else { %>
          <p>아직 공개 소스 패키지가 생성되지 않았습니다. 운영자가 빌드 스크립트를 실행하면 다운로드가 활성화됩니다.</p>
        <% } %>
      </aside>

      <section class="panel section" style="grid-column:1 / -1;">
        <h2>민감정보 제외 규칙</h2>
        <div class="two-col">
          <div>
            <h3>제외 폴더</h3>
            <p class="muted"><%= excludedDirs.length ? excludedDirs.join(', ') : '-' %></p>
          </div>
          <div>
            <h3>제외 파일/패턴</h3>
            <p class="muted"><%= excludedFiles.concat(excludedPatterns).join(', ') || '-' %></p>
          </div>
        </div>
      </section>
    </div>
  </main>
</body>
</html>
