twin / styles.py
ed-donner's picture
Upload folder using huggingface_hub
96b63be verified
"""Styling constants for the digital twin Gradio app."""
GOLD = "#ecad0a"
BLUE = "#209dd7"
PURPLE = "#753991"
EXAMPLES = [
"Tell me about your background and experience.",
"What kinds of projects are you working on now?",
"What are your strongest technical skills?",
"How can I get in touch with you?",
]
CSS = """
:root {
--twin-gold: #ecad0a;
--twin-blue: #209dd7;
--twin-purple: #753991;
--twin-bg: #0d0d10;
--twin-surface: #16161b;
--twin-surface-2: #1c1c22;
--twin-border: #2a2a32;
--twin-border-strong: #3a3a44;
--twin-text: #ececef;
--twin-muted: #8c8c95;
}
/* Light mode: Gradio adds `.dark` to <body> when dark; absence = light.
Only the neutral palette flips — gold/blue/purple accents stay identical. */
body:not(.dark) {
--twin-bg: #f4f4f6;
--twin-surface: #ffffff;
--twin-surface-2: #ededf0;
--twin-border: #dcdce2;
--twin-border-strong: #b8b8c0;
--twin-text: #1a1a20;
--twin-muted: #6a6a72;
}
footer, .built-with, .show-api, .api-docs { display: none !important; }
html, body, gradio-app { background: var(--twin-bg) !important; }
/* ---------- Stable layout ---------- */
.gradio-container {
background: var(--twin-bg) !important;
color: var(--twin-text) !important;
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif !important;
width: 100% !important;
max-width: 880px !important;
min-width: 0 !important;
margin: 0 auto !important;
padding: 32px 24px 48px !important;
}
.gradio-container .main, .gradio-container .contain, .gradio-container .wrap {
width: 100% !important;
max-width: 100% !important;
min-width: 0 !important;
}
.gradio-container * { min-width: 0; }
/* ---------- Title ---------- */
.gradio-container h1 {
color: var(--twin-text) !important;
font-size: 26px !important;
font-weight: 700 !important;
letter-spacing: -0.02em !important;
border-left: 3px solid var(--twin-gold);
padding-left: 12px !important;
margin: 4px 0 8px !important;
text-align: left !important;
}
/* ---------- Sharp corners on structural pieces ---------- */
.chatbot, .chatbot *, .block, .form,
button, input, textarea,
.examples button {
border-radius: 0 !important;
}
/* ---------- Block surfaces ---------- */
.block, .form { background: transparent !important; box-shadow: none !important; }
/* ---------- Hide the Chatbot label / header strip ---------- */
.chatbot > .block-label,
.chatbot > label,
.chatbot .label-wrap,
.chatbot .block-label,
.chatbot > .label-container {
display: none !important;
}
/* ---------- Chatbot frame ---------- */
.chatbot, .chatbot.block {
background: var(--twin-surface) !important;
border: 1px solid var(--twin-border) !important;
min-height: 460px !important;
box-shadow: none !important;
}
.chatbot .placeholder, .chatbot .placeholder * { color: var(--twin-muted) !important; }
/* ---------- Message rows: strip parent backgrounds ---------- */
.message-row,
.message-row > div,
.message-row .role,
.message-wrap, .bubble-wrap {
background: transparent !important;
border: 0 !important;
box-shadow: none !important;
}
/* ---------- Reset borders on every bubble variant first ---------- */
.message-row .message,
.message-row .message-bubble,
.message-row .bubble {
border: 0 !important;
box-shadow: none !important;
padding: 6px 10px !important;
}
/* ---------- Bubble backgrounds (broad to cover Gradio variants) ---------- */
.message-row.user-row .message,
.message-row.user-row .message-bubble,
.message-row.user-row .bubble,
.message-row[data-role="user"] .message,
.message-row[data-role="user"] .message-bubble {
background: var(--twin-blue) !important;
color: #ffffff !important;
}
.message-row.bot-row .message,
.message-row.bot-row .message-bubble,
.message-row.bot-row .bubble,
.message-row[data-role="assistant"] .message,
.message-row[data-role="assistant"] .message-bubble {
background: var(--twin-surface-2) !important;
color: var(--twin-text) !important;
}
/* ---------- Purple stripe ----------
Apply to every common bubble class for assistant rows (we don't know which
one the running Gradio uses), then suppress on any *nested* instance so the
stripe lands on the outermost matching element only — exactly one stripe. */
.message-row.bot-row .message,
.message-row.bot-row .bubble,
.message-row.bot-row .message-bubble,
.message-row[data-role="assistant"] .message,
.message-row[data-role="assistant"] .bubble,
.message-row[data-role="assistant"] .message-bubble {
border-left: 2px solid var(--twin-purple) !important;
}
.message-row.bot-row .message .message,
.message-row.bot-row .message .bubble,
.message-row.bot-row .message .message-bubble,
.message-row.bot-row .bubble .message,
.message-row.bot-row .bubble .bubble,
.message-row.bot-row .bubble .message-bubble,
.message-row.bot-row .message-bubble .message,
.message-row.bot-row .message-bubble .bubble,
.message-row.bot-row .message-bubble .message-bubble,
.message-row[data-role="assistant"] .message .message,
.message-row[data-role="assistant"] .message .bubble,
.message-row[data-role="assistant"] .message .message-bubble,
.message-row[data-role="assistant"] .bubble .message,
.message-row[data-role="assistant"] .bubble .bubble,
.message-row[data-role="assistant"] .bubble .message-bubble,
.message-row[data-role="assistant"] .message-bubble .message,
.message-row[data-role="assistant"] .message-bubble .bubble,
.message-row[data-role="assistant"] .message-bubble .message-bubble {
border-left: 0 !important;
}
/* ---------- Uniform font size in bubbles ----------
The "first paragraph different size" was caused by a leaky `.prose p:first-of-type`
selector. Force every paragraph in a bubble to the same size. */
.message-row .message,
.message-row .message-bubble,
.message-row .bubble {
font-size: 14px !important;
line-height: 1.55 !important;
}
.message-row .message p,
.message-row .message-bubble p,
.message-row .bubble p,
.message-row .prose p {
font-size: 14px !important;
line-height: 1.55 !important;
margin: 0 0 8px !important;
color: inherit !important;
}
.message-row .message p:last-child,
.message-row .message-bubble p:last-child,
.message-row .bubble p:last-child,
.message-row .prose p:last-child { margin-bottom: 0 !important; }
/* Strip stray internal borders/backgrounds from anything inside a bubble */
.message-row .message *,
.message-row .message-bubble *,
.message-row .bubble * {
background: transparent !important;
border-color: transparent !important;
box-shadow: none !important;
color: inherit !important;
}
.message-row .message a,
.message-row .message-bubble a {
color: var(--twin-gold) !important;
text-decoration: underline;
}
/* ---------- Input row alignment ---------- */
.input-row,
.gr-input-row,
.chat-input-row,
form[class*="input"] { align-items: stretch !important; }
textarea, input[type="text"] {
background: var(--twin-surface) !important;
border: 1px solid var(--twin-border) !important;
color: var(--twin-text) !important;
font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif !important;
font-size: 14px !important;
padding: 12px 14px !important;
line-height: 1.4 !important;
min-height: 48px !important;
}
textarea:focus, input[type="text"]:focus {
border-color: var(--twin-gold) !important;
outline: none !important;
box-shadow: 0 0 0 1px var(--twin-gold) !important;
}
textarea::placeholder, input::placeholder { color: var(--twin-muted) !important; }
/* ---------- Buttons ---------- */
button {
font-family: 'JetBrains Mono', 'SF Mono', Menlo, monospace !important;
letter-spacing: 0.12em !important;
text-transform: uppercase !important;
font-size: 11px !important;
font-weight: 600 !important;
border: 1px solid var(--twin-border) !important;
background: transparent !important;
color: var(--twin-text) !important;
padding: 0 16px !important;
min-height: 48px !important;
align-self: stretch !important;
display: inline-flex !important;
align-items: center !important;
justify-content: center !important;
cursor: pointer;
transition: background 0.12s ease, color 0.12s ease, border-color 0.12s ease;
}
button:hover { border-color: var(--twin-gold) !important; color: var(--twin-gold) !important; }
button.primary,
button[variant="primary"],
button.submit,
button.submit-button,
.submit-button,
button.lg.primary {
background: var(--twin-gold) !important;
border: 1px solid var(--twin-gold) !important;
color: #111111 !important;
min-height: 48px !important;
align-self: stretch !important;
padding: 0 14px !important;
display: inline-flex !important;
align-items: center !important;
justify-content: center !important;
}
button.primary:hover,
button.submit:hover,
.submit-button:hover,
button.lg.primary:hover {
background: #ffc320 !important;
border-color: #ffc320 !important;
color: #111111 !important;
}
/* ---------- Submit-button icon: center vertically and size correctly ---------- */
button.submit svg,
button.submit-button svg,
.submit-button svg,
button.primary svg,
button[variant="primary"] svg {
width: 18px !important;
height: 18px !important;
margin: 0 auto !important;
display: block !important;
align-self: center !important;
color: #111111 !important;
fill: currentColor !important;
stroke: currentColor !important;
}
/* ---------- Examples ---------- */
.examples, .examples-holder, [data-testid="examples"] {
background: transparent !important;
padding: 0 !important;
margin-top: 14px !important;
}
.examples table, .examples-table { background: transparent !important; border: 0 !important; }
.examples button, .example, .examples td button, [data-testid="examples"] button {
background: var(--twin-surface) !important;
border: 1px solid var(--twin-border) !important;
color: var(--twin-text) !important;
text-transform: none !important;
letter-spacing: 0 !important;
font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif !important;
font-size: 13px !important;
font-weight: 400 !important;
padding: 10px 14px !important;
text-align: left !important;
min-height: 0 !important;
align-self: auto !important;
display: inline-block !important;
}
.examples button:hover, .example:hover, [data-testid="examples"] button:hover {
border-color: var(--twin-blue) !important;
color: var(--twin-blue) !important;
background: var(--twin-surface) !important;
}
/* ---------- Icon buttons (clear, retry, copy) ---------- */
.icon-button, .chatbot .icon-button {
color: var(--twin-muted) !important;
background: transparent !important;
border: 0 !important;
min-height: 0 !important;
align-self: auto !important;
padding: 4px !important;
display: inline-flex !important;
align-items: center !important;
justify-content: center !important;
}
.icon-button:hover, .chatbot .icon-button:hover { color: var(--twin-gold) !important; }
/* ---------- Scrollbar ---------- */
::-webkit-scrollbar { width: 10px; height: 10px; }
::-webkit-scrollbar-track { background: var(--twin-bg); }
::-webkit-scrollbar-thumb { background: var(--twin-border-strong); }
::-webkit-scrollbar-thumb:hover { background: var(--twin-purple); }
/* ---------- Selection ---------- */
::selection { background: var(--twin-gold); color: #111111; }
/* ---------- Mobile ---------- */
@media (max-width: 640px) {
.gradio-container { padding: 22px 14px 36px !important; }
.gradio-container h1 { font-size: 22px !important; }
}
"""
JS = """
() => {
document.title = 'Digital Twin';
const focusInput = () => {
const areas = document.querySelectorAll('textarea');
if (areas.length) areas[areas.length - 1].focus();
};
setTimeout(focusInput, 300);
// Re-focus the message field whenever Gradio re-enables it
// (i.e. after the assistant finishes responding).
const watchTextarea = (area) => {
if (area.dataset.twinWatched) return;
area.dataset.twinWatched = '1';
let wasDisabled = area.disabled || area.readOnly;
new MutationObserver(() => {
const isDisabled = area.disabled || area.readOnly;
if (wasDisabled && !isDisabled) area.focus();
wasDisabled = isDisabled;
}).observe(area, { attributes: true, attributeFilter: ['disabled', 'readonly'] });
};
const scan = () => document.querySelectorAll('textarea').forEach(watchTextarea);
setTimeout(scan, 500);
new MutationObserver(scan).observe(document.body, { childList: true, subtree: true });
}
"""