PageSourceSearch

https://smilecap.io/wp-content/themes/smilecap_gen3/assets/js/config.js?ver=1791196750

js smilecap.io collected 2026-10-05 10:39:19 UTC 30,804 bytes, 735 lines download raw bytes

1// テンプレートディレクトリのURIを取得
2const getTemplateDirectoryUri = () => {
3  // window.templateDirectoryUriが未定義の場合は遅延して再試行
4  if (typeof window.templateDirectoryUri === 'undefined') {
5    console.warn('window.templateDirectoryUriが未定義です。デフォルトパスを使用します。');
6    return '';
7  }
8  return window.templateDirectoryUri || '';
9};
10
11// 音声ファイルのパスを生成する関数
12window.getSoundPath = (soundFile) => {
13  const templateUri = getTemplateDirectoryUri();
14  const path = templateUri ? `${templateUri}/assets/sound/${soundFile}` : `sound/${soundFile}`;
15  return path;
16};
17
18// window.templateDirectoryUriが後から設定された場合の遅延初期化
19const initializeTemplateUri = () => {
20  if (typeof window.templateDirectoryUri !== 'undefined' && window.templateDirectoryUri) {
21    console.log('window.templateDirectoryUriが設定されました:', window.templateDirectoryUri);
22    // CONFIGの音声オプションを再生成
23    if (window.CONFIG && window.CONFIG.soundOptions) {
24      window.CONFIG.soundOptions.forEach(sound => {
25        if (sound.sound && sound.sound.startsWith('sound/')) {
26          sound.sound = window.getSoundPath(sound.sound.replace('sound/', ''));
27        }
28      });
29    }
30  }
31};
32
33// DOMContentLoaded後に遅延初期化を実行
34if (document.readyState === 'loading') {
35  document.addEventListener('DOMContentLoaded', initializeTemplateUri);
36} else {
37  initializeTemplateUri();
38}
39
40// 設定と定数
41window.CONFIG = {
42  // デバイス検出
43  isIPhone: /iPhone|iPad|iPod/.test(navigator.userAgent),
44  isMobile: /iPhone|iPad|iPod|Android/.test(navigator.userAgent),
45  
46  // 決済設定
47  payment: {
48    // Stripe決済ページのURL(ユーザー登録・課金誘導時に使用)
49    stripeCheckoutUrl: '/registration'
50  },
51  
52  // ビデオ設定
53  video: {
54    desktop: { width: 1280, height: 720 },
55    mobile: { widthRatio: 1, heightRatio: 0.6 }
56  },
57  
58  
59  
60  // 言語設定
61  languages: {
62    japanese: { name: '日本語', flag: '🇯🇵', code: 'ja' },
63    english: { name: 'English', flag: '🇺🇸', code: 'en' },
64    spanish: { name: 'Español', flag: '🇪🇸', code: 'es' },
65    korean: { name: '한국말', flag: '🇰🇷', code: 'kr' }
66  },
67  
68  // アプリ表示設定
69  app: {
70    // ベータ/バージョンラベル(front-page の BETA 表示に反映)
71    betaLabel: 'BETA 0.4'
72  },
73  
74  // 現在の言語設定
75  currentLanguage: 'japanese',
76  
77  // 音声設定
78  soundOptions: [
79    {
80      id: 'cheese_bandou',
81      name: 'ハイチーズ 01',
82      sound: window.getSoundPath('01_cheese_bandou.m4a'),
83      emoji: '🧀',
84      shutterTiming: [3.4, 3.8, 4.2, 7.0, 7.4, 7.9],
85      category: 'japanese'
86    },
87    {
88      id: 'cheese_miyabi',
89      name: 'ハイチーズ 02',
90      sound: window.getSoundPath('02_cheese_miyabi.m4a'),
91      emoji: '🧀',
92      shutterTiming: [6.2, 6.3, 7.3, 9.2, 10.14, 10.6, 11.05],
93      category: 'japanese'
94    },
95    {
96      id: 'kids_photographer',
97      name: 'キッズフォトグラファー',
98      sound: window.getSoundPath('03_kids_photographer.m4a'),
99      emoji: '📷',
100      shutterTiming: [5.8, 6.1, 6.6, 7.1, 7.6, 8.9, 10.2, 11.0, 11.5, 13.4, 13.8],
101      category: 'japanese'
102    },
103    {
104      id: 'elephant_lion_kurita',
105      name: 'ぞうさん',
106      sound: window.getSoundPath('04_elephant_lion_kurita.m4a'),
107      emoji: '🐘',
108      shutterTiming: [6.7, 7.2, 7.6, 7.9, 13.1, 13.4, 13.8],
109      category: 'japanese'
110    },
111    {
112      id: 'cheese_long_yanaga',
113      name: 'お姉さん',
114      sound: window.getSoundPath('05_cheese_long_yanaga.m4a'),
115      emoji: '👩',
116      shutterTiming: [6.0, 12.2, 12.6, 16.2, 17.05],
117      category: 'japanese'
118    },
119    {
120      id: 'funny_face_koharu',
121      name: '変顔',
122      sound: window.getSoundPath('06_funny_face_koharu.m4a'),
123      emoji: '😜',
124      shutterTiming: [6.9, 7.2, 7.8, 13.0, 13.4, 15.9, 16.4, 16.8],
125      category: 'japanese'
126    },
127    {
128      id: 'yeah_habakeito',
129      name: 'YEAH',
130      sound: window.getSoundPath('07_yeah_habakeito.m4a'),
131      emoji: '😄',
132      shutterTiming: [5.1, 6.4, 9.3, 9.7, 10.1, 10.5, 19.8, 21.6, 23.2, 24.8, 27.0, 27.5, 27.9, 28.3, 28.8],
133      category: 'japanese'
134    },
135    {
136      id: 'toy_01',
137      name: 'おもちゃ 01',
138      sound: window.getSoundPath('09_toy_01.m4a'),
139      emoji: '🧸',
140      shutterTiming: [0.2, 2.2, 3.1, 5.0, 5.2, 6.2, 7.0, 9.0, 9.2, 11.1, 11.2],
141      category: 'japanese'
142    },
143    {
144      id: 'toy_02',
145      name: 'おもちゃ 02',
146      sound: window.getSoundPath('10_toy_02.m4a'),
147      emoji: '🔔',
148      shutterTiming: [1.0, 1.2, 2.2, 4.1, 5.1, 8.0, 9.0, 11.0, 12.0, 13.1, 14.0],
149      category: 'japanese'
150    },
151    {
152      id: 'dog_01',
153      name: 'いぬ',
154      sound: window.getSoundPath('11_dog_01.m4a'),
155      emoji: '🐕',
156      shutterTiming: [0.2, 1.2, 2.0, 2.1, 4.0, 4.2, 6.1, 6.2],
157      category: 'japanese'
158    },
159    {
160      id: 'english_cheese',
161      name: 'Cheese',
162      sound: window.getSoundPath('english/01_cheese.m4a'),
163      emoji: '🧀',
164      shutterTiming: [3.7, 4.1, 4.6, 8.2, 8.6, 10.7,11.1, 13.9, 14.8,15.7,16.7,17.9],
165      category: 'english'
166    },
167    {
168      id: 'english_peekaboo',
169      name: 'Peekaboo',
170      sound: window.getSoundPath('english/02_peekaboo.m4a'),
171      emoji: '👀',
172      shutterTiming: [5.0, 5.4, 9.4, 9.9, 13.6, 14.1, 16.2, 16.6, 19.4, 19.9, 22.8, 23.2, 23.7, 24.1],
173      category: 'english'
174    },
175    {
176      id: 'english_animal',
177      name: 'Animal Sounds',
178      sound: window.getSoundPath('english/03_animal.m4a'),
179      emoji: '🐦',
180      shutterTiming: [3.2, 3.6, 5.9, 6.3, 9.6, 10.0, 16.3, 16.7, 23.1, 23.6, 24.0],
181      category: 'english'
182    },
183    {
184      id: 'english_funny_face',
185      name: 'Funny Face',
186      sound: window.getSoundPath('english/04_funny_face.m4a'),
187      emoji: '😜',
188      shutterTiming: [3.7, 4.2, 4.6, 8.5, 9.0, 13.5, 14.0, 14.5, 19.1, 19.5,24.2,24.7, 25.1, 29.6, 30.0, 30.5],
189      category: 'english'
190    },
191    {
192      id: 'english_fishface',
193      name: 'Fish Face',
194      sound: window.getSoundPath('english/05_fishface.m4a'),
195      emoji: '🐟',
196      shutterTiming: [6.0, 6.5, 9.7, 10.2, 13.0, 13.5, 15.1, 15.6, 18.9, 19.4, 19.9, 20.4, 23.8, 24.2,24.7, 25.1, 30.3,30.8],
197      category: 'english'
198    },
199    {
200      id: 'korean_pose',
201      name: 'Pose',
202      sound: window.getSoundPath('korean/01_pose.m4a'),
203      emoji: '🕺',
204      shutterTiming: [3.1, 4, 4.1, 4.2, 8, 10.2, 13.1, 13.2, 14, 14.15, 18, 18.2, 23, 23.2, 24.1, 25],
205      category: 'korean'
206    },
207    {
208      id: 'korean_smile',
209      name: 'Smile',
210      sound: window.getSoundPath('korean/02_smile.m4a'),
211      emoji: '😆',
212      shutterTiming: [3.1, 8, 8.2, 9, 9.1, 14.2, 15, 15.2, 20.8, 20.22, 21.1, 23, 23],
213      category: 'korean'
214    },
215    {
216      id: 'korean_wink',
217      name: 'Wink',
218      sound: window.getSoundPath('korean/03_wink.m4a'),
219      emoji: '😉',
220      shutterTiming: [3.1, 8, 8.1, 8.2, 12.1, 13, 13.2, 17.2, 23.2, 24.1, 25],
221      category: 'korean'
222    },
223    {
224      id: 'korean_heart',
225      name: 'Heart',
226      sound: window.getSoundPath('korean/04_heart.m4a'),
227      emoji: '♥️',
228      shutterTiming: [3.1, 5.1, 5.2, 9.2, 10, 10.2, 11, 14, 14.2, 15, 15.2, 18.1, 19, 19.2, 20],
229      category: 'korean'
230    },
231    {
232      id: 'no_sound',
233      name: '音声なし',
234      sound: '',
235      emoji: '🔇',
236      shutterTiming: [0],
237      category: 'japanese'
238    },
239    {
240      id: 'no_sound_english',
241      name: 'No Sound',
242      sound: '',
243      emoji: '🔇',
244      shutterTiming: [0],
245      category: 'english'
246    }
247    ,
248    {
249      id: 'no_sound_korean',
250      name: '무음',
251      sound: '',
252      emoji: '🔇',
253      shutterTiming: [0],
254      category: 'korean'
255    }
256  ],
257  
258  // デフォルト音声設定
259  defaultSound: 'cheese_bandou',
260  
261  
262  
263  // 表情分析の基準値
264  expression: {
265    baseMouthSize: 5,
266    baseEyeSize: 8,
267    baseEyebrowHeight: 80,
268    baseCheekHeight: 120
269  },
270  
271  // 写真管理
272  photo: {
273    maxPhotos: 999,
274    jpegQuality: 0.95
275  },
276
277  // 決済設定
278  payment: {
279    // Stripe決済ページのURL(ユーザー登録・課金誘導時に使用)
280    stripeCheckoutUrl: '/registration'
281  }
282};
283
284// iPhone用の設定調整
285window.getIPhoneAdjustedConfig = function() {
286  if (!window.CONFIG.isIPhone) return window.CONFIG;
287  
288  return {
289    ...window.CONFIG,
290    expression: {
291      ...window.CONFIG.expression,
292      baseMouthSize: 15,
293      baseEyeSize: 8,
294      baseEyebrowHeight: 80,
295      baseCheekHeight: 120
296    }
297  };
298};
299
300// ローカルストレージから初期言語を適用(URLより優先度は低い)
301(function applyInitialLanguageFromStorage() {
302  try {
303    var saved = localStorage.getItem('sc_language');
304    if (!saved) return;
305    if (window.CONFIG && window.CONFIG.languages && window.CONFIG.languages[saved]) {
306      window.CONFIG.currentLanguage = saved;
307    }
308  } catch (_) {}
309})();
310
311// 簡易翻訳辞書とヘルパー
312window.I18N = {
313  japanese: {
314    'confirm.reload_camera': 'カメラの問題を解決するため、ページをリロードしますか?',
315    'confirm.reload_audio': '音声の問題を解決するため、ページをリロードしますか?',
316    'error.camera.default': 'Webカメラにアクセスできません。接続や権限を確認してください。',
317    'error.camera.notAllowed': 'ブラウザのカメラ許可が拒否されています。設定から許可してください。',
318    'error.camera.notFound': 'カメラデバイスが見つかりません。接続やデバイス設定を確認してください。',
319    'error.camera.notReadable': '別のアプリでカメラが使用中の可能性があります。アプリを閉じて再試
319行してください。',
320    'error.audio.default': '音声ファイルの読み込みに失敗しました。ネットワーク接続を確認してください。',
321    'error.audio.encoding': '音声ファイルの形式がサポートされていません。',
322    'error.audio.network': 'ネットワークエラーが発生しました。接続を確認してください。',
323    'logo.removal.confirm': 'ロゴを非表示にするには有料プランへの登録が必要です。\n\n料金: ¥500 (税込)\n\n有料プランにアップグレードしますか?',
324    'upgrade.confirm': '有料プランにアップグレードしますか?\n\n料金: ¥500 (税込)\n特典: ロゴ非表示機能が利用できます\n\n登録ページに移動します。',
325    'plan.paid': '有料プラン',
326    'plan.free': '無料プラン',
327    'plan.paid.desc': 'すべての機能をご利用いただけます',
328    'plan.free.desc': '基本的な機能をご利用いただけます',
329    'feature.available': '利用可能',
330    'feature.paid_only': '有料プラン限定',
331    // UI translations
332    'i18n.banner.title': '有料プランでロゴ非表示にできます',
333    'i18n.banner.subtitle': '¥500(税込) で撮影中・撮影画像のロゴを削除',
334    'i18n.logo.label': 'ロゴを表示する',
335    'i18n.logo.desc': '撮影中の画面と撮影した画像の左下にSmileCapロゴを表示します',
336    'i18n.countdown.label': 'カウントダウンを表示する',
337    'i18n.countdown.desc': '撮影時に5秒のカウントダウンを表示します',
338    'i18n.startup.title': '山下幸輝さんのかけ声で写真を撮ろう!',
339    'i18n.startup.p1': '1. 音声をONにしてください。',
340    'i18n.startup.p2': '2. マナーモードをOFFにしてください。',
341    'i18n.startup.p3': '3. 設定でかけ声を選択できます。',
342    'i18n.startup.p4': 'それではお楽しみください!',
343    'i18n.startup.start': 'START',
344    'i18n.voiceAlert.japanese': 'この音声は山下幸輝さんの音声ではありません',
345    'i18n.upgrade.title': '🎉有料プランに切り替わりました',
346    'i18n.upgrade.p1': 'SmileCapプレミアムプランへのアップグレードが完了しました。',
347    'i18n.upgrade.p2': 'ロゴを非表示にしますか?',
348    'i18n.upgrade.hideLogo': 'ロゴを非表示にする',
349    'i18n.upgrade.keepLater': '後で設定する',
350    'i18n.cameraError.title': '📷カメラが見つかりません',
351    'i18n.audioError.title': '🔊音声が読み込めません',
352    'i18n.common.retry': '再試行',
353    'i18n.common.close': '閉じる',
354    'i18n.gallery.title': '撮影した写真',
355    'i18n.gallery.multi': 'check_box',
356    'i18n.gallery.save': '選択した写真を保存',
357    'i18n.gallery.delete': 'delete',
358    'i18n.category.coki': '🎵 CO-KI',
359    'i18n.category.pawticket': '🎫 PawTicket',
360    'i18n.category.shokuji': '🍽️ Shokuji',
361    'i18n.category.japanese': '🇯🇵 JP',
362    'i18n.category.english': '🇺🇸 EN',
363    'i18n.category.korean': '🇰🇷 KR',
364    // Logo removal dialog
365    'i18n.logoRemoval.message': 'ロゴを非表示にするには有料プランへの登録が必要です',
366    'i18n.logoRemoval.priceNote': '(税込)',
367    'i18n.logoRemoval.description': '有料プランでは、撮影した画像からSmileCapロゴを削除できます。',
368    'i18n.logoRemoval.cancel': 'キャンセル',
369    'i18n.logoRemoval.confirm': '有料プランにアップグレード'
370  },
371  english: {
372    'confirm.reload_camera': 'Reload the page to resolve the camera issue?',
373    'confirm.reload_audio': 'Reload the page to resolve the audio issue?',
374    'error.camera.default': 'Cannot access the webcam. Check connection and permissions.',
375    'error.camera.notAllowed': 'Camera permission is denied. Please allow it in your browser settings.',
376    'error.camera.notFound': 'No camera device found. Check the connection and device settings.',
377    'error.camera.notReadable': 'Camera may be in use by another app. Close it and try again.',
378    'error.audio.default': 'Failed to load audio. Please check your network connection.',
379    'error.audio.encoding': 'The audio format is not supported.',
380    'error.audio.network': 'A network error occurred. Please check your connection.',
381    'logo.removal.confirm': 'Hiding the logo requires a paid plan.\n\nPrice: ¥500 (tax included)\n\nWould you like to upgrade now?',
382    'upgrade.confirm': 'Upgrade to the paid plan?\n\nPrice: ¥500 (tax included)\nBenefit: Hide logo feature\n\nYou will be redirected to the registration page.',
383    'plan.paid': 'Paid Plan',
384    'plan.free': 'Free Plan',
385    'plan.paid.desc': 'All features are available.',
386    'plan.free.desc': 'Basic features are available.',
387    'feature.available': 'Available',
388    'feature.paid_only': 'Paid plan only',
389    // UI translations
390    'i18n.banner.title': 'Hide logo with Paid Plan',
391    'i18n.banner.subtitle': 'Remove logo from live view and photos for ¥500',
392    'i18n.logo.label': 'Show logo',
393    'i18n.logo.desc': 'Show SmileCap logo at bottom-left on live view and photos.',
394    'i18n.countdown.label': 'Show countdown',
395    'i18n.countdown.desc': 'Show a 5-second countdown when shooting.',
396    'i18n.startup.title': 'Take photos with Yukiteru Yamashita\'s voice prompts!',
397    'i18n.startup.p1': '1. Please turn sound ON (audio will play).',
398    'i18n.startup.p2': '2. Please turn Silent Mode OFF.',
399    'i18n.startup.p3': '3. You can select voice prompts in Settings.',
400    'i18n.startup.p4': 'Enjoy!',
401    'i18n.startup.start': 'START',
402    'i18n.voiceAlert.english': 'This voice is not by Yukiteru Yamashita.',
403    'i18n.upgrade.title': '🎉Upgraded to Paid Plan',
404    'i18n.upgrade.p1': 'Your SmileCap Premium upgrade is complete.',
405    'i18n.upgrade.p2': 'Hide the logo now?',
406    'i18n.upgrade.hideLogo': 'Hide logo',
407    'i18n.upgrade.keepLater': 'Decide later',
408    'i18n.cameraError.title': '📷Camera not found',
409    'i18n.audioError.title': '🔊Audio cannot be loaded',
410    'i18n.common.retry': 'Retry',
411    'i18n.common.close': 'Close',
412    'i18n.gallery.title': 'Captured Photos',
413    'i18n.gallery.multi': 'check_box',
414    'i18n.gallery.save': 'Save selected photos',
415    'i18n.gallery.delete': 'delete',
416    'i18n.category.coki': '🎵 CO-KI',
417    'i18n.category.pawticket': '🎫 PawTicket',
418    'i18n.category.shokuji': '🍽️ Shokuji',
419    'i18n.category.japanese': '🇯🇵 JP',
420    'i18n.category.english': '🇺🇸 EN',
421    'i18n.category.korean': '🇰🇷 KR',
422    // Logo removal dialog
423    'i18n.logoRemoval.message': 'Hiding the logo requires a paid plan',
424    'i18n.logoRemoval.priceNote': '(tax included)',
425    'i18n.logoRemoval.description': 'With the paid plan, you can remove the SmileCap logo from captured photos.',
426    'i18n.logoRemoval.cancel': 'Cancel',
427    'i18n.logoRemoval.confirm': 'Upgrade to Paid Plan'
428  },
429  spanish: {
430    'confirm.reload_camera': '¿Recargar la página para resolver el problema de la cámara?',
431    'confirm.reload_audio': '¿Recargar la página para resolver el problema de audio?',
432    'error.camera.default': 'No se puede acceder a la cámara. Verifique la conexión y permisos.',
433    'error.camera.notAllowed': 'Permiso de cámara denegado. Permítalo en la configuración del navegador.',
434    'error.camera.notFound': 'No se encontró cámara. Verifique la conexión y la configuración del dispositivo.',
435    'error.camera.notReadable': 'La cámara puede estar en uso por otra aplicación. Ciérrela e inténtelo de nuevo.',
436    'error.audio.default': 'Error al cargar el audio. Verifique su conexión a internet.',
437    'error.audio.encoding': 'Formato de audio no compatible.',
438    'error.audio.network': 'Se produjo un error de red. Verifique su conexión.',
439    'logo.removal.confirm': 'Ocultar el logotipo requiere un plan de pago.\n\nPrecio: ¥500 (impuestos incluidos)\n\n¿Desea actualizar ahora?',
440    'upgrade.confirm': '¿Actualizar al plan de pago?\n\nPrecio: ¥500 (impuestos incluidos)\nBeneficio: función para ocultar el logotipo\n\nSerá redirigido a la página de registro.',
441    'plan.paid': 'Plan de pago',
442    'plan.free': 'Plan gratuito',
443    'plan.paid.desc': 'Todas las funciones disponibles.',
444    'plan.free.desc': 'Funciones básicas disponibles.',
445    'feature.available': 'Disponible',
446    'feature.paid_only': 'Solo plan de pago',
447    // UI translations
448    'i18n.banner.title': 'Oculta el logo con el plan de pago',
449    'i18n.banner.subtitle': 'Elimina el logo de la vista y fotos por ¥500',
450    'i18n.logo.label': 'Mostrar logo',
451    'i18n.logo.desc': 'Mostrar el logo de SmileCap en la esquina inferior izquierda.',
452    'i18n.countdown.label': 'Mostrar cuenta regresiva',
453    'i18n.countdown.desc': 'Mostrar cuenta regresiva de 5 segundos al disparar.',
454    'i18n.startup.title': '🎵Disfruta con el sonido activado',
455    'i18n.startup.p1': 'Sonidos divertidos durante la toma.',
456    'i18n.startup.p2': 'Para una mejor experiencia, activa el sonido.',
457    'i18n.startup.start': 'INICIAR',
458    'i18n.upgrade.title': '🎉Actualizado al plan de pago',
459    'i18n.upgrade.p1': 'La actualización a SmileCap Premium se ha completado.',
460    'i18n.upgrade.p2': '¿Ocultar el logo ahora?',
461    'i18n.upgrade.hideLogo': 'Ocultar logo',
462    'i18n.upgrade.keepLater': 'Decidir después',
463    'i18n.cameraError.title': '📷Cámara no encontrada',
464    'i18n.audioError.title': '🔊No se puede cargar el audio',
465    'i18n.common.retry': 'Reintentar',
466    'i18n.common.close': 'Cerrar',
467    'i18n.gallery.title': 'Fotos capturadas',
468    'i18n.gallery.multi': 'check_box',
469    'i18n.gallery.save': 'Guardar fotos seleccionadas',
470    'i18n.gallery.delete': 'delete',
471    'i18n.category.japanese': '🇯🇵 JP',
472    'i18n.category.english': '🇺🇸 EN',
473    'i18n.category.korean': '🇰🇷 KR',
474    // Logo removal dialog
475    'i18n.logoRemoval.message': 'Ocultar el logotipo requiere un plan de pago',
476    'i18n.logoRemoval.priceNote': '(impuestos incluidos)',
477    'i18n.logoRemoval.description': 'Con el plan de pago, puedes eliminar el logo de SmileCap de las fotos.',
478    'i18n.logoRemoval.cancel': 'Cancelar',
479    'i18n.logoRemoval.confirm': 'Actualizar al plan de pago'
480  },
481  korean: {
482    'confirm.reload_camera': '카메라 문제를 해결하기 위해 페이지를 새로고침하시겠습니까?',
483    'confirm.reload_audio': '오디오 문제를 해결하기 위해 페이지를 새로고침하시겠습니까?',
484    'error.camera.default': '웹캠에 접근할 수 없습니다. 연결과 권한을 확인하세요.',
485    'error.camera.notAllowed': '카메라 권한이 거부되었습니다. 브라우저 설정에서 허용하세요.',
486    'error.camera.notFound': '카메라 장치를 찾을 수 없습니다. 연결과 장치 설정을 확인하세요.',
487    'error.camera.notReadable': '다른 앱에서 카메라를 사용 중일 수 있습니다. 종료 후 다시 시도하세요.',
488    'error.audio.default': '오디오를 불러오지 못했습니다. 네트워크 연결을 확인하세요.',
489    'error.audio.encoding': '지원되지 않는 오디오 형식입니다.',
490    'error.audio.network': '네트워크 오류가 발생했습니다. 연결을 확인하세요.',
491    'logo.removal.confirm': '로고를 숨기려면 유료 플랜이 필요합니다.\n\n가격: ¥500 (세금 포함)\n\n지금 업그레이드하시겠습니까?',
492    'upgrade.confirm': '유료 플랜으로 업그레이드하시겠습니까?\n\n가격: ¥500 (세금 포함)\n혜택: 로고 숨김 기능\n\n등록 페이지로 이동합니다.',
493    'plan.paid': '유료 플랜',
494    'plan.free': '무료 플랜',
495    'plan.paid.desc': '모든 기능을 사용할 수 있습니다.',
496    'plan.free.desc': '기본 기능을 사용할 수 있습니다.',
497    'feature.available': '이용 가능',
498    'feature.paid_only': '유료 플랜 전용',
499    // UI translations
500    'i18n.banner.title': '유료 플랜으로 로고 숨기기',
501    'i18n.banner.subtitle': '라이브/사진에서 로고 제거 (¥500)',
502    'i18n.logo.label': '로고 표시',
503    'i18n.logo.desc': '라이브 화면과 사진 왼쪽 하단에 로고를 표시합니다.',
504    'i18n.countdown.label': '카운트다운 표시',
505    'i18n.countdown.desc': '촬영 시 5초 카운트다운을 표시합니다.',
506    'i18n.startup.title': '🎵사운드를 켜고 즐기세요',
507    'i18n.startup.p1': '촬영 시 재미있는 사운드가 재생됩니다.',
508    'i18n.startup.p2': '더 나은 경험을 위해 사운드를 켜 주세요.',
509    'i18n.startup.start': '시작',
510    'i18n.upgrade.title': '🎉유료 플랜으로 업그레이드됨',
511    'i18n.upgrade.p1': 'SmileCap 프리미엄 업그레이드가 완료되었습니다.',
512    'i18n.upgrade.p2': '지금 로고를 숨길까요?',
513    'i18n.upgrade.hideLogo': '로고 숨기기',
514    'i18n.upgrade.keepLater': '나중에 결정',
515    'i18n.cameraError.title': '📷카메라를 찾을 수 없습니다',
516    'i18n.audioError.title': '🔊오디오를 불러올 수 없습니다',
517    'i18n.common.retry': '다시 시도',
518    'i18n.common.close': '닫기',
519    'i18n.gallery.title': '촬영한 사진',
520    'i18n.gallery.multi': 'check_box',
521    'i18n.gallery.save': '선택한 사진 저장',
522    'i18n.gallery.delete': 'delete',
523    'i18n.category.japanese': '🇯🇵 JP',
524    'i18n.category.english': '🇺🇸 EN',
525    'i18n.category.korean': '🇰🇷 KR',
526    // Logo removal dialog
527    'i18n.logoRemoval.message': '로고를 숨기려면 유료 플랜이 필요합니다',
528    'i18n.logoRemoval.priceNote': '(세금 포함)',
529    'i18n.logoRemoval.description': '유료 플랜에서는 촬영한 사진에서 SmileCap 로고를 제거할 수 있습니다.',
530    'i18n.logoRemoval.cancel': '취소',
531    'i18n.logoRemoval.confirm': '유료 플랜으로 업그레이드'
532  }
533};
534
535window.t = function(key) {
536  try {
537    var langKey = window.getCurrentLanguage ? window.getCurrentLanguage() : 'japanese';
538    var dict = window.I18N[langKey] || window.I18N.japanese;
539    return (dict && dict[key]) || (window.I18N.japanese[key] || key);
540  } catch (_) {
541    return key;
542  }
543};
544
545// URLパラメータから初期言語を適用
546(function applyInitialLanguageFromQuery() {
547  try {
548    var params = new URLSearchParams(window.location.search || '');
549    // la または lang をサポート
550    var raw = params.get('la') || params.get('lang');
551    if (!raw) return;
552
553    // 正規化(小文字)
554    var code = String(raw).trim().toLowerCase();
555
556    // 言語コード/キーのマッピング
557    // 許可コード: ja, en, es, cn(=zh), kr(=ko)
558    var codeToKey = {
559      'ja': 'japanese',
560      'en': 'english',
561      'es': 'spanish',
562      'cn': 'japanese', // デフォルト: 中国語未実装のため日本語にフォールバック
563      'zh': 'japanese', // 同上
564      'kr': 'korean',
565      'ko': 'korean'
566    };
567
568    // 直接キー指定にも対応(japanese/english/spanish/korean)
569    var directKeys = ['japanese', 'english', 'spanish', 'korean'];
570
571    var targetKey = null;
572    if (codeToKey[code]) {
573      targetKey = codeToKey[code];
574    } else if (directKeys.indexOf(code) !== -1) {
575      targetKey = code;
576    }
577
578    if (targetKey && window.CONFIG.languages[targetKey]) {
579      window.CONFIG.currentLanguage = targetKey;
580    try { localStorage.setItem('sc_language', targetKey); } catch (_) {}
581      // ドロップダウンが存在する場合は同期(後段のDOM読み込み後でも効くよう遅延)
582      setTimeout(function() {
583        try {
584          var select = document.getElementById('languageSelect');
585          if (select) select.value = targetKey;
586          // 音声カードの再生成を促すイベント(存在すれば)
587          if (window.faceLandmarksApp && typeof window.faceLandmarksApp.updateSoundOptionsDisplay === 'function') {
588            window.faceLandmarksApp.updateSoundOptionsDisplay();
589          }
590        } catch (e) {}
591      }, 0);
592    }
593  } catch (e) {
594    // 失敗時は黙ってスキップ
595  }
596})();
597
598// 言語設定を取得する関数
599window.getLanguages = function() {
600  return window.CONFIG.languages;
601}
602
603// 現在の言語を取得する関数
604window.getCurrentLanguage = function() {
605  return window.CONFIG.currentLanguage;
606}
607
608// 言語を設定する関数
609window.setCurrentLanguage = function(language) {
610  if (window.CONFIG.languages[language]) {
611    window.CONFIG.currentLanguage = language;
612    try { localStorage.setItem('sc_language', language); } catch (_) {}
613    return true;
614  }
615  return false;
616}
617
618// 音声オプションを取得する関数
619window.getSoundOptions = function() {
620  // プロジェクト設定のsoundOptionsをチェック
621  if (typeof window.ProjectConfigManager !== 'undefined') {
622    const projectSoundOptions = window.ProjectConfigManager.get('soundOptions');
623    if (projectSoundOptions && projectSoundOptions.length > 0) {
624      // プロジェクト設定の音声パスを完全なパスに変換
625      const processedProjectSounds = projectSoundOptions.map(option => {
626        if (!option.sound) return option;
627        
628        // 既に完全URLの場合はそのまま
629        if (option.sound.startsWith('http')) {
630          return option;
631        }
632        
633        // /assets/で始まるパスはtemplateDirectoryUriを付与
634        if (option.sound.startsWith('/assets/')) {
635          const templateUri = window.templateDirectoryUri || getTemplateDirectoryUri();
636          if (templateUri) {
637            return {
638              ...option,
639              sound: templateUri + option.sound
640            };
641          }
642        }
643        
644        // 相対パスの場合はgetSoundPathで処理
645        if (!option.sound.includes('/assets/sound/')) {
646          return {
647            ...option,
648            sound: window.getSoundPath(option.sound)
649          };
650        }
651        
652        // その他の場合はそのまま
653        return option;
654      });
655      
656      console.log('📢 プロジェクト音声オプション:', processedProjectSounds.map(o => ({ id: o.id, sound: o.sound, category: o.category })));
657      
658      // プロジェクト設定のsoundOptionsとデフォルトのsoundOptionsをマージ
659      const mergedSounds = [...processedProjectSounds, ...window.CONFIG.soundOptions];
660      
661      // プロジェクト設定のカテゴリラベルを翻訳に追加
662      const projectConfig = window.ProjectConfigManager.getCurrentConfig();
663      if (projectConfig && projectConfig.soundCategory) {
664        const categoryId = projectConfig.soundCategory.id;
665        const labels = projectConfig.soundCategory.label;
666        
667        // 日本語と英語の翻訳に追加
668        if (window.CONFIG.languages.japanese) {
669          window.CONFIG.languages.japanese[`i18n.category.${categoryId}`] = labels.japanese || labels.english;
670        }
671        if (window.CONFIG.languages.english) {
672          window.CONFIG.languages.english[`i18n.category.${categoryId}`] = labels.english || labels.japanese;
673        }
674      }
675      
676      return mergedSounds;
677    }
678  }
679  return window.CONFIG.soundOptions;
680}
681
682// デフォルト音声を取得する関数
683window.getDefaultSound = function() {
684  // プロジェクト設定のデフォルト音声をチェック
685  if (typeof window.ProjectConfigManager !== 'undefined') {
686    const projectDefaultSound = window.ProjectConfigManager.get('defaults.selectedSound');
687    if (projectDefaultSound) {
688      return projectDefaultSound;
689    }
690  }
691  return window.CONFIG.defaultSound;
692}
693
694// 音声IDから音声設定を取得する関数
695window.getSoundOptionById = function(id) {
696  const allSoundOptions = window.getSoundOptions();
697  return allSoundOptions.find(option => option.id === id);
698}
699
700// 音声名を取得する関数
701window.getSoundName = function(soundOption, language = null) {
702  return soundOption.name || soundOption.id;
703}
704
705
706// 音声オプションのHTMLを生成する関数
707window.generateSoundOptionsHTML = function(language = null) {
708  const soundOptions = window.getSoundOptions();
709  const defaultSound = window.getDefaultSound();
710  const currentLang = language || window.getCurrentLanguage();
711  
712  return soundOptions.map(option => {
713    const isSelected = option.id === defaultSound;
714    const selectedClass = isSelected ? 'selected' : '';
715    const soundName = window.getSoundName(option, currentLang);
716    
717    return `
718      <div class="sound-option ${selectedClass}" 
719           data-sound-id="${option.id}"
720           data-sound="${option.sound}" 
721           data-shutter-timing="${option.shutterTiming ? option.shutterTiming.join(',') : '0'}">
722        <div class="sound-image">
723          <span style="font-size: 60px;">${option.emoji}</span>
724        </div>
725        <span class="sound-name">${soundName}</span>
726      </div>
727    `;
728  }).join('');
729}
730
731
732
733
734
735

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.