/**
* WooCommerce panels inside the site window.
*
* Two mount points, both provided by the site window's extension
* actions: the preview pane's `header` slot (product / order / coupon
* facts) and the folder view's group slot (store totals). The panel
* markup is identical in both, so one rule set covers them.
*
* Uses the shell's own custom properties throughout so the panels
* follow the active desktop theme and colour scheme without a single
* hard-coded colour beyond the status tints.
*/
.os-woo-panel {
margin-block-end: 16px;
padding: 12px 14px;
border: 1px solid var( --os-ui-border, rgba( 0, 0, 0, 0.12 ) );
border-radius: 8px;
background: var( --os-ui-bg-subtle, rgba( 0, 0, 0, 0.02 ) );
}
.os-woo-panel__title {
margin: 0 0 10px;
font-size: 12px;
font-weight: 600;
letter-spacing: 0.04em;
text-transform: uppercase;
color: var( --os-ui-fg-muted, #787c82 );
}
.os-woo-panel__rows {
display: grid;
/* `auto` label column so long values keep the space they need,
* with a floor that stops one-word labels collapsing. */
grid-template-columns: minmax( 90px, auto ) 1fr;
gap: 6px 14px;
margin: 0;
font-size: 13px;
}
/* Each row is its own grid item pair — `display: contents` lets the
* wrapper carry the semantics (`
` grouping a `
`/``)
* while the two children land directly in the parent grid's columns. */
.os-woo-panel__row {
display: contents;
}
.os-woo-panel__label {
margin: 0;
color: var( --os-ui-fg-muted, #787c82 );
}
.os-woo-panel__value {
margin: 0;
color: var( --os-ui-fg, inherit );
overflow-wrap: anywhere;
}
/* Placeholder rows hold the panel's final height from the first
* frame, so the real data swaps in without shoving the folder tiles
* or the preview content down. Each placeholder is two grid cells
* carrying a muted bar. */
.os-woo-panel__row--placeholder > span {
display: block;
height: 1em;
border-radius: 3px;
background: var( --os-ui-border, rgba( 0, 0, 0, 0.09 ) );
}
/* The value column reads as the "content" bar — narrower than its
* cell so the shell doesn't look like a solid block. */
.os-woo-panel__row--placeholder > span:last-child {
width: 60%;
}
@media ( prefers-reduced-motion: no-preference ) {
.os-woo-panel[ aria-busy='true' ]
.os-woo-panel__row--placeholder > span {
animation: os-woo-pulse 1.4s ease-in-out infinite;
}
}
@keyframes os-woo-pulse {
0%,
100% {
opacity: 1;
}
50% {
opacity: 0.45;
}
}
.os-woo-panel__error {
grid-column: 1 / -1;
margin: 0;
color: var( --os-ui-fg-muted, #787c82 );
font-size: 13px;
}
.os-woo-panel__was {
color: var( --os-ui-fg-muted, #787c82 );
margin-inline-end: 4px;
}
/* ----- Status pills -----
*
* `` owns the pill shape, the tone-coded dot and the
* theming tokens. Nothing to style here beyond keeping it on the
* baseline of its row. */
.os-woo-panel__pill {
vertical-align: middle;
}
/* ----- Order line items ----- */
.os-woo-panel__items {
margin: 0;
padding: 0;
list-style: none;
}
.os-woo-panel__item {
display: flex;
justify-content: space-between;
gap: 12px;
padding-block: 2px;
}
.os-woo-panel__item-total {
color: var( --os-ui-fg-muted, #787c82 );
white-space: nowrap;
}
.os-woo-panel__email,
.os-woo-panel__link {
color: var( --os-ui-accent, #2271b1 );
}
.os-woo-panel__item-qty {
color: var( --os-ui-fg-muted, #787c82 );
margin-inline-end: 4px;
}
/* ----- Tile badges ----- */
/* Out-of-stock (and low-stock / sale) products carry a corner ribbon
* on the tile itself, not just a band heading — a merchant scanning
* the grid shouldn't have to read headings to spot an empty shelf.
*
* `` owns the 45° slice, the clipping and the tone
* colours; all that's needed here is to keep it off the pointer and
* step the type down to suit an 88px tile. The tile is already
* `position: absolute`, which is the positioned parent the ribbon
* anchors to. */
os-ribbon.os-woo-ribbon {
/* The component defaults to a 90px corner window with a 140px
* banner — sized for a card, which swallows a tile whole. These
* custom properties are its documented tuning surface. Values
* here suit the large product tile (104px image well); the
* regular-tile fallback below steps them down again. */
--os-ui-ribbon-size: 64px;
--os-ui-ribbon-banner-width: 92px;
--os-ui-ribbon-padding: 3px 0;
--os-ui-ribbon-font: 700 9px/1.4 var( --os-font, system-ui );
--os-ui-ribbon-tracking: 0.03em;
pointer-events: none;
}
/* Regular-sized tiles (a section that didn't opt into `tileSize:
* 'large'`) get a proportionally smaller ribbon. */
.os-my-wordpress__tiles:not( .os-my-wordpress__tiles--large )
os-ribbon.os-woo-ribbon {
--os-ui-ribbon-size: 52px;
--os-ui-ribbon-banner-width: 76px;
--os-ui-ribbon-padding: 2px 0;
--os-ui-ribbon-font: 700 8px/1.4 var( --os-font, system-ui );
}
/* ----- Store panel on the folder view ----- */
.os-my-wordpress__group-extras:empty {
display: none;
}
.os-my-wordpress__group-extras {
padding: 12px 16px 0;
}
.os-woo-panel--store .os-woo-panel__rows {
/* Three headline numbers read better side by side than stacked,
* and the folder view has the full window width to spend. */
grid-template-columns: repeat( auto-fit, minmax( 160px, 1fr ) );
gap: 4px 24px;
}
.os-woo-panel--store .os-woo-panel__row {
display: flex;
flex-direction: column;
gap: 2px;
}
.os-woo-panel--store .os-woo-panel__value {
font-size: 20px;
font-weight: 600;
}
/* ----- Customer tiles ----- */
/*
* The band badge, for VIP and lapsed only.
*
* It lives INSIDE the 48px avatar box, straddling its bottom edge —
* the way a status dot sits on a contact photo. That costs the tile
* no vertical space, so the icon stays an icon: a face, a name, and
* at most one mark. Everything else about a customer (spend, orders,
* last purchase) is one click away in the pane, which has room to say
* it properly.
*
* Two rejected alternatives, both tried: ``, the 45°
* corner banner the Products grid uses — right across a product
* photo, vandalism across a face, covering a third of the avatar at
* this size. And a line of text under the name — spend and order
* count stacked above a 48px avatar crowd the tile past reading, and
* they push the name down the grid.
*/
.os-woo-customer-avatar {
position: relative;
/* The badge is wider than 48px and must not be clipped. */
overflow: visible;
}
.os-woo-customer-band {
position: absolute;
inset-block-end: -4px;
inset-inline-start: 50%;
transform: translateX( -50% );
z-index: 1;
padding: 0 5px;
border-radius: 999px;
font-size: 8px;
font-weight: 700;
line-height: 14px;
letter-spacing: 0.04em;
text-transform: uppercase;
white-space: nowrap;
pointer-events: none;
}
/*
* A solid surface under the tone, because this sits ON a photograph:
* the 12%-alpha wash `` uses would take whatever colour the
* avatar happens to be. The tone survives as the text and the ring,
* read from the same tokens (and the same fallback literals) the
* panel's band pill uses — so tile and pane can never disagree, and a
* desktop theme moves both at once.
*/
.os-woo-customer-band--vip,
.os-woo-customer-band--lapsed {
background: var( --os-ui-surface, #fff );
box-shadow:
inset 0 0 0 1px currentColor,
0 1px 3px rgba( 0, 0, 0, 0.35 );
}
.os-woo-customer-band--vip {
color: var( --os-ui-badge-success, var( --os-ui-success-fg, #1a7f37 ) );
}
.os-woo-customer-band--lapsed {
color: var( --os-ui-badge-warning, var( --os-ui-warning-fg, #9a6700 ) );
}
/*
* RTL: `translateX(-50%)` is a physical transform, so the logical
* `inset-inline-start` above would place the badge correctly and the
* transform would then drag it the wrong way.
*/
[ dir='rtl' ] .os-woo-customer-band {
transform: translateX( 50% );
}
/* ----- Customer panel ----- */
/*
* The band pill sits inline after the lifetime-spend figure, so it
* needs to sit on the text baseline rather than on the line box —
* otherwise it rides high next to the larger spend number.
*/
.os-woo-panel--customer .os-woo-panel__pill {
vertical-align: baseline;
}
/* ----- The Customer window ----- */
/*
* A native window on one person. The layout is a single column with
* generous vertical rhythm rather than a dashboard grid: this is a
* thing you READ — who they are, what they're worth, what they
* bought — not a control panel you operate.
*/
.os-woo-customer-window {
display: flex;
flex-direction: column;
gap: 20px;
padding: 20px 24px 24px;
box-sizing: border-box;
min-height: 100%;
color: var( --os-ui-fg, #1e1e1e );
font-size: 13px;
line-height: 1.5;
}
.os-woo-customer-window__loading,
.os-woo-customer-window__empty {
display: flex;
align-items: center;
justify-content: center;
flex: 1;
min-height: 160px;
color: var( --os-ui-fg-muted, rgba( 0, 0, 0, 0.6 ) );
}
/* ----- Identity strip ----- */
.os-woo-customer-window__header {
display: flex;
align-items: center;
gap: 16px;
}
.os-woo-customer-window__avatar {
width: 64px;
height: 64px;
border-radius: 50%;
object-fit: cover;
flex: none;
}
.os-woo-customer-window__identity {
display: flex;
flex-direction: column;
gap: 4px;
min-width: 0;
}
.os-woo-customer-window__name {
margin: 0;
font-size: 20px;
font-weight: 600;
line-height: 1.2;
}
.os-woo-customer-window__contact {
margin: 0;
color: var( --os-ui-fg-muted, rgba( 0, 0, 0, 0.6 ) );
overflow-wrap: anywhere;
}
.os-woo-customer-window__band {
align-self: flex-start;
}
/* ----- The four numbers ----- */
/*
* `auto-fit` rather than a fixed four-up: the window is resizable and
* a merchant may well park it narrow beside an order. Four cards
* become two rows, then one, without a media query.
*/
.os-woo-customer-window__stats {
display: grid;
grid-template-columns: repeat( auto-fit, minmax( 150px, 1fr ) );
gap: 12px;
}
.os-woo-customer-window__stat {
display: flex;
flex-direction: column;
gap: 2px;
padding: 12px 14px;
border-radius: 8px;
background: var( --os-ui-surface-2, rgba( 0, 0, 0, 0.04 ) );
}
.os-woo-customer-window__stat-value {
font-size: 19px;
font-weight: 600;
line-height: 1.2;
overflow-wrap: anywhere;
}
.os-woo-customer-window__stat-label {
font-size: 11px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.04em;
color: var( --os-ui-fg-muted, rgba( 0, 0, 0, 0.6 ) );
}
.os-woo-customer-window__stat-hint {
font-size: 11px;
color: var( --os-ui-fg-muted, rgba( 0, 0, 0, 0.6 ) );
}
/* ----- Sections ----- */
.os-woo-customer-window__section {
display: flex;
flex-direction: column;
gap: 8px;
}
.os-woo-customer-window__section-title {
margin: 0;
font-size: 11px;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.04em;
color: var( --os-ui-fg-muted, rgba( 0, 0, 0, 0.6 ) );
}
.os-woo-customer-window__favourite {
margin: 0;
}
/* ----- Order history ----- */
/*
* Three columns per row — identity, context, money — with the total
* pinned to the end so a column of prices lines up and can be
* scanned vertically. A table would give the same alignment and cost
* a header row saying "Order / Date / Total", which the rows already
* say for themselves.
*/
.os-woo-customer-window__orders {
margin: 0;
padding: 0;
list-style: none;
display: flex;
flex-direction: column;
}
.os-woo-customer-window__order {
display: grid;
grid-template-columns: minmax( 0, auto ) 1fr auto;
align-items: center;
gap: 4px 12px;
padding: 8px 0;
border-block-end: 1px solid
var( --os-ui-border, rgba( 0, 0, 0, 0.08 ) );
}
.os-woo-customer-window__order:last-child {
border-block-end: 0;
}
.os-woo-customer-window__order-head {
display: flex;
align-items: center;
gap: 8px;
min-width: 0;
}
.os-woo-customer-window__order-meta {
color: var( --os-ui-fg-muted, rgba( 0, 0, 0, 0.6 ) );
font-size: 12px;
min-width: 0;
}
.os-woo-customer-window__order-total {
font-weight: 600;
text-align: end;
white-space: nowrap;
}
/* ----- Addresses ----- */
.os-woo-customer-window__addresses {
display: grid;
grid-template-columns: repeat( auto-fit, minmax( 200px, 1fr ) );
gap: 12px;
}
.os-woo-customer-window__address-label {
font-size: 11px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.04em;
color: var( --os-ui-fg-muted, rgba( 0, 0, 0, 0.6 ) );
margin-block-end: 2px;
}
.os-woo-customer-window__address-value {
overflow-wrap: anywhere;
}
/* ----- Actions ----- */
/*
* `margin-block-start: auto` pins the row to the bottom of a window
* taller than its content, so the buttons are where the hand expects
* them regardless of how much order history a customer has.
*/
.os-woo-customer-window__actions {
display: flex;
flex-wrap: wrap;
gap: 8px;
margin-block-start: auto;
padding-block-start: 4px;
}