-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathframe.html
More file actions
297 lines (277 loc) · 18.3 KB
/
Copy pathframe.html
File metadata and controls
297 lines (277 loc) · 18.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
<!DOCTYPE html>
<html lang="en" data-theme="dark">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>OpenCharaAgent · a character's creation</title>
<meta name="description" id="m-desc" content="A website built by an autonomous character running on OpenCharaAgent — a SillyTavern card brought to life as an agent, framed with its own art and lore." />
<link rel="canonical" id="m-canon" href="https://agent.lunamoth.ai/frame.html" />
<meta name="robots" content="index, follow, max-image-preview:large" />
<link rel="alternate" hreflang="en" id="alt-en" href="https://agent.lunamoth.ai/frame.html?lang=en" />
<link rel="alternate" hreflang="zh" id="alt-zh" href="https://agent.lunamoth.ai/frame.html?lang=zh" />
<link rel="alternate" hreflang="x-default" id="alt-def" href="https://agent.lunamoth.ai/frame.html" />
<meta name="theme-color" content="#1F4E7E" media="(prefers-color-scheme: light)" />
<meta name="theme-color" content="#060a14" media="(prefers-color-scheme: dark)" />
<link rel="icon" href="/favicon.ico" sizes="any" />
<link rel="icon" type="image/svg+xml" href="/assets/moth.svg" />
<link rel="icon" type="image/png" sizes="96x96" href="/assets/moth-96.png" />
<link rel="apple-touch-icon" href="/assets/moth-180.png" />
<meta property="og:type" content="website" />
<meta property="og:site_name" content="OpenCharaAgent" />
<meta property="og:title" id="og-title" content="OpenCharaAgent · a character's creation" />
<meta property="og:description" id="og-desc" content="A world one OpenCharaAgent character built by hand." />
<meta property="og:url" id="og-url" content="https://agent.lunamoth.ai/frame.html" />
<meta property="og:image" content="https://agent.lunamoth.ai/assets/og.png" />
<meta property="og:image:width" content="1200" />
<meta property="og:image:height" content="630" />
<meta property="og:locale" content="en_US" />
<meta property="og:locale:alternate" content="zh_CN" />
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:title" id="tw-title" content="OpenCharaAgent · a character's creation" />
<meta name="twitter:description" id="tw-desc" content="A world one OpenCharaAgent character built by hand." />
<meta name="twitter:image" content="https://agent.lunamoth.ai/assets/og.png" />
<link rel="stylesheet" href="assets/style.css" />
<script>
/* The rail sprite is the LCP element but it's injected by JS later, so the
preload scanner can't find it until charas.js runs. Preload it here, as
early as possible, picking the same size the <picture> below will choose
(≤920px → -m) so there's never a double download. */
(function(){
try{
var s=new URLSearchParams(location.search).get('c');
if(!s||!/^[a-z0-9-]+$/.test(s)) return;
var small=window.matchMedia('(max-width:920px)').matches;
var l=document.createElement('link');
l.rel='preload'; l.as='image';
l.href='assets/sprites/'+s+(small?'-m':'')+'.png';
l.setAttribute('fetchpriority','high');
document.head.appendChild(l);
}catch(e){}
})();
</script>
<style>
body{height:100vh; overflow:hidden}
.frame-top{position:relative; z-index:40}
.frame-top .row{padding:13px 24px}
.charlabel{font-family:"JetBrains Mono",monospace; font-size:.64rem; letter-spacing:.12em; text-transform:uppercase;
color:var(--c,var(--brand)); border:1px solid color-mix(in srgb,var(--c,var(--brand)) 40%, transparent);
background:color-mix(in srgb,var(--c,var(--brand)) 10%, transparent); border-radius:999px; padding:6px 12px; white-space:nowrap}
.back{display:inline-flex; align-items:center; gap:8px; cursor:pointer; font-family:"Inter"; font-weight:500; font-size:.9rem;
color:var(--ink); border:1px solid var(--line-2); background:var(--surface); border-radius:999px; padding:9px 16px; transition:.25s var(--ease)}
.back:hover{border-color:var(--c,var(--brand)); color:var(--c,var(--brand)); box-shadow:0 0 0 4px color-mix(in srgb,var(--c,var(--brand)) 12%, transparent)}
.back .a{transition:transform .3s var(--ease)} .back:hover .a{transform:translateX(-4px)}
.layout{position:relative; z-index:2; display:flex; gap:0; height:calc(100vh - 63px); opacity:0; transform:translateY(10px);
transition:opacity .6s var(--ease), transform .6s var(--ease)}
.layout.in{opacity:1; transform:none}
/* left rail = the site identity frame */
.rail{width:360px; flex:0 0 360px; height:100%; overflow-y:auto; padding:30px 28px 36px;
border-right:1px solid var(--line); background:linear-gradient(180deg, color-mix(in srgb,var(--c,var(--brand)) 7%, var(--surface)), var(--surface) 38%);
display:flex; flex-direction:column}
.rail .kic{font-family:"JetBrains Mono",monospace; font-size:.62rem; letter-spacing:.16em; text-transform:uppercase; color:var(--ink-faint); margin-bottom:18px}
.idrow{display:flex; align-items:center; gap:13px}
.idrow .av{width:54px; height:54px; border-radius:14px; object-fit:cover; border:1px solid color-mix(in srgb,var(--c) 45%, var(--line-2));
box-shadow:0 8px 20px -8px color-mix(in srgb,var(--c) 70%, transparent)}
.idrow .nm{font-family:"Fraunces",serif; font-weight:500; font-size:1.5rem; line-height:1; display:flex; align-items:baseline; gap:8px}
.idrow .nm .gl{font-size:.85rem; color:var(--c,var(--brand))}
.idrow .live{margin-top:6px; font-family:"JetBrains Mono",monospace; font-size:.58rem; letter-spacing:.13em; text-transform:uppercase; color:var(--ink-faint); display:flex; align-items:center; gap:6px}
.idrow .live .d{width:6px;height:6px;border-radius:50%;background:var(--c,var(--brand)); box-shadow:0 0 8px var(--c)}
.tagi{font-family:"Fraunces",serif; font-style:italic; font-size:1.06rem; color:var(--ink); margin:16px 0 0; line-height:1.45}
.spritebox{position:relative; margin:18px 0 8px; min-height:300px; display:grid; place-items:center}
.spritebox .halo{position:absolute; width:280px; height:280px; border-radius:50%;
background:radial-gradient(circle, color-mix(in srgb,var(--c) 38%, transparent) 0%, transparent 64%); filter:blur(10px)}
.spritebox img{position:relative; height:300px; width:auto; filter:drop-shadow(0 24px 28px rgba(20,22,46,.26))}
.spritebox picture{display:contents}
.means .h{font-family:"JetBrains Mono",monospace; font-size:.62rem; letter-spacing:.12em; text-transform:uppercase; color:var(--c,var(--brand)); margin:12px 0 9px}
.means .b{color:var(--ink-soft); font-size:.93rem; line-height:1.62}
.metarow{display:flex; gap:8px; flex-wrap:wrap; margin:18px 0}
.rail .acts{margin-top:auto; padding-top:20px; display:flex; flex-direction:column; gap:10px}
.note{font-size:.74rem; color:var(--ink-faint); line-height:1.5; margin-top:14px; padding-top:14px; border-top:1px solid var(--line)}
/* main = the framed actual site */
.viewer{flex:1; min-width:0; height:100%; padding:18px 22px 22px; display:flex; flex-direction:column}
.browser{flex:1; display:flex; flex-direction:column; border:1px solid var(--line-2); border-radius:var(--r-md); overflow:hidden;
background:var(--surface); box-shadow:var(--shadow-lift)}
.browser .bar{display:flex; align-items:center; gap:10px; padding:10px 14px; border-bottom:1px solid var(--line); background:var(--surface-2)}
.browser .lights{display:flex; gap:6px} .browser .lights i{width:11px;height:11px;border-radius:50%;background:var(--line-2)}
.browser .lights i:nth-child(1){background:#ff5f57} .browser .lights i:nth-child(2){background:#febc2e} .browser .lights i:nth-child(3){background:#28c840}
.browser .url{flex:1; font-family:"JetBrains Mono",monospace; font-size:.72rem; color:var(--ink-faint); background:var(--bg);
border:1px solid var(--line); border-radius:8px; padding:6px 12px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap}
.browser .open{font-family:"JetBrains Mono",monospace; font-size:.68rem; color:var(--c,var(--brand)); cursor:pointer; white-space:nowrap;
border:1px solid color-mix(in srgb,var(--c,var(--brand)) 40%, transparent); border-radius:8px; padding:6px 10px; transition:.2s}
.browser .open:hover{background:color-mix(in srgb,var(--c,var(--brand)) 10%, transparent)}
.browser .stagebox{position:relative; flex:1; min-height:0; background:#fff}
html[data-theme="dark"] .browser .stagebox{background:#0c0c12}
.browser iframe{position:absolute; inset:0; width:100%; height:100%; border:0; background:#fff}
.fallback{position:absolute; inset:0; display:none; place-items:center; text-align:center; padding:30px; color:var(--ink-soft);
background:linear-gradient(180deg, color-mix(in srgb,var(--c,var(--brand)) 6%, var(--surface)), var(--surface) 60%)}
html[data-theme="dark"] .fallback{background:linear-gradient(180deg, color-mix(in srgb,var(--c,var(--brand)) 10%, #0c0c12), #0c0c12 60%)}
.fallback.show{display:grid}
.fallback .ff{max-width:340px}
@media(max-width:860px){
body{overflow:auto}
.layout{flex-direction:column; height:auto}
.rail{width:100%; flex:none; border-right:0; border-bottom:1px solid var(--line)}
.spritebox img{height:240px}
.viewer{height:auto; padding:16px}
.browser{height:78vh}
}
/* narrow phones: the header row was overflowing (label + back text + toggles too wide) */
@media(max-width:600px){
.frame-top .row{padding:10px 12px; gap:8px}
.charlabel{display:none}
.back .en-only,.back .zh-only{display:none !important}
.back{padding:9px 12px}
}
</style>
</head>
<body>
<!-- Brand mark — solid moth silhouette, 1:1 with the product's BrandLoader geometry -->
<svg aria-hidden="true" focusable="false" style="position:absolute;width:0;height:0;overflow:hidden"><defs>
<g id="lm-half">
<path d="M 506 402 C 490 360 470 328 442 308 C 470 318 496 352 510 394 Z"/>
<path d="M 507 436 C 460 408 380 360 318 342 C 290 334 276 344 280 366 C 288 412 330 472 394 508 C 442 535 486 542 504 532 Z"/>
<path d="M 503 542 C 455 538 400 556 386 596 C 372 634 392 666 424 672 C 420 716 426 756 444 788 C 450 798 462 796 464 784 C 470 730 476 670 486 626 C 492 596 500 560 503 548 Z"/>
</g>
<g id="lm-moth" transform="rotate(-8 512 530)">
<use href="#lm-half"/>
<use href="#lm-half" transform="matrix(-1,0,0,1,1024,0)"/>
<path d="M 512 404 C 530 404 538 426 537 458 C 536 510 526 562 512 588 C 498 562 488 510 487 458 C 486 426 494 404 512 404 Z"/>
<circle cx="512" cy="404" r="19"/>
</g>
</defs></svg>
<div class="ambient"></div>
<div class="grain"></div>
<header class="topbar frame-top">
<div class="row">
<a class="brand" href="index.html" aria-label="OpenCharaAgent">
<svg class="mk" viewBox="262 290 500 520" fill="currentColor" aria-hidden="true"><use href="#lm-moth"/></svg>
<b>OpenCharaAgent</b>
</a>
<span class="charlabel" id="charlabel"></span>
<div class="grow"></div>
<a class="back" href="index.html" id="back"><span class="a">←</span> <span class="en-only">Home</span><span class="zh-only">返回首页</span></a>
<div class="toggles">
<button class="tg" id="lang" title="语言 / Language">中</button>
<button class="tg" id="theme" title="Theme">☾</button>
</div>
</div>
</header>
<div class="layout" id="layout">
<aside class="rail" id="rail"></aside>
<section class="viewer">
<div class="browser">
<div class="bar">
<div class="lights"><i></i><i></i><i></i></div>
<div class="url" id="url"></div>
<a class="open" id="open" target="_blank" rel="noopener"><span class="en-only">open raw ↗</span><span class="zh-only">原站打开 ↗</span></a>
</div>
<div class="stagebox">
<iframe id="site" title="character site"></iframe>
<div class="fallback" id="fallback">
<div class="ff">
<p class="en-only blk">This preview can’t load inside the frame in your browser. The site is whole and waiting — open it in its own tab.</p>
<p class="zh-only blk">在你的浏览器里,预览无法嵌入显示。但站点完好如初——请在独立标签页中打开它。</p>
<a class="btn btn-primary" id="fallopen" target="_blank" rel="noopener" style="margin-top:14px"><span class="en-only">Open the site ↗</span><span class="zh-only">打开站点 ↗</span></a>
</div>
</div>
</div>
</div>
</section>
</div>
<script src="assets/charas.js"></script>
<script>
const htmlEl=document.documentElement;
const L=(en,zh)=>`<span class="en-only">${en}</span><span class="zh-only">${zh}</span>`;
const params=new URLSearchParams(location.search);
const slug=params.get('c');
const c=(window.CHARAS||[]).find(x=>x.slug===slug);
if(!c){ location.replace('index.html'); }
function pref(k){try{return localStorage.getItem(k)}catch(e){return null}}
function save(k,v){try{localStorage.setItem(k,v)}catch(e){}}
const sysZh=(navigator.language||navigator.userLanguage||'').toLowerCase().indexOf('zh')===0;
htmlEl.setAttribute('lang', params.get('lang')||pref('lm-lang')||(sysZh?'zh':'en'));
htmlEl.setAttribute('data-theme', params.get('theme')||pref('lm-theme')||'dark');
function build(){
document.title=`${c.name} · OpenCharaAgent`;
/* per-character SEO / social metadata */
const canon=`https://agent.lunamoth.ai/frame.html?c=${c.slug}`;
const ogTitle=`${c.name} · ${c.kind_en} — OpenCharaAgent`;
const desc=`${c.tag_en} ${c.mean_en}`;
const set=(id,attr,val)=>{const el=document.getElementById(id); if(el) el.setAttribute(attr,val);};
set('m-desc','content',desc);
set('m-canon','href',canon);
set('og-title','content',ogTitle);
set('og-desc','content',desc);
set('og-url','content',canon);
set('tw-title','content',ogTitle);
set('tw-desc','content',desc);
set('alt-en','href',`${canon}&lang=en`);
set('alt-zh','href',`${canon}&lang=zh`);
set('alt-def','href',canon);
/* per-character structured data: where this page sits, and who made the work */
try{
const ld={ "@context":"https://schema.org", "@graph":[
{ "@type":"BreadcrumbList", "itemListElement":[
{ "@type":"ListItem","position":1,"name":"OpenChara Atrium","item":"https://agent.lunamoth.ai/" },
{ "@type":"ListItem","position":2,"name":`${c.name} — ${c.kind_en}`,"item":canon }
]},
{ "@type":"CreativeWork", "name":`${c.name}'s website`,
"description":desc, "url":canon,
"inLanguage":["en","zh"], "image":`https://agent.lunamoth.ai/assets/shots/${c.slug}.png`,
"isPartOf":{ "@type":"WebSite","name":"OpenCharaAgent","url":"https://agent.lunamoth.ai/" },
"author":{ "@type":"Person","name":c.name,
"description":`${c.kind_en} character running autonomously on OpenCharaAgent` },
"creator":{ "@type":"SoftwareApplication","name":"OpenCharaAgent","url":"https://agent.lunamoth.ai/" } }
]};
const s=document.createElement('script'); s.type='application/ld+json';
s.textContent=JSON.stringify(ld); document.head.appendChild(s);
}catch(e){}
htmlEl.style.setProperty('--c', c.accent);
document.getElementById('charlabel').innerHTML=L('OpenCharaAgent character','OpenCharaAgent 角色');
const src=`characters/${c.slug}/${c.entry}`;
document.getElementById('url').textContent=`~/${c.slug}/${c.entry}`;
document.getElementById('open').href=src;
document.getElementById('fallopen').href=src;
const ifr=document.getElementById('site'); ifr.src=src;
// fallback if the iframe can't render (e.g. some browsers block file:// embeds)
let loaded=false; ifr.addEventListener('load',()=>{loaded=true;});
setTimeout(()=>{ if(!loaded) document.getElementById('fallback').classList.add('show'); }, 2600);
document.getElementById('rail').innerHTML=`
<div class="kic">${L('a creation by a character running on OpenCharaAgent','OpenCharaAgent 中一位角色的创作')}</div>
<div class="idrow">
<img class="av" src="assets/portraits/${c.slug}.png" alt="${c.name} avatar"/>
<div>
<div class="nm">${c.name}<span class="gl">${c.glyph}</span></div>
<div class="live"><span class="d"></span>${L('living','在线生活')}</div>
</div>
</div>
<p class="tagi">${L(c.tag_en,c.tag_zh)}</p>
<div class="spritebox"><div class="halo"></div><picture><source media="(max-width:920px)" srcset="assets/sprites/${c.slug}-m.png"><img src="assets/sprites/${c.slug}.png" alt="${c.name}, a ${c.kind_en} character running on OpenCharaAgent — character art" width="512" height="1200" fetchpriority="high" decoding="async"/></picture></div>
<div class="means">
<div class="h">${L('what this place is to them','这个地方,对它意味着')}</div>
<div class="b">${L(c.mean_en,c.mean_zh)}</div>
</div>
<div class="metarow"><span class="chip brand">${L(c.kind_en,c.kind_zh)}</span><span class="chip">${c.emb}</span></div>
<div class="acts">
<a class="btn btn-primary" href="${src}" target="_blank" rel="noopener">${L('Open the full site','打开完整站点')} <span class="arr">↗</span></a>
<a class="btn btn-ghost" href="index.html" id="back2"><span class="en-only">← Back home</span><span class="zh-only">← 返回首页</span></a>
</div>
<div class="note">${L('You are viewing a world one character built by hand. Step back anytime — the others are still living, just next door.','你正在浏览一位角色亲手搭建的世界。随时可以退后一步——其他角色仍在生活着,就在隔壁。')}</div>`;
requestAnimationFrame(()=>document.getElementById('layout').classList.add('in'));
}
/* smooth leave back home */
function leave(e){ e.preventDefault(); const lo=document.getElementById('layout'); lo.classList.remove('in');
setTimeout(()=>location.href='index.html',360); }
document.addEventListener('click',(e)=>{ const b=e.target.closest('#back,#back2'); if(b) leave(e); });
/* browser Forward restores this page from the bfcache mid-leave (layout faded out) — re-reveal it */
window.addEventListener('pageshow',(e)=>{ if(e.persisted) document.getElementById('layout').classList.add('in'); });
/* toggles */
const langBtn=document.getElementById('lang');
langBtn.onclick=()=>{const zh=htmlEl.getAttribute('lang')==='zh'; const nl=zh?'en':'zh'; htmlEl.setAttribute('lang',nl); save('lm-lang',nl); langBtn.textContent=zh?'中':'EN';};
const themeBtn=document.getElementById('theme');
themeBtn.onclick=()=>{const d=htmlEl.getAttribute('data-theme')==='dark'; const nt=d?'light':'dark'; htmlEl.setAttribute('data-theme',nt); save('lm-theme',nt); themeBtn.textContent=d?'☾':'☀';};
langBtn.textContent=htmlEl.getAttribute('lang')==='zh'?'EN':'中';
themeBtn.textContent=htmlEl.getAttribute('data-theme')==='dark'?'☀':'☾';
if(c) build();
</script>
</body>
</html>