feat(web): UI component layer + dark-theme reskin
The TRM tokens were good but every screen hand-rolled inputs and buttons as bare outlines on near-black panels, so fields, cards and buttons were visually indistinguishable. Add a component layer (.input/.select/.textarea as recessed slots, .btn family with a FILLED primary, .card scaffolding) and adopt it across the booth/shift/login/tariff/site screens — several of which were still light-theme inline styles dropped on a dark background. Claude-Session: https://claude.ai/code/session_01Xcm6ikLgGoCxxHrxtjkk5V
This commit is contained in:
@@ -194,3 +194,164 @@ body {
|
||||
outline: 1px solid var(--color-term-amber);
|
||||
outline-offset: 1px;
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
COMPONENT LAYER
|
||||
The TRM tokens are good, but every screen hand-rolled inputs and
|
||||
buttons as bare outlines on near-black panels, so a field, a card,
|
||||
and a button were visually indistinguishable. These classes give
|
||||
each control a real identity:
|
||||
- inputs read as RECESSED slots (lighter fill + inset shadow)
|
||||
- the primary button is FILLED (accent body, dark text) — the
|
||||
one obvious action; neutrals are filled grey, not bare outlines
|
||||
- explicit hover / active / focus / disabled states everywhere
|
||||
Use @apply so the classes compose with Tailwind utilities.
|
||||
============================================================ */
|
||||
@layer components {
|
||||
/* ---- Form fields: a recessed slot, clearly an input ---- */
|
||||
.input,
|
||||
.select,
|
||||
.textarea {
|
||||
@apply w-full rounded-term border bg-term-bg px-2.5 text-term-text
|
||||
placeholder:text-term-muted;
|
||||
border-color: #3a414c; /* lighter than panel borders */
|
||||
height: var(--control-h-md);
|
||||
box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.45);
|
||||
transition: border-color 120ms var(--ease-snap), box-shadow 120ms var(--ease-snap);
|
||||
}
|
||||
.textarea {
|
||||
height: auto;
|
||||
@apply py-2 leading-snug;
|
||||
}
|
||||
.input::placeholder,
|
||||
.textarea::placeholder {
|
||||
@apply text-term-muted;
|
||||
}
|
||||
.input:hover,
|
||||
.select:hover,
|
||||
.textarea:hover {
|
||||
border-color: #4a525f;
|
||||
}
|
||||
.input:focus,
|
||||
.select:focus,
|
||||
.textarea:focus {
|
||||
outline: none;
|
||||
border-color: var(--color-term-amber);
|
||||
box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.45), 0 0 0 1px var(--color-term-amber);
|
||||
}
|
||||
.input:disabled,
|
||||
.select:disabled,
|
||||
.textarea:disabled {
|
||||
@apply cursor-not-allowed opacity-50;
|
||||
}
|
||||
/* Small / dense variant for inline table cells */
|
||||
.input-sm {
|
||||
height: var(--control-h-sm);
|
||||
@apply px-2 text-[12px];
|
||||
}
|
||||
|
||||
.field {
|
||||
@apply flex flex-col gap-1;
|
||||
}
|
||||
.label {
|
||||
@apply text-[11px] uppercase tracking-wider text-term-muted;
|
||||
}
|
||||
.hint {
|
||||
@apply text-[11px] leading-snug text-term-muted;
|
||||
}
|
||||
|
||||
/* ---- Buttons: a button must look pressable, never like a field ---- */
|
||||
.btn {
|
||||
@apply inline-flex items-center justify-center gap-1.5 rounded-term border
|
||||
px-3 text-[12px] font-semibold uppercase tracking-wider
|
||||
transition-colors select-none;
|
||||
height: var(--control-h-md);
|
||||
/* Neutral default: a filled grey body, not a bare outline. */
|
||||
background: var(--color-term-panel-2);
|
||||
border-color: #3a414c;
|
||||
color: var(--color-term-text);
|
||||
}
|
||||
.btn:hover:not(:disabled) {
|
||||
background: #2a313b;
|
||||
border-color: #4a525f;
|
||||
}
|
||||
.btn:active:not(:disabled) {
|
||||
transform: translateY(1px);
|
||||
}
|
||||
.btn:disabled {
|
||||
@apply cursor-not-allowed opacity-40;
|
||||
}
|
||||
.btn-sm {
|
||||
height: var(--control-h-sm);
|
||||
@apply px-2.5 text-[11px];
|
||||
}
|
||||
.btn-lg {
|
||||
height: var(--control-h-lg);
|
||||
@apply px-5 text-[13px];
|
||||
}
|
||||
|
||||
/* Primary: FILLED amber, dark text — the unmistakable main action. */
|
||||
.btn-primary {
|
||||
background: var(--color-term-amber);
|
||||
border-color: var(--color-term-amber);
|
||||
color: #0b0d10;
|
||||
}
|
||||
.btn-primary:hover:not(:disabled) {
|
||||
background: #ffb733;
|
||||
border-color: #ffb733;
|
||||
}
|
||||
|
||||
/* Semantic filled variants (entry / payment / destructive). */
|
||||
.btn-go {
|
||||
background: var(--color-term-green);
|
||||
border-color: var(--color-term-green);
|
||||
color: #f2f2ee;
|
||||
}
|
||||
.btn-go:hover:not(:disabled) {
|
||||
background: #38a85a;
|
||||
border-color: #38a85a;
|
||||
}
|
||||
.btn-pay {
|
||||
background: var(--color-term-cyan);
|
||||
border-color: var(--color-term-cyan);
|
||||
color: #f2f2ee;
|
||||
}
|
||||
.btn-pay:hover:not(:disabled) {
|
||||
background: #2f74e0;
|
||||
border-color: #2f74e0;
|
||||
}
|
||||
.btn-danger {
|
||||
background: transparent;
|
||||
border-color: var(--color-term-red);
|
||||
color: var(--color-term-red);
|
||||
}
|
||||
.btn-danger:hover:not(:disabled) {
|
||||
background: color-mix(in srgb, var(--color-term-red) 14%, transparent);
|
||||
}
|
||||
|
||||
/* Ghost: lowest-emphasis (cancel, secondary nav) — text + hover only. */
|
||||
.btn-ghost {
|
||||
background: transparent;
|
||||
border-color: transparent;
|
||||
color: var(--color-term-muted);
|
||||
}
|
||||
.btn-ghost:hover:not(:disabled) {
|
||||
background: var(--color-term-panel-2);
|
||||
color: var(--color-term-text);
|
||||
border-color: transparent;
|
||||
}
|
||||
|
||||
/* ---- Card: a panel that is clearly a container, not a field ---- */
|
||||
.card {
|
||||
@apply rounded-term border border-term-border bg-term-panel;
|
||||
}
|
||||
.card-head {
|
||||
@apply flex items-center justify-between border-b border-term-border
|
||||
bg-term-panel-2 px-4 py-2 text-[12px] uppercase tracking-wider text-term-muted;
|
||||
}
|
||||
.card-body {
|
||||
@apply p-4;
|
||||
}
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
|
||||
Reference in New Issue
Block a user