fix(shopfloor): explicit hex colors like Odoo's own kanban

Why the borders weren't showing: the previous approach used
color-mix(var(--bs-body-color) 4%, var(--o-view-background-color)) for
card/column backgrounds. Under Odoo 19 the resolved values for those
variables were nearly identical to var(--bs-body-bg), so the card
surfaces visually merged into the page. Same problem for borders:
var(--bs-border-color) can render extremely faint depending on theme.

Checked what Odoo's native kanban does — dug through the compiled
CSS and found:
    .o_kanban_record { background-color: white;
                        border: 1px solid #d8dadd; }
    .o_kanban_group  { background: var(--KanbanGroup-background); }

Odoo uses EXPLICIT hex values and card-specific tokens, not the
generic body/border variables. Adopted the same approach.

New tokens in _fp_shopfloor_tokens.scss — all explicit, plus a
dark-mode override block keyed off [data-bs-theme="dark"] and
.o_dark_mode (Odoo 19 uses both):

    light                             dark
    ------------------------          ------------------
    --fp-page-bg: #f3f4f6             #13161a
    --fp-column-bg: #e9ebef           #1a1e24
    --fp-card-bg: #ffffff             #22262d
    --fp-card-soft-bg: #f8fafc        #1c2027
    --fp-border-color: #d8dadd        #343942
    --fp-ink: #1f2937                 #e5e7eb
    --fp-ink-mute: #6b7280             #8a909a

    shadow scale switched from color-mix to explicit rgba(0,0,0,...)
    so it renders identically across browsers.

All three SCSS files updated via sed to swap
var(--bs-border-color)  ->  #{$fp-border}
...then $fp-border resolves to var(--fp-border-color, #d8dadd) — a
proper card-level border that is VISIBLE (28 refs to --fp-card-bg
and 35 refs to --fp-border-color confirmed in the compiled bundle).

Plant Overview specifically now has:
  * Column: #f8fafc bg + #d8dadd border + shadow
    (column is brighter than the page it sits on)
  * Column HEADER: #ffffff inside the column, with bottom border
    (clear separator between stages)
  * Card: solid #ffffff bg + #d8dadd border + shadow
    (brightest surface, pops off the column)
  * Gap between columns: 16px so the column borders don't touch

Module version bumped to 19.0.3.0.0. Bundle regenerated at
/web/assets/0cd8bc1/web.assets_backend.min.css (1.45 MB, id 1939).
Verified by parsing compiled CSS:
  .o_fp_po_card: background-color: var(--fp-card-bg, #ffffff);
                 border: 1px solid var(--fp-border-color, #d8dadd);
  .o_fp_po_column: background-color: var(--fp-card-soft-bg, #f8fafc);
                   border: 1px solid var(--fp-border-color, #d8dadd);

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
gsinghpal
2026-04-18 19:13:38 -04:00
parent 2588a2b651
commit 81277edb25
5 changed files with 86 additions and 58 deletions

View File

@@ -5,7 +5,7 @@
{
'name': 'Fusion Plating — Shop Floor',
'version': '19.0.2.0.0',
'version': '19.0.3.0.0',
'category': 'Manufacturing/Plating',
'summary': 'Shop-floor tablet stations, QR scanning, bake window enforcer, '
'first-piece inspection gates.',

View File

@@ -31,32 +31,39 @@ $fp-radius-lg : 20px;
$fp-radius-xl : 28px;
$fp-radius-pill: 999px;
// ---------- Surfaces — depth by TINT, not by border --------------------------
// The page gets a slightly tinted background; cards sit on a lighter
// surface. That tint difference replaces the need for card borders.
$fp-page : color-mix(in srgb, var(--bs-body-color) 2.5%,
var(--o-view-background-color, var(--bs-body-bg)));
$fp-card : var(--o-view-background-color, var(--bs-body-bg));
$fp-card-soft : color-mix(in srgb, var(--bs-body-color) 4%,
var(--o-view-background-color, var(--bs-body-bg)));
// ---------- Surfaces — explicit values, matching Odoo kanban conventions -----
// Odoo's own kanban uses hex values (not CSS vars) because --bs-border-color
// and --bs-body-bg drift between themes and addons. We follow the same
// approach with clear, strong contrast: tinted page, tinted column, SOLID
// WHITE card with a VISIBLE border.
//
// Dark mode: overrides are applied at the bottom of each file via
// :root[data-bs-theme="dark"], body.o_dark_mode, or @media dark-pref.
$fp-page : var(--fp-page-bg, #f3f4f6);
$fp-column : var(--fp-column-bg, #e9ebef);
$fp-card : var(--fp-card-bg, #ffffff);
$fp-card-soft : var(--fp-card-soft-bg, #f8fafc);
$fp-border : var(--fp-border-color, #d8dadd);
$fp-border-strong : var(--fp-border-strong, #b6babf);
// ---------- Text tiers -------------------------------------------------------
$fp-ink : var(--bs-body-color);
$fp-ink-soft : color-mix(in srgb, var(--bs-body-color) 70%, transparent);
$fp-ink-mute : color-mix(in srgb, var(--bs-body-color) 48%, transparent);
$fp-ink-faint : color-mix(in srgb, var(--bs-body-color) 28%, transparent);
$fp-ink : var(--fp-ink, #1f2937);
$fp-ink-soft : var(--fp-ink-soft, #4b5563);
$fp-ink-mute : var(--fp-ink-mute, #6b7280);
$fp-ink-faint : var(--fp-ink-faint, #9ca3af);
// ---------- Elevation — soft, layered, theme-safe ----------------------------
// Shadows built on the foreground colour so they darken appropriately in
// light mode and show a subtle halo in dark mode.
$fp-elev-1 : 0 1px 2px color-mix(in srgb, var(--bs-body-color) 5%, transparent),
0 1px 3px color-mix(in srgb, var(--bs-body-color) 7%, transparent);
$fp-elev-2 : 0 2px 4px color-mix(in srgb, var(--bs-body-color) 6%, transparent),
0 6px 14px color-mix(in srgb, var(--bs-body-color) 9%, transparent);
$fp-elev-3 : 0 4px 8px color-mix(in srgb, var(--bs-body-color) 8%, transparent),
0 12px 28px color-mix(in srgb, var(--bs-body-color) 12%, transparent);
$fp-elev-hover : 0 6px 12px color-mix(in srgb, var(--bs-body-color) 10%, transparent),
0 18px 36px color-mix(in srgb, var(--bs-body-color) 14%, transparent);
// ---------- Elevation — explicit rgba shadows --------------------------------
// Explicit rgba values (not color-mix) so they render identically across
// browsers and themes. In dark mode the shadows still work against the
// darker surfaces because they're translucent.
$fp-elev-1 : 0 1px 2px rgba(0, 0, 0, 0.06),
0 1px 3px rgba(0, 0, 0, 0.08);
$fp-elev-2 : 0 2px 4px rgba(0, 0, 0, 0.06),
0 6px 14px rgba(0, 0, 0, 0.10);
$fp-elev-3 : 0 4px 8px rgba(0, 0, 0, 0.10),
0 12px 28px rgba(0, 0, 0, 0.14);
$fp-elev-hover : 0 6px 12px rgba(0, 0, 0, 0.12),
0 18px 36px rgba(0, 0, 0, 0.16);
// ---------- Semantic colour helpers (NOT gradients) --------------------------
$fp-accent : var(--o-action); // the one action colour
@@ -129,3 +136,24 @@ $fp-touch-min : 48px; // larger than Apple's 44px minimum — shop floor
@content;
}
}
// =============================================================================
// Dark-mode palette overrides
// Odoo 19 flips dark mode via [data-bs-theme="dark"] on the root.
// We provide a matching-level body selector for compatibility.
// =============================================================================
:root[data-bs-theme="dark"],
body.o_dark_mode,
.o_dark_mode {
--fp-page-bg : #13161a;
--fp-column-bg : #1a1e24;
--fp-card-bg : #22262d;
--fp-card-soft-bg : #1c2027;
--fp-border-color : #343942;
--fp-border-strong : #4a505a;
--fp-ink : #e5e7eb;
--fp-ink-soft : #c8ccd2;
--fp-ink-mute : #8a909a;
--fp-ink-faint : #5a606b;
}

View File

@@ -87,7 +87,7 @@
min-width: 240px;
min-height: $fp-touch-min;
padding: $fp-space-2 $fp-space-4;
border: 1px solid var(--bs-border-color);
border: 1px solid #{$fp-border};
border-radius: $fp-radius-md;
background-color: $fp-card;
color: $fp-ink;
@@ -102,7 +102,7 @@
.o_fp_scan_toggle {
min-height: $fp-touch-min;
padding: 0 $fp-space-5;
border: 1px solid var(--bs-border-color);
border: 1px solid #{$fp-border};
border-radius: $fp-radius-md;
background-color: $fp-card;
color: $fp-ink;
@@ -117,7 +117,7 @@
@include fp-hover-only {
&:hover {
box-shadow: $fp-elev-2;
border-color: color-mix(in srgb, #{$fp-accent} 45%, var(--bs-border-color));
border-color: color-mix(in srgb, #{$fp-accent} 45%, #{$fp-border});
}
}
&:active { transform: scale(0.97); }
@@ -206,7 +206,7 @@
.o_fp_kpi {
position: relative;
padding: $fp-space-5;
border: 1px solid var(--bs-border-color);
border: 1px solid #{$fp-border};
border-radius: $fp-radius-lg;
background-color: $fp-card;
box-shadow: $fp-elev-1;
@@ -217,7 +217,7 @@
&:hover {
transform: translateY(-2px);
box-shadow: $fp-elev-2;
border-color: color-mix(in srgb, #{$fp-accent} 30%, var(--bs-border-color));
border-color: color-mix(in srgb, #{$fp-accent} 30%, #{$fp-border});
}
}
@@ -334,7 +334,7 @@
// -------------------------------------------------------------------------
.o_fp_panel {
background-color: $fp-card;
border: 1px solid var(--bs-border-color);
border: 1px solid #{$fp-border};
border-radius: $fp-radius-lg;
box-shadow: $fp-elev-1;
padding: $fp-space-5;
@@ -346,7 +346,7 @@
align-items: center;
margin-bottom: $fp-space-4;
padding-bottom: $fp-space-3;
border-bottom: 1px solid var(--bs-border-color);
border-bottom: 1px solid #{$fp-border};
h3 {
font-size: $fp-text-lg;
@@ -411,7 +411,7 @@
align-items: center;
gap: $fp-space-3;
padding: $fp-space-3 $fp-space-4;
border: 1px solid var(--bs-border-color);
border: 1px solid #{$fp-border};
border-radius: $fp-radius-md;
background-color: $fp-card;
min-height: 64px;
@@ -423,7 +423,7 @@
@include fp-hover-only {
&:hover {
background-color: color-mix(in srgb, #{$fp-accent} 4%, $fp-card);
border-color: color-mix(in srgb, #{$fp-accent} 45%, var(--bs-border-color));
border-color: color-mix(in srgb, #{$fp-accent} 45%, #{$fp-border});
box-shadow: $fp-elev-2;
}
}
@@ -486,7 +486,7 @@
}
.o_fp_tile {
padding: $fp-space-4;
border: 1px solid var(--bs-border-color);
border: 1px solid #{$fp-border};
border-radius: $fp-radius-md;
background-color: $fp-card;
cursor: pointer;
@@ -499,7 +499,7 @@
&:hover {
transform: translateY(-1px);
background-color: color-mix(in srgb, #{$fp-accent} 4%, $fp-card);
border-color: color-mix(in srgb, #{$fp-accent} 45%, var(--bs-border-color));
border-color: color-mix(in srgb, #{$fp-accent} 45%, #{$fp-border});
box-shadow: $fp-elev-2;
}
}
@@ -550,7 +550,7 @@
align-items: center;
gap: $fp-space-3;
padding: $fp-space-3 $fp-space-4;
border: 1px solid var(--bs-border-color);
border: 1px solid #{$fp-border};
border-radius: $fp-radius-md;
background-color: $fp-card;
min-height: 64px;
@@ -569,7 +569,7 @@
&[data-state="missed_window"], &[data-state="fail"], &[data-state="on_hold"] {
box-shadow: inset 4px 0 0 0 $fp-bad;
background-color: fp-wash(--bs-danger, 5%);
border-color: color-mix(in srgb, #{$fp-bad} 35%, var(--bs-border-color));
border-color: color-mix(in srgb, #{$fp-bad} 35%, #{$fp-border});
}
&[data-state="baked"], &[data-state="pass"] {
box-shadow: inset 4px 0 0 0 $fp-ok;

View File

@@ -84,7 +84,7 @@
padding: 0 $fp-space-4;
border-radius: $fp-radius-md;
font-weight: $fp-weight-semibold;
border: 1px solid var(--bs-border-color);
border: 1px solid #{$fp-border};
background-color: $fp-card;
color: $fp-ink;
box-shadow: $fp-elev-1;
@@ -94,7 +94,7 @@
@include fp-hover-only {
&:hover {
box-shadow: $fp-elev-2;
border-color: color-mix(in srgb, #{$fp-accent} 45%, var(--bs-border-color));
border-color: color-mix(in srgb, #{$fp-accent} 45%, #{$fp-border});
}
}
&:active { transform: scale(0.97); }
@@ -149,7 +149,7 @@
.o_fp_kpi {
position: relative;
padding: $fp-space-5;
border: 1px solid var(--bs-border-color);
border: 1px solid #{$fp-border};
border-radius: $fp-radius-lg;
background-color: $fp-card;
box-shadow: $fp-elev-1;
@@ -160,7 +160,7 @@
&:hover {
transform: translateY(-2px);
box-shadow: $fp-elev-2;
border-color: color-mix(in srgb, #{$fp-accent} 30%, var(--bs-border-color));
border-color: color-mix(in srgb, #{$fp-accent} 30%, #{$fp-border});
}
}
@@ -226,7 +226,7 @@
// -------------------------------------------------------------------------
.o_fp_panel {
background-color: $fp-card;
border: 1px solid var(--bs-border-color);
border: 1px solid #{$fp-border};
border-radius: $fp-radius-lg;
box-shadow: $fp-elev-1;
padding: $fp-space-5;
@@ -238,7 +238,7 @@
align-items: center;
margin-bottom: $fp-space-4;
padding-bottom: $fp-space-3;
border-bottom: 1px solid var(--bs-border-color);
border-bottom: 1px solid #{$fp-border};
h3 {
font-size: $fp-text-lg;
@@ -309,7 +309,7 @@
.o_fp_mgr_card {
position: relative;
background-color: $fp-card;
border: 1px solid var(--bs-border-color);
border: 1px solid #{$fp-border};
border-radius: $fp-radius-md;
overflow: hidden;
transition: transform $fp-dur-fast $fp-ease,
@@ -318,7 +318,7 @@
@include fp-hover-only {
&:hover {
border-color: color-mix(in srgb, #{$fp-accent} 45%, var(--bs-border-color));
border-color: color-mix(in srgb, #{$fp-accent} 45%, #{$fp-border});
box-shadow: $fp-elev-2;
transform: translateY(-1px);
}
@@ -327,7 +327,7 @@
// Priority stripe (4px) on the left — only when priority is set
&[data-priority="2"] {
background-color: color-mix(in srgb, #{$fp-bad} 4%, $fp-card);
border-color: color-mix(in srgb, #{$fp-bad} 35%, var(--bs-border-color));
border-color: color-mix(in srgb, #{$fp-bad} 35%, #{$fp-border});
&::before {
content: "";
position: absolute; left: 0; top: 0; bottom: 0;
@@ -376,7 +376,7 @@
align-items: center;
padding: $fp-space-2 $fp-space-3;
background-color: $fp-card;
border: 1px solid var(--bs-border-color);
border: 1px solid #{$fp-border};
border-radius: $fp-radius-sm;
font-size: $fp-text-sm;
@@ -401,7 +401,7 @@
min-width: 140px; max-width: 220px;
min-height: 40px;
padding: 0 $fp-space-3;
border: 1px solid var(--bs-border-color);
border: 1px solid #{$fp-border};
border-radius: $fp-radius-sm;
background-color: $fp-card;
color: $fp-ink;
@@ -454,7 +454,7 @@
align-items: center;
gap: $fp-space-3;
padding: $fp-space-3 $fp-space-4;
border: 1px solid var(--bs-border-color);
border: 1px solid #{$fp-border};
border-radius: $fp-radius-md;
background-color: $fp-card;
cursor: pointer;
@@ -467,7 +467,7 @@
@include fp-hover-only {
&:hover {
background-color: color-mix(in srgb, #{$fp-accent} 4%, $fp-card);
border-color: color-mix(in srgb, #{$fp-accent} 45%, var(--bs-border-color));
border-color: color-mix(in srgb, #{$fp-accent} 45%, #{$fp-border});
box-shadow: $fp-elev-2;
}
}

View File

@@ -68,7 +68,7 @@
.o_fp_po_search_input {
padding: 0 $fp-space-4 0 $fp-space-7;
min-height: $fp-touch-min;
border: 1px solid var(--bs-border-color);
border: 1px solid #{$fp-border};
border-radius: $fp-radius-md;
background-color: $fp-card;
color: $fp-ink;
@@ -95,7 +95,7 @@
.o_fp_po_refresh_btn {
width: $fp-touch-min; height: $fp-touch-min;
display: flex; align-items: center; justify-content: center;
border: 1px solid var(--bs-border-color);
border: 1px solid #{$fp-border};
border-radius: $fp-radius-md;
background-color: $fp-card;
color: $fp-ink;
@@ -132,7 +132,7 @@
display: flex;
flex-direction: column;
background-color: $fp-card-soft;
border: 1px solid var(--bs-border-color);
border: 1px solid #{$fp-border};
border-radius: $fp-radius-lg;
box-shadow: $fp-elev-1;
max-height: calc(100vh - 180px);
@@ -148,7 +148,7 @@
.o_fp_po_col_header {
display: flex; align-items: center; justify-content: space-between;
padding: $fp-space-4;
border-bottom: 1px solid var(--bs-border-color);
border-bottom: 1px solid #{$fp-border};
background-color: $fp-card;
.o_fp_po_col_name {
@@ -214,7 +214,7 @@
.o_fp_po_card {
position: relative;
background-color: $fp-card;
border: 1px solid var(--bs-border-color);
border: 1px solid #{$fp-border};
border-radius: $fp-radius-md;
padding: $fp-space-3 $fp-space-4;
margin-bottom: $fp-space-2;
@@ -227,7 +227,7 @@
@include fp-hover-only {
&:hover {
border-color: color-mix(in srgb, #{$fp-accent} 45%, var(--bs-border-color));
border-color: color-mix(in srgb, #{$fp-accent} 45%, #{$fp-border});
box-shadow: $fp-elev-2;
transform: translateY(-1px);
}
@@ -251,7 +251,7 @@
}
&[data-priority="2"], &.o_fp_po_card_hot {
background-color: color-mix(in srgb, #{$fp-bad} 5%, $fp-card);
border-color: color-mix(in srgb, #{$fp-bad} 35%, var(--bs-border-color));
border-color: color-mix(in srgb, #{$fp-bad} 35%, #{$fp-border});
&::before { background-color: $fp-bad; }
}
&[data-priority="1"], &.o_fp_po_card_urgent {
@@ -271,7 +271,7 @@
border-radius: $fp-radius-sm;
object-fit: contain;
background-color: $fp-card-soft;
border: 1px solid var(--bs-border-color);
border: 1px solid #{$fp-border};
padding: 2px;
}
.o_fp_po_card_avatar_blank {