Files
3d-trpay/index.html
T
yilsemandClaude Opus 4.8 5037c7114f 3D toplanti oynatici: orijinal + Turkce dublaj, zaman cizelgesi
- Three.js 3D oda, 7 kisi (6 oturan + ayakta kayinvalide)
- Orijinal/Turkce ses gecisi, konusmaci vurgusu, cift-dil altyazi
- 299 blok Turkce dublaj (7 ElevenLabs sesi, cinsiyet/yas uyumlu)
- nginx + Dockerfile (Coolify deploy)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-04 04:26:09 +03:00

263 lines
16 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="tr">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Toplantı 3D — Orijinal / Türkçe</title>
<style>
:root{ --bg:#0d1117; --panel:#161b22cc; --border:#30363d; --txt:#e6edf3; --mut:#8b949e; --accent:#2f81f7; }
*{box-sizing:border-box}
html,body{margin:0;height:100%;background:var(--bg);color:var(--txt);font-family:"Segoe UI",system-ui,sans-serif;overflow:hidden}
#scene{position:fixed;inset:0}
#topbar{position:fixed;top:0;left:0;right:0;height:52px;display:flex;align-items:center;gap:14px;
padding:0 16px;background:linear-gradient(#0d1117ee,#0d111700);z-index:10;pointer-events:none}
#topbar h1{font-size:15px;margin:0;font-weight:600;pointer-events:auto}
#topbar .sub{font-size:12px;color:var(--mut);font-weight:400}
.spacer{flex:1}
.toggle{pointer-events:auto;display:flex;background:#21262d;border:1px solid var(--border);border-radius:20px;overflow:hidden}
.toggle button{background:none;border:0;color:var(--mut);padding:7px 15px;font-size:13px;cursor:pointer;font-weight:600}
.toggle button.on{background:var(--accent);color:#fff}
.toggle button.on.trm{background:#8957e5}
#legend{position:fixed;top:60px;left:12px;z-index:10;background:var(--panel);border:1px solid var(--border);
border-radius:10px;padding:10px 12px;font-size:11.5px;backdrop-filter:blur(6px);max-width:220px}
#legend .row{display:flex;align-items:center;gap:7px;margin-top:5px}
#legend .dot{width:11px;height:11px;border-radius:50%;flex:none}
#subtitle{position:fixed;left:50%;transform:translateX(-50%);bottom:148px;width:min(780px,92vw);z-index:10;
background:var(--panel);border:1px solid var(--border);border-radius:12px;padding:12px 16px;backdrop-filter:blur(8px);
text-align:center;transition:opacity .2s}
#subtitle .who{font-weight:700;font-size:13px;margin-bottom:5px}
#subtitle .orig{font-size:14px;line-height:1.45}
#subtitle .tr{font-size:14px;line-height:1.45;color:var(--mut);margin-top:4px}
#subtitle.trmode .orig{color:var(--mut);font-size:12.5px}
#subtitle.trmode .tr{color:var(--txt)}
.badge{display:inline-block;font-size:10px;background:#3d2a00;color:#e0b400;border:1px solid #6b4a00;
border-radius:6px;padding:1px 6px;margin-left:6px;vertical-align:middle}
#controls{position:fixed;left:0;right:0;bottom:0;z-index:10;background:linear-gradient(#0d111700,#0d1117ee 42%);padding:12px 18px 14px}
#tl{width:100%;height:34px;display:block;cursor:pointer;border-radius:6px}
#bar{display:flex;align-items:center;gap:12px;margin-top:8px}
.btn{background:#21262d;border:1px solid var(--border);color:var(--txt);width:40px;height:40px;border-radius:50%;
font-size:15px;cursor:pointer;display:flex;align-items:center;justify-content:center}
.btn:hover{border-color:var(--accent)}
.btn.play{width:46px;height:46px;font-size:17px;background:var(--accent);border-color:var(--accent)}
#time{font-variant-numeric:tabular-nums;font-size:13px;color:var(--mut);min-width:190px}
#hint{font-size:11px;color:var(--mut)}
@media(max-width:640px){#subtitle{bottom:138px}#legend{display:none}#hint{display:none}}
</style>
</head>
<body>
<div id="scene"></div>
<div id="topbar">
<h1>Toplantı — 3D Oda <span class="sub">· uzlaşma görüşmesi · 09:0031:13</span></h1>
<div class="spacer"></div>
<div class="toggle" id="langToggle">
<button data-m="orig" class="on">🔊 Orijinal</button>
<button data-m="tr">🇹🇷 Türkçe</button>
</div>
</div>
<div id="legend"><b>Masadaki kişiler</b><div id="legendRows"></div></div>
<div id="subtitle">
<div class="who" id="subWho"></div>
<div class="orig" id="subOrig">Başlamak için ▶ tuşuna bas</div>
<div class="tr" id="subTr"></div>
</div>
<div id="controls">
<canvas id="tl"></canvas>
<div id="bar">
<button class="btn play" id="play"></button>
<button class="btn" id="prev" title="Önceki konuşma"></button>
<button class="btn" id="next" title="Sonraki konuşma"></button>
<span id="time">00:00 / 00:00</span>
<div class="spacer"></div>
<span id="hint">Zaman çizelgesine tıkla/sürükle · fareyle sahneyi döndür · çift tık kişi→ilk sözü</span>
</div>
</div>
<audio id="ao" src="toplanti_orijinal.mp3" preload="metadata"></audio>
<audio id="at" preload="metadata"></audio>
<script src="three.min.js"></script>
<script src="OrbitControls.js"></script>
<script src="data.js"></script>
<script>
const ROLES=MEET.roles, TL=MEET.TL, OFFSET=540;
const POS3D={
"Kaan":{x:-1.6,z:2.3,stand:false}, "Marissa":{x:1.6,z:2.3,stand:false},
"Dolandırıcı (D-)":{x:-1.6,z:-2.3,stand:false}, "Uzlaşmacı (U)":{x:0.4,z:-2.3,stand:false},
"Yaşlı adam (V)":{x:-3.7,z:0,stand:false}, "Lider Uzlaşmacı (L.U)":{x:3.7,z:0,stand:false},
"Kayınvalide":{x:3.3,z:-2.6,stand:true}
};
const SHORT={"Kaan":"Kaan","Marissa":"Marissa (eş)","Dolandırıcı (D-)":"Dolandırıcı","Uzlaşmacı (U)":"Uzlaşmacı",
"Yaşlı adam (V)":"Yaşlı adam","Lider Uzlaşmacı (L.U)":"Lider Uzlaşmacı","Kayınvalide":"Kayınvalide ⬆"};
function roundRect(g,x,y,w,h,r){g.beginPath();g.moveTo(x+r,y);g.arcTo(x+w,y,x+w,y+h,r);g.arcTo(x+w,y+h,x,y+h,r);g.arcTo(x,y+h,x,y,r);g.arcTo(x,y,x+w,y,r);g.closePath();}
function makeLabel(text,color,scale){ scale=scale||1;
const c=document.createElement('canvas'); c.width=256; c.height=64; const g=c.getContext('2d');
g.fillStyle="rgba(13,17,23,0.85)"; roundRect(g,4,10,248,44,10); g.fill();
g.strokeStyle=color; g.lineWidth=2; roundRect(g,4,10,248,44,10); g.stroke();
g.fillStyle=color; g.font="bold 24px Segoe UI"; g.textAlign="center"; g.textBaseline="middle"; g.fillText(text,128,33);
const spr=new THREE.Sprite(new THREE.SpriteMaterial({map:new THREE.CanvasTexture(c),transparent:true,depthTest:false}));
spr.scale.set(1.9*scale,0.48*scale,1); return spr;
}
const scene=new THREE.Scene();
scene.background=new THREE.Color(0x0d1117);
scene.fog=new THREE.Fog(0x0d1117,16,34);
const camera=new THREE.PerspectiveCamera(48,innerWidth/innerHeight,0.1,100);
camera.position.set(0,6.2,9.6);
const renderer=new THREE.WebGLRenderer({antialias:true});
renderer.setSize(innerWidth,innerHeight); renderer.setPixelRatio(Math.min(devicePixelRatio,2));
document.getElementById('scene').appendChild(renderer.domElement);
const controls=new THREE.OrbitControls(camera,renderer.domElement);
controls.enableDamping=true; controls.dampingFactor=0.08; controls.target.set(0,1,0);
controls.maxPolarAngle=Math.PI*0.49; controls.minDistance=5; controls.maxDistance=22;
scene.add(new THREE.AmbientLight(0xffffff,0.55));
const key=new THREE.DirectionalLight(0xffffff,0.9); key.position.set(6,12,8); scene.add(key);
const fill=new THREE.DirectionalLight(0x88aaff,0.35); fill.position.set(-8,6,-6); scene.add(fill);
const floor=new THREE.Mesh(new THREE.PlaneGeometry(40,40),new THREE.MeshStandardMaterial({color:0x1c2230,roughness:0.95}));
floor.rotation.x=-Math.PI/2; scene.add(floor);
const rug=new THREE.Mesh(new THREE.CircleGeometry(7.5,48),new THREE.MeshStandardMaterial({color:0x232a3a,roughness:1}));
rug.rotation.x=-Math.PI/2; rug.position.y=0.01; scene.add(rug);
const TABLE_H=1.0;
const tableTop=new THREE.Mesh(new THREE.BoxGeometry(6.4,0.16,3.2),new THREE.MeshStandardMaterial({color:0x6b4a2b,roughness:0.6}));
tableTop.position.y=TABLE_H; scene.add(tableTop);
[[2.9,1.4],[-2.9,1.4],[2.9,-1.4],[-2.9,-1.4]].forEach(function(p){
const leg=new THREE.Mesh(new THREE.CylinderGeometry(0.11,0.11,TABLE_H,12),new THREE.MeshStandardMaterial({color:0x4a3220}));
leg.position.set(p[0],TABLE_H/2,p[1]); scene.add(leg);
});
const rec=new THREE.Mesh(new THREE.BoxGeometry(0.5,0.12,0.28),new THREE.MeshStandardMaterial({color:0xd21f1f,emissive:0x330000,roughness:0.4}));
rec.position.set(-1.3,TABLE_H+0.14,1.0); scene.add(rec);
const recLbl=makeLabel("🔴 KAYIT","#ff6666",0.55); recLbl.position.set(-1.3,TABLE_H+0.55,1.0); scene.add(recLbl);
const SKIN=0xe8b98c, chars={};
function makeChar(role){
const p=POS3D[role], col=new THREE.Color(ROLES[role].renk);
const grp=new THREE.Group();
const cloth=new THREE.MeshStandardMaterial({color:col,roughness:0.7});
const skin=new THREE.MeshStandardMaterial({color:SKIN,roughness:0.6});
const hair=new THREE.MeshStandardMaterial({color:0x2a2018,roughness:0.9});
let headY;
if(p.stand){
const legL=new THREE.Mesh(new THREE.CylinderGeometry(0.1,0.1,0.85,10),cloth); legL.position.set(-0.13,0.42,0);
const legR=legL.clone(); legR.position.x=0.13; grp.add(legL,legR);
const torso=new THREE.Mesh(new THREE.CylinderGeometry(0.22,0.32,0.85,14),cloth); torso.position.y=1.28; grp.add(torso);
const head=new THREE.Mesh(new THREE.SphereGeometry(0.19,20,20),skin); head.position.y=1.86; grp.add(head);
const h=new THREE.Mesh(new THREE.SphereGeometry(0.205,18,18,0,Math.PI*2,0,Math.PI*0.62),hair); h.position.y=1.9; grp.add(h);
headY=1.86;
} else {
const seat=new THREE.Mesh(new THREE.BoxGeometry(0.56,0.1,0.56),new THREE.MeshStandardMaterial({color:0x394150,roughness:0.8}));
seat.position.set(0,0.5,-0.08); grp.add(seat);
const back=new THREE.Mesh(new THREE.BoxGeometry(0.56,0.62,0.08),new THREE.MeshStandardMaterial({color:0x394150,roughness:0.8}));
back.position.set(0,0.8,-0.34); grp.add(back);
const thigh=new THREE.Mesh(new THREE.BoxGeometry(0.34,0.14,0.42),cloth); thigh.position.set(0,0.6,0.14); grp.add(thigh);
const shinL=new THREE.Mesh(new THREE.CylinderGeometry(0.08,0.08,0.5,10),cloth); shinL.position.set(-0.12,0.3,0.32); grp.add(shinL);
const shinR=shinL.clone(); shinR.position.x=0.12; grp.add(shinR);
const torso=new THREE.Mesh(new THREE.CylinderGeometry(0.2,0.3,0.62,14),cloth); torso.position.y=0.98; grp.add(torso);
const head=new THREE.Mesh(new THREE.SphereGeometry(0.185,20,20),skin); head.position.y=1.44; grp.add(head);
const h=new THREE.Mesh(new THREE.SphereGeometry(0.2,18,18,0,Math.PI*2,0,Math.PI*0.6),hair); h.position.y=1.48; grp.add(h);
headY=1.44;
}
const ring=new THREE.Mesh(new THREE.TorusGeometry(0.55,0.06,10,32),new THREE.MeshStandardMaterial({color:col,emissive:col,emissiveIntensity:1.4}));
ring.rotation.x=-Math.PI/2; ring.position.y=0.04; ring.visible=false; grp.add(ring);
const lbl=makeLabel(SHORT[role],"#"+col.getHexString()); lbl.position.y=headY+0.55; grp.add(lbl);
grp.position.set(p.x,0,p.z); grp.rotation.y=Math.atan2(-p.x,-p.z);
scene.add(grp); chars[role]={grp:grp,ring:ring,active:false};
}
Object.keys(POS3D).forEach(makeChar);
// ---- audio + timeline ----
const ao=document.getElementById('ao'), at=document.getElementById('at');
let mode='orig';
const TL_TR=MEET.TL_tr||null;
if(MEET.trAudio){ at.src=MEET.trAudio; }
const TR_READY=!!MEET.trAudio && !!TL_TR;
function A(){ return (mode==='tr'&&TR_READY)?at:ao; }
function curTL(){ return (mode==='tr'&&TR_READY)?TL_TR:TL; }
const lr=document.getElementById('legendRows');
Object.keys(POS3D).forEach(function(r){
const d=document.createElement('div'); d.className='row';
d.innerHTML='<span class="dot" style="background:'+ROLES[r].renk+'"></span>'+SHORT[r]+(POS3D[r].stand?' <span style="color:#8b949e">(ayakta)</span>':'');
lr.appendChild(d);
});
const tl=document.getElementById('tl'), tg=tl.getContext('2d');
function fitTL(){ tl.width=tl.clientWidth*devicePixelRatio; tl.height=34*devicePixelRatio; tg.setTransform(devicePixelRatio,0,0,devicePixelRatio,0,0); drawTL(); }
function drawTL(){
const T=curTL(), dur=A().duration||T[T.length-1].e, W=tl.clientWidth, H=34;
tg.clearRect(0,0,W,H);
tg.fillStyle="#21262d"; roundRect(tg,0,11,W,12,6); tg.fill();
tg.globalAlpha=0.85;
T.forEach(function(b){ const x=b.t/dur*W, w=Math.max(1,(b.e-b.t)/dur*W); tg.fillStyle=(ROLES[b.rol]&&ROLES[b.rol].renk)||"#888"; tg.fillRect(x,11,w,12); });
tg.globalAlpha=1;
const ph=(A().currentTime/dur)*W; tg.fillStyle="#fff"; tg.fillRect(ph-1,6,2,22); tg.beginPath(); tg.arc(ph,6,4,0,7); tg.fill();
}
function seekTL(e){ const r=tl.getBoundingClientRect(); const x=(e.clientX-r.left)/r.width; const a=A(); a.currentTime=Math.max(0,Math.min(1,x))*(a.duration||1); update(); }
let drag=false;
tl.addEventListener('mousedown',function(e){drag=true;seekTL(e);});
addEventListener('mousemove',function(e){if(drag)seekTL(e);});
addEventListener('mouseup',function(){drag=false;});
function curIndex(){ const t=A().currentTime, T=curTL(); for(let i=0;i<T.length;i++){ if(t>=T[i].t&&t<T[i].e) return i; } return t<T[0].t?-1:T.length-1; }
let lastRole=null;
function highlight(role){ if(role===lastRole)return; lastRole=role; for(const r in chars){ const on=(r===role); chars[r].active=on; chars[r].ring.visible=on; } }
const fmt=function(s){s=Math.max(0,s|0);return String(s/60|0).padStart(2,'0')+':'+String(s%60).padStart(2,'0');};
const sub=document.getElementById('subtitle'),sW=document.getElementById('subWho'),sO=document.getElementById('subOrig'),sT=document.getElementById('subTr');
function update(){
const a=A(), T=curTL(), dur=a.duration||T[T.length-1].e;
document.getElementById('time').textContent=fmt(a.currentTime+OFFSET)+' / '+fmt(dur+OFFSET)+' (kayıt saati)';
const i=curIndex();
if(i>=0){ const b=T[i]; highlight(b.rol);
let badge=(mode==='tr'&&!TR_READY)?' <span class="badge">ses hazırlanıyor</span>':'';
sW.innerHTML=(SHORT[b.rol]||b.rol)+badge; sW.style.color=(ROLES[b.rol]&&ROLES[b.rol].renk)||'#fff';
sO.textContent=b.orig; sT.textContent=b.tr; sub.style.opacity=1;
} else { highlight(null); sub.style.opacity=.6; }
drawTL();
}
ao.addEventListener('timeupdate',function(){if(mode==='orig'||!TR_READY)update();});
at.addEventListener('timeupdate',function(){if(mode==='tr'&&TR_READY)update();});
ao.addEventListener('loadedmetadata',function(){fitTL();update();});
at.addEventListener('loadedmetadata',function(){if(mode==='tr')fitTL();});
const playBtn=document.getElementById('play');
playBtn.onclick=function(){ const a=A(); if(a.paused){a.play();playBtn.textContent='⏸';}else{a.pause();playBtn.textContent='▶';} };
document.getElementById('prev').onclick=function(){ const T=curTL(); let i=curIndex(); const a=A(); if(i>0&&a.currentTime-T[i].t<0.4)i--; a.currentTime=T[Math.max(0,i)].t; update(); };
document.getElementById('next').onclick=function(){ const T=curTL(); let i=curIndex(); const a=A(); a.currentTime=T[Math.min(T.length-1,i+1)].t; update(); };
document.addEventListener('keydown',function(e){ if(e.code==='Space'){e.preventDefault();playBtn.onclick();} if(e.code==='ArrowRight')A().currentTime+=5; if(e.code==='ArrowLeft')A().currentTime-=5; });
document.getElementById('langToggle').addEventListener('click',function(e){
const btn=e.target.closest('button'); if(!btn)return; const m=btn.dataset.m; if(m===mode)return;
[].forEach.call(e.currentTarget.children,function(b){ b.classList.toggle('on',b===btn); b.classList.toggle('trm',b===btn&&m==='tr'); });
const wasPlaying=!A().paused; const idx=curIndex(); A().pause();
mode=m; sub.classList.toggle('trmode',m==='tr');
const T=curTL(); const b=T[Math.max(0,idx)]; if(b)A().currentTime=b.t;
fitTL(); update(); if(wasPlaying){A().play();playBtn.textContent='⏸';}
});
renderer.domElement.addEventListener('dblclick',function(ev){
const rc=new THREE.Raycaster(), m=new THREE.Vector2((ev.clientX/innerWidth)*2-1,-(ev.clientY/innerHeight)*2+1);
rc.setFromCamera(m,camera);
const gs=Object.keys(chars).map(function(r){return chars[r].grp;});
const hit=rc.intersectObjects(gs,true)[0]; if(!hit)return;
let g=hit.object; while(g.parent&&gs.indexOf(g)<0)g=g.parent;
const role=Object.keys(chars).find(function(r){return chars[r].grp===g;});
const T=curTL(), b=T.find(function(x){return x.rol===role;}); if(b){A().currentTime=b.t;update();}
});
function loop(){
requestAnimationFrame(loop);
const t=performance.now()/1000;
for(const r in chars){ const c=chars[r];
if(c.active){ c.grp.position.y=Math.sin(t*4)*0.04; c.ring.rotation.z+=0.02; c.ring.material.emissiveIntensity=1.1+Math.sin(t*6)*0.5; }
else if(c.grp.position.y!==0){ c.grp.position.y*=0.85; if(Math.abs(c.grp.position.y)<0.002)c.grp.position.y=0; }
}
controls.update(); renderer.render(scene,camera);
}
loop();
addEventListener('resize',function(){ camera.aspect=innerWidth/innerHeight; camera.updateProjectionMatrix(); renderer.setSize(innerWidth,innerHeight); fitTL(); });
fitTL();
</script>
</body>
</html>