diff options
Diffstat (limited to 'public/bluebey-studio/index.html')
| -rw-r--r-- | public/bluebey-studio/index.html | 215 |
1 files changed, 215 insertions, 0 deletions
diff --git a/public/bluebey-studio/index.html b/public/bluebey-studio/index.html new file mode 100644 index 0000000..dea745f --- /dev/null +++ b/public/bluebey-studio/index.html @@ -0,0 +1,215 @@ +<!doctype html> +<html lang="ja"> +<head> +<meta charset="utf-8"> +<meta name="viewport" content="width=device-width, initial-scale=1"> +<title>ぶるべー スタジオ</title> +<!-- An inline empty icon stops the browser from requesting /favicon.ico, which would otherwise show up as a confusing 404 in the console. --> +<link rel="icon" href="data:,"> +<link rel="stylesheet" href="src/style.css"> +<script> +/* + * Boot watchdog. Runs as a plain (non-module) script so it is always executed, + * even when the module entry point cannot be loaded at all. It turns a silent + * "loading forever" screen into an explanation of what went wrong. + */ +(function () { + var failures = []; + var started = Date.now(); + window.addEventListener('error', function (event) { + var target = event.target; + if (target && (target.src || target.href)) { + failures.push((target.tagName === 'SCRIPT' ? 'JS: ' : 'ファイル: ') + (target.src || target.href)); + } + }, true); + window.__bluebeyFailures = failures; + + var timer = setInterval(function () { + if (window.__bluebeyReady) { + clearInterval(timer); + var done = document.getElementById('loading-error'); + if (done && done.dataset.watchdog) done.textContent = ''; + return; + } + if (Date.now() - started < 9000) return; + clearInterval(timer); + + var text = document.getElementById('loading-text'); + var box = document.getElementById('loading-error'); + if (text) text.textContent = '読み込みが止まっています。'; + if (!box) return; + box.dataset.watchdog = '1'; + + var lines = []; + if (location.protocol === 'file:') { + lines.push('このページはファイルとして直接開かれています(' + location.protocol + ')。'); + lines.push('ブラウザの制限により、この開き方ではJavaScriptモジュールを読み込めません。'); + lines.push(''); + lines.push('次のどちらかで開いてください。'); + lines.push('1) bluebey-studio フォルダで python serve.py を実行し、'); + lines.push(' 表示された http://127.0.0.1:8000/ を開く(推奨)'); + lines.push('2) bluebey-studio-standalone.html をダブルクリックする(1ファイル版)'); + } else { + lines.push('必要なファイルを読み込めませんでした。ページの場所とフォルダの中身を確認してください。'); + lines.push('(配布するときは bluebey-studio フォルダの中身をすべてコピーしてください)'); + } + if (failures.length) { + lines.push(''); + lines.push('読み込めなかったもの:'); + for (var i = 0; i < failures.length && i < 8; i++) lines.push('・' + failures[i]); + } + box.textContent = lines.join('\n'); + }, 1000); +})(); +</script> +<script type="importmap"> +{ + "imports": { + "three": "./vendor/three/build/three.module.js", + "three/addons/": "./vendor/three/examples/jsm/", + "opentype.js": "./vendor/lib/opentype.module.js" + } +} +</script> +</head> +<body> + +<div id="stage"> + <canvas id="view" aria-label="3D viewport"></canvas> +</div> + +<header id="topbar"> + <h1>ぶるべー スタジオ</h1> + <div class="topbar-actions"> + <button type="button" id="btn-shot" title="今の見た目をPNGで保存 (S)">PNG保存</button> + <button type="button" id="btn-copy" title="画像をクリップボードにコピー (C)">コピー</button> + <button type="button" id="btn-help" title="ヘルプ (?)">?</button> + <button type="button" id="btn-panel" title="パネルの表示/非表示 (Tab)">≡</button> + </div> +</header> + +<aside id="panel" aria-label="コントロールパネル"></aside> + +<p id="viewport-hint"> + ドラッグ:回転 / ホイール:ズーム / 右ドラッグ:移動 / モデルをクリック:ボーン選択 +</p> + +<div id="toast" role="status" aria-live="polite"></div> + +<div id="loading"> + <div class="loading-box"> + <div class="loading-spinner"></div> + <p id="loading-text">ぶるべーを読み込んでいます…</p> + <p id="loading-error"></p> + </div> +</div> + +<div id="help" hidden> + <div class="help-sheet"> + <button type="button" class="help-close" id="help-close" aria-label="閉じる">×</button> + <h2>ぶるべー スタジオの使い方</h2> + + <h3>これは何?</h3> + <p> + <code>bluebey.glb</code>(ぶるべーの3Dモデル)をブラウザで読み込み、自由にポーズをとらせ、 + 顔の表情を自由に作って、2Dの画像(PNG)として書き出せるスタジオです。 + 元の手描きパーツ(GIMPで作った目と口の画像)もそのまま使えます。 + </p> + + <h3>ポーズ</h3> + <ul> + <li>右パネルの「ポーズ」でボーンを選び、X/Y/Zのスライダーを動かします。</li> + <li>モデルをクリックすると、その場所にいちばん近いボーンが選ばれます。</li> + <li>ギズモ(回転の輪)を直接ドラッグしても回せます。</li> + <li>スライダーの値は「休めの姿勢(レストポーズ)」からの差分です。<code>0,0,0</code> で元の姿勢。</li> + <li>気に入ったポーズは「ポーズを保存」でファイルに残せます。</li> + </ul> + + <h3>表情</h3> + <ul> + <li>目:開き具合(まばたき・ウインク)、目線(上下左右)、瞳の大きさ、ハイライト、閉じ方の種類。</li> + <li>口:笑いの深さ、口の開き、舌、口角の点、太さ、色。</li> + <li>「目線パッド」をドラッグすると両目が一緒に動きます。</li> + <li>プリセット(にっこり・ウインク・びっくり…)から始めると早いです。</li> + <li>「手描きパーツを使う」を選ぶと、元のGIMP製パーツに切り替わります。</li> + </ul> + + <h3>3Dのまま/2Dにする</h3> + <ul> + <li>「表示」で リアル/フラット(トゥーン)/線画/線画(透明) を切り替えます。</li> + <li>線画は輪郭線+白ぬり。線の太さと色を変えられます。</li> + <li>「正投影」にすると遠近のゆがみがなくなり、図面のような絵になります。</li> + <li>背景は透明・白・好きな色から選べます。<code>PNG保存</code>は透明のまま保存できます。</li> + </ul> + + <h3>キーボード</h3> + <ul class="keys"> + <li><kbd>S</kbd> PNG保存</li> + <li><kbd>C</kbd> 画像をコピー</li> + <li><kbd>Tab</kbd> パネルの表示/非表示</li> + <li><kbd>?</kbd> このヘルプ</li> + <li><kbd>1</kbd>–<kbd>5</kbd> カメラの向き(正面・斜め・横・後ろ・俯瞰)</li> + <li><kbd>R</kbd> ポーズをリセット</li> + <li><kbd>G</kbd> ギズモの切り替え</li> + <li><kbd>Space</kbd> 生きている演出(ゆれ・まばたき)の開始/停止</li> + </ul> + + <h3>書き出しのヒント</h3> + <ul> + <li>PNGは「書き出しサイズ」を2倍・3倍にすると高解像度になります(印刷やスライド用)。</li> + <li>ポーズと表情の全設定は「設定を保存」でJSONに残せます。READMEも参照してください。</li> + </ul> + </div> +</div> + +<div id="start-notice" hidden> + <div class="start-sheet" role="dialog" aria-modal="true" aria-labelledby="start-title"> + <h2 id="start-title">ご利用の前に</h2> + <p>このスタジオは、市政・政治活動とは関係のない個人的な制作物です。</p> + <p>このスタジオで作成した画像などは、自由に使っていただけます。ただし、商用利用やそのほか特殊な使い方をされる場合は、小平市の確認が必要ですので、このサイトの<a href="/contact/" target="_blank" rel="noopener">お問い合わせ</a>からご連絡ください。</p> + <p>次のような使い方はご遠慮ください。</p> + <ul> + <li>公序良俗に反する目的での利用</li> + <li>ぶるべーのイメージを損なうような攻撃的・差別的・性的・過激な利用</li> + <li>反社会的勢力や違法行為に関わる利用</li> + <li>政治的意図があるように見える利用(例:特定の政治家や政党をぶるべーが応援しているような表現)</li> + <li>特定の宗教・宗派への勧誘や布教と誤解される利用</li> + <li>小平市が公認・推薦・後援しているように見える利用(市の公式発表や公文書と誤認させる利用を含む)</li> + <li>特定の企業・商品・思想・団体を、ぶるべーや小平市が推薦・保証しているように見える利用</li> + </ul> + <p>ぶるべーの著作権は小平市にあります。このスタジオと3Dモデルは作者(安竹洋平)が自作したものです。</p> + <div class="start-actions"> + <label class="start-skip"><input type="checkbox" id="start-skip"> 次回から表示しない</label> + <button type="button" id="start-ok" class="start-ok">同意して使う</button> + </div> + </div> +</div> + +<script> +/* + * 起動時のご利用について。毎回出し、「次回から表示しない」を選んだときだけ覚える。 + * アプリが立ち上がってから出す(読み込みに失敗したときは、その説明を隠さないため)。 + * モジュールが読めない場合でも動くよう、module ではない普通のスクリプト。 + */ +(function () { + var KEY = 'bluebey.usage-notice.v1'; + var box = document.getElementById('start-notice'); + if (!box) return; + try { if (localStorage.getItem(KEY) === 'skip') return; } catch (error) {} + var timer = setInterval(function () { + if (!window.__bluebeyReady) return; + clearInterval(timer); + box.hidden = false; + }, 150); + document.getElementById('start-ok').addEventListener('click', function () { + box.hidden = true; + try { + if (document.getElementById('start-skip').checked) localStorage.setItem(KEY, 'skip'); + } catch (error) {} + }); +})(); +</script> + +<script type="module" src="src/main.js"></script> +</body> +</html> |
