
Gemini Design
- 1 installs
- 1 repo stars
- Updated March 20, 2026
- johnlindquist/gemini-design
Generates layout and design mockup variations via the Gemini CLI using your project's design context, then builds a comparison preview gallery.
About
Combines a project's design language file with Gemini CLI to produce multiple design mockup variations as routes or components, each differing in one dimension, plus a preview gallery. A developer uses it when mocking up layouts or exploring design variations.
- Generates 3 variations by default, each varying one dimension for meaningful comparison
- Requires Gemini CLI and a .impeccable.md design-context file
Gemini Design by the numbers
- 1 all-time installs (skills.sh)
- Ranked #1,609 of 1,880 Design & UI/UX skills by installs in the Skillselion catalog
- Data as of Aug 2, 2026 (Skillselion catalog sync)
npx skills add https://github.com/johnlindquist/gemini-design --skill gemini-designAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 1 |
|---|---|
| repo stars | ★ 1 |
| Last updated | March 20, 2026 |
| Repository | johnlindquist/gemini-design ↗ |
What it does
Generates layout and design mockup variations via the Gemini CLI using your project's design context, then builds a comparison preview gallery.
Files
Generate design mockups by combining your project's design language (.impeccable.md) with Gemini CLI's layout generation capabilities. Produces multiple variations with a comparison preview.
Prerequisites
1. Gemini CLI must be installed and authenticated. Run gemini --version to verify — if it fails, stop and tell the user. 2. Design context must exist — check .impeccable.md in project root. If missing, run teach-impeccable first. Do NOT proceed without design context.
Workflow
Step 1: Gather Context
Read all that exist:
.impeccable.md— design language, brand, principlespackage.json— framework, dependencies, available libraries- Existing routes/pages/components relevant to the request
- Design tokens / CSS variables / tailwind config / theme files
Determine the output format from the project:
- Next.js App Router →
app/mockups/v1/page.tsx,v2/page.tsx,v3/page.tsx - Next.js Pages →
pages/mockups/v1.tsx,v2.tsx,v3.tsx - Remix/SvelteKit → framework-appropriate route files
- React SPA →
src/components/mockups/FeatureV1.tsx,V2.tsx,V3.tsx - Plain HTML →
mockups/v1.html,v2.html,v3.html - Other → ask the user
Step 2: Build the Gemini Prompt
Use the prompt-template reference. Write the prompt to a temp file — never inline it in -p (escaping issues).
Key rules:
- Always include full
.impeccable.mdcontent - Include existing code context (the component being redesigned, or similar components for style reference)
- Include relevant design reference excerpts — pull from
frontend-designskill references (typography, color, spatial, motion) when they match the task. See design-references. - Specify exact file format, framework, and available dependencies
- Request a single complete file — no explanations, no markdown wrapping, no import maps
- For large codebases, use
packxto bundle context. See context-bundling.
Step 3: Generate with Gemini CLI
Write prompt to temp file and pipe via stdin:
cat /tmp/gemini-prompt-v1.txt | gemini -m gemini-3.1-pro-preview -p "Generate the file exactly as instructed. Output ONLY the file content, no markdown fences." -y --sandboxFlags: -y (auto-approve file writes), --sandbox (safe execution).
Step 4: Validate Output
After each generation, verify the output: 1. Check the file exists and is non-empty 2. Check it contains valid JSX/HTML (look for opening tags) 3. If Gemini wrapped output in markdown fences (\\\`), strip them 4. If the file is truncated or contains errors, re-run with a shorter prompt or split the component
If validation fails twice, tell the user and suggest simplifying the request.
Step 5: Create Variations
Generate 3 variations by default. Each variation should differ in exactly one dimension — this makes comparison meaningful. See variation-strategies for the full matrix.
Quick reference — pick one dimension per set:
- Layout: grid / stack / asymmetric / sidebar / split-screen
- Density: spacious / balanced / compact
- Hierarchy: typography-led / color-led / spatial
- Interaction model: static / progressive-disclosure / command-palette
- Visual tone: minimal / editorial / bold-graphic / organic
Run sequentially with 2s delay between calls to avoid rate limits:
for v in 1 2 3; do
cat /tmp/gemini-prompt-v${v}.txt | gemini -m gemini-3.1-pro-preview -p "..." -y --sandbox
sleep 2
doneStep 6: Create Comparison Preview
Generate a preview gallery page that shows all variations with navigation. Use the preview-gallery reference for framework-specific templates.
The gallery should provide:
- Tab/button navigation between variations
- Labels describing each variation's design direction
- A "pick this one" action for each variation
Step 7: Iterate
After the user reviews:
- Pick → copy winning variation to the target location, clean up mockups directory
- Combine → merge elements from multiple variations (Claude does this, not Gemini)
- Refine → re-run specific variation with adjusted direction
- Expand → generate 3 more variations exploring a new dimension
- Theme variant → re-run winner in dark/light mode or responsive variant
Important Notes
- If Gemini produces generic/templated output, the prompt needs more design context. Enrich with
.impeccable.mdexcerpts and specific anti-patterns from the frontend-design skill. - Use
--include-directoriesflag to give Gemini access to additional project directories for file reads. - The
--output-format jsonflag can capture structured output for programmatic processing.
Impeccable Design Context
Project
ChatGPT clone — a conversational AI interface for education and student use, built with Next.js and Tailwind CSS.
Design Context
Users
Education-focused: students and learners using the app for study, research, writing assistance, and learning. They need a distraction-free, fast interface that helps them think clearly and find answers efficiently. Sessions may be long (studying) or quick (fact-checking), so the UI must support both sustained focus and rapid interaction.
Brand Personality
Bold, modern, precise. Three words: confident, sharp, intelligent.
The interface should feel like a premium tool that takes the user seriously — not playful or cute, but empowering and focused. It should convey capability and reliability.
Aesthetic Direction
- Visual tone: Dark mode only. High-contrast, clean lines, generous whitespace. Inspired by v0.dev/Vercel's sharp dark aesthetic with ChatGPT's proven conversation layout patterns.
- References: ChatGPT (conversation UX, sidebar history, message flow), v0.dev/Vercel (dark theme execution, monospace accents, developer-grade polish)
- Anti-references: Avoid overly colorful, playful, or cluttered designs. No gradient overload. No rounded-everything soft aesthetic. No information overload.
- Theme: Dark mode only — deep neutral backgrounds (gray-950/900), crisp text, subtle borders
Color Palette
- Background:
#0a0a0a(near-black),#171717(surface),#262626(elevated) - Text:
#fafafa(primary),#a3a3a3(secondary),#737373(muted) - Accent:
#3b82f6(blue-500) for interactive elements, links, focus states - Success/Error/Warning:
#22c55e/#ef4444/#eab308 - Border:
#262626(subtle),#404040(emphasis)
Typography
- Sans-serif: Inter or Geist Sans — clean, modern, excellent readability
- Monospace: Geist Mono or JetBrains Mono — for code blocks and technical content
- Scale: 14px base, 1.5 line-height for body, tighter for headings
Spacing
- Base unit: 4px (Tailwind default)
- Content max-width: 768px for conversation area
- Consistent padding: 16px (p-4) for cards/containers, 24px (p-6) for page-level
Design Principles
1. Clarity over cleverness — Every element should immediately communicate its purpose. Students are here to learn, not to decode the UI. 2. Density with breathing room — Show enough information to be useful without overwhelming. Whitespace is a feature, not waste. 3. Speed is a feature — Perceived and actual performance matter. Instant feedback, streaming responses, no unnecessary loading states. 4. Consistent confidence — Every interaction should feel intentional and polished. No janky transitions, no layout shifts, no mystery meat navigation. 5. Accessible by default — WCAG AA compliance, keyboard-navigable, proper focus management, readable contrast ratios throughout.
Component Patterns
- Conversation messages: Full-width with subtle differentiation between user/assistant (background shift, not bubbles)
- Sidebar: Collapsible conversation history, dark with hover states
- Input area: Fixed bottom, prominent textarea with send button, model selector
- Code blocks: Syntax-highlighted with copy button, monospace font, slightly elevated background
- Markdown rendering: Clean typography for headings, lists, tables, inline code
ChatGPT Clone — Education-Focused AI Chat Interface
Tech Stack
- Next.js + Tailwind CSS
- Dark mode only
Design Context
Users
Education-focused: students and learners using the app for study, research, writing assistance, and learning. Distraction-free, fast interface for sustained focus and rapid interaction.
Brand Personality
Bold, modern, precise. Confident, sharp, intelligent.
Aesthetic Direction
- Dark mode only. High-contrast, clean lines, generous whitespace.
- References: ChatGPT (conversation UX), v0.dev/Vercel (dark theme polish)
- Anti-references: No playful/cluttered/gradient-heavy designs
Design Principles
1. Clarity over cleverness — Every element communicates its purpose immediately 2. Density with breathing room — Useful without overwhelming 3. Speed is a feature — Instant feedback, streaming responses 4. Consistent confidence — Polished, intentional interactions 5. Accessible by default — WCAG AA, keyboard-navigable, proper contrast
Colors
- Backgrounds:
#0a0a0a,#171717,#262626 - Text:
#fafafa/#a3a3a3/#737373 - Accent:
#3b82f6(blue-500) - Borders:
#262626/#404040
Typography
- Sans: Inter or Geist Sans, 14px base
- Mono: Geist Mono or JetBrains Mono for code
See .impeccable.md for full design specification.
Usage: gemini [options] [command]
Gemini CLI - Defaults to interactive mode. Use -p/--prompt for non-interactive (headless) mode.
Commands: gemini [query..] Launch Gemini CLI [default] gemini mcp Manage MCP servers gemini extensions <command> Manage Gemini CLI extensions. [aliases: extension] gemini skills <command> Manage agent skills. [aliases: skill] gemini hooks <command> Manage Gemini CLI hooks. [aliases: hook]
Positionals: query Initial prompt. Runs in interactive mode by default; use -p/--prompt for non-interactive.
Options: -d, --debug Run in debug mode (open debug console with F12) [boolean] [default: false] -m, --model Model [string] -p, --prompt Run in non-interactive (headless) mode with the given prompt. Appended to input on stdin (if any). [string] -i, --prompt-interactive Execute the provided prompt and continue in interactive mode [string] -s, --sandbox Run in sandbox? [boolean] -y, --yolo Automatically accept all actions (aka YOLO mode, see https://www.youtube.com/watch?v=xvFZjo5PgG0 for more details)? [boolean] [default: false] --approval-mode Set the approval mode: default (prompt for approval), auto_edit (auto-approve edit tools), yolo (auto-approve all tools), plan (read-only mode) [string] [choices: "default", "auto_edit", "yolo", "plan"] --policy Additional policy files or directories to load (comma-separated or multiple --policy) [array] --acp Starts the agent in ACP mode [boolean] --experimental-acp Starts the agent in ACP mode (deprecated, use --acp instead) [boolean] --allowed-mcp-server-names Allowed MCP server names [array] --allowed-tools [DEPRECATED: Use Policy Engine instead See https://geminicli.com/docs/core/policy-engine] Tools that are allowed to run without confirmation [array] -e, --extensions A list of extensions to use. If not provided, all extensions are used. [array] -l, --list-extensions List all available extensions and exit. [boolean] -r, --resume Resume a previous session. Use "latest" for most recent or index number (e.g. --resume 5) [string] --list-sessions List available sessions for the current project and exit. [boolean] --delete-session Delete a session by index number (use --list-sessions to see available sessions). [string] --include-directories Additional directories to include in the workspace (comma-separated or multiple --include-directories) [array] --screen-reader Enable screen reader mode for accessibility. [boolean] -o, --output-format The format of the CLI output. [string] [choices: "text", "json", "stream-json"] --raw-output Disable sanitization of model output (e.g. allow ANSI escape sequences). WARNING: This can be a security risk if the model output is untrusted. [boolean] --accept-raw-output-risk Suppress the security warning when using --raw-output. [boolean] -v, --version Show version number [boolean] -h, --help Show help [boolean]
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ChatGPT Clone — Design Variations</title>
<style>
* { margin: 0; box-sizing: border-box; }
body { font-family: system-ui, -apple-system, sans-serif; background: #0a0a0a; color: #fafafa; }
.header { padding: 1.5rem 2rem; border-bottom: 1px solid #262626; }
.header h1 { font-size: 1.25rem; font-weight: 600; margin-bottom: 0.25rem; }
.header p { color: #737373; font-size: 0.875rem; }
.tabs { display: flex; gap: 0.5rem; padding: 1rem 2rem; border-bottom: 1px solid #262626; }
.tab {
padding: 0.625rem 1.25rem;
border: 1px solid #262626;
border-radius: 8px;
cursor: pointer;
background: transparent;
color: #a3a3a3;
font-size: 0.875rem;
transition: all 150ms ease;
}
.tab:hover { border-color: #404040; color: #fafafa; }
.tab.active { border-color: #3b82f6; color: #fafafa; background: rgba(59, 130, 246, 0.1); }
.tab .label { font-weight: 600; }
.tab .desc { font-size: 0.75rem; color: #737373; margin-top: 2px; }
.tab.active .desc { color: #a3a3a3; }
iframe { width: 100%; height: calc(100vh - 140px); border: none; background: #0a0a0a; }
.actions { padding: 0.5rem 2rem; display: flex; gap: 0.75rem; align-items: center; }
.actions a { color: #3b82f6; text-decoration: none; font-size: 0.8125rem; }
.actions a:hover { text-decoration: underline; }
</style>
</head>
<body>
<div class="header">
<h1>ChatGPT Clone — Design Variations</h1>
<p>3 layout variations. Click tabs to compare, open in new tab for full experience.</p>
</div>
<div class="tabs" id="tabs"></div>
<div class="actions">
<a href="#" id="open-link" target="_blank" rel="noopener">Open in new tab ↗</a>
</div>
<iframe id="preview"></iframe>
<script>
const variations = [
{ name: 'V1 — Classic Sidebar', desc: 'Traditional ChatGPT layout with persistent sidebar', src: 'v1.html' },
{ name: 'V2 — Centered Focus', desc: 'No sidebar, full-width centered conversation', src: 'v2.html' },
{ name: 'V3 — Split Panel', desc: 'Messaging-app style with conversation list + chat', src: 'v3.html' },
];
const tabs = document.getElementById('tabs');
const preview = document.getElementById('preview');
const openLink = document.getElementById('open-link');
variations.forEach((v, i) => {
const btn = document.createElement('button');
btn.className = 'tab' + (i === 0 ? ' active' : '');
btn.innerHTML = `<div class="label">${v.name}</div><div class="desc">${v.desc}</div>`;
btn.onclick = () => {
preview.src = v.src;
openLink.href = v.src;
document.querySelectorAll('.tab').forEach(t => t.classList.remove('active'));
btn.classList.add('active');
};
tabs.appendChild(btn);
});
preview.src = variations[0].src;
openLink.href = variations[0].src;
</script>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ChatGPT - Python Data Structures</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
<style>
:root {
--bg-base: #0a0a0a;
--bg-surface: #171717;
--bg-elevated: #262626;
--text-primary: #fafafa;
--text-secondary: #a3a3a3;
--text-muted: #737373;
--accent: #3b82f6;
--accent-hover: #2563eb;
--success: #22c55e;
--error: #ef4444;
--warning: #eab308;
--border-subtle: #262626;
--border-emphasis: #404040;
--font-sans: 'Inter', system-ui, -apple-system, sans-serif;
--font-mono: 'JetBrains Mono', ui-monospace, monospace;
--space-1: 4px;
--space-2: 8px;
--space-3: 12px;
--space-4: 16px;
--space-5: 20px;
--space-6: 24px;
--space-8: 32px;
--space-12: 48px;
}
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
font-family: var(--font-sans);
font-size: 14px;
line-height: 1.5;
background-color: var(--bg-base);
color: var(--text-primary);
display: flex;
height: 100vh;
overflow: hidden;
-webkit-font-smoothing: antialiased;
text-rendering: optimizeLegibility;
}
svg {
width: 16px;
height: 16px;
fill: none;
stroke: currentColor;
stroke-width: 2;
stroke-linecap: round;
stroke-linejoin: round;
}
/* --- Sidebar --- */
.sidebar {
width: 260px;
background-color: var(--bg-base);
border-right: 1px solid var(--border-subtle);
display: flex;
flex-direction: column;
flex-shrink: 0;
}
.sidebar-header {
padding: var(--space-4);
display: flex;
flex-direction: column;
gap: var(--space-3);
}
.btn-new-chat {
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
background-color: transparent;
color: var(--text-primary);
border: 1px solid var(--border-emphasis);
border-radius: 8px;
padding: var(--space-2) var(--space-3);
font-family: var(--font-sans);
font-size: 14px;
font-weight: 500;
cursor: pointer;
transition: background-color 150ms ease, border-color 150ms ease;
}
.btn-new-chat:hover {
background-color: var(--bg-surface);
}
.btn-new-chat svg {
color: var(--text-secondary);
}
.search-container {
position: relative;
}
.search-icon {
position: absolute;
left: var(--space-3);
top: 50%;
transform: translateY(-50%);
color: var(--text-muted);
width: 14px;
height: 14px;
}
.search-input {
width: 100%;
background-color: transparent;
border: 1px solid var(--border-subtle);
border-radius: 6px;
padding: var(--space-2) var(--space-3) var(--space-2) 32px;
color: var(--text-primary);
font-family: var(--font-sans);
font-size: 13px;
transition: border-color 150ms ease;
}
.search-input:focus {
outline: none;
border-color: var(--border-emphasis);
}
.search-input::placeholder {
color: var(--text-muted);
}
.sidebar-content {
flex: 1;
overflow-y: auto;
padding: 0 var(--space-2) var(--space-4);
display: flex;
flex-direction: column;
gap: 2px;
}
.sidebar-content::-webkit-scrollbar {
width: 4px;
}
.sidebar-content::-webkit-scrollbar-thumb {
background-color: var(--border-subtle);
border-radius: 4px;
}
.chat-item {
display: flex;
flex-direction: column;
padding: var(--space-2) var(--space-3);
border-radius: 6px;
cursor: pointer;
transition: background-color 150ms ease;
text-decoration: none;
color: inherit;
border-left: 2px solid transparent;
margin-left: 2px;
}
.chat-item:hover {
background-color: var(--bg-surface);
}
.chat-item.active {
background-color: var(--bg-surface);
border-left-color: var(--accent);
}
.chat-title {
font-size: 13px;
font-weight: 500;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
color: var(--text-primary);
}
.chat-time {
font-size: 11px;
color: var(--text-muted);
margin-top: 2px;
}
.sidebar-footer {
padding: var(--space-3) var(--space-4);
border-top: 1px solid var(--border-subtle);
display: flex;
align-items: center;
justify-content: space-between;
background-color: var(--bg-base);
}
.user-profile {
display: flex;
align-items: center;
gap: var(--space-3);
cursor: pointer;
padding: var(--space-1);
border-radius: 6px;
transition: background-color 150ms ease;
}
.user-profile:hover {
background-color: var(--bg-surface);
}
.avatar {
width: 28px;
height: 28px;
border-radius: 50%;
background-color: var(--accent);
display: flex;
align-items: center;
justify-content: center;
font-weight: 600;
font-size: 12px;
color: #fff;
}
.user-name {
font-size: 13px;
font-weight: 500;
}
.btn-icon {
background: none;
border: none;
color: var(--text-secondary);
cursor: pointer;
padding: var(--space-1);
border-radius: 4px;
transition: color 150ms ease, background-color 150ms ease;
display: flex;
align-items: center;
justify-content: center;
}
.btn-icon:hover {
color: var(--text-primary);
background-color: var(--bg-surface);
}
/* --- Main Area --- */
.main-area {
flex: 1;
display: flex;
flex-direction: column;
position: relative;
}
.header {
height: 56px;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 var(--space-6);
border-bottom: 1px solid var(--border-subtle);
background-color: var(--bg-base);
z-index: 10;
}
.header-title {
font-size: 14px;
font-weight: 600;
color: var(--text-primary);
}
.model-selector {
display: flex;
align-items: center;
gap: var(--space-2);
color: var(--text-secondary);
font-size: 13px;
font-weight: 500;
padding: var(--space-1) var(--space-2);
border-radius: 6px;
cursor: pointer;
transition: background-color 150ms ease, color 150ms ease;
}
.model-selector:hover {
background-color: var(--bg-surface);
color: var(--text-primary);
}
.conversation-container {
flex: 1;
overflow-y: auto;
scroll-behavior: smooth;
}
.message {
padding: var(--space-6) 0;
width: 100%;
}
.message.assistant {
background-color: var(--bg-surface);
border-top: 1px solid var(--border-subtle);
border-bottom: 1px solid var(--border-subtle);
}
.message.user {
background-color: transparent;
}
.message-content {
max-width: 768px;
margin: 0 auto;
padding: 0 var(--space-6);
display: flex;
gap: var(--space-4);
}
.message-avatar {
width: 28px;
height: 28px;
border-radius: 4px;
flex-shrink: 0;
display: flex;
align-items: center;
justify-content: center;
margin-top: 2px;
font-weight: 600;
font-size: 12px;
}
.message.user .message-avatar {
background-color: var(--accent);
color: #fff;
}
.message.assistant .message-avatar {
background-color: #10a37f;
color: #fff;
}
.message.assistant .message-avatar svg {
width: 18px;
height: 18px;
}
.message-body {
flex: 1;
min-width: 0;
color: var(--text-primary);
line-height: 1.6;
}
/* --- Markdown Styles --- */
.message-body p {
margin-bottom: var(--space-4);
}
.message-body p:last-child {
margin-bottom: 0;
}
.message-body h3 {
font-size: 16px;
font-weight: 600;
margin-top: var(--space-6);
margin-bottom: var(--space-3);
color: var(--text-primary);
}
.message-body h3:first-child {
margin-top: 0;
}
.message-body ul {
margin-bottom: var(--space-4);
padding-left: var(--space-6);
color: var(--text-secondary);
}
.message-body li {
margin-bottom: var(--space-2);
}
.message-body strong {
color: var(--text-primary);
font-weight: 600;
}
.message-body code:not(pre code) {
font-family: var(--font-mono);
font-size: 13px;
background-color: var(--bg-elevated);
padding: 2px 6px;
border-radius: 4px;
color: var(--text-primary);
}
.code-block {
background-color: var(--bg-base);
border: 1px solid var(--border-subtle);
border-radius: 8px;
margin-bottom: var(--space-4);
overflow: hidden;
}
.code-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: var(--space-2) var(--space-4);
background-color: var(--bg-elevated);
border-bottom: 1px solid var(--border-subtle);
font-family: var(--font-sans);
font-size: 12px;
color: var(--text-secondary);
}
.btn-copy {
display: flex;
align-items: center;
gap: var(--space-2);
background: none;
border: none;
color: var(--text-muted);
font-family: var(--font-sans);
font-size: 12px;
cursor: pointer;
transition: color 150ms ease;
}
.btn-copy:hover {
color: var(--text-primary);
}
.btn-copy svg {
width: 14px;
height: 14px;
}
.code-block pre {
margin: 0;
padding: var(--space-4);
overflow-x: auto;
font-family: var(--font-mono);
font-size: 13px;
line-height: 1.5;
}
/* Syntax Highlighting */
.token-keyword { color: #c678dd; }
.token-function { color: #61afef; }
.token-string { color: #98c379; }
.token-comment { color: #5c6370; font-style: italic; }
.token-operator { color: #56b6c2; }
.token-punctuation { color: #abb2bf; }
.token-number { color: #d19a66; }
.table-container {
width: 100%;
overflow-x: auto;
margin-bottom: var(--space-4);
border: 1px solid var(--border-subtle);
border-radius: 8px;
}
table {
width: 100%;
border-collapse: collapse;
text-align: left;
font-size: 14px;
}
th, td {
padding: var(--space-3) var(--space-4);
border-bottom: 1px solid var(--border-subtle);
}
th {
background-color: var(--bg-elevated);
font-weight: 600;
color: var(--text-primary);
}
tr:last-child td {
border-bottom: none;
}
tr:hover td {
background-color: rgba(255, 255, 255, 0.02);
}
/* --- Input Area --- */
.input-area {
position: absolute;
bottom: 0;
left: 0;
right: 0;
background-image: linear-gradient(180deg, transparent, var(--bg-base) 24px, var(--bg-base) 100%);
padding: var(--space-6);
display: flex;
flex-direction: column;
align-items: center;
pointer-events: none; /* Let clicks pass through gradient */
}
.input-wrapper {
width: 100%;
max-width: 768px;
pointer-events: auto; /* Re-enable clicks for input */
}
.input-container {
position: relative;
background-color: var(--bg-surface);
border: 1px solid var(--border-subtle);
border-radius: 12px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
transition: border-color 150ms ease, box-shadow 150ms ease;
}
.input-container:focus-within {
border-color: var(--border-emphasis);
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.3);
}
.chat-input {
width: 100%;
min-height: 52px;
max-height: 200px;
background: transparent;
border: none;
color: var(--text-primary);
font-family: var(--font-sans);
font-size: 15px;
padding: 14px 48px 14px 16px;
resize: none;
outline: none;
line-height: 1.5;
overflow-y: hidden;
}
.chat-input::placeholder {
color: var(--text-muted);
}
.btn-send {
position: absolute;
right: 8px;
bottom: 8px;
width: 32px;
height: 32px;
border-radius: 8px;
background-color: var(--accent);
color: #fff;
border: none;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: background-color 150ms ease;
}
.btn-send:hover {
background-color: var(--accent-hover);
}
.btn-send svg {
width: 16px;
height: 16px;
stroke-width: 2.5;
}
.disclaimer {
margin-top: var(--space-3);
font-size: 12px;
color: var(--text-muted);
text-align: center;
}
.scroll-spacer {
height: 160px;
}
</style>
</head>
<body>
<!-- Sidebar -->
<aside class="sidebar">
<div class="sidebar-header">
<button class="btn-new-chat">
<span>New Chat</span>
<svg viewBox="0 0 24 24"><path d="M12 5v14m-7-7h14"></path></svg>
</button>
<div class="search-container">
<svg class="search-icon" viewBox="0 0 24 24"><path d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"></path></svg>
<input type="text" class="search-input" placeholder="Search conversations...">
</div>
</div>
<div class="sidebar-content">
<a href="#" class="chat-item">
<span class="chat-title">Quantum Physics Explained</span>
<span class="chat-time">2h ago</span>
</a>
<a href="#" class="chat-item">
<span class="chat-title">Essay: Climate Change</span>
<span class="chat-time">Yesterday</span>
</a>
<a href="#" class="chat-item active">
<span class="chat-title">Python Data Structures</span>
<span class="chat-time">Yesterday</span>
</a>
<a href="#" class="chat-item">
<span class="chat-title">History of Renaissance Art</span>
<span class="chat-time">2 days ago</span>
</a>
<a href="#" class="chat-item">
<span class="chat-title">Calculus Integration Help</span>
<span class="chat-time">3 days ago</span>
</a>
<a href="#" class="chat-item">
<span class="chat-title">Creative Writing Prompts</span>
<span class="chat-time">Last week</span>
</a>
<a href="#" class="chat-item">
<span class="chat-title">Biology Cell Division</span>
<span class="chat-time">Last week</span>
</a>
<a href="#" class="chat-item">
<span class="chat-title">Study Plan Generator</span>
<span class="chat-time">Last month</span>
</a>
</div>
<div class="sidebar-footer">
<div class="user-profile">
<div class="avatar">AS</div>
<span class="user-name">Alex S.</span>
</div>
<button class="btn-icon" aria-label="Settings">
<svg viewBox="0 0 24 24"><path d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z"></path><path d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"></path></svg>
</button>
</div>
</aside>
<!-- Main Content -->
<main class="main-area">
<header class="header">
<h1 class="header-title">Python Data Structures</h1>
<div class="model-selector">
<span>GPT-4</span>
<svg viewBox="0 0 24 24"><path d="M19 9l-7 7-7-7"></path></svg>
</div>
</header>
<div class="conversation-container">
<!-- Message 1: User -->
<div class="message user">
<div class="message-content">
<div class="message-avatar">AS</div>
<div class="message-body">
<p>Can you explain the difference between lists and tuples in Python?</p>
</div>
</div>
</div>
<!-- Message 2: Assistant -->
<div class="message assistant">
<div class="message-content">
<div class="message-avatar">
<svg viewBox="0 0 41 41" fill="none" xmlns="http://www.w3.org/2000/svg" stroke="none"><path d="M37.5324 16.8707C37.9808 15.5241 38.1363 14.0974 37.9886 12.6859C37.8409 11.2744 37.3934 9.91076 36.676 8.68622C35.6126 6.83404 33.9882 5.3676 32.0373 4.4985C30.0864 3.62941 27.9104 3.40259 25.8215 3.85078C24.8796 2.78812 23.7219 1.94539 22.4257 1.38048C21.1295 0.81557 19.7249 0.541459 18.3058 0.575084C16.2168 0.62779 14.1956 1.38536 12.5404 2.73507C10.8853 4.08479 9.6845 5.95423 9.11736 8.01633C7.68065 8.2396 6.32074 8.78484 5.15017 9.60565C3.97959 10.4265 2.84654 11.458 2.0628 12.7163C1.00282 14.5684 0.536761 16.7324 0.725964 18.8875C0.915166 21.0425 1.75168 23.0763 3.1202 24.6953C2.67175 26.0419 2.51622 27.4686 2.66395 28.8801C2.81168 30.2916 3.25921 31.6552 3.97658 32.8798C5.04001 34.7319 6.66442 36.1984 8.61531 37.0675C10.5662 37.9366 12.7422 38.1634 14.8311 37.7152C15.773 38.7779 16.9307 39.6206 18.2269 40.1855C19.5231 40.7504 20.9276 41.0245 22.3468 40.9909C24.4357 40.9382 26.4569 40.1806 28.1121 38.8309C29.7672 37.4812 30.9681 35.6118 31.5352 33.5497C32.9719 33.3264 34.3318 32.7812 35.5024 31.9603C36.673 31.1395 37.806 30.108 38.5898 28.8497C39.6498 26.9976 40.1158 24.8336 39.9266 22.6785C39.7374 20.5235 38.9009 18.4897 37.5324 16.8707ZM29.8973 23.6339L20.6558 28.9806C20.3703 29.1454 20.043 29.2325 19.7088 29.2325C19.3745 29.2325 19.0472 29.1454 18.7617 28.9806L10.4687 24.1834C10.1832 24.0186 9.94824 23.7836 9.78347 23.4981C9.6187 23.2126 9.53165 22.8853 9.53165 22.5511V12.9568C9.53165 12.6226 9.6187 12.2953 9.78347 12.0098C9.94824 11.7243 10.1832 11.4893 10.4687 11.3245L18.7617 6.5273C19.0472 6.36254 19.3745 6.27549 19.7088 6.27549C20.043 6.27549 20.3703 6.36254 20.6558 6.5273L28.9488 11.3245C29.2343 11.4893 29.4693 11.7243 29.6341 12.0098C29.7989 12.2953 29.8859 12.6226 29.8859 12.9568V22.5511C29.8859 22.8853 29.7989 23.2126 29.6341 23.4981C29.4693 23.7836 29.2343 24.0186 28.9488 24.1834L29.8973 23.6339Z" fill="currentColor"></path></svg>
</div>
<div class="message-body">
<h3>Understanding Lists and Tuples</h3>
<p>In Python, both lists and tuples are used to store collections of data, but they have one fundamental difference: <strong>mutability</strong>.</p>
<ul>
<li><strong>Lists</strong> are mutable. You can add, remove, or change items after the list is created. Defined using square brackets <code>[]</code>.</li>
<li><strong>Tuples</strong> are immutable. Once created, their contents cannot be modified. Defined using parentheses <code>()</code>.</li>
</ul>
<div class="code-block">
<div class="code-header">
<span>python</span>
<button class="btn-copy">
<svg viewBox="0 0 24 24"><path d="M8 5H6a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2v-1M8 5a2 2 0 002 2h2a2 2 0 002-2M8 5a2 2 0 012-2h2a2 2 0 012 2m0 0h2a2 2 0 012 2v3m2 4H10m0 0l3-3m-3 3l3 3"></path></svg>
Copy code
</button>
</div>
<pre><code><span class="token-comment"># List example (Mutable)</span>
<span class="token-keyword">my_list</span> <span class="token-operator">=</span> <span class="token-punctuation">[</span><span class="token-string">"apple"</span><span class="token-punctuation">,</span> <span class="token-string">"banana"</span><span class="token-punctuation">,</span> <span class="token-string">"cherry"</span><span class="token-punctuation">]</span>
<span class="token-keyword">my_list</span><span class="token-punctuation">[</span><span class="token-number">0</span><span class="token-punctuation">]</span> <span class="token-operator">=</span> <span class="token-string">"orange"</span> <span class="token-comment"># Valid operation</span>
<span class="token-keyword">my_list</span><span class="token-punctuation">.</span><span class="token-function">append</span><span class="token-punctuation">(</span><span class="token-string">"grape"</span><span class="token-punctuation">)</span>
<span class="token-comment"># Tuple example (Immutable)</span>
<span class="token-keyword">my_tuple</span> <span class="token-operator">=</span> <span class="token-punctuation">(</span><span class="token-string">"apple"</span><span class="token-punctuation">,</span> <span class="token-string">"banana"</span><span class="token-punctuation">,</span> <span class="token-string">"cherry"</span><span class="token-punctuation">)</span>
<span class="token-comment"># my_tuple[0] = "orange" # This would raise a TypeError</span></code></pre>
</div>
</div>
</div>
</div>
<!-- Message 3: User -->
<div class="message user">
<div class="message-content">
<div class="message-avatar">AS</div>
<div class="message-body">
<p>When should I use each one?</p>
</div>
</div>
</div>
<!-- Message 4: Assistant -->
<div class="message assistant">
<div class="message-content">
<div class="message-avatar">
<svg viewBox="0 0 41 41" fill="none" xmlns="http://www.w3.org/2000/svg" stroke="none"><path d="M37.5324 16.8707C37.9808 15.5241 38.1363 14.0974 37.9886 12.6859C37.8409 11.2744 37.3934 9.91076 36.676 8.68622C35.6126 6.83404 33.9882 5.3676 32.0373 4.4985C30.0864 3.62941 27.9104 3.40259 25.8215 3.85078C24.8796 2.78812 23.7219 1.94539 22.4257 1.38048C21.1295 0.81557 19.7249 0.541459 18.3058 0.575084C16.2168 0.62779 14.1956 1.38536 12.5404 2.73507C10.8853 4.08479 9.6845 5.95423 9.11736 8.01633C7.68065 8.2396 6.32074 8.78484 5.15017 9.60565C3.97959 10.4265 2.84654 11.458 2.0628 12.7163C1.00282 14.5684 0.536761 16.7324 0.725964 18.8875C0.915166 21.0425 1.75168 23.0763 3.1202 24.6953C2.67175 26.0419 2.51622 27.4686 2.66395 28.8801C2.81168 30.2916 3.25921 31.6552 3.97658 32.8798C5.04001 34.7319 6.66442 36.1984 8.61531 37.0675C10.5662 37.9366 12.7422 38.1634 14.8311 37.7152C15.773 38.7779 16.9307 39.6206 18.2269 40.1855C19.5231 40.7504 20.9276 41.0245 22.3468 40.9909C24.4357 40.9382 26.4569 40.1806 28.1121 38.8309C29.7672 37.4812 30.9681 35.6118 31.5352 33.5497C32.9719 33.3264 34.3318 32.7812 35.5024 31.9603C36.673 31.1395 37.806 30.108 38.5898 28.8497C39.6498 26.9976 40.1158 24.8336 39.9266 22.6785C39.7374 20.5235 38.9009 18.4897 37.5324 16.8707ZM29.8973 23.6339L20.6558 28.9806C20.3703 29.1454 20.043 29.2325 19.7088 29.2325C19.3745 29.2325 19.0472 29.1454 18.7617 28.9806L10.4687 24.1834C10.1832 24.0186 9.94824 23.7836 9.78347 23.4981C9.6187 23.2126 9.53165 22.8853 9.53165 22.5511V12.9568C9.53165 12.6226 9.6187 12.2953 9.78347 12.0098C9.94824 11.7243 10.1832 11.4893 10.4687 11.3245L18.7617 6.5273C19.0472 6.36254 19.3745 6.27549 19.7088 6.27549C20.043 6.27549 20.3703 6.36254 20.6558 6.5273L28.9488 11.3245C29.2343 11.4893 29.4693 11.7243 29.6341 12.0098C29.7989 12.2953 29.8859 12.6226 29.8859 12.9568V22.5511C29.8859 22.8853 29.7989 23.2126 29.6341 23.4981C29.4693 23.7836 29.2343 24.0186 28.9488 24.1834L29.8973 23.6339Z" fill="currentColor"></path></svg>
</div>
<div class="message-body">
<p>Choosing between lists and tuples generally comes down to whether you need the collection to change over time. Here is a breakdown of when to use each:</p>
<div class="table-container">
<table>
<thead>
<tr>
<th>Feature</th>
<th>List</th>
<th>Tuple</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Modification</strong></td>
<td>Data changes frequently (append, remove, update)</td>
<td>Data is constant and should not be modified</td>
</tr>
<tr>
<td><strong>Performance</strong></td>
<td>Slightly slower iteration, takes more memory</td>
<td>Faster iteration, more memory-efficient</td>
</tr>
<tr>
<td><strong>Use Case</strong></td>
<td>Dynamic collections like user inputs, active records</td>
<td>Fixed collections like coordinates, database records</td>
</tr>
</tbody>
</table>
</div>
<p>As a rule of thumb, use a <strong>list</strong> when you have a collection of homogeneous items that might grow or shrink. Use a <strong>tuple</strong> for heterogeneous items or when you want to ensure the data remains constant throughout the program's lifecycle, providing a structural guarantee and a slight performance edge.</p>
</div>
</div>
</div>
<!-- Spacer for input area -->
<div class="scroll-spacer"></div>
</div>
<!-- Input Area -->
<div class="input-area">
<div class="input-wrapper">
<div class="input-container">
<textarea class="chat-input" placeholder="Message ChatGPT..." rows="1" aria-label="Message ChatGPT"></textarea>
<button class="btn-send" aria-label="Send message">
<svg viewBox="0 0 24 24"><path d="M5 10l7-7m0 0l7 7m-7-7v18"></path></svg>
</button>
</div>
<div class="disclaimer">
ChatGPT can make mistakes. Check important info.
</div>
</div>
</div>
</main>
<script>
// Auto-resize textarea
const textarea = document.querySelector('.chat-input');
textarea.addEventListener('input', function() {
this.style.height = 'auto';
const newHeight = Math.min(this.scrollHeight, 200);
this.style.height = newHeight + 'px';
// Handle empty state to return to default height
if (this.value === '') {
this.style.height = '52px';
}
});
</script>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ChatGPT Clone</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
<style>
:root {
/* Color Palette */
--bg: #0a0a0a;
--surface: #171717;
--elevated: #262626;
--text-primary: #fafafa;
--text-secondary: #a3a3a3;
--text-muted: #737373;
--accent: #3b82f6;
--accent-hover: #2563eb;
--success: #22c55e;
--error: #ef4444;
--warning: #eab308;
--border-subtle: #262626;
--border-emphasis: #404040;
/* Typography & Spacing */
--font-sans: 'Inter', sans-serif;
--font-mono: 'JetBrains Mono', monospace;
--base-size: 14px;
--line-height: 1.5;
--space-1: 4px;
--space-2: 8px;
--space-3: 12px;
--space-4: 16px;
--space-6: 24px;
--space-8: 32px;
--max-width: 768px;
--transition: 150ms ease;
}
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
background-color: var(--bg);
color: var(--text-primary);
font-family: var(--font-sans);
font-size: var(--base-size);
line-height: var(--line-height);
display: flex;
flex-direction: column;
height: 100vh;
overflow: hidden;
-webkit-font-smoothing: antialiased;
}
button {
font-family: inherit;
background: none;
border: none;
cursor: pointer;
color: inherit;
}
button:focus-visible, a:focus-visible, textarea:focus-visible {
outline: 2px solid var(--accent);
outline-offset: 2px;
border-radius: 4px;
}
/* Top Bar */
header {
display: flex;
justify-content: space-between;
align-items: center;
padding: var(--space-3) var(--space-6);
height: 56px;
flex-shrink: 0;
border-bottom: 1px solid var(--border-subtle);
}
.icon-btn {
display: flex;
align-items: center;
justify-content: center;
width: 36px;
height: 36px;
border-radius: 6px;
color: var(--text-secondary);
transition: background-color var(--transition), color var(--transition);
}
.icon-btn:hover {
background-color: var(--elevated);
color: var(--text-primary);
}
.model-selector {
display: flex;
align-items: center;
gap: var(--space-2);
font-weight: 500;
font-size: 15px;
color: var(--text-secondary);
padding: var(--space-2) var(--space-3);
border-radius: 6px;
transition: background-color var(--transition), color var(--transition);
}
.model-selector:hover {
background-color: var(--elevated);
color: var(--text-primary);
}
.header-right {
display: flex;
align-items: center;
gap: var(--space-3);
}
.avatar-circle {
width: 32px;
height: 32px;
border-radius: 50%;
background-color: var(--elevated);
display: flex;
align-items: center;
justify-content: center;
font-weight: 600;
font-size: 13px;
color: var(--text-primary);
border: 1px solid var(--border-emphasis);
flex-shrink: 0;
}
/* Main Conversation Area */
main {
flex-grow: 1;
overflow-y: auto;
scroll-behavior: smooth;
padding-bottom: 180px; /* Space for fixed input area */
}
.message-band {
padding: var(--space-6) 0;
display: flex;
justify-content: center;
}
.message-band.assistant {
background-color: var(--surface);
border-top: 1px solid var(--border-subtle);
border-bottom: 1px solid var(--border-subtle);
}
.message-inner {
width: 100%;
max-width: var(--max-width);
padding: 0 var(--space-6);
display: flex;
gap: var(--space-4);
}
.message-icon {
width: 28px;
height: 28px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
font-weight: 600;
font-size: 11px;
margin-top: 2px;
}
.message-icon.ai {
background-color: var(--text-primary);
color: var(--bg);
}
.message-icon.user {
background-color: var(--elevated);
border: 1px solid var(--border-emphasis);
color: var(--text-primary);
}
.message-content {
flex-grow: 1;
min-width: 0;
}
/* Markdown Content Formatting */
.message-content p {
margin-bottom: var(--space-4);
}
.message-content p:last-child {
margin-bottom: 0;
}
.message-content h3 {
font-size: 16px;
font-weight: 600;
margin-bottom: var(--space-3);
color: var(--text-primary);
}
.message-content strong {
font-weight: 600;
color: var(--text-primary);
}
.message-content ul {
margin-bottom: var(--space-4);
padding-left: var(--space-6);
}
.message-content li {
margin-bottom: var(--space-2);
}
.message-content li:last-child {
margin-bottom: 0;
}
.message-content code {
font-family: var(--font-mono);
font-size: 13px;
background-color: var(--elevated);
padding: 2px 4px;
border-radius: 4px;
color: var(--text-secondary);
}
/* Fenced Code Block */
.code-block {
margin: var(--space-4) 0;
background-color: var(--bg);
border: 1px solid var(--border-subtle);
border-radius: 6px;
overflow: hidden;
}
.code-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: var(--space-2) var(--space-3);
background-color: var(--elevated);
border-bottom: 1px solid var(--border-subtle);
font-size: 12px;
color: var(--text-secondary);
}
.code-copy {
display: flex;
align-items: center;
gap: 6px;
font-size: 12px;
color: var(--text-secondary);
transition: color var(--transition);
}
.code-copy:hover {
color: var(--text-primary);
}
.code-content {
padding: var(--space-4);
overflow-x: auto;
font-family: var(--font-mono);
font-size: 13px;
line-height: 1.6;
}
.code-content code {
background-color: transparent;
padding: 0;
color: var(--text-primary);
}
.syntax-comment { color: var(--text-muted); }
.syntax-keyword { color: var(--accent); }
.syntax-string { color: var(--success); }
.syntax-func { color: var(--warning); }
/* Table Formatting */
.message-content table {
width: 100%;
border-collapse: collapse;
margin: var(--space-4) 0;
font-size: 14px;
}
.message-content th,
.message-content td {
padding: var(--space-3) var(--space-4);
border: 1px solid var(--border-subtle);
text-align: left;
}
.message-content th {
background-color: var(--elevated);
font-weight: 500;
color: var(--text-primary);
}
/* Fixed Input Area */
.input-area {
position: fixed;
bottom: 0;
left: 0;
right: 0;
padding: var(--space-4) var(--space-6) var(--space-4);
background: linear-gradient(180deg, transparent 0%, var(--bg) 20%);
}
.input-container {
max-width: var(--max-width);
margin: 0 auto;
background-color: var(--elevated);
border: 1px solid var(--border-subtle);
border-radius: 16px;
padding: var(--space-3);
box-shadow: 0 4px 24px rgba(0, 0, 0, 0.4);
display: flex;
flex-direction: column;
gap: var(--space-2);
transition: border-color var(--transition);
}
.input-container:focus-within {
border-color: var(--border-emphasis);
}
textarea {
width: 100%;
background: transparent;
border: none;
color: var(--text-primary);
font-family: var(--font-sans);
font-size: 15px;
line-height: 1.5;
resize: none;
outline: none;
max-height: 200px;
padding: var(--space-2) var(--space-1);
}
textarea::placeholder {
color: var(--text-muted);
}
.input-toolbar {
display: flex;
justify-content: space-between;
align-items: flex-end;
}
.toolbar-actions {
display: flex;
gap: var(--space-1);
}
.toolbar-btn {
display: flex;
align-items: center;
justify-content: center;
width: 32px;
height: 32px;
border-radius: 6px;
color: var(--text-secondary);
transition: background-color var(--transition), color var(--transition);
}
.toolbar-btn:hover {
background-color: var(--border-subtle);
color: var(--text-primary);
}
.send-btn {
display: flex;
align-items: center;
justify-content: center;
width: 32px;
height: 32px;
border-radius: 50%;
background-color: var(--text-primary);
color: var(--bg);
transition: opacity var(--transition), transform var(--transition);
}
.send-btn:hover {
opacity: 0.9;
transform: translateY(-1px);
}
.send-btn svg {
width: 16px;
height: 16px;
}
.disclaimer {
text-align: center;
font-size: 12px;
color: var(--text-muted);
margin-top: var(--space-3);
max-width: var(--max-width);
margin-left: auto;
margin-right: auto;
}
/* Custom Scrollbar */
::-webkit-scrollbar {
width: 8px;
height: 8px;
}
::-webkit-scrollbar-track {
background: transparent;
}
::-webkit-scrollbar-thumb {
background: var(--border-emphasis);
border-radius: 4px;
}
::-webkit-scrollbar-thumb:hover {
background: var(--text-muted);
}
</style>
</head>
<body>
<header>
<button class="icon-btn" aria-label="Toggle sidebar">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<line x1="3" y1="12" x2="21" y2="12"></line>
<line x1="3" y1="6" x2="21" y2="6"></line>
<line x1="3" y1="18" x2="21" y2="18"></line>
</svg>
</button>
<button class="model-selector">
GPT-4
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<polyline points="6 9 12 15 18 9"></polyline>
</svg>
</button>
<div class="header-right">
<button class="icon-btn" aria-label="New chat">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M12 5v14M5 12h14"></path>
</svg>
</button>
<div class="avatar-circle">A</div>
</div>
</header>
<main>
<!-- User Message 1 -->
<div class="message-band">
<div class="message-inner">
<div class="message-icon user">A</div>
<div class="message-content">
<p>Can you explain the difference between lists and tuples in Python?</p>
</div>
</div>
</div>
<!-- Assistant Message 1 -->
<div class="message-band assistant">
<div class="message-inner">
<div class="message-icon ai">AI</div>
<div class="message-content">
<h3>Lists vs. Tuples in Python</h3>
<p>Both lists and tuples are used to store collections of data in Python, but they have one fundamental difference: <strong>mutability</strong>.</p>
<ul>
<li><strong>Lists</strong> are mutable. You can add, remove, or change items after creation. They use square brackets <code>[]</code>.</li>
<li><strong>Tuples</strong> are immutable. Once created, their contents cannot be modified. They use parentheses <code>()</code>.</li>
</ul>
<div class="code-block">
<div class="code-header">
<span>python</span>
<button class="code-copy">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect>
<path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path>
</svg>
Copy code
</button>
</div>
<div class="code-content">
<pre><code><span class="syntax-comment"># List example (Mutable)</span>
my_list = [<span class="syntax-string">1</span>, <span class="syntax-string">2</span>, <span class="syntax-string">3</span>]
my_list.<span class="syntax-func">append</span>(<span class="syntax-string">4</span>)
my_list[<span class="syntax-string">0</span>] = <span class="syntax-string">99</span>
<span class="syntax-func">print</span>(my_list) <span class="syntax-comment"># Output: [99, 2, 3, 4]</span>
<span class="syntax-comment"># Tuple example (Immutable)</span>
my_tuple = (<span class="syntax-string">1</span>, <span class="syntax-string">2</span>, <span class="syntax-string">3</span>)
<span class="syntax-comment"># my_tuple.append(4) # AttributeError</span>
<span class="syntax-comment"># my_tuple[0] = 99 # TypeError</span>
<span class="syntax-func">print</span>(my_tuple) <span class="syntax-comment"># Output: (1, 2, 3)</span></code></pre>
</div>
</div>
</div>
</div>
</div>
<!-- User Message 2 -->
<div class="message-band">
<div class="message-inner">
<div class="message-icon user">A</div>
<div class="message-content">
<p>When should I use each one?</p>
</div>
</div>
</div>
<!-- Assistant Message 2 -->
<div class="message-band assistant">
<div class="message-inner">
<div class="message-icon ai">AI</div>
<div class="message-content">
<p>Choosing between a list and a tuple depends on how you intend to use the data.</p>
<table>
<thead>
<tr>
<th>Feature</th>
<th>List</th>
<th>Tuple</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Mutability</strong></td>
<td>Mutable (can change)</td>
<td>Immutable (fixed)</td>
</tr>
<tr>
<td><strong>Syntax</strong></td>
<td>Square brackets <code>[]</code></td>
<td>Parentheses <code>()</code></td>
</tr>
<tr>
<td><strong>Performance</strong></td>
<td>Slightly slower (dynamic memory)</td>
<td>Faster (fixed memory allocation)</td>
</tr>
<tr>
<td><strong>Use Case</strong></td>
<td>Collections of homogenous items that may change over time.</td>
<td>Heterogeneous data or fixed collections (like coordinates).</td>
</tr>
</tbody>
</table>
<p>As a general rule, use a <strong>list</strong> when you expect the collection to grow, shrink, or change. Use a <strong>tuple</strong> when you want to ensure the data remains constant throughout the program's execution, which also provides a slight performance benefit and allows it to be used as a dictionary key.</p>
</div>
</div>
</div>
</main>
<div class="input-area">
<div class="input-container">
<textarea rows="1" placeholder="Message ChatGPT..." aria-label="Message ChatGPT"></textarea>
<div class="input-toolbar">
<div class="toolbar-actions">
<button class="toolbar-btn" aria-label="Attach file">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M21.44 11.05l-9.19 9.19a6 6 0 0 1-8.49-8.49l9.19-9.19a4 4 0 0 1 5.66 5.66l-9.2 9.19a2 2 0 0 1-2.83-2.83l8.49-8.48"></path>
</svg>
</button>
<button class="toolbar-btn" aria-label="Web search">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="12" r="10"></circle>
<line x1="2" y1="12" x2="22" y2="12"></line>
<path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z"></path>
</svg>
</button>
<button class="toolbar-btn" aria-label="Code interpreter">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<polyline points="4 17 10 11 4 5"></polyline>
<line x1="12" y1="19" x2="20" y2="19"></line>
</svg>
</button>
</div>
<button class="send-btn" aria-label="Send message">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round">
<line x1="12" y1="19" x2="12" y2="5"></line>
<polyline points="5 12 12 5 19 12"></polyline>
</svg>
</button>
</div>
</div>
<div class="disclaimer">
ChatGPT can make mistakes. Check important info.
</div>
</div>
<script>
const textarea = document.querySelector('textarea');
textarea.addEventListener('input', function() {
this.style.height = 'auto';
this.style.height = (this.scrollHeight) + 'px';
});
textarea.addEventListener('keydown', function(e) {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();
// Submit logic would go here
}
});
</script>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ChatGPT Clone - Split Panel</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
<style>
:root {
--bg-base: #0a0a0a;
--bg-surface: #171717;
--bg-elevated: #262626;
--text-primary: #fafafa;
--text-secondary: #a3a3a3;
--text-muted: #737373;
--accent: #3b82f6;
--success: #22c55e;
--error: #ef4444;
--warning: #eab308;
--border-subtle: #262626;
--border-emphasis: #404040;
--font-sans: 'Inter', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
--font-mono: 'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
}
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
font-family: var(--font-sans);
background-color: var(--bg-base);
color: var(--text-primary);
font-size: 14px;
line-height: 1.5;
height: 100vh;
display: flex;
overflow: hidden;
-webkit-font-smoothing: antialiased;
}
a {
color: var(--accent);
text-decoration: none;
}
a:hover {
text-decoration: underline;
}
button {
background: none;
border: none;
color: inherit;
font: inherit;
cursor: pointer;
}
button:focus-visible, input:focus-visible, textarea:focus-visible {
outline: 2px solid var(--accent);
outline-offset: -1px;
}
/* Icons */
svg {
width: 16px;
height: 16px;
fill: currentColor;
}
/* Layout */
.app-container {
display: flex;
width: 100%;
height: 100%;
}
.sidebar {
width: 320px;
min-width: 320px;
display: flex;
flex-direction: column;
background-color: var(--bg-surface);
border-right: 1px solid var(--border-subtle);
height: 100%;
}
.main-content {
flex: 1;
display: flex;
flex-direction: column;
background-color: var(--bg-base);
height: 100%;
min-width: 0;
}
/* Sidebar Header & Search */
.sidebar-header {
padding: 16px;
display: flex;
flex-direction: column;
gap: 16px;
border-bottom: 1px solid var(--border-subtle);
}
.sidebar-title-row {
display: flex;
justify-content: space-between;
align-items: center;
}
.sidebar-title {
font-size: 16px;
font-weight: 600;
}
.icon-btn {
display: flex;
align-items: center;
justify-content: center;
width: 32px;
height: 32px;
border-radius: 6px;
color: var(--text-secondary);
transition: background-color 150ms ease, color 150ms ease;
}
.icon-btn:hover {
background-color: var(--bg-elevated);
color: var(--text-primary);
}
.search-box {
position: relative;
display: flex;
align-items: center;
}
.search-icon {
position: absolute;
left: 12px;
color: var(--text-muted);
}
.search-input {
width: 100%;
background-color: var(--bg-elevated);
border: 1px solid transparent;
border-radius: 6px;
padding: 8px 12px 8px 36px;
color: var(--text-primary);
font-family: inherit;
font-size: 13px;
transition: border-color 150ms ease;
}
.search-input::placeholder {
color: var(--text-muted);
}
.search-input:focus {
border-color: var(--border-emphasis);
outline: none;
}
/* Conversation List */
.conversation-list {
flex: 1;
overflow-y: auto;
padding: 12px 8px;
display: flex;
flex-direction: column;
gap: 24px;
}
.time-group {
display: flex;
flex-direction: column;
gap: 4px;
}
.time-group-label {
font-size: 11px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.5px;
color: var(--text-muted);
padding: 0 8px 8px 8px;
}
.chat-item {
display: flex;
flex-direction: column;
padding: 10px 12px;
border-radius: 8px;
cursor: pointer;
transition: background-color 150ms ease;
position: relative;
border-left: 2px solid transparent;
margin-left: -2px;
}
.chat-item:hover {
background-color: var(--bg-elevated);
}
.chat-item.active {
background-color: var(--bg-elevated);
border-left-color: var(--accent);
}
.chat-item-header {
display: flex;
justify-content: space-between;
align-items: baseline;
margin-bottom: 4px;
}
.chat-item-title {
font-weight: 500;
font-size: 13px;
color: var(--text-primary);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
padding-right: 12px;
}
.chat-item-time {
font-size: 11px;
color: var(--text-muted);
flex-shrink: 0;
}
.chat-item-preview {
font-size: 13px;
color: var(--text-muted);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
/* Main Content Header */
.main-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 16px 24px;
border-bottom: 1px solid var(--border-subtle);
height: 65px;
}
.main-title {
font-size: 15px;
font-weight: 500;
}
.main-actions {
display: flex;
gap: 8px;
}
/* Chat Area */
.chat-scroll-area {
flex: 1;
overflow-y: auto;
padding: 24px 0;
display: flex;
flex-direction: column;
gap: 32px;
}
.message-wrapper {
max-width: 768px;
width: 100%;
margin: 0 auto;
padding: 0 24px;
display: flex;
}
/* User Message */
.message-wrapper.user {
justify-content: flex-end;
}
.message.user {
background-color: var(--bg-elevated);
padding: 12px 16px;
border-radius: 16px;
border-bottom-right-radius: 4px;
max-width: 80%;
}
/* Assistant Message */
.message-wrapper.assistant {
justify-content: flex-start;
}
.message.assistant {
display: flex;
gap: 16px;
width: 100%;
}
.ai-avatar {
width: 28px;
height: 28px;
border-radius: 4px;
background-color: var(--bg-elevated);
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
border: 1px solid var(--border-emphasis);
}
.message-content {
flex: 1;
min-width: 0;
display: flex;
flex-direction: column;
gap: 16px;
padding-top: 4px;
}
/* Markdown Styles */
.message-content h3 {
font-size: 16px;
font-weight: 600;
margin-bottom: 8px;
color: var(--text-primary);
}
.message-content p {
margin-bottom: 12px;
}
.message-content p:last-child {
margin-bottom: 0;
}
.message-content ul {
padding-left: 20px;
margin-bottom: 12px;
display: flex;
flex-direction: column;
gap: 6px;
}
.message-content li {
color: var(--text-secondary);
}
.message-content strong {
color: var(--text-primary);
font-weight: 600;
}
.code-block {
background-color: var(--bg-surface);
border: 1px solid var(--border-subtle);
border-radius: 8px;
overflow: hidden;
margin: 16px 0;
}
.code-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 8px 12px;
background-color: var(--bg-elevated);
border-bottom: 1px solid var(--border-subtle);
font-family: var(--font-sans);
font-size: 12px;
color: var(--text-secondary);
}
.copy-btn {
display: flex;
align-items: center;
gap: 6px;
font-size: 12px;
color: var(--text-muted);
}
.copy-btn:hover {
color: var(--text-primary);
}
.code-content {
padding: 16px;
overflow-x: auto;
font-family: var(--font-mono);
font-size: 13px;
line-height: 1.6;
}
.syntax-keyword { color: #c678dd; }
.syntax-string { color: #98c379; }
.syntax-function { color: #61afef; }
.syntax-comment { color: #5c6370; }
.syntax-punctuation { color: #abb2bf; }
.syntax-variable { color: #e06c75; }
table {
width: 100%;
border-collapse: collapse;
margin: 16px 0;
font-size: 13px;
}
th, td {
border: 1px solid var(--border-subtle);
padding: 10px 16px;
text-align: left;
}
th {
background-color: var(--bg-surface);
font-weight: 600;
color: var(--text-primary);
}
td {
color: var(--text-secondary);
}
/* Input Area */
.input-area {
padding: 0 24px 24px 24px;
max-width: 768px;
width: 100%;
margin: 0 auto;
}
.input-container {
display: flex;
align-items: flex-end;
background-color: var(--bg-surface);
border: 1px solid var(--border-subtle);
border-radius: 12px;
padding: 8px 12px;
transition: border-color 150ms ease;
}
.input-container:focus-within {
border-color: var(--border-emphasis);
}
.input-action-btn {
display: flex;
align-items: center;
justify-content: center;
width: 32px;
height: 32px;
border-radius: 6px;
color: var(--text-muted);
flex-shrink: 0;
margin-bottom: 2px;
transition: background-color 150ms ease, color 150ms ease;
}
.input-action-btn:hover {
background-color: var(--bg-elevated);
color: var(--text-primary);
}
.send-btn {
color: var(--accent);
}
.send-btn:hover {
background-color: rgba(59, 130, 246, 0.1);
color: #60a5fa;
}
.chat-input {
flex: 1;
background: none;
border: none;
color: var(--text-primary);
font-family: inherit;
font-size: 14px;
line-height: 1.5;
padding: 8px 12px;
resize: none;
min-height: 24px;
max-height: 200px;
overflow-y: auto;
}
.chat-input::placeholder {
color: var(--text-muted);
}
.chat-input:focus {
outline: none;
}
/* Custom Scrollbar */
::-webkit-scrollbar {
width: 8px;
height: 8px;
}
::-webkit-scrollbar-track {
background: transparent;
}
::-webkit-scrollbar-thumb {
background-color: var(--border-subtle);
border-radius: 4px;
}
::-webkit-scrollbar-thumb:hover {
background-color: var(--border-emphasis);
}
</style>
</head>
<body>
<div class="app-container">
<aside class="sidebar">
<div class="sidebar-header">
<div class="sidebar-title-row">
<h1 class="sidebar-title">Chats</h1>
<button class="icon-btn" aria-label="New chat">
<svg viewBox="0 0 24 24"><path d="M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/></svg>
</button>
</div>
<div class="search-box">
<svg class="search-icon" viewBox="0 0 24 24"><path d="M15.5 14h-.79l-.28-.27C15.41 12.59 16 11.11 16 9.5 16 5.91 13.09 3 9.5 3S3 5.91 3 9.5 5.91 16 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z"/></svg>
<input type="text" class="search-input" placeholder="Search chats...">
</div>
</div>
<div class="conversation-list">
<div class="time-group">
<div class="time-group-label">Today</div>
<div class="chat-item active">
<div class="chat-item-header">
<span class="chat-item-title">Python Data Structures</span>
<span class="chat-item-time">10:42 AM</span>
</div>
<div class="chat-item-preview">Here is a comparison table showing...</div>
</div>
<div class="chat-item">
<div class="chat-item-header">
<span class="chat-item-title">Quantum Physics Explained</span>
<span class="chat-item-time">8:15 AM</span>
</div>
<div class="chat-item-preview">Superposition means a particle exists in...</div>
</div>
</div>
<div class="time-group">
<div class="time-group-label">Yesterday</div>
<div class="chat-item">
<div class="chat-item-header">
<span class="chat-item-title">Essay: Climate Change</span>
<span class="chat-item-time">Yesterday</span>
</div>
<div class="chat-item-preview">The economic impacts of global warming...</div>
</div>
<div class="chat-item">
<div class="chat-item-header">
<span class="chat-item-title">History of Renaissance Art</span>
<span class="chat-item-time">Yesterday</span>
</div>
<div class="chat-item-preview">Da Vinci's sfumato technique revolutionized...</div>
</div>
</div>
<div class="time-group">
<div class="time-group-label">Previous 7 Days</div>
<div class="chat-item">
<div class="chat-item-header">
<span class="chat-item-title">Calculus Integration Help</span>
<span class="chat-item-time">Mon</span>
</div>
<div class="chat-item-preview">To solve this using integration by parts...</div>
</div>
<div class="chat-item">
<div class="chat-item-header">
<span class="chat-item-title">Creative Writing Prompts</span>
<span class="chat-item-time">Sun</span>
</div>
<div class="chat-item-preview">Imagine a city where the architecture...</div>
</div>
<div class="chat-item">
<div class="chat-item-header">
<span class="chat-item-title">Biology Cell Division</span>
<span class="chat-item-time">Sat</span>
</div>
<div class="chat-item-preview">During mitosis, the chromosomes align...</div>
</div>
<div class="chat-item">
<div class="chat-item-header">
<span class="chat-item-title">Study Plan Generator</span>
<span class="chat-item-time">Fri</span>
</div>
<div class="chat-item-preview">Based on your finals schedule, I recommend...</div>
</div>
</div>
</div>
</aside>
<main class="main-content">
<div class="main-header">
<h2 class="main-title">Python Data Structures</h2>
<div class="main-actions">
<button class="icon-btn" aria-label="Share">
<svg viewBox="0 0 24 24"><path d="M18 16.08c-.76 0-1.44.3-1.96.77L8.91 12.7c.05-.23.09-.46.09-.7s-.04-.47-.09-.7l7.05-4.11c.54.5 1.25.81 2.04.81 1.66 0 3-1.34 3-3s-1.34-3-3-3-3 1.34-3 3c0 .24.04.47.09.7L8.04 9.81C7.5 9.31 6.79 9 6 9c-1.66 0-3 1.34-3 3s1.34 3 3 3c.79 0 1.5-.31 2.04-.81l7.12 4.16c-.05.21-.08.43-.08.65 0 1.61 1.31 2.92 2.92 2.92 1.61 0 2.92-1.31 2.92-2.92s-1.31-2.92-2.92-2.92z"/></svg>
</button>
<button class="icon-btn" aria-label="Settings">
<svg viewBox="0 0 24 24"><path d="M12 8c1.1 0 2-.9 2-2s-.9-2-2-2-2 .9-2 2 .9 2 2 2zm0 2c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm0 6c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2z"/></svg>
</button>
</div>
</div>
<div class="chat-scroll-area">
<div class="message-wrapper user">
<div class="message user">
<div class="message-content">
<p>Can you explain the difference between lists and tuples in Python?</p>
</div>
</div>
</div>
<div class="message-wrapper assistant">
<div class="message assistant">
<div class="ai-avatar">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 2v20M17 5H9.5a3.5 3.5 0 0 0 0 7h5a3.5 3.5 0 0 1 0 7H6"/></svg>
</div>
<div class="message-content">
<h3>Lists vs. Tuples</h3>
<p>In Python, both lists and tuples are built-in data structures used to store collections of items, but they have one fundamental difference: <strong>mutability</strong>.</p>
<ul>
<li><strong>Lists are mutable:</strong> You can change, add, or remove elements after the list is created. They are defined using square brackets <code>[]</code>.</li>
<li><strong>Tuples are immutable:</strong> Once a tuple is created, its elements cannot be altered. They are defined using parentheses <code>()</code>.</li>
</ul>
<div class="code-block">
<div class="code-header">
<span>python</span>
<button class="copy-btn">
<svg viewBox="0 0 24 24"><path d="M16 1H4c-1.1 0-2 .9-2 2v14h2V3h12V1zm3 4H8c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h11c1.1 0 2-.9 2-2V7c0-1.1-.9-2-2-2zm0 16H8V7h11v14z"/></svg>
Copy
</button>
</div>
<div class="code-content">
<pre><span class="syntax-comment"># This is a list (mutable)</span>
<span class="syntax-variable">my_list</span> <span class="syntax-punctuation">=</span> <span class="syntax-punctuation">[</span><span class="syntax-string">"apple"</span><span class="syntax-punctuation">,</span> <span class="syntax-string">"banana"</span><span class="syntax-punctuation">,</span> <span class="syntax-string">"cherry"</span><span class="syntax-punctuation">]</span>
<span class="syntax-variable">my_list</span><span class="syntax-punctuation">[</span><span class="syntax-keyword">0</span><span class="syntax-punctuation">]</span> <span class="syntax-punctuation">=</span> <span class="syntax-string">"orange"</span> <span class="syntax-comment"># Valid: modifies the first element</span>
<span class="syntax-variable">my_list</span><span class="syntax-punctuation">.</span><span class="syntax-function">append</span><span class="syntax-punctuation">(</span><span class="syntax-string">"grape"</span><span class="syntax-punctuation">)</span> <span class="syntax-comment"># Valid: adds a new element</span>
<span class="syntax-comment"># This is a tuple (immutable)</span>
<span class="syntax-variable">my_tuple</span> <span class="syntax-punctuation">=</span> <span class="syntax-punctuation">(</span><span class="syntax-string">"apple"</span><span class="syntax-punctuation">,</span> <span class="syntax-string">"banana"</span><span class="syntax-punctuation">,</span> <span class="syntax-string">"cherry"</span><span class="syntax-punctuation">)</span>
<span class="syntax-variable">my_tuple</span><span class="syntax-punctuation">[</span><span class="syntax-keyword">0</span><span class="syntax-punctuation">]</span> <span class="syntax-punctuation">=</span> <span class="syntax-string">"orange"</span> <span class="syntax-comment"># TypeError: 'tuple' object does not support item assignment</span></pre>
</div>
</div>
<p>Because tuples are immutable, they are slightly more memory-efficient and generally faster to iterate over than lists.</p>
</div>
</div>
</div>
<div class="message-wrapper user">
<div class="message user">
<div class="message-content">
<p>When should I use each one?</p>
</div>
</div>
</div>
<div class="message-wrapper assistant">
<div class="message assistant">
<div class="ai-avatar">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 2v20M17 5H9.5a3.5 3.5 0 0 0 0 7h5a3.5 3.5 0 0 1 0 7H6"/></svg>
</div>
<div class="message-content">
<p>Here is a comparison to help you decide when to use a list versus a tuple:</p>
<table>
<thead>
<tr>
<th>Feature</th>
<th>List</th>
<th>Tuple</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Use Case</strong></td>
<td>Collections of items that may need to change during program execution (e.g., active user sessions, a shopping cart).</td>
<td>Collections of items that should remain constant (e.g., configuration settings, coordinates, days of the week).</td>
</tr>
<tr>
<td><strong>Syntax</strong></td>
<td>Square brackets <code>[]</code></td>
<td>Parentheses <code>()</code></td>
</tr>
<tr>
<td><strong>Performance</strong></td>
<td>Slightly slower, consumes more memory.</td>
<td>Faster iteration, more memory efficient.</td>
</tr>
<tr>
<td><strong>Dictionary Keys</strong></td>
<td>Cannot be used as dictionary keys (unhashable).</td>
<td>Can be used as dictionary keys (hashable).</td>
</tr>
</tbody>
</table>
<p><strong>Summary:</strong> Use a <strong>list</strong> if you expect the collection of data to grow, shrink, or change over time. Use a <strong>tuple</strong> when you want to ensure the data cannot be modified, which provides a guarantee of data integrity and a slight performance boost.</p>
</div>
</div>
</div>
</div>
<div class="input-area">
<div class="input-container">
<button class="input-action-btn" aria-label="Attach file">
<svg viewBox="0 0 24 24"><path d="M16.5 6v11.5c0 2.21-1.79 4-4 4s-4-1.79-4-4V5a2.5 2.5 0 0 1 5 0v10.5c0 .55-.45 1-1 1s-1-.45-1-1V6H10v9.5a2.5 2.5 0 0 0 5 0V5c0-2.21-1.79-4-4-4S7 2.79 7 5v12.5c0 3.04 2.46 5.5 5.5 5.5s5.5-2.46 5.5-5.5V6h-1.5z"/></svg>
</button>
<textarea class="chat-input" placeholder="Type a message..." rows="1" oninput="this.style.height = '';this.style.height = this.scrollHeight + 'px'"></textarea>
<button class="input-action-btn send-btn" aria-label="Send message">
<svg viewBox="0 0 24 24"><path d="M2.01 21L23 12 2.01 3 2 10l15 2-15 2z"/></svg>
</button>
</div>
</div>
</main>
</div>
<script>
document.querySelectorAll('.chat-item').forEach(item => {
item.addEventListener('click', () => {
document.querySelector('.chat-item.active')?.classList.remove('active');
item.classList.add('active');
});
});
</script>
</body>
</html>
Context Bundling for Large Codebases
When the project is large, use packx to bundle relevant code context before building the Gemini prompt.
When to Bundle
- The component/page being redesigned imports from many files
- You need to show Gemini the design system (tokens, shared components, utilities)
- The project has a complex directory structure
How to Bundle
# Bundle specific directories relevant to design
packx --limit 49k --include "src/components/**" --include "src/styles/**" --output /tmp/design-context.txt
# Bundle a specific feature area
packx --limit 49k --include "src/app/dashboard/**" --output /tmp/feature-context.txtUsing Bundled Context in Prompts
Write the full prompt (including bundled context) to a temp file, then pipe to Gemini:
# Write prompt to temp file
cat > /tmp/gemini-prompt.txt << 'PROMPT_EOF'
You are a senior UI/UX designer...
## Existing Codebase Context
[PACKX OUTPUT GOES HERE]
## Design System
[.impeccable.md CONTENT]
## Task
[WHAT TO BUILD]
PROMPT_EOF
# Send to Gemini
cat /tmp/gemini-prompt.txt | gemini -m gemini-3.1-pro-preview -p "Follow the instructions from stdin." -yContext Priority
When hitting context limits, prioritize in this order: 1. .impeccable.md (design language — always include) 2. The specific file being redesigned 3. Shared design tokens / CSS variables 4. Related components that should match style 5. Layout/routing patterns
Design Reference Integration
The frontend-design skill has detailed reference docs that should be selectively included in Gemini prompts based on the task.
When to Include Each Reference
| Task involves | Include this reference |
|---|---|
| Font choices, type scale, headings | typography |
| Color palette, dark mode, theming | color-and-contrast |
| Layout, grid, spacing, padding | spatial-design |
| Animations, transitions, hover effects | motion-design |
| Forms, buttons, modals, loading states | interaction-design |
How to Include
Read the relevant reference file from ~/.agents/skills/frontend-design/reference/ and include the key rules section (not the full file). Extract 10-15 most relevant lines.
Example for a dashboard mockup (needs spatial + typography):
## Typography Guidelines (from project design system)
- Use a modular type scale: 12 / 14 / 16 / 20 / 24 / 32 / 40 / 48px
- Body text: 16px/1.5 line-height minimum
- Headings: use weight contrast (600-700) rather than just size
- Don't use more than 2 font families
- Use fluid sizing: clamp(1rem, 0.5rem + 1vw, 1.25rem)
## Spatial Guidelines (from project design system)
- Base unit: 4px. Scale: 4, 8, 12, 16, 24, 32, 48, 64
- Group related items with tight spacing (8-12px)
- Separate sections with generous spacing (32-48px)
- Don't use the same spacing everywhere — create rhythm
- Use container queries for component-level responsivenessReference File Paths
~/.agents/skills/frontend-design/reference/typography.md
~/.agents/skills/frontend-design/reference/color-and-contrast.md
~/.agents/skills/frontend-design/reference/spatial-design.md
~/.agents/skills/frontend-design/reference/motion-design.md
~/.agents/skills/frontend-design/reference/interaction-design.md
~/.agents/skills/frontend-design/reference/responsive-design.md
~/.agents/skills/frontend-design/reference/ux-writing.mdAnti-Patterns to Always Include
Always include these anti-patterns in every Gemini prompt — they prevent "AI slop":
## Anti-Patterns (NEVER USE THESE)
- Card grids with icon + heading + text repeated identically
- Glassmorphism/blur effects used decoratively
- Pure black (#000) or pure white (#fff) — tint your neutrals
- Cyan-on-dark, purple-to-blue gradients, neon accents
- Gradient text on headings or metrics
- Bounce/elastic easing curves
- Same spacing everywhere — vary it for rhythm
- Center-aligning everything
- Rounded rectangles with generic drop shadows
- Dark mode with glowing accents as a substitute for actual design decisions
- Big icons with rounded corners above every heading
- Monospace typography as lazy shorthand for "technical" vibes
- Modals for things that could be inline or in a panelPreview Gallery Templates
After generating variations, create a comparison gallery page. Choose the template matching the project's framework.
Next.js App Router
Create app/mockups/page.tsx:
'use client'
import { useState } from 'react'
const variations = [
{ id: '01', name: 'Spacious Editorial', description: 'Typography-led hierarchy with generous whitespace', path: '/mockups/01-spacious-editorial' },
{ id: '02', name: 'Compact Sidebar', description: 'Sidebar navigation, data-dense layout', path: '/mockups/02-compact-sidebar' },
{ id: '03', name: 'Minimal Command Palette', description: 'Command-palette-driven, minimal chrome', path: '/mockups/03-minimal-command-palette' },
]
export default function MockupGallery() {
const [selected, setSelected] = useState(variations[0].id)
return (
<div style={{ fontFamily: 'system-ui', padding: '2rem', maxWidth: '80rem', margin: '0 auto' }}>
<h1 style={{ fontSize: '1.5rem', fontWeight: 600, marginBottom: '0.5rem' }}>Design Variations</h1>
<p style={{ color: '#666', marginBottom: '2rem' }}>Click to preview each variation. Open in a new tab for full experience.</p>
<div style={{ display: 'flex', gap: '1rem', marginBottom: '2rem' }}>
{variations.map(v => (
<button key={v.id} onClick={() => setSelected(v.id)}
style={{ padding: '0.75rem 1.5rem', border: selected === v.id ? '2px solid #0070f3' : '2px solid #e5e5e5',
borderRadius: '8px', background: selected === v.id ? '#f0f7ff' : '#fff', cursor: 'pointer', textAlign: 'left' }}>
<div style={{ fontWeight: 600 }}>{v.name}</div>
<div style={{ fontSize: '0.85rem', color: '#666' }}>{v.description}</div>
</button>
))}
</div>
<div style={{ display: 'flex', gap: '1rem' }}>
<iframe src={variations.find(v => v.id === selected)?.path}
style={{ width: '100%', height: '80vh', border: '1px solid #e5e5e5', borderRadius: '8px' }} />
</div>
<div style={{ marginTop: '1rem', display: 'flex', gap: '0.5rem' }}>
<a href={variations.find(v => v.id === selected)?.path} target="_blank" rel="noopener"
style={{ color: '#0070f3', textDecoration: 'none' }}>Open in new tab ↗</a>
</div>
</div>
)
}Plain HTML
Create mockups/index.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Design Variations</title>
<style>
* { margin: 0; box-sizing: border-box; }
body { font-family: system-ui; padding: 2rem; }
.tabs { display: flex; gap: 0.5rem; margin-bottom: 1rem; }
.tab { padding: 0.5rem 1rem; border: 2px solid #e5e5e5; border-radius: 6px; cursor: pointer; background: #fff; }
.tab.active { border-color: #0070f3; background: #f0f7ff; }
iframe { width: 100%; height: 85vh; border: 1px solid #e5e5e5; border-radius: 8px; }
</style>
</head>
<body>
<h1>Design Variations</h1>
<div class="tabs" id="tabs"></div>
<iframe id="preview"></iframe>
<script>
const variations = [
{ name: 'V1 — Spacious Editorial', src: 'v1.html' },
{ name: 'V2 — Compact Sidebar', src: 'v2.html' },
{ name: 'V3 — Minimal Command Palette', src: 'v3.html' },
];
const tabs = document.getElementById('tabs');
const preview = document.getElementById('preview');
variations.forEach((v, i) => {
const btn = document.createElement('button');
btn.className = 'tab' + (i === 0 ? ' active' : '');
btn.textContent = v.name;
btn.onclick = () => {
preview.src = v.src;
document.querySelectorAll('.tab').forEach(t => t.classList.remove('active'));
btn.classList.add('active');
};
tabs.appendChild(btn);
});
preview.src = variations[0].src;
</script>
</body>
</html>React SPA
Create a preview component and add a route:
import { useState } from 'react'
import V1 from './mockups/FeatureV1'
import V2 from './mockups/FeatureV2'
import V3 from './mockups/FeatureV3'
const variations = [
{ name: 'Spacious Editorial', Component: V1 },
{ name: 'Compact Sidebar', Component: V2 },
{ name: 'Minimal Command Palette', Component: V3 },
]
export function MockupGallery() {
const [idx, setIdx] = useState(0)
const { Component } = variations[idx]
return (
<div>
<div style={{ display: 'flex', gap: 8, padding: 16, borderBottom: '1px solid #eee' }}>
{variations.map((v, i) => (
<button key={i} onClick={() => setIdx(i)}
style={{ padding: '8px 16px', background: i === idx ? '#0070f3' : '#f5f5f5',
color: i === idx ? '#fff' : '#333', border: 'none', borderRadius: 6, cursor: 'pointer' }}>
{v.name}
</button>
))}
</div>
<Component />
</div>
)
}Adaptation Notes
- Update the
variationsarray to match your actual file paths and names - The gallery is intentionally plain — it shouldn't impose its own design on the mockups
- For responsive testing, add viewport width controls (320px, 768px, 1024px, 1440px)
- The iframe approach (Next.js/HTML) isolates each variation's styles completely
Gemini Prompt Construction
Always write prompts to a temp file and pipe via stdin. Never inline complex prompts in -p.
Template
You are an elite UI designer who writes production-ready [FRAMEWORK] code. You have a strong opinion about aesthetics and never produce generic, templated interfaces.
## Design System (FOLLOW EXACTLY)
[PASTE FULL .impeccable.md CONTENT]
## Anti-Patterns (AVOID THESE)
These are hallmarks of AI-generated design slop. Never use:
- Card grids with icon + heading + text repeated identically
- Glassmorphism/blur effects used decoratively
- Pure black (#000) or pure white (#fff) — always tint
- Cyan-on-dark, purple-to-blue gradients, neon accents
- Gradient text on headings or metrics
- Bounce/elastic easing curves
- Identical spacing everywhere — create rhythm through variation
- Centering everything — left-aligned text with asymmetric layouts feels more designed
## Design Reference
[INCLUDE RELEVANT EXCERPT from frontend-design references — e.g., typography scale, color strategy, spacing system]
## Existing Code Context
[PASTE RELEVANT EXISTING CODE — the component being redesigned, or similar components that establish the visual language]
## Task
[SPECIFIC DESCRIPTION — what to build, what content it shows, what interactions it supports]
## Variation Direction
[FOR THIS SPECIFIC VARIATION: exactly what design dimension to explore and how]
## Output Rules
- Output ONLY the complete file content — no markdown fences, no explanations, no comments about the code
- Use only these dependencies: [LIST FROM package.json]
- File will be saved to: [TARGET_PATH]
- [FRAMEWORK CONVENTIONS: e.g., "export default function Page()" for Next.js App Router]
- Include sample data inline as const — do not import from external files
- All styles inline or via the project's existing styling approach (tailwind/CSS modules/styled-components)Prompt Quality Checklist
Before sending to Gemini, verify:
- [ ]
.impeccable.mdcontent is included in full - [ ] Anti-patterns section is present
- [ ] At least one code context file is included (even for blank-slate designs, include a similar existing component)
- [ ] Content is specific — real data shapes, real labels, real metrics
- [ ] Variation direction is concrete and singular (one dimension)
- [ ] Output rules specify exact framework conventions
Content Specificity
Bad: "Create a dashboard" Good: "Create a project dashboard for a SaaS analytics tool showing: a metrics bar with 4 KPIs (MRR $42.1k trending up 12%, churn 2.3% trending down, MAU 8,421 trending up, NPS 72 stable), a sparkline chart area showing revenue over 12 months, and a recent activity feed with 5 items showing user avatar, action text, and relative timestamp"
Bad: "Create a settings page" Good: "Create an account settings page with: a profile section (avatar upload, name, email, timezone dropdown), a notification preferences section (email digest frequency, push notification toggles for 4 categories), and a danger zone section (export data button, delete account with confirmation)"
Stdin Pipe Pattern
# Write prompt to temp file
cat > /tmp/gemini-prompt-v1.txt << 'PROMPT_EOF'
[full prompt here]
PROMPT_EOF
# Pipe to Gemini — the -p flag appends to stdin
cat /tmp/gemini-prompt-v1.txt | gemini -m gemini-3.1-pro-preview \
-p "Generate the file exactly as instructed above. Output ONLY the raw file content." \
-y --sandboxHandling Gemini Output Issues
If Gemini wraps output in markdown fences:
# Strip leading/trailing markdown fences from generated file
sed -i '' '1{/^```/d}' /path/to/output.tsx
sed -i '' '${/^```/d}' /path/to/output.tsxIf Gemini adds explanatory text before/after the code, manually extract the code block or re-run with stronger "output ONLY the file" instructions.
Variation Strategies
Each variation set should explore one dimension so the user can make a meaningful comparison. Don't randomize multiple things — isolate the variable.
Variation Dimensions
Layout Structure
- V1: Traditional top-nav + main content area
- V2: Sidebar navigation with content area (Linear-style)
- V3: Command-palette-driven with minimal persistent chrome (Raycast-style)
Visual Density
- V1: Spacious — generous padding, one focal element per viewport section, dramatic whitespace
- V2: Balanced — standard padding, multiple visible elements, readable rhythm
- V3: Compact — tight spacing, data-rich tables/lists, power-user optimized
Hierarchy Approach
- V1: Typography-led — size and weight create all hierarchy, minimal color
- V2: Color-led — color blocks, accents, and tints guide attention
- V3: Spatial — position, grouping, and negative space create importance
Interaction Model
- V1: Static overview — all information visible, scroll-based
- V2: Progressive disclosure — sections expand, details on hover/click
- V3: Tab/panel navigation — content organized into switchable views
Visual Tone
- V1: Minimal/refined — restrained palette, thin type, geometric
- V2: Editorial/magazine — bold typography, asymmetric layouts, dramatic scale
- V3: Organic/warm — rounded shapes, warm palette, handcraft feel
Content Organization
- V1: Card grid — modular, scannable, each card is self-contained
- V2: List/feed — vertical stream, timeline-like, chronological
- V3: Dashboard panels — resizable sections, data-forward, mixed content types
Theme Variants (use as a second pass)
- Light → Dark (or reverse)
- Desktop → Mobile (responsive variant)
- Default → High contrast (accessibility variant)
Choosing the Right Dimension
Match the dimension to the user's need:
| User says | Best dimension |
|---|---|
| "I don't know what layout to use" | Layout Structure |
| "It feels too cramped/empty" | Visual Density |
| "Everything looks the same importance" | Hierarchy Approach |
| "Too much info on screen" | Interaction Model |
| "It looks generic" | Visual Tone |
| "I have a lot of different content types" | Content Organization |
Variation Naming
Name variations descriptively, not just V1/V2/V3:
app/mockups/spacious-editorial/page.tsx
app/mockups/compact-sidebar/page.tsx
app/mockups/minimal-command-palette/page.tsxOr with numeric prefix for ordering:
app/mockups/01-spacious-editorial/page.tsx
app/mockups/02-compact-sidebar/page.tsx
app/mockups/03-minimal-command-palette/page.tsx