/* Bits every admin screen ended up with a copy of, because each was lifted
   from its own mock page. Defined once here and linked before the page
   sheets. Anything the design system already owns lives in admin.css - this
   is only for what the screens genuinely share and admin.css does not have. */

.empty-row{text-align:center;color:var(--muted);padding:36px 0}
.topbar-title{font-size:16px;font-weight:700;color:var(--primary);}
.topbar-actions{display:flex;gap:8px;align-items:center;}

/* Room under the last row on every screen. Without it a grid or a table ends flush
   against the bottom of the window with nothing to say the list has finished. The
   shell's .main-scroll carries no padding of its own. */
.main-scroll{padding-bottom:24px}

/* The three tokens the mock's screens use and admin.css does not. They were the only
   thing left between our screens and the mock once everything else was measured
   equal: a firmer border, a slightly rounder corner, and the mock's own shadow.

   admin.css stays byte-identical to the mock's shared sheet on purpose - that is a
   thing we can check - so the difference lives here instead, in one place. Deleting
   this block puts every admin screen back on the unified scales agreed on 29 August. */
:root{
  --border:#D6E0F0;
  --radius-lg:14px;
  --shadow:0 2px 8px rgba(0,51,160,.08);
  --elev-1:0 2px 8px rgba(0,51,160,.08);
}

/* ─── The image picker ────────────────────────────────────────────────
   Ported from inventory/products.html, where the same drop zone appears three
   times: the product's images, the brand's logo, and - through the ti-photo
   placeholder in the tree - the category's. Shared here rather than in a page
   sheet because banners and collections need the same box.

   Two rules below are not in the mock. .image-drop is given position:relative
   so the file input can be stretched over it: the mock opens the picker with
   an onclick that reaches for the input by id, and an invisible input covering
   the box does the same without interop - and brings native drag-and-drop with
   it, which is what the box's own text promises. Neither rule moves anything
   the eye can measure. */
.image-drop{border:2px dashed var(--border);border-radius:var(--radius);padding:30px;text-align:center;cursor:pointer;transition:.15s;margin-bottom:10px;position:relative;}
.image-drop:hover{border-color:var(--primary);background:var(--primary-ll);}
.image-drop input[type=file]{position:absolute;inset:0;width:100%;height:100%;opacity:0;cursor:pointer;}
.image-drop-icon{font-size:32px;margin-bottom:6px;}
.image-drop-text{font-size:13px;color:var(--muted);}

/* The mock writes this one inline on the brand logo box. A single image needs
   less room than a gallery, so it is a named variant instead. */
.image-drop-sm{padding:16px;}

.image-previews{display:flex;gap:10px;flex-wrap:wrap;}
.image-preview{position:relative;width:80px;height:80px;border-radius:8px;overflow:hidden;border:2px solid var(--border);}
.image-preview img{width:100%;height:100%;object-fit:cover;}
.image-preview .img-primary{position:absolute;bottom:0;left:0;right:0;background:var(--primary);color:#fff;text-align:center;font-size:10px;padding:2px;}
.image-preview .img-del{position:absolute;top:3px;right:3px;width:18px;height:18px;background:var(--danger);color:#fff;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:11px;cursor:pointer;}

/* The mock's own empty preview, written inline there. */
.image-preview-empty{width:100%;height:100%;background:var(--bg);display:flex;align-items:center;justify-content:center;font-size:24px;}

/* The mock writes this margin inline under the drop zone's prompt. */
.image-drop .form-hint{margin-top:4px;}

/* What went wrong under the drop zone, in the same place the hint sits. */
.upload-error{color:var(--danger);}

/* ─── Waiting ─────────────────────────────────────────────────────────
   The shell paints in about ninety milliseconds and the rows arrive around
   three seconds later, because the database answers from another building.
   Three seconds of blank page is the thing being fixed here.

   A spinner in the middle says only that something is happening. These say
   what is coming and hold its place, so nothing jumps when the real rows
   land. The trick is that a skeleton is built out of the real components:
   put .sk on a leaf and its own text sets its height and width, so the
   placeholder cannot drift out of step with the thing it stands in for. */
.sk{
  color:transparent!important;
  /* Forced for the same reason the colour is: the thing standing in for a card has
     the card's own class on it, and a white card background would win otherwise -
     the placeholder then reads as an empty card rather than as a wait.

     --slate-ll is #F1F5F9 and the page is #F0F3F9: one point apart, so on the page
     the placeholder was invisible and inside a card it barely registered. A step
     down the same scale is the difference between a shape and a smudge. */
  background:var(--slate-l)!important;
  border-color:transparent!important;
  box-shadow:none!important;
  border-radius:6px;
  position:relative;
  overflow:hidden;
  user-select:none;
  pointer-events:none;
}
.sk *{visibility:hidden}
.sk::after{
  content:'';
  position:absolute;
  inset:0;
  transform:translateX(-100%);
  background:linear-gradient(90deg,transparent,rgba(255,255,255,.9),transparent);
  animation:sk-sweep 1.4s ease-in-out infinite;
}
@keyframes sk-sweep{100%{transform:translateX(100%)}}

/* The sweep is decoration on top of information. Anyone who has asked for
   less movement still gets the shape, just still. */
@media (prefers-reduced-motion:reduce){
  .sk::after{animation:none;display:none}
}


/* ─── The narrowing chip ──────────────────────────────────────────────
   A yes-or-no question with its own count, from the orders screen's ops bar.
   It lives here now that the catalogue's reference tabs ask the same kind of
   question - which of these are stopped, which are empty, which have no
   picture - and the count is what makes it worth pressing: it says how many
   before you press. */
.ops-chip{display:inline-flex;align-items:center;gap:5px;padding:5px 11px;border:1px solid var(--border);border-radius:20px;background:var(--card);font-size:12px;font-weight:600;color:var(--text2);cursor:pointer;font-family:inherit;transition:.15s}
.ops-chip b{background:var(--bg);border-radius:10px;padding:0 6px;font-size:11px}
.ops-chip:hover{border-color:var(--primary);color:var(--primary)}
.ops-chip.is-on{background:var(--primary);border-color:var(--primary);color:#fff}
.ops-chip.is-on b{background:rgba(255,255,255,.22);color:#fff}


/* ─── The category picker ─────────────────────────────────────────────
   Two halves of one control: what is chosen, and what there is to choose from.
   The chosen half sits on top because that is the answer - the old <select
   multiple> hid it below the fold of a four-row box, so the one thing you
   needed to read back was the one thing you could not see. */
.cat-pick{border:1px solid var(--border);border-radius:var(--radius);overflow:hidden;background:var(--card)}

.cat-pick-chosen{display:flex;flex-wrap:wrap;gap:6px;padding:9px 10px;background:var(--bg);border-bottom:1px solid var(--border);min-height:44px;align-items:center}
.cat-pick-none{font-size:12px;color:var(--muted)}

.cat-chip{display:inline-flex;align-items:center;gap:5px;background:var(--card);border:1px solid var(--border);border-radius:20px;padding:3px 4px 3px 4px;font-size:12px;color:var(--text2)}
.cat-chip-text{padding:0 2px}

/* The lead is marked on the chip rather than implied by position: a list carries
   no intent, and "the first one" was never something anyone could set. */
.cat-chip.is-lead{border-color:var(--primary);background:var(--primary-ll);color:var(--primary);font-weight:700}
.cat-chip-lead,.cat-chip-x{display:inline-flex;align-items:center;justify-content:center;width:20px;height:20px;border:none;border-radius:50%;background:none;cursor:pointer;color:var(--muted);font-size:13px;padding:0;font-family:inherit}
.cat-chip-lead:hover{color:var(--warning);background:var(--bg)}
.cat-chip.is-lead .cat-chip-lead{color:var(--warning)}
.cat-chip-x:hover{color:var(--danger);background:var(--danger-l)}

.cat-pick-tree{max-height:190px;overflow-y:auto;padding:6px 0}
.cat-pick-row{display:flex;align-items:center;gap:8px;padding:5px 10px;font-size:13px;color:var(--text2);cursor:pointer}
.cat-pick-row:hover{background:var(--bg)}
.cat-pick-row.is-on{color:var(--primary);font-weight:700}
.cat-pick-row input{cursor:pointer;margin:0}
.cat-pick-row.is-off .cat-pick-name{color:var(--muted)}
.cat-pick-off{font-size:10px;background:var(--bg);color:var(--muted);border-radius:10px;padding:1px 6px}


/* ─── Margin, read off the prices as they are typed ───────────────────
   Two figures the form can work out for itself. Cost and price are entered a
   line apart, and the number that decides whether the price is worth setting
   was the one nobody had. */
.profit-badge{display:inline-flex;align-items:center;gap:4px;background:var(--primary-ll);color:var(--primary);border-radius:20px;padding:4px 11px;font-size:12px;font-weight:700;white-space:nowrap}
.profit-badge.is-loss{background:var(--danger-l);color:var(--danger)}

/* A hint that belongs to the label rather than to the box under it. */
.form-hint-inline{font-size:11px;font-weight:400;color:var(--muted)}

/* Hints under a field used to push the next row down with them, so two columns
   started at two heights. Each cell now stands on its own top edge. */
.modal .form-grid,.modal .form-grid-2,.modal .form-grid-3{align-items:start}

/* Said once, above the form, when the product being edited is retired. */
.archived-note{display:flex;align-items:center;gap:8px;flex-wrap:wrap;background:var(--bg);border:1px solid var(--border);border-radius:var(--radius);padding:10px 12px;font-size:13px;color:var(--text2);margin-bottom:14px}
.archived-note .ti{color:var(--muted)}
.archived-note button{margin-inline-start:auto}

/* The unit a box is measured in, sitting against it. The mock wrote it as a .btn with
   its styling inline; it is not a button and nothing here is clickable. */
.input-suffix{display:flex;align-items:center;padding:0 11px;background:var(--bg);border:1px solid var(--border);color:var(--muted);font-size:13px;white-space:nowrap}

.prd-margins{display:flex;gap:8px;flex-wrap:wrap;align-items:center;min-height:38px}

/* Four states side by side rather than folded into a dropdown. It is the field that
   decides whether the product exists to a customer, and it was the last row of a
   section about quantities. */
.state-pick{display:flex;gap:6px;flex-wrap:wrap;margin-bottom:8px}
.state-btn{padding:7px 14px;border:1px solid var(--border);border-radius:20px;background:var(--card);font-size:13px;font-weight:600;color:var(--text2);cursor:pointer;font-family:inherit;transition:.15s}
.state-btn:hover:not(:disabled){border-color:var(--primary);color:var(--primary)}
.state-btn.is-on{background:var(--primary);border-color:var(--primary);color:#fff}
.state-btn:disabled{opacity:.5;cursor:not-allowed}

/* admin.css sets the type on .toggle-info label and .toggle-info small - the element
   form the settings screen uses. The mock writes the same row with divs carrying
   .toggle-label and .toggle-desc, and those two get position but no type, so the
   label came out at body size and the description was not muted. */
.toggle-label{font-size:13px;font-weight:700;color:var(--text)}
.toggle-desc{font-size:12px;color:var(--muted)}

/* The children hang off one hairline, the same way the categories tree draws them.
   Indentation alone at this size read as a wobble rather than as a level. */
.cat-pick-tree{position:relative}
.cat-pick-row{position:relative}
.cat-pick-row[data-depth]::before{content:'';position:absolute;top:0;bottom:0;width:1px;background:var(--border)}
.cat-pick-row[data-depth="1"]::before{inset-inline-start:18px}
.cat-pick-row[data-depth="2"]::before{inset-inline-start:36px}
.cat-pick-row[data-depth="3"]::before{inset-inline-start:54px}

/* The star that hands a tile the card image, opposite the delete cross. The mock has
   no such control - it makes the first picture primary, and "first" stops being
   something you can point at once the tiles wrap onto a second line.

   It stays visible rather than appearing on hover: this screen already decided once
   that a control hidden behind the mouse is a control nobody finds, and it has to be
   in the tab order anyway. */
.image-preview .img-lead{position:absolute;top:3px;left:3px;width:18px;height:18px;background:rgba(15,23,42,.5);color:#fff;border:none;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:11px;cursor:pointer;padding:0;transition:background .15s}
.image-preview .img-lead:hover{background:var(--warning)}
.image-preview .img-del{border:none;padding:0;font-family:inherit}

.tag-pick{display:flex;flex-wrap:wrap;gap:6px}


/* ─── The product form, section by section ────────────────────────────
   Eight sections down one white sheet gave every one of them the same weight,
   and the only thing separating "التسعير" from "المخزون" was a line under a
   heading. Each one is a card now, and the body they sit on is the shaded
   ground - a white card on a white sheet has no edge, and the edge is the
   whole point.

   Scoped to this modal on purpose: .form-section is shared with every other
   modal in the admin, and most of them carry one section where a card would
   be a box drawn around the only thing in the room.

   The head is the card's own strip rather than a rule under some text: the
   title's 2px underline plus a card border is two devices doing one job. This
   is the same body the reference panels on this screen already use. */
.prd-modal .modal-body{background:var(--bg)}
.prd-modal .form-section{background:var(--card);border:1px solid var(--border);border-radius:var(--radius-lg);padding:0 16px 16px;margin-bottom:12px}
.prd-modal .form-section:last-child{margin-bottom:0}
.prd-modal .form-section-title{margin:0 -16px 14px;padding:11px 16px;border-bottom:1px solid var(--border);border-width:0 0 1px;border-color:var(--border)}

/* The picker drops its own frame inside a card: a bordered box in a bordered box
   draws the same boundary twice. Same call the reference panels made when a table
   moved inside one. */
.prd-modal .cat-pick{border:none;border-radius:0;margin:0 -16px -16px;background:none}
.prd-modal .cat-pick-chosen{border-top:0}
.prd-modal .cat-pick-tree{max-height:210px}


/* ─── Form type inside a modal ────────────────────────────────────────
   The label and the hint under it were the same colour (--muted) at 12px and
   11px, so a field name and the note about it read as one grey clump. The
   name is the darker, heavier one now and the note stays quiet - weight and
   colour do the separating, not size alone.

   Nothing in the form set a line-height, so every line sat at the browser's
   "normal", which is tight for Arabic and tightest on the small text that
   wraps. And a textarea was 13px while the input beside it was 14 - the same
   kind of box in two sizes. */
.modal .form-group>label,.modal .form-label{font-size:13px;font-weight:700;color:var(--text2);line-height:1.5}
.modal .form-hint{font-size:12px;line-height:1.55}
.modal .form-hint-inline{font-size:12px}
.modal .form-input,.modal .form-select,.modal .form-textarea{line-height:1.5}
.modal .form-textarea{font-size:14px}
.modal .form-group{gap:6px}

/* A grid's gap only works inside that grid. Two of them stacked in one section
   met with nothing between them, which is how a hint ended up touching the
   label of the row below it. Adjacent margins collapse, so the block after the
   section title keeps the title's own spacing rather than doubling it. */
.modal .form-section>.form-grid,
.modal .form-section>.form-grid-2,
.modal .form-section>.form-grid-3{margin-top:14px}

/* A colour field is a swatch, not a text box. .form-input's 9px padding squeezed the
   browser's swatch down to a dash, so the control read as empty. And the field is
   optional, so there is a way back out of it beside the swatch. */
.color-pick{display:flex;align-items:center;gap:8px}
.color-swatch{width:52px;height:38px;padding:3px;border:1px solid var(--border);border-radius:8px;background:var(--card);cursor:pointer;flex-shrink:0}
.color-swatch::-webkit-color-swatch-wrapper{padding:0}
.color-swatch::-webkit-color-swatch{border:none;border-radius:5px}

/* ─── Let nested text keep its container's size ───────────────────────
   admin.css carries `*{font-family:…;font-size:14px}`. The font-family half is
   deliberate - it is what stops the icon font from leaking into ordinary text.
   The font-size half is not: on `*` it does not cascade, it *reassigns*, so any
   element nested inside a sized container snaps back to 14px however small the
   container is. The mock hides this because it writes those lines as bare text
   nodes; the moment a @foreach wraps one in a <span> the size jumps.

   Measured: the kanban card's item list rendered 14px inside an 11px box, and
   fifteen more places across the admin did the same - a <strong> inside an 11px
   hint, the category picker's names, the dashboard's donut legend, the invoice
   sort caret. Restoring inheritance for phrasing elements fixes all of them and
   changes nothing else: cells and headings keep their own rules, which outrank
   this one. Deliberately not `*` - table cells rely on the 14px.

   dt/dd joined on 12 September for the same reason: the points scheme card sets its
   spec list to 12px and it was measured rendering at 14. They are phrasing, not
   cells - nothing in the admin sizes a definition list off the 14px default. */
span,b,strong,em,small,a,label,dt,dd{font-size:inherit}

/* ── Invoice modal ─────────────────────────────────────────────────────────────
   Everything else this modal draws is already in admin.css, which was extracted
   from the very screen it is ported from. These are the few the mock kept in its
   own page <style>, plus the modifiers we needed. They live here rather than in a
   page file because the modal opens from both the invoices screen and the order. */
.ihs-paid{color:#4ADE80;}
.ihs-due{color:#FCD34D;}
.mv-key{color:var(--primary);}

/* The tax row explains itself: the rate as it was stored, and whether the customer
   was exempt at the time. Both are snapshots, which is what the note underneath is
   there to say. */
.tot-tax-lbl{display:flex;align-items:center;gap:6px;}
.tax-rate-chip{
    background:var(--primary-ll);color:var(--primary);font-size:10px;font-weight:700;
    padding:1px 7px;border-radius:10px;border:1px solid var(--primary-l);
}
.tax-exempt-chip{
    background:var(--slate-ll);color:var(--text2);font-size:10px;font-weight:700;
    padding:1px 7px;border-radius:10px;border:1px solid var(--border);
}
.tax-note{
    display:flex;align-items:center;gap:6px;margin-top:8px;
    font-size:11px;line-height:1.5;color:var(--muted);
}
.pay-empty{text-align:center;padding:14px;color:var(--muted);font-size:13px;}
.inv-hero-state{display:flex;align-items:center;gap:6px;}

/* ── Buttons that are waiting, and buttons that cannot be pressed ──────────────
   admin.css carries no :disabled rule for .btn at all, so every guarded button
   in the admin drew exactly like a live one - a manual order modal with nothing
   in it yet still looked ready to submit. The guard was there; only the saying
   of it was missing. */
.btn:disabled,
.btn[disabled]{opacity:.45;cursor:not-allowed;box-shadow:none;}
.btn:disabled:hover,
.btn[disabled]:hover{filter:none;}

/* The database is hosted away from us, so a round trip is a quarter of a second
   at best. A button that looks untouched for that long invites a second press:
   this says the press landed, on the button that was pressed. */
.btn-spin{
    display:inline-block;width:13px;height:13px;flex:0 0 auto;
    border:2px solid currentColor;border-top-color:transparent;border-radius:50%;
    animation:btn-spin .6s linear infinite;
}
@keyframes btn-spin{to{transform:rotate(360deg)}}

/* Anyone who asked for less movement still gets the disc, just still - the same
   trade the loading skeletons make. */
@media (prefers-reduced-motion:reduce){
    .btn-spin{animation:none;border-top-color:currentColor;opacity:.5;}
}

/* ── Toast ─────────────────────────────────────────────────────────────────────
   admin.css fades opacity alone and sets pointer-events:none, so it arrived
   without moving and could not be dismissed by the click already wired to it.
   Messages stack here, one under the next, drawn by a single host in the layout. */
.toast-stack{
    position:fixed;top:64px;inset-inline-end:20px;z-index:999;
    display:flex;flex-direction:column;align-items:flex-start;gap:8px;
    pointer-events:none;
}

/* admin.css anchors the toast to the bottom and centres it on the page, which in a
   full-width screen lands it in the middle of the header text - Peter's words were
   that it arrives "in the middle of the page, in the middle of the words". It moves
   to the free corner instead: the sidebar owns the inline start, so the inline end
   is the one edge with nothing under it. */
.toast{
    position:relative;
    top:auto;bottom:auto;left:auto;transform:none;
    max-width:min(420px,calc(100vw - 40px));
    padding:11px 16px 13px;font-size:13px;font-weight:700;line-height:1.45;
    border-radius:var(--radius);
    box-shadow:var(--elev-3);
    display:flex;align-items:center;gap:9px;
    white-space:normal;
    overflow:hidden;
    pointer-events:auto;cursor:pointer;
    /* An animation, not a transition: the element is inserted already showing, so
       there is no second frame for a transition to run between. */
    animation:toast-in .28s cubic-bezier(.2,.9,.3,1.25) both;
}

/* It says how long it has left. A message that vanishes without warning reads as a
   glitch; a bar draining says the screen is done talking. */
.toast::after{
    content:"";
    position:absolute;bottom:0;inset-inline:0;height:2px;
    background:rgba(255,255,255,.5);
    transform-origin:right;
    animation:toast-drain 5s linear forwards;
}

@keyframes toast-in{
    from{opacity:0;transform:translateX(-16px) scale(.96);}
    to{opacity:1;transform:none;}
}

@keyframes toast-drain{
    from{transform:scaleX(1);}
    to{transform:scaleX(0);}
}

/* Green says it happened, red says it did not. Sharing one dark box made a
   refusal read like a confirmation until you had read the sentence. */
.toast.is-ok{background:var(--success);color:#fff;}
.toast.is-bad{background:var(--danger);color:#fff;}

@media (prefers-reduced-motion:reduce){
    /* The bar stays - it carries information, not decoration. */
    .toast{animation:toast-fade .22s ease both;}
    @keyframes toast-fade{from{opacity:0;}to{opacity:1;}}
}

/* A cancelled order, in the colour the system already uses for a refusal.
   Not by repainting .st-cancelled: that class also carries "مؤرشف" on a product and
   "مرفوض" on a customer, and archiving a product is the ordinary end of its life,
   not a failure - it must not turn red along with this. */
/* On and off, for the rows that are switched rather than moved through a cycle -
   a delivery area, say. The mock reaches for .badge here, and admin.css has no base
   rule for that name (only the colours and the bell's own), so it rendered as bare
   coloured text with no shape at all - the trap recorded on 9 September. */
.status-badge.st-on{background:var(--success-l);color:var(--success);}
.status-badge.st-off{background:var(--warning-l);color:var(--warning);}

.status-badge.st-void{background:var(--danger-l);color:var(--danger);}

/* ── Buttons that are really links ──────────────────────────────────────────
   The mock only ever draws these as <button>, so admin.css never had to say it.
   An action that opens its own tab has to be an anchor - and an anchor arrives
   underlined, in the browser's own colour, and as an inline box that sits off the
   baseline of the buttons beside it. */
a.btn, a.ra-btn, a.rm-item, a.oc-print { text-decoration: none; }

/* Colour is said separately: `a.oc-print` would out-rank the card's own rule and
   turn the quiet corner icon back into body text. */
a.btn, a.ra-btn { color: inherit; }
a.rm-item { color: var(--text); }
a.btn-secondary { color: var(--text2); }
a.btn-secondary:hover { color: var(--primary); }
a.ra-btn { display: inline-flex; }

/* ── Avatar tints ───────────────────────────────────────────────────────────
   .cust-av and the profile circle both set color:#fff and no background - in the
   mock the colour is written inline on each row, because there the customers are
   invented. Ported without it, every avatar was white on white. The eight are the
   mock's own, picked from the name so one customer keeps one colour everywhere. */
.av-1{background:#0033A0}
.av-2{background:#16A34A}
.av-3{background:#D97706}
.av-4{background:#DC2626}
.av-5{background:#00A3E0}
.av-6{background:#475569}
.av-7{background:#0891B2}
.av-8{background:#059669}

/* The tick column on every list. It was an inline width on each screen's own <th>. */
.cb-col{width:36px}

/* ── Row actions are not a hover secret ─────────────────────────────────────
   admin.css hides them at opacity:0 until the row is hovered. On the customers
   list that made the only way into a profile invisible - nothing on the screen
   said the eye was there. They are also unreachable that way by keyboard, and
   unfindable on a touch screen. Same reasoning as the brand card on 9 September:
   quiet in the corner, but present. */
.row-actions{opacity:1}

/* The quiet red. It was written for the order modal's cancel button - the only button
   that deletes an order was the loudest thing in the room - and the customer profile's
   block button is the same kind of act, so it stops belonging to one page. */
.btn-danger-soft{background:var(--danger-l);color:var(--danger)}
.btn-danger-soft:hover{background:var(--danger);color:#fff}

/* The invoiced figure under an order's total, when a short pick made them differ.
   Not a discount and not part of the sum, so it sits below the final line and is
   drawn as a note rather than as another row of the arithmetic. */
.tot-row.tot-billed{color:var(--muted);font-size:12px;border-top:1px dashed var(--border);padding-top:8px}


/* ── One head for every table ────────────────────────────────────────────────
   admin.css draws .items-table's head as a solid navy bar, and the mock's other
   tables grey. Both shapes were in the admin at once - and inside the order modal
   both appeared in the same modal, because only one of its two tables sits in an
   items-table-wrap (the wrap carries the grey rule). A table head is furniture, not
   a thing to distinguish tables by; two colours for it read as two kinds of object.
   Peter's call on 11 September: grey everywhere. */
.items-table th{background:var(--bg);color:var(--muted);padding:10px 14px;text-align:right;font-size:11px;font-weight:700;border-bottom:2px solid var(--border)}


/* ── A list is one body ──────────────────────────────────────────────────────
   A table running wall to wall with a white pager bar floating under it reads as
   two loose things on the page rather than as one list. The panel gives them a
   shared edge and puts the pager inside it as a footer - the shape the products
   tab was rebuilt into on 9 September and the customers list on 10 September.

   Named for the job, not the screen: the invoices list asked for the same thing
   on 11 September, which is when .cust-panel stopped being a customers rule. */
.list-panel{
    margin:0 20px 20px;
    border:1px solid var(--border);
    border-radius:var(--radius-lg);
    background:var(--card);
    overflow:hidden;
}

/* The selection bar as its own object. The mock draws it edge to edge with a rule
   under it, which glues it to the table head - and it is not a header, it is a thing
   that appeared because you ticked rows. So it takes the table's radius and a gap
   under it, and arrives with a small movement rather than blinking into place. */
.bulk-bar.bulk-panel{
    margin:0 20px 12px;padding:10px 14px;
    border:1px solid var(--primary-l);border-radius:var(--radius-lg);
    box-shadow:var(--elev-1);
    animation:bulk-in .18s ease both;
}
@keyframes bulk-in{from{opacity:0;transform:translateY(-6px)}to{opacity:1;transform:none}}
@media (prefers-reduced-motion:reduce){.bulk-bar.bulk-panel{animation:none}}


/* ── The pager, one shape for every list ─────────────────────────────────────
   Written for the orders list and now worn by all four, so it lives here rather
   than in orders.css. The bar is the panel's footer: it takes the page background
   and a hairline above it rather than floating as a white strip of its own. */
.table-foot{display:flex;align-items:center;gap:12px;flex-wrap:wrap;padding:11px 16px;border-top:1px solid var(--border);font-size:12px;color:var(--muted);background:var(--bg)}
.pager-mini{margin-inline-start:auto;display:flex;gap:5px;align-items:center}
.pgm{min-width:30px;height:30px;padding:0 9px;border:1px solid var(--border);border-radius:8px;background:var(--card);font-size:12px;font-weight:700;color:var(--text2);cursor:pointer;font-family:inherit}
.pgm.is-active{background:var(--primary);border-color:var(--primary);color:#fff}
.pgm:disabled{opacity:.45;cursor:not-allowed}
.pg-size{padding:5px 9px}

/* ── The controls over a list are one body ───────────────────────────────────
   admin.css draws .filters-row as a white bar from wall to wall, and anything a
   screen adds under it (status tabs, attention chips) sits on the page inset by
   20px. That is three bands with three different edges over a table with a fourth.
   In the panel they share one edge with the list below them, and the hairline under
   the search row is the only thing separating them. */
.control-panel{
    margin:12px 20px;
    border:1px solid var(--border);
    border-radius:var(--radius-lg);
    background:var(--card);
    overflow:hidden;
}
.control-panel>.filters-row{margin:0;border-bottom:1px solid var(--border)}
/* Alone in the panel it needs no rule under it - the panel's own edge is there,
   and the two together draw the same line twice. It keeps its own padding for the
   same reason: the band is the panel, so it must not be padded a second time. */
.control-panel>.filters-row:last-child{border-bottom:none}
.control-panel>.status-tabs,
.control-panel>.ops-bar{padding:12px 16px 0}
.control-panel>.status-tabs:last-child,
.control-panel>.ops-bar:last-child{padding-bottom:12px}

/* Status tabs. Written for the orders screen and asked for by the returns screen too,
   so the shape lives here and each screen keeps only its own stage tints. */
.status-tabs{display:flex;gap:8px;flex-wrap:wrap;padding:0 20px 12px;flex-shrink:0}
.st-tab{display:inline-flex;align-items:center;gap:6px;padding:8px 14px;border:1px solid transparent;border-radius:var(--radius);font-size:13px;font-weight:700;line-height:1.45;color:var(--text);cursor:pointer;font-family:inherit;transition:.15s}
.st-tab .st-n{font-size:11px;font-weight:800;padding:1px 8px;border-radius:10px;background:rgba(0,0,0,.1)}
.st-tab i{font-size:14px}
.st-tab:hover{transform:translateY(-1px)}
.st-tab.is-on{border-color:currentColor;box-shadow:var(--elev-1)}
.st-tab.is-on .st-n{background:rgba(0,0,0,.16)}

/* ─── طي القائمة الجانبية ──────────────────────────────────────────────
   admin.css draws the rail at a flat 240px, and it stays byte-identical to the
   mock, so the collapse lives here.

   Nothing outside the rail needs a rule: .main is flex:1 and takes the width back
   by itself. The labels are not hidden, they are clipped - the rail turns its
   overflow off and every row is nowrap, so the text slides out under the edge as
   the width closes instead of reflowing mid-animation. The icons move 12px to sit
   centred in the narrow rail, and .nav-item already carries a transition in
   admin.css, so they slide there rather than jump. */
.sidebar{transition:width .24s cubic-bezier(.4,0,.2,1);overflow-x:hidden}
.sidebar-nav{overflow-x:hidden}
.nav-item{white-space:nowrap}
.logo-names,.nav-label{transition:opacity .14s ease}

/* The foot button wears the nav row's shape so it reads as part of the list, but
   quieter - it is not a place to go. */
.nav-collapse{display:flex;align-items:center;gap:9px;width:100%;padding:8px 14px;
    background:none;border:none;border-radius:var(--radius);color:#64748B;
    font-size:13px;font-family:inherit;text-align:start;cursor:pointer;
    white-space:nowrap;transition:.12s}
.nav-collapse:hover{background:rgba(255,255,255,.05);color:#CBD5E1}
.nav-collapse .ti{transition:transform .24s cubic-bezier(.4,0,.2,1)}

/* The collapsed state is an attribute on <html>, written by assets/nav.js before the
   first paint - not a class on the rail. A class would put Blazor in charge of how
   wide it is, and Blazor does not speak until the circuit connects, which is exactly
   the moment the fold would be visible on every hard refresh. */
[data-nav="collapsed"] .sidebar{width:72px}
[data-nav="collapsed"] .sidebar-logo{padding-inline:17px}
/* .nav-item carries a 3px border on the start edge for the active marker, and it
   sits outside the padding - so the two rows need different insets to put their
   icons on the same centre line. */
[data-nav="collapsed"] .nav-item{padding-inline:23px 26px}
[data-nav="collapsed"] .nav-collapse{padding-inline:26px}
[data-nav="collapsed"] .logo-names,
[data-nav="collapsed"] .nav-label{opacity:0;pointer-events:none}
[data-nav="collapsed"] .nav-collapse .ti{transform:rotate(180deg)}

/* The section headings have no room to be read, and dropping them outright would
   run five groups of icons together. They become the line that used to sit under
   them instead, so the grouping survives the collapse. */
[data-nav="collapsed"] .nav-section{
    height:1px;padding:0;margin:10px 20px 6px;overflow:hidden;
    background:rgba(255,255,255,.1);color:transparent}

@media (prefers-reduced-motion:reduce){
    .sidebar,.logo-names,.nav-label,.nav-collapse .ti{transition:none}
}

/* The badge tile is brand blue in the mock because there was nothing to put in it.
   Once a real logo is uploaded the blue is a colour standing behind a picture that
   brought its own, so it steps back to white and the image sits inside the tile
   whole - contain, not cover, because a cropped logo is a wrong logo. */
.logo-icon.has-logo{background:#fff;padding:3px}
.logo-img{width:100%;height:100%;object-fit:contain;display:block}
/* The name is the seller's now, so it can be longer than the mock's two words. It
   wraps rather than being cut - a clipped company name is the wrong company name -
   and min-width lets it, since a flex item will not shrink below its content
   otherwise and a long word would push out of the rail. */
.logo-names{min-width:0}
.logo-text{line-height:1.35;overflow-wrap:anywhere}

/* The reason the save is off, under the foot it belongs to - a grey button that does
   not say why is the trap the order form closed on 10 September. Written for the
   return form, asked for by the coupon form too, so it lives here now. */
.modal-blocker{padding:8px 20px 12px;font-size:12px;color:var(--muted);display:flex;align-items:center;gap:6px;border-top:1px solid var(--border);background:var(--bg)}

/* A read-only figure beside the fields, for something the form states but must not
   set - how many times a coupon has gone out, which only the order path writes. */
.form-readonly{padding:9px 12px;border:1px dashed var(--border);border-radius:8px;font-size:13px;color:var(--text2);background:var(--bg)}

/* ── The view switcher ────────────────────────────────────────────────────────
   Four screens had grown their own copy of this - ret- on returns, dlv- on
   delivery, prd- on the catalogue - and marketing had grown something else
   entirely: a browser-tab strip attached to a card. Same job, one shape now.

   A pill rail rather than attached tabs on purpose: these are not documents you
   open, they are views of one screen, and the solid pill says which one you are
   looking at without pretending the page underneath changed. */
.tabs-switch{width:fit-content;max-width:100%;display:flex;gap:4px;margin:12px 20px;
    background:var(--card);border:1px solid var(--border);border-radius:var(--radius);
    padding:5px;overflow-x:auto;flex-shrink:0}
.tabs-switch::-webkit-scrollbar{height:3px}
.tabs-switch::-webkit-scrollbar-thumb{background:var(--primary-l);border-radius:6px}
.tab-switch-btn{flex-shrink:0;display:flex;align-items:center;justify-content:center;gap:6px;
    padding:8px 14px;border:none;background:none;border-radius:var(--radius);
    font-size:13px;font-weight:600;font-family:inherit;line-height:1.45;
    color:var(--muted);cursor:pointer;white-space:nowrap;transition:.15s}
.tab-switch-btn:hover{color:var(--text2)}
.tab-switch-btn.active{background:var(--primary);color:#fff}
/* The count on a tab, quiet inside it - it says how many, not "look here". */
.tab-switch-n{border-radius:10px;padding:1px 7px;font-size:10px;background:rgba(0,0,0,.08);color:inherit}
.tab-switch-btn.active .tab-switch-n{background:rgba(255,255,255,.25);color:#fff}

/* The icon on a KPI card. Two of the four tints were written on the returns sheet and
   the markup there was already asking for the other two - green and red fell through
   to the text colour, so half that strip's icons were saying nothing. Four now, and
   in the shared sheet because marketing wants them too. */
.ks-icon.blue{color:var(--primary)}
.ks-icon.orange{color:var(--orange)}
.ks-icon.green{color:var(--success)}
.ks-icon.red{color:var(--danger)}
.ks-icon.slate{color:var(--slate)}

/* A form hint that is telling you something is wrong rather than explaining a field.
   `is-warn` exists in orders.css but only ever on .oc-note - check-classes.py reads
   names and not combinations, so the name passing the check meant nothing here. */
.form-hint.is-warn{color:var(--warning);font-weight:700}

/* admin.css paints the banner amber, which is right for "look at this" and wrong for
   a refusal and for a confirmation. Two screens ask for these now - the order modal
   and the offer form - so they live here rather than in either one's file. */
.alert-banner.is-bad{background:var(--danger-l);border-color:var(--danger);color:var(--danger)}
.alert-banner.is-good{background:var(--success-l);border-color:var(--success);color:var(--success)}
