kiwi/frontend/src/theme.css

846 lines
19 KiB
CSS

/**
* Central Theme System for Kiwi
*
* This file contains all reusable, theme-aware, responsive CSS classes.
* Components should use these classes instead of custom styles where possible.
*/
/* ============================================
LAYOUT UTILITIES - RESPONSIVE GRIDS
============================================ */
.grid-responsive {
display: grid;
gap: var(--spacing-md);
}
.grid-auto {
display: grid;
gap: var(--spacing-md);
grid-template-columns: 1fr;
}
/* Stats grid — horizontal strip of compact stats */
.grid-stats {
display: grid;
gap: var(--spacing-md);
grid-template-columns: 1fr;
}
.grid-stats-strip {
display: flex;
gap: 0;
overflow: hidden;
border-radius: var(--radius-lg);
background: var(--color-bg-secondary);
border: 1px solid var(--color-border);
}
.grid-stats-strip .stat-strip-item {
flex: 1;
text-align: center;
padding: var(--spacing-sm) var(--spacing-xs);
border-right: 1px solid var(--color-border);
}
.grid-stats-strip .stat-strip-item:last-child {
border-right: none;
}
/* Force specific column counts */
.grid-1 { grid-template-columns: 1fr; }
.grid-2 { grid-template-columns: repeat(2, 1fr); }
.grid-3 { grid-template-columns: repeat(3, 1fr); }
.grid-4 { grid-template-columns: repeat(4, 1fr); }
/* ============================================
FLEXBOX UTILITIES
============================================ */
.flex { display: flex; }
.flex-col { display: flex; flex-direction: column; }
.flex-wrap { flex-wrap: wrap; }
.flex-center {
display: flex;
align-items: center;
justify-content: center;
}
.flex-between {
display: flex;
justify-content: space-between;
align-items: center;
}
.flex-start {
display: flex;
justify-content: flex-start;
align-items: center;
}
.flex-end {
display: flex;
justify-content: flex-end;
align-items: center;
}
.flex-responsive {
display: flex;
gap: var(--spacing-md);
flex-wrap: wrap;
}
/* ============================================
SPACING UTILITIES
============================================ */
.gap-xs { gap: var(--spacing-xs); }
.gap-sm { gap: var(--spacing-sm); }
.gap-md { gap: var(--spacing-md); }
.gap-lg { gap: var(--spacing-lg); }
.gap-xl { gap: var(--spacing-xl); }
.p-0 { padding: 0; }
.p-xs { padding: var(--spacing-xs); }
.p-sm { padding: var(--spacing-sm); }
.p-md { padding: var(--spacing-md); }
.p-lg { padding: var(--spacing-lg); }
.p-xl { padding: var(--spacing-xl); }
.m-0 { margin: 0; }
.m-xs { margin: var(--spacing-xs); }
.m-sm { margin: var(--spacing-sm); }
.m-md { margin: var(--spacing-md); }
.m-lg { margin: var(--spacing-lg); }
.m-xl { margin: var(--spacing-xl); }
.mt-xs { margin-top: var(--spacing-xs); }
.mt-sm { margin-top: var(--spacing-sm); }
.mt-md { margin-top: var(--spacing-md); }
.mb-xs { margin-bottom: var(--spacing-xs); }
.mb-sm { margin-bottom: var(--spacing-sm); }
.mb-md { margin-bottom: var(--spacing-md); }
.mb-lg { margin-bottom: var(--spacing-lg); }
.ml-xs { margin-left: var(--spacing-xs); }
.ml-md { margin-left: var(--spacing-md); }
.mr-md { margin-right: var(--spacing-md); }
.pt-md { padding-top: var(--spacing-md); }
.pb-md { padding-bottom: var(--spacing-md); }
.pl-md { padding-left: var(--spacing-md); }
.pr-md { padding-right: var(--spacing-md); }
/* ============================================
CARD COMPONENTS - THEME AWARE
============================================ */
.card {
background: var(--color-bg-card);
border-radius: var(--radius-xl);
padding: var(--spacing-lg);
box-shadow: var(--shadow-md);
border: 1px solid var(--color-border);
transition: box-shadow 0.2s ease;
}
.card:hover {
box-shadow: var(--shadow-lg);
}
.card-sm {
background: var(--color-bg-card);
border-radius: var(--radius-lg);
padding: var(--spacing-md);
box-shadow: var(--shadow-sm);
border: 1px solid var(--color-border);
}
.card-secondary {
background: var(--color-bg-secondary);
border-radius: var(--radius-lg);
padding: var(--spacing-md);
box-shadow: var(--shadow-sm);
border: 1px solid var(--color-border);
}
/* Status border variants */
.card-success { border-left: 3px solid var(--color-success); }
.card-warning { border-left: 3px solid var(--color-warning); }
.card-error { border-left: 3px solid var(--color-error); }
.card-info { border-left: 3px solid var(--color-info); }
/* ============================================
BUTTON COMPONENTS - THEME AWARE
============================================ */
.btn {
padding: var(--spacing-sm) var(--spacing-md);
border: 1px solid transparent;
border-radius: var(--radius-md);
font-size: var(--font-size-sm);
font-weight: 600;
font-family: var(--font-body);
cursor: pointer;
transition: all 0.18s ease;
white-space: nowrap;
display: inline-flex;
align-items: center;
justify-content: center;
gap: var(--spacing-xs);
}
.btn:hover {
transform: translateY(-1px);
}
.btn:active {
transform: translateY(0);
}
.btn:disabled {
opacity: 0.45;
cursor: not-allowed;
transform: none;
}
/* Button variants */
.btn-primary {
background: var(--gradient-primary);
color: #1e1c1a;
border: none;
font-weight: 700;
box-shadow: var(--shadow-amber);
}
.btn-primary:hover:not(:disabled) {
box-shadow: 0 6px 20px rgba(232, 168, 32, 0.35);
}
.btn-success {
background: var(--color-success);
color: white;
}
.btn-success:hover:not(:disabled) {
background: var(--color-success-dark);
}
.btn-error {
background: var(--color-error);
color: white;
}
.btn-error:hover:not(:disabled) {
background: var(--color-error-dark);
}
.btn-info {
background: var(--color-info);
color: white;
}
.btn-info:hover:not(:disabled) {
background: var(--color-info-dark);
}
.btn-secondary {
background: var(--color-bg-elevated);
color: var(--color-text-secondary);
border: 1px solid var(--color-border);
}
.btn-secondary:hover:not(:disabled) {
background: var(--color-bg-primary);
border-color: var(--color-primary);
color: var(--color-primary);
}
.btn-secondary.active {
background: var(--color-primary);
color: #1e1c1a;
border-color: var(--color-primary);
font-weight: 700;
}
/* Pill chip button — for filter chips */
.btn-chip {
padding: var(--spacing-xs) var(--spacing-sm);
border: 1px solid var(--color-border);
border-radius: var(--radius-pill);
font-size: var(--font-size-xs);
font-weight: 500;
font-family: var(--font-body);
background: var(--color-bg-elevated);
color: var(--color-text-secondary);
cursor: pointer;
transition: all 0.18s ease;
white-space: nowrap;
}
.btn-chip:hover {
border-color: var(--color-primary);
color: var(--color-primary);
}
.btn-chip.active {
background: var(--color-primary);
color: #1e1c1a;
border-color: var(--color-primary);
font-weight: 700;
}
/* Button sizes */
.btn-sm {
padding: var(--spacing-xs) var(--spacing-sm);
font-size: var(--font-size-xs);
}
.btn-lg {
padding: var(--spacing-md) var(--spacing-xl);
font-size: var(--font-size-base);
}
/* Icon-only action button */
.btn-icon {
width: 32px;
height: 32px;
padding: 0;
border: none;
border-radius: var(--radius-md);
background: transparent;
color: var(--color-text-muted);
cursor: pointer;
transition: all 0.18s ease;
display: inline-flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
}
.btn-icon:hover {
background: var(--color-bg-primary);
color: var(--color-text-primary);
transform: none;
}
.btn-icon.btn-icon-danger:hover {
color: var(--color-error);
}
.btn-icon.btn-icon-success:hover {
color: var(--color-success);
}
/* ============================================
FORM COMPONENTS - THEME AWARE
============================================ */
.form-group {
margin-bottom: var(--spacing-md);
}
.form-label {
display: block;
margin-bottom: var(--spacing-xs);
font-weight: 600;
color: var(--color-text-secondary);
font-size: var(--font-size-xs);
text-transform: uppercase;
letter-spacing: 0.06em;
font-family: var(--font-body);
}
.form-input,
.form-select,
.form-textarea {
width: 100%;
padding: var(--spacing-sm) var(--spacing-md);
border: 1px solid var(--color-border);
border-radius: var(--radius-md);
background: var(--color-bg-input);
color: var(--color-text-primary);
font-size: var(--font-size-sm);
font-family: var(--font-body);
transition: border-color 0.18s ease, box-shadow 0.18s ease;
box-sizing: border-box;
}
.form-input:focus,
.form-select:focus,
.form-textarea:focus {
outline: none;
border-color: var(--color-primary);
box-shadow: 0 0 0 3px var(--color-warning-bg);
}
.form-textarea {
resize: vertical;
min-height: 80px;
font-family: var(--font-body);
}
.form-row {
display: grid;
gap: var(--spacing-md);
grid-template-columns: 1fr;
}
/* Chip row filter bar — horizontal scroll */
.filter-chip-row {
display: flex;
gap: var(--spacing-xs);
overflow-x: auto;
padding-bottom: var(--spacing-xs);
scrollbar-width: none;
min-width: 0; /* allow flex item to shrink below content; lets overflow-x scroll internally */
width: 100%;
}
.filter-chip-row::-webkit-scrollbar {
display: none;
}
/* ============================================
TEXT UTILITIES
============================================ */
.text-xs { font-size: var(--font-size-xs); }
.text-sm { font-size: var(--font-size-sm); }
.text-base { font-size: var(--font-size-base); }
.text-lg { font-size: var(--font-size-lg); }
.text-xl { font-size: var(--font-size-xl); }
.text-2xl { font-size: var(--font-size-2xl); }
/* Display font */
.text-display {
font-family: var(--font-display);
font-style: italic;
}
/* Mono font */
.text-mono {
font-family: var(--font-mono);
}
.text-primary { color: var(--color-text-primary); }
.text-secondary { color: var(--color-text-secondary); }
.text-muted { color: var(--color-text-muted); }
.text-success { color: var(--color-success); }
.text-warning { color: var(--color-warning); }
.text-error { color: var(--color-error); }
.text-info { color: var(--color-info); }
.text-amber { color: var(--color-primary); }
.text-center { text-align: center; }
.text-left { text-align: left; }
.text-right { text-align: right; }
.font-bold { font-weight: 700; }
.font-semibold { font-weight: 600; }
.font-normal { font-weight: 400; }
/* ============================================
LOCATION DOT INDICATORS
============================================ */
.loc-dot {
width: 10px;
height: 10px;
border-radius: 50%;
flex-shrink: 0;
display: inline-block;
}
.loc-dot-fridge { background: var(--color-loc-fridge); }
.loc-dot-freezer { background: var(--color-loc-freezer); }
.loc-dot-garage_freezer { background: var(--color-loc-garage-freezer); }
.loc-dot-pantry { background: var(--color-loc-pantry); }
.loc-dot-cabinet { background: var(--color-loc-cabinet); }
/* Location left-border strip on inventory rows */
.inv-row-fridge { border-left-color: var(--color-loc-fridge) !important; }
.inv-row-freezer { border-left-color: var(--color-loc-freezer) !important; }
.inv-row-garage_freezer { border-left-color: var(--color-loc-garage-freezer) !important; }
.inv-row-pantry { border-left-color: var(--color-loc-pantry) !important; }
.inv-row-cabinet { border-left-color: var(--color-loc-cabinet) !important; }
/* ============================================
RESPONSIVE UTILITIES
============================================ */
.mobile-only { display: none; }
.desktop-only { display: block; }
.w-full { width: 100%; }
.w-auto { width: auto; }
.h-full { height: 100%; }
.h-auto { height: auto; }
/* ============================================
MOBILE BREAKPOINTS (<=480px)
============================================ */
@media (max-width: 480px) {
.mobile-only { display: block; }
.desktop-only { display: none; }
.grid-2,
.grid-3,
.grid-4 {
grid-template-columns: 1fr !important;
}
.flex-responsive {
flex-direction: column;
}
.btn-mobile-full {
width: 100%;
min-width: 100%;
}
.card {
padding: var(--spacing-md);
border-radius: var(--radius-lg);
}
.card-sm {
padding: var(--spacing-sm);
}
.btn {
white-space: normal;
text-align: center;
}
}
/* ============================================
TABLET BREAKPOINTS (481px - 768px)
============================================ */
@media (min-width: 481px) and (max-width: 768px) {
.grid-3,
.grid-4 {
grid-template-columns: repeat(2, 1fr);
}
.grid-auto {
grid-template-columns: repeat(2, 1fr);
}
.grid-stats {
grid-template-columns: repeat(2, 1fr);
}
.form-row {
grid-template-columns: 1fr 1fr;
}
}
/* ============================================
DESKTOP BREAKPOINTS (769px - 1024px)
============================================ */
@media (min-width: 769px) and (max-width: 1024px) {
.grid-auto {
grid-template-columns: repeat(2, 1fr);
}
.grid-stats {
grid-template-columns: repeat(4, 1fr);
}
.grid-4 {
grid-template-columns: repeat(3, 1fr);
}
}
/* ============================================
LARGE DESKTOP (>=1025px)
============================================ */
@media (min-width: 1025px) {
.grid-auto {
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
}
.grid-stats {
grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
}
.form-row {
grid-template-columns: 1fr 1fr;
}
}
/* ============================================
STATUS & STATE UTILITIES
============================================ */
.status-badge {
display: inline-flex;
align-items: center;
padding: 3px var(--spacing-sm);
border-radius: var(--radius-pill);
font-size: var(--font-size-xs);
font-weight: 600;
font-family: var(--font-mono);
letter-spacing: 0.02em;
}
.status-success {
background: var(--color-success-bg);
color: var(--color-success-light);
border: 1px solid var(--color-success-border);
}
.status-warning {
background: var(--color-warning-bg);
color: var(--color-warning-light);
border: 1px solid var(--color-warning-border);
}
.status-error {
background: var(--color-error-bg);
color: var(--color-error-light);
border: 1px solid var(--color-error-border);
}
.status-info {
background: var(--color-info-bg);
color: var(--color-info-light);
border: 1px solid var(--color-info-border);
}
/* ============================================
ANIMATION UTILITIES
============================================ */
.fade-in {
animation: fadeIn 0.3s ease-in;
}
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
.slide-up {
animation: slideUp 0.3s ease-out;
}
@keyframes slideUp {
from {
opacity: 0;
transform: translateY(16px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
/* Urgency pulse — for items expiring very soon */
@keyframes urgencyPulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.6; }
}
.pulse-urgent {
animation: urgencyPulse 1.8s ease-in-out infinite;
}
/* ============================================
LOADING UTILITIES
============================================ */
.spinner {
border: 2px solid var(--color-border);
border-top: 2px solid var(--color-primary);
border-radius: 50%;
width: 36px;
height: 36px;
animation: spin 0.9s linear infinite;
margin: 0 auto;
}
.spinner-sm {
width: 18px;
height: 18px;
border-width: 2px;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
/* ============================================
DIVIDER
============================================ */
.divider {
height: 1px;
background: var(--color-border);
margin: var(--spacing-lg) 0;
}
.divider-md {
margin: var(--spacing-md) 0;
}
/* ============================================
SECTION HEADERS (display font)
============================================ */
.section-title {
font-family: var(--font-display);
font-style: italic;
font-weight: 600;
color: var(--color-text-primary);
margin: 0;
}
/* ============================================
EASTER EGG — GRID KITCHEN NEON MODE
Activated via Konami code
============================================ */
body.neon-mode .card,
body.neon-mode .card-sm,
body.neon-mode .card-secondary {
box-shadow:
0 0 0 1px rgba(255, 0, 110, 0.35),
0 0 12px rgba(255, 0, 110, 0.18),
0 2px 20px rgba(131, 56, 236, 0.15);
}
body.neon-mode .btn-primary {
box-shadow: 0 0 18px rgba(255, 0, 110, 0.55), 0 0 36px rgba(131, 56, 236, 0.25);
color: #fff;
}
body.neon-mode .wordmark-kiwi {
text-shadow: 0 0 10px rgba(255, 0, 110, 0.7), 0 0 24px rgba(131, 56, 236, 0.5);
}
body.neon-mode .sidebar,
body.neon-mode .bottom-nav {
border-color: rgba(255, 0, 110, 0.3);
box-shadow: 4px 0 20px rgba(255, 0, 110, 0.12);
}
body.neon-mode .sidebar-item.active,
body.neon-mode .nav-item.active {
text-shadow: 0 0 8px currentColor;
}
/* Scanline overlay */
body.neon-mode::after {
content: '';
position: fixed;
inset: 0;
background: repeating-linear-gradient(
0deg,
transparent,
transparent 3px,
rgba(0, 0, 0, 0.08) 3px,
rgba(0, 0, 0, 0.08) 4px
);
pointer-events: none;
z-index: 9998;
animation: scanlineScroll 8s linear infinite;
}
@keyframes scanlineScroll {
0% { background-position: 0 0; }
100% { background-position: 0 80px; }
}
/* CRT flicker on wordmark */
body.neon-mode .wordmark-kiwi {
animation: crtFlicker 6s ease-in-out infinite;
}
@keyframes crtFlicker {
0%, 94%, 100% { opacity: 1; }
95% { opacity: 0.88; }
97% { opacity: 0.95; }
98% { opacity: 0.82; }
}
/* ============================================
EASTER EGG — KIWI BIRD SPRITE
============================================ */
.kiwi-bird-stage {
position: fixed;
bottom: 72px; /* above bottom nav */
left: 0;
right: 0;
height: 72px;
pointer-events: none;
z-index: 9999;
overflow: hidden;
}
@media (min-width: 769px) {
.kiwi-bird-stage {
bottom: 0;
left: 200px; /* clear the sidebar */
}
}
.kiwi-bird {
position: absolute;
bottom: 8px;
width: 64px;
height: 64px;
will-change: transform;
}
/* Enters from right, walks left */
.kiwi-bird.rtl {
animation: kiwiWalkRtl 5.5s ease-in-out forwards;
}
.kiwi-bird.rtl .kiwi-svg {
transform: scaleX(1); /* faces left */
}
/* Enters from left, walks right */
.kiwi-bird.ltr {
animation: kiwiWalkLtr 5.5s ease-in-out forwards;
}
.kiwi-bird.ltr .kiwi-svg {
transform: scaleX(-1); /* faces right */
}
/* Bob on each step */
.kiwi-svg {
display: block;
animation: kiwiBob 0.38s steps(1) infinite;
}
@keyframes kiwiWalkRtl {
0% { right: -80px; }
15% { right: 35%; } /* enter and slow */
40% { right: 35%; } /* pause — sniffing */
55% { right: 38%; } /* tiny shuffle */
60% { right: 35%; }
85% { right: 35%; }
100% { right: calc(100% + 80px); } /* exit left */
}
@keyframes kiwiWalkLtr {
0% { left: -80px; }
15% { left: 35%; }
40% { left: 35%; }
55% { left: 38%; }
60% { left: 35%; }
85% { left: 35%; }
100% { left: calc(100% + 80px); }
}
@keyframes kiwiBob {
0% { transform: translateY(0) scaleX(var(--bird-flip, 1)); }
50% { transform: translateY(-4px) scaleX(var(--bird-flip, 1)); }
}