Solarpunk Design System
module.exports = {
theme: {
extend: {
colors: {
// Solarpunk Primary - Greens
'fern': {
DEFAULT: '#4A7C59',
dark: '#3D6A4A',
light: '#5A8C69',
},
'moss': '#5C7F5C',
'leaf': '#7CB342',
'sage': {
DEFAULT: '#9CAF88',
light: '#B8C9A8',
},
// Solarpunk Secondary - Earth & Gold
'amber': {
DEFAULT: '#E6A832',
dark: '#D49A28',
light: '#F0BC52',
},
'honey': '#DAA520',
'terracotta': '#C17F59',
// Solarpunk Neutrals
'cream': '#FAF7F0',
'oat': '#F5F0E6',
'parchment': '#EDE8DB',
'stone': {
DEFAULT: '#A39E93',
dark: '#7D796F',
},
// Status (override defaults)
'status-ok': '#7CB342',
'status-warning': '#E6A832',
'status-critical': '#E07A5F',
},
fontFamily: {
'display': ['Cormorant Garamond', 'Georgia', 'serif'],
'body': ['Nunito', 'system-ui', 'sans-serif'],
},
borderRadius: {
'sp-sm': '8px',
'sp-md': '12px',
'sp-lg': '16px',
'sp-xl': '20px',
},
},
},
}
/* ========================================
FÜLLHORN SOLARPUNK THEME
NiceGUI / Quasar Override
======================================== */
@import url('https://fonts.googleapis.com/css2?family=Cormorant+Garamond:wght@500;600;700&family=Nunito:wght@400;500;600;700&display=swap');
:root {
/* Primary - Greens */
--fern: #4A7C59;
--fern-dark: #3D6A4A;
--moss: #5C7F5C;
--leaf: #7CB342;
--sage: #9CAF88;
--sage-light: #B8C9A8;
/* Secondary - Earth & Gold */
--amber: #E6A832;
--honey: #DAA520;
--terracotta: #C17F59;
/* Neutrals */
--cream: #FAF7F0;
--oat: #F5F0E6;
--parchment: #EDE8DB;
--stone: #A39E93;
--charcoal: #3D3D3D;
/* Status */
--status-ok: #7CB342;
--status-warning: #E6A832;
--status-critical: #E07A5F;
/* Quasar Primary Override */
--q-primary: #4A7C59;
--q-secondary: #E6A832;
--q-positive: #7CB342;
--q-negative: #E07A5F;
--q-warning: #E6A832;
}
/* Base */
body {
font-family: 'Nunito', system-ui, sans-serif;
background: var(--cream) !important;
}
/* Typography */
.text-h4, .text-h5, .text-h6 {
font-family: 'Cormorant Garamond', Georgia, serif !important;
color: var(--fern) !important;
}
/* Cards */
.q-card {
background: white !important;
border-radius: 12px !important;
box-shadow: 0 2px 8px rgba(74, 124, 89, 0.1) !important;
}
/* Buttons */
.q-btn--standard.bg-primary {
background: var(--fern) !important;
}
.q-btn--standard.bg-primary:hover {
background: var(--fern-dark) !important;
}
.q-btn--outline.text-primary {
color: var(--fern) !important;
border-color: var(--fern) !important;
}
.q-btn--standard.bg-secondary,
.q-btn--standard.bg-positive {
background: var(--amber) !important;
}
.q-btn--standard.bg-negative {
background: var(--status-critical) !important;
}
/* Inputs */
.q-field--outlined .q-field__control {
border-radius: 12px !important;
}
.q-field--outlined .q-field__control:before {
border-color: var(--sage-light) !important;
}
.q-field--outlined.q-field--focused .q-field__control:after {
border-color: var(--fern) !important;
}
/* Bottom Navigation */
.q-footer, .bottom-nav {
background: white !important;
border-top: 1px solid var(--sage-light) !important;
}
.q-tab--active {
color: var(--fern) !important;
}
/* Item Cards - Status Border */
.item-card {
border-left: 4px solid var(--status-ok);
border-radius: 12px;
background: white;
}
.item-card.status-warning {
border-left-color: var(--status-warning);
}
.item-card.status-critical {
border-left-color: var(--status-critical);
}
/* Chips Base */
.solarpunk-chip {
min-height: 44px;
padding: 10px 20px;
border-radius: 9999px;
font-weight: 500;
cursor: pointer;
transition: all 0.2s ease;
}
/* Unit Chips */
.chip-unit {
background: var(--oat);
color: var(--charcoal);
border: 2px solid var(--sage-light);
}
.chip-unit:hover {
border-color: var(--sage);
}
.chip-unit.active {
background: var(--fern);
color: white;
border-color: var(--fern);
}
/* Ring-Dot for Category/Type Chips */
.ring-dot {
width: 18px;
height: 18px;
border-radius: 50%;
border: 2px solid currentColor;
background: white;
flex-shrink: 0;
}
.ring-dot.filled {
background: radial-gradient(circle, currentColor 35%, white 35%);
}
.chip-category.active .ring-dot,
.chip-type.active .ring-dot {
border-color: white;
background: radial-gradient(circle, white 35%, transparent 35%);
}
| Bisherig (Tailwind) | Solarpunk | Hex |
|---|---|---|
primary / blue-* |
fern |
#4A7C59 |
secondary |
amber |
#E6A832 |
green-500 (OK) |
leaf / status-ok |
#7CB342 |
orange-500 / yellow-500 |
status-warning |
#E6A832 |
red-500 |
status-critical |
#E07A5F |
gray-100 / gray-200 |
cream / oat |
#FAF7F0 |
gray-500 / gray-600 |
stone |
#A39E93 |
border-gray-200 |
sage-light |
#B8C9A8 |
# Primary Button
ui.button('Speichern', icon='save').classes(
'bg-fern text-white rounded-sp-md'
)
# Secondary Button
ui.button('Zurück', icon='arrow_back').props(
'flat no-caps'
).classes('text-fern')
# Accent/CTA Button
ui.button('Weiter', icon='arrow_forward').classes(
'bg-amber text-white rounded-sp-md'
)
# Danger Button
ui.button('Löschen', icon='delete').classes(
'bg-status-critical text-white'
)
# Unit Chip (simplified)
def unit_chip(label: str, selected: bool = False):
classes = 'chip-unit active' if selected else 'chip-unit'
return ui.element('span').classes(classes).text(label)
# Category Chip (with ring-dot)
def category_chip(name: str, color: str, selected: bool = False):
with ui.element('span').classes(
f'solarpunk-chip chip-category {"active" if selected else ""}'
).style(f'--chip-color: {color}'):
ui.element('span').classes(
f'ring-dot {"filled" if selected else ""}'
).style(f'border-color: {color}')
ui.label(name)
def item_card(item: Item):
# Status based on days until expiry
status = 'status-critical' if item.days_left < 3 else \
'status-warning' if item.days_left < 7 else ''
with ui.card().classes(f'item-card {status} w-full mb-2'):
with ui.row().classes('items-start gap-3'):
# Status dot
ui.element('span').classes(
'w-3 h-3 rounded-full mt-1'
).style(f'background: var(--{status or "status-ok"})')
# Content
with ui.column().classes('flex-1 gap-1'):
ui.label(item.name).classes('font-semibold')
ui.label(f'{item.quantity} {item.unit}').classes(
'text-sm text-stone'
)
with ui.row().classes('items-center gap-1'):
ui.icon('place', size='16px').classes('text-terracotta')
ui.label(item.location).classes('text-xs text-stone')
# Expiry
with ui.column().classes('text-right'):
ui.label('Ablauf:').classes('text-xs text-stone')
ui.label(item.expiry_text).classes(
f'font-semibold text-{status or "status-ok"}'
)