/* =====================================================================
   components.css - shared backoffice component rules.

   Static stylesheet, deliberately NOT a Vite entry: in this repo every
   always-on, layout-level sheet lives in public/css/backend/ and is
   <link>ed from layout/css.blade.php (theme.css, layout/layout.css);
   resources/css/backend/ is reserved for page-scoped @vite bundles.
   Loaded from BOTH backend/layout/css.blade.php and
   backend/auth/layout/css.blade.php.

   The form rules below are hoisted out of 14 near-identical inline
   <style> blocks that differed only by their #form-id prefix. They are
   scoped to an OPT-IN `class="admin-form"` on the <form> element, which
   nothing carries yet - so this file is inert on every existing page and
   this change is provably pixel-identical. Wave 2 adds the class and
   deletes the per-page copies, form by form.

   Where a page keeps a #id-prefixed copy of a rule, that copy has
   specificity (1,0,1) against this file's (0,1,1) and still wins, so the
   two can coexist during the migration.
   ===================================================================== */

/* ---------------------------------------------------------------------
   Form section cards.
   Byte-identical in all 14 blocks: blog/category_form, blog/create,
   blog/tag_form, color_group/edit, customization_capability/edit,
   master_unit/edit, master_unit/group_edit, product_attributes/create,
   product_category/create, product_group/edit, product_master/edit,
   product_tags/edit, user/create, user_group/create.
   --------------------------------------------------------------------- */
.admin-form section{
    scroll-margin-top: var(--scroll-mt);
    background-color: var(--surface);
    box-shadow: var(--shadow-section);
}
.admin-form section .sec-header{
    border-bottom: 1px solid var(--border);
}
/* 13 of the 14 declare this. The 14th, blog/category_form, has no
   .sec-footer element at all, so hoisting cannot repaint it. */
.admin-form section .sec-footer{
    border-top: 1px solid var(--border);
}
.admin-form .sec-form-title{
    font-size: 16px;
    font-weight: 500;
    line-height: 1.0;
}

/* ---------------------------------------------------------------------
   EN/TH tab strip. 7 byte-identical copies: blog/category_form,
   blog/create, blog/tag_form, product_attributes/create,
   product_category/create, product_group/edit, product_tags/edit.

   NOT hoisted: the *other* two .nav-change definitions, in
   user/account.blade.php and product_attributes/index.blade.php. Those add
   position:relative, a transition and an .active::after underline bar.
   They are index/account screens, not .admin-form forms, and unifying
   them would either add or remove that underline. Left per-page.
   --------------------------------------------------------------------- */
.admin-form .nav-change > a,
.admin-form .nav-change > button{
    color: var(--accent);
    font-size: 13px;
    font-weight: 500;
    cursor: pointer;
}
.admin-form .nav-change > a:hover,
.admin-form .nav-change > a.active,
.admin-form .nav-change > button:hover,
.admin-form .nav-change > button.active{
    color: var(--accent-hover);
}

/* Keep the Pages list readable on narrow screens; the existing Bootstrap
   responsive wrapper provides the horizontal scroll when this minimum is hit. */
.page-list-table{
    min-width: 900px;
}
.page-list-table th:nth-child(1),
.page-list-table td:nth-child(1){
    min-width: 200px;
}
.page-list-table th:nth-child(2),
.page-list-table td:nth-child(2){
    min-width: 155px;
}
.page-list-table th:nth-child(3),
.page-list-table td:nth-child(3){
    min-width: 105px;
}
.page-list-table th:nth-child(4),
.page-list-table td:nth-child(4){
    min-width: 140px;
}
.page-list-table th:nth-child(5),
.page-list-table td:nth-child(5){
    min-width: 340px;
    white-space: nowrap;
}
.page-list-actions{
    display: flex;
    flex-wrap: nowrap;
    gap: .25rem;
    width: max-content;
}

/* Byte-identical in customization_capability/edit, master_unit/edit and
   master_unit/group_edit - and class="field-note" appears on no other
   page, so this cannot leak. (product_master/edit has the same 12px under
   a different class name, .code-locked-note; left alone, one occurrence.) */
.admin-form .field-note{
    font-size: 12px;
}

/* ---------------------------------------------------------------------
   Two-column aside (the "publish box floats right on desktop" layout).
   OPT-IN via an extra `r1-aside` class - `r1` alone is NOT enough.

   Why: product_group/edit and product_tags/edit both use
   class="row r1 m-0" with a .col1 and a .col3 but NO .col2, and neither
   declares any .r1 rules of its own. The min-width:768px branch here sets
   .col3{position:absolute}, which would yank their right-hand column out
   of flow. Only these 8 pages define these rules today and therefore
   qualify for the class:
       blog/category_form, blog/create, blog/tag_form, color_group/edit,
       product_attributes/create, product_category/create,
       user/create, user_group/create
   --------------------------------------------------------------------- */
.admin-form .r1-aside{
    position: relative;
}
.admin-form .r1-aside > .col1{
    order: 1;
}
.admin-form .r1-aside > .col2{
    order: 3;
}
.admin-form .r1-aside > .col3{
    order: 2;
}
@media screen and (min-width:768px){
    .admin-form .r1-aside > .col1{
        order: 1;
    }
    .admin-form .r1-aside > .col2{
        order: 2;
    }
    .admin-form .r1-aside > .col3{
        position: absolute;
        top: 0;
        right: 0;
        order: 3;
    }
}

/* ---------------------------------------------------------------------
   Page background. Exactly 5 pages inline
   #app-wrapper,#app-body{background-color:var(--themeColor-4)}:
   blog/create, product_attributes/create, product_category/create,
   user/create, user_group/create. They opt in by declaring the
   body-class section, which layout/app.blade.php yields into <body>.
   --------------------------------------------------------------------- */
/* #app-wrapper was the flex row that sat the old 190px sidebar beside the
   content.  The top navigation replaced it, so the element is gone and only
   #app-body remains to paint. */
body.page-grey #app-body{
    background-color: var(--surface-sunken);
}

/* ---------------------------------------------------------------------
   Empty states. Generalised from the .inq-* block in
   resources/js/components/backend/inquiry/inquiryList.vue.

   The dashed border / #fbfbfc panel from .inq-empty is deliberately NOT
   carried over: there the block sits *below* the table and needs its own
   frame, whereas x-empty-state and table.vue's version sit inside a
   <td> that already has the table's borders. Nothing uses these classes
   before this commit, so there is nothing to repaint.
   --------------------------------------------------------------------- */
.empty-state{
    padding: 2.5rem 1rem;
    text-align: center;
}
.empty-state-icon{
    font-size: 34px;
    color: var(--border);
    display: block;
    margin-bottom: .5rem;
}
.empty-state-title{
    font-size: 16px;
    font-weight: 600;
    margin: 0 0 .25rem;
}
.empty-state-text{
    font-size: 13px;
    color: #50575e;   /* no token has this value; --text-muted is #646970, which is different */
    max-width: 460px;
    margin-left: auto;
    margin-right: auto;
}

/* ---------------------------------------------------------------------
   Keyboard focus. Before this there was no :focus-visible rule anywhere
   in the backoffice except product/modelImagesPanel.vue:411-424, so
   keyboard users had only the UA default (which several resets hide).

   :where() holds the specificity at 0,0,0 so literally any component
   rule can override it without !important. outline does not participate
   in layout, and :focus-visible never matches a mouse click, so this is
   invisible to pointer users.
   --------------------------------------------------------------------- */
:where(a, button, [tabindex]):focus-visible{
    outline: 2px solid var(--focus-ring);
    outline-offset: 2px;
}

/* ---------------------------------------------------------------------
   AJAX validation errors.

   resources/js/lib/ajaxForm.js creates its 422 messages in JavaScript, as
   <small class="warning text-danger pe-ajax-error">, and tags the offending
   field's wrapper .pe-invalid. Bootstrap's .text-danger colours the text,
   but the two things that make a server-rendered error LOOK like an error
   cannot reach a DOM-created node:

     - the exclamation glyph. inputForm.vue draws it with a ::before on
       small.warning inside a <style scoped> block, and a scoped rule only
       matches elements Vue rendered (they carry its data-v- attribute). A
       node created by document.createElement() never does.
     - the red input border. .pe-invalid input{} exists, but scoped to
       #product-editor in resources/css/backend/productEditor.css, so it
       stops at the product editor and no other admin form gets it.

   Both are restated here behind the same .admin-form opt-in as everything
   above, so a form that adopts ajaxForm() gets the full treatment. This
   does NOT contradict the "not hoisted" note on small.warning::before
   below: that one is about the 19+ server-rendered small.warning elements,
   which keep their per-page behaviour. .pe-ajax-error exists only on nodes
   ajaxForm() itself created, and no page defines a rule for it.
   --------------------------------------------------------------------- */
.admin-form .pe-ajax-error::before{
    content: "\F33A";
    font-family: var(--bootstrap-icons);   /* declared in theme.css:34 */
    margin-right: 5px;
}
/* !important because the component stylesheets set border-color on the
   control itself at equal or higher specificity, and a field that is
   invalid has to win that. */
.admin-form .pe-invalid input,
.admin-form .pe-invalid select,
.admin-form .pe-invalid textarea{
    border-color: #d63638 !important;      /* --themeColor-13 */
    box-shadow: 0 0 0 2px rgba(214, 54, 56, .12) !important;
}

/* =====================================================================
   DELIBERATELY NOT HOISTED - each of these breaks something.

   1. small.warning::before  (the exclamation glyph, content "\F33A")
      class="warning" is on 19+ <small> elements across Blade AND Vue
      (inputForm.vue:28, checkListForm.vue:33, blog/tagForm.vue:83,
      product_tags/edit:145, ...) but only 3 pages define the ::before
      icon (blog/create, user/create, user_group/create). Hoisting would
      stamp a warning glyph onto every validation message in the app.

   2. label sizing. Two incompatible conventions, left as they are:
        label{font-size:13px;font-weight:500}   - 6 pages
            blog/create (declares both), product_attributes/create,
            product_category/create, product_group/edit, user/create,
            user_group/create
        label.mt-2{font-size:13px;font-weight:500} - 7 pages
            blog/create (declares both), color_group/edit,
            customization_capability/edit, master_unit/edit,
            master_unit/group_edit, product_master/edit, product_tags/edit
      Unifying on either one repaints every label in the other group.
      This needs a design decision, not a refactor.

   3. Bare .r1 order rules - see the r1-aside note above.

   4. Genuinely page-specific CSS, left entirely alone:
      #color-group-form's swatch picker, #blog-form's .tag-grid /
      .cover-preview / .helper-link, #capability-form .icon-preview-box,
      #create-user-form .see-pass, #product-master-form .code-locked-note,
      #my-account's tab panel, #loginPage, and the two large bespoke
      blocks at product_attributes/index.blade.php:8-62 and
      product/import.blade.php:8-115.
   ===================================================================== */

/* =====================================================================
   1001 BRANDING PRIMITIVES
   ---------------------------------------------------------------------
   Everything below is a shape Bootstrap 5.3 does not ship: the green
   action buttons, the KPI tiles, the compact filter controls, the data
   card, the status pill, the pagination squares and the page footer.

   Rule for this block: no literal colours, no literal radii, no literal
   heights.  Every value is a token from theme.css, so the whole admin
   re-themes from one file.  If you need a value that has no token, add
   the token - do not inline the hex here.

   Source of truth for the numbers: "1001 Blankets Admin UI - Design
   Spec" (button 44px / radius 10px, card radius 16px, table row 52px,
   pagination 36px) with the CSS/Bootstrap sheet supplying the table
   header ground and row hover it is the only one to define.
   ===================================================================== */

/* ---- action buttons -------------------------------------------------- */
.btn-1001-primary,
.btn-1001-outline,
.btn-1001-danger{
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 8px;
    min-height: var(--btn-h);
    padding: 5px 12px;
    border-radius: var(--radius-sm);
    border: 1px solid transparent;
    font-family: var(--font-ui);
    font-size: var(--ui-font-size-md);
    line-height: 1.4;
    font-weight: 500;
    white-space: nowrap;
    cursor: pointer;
    transition: var(--transition);
}
.btn-1001-primary{
    background: var(--brand);
    border-color: var(--brand);
    color: var(--on-brand);
}
.btn-1001-primary:hover,
.btn-1001-primary:focus{
    background: var(--brand-hover);
    border-color: var(--brand-hover);
    color: var(--on-brand);
}
.btn-1001-outline{
    background: var(--surface);
    border-color: var(--border);
    color: var(--text);
}
.btn-1001-outline:hover,
.btn-1001-outline:focus{
    border-color: var(--brand);
    color: var(--brand-hover);
}
.btn-1001-danger{
    background: var(--surface);
    border-color: var(--danger);
    color: var(--danger);
}
.btn-1001-danger:hover,
.btn-1001-danger:focus{
    background: var(--danger-soft);
    border-color: var(--danger);
    color: var(--danger-hover);
}
.btn-1001-primary[disabled],
.btn-1001-outline[disabled],
.btn-1001-danger[disabled],
.btn-1001-primary.disabled,
.btn-1001-outline.disabled,
.btn-1001-danger.disabled{
    opacity: .5;
    pointer-events: none;
}

/* ---- KPI tiles ------------------------------------------------------- */
.stat-card{
    display: flex;
    align-items: center;
    gap: var(--ui-space-md);
    min-height: var(--card-min-h);
    padding: var(--ui-space-md) var(--ui-space-lg);
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow-md);
    color: var(--text);
    text-decoration: none;
    transition: var(--transition);
}
a.stat-card:hover{
    border-color: var(--brand);
    color: var(--text);
}
.stat-card-icon{
    display: flex;
    align-items: center;
    justify-content: center;
    flex: 0 0 auto;
    width: 36px;
    height: 36px;
    border-radius: var(--radius-md);
    font-size: 17px;
}
.stat-card-body{
    display: flex;
    flex-direction: column;
    min-width: 0;
}
.stat-card-value{
    font-family: var(--font-ui);
    font-size: var(--ui-metric-size);
    line-height: 1.2;
    font-weight: 700;
    color: var(--text);
}
.stat-card-label{
    font-size: var(--ui-font-size-sm);
    line-height: 1.4;
    color: var(--text-muted);
}
/* The "+12% vs last month" line under a metric. */
.stat-card-meta{
    font-size: var(--ui-font-size-xs);
    line-height: 1.4;
    color: var(--text-muted);
}
.stat-card-tone-1 .stat-card-icon{ background: var(--tint-1-bg); color: var(--tint-1-fg); }
.stat-card-tone-2 .stat-card-icon{ background: var(--tint-2-bg); color: var(--tint-2-fg); }
.stat-card-tone-3 .stat-card-icon{ background: var(--tint-3-bg); color: var(--tint-3-fg); }
.stat-card-tone-4 .stat-card-icon{ background: var(--tint-4-bg); color: var(--tint-4-fg); }
.stat-card-tone-5 .stat-card-icon{ background: var(--tint-5-bg); color: var(--tint-5-fg); }

/* ---- page header band ------------------------------------------------ */
.page-header-icon{
    display: flex;
    align-items: center;
    justify-content: center;
    flex: 0 0 auto;
    width: 38px;
    height: 38px;
    margin-right: var(--ui-space-md);
    border-radius: var(--radius-md);
    background: var(--brand);
    color: var(--on-brand);
    font-size: 18px;
}
.page-header-subtitle{
    display: block;
    margin: 1px 0 0;
    font-size: var(--ui-font-size-md);
    line-height: 1.4;
    font-weight: 400;
    color: var(--text-muted);
}
/* The h1 in the redesigned band is the display face; the legacy clamp in
   layout.css still governs headers that do not opt in. */
.heading .heading-title.page-title-lg{
    font-family: var(--font-display);
    font-size: var(--ui-title-size);
    line-height: 1.25;
    font-weight: 600;
    color: var(--text);
}
/* Headers that opt into the subtitle band (<x-page-header :subtitle>) let the
   action buttons wrap under the title instead of squeezing it into a narrow
   column, and keep each button label on one line. Taken from the rules the
   dashboard had for itself in resources/css/backend/dashboard.css. */
.heading.page-header-rich{
    flex-wrap: wrap;
    row-gap: var(--ui-space-sm);
}
.heading.page-header-rich .page-header-heading{
    min-width: 0;
}
.heading.page-header-rich .heading-right{
    flex-wrap: wrap;
    row-gap: var(--ui-space-sm);
}
.heading.page-header-rich .heading-right > *{
    white-space: nowrap;
}
.page-header-banner{
    font-family: var(--font-accent);
    font-style: italic;
    font-weight: 700;
    font-size: clamp(15px, 1.3vw, 20px);
    line-height: 1.2;
    color: var(--text);
    text-align: right;
}

/* ---- data card + table ------------------------------------------------ */
.table-card{
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow-md);
    padding: var(--ui-space-md);
}
.filter-control{
    height: var(--filter-h);
    min-width: 90px;
    max-width: 160px;
    padding: 0 26px 0 10px;
    border: 1px solid var(--border);
    border-radius: var(--ui-radius-sm);
    background-color: var(--input-fill);
    font-size: var(--ui-font-size-md);
    font-weight: 400;
    color: var(--text);
}
.status-badge-active,
.status-badge-inactive{
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 2px 9px;
    border-radius: var(--radius-pill);
    font-size: var(--ui-font-size-xs);
    font-weight: 500;
    white-space: nowrap;
}
.status-badge-active{
    background: var(--success-soft);
    color: var(--green-700);
}
.status-badge-inactive{
    background: var(--surface-sunken);
    color: var(--text-muted);
}
.status-badge-active::before,
.status-badge-inactive::before{
    content: '';
    width: 6px;
    height: 6px;
    border-radius: var(--radius-pill);
    background: currentColor;
}
.thumb-42{
    width: var(--ui-thumb-size);
    height: var(--ui-thumb-size);
    border-radius: var(--ui-radius-sm);
    object-fit: cover;
    background: var(--surface-sunken);
}
.page-btn{
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: var(--page-btn);
    height: var(--page-btn);
    padding: 0 7px;
    border: 1px solid var(--border);
    border-radius: var(--ui-radius-sm);
    background: var(--surface);
    color: var(--text);
    font-size: var(--ui-font-size-md);
    font-weight: 500;
}
.page-btn:hover:not([disabled]){
    border-color: var(--brand);
    color: var(--brand-hover);
}
.page-btn.is-active{
    background: var(--brand);
    border-color: var(--brand);
    color: var(--on-brand);
}
.page-btn[disabled]{
    opacity: .45;
    pointer-events: none;
}

/* ---- page footer ------------------------------------------------------ */
.page-footer{
    margin-top: var(--section-gap);
    padding: var(--ui-space-md) 0 var(--ui-space-lg);
    border-top: 1px solid var(--border);
    background: var(--surface);
}
.page-footer-inner{
    max-width: 1920px;
    margin: 0 auto;
    padding: 0 var(--page-pad-x);
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
    justify-content: space-between;
    align-items: center;
    font-size: var(--ui-font-size-xs);
    line-height: 1.4;
    color: var(--text-muted);
}
.page-footer-leaf{
    margin-left: 6px;
    color: var(--brand);
}

/* ---------------------------------------------------------------------
   Grid cells that the SERVER renders as markup.

   productGridController builds the image, colour, stock and action cells
   as HTML strings and table.vue prints them through v-html, so a
   <style scoped> rule in the component cannot reach them - v-html output
   carries no scope attribute. They have to be styled from here, and the
   class names are part of that controller's contract: renaming one here
   without renaming it there leaves an unstyled element with no error
   anywhere. That is exactly how .tb-swatch first shipped invisible.
   --------------------------------------------------------------------- */

/* Placeholder for a variant with neither its own image nor a product
   cover. Deliberately local markup rather than the off-site dummyimage
   URL the old row used - that was one third-party request per empty
   cell, on a page that can show 100 rows. */
.tb-img-empty{
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: var(--ui-thumb-size);
    height: var(--ui-thumb-size);
    border: 1px dashed var(--border);
    border-radius: var(--ui-radius-sm);
    background: var(--surface-sunken);
    color: var(--text-muted);
    font-size: 14px;
}

/* Colour chip. `background` is set inline from the master's hex, which the
   controller matches against a hex literal before emitting; everything
   that is not colour lives here. */
.tb-swatch{
    display: inline-block;
    vertical-align: -2px;
    width: 12px;
    height: 12px;
    margin-right: 6px;
    border: 1px solid var(--border);
    border-radius: var(--radius-pill);
}

/* Row actions. Sized to the 36px touch target the rest of the admin uses
   rather than left as a bare icon. */
.tb-action{
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 28px;
    height: 28px;
    border-radius: var(--radius-sm);
    color: var(--text-muted);
    font-size: 14px;
    transition: var(--transition);
}
.tb-action:hover{
    background: var(--brand-soft);
    color: var(--brand-hover);
}

/* Identity columns must not wrap. A SKU broken across two lines doubles
   the row height and stops the eye scanning the column, which is the only
   thing that column is for. Size and the timestamp are the same case. */
.table-card td.cell-sku,
.table-card td.cell-size,
.table-card td.cell-updated_at,
.table-card td.cell-ref_no,
.table-card td.cell-color,
.table-card td.cell-stock,
.table-card td.cell-moq{
    white-space: nowrap;
}

/* =====================================================================
   BOOTSTRAP DENSITY OVERRIDES
   ---------------------------------------------------------------------
   Bootstrap's control and table defaults are mobile-first: 16px text,
   ~44px controls, roomy cell padding. On the 1920px desktop this admin
   is actually used on, that costs about a third of the rows that fit on
   a screen, for no gain - nobody is tapping these with a thumb.

   These are the density rules from the 1001 Admin typography spec, and
   they deliberately take priority over Bootstrap's defaults. Everything
   is a token, so the whole scale moves from theme.css.

   Scoped to nothing on purpose: it is the admin's baseline, and the one
   screen with its own shell (login) wants it too.
   ===================================================================== */

.form-control,
.form-select{
    min-height: var(--ui-control-height);
    height: var(--ui-control-height);
    padding: 5px 10px;
    font-size: var(--ui-font-size-md);
    border-radius: var(--ui-radius-sm);
}

/* A textarea is the exception: it grows, so a fixed height would fight
   whatever the author asked for. */
textarea.form-control{
    height: auto;
    min-height: 70px;
}

/* Bootstrap sizes a select's arrow gutter for 16px text. */
.form-select{
    padding-right: 26px;
    background-size: 12px 9px;
}

.form-label{
    margin-bottom: 3px;
    font-size: var(--ui-font-size-sm);
    font-weight: 500;
}

.btn{
    min-height: var(--ui-button-height);
    padding: 5px 12px;
    font-size: var(--ui-font-size-md);
    line-height: 1.4;
    border-radius: var(--ui-radius-sm);
}
/* .btn-lg exists for the odd marketing surface; it has no place on an
   admin screen, so it is pulled back to something merely emphatic. */
.btn-lg{
    min-height: 38px;
    padding: 6px 16px;
    font-size: var(--ui-font-size-base);
}
.btn-sm{
    min-height: 28px;
    padding: 3px 9px;
    font-size: var(--ui-font-size-sm);
}

.table > :not(caption) > * > *{
    padding: 7px 10px;
}
.table th{
    font-size: var(--ui-font-size-sm);
    font-weight: 600;
    vertical-align: middle;
}
.table td{
    font-size: var(--ui-table-size);
    vertical-align: middle;
}

.card{
    border-radius: var(--ui-radius-md);
}
.card-header{
    padding: 10px 14px;
}
.card-body{
    padding: var(--ui-space-md) var(--ui-space-lg);
}

.badge{
    font-size: var(--ui-font-size-xs);
    font-weight: 500;
}

/* Section and card titles. h1 is left to .heading-title / the page-title
   classes, which already resolve from --ui-title-size. */
.sec-form-title,
h2, .h2{
    font-size: var(--ui-font-size-lg);
    font-weight: 600;
    line-height: 1.3;
}
h3, .h3,
h4, .h4{
    font-size: var(--ui-font-size-base);
    font-weight: 600;
    line-height: 1.3;
}
/* Shared PWA controls live outside Vue mount points in both Blade shells. */
.backend-pwa-offline {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: var(--ui-space-md);
    padding: var(--ui-space-sm) max(var(--ui-space-md), env(safe-area-inset-right)) var(--ui-space-sm) max(var(--ui-space-md), env(safe-area-inset-left));
    background: var(--navy);
    color: var(--text-invert);
    text-align: center;
}

.backend-pwa-controls [hidden] { display: none !important; }

.backend-pwa-disabled { opacity: .5; }

.backend-pwa-back { display: none; }

.backend-pwa-update {
    position: fixed;
    top: calc(var(--topnav-h) + var(--ui-space-md) + env(safe-area-inset-top));
    right: calc(var(--ui-space-md) + env(safe-area-inset-right));
    z-index: 1090;
    max-width: min(350px, calc(100vw - 2 * var(--ui-space-md)));
}

@media (display-mode: standalone) {
    body { padding-bottom: env(safe-area-inset-bottom); }
    .backend-pwa-back {
        display: block;
        padding: calc(var(--ui-space-sm) + env(safe-area-inset-top)) max(var(--ui-space-md), env(safe-area-inset-right)) var(--ui-space-sm) max(var(--ui-space-md), env(safe-area-inset-left));
        background: var(--surface);
    }
}

@media (max-width: 575.98px) {
    .backend-pwa-offline { flex-wrap: wrap; }
    .backend-pwa-update { left: var(--ui-space-md); right: var(--ui-space-md); max-width: none; }
}
