428 lines
7.9 KiB
Markdown
428 lines
7.9 KiB
Markdown
|
|
# CSS Customization
|
|||
|
|
|
|||
|
|
The **gbtheme** CSS files define the visual style of the bot UI. They are split into three layers to make them easy to extend.
|
|||
|
|
|
|||
|
|
## Files
|
|||
|
|
|
|||
|
|
| File | Role |
|
|||
|
|
|------|------|
|
|||
|
|
| `main.css` | Core layout, typography, and global variables. |
|
|||
|
|
| `components.css` | Styles for reusable UI components (buttons, cards, modals). |
|
|||
|
|
| `responsive.css` | Media queries for mobile, tablet, and desktop breakpoints. |
|
|||
|
|
|
|||
|
|
## CSS Variables (in `main.css`)
|
|||
|
|
|
|||
|
|
```css
|
|||
|
|
:root {
|
|||
|
|
--primary-color: #2563eb;
|
|||
|
|
--secondary-color: #64748b;
|
|||
|
|
--background-color: #ffffff;
|
|||
|
|
--text-color: #1e293b;
|
|||
|
|
--border-radius: 8px;
|
|||
|
|
--spacing-unit: 8px;
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
Changing a variable updates the entire theme without editing individual rules.
|
|||
|
|
|
|||
|
|
## Extending the Theme
|
|||
|
|
|
|||
|
|
1. **Add a new variable** – Append to `:root` and reference it in any selector.
|
|||
|
|
2. **Override a component** – Duplicate the selector in `components.css` after the original definition; the later rule wins.
|
|||
|
|
3. **Create a dark mode** – Add a `@media (prefers-color-scheme: dark)` block that redefines the variables.
|
|||
|
|
|
|||
|
|
```css
|
|||
|
|
@media (prefers-color-scheme: dark) {
|
|||
|
|
:root {
|
|||
|
|
--primary-color: #3b82f6;
|
|||
|
|
--background-color: #111827;
|
|||
|
|
--text-color: #f9fafb;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## Best Practices
|
|||
|
|
|
|||
|
|
* Keep the file size small – avoid large image data URIs; store images in `assets/`.
|
|||
|
|
* Use `rem` units for font sizes; they scale with the root `font-size`.
|
|||
|
|
* Limit the depth of nesting; flat selectors improve performance.
|
|||
|
|
|
|||
|
|
All CSS files are loaded in `index.html` in the order: `main.css`, `components.css`, `responsive.css`.
|
|||
|
|
|
|||
|
|
## Component Styling Guide
|
|||
|
|
|
|||
|
|
### Message Bubbles
|
|||
|
|
|
|||
|
|
Customize chat message appearance:
|
|||
|
|
|
|||
|
|
```css
|
|||
|
|
/* User messages */
|
|||
|
|
.message-user {
|
|||
|
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|||
|
|
color: white;
|
|||
|
|
padding: 12px 16px;
|
|||
|
|
border-radius: 18px 18px 4px 18px;
|
|||
|
|
max-width: 70%;
|
|||
|
|
margin-left: auto;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/* Bot messages */
|
|||
|
|
.message-bot {
|
|||
|
|
background: #f7fafc;
|
|||
|
|
color: #2d3748;
|
|||
|
|
padding: 12px 16px;
|
|||
|
|
border-radius: 18px 18px 18px 4px;
|
|||
|
|
max-width: 70%;
|
|||
|
|
border: 1px solid #e2e8f0;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/* Typing indicator */
|
|||
|
|
.typing-indicator {
|
|||
|
|
display: inline-flex;
|
|||
|
|
padding: 16px;
|
|||
|
|
background: #edf2f7;
|
|||
|
|
border-radius: 18px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.typing-indicator span {
|
|||
|
|
height: 8px;
|
|||
|
|
width: 8px;
|
|||
|
|
background: #718096;
|
|||
|
|
border-radius: 50%;
|
|||
|
|
margin: 0 2px;
|
|||
|
|
animation: typing 1.4s infinite;
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### Input Field
|
|||
|
|
|
|||
|
|
Style the message input area:
|
|||
|
|
|
|||
|
|
```css
|
|||
|
|
.input-container {
|
|||
|
|
padding: 16px;
|
|||
|
|
background: white;
|
|||
|
|
border-top: 1px solid #e2e8f0;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.input-wrapper {
|
|||
|
|
display: flex;
|
|||
|
|
align-items: center;
|
|||
|
|
background: #f7fafc;
|
|||
|
|
border: 2px solid #e2e8f0;
|
|||
|
|
border-radius: 24px;
|
|||
|
|
padding: 8px 16px;
|
|||
|
|
transition: all 0.2s;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.input-wrapper:focus-within {
|
|||
|
|
border-color: var(--primary-color);
|
|||
|
|
background: white;
|
|||
|
|
box-shadow: 0 0 0 3px rgba(66, 153, 225, 0.1);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.message-input {
|
|||
|
|
flex: 1;
|
|||
|
|
border: none;
|
|||
|
|
background: transparent;
|
|||
|
|
outline: none;
|
|||
|
|
font-size: 16px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.send-button {
|
|||
|
|
background: var(--primary-color);
|
|||
|
|
color: white;
|
|||
|
|
border: none;
|
|||
|
|
border-radius: 50%;
|
|||
|
|
width: 36px;
|
|||
|
|
height: 36px;
|
|||
|
|
display: flex;
|
|||
|
|
align-items: center;
|
|||
|
|
justify-content: center;
|
|||
|
|
cursor: pointer;
|
|||
|
|
transition: transform 0.2s;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.send-button:hover {
|
|||
|
|
transform: scale(1.1);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.send-button:active {
|
|||
|
|
transform: scale(0.95);
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### Buttons
|
|||
|
|
|
|||
|
|
Consistent button styling:
|
|||
|
|
|
|||
|
|
```css
|
|||
|
|
/* Primary button */
|
|||
|
|
.btn-primary {
|
|||
|
|
background: var(--primary-color);
|
|||
|
|
color: white;
|
|||
|
|
border: none;
|
|||
|
|
padding: 10px 20px;
|
|||
|
|
border-radius: 8px;
|
|||
|
|
font-weight: 500;
|
|||
|
|
cursor: pointer;
|
|||
|
|
transition: all 0.2s;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.btn-primary:hover {
|
|||
|
|
filter: brightness(110%);
|
|||
|
|
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/* Secondary button */
|
|||
|
|
.btn-secondary {
|
|||
|
|
background: transparent;
|
|||
|
|
color: var(--primary-color);
|
|||
|
|
border: 2px solid var(--primary-color);
|
|||
|
|
padding: 8px 18px;
|
|||
|
|
border-radius: 8px;
|
|||
|
|
font-weight: 500;
|
|||
|
|
cursor: pointer;
|
|||
|
|
transition: all 0.2s;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.btn-secondary:hover {
|
|||
|
|
background: var(--primary-color);
|
|||
|
|
color: white;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/* Icon button */
|
|||
|
|
.btn-icon {
|
|||
|
|
background: transparent;
|
|||
|
|
border: none;
|
|||
|
|
width: 40px;
|
|||
|
|
height: 40px;
|
|||
|
|
border-radius: 50%;
|
|||
|
|
display: flex;
|
|||
|
|
align-items: center;
|
|||
|
|
justify-content: center;
|
|||
|
|
cursor: pointer;
|
|||
|
|
transition: background 0.2s;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.btn-icon:hover {
|
|||
|
|
background: rgba(0, 0, 0, 0.05);
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## Animation Library
|
|||
|
|
|
|||
|
|
### Entrance Animations
|
|||
|
|
|
|||
|
|
```css
|
|||
|
|
@keyframes slideInUp {
|
|||
|
|
from {
|
|||
|
|
transform: translateY(20px);
|
|||
|
|
opacity: 0;
|
|||
|
|
}
|
|||
|
|
to {
|
|||
|
|
transform: translateY(0);
|
|||
|
|
opacity: 1;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
@keyframes fadeIn {
|
|||
|
|
from { opacity: 0; }
|
|||
|
|
to { opacity: 1; }
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
@keyframes scaleIn {
|
|||
|
|
from {
|
|||
|
|
transform: scale(0.95);
|
|||
|
|
opacity: 0;
|
|||
|
|
}
|
|||
|
|
to {
|
|||
|
|
transform: scale(1);
|
|||
|
|
opacity: 1;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/* Apply animations */
|
|||
|
|
.message {
|
|||
|
|
animation: slideInUp 0.3s ease-out;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.modal {
|
|||
|
|
animation: scaleIn 0.2s ease-out;
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### Loading States
|
|||
|
|
|
|||
|
|
```css
|
|||
|
|
/* Spinner */
|
|||
|
|
.spinner {
|
|||
|
|
width: 40px;
|
|||
|
|
height: 40px;
|
|||
|
|
border: 3px solid #e2e8f0;
|
|||
|
|
border-top-color: var(--primary-color);
|
|||
|
|
border-radius: 50%;
|
|||
|
|
animation: spin 0.8s linear infinite;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
@keyframes spin {
|
|||
|
|
to { transform: rotate(360deg); }
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/* Skeleton loader */
|
|||
|
|
.skeleton {
|
|||
|
|
background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
|
|||
|
|
background-size: 200% 100%;
|
|||
|
|
animation: loading 1.5s infinite;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
@keyframes loading {
|
|||
|
|
0% { background-position: 200% 0; }
|
|||
|
|
100% { background-position: -200% 0; }
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## Responsive Design Patterns
|
|||
|
|
|
|||
|
|
### Mobile-First Approach
|
|||
|
|
|
|||
|
|
```css
|
|||
|
|
/* Base mobile styles */
|
|||
|
|
.container {
|
|||
|
|
padding: 16px;
|
|||
|
|
width: 100%;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/* Tablet and up */
|
|||
|
|
@media (min-width: 768px) {
|
|||
|
|
.container {
|
|||
|
|
padding: 24px;
|
|||
|
|
max-width: 768px;
|
|||
|
|
margin: 0 auto;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/* Desktop */
|
|||
|
|
@media (min-width: 1024px) {
|
|||
|
|
.container {
|
|||
|
|
padding: 32px;
|
|||
|
|
max-width: 1024px;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/* Wide screens */
|
|||
|
|
@media (min-width: 1440px) {
|
|||
|
|
.container {
|
|||
|
|
max-width: 1280px;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### Touch-Friendly Styles
|
|||
|
|
|
|||
|
|
```css
|
|||
|
|
/* Increase touch targets on mobile */
|
|||
|
|
@media (pointer: coarse) {
|
|||
|
|
button, a, input, select {
|
|||
|
|
min-height: 44px;
|
|||
|
|
min-width: 44px;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.btn-primary, .btn-secondary {
|
|||
|
|
padding: 12px 24px;
|
|||
|
|
font-size: 16px;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/* Disable hover effects on touch devices */
|
|||
|
|
@media (hover: none) {
|
|||
|
|
.btn-primary:hover {
|
|||
|
|
filter: none;
|
|||
|
|
box-shadow: none;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## Theme Variants
|
|||
|
|
|
|||
|
|
### Dark Mode
|
|||
|
|
|
|||
|
|
```css
|
|||
|
|
@media (prefers-color-scheme: dark) {
|
|||
|
|
:root {
|
|||
|
|
--primary-color: #60a5fa;
|
|||
|
|
--secondary-color: #94a3b8;
|
|||
|
|
--background-color: #0f172a;
|
|||
|
|
--text-color: #f1f5f9;
|
|||
|
|
--border-color: #334155;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.message-bot {
|
|||
|
|
background: #1e293b;
|
|||
|
|
color: #f1f5f9;
|
|||
|
|
border-color: #334155;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
.input-wrapper {
|
|||
|
|
background: #1e293b;
|
|||
|
|
border-color: #334155;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### High Contrast
|
|||
|
|
|
|||
|
|
```css
|
|||
|
|
@media (prefers-contrast: high) {
|
|||
|
|
:root {
|
|||
|
|
--primary-color: #0066cc;
|
|||
|
|
--text-color: #000000;
|
|||
|
|
--background-color: #ffffff;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
* {
|
|||
|
|
border-width: 2px !important;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
button:focus, input:focus {
|
|||
|
|
outline: 3px solid #000000 !important;
|
|||
|
|
outline-offset: 2px !important;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## Performance Tips
|
|||
|
|
|
|||
|
|
1. **Use CSS Variables**: Change themes by updating variables, not entire stylesheets
|
|||
|
|
2. **Minimize Specificity**: Keep selectors simple for faster parsing
|
|||
|
|
3. **Avoid Deep Nesting**: Maximum 3 levels deep
|
|||
|
|
4. **Use Transform/Opacity**: For animations instead of layout properties
|
|||
|
|
5. **Lazy Load Non-Critical CSS**: Load theme variations on demand
|
|||
|
|
|
|||
|
|
## Browser Compatibility
|
|||
|
|
|
|||
|
|
```css
|
|||
|
|
/* Provide fallbacks for older browsers */
|
|||
|
|
.gradient-bg {
|
|||
|
|
background: #3b82f6; /* Fallback */
|
|||
|
|
background: linear-gradient(135deg, #3b82f6 0%, #8b5cf6 100%);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/* Use @supports for progressive enhancement */
|
|||
|
|
@supports (backdrop-filter: blur(10px)) {
|
|||
|
|
.modal-backdrop {
|
|||
|
|
backdrop-filter: blur(10px);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## See Also
|
|||
|
|
|
|||
|
|
- [Theme Structure](./structure.md) - File organization
|
|||
|
|
- [Chapter 4: User Interface](../chapter-04-gbui/README.md) - Applying themes to templates
|
|||
|
|
- [Chapter 6: BASIC](../chapter-06-gbdialog/README.md) - Dynamic theme switching
|
|||
|
|
|
|||
|
|
## Next Step
|
|||
|
|
|
|||
|
|
Return to [Chapter 5 Overview](./README.md) or continue to [Chapter 6: BASIC Dialogs](../chapter-06-gbdialog/README.md).
|