/* ═══════════════════════════════════════════════════════════════════════════
 * BAS Dashboard — SHARED COMPONENT LAYER
 *
 * 🔴 THIS IS THE ONLY PLACE A FORM CONTROL, BUTTON, CARD, TABLE OR PILL IS
 *    DEFINED. Feature pages use these classes. They do not redeclare them.
 *
 * Why this exists
 * ───────────────
 * Amy, 2026-09-11: "we can't keep having these new features make up their own
 * design decisions… so we aren't generating style sheets millions of miles long."
 *
 * She is right, and the old rule caused it. Feature pages were told to take a
 * 2-3 letter prefix and re-declare the same rules inline, on the grounds that
 * LiteSpeed defers enqueued assets on standalone dashboard pages. By the time
 * that rule was written the shell had ALREADY solved the timing problem:
 * BAS_VM_Dashboard_Shell::render_head_css() enqueues dashboard-shell.css and
 * calls wp_print_styles() itself at wp_head priority 99, so the <link> lands in
 * <head> with a version cache-buster and none of LiteSpeed's deferral applies.
 * Nobody propagated that to feature pages, so we grew seven identical copies of
 * one input rule across .bas-pe .bas-pl .bas-st .bas-ol .bas-pp .bas-ms .bas-pc.
 *
 * This file is printed by the same mechanism, immediately after the shell sheet,
 * on every dashboard page. A feature page's own stylesheet should now contain
 * ONLY layout that is genuinely unique to it.
 *
 * Rules for anyone adding to this file
 * ────────────────────────────────────
 *   0. 🔴 IF A SHARED SCRIPT EMITS A CLASS, IT IS DEFINED HERE — never in the
 *      template of whichever page was built first. scripts/bas-pricing.js draws
 *      the recipe card for two pages; its CSS sat inline in the Pricing
 *      Calculator's template, so the other page rendered grey blocks for six
 *      weeks while the page that owned the CSS looked fine. And no colour,
 *      border, radius or padding inside a JS template string: a hex baked into
 *      a template literal cannot be reached from here and will not follow dark
 *      mode. Width is layout and may stay inline. (See BAS-DESIGN-SYSTEM.md,
 *      "The two gaps this rule keeps falling through".)
 *   1. If two feature pages would both want it, it belongs here.
 *   2. Values come from styles/product-editor.css — the agreed visual contract.
 *      Do not introduce a new radius, shadow or border width without changing it
 *      here for everyone.
 *   3. No icons, no emoji. Typographic glyphs only (− + ✓ ×).
 *   4. Every component gets its dark-mode rule in the same block, not bolted on
 *      later by whoever notices.
 *   5. Prefer a PHP helper in BAS_VM_DS over hand-written markup, so the classes
 *      and the element structure stay in step.
 *
 * @package BAS_Vendor_Map
 * @since   6.147.0
 * ═══════════════════════════════════════════════════════════════════════════ */

/* ── Tokens the components need ──────────────────────────────────────────────
 * The shell already defines --bs-green-*, --bs-tomato, --bs-ink*, --bs-line*,
 * --bs-cream*, --font-*. These are the extra surface values that every feature
 * page had been hard-coding as raw hex.                                       */
:root {
	--bs-field:       #ddd2b0;   /* input border */
	--bs-field-bg:    #fffdf6;   /* input fill */
	--bs-card-line:   #eee3c8;   /* card hairline */
	--bs-rule:        #f0e8cd;   /* table row rule */
	--bs-muted:       #8a8a7a;   /* hint text */
	--bs-card-bg:     #ffffff;
	--bs-focus:       rgba(48,178,180,.15);
	--bs-counter-over: #b4541c;  /* over-budget counter — a warning, not an error */

	/* v7.51.0 — the Market Planner's surfaces, which were raw hex with no dark
	   value (so the wrap-up panel stayed cream under light text). Each tint
	   pairs with its own ink so the two always flip together. */
	--bs-surface-2:   #f9f7f2;   /* soft panel, form well */
	--bs-tint-butter: #fffdf0;   /* "needs you" panel — the wrap-up */
	--bs-tag-butter:  #fff2d6;   /* small yellow tag — ESTIMATE, Weak */
	--bs-butter-ink:  #7a5c00;   /* text on either butter tint */
	--bs-tint-green:  #f3fbf3;   /* done / selected */
	--bs-green-ink:   #1d741b;   /* green text — on white or the green tint */
	--bs-line-green:  #e0f0d0;   /* hairline inside a green area */
	--bs-tint-tomato: #fff4f1;   /* error surface */

	--bs-r-field:     10px;      /* radius: inputs, tips */
	--bs-r-card:      14px;      /* radius: cards */
	--bs-r-pill:      999px;
	--bs-pad-card:    22px;

	--bs-shadow-btn:      0 4px 0 rgba(29,116,27,.35);
	--bs-shadow-btn-teal: 0 4px 0 rgba(24,110,112,.30);
	--bs-shadow-drop:     0 8px 24px rgba(0,0,0,.12);
	--bs-shadow-modal:    0 20px 60px rgba(0,0,0,.30);
}

html[data-bas-theme="dark"] {
	--bs-counter-over: #e8a35e;
	--bs-field:     #3a3a3d;
	--bs-field-bg:  #161618;
	--bs-card-line: #323234;
	--bs-rule:      #2a2a2c;
	--bs-muted:     #8f8f92;
	--bs-card-bg:   #1e1e20;
	--bs-surface-2:   #19191b;
	--bs-tint-butter: #22201a;
	--bs-tag-butter:  #3a3319;
	--bs-butter-ink:  #ffda3d;
	--bs-tint-green:  #1b2419;
	--bs-green-ink:   #c2e86b;
	--bs-line-green:  #2c3a28;
	--bs-tint-tomato: #2c1b17;
}

/* ── Page root ───────────────────────────────────────────────────────────────
 * Put .bas-ds on a feature page's outermost element. It sets the type stack and
 * opens a container-query context so layouts respond to the CONTENT width, which
 * is what changes when the sidebar collapses — not the viewport.              */
.bas-ds {
	font-family: var(--font-body);
	font-size: 15px;
	line-height: 1.5;
	color: var(--bs-ink);
	container-type: inline-size;
	container-name: basds;
}

/* ── Page header ─────────────────────────────────────────────────────────── */
.bas-ds-top { margin: 0 0 20px; }
.bas-ds-eyebrow {
	font-size: 11px; font-weight: 900; letter-spacing: .14em;
	text-transform: uppercase; color: var(--bs-green-700); margin-bottom: 6px;
}
.bas-ds-title {
	font-family: var(--font-display); font-size: 32px; line-height: 1.15;
	margin: 0 0 6px; color: var(--bs-ink);
}
.bas-ds-sub { font-size: 14px; color: var(--bs-muted); margin: 0; max-width: 62ch; }
/* Eyebrow with the plan pill beside it — "MY KITCHEN [Maker]". Lived on
   Ingredients & Recipes as .bas-ks-eyebrow-row; the Studio home wanted the
   same line, so it is a component now rather than a second private copy. */
.bas-ds-eyebrow-row { display: flex; align-items: center; flex-wrap: wrap; gap: 10px; margin-bottom: 6px; }
.bas-ds-eyebrow-row > .bas-ds-eyebrow { margin-bottom: 0; }

/* ── Card ────────────────────────────────────────────────────────────────── */
.bas-ds-card {
	background: var(--bs-card-bg);
	border: 1px solid var(--bs-card-line);
	border-radius: var(--bs-r-card);
	padding: var(--bs-pad-card);
	margin-bottom: 20px;
	scroll-margin-top: 90px;   /* .bas-ds-content is the scrollport, not the window */
}
.bas-ds-card > h2 {
	font-family: var(--font-display); font-size: 21px; margin: 0 0 4px; color: var(--bs-ink);
}
.bas-ds-card > h3 {
	font-size: 15px; font-weight: 900; margin: 20px 0 4px; color: var(--bs-green-700);
}
.bas-ds-card-sub { font-size: 13px; color: var(--bs-muted); margin: 0 0 16px; max-width: 62ch; }

/* ── Form controls ───────────────────────────────────────────────────────────
 * 🔴 The rule Amy was talking about. Seven pages had their own copy of this.
 * Anything that takes typed input in the vendor dashboard looks like this.    */
.bas-ds-label,
.bas-ds label {
	display: block; font-weight: 700; margin: 14px 0 5px;
	font-size: 14px; color: var(--bs-ink);
}
.bas-ds input[type="text"],
.bas-ds input[type="number"],
.bas-ds input[type="search"],
.bas-ds input[type="email"],
.bas-ds input[type="url"],
.bas-ds input[type="tel"],
.bas-ds input[type="date"],
.bas-ds input[type="time"],
.bas-ds input[type="password"],
.bas-ds textarea,
.bas-ds select,
/* 🔴 Doubled on purpose — see the block below. Not a typo, do not "tidy" it. */
.bas-ds-input.bas-ds-input {
	width: 100%;
	box-sizing: border-box;
	font-family: inherit;
	font-size: 15px;
	/* 🔴 6.168.0 — THE HEIGHT IS PART OF THE COMPONENT. See the block below. */
	min-height: 46px;
	line-height: 1.35;
	padding: 10px 12px;
	border: 1.5px solid var(--bs-field);
	border-radius: var(--bs-r-field);
	background: var(--bs-field-bg);
	color: var(--bs-ink);
}

/* ── Why .bas-ds-input is written twice ──────────────────────────────────────
   🔴 6.168.0. A single class is specificity (0,1,0). A theme's
   `input[type="text"]` is (0,1,1) — one type selector plus one attribute
   selector — which is MORE specific, and Divi ships that rule inline in
   <head>, so it also comes later. Measured in the live DOM on My Store:

     #bas-ssh-f-eyebrow  →  padding 2px, border 1px solid #bbb, background #fff

   That is Divi's rule, whole, on one of our fields. Every value we wrote lost
   except border-radius, which Divi happens not to set. It only bit fields that
   are NOT inside a .bas-ds root — which, until this version, meant every field
   in every modal, because BAS_VM_DS::modal_open() built the box without the
   class. Both halves are fixed: the class is on the modal box now, AND the
   component no longer depends on an ancestor to win. Repeating the class is
   the cheapest way to (0,2,0) and it beats a theme type+attribute rule with no
   !important anywhere, which matters because !important is unarguable and the
   next person needs to be able to override this for a real reason.

   ── Why min-height and line-height are on that rule ─────────────────────────
   🔴 Amy, 2026-09-13: "still having these annoying issues with inputs coming
   out too short to be usable on a mobile. We should have had a shared class on
   these a long time ago instead of me having to gripe about them every time we
   add an input… You got the sizing on the Buttons input just fine."

   She had it exactly: the Buttons inputs were fine because SOMEBODY WROTE
   `min-height: 44px` on them by hand, in styles/bas-shop-sections-admin.css,
   on that one page, for that one row. Every other field on the platform got
   whatever padding plus line-height happened to compute to — and line-height
   is INHERITED. Inside .bas-ds it is 1.5 and a field lands near 46px; inside a
   modal appended to <body>, or on a Dokan page whose theme sets line-height 1,
   the same class lands at 38px or less. That is the bug, it is not a phone
   bug, and it produced a different answer on every screen we built.

   So the height moves into the component, for every pointer, and the
   line-height is declared rather than inherited so the answer does not depend
   on where the field is mounted.

   46px is not a phone number either — it is one number that clears the 44px
   Apple/Google touch floor everywhere, so there is no second set of sizes to
   keep in step. @media (pointer: coarse) further down now only raises the FONT
   to 16px, which is the thing that actually has to change on a phone (below
   16px, iOS Safari zooms the page on focus).

   🔴 DO NOT write min-height on an input on a feature page again. If a field
   somewhere needs to be a different size, it needs a variant here. */

/* The exception that proves it: a field inside a tight control cluster — a
   number spinner in a toolbar — where 46px would break the row it sits in. */
.bas-ds-input--tight.bas-ds-input--tight { min-height: 38px; padding: 7px 10px; }

/* 7.59.0 — a <textarea> wearing .bas-ds-input is a text AREA wherever it is
   mounted. `.bas-ds textarea` below only reaches one inside a .bas-ds root, so
   a textarea in Ask Bea (which floats outside every page root) collapsed to a
   46px one-liner. Same values as that rule. Doubled for the same reason as
   the base rule; --auto below matches it and comes later, so it wins. */
textarea.bas-ds-input.bas-ds-input { min-height: 96px; line-height: 1.5; resize: vertical; }

/* A chat box: starts one line tall, grows with its text (script sets the
   height), never offers a resize handle. Ask Bea's composer. */
textarea.bas-ds-input--auto.bas-ds-input--auto { min-height: 46px; max-height: 120px; resize: none; overflow-y: auto; }
.bas-ds input:focus,
.bas-ds textarea:focus,
.bas-ds select:focus,
.bas-ds-input:focus {
	outline: 0;
	border-color: var(--bs-green-600);
	box-shadow: 0 0 0 3px var(--bs-focus);
}
.bas-ds input[readonly],
.bas-ds textarea[readonly] { background: var(--bs-line-2); color: var(--bs-ink-soft); }
.bas-ds input[disabled],
.bas-ds select[disabled] { opacity: .55; cursor: not-allowed; }
.bas-ds textarea { min-height: 96px; line-height: 1.5; resize: vertical; }
.bas-ds ::placeholder { color: #c4bda8; }
html[data-bas-theme="dark"] .bas-ds ::placeholder { color: #6a6a6d; }

/* A field big enough to be the only thing on screen — first question of a flow. */
.bas-ds-input--lg.bas-ds-input--lg { font-size: 20px; min-height: 58px; padding: 15px 18px; border-radius: 12px; border-width: 2px; }

.bas-ds-hint {
	display: block; font-size: 12px; color: var(--bs-muted);
	font-weight: 400; margin-top: 4px;
}
.bas-ds-opt { font-weight: 400; color: var(--bs-muted); font-size: 12px; }
.bas-ds-req  { color: var(--bs-tomato); }

/* ── Character counter (6.179.0) ─────────────────────────────────────────────
   For a field whose text has a layout budget somewhere the vendor cannot see
   while typing. Lives here rather than on the one page that needed it first:
   Store Name, the Bloom slide headlines and the shop section ledes all have the
   same problem, and a counter invented per page is how a component ends up with
   five spellings.

   Not a maxlength. Going over is allowed and the field keeps its own hard cap —
   this says "past here it gets trimmed in the header", which is advice, not an
   error, so it warms rather than turning red. --bs-tomato is reserved for
   something being wrong.

   Markup comes from BAS_VM_DS::counter(); the shell script wires every
   [data-bas-count-for] on the page. */
.bas-ds-counter {
	display: block; font-size: 12px; font-weight: 400;
	color: var(--bs-muted); margin-top: 4px;
	font-variant-numeric: tabular-nums;
}
.bas-ds-counter b { font-weight: 700; color: var(--bs-ink); }
.bas-ds-counter.is-over b { color: var(--bs-counter-over); }
.bas-ds-counter-note { display: none; }
.bas-ds-counter.is-over .bas-ds-counter-note { display: inline; }

/* Field rows. Collapse to one column on a narrow CONTENT area. */
.bas-ds-row2 { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; }
.bas-ds-row3 { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 16px; }
/* 🔴 A label whose text wraps must not drag its input down with it. (v6.161.0)
   Amy, on the ingredient modal: "the stock inputs are not aligned." They were not:
   "How much you have now" wrapped to two lines in a 520px modal and "Counted in" did
   not, so one input sat a line below its neighbours. Shortening that one label fixes
   that one case; this fixes the class of it. Each label becomes a column and its
   control is pushed to the bottom, so the row of controls lines up however the text
   above them falls. */
/* 🔴 7.22.1 — the rule below was written for markup the DS never emits.
   BAS_VM_DS::field() prints label, input and hint as three SIBLINGS, so inside
   a 2-column grid each one became its own cell: label, input, hint, label…
   and the second field's control landed under the first field's label.
   Amy, on the Shipping page: "What do you charge for postage alignment is a
   little off." It was off on every DS field in a row, everywhere.
   field()/select()/textarea() now wrap themselves in .bas-ds-f — ONE fix in the
   component sheet rather than a grid override per feature page. */
/* 🔴 7.23.3 — `margin-top: auto` aligned the wrong edge. It pushes a control
   to the BOTTOM of its cell, so a field WITHOUT a hint sank to meet the bottom
   of a neighbour that HAD one. Amy's "Medium postage" sat 24px below "Anything
   with no size set" for exactly this reason. Measured in a harness rather than
   eyeballed: tops were 249 vs 225, and are now 205 vs 205.
   Subgrid lines up label / control / hint ACROSS the row, which also finally
   fixes the wrapping-label case 6.161.0 was written for and never solved
   (that rule targeted `label > input`, markup the DS has never emitted).
   The flex block below stays as the fallback. */
.bas-ds-row2 > .bas-ds-f,
.bas-ds-row3 > .bas-ds-f { display: flex; flex-direction: column; }
.bas-ds-row2 > .bas-ds-f > .bas-ds-hint,
.bas-ds-row3 > .bas-ds-f > .bas-ds-hint { margin-top: 6px; }

.bas-ds-row2,
.bas-ds-row3 { align-items: start; }

@supports (grid-template-rows: subgrid) {
	.bas-ds-row2 > .bas-ds-f,
	.bas-ds-row3 > .bas-ds-f {
		display: grid;
		grid-template-rows: subgrid;
		grid-row: span 3;      /* label, control, hint — an absent hint just leaves its row empty */
		row-gap: 0;
	}
	.bas-ds-row2 > .bas-ds-f > *,
	.bas-ds-row3 > .bas-ds-f > * { margin-top: 0; align-self: start; }
	.bas-ds-row2 > .bas-ds-f > .bas-ds-hint,
	.bas-ds-row3 > .bas-ds-f > .bas-ds-hint { margin-top: 6px; }
}

.bas-ds-row2 > label,
.bas-ds-row3 > label { display: flex; flex-direction: column; }
.bas-ds-row2 > label > input,
.bas-ds-row2 > label > select,
.bas-ds-row2 > label > textarea,
.bas-ds-row3 > label > input,
.bas-ds-row3 > label > select,
.bas-ds-row3 > label > textarea { margin-top: auto; }
@container basds (max-width: 640px) {
	.bas-ds-row2, .bas-ds-row3 { grid-template-columns: 1fr; }
}
@media (max-width: 600px) {
	.bas-ds-row2, .bas-ds-row3 { grid-template-columns: 1fr; }
}

/* Checkbox / radio beside its own label — the one case where a label is inline. */
/* ── Checkboxes and radios ────────────────────────────────────────────────
   🔴 6.167.0 — Amy, walking the Shop Sections builder: "The scrolling strip
   checkbox needs some spacing between it and the label. The checkboxes have
   spacing issues all over — I think this could be a universal css fix so we
   don't get mile long css files and every check box ever would never have this
   problem again."

   She is right, and this is that fix. It is deliberately blunt:

   - `display: flex !important` — the gap below only works if the label is
     still a flex container. Dokan, Divi and WooCommerce all ship rules that
     turn a `<label>` into a block or an inline-block on their own pages, and
     when one of them wins the gap silently does nothing and the box ends up
     jammed against its words. One `!important` here is cheaper than finding
     out which stylesheet did it on every page we ever build.
   - the box gets an explicit size and `flex: none`, because
     `dashboard-shell.css` strips padding, border and width off every checkbox
     inside .bas-ds-content to stop them being pill-ified, and a 0-padding
     checkbox with no width set renders at whatever the browser feels like.
   - `align-items: flex-start` with a nudge, so a label that wraps to two lines
     keeps its box next to the FIRST line instead of floating in the middle.

   Anything using .bas-ds-check gets this. Do not re-declare checkbox spacing
   on a feature page — if something here is wrong, fix it here. */
.bas-ds-check {
	display: flex !important;
	align-items: flex-start;
	gap: 10px;
	font-weight: 400; font-size: 13.5px; margin: 10px 0 0;
	cursor: pointer;
}
.bas-ds-check > input[type="checkbox"],
.bas-ds-check > input[type="radio"] {
	flex: none;
	width: 17px !important;
	height: 17px !important;
	min-width: 17px;
	margin: 1px 0 0 !important;
	accent-color: var(--bs-sage, #70ac43);
	cursor: pointer;
}
.bas-ds-check > span { flex: 1 1 auto; min-width: 0; line-height: 1.35; }
.bas-ds-check input:disabled,
.bas-ds-check input:disabled + span { cursor: default; opacity: .55; }

/* A row of them — the same gap between the boxes as inside them would read as
   one long line, so the row gets more. */
.bas-ds-checkrow {
	display: flex; flex-wrap: wrap; gap: 8px 28px; margin: 12px 0 0;
}
.bas-ds-checkrow .bas-ds-check { margin: 0; }

/* A GRID of them — for a set with many short options, where a flex row leaves a
   ragged last line. The Big 9 allergen picker is the case this was built for;
   before 7.3.0 it was declared twice (once in styles/bas-pricing.css, once as
   inline styles in a template) because the ingredient dialog existed twice. */
.bas-ds-checkgrid {
	display: grid;
	grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
	gap: 4px 14px;
	margin: 8px 0 0;
}
.bas-ds-checkgrid .bas-ds-check { margin: 0; }

/* A BIG TICK ROW — the whole row is the target, for a list worked one-handed
   (7.50.0, first used by the Market Day page). 56px tall, a 26px box, and the
   ticked state drawn from the checkbox itself with :has(), so a page needs no
   script to look right and no class of its own to style it.

     <label class="bas-ds-check bas-ds-check--row">
       <input type="checkbox">
       <span><span class="bas-ds-check__main">24 × Sourdough</span>
             <span class="bas-ds-check__sub">3 already paid for</span></span>
     </label>

   The doubled class on the box outranks the touch-size rule further down,
   which would otherwise shrink it back to 22px on a phone — the one place it
   has to be big. */
.bas-ds-check.bas-ds-check--row {
	align-items: center;
	gap: 14px;
	min-height: 56px;
	margin: 0 0 8px;
	padding: 12px 14px;
	background: var(--bs-card-bg);
	border: 1.5px solid var(--bs-card-line);
	border-radius: var(--bs-r-field);
	font-size: 17px; line-height: 1.3; font-weight: 700;
	color: var(--bs-ink);
	-webkit-tap-highlight-color: transparent;
	transition: background .15s, border-color .15s;
}
.bas-ds-check.bas-ds-check--row > input[type="checkbox"] {
	width: 26px !important; height: 26px !important; min-width: 26px;
	margin: 0 !important;
	accent-color: var(--bs-green-700);
}
.bas-ds-check--row > span { display: flex; flex-direction: column; gap: 2px; overflow-wrap: anywhere; }
.bas-ds-check__sub { font-size: 14px; font-weight: 400; color: var(--bs-ink-soft); }
.bas-ds-check--row:has(> input:checked) { background: var(--bs-cream-2); border-color: var(--bs-green-300); }
.bas-ds-check--row:has(> input:checked) .bas-ds-check__main { text-decoration: line-through; color: var(--bs-ink-muted); }
.bas-ds-check--row:focus-within { outline: 3px solid var(--bs-green-600); outline-offset: 2px; }
html[data-bas-theme="dark"] .bas-ds-check--row:has(> input:checked) { border-color: var(--bs-green-700); }

/* v7.51.0 — every <fieldset> in the dashboard, not only .bas-ds-set. The UA
   `min-width: min-content` let the Market Planner's "What was the day like?"
   box grow to ~490px on a 375px phone and push the sell-out time, closing time
   and two weather buttons off the right edge. Same fault as the ingredient
   modal below; fixed once here so no screen meets it again. */
.bas-ds fieldset,
.bas-ds-content fieldset { min-width: 0; max-width: 100%; box-sizing: border-box; }

/* ── Grouped set ─────────────────────────────────────────────────────────────
   A <fieldset> that looks like it belongs to this design system: hairline box,
   small shouting legend.

   🔴 `min-width: 0` is load-bearing. A <fieldset> has a UA `min-width:
   min-content` that a grid or flex parent honours, so one long option inside it
   pushes the whole dialog wider than its box — which is what it did in the
   ingredient modal at 520px before anyone worked out why. */
.bas-ds-set {
	border: 1px solid var(--bs-card-line);
	border-radius: var(--bs-r-field);
	padding: 12px 14px 14px;
	margin: 16px 0 0;
	min-width: 0;
	width: 100%;
	box-sizing: border-box;
}
.bas-ds-set > legend {
	font-size: 11px;
	font-weight: 900;
	letter-spacing: .09em;
	text-transform: uppercase;
	color: var(--bs-green-700, #1d741b);
	padding: 0 6px;
}

/* ── List rows ───────────────────────────────────────────────────────────────
   🔴 6.168.0 — the SECOND thing Amy was seeing as "spacing issues", and it is
   the same shape as the input one: a rule of ours losing a specificity fight
   it never knew it was in.

   Dokan's style.css ships:

       .dokan-dashboard .dokan-dashboard-content ul li { list-style:none; margin:0; padding:0 }

   That is (0,2,2). Any single-class rule a feature page writes for an <li> is
   (0,1,0) and loses outright — so `.bas-ss-row { padding: 16px 18px }` has been
   rendering at padding 0 since the day it was written, on the live dashboard,
   while looking perfectly correct in the stylesheet. Measured in the DOM on My
   Store → Sections: the section rows computed to `padding: 0px`. Nobody could
   have found that by reading CSS, which is why this note names the rule.

   A neutralising rule cannot fix it — `revert`/`unset` roll back to the
   user-agent sheet, they do not hand control back to a lower-specificity author
   rule — so the answer is a real component, declared at a specificity that
   wins, that lists opt into by class.

   🔴 ANY <li> IN THIS DASHBOARD THAT NEEDS PADDING TAKES THIS CLASS. Do not
   write padding on an li on a feature page; it will silently do nothing. The
   feature page still owns the row's LAYOUT (its grid, its columns, what wraps);
   this owns the box it happens in. */
.bas-ds li.bas-ds-listrow,
.dokan-dashboard .dokan-dashboard-content .bas-ds li.bas-ds-listrow {
	list-style: none;
	margin: 0;
	padding: 16px 18px;
	border: 1px solid var(--bs-card-line);
	border-radius: var(--bs-r-card);
	background: var(--bs-card-bg);
}

/* Reorder states. They live here rather than on the feature page for the same
   reason the box does: the rule above sets the `border` SHORTHAND at (0,4,2),
   so a feature page's `.thing.is-over { border-color: … }` at (0,2,0) is
   overwritten by it and the drop target shows nothing. */
.bas-ds li.bas-ds-listrow.is-over,
.dokan-dashboard .dokan-dashboard-content .bas-ds li.bas-ds-listrow.is-over {
	border-color: var(--bs-green-600);
}
.bas-ds li.bas-ds-listrow.is-dragging,
.dokan-dashboard .dokan-dashboard-content .bas-ds li.bas-ds-listrow.is-dragging {
	opacity: .5;
}

/* The row a validation message is talking about. Same reason the drag states
   live here: the box rule above sets the `border` SHORTHAND at (0,4,2), so a
   feature page's own `.is-bad { border-color: … }` is overwritten by it and the
   row the vendor is being sent to look at shows nothing. (v6.171.1) */
.bas-ds li.bas-ds-listrow.is-bad,
.dokan-dashboard .dokan-dashboard-content .bas-ds li.bas-ds-listrow.is-bad {
	border-color: var(--bs-tomato);
	box-shadow: 0 0 0 3px rgba(255,76,37,.12);
}

/* The quieter variant, for a row that sits inside a card rather than on the
   page ground — it needs to read as nested, not as a second card. */
.bas-ds li.bas-ds-listrow--sunk,
.dokan-dashboard .dokan-dashboard-content .bas-ds li.bas-ds-listrow--sunk {
	background: var(--bs-cream-2);
}

/* The list they sit in. Same fight, same answer. */
.bas-ds ul.bas-ds-listrows,
.dokan-dashboard .dokan-dashboard-content .bas-ds ul.bas-ds-listrows {
	list-style: none;
	margin: 0;
	padding: 0;
	display: grid;
	gap: 10px;
}

/* ── Reveal: settings that appear when something is switched on ───────────
   🔴 6.167.0 — Amy: "When I selected a row of photos the options displayed
   under the scrolling header which was confusing… maybe the items that popped
   up under scrolling header were settings for the scrolling header but didn't
   show up until I picked a row of photos."

   That is exactly what happened. Fields appearing out of nowhere land visually
   wherever they fit, and the vendor attaches them to whatever heading is
   nearest. A revealed group is now indented behind a rule that points back at
   the control that revealed it, so where it came from is not a guess.

   Also fixes the display:flex/[hidden] trap in one place: a class rule outranks
   the UA sheet, so a "hidden" flex row keeps its full height. */
.bas-ds-reveal {
	margin: 14px 0 0;
	padding: 4px 0 2px 16px;
	border-left: 2px solid var(--bs-line, #e7e2d2);
}
.bas-ds-reveal[hidden] { display: none !important; }
.bas-ds-reveal > :first-child { margin-top: 0; }
html[data-bas-theme="dark"] .bas-ds-reveal { border-left-color: rgba(255,255,255,.14); }

/* Field-level validation. Inline, never window.alert(). */
.bas-ds-err {
	background: #fff5f2; border: 1.5px solid rgba(255,76,37,.35);
	border-radius: var(--bs-r-field); padding: 11px 15px;
	font-size: 14px; font-weight: 700; color: #b8371c; margin: 14px 0 0;
}
html[data-bas-theme="dark"] .bas-ds-err { background: rgba(255,76,37,.10); color: #ff8f75; }

/* 🔴 7.0.1 — the [hidden] trap, closed here for every toggled notice at once.
   A class rule that sets `display` outranks the UA sheet's `[hidden]`, so an
   element "hidden" in markup keeps its full height and the vendor reads a
   validation error that does not apply to them. These three carry no display of
   their own TODAY, which is precisely why the next person to add
   `display: flex` to one would break all of them silently. Amy's rule for a
   fault of this kind: fix it once, in the shared sheet. */
.bas-ds-err[hidden],
.bas-ds-warn[hidden],
.bas-ds-tip[hidden] { display: none !important; }
.bas-ds-input.is-invalid.is-invalid { border-color: var(--bs-tomato); background: #fff5f2; }

/* Flash a field you just sent someone to, so they can see where they landed. */
.bas-ds-flash { animation: basDsFlash 1.4s ease; }
@keyframes basDsFlash {
	0%, 100% { box-shadow: none; }
	20%, 60% { box-shadow: 0 0 0 4px rgba(255,218,61,.55); }
}

/* ── Buttons ─────────────────────────────────────────────────────────────── */
.bas-ds-btn {
	display: inline-flex; align-items: center; gap: 7px;
	font-family: inherit; font-size: 14px; font-weight: 800; line-height: 1.2;
	padding: 10px 20px; border-radius: var(--bs-r-pill);
	background: var(--bs-card-bg); color: var(--bs-ink);
	border: 1.5px solid var(--bs-field); cursor: pointer;
	/* An <a> wearing the class is a button too (Studio home, Review Cards) —
	   it must not pick up the theme's link underline. */
	text-decoration: none;
}
.bas-ds-btn:hover { filter: brightness(1.06); }
.bas-ds-btn[disabled] { opacity: .55; cursor: not-allowed; filter: none; }

.bas-ds-btn--primary {
	background: linear-gradient(135deg, var(--bs-green-300), var(--bs-green-500) 60%, var(--bs-green-700));
	color: #fff; border: 0; box-shadow: var(--bs-shadow-btn);
}
.bas-ds-btn--teal    { background: var(--bs-green-600); color: #fff; border: 0; box-shadow: var(--bs-shadow-btn-teal); }
.bas-ds-btn--outline { background: var(--bs-card-bg); color: var(--bs-green-500); border: 1.5px solid var(--bs-green-500); }
.bas-ds-btn--danger  { background: var(--bs-card-bg); color: var(--bs-tomato); border: 1.5px solid rgba(255,76,37,.45); }
.bas-ds-btn--ghost   { background: none; border: 1.5px dashed var(--bs-card-line); color: var(--bs-green-700); }
.bas-ds-btn--sm      { font-size: 13px; padding: 7px 16px; }
.bas-ds-btn--lg      { font-size: 15.5px; padding: 13px 28px; }

/* A button that reads as a link. For "skip this", "back to…", inline fixes. */
.bas-ds-link {
	background: none; border: 0; padding: 0; font-family: inherit;
	font-size: 13px; font-weight: 800; color: var(--bs-green-600);
	text-decoration: underline; cursor: pointer;
}

/* ── Segmented tabs ──────────────────────────────────────────────────────── */
.bas-ds-tabs {
	display: inline-flex; gap: 4px; padding: 4px; max-width: 100%; overflow-x: auto;
	background: var(--bs-cream-2); border: 1px solid var(--bs-card-line);
	border-radius: var(--bs-r-pill); margin-bottom: 22px;
}
.bas-ds-tab {
	border: 0; background: none; font-family: inherit; font-size: 13.5px; font-weight: 800;
	color: var(--bs-ink-soft); padding: 8px 20px; border-radius: var(--bs-r-pill);
	cursor: pointer; white-space: nowrap; display: flex; align-items: center; gap: 7px;
}
/* .bas-wizard-step--active is what the recipe wizard's own JS sets. Aliased here
   rather than mirrored in a page sheet, so the step strip IS the tab component
   instead of looking like it. (v6.161.0) */
.bas-ds-tab.bas-wizard-step--active,
.bas-ds-tab.is-on {
	background: var(--bs-card-bg); color: var(--bs-ink);
	box-shadow: 0 1px 3px rgba(49,49,49,.14);
}

/* ── Numbered steps (7.38.0) ─────────────────────────────────────────────
 * A row of "Step 1 · Step 2 · Step 3" pills across the top of a tool. First
 * used by the Label Maker, which had a 196px left rail that squeezed its inputs
 * to nothing on a tablet (Amy, 2026-09-24). Markup:
 *
 *   nav.bas-ds-steps > ol.bas-ds-steps__list > li.bas-ds-steps__item
 *     > button.bas-ds-step[.is-on][.is-ok|.is-warn|.is-bad]
 *         > span.bas-ds-step__num
 *         > span.bas-ds-step__text > __eyebrow + __label + __note
 *
 * .is-on is the step on screen. .is-ok / .is-warn / .is-bad paint the number:
 * done, needs a look, something missing. The steps wrap on their own (flex
 * basis, no query) so the row works on a page with or without a `basds`
 * container — the Label Maker must not open one; see bas-label-maker.css. */
.bas-ds-steps { margin: 0 0 18px; }
.bas-ds-steps__list {
	list-style: none; margin: 0; padding: 0;
	display: flex; flex-wrap: wrap; gap: 8px;
	counter-reset: none;
}
.bas-ds-steps__item { flex: 1 1 170px; min-width: 0; margin: 0; }
.bas-ds-step {
	width: 100%; height: 100%;
	display: flex; align-items: center; gap: 10px;
	padding: 9px 14px 9px 9px;
	font-family: inherit; text-align: left; cursor: pointer;
	background: var(--bs-card-bg); color: var(--bs-ink-soft);
	border: 1.5px solid var(--bs-card-line); border-radius: var(--bs-r-pill);
	transition: border-color .12s ease, box-shadow .12s ease;
}
.bas-ds-step:hover { border-color: var(--bs-field); }
.bas-ds-step:focus-visible { outline: 2px solid var(--bs-green-600); outline-offset: 2px; }
.bas-ds-step.is-on {
	border-color: var(--bs-green-500);
	box-shadow: 0 0 0 3px rgba(112,172,67,.16);
	color: var(--bs-ink);
}
.bas-ds-step__num {
	flex: 0 0 auto; width: 30px; height: 30px; border-radius: 50%;
	display: inline-flex; align-items: center; justify-content: center;
	font-size: 13px; font-weight: 900; line-height: 1;
	background: var(--bs-cream-2); color: var(--bs-ink-soft);
	border: 1.5px solid var(--bs-card-line);
}
.bas-ds-step.is-on  .bas-ds-step__num { background: var(--bs-green-500); border-color: var(--bs-green-500); color: #fff; }
.bas-ds-step.is-ok  .bas-ds-step__num { background: var(--bs-green-700); border-color: var(--bs-green-700); color: #fff; }
.bas-ds-step.is-warn .bas-ds-step__num { background: var(--bs-butter); border-color: var(--bs-butter); color: var(--bs-ink); }
.bas-ds-step.is-bad .bas-ds-step__num { background: var(--bs-tomato); border-color: var(--bs-tomato); color: #fff; }
.bas-ds-step__text { display: flex; flex-direction: column; min-width: 0; line-height: 1.2; }
.bas-ds-step__eyebrow {
	font-size: 10.5px; font-weight: 800; letter-spacing: .08em; text-transform: uppercase;
	color: var(--bs-muted);
}
.bas-ds-step__label { font-size: 14px; font-weight: 800; color: inherit; }
.bas-ds-step__note {
	font-size: 11.5px; color: var(--bs-muted);
	white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.bas-ds-step.is-ok  .bas-ds-step__note { color: var(--bs-green-700); }
.bas-ds-step.is-bad .bas-ds-step__note { color: var(--bs-ink); font-weight: 700; }
html[data-bas-theme="dark"] .bas-ds-step__num { background: #2a2a2c; color: var(--bs-ink); }
html[data-bas-theme="dark"] .bas-ds-step.is-ok .bas-ds-step__note { color: var(--bs-green-300); }

/* ── Choice chips ────────────────────────────────────────────────────────── */
.bas-ds-chips { display: flex; gap: 7px; flex-wrap: wrap; }
.bas-ds-chip {
	font-family: inherit; font-size: 13px; font-weight: 800; padding: 6px 14px;
	border-radius: var(--bs-r-pill); border: 1.5px solid var(--bs-field);
	background: var(--bs-field-bg); color: var(--bs-ink-soft); cursor: pointer;
}
.bas-ds-chip:hover { border-color: var(--bs-green-600); color: var(--bs-green-600); }
.bas-ds-chip.is-on { background: var(--bs-green-600); border-color: var(--bs-green-600); color: #fff; }
.bas-ds-chips--lg .bas-ds-chip { font-size: 15px; padding: 12px 22px; border-width: 2px; }

/* A chip-sized choice with an explanation under it. */
/* 7.22.0 — the GRID the choices sit in. It was declared privately in
   vendor-kitchen-studio.php and again in vendor-pricing-calculator.php; a third
   copy was about to be born on the Shipping page. One rule, every page. */
.bas-ds-choices {
	display: grid;
	grid-template-columns: 1fr 1fr;
	gap: 12px;
	margin-bottom: 24px;
}
@container basds (max-width: 640px) {
	.bas-ds-choices { grid-template-columns: 1fr; }
}

.bas-ds-choice {
	display: block; text-align: left; font-family: inherit; cursor: pointer;
	background: var(--bs-field-bg); border: 2px solid var(--bs-card-line);
	border-radius: var(--bs-r-card); padding: 16px 18px;
}
.bas-ds-choice strong { display: block; font-size: 15px; font-weight: 800; color: var(--bs-ink); margin-bottom: 3px; }
.bas-ds-choice span   { display: block; font-size: 13px; line-height: 1.45; color: var(--bs-muted); }
.bas-ds-choice:hover  { border-color: var(--bs-green-600); }
.bas-ds-choice.is-on  {
	border-color: var(--bs-green-500); background: rgba(112,172,67,.08);
	box-shadow: 0 0 0 3px rgba(112,172,67,.18);
}
.bas-ds-choice.is-on span { color: var(--bs-ink-soft); }

/* 🔴 6.173.0 — a pill inside a choice. `.bas-ds-choice span { display: block }`
   above outranks `.bas-ds-pill { display: inline-block }` (same specificity, but
   it comes later), so a pill dropped into a choice row rendered as a full-width
   bar across the row. Found on the POS tender sheet, where the [Bloom] chip sits
   beside the tender name — but it is a collision between two shared components,
   so it is fixed once here rather than in that one page's stylesheet. */
.bas-ds-choice .bas-ds-pill { display: inline-block; vertical-align: 2px; }

/* A choice the vendor cannot pick — greyed, still readable, still says what it
   is. The card tenders on a Maker plan, and any tender a back-dated sale rules
   out. No hover response: nothing happens if you press it. */
.bas-ds-choice:disabled { opacity: .5; cursor: default; }
.bas-ds-choice:disabled:hover { border-color: var(--bs-card-line); }

/* ── Status pills ────────────────────────────────────────────────────────── */
.bas-ds-pill {
	display: inline-block; padding: 4px 12px; border-radius: var(--bs-r-pill);
	font-size: 10.5px; font-weight: 900; letter-spacing: .07em; text-transform: uppercase;
	background: #f0ead6; color: var(--bs-ink);
}
.bas-ds-pill--ok      { background: var(--bs-green-700); color: #fff; }
.bas-ds-pill--info    { background: rgba(48,178,180,.16); color: #1e797b; }
.bas-ds-pill--warn    { background: var(--bs-butter); color: var(--bs-ink); }
.bas-ds-pill--danger  { background: var(--bs-tomato); color: #fff; }
.bas-ds-pill--muted   { background: #efe9dd; color: #7a7466; }
/* 7.64.1 — the plan a locked control needs ("Bloom plan"). Same butter badge the
   product editor's locked type cards carry (.bas-pe-lock-badge), as a shared
   pill so any page can put it on a button. */
.bas-ds-pill--upgrade { background: var(--bs-butter); color: var(--bs-ink); }
.bas-ds-btn .bas-ds-pill { margin-left: 8px; padding: 2px 9px; vertical-align: 1px; }
html[data-bas-theme="dark"] .bas-ds-pill       { background: #2a2a2c; color: var(--bs-ink); }
html[data-bas-theme="dark"] .bas-ds-pill--muted { background: #2a2a2c; color: var(--bs-ink-muted); }

/* ── Money — a figure that is either kept or lost ─────────────────────────────
 * 7.8.0. Business profit now appears on the products list and on My Stats, and
 * the Orders screen is next (handoff §4.2). Three pages, one treatment, defined
 * once — the alternative is what this file exists to stop.
 *
 * 🔴 Brand colours only. Dark Forest Green for money kept, Burnt Orange for
 * money lost. Amy, after a pricing card invented a colour: "you are to only use
 * brand colors." Green-700 is unreadable on the dark surface, so dark mode
 * steps up to Green-300 rather than keeping a colour nobody can see.           */
.bas-ds-money { font-weight: 900; font-variant-numeric: tabular-nums; white-space: nowrap; }
.bas-ds-money--gain  { color: var(--bs-green-700); }
.bas-ds-money--loss  { color: var(--bs-tomato); }
.bas-ds-money--muted { color: var(--bs-muted); font-weight: 700; }
/* The margin that rides beside the figure. Quieter on purpose: the money is the
   answer, the percentage is the context. */
.bas-ds-money__sub { font-weight: 700; font-size: .85em; color: var(--bs-muted); margin-left: 5px; }
/* An estimate rather than a recorded figure — an order costed from today's
   recipe prices instead of the day it sold. Typographic, per rule 3. */
.bas-ds-money__est { font-weight: 700; font-size: .8em; color: var(--bs-muted); margin-left: 3px; }
html[data-bas-theme="dark"] .bas-ds-money--gain { color: var(--bs-green-300); }

/* ── Tables ──────────────────────────────────────────────────────────────── */
.bas-ds-scroll { overflow-x: auto; }
.bas-ds-table { width: 100%; border-collapse: collapse; font-size: 13.5px; }
.bas-ds-table th {
	font-size: 11px; font-weight: 900; letter-spacing: .1em; text-transform: uppercase;
	color: var(--bs-green-700); text-align: left; padding: 8px 10px;
	border-bottom: 2px solid var(--bs-green-300); white-space: nowrap;
}
.bas-ds-table td { padding: 9px 10px; border-bottom: 1px solid var(--bs-rule); vertical-align: middle; }
.bas-ds-table tbody tr:hover { background: var(--bs-field-bg); }
.bas-ds-table td input, .bas-ds-table td select { margin: 0; padding: 7px 9px; font-size: 14px; }
.bas-ds-table .num { text-align: right; font-variant-numeric: tabular-nums; }
.bas-ds-table tfoot td {
	border-bottom: 0; border-top: 2px solid var(--bs-green-300);
	font-weight: 900; padding-top: 12px;
}
/* Row headers (<th scope="row">) are body cells that name their row — they
   read like a cell, not like the column header strip. Without this every
   row-labelled table got a green caps label and a green rule under each row.
   (v7.48.0, first used by the Market Planner season table.) */
.bas-ds-table tbody th,
.bas-ds-table tfoot th {
	font-size: inherit; font-weight: 700; letter-spacing: normal; text-transform: none;
	color: var(--bs-ink); padding: 9px 10px; border-bottom: 1px solid var(--bs-rule);
	white-space: normal; vertical-align: middle;
}

/* ── Notices ─────────────────────────────────────────────────────────────── */
.bas-ds-tip {
	background: var(--bs-cream-2); border: 1px solid var(--bs-card-line);
	border-radius: var(--bs-r-field); padding: 12px 16px;
	font-size: 13.5px; color: var(--bs-ink-soft); margin: 0 0 16px;
}
.bas-ds-warn {
	background: #fff5f2; border: 1.5px solid rgba(255,76,37,.35);
	border-radius: var(--bs-r-field); padding: 12px 16px;
	font-size: 13.5px; color: var(--bs-ink); margin: 0 0 16px;
}
html[data-bas-theme="dark"] .bas-ds-warn { background: rgba(255,76,37,.10); }

/* 🔴 6.173.0 — a link inside a notice had no rule at all, so it fell back to the
   browser's default navy. On a dark-mode card that is navy on near-black. Every
   unclassed <a> in a tip or a warn on every page has been doing this. */
.bas-ds-tip a,
.bas-ds-warn a { color: var(--bs-green-600); font-weight: 700; text-decoration: underline; }
.bas-ds-tip a:hover,
.bas-ds-warn a:hover { color: var(--bs-green-700); }

/* Inline save feedback. Pair with role="status" aria-live="polite". */
.bas-ds-status { font-size: 13.5px; font-weight: 800; }
.bas-ds-status--ok  { color: var(--bs-green-700); }
.bas-ds-status--err { color: var(--bs-tomato); }

/* ── Empty state ─────────────────────────────────────────────────────────── */
.bas-ds-empty {
	background: var(--bs-card-bg); border: 1px solid var(--bs-card-line);
	border-radius: var(--bs-r-card); padding: 44px 20px; text-align: center;
}
.bas-ds-empty h2 { font-family: var(--font-display); font-size: 21px; margin: 0 0 6px; }
.bas-ds-empty p  { font-size: 14px; color: var(--bs-muted); margin: 0 auto 18px; max-width: 52ch; }

.bas-ds-loading { padding: 30px 0; text-align: center; color: var(--bs-muted); font-size: 14px; }

/* ── Progress ────────────────────────────────────────────────────────────── */
.bas-ds-progress { height: 6px; border-radius: var(--bs-r-pill); background: var(--bs-card-line); overflow: hidden; }
.bas-ds-progress > span {
	display: block; height: 100%; width: 0; border-radius: var(--bs-r-pill);
	background: linear-gradient(90deg, var(--bs-green-300), var(--bs-green-500));
	transition: width .35s cubic-bezier(.22,1,.36,1);
}

/* ── Accordion ───────────────────────────────────────────────────────────── */
.bas-ds-fold { border-top: 1px solid var(--bs-rule); padding-top: 14px; margin-top: 18px; }
.bas-ds-fold > summary {
	font-size: 13px; font-weight: 800; color: var(--bs-green-600);
	cursor: pointer; list-style: none;
}
.bas-ds-fold > summary::-webkit-details-marker { display: none; }
.bas-ds-fold > summary::before { content: "+ "; font-weight: 900; }
.bas-ds-fold[open] > summary::before { content: "\2212 "; }

/* ── Modal ───────────────────────────────────────────────────────────────────
 * Lives inside .bas-ds-content (z-index 999970), so 99999 here is within that
 * stacking context and correct. A modal reparented to <body> needs 9999900+.  */
/* 🔴 z-index 99999 put every modal UNDER the dashboard chrome. (v6.161.0)
   Amy: "the edit recipe window is under the tool bar cutting off the top of the
   window." The shell's own layers are an order of magnitude higher — content
   999970, the docked bar 999975, the topbar 999980, the sidebar 999990, Ask Bea
   999995 — so a five-nines modal sat below all of them and the topbar clipped its
   header. A modal is the most important thing on screen by definition: it goes
   above every one of them. */
.bas-ds-modal { position: fixed; inset: 0; z-index: 1000000; display: none; align-items: center; justify-content: center; padding: 20px; }
.bas-ds-modal.is-open { display: flex; }
.bas-ds-modal__veil { position: absolute; inset: 0; background: rgba(49,49,49,.55); }
.bas-ds-modal__box {
	position: relative; display: flex; flex-direction: column;
	background: var(--bs-card-bg); border-radius: 18px;
	width: 100%; max-width: 520px; max-height: 90vh; overflow: hidden;
	box-shadow: var(--bs-shadow-modal);
	/* 🔴 6.168.0 — the box carries .bas-ds now (for the type stack and so that
	   bare inputs inside a dialog are styled at all), but it must NOT open a
	   new `basds` container context. A modal box is 520px, which is under the
	   640px breakpoint, so every .bas-ds-row2 in every modal on the platform
	   would silently collapse to one column — including the ingredient modal,
	   whose two-column row was deliberately fixed in 6.161.0. The container
	   query stays measured against the page behind it. Take this line out and
	   you have quietly restyled a dozen dialogs nobody asked you to touch. */
	container-type: normal;
}
/* 7.61.0 — a wider dialog for one that holds a table (a pickup spot's weekly
   hours). Still no container context of its own, per the note above. */
.bas-ds-modal--wide .bas-ds-modal__box { max-width: 760px; }

.bas-ds-modal__head {
	display: flex; align-items: center; justify-content: space-between; gap: 12px;
	padding: 16px 20px; border-bottom: 1px solid var(--bs-rule); flex-shrink: 0;
}
.bas-ds-modal__head h2 { font-family: var(--font-display); font-size: 19px; margin: 0; color: var(--bs-ink); }
.bas-ds-modal__x {
	background: none; border: 0; cursor: pointer; font-size: 24px; line-height: 1;
	padding: 6px 12px; border-radius: 8px; color: var(--bs-ink-soft);
}
.bas-ds-modal__x:hover { background: var(--bs-line-2); }
.bas-ds-modal__body { padding: 20px; overflow-y: auto; }
.bas-ds-modal__foot {
	display: flex; gap: 10px; align-items: center; flex-wrap: wrap;
	padding: 16px 20px; border-top: 1px solid var(--bs-rule); flex-shrink: 0;
}

/* ── Action row ──────────────────────────────────────────────────────────── */
.bas-ds-actions {
	display: flex; gap: 12px; align-items: center; flex-wrap: wrap;
	margin-top: 24px; padding-top: 20px; border-top: 1px solid var(--bs-rule);
}
.bas-ds-actions--right { justify-content: flex-end; }

/* ── Utilities ───────────────────────────────────────────────────────────── */
.bas-ds-sr {
	position: absolute; width: 1px; height: 1px; overflow: hidden;
	clip: rect(0 0 0 0); white-space: nowrap;
}
.bas-ds-num { font-variant-numeric: tabular-nums; }

/* ── Responsive ──────────────────────────────────────────────────────────── */
@container basds (max-width: 640px) {
	.bas-ds-title { font-size: 26px; }
	.bas-ds-card  { padding: 16px; }
}

/* ── Row actions ─────────────────────────────────────────────────────────────
   The Edit / Delete pair at the end of a table row. Lives here rather than in
   each feature's own sheet — the Orders list, the products list and the
   ingredient library all want the same thing. (v6.157.0) */
.bas-ds-rowacts { display: flex; gap: 6px; justify-content: flex-end; flex-wrap: wrap; }

/* A row that no longer counts — a refunded sale, a voided one, a cancelled
   booking. Still legible, still there (removing it is how a vendor ends up
   wondering where the money went), but plainly not live. (6.173.0) */
.bas-ds-table tr.is-void td { opacity: .55; }
.bas-ds-table tr.is-void td:nth-child(4) { text-decoration: line-through; }

/* ── Docked action bar ───────────────────────────────────────────────────────
   Save / Delete / Back, always in reach instead of at the bottom of a long
   scroll. Amy, 2026-09-12: "the save bar is not at the bottom of the screen
   like the label maker and brand generator and product editor — it's just
   floating over everything."

   It was `position: sticky; bottom: 0`, which is the wrong answer and was
   already known to be the wrong answer. styles/bas-label-maker.css has carried
   the finding since 2026-09-09, after the same complaint was made twice about
   two different pages:

   🔴 THE SCROLLPORT IS NOT THE WINDOW. #bas-ds-content is position:fixed with
      overflow-y:auto, so IT scrolls, and a sticky `bottom: 0` resolves against
      its PADDING box. dashboard-shell.css puts `padding-bottom: 110px
      !important` there so content can clear the Ask Bea launcher. A sticky bar
      therefore comes to rest 110px above the window — correctly pinned, wrong
      place, and it reads as a slab floating over the page. Doing arithmetic
      against that 110px is a trap: it is !important, load-bearing, and lives in
      another file.

   🔴 AND AN overflow OTHER THAN visible ON ANY ANCESTOR KILLS STICKY SILENTLY.
      The Dokan wrappers that appear on /dashboard/settings/* routes compute to
      overflow: hidden, so on those routes a sticky bar simply rides the page
      down with no error at all.

   So it is fixed to the window, tracking .bas-ds-content's left edge — the
   Label Maker's dock, generalised so every page gets it from one class instead
   of re-deriving it and getting it wrong. Fixed is safe in this subtree:
   .bas-vm-assistant is fixed here too, and dashboard-shell.css uses
   `overflow-x: clip` on .bas-ds-content precisely so fixed children escape.

   🔴 Verify by scrolling the real page and watching getBoundingClientRect(),
      never by reading this comment back to yourself.

   THE BEA RULE: a docked bar covers the bottom of the scrollport, so two things
   must move — the page needs that much more scroll room on top of the 110px the
   shell already reserves, and Bea steps UP onto the bar rather than sitting on
   the Save button. Both are below. A dock that skips them buries the last
   control on the page, which is the bug the dock was added to fix.        */
.bas-ds-actionbar {
	position: fixed;
	left:   var(--sidebar-w, 0px);
	right:  0;
	bottom: 0;
	z-index: 999975;  /* over #bas-ds-content (999970), under the topbar (999980),
	                     the sidebar (999990) and Ask Bea (999995) */
	display: flex;
	align-items: center;
	gap: 10px;
	flex-wrap: wrap;
	margin: 0;
	padding: 12px 24px calc(12px + env(safe-area-inset-bottom, 0px));
	background: var(--bs-card-bg);
	border: 0;
	border-top: 2px solid var(--bs-card-line);
	border-radius: 0;
	box-shadow: 0 -8px 22px rgba(29, 36, 25, .12);
}
.bas-ds-actionbar .bas-ds-btn--primary { margin-left: auto; }

/* The "where this goes next" cluster inside the bar — Price it / Make a label /
   Scale a batch. A plain wrapper so the group wraps as one unit rather than
   breaking apart mid-way across a narrow window. It sits before the primary
   button, which keeps its own margin-left:auto and stays hard right. (v6.160.0) */
.bas-ds-toolgroup { display: flex; gap: 8px; flex-wrap: wrap; align-items: center; }
.bas-ds-toolgroup:empty { display: none; }

/* Track .bas-ds-content's left edge: collapsed sidebar, no BAS shell at all,
   and the 900px point where the sidebar becomes a drawer. */
.bas-ds-collapsed .bas-ds-actionbar { left: 64px; }
body:not(:has(.bas-ds-sidebar)) .bas-ds-actionbar { left: 0; }
@media (max-width: 900px) {
	.bas-ds-actionbar,
	.bas-ds-collapsed .bas-ds-actionbar { left: 0; }
}

/* Scroll room for the dock, on top of the shell's 110px for Bea. --bas-ds-actionbar-h
   is the fallback; a page whose bar is taller sets it on :root from a measured
   height, the way the Label Maker does. */
body:has(.bas-ds-actionbar) .bas-ds-content {
	padding-bottom: calc(110px + var(--bas-ds-actionbar-h, 76px)) !important;
}
/* Bea steps up onto the bar instead of covering Save. */
body:has(.bas-ds-actionbar) .bas-vm-assistant {
	bottom: calc(24px + var(--bas-ds-actionbar-h, 76px));
}

@media (max-width: 560px) {
	.bas-ds-actionbar { gap: 8px; padding: 10px 12px calc(10px + env(safe-area-inset-bottom, 0px)); }
	.bas-ds-actionbar .bas-ds-btn { flex: 1 1 auto; justify-content: center; }
	.bas-ds-toolgroup { flex: 1 1 100%; }
	.bas-ds-actionbar .bas-ds-btn--primary { margin-left: 0; }
}

/* ── Recipe card + grid ──────────────────────────────────────────────────────
   One recipe, drawn the same way everywhere it appears. These rules used to be
   inline in templates/vendor-pricing-calculator.php, so the identical markup
   that scripts/bas-pricing.js renders on Ingredients & Recipes came out with no
   card at all — a grey block with the numbers stacked down the middle. Amy,
   2026-09-12: "the My Recipes tab has its styling all kinds of confused."

   They live here rather than in a second page sheet because two pages render
   this component from one function. Written against --bs-* directly, so there
   is no page-token dependency and dark mode comes along for free. (v6.159.0) */
.bas-pc-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); gap: 16px; }
.bas-pc-rcard {
	background: var(--bs-card-bg); border: 1px solid var(--bs-card-line);
	border-radius: var(--bs-r-card); padding: 0; cursor: pointer;
	text-align: left; font-family: inherit; width: 100%; display: block; overflow: hidden;
}
.bas-pc-rcard:hover { border-color: var(--bs-green-600); transform: translateY(-2px); transition: .15s; }
/* The linked product's own photo. A recipe should look like the thing it makes. */
.bas-pc-rcard__img { display: block; width: 100%; aspect-ratio: 4/3; object-fit: cover; background: var(--bs-cream-2); }
.bas-pc-rcard__img--none {
	display: flex; align-items: center; justify-content: center;
	font-family: var(--font-display); font-size: 38px; color: var(--bs-green-700);
	background: linear-gradient(150deg, var(--bs-cream-2), var(--bs-card-bg));
}
.bas-pc-rcard__body { display: block; padding: 16px 18px; }
.bas-pc-rcard h3 { font-family: var(--font-display); font-size: 17px; font-weight: 700; margin: 0 0 2px; color: var(--bs-ink); }
.bas-pc-rcard__prod { display: block; font-size: 12px; color: var(--bs-muted); margin-bottom: 8px; }
.bas-pc-rcard dl { display: grid; grid-template-columns: auto auto; gap: 4px 12px; margin: 8px 0 12px; font-size: 13px; }
.bas-pc-rcard dt { color: var(--bs-muted); }
.bas-pc-rcard dd { margin: 0; text-align: right; font-weight: 800; font-variant-numeric: tabular-nums; color: var(--bs-ink); }

/* List view — the same card laid on its side, not a second component. */
.bas-pc-grid.is-list { grid-template-columns: 1fr; gap: 10px; }
.bas-pc-grid.is-list .bas-pc-rcard { display: grid; grid-template-columns: 92px 1fr; }
.bas-pc-grid.is-list .bas-pc-rcard__img { aspect-ratio: 1; height: 100%; }
.bas-pc-grid.is-list .bas-pc-rcard__body { padding: 14px 18px; display: flex; flex-wrap: wrap; align-items: center; gap: 6px 18px; }
.bas-pc-grid.is-list .bas-pc-rcard h3 { margin: 0; flex: 1 1 180px; }
.bas-pc-grid.is-list .bas-pc-rcard__prod { flex: 1 1 140px; margin: 0; }
.bas-pc-grid.is-list .bas-pc-rcard dl { margin: 0; grid-template-columns: repeat(4, auto); gap: 2px 14px; font-size: 12.5px; }
.bas-pc-grid.is-list .bas-pc-rcard dd { text-align: left; }

/* Grid / List toggle */
.bas-pc-views {
	display: inline-flex; gap: 4px; background: var(--bs-cream-2);
	border: 1px solid var(--bs-card-line); border-radius: var(--bs-r-pill); padding: 3px; margin-left: auto;
}
.bas-pc-viewbtn {
	border: 0; background: none; font-family: inherit; font-size: 12.5px; font-weight: 800;
	color: var(--bs-ink-soft); padding: 6px 16px; border-radius: var(--bs-r-pill); cursor: pointer;
}
.bas-pc-viewbtn.is-on { background: var(--bs-card-bg); color: var(--bs-ink); box-shadow: 0 1px 3px rgba(49,49,49,.14); }

/* The Alerts list is the same card in a single column, each one followed by the
   link that fixes it. Spacing belongs here, not as an inline margin on every
   card the JS builds. */
#bas-alerts-list { display: flex; flex-direction: column; gap: 4px; }
#bas-alerts-list .bas-pc-rcard { width: 100%; }
.bas-pc-alertlink { margin: 2px 0 18px; font-size: 13px; }
.bas-pc-alertlink a { color: var(--bs-green-600); font-weight: 800; }


/* ── Modals: clearing the dashboard chrome ───────────────────────────────────
   Amy, 2026-09-13, on the Sections editor: "Adding or editing a section is
   sitting under the dashboard menu so i can't see the top."

   🔴 IT IS NOT A z-index YOU CAN WIN FROM IN HERE. .bas-ds-content is
      position:fixed with z-index:999970 (dashboard-shell.css), which makes it a
      stacking context — so a modal nested inside it paints at 999970 no matter
      what number it carries, and the topbar at 999980 and the sidebar at 999990
      both sit on top of it. The modal was correctly centred on the viewport and
      its head was simply underneath the header.

   Two halves, and both are needed:
   1. While any modal is open, drop the topbar and sidebar BELOW the content
      layer, so the veil dims them and the modal covers them. :has() does this
      with no JS; features that already add .bas-ds-modal-open to <body> get the
      same result on older engines.
   2. Never centre a tall modal against chrome we do not control. The box starts
      below the header and uses dvh, so a long form on a phone scrolls inside
      the modal instead of hanging off the top of the screen.                  */
body:has(.bas-ds-modal.is-open) .bas-ds-topbar,
body:has(.bas-ds-modal.is-open) .bas-ds-sidebar,
body.bas-ds-modal-open .bas-ds-topbar,
body.bas-ds-modal-open .bas-ds-sidebar { z-index: 999960; }

.bas-ds-modal {
	align-items: flex-start;
	padding: calc(var(--admin-bar-h, 0px) + var(--topbar-h, 64px) + 14px) 16px 16px;
	overflow-y: auto;
	overscroll-behavior: contain;
}
.bas-ds-modal__box {
	margin: 0 auto;
	max-height: calc(100dvh - var(--admin-bar-h, 0px) - var(--topbar-h, 64px) - 30px);
}

/* ── A whole dashboard page in a window (7.64.0) ─────────────────────────────
   .bas-ds-modal--frame holds an <iframe> of another dashboard page loaded with
   ?bas_embed=1 — the Pricing Calculator opened from the product editor, so a
   vendor prices a product without leaving it. Amy: "keep the modal above the
   dashboard top menu so that it can be closed and it must be mobile friendly."

   So unlike every other dialog it does NOT start below the header: it covers
   the header, the sidebar and Ask Bea, and its own head carries the close
   button. That only works because the script appends it to <body> — inside
   .bas-ds-content (z-index 999970, a stacking context) no number would clear
   the topbar. On a phone it is the whole screen, no rounded corners, no veil
   showing round the edge.                                                    */
.bas-ds-modal--frame { z-index: 1000030; padding: 16px; align-items: stretch; overflow: hidden; }
.bas-ds-modal--frame .bas-ds-modal__box {
	max-width: 1180px;
	height: calc(100dvh - 32px);
	max-height: none;
}
.bas-ds-modal--frame .bas-ds-modal__head { padding: 10px 12px 10px 20px; }
.bas-ds-modal--frame .bas-ds-modal__x { min-width: 44px; min-height: 44px; }
.bas-ds-modal--frame .bas-ds-modal__body { position: relative; flex: 1 1 auto; min-height: 0; padding: 0; overflow: hidden; }
/* Absolutely placed so its height never depends on how a browser resolves a
   percentage inside a flex item. */
.bas-ds-modal__frame { position: absolute; inset: 0; display: block; width: 100%; height: 100%; border: 0; background: var(--bs-cream, #fffbef); }
@media (max-width: 640px) {
	.bas-ds-modal--frame { padding: 0; }
	.bas-ds-modal--frame .bas-ds-modal__box {
		height: 100dvh;
		border-radius: 0;
		padding-top: env(safe-area-inset-top, 0px);
	}
}

/* A .bas-ds-choice that IS a radio, rather than a button a script has to keep
   in sync. Same selected look, no JS to forget. (6.166.0) */
.bas-ds-choice:has(input[type="radio"]:checked),
.bas-ds-choice:has(input[type="checkbox"]:checked) {
	border-color: var(--bs-green-500);
	background: rgba(112,172,67,.08);
	box-shadow: 0 0 0 3px rgba(112,172,67,.18);
}

/* ── Touch targets ───────────────────────────────────────────────────────────
   Amy, 2026-09-13: "Please make sure all of the inputs are tall enough to be
   usable on a mobile device (and compatible with all mobile devices)."

   Keyed on the POINTER, not the viewport: a vendor on a tablet in landscape has
   a wide screen and fingers, and a narrow desktop window has a mouse. 44px is
   the floor both Apple and Google publish. The 16px font is not cosmetic —
   below 16px, iOS Safari zooms the page on focus and the vendor lands on a
   half-off-screen form, which is the "compatible with all mobile devices" half
   of that sentence.                                                           */
@media (pointer: coarse) {
	.bas-ds input[type="text"],
	.bas-ds input[type="number"],
	.bas-ds input[type="search"],
	.bas-ds input[type="email"],
	.bas-ds input[type="url"],
	.bas-ds input[type="tel"],
	.bas-ds input[type="date"],
	.bas-ds input[type="time"],
	.bas-ds input[type="password"],
	.bas-ds select,
	.bas-ds-input.bas-ds-input {
		/* The height is on the base rule now, for every pointer. What is left
		   here is the one thing that genuinely is a touch concern: below 16px,
		   iOS Safari zooms the page on focus and the vendor lands on a
		   half-off-screen form. */
		font-size: 16px;
		padding: 11px 13px;
	}
	.bas-ds textarea { font-size: 16px; padding: 11px 13px; }
	.bas-ds-btn { min-height: 46px; }
	.bas-ds-btn--sm { min-height: 44px; padding: 9px 16px; font-size: 14px; }
	.bas-ds input[type="checkbox"],
	.bas-ds input[type="radio"] { width: 22px; height: 22px; }
	/* Same reach as the base rule, or a touch device gets the old 17px box. */
	.bas-ds-check > input[type="checkbox"],
	.bas-ds-check > input[type="radio"] {
		width: 22px !important; height: 22px !important; min-width: 22px;
	}
}

/* ── Copy field ──────────────────────────────────────────────────────────────
 * A read-only value with a Copy button. 6.181.0.
 *
 * 🔴 WHY THIS IS HERE AND NOT ON A PAGE. There were NINE hand-written copies of
 * "readonly input + copy button + navigator.clipboard with an execCommand
 * fallback + swap the label to Copied! for 1.8s" — QR Codes, Share page, Refer
 * & Earn, First Sale, Brand Generator (twice), Review Cards, Events, Batch
 * Scaler. Nine sets of styles, nine sets of JS, and a vendor who learned the
 * button on one page found a different-looking one on the next. Amy, 2026-09-11:
 * fix the recurring kind once, here, "so we don't get mile long css files".
 *
 * Markup comes from BAS_VM_DS::copy_field(). The click handler is ONE delegated
 * listener in BAS_VM_Dashboard_Shell::render_offset_js(), which prints on every
 * dashboard page including the standalone feature pages — so a copy field works
 * anywhere without the page shipping a line of JS.
 *
 * The value is an <input readonly> rather than a <div> on purpose: it is
 * selectable, it is reachable by keyboard, and the execCommand fallback on an
 * old iOS Safari needs a real field to select.                                */
.bas-ds-copyfield {
	display: flex; gap: 8px; align-items: stretch;
	flex-wrap: wrap;
}
.bas-ds-copyfield__val {
	flex: 1 1 260px; min-width: 0;
	font-family: var(--font-body);
	font-size: 14px; font-weight: 700;
	color: var(--bs-ink);
	background: var(--bs-field-bg);
	border: 1.5px solid var(--bs-field);
	border-radius: var(--bs-r-pill);
	padding: 10px 16px;
	/* A link is read left-to-right and the interesting half is the END of it.
	   On a narrow content area the field scrolls; start it at the left but let
	   the vendor drag. text-overflow on an input is ignored by every browser,
	   which is why this is not ellipsised. */
	text-overflow: clip;
}
.bas-ds-copyfield__val:focus {
	outline: none;
	border-color: var(--bs-green-500);
	box-shadow: 0 0 0 3px var(--bs-focus);
}
.bas-ds-copyfield__btn { flex: 0 0 auto; }
/* The confirmed state. Kept off :active so it survives the pointer leaving. */
.bas-ds-copyfield__btn.is-copied {
	background: var(--bs-green-700); color: #fff; border-color: var(--bs-green-700);
	box-shadow: none;
}
@container basds (max-width: 460px) {
	.bas-ds-copyfield__btn { width: 100%; justify-content: center; }
}

/* ── Link card ───────────────────────────────────────────────────────────────
 * "Here is your link, here is where to use it, and here — quietly, folded away —
 * is the long address it opens." 6.181.0.
 *
 * 🔴 THE CARD HAS EXACTLY ONE COPY BUTTON, AND IT IS THE SHORT LINK. A vendor
 * asked to choose between two links will paste the wrong one roughly half the
 * time, and the wrong one is the eighty-two-character one on a jar label. The
 * long URL is not an option presented next to the short one; it is the answer
 * to "why did the address change when I clicked View shop", which is a
 * different question and belongs inside a <details>.                          */
.bas-ds-linkcard__eyebrow {
	font-size: 11px; font-weight: 900; letter-spacing: .12em;
	text-transform: uppercase; color: var(--bs-green-700);
	margin: 0 0 8px;
}
.bas-ds-linkcard__why {
	margin: 10px 0 0; font-size: 13px; line-height: 1.55; color: var(--bs-muted);
	max-width: 56ch;
}
/* The long address inside the fold. Monospace so it reads as machinery rather
   than as a second thing to copy. */
.bas-ds-linkcard__long {
	display: block; margin: 8px 0 0;
	font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
	font-size: 12.5px; line-height: 1.5; color: var(--bs-ink-soft);
	word-break: break-all;
}

/* ── Announcement banner ─────────────────────────────────────────────────────
 * A dismissible "this is new" strip above the page. 6.181.0, redrawn 7.0.0.
 *
 * 🔴 WHAT THE FIRST VERSION GOT WRONG, because the next one will be tempted the
 * same way. It was cream on cream — .bas-ds-content is already --bs-cream, so a
 * --bs-cream-2 panel on it has almost no edge and the whole thing read as part
 * of the page furniture. Amy, 2026-09-18: *"This does not stand out at all."*
 * An announcement is the one component on the page that has to interrupt.
 *
 * So: the green-tinted callout surface, which is the house pattern for "look at
 * this" (it is what the vendor emails use for their callout boxes), a full
 * green border rather than a left rail, and a NEW pill. Green is legitimate
 * here — this is not "done", it is "new", and the pill says which.
 *
 * 🔴 AND IT HAS EXACTLY ONE BUTTON. The first version put a full-size primary
 * beside a --sm outline, which is the mismatch Amy caught: *"the buttons are
 * two different sizes."* The fix is not to match the sizes. It is that a banner
 * gets ONE action — copy the thing — and anything else is a text link. Two
 * buttons in a strip this size is two decisions where the vendor should have
 * none.                                                                        */
.bas-ds-announce {
	position: relative;
	display: flex; align-items: flex-start; gap: 14px;
	background: #f4fdf6;
	border: 1.5px solid var(--bs-green-500);
	border-radius: var(--bs-r-card);
	padding: 20px 22px 20px 22px;
	margin: 0 0 20px;
	box-shadow: 0 2px 10px rgba(112,172,67,.10);
}
html[data-bas-theme="dark"] .bas-ds-announce {
	background: rgba(112,172,67,.10);
	box-shadow: none;
}
.bas-ds-announce__body { flex: 1 1 auto; min-width: 0; }

/* The pill is the thing that makes it read as news at a glance, before the
   headline is read at all. */
.bas-ds-announce__new {
	display: inline-block; margin: 0 0 8px;
	background: var(--bs-green-700); color: #fff;
	border-radius: var(--bs-r-pill); padding: 4px 12px;
	font-size: 10.5px; font-weight: 900; letter-spacing: .12em; text-transform: uppercase;
}
.bas-ds-announce__title {
	font-family: var(--font-display);
	font-size: 21px; line-height: 1.2; margin: 0 0 6px; color: var(--bs-ink);
	/* Clear of the dismiss button, which is absolutely positioned top-right. */
	padding-right: 110px;
}
@container basds (max-width: 560px) { .bas-ds-announce__title { padding-right: 0; } }
.bas-ds-announce__text {
	margin: 0; font-size: 14px; line-height: 1.55; color: var(--bs-ink-soft);
	max-width: 68ch;
}
.bas-ds-announce__text a { color: var(--bs-green-700); font-weight: 700; text-decoration: underline; }

.bas-ds-announce__actions {
	display: flex; flex-wrap: wrap; gap: 12px 16px; align-items: center;
	margin-top: 14px;
}
/* The copy field inside a banner is narrower than a card's — the banner is a
   trailer, not the feature. */
.bas-ds-announce .bas-ds-copyfield { flex: 1 1 320px; max-width: 480px; }
/* White, not --bs-field-bg: on the tinted surface the cream fill went grey and
   read as a disabled input. */
.bas-ds-announce .bas-ds-copyfield__val { background: #fff; border-color: #cde5c0; }
html[data-bas-theme="dark"] .bas-ds-announce .bas-ds-copyfield__val {
	background: var(--bs-field-bg); border-color: var(--bs-field);
}

/* The second thing to do is a link, never a second button. */
.bas-ds-announce__more {
	font-size: 13.5px; font-weight: 800; color: var(--bs-green-700);
	text-decoration: underline; white-space: nowrap;
}
.bas-ds-announce__more:hover { color: var(--bs-green-500); }

/* 🔴 The dismiss control is a LABELLED BUTTON. 7.0.0 had a bare × glyph in
   --bs-muted in the corner; Amy: "that x is really hard to see." It is also the
   only way to remove the banner, which makes it the one control here that has
   to be unmissable — and an icon-only control is unmissable to nobody. Bordered,
   named, and a real hit target on a phone. */
.bas-ds-announce__x {
	position: absolute; top: 12px; right: 12px;
	display: inline-flex; align-items: center; gap: 6px;
	min-height: 34px; padding: 6px 14px;
	background: #fff; color: var(--bs-ink-soft);
	border: 1.5px solid #cde5c0; border-radius: var(--bs-r-pill);
	font-family: inherit; font-size: 12.5px; font-weight: 800;
	line-height: 1; cursor: pointer;
}
.bas-ds-announce__x > span { font-size: 15px; line-height: 1; }
.bas-ds-announce__x:hover { background: var(--bs-green-700); border-color: var(--bs-green-700); color: #fff; }
html[data-bas-theme="dark"] .bas-ds-announce__x {
	background: var(--bs-card-bg); border-color: var(--bs-card-line); color: var(--bs-ink);
}
html[data-bas-theme="dark"] .bas-ds-announce__x:hover { background: var(--bs-green-700); color: #fff; }

@media (pointer: coarse) { .bas-ds-announce__x { min-height: 44px; } }

@container basds (max-width: 560px) {
	.bas-ds-announce { padding: 18px; }
	/* Stacked, the button stops being a corner affordance — give it its own
	   line under the content rather than overlapping the headline. */
	.bas-ds-announce__x { position: static; margin-top: 14px; width: 100%; justify-content: center; }
	.bas-ds-announce .bas-ds-copyfield { flex: 1 1 100%; max-width: none; }
}

/* ── Shop handle editor ──────────────────────────────────────────────────────
 * The {handle}.bsage.shop field, on My Store → Shop Link. 7.0.0.
 *
 * The suffix is not an input and never has been editable — showing it as static
 * text joined to the field is the only honest way to render "you are choosing
 * the part before the dot". Two inputs, or one input containing the whole
 * address, both invite a vendor to type over the bit they do not own.          */
.bas-ds-handle {
	display: flex; align-items: stretch; flex-wrap: wrap; gap: 0;
	max-width: 520px;
}
.bas-ds-handle > .bas-ds-input {
	flex: 1 1 200px; min-width: 0;
	border-top-right-radius: 0; border-bottom-right-radius: 0;
	border-right-width: 0;
}
.bas-ds-handle__suffix {
	flex: 0 0 auto;
	display: inline-flex; align-items: center;
	padding: 0 16px;
	background: var(--bs-cream-2);
	border: 1.5px solid var(--bs-field);
	border-top-right-radius: var(--bs-r-field);
	border-bottom-right-radius: var(--bs-r-field);
	font-size: 14px; font-weight: 800; color: var(--bs-muted);
	white-space: nowrap;
}
@container basds (max-width: 420px) {
	/* Stacked, the suffix stops being a suffix — give it its own full-width
	   row and square off the join rather than leaving half a pill floating. */
	.bas-ds-handle > .bas-ds-input {
		border-right-width: 1.5px;
		border-radius: var(--bs-r-field) var(--bs-r-field) 0 0;
	}
	.bas-ds-handle__suffix {
		flex: 1 1 100%; border-top-width: 0;
		border-radius: 0 0 var(--bs-r-field) var(--bs-r-field);
		justify-content: center;
	}
}

/* ═══════════════════════════════════════════════════════════════════════════
 * 7.54.0 — list pages and conversations
 *
 * Added when Email & Text was rebuilt onto this sheet. Amy: "You can see in
 * the orders tab the example of what tables look like." The Orders list has
 * the look she means — a table in a card, a toolbar over it, a two-line first
 * cell, Previous / Next under it, and rows that turn into cards on a phone —
 * but it is all private .bas-ol-* rules in templates/vendor-orders.php. Those
 * pieces are here now so the next list is built from them instead of copying
 * that file. (Orders itself still uses its own copies; migrate it the next
 * time that page is touched — BAS-DESIGN-SYSTEM.md, Migration status.)
 * ═══════════════════════════════════════════════════════════════════════════ */

/* ── Toolbar over a table ────────────────────────────────────────────────────
 * Count on the left, actions on the right. A search or select dropped in it
 * takes the room that is left.                                              */
.bas-ds-bar {
	display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
	margin: 0 0 16px;
}
.bas-ds-bar__grow { flex: 1 1 200px; min-width: 0; }
.bas-ds-bar__count { font-size: 14px; font-weight: 800; color: var(--bs-ink); margin: 0; }
.bas-ds-bar > .bas-ds-f { margin: 0; }
.bas-ds-bar .bas-ds-label { margin-top: 0; }

/* ── Table: a cell with a main line and a quiet second line ────────────────── */
.bas-ds-cell__main { display: block; font-weight: 700; color: var(--bs-ink); overflow-wrap: anywhere; }
.bas-ds-cell__sub  { display: block; font-size: 12px; color: var(--bs-muted); margin-top: 1px; overflow-wrap: anywhere; }
.bas-ds-cell--none { color: var(--bs-muted); }

/* ── Table: rows become cards on a narrow content area ─────────────────────
 * Opt in with .bas-ds-table--stack and put data-label on every <td>. The
 * first cell leads; every other cell gets its column name as a caption, so
 * nothing is hidden, it is only rearranged. Written for a content area, not
 * a window — see the note on `basds` at the top of this file.            */
@container basds (max-width: 640px) {
	.bas-ds-table--stack,
	.bas-ds-table--stack tbody { display: block; }
	.bas-ds-table--stack thead { display: none; }
	.bas-ds-table--stack tr {
		display: grid; grid-template-columns: 1fr 1fr; gap: 10px 14px;
		background: var(--bs-card-bg);
		border: 1px solid var(--bs-card-line); border-radius: 12px;
		padding: 14px; margin: 0 0 12px;
	}
	.bas-ds-table--stack tbody tr:hover { background: var(--bs-card-bg); }
	.bas-ds-table--stack td { display: block; padding: 0; border: 0; min-width: 0; }
	.bas-ds-table--stack td:first-child { grid-column: 1 / -1; }
	.bas-ds-table--stack td.is-wide { grid-column: 1 / -1; }
	.bas-ds-table--stack td[data-label]::before {
		content: attr(data-label); display: block;
		font-size: 10px; font-weight: 900; letter-spacing: .12em; text-transform: uppercase;
		color: var(--bs-green-700); margin-bottom: 2px;
	}
	.bas-ds-table--stack td:first-child::before { display: none; }
	.bas-ds-table--stack .bas-ds-rowacts { justify-content: flex-start; }
	.bas-ds-card:has(> .bas-ds-scroll > .bas-ds-table--stack) { padding: 14px; }
}
html[data-bas-theme="dark"] .bas-ds-table--stack td[data-label]::before { color: var(--bs-green-300); }

/* ── Pager ─────────────────────────────────────────────────────────────────── */
.bas-ds-pager {
	display: flex; gap: 8px; align-items: center; justify-content: center;
	flex-wrap: wrap; margin: 18px 0 0;
}
.bas-ds-pager[hidden] { display: none !important; }
.bas-ds-pager__at { font-size: 13px; color: var(--bs-ink-soft); font-variant-numeric: tabular-nums; }

/* ── Tab with a count beside its name ───────────────────────────────────────
 * A pill inside a tab keeps its own size; the tab's gap spaces it.           */
.bas-ds-tab .bas-ds-pill { padding: 2px 8px; letter-spacing: 0; }

/* ── File drop target ────────────────────────────────────────────────────────
 * A dashed box the vendor can drop a file on, or click. The real
 * <input type="file"> sits inside it, visually hidden, so the label click
 * opens the picker with no script at all.                                   */
.bas-ds-drop {
	display: block; text-align: center; cursor: pointer;
	border: 2px dashed var(--bs-field); border-radius: var(--bs-r-card);
	background: var(--bs-field-bg); padding: 28px 20px; margin: 0 0 16px;
	font-size: 14px; color: var(--bs-ink-soft);
	transition: border-color .15s ease, background .15s ease;
}
.bas-ds-drop:hover,
.bas-ds-drop.is-over { border-color: var(--bs-green-500); background: var(--bs-tint-green); }
.bas-ds-drop:focus-within { outline: 3px solid var(--bs-green-600); outline-offset: 2px; }
.bas-ds-drop strong { color: var(--bs-green-600); text-decoration: underline; }
.bas-ds-drop__file { display: block; margin-top: 6px; font-weight: 800; color: var(--bs-green-700); }
.bas-ds-drop__file:empty { display: none; }
html[data-bas-theme="dark"] .bas-ds-drop__file { color: var(--bs-green-300); }

/* ── Conversation thread ─────────────────────────────────────────────────────
 * One running conversation with one customer, drawn by the shared
 * conversation script (BAS_VM_Messages) on the Email & Text page and on the
 * order screen. What the vendor sent sits on the right in sage, the way Bea's
 * chat does it (7.52.0); what the customer sent sits on the left.           */
.bas-ds-thread {
	display: flex; flex-direction: column; gap: 10px;
	max-height: 420px; overflow-y: auto;
	padding: 2px 2px 6px; margin: 0 0 14px;
}
.bas-ds-thread > .bas-ds-loading,
.bas-ds-thread > .bas-ds-hint { margin: 0; padding: 18px 0; text-align: center; }
.bas-ds-msg {
	align-self: flex-start; max-width: 86%;
	background: var(--bs-surface-2); border: 1px solid var(--bs-card-line);
	border-radius: var(--bs-r-card); border-bottom-left-radius: 4px;
	padding: 10px 14px;
}
.bas-ds-msg--out {
	align-self: flex-end;
	background: var(--bs-tint-green); border-color: var(--bs-line-green);
	border-bottom-left-radius: var(--bs-r-card); border-bottom-right-radius: 4px;
}
.bas-ds-msg.is-failed { border-color: rgba(255,76,37,.45); background: var(--bs-tint-tomato); }
.bas-ds-msg__meta {
	display: flex; flex-wrap: wrap; align-items: center; gap: 4px 8px;
	font-size: 11.5px; color: var(--bs-muted); margin: 0 0 4px;
}
.bas-ds-msg__meta .bas-ds-pill { padding: 2px 8px; }
.bas-ds-msg__subject { font-size: 13.5px; font-weight: 800; color: var(--bs-ink); margin: 0 0 2px; overflow-wrap: anywhere; }
.bas-ds-msg__body { font-size: 14px; line-height: 1.5; color: var(--bs-ink); white-space: pre-wrap; overflow-wrap: anywhere; }
.bas-ds-msg__err { font-size: 12px; font-weight: 700; color: #b8371c; margin: 4px 0 0; }
html[data-bas-theme="dark"] .bas-ds-msg__err { color: #ff8f75; }

/* ═══════════════════════════════════════════════════════════════════════════
 * 7.55.0 — what the Orders list needed to move onto this sheet
 *
 * Amy: "If it has its own styles please make it adopt the shared styles so we
 * can keep our css clean and lean." templates/vendor-orders.php carried ~220
 * lines of private .bas-ol-* rules. The pieces below are the parts no shared
 * component already covered; everything else it used was here already.
 * ═══════════════════════════════════════════════════════════════════════════ */

/* ── Page header with actions on the right ──────────────────────────────── */
.bas-ds-top--split { display: flex; align-items: flex-end; gap: 14px 20px; flex-wrap: wrap; }
.bas-ds-top--split > :first-child { flex: 1 1 260px; min-width: 0; }
.bas-ds-top__actions { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }

/* ── Filter chips as links, with a count ─────────────────────────────────── */
a.bas-ds-chip { display: inline-flex; align-items: center; gap: 7px; text-decoration: none; }
.bas-ds-chip__n { font-size: 12px; font-weight: 900; opacity: .75; font-variant-numeric: tabular-nums; }
.bas-ds-chip.is-on .bas-ds-chip__n { opacity: .9; }
.bas-ds-chips + .bas-ds-card { margin-top: 16px; }

/* ── Toolbar controls ───────────────────────────────────────────────────── */
.bas-ds-bar > select.bas-ds-input { width: auto; flex: 0 1 240px; }
.bas-ds-bar > .bas-ds-status { flex: 1 1 100%; }
.bas-ds-bar > .bas-ds-status:empty { display: none; }
@container basds (max-width: 640px) {
	.bas-ds-bar > *,
	.bas-ds-bar > select.bas-ds-input { flex: 1 1 100%; width: 100%; }
}

/* ── An empty state inside a card doesn't draw a second frame ───────────── */
.bas-ds-card .bas-ds-empty { border: 0; background: none; padding: 36px 16px; }

/* ── Table cells: thumbnail, linked name, small tag ─────────────────────── */
.bas-ds-cellrow { display: flex; align-items: center; gap: 12px; min-width: 0; }
.bas-ds-cellrow > div { min-width: 0; }
.bas-ds-thumb {
	width: 44px; height: 44px; flex: none; object-fit: cover;
	border-radius: 9px; background: var(--bs-cream-2); border: 1px solid var(--bs-card-line);
}
a.bas-ds-cell__main { text-decoration: none; }
a.bas-ds-cell__main:hover { color: var(--bs-green-700); }
.bas-ds-cell__tag {
	display: block; font-size: 11px; font-weight: 900; letter-spacing: .08em;
	text-transform: uppercase; color: var(--bs-green-600);
}
.bas-ds-cell__sub .bas-ds-link { font-size: inherit; }
html[data-bas-theme="dark"] a.bas-ds-cell__main:hover { color: var(--bs-green-300); }

/* ── A tick-box column ──────────────────────────────────────────────────── */
.bas-ds-table .bas-ds-tick { width: 32px; padding-left: 2px; padding-right: 2px; }
.bas-ds-table .bas-ds-tick input[type="checkbox"] {
	width: 17px; height: 17px; margin: 0; accent-color: var(--bs-green-500); cursor: pointer;
}
@container basds (max-width: 640px) {
	.bas-ds-table--stack tr:has(> td.bas-ds-tick) { position: relative; padding-left: 42px; }
	.bas-ds-table--stack td.bas-ds-tick { position: absolute; left: 13px; top: 18px; width: auto; }
	.bas-ds-table--stack td.bas-ds-tick + td { grid-column: 1 / -1; }
	.bas-ds-table--stack td.bas-ds-tick::before,
	.bas-ds-table--stack td.bas-ds-tick + td::before { display: none; }
}

/* ── Round row buttons ──────────────────────────────────────────────────────
 * One-glyph actions at the end of a row (✓ complete, › open). Visible, not
 * hidden in a "…" menu, and sized to the row — Amy, 2026-09-10.            */
.bas-ds-iconbtn {
	width: 30px; height: 30px; padding: 0; flex: none;
	display: inline-flex; align-items: center; justify-content: center;
	border-radius: 50%; border: 1.5px solid var(--bs-field);
	background: var(--bs-card-bg); color: var(--bs-ink-soft);
	font-family: inherit; font-size: 13px; font-weight: 900; line-height: 1;
	text-decoration: none; cursor: pointer;
}
.bas-ds-iconbtn:hover { border-color: var(--bs-green-500); color: var(--bs-green-700); }
.bas-ds-iconbtn[disabled] { opacity: .35; cursor: default; }
.bas-ds-iconbtn[disabled]:hover { border-color: var(--bs-field); color: var(--bs-ink-soft); }
@media (pointer: coarse) { .bas-ds-iconbtn { width: 38px; height: 38px; } }
html[data-bas-theme="dark"] .bas-ds-iconbtn:hover { color: var(--bs-green-300); }

/* ── One more pill tone: waiting on the customer (pending payment) ─────── */
.bas-ds-pill--coral { background: rgba(255,118,96,.18); color: #b8371c; }
html[data-bas-theme="dark"] .bas-ds-pill--coral { background: rgba(255,118,96,.18); color: #ff8f75; }

/* ═══════════════════════════════════════════════════════════════════════════
 * 7.56.0 — what the Order screen needed to move onto this sheet
 *
 * templates/vendor-order-detail.php carried ~165 lines of private .bas-os-*
 * rules, and the Pickup-date and Reminder cards in its rail carried their own
 * inline <style> blocks on top. These are the pieces no shared component
 * covered. A receipt, a POS summary or a subscription detail page wants the
 * same line items, totals, notes and key/value list.
 * ═══════════════════════════════════════════════════════════════════════════ */

/* ── A tighter card, for a side rail ──────────────────────────────────────── */
.bas-ds-card--compact { padding: 18px; }
.bas-ds-card--compact > h2 { font-size: 18px; }

/* ── Line items ───────────────────────────────────────────────────────────── */
.bas-ds-item { display: flex; gap: 14px; padding: 14px 0; border-bottom: 1px solid var(--bs-rule); }
.bas-ds-item:first-of-type { padding-top: 0; }
.bas-ds-item:last-of-type { border-bottom: 0; padding-bottom: 0; }
.bas-ds-item > .bas-ds-thumb { width: 60px; height: 60px; border-radius: var(--bs-r-field); }
.bas-ds-item__body { flex: 1 1 auto; min-width: 0; }
.bas-ds-item__name { font-weight: 700; font-size: 15px; margin: 0 0 2px; color: var(--bs-ink); }
.bas-ds-item__name a { color: inherit; text-decoration: none; }
.bas-ds-item__name a:hover { color: var(--bs-green-700); }
.bas-ds-item__right { text-align: right; white-space: nowrap; }
.bas-ds-item__qty { font-size: 14px; font-weight: 700; color: var(--bs-ink-soft); }
.bas-ds-item__total { font-size: 15px; font-weight: 900; color: var(--bs-green-700); margin-top: 2px; font-variant-numeric: tabular-nums; }
.bas-ds-item__body .bas-ds-pill { margin-top: 6px; }
/* WooCommerce's item meta (wc_display_item_meta) — options the customer chose. */
.bas-ds-item__meta, .bas-ds-item__meta ul { list-style: none; margin: 6px 0 0; padding: 0; font-size: 13px; line-height: 1.5; color: var(--bs-ink-soft); }
.bas-ds-item__meta li { list-style: none; margin: 0; padding: 0; }
.bas-ds-item__meta p { margin: 0; display: inline; }
.bas-ds-item__meta strong { color: var(--bs-ink); font-weight: 700; }
@container basds (max-width: 640px) {
	/* Price and quantity drop under the item rather than fighting the name for
	   the last 60px. The thumbnail keeps its column so the list still scans. */
	.bas-ds-item { flex-wrap: wrap; }
	.bas-ds-item > .bas-ds-thumb { width: 52px; height: 52px; }
	.bas-ds-item__right {
		flex: 1 0 100%; display: flex; align-items: baseline; gap: 10px;
		text-align: left; padding-left: 66px;
	}
}
html[data-bas-theme="dark"] .bas-ds-item__total { color: var(--bs-green-300); }

/* ── Totals under a list of items ─────────────────────────────────────────── */
.bas-ds-totals { margin-top: 18px; padding-top: 14px; border-top: 2px solid var(--bs-green-300); }
.bas-ds-totals__row { display: flex; justify-content: space-between; gap: 16px; font-size: 14px; color: var(--bs-ink-soft); padding: 4px 0; font-variant-numeric: tabular-nums; }
.bas-ds-totals__row--grand { font-size: 17px; font-weight: 900; color: var(--bs-green-700); padding-top: 9px; }
html[data-bas-theme="dark"] .bas-ds-totals__row--grand { color: var(--bs-green-300); }

/* ── Notes (an order's history, newest first) ─────────────────────────────── */
.bas-ds-note {
	padding: 12px 14px; margin: 0 0 10px;
	background: var(--bs-field-bg); border: 1px solid var(--bs-rule); border-radius: var(--bs-r-field);
}
.bas-ds-note--sent { background: var(--bs-tint-butter); border-color: var(--bs-tag-butter); }
.bas-ds-note__meta { font-size: 11.5px; color: var(--bs-muted); margin: 0 0 4px; }
.bas-ds-note__tag { font-weight: 900; letter-spacing: .06em; text-transform: uppercase; color: var(--bs-butter-ink); }
.bas-ds-note__body { font-size: 14px; line-height: 1.55; color: var(--bs-ink); overflow-wrap: anywhere; }
.bas-ds-note__body p { margin: 0 0 6px; }
.bas-ds-note__body p:last-child { margin: 0; }

/* ── Key / value list (dl) ────────────────────────────────────────────────── */
.bas-ds-kv { font-size: 14px; line-height: 1.6; margin: 0; }
.bas-ds-kv dt {
	font-size: 11px; font-weight: 900; letter-spacing: .14em; text-transform: uppercase;
	color: var(--bs-green-700); margin: 14px 0 0;
}
.bas-ds-kv dt:first-of-type { margin-top: 0; }
.bas-ds-kv dd { margin: 2px 0 0; overflow-wrap: anywhere; }
.bas-ds-kv a { color: var(--bs-green-600); text-decoration: none; }
.bas-ds-kv a:hover { text-decoration: underline; }
.bas-ds-kv .bas-ds-tip { margin: 6px 0 0; }
html[data-bas-theme="dark"] .bas-ds-kv dt { color: var(--bs-green-300); }

/* ── The [hidden] trap, closed for two more components ─────────────────────
 * .bas-ds-btn (inline-flex) and .bas-ds-hint (block) set a display, so the
 * UA sheet's [hidden] loses to them and a "hidden" button or hint stays on
 * screen. Found moving the Pickup-date card over (its "Change the date"
 * button hides while the form is open), and it was also quietly keeping the
 * Export CSV button and the text character count visible on Email & Text. */
.bas-ds-btn[hidden],
.bas-ds-hint[hidden] { display: none !important; }
