<div class="wa-mockup">
<!-- ====== HEADER (like WhatsApp top bar) ====== -->
<header class="wa-topbar" role="banner" aria-label="Chat header">
<div class="wa-avatar" aria-hidden="true"></div>
<div class="wa-title">
<div class="wa-name">Nic’s Demo Chat</div>
<div class="wa-status"><span class="wa-dot"></span>online</div>
</div>
<div class="wa-actions" aria-label="Header actions">
<button type="button" class="wa-iconbtn" aria-label="Search">
<!-- simple inline SVG icon -->
<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M10 18a8 8 0 1 1 5.293-14.01A8 8 0 0 1 10 18Zm0-2a6 6 0 1 0-4.243-10.243A6 6 0 0 0 10 16Zm10.707 4.293-5.12-5.12 1.414-1.414 5.12 5.12-1.414 1.414Z"/></svg>
</button>
<button type="button" class="wa-iconbtn" aria-label="More">
<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M12 7a2 2 0 1 1 0-4 2 2 0 0 1 0 4Zm0 7a2 2 0 1 1 0-4 2 2 0 0 1 0 4Zm0 7a2 2 0 1 1 0-4 2 2 0 0 1 0 4Z"/></svg>
</button>
</div>
</header>
<!-- ====== CHAT AREA ====== -->
<main class="wa-chat" role="main" aria-label="Chat messages" id="waChatArea">
<div class="wa-day">Today</div>
<div class="wa-msg wa-in">
<div class="wa-bubble">
Hey! This is a fake WhatsApp-style UI mockup in a Gutenberg HTML block 😄
<div class="wa-meta">
<time datetime="2026-02-21T20:10">8:10pm</time>
</div>
</div>
</div>
<div class="wa-msg wa-out">
<div class="wa-bubble">
Nice — so the *layout* is HTML, the *look* is CSS, and the *behavior* is JS?
<div class="wa-meta">
<time datetime="2026-02-21T20:11">8:11pm</time>
<span class="wa-ticks" aria-label="Delivered">
<!-- checkmarks -->
<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M9.2 16.6 4.9 12.3 3.5 13.7l5.7 5.7L21 7.6 19.6 6.2Z"/></svg>
<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M9.2 16.6 4.9 12.3 3.5 13.7l5.7 5.7L21 7.6 19.6 6.2Z"/></svg>
</span>
</div>
</div>
</div>
<div class="wa-msg wa-in">
<div class="wa-bubble">
Exactly. Try typing below and hit Enter — it “sends” a message and autoscrolls.
<div class="wa-meta">
<time datetime="2026-02-21T20:12">8:12pm</time>
</div>
</div>
</div>
</main>
<!-- ====== INPUT BAR ====== -->
<footer class="wa-inputbar" role="contentinfo" aria-label="Message input">
<button type="button" class="wa-iconbtn" aria-label="Emoji">
<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M12 22A10 10 0 1 1 22 12 10.012 10.012 0 0 1 12 22Zm0-18a8 8 0 1 0 8 8 8.009 8.009 0 0 0-8-8Zm-3 8a1.5 1.5 0 1 1 1.5-1.5A1.502 1.502 0 0 1 9 12Zm6 0a1.5 1.5 0 1 1 1.5-1.5A1.502 1.502 0 0 1 15 12Zm-3 6a5.5 5.5 0 0 1-4.763-2.75l1.726-1A3.5 3.5 0 0 0 12 16a3.5 3.5 0 0 0 3.037-1.75l1.726 1A5.5 5.5 0 0 1 12 18Z"/></svg>
</button>
<div class="wa-inputwrap">
<label class="wa-sr-only" for="waMessageInput">Type a message</label>
<input id="waMessageInput" class="wa-input" type="text" placeholder="Message" autocomplete="off" />
</div>
<button type="button" class="wa-send" id="waSendBtn" aria-label="Send message">
<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M2 21 23 12 2 3v7l15 2-15 2v7Z"/></svg>
</button>
</footer>
<!-- ====== EXPLAINER ====== -->
<section class="wa-explain" aria-label="How it works">
<h3>How this mock chat UI is built (HTML + CSS + JS)</h3>
<div class="wa-explain-grid">
<div class="wa-card">
<h4>1) HTML = the structure</h4>
<ul>
<li><code><header class="wa-topbar"></code> = top bar (avatar, name, actions)</li>
<li><code><main class="wa-chat"></code> = scrollable message area</li>
<li><code>.wa-msg.wa-in</code> and <code>.wa-msg.wa-out</code> = left/right message wrappers</li>
<li><code>.wa-bubble</code> = the message bubble itself</li>
<li><code><footer class="wa-inputbar"></code> = input + send button</li>
</ul>
</div>
<div class="wa-card">
<h4>2) CSS = the look & layout</h4>
<ul>
<li><code>display:flex</code> aligns the header and input elements neatly</li>
<li><code>.wa-in</code> vs <code>.wa-out</code> changes bubble color + alignment</li>
<li><code>.wa-chat</code> uses <code>overflow:auto</code> so messages scroll</li>
<li>Soft shadows + rounded corners mimic a modern chat aesthetic</li>
</ul>
</div>
<div class="wa-card">
<h4>3) JS = behavior</h4>
<ul>
<li>Reads the input value</li>
<li>Creates a new message element (<code>document.createElement</code>)</li>
<li>Appends it into the chat area</li>
<li>Auto-scrolls to the newest message</li>
<li>Enter key triggers “send”</li>
</ul>
</div>
</div>
<details class="wa-details">
<summary>Tip: what to edit first</summary>
<ul>
<li>Change the contact name in <code>.wa-name</code></li>
<li>Change initial messages inside <code><main class="wa-chat"></code></li>
<li>Change bubble colors in CSS variables at the top of the CSS block</li>
</ul>
</details>
</section>
<!-- ====== CSS (scoped) ====== -->
<style>
.wa-mockup{
--wa-bg: #0b141a;
--wa-panel: #111b21;
--wa-panel-2: #0f1a20;
--wa-text: #e9edef;
--wa-subtext: rgba(233,237,239,.72);
--wa-in: #1f2c34;
--wa-out: #005c4b;
--wa-border: rgba(255,255,255,.08);
--wa-shadow: 0 12px 30px rgba(0,0,0,.35);
--wa-radius: 18px;
font-family: ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial, "Apple Color Emoji","Segoe UI Emoji";
color: var(--wa-text);
max-width: 520px;
margin: 0 auto;
border: 1px solid var(--wa-border);
border-radius: 22px;
overflow: hidden;
box-shadow: var(--wa-shadow);
background: var(--wa-panel);
}
.wa-topbar{
display:flex;
align-items:center;
gap: 12px;
padding: 12px 14px;
background: var(--wa-panel-2);
border-bottom: 1px solid var(--wa-border);
}
.wa-avatar{
width: 38px;
height: 38px;
border-radius: 999px;
background: radial-gradient(circle at 30% 30%, rgba(255,255,255,.18), rgba(255,255,255,.05));
border: 1px solid var(--wa-border);
flex: 0 0 auto;
}
.wa-title{ flex: 1 1 auto; min-width: 0; }
.wa-name{
font-weight: 700;
font-size: 14px;
line-height: 1.2;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.wa-status{
display:flex;
align-items:center;
gap: 6px;
font-size: 12px;
color: var(--wa-subtext);
margin-top: 2px;
}
.wa-dot{
width: 6px; height: 6px; border-radius: 999px;
background: #35d07f;
box-shadow: 0 0 0 3px rgba(53,208,127,.12);
}
.wa-actions{ display:flex; gap: 6px; }
.wa-iconbtn{
width: 38px;
height: 38px;
border: 1px solid var(--wa-border);
background: rgba(255,255,255,.04);
border-radius: 12px;
display:grid;
place-items:center;
cursor:pointer;
}
.wa-iconbtn svg{ width: 18px; height: 18px; fill: var(--wa-text); opacity: .9; }
.wa-chat{
background:
radial-gradient(circle at 10% 10%, rgba(255,255,255,.04), transparent 40%),
radial-gradient(circle at 90% 40%, rgba(255,255,255,.03), transparent 40%),
var(--wa-bg);
padding: 14px;
height: 420px;
overflow: auto;
}
.wa-day{
display:inline-block;
margin: 0 auto 12px;
padding: 6px 10px;
border-radius: 999px;
background: rgba(255,255,255,.06);
border: 1px solid var(--wa-border);
color: var(--wa-subtext);
font-size: 12px;
position: sticky;
top: 8px;
left: 50%;
transform: translateX(-50%);
backdrop-filter: blur(6px);
}
.wa-msg{ display:flex; margin: 10px 0; }
.wa-msg.wa-in{ justify-content:flex-start; }
.wa-msg.wa-out{ justify-content:flex-end; }
.wa-bubble{
max-width: 78%;
padding: 10px 12px 8px;
border-radius: var(--wa-radius);
border: 1px solid var(--wa-border);
line-height: 1.35;
font-size: 14px;
position: relative;
word-wrap: break-word;
white-space: pre-wrap;
}
.wa-in .wa-bubble{ background: var(--wa-in); border-top-left-radius: 8px; }
.wa-out .wa-bubble{ background: var(--wa-out); border-top-right-radius: 8px; }
.wa-meta{
display:flex;
align-items:center;
justify-content:flex-end;
gap: 6px;
margin-top: 6px;
font-size: 11px;
color: rgba(255,255,255,.72);
}
.wa-ticks{ display:inline-flex; gap: 2px; opacity: .95; }
.wa-ticks svg{ width: 14px; height: 14px; fill: rgba(255,255,255,.85); }
.wa-inputbar{
display:flex;
align-items:center;
gap: 10px;
padding: 12px;
background: var(--wa-panel-2);
border-top: 1px solid var(--wa-border);
}
.wa-inputwrap{ flex: 1 1 auto; }
.wa-input{
width: 100%;
padding: 12px 12px;
border-radius: 14px;
border: 1px solid var(--wa-border);
background: rgba(255,255,255,.05);
color: var(--wa-text);
outline: none;
font-size: 14px;
}
.wa-input::placeholder{ color: rgba(233,237,239,.55); }
.wa-send{
width: 44px;
height: 44px;
border-radius: 14px;
border: 1px solid var(--wa-border);
background: rgba(53,208,127,.18);
cursor:pointer;
display:grid;
place-items:center;
}
.wa-send svg{ width: 18px; height: 18px; fill: var(--wa-text); }
.wa-explain{
padding: 14px;
background: #0f1a20;
border-top: 1px solid var(--wa-border);
}
.wa-explain h3{
margin: 0 0 10px;
font-size: 16px;
letter-spacing: .2px;
}
.wa-explain-grid{
display:grid;
grid-template-columns: 1fr;
gap: 10px;
}
@media (min-width: 640px){
.wa-explain-grid{ grid-template-columns: 1fr 1fr 1fr; }
}
.wa-card{
border: 1px solid var(--wa-border);
border-radius: 16px;
background: rgba(255,255,255,.03);
padding: 12px;
}
.wa-card h4{
margin: 0 0 8px;
font-size: 13px;
opacity: .95;
}
.wa-card ul{
margin: 0;
padding-left: 18px;
color: var(--wa-subtext);
font-size: 12.5px;
line-height: 1.45;
}
.wa-card code{
background: rgba(255,255,255,.06);
padding: 2px 6px;
border-radius: 8px;
border: 1px solid var(--wa-border);
color: var(--wa-text);
font-size: 12px;
}
.wa-details{
margin-top: 10px;
border: 1px solid var(--wa-border);
border-radius: 14px;
padding: 10px 12px;
background: rgba(255,255,255,.02);
color: var(--wa-subtext);
font-size: 13px;
}
.wa-details summary{
cursor: pointer;
color: var(--wa-text);
font-weight: 600;
margin-bottom: 6px;
}
.wa-sr-only{
position:absolute;
width:1px;
height:1px;
padding:0;
margin:-1px;
overflow:hidden;
clip:rect(0,0,0,0);
white-space:nowrap;
border:0;
}
</style>
<!-- ====== JS (scoped) ====== -->
<script>
(function(){
const root = document.currentScript.closest('.wa-mockup');
if (!root) return;
const chat = root.querySelector('#waChatArea');
const input = root.querySelector('#waMessageInput');
const sendBtn = root.querySelector('#waSendBtn');
function pad2(n){ return String(n).padStart(2,'0'); }
function formatTime(d){
let h = d.getHours();
const m = pad2(d.getMinutes());
const ampm = h >= 12 ? 'pm' : 'am';
h = h % 12; if (h === 0) h = 12;
return h + ':' + m + ampm;
}
function scrollToBottom(){
chat.scrollTop = chat.scrollHeight;
}
function addOutgoingMessage(text){
const now = new Date();
const wrap = document.createElement('div');
wrap.className = 'wa-msg wa-out';
const bubble = document.createElement('div');
bubble.className = 'wa-bubble';
bubble.textContent = text;
const meta = document.createElement('div');
meta.className = 'wa-meta';
const time = document.createElement('time');
time.dateTime = now.toISOString();
time.textContent = formatTime(now);
const ticks = document.createElement('span');
ticks.className = 'wa-ticks';
ticks.setAttribute('aria-label', 'Delivered');
ticks.innerHTML = `
<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M9.2 16.6 4.9 12.3 3.5 13.7l5.7 5.7L21 7.6 19.6 6.2Z"/></svg>
<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M9.2 16.6 4.9 12.3 3.5 13.7l5.7 5.7L21 7.6 19.6 6.2Z"/></svg>
`;
meta.appendChild(time);
meta.appendChild(ticks);
bubble.appendChild(meta);
wrap.appendChild(bubble);
chat.appendChild(wrap);
scrollToBottom();
}
function send(){
const text = (input.value || '').trim();
if (!text) return;
addOutgoingMessage(text);
input.value = '';
input.focus();
// Optional: fake “reply” after a moment (demo behavior)
window.setTimeout(() => {
const reply = document.createElement('div');
reply.className = 'wa-msg wa-in';
const bubble = document.createElement('div');
bubble.className = 'wa-bubble';
bubble.textContent = "Mock reply: got it ✅ (this is just a demo)";
const meta = document.createElement('div');
meta.className = 'wa-meta';
const time = document.createElement('time');
const now = new Date();
time.dateTime = now.toISOString();
time.textContent = formatTime(now);
meta.appendChild(time);
bubble.appendChild(meta);
reply.appendChild(bubble);
chat.appendChild(reply);
scrollToBottom();
}, 650);
}
sendBtn.addEventListener('click', send);
input.addEventListener('keydown', (e) => {
if (e.key === 'Enter') {
e.preventDefault();
send();
}
});
scrollToBottom();
})();
</script>
</div>