Tower: upload laundry_management 19.0.19.0.4 (via marketplace)

This commit is contained in:
2026-05-01 15:00:19 +00:00
parent 4ad5be42ac
commit b4322a0037

View File

@@ -0,0 +1,994 @@
// ============================================================================
// Laundry Management — POS / Backend Design System
// 8px grid, soft shadows, premium pill components.
// ============================================================================
// ── Tokens ─────────────────────────────────────────────────────────────────
$lm-space-1: 4px;
$lm-space-2: 8px;
$lm-space-3: 12px;
$lm-space-4: 16px;
$lm-space-5: 24px;
$lm-radius-sm: 6px;
$lm-radius-md: 10px;
$lm-radius-lg: 14px;
$lm-shadow-sm: 0 1px 2px rgba(15, 23, 42, 0.06), 0 1px 1px rgba(15, 23, 42, 0.04);
$lm-shadow-md: 0 4px 12px rgba(15, 23, 42, 0.08), 0 2px 4px rgba(15, 23, 42, 0.05);
$lm-shadow-lg: 0 10px 30px rgba(15, 23, 42, 0.12), 0 4px 8px rgba(15, 23, 42, 0.06);
$lm-color-urgent: #EF4444;
$lm-color-delivery: #10B981;
$lm-color-vip: #8B5CF6;
$lm-color-normal: #6B7280;
$lm-color-type: #4F46E5;
$lm-font-xs: 11px;
$lm-font-sm: 13px;
$lm-font-md: 14px;
$lm-font-lg: 16px;
$lm-font-xl: 18px;
$lm-bg-card: #FFFFFF;
$lm-bg-soft: #F8FAFC;
$lm-border: #E2E8F0;
$lm-text: #0F172A;
$lm-text-muted: #64748B;
// ── Pill component (shared across panel / kanban / receipt) ───────────────
.laundry-pill {
display: inline-flex;
align-items: center;
padding: $lm-space-1 $lm-space-3;
border-radius: 9999px;
font-size: $lm-font-sm;
font-weight: 600;
line-height: 1.2;
color: #fff;
background-color: $lm-color-normal;
box-shadow: $lm-shadow-sm;
white-space: nowrap;
transition: transform 0.12s ease, box-shadow 0.12s ease;
&:hover { transform: translateY(-1px); box-shadow: $lm-shadow-md; }
.fa { font-size: 0.95em; }
&--type { background-color: $lm-color-type; }
&--attr { background-color: $lm-color-normal; }
&--urgent { background-color: $lm-color-urgent; }
&--delivery { background-color: $lm-color-delivery; }
&--vip { background-color: $lm-color-vip; }
&--deferred { background-color: #F59E0B; }
// Semantic data overrides — win over default --attr
&[data-priority="urgent"] { background-color: $lm-color-urgent; }
&[data-delivery="1"] { background-color: $lm-color-delivery; }
}
// ── Settle Due Banner (POS Mode indicator) ────────────────────────────────
.laundry-settle-banner {
margin: $lm-space-2 $lm-space-3 0 $lm-space-3;
padding: $lm-space-3 $lm-space-4;
background: linear-gradient(135deg, #F59E0B 0%, #F97316 100%);
color: #fff;
border-radius: $lm-radius-md;
box-shadow: $lm-shadow-md;
display: flex;
align-items: center;
justify-content: space-between;
gap: $lm-space-3;
animation: laundry-settle-banner-pulse 2.4s ease-in-out infinite;
&__lead {
display: flex;
align-items: center;
gap: $lm-space-3;
min-width: 0;
}
&__icon {
font-size: $lm-font-xl;
flex-shrink: 0;
}
&__text {
display: flex;
flex-direction: column;
min-width: 0;
}
&__title {
font-size: $lm-font-md;
font-weight: 800;
letter-spacing: 0.06em;
line-height: 1.15;
text-transform: uppercase;
}
&__meta {
display: flex;
align-items: baseline;
gap: $lm-space-3;
margin-top: 3px;
min-width: 0;
}
&__partner {
font-size: $lm-font-sm;
font-weight: 600;
opacity: 0.95;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
max-width: 40ch;
}
&__amount {
font-size: $lm-font-md;
font-weight: 800;
letter-spacing: 0.01em;
padding: 1px 8px;
background: rgba(255, 255, 255, 0.2);
border-radius: $lm-radius-sm;
white-space: nowrap;
}
&__hint {
font-size: $lm-font-xs;
opacity: 0.85;
margin-top: 2px;
font-style: italic;
}
&__exit {
background: rgba(255, 255, 255, 0.18);
color: #fff;
border: 1px solid rgba(255, 255, 255, 0.4);
padding: $lm-space-2 $lm-space-3;
border-radius: $lm-radius-sm;
font-size: $lm-font-sm;
font-weight: 600;
cursor: pointer;
flex-shrink: 0;
transition: background 0.15s ease, transform 0.12s ease;
&:hover { background: rgba(255, 255, 255, 0.3); transform: translateY(-1px); }
&:active { transform: translateY(0); }
&:focus-visible {
outline: 2px solid #fff;
outline-offset: 2px;
}
}
}
@keyframes laundry-settle-banner-pulse {
0%, 100% { box-shadow: 0 4px 12px rgba(245, 158, 11, 0.35); }
50% { box-shadow: 0 4px 20px rgba(245, 158, 11, 0.65); }
}
// ── Global lock of POS chrome while settle-due mode is active ─────────────
// The banner component toggles `pos-laundry-settle-active` on <body>.
// CSS is a clarity layer, not the safety layer — JS (pos_store_patch /
// order_tabs_patch / navbar_patch) is the real gate. But here we also
// disable pointer-events on the peripherals because SINGLE-ORDER MODE
// guarantees the only tab is the settle order itself; everything else
// is visual chrome that shouldn't respond to clicks.
body.pos-laundry-settle-active {
// Every non-active tab (should be zero under single-order mode —
// but defense in depth): hatched, dim, unclickable.
.floating-order-container .btn:not(.active) {
pointer-events: none;
opacity: 0.35;
filter: saturate(0.3);
position: relative;
&::after {
content: "";
position: absolute;
inset: 0;
border-radius: inherit;
background: repeating-linear-gradient(
135deg,
rgba(245, 158, 11, 0.0) 0 6px,
rgba(245, 158, 11, 0.15) 6px 12px
);
pointer-events: none;
}
}
// Active settle tab — keep visible & highlighted in warning orange.
.floating-order-container .btn.active {
outline: 2px solid #F59E0B;
outline-offset: 2px;
pointer-events: none; // the tab IS the current order; no switch needed
}
// "+" new-order button inside ListContainer — hard-disabled visually.
// JS addNewOrder patch also blocks it.
.list-container-add,
.o_list_container_add,
button[title*="Add a new order" i],
button[aria-label*="Add a new order" i] {
pointer-events: none;
opacity: 0.35;
filter: saturate(0.3);
}
}
// ── Order Context Panel (POS right-side card) ─────────────────────────────
.laundry-context-panel {
margin: $lm-space-2 $lm-space-3;
padding: $lm-space-3 $lm-space-4;
background: $lm-bg-card;
border: 1px solid $lm-border;
border-radius: $lm-radius-md;
box-shadow: $lm-shadow-sm;
display: flex;
flex-direction: column;
gap: $lm-space-2;
transition: box-shadow 0.18s ease, transform 0.18s ease;
&:hover { box-shadow: $lm-shadow-md; }
&__header {
display: flex;
align-items: center;
justify-content: space-between;
}
&__title {
font-size: $lm-font-xs;
font-weight: 700;
letter-spacing: 0.06em;
text-transform: uppercase;
color: $lm-text-muted;
}
&__edit {
background: transparent;
border: 0;
color: $lm-text-muted;
padding: $lm-space-1 $lm-space-2;
border-radius: $lm-radius-sm;
cursor: pointer;
transition: background 0.12s ease, color 0.12s ease;
&:hover { background: $lm-bg-soft; color: $lm-text; }
&:focus-visible {
outline: 2px solid rgba(99, 102, 241, 0.45);
outline-offset: 2px;
}
}
&__row {
display: flex;
flex-wrap: wrap;
gap: $lm-space-2;
}
&__attrs .laundry-pill { font-size: $lm-font-xs; padding: 2px $lm-space-2; }
&__empty {
font-size: $lm-font-sm;
color: $lm-text-muted;
background: $lm-bg-soft;
padding: $lm-space-2 $lm-space-3;
border-radius: $lm-radius-sm;
text-align: center;
}
&__cta {
display: flex;
align-items: center;
justify-content: center;
gap: $lm-space-2;
width: 100%;
padding: $lm-space-3 $lm-space-4;
background: linear-gradient(135deg, #4F46E5 0%, #6366F1 100%);
color: #fff;
font-size: $lm-font-md;
font-weight: 600;
border: 0;
border-radius: $lm-radius-md;
box-shadow: $lm-shadow-sm;
cursor: pointer;
transition: transform 0.12s ease, box-shadow 0.12s ease, filter 0.12s ease;
.fa { font-size: 1.05em; }
&:hover {
transform: translateY(-1px);
box-shadow: $lm-shadow-md;
filter: brightness(1.05);
}
&:active { transform: translateY(0); filter: brightness(0.95); }
&:focus-visible {
outline: 2px solid rgba(99, 102, 241, 0.55);
outline-offset: 2px;
}
}
&__edit-label {
margin-inline-start: $lm-space-1;
font-size: $lm-font-xs;
font-weight: 600;
}
&__delivery {
display: flex;
flex-direction: column;
gap: $lm-space-1;
padding-top: $lm-space-2;
border-top: 1px dashed $lm-border;
}
&__delivery-row {
display: flex;
align-items: center;
gap: $lm-space-2;
font-size: $lm-font-sm;
color: $lm-text;
.fa { color: $lm-color-delivery; width: 14px; text-align: center; }
}
&[data-empty="1"] { background: $lm-bg-soft; }
&[data-delivery="1"] { border-left: 3px solid $lm-color-delivery; }
}
// ── Popup polish (shared by all 3 laundry popups) ─────────────────────────
.modal .modal-dialog {
.btn.btn-outline-primary,
.btn.btn-primary {
border-radius: $lm-radius-md;
transition: transform 0.12s ease, box-shadow 0.12s ease,
background-color 0.12s ease, border-color 0.12s ease;
}
.btn.btn-primary {
box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.18);
}
.btn.btn-outline-primary:hover {
transform: translateY(-1px);
box-shadow: $lm-shadow-md;
}
.modal-footer .btn-primary,
.modal-footer .btn-secondary {
position: sticky;
bottom: 0;
}
}
// ── Receipt details (printer-friendly) ────────────────────────────────────
.laundry-receipt-details {
margin-top: $lm-space-2;
text-align: center;
&__sep {
border-top: 1px dashed #000;
margin: $lm-space-2 0;
}
&__title {
font-weight: 700;
font-size: $lm-font-md;
margin-bottom: $lm-space-1;
letter-spacing: 0.04em;
}
&__row {
display: flex;
justify-content: space-between;
gap: $lm-space-3;
font-size: $lm-font-sm;
margin: 2px 0;
}
&__label {
font-weight: 600;
color: #000;
min-width: 80px;
text-align: start;
}
&__value {
text-align: end;
flex: 1;
}
&__chip {
display: inline-block;
padding: 0 $lm-space-2;
margin: 0 2px;
border: 1px solid #000;
border-radius: $lm-radius-sm;
font-size: $lm-font-xs;
}
}
// RTL safety
[dir="rtl"] .laundry-receipt-details__label { text-align: end; }
[dir="rtl"] .laundry-receipt-details__value { text-align: start; }
// ── Operational Control Board (laundry.order kanban) ──────────────────────
.laundry-board {
.laundry-board__card {
position: relative;
background: $lm-bg-card;
border: 1px solid $lm-border;
border-radius: $lm-radius-md;
box-shadow: $lm-shadow-sm;
padding: $lm-space-3 $lm-space-4 $lm-space-3 ($lm-space-4 + 4px);
display: flex;
flex-direction: column;
gap: $lm-space-2;
transition: box-shadow 0.18s ease, transform 0.18s ease;
&:hover { box-shadow: $lm-shadow-md; transform: translateY(-1px); }
}
.laundry-board__strip {
position: absolute;
left: 0; top: 0; bottom: 0;
width: 4px;
border-top-left-radius: $lm-radius-md;
border-bottom-left-radius: $lm-radius-md;
background: $lm-color-normal;
}
.laundry-board__card[data-state="intake"] .laundry-board__strip { background: #3B82F6; }
.laundry-board__card[data-state="processing"] .laundry-board__strip { background: #F59E0B; }
.laundry-board__card[data-state="ready"] .laundry-board__strip { background: $lm-color-delivery; }
.laundry-board__card[data-state="delivered"] .laundry-board__strip { background: $lm-color-normal; }
.laundry-board__card[data-priority="urgent"] .laundry-board__strip { background: $lm-color-urgent; }
.laundry-board__card[data-priority="urgent"] { border-color: rgba(239, 68, 68, 0.35); }
.laundry-board__head {
display: flex;
flex-direction: column;
gap: 2px;
}
.laundry-board__title-row {
display: flex;
align-items: center;
justify-content: space-between;
gap: $lm-space-2;
}
.laundry-board__name {
font-size: $lm-font-md;
color: $lm-text;
}
.laundry-board__total {
font-size: $lm-font-md;
font-weight: 700;
color: $lm-text;
}
.laundry-board__customer {
font-size: $lm-font-sm;
color: $lm-text-muted;
display: flex;
align-items: center;
}
.laundry-board__badges {
display: flex;
flex-wrap: wrap;
gap: $lm-space-1;
}
.laundry-board__meta {
display: flex;
flex-wrap: wrap;
gap: $lm-space-3;
font-size: $lm-font-xs;
color: $lm-text-muted;
}
.laundry-board__due {
color: $lm-color-urgent;
font-weight: 700;
}
.laundry-board__actions { margin-top: $lm-space-1; }
}
// ── ProductConfiguratorPopup — laundry enhancement ─────────────────────
// Scoped exclusively under `.popup-product-configurator.laundry-enhanced`,
// applied to the modal-content via Dialog's `contentClass` prop from
// xml/product_configurator_popup.xml. Non-laundry configurator popups are
// unaffected.
.popup-product-configurator.laundry-enhanced {
// Attribute group — stronger title, better spacing.
.modal-body .attribute {
margin-bottom: $lm-space-4 !important;
padding-bottom: $lm-space-3;
border-bottom: 1px dashed $lm-border;
&:last-child {
margin-bottom: 0 !important;
padding-bottom: 0;
border-bottom: 0;
}
.attribute_name {
font-size: $lm-font-sm;
font-weight: 800 !important;
text-transform: uppercase;
letter-spacing: 0.09em;
color: $lm-text-muted;
margin-bottom: $lm-space-3 !important;
}
}
// Touch-first tiles — applies to both Radio and Pills renderers,
// which share the same `.configurator_radio > .attribute-name-cell`
// structure in core.
.configurator_radio {
> .d-flex {
flex-wrap: wrap !important;
gap: $lm-space-3 !important;
}
.attribute-name-cell {
flex: 1 1 calc(33% - #{$lm-space-3});
min-width: 140px;
margin: 0;
padding: 0;
// Hide the native radio — the label IS the tile.
.form-check-input,
.radio-check {
position: absolute !important;
opacity: 0 !important;
pointer-events: none !important;
width: 0 !important;
height: 0 !important;
}
> label {
display: flex !important;
align-items: center;
justify-content: center;
flex-wrap: wrap;
gap: $lm-space-2;
width: 100%;
min-height: 64px;
padding: 12px 16px !important;
border: 2px solid $lm-border !important;
border-radius: 12px !important;
background: #fff !important;
color: $lm-text !important;
font-size: $lm-font-md;
font-weight: 700;
line-height: 1.25;
cursor: pointer;
position: relative;
box-shadow: none !important;
transition: transform 0.14s ease,
border-color 0.14s ease,
box-shadow 0.16s ease,
background 0.14s ease,
color 0.14s ease;
&:hover {
transform: translateY(-1px);
box-shadow: $lm-shadow-sm !important;
}
&:active { transform: translateY(0); }
&:focus-visible {
outline: 2px solid rgba(79, 70, 229, 0.45);
outline-offset: 2px;
}
// Wrap text span so conflict styling still readable.
> span:first-child { word-break: break-word; }
}
}
// Selected state:
// - Pills renderer: core toggles `.active` on the <label>.
// - Radio renderer: input is :checked and precedes label.
.attribute-name-cell > input:checked + label,
.attribute-name-cell > label.active {
border-color: $lm-color-type !important;
box-shadow: 0 0 0 3px rgba(79, 70, 229, 0.14),
$lm-shadow-sm !important;
background: linear-gradient(135deg,
rgba(79, 70, 229, 0.08) 0%,
rgba(79, 70, 229, 0.00) 100%) !important;
color: $lm-color-type !important;
> span:first-child { color: $lm-color-type !important; }
&::after {
content: "\f00c"; // FontAwesome check
font-family: "FontAwesome", "Font Awesome 6 Free", sans-serif;
font-weight: 900;
position: absolute;
top: 6px;
right: 6px;
width: 22px;
height: 22px;
border-radius: 50%;
background: $lm-color-type;
color: #fff;
display: flex;
align-items: center;
justify-content: center;
font-size: 11px;
box-shadow: $lm-shadow-sm;
}
}
// Price extra chip — stronger, more scannable.
.price-extra-cell {
margin: 0 !important;
.price_extra {
font-size: $lm-font-xs !important;
font-weight: 800 !important;
padding: 2px 10px !important;
border-radius: 9999px !important;
background: rgba(245, 158, 11, 0.18) !important;
color: #B45309 !important;
letter-spacing: 0.02em;
}
}
// Conflict state (core adds `text-muted` on the inner span).
.attribute-name-cell > label > span.text-muted {
opacity: 0.6;
text-decoration: line-through;
}
// Custom-value text input — keep larger, consistent.
.custom-value-cell {
flex: 1 1 100%;
.form-control { height: 48px; font-size: $lm-font-md; }
}
}
// CTA footer — bigger, bolder, full width on tablet.
.modal-footer {
padding: $lm-space-3 $lm-space-4;
> .d-flex {
gap: $lm-space-3 !important;
}
.btn.btn-primary,
.btn.btn-secondary {
min-height: 56px;
font-size: $lm-font-lg;
font-weight: 800;
letter-spacing: 0.02em;
border-radius: $lm-radius-md;
}
.btn.btn-primary {
box-shadow: 0 2px 8px rgba(79, 70, 229, 0.22);
&:hover:not(.disabled) {
box-shadow: 0 4px 14px rgba(79, 70, 229, 0.32);
}
&.disabled {
opacity: 0.55;
box-shadow: none;
}
}
}
// Full-width primary button below tablet breakpoint.
@media (max-width: 768px) {
.modal-footer > .d-flex {
flex-direction: column-reverse;
.btn { width: 100% !important; }
}
}
// Archived-combination banner — keep legible but calmer.
.alert.alert-warning {
border-radius: $lm-radius-md;
border: 0;
background: rgba(245, 158, 11, 0.12);
color: #92400E;
font-weight: 600;
}
}
// ── Laundry Orders POS popup ──────────────────────────────────────────
// Scoped to `.laundry-orders-popup` (applied to .modal-content via the
// Dialog `contentClass` prop). Touches only modal contents — no leak
// to other POS popups.
.laundry-orders-popup {
.laundry-orders-popup__body {
padding: $lm-space-3;
display: flex;
flex-direction: column;
gap: $lm-space-3;
max-height: 70vh;
overflow-y: auto;
}
.laundry-orders-popup__header {
display: flex;
align-items: center;
justify-content: space-between;
gap: $lm-space-3;
padding-bottom: $lm-space-2;
border-bottom: 1px solid $lm-border;
}
.laundry-orders-popup__partner {
font-size: $lm-font-lg;
font-weight: 700;
color: $lm-text;
.fa { color: $lm-color-type; }
}
.laundry-orders-popup__phone {
font-size: $lm-font-sm;
font-weight: 500;
color: $lm-text-muted;
.fa { color: $lm-text-muted; font-size: 0.85em; }
}
.laundry-orders-popup__count { font-size: $lm-font-sm; }
.laundry-orders-popup__search {
margin-bottom: 0;
.input-group .btn { font-weight: 600; }
}
.laundry-orders-popup__error {
margin-bottom: 0;
border-radius: $lm-radius-md;
}
// Loading skeleton
.laundry-orders-popup__loading {
display: flex;
flex-direction: column;
gap: $lm-space-2;
}
.laundry-orders-popup__skeleton {
height: 96px;
border-radius: $lm-radius-md;
background: linear-gradient(90deg,
$lm-bg-soft 0%, #ECF0F4 50%, $lm-bg-soft 100%);
background-size: 200% 100%;
animation: laundry-skeleton-shimmer 1.4s ease-in-out infinite;
}
@keyframes laundry-skeleton-shimmer {
0% { background-position: 200% 0; }
100% { background-position: -200% 0; }
}
// Empty state
.laundry-orders-popup__empty {
background: $lm-bg-soft;
border: 1px dashed $lm-border;
border-radius: $lm-radius-md;
.fa { color: $lm-text-muted; opacity: 0.5; }
}
// Card list
.laundry-orders-popup__list {
display: flex;
flex-direction: column;
gap: $lm-space-3;
}
.laundry-orders-popup__card {
background: $lm-bg-card;
border: 1px solid $lm-border;
border-left: 4px solid $lm-color-normal;
border-radius: $lm-radius-md;
padding: $lm-space-3 $lm-space-4;
display: flex;
flex-direction: column;
gap: $lm-space-2;
box-shadow: $lm-shadow-sm;
transition: box-shadow 0.16s ease;
&:hover { box-shadow: $lm-shadow-md; }
&[data-state="intake"] { border-left-color: #3B82F6; }
&[data-state="processing"] { border-left-color: #F59E0B; }
&[data-state="ready"] { border-left-color: $lm-color-delivery; }
&[data-state="delivered"] { border-left-color: $lm-color-normal; }
&[data-state="cancelled"] {
border-left-color: $lm-text-muted;
opacity: 0.75;
}
&[data-payment="due"] { border-left-color: $lm-color-urgent; }
&[data-busy="1"] { opacity: 0.7; pointer-events: none; }
}
.laundry-orders-popup__card-head {
display: flex;
justify-content: space-between;
align-items: baseline;
gap: $lm-space-3;
}
.laundry-orders-popup__order-name {
font-size: $lm-font-md;
font-weight: 800;
color: $lm-text;
letter-spacing: 0.01em;
}
.laundry-orders-popup__pos-ref {
font-size: $lm-font-xs;
color: $lm-text-muted;
font-weight: 500;
}
.laundry-orders-popup__date {
font-size: $lm-font-xs;
color: $lm-text-muted;
white-space: nowrap;
}
.laundry-orders-popup__card-meta {
display: flex;
justify-content: space-between;
align-items: baseline;
gap: $lm-space-3;
font-size: $lm-font-sm;
color: $lm-text;
.fa { color: $lm-text-muted; }
}
.laundry-orders-popup__services {
font-size: $lm-font-xs;
max-width: 280px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
display: inline-block;
vertical-align: bottom;
}
.laundry-orders-popup__total { font-weight: 700; font-size: $lm-font-md; }
.laundry-orders-popup__due { color: $lm-color-urgent; font-weight: 700; }
// Badges
.laundry-orders-popup__badges {
display: flex;
flex-wrap: wrap;
gap: $lm-space-2;
}
.laundry-badge {
display: inline-flex;
align-items: center;
padding: 3px $lm-space-2;
font-size: $lm-font-xs;
font-weight: 700;
letter-spacing: 0.04em;
text-transform: uppercase;
border-radius: 9999px;
white-space: nowrap;
line-height: 1.3;
.fa { font-size: 0.85em; }
}
.badge-state-intake { background: rgba(59,130,246,.12); color: #1E40AF; }
.badge-state-processing { background: rgba(245,158,11,.14); color: #92400E; }
.badge-state-ready { background: rgba(16,185,129,.14); color: #065F46; }
.badge-state-delivered { background: rgba(107,114,128,.16); color: #1F2937; }
.badge-state-cancelled { background: rgba(107,114,128,.10); color: $lm-text-muted; }
.badge-payment-paid { background: rgba(16,185,129,.16); color: #065F46; }
.badge-payment-deferred { background: rgba(245,158,11,.16); color: #92400E; }
.badge-payment-settled { background: rgba(99,102,241,.12); color: $lm-color-type; }
.badge-payment-due { background: rgba(239,68,68,.16); color: #991B1B; }
.laundry-badge--delivery {
background: rgba(16,185,129,.14);
color: $lm-color-delivery;
}
.laundry-badge--source {
background: $lm-bg-soft;
color: $lm-text-muted;
}
// Actions row
.laundry-orders-popup__actions {
display: flex;
flex-wrap: wrap;
gap: $lm-space-2;
padding-top: $lm-space-2;
border-top: 1px dashed $lm-border;
.btn {
min-height: 44px; // touch target
font-weight: 700;
letter-spacing: 0.01em;
padding-inline: $lm-space-3;
}
}
.laundry-orders-popup__due-hint { margin-top: 2px; }
}
// ── Thermal Work Order receipt ─────────────────────────────────────────
// Rendered by LaundryWorkOrderThermal when sent through pos.printer.print.
// Sized for an 80 mm POS printer; @media print collapses to full-width
// for the browser fallback (`webPrintFallback: true`).
.laundry-thermal {
font-family: "Courier New", "Courier", monospace;
font-size: 12px;
line-height: 1.4;
width: 76mm;
padding: 4mm;
color: #000;
background: #fff;
&__header { text-align: center; margin-bottom: 4px; }
&__company { font-weight: 700; font-size: 13px; }
&__title {
font-weight: 800; font-size: 16px;
margin: 4px 0 2px; letter-spacing: 0.06em;
}
&__name { font-size: 13px; font-weight: 700; }
&__divider { border-top: 1px dashed #000; margin: 4px 0; }
&__row {
display: flex; justify-content: space-between; gap: 8px;
margin: 1px 0;
}
&__label { font-weight: 700; }
&__lines {
width: 100%;
font-size: 11px;
border-collapse: collapse;
th, td { padding: 2px 4px; vertical-align: top; text-align: left; }
th { border-bottom: 1px solid #000; font-weight: 700; }
td.qty, th.qty { width: 28px; text-align: center; }
td.amount, th.amount { text-align: right; white-space: nowrap; }
}
&__tracking { font-size: 10px; color: #555; margin-top: 1px; }
&__totals { margin-top: 4px; }
&__total { font-weight: 700; }
&__due {
font-weight: 800;
border-top: 1px solid #000;
padding-top: 2px;
margin-top: 2px;
}
&__status {
text-align: center;
font-weight: 700;
font-size: 13px;
margin-top: 4px;
letter-spacing: 0.05em;
}
&__footer {
text-align: center;
font-size: 10px;
margin-top: 6px;
font-style: italic;
}
}
@media print {
// Browser-fallback path — let the receipt fill the printable area.
.laundry-thermal { width: 100%; max-width: 80mm; margin: 0 auto; }
}