<main class="thread">
<p class="msg-user">Is 221 a prime number?</p>
<article class="msg-ai" id="reply" aria-label="Assistant reply" aria-busy="false">
<div class="reasoning" id="reasoning">
<button type="button" class="reasoning-toggle" id="reasoning-toggle" aria-expanded="true" aria-controls="reasoning-body">
<span class="reasoning-label" id="reasoning-label">Thinking</span>
<span class="reasoning-time" id="reasoning-time"></span>
<svg class="chevron" viewBox="0 0 20 20" aria-hidden="true"><path d="M6 8l4 4 4-4"></path></svg>
</button>
<div class="reasoning-body" id="reasoning-body">
<div class="reasoning-inner">
<p id="reasoning-text"></p>
</div>
</div>
</div>
<p class="answer" id="answer"></p>
</article>
<div class="actions">
<button type="button" class="btn" id="stop" disabled>Stop</button>
<button type="button" class="btn" id="retry">Ask again</button>
</div>
<p class="sr-only" id="status" role="status"></p>
</main>
*, *::before, *::after {
box-sizing: border-box;
}
:root {
--page: #f8fafc;
--text: #0f172a;
--muted: #5b6678;
--faint: #64748b;
--line: #e2e8f0;
--bubble: #e8eef6;
--shine: #0f172a;
--focus: #2563eb;
}
body {
font-family: system-ui, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
margin: 0;
background: var(--page);
color: var(--text);
}
.thread {
display: flex;
flex-direction: column;
gap: 20px;
max-width: 640px;
margin: 0 auto;
padding: clamp(28px, 12vh, 96px) 20px 32px;
}
.msg-user {
align-self: flex-end;
max-width: 80%;
margin: 0;
padding: 12px 18px;
border-radius: 20px 20px 6px 20px;
background: var(--bubble);
line-height: 1.5;
overflow-wrap: anywhere;
}
.msg-ai {
font-size: 1.0625rem;
line-height: 1.65;
overflow-wrap: anywhere;
}
.reasoning-toggle {
display: inline-flex;
align-items: center;
gap: 8px;
min-height: 36px;
padding: 4px 6px;
margin-left: -6px;
border: 0;
border-radius: 8px;
background: none;
color: var(--muted);
font: inherit;
font-size: 0.9375rem;
font-weight: 600;
cursor: pointer;
}
.reasoning-toggle:hover {
color: var(--text);
}
.reasoning-toggle:focus-visible {
outline: 2px solid var(--focus);
outline-offset: 2px;
}
.reasoning-time {
font-weight: 500;
font-variant-numeric: tabular-nums;
color: var(--faint);
}
.reasoning-time:empty {
display: none;
}
.chevron {
width: 18px;
height: 18px;
fill: none;
stroke: currentColor;
stroke-width: 2;
stroke-linecap: round;
stroke-linejoin: round;
transition: transform 0.25s ease;
}
.is-collapsed .chevron {
transform: rotate(-90deg);
}
.is-thinking .reasoning-label {
background: linear-gradient(90deg, var(--muted) 0%, var(--muted) 40%, var(--shine) 50%, var(--muted) 60%, var(--muted) 100%);
background-size: 250% 100%;
-webkit-background-clip: text;
background-clip: text;
color: transparent;
animation: shimmer 1.8s linear infinite;
}
.reasoning-body {
display: grid;
grid-template-rows: 1fr;
transition: grid-template-rows 0.3s ease;
}
.is-collapsed .reasoning-body {
grid-template-rows: 0fr;
}
.reasoning-inner {
min-height: 0;
overflow: hidden;
}
.is-collapsed .reasoning-inner {
visibility: hidden;
transition: visibility 0s 0.3s;
}
.reasoning-inner p {
margin: 8px 0 4px;
padding-left: 14px;
border-left: 2px solid var(--line);
color: var(--muted);
font-size: 0.9375rem;
line-height: 1.65;
}
.answer {
margin: 14px 0 0;
}
.answer:empty {
display: none;
}
.answer.is-muted {
color: var(--faint);
font-style: italic;
}
.actions {
display: flex;
gap: 10px;
}
.btn {
min-height: 40px;
padding: 8px 16px;
border: 1px solid #b8c2d0;
border-radius: 10px;
background: transparent;
color: var(--text);
font: inherit;
font-size: 0.9375rem;
font-weight: 600;
cursor: pointer;
}
.btn:hover:not(:disabled) {
background: var(--bubble);
}
.btn:focus-visible {
outline: 2px solid var(--focus);
outline-offset: 2px;
}
.btn:disabled {
opacity: 0.5;
cursor: default;
}
.sr-only {
position: absolute;
width: 1px;
height: 1px;
overflow: hidden;
clip-path: inset(50%);
white-space: nowrap;
}
@keyframes shimmer {
from {
background-position: 100% 0;
}
to {
background-position: 0 0;
}
}
@media (pointer: coarse) {
.reasoning-toggle,
.btn {
min-height: 44px;
}
}
@media (prefers-color-scheme: dark) {
:root {
--page: #0f172a;
--text: #e2e8f0;
--muted: #a3afc2;
--faint: #94a3b8;
--line: #334155;
--bubble: #1e293b;
--shine: #ffffff;
--focus: #60a5fa;
}
.btn {
border-color: #475569;
}
}
@media (prefers-reduced-motion: reduce) {
.is-thinking .reasoning-label {
animation: none;
background: none;
color: var(--muted);
}
.reasoning-body,
.chevron {
transition: none;
}
}
const reasoningText = "221 is odd, so 2 is not a factor. Its digits add up to 5, so 3 is out too, and it doesn't end in 0 or 5. Next, 221 ÷ 7 is about 31.6 and 221 ÷ 11 is about 20.1, so neither divides evenly. 221 ÷ 13 is exactly 17. I only need to test primes up to 14, because 15 × 15 is already 225.";
const answerText = "No, 221 is not prime. It equals 13 × 17, so its divisors are 1, 13, 17 and 221.";
const reply = document.getElementById("reply");
const panel = document.getElementById("reasoning");
const toggle = document.getElementById("reasoning-toggle");
const label = document.getElementById("reasoning-label");
const clockEl = document.getElementById("reasoning-time");
const thoughts = document.getElementById("reasoning-text");
const answer = document.getElementById("answer");
const stopBtn = document.getElementById("stop");
const retryBtn = document.getElementById("retry");
const status = document.getElementById("status");
let run = 0;
let phase = "idle";
let started = 0;
let streamTimer = null;
let clockTimer = null;
let userToggled = false;
function setOpen(open) {
panel.classList.toggle("is-collapsed", !open);
toggle.setAttribute("aria-expanded", String(open));
}
function elapsed() {
return Math.floor((performance.now() - started) / 1000);
}
function seconds(n) {
return n + (n === 1 ? " second" : " seconds");
}
function wait(ms) {
return new Promise(function(resolve) {
setTimeout(resolve, ms);
});
}
// Add the text to an element one word at a time
function streamWords(el, text, delay) {
const words = text.split(" ");
let i = 0;
return new Promise(function(resolve) {
streamTimer = setInterval(function() {
el.textContent += (i ? " " : "") + words[i];
i++;
if (i === words.length) {
clearInterval(streamTimer);
resolve();
}
}, delay);
});
}
function setBusy(busy) {
const focused = document.activeElement;
reply.setAttribute("aria-busy", String(busy));
stopBtn.disabled = !busy;
retryBtn.disabled = busy;
// Move focus off a button that was just disabled
if (focused === stopBtn && !busy) retryBtn.focus();
if (focused === retryBtn && busy) stopBtn.focus();
}
async function ask() {
const id = ++run;
clearInterval(streamTimer);
clearInterval(clockTimer);
phase = "thinking";
started = performance.now();
userToggled = false;
thoughts.textContent = "";
answer.textContent = "";
answer.classList.remove("is-muted");
label.textContent = "Thinking";
clockEl.textContent = "0s";
panel.classList.add("is-thinking");
setOpen(true);
setBusy(true);
status.textContent = "Thinking";
// Tick the timer while the model thinks
clockTimer = setInterval(function() {
clockEl.textContent = elapsed() + "s";
}, 250);
await streamWords(thoughts, reasoningText, 70);
if (id !== run) return;
await wait(400);
if (id !== run) return;
clearInterval(clockTimer);
const took = Math.max(1, elapsed());
panel.classList.remove("is-thinking");
label.textContent = "Thought for " + seconds(took);
clockEl.textContent = "";
// Fold the reasoning away unless the reader opened or closed it
if (!userToggled) setOpen(false);
status.textContent = "Thought for " + seconds(took);
phase = "answering";
await streamWords(answer, answerText, 45);
if (id !== run) return;
phase = "idle";
setBusy(false);
status.textContent = "Answer ready";
}
function stop() {
if (phase === "idle") return;
run++;
clearInterval(streamTimer);
clearInterval(clockTimer);
if (phase === "thinking") {
const s = elapsed();
panel.classList.remove("is-thinking");
label.textContent = s ? "Stopped after " + seconds(s) : "Stopped";
clockEl.textContent = "";
}
if (answer.textContent) {
answer.textContent += " (stopped)";
} else {
answer.textContent = "You stopped this reply.";
answer.classList.add("is-muted");
}
phase = "idle";
setBusy(false);
status.textContent = "Reply stopped";
}
toggle.addEventListener("click", function() {
userToggled = true;
setOpen(toggle.getAttribute("aria-expanded") !== "true");
});
stopBtn.addEventListener("click", stop);
retryBtn.addEventListener("click", ask);
setTimeout(ask, 500);