index.html
Rama main · réplica de lectura del repositorio original.
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta
name="description"
content="Modern Microsoft Comic Chat port running live rooms on Cloudflare Durable Objects. Validated & built against the original 1996+ engine to render conversation as a comic strip."
/>
<title>Comic Chat - Web Port of the 1996 Microsoft Classic</title>
<!--if:SITE_ORIGIN-->
<link rel="canonical" href="%SITE_ORIGIN%/" />
<!--/if-->
<meta property="og:type" content="website" />
<meta property="og:title" content="Comic Chat Web" />
<meta
property="og:description"
content="Modern Microsoft Comic Chat port running live rooms on Cloudflare Durable Objects. Validated & built against the original 1996+ engine to render conversation as a comic strip."
/>
<!--if:SITE_ORIGIN-->
<meta property="og:url" content="%SITE_ORIGIN%/" />
<meta property="og:image" content="%SITE_ORIGIN%/og.jpg" />
<meta property="og:image:width" content="1200" />
<meta property="og:image:height" content="600" />
<meta
property="og:image:alt"
content="Three-panel comic strip of Comic Chat characters discussing the TypeScript port"
/>
<meta name="twitter:card" content="summary_large_image" />
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "WebApplication",
"name": "Comic Chat Web",
"url": "%SITE_ORIGIN%/",
"description": "Modern Microsoft Comic Chat port running live rooms on Cloudflare Durable Objects. Validated & built against the original 1996+ engine to render conversation as a comic strip.",
"applicationCategory": "CommunicationApplication",
"operatingSystem": "Any",
"image": "%SITE_ORIGIN%/og.jpg"
}
</script>
<!--/if-->
<link rel="stylesheet" href="/src/browser/room.css" />
<link rel="icon" href="/favicon.ico" sizes="any" />
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png" />
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png" />
<link rel="apple-touch-icon" href="/apple-touch-icon.png" />
<link rel="manifest" href="/manifest.webmanifest" />
</head>
<body class="app">
<svg width="0" height="0" class="icon-sprite" aria-hidden="true">
<defs>
<symbol id="ic-members" viewBox="0 0 16 16">
<circle cx="10.6" cy="5" r="2.1" />
<path d="M6.2 14v-2.1a4.4 4.4 0 0 1 8.8 0V14z" />
<circle
class="gap-s"
cx="5.6"
cy="6.1"
r="2.7"
fill="currentColor"
stroke-width="1"
/>
<path
class="gap-s"
d="M1 14v-2.2a4.6 4.6 0 0 1 9.2 0V14z"
fill="currentColor"
stroke-width="1"
/>
</symbol>
<symbol id="ic-member" viewBox="0 0 16 16">
<circle cx="8" cy="6.1" r="2.7" />
<path d="M3.4 14v-2.2a4.6 4.6 0 0 1 9.2 0V14z" />
</symbol>
<symbol id="ic-pose" viewBox="0 0 16 16">
<g
fill="none"
stroke="currentColor"
stroke-width="0.9"
stroke-linejoin="round"
>
<circle cx="8" cy="8" r="5.4" />
<path
d="M8 8 L8 2.6 M8 8 L12.676 5.3 M8 8 L12.676 10.7 M8 8 L8 13.4 M8 8 L3.324 10.7 M8 8 L3.324 5.3"
/>
</g>
<circle cx="8" cy="8" r="1.4" fill="currentColor" />
</symbol>
<symbol id="ic-bg" viewBox="0 0 16 16">
<rect
x="1.4"
y="2.8"
width="13.2"
height="10.4"
fill="none"
stroke="currentColor"
stroke-width="1.5"
/>
<circle cx="5" cy="6.2" r="1.5" />
<path d="M2.2 13.2 6 8.4l2.4 3 2.1-2.6 3.3 4.4z" />
</symbol>
<symbol id="ic-more" viewBox="0 0 16 16">
<circle cx="3.4" cy="8" r="1.6" />
<circle cx="8" cy="8" r="1.6" />
<circle cx="12.6" cy="8" r="1.6" />
</symbol>
</defs>
</svg>
<header class="titlebar">
<div class="titlebar-heading">
<h1 class="titlebar-title">
<button id="title-home" class="titlebar-text" type="button">
Comic Chat Web
</button>
</h1>
<span id="title-room" class="titlebar-room"></span>
</div>
<span class="titlebar-actions">
<button
id="notify-bell"
class="titlebar-button"
type="button"
aria-pressed="false"
title="Room notifications"
aria-label="Room notifications"
hidden
>
<svg
viewBox="0 0 16 16"
width="18"
height="18"
fill="currentColor"
aria-hidden="true"
>
<path
d="M8 1.2C5.6 1.2 3.9 3 3.9 5.4v3.2L2.5 10.9v1.2h11v-1.2L12.1 8.6V5.4C12.1 3 10.4 1.2 8 1.2z"
/>
<path d="M6.4 13.1a1.6 1.6 0 0 0 3.2 0z" />
</svg>
</button>
<a
class="titlebar-button"
href="/agent-skill.html"
aria-label="Agent skill: build a strip with an AI agent"
title="Agent skill: build a strip with an AI agent"
>
<img
class="titlebar-icon"
src="/assets/help-icon.png?v=2"
alt=""
width="139"
height="128"
/>
</a>
<!--if:SUPPORT_URL-->
<a
class="titlebar-button"
href="%SUPPORT_URL%"
target="_blank"
rel="noreferrer"
aria-label="Buy me a coffee"
title="Buy me a coffee"
>
<svg
viewBox="0 0 16 16"
width="18"
height="18"
fill="none"
stroke="currentColor"
stroke-width="1.4"
stroke-linejoin="round"
stroke-linecap="round"
aria-hidden="true"
>
<path
d="M5.6 2.2c0-.9 1.1-.9 1.1-1.8M9.3 2.2c0-.9 1.1-.9 1.1-1.8"
stroke-width="1.1"
/>
<rect x="2.6" y="4.2" width="10.8" height="2.1" rx="1.05" />
<path
d="M4 8.3h8l-0.85 5.5a1.4 1.4 0 0 1-1.39 1.2H6.24a1.4 1.4 0 0 1-1.39-1.2L4 8.3Z"
/>
</svg>
</a>
<!--/if-->
<a
class="titlebar-button"
href="https://github.com/remsky/comic-chat-web"
target="_blank"
rel="noreferrer"
aria-label="View source on GitHub"
title="View source on GitHub"
>
<svg viewBox="0 0 16 16" width="18" height="18" aria-hidden="true">
<path
fill="currentColor"
d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.01 8.01 0 0 0 16 8c0-4.42-3.58-8-8-8Z"
/>
</svg>
</a>
<a
class="titlebar-button"
href="/studio.html"
title="Build a strip panel by panel"
>
Studio
</a>
</span>
</header>
<main id="room" class="workspace">
<section id="strip" class="strip" aria-label="Comic strip">
<div id="panels" class="strip-page" aria-live="polite"></div>
<ol id="transcript" class="transcript" aria-live="polite"></ol>
</section>
<div
id="sidebar-resizer"
class="sidebar-resizer"
aria-hidden="true"
></div>
<aside class="sidebar" aria-label="Room members">
<section class="sidebar-members">
<h2 class="sidebar-heading">Members</h2>
<p id="notify-count" class="notify-count" hidden></p>
<ul id="roster" class="roster"></ul>
</section>
<section class="sidebar-self">
<span class="profile-name-row">
<input
id="profile-name"
maxlength="24"
autocomplete="nickname"
aria-label="Nickname"
/>
<button id="profile-name-apply" class="strip-button" type="button">
Set
</button>
</span>
<p id="profile-error" class="field-error" role="alert" hidden></p>
<figure id="bodycam" class="bodycam" hidden>
<canvas
id="bodycam-canvas"
tabindex="0"
aria-label="Emotion wheel: drag the dot from the center toward an emotion; distance sets intensity"
></canvas>
<div class="bodycam-actions">
<button
id="bodycam-lock"
class="bodycam-edit"
type="button"
aria-pressed="false"
title="Freeze expression"
>
<svg class="lock-open" viewBox="0 0 16 16" aria-hidden="true">
<path
d="M11 1a4 4 0 0 0-4 4v2h1.8V5a2.2 2.2 0 0 1 4.4 0v1H15V5a4 4 0 0 0-4-4z"
/>
<rect x="1" y="7" width="10" height="8" />
</svg>
<svg class="lock-closed" viewBox="0 0 16 16" aria-hidden="true">
<path
d="M8 1a4 4 0 0 0-4 4v2h1.8V5a2.2 2.2 0 0 1 4.4 0v2H12V5a4 4 0 0 0-4-4z"
/>
<rect x="3" y="7" width="10" height="8" />
</svg>
</button>
<button
id="bodycam-send"
class="bodycam-edit"
type="button"
title="Send expression"
>
<svg viewBox="0 0 16 16" aria-hidden="true">
<path
d="M8 1.5l5.5 5.5h-3v8h-5V7h-3z"
fill="none"
stroke="currentColor"
stroke-width="1.4"
/>
</svg>
</button>
</div>
<button
id="avatar-edit"
class="bodycam-edit"
type="button"
aria-expanded="false"
aria-controls="profile-avatar-picker"
>
Change Character…
</button>
</figure>
<fieldset
id="profile-avatar-picker"
class="character-picker profile-picker-grid"
>
<legend>Character</legend>
<div id="profile-avatar-options" class="character-options"></div>
</fieldset>
</section>
<div id="background-picker" class="background-picker" hidden>
<label class="background-picker-classic">
Background
<select id="background-select"></select>
</label>
<fieldset class="background-picker-grid character-picker">
<legend>Background</legend>
<div id="background-options" class="character-options"></div>
</fieldset>
</div>
<button
id="sidebar-more"
class="strip-button"
type="button"
aria-expanded="false"
aria-controls="sidebar-controls"
>
More…
</button>
<div id="sidebar-controls" class="sidebar-controls">
<div id="profile-picker" class="profile-picker">
<fieldset class="room-picker">
<legend>Room</legend>
<div id="profile-room-options" class="room-options"></div>
</fieldset>
</div>
<label class="tweaks-toggle">
<input id="text-toggle" type="checkbox" />
Plain text view
</label>
<label class="tweaks-toggle">
<input id="title-toggle" type="checkbox" checked />
Title screen
</label>
<label class="tweaks-toggle">
<input
id="modern-toggle"
type="checkbox"
checked
aria-describedby="tweaks-hint"
/>
Modern tweaks
</label>
<label class="tweaks-toggle tweaks-toggle--nested">
<input id="name-toggle" type="checkbox" />
Show nametags
</label>
<p id="tweaks-hint" class="tweaks-hint">
Smaller text fits more per balloon. Turn off for the original 1996
layout; panels may break differently for other viewers.
</p>
<div class="strip-actions">
<button id="save-strip" class="strip-button" type="button">
Save Strip…
</button>
<!-- the build stamps the operator details the report draft names -->
<button
id="report-room"
class="strip-button"
type="button"
data-site="%SITE_ORIGIN%"
data-contact="%OPERATOR_CONTACT%"
data-jurisdiction="%OPERATOR_JURISDICTION%"
>
Report…
</button>
<button
id="leave-room"
class="strip-button strip-button--leave"
type="button"
>
Disconnect
</button>
</div>
</div>
</aside>
</main>
<div class="dock">
<div id="mobile-sheet" class="mobile-sheet" hidden></div>
<nav class="mobile-toolbar" aria-label="Panels">
<button
type="button"
class="toolbar-button"
data-panel="members"
aria-expanded="false"
>
<svg class="toolbar-icon members-many" aria-hidden="true">
<use href="#ic-members" />
</svg>
<svg class="toolbar-icon members-alone" aria-hidden="true">
<use href="#ic-member" />
</svg>
<span>Members</span>
</button>
<button
type="button"
class="toolbar-button"
data-panel="pose"
aria-expanded="false"
>
<svg class="toolbar-icon" aria-hidden="true">
<use href="#ic-pose" />
</svg>
<span>Pose</span>
</button>
<button
type="button"
class="toolbar-button"
data-panel="bg"
aria-expanded="false"
hidden
>
<svg class="toolbar-icon" aria-hidden="true">
<use href="#ic-bg" />
</svg>
<span>Background</span>
</button>
<button
type="button"
class="toolbar-button"
data-panel="more"
aria-expanded="false"
>
<svg class="toolbar-icon" aria-hidden="true">
<use href="#ic-more" />
</svg>
<span>More</span>
</button>
</nav>
<p id="filter-notice" class="filter-notice" role="alert" hidden></p>
<form id="composer" class="composer">
<select id="composer-mode" aria-label="Balloon type">
<option value="1">Say</option>
<option value="3">Think</option>
<option value="2">Whisper</option>
<option value="5">Action</option>
</select>
<input
id="composer-text"
maxlength="1000"
required
placeholder="Type your message"
autocomplete="off"
aria-label="Message"
/>
<button type="submit">Send</button>
</form>
<footer class="legal-footer">
<span>© 2026 Comic Chat Web contributors</span>
<span aria-hidden="true">·</span>
<a href="https://github.com/remsky/comic-chat-web/blob/main/LICENSE"
>AGPL-3.0</a
>
<span aria-hidden="true">·</span>
<a href="/legal.html#terms">Terms</a>
<span aria-hidden="true">·</span>
<a href="/legal.html#privacy">Privacy</a>
</footer>
</div>
<section id="join" class="join-overlay" aria-label="Connect to a room">
<div class="dialog">
<header class="dialog-titlebar">Connect</header>
<form id="join-form" class="dialog-body">
<label>
Nickname
<input
id="join-name"
name="name"
required
maxlength="24"
placeholder="Your name"
autocomplete="nickname"
/>
</label>
<p id="join-name-error" class="field-error" role="alert" hidden></p>
<fieldset class="room-picker">
<legend>Room</legend>
<div id="join-room-options" class="room-options"></div>
</fieldset>
<fieldset id="join-avatar" class="character-picker">
<legend>Character</legend>
<div class="character-options"></div>
<p id="join-avatar-error" class="field-error" role="alert" hidden>
Pick a character to connect.
</p>
</fieldset>
<div class="dialog-buttons">
<p class="join-consent">
Rooms are public. By connecting you confirm you are 18 or older and
accept the <a href="/legal.html#terms">Terms</a> and
<a href="/legal.html#privacy">Privacy Policy</a>.
</p>
<label class="remember-toggle">
<input id="join-remember" type="checkbox" />
Remember me
</label>
<button type="submit">Connect</button>
</div>
</form>
</div>
<p class="join-tagline">
Modern port of Microsoft Comic Chat (1996), drawing live chat as comic
strips on Cloudflare Durable Objects.
</p>
</section>
<dialog id="notify-dialog" class="notify-dialog">
<header class="dialog-titlebar">Notifications</header>
<form method="dialog" class="dialog-body">
<p id="notify-dialog-text" class="notify-dialog-text"></p>
<div class="dialog-buttons">
<button type="submit">Close</button>
</div>
</form>
</dialog>
<p id="status" class="status" role="status">Loading fonts and avatar art…</p>
<script type="module" src="/src/browser/room.ts"></script>
</body>
</html>