ginipick commited on
Commit
147ca0f
Β·
verified Β·
1 Parent(s): fa9425b

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +358 -418
app.py CHANGED
@@ -1569,7 +1569,7 @@ HTML = """
1569
  </div>
1570
  </section>
1571
 
1572
- <script>
1573
  let projects=[], fb=null;
1574
  const grid=document.getElementById('grid'), viewer=document.getElementById('viewer');
1575
  pdfjsLib.GlobalWorkerOptions.workerSrc='/static/pdf.worker.js';
@@ -1584,115 +1584,114 @@ HTML = """
1584
  // ν˜„μž¬ μ—΄λ¦° PDF의 ID
1585
  let currentPdfId = null;
1586
 
1587
- // μ˜€λ””μ˜€ μ»¨ν…μŠ€νŠΈμ™€ μ΄ˆκΈ°ν™” μƒνƒœ 관리
1588
- let audioInitialized = false;
1589
- let audioContext = null;
1590
 
1591
- // μ˜€λ””μ˜€ μ΄ˆκΈ°ν™” ν•¨μˆ˜
1592
- function initializeAudio() {
1593
- if (audioInitialized) return Promise.resolve();
1594
-
1595
- return new Promise((resolve) => {
1596
- // μ˜€λ””μ˜€ μ»¨ν…μŠ€νŠΈ 생성 (μ‚¬μš©μž μƒν˜Έμž‘μš©μ΄ ν•„μš” μ—†λŠ” μ΄ˆκΈ°ν™”)
1597
- audioContext = new (window.AudioContext || window.webkitAudioContext)();
1598
-
1599
- // MP3 λ‘œλ“œ 및 μ΄ˆκΈ°ν™”
1600
- const audio = new Audio('/static/turnPage2.mp3');
1601
- audio.volume = 0.01; // μ΅œμ†Œ λ³Όλ₯¨μœΌλ‘œ μ„€μ •
1602
-
1603
- // λ‘œλ“œλœ μ˜€λ””μ˜€ μž¬μƒ μ‹œλ„ (μ‚¬μš©μž μƒν˜Έμž‘μš© μš”κ΅¬λ  수 있음)
1604
- const playPromise = audio.play();
1605
-
1606
- if (playPromise !== undefined) {
1607
- playPromise
1608
- .then(() => {
1609
- // μ„±κ³΅μ μœΌλ‘œ μž¬μƒλ¨ - μ¦‰μ‹œ μΌμ‹œμ •μ§€
1610
- audio.pause();
1611
- audioInitialized = true;
1612
- console.log('μ˜€λ””μ˜€ μ΄ˆκΈ°ν™” 성곡');
1613
- resolve();
1614
- })
1615
- .catch((error) => {
1616
- console.log('μžλ™ μ˜€λ””μ˜€ μ΄ˆκΈ°ν™” μ‹€νŒ¨, μ‚¬μš©μž μƒν˜Έμž‘μš© ν•„μš”:', error);
1617
-
1618
- // μ‚¬μš©μž μƒν˜Έμž‘μš©μ΄ ν•„μš”ν•œ 경우, 이벀트 λ¦¬μŠ€λ„ˆ μΆ”κ°€
1619
- const initOnUserAction = function() {
1620
- const tempAudio = new Audio('/static/turnPage2.mp3');
1621
- tempAudio.volume = 0.01;
1622
- tempAudio.play()
1623
- .then(() => {
1624
- tempAudio.pause();
1625
- audioInitialized = true;
1626
- console.log('μ‚¬μš©μž μƒν˜Έμž‘μš©μœΌλ‘œ μ˜€λ””μ˜€ μ΄ˆκΈ°ν™” 성곡');
1627
- resolve();
1628
-
1629
- // 이벀트 λ¦¬μŠ€λ„ˆ 제거
1630
- ['click', 'touchstart', 'keydown'].forEach(event => {
1631
- document.removeEventListener(event, initOnUserAction, { capture: true });
1632
- });
1633
- })
1634
- .catch(e => console.error('μ˜€λ””μ˜€ μ΄ˆκΈ°ν™” μ‹€νŒ¨:', e));
1635
- };
1636
-
1637
- // μ‚¬μš©μž μƒν˜Έμž‘μš© μ΄λ²€νŠΈμ— λ¦¬μŠ€λ„ˆ μΆ”κ°€
1638
- ['click', 'touchstart', 'keydown'].forEach(event => {
1639
- document.addEventListener(event, initOnUserAction, { once: true, capture: true });
1640
- });
1641
-
1642
- // νŽ˜μ΄μ§€ λ‘œλ“œ 직후 μ‚¬μš©μžμ—κ²Œ μ˜€λ””μ˜€ ν™œμ„±ν™” μš”μ²­
1643
- if (window.location.pathname.startsWith('/view/')) {
1644
- // μ˜€λ””μ˜€ ν™œμ„±ν™” μ•ˆλ‚΄ λ©”μ‹œμ§€ ν‘œμ‹œ (λ°”λ‘œκ°€κΈ° 링크둜 μ ‘μ†ν•œ 경우)
1645
- setTimeout(() => {
1646
- const audioPrompt = document.createElement('div');
1647
- audioPrompt.style.position = 'fixed';
1648
- audioPrompt.style.bottom = '80px';
1649
- audioPrompt.style.left = '50%';
1650
- audioPrompt.style.transform = 'translateX(-50%)';
1651
- audioPrompt.style.backgroundColor = 'rgba(0,0,0,0.7)';
1652
- audioPrompt.style.color = 'white';
1653
- audioPrompt.style.padding = '10px 20px';
1654
- audioPrompt.style.borderRadius = '20px';
1655
- audioPrompt.style.zIndex = '10000';
1656
- audioPrompt.style.cursor = 'pointer';
1657
- audioPrompt.innerHTML = 'νŽ˜μ΄μ§€ μ–΄λ””λ“  ν΄λ¦­ν•˜μ—¬ μ†Œλ¦¬ 효과 ν™œμ„±ν™” <i class="fas fa-volume-up"></i>';
1658
- audioPrompt.id = 'audioPrompt';
1659
 
1660
- // 클릭 μ‹œ μ†Œλ¦¬ ν™œμ„±ν™” 및 λ©”μ‹œμ§€ 제거
1661
- audioPrompt.addEventListener('click', function() {
1662
- initOnUserAction();
1663
- audioPrompt.remove();
1664
- });
 
 
 
 
 
 
 
 
 
 
 
 
 
1665
 
1666
- document.body.appendChild(audioPrompt);
 
 
 
1667
 
1668
- // 10초 ν›„ μžλ™μœΌλ‘œ μˆ¨κΉ€
1669
- setTimeout(() => {
1670
- if (document.getElementById('audioPrompt')) {
1671
- document.getElementById('audioPrompt').remove();
1672
- }
1673
- }, 10000);
1674
- }, 2000);
1675
- }
1676
- });
1677
- } else {
1678
- // λΈŒλΌμš°μ €κ°€ Promise 기반 μž¬μƒμ„ μ§€μ›ν•˜μ§€ μ•ŠλŠ” 경우
1679
- audioInitialized = true;
1680
- resolve();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1681
  }
1682
- });
1683
- }
1684
-
1685
- // νŽ˜μ΄μ§€ λ‘œλ“œ μ‹œ μ˜€λ””μ˜€ μ΄ˆκΈ°ν™” μ‹œλ„
1686
- document.addEventListener('DOMContentLoaded', initializeAudio);
1687
 
1688
  /* ── μœ ν‹Έ ── */
1689
  function $id(id){return document.getElementById(id)}
1690
 
1691
- // 파일 μ—…λ‘œλ“œ 이벀트 처리
1692
- // νŽ˜μ΄μ§€ λ‘œλ“œ μ‹œ μ˜€λ””μ˜€ μ΄ˆκΈ°ν™” μ‹œλ„
1693
- document.addEventListener('DOMContentLoaded', initializeAudio);
1694
  console.log("DOM λ‘œλ“œ μ™„λ£Œ, 이벀트 μ„€μ • μ‹œμž‘");
1695
 
 
 
 
1696
  // PDF μ—…λ‘œλ“œ λ²„νŠΌ
1697
  const pdfBtn = document.getElementById('pdfUploadBtn');
1698
  const pdfInput = document.getElementById('pdfInput');
@@ -1727,6 +1726,28 @@ document.addEventListener('DOMContentLoaded', initializeAudio);
1727
 
1728
  // κ΄€λ¦¬μž λ²„νŠΌ 이벀트 μ„€μ •
1729
  setupAdminFunctions();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1730
  });
1731
 
1732
  // μ„œλ²„μ— PDF μ—…λ‘œλ“œ ν•¨μˆ˜
@@ -1763,46 +1784,44 @@ document.addEventListener('DOMContentLoaded', initializeAudio);
1763
  }
1764
  }
1765
 
1766
- function addCard(i, thumb, title, isCached = false, pdfId = null) {
1767
- const d = document.createElement('div');
1768
- d.className = 'card fade-in';
1769
- d.onclick = () => open(i);
1770
-
1771
- // PDF IDκ°€ 있으면 데이터 μ†μ„±μœΌλ‘œ μ €μž₯
1772
- if (pdfId) {
1773
- d.dataset.pdfId = pdfId;
1774
- }
1775
-
1776
- // 제λͺ© 처리
1777
- const displayTitle = title ?
1778
- (title.length > 15 ? title.substring(0, 15) + '...' : title) :
1779
- 'ν”„λ‘œμ νŠΈ ' + (i+1);
1780
-
1781
- // μΊμ‹œ μƒνƒœ 뱃지 μΆ”κ°€
1782
- const cachedBadge = isCached ?
1783
- '<div class="cached-status">μΊμ‹œλ¨</div>' : '';
1784
-
1785
- // λ°”λ‘œκ°€κΈ° 링크 μΆ”κ°€ (PDF IDκ°€ μžˆλŠ” κ²½μš°μ—λ§Œ)
1786
- const linkHtml = pdfId ?
1787
- `<div style="position: absolute; bottom: 55px; left: 50%; transform: translateX(-50%); z-index:5;">
1788
- <a href="/view/${pdfId}" target="_blank" style="color:#4a6ee0; font-size:11px;">Shaer Link</a>
1789
- </div>` : '';
1790
-
1791
- d.innerHTML = `
1792
- <div class="card-inner">
1793
- ${cachedBadge}
1794
- <img src="${thumb}" alt="${displayTitle}" loading="lazy">
1795
- ${linkHtml}
1796
- <p title="${title || 'ν”„λ‘œμ νŠΈ ' + (i+1)}">${displayTitle}</p>
1797
- </div>
1798
- `;
1799
- grid.appendChild(d);
1800
-
1801
- // ν”„λ‘œμ νŠΈκ°€ 있으면 'ν”„λ‘œμ νŠΈ μ—†μŒ' λ©”μ‹œμ§€ 숨기기
1802
- $id('noProjects').style.display = 'none';
1803
- }
1804
-
1805
-
1806
 
1807
  /* ── ν”„λ‘œμ νŠΈ μ €μž₯ ── */
1808
  function save(pages, title, isCached = false, pdfId = null) {
@@ -1963,6 +1982,9 @@ function addCard(i, thumb, title, isCached = false, pdfId = null) {
1963
  /* ── PDF ID둜 PDF μ—΄κΈ° ── */
1964
  async function openPdfById(pdfId, pdfPath, isCached = false) {
1965
  try {
 
 
 
1966
  // λ¨Όμ € ν™ˆ ν™”λ©΄μ—μ„œ μΉ΄λ“œλ₯Ό μ°Ύμ•„μ„œ ν΄λ¦­ν•˜λŠ” 방법 μ‹œλ„
1967
  let foundCard = false;
1968
  const cards = document.querySelectorAll('.card');
@@ -2270,157 +2292,138 @@ function addCard(i, thumb, title, isCached = false, pdfId = null) {
2270
  }
2271
  }
2272
 
2273
-
2274
- function createFlipBook(pages) {
2275
- console.log('FlipBook 생성 μ‹œμž‘. νŽ˜μ΄μ§€ 수:', pages.length);
2276
-
2277
- try {
2278
- // ν™”λ©΄ λΉ„μœ¨ 계산
2279
- const calculateAspectRatio = () => {
2280
- const windowWidth = window.innerWidth;
2281
- const windowHeight = window.innerHeight;
2282
- const aspectRatio = windowWidth / windowHeight;
2283
 
2284
- // λ„ˆλΉ„ λ˜λŠ” 높이 κΈ°μ€€μœΌλ‘œ μ΅œλŒ€ 90% μ œν•œ
2285
- let width, height;
2286
- if (aspectRatio > 1) { // κ°€λ‘œ ν™”λ©΄
2287
- height = Math.min(windowHeight * 0.9, windowHeight - 40);
2288
- width = height * aspectRatio * 0.8; // κ°€λ‘œ ν™”λ©΄μ—μ„œλŠ” μ•½κ°„ μ€„μž„
2289
- if (width > windowWidth * 0.9) {
2290
- width = windowWidth * 0.9;
2291
- height = width / (aspectRatio * 0.8);
2292
- }
2293
- } else { // μ„Έλ‘œ ν™”λ©΄
2294
- width = Math.min(windowWidth * 0.9, windowWidth - 40);
2295
- height = width / aspectRatio * 0.9; // μ„Έλ‘œ ν™”λ©΄μ—μ„œλŠ” μ•½κ°„ 늘림
2296
- if (height > windowHeight * 0.9) {
2297
- height = windowHeight * 0.9;
2298
- width = height * aspectRatio * 0.9;
2299
- }
2300
- }
2301
-
2302
- // 졜적 μ‚¬μ΄μ¦ˆ λ°˜ν™˜
2303
- return {
2304
- width: Math.round(width),
2305
- height: Math.round(height)
2306
- };
2307
- };
2308
-
2309
- // 초기 ν™”λ©΄ λΉ„μœ¨ 계산
2310
- const size = calculateAspectRatio();
2311
- viewer.style.width = size.width + 'px';
2312
- viewer.style.height = size.height + 'px';
2313
-
2314
- // μ‚¬μš΄λ“œ μ΄ˆκΈ°ν™” μ—¬λΆ€ 확인
2315
- if (!audioInitialized) {
2316
- initializeAudio()
2317
- .then(() => console.log('FlipBook 생성 μ „ μ˜€λ””μ˜€ μ΄ˆκΈ°ν™” μ™„λ£Œ'))
2318
- .catch(e => console.warn('FlipBook 생성 μ‹œ μ˜€λ””μ˜€ μ΄ˆκΈ°ν™” μ‹€νŒ¨:', e));
2319
- }
2320
-
2321
- // νŽ˜μ΄μ§€ 데이터 μ •μ œ (빈 νŽ˜μ΄μ§€ 처리)
2322
- const validPages = pages.map(page => {
2323
- // srcκ°€ μ—†λŠ” νŽ˜μ΄μ§€λŠ” λ‘œλ”© 쀑 μ΄λ―Έμ§€λ‘œ λŒ€μ²΄
2324
- if (!page || !page.src) {
2325
- return {
2326
- src: 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTAwIiBoZWlnaHQ9IjEwMCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZjVmNWY1Ii8+PHRleHQgeD0iNTAlIiB5PSI1MCUiIGZvbnQtZmFtaWx5PSJBcmlhbCIgZm9udC1zaXplPSIxMiIgdGV4dC1hbmNob3I9Im1pZGRsZSIgZHk9Ii4zZW0iIGZpbGw9IiM1NTUiPkxvYWRpbmcuLi48L3RleHQ+PC9zdmc+',
2327
- thumb: page && page.thumb ? page.thumb : ''
2328
- };
2329
- }
2330
- return page;
2331
- });
2332
-
2333
- fb = new FlipBook(viewer, {
2334
- pages: validPages,
2335
- viewMode: 'webgl',
2336
- autoSize: true,
2337
- flipDuration: 800,
2338
- backgroundColor: '#fff',
2339
- /* πŸ”Š λ‚΄μž₯ μ‚¬μš΄λ“œ */
2340
- sound: true,
2341
- assets: {flipMp3: '/static/turnPage2.mp3', hardFlipMp3: '/static/turnPage2.mp3'}, // μ ˆλŒ€ 경둜둜 μˆ˜μ •
2342
- controlsProps: {
2343
- enableFullscreen: true,
2344
- enableToc: true,
2345
- enableDownload: false,
2346
- enablePrint: false,
2347
- enableZoom: true,
2348
- enableShare: true, // 곡유 λ²„νŠΌ ν™œμ„±ν™”
2349
- enableSearch: true,
2350
- enableAutoPlay: true,
2351
- enableAnnotation: false,
2352
- enableSound: true,
2353
- enableLightbox: false,
2354
- layout: 10, // λ ˆμ΄μ•„μ›ƒ μ˜΅μ…˜
2355
- skin: 'light', // μŠ€ν‚¨ μŠ€νƒ€μΌ
2356
- autoNavigationTime: 3600, // μžλ™ λ„˜κΉ€ μ‹œκ°„(초)
2357
- hideControls: false, // 컨트둀 μˆ¨κΉ€ λΉ„ν™œμ„±ν™”
2358
- paddingTop: 10, // 상단 νŒ¨λ”©
2359
- paddingLeft: 10, // 쒌츑 νŒ¨λ”©
2360
- paddingRight: 10, // 우츑 νŒ¨λ”©
2361
- paddingBottom: 10, // ν•˜λ‹¨ νŒ¨λ”©
2362
- pageTextureSize: 1024, // νŽ˜μ΄μ§€ ν…μŠ€μ²˜ 크기
2363
- thumbnails: true, // 섬넀일 ν™œμ„±ν™”
2364
- autoHideControls: false, // μžλ™ μˆ¨κΉ€ λΉ„ν™œμ„±ν™”
2365
- controlsTimeout: 8000, // 컨트둀 ν‘œμ‹œ μ‹œκ°„ μ—°μž₯
2366
- shareHandler: copyPdfShareUrl // 곡유 ν•Έλ“€λŸ¬ μ„€μ •
2367
- }
2368
- });
2369
-
2370
- // ν™”λ©΄ 크기 λ³€κ²½ μ‹œ FlipBook 크기 μ‘°μ •
2371
- window.addEventListener('resize', () => {
2372
- if (fb) {
2373
- const newSize = calculateAspectRatio();
2374
- viewer.style.width = newSize.width + 'px';
2375
- viewer.style.height = newSize.height + 'px';
2376
- fb.resize();
2377
- }
2378
- });
2379
-
2380
- // FlipBook 생성 ν›„ μ»¨νŠΈλ‘€λ°” κ°•μ œ ν‘œμ‹œ
2381
- setTimeout(() => {
2382
  try {
2383
- // μ»¨νŠΈλ‘€λ°” κ΄€λ ¨ μš”μ†Œ μ°ΎκΈ° 및 μŠ€νƒ€μΌ 적용
2384
- const menuBars = document.querySelectorAll('.flipbook-container .fb3d-menu-bar');
2385
- if (menuBars && menuBars.length > 0) {
2386
- menuBars.forEach(menuBar => {
2387
- menuBar.style.display = 'block';
2388
- menuBar.style.opacity = '1';
2389
- menuBar.style.visibility = 'visible';
2390
- menuBar.style.zIndex = '9999';
2391
- });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2392
  }
2393
- } catch (e) {
2394
- console.warn('μ»¨νŠΈλ‘€λ°” μŠ€νƒ€μΌ 적용 쀑 였λ₯˜:', e);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2395
  }
2396
- }, 1000);
2397
-
2398
- console.log('FlipBook 생성 μ™„λ£Œ');
2399
- } catch (error) {
2400
- console.error('FlipBook 생성 쀑 였λ₯˜ λ°œμƒ:', error);
2401
- showError("FlipBook을 μƒμ„±ν•˜λŠ” 쀑 였λ₯˜κ°€ λ°œμƒν–ˆμŠ΅λ‹ˆλ‹€.");
2402
- }
2403
- }
2404
 
2405
  /* ── λ„€λΉ„κ²Œμ΄μ…˜ ── */
2406
- $id('homeButton').onclick=()=>{
2407
- if(fb) {
2408
- fb.destroy();
2409
- viewer.innerHTML = '';
2410
- fb = null;
2411
- }
2412
- toggle(true);
2413
-
2414
- // λ‘œλ”© 인디케이터 정리
2415
- if (pageLoadingInterval) {
2416
- clearInterval(pageLoadingInterval);
2417
- pageLoadingInterval = null;
2418
- }
2419
- $id('loadingPages').style.display = 'none';
2420
- currentLoadingPdfPath = null;
2421
- currentPdfId = null;
2422
- };
2423
-
2424
  function toggle(showHome){
2425
  $id('home').style.display=showHome?'block':'none';
2426
  $id('viewerPage').style.display=showHome?'none':'block';
@@ -2438,154 +2441,91 @@ function createFlipBook(pages) {
2438
  /* -- κ΄€λ¦¬μž κΈ°λŠ₯ -- */
2439
  function setupAdminFunctions() {
2440
  // κ΄€λ¦¬μž λ²„νŠΌ 클릭 - λͺ¨λ‹¬ ν‘œμ‹œ
2441
- $id('adminButton').addEventListener('click', function() {
2442
- $id('adminLoginModal').style.display = 'flex';
2443
- $id('adminPasswordInput').value = '';
2444
- $id('adminPasswordInput').focus();
2445
- });
 
 
 
 
 
 
 
 
 
 
 
 
 
2446
 
2447
  // λͺ¨λ‹¬ λ‹«κΈ° λ²„νŠΌ
2448
- $id('adminLoginClose').addEventListener('click', function() {
2449
- $id('adminLoginModal').style.display = 'none';
2450
- });
 
 
 
 
2451
 
2452
  // μ—”ν„° ν‚€λ‘œ 둜그인
2453
- $id('adminPasswordInput').addEventListener('keyup', function(e) {
2454
- if (e.key === 'Enter') {
2455
- $id('adminLoginButton').click();
2456
- }
2457
- });
2458
-
2459
- // 둜그인 λ²„νŠΌ
2460
- $id('adminLoginButton').addEventListener('click', async function() {
2461
- const password = $id('adminPasswordInput').value;
2462
-
2463
- try {
2464
- showLoading("둜그인 쀑...");
2465
-
2466
- const formData = new FormData();
2467
- formData.append('password', password);
2468
-
2469
- const response = await fetch('/api/admin-login', {
2470
- method: 'POST',
2471
- body: formData
2472
- });
2473
-
2474
- const data = await response.json();
2475
-
2476
- hideLoading();
2477
-
2478
- if (data.success) {
2479
- // 둜그인 성곡 - κ΄€λ¦¬μž νŽ˜μ΄μ§€ ν‘œμ‹œ
2480
- $id('adminLoginModal').style.display = 'none';
2481
- showAdminPage();
2482
- } else {
2483
- // 둜그인 μ‹€νŒ¨
2484
- showError("κ΄€λ¦¬μž 인증 μ‹€νŒ¨: λΉ„λ°€λ²ˆν˜Έκ°€ μΌμΉ˜ν•˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€.");
2485
  }
2486
- } catch (error) {
2487
- console.error("κ΄€λ¦¬μž 둜그인 였λ₯˜:", error);
2488
- hideLoading();
2489
- showError("둜그인 처리 쀑 였λ₯˜κ°€ λ°œμƒν–ˆμŠ΅λ‹ˆλ‹€.");
2490
- }
2491
- });
2492
-
2493
- // κ΄€λ¦¬μž νŽ˜μ΄μ§€ λ’€λ‘œκ°€κΈ°
2494
- $id('adminBackButton').addEventListener('click', function() {
2495
- $id('adminPage').style.display = 'none';
2496
- $id('home').style.display = 'block';
2497
- });
2498
- }
2499
-
2500
- /* ── μΉ΄λ“œ β†’ FlipBook ── */
2501
- async function open(i) {
2502
- // κΈ°μ‘΄ open ν•¨μˆ˜ λ‚΄μš©...
2503
- }
2504
-
2505
- /* ── PDF ID둜 PDF μ—΄κΈ° ── */
2506
- async function openPdfById(pdfId, pdfPath, isCached = false) {
2507
- try {
2508
- // μ˜€λ””μ˜€ μ΄ˆκΈ°ν™” μ‹œλ„
2509
- await initializeAudio().catch(e => console.warn('PDF μ—΄κΈ° μ „ μ˜€λ””μ˜€ μ΄ˆκΈ°ν™” μ‹€νŒ¨:', e));
2510
-
2511
- // λ¨Όμ € ν™ˆ ν™”λ©΄μ—μ„œ μΉ΄λ“œλ₯Ό μ°Ύμ•„μ„œ ν΄λ¦­ν•˜λŠ” 방법 μ‹œλ„
2512
- let foundCard = false;
2513
- const cards = document.querySelectorAll('.card');
2514
-
2515
- for (let i = 0; i < cards.length; i++) {
2516
- if (cards[i].dataset.pdfId === pdfId) {
2517
- cards[i].click();
2518
- foundCard = true;
2519
- break;
2520
  }
2521
- }
2522
-
2523
- // μΉ΄λ“œλ₯Ό μ°Ύμ§€ λͺ»ν•œ 경우 직접 μ˜€ν”ˆ
2524
- if (!foundCard) {
2525
- toggle(false);
2526
- showLoading("PDF μ€€λΉ„ 쀑...");
2527
-
2528
- let pages = [];
2529
 
2530
- // 이미 μΊμ‹œλœ 경우 μΊμ‹œλœ 데이터 μ‚¬μš©
2531
- if (isCached) {
2532
- try {
2533
- const response = await fetch(`/api/cached-pdf?path=${encodeURIComponent(pdfPath)}`);
2534
- const cachedData = await response.json();
 
2535
 
2536
- if (cachedData.status === "completed" && cachedData.pages) {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2537
  hideLoading();
2538
- createFlipBook(cachedData.pages);
2539
- // ν˜„μž¬ μ—΄λ¦° PDF의 ID μ €μž₯
2540
- currentPdfId = pdfId;
2541
- return;
2542
  }
2543
- } catch (error) {
2544
- console.error("μΊμ‹œ 데이터 λ‘œλ“œ μ‹€νŒ¨:", error);
2545
- }
2546
- }
2547
-
2548
- // 썸넀일 κ°€μ Έμ˜€κΈ°
2549
- try {
2550
- const thumbResponse = await fetch(`/api/pdf-thumbnail?path=${encodeURIComponent(pdfPath)}`);
2551
- const thumbData = await thumbResponse.json();
2552
-
2553
- if (thumbData.thumbnail) {
2554
- pages = [{
2555
- src: thumbData.thumbnail,
2556
- thumb: thumbData.thumbnail,
2557
- path: pdfPath,
2558
- cached: isCached
2559
- }];
2560
- }
2561
- } catch (error) {
2562
- console.error("썸넀일 λ‘œλ“œ μ‹€νŒ¨:", error);
2563
  }
2564
 
2565
- // 일단 κΈ°λ³Έ νŽ˜μ΄μ§€ μΆ”κ°€
2566
- if (pages.length === 0) {
2567
- pages = [{
2568
- path: pdfPath,
2569
- cached: isCached
2570
- }];
2571
  }
2572
-
2573
- // ν”„λ‘œμ νŠΈμ— μΆ”κ°€ν•˜κ³  λ·°μ–΄ μ‹€ν–‰
2574
- const projectId = projects.push(pages) - 1;
2575
- hideLoading();
2576
- open(projectId);
2577
-
2578
- // ν˜„μž¬ μ—΄λ¦° PDF의 ID μ €μž₯
2579
- currentPdfId = pdfId;
2580
  }
2581
- } catch (error) {
2582
- console.error("PDF ID둜 μ—΄κΈ° μ‹€νŒ¨:", error);
2583
- hideLoading();
2584
- showError("PDFλ₯Ό μ—΄ 수 μ—†μŠ΅λ‹ˆλ‹€. λ‹€μ‹œ μ‹œλ„ν•΄μ£Όμ„Έμš”.");
2585
- }
2586
- }
2587
-
2588
-
2589
 
2590
  // κ΄€λ¦¬μž νŽ˜μ΄μ§€ ν‘œμ‹œ
2591
  async function showAdminPage() {
 
1569
  </div>
1570
  </section>
1571
 
1572
+ <script>
1573
  let projects=[], fb=null;
1574
  const grid=document.getElementById('grid'), viewer=document.getElementById('viewer');
1575
  pdfjsLib.GlobalWorkerOptions.workerSrc='/static/pdf.worker.js';
 
1584
  // ν˜„μž¬ μ—΄λ¦° PDF의 ID
1585
  let currentPdfId = null;
1586
 
1587
+ // μ˜€λ””μ˜€ μ»¨ν…μŠ€νŠΈμ™€ μ΄ˆκΈ°ν™” μƒνƒœ 관리
1588
+ let audioInitialized = false;
1589
+ let audioContext = null;
1590
 
1591
+ // μ˜€λ””μ˜€ μ΄ˆκΈ°ν™” ν•¨μˆ˜
1592
+ function initializeAudio() {
1593
+ if (audioInitialized) return Promise.resolve();
1594
+
1595
+ return new Promise((resolve) => {
1596
+ // μ˜€λ””μ˜€ μ»¨ν…μŠ€νŠΈ 생성 (μ‚¬μš©μž μƒν˜Έμž‘μš©μ΄ ν•„μš” μ—†λŠ” μ΄ˆκΈ°ν™”)
1597
+ audioContext = new (window.AudioContext || window.webkitAudioContext)();
1598
+
1599
+ // MP3 λ‘œλ“œ 및 μ΄ˆκΈ°ν™”
1600
+ const audio = new Audio('/static/turnPage2.mp3');
1601
+ audio.volume = 0.01; // μ΅œμ†Œ λ³Όλ₯¨μœΌλ‘œ μ„€μ •
1602
+
1603
+ // λ‘œλ“œλœ μ˜€λ””μ˜€ μž¬μƒ μ‹œλ„ (μ‚¬μš©μž μƒν˜Έμž‘μš© μš”κ΅¬λ  수 있음)
1604
+ const playPromise = audio.play();
1605
+
1606
+ if (playPromise !== undefined) {
1607
+ playPromise
1608
+ .then(() => {
1609
+ // μ„±κ³΅μ μœΌλ‘œ μž¬μƒλ¨ - μ¦‰μ‹œ μΌμ‹œμ •μ§€
1610
+ audio.pause();
1611
+ audioInitialized = true;
1612
+ console.log('μ˜€λ””μ˜€ μ΄ˆκΈ°ν™” 성곡');
1613
+ resolve();
1614
+ })
1615
+ .catch((error) => {
1616
+ console.log('μžλ™ μ˜€λ””μ˜€ μ΄ˆκΈ°ν™” μ‹€νŒ¨, μ‚¬μš©μž μƒν˜Έμž‘μš© ν•„μš”:', error);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1617
 
1618
+ // μ‚¬μš©μž μƒν˜Έμž‘μš©μ΄ ν•„μš”ν•œ 경우, 이벀트 λ¦¬μŠ€λ„ˆ μΆ”κ°€
1619
+ const initOnUserAction = function() {
1620
+ const tempAudio = new Audio('/static/turnPage2.mp3');
1621
+ tempAudio.volume = 0.01;
1622
+ tempAudio.play()
1623
+ .then(() => {
1624
+ tempAudio.pause();
1625
+ audioInitialized = true;
1626
+ console.log('μ‚¬μš©μž μƒν˜Έμž‘μš©μœΌλ‘œ μ˜€λ””μ˜€ μ΄ˆκΈ°ν™” 성곡');
1627
+ resolve();
1628
+
1629
+ // 이벀트 λ¦¬μŠ€λ„ˆ 제거
1630
+ ['click', 'touchstart', 'keydown'].forEach(event => {
1631
+ document.removeEventListener(event, initOnUserAction, { capture: true });
1632
+ });
1633
+ })
1634
+ .catch(e => console.error('μ˜€λ””μ˜€ μ΄ˆκΈ°ν™” μ‹€νŒ¨:', e));
1635
+ };
1636
 
1637
+ // μ‚¬μš©μž μƒν˜Έμž‘μš© μ΄λ²€νŠΈμ— λ¦¬μŠ€λ„ˆ μΆ”κ°€
1638
+ ['click', 'touchstart', 'keydown'].forEach(event => {
1639
+ document.addEventListener(event, initOnUserAction, { once: true, capture: true });
1640
+ });
1641
 
1642
+ // νŽ˜μ΄μ§€ λ‘œλ“œ 직후 μ‚¬μš©μžμ—κ²Œ μ˜€λ””μ˜€ ν™œμ„±ν™” μš”μ²­
1643
+ if (window.location.pathname.startsWith('/view/')) {
1644
+ // μ˜€λ””μ˜€ ν™œμ„±ν™” μ•ˆλ‚΄ λ©”μ‹œμ§€ ν‘œμ‹œ (λ°”λ‘œκ°€κΈ° 링크둜 μ ‘μ†ν•œ 경우)
1645
+ setTimeout(() => {
1646
+ const audioPrompt = document.createElement('div');
1647
+ audioPrompt.style.position = 'fixed';
1648
+ audioPrompt.style.bottom = '80px';
1649
+ audioPrompt.style.left = '50%';
1650
+ audioPrompt.style.transform = 'translateX(-50%)';
1651
+ audioPrompt.style.backgroundColor = 'rgba(0,0,0,0.7)';
1652
+ audioPrompt.style.color = 'white';
1653
+ audioPrompt.style.padding = '10px 20px';
1654
+ audioPrompt.style.borderRadius = '20px';
1655
+ audioPrompt.style.zIndex = '10000';
1656
+ audioPrompt.style.cursor = 'pointer';
1657
+ audioPrompt.innerHTML = 'νŽ˜μ΄μ§€ μ–΄λ””λ“  ν΄λ¦­ν•˜μ—¬ μ†Œλ¦¬ 효과 ν™œμ„±ν™” <i class="fas fa-volume-up"></i>';
1658
+ audioPrompt.id = 'audioPrompt';
1659
+
1660
+ // 클릭 μ‹œ μ†Œλ¦¬ ν™œμ„±ν™” 및 λ©”μ‹œμ§€ 제거
1661
+ audioPrompt.addEventListener('click', function() {
1662
+ initOnUserAction();
1663
+ audioPrompt.remove();
1664
+ });
1665
+
1666
+ document.body.appendChild(audioPrompt);
1667
+
1668
+ // 10초 ν›„ μžλ™μœΌλ‘œ μˆ¨κΉ€
1669
+ setTimeout(() => {
1670
+ if (document.getElementById('audioPrompt')) {
1671
+ document.getElementById('audioPrompt').remove();
1672
+ }
1673
+ }, 10000);
1674
+ }, 2000);
1675
+ }
1676
+ });
1677
+ } else {
1678
+ // λΈŒλΌμš°μ €κ°€ Promise 기반 μž¬μƒμ„ μ§€μ›ν•˜μ§€ μ•ŠλŠ” 경우
1679
+ audioInitialized = true;
1680
+ resolve();
1681
+ }
1682
+ });
1683
  }
 
 
 
 
 
1684
 
1685
  /* ── μœ ν‹Έ ── */
1686
  function $id(id){return document.getElementById(id)}
1687
 
1688
+ // DOM이 λ‘œλ“œλ˜λ©΄ μ‹€ν–‰
1689
+ document.addEventListener('DOMContentLoaded', function() {
 
1690
  console.log("DOM λ‘œλ“œ μ™„λ£Œ, 이벀트 μ„€μ • μ‹œμž‘");
1691
 
1692
+ // μ˜€λ””μ˜€ μ΄ˆκΈ°ν™” μ‹œλ„
1693
+ initializeAudio().catch(e => console.warn('μ˜€λ””μ˜€ μ΄ˆκΈ°ν™” μ‹€νŒ¨:', e));
1694
+
1695
  // PDF μ—…λ‘œλ“œ λ²„νŠΌ
1696
  const pdfBtn = document.getElementById('pdfUploadBtn');
1697
  const pdfInput = document.getElementById('pdfInput');
 
1726
 
1727
  // κ΄€λ¦¬μž λ²„νŠΌ 이벀트 μ„€μ •
1728
  setupAdminFunctions();
1729
+
1730
+ // ν™ˆ λ²„νŠΌ 이벀트 μ„€μ •
1731
+ const homeButton = document.getElementById('homeButton');
1732
+ if (homeButton) {
1733
+ homeButton.addEventListener('click', function() {
1734
+ if(fb) {
1735
+ fb.destroy();
1736
+ viewer.innerHTML = '';
1737
+ fb = null;
1738
+ }
1739
+ toggle(true);
1740
+
1741
+ // λ‘œλ”© 인디케이터 정리
1742
+ if (pageLoadingInterval) {
1743
+ clearInterval(pageLoadingInterval);
1744
+ pageLoadingInterval = null;
1745
+ }
1746
+ $id('loadingPages').style.display = 'none';
1747
+ currentLoadingPdfPath = null;
1748
+ currentPdfId = null;
1749
+ });
1750
+ }
1751
  });
1752
 
1753
  // μ„œλ²„μ— PDF μ—…λ‘œλ“œ ν•¨μˆ˜
 
1784
  }
1785
  }
1786
 
1787
+ function addCard(i, thumb, title, isCached = false, pdfId = null) {
1788
+ const d = document.createElement('div');
1789
+ d.className = 'card fade-in';
1790
+ d.onclick = () => open(i);
1791
+
1792
+ // PDF IDκ°€ 있으면 데이터 μ†μ„±μœΌλ‘œ μ €μž₯
1793
+ if (pdfId) {
1794
+ d.dataset.pdfId = pdfId;
1795
+ }
1796
+
1797
+ // 제λͺ© 처리
1798
+ const displayTitle = title ?
1799
+ (title.length > 15 ? title.substring(0, 15) + '...' : title) :
1800
+ 'ν”„λ‘œμ νŠΈ ' + (i+1);
1801
+
1802
+ // μΊμ‹œ μƒνƒœ 뱃지 μΆ”κ°€
1803
+ const cachedBadge = isCached ?
1804
+ '<div class="cached-status">μΊμ‹œλ¨</div>' : '';
1805
+
1806
+ // λ°”λ‘œκ°€κΈ° 링크 μΆ”κ°€ (PDF IDκ°€ μžˆλŠ” κ²½μš°μ—λ§Œ)
1807
+ const linkHtml = pdfId ?
1808
+ `<div style="position: absolute; bottom: 55px; left: 50%; transform: translateX(-50%); z-index:5;">
1809
+ <a href="/view/${pdfId}" target="_blank" style="color:#4a6ee0; font-size:11px;">Share Link</a>
1810
+ </div>` : '';
1811
+
1812
+ d.innerHTML = `
1813
+ <div class="card-inner">
1814
+ ${cachedBadge}
1815
+ <img src="${thumb}" alt="${displayTitle}" loading="lazy">
1816
+ ${linkHtml}
1817
+ <p title="${title || 'ν”„λ‘œμ νŠΈ ' + (i+1)}">${displayTitle}</p>
1818
+ </div>
1819
+ `;
1820
+ grid.appendChild(d);
1821
+
1822
+ // ν”„λ‘œμ νŠΈκ°€ 있으면 'ν”„λ‘œμ νŠΈ μ—†μŒ' λ©”μ‹œμ§€ 숨기기
1823
+ $id('noProjects').style.display = 'none';
1824
+ }
 
 
1825
 
1826
  /* ── ν”„λ‘œμ νŠΈ μ €μž₯ ── */
1827
  function save(pages, title, isCached = false, pdfId = null) {
 
1982
  /* ── PDF ID둜 PDF μ—΄κΈ° ── */
1983
  async function openPdfById(pdfId, pdfPath, isCached = false) {
1984
  try {
1985
+ // μ˜€λ””μ˜€ μ΄ˆκΈ°ν™” μ‹œλ„
1986
+ await initializeAudio().catch(e => console.warn('PDF μ—΄κΈ° μ „ μ˜€λ””μ˜€ μ΄ˆκΈ°ν™” μ‹€νŒ¨:', e));
1987
+
1988
  // λ¨Όμ € ν™ˆ ν™”λ©΄μ—μ„œ μΉ΄λ“œλ₯Ό μ°Ύμ•„μ„œ ν΄λ¦­ν•˜λŠ” 방법 μ‹œλ„
1989
  let foundCard = false;
1990
  const cards = document.querySelectorAll('.card');
 
2292
  }
2293
  }
2294
 
2295
+ function createFlipBook(pages) {
2296
+ console.log('FlipBook 생성 μ‹œμž‘. νŽ˜μ΄μ§€ 수:', pages.length);
 
 
 
 
 
 
 
 
2297
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2298
  try {
2299
+ // ν™”λ©΄ λΉ„μœ¨ 계산
2300
+ const calculateAspectRatio = () => {
2301
+ const windowWidth = window.innerWidth;
2302
+ const windowHeight = window.innerHeight;
2303
+ const aspectRatio = windowWidth / windowHeight;
2304
+
2305
+ // λ„ˆλΉ„ λ˜λŠ” 높이 κΈ°μ€€μœΌλ‘œ μ΅œλŒ€ 90% μ œν•œ
2306
+ let width, height;
2307
+ if (aspectRatio > 1) { // κ°€λ‘œ ν™”λ©΄
2308
+ height = Math.min(windowHeight * 0.9, windowHeight - 40);
2309
+ width = height * aspectRatio * 0.8; // κ°€λ‘œ ν™”λ©΄μ—μ„œλŠ” μ•½κ°„ μ€„μž„
2310
+ if (width > windowWidth * 0.9) {
2311
+ width = windowWidth * 0.9;
2312
+ height = width / (aspectRatio * 0.8);
2313
+ }
2314
+ } else { // μ„Έλ‘œ ν™”λ©΄
2315
+ width = Math.min(windowWidth * 0.9, windowWidth - 40);
2316
+ height = width / aspectRatio * 0.9; // μ„Έλ‘œ ν™”λ©΄μ—μ„œλŠ” μ•½κ°„ 늘림
2317
+ if (height > windowHeight * 0.9) {
2318
+ height = windowHeight * 0.9;
2319
+ width = height * aspectRatio * 0.9;
2320
+ }
2321
+ }
2322
+
2323
+ // 졜적 μ‚¬μ΄μ¦ˆ λ°˜ν™˜
2324
+ return {
2325
+ width: Math.round(width),
2326
+ height: Math.round(height)
2327
+ };
2328
+ };
2329
+
2330
+ // 초기 ν™”λ©΄ λΉ„μœ¨ 계산
2331
+ const size = calculateAspectRatio();
2332
+ viewer.style.width = size.width + 'px';
2333
+ viewer.style.height = size.height + 'px';
2334
+
2335
+ // μ‚¬μš΄λ“œ μ΄ˆκΈ°ν™” μ—¬λΆ€ 확인
2336
+ if (!audioInitialized) {
2337
+ initializeAudio()
2338
+ .then(() => console.log('FlipBook 생성 μ „ μ˜€λ””μ˜€ μ΄ˆκΈ°ν™” μ™„λ£Œ'))
2339
+ .catch(e => console.warn('FlipBook 생성 μ‹œ μ˜€λ””μ˜€ μ΄ˆκΈ°ν™” μ‹€νŒ¨:', e));
2340
  }
2341
+
2342
+ // νŽ˜μ΄μ§€ 데이터 μ •μ œ (빈 νŽ˜μ΄μ§€ 처리)
2343
+ const validPages = pages.map(page => {
2344
+ // srcκ°€ μ—†λŠ” νŽ˜μ΄μ§€λŠ” λ‘œλ”© 쀑 μ΄λ―Έμ§€λ‘œ λŒ€μ²΄
2345
+ if (!page || !page.src) {
2346
+ return {
2347
+ src: 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTAwIiBoZWlnaHQ9IjEwMCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZjVmNWY1Ii8+PHRleHQgeD0iNTAlIiB5PSI1MCUiIGZvbnQtZmFtaWx5PSJBcmlhbCIgZm9udC1zaXplPSIxMiIgdGV4dC1hbmNob3I9Im1pZGRsZSIgZHk9Ii4zZW0iIGZpbGw9IiM1NTUiPkxvYWRpbmcuLi48L3RleHQ+PC9zdmc+',
2348
+ thumb: page && page.thumb ? page.thumb : ''
2349
+ };
2350
+ }
2351
+ return page;
2352
+ });
2353
+
2354
+ fb = new FlipBook(viewer, {
2355
+ pages: validPages,
2356
+ viewMode: 'webgl',
2357
+ autoSize: true,
2358
+ flipDuration: 800,
2359
+ backgroundColor: '#fff',
2360
+ /* πŸ”Š λ‚΄μž₯ μ‚¬μš΄λ“œ */
2361
+ sound: true,
2362
+ assets: {flipMp3: '/static/turnPage2.mp3', hardFlipMp3: '/static/turnPage2.mp3'}, // μ ˆλŒ€ 경둜둜 μˆ˜μ •
2363
+ controlsProps: {
2364
+ enableFullscreen: true,
2365
+ enableToc: true,
2366
+ enableDownload: false,
2367
+ enablePrint: false,
2368
+ enableZoom: true,
2369
+ enableShare: true, // 곡유 λ²„νŠΌ ν™œμ„±ν™”
2370
+ enableSearch: true,
2371
+ enableAutoPlay: true,
2372
+ enableAnnotation: false,
2373
+ enableSound: true,
2374
+ enableLightbox: false,
2375
+ layout: 10, // λ ˆμ΄μ•„μ›ƒ μ˜΅μ…˜
2376
+ skin: 'light', // μŠ€ν‚¨ μŠ€νƒ€μΌ
2377
+ autoNavigationTime: 3600, // μžλ™ λ„˜κΉ€ μ‹œκ°„(초)
2378
+ hideControls: false, // 컨트둀 μˆ¨κΉ€ λΉ„ν™œμ„±ν™”
2379
+ paddingTop: 10, // 상단 νŒ¨λ”©
2380
+ paddingLeft: 10, // 쒌츑 νŒ¨λ”©
2381
+ paddingRight: 10, // 우츑 νŒ¨λ”©
2382
+ paddingBottom: 10, // ν•˜λ‹¨ νŒ¨λ”©
2383
+ pageTextureSize: 1024, // νŽ˜μ΄μ§€ ν…μŠ€μ²˜ 크기
2384
+ thumbnails: true, // 섬넀일 ν™œμ„±ν™”
2385
+ autoHideControls: false, // μžλ™ μˆ¨κΉ€ λΉ„ν™œμ„±ν™”
2386
+ controlsTimeout: 8000, // 컨트둀 ν‘œμ‹œ μ‹œκ°„ μ—°μž₯
2387
+ shareHandler: copyPdfShareUrl // 곡유 ν•Έλ“€λŸ¬ μ„€μ •
2388
+ }
2389
+ });
2390
+
2391
+ // ν™”λ©΄ 크기 λ³€κ²½ μ‹œ FlipBook 크기 μ‘°μ •
2392
+ window.addEventListener('resize', () => {
2393
+ if (fb) {
2394
+ const newSize = calculateAspectRatio();
2395
+ viewer.style.width = newSize.width + 'px';
2396
+ viewer.style.height = newSize.height + 'px';
2397
+ fb.resize();
2398
+ }
2399
+ });
2400
+
2401
+ // FlipBook 생성 ν›„ μ»¨νŠΈλ‘€λ°” κ°•μ œ ν‘œμ‹œ
2402
+ setTimeout(() => {
2403
+ try {
2404
+ // μ»¨νŠΈλ‘€λ°” κ΄€λ ¨ μš”μ†Œ μ°ΎκΈ° 및 μŠ€νƒ€μΌ 적용
2405
+ const menuBars = document.querySelectorAll('.flipbook-container .fb3d-menu-bar');
2406
+ if (menuBars && menuBars.length > 0) {
2407
+ menuBars.forEach(menuBar => {
2408
+ menuBar.style.display = 'block';
2409
+ menuBar.style.opacity = '1';
2410
+ menuBar.style.visibility = 'visible';
2411
+ menuBar.style.zIndex = '9999';
2412
+ });
2413
+ }
2414
+ } catch (e) {
2415
+ console.warn('μ»¨νŠΈλ‘€λ°” μŠ€νƒ€μΌ 적용 쀑 였λ₯˜:', e);
2416
+ }
2417
+ }, 1000);
2418
+
2419
+ console.log('FlipBook 생성 μ™„λ£Œ');
2420
+ } catch (error) {
2421
+ console.error('FlipBook 생성 쀑 였λ₯˜ λ°œμƒ:', error);
2422
+ showError("FlipBook을 μƒμ„±ν•˜λŠ” 쀑 였λ₯˜κ°€ λ°œμƒν–ˆμŠ΅λ‹ˆλ‹€.");
2423
  }
2424
+ }
 
 
 
 
 
 
 
2425
 
2426
  /* ── λ„€λΉ„κ²Œμ΄μ…˜ ── */
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2427
  function toggle(showHome){
2428
  $id('home').style.display=showHome?'block':'none';
2429
  $id('viewerPage').style.display=showHome?'none':'block';
 
2441
  /* -- κ΄€λ¦¬μž κΈ°λŠ₯ -- */
2442
  function setupAdminFunctions() {
2443
  // κ΄€λ¦¬μž λ²„νŠΌ 클릭 - λͺ¨λ‹¬ ν‘œμ‹œ
2444
+ const adminButton = document.getElementById('adminButton');
2445
+ const adminLoginModal = document.getElementById('adminLoginModal');
2446
+ const adminLoginClose = document.getElementById('adminLoginClose');
2447
+ const adminLoginButton = document.getElementById('adminLoginButton');
2448
+ const adminPasswordInput = document.getElementById('adminPasswordInput');
2449
+ const adminBackButton = document.getElementById('adminBackButton');
2450
+
2451
+ if (adminButton) {
2452
+ adminButton.addEventListener('click', function() {
2453
+ if (adminLoginModal) {
2454
+ adminLoginModal.style.display = 'flex';
2455
+ if (adminPasswordInput) {
2456
+ adminPasswordInput.value = '';
2457
+ adminPasswordInput.focus();
2458
+ }
2459
+ }
2460
+ });
2461
+ }
2462
 
2463
  // λͺ¨λ‹¬ λ‹«κΈ° λ²„νŠΌ
2464
+ if (adminLoginClose) {
2465
+ adminLoginClose.addEventListener('click', function() {
2466
+ if (adminLoginModal) {
2467
+ adminLoginModal.style.display = 'none';
2468
+ }
2469
+ });
2470
+ }
2471
 
2472
  // μ—”ν„° ν‚€λ‘œ 둜그인
2473
+ if (adminPasswordInput) {
2474
+ adminPasswordInput.addEventListener('keyup', function(e) {
2475
+ if (e.key === 'Enter' && adminLoginButton) {
2476
+ adminLoginButton.click();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2477
  }
2478
+ });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2479
  }
 
 
 
 
 
 
 
 
2480
 
2481
+ // 둜그인 λ²„νŠΌ
2482
+ if (adminLoginButton) {
2483
+ adminLoginButton.addEventListener('click', async function() {
2484
+ if (!adminPasswordInput) return;
2485
+
2486
+ const password = adminPasswordInput.value;
2487
 
2488
+ try {
2489
+ showLoading("둜그인 쀑...");
2490
+
2491
+ const formData = new FormData();
2492
+ formData.append('password', password);
2493
+
2494
+ const response = await fetch('/api/admin-login', {
2495
+ method: 'POST',
2496
+ body: formData
2497
+ });
2498
+
2499
+ const data = await response.json();
2500
+
2501
+ hideLoading();
2502
+
2503
+ if (data.success) {
2504
+ // 둜그인 성곡 - κ΄€λ¦¬μž νŽ˜μ΄μ§€ ν‘œμ‹œ
2505
+ if (adminLoginModal) {
2506
+ adminLoginModal.style.display = 'none';
2507
+ }
2508
+ showAdminPage();
2509
+ } else {
2510
+ // 둜그인 μ‹€νŒ¨
2511
+ showError("κ΄€λ¦¬μž 인증 μ‹€νŒ¨: λΉ„λ°€λ²ˆν˜Έκ°€ μΌμΉ˜ν•˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€.");
2512
+ }
2513
+ } catch (error) {
2514
+ console.error("κ΄€λ¦¬μž 둜그인 였λ₯˜:", error);
2515
  hideLoading();
2516
+ showError("둜그인 처리 쀑 였λ₯˜κ°€ λ°œμƒν–ˆμŠ΅λ‹ˆλ‹€.");
 
 
 
2517
  }
2518
+ });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2519
  }
2520
 
2521
+ // κ΄€λ¦¬μž νŽ˜μ΄μ§€ λ’€λ‘œκ°€κΈ°
2522
+ if (adminBackButton) {
2523
+ adminBackButton.addEventListener('click', function() {
2524
+ document.getElementById('adminPage').style.display = 'none';
2525
+ document.getElementById('home').style.display = 'block';
2526
+ });
2527
  }
 
 
 
 
 
 
 
 
2528
  }
 
 
 
 
 
 
 
 
2529
 
2530
  // κ΄€λ¦¬μž νŽ˜μ΄μ§€ ν‘œμ‹œ
2531
  async function showAdminPage() {