/* ══════════════════════════════════════════════════════════════════════════
   UNIVERSAL KIT — COMPONENTS
   ══════════════════════════════════════════════════════════════════════════
   Loaded by admin.css and app.css after kit-tokens.css. A component defined
   here is defined ONCE; each side varies it with a modifier rather than by
   redeclaring it.

   ok-card is the first, and the measurement that decided its shape is worth
   keeping: it was declared three times — twice in admin.css, disagreeing with
   itself, and once in app.css — across 120 admin uses and 3 front-end uses.
   The base below is the admin's effective definition property for property,
   because moving 120 screens to spare 3 is the wrong direction.
   ══════════════════════════════════════════════════════════════════════════ */

/* A panel: a surface, a hairline, a radius, and a lift small enough to read as
   an edge rather than a shadow.

   margin-bottom is in the BASE rather than a modifier, and that is a decision
   about work, not about tidiness: admin stacks cards down a column in 120
   places, the front end puts them in grids that own their gap in 3. Opting the
   3 out with a modifier that already existed is smaller and safer than adding a
   class to 120. */
.ok-card{
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  box-shadow: var(--shadow-sm);
  overflow: hidden;
  margin-bottom: var(--sp-8);
}

/* For a card inside a grid or a rail, where the container owns the spacing. */
.ok-card--flush{ margin-bottom: 0; }

/* A card that IS a link — the whole panel is an anchor. This is where the
   front-end cards' flex column, inherited colour, removed underline and hover
   transition live; they were never card properties, they were anchor
   properties that happened to be written into the card. */
.ok-card--link{
  display: flex;
  flex-direction: column;
  color: inherit;
  text-decoration: none;
  transition: box-shadow var(--dur), transform var(--dur);
}


/* ── ok-btn ───────────────────────────────────────────────────────────────
   A button is the same IDEA on both sides and not the same size, and the
   measurement says so plainly: five properties already agreed, six differed,
   and the six were not drift. The front-end button is 41% taller and 11%
   larger than the admin's, and below 820px it carries min-height:44px — a
   touch target a toolbar does not need.

   So the contract is shared and the measurements are named. Unifying the sizes
   would mean either shrinking 125 customer-facing buttons past their touch
   target or growing 305 admin buttons and re-flowing every dense toolbar. That
   wins a compliance number by making one side of the product worse.

   Each side declares the five size tokens in its own :root. Nothing renders
   differently; what changes is that the agreed half now has one source and the
   differing half is declared rather than duplicated. */
/* letter-spacing is a token because it was on the admin button and absent from
   the front-end one. Dropping it would have retracked 305 admin buttons — not
   proposed in the mockup and not asked for. Caught by diffing the old
   stylesheets against the new ones rather than against a baseline typed by
   hand, which is what reported three false changes on the first pass. */
.btn,
.ok-btn{
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: var(--panel);
  border: 1px solid var(--line-2);
  color: var(--ink);
  cursor: pointer;
  white-space: nowrap;

  border-radius: var(--btn-radius);
  padding:       var(--btn-pad);
  font-size:     var(--btn-fs);
  font-weight:   var(--btn-fw);
  gap:           var(--btn-gap);
  transition:    var(--btn-dur);
  letter-spacing: var(--btn-ls);
}


/* ── ok-field ─────────────────────────────────────────────────────────────
   A labelled control: the label above, the control below, a small gap.

   This one is in the Kit for a reason the measurement found rather than a
   tidiness argument. `.field` and `.inp` are styled in admin.css and appear
   21 times in front-end templates with ZERO rules behind them — the product
   inquiry form renders four labelled inputs as bare HTML, with the label and
   the box run together and the input at browser default.

   So this is not "unify two definitions". There was one definition and one set
   of consumers that had been getting nothing. The values are the admin's,
   because the admin's is the one that was designed. */
/* No margin-bottom here, deliberately. The admin's .field has none at top level
   — its spacing comes from the grids and sections around it — so putting one in
   the base would have added 12px under 432 admin fields. Caught by diffing the
   effective definition before and after rather than assuming the base was
   additive. Spacing stays with whatever lays the fields out. */
.field,
.ok-field{
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
}
.ok-field label,
.field > label{
  font-size: var(--fs-label, 11.5px);
  font-weight: 600;
  color: var(--muted);
}

/* The control itself. Sized from the button tokens so a field and a button on
   the same row line up, which they did not before. */
.inp,
.ok-input{
  width: 100%;
  background: var(--panel);
  border: 1px solid var(--line);
  border-radius: var(--r-xs, 6px);
  color: var(--ink);
  font-size: var(--btn-fs);
  padding: var(--btn-pad);
  outline: none;
  transition: border-color var(--dur), box-shadow var(--dur);
}
.inp:focus,
.ok-input:focus{
  border-color: var(--brand, var(--info));
  box-shadow: 0 0 0 3px var(--brand-glow, rgba(37,99,235,.15));
}
textarea.inp,
textarea.ok-input{ resize: vertical; min-height: 96px; }


/* ── Skeleton ─────────────────────────────────────────────────────────────
   A shimmering placeholder for content that has not arrived yet.

   This existed in app.css and NO view used it — the front end had the rule and
   never reached for it, and the admin, where waiting actually happens (media
   libraries, analytics, long lists), had no rule at all. Moved to the Kit so
   one definition serves both, and given the size modifiers the admin needs so
   a placeholder can be a line of text or a whole tile without anyone writing
   fresh dimensions at the call site.

   Honest about what it does NOT do: nothing here shows a skeleton. Wiring one
   into a screen means that screen knowing it is loading, which is a per-screen
   change. This is the piece those screens will share. */
.ok-skeleton{
  background: linear-gradient(90deg,
    var(--bg-soft, #f2f4f7) 25%, var(--line) 50%, var(--bg-soft, #f2f4f7) 75%);
  background-size: 200% 100%;
  animation: ok-skl 1.4s linear infinite;
  border-radius: var(--r-xs, 6px);
}
.ok-skeleton--text{ height: 12px; margin-bottom: var(--sp-4); }
.ok-skeleton--title{ height: 20px; width: 45%; margin-bottom: var(--sp-6); }
.ok-skeleton--tile{ aspect-ratio: 4 / 3; border-radius: var(--r-sm); }
.ok-skeleton--row{ height: 44px; margin-bottom: var(--sp-2); }

@keyframes ok-skl{
  0%   { background-position: 200% 0; }
  100% { background-position: -200% 0; }
}

/* Motion is decoration here; a person who has asked for less of it should get
   a flat placeholder, not a pulsing one. */
@media (prefers-reduced-motion: reduce){
  .ok-skeleton{ animation: none; }
}


/* ── ok-check ─────────────────────────────────────────────────────────────
   A checkbox with its label, as one clickable row.

   The public form builder rendered these with the layout written inline —
   flex, gap, padding, border and radius typed into the markup on two elements.
   Admin had `.ef-check-row` doing the same job in CSS; the front end had the
   same idea and no name for it, so the two could not agree even in principle.

   Named for the ROLE, so a checkbox in a contact form and one in a settings
   panel are the same component with different surroundings. */
.ok-check{
  display: flex;
  align-items: center;
  gap: var(--sp-4);
  font-weight: 500;
  cursor: pointer;
}
/* The boxed variant — a single consent-style checkbox that stands on its own
   rather than sitting in a list. */
.ok-check--boxed{
  gap: var(--sp-5);
  padding: var(--sp-5) var(--sp-7);
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
}
.ok-check-list{ display: flex; flex-direction: column; gap: var(--sp-3); margin-top: var(--sp-2); }

/* The admin's stacked variant: rows in a list, separated by a rule rather than
   boxed. `.ef-check-row` is 29 markup sites and keeps its name — it composes
   with ok-check rather than being renamed, because renaming 29 sites to reach
   the same rendering is work with no result. */
.ok-check--row,
.ef-check-row{
  display: flex;
  align-items: center;
  gap: var(--sp-5);
  padding: var(--sp-4) 0;
  border-bottom: 1px solid var(--line);
}

/* A public form's field label. Sized larger than the admin's `.ok-field label`
   on purpose: a visitor filling a contact form reads it once, an operator
   scanning a settings panel reads forty. Same role, different reading. */
.ok-flabel{display:block;font-size:13px;font-weight:700;margin-bottom:var(--sp-2)}

/* A card that sits on a TINTED page rather than a white one.
   The admin's settings panels sit on a grey page, so --surface (itself grey)
   would leave them with no edge but their border. --panel is the surface that
   contrasts with the page it is on. Not a variant of taste: a card on white and
   a card on grey are answering different questions. */
.ok-card--on-tint{ background: var(--panel); }


/* ── Button states ────────────────────────────────────────────────────────
   The base shipped in 11.312 with no hover, no focus ring and no disabled
   state. A button that does not react to the pointer reads as decoration, and
   one with no focus ring is unusable by keyboard — which is not a style
   preference, it is whether the control works for everyone.

   :focus-visible rather than :focus, so a mouse click does not leave a ring
   behind while a Tab press does. */
.btn:hover,
.ok-btn:hover{ border-color: var(--line-2); background: var(--surface-2, var(--panel)); }
.btn:active,
.ok-btn:active{ transform: translateY(1px); }
.btn:focus-visible,
.ok-btn:focus-visible{
  outline: none;
  box-shadow: 0 0 0 3px var(--brand-glow, rgba(37,99,235,.25));
  border-color: var(--brand, var(--info));
}
.btn[disabled], .btn:disabled,
.ok-btn[disabled], .ok-btn:disabled{
  opacity: .55; cursor: not-allowed; transform: none;
}
.btn[disabled]:hover,
.ok-btn[disabled]:hover{ background: var(--panel); border-color: var(--line-2); }

/* ── Button variants ──────────────────────────────────────────────────────
   The front end rebuilds these constantly: a brand call to action, a WhatsApp
   button, a quiet ghost, a destructive action, and a full-width block for
   mobile. Each is a colour decision on top of the shared contract — none of
   them re-declares padding, radius or weight. */
.ok-btn--cta{
  background: var(--brand, #f5a300);
  border-color: var(--brand, #f5a300);
  color: var(--brand-ink, #1c0e00);
  font-weight: 800;
}
.ok-btn--cta:hover{ filter: brightness(.95); background: var(--brand, #f5a300); }

/* WhatsApp green is fixed on purpose. It is a brand colour, not a theme
   colour — following the site palette would make it unrecognisable, which is
   the one thing this button cannot afford. */
.ok-btn--wa{ background:#25d366; border-color:#25d366; color:#08331b; }
.ok-btn--wa:hover{ background:#1fb757; border-color:#1fb757; }

.ok-btn--ghost{ background: transparent; border-color: var(--line); }
.ok-btn--ghost:hover{ background: var(--surface-2, rgba(0,0,0,.04)); }

.ok-btn--danger{ background: var(--err); border-color: var(--err); color:#fff; }
.ok-btn--danger:hover{ filter: brightness(.94); background: var(--err); }

.ok-btn--block{ width:100%; justify-content:center; }
.ok-btn--sm{ padding: var(--sp-3) var(--sp-6); font-size: var(--fs-xs, 11.5px); }
.ok-btn--lg{ padding: var(--sp-6) var(--sp-9); font-size: 16px; }
.ok-btn--icon{ padding: var(--sp-4); aspect-ratio: 1; }


/* ── Input states and shapes ──────────────────────────────────────────────
   The base had a focus ring and nothing else: no disabled look, no invalid
   state, no placeholder colour, and select/textarea inherited none of it.

   :user-invalid rather than :invalid — :invalid is true from the moment an
   empty required field renders, so a form would open with every box already
   red. :user-invalid waits until the person has actually tried. */
.inp, .ok-input,
select.inp, select.ok-input,
textarea.inp, textarea.ok-input{ font-family: inherit; }

.inp::placeholder,
.ok-input::placeholder{ color: var(--faint); }

.inp:hover,
.ok-input:hover{ border-color: var(--line-2); }

.inp[disabled], .inp:disabled,
.ok-input[disabled], .ok-input:disabled{
  background: var(--surface-2, rgba(0,0,0,.03));
  color: var(--muted);
  cursor: not-allowed;
}

.inp:user-invalid,
.ok-input:user-invalid{ border-color: var(--err); }
.inp:user-invalid:focus,
.ok-input:user-invalid:focus{ box-shadow: 0 0 0 3px color-mix(in srgb, var(--err) 20%, transparent); }

/* A select needs its own arrow once appearance is reset, or it looks like a
   text box that cannot be typed in. */
select.ok-input, select.inp{
  appearance: none;
  background-image: linear-gradient(45deg, transparent 50%, currentColor 50%),
                    linear-gradient(135deg, currentColor 50%, transparent 50%);
  background-position: calc(100% - 18px) calc(50% + 2px), calc(100% - 13px) calc(50% + 2px);
  background-size: 5px 5px, 5px 5px;
  background-repeat: no-repeat;
  padding-right: var(--sp-10);
}

textarea.ok-input, textarea.inp{ resize: vertical; min-height: 96px; line-height: 1.55; }

/* Sizes, matching the button's so a field and a button on one row line up. */
.ok-input--sm{ padding: var(--sp-3) var(--sp-6); font-size: var(--fs-xs, 11.5px); }
.ok-input--lg{ padding: var(--sp-6) var(--sp-8); font-size: 16px; }

/* A field that failed validation, with its message. */
.ok-field--error .ok-input{ border-color: var(--err); }
.ok-field-error{ font-size: var(--fs-xs, 11.5px); color: var(--err); margin-top: var(--sp-2); }
.ok-field-hint{ font-size: var(--fs-xs, 11.5px); color: var(--muted); margin-top: var(--sp-2); }


/* ── ok-pill ──────────────────────────────────────────────────────────────
   27 front-end families declare this shape — product flags, blog tags, status
   chips, result counts. Each re-states radius, padding and weight. */
.ok-pill{
  display: inline-flex; align-items: center; gap: var(--sp-2);
  padding: var(--sp-1) var(--sp-5);
  border-radius: var(--r-pill);
  font-size: var(--fs-xs, 11px); font-weight: 700; letter-spacing: .02em;
  background: var(--surface-2, rgba(0,0,0,.05)); color: var(--muted);
  white-space: nowrap;
}
.ok-pill--ok{ background: color-mix(in srgb, var(--ok) 14%, transparent); color: var(--ok); }
.ok-pill--warn{ background: color-mix(in srgb, var(--warn) 16%, transparent); color: var(--warn); }
.ok-pill--err{ background: color-mix(in srgb, var(--err) 12%, transparent); color: var(--err); }
.ok-pill--brand{ background: color-mix(in srgb, var(--brand, #f5a300) 16%, transparent); color: var(--brand-ink, #7a4f00); }
/* A strip of pills over an image — the product card's flags. It wraps, because
   three flags on a 280px card do not fit one line (11.322). */
.ok-pills{ display:flex; flex-wrap:wrap; gap: var(--sp-3); row-gap: var(--sp-3); }

/* ── ok-grid ──────────────────────────────────────────────────────────────
   42 families declare their own. Columns are a property, not a new class, so a
   grid changes at the call site instead of gaining a variant. */
.ok-grid{ display:grid; gap: var(--gap-grid); grid-template-columns: repeat(var(--cols, 3), minmax(0, 1fr)); }
@media (max-width: 768px){
  .ok-grid{ grid-template-columns: repeat(var(--cols-m, 1), minmax(0, 1fr)); }
}

/* ── ok-tile ──────────────────────────────────────────────────────────────
   24 families build a square with a radius and a centred glyph: category
   icons, service icons, media placeholders, the floating bar. */
.ok-tile{
  width: 38px; height: 38px; flex: none;
  display: grid; place-items: center;
  border-radius: var(--r-sm);
  background: var(--surface-2, rgba(0,0,0,.05)); color: var(--muted);
  overflow: hidden;
}
.ok-tile--lg{ width: 52px; height: 52px; border-radius: var(--r); }
.ok-tile--brand{ background: color-mix(in srgb, var(--brand, #f5a300) 14%, transparent); color: var(--brand-ink, #7a4f00); }

/* ── ok-rail ──────────────────────────────────────────────────────────────
   20 families declare a sticky sidebar and each works out its own offset. */
.ok-rail{ position: sticky; top: var(--sp-8); align-self: start; }

/* ── ok-thumb ─────────────────────────────────────────────────────────────
   A product thumbnail: a fixed-ratio box that crops rather than stretches, and
   holds its shape before the image loads so the grid does not jump.

   aspect-ratio is set from a property so a square catalogue and a 4:3 gallery
   are the same component. */
.ok-thumb{
  position: relative;
  aspect-ratio: var(--thumb-ratio, 1);
  background: var(--surface-2, rgba(0,0,0,.04));
  border-radius: var(--r-sm);
  overflow: hidden;
  display: block;
}
.ok-thumb > img{ width:100%; height:100%; object-fit: cover; display:block; }
/* contain, for a logo or a diagram that must not be cropped. */
.ok-thumb--contain > img{ object-fit: contain; }
.ok-thumb--ratio-4-3{ --thumb-ratio: 4 / 3; }
.ok-thumb--ratio-16-9{ --thumb-ratio: 16 / 9; }


/* ── ok-gallery ───────────────────────────────────────────────────────────
   A main image with a thumbnail strip. The product page has 98 pgal- rules
   doing this; the blog and service pages each rebuilt a smaller version.

   Composes rather than repeats: the frames are ok-thumb, so a gallery inherits
   the ratio behaviour and the pre-load box instead of stating them again. */
.ok-gallery{ display:flex; flex-direction:column; gap: var(--sp-6); }
.ok-gallery-main{ --thumb-ratio: 4 / 3; border-radius: var(--r); }
.ok-gallery-strip{
  display:flex; gap: var(--sp-4);
  overflow-x:auto; scroll-snap-type: x mandatory;
  /* The strip scrolls on narrow screens; hiding the bar would remove the only
     signal that there is more to see. It is styled thin instead. */
  scrollbar-width: thin;
}
.ok-gallery-strip > *{ flex: 0 0 72px; scroll-snap-align: start; }
.ok-gallery-strip .ok-thumb{ cursor: pointer; opacity: .72; transition: opacity var(--dur), outline-color var(--dur); outline: 2px solid transparent; }
.ok-gallery-strip .ok-thumb:hover{ opacity: 1; }
.ok-gallery-strip .ok-thumb.is-active{ opacity: 1; outline-color: var(--brand, var(--info)); }
/* A side strip on desktop, under the image on mobile. */
.ok-gallery--side{ display:grid; grid-template-columns: 72px minmax(0,1fr); gap: var(--sp-6); }
.ok-gallery--side .ok-gallery-strip{ flex-direction: column; overflow-x: visible; overflow-y: auto; }
@media (max-width: 768px){
  .ok-gallery--side{ grid-template-columns: minmax(0,1fr); }
  .ok-gallery--side .ok-gallery-strip{ flex-direction: row; }
}

/* ── ok-lightbox ──────────────────────────────────────────────────────────
   Full-screen image view. Uses [hidden] rather than a class, so a browser with
   no CSS still hides it and the state lives in one attribute.

   dvh, not vh: on a phone the address bar makes vh taller than the screen, so
   a vh-sized overlay is scrollable and the close button drifts off. */
.ok-lightbox{
  position: fixed; inset: 0; z-index: 2000;
  display: flex; align-items: center; justify-content: center;
  padding: var(--sp-8);
  background: rgba(8,10,14,.88);
}
.ok-lightbox[hidden]{ display: none; }
.ok-lightbox-img{
  max-width: min(100%, 1200px);
  max-height: calc(100dvh - var(--sp-10) * 2);
  object-fit: contain; border-radius: var(--r-sm);
}
.ok-lightbox-close{
  position: absolute; top: var(--sp-8); right: var(--sp-8);
  width: 40px; height: 40px; display: grid; place-items: center;
  border-radius: var(--r-pill); border: none; cursor: pointer;
  background: rgba(255,255,255,.14); color: #fff; font-size: 20px; line-height: 1;
}
.ok-lightbox-close:hover{ background: rgba(255,255,255,.24); }
.ok-lightbox-nav{
  position: absolute; top: 50%; transform: translateY(-50%);
  width: 44px; height: 44px; display: grid; place-items: center;
  border-radius: var(--r-pill); border: none; cursor: pointer;
  background: rgba(255,255,255,.14); color: #fff;
}
.ok-lightbox-nav--prev{ left: var(--sp-8); }
.ok-lightbox-nav--next{ right: var(--sp-8); }
.ok-lightbox-count{
  position: absolute; bottom: var(--sp-8); left: 50%; transform: translateX(-50%);
  color: rgba(255,255,255,.75); font-size: var(--fs-sm, 13px);
}

/* ── ok-stickybar ─────────────────────────────────────────────────────────
   The bar that follows the page: a mobile action bar at the bottom, or a
   sticky buy row at the top. One component, two edges.

   env(safe-area-inset-bottom) keeps the buttons above the iPhone home
   indicator, which otherwise sits on top of them. */
.ok-stickybar{
  position: sticky; z-index: 900;
  display: flex; align-items: center; gap: var(--sp-5);
  padding: var(--pad-tight);
  background: var(--panel);
  border-top: 1px solid var(--line);
}
.ok-stickybar--bottom{
  bottom: 0;
  padding-bottom: calc(var(--sp-5) + env(safe-area-inset-bottom, 0px));
}
.ok-stickybar--top{ top: 0; border-top: none; border-bottom: 1px solid var(--line); }
.ok-stickybar--fixed{ position: fixed; left: 0; right: 0; }
.ok-stickybar > .ok-btn{ flex: 1; }

/* ── ok-menu ──────────────────────────────────────────────────────────────
   A dropdown hung off a trigger — the user menu, a row's actions, a language
   picker.

   [hidden] again for state. min-width rather than width so a long item is not
   clipped, and the panel is anchored to the right edge because a menu opened
   from a top-right avatar should not run off the screen. */
.ok-menu{ position: relative; display: inline-flex; }
.ok-menu-panel{
  position: absolute; top: calc(100% + var(--sp-3)); right: 0; z-index: 1000;
  min-width: 200px; padding: var(--sp-3);
  background: var(--panel);
  border: 1px solid var(--line); border-radius: var(--r);
  box-shadow: var(--shadow);
}
.ok-menu-panel[hidden]{ display: none; }
.ok-menu-item{
  display: flex; align-items: center; gap: var(--sp-5);
  padding: var(--sp-4) var(--sp-5);
  border-radius: var(--r-xs);
  color: var(--ink); text-decoration: none; font-size: var(--fs-sm, 13.5px);
  cursor: pointer; white-space: nowrap;
}
.ok-menu-item:hover{ background: var(--surface-2, rgba(0,0,0,.05)); }
.ok-menu-item--danger{ color: var(--err); }
.ok-menu-sep{ height:1px; background: var(--line); margin: var(--sp-3) 0; }
.ok-menu-head{
  padding: var(--sp-4) var(--sp-5) var(--sp-3);
  font-size: var(--fs-xs, 11px); text-transform: uppercase;
  letter-spacing: .05em; color: var(--muted);
}
/* The avatar that usually triggers it. */
.ok-avatar{
  width: 32px; height: 32px; border-radius: var(--r-pill);
  display: grid; place-items: center; flex: none;
  background: var(--surface-2, rgba(0,0,0,.06)); color: var(--muted);
  font-size: var(--fs-sm, 13px); font-weight: 700; overflow: hidden;
}
.ok-avatar > img{ width:100%; height:100%; object-fit: cover; }


/* ── ok-product ───────────────────────────────────────────────────────────
   The product card, as a whole component rather than a frame with everything
   else typed at the call site. `.pcard` styled the box and the image and
   stopped — title, price, meta and actions had no names, so every listing
   invented its own.

   Composed, not restated: the frame is ok-card, the image ok-thumb, the flags
   ok-pills, the button ok-btn. This file adds only the arrangement. */
.ok-product{ display:flex; flex-direction:column; overflow:hidden; }
.ok-product .ok-thumb{ border-radius:0; }
.ok-product-media{ position:relative; }
/* The flag strip sits over the image, inset from the corner, and wraps —
   three flags on a 280px card do not fit one line (11.322). */
.ok-product-media .ok-pills{
  position:absolute; top:var(--sp-5); left:var(--sp-5);
  max-width:calc(100% - var(--sp-10));
}
.ok-product-body{ display:flex; flex-direction:column; gap:var(--sp-4); padding:var(--pad-card-sm); flex:1; }
.ok-product-title{
  font-size:var(--fs-ui, 14px); font-weight:700; line-height:1.35; margin:0;
  /* Two lines, then ellipsis. A three-word product and a fifteen-word one must
     leave the cards the same height, or the grid goes ragged. */
  display:-webkit-box; -webkit-line-clamp:2; -webkit-box-orient:vertical;
  overflow:hidden;
}
.ok-product-meta{ font-size:var(--fs-xs, 12px); color:var(--muted); }
.ok-product-price{ font-size:16px; font-weight:800; margin-top:auto; }
.ok-product-price small{ font-size:var(--fs-xs, 12px); font-weight:600; color:var(--muted); }
.ok-product-actions{ display:flex; gap:var(--sp-4); padding:0 var(--pad-card-sm) var(--pad-card-sm); }
.ok-product-actions > .ok-btn{ flex:1; }

/* The whole card is a link, but the actions inside it are not — so the anchor
   covers the media and text via a pseudo-element instead of wrapping
   everything, which would nest a button inside an anchor. */
.ok-product-link{ position:absolute; inset:0; z-index:1; }
.ok-product-actions{ position:relative; z-index:2; }

/* ── Row layout ───────────────────────────────────────────────────────────
   The same product, on its side. A list view is not a different component —
   it is the same one arranged along the other axis, which is why switching
   grid to list should not swap class names on every card. */
.ok-product--row{ flex-direction:row; align-items:stretch; }
.ok-product--row .ok-product-media{ flex:0 0 clamp(96px, 26%, 200px); }
.ok-product--row .ok-thumb{ height:100%; }
.ok-product--row .ok-product-body{ padding:var(--sp-6) var(--sp-7); }
.ok-product--row .ok-product-title{ -webkit-line-clamp:1; font-size:15px; }
.ok-product--row .ok-product-actions{ padding:0 var(--sp-7) var(--sp-6); }
@media (max-width: 768px){
  /* Below the single breakpoint settled in 11.336, a row becomes a card again
     — a 96px image beside two lines of text is not readable on a phone. */
  .ok-product--row{ flex-direction:column; }
  .ok-product--row .ok-product-media{ flex:none; }
}

/* ── ok-productlist ───────────────────────────────────────────────────────
   The listing shell: a toolbar, the results, and the empty case. Every listing
   in the product rebuilt this — shop, category, search, related. */
.ok-productlist{ display:flex; flex-direction:column; gap:var(--gap-grid); }
.ok-productlist-bar{
  display:flex; align-items:center; gap:var(--sp-5); flex-wrap:wrap;
  padding-bottom:var(--sp-5); border-bottom:1px solid var(--line);
}
.ok-productlist-count{ font-size:var(--fs-sm, 13.5px); color:var(--muted); margin-right:auto; }
.ok-productlist-empty{ padding:var(--sp-10) var(--sp-8); text-align:center; color:var(--muted); }


/* ══════════════════════════════════════════════════════════════════════════
   Patterns from the reference designs. Each appears in more than one of them,
   which is the test applied — a shape one design uses is that design's taste,
   a shape three use is a pattern.
   ══════════════════════════════════════════════════════════════════════════ */

/* ── ok-section ───────────────────────────────────────────────────────────
   The band a page is made of, with its heading and a "View all" on the right.
   Every reference is built from these and every one of them gives the band
   real room — which is what makes those pages feel calm and ours feel packed.
   The gap is --gap-section, so page rhythm is one decision, not forty. */
.ok-section{ display:flex; flex-direction:column; gap: var(--gap-card); margin-bottom: var(--gap-section); }
.ok-section-head{ display:flex; align-items:center; justify-content:space-between; gap: var(--sp-6); }
.ok-section-title{ font-size:20px; font-weight:800; letter-spacing:-.01em; margin:0; }
.ok-section-sub{ font-size: var(--fs-sm, 13.5px); color: var(--muted); margin: 4px 0 0; }
.ok-section-more{
  display:inline-flex; align-items:center; gap: var(--sp-2);
  font-size: var(--fs-sm, 13.5px); font-weight:600; color: var(--muted);
  text-decoration:none; white-space:nowrap;
}
.ok-section-more:hover{ color: var(--ink); }

/* ── ok-rating ────────────────────────────────────────────────────────────
   Stars and a review count. Both marketplace references show it under every
   product; ours shows nothing, so a card gives a shopper no reason to trust it.

   The stars are a background gradient clipped to a width, not five glyphs —
   half a star is common and five separate icons cannot show it. */
.ok-rating{ display:inline-flex; align-items:center; gap: var(--sp-3); font-size: var(--fs-xs, 12px); }
.ok-rating-stars{
  --pct: 100%;
  position:relative; display:inline-block; width:64px; height:12px;
  background: linear-gradient(90deg, var(--star, #f5a300) var(--pct), var(--line) var(--pct));
  -webkit-mask: repeat-x left center/12.8px 12px
    radial-gradient(circle at 50% 42%, #000 0 4.6px, transparent 4.8px);
  mask: repeat-x left center/12.8px 12px
    radial-gradient(circle at 50% 42%, #000 0 4.6px, transparent 4.8px);
}
.ok-rating-count{ color: var(--muted); }

/* ── ok-price ─────────────────────────────────────────────────────────────
   A price, optionally with what it was. Both marketplaces show the old price
   struck through beside the new one; ours has no name for either, so a
   discount cannot be shown without inventing markup at the call site. */
.ok-price{ display:inline-flex; align-items:baseline; gap: var(--sp-4); flex-wrap:wrap; }
.ok-price-now{ font-size:16px; font-weight:800; }
.ok-price-was{ font-size: var(--fs-xs, 12px); color: var(--muted); text-decoration: line-through; }
.ok-price-off{ font-size: var(--fs-xs, 12px); font-weight:700; color: var(--err); }
.ok-price-note{ font-size: var(--fs-xs, 12px); font-weight:600; color: var(--muted); }

/* ── ok-iconbtn ───────────────────────────────────────────────────────────
   A round button carrying only an icon: the wishlist heart on a card, a
   carousel arrow, a close. All three references use it; ours built each one
   separately. */
.ok-iconbtn{
  display:grid; place-items:center; flex:none;
  width:34px; height:34px; border-radius: var(--r-pill);
  border:1px solid var(--line); background: var(--panel); color: var(--muted);
  cursor:pointer; transition: var(--btn-dur);
}
.ok-iconbtn:hover{ color: var(--ink); border-color: var(--line-2); }
.ok-iconbtn--float{
  position:absolute; top: var(--sp-5); right: var(--sp-5); z-index:2;
  border-color: transparent; box-shadow: var(--shadow-sm);
}
.ok-iconbtn--lg{ width:44px; height:44px; }
.ok-iconbtn.is-on{ color: var(--err); }

/* ── ok-filters ───────────────────────────────────────────────────────────
   The row of rounded filter controls above a listing. Both shop references
   use it; ours has a filter rail and no compact row, so mobile filtering has
   nowhere to live. */
.ok-filters{ display:flex; align-items:center; gap: var(--sp-5); flex-wrap:wrap; }
.ok-filter{
  display:inline-flex; align-items:center; gap: var(--sp-4);
  padding: var(--sp-5) var(--sp-8);
  border:1px solid var(--line); border-radius: var(--r-pill);
  background: var(--panel); color: var(--ink);
  font-size: var(--fs-sm, 13.5px); font-weight:600; cursor:pointer; white-space:nowrap;
}
.ok-filter:hover{ border-color: var(--line-2); }
.ok-filter.is-on, .ok-filter.on{ border-color: var(--ink); background: var(--ink); color: var(--panel); }
.ok-filter-sep{ width:1px; height:22px; background: var(--line); }

/* ── ok-seg ───────────────────────────────────────────────────────────────
   A segmented control — All / Men / Women, Dashboard / Website. A group of
   choices where exactly one is on, which is a different control from a row of
   filters where several can be. */
.ok-seg{ display:inline-flex; padding:4px; gap:2px; background: var(--surface-2); border-radius: var(--r-pill); }
.ok-seg-item{
  display:inline-flex; align-items:center; gap: var(--sp-3);
  padding: var(--sp-4) var(--sp-8); border:none; background:transparent;
  border-radius: var(--r-pill); color: var(--muted);
  font-size: var(--fs-sm, 13.5px); font-weight:600; cursor:pointer; white-space:nowrap;
}
.ok-seg-item.is-on, .ok-seg-item.on{ background: var(--panel); color: var(--ink); box-shadow: var(--shadow-sm); }

/* ── ok-stepper ───────────────────────────────────────────────────────────
   Quantity, minus and plus. The cart reference uses it on every line. */
.ok-stepper{
  display:inline-flex; align-items:center;
  border:1px solid var(--line); border-radius: var(--r-sm); overflow:hidden;
}
.ok-stepper button{
  width:32px; height:32px; display:grid; place-items:center;
  border:none; background:transparent; color: var(--muted);
  font-size:16px; line-height:1; cursor:pointer;
}
.ok-stepper button:hover{ background: var(--surface-2); color: var(--ink); }
.ok-stepper input{
  width:40px; height:32px; border:none; text-align:center;
  background:transparent; color: var(--ink); font: inherit; font-weight:700;
  /* The spinners duplicate the buttons either side and make the field wider
     than the number it holds. */
  -moz-appearance:textfield;
}
.ok-stepper input::-webkit-outer-spin-button,
.ok-stepper input::-webkit-inner-spin-button{ -webkit-appearance:none; margin:0; }

/* ── ok-summary ───────────────────────────────────────────────────────────
   Label-and-value rows with a total: an order summary, a spec block, a
   totals panel. */
.ok-summary{ display:flex; flex-direction:column; gap: var(--sp-5); }
.ok-summary-row{ display:flex; align-items:baseline; justify-content:space-between; gap: var(--sp-6); }
.ok-summary-row dt, .ok-summary-label{ color: var(--muted); font-size: var(--fs-sm, 13.5px); margin:0; }
.ok-summary-row dd, .ok-summary-value{ margin:0; font-weight:600; }
.ok-summary-total{
  display:flex; align-items:baseline; justify-content:space-between; gap: var(--sp-6);
  padding-top: var(--sp-6); border-top:1px solid var(--line);
}
.ok-summary-total .ok-summary-value{ font-size:20px; font-weight:800; }

/* ── ok-linecard ──────────────────────────────────────────────────────────
   A cart or order line: thumbnail, title with a variant under it, a control,
   a price, a remove. The cart reference is entirely made of these. */
.ok-linecard{ display:flex; align-items:center; gap: var(--gap-card); padding: var(--pad-card-sm); }
.ok-linecard .ok-thumb{ width:64px; flex:none; }
.ok-linecard-body{ flex:1; min-width:0; }
.ok-linecard-title{ font-weight:700; font-size: var(--fs-ui, 14px); margin:0; }
.ok-linecard-meta{ font-size: var(--fs-xs, 12px); color: var(--muted); margin-top:2px; }
@media (max-width: 768px){
  .ok-linecard{ flex-wrap:wrap; }
  .ok-linecard-body{ flex:1 1 60%; }
}

/* ── ok-brandtile ─────────────────────────────────────────────────────────
   A logo, a name and a line under it — official stores, payment methods,
   supplier logos. Two references use it for three different things, which is
   the argument for it being one component. */
.ok-brandtile{
  display:flex; align-items:center; gap: var(--sp-6);
  padding: var(--pad-card-sm); text-decoration:none; color:inherit;
}
.ok-brandtile-name{ font-weight:700; font-size: var(--fs-ui, 14px); }
.ok-brandtile-note{ font-size: var(--fs-xs, 12px); color: var(--muted); }
/* Selectable, for a payment method. */
.ok-brandtile--pick{ border:1px solid var(--line); border-radius: var(--r-sm); cursor:pointer; justify-content:center; }
.ok-brandtile--pick.is-on{ border-color: var(--brand); box-shadow: 0 0 0 1px var(--brand); }

/* ── ok-hero ──────────────────────────────────────────────────────────────
   The split banner every reference opens with: a wide panel and a narrow one
   beside it, stacking on a phone. */
.ok-hero{ display:grid; gap: var(--gap-grid); grid-template-columns: minmax(0,2fr) minmax(0,1fr); }
.ok-hero-panel{ display:flex; flex-direction:column; justify-content:center; gap: var(--sp-6); padding: var(--sp-12); border-radius: var(--r-lg); }
.ok-hero-kicker{ font-size: var(--fs-xs, 12px); font-weight:800; letter-spacing:.08em; text-transform:uppercase; }
.ok-hero-title{ font-size:34px; line-height:1.1; font-weight:800; letter-spacing:-.02em; margin:0; }
.ok-hero-text{ color: var(--muted); margin:0; max-width:46ch; }
@media (max-width: 768px){
  .ok-hero{ grid-template-columns: minmax(0,1fr); }
  .ok-hero-title{ font-size:26px; }
  .ok-hero-panel{ padding: var(--sp-10); }
}

/* ── ok-navlist ───────────────────────────────────────────────────────────
   The vertical nav with an active pill — the dashboard reference's sidebar,
   and the shape our own admin nav already has without a name. */
.ok-navlist{ display:flex; flex-direction:column; gap:2px; }
.ok-navlist-item{
  display:flex; align-items:center; gap: var(--sp-6);
  padding: var(--sp-5) var(--sp-7); border-radius: var(--r-sm);
  color: var(--muted); text-decoration:none;
  font-size: var(--fs-sm, 13.5px); font-weight:600;
}
.ok-navlist-item:hover{ background: var(--surface-2); color: var(--ink); }
.ok-navlist-item.is-on, .ok-navlist-item.on{ background: var(--brand); color: var(--brand-ink); }
.ok-navlist-head{
  padding: var(--sp-6) var(--sp-7) var(--sp-3);
  font-size: var(--fs-xs, 11px); text-transform:uppercase; letter-spacing:.05em; color: var(--faint);
}


/* ══════════════════════════════════════════════════════════════════════════
   Mobile. Measured rather than assumed: every component was checked for a
   fixed width wider than a phone, a flex row that cannot wrap, and a tap
   target under 44px. Four broke and six were too small to hit reliably.
   ══════════════════════════════════════════════════════════════════════════ */

/* Tap targets. 44px is Apple's minimum and roughly the width of an adult
   fingertip; below it, misses become common — and a miss on "remove from cart"
   is a different thing from a miss on a desktop hover menu.

   Enforced with min-height and centring rather than by growing padding, so the
   component looks the same on a desktop and is merely easier to hit on a
   touch device. Applied on (pointer: coarse) — a mouse does not need it, and
   growing everything for everyone would waste space on the screens that have
   least of it anyway. */
@media (pointer: coarse){
  .ok-btn, .ok-btn--sm,
  .ok-filter, .ok-seg-item,
  .ok-menu-item, .ok-navlist-item,
  .ok-nav-item, .ok-mega-link, .ok-search-sug, .ok-catcard--row{
    min-height: 44px;
  }
  .ok-search-btn{ width: 44px; height: 44px; }
  .ok-search input{ min-height: 44px; }

  /* Found by the gate once it derived its own list instead of using a typed
     one. Each of these is pressed on a phone and none was large enough. */
  .ok-lightbox-close, .ok-lightbox-nav{ width: 48px; height: 48px; }
  .ok-brandtile--pick{ min-height: 44px; }
  .ok-check{ min-height: 44px; }
  /* The gallery strip's frames are the tap target — a 72px thumbnail is wide
     enough, but the strip declares the height and it was not. */
  .ok-gallery-strip > *{ min-height: 44px; }
  .ok-gallery-strip{ min-height: 44px; }
  .ok-stepper{ min-height: 44px; }

  /* Caught by the gate the moment these were added — which is the point of
     deriving the list instead of typing it. */
  .ok-faq-q{ min-height: 44px; }
  .ok-variant span{ min-height: 44px; }
  .ok-variant--swatch span{ width: 44px; height: 44px; }
  .ok-tab{ min-height: 44px; }
  .ok-accordion-head{ min-height: 44px; }
  .ok-iconbtn{ width: 44px; height: 44px; }
  .ok-stepper button{ width: 44px; height: 44px; }
  .ok-stepper input{ height: 44px; }
  /* The checkbox itself, not just its label row — a 13px box is not tappable
     and the label being clickable does not help someone aiming at the box. */
  .ok-check input[type="checkbox"]{ width: 20px; height: 20px; }
}

@media (max-width: 768px){
  /* A dropdown with a 200px minimum sits fine on a phone until it is opened
     from something near the right edge, where it then runs off. Anchored to
     both edges instead, so it can never be wider than the screen. */
  .ok-menu-panel{ min-width: 0; width: max-content; max-width: calc(100vw - var(--sp-10)); }

  /* A heading and a "View all" on one line: fine until the heading is long,
     at which point the link is pushed off. It wraps under instead. */
  .ok-section-head{ flex-wrap: wrap; }
  .ok-section-title{ font-size: 18px; }

  /* Label and value on one line with a long value — "PKR 12,508,000" beside
     "Estimated delivery charge" does not fit 360px. */
  .ok-summary-row{ flex-wrap: wrap; }

  /* A cart line is five things across; on a phone the thumbnail and title stay
     on the first row and the controls move under them. */
  .ok-linecard{ align-items: flex-start; }
  .ok-linecard .ok-stepper,
  .ok-linecard .ok-price-now{ margin-top: var(--sp-4); }

  /* The filter row scrolls sideways rather than stacking. Six filters stacked
     is six rows of chrome before any product; scrolling keeps the first two
     visible and signals there are more. */
  .ok-filters{
    flex-wrap: nowrap; overflow-x: auto;
    scrollbar-width: none;
    /* Bleed to the screen edges so a half-visible chip at the edge reads as
       "scroll me" rather than as a layout mistake. */
    margin-inline: calc(var(--sp-8) * -1);
    padding-inline: var(--sp-8);
  }
  .ok-filters::-webkit-scrollbar{ display: none; }
  .ok-seg{ width: 100%; }
  .ok-seg-item{ flex: 1; justify-content: center; }

  /* Actions go full width — two half-width buttons on a 360px screen are
     narrower than the text in them. */
  .ok-product-actions{ flex-direction: column; }
  .ok-productlist-bar{ flex-wrap: wrap; }

  /* A sticky rail on a phone is a block that follows you down a single
     column, covering what you came to read. */
  .ok-rail{ position: static; }

  .ok-lightbox{ padding: var(--sp-5); }
  .ok-lightbox-close{ top: var(--sp-5); right: var(--sp-5); }
  .ok-lightbox-nav--prev{ left: var(--sp-3); }
  .ok-lightbox-nav--next{ right: var(--sp-3); }

  /* The page scale steps down — 34px headings and 24px card padding are a
     desktop's proportions, not a phone's. */
  .ok-section{ margin-bottom: var(--sp-12); }
}


/* ══════════════════════════════════════════════════════════════════════════
   Navigation and discovery.
   ══════════════════════════════════════════════════════════════════════════ */

/* ── ok-nav ───────────────────────────────────────────────────────────────
   The horizontal bar of top-level links. Ours had this shape inside the header
   builder with no name of its own, which is why the mobile drawer and the
   desktop bar could not share a single list. */
.ok-nav{ display:flex; align-items:center; gap: var(--sp-2); }
.ok-nav-item{
  display:inline-flex; align-items:center; gap: var(--sp-3);
  padding: var(--sp-5) var(--sp-7); border-radius: var(--r-sm);
  color: var(--ink); text-decoration:none;
  font-size: var(--fs-ui, 14px); font-weight:600; white-space:nowrap;
}
.ok-nav-item:hover{ background: var(--surface-2); }
.ok-nav-item.is-on{ color: var(--brand-ink); background: color-mix(in srgb, var(--brand) 16%, transparent); }
/* A parent that opens something. The chevron turns when it does. */
.ok-nav-item[aria-expanded="true"] .ok-nav-caret{ transform: rotate(180deg); }
.ok-nav-caret{ display:inline-flex; transition: transform var(--dur); }

/* ── ok-mega ──────────────────────────────────────────────────────────────
   The full-width panel under a nav item. Ours was a two-column grid with
   nothing else — no name for a column, a heading, or a link, so every mega
   panel arranged itself.

   Full-bleed and anchored to the header rather than to the item, because a
   panel hung off a right-hand item would otherwise open off-screen. */
.ok-mega{
  position:absolute; left:0; right:0; top:100%; z-index:1000;
  padding: var(--sp-12);
  background: var(--panel);
  border-top:1px solid var(--line);
  box-shadow: var(--shadow);
}
.ok-mega[hidden]{ display:none; }
.ok-mega-grid{ display:grid; gap: var(--sp-12); grid-template-columns: repeat(var(--cols, 4), minmax(0,1fr)); }
.ok-mega-col{ display:flex; flex-direction:column; gap: var(--sp-3); }
.ok-mega-head{
  font-size: var(--fs-xs, 11px); font-weight:800; letter-spacing:.05em;
  text-transform:uppercase; color: var(--muted); margin-bottom: var(--sp-3);
}
.ok-mega-link{
  display:flex; align-items:center; gap: var(--sp-5);
  padding: var(--sp-3) 0; color: var(--ink); text-decoration:none;
  font-size: var(--fs-sm, 13.5px);
}
.ok-mega-link:hover{ color: var(--brand-ink); }
/* A promotional panel in the last column — every reference puts one there. */
.ok-mega-promo{ border-radius: var(--r); overflow:hidden; padding: var(--sp-10); display:flex; flex-direction:column; justify-content:flex-end; gap: var(--sp-4); min-height:180px; }

/* ── ok-search ────────────────────────────────────────────────────────────
   The search field with its button, and the suggestions under it. There was
   no name for this at all: the header builder drew one inline, and the search
   page drew another.

   The input has no border of its own — the wrapper carries it, so the focus
   ring surrounds the field AND its button rather than half the control. */
.ok-search{
  display:flex; align-items:center;
  border:1px solid var(--line); border-radius: var(--r-pill);
  background: var(--panel); overflow:hidden;
  transition: box-shadow var(--dur), border-color var(--dur);
}
.ok-search:focus-within{
  border-color: var(--brand);
  box-shadow: 0 0 0 3px var(--brand-glow);
}
.ok-search input{
  flex:1; min-width:0; border:none; background:transparent; color: var(--ink);
  padding: var(--sp-5) var(--sp-8); font: inherit; font-size: var(--fs-ui, 14px);
}
.ok-search input:focus{ outline:none; }
.ok-search input::placeholder{ color: var(--faint); }
.ok-search-btn{
  flex:none; display:grid; place-items:center;
  width:40px; height:40px; margin: 3px; border:none; cursor:pointer;
  border-radius: var(--r-pill); background: var(--brand); color: var(--brand-ink);
}
.ok-search--plain .ok-search-btn{ background:transparent; color: var(--muted); }
.ok-search-panel{
  position:absolute; left:0; right:0; top:calc(100% + var(--sp-3)); z-index:1000;
  padding: var(--sp-4);
  background: var(--panel); border:1px solid var(--line);
  border-radius: var(--r); box-shadow: var(--shadow);
}
.ok-search-panel[hidden]{ display:none; }
.ok-search-sug{
  display:flex; align-items:center; gap: var(--sp-5);
  padding: var(--sp-5) var(--sp-6); border-radius: var(--r-xs);
  color: var(--ink); text-decoration:none; font-size: var(--fs-sm, 13.5px);
}
.ok-search-sug:hover, .ok-search-sug.is-on{ background: var(--surface-2); }

/* ── ok-mobilebar ─────────────────────────────────────────────────────────
   The fixed bar of primary actions at the bottom of a phone screen. Ours
   existed as .mob-bar with its own everything; this is the same idea named
   and composed.

   Distinct from ok-stickybar: that one is a bar within a page that sticks;
   this is the app-level bar that is always there. They look alike and behave
   differently, which is why they are two components rather than a modifier. */
.ok-mobilebar{
  display:none;
  position:fixed; left:0; right:0; bottom:0; z-index:940;
  background: var(--panel);
  border-top:1px solid var(--line);
  padding-bottom: env(safe-area-inset-bottom, 0px);
}
.ok-mobilebar-inner{ display:flex; align-items:stretch; }
.ok-mobilebar-item{
  flex:1; display:flex; flex-direction:column; align-items:center; justify-content:center;
  gap:3px; padding: var(--sp-4) var(--sp-2);
  min-height:56px;
  color: var(--muted); text-decoration:none;
  font-size:10.5px; font-weight:600; line-height:1.2;
}
.ok-mobilebar-item.is-on, .ok-mobilebar-item.on{ color: var(--brand-ink); }
.ok-mobilebar-item svg{ width:20px; height:20px; }
/* A count on a tab — cart items, unread. */
.ok-mobilebar-badge{
  position:absolute; transform: translate(12px,-8px);
  min-width:16px; height:16px; padding:0 4px;
  display:grid; place-items:center;
  border-radius: var(--r-pill); background: var(--err); color:#fff;
  font-size:10px; font-weight:800;
}
@media (max-width: 768px){
  .ok-mobilebar{ display:block; }
  /* The page needs room for it, or the bar covers the last thing on the page.
     A component that floats over content is responsible for the space it
     takes — expecting every page to remember is how footers end up hidden. */
  body:has(.ok-mobilebar){ padding-bottom: calc(56px + env(safe-area-inset-bottom, 0px)); }
}

/* ── ok-catcard ───────────────────────────────────────────────────────────
   A category: an image or icon, a name, and how many things are in it. Ours
   had .cg-card for the boxed one and .subcat-ic for the round one, sharing
   nothing. */
.ok-catcard{
  display:flex; flex-direction:column; align-items:center; gap: var(--sp-5);
  padding: var(--pad-card-sm);
  text-decoration:none; color:inherit; text-align:center;
}
.ok-catcard .ok-thumb{ width:100%; }
.ok-catcard-name{ font-weight:700; font-size: var(--fs-ui, 14px); }
.ok-catcard-count{ font-size: var(--fs-xs, 12px); color: var(--muted); }

/* The round variant — the circle of category images every marketplace opens
   with. A circle, not a rounded square: at this size the difference is what
   separates "a category" from "a product". */
.ok-catcard--round .ok-thumb{
  width:104px; aspect-ratio:1; border-radius: var(--r-pill);
  background: var(--surface-2);
}
.ok-catcard--round{ padding: var(--sp-5); }

/* The row variant — an icon beside a name, for a sidebar or a compact list. */
.ok-catcard--row{ flex-direction:row; align-items:center; text-align:left; }
.ok-catcard--row .ok-thumb{ width:44px; flex:none; }
.ok-catcard--row .ok-catcard-name{ flex:1; }

/* ── ok-catgrid ───────────────────────────────────────────────────────────
   Categories laid out. Two behaviours in one component because they are the
   same content: a wrapping grid on desktop, and on a phone a single row that
   scrolls — which is what every reference does, because eight stacked category
   tiles push the actual products below the fold. */
.ok-catgrid{ display:grid; gap: var(--gap-grid); grid-template-columns: repeat(var(--cols, 6), minmax(0,1fr)); }
@media (max-width: 768px){
  .ok-catgrid{
    display:flex; overflow-x:auto;
    scroll-snap-type: x mandatory; scrollbar-width:none;
    margin-inline: calc(var(--sp-8) * -1); padding-inline: var(--sp-8);
  }
  .ok-catgrid::-webkit-scrollbar{ display:none; }
  .ok-catgrid > *{ flex:0 0 auto; scroll-snap-align:start; width:112px; }
  .ok-catgrid > .ok-catcard--row{ width:min(72vw, 260px); }
}

/* ── ok-listcard ──────────────────────────────────────────────────────────
   A row in a list of things that are not products: an article, a download, a
   service, a search result. Ours had bside-, dl-, search-list- and svc- each
   doing it differently. */
.ok-listcard{
  display:flex; align-items:flex-start; gap: var(--gap-card);
  padding: var(--pad-card-sm);
  text-decoration:none; color:inherit;
}
.ok-listcard .ok-thumb{ width:88px; flex:none; }
.ok-listcard-body{ flex:1; min-width:0; display:flex; flex-direction:column; gap: var(--sp-3); }
.ok-listcard-title{
  margin:0; font-size: var(--fs-ui, 14px); font-weight:700; line-height:1.4;
  display:-webkit-box; -webkit-line-clamp:2; -webkit-box-orient:vertical; overflow:hidden;
}
.ok-listcard-text{
  margin:0; font-size: var(--fs-sm, 13.5px); color: var(--muted); line-height:1.5;
  display:-webkit-box; -webkit-line-clamp:2; -webkit-box-orient:vertical; overflow:hidden;
}
.ok-listcard-meta{ display:flex; align-items:center; gap: var(--sp-5); font-size: var(--fs-xs, 12px); color: var(--muted); flex-wrap:wrap; }
.ok-listcard-aside{ flex:none; display:flex; flex-direction:column; align-items:flex-end; gap: var(--sp-4); }
@media (max-width: 768px){
  .ok-listcard .ok-thumb{ width:72px; }
  .ok-listcard-aside{ flex-direction:row; align-items:center; }
}


/* ══════════════════════════════════════════════════════════════════════════
   Product page sections. Every one of these is a section the CMS already
   offers in the product schema — they are not new ideas, they are the shapes
   the schema has been rendering without names.
   ══════════════════════════════════════════════════════════════════════════ */

/* ── ok-iconbox ───────────────────────────────────────────────────────────
   Icon, heading, a line of text. The schema has offered an "Icon list" block
   for a long time and the stylesheet had NOT ONE RULE for it — so the block
   saved, rendered, and looked like unstyled HTML.

   Three arrangements because pages need all three: stacked for a feature grid,
   a row for a compact list, and centred for a three-across band. */
.ok-iconbox{ display:flex; gap: var(--gap-card); align-items:flex-start; }
.ok-iconbox-body{ flex:1; min-width:0; }
.ok-iconbox-title{ margin:0 0 var(--sp-3); font-size: var(--fs-ui, 15px); font-weight:700; }
.ok-iconbox-text{ margin:0; font-size: var(--fs-sm, 13.5px); color: var(--muted); line-height:1.6; }
.ok-iconbox--stack{ flex-direction:column; }
.ok-iconbox--center{ flex-direction:column; align-items:center; text-align:center; }

/* ── ok-feature ───────────────────────────────────────────────────────────
   A checked list of what a product does. Distinct from an icon box: a feature
   is one line asserting a fact, and giving each its own icon and paragraph
   turns a scannable list into a wall. */
.ok-features{ display:grid; gap: var(--sp-5); grid-template-columns: repeat(var(--cols, 2), minmax(0,1fr)); }
.ok-feature{ display:flex; align-items:flex-start; gap: var(--sp-5); font-size: var(--fs-sm, 13.5px); line-height:1.55; }
.ok-feature-mark{ flex:none; margin-top:2px; color: var(--ok); }
@media (max-width: 768px){ .ok-features{ grid-template-columns: minmax(0,1fr); } }

/* ── ok-specs ─────────────────────────────────────────────────────────────
   Label and value rows — the specification table.

   A real table on desktop and stacked pairs on a phone. NOT a <table> forced
   to scroll: a spec sheet read on a phone is read one row at a time, and
   sideways scrolling hides exactly the value the reader is looking for. */
.ok-specs{ width:100%; border-collapse:collapse; }
.ok-specs th, .ok-specs td{
  padding: var(--sp-6) var(--sp-7);
  text-align:left; vertical-align:top;
  border-bottom:1px solid var(--line);
  font-size: var(--fs-sm, 13.5px);
}
.ok-specs th{ color: var(--muted); font-weight:600; width:38%; }
.ok-specs td{ font-weight:600; }
.ok-specs tr:last-child th, .ok-specs tr:last-child td{ border-bottom:none; }
.ok-specs--zebra tbody tr:nth-child(odd){ background: var(--surface-2); }
@media (max-width: 768px){
  .ok-specs, .ok-specs tbody, .ok-specs tr, .ok-specs th, .ok-specs td{ display:block; width:auto; }
  .ok-specs tr{ padding: var(--sp-5) 0; border-bottom:1px solid var(--line); }
  .ok-specs th{ padding:0 0 2px; border:none; }
  .ok-specs td{ padding:0; border:none; }
}

/* ── ok-testimonial ───────────────────────────────────────────────────────
   A quote with who said it. The schema offers testimonials; the stylesheet
   had eleven rules under a different prefix and no name for the card itself. */
.ok-testimonial{ display:flex; flex-direction:column; gap: var(--gap-card); }
.ok-testimonial-quote{ margin:0; font-size: 15px; line-height:1.65; }
.ok-testimonial-by{ display:flex; align-items:center; gap: var(--sp-5); margin-top:auto; }
.ok-testimonial-name{ font-weight:700; font-size: var(--fs-sm, 13.5px); }
.ok-testimonial-role{ font-size: var(--fs-xs, 12px); color: var(--muted); }
/* The quote mark is decoration, so it is a pseudo-element — a glyph in the
   markup would be read aloud by a screen reader before the quote itself. */
.ok-testimonial--marked .ok-testimonial-quote::before{
  content:'\201C'; display:block; font-size:40px; line-height:.6;
  color: var(--line-2); margin-bottom: var(--sp-4);
}

/* ── ok-logos ─────────────────────────────────────────────────────────────
   The client and certificate strips. Both are the same thing — a row of marks
   that are not links to anywhere the reader wants to go — and the CMS offers
   both sections with ZERO rules between them.

   Greyed and lifted on hover: a wall of full-colour logos competes with the
   product for attention, which is the opposite of what a trust strip is for. */
.ok-logos{ display:grid; gap: var(--gap-grid); grid-template-columns: repeat(var(--cols, 6), minmax(0,1fr)); align-items:center; }
.ok-logo{
  display:grid; place-items:center; padding: var(--sp-7);
  filter: grayscale(1); opacity:.62; transition: var(--dur);
}
.ok-logo:hover{ filter:none; opacity:1; }
.ok-logo img{ max-width:100%; max-height:44px; object-fit:contain; }
.ok-logos--plain .ok-logo{ filter:none; opacity:1; }
@media (max-width: 768px){ .ok-logos{ grid-template-columns: repeat(3, minmax(0,1fr)); } }

/* ── ok-faq ───────────────────────────────────────────────────────────────
   Question and answer, collapsible. Built on <details> so it works with
   JavaScript off and the browser handles the open state — a div-and-a-click
   handler gets this wrong in ways nobody tests. */
.ok-faq{ display:flex; flex-direction:column; }
.ok-faq-item{ border-bottom:1px solid var(--line); }
.ok-faq-q{
  display:flex; align-items:center; justify-content:space-between; gap: var(--sp-6);
  padding: var(--sp-7) 0; cursor:pointer; list-style:none;
  font-weight:700; font-size: var(--fs-ui, 15px);
}
.ok-faq-q::-webkit-details-marker{ display:none; }
.ok-faq-mark{ flex:none; color: var(--muted); transition: transform var(--dur); }
.ok-faq-item[open] .ok-faq-mark{ transform: rotate(180deg); }
.ok-faq-a{ padding:0 0 var(--sp-8); color: var(--muted); line-height:1.65; font-size: var(--fs-sm, 13.5px); }

/* ── ok-variants ──────────────────────────────────────────────────────────
   Choosing between versions of a product — capacity, colour, reach. The CMS
   has no styling for this at all today, and it is the control a shopper uses
   most on a product page.

   The input is real and visually hidden rather than removed: a hidden radio
   still gets a focus ring, still works from the keyboard, and still groups. */
.ok-variants{ display:flex; flex-direction:column; gap: var(--sp-6); }
.ok-variant-label{ font-size: var(--fs-xs, 12px); font-weight:700; color: var(--muted); text-transform:uppercase; letter-spacing:.04em; }
.ok-variant-opts{ display:flex; flex-wrap:wrap; gap: var(--sp-4); }
.ok-variant{ position:relative; display:inline-flex; }
.ok-variant input{ position:absolute; opacity:0; width:100%; height:100%; margin:0; cursor:pointer; }
.ok-variant span{
  display:inline-flex; align-items:center; justify-content:center; gap: var(--sp-3);
  min-width:52px; padding: var(--sp-5) var(--sp-7);
  border:1px solid var(--line); border-radius: var(--r-sm);
  font-size: var(--fs-sm, 13.5px); font-weight:600;
}
.ok-variant input:checked + span{ border-color: var(--ink); background: var(--ink); color: var(--panel); }
.ok-variant input:focus-visible + span{ box-shadow: 0 0 0 3px var(--brand-glow); }
/* Out of stock is shown, not hidden — a shopper who cannot find the size they
   wanted assumes the shop does not sell it. */
.ok-variant input:disabled + span{ opacity:.45; text-decoration:line-through; cursor:not-allowed; }
/* A colour swatch is the same control with the value as its face. */
.ok-variant--swatch span{ min-width:0; width:36px; height:36px; padding:0; border-radius: var(--r-pill); background: var(--swatch, var(--surface-2)); }
.ok-variant--swatch input:checked + span{ box-shadow: 0 0 0 2px var(--panel), 0 0 0 4px var(--ink); }

/* ── ok-showcase ──────────────────────────────────────────────────────────
   Alternating image-and-text bands. The schema offers this and the stylesheet
   had one rule: a flex column with a gap. */
.ok-showcase{ display:grid; gap: var(--sp-12); align-items:center; grid-template-columns: repeat(2, minmax(0,1fr)); }
.ok-showcase-body{ display:flex; flex-direction:column; gap: var(--sp-6); }
.ok-showcase-title{ margin:0; font-size:24px; font-weight:800; letter-spacing:-.01em; }
.ok-showcase-text{ margin:0; color: var(--muted); line-height:1.7; }
/* Reversed by moving the MEDIA, not by row-reverse — with row-reverse the tab
   order follows the visual order and a keyboard user reads the bands in an
   order that alternates for no reason. */
.ok-showcase--flip .ok-showcase-media{ order:2; }
@media (max-width: 768px){
  .ok-showcase{ grid-template-columns: minmax(0,1fr); gap: var(--sp-8); }
  .ok-showcase--flip .ok-showcase-media{ order:0; }
  .ok-showcase-title{ font-size:20px; }
}


/* ── ok-tabs ──────────────────────────────────────────────────────────────
   A row of tabs and their panels. The admin had 31 rules doing this across
   three prefixes; the front end had three, and the Kit had none.

   Two things this gets right that hand-rolled tabs usually do not:

   The strip scrolls sideways on a narrow screen rather than wrapping. Wrapped
   tabs push the panel down by a row each time and the panel jumps as the user
   switches — scrolling keeps the content where it was.

   The active tab scroll-snaps into view, so on a phone tab six is reachable
   without the user discovering the strip is scrollable. */
.ok-tabs{ display:flex; flex-direction:column; gap: var(--gap-card); }
.ok-tabs-strip{
  display:flex; gap: var(--sp-2);
  border-bottom:1px solid var(--line);
  overflow-x:auto; scrollbar-width:none;
  scroll-snap-type: x proximity;
}
.ok-tabs-strip::-webkit-scrollbar{ display:none; }
.ok-tab{
  flex:none; scroll-snap-align:start;
  display:inline-flex; align-items:center; gap: var(--sp-3);
  padding: var(--sp-6) var(--sp-8);
  border:none; background:none; cursor:pointer; white-space:nowrap;
  color: var(--muted); font: inherit; font-size: var(--fs-ui, 14px); font-weight:600;
  /* The underline is a transparent border, not a pseudo-element, so the tab
     does not shift by 2px when it becomes active. */
  border-bottom:2px solid transparent;
  margin-bottom:-1px;
}
.ok-tab:hover{ color: var(--ink); }
.ok-tab.is-on, .ok-tab.on{ color: var(--ink); border-bottom-color: var(--brand); }
.ok-tab-count{ font-size: var(--fs-xs, 11px); color: var(--muted); }
.ok-tab-panel[hidden]{ display:none; }

/* Pill tabs — the same control with a different face, for when tabs sit on a
   card rather than under a heading. */
.ok-tabs--pills .ok-tabs-strip{ border-bottom:none; gap: var(--sp-3); }
.ok-tabs--pills .ok-tab{
  border-bottom:none; margin-bottom:0;
  border-radius: var(--r-pill); background: var(--surface-2);
}
/* `.ok-tab.on` was in this list without the --pills prefix, so EVERY tab strip
   in the admin took the pill treatment: an ink background with panel-coloured
   text. On a light strip that reads as the selected tab being switched off and
   the other three being the live ones — which is what the page builder's
   "Sections" tab looked like.

   From 11.368, where the Kit was taught to accept `.on` as well as `.is-on` and
   this selector picked up a bare `.ok-tab.on` on the way. */
.ok-tabs--pills .ok-tab.is-on, .ok-tabs--pills .ok-tab.on{ background: var(--ink); color: var(--panel); }

/* Vertical tabs — a rail of choices beside the panel, which is what the admin
   product editor already does. Collapses to a horizontal strip on a phone
   rather than eating half the screen width. */
.ok-tabs--side{ display:grid; grid-template-columns: 200px minmax(0,1fr); gap: var(--sp-12); }
.ok-tabs--side .ok-tabs-strip{
  flex-direction:column; border-bottom:none;
  border-right:1px solid var(--line); overflow:visible;
}
.ok-tabs--side .ok-tab{
  justify-content:flex-start; border-bottom:none; margin-bottom:0;
  border-right:2px solid transparent; margin-right:-1px;
  border-radius: var(--r-xs) 0 0 var(--r-xs);
}
.ok-tabs--side .ok-tab.is-on, .ok-tabs--side .ok-tab.on{ border-right-color: var(--brand); background: var(--surface-2); }

@media (max-width: 768px){
  .ok-tabs--side{ grid-template-columns: minmax(0,1fr); gap: var(--gap-card); }
  .ok-tabs--side .ok-tabs-strip{
    flex-direction:row; border-right:none; border-bottom:1px solid var(--line);
    overflow-x:auto;
  }
  .ok-tabs--side .ok-tab{
    border-right:none; margin-right:0;
    border-bottom:2px solid transparent; margin-bottom:-1px;
    border-radius:0;
  }
  .ok-tabs--side .ok-tab.is-on, .ok-tabs--side .ok-tab.on{ border-bottom-color: var(--brand); border-right-color:transparent; }
  /* The strip bleeds to the screen edges so a half-visible tab reads as
     "scroll me" rather than as a clipped layout. */
  .ok-tabs-strip{
    margin-inline: calc(var(--sp-8) * -1);
    padding-inline: var(--sp-8);
  }
}

/* ── ok-accordion ─────────────────────────────────────────────────────────
   The general accordion. ok-faq is this shape with question-and-answer
   wording; this is the one for anything else — spec groups, filter groups, a
   long form split into steps.

   Built on <details> so it works without JavaScript and the browser owns the
   open state. `name=` makes a group exclusive without a line of script — one
   open at a time, which is what an accordion usually means. */
.ok-accordion{ display:flex; flex-direction:column; }
.ok-accordion-item{ border-bottom:1px solid var(--line); }
.ok-accordion-item:first-child{ border-top:1px solid var(--line); }
.ok-accordion-head{
  display:flex; align-items:center; gap: var(--sp-5);
  padding: var(--sp-7) 0; cursor:pointer; list-style:none;
  font-weight:700; font-size: var(--fs-ui, 15px);
}
.ok-accordion-head::-webkit-details-marker{ display:none; }
.ok-accordion-title{ flex:1; min-width:0; }
.ok-accordion-mark{ flex:none; color: var(--muted); transition: transform var(--dur); }
.ok-accordion-item[open] > .ok-accordion-head .ok-accordion-mark{ transform: rotate(180deg); }
.ok-accordion-body{ padding:0 0 var(--sp-8); color: var(--muted); line-height:1.65; font-size: var(--fs-sm, 13.5px); }

/* Boxed: each item its own card, for when the accordion is the page rather
   than a strip inside one. */
.ok-accordion--boxed{ gap: var(--sp-5); }
.ok-accordion--boxed .ok-accordion-item{
  border:1px solid var(--line); border-radius: var(--r-lg);
  padding:0 var(--pad-card-sm);
}
.ok-accordion--boxed .ok-accordion-item:first-child{ border-top:1px solid var(--line); }


/* ══════════════════════════════════════════════════════════════════════════
   ADOPTION LAYER — the Kit takes over names the product already uses.

   The alternative was renaming 4,843 markup sites to ok-* equivalents. That is
   the same trade already settled for .ef-check-row in 11.341: renaming markup
   to arrive at IDENTICAL rendering is work with no result, and 4,843 edits is
   4,843 chances to break a screen nobody thought to test.

   So the Kit adopts the names instead. `.btn` was already an alias of ok-btn;
   these extend that to the variants and the form furniture. After this the
   classes on a form screen ARE Kit classes — they just have short names.

   Where a value differed between admin.css and app.css, the density tokens
   settle it: the shape is here, the size comes from --btn-* and --pad-*, which
   each side already sets. That is why adopting does not flatten the admin into
   the front end's proportions.
   ══════════════════════════════════════════════════════════════════════════ */

/* Button variants under their existing names. */
/* The primary button's fill is a token, because the admin uses a gradient and
   the front end a flat brand colour — a real design difference, not drift. Both
   were hard-coding it over the Kit's rule, so the Kit's version never rendered
   anywhere. */
.btn.primary,  .ok-btn.primary{
  background: var(--btn-primary-bg, var(--brand));
  border-color: var(--btn-primary-border, var(--brand));
  color: var(--btn-primary-ink, var(--brand-ink));
  font-weight: 800;
}
.btn.primary:hover, .ok-btn.primary:hover{ filter: brightness(.95); background: var(--brand); }
.btn.ghost,    .ok-btn.ghost{ background: transparent; border-color: var(--line); }
.btn.ghost:hover, .ok-btn.ghost:hover{ background: var(--surface-2, rgba(0,0,0,.04)); }
.btn.danger,   .ok-btn.danger{ background: var(--err); border-color: var(--err); color:#fff; }
.btn.danger:hover, .ok-btn.danger:hover{ filter: brightness(.94); background: var(--err); }
.btn.sm, .ok-btn.sm, .ok-btn--sm{
  padding: var(--btn-pad-sm); font-size: var(--btn-fs-sm); border-radius: var(--btn-radius-sm);
}
.btn.lg, .ok-btn.lg, .ok-btn--lg{
  padding: var(--btn-pad-lg); font-size: var(--btn-fs-lg); border-radius: var(--btn-radius-lg);
}
.btn.block,    .ok-btn.block{ width:100%; justify-content:center; }

/* Form furniture. `.hint` and `.field label` had no Kit equivalent at all —
   they are the text that tells someone what a field wants, which is the part
   of a form most likely to be read and least likely to be styled. */
.hint, .ok-field-hint{ font-size: var(--fs-xs, 11.5px); color: var(--muted); margin-top: var(--sp-2); }
.field > label, .ok-field > label{
  display:block; font-size: var(--fs-label, 11.5px); font-weight:700;
  color: var(--muted); margin-bottom: var(--sp-2);
}
.field.required > label::after, .ok-field.required > label::after{
  content:' *'; color: var(--err);
}

/* The toggle. Adopted rather than renamed because 135 markup sites use `.tog`
   and a switch is a switch. */
.tog, .ok-switch{
  -webkit-appearance:none; appearance:none; position:relative; flex:none;
  width:38px; height:22px; border-radius: var(--r-pill);
  background: var(--line-2); cursor:pointer; transition: var(--dur);
}
.tog::after, .ok-switch::after{
  content:''; position:absolute; top:3px; left:3px;
  width:16px; height:16px; border-radius: var(--r-pill);
  background:#fff; transition: transform var(--dur);
}
.tog:checked, .ok-switch:checked{ background: var(--brand); }
.tog:checked::after, .ok-switch:checked::after{ transform: translateX(16px); }
.tog:focus-visible, .ok-switch:focus-visible{ outline:none; box-shadow: 0 0 0 3px var(--brand-glow); }
@media (pointer: coarse){
  /* The switch itself stays 38px — it is the LABEL ROW that must be tappable,
     and growing the switch would make it look like a different control on a
     phone than on a desktop. */
  .ok-check:has(.tog), label:has(> .tog){ min-height: 44px; }
}

/* Utilities the product already writes, given one definition instead of two.
   Not new API — these exist in both stylesheets today with values that drifted
   apart. */
.muted, .t-muted{ color: var(--muted); }
.fs-12{ font-size:12px; }
.fs-13{ font-size:13px; }


/* ── Settings screens ─────────────────────────────────────────────────────
   346 uses across 24 classes, and five of them were Kit components under
   another name. Compared property by property, the differences were all SIZE
   — gap, padding, font-size — never shape. Size is what the density tokens
   already decide, so adopting these does not change how a settings screen
   looks; it stops it having its own opinion about what a field is. */

/* A settings field is ok-field. The gap differed by 2px, which is the sort of
   difference that exists because two people typed a number, not because
   anything needed it. */
.ms-field{ display:flex; flex-direction:column; gap: var(--sp-3); margin-bottom: var(--sp-7); }

/* The toggle row is ok-check--row with the control pushed to the far end —
   a settings row reads "label ......... switch", not "switch label". */
/* Each toggle is its own bordered card, not a ruled row. I made it a row
   first and the before/after diff caught it: the settings screen lays these
   out in a GRID (ms-toggle-grid), and a bottom border on a grid cell draws a
   line under one item and not the one beside it. */
.ms-toggle-item{
  display:flex; align-items:center; justify-content:space-between;
  gap: var(--sp-5); padding: var(--pad-tight);
  background: var(--panel);
  border:1px solid var(--line); border-radius: var(--r-sm);
}
/* auto-fill, not a fixed count: a settings panel is whatever width the sidebar
   leaves it, and a fixed column count breaks at exactly one browser size. */
.ms-toggle-grid{
  display:grid; gap: var(--gap-grid);
  grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
}

/* The section's own parts. ok-card gives the frame (11.343); these are what
   goes inside it. */
.ms-section-head{
  display:flex; align-items:center; gap: var(--sp-5);
  padding: var(--pad-panel);
  border-bottom:1px solid var(--line);
  /* --surface, not --surface-2. They are two greys and I used the wrong one;
     the before/after diff caught it. */
  background: var(--surface);
}
.ms-section-body{ padding: var(--pad-card); }
.ms-section-title{ font-size: var(--fs-ui, 15px); font-weight:700; line-height:1.3; margin:0; }
.ms-section-icon{
  width:30px; height:30px; flex:none; display:grid; place-items:center;
  border-radius: var(--r-sm);
  background: var(--brand-tint, var(--surface-2)); color: var(--brand-ink);
}

/* The settings sidebar is ok-navlist. */
.ms-sidenav{
  width:200px; flex:none; align-self:flex-start;
  /* Offset by the admin's own sticky header, not a bare 16px. Without --th
     the sidebar sticks UNDERNEATH the header and its first item is never
     reachable — caught by diffing against what admin.css had. */
  position:sticky; top: calc(var(--th, 0px) + var(--sp-8));
  background: var(--panel); border:1px solid var(--line); border-radius: var(--r);
  box-shadow: var(--shadow-sm); overflow:hidden;
  padding: var(--sp-3);
}
.ms-subnav{ list-style:none; margin:0; padding:0 var(--sp-3); display:flex; flex-direction:column; gap:1px; }
.ms-nav-hd{
  padding: var(--sp-6) var(--sp-7) var(--sp-3);
  font-size: var(--fs-xs, 11px); font-weight:800;
  text-transform:uppercase; letter-spacing:.08em; color: var(--faint);
}
/* The dot is a status mark, not decoration — it says whether a section has
   been configured, so it keeps its colour meaning. */
.ms-dot{
  width:10px; height:10px; flex:none; border-radius: var(--r-pill);
  background: var(--line-2); display:inline-block;
}
.ms-dot.is-set{ background: var(--ok); }

@media (max-width: 768px){
  /* The sidebar stops being a sidebar. A 200px rail beside a settings panel on
     a 360px screen leaves 160px for the actual form. */
  .ms-sidenav{ width:auto; position:static; }
  .ms-subnav{ flex-direction:row; overflow-x:auto; scrollbar-width:none; }
  .ms-subnav::-webkit-scrollbar{ display:none; }
  .ms-subnav > li{ flex:none; }
  .ms-nav-hd{ display:none; }
}


/* ══════════════════════════════════════════════════════════════════════════
   Approved from MOCKUP.html. Each adopts the class the product already writes
   rather than an ok- twin, so no markup file changes and no page can break
   from this step.
   ══════════════════════════════════════════════════════════════════════════ */

/* ── Empty state ──────────────────────────────────────────────────────────
   31 pages show "nothing here" and each wrote its own centred grey line.

   Every one of these ends with the action the person can take. An empty state
   that only says "nothing found" leaves them stuck — that is the difference
   between an empty page and a dead end. */
.empty, .ok-empty{
  display:flex; flex-direction:column; align-items:center; text-align:center;
  gap: var(--sp-5); padding: var(--sp-16) var(--sp-8); color: var(--muted);
}
.empty-ico, .ok-empty-ico{
  width:56px; height:56px; display:grid; place-items:center;
  border-radius: var(--r-pill); background: var(--surface-2); color: var(--faint);
}
.empty-ico svg, .ok-empty-ico svg{ width:26px; height:26px; }
.empty-title, .ok-empty-title{ font-size:16px; font-weight:700; color: var(--ink); margin:0; }
.empty-text, .ok-empty-text{ margin:0; font-size: var(--fs-sm, 13.5px); max-width:46ch; line-height:1.6; }
/* Inside a table, spanning it, rather than a block after it. */
.empty--row{ padding: var(--sp-12) var(--sp-8); }

/* ── Table ────────────────────────────────────────────────────────────────
   22 pages, and `.tbl` had no rule of its own in either stylesheet — the
   admin's lists were unstyled tables inside a styled wrapper. */
.tablewrap, .ok-tablewrap{
  border:1px solid var(--line); border-radius: var(--r-lg);
  overflow:hidden; background: var(--panel);
}
/* Four tables in the product are written with no class at all — the products
   list is one of them, so the most-used screen in the admin was the one the
   table styling did not reach. A bare <table> inside a .tablewrap can only be
   the list it wraps, so it is styled as one rather than 4 markup edits. */
.tablewrap > table, .ok-tablewrap > table{
  width:100%; border-collapse:collapse; font-size: var(--fs-sm, 13.5px);
}
.tablewrap > table > thead th, .ok-tablewrap > table > thead th{
  text-align:left; font-weight:700; color: var(--muted);
  font-size: var(--fs-xs, 11.5px); text-transform:uppercase; letter-spacing:.04em;
  padding: var(--sp-6) var(--sp-7); background: var(--surface-2);
  border-bottom:1px solid var(--line); white-space:nowrap;
}
.tablewrap > table > tbody td, .ok-tablewrap > table > tbody td{
  padding: var(--sp-6) var(--sp-7); border-bottom:1px solid var(--line); vertical-align:middle;
}
.tablewrap > table > tbody tr:last-child td{ border-bottom:none; }
.tablewrap > table > tbody tr:hover td{ background: var(--surface-2); }
.tbl, .ok-tbl{ width:100%; border-collapse:collapse; font-size: var(--fs-sm, 13.5px); }
.tbl th, .ok-tbl th{
  text-align:left; font-weight:700; color: var(--muted);
  font-size: var(--fs-xs, 11.5px); text-transform:uppercase; letter-spacing:.04em;
  padding: var(--sp-6) var(--sp-7); background: var(--surface-2);
  border-bottom:1px solid var(--line); white-space:nowrap;
}
.tbl td, .ok-tbl td{ padding: var(--sp-6) var(--sp-7); border-bottom:1px solid var(--line); vertical-align:middle; }
.tbl tbody tr:last-child td{ border-bottom:none; }
.tbl tbody tr:hover td{ background: var(--surface-2); }
/* Tabular figures so digits line up column-wise and 4,250,000 is visibly
   larger than 640,000 at a glance rather than after reading both. */
.tbl-num{ text-align:right; font-variant-numeric: tabular-nums; }
.tbl-actions{ display:flex; gap: var(--sp-3); justify-content:flex-end; }
@media (max-width: 768px){
  /* A column marked this way LEAVES on a phone rather than the table scrolling.
     Sideways scrolling hides the column the reader is looking for, and gives
     them no way to know it is there. */
  .tbl-hide-sm{ display:none; }
}

/* ── Pagination ───────────────────────────────────────────────────────────
   The count on the right exists because "1–12 of 104" tells someone whether to
   keep clicking, and a bare row of numbers does not. */
.pgn, .ok-pgn{ display:flex; align-items:center; gap: var(--sp-3); flex-wrap:wrap; margin-top: var(--sp-12); }
.pgn-btn, .ok-pgn-btn{
  min-width:38px; min-height:38px; padding:0 var(--sp-5);
  display:inline-flex; align-items:center; justify-content:center;
  border:1px solid var(--line); border-radius: var(--r-sm);
  background: var(--panel); color: var(--ink); text-decoration:none;
  font-size: var(--fs-sm, 13.5px); font-weight:600;
}
.pgn-btn:hover{ border-color: var(--line-2); }
.pgn-btn.is-on{ background: var(--ink); border-color: var(--ink); color: var(--panel); }
.pgn-btn[aria-disabled="true"]{ opacity:.4; pointer-events:none; }
.pgn-gap{ padding:0 var(--sp-3); color: var(--faint); }
.pgn-of{ margin-left:auto; font-size: var(--fs-xs, 12px); color: var(--muted); }

/* ── Breadcrumb ───────────────────────────────────────────────────────────
   Three pages already wrote .crumb-sep with nothing behind it. */
.crumbs, .ok-crumbs{
  display:flex; align-items:center; gap: var(--sp-3); flex-wrap:wrap;
  font-size: var(--fs-xs, 12px); color: var(--muted); margin-bottom: var(--sp-7);
}
.crumb, .ok-crumb{ color: var(--muted); text-decoration:none; white-space:nowrap; }
.crumb:hover{ color: var(--ink); }
.crumb[aria-current]{ color: var(--ink); font-weight:600; }
.crumb-sep, .ok-crumb-sep{ color: var(--faint); }
@media (max-width: 768px){
  /* Only the parent and the current page survive. A four-level trail wraps to
     two lines on a 360px screen and pushes the page title below the fold — so
     the reader loses what they came for in order to see where it sits. */
  .crumbs > *{ display:none; }
  .crumbs > *:nth-last-child(3),
  .crumbs > *:nth-last-child(2){ display:inline; }
}

/* ── Alert ────────────────────────────────────────────────────────────────
   Five pages write .alert and eight write .flash — the same thing under two
   names from two eras. Both adopted, so neither has to be renamed. */
.alert, .flash, .ok-alert{
  display:flex; align-items:flex-start; gap: var(--sp-5);
  padding: var(--pad-panel); border-radius: var(--r);
  border:1px solid var(--line); background: var(--surface-2);
  font-size: var(--fs-sm, 13.5px); line-height:1.55;
}
.alert-ico{ flex:none; margin-top:1px; }
.alert-body{ flex:1; min-width:0; }
.alert-title{ font-weight:700; margin:0 0 2px; }
.alert.is-ok,   .flash.is-ok{   border-color: var(--ok);   background: color-mix(in srgb, var(--ok) 8%, transparent); }
.alert.is-warn, .flash.is-warn{ border-color: var(--warn); background: color-mix(in srgb, var(--warn) 10%, transparent); }
.alert.is-err,  .flash.is-err{  border-color: var(--err);  background: color-mix(in srgb, var(--err) 8%, transparent); }
.alert.is-ok .alert-ico{ color: var(--ok); }
.alert.is-warn .alert-ico{ color: var(--warn); }
.alert.is-err .alert-ico{ color: var(--err); }

/* ── Count badge ──────────────────────────────────────────────────────────
   Both uses are on navigation, where a number written into the label reads as
   part of it: "Inquiries 7" is a label, "Inquiries" with a badge is a count. */
.countbadge, .ok-countbadge{
  min-width:18px; height:18px; padding:0 5px;
  display:inline-grid; place-items:center;
  border-radius: var(--r-pill); background: var(--err); color:#fff;
  font-size:10.5px; font-weight:800; line-height:1;
}
.countbadge--quiet{ background: var(--surface-2); color: var(--muted); }

@media (pointer: coarse){
  .pgn-btn{ min-width:44px; min-height:44px; }
}


/* ══════════════════════════════════════════════════════════════════════════
   Admin screen furniture. 32 screens, 1,314 uses outside the Kit, and the
   classes below are what repeats across nearly all of them.
   ══════════════════════════════════════════════════════════════════════════ */

/* ── Card parts ───────────────────────────────────────────────────────────
   `.ok-card-hd` and `.ok-card-bd` are used 70 times and were defined in
   admin.css — Kit NAMES living outside the Kit, which is the worst of both:
   they look shared and are not, so the front end could never use them. */
.ok-card-hd, .card-hd{
  display:flex; align-items:center; gap: var(--sp-5);
  padding: var(--pad-panel);
  border-bottom:1px solid var(--line);
  font-weight:700; font-size: var(--fs-ui, 14px);
}
.ok-card-bd, .card-bd{ padding: var(--pad-card); }
.ok-card-ft, .card-ft{
  padding: var(--pad-panel);
  border-top:1px solid var(--line);
  display:flex; align-items:center; gap: var(--sp-5);
}

/* `.card` is 144 uses and is ok-card under a shorter name. Adopted rather than
   renamed, for the same reason as .btn and .inp. */
.card{
  background: var(--surface); border:1px solid var(--line);
  border-radius: var(--r-lg); box-shadow: var(--shadow-sm);
  margin-bottom: var(--sp-8); overflow:hidden;
}

/* ── .pad ─────────────────────────────────────────────────────────────────
   122 uses and NO RULE ANYWHERE — not in admin.css, not in app.css, not here.
   Every one of those 122 elements has been asking for padding and getting
   none since it was written.

   Given the padding it was clearly meant to have rather than deleted from the
   markup: the class says what the author wanted, and honouring it is a smaller
   change than removing it from 122 places. */
.pad{ padding: var(--pad-card); }
.pad-sm{ padding: var(--pad-card-sm); }
.pad-0{ padding:0; }

/* ── Page head ────────────────────────────────────────────────────────────
   The title row every admin screen opens with. */
.pagehead{
  display:flex; align-items:flex-start; justify-content:space-between;
  gap: var(--gap-card); margin-bottom: var(--gap-card); flex-wrap:wrap;
}
.pagehead h1{ margin:0; font-size:20px; font-weight:800; letter-spacing:-.01em; }
.pagehead p{ margin:4px 0 0; color: var(--muted); font-size: var(--fs-sm, 13.5px); }
.sec-title{ font-size: var(--fs-ui, 15px); font-weight:700; margin:0 0 var(--sp-5); }

/* ── Two-column form row ──────────────────────────────────────────────────
   63 uses. Collapses on a phone — two 160px fields side by side on a 360px
   screen fit neither their labels nor their values. */
.col2{ display:grid; gap: var(--gap-card); grid-template-columns: repeat(2, minmax(0,1fr)); }
.col3{ display:grid; gap: var(--gap-card); grid-template-columns: repeat(3, minmax(0,1fr)); }
.span-all{ grid-column: 1 / -1; }
@media (max-width: 768px){
  .col2, .col3{ grid-template-columns: minmax(0,1fr); }
}

/* ── Badge ────────────────────────────────────────────────────────────────
   29 uses. This is ok-pill under an older name; its colour modifiers are
   already defined in admin.css and stay there. */
.badge{
  display:inline-flex; align-items:center; gap: var(--sp-2);
  padding: var(--sp-1) var(--sp-5); border-radius: var(--r-pill);
  font-size: var(--fs-xs, 11px); font-weight:700; white-space:nowrap;
}

/* ── Spacing utilities ────────────────────────────────────────────────────
   These exist in both stylesheets with values that drifted. One definition,
   from the scale, so `mb-3` means the same thing on both sides. */
.mb0{ margin-bottom:0; }
.mb-1{ margin-bottom: var(--sp-2); }
.mb-2{ margin-bottom: var(--sp-4); }
.mb-3{ margin-bottom: var(--sp-6); }
.mb-4{ margin-bottom: var(--sp-8); }
.mt-2{ margin-top: var(--sp-4); }
.mt-3{ margin-top: var(--sp-6); }
.mt-4{ margin-top: var(--sp-8); }
.fw-7{ font-weight:700; }
.fw-8{ font-weight:800; }
.t-center{ text-align:center; }
.fs-125{ font-size:12.5px; }


/* ══════════════════════════════════════════════════════════════════════════
   Shop and category. These pages were 15% through the Kit — the lowest of any
   area, and the side customers see. The classes below are the shapes those
   pages already write; adopted rather than renamed, so the migration is a
   stylesheet change and no view file is touched.
   ══════════════════════════════════════════════════════════════════════════ */

/* ── Sub-category card ────────────────────────────────────────────────────
   This is ok-catcard: a round image, a name, a count. It was built twice —
   once here and once as ok-catcard — with the same idea and no shared name. */
.subcat-c{ font-size: var(--fs-xs, 12px); color: var(--muted); }
.subcat-im{
  width:64px; height:64px; flex:none;
  border-radius: var(--r-pill); overflow:hidden;
  background: var(--surface-2); display:grid; place-items:center;
}
.subcat-nm{ font-weight:800; font-size: var(--fs-ui, 14px); }
.subcat-desc{ font-size:12.5px; color: var(--muted); line-height:1.4; }
.subcat-ph{ font-size:28px; color: var(--faint); }

/* The strip of them. On a phone it scrolls sideways rather than wrapping —
   eight stacked category tiles push the actual products below the fold, which
   is the single commonest reason a shop page feels empty on mobile. */
.subcats{
  display:grid; gap: var(--gap-grid);
  grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
}
@media (max-width: 768px){
  .subcats{
    display:flex; overflow-x:auto;
    scroll-snap-type: x mandatory; scrollbar-width:none;
    margin-inline: calc(var(--sp-8) * -1); padding-inline: var(--sp-8);
  }
  .subcats::-webkit-scrollbar{ display:none; }
  .subcats > *{ flex:0 0 auto; scroll-snap-align:start; width:132px; }
}

/* ── Category grid ────────────────────────────────────────────────────────
   Was repeat(3,1fr) with no narrow-screen rule — three columns on a 360px
   screen gives 106px per cell, which fits neither the name nor the image. */
.cat-grid{
  display:grid; gap: var(--gap-grid);
  grid-template-columns: repeat(var(--cols, 3), minmax(0,1fr));
}
@media (max-width: 768px){ .cat-grid{ grid-template-columns: repeat(2, minmax(0,1fr)); } }

/* ── Filter group ─────────────────────────────────────────────────────────
   This is ok-accordion under another name: a heading you press and a body that
   opens. Adopted so a filter group and a spec group behave the same. */
.fgroup{ padding: var(--sp-7) 0; border-bottom:1px solid var(--line); }
.fgroup:last-child{ border-bottom:none; }
.fgroup-hd{
  display:flex; align-items:center; justify-content:space-between; gap: var(--sp-5);
  cursor:pointer; font-weight:700; font-size: var(--fs-ui, 14px);
  list-style:none;
}
.fgroup-hd::-webkit-details-marker{ display:none; }
.fgroup-body{ display:flex; flex-direction:column; gap: var(--sp-3); margin-top: var(--sp-5); }

@media (pointer: coarse){
  .fgroup-hd{ min-height:44px; }
}


/* ── Admin shell ──────────────────────────────────────────────────────────
   The chrome every admin page sits inside: sidebar, nav links, groups, the
   search at its top. These are ok-navlist, ok-search and ok-countbadge built a
   second time, and fixing them reaches every admin screen at once.
   ══════════════════════════════════════════════════════════════════════════ */

/* This is ok-navlist-item. */
.navlink{
  display:flex; align-items:center; gap: var(--sp-5);
  padding: var(--sp-4) var(--sp-5) var(--sp-4) var(--sp-6);
  border-radius: var(--r-sm);
  color: var(--muted); text-decoration:none;
  font-size: var(--fs-sm, 13.5px); font-weight:600;
}
.navlink:hover{ background: var(--surface-2); color: var(--ink); }
.navlink.on{
  background: var(--navlink-on-bg, var(--brand-tint, var(--surface-2)));
  color: var(--navlink-on-ink, var(--brand-ink));
}

/* This is ok-search at sidebar scale — but its surface is a TOKEN, not
   --panel. I hard-coded --panel here in 11.360 and the admin sidebar is dark,
   so the search box rendered as a white block on black. The Kit cannot know
   what a component sits on; the side that owns the surface has to say. */
.navsearch{
  display:flex; align-items:center; gap: var(--sp-4);
  padding: var(--sp-5) var(--sp-7);
  border: 1px solid var(--navsearch-border, var(--line));
  border-radius: var(--navsearch-radius, var(--r-pill));
  background: var(--navsearch-bg, transparent);
}
.navsearch:focus-within{ border-color: var(--brand); box-shadow: 0 0 0 3px var(--brand-glow); }
.navsearch input{ color: inherit; }
.navsearch input::placeholder{ color: var(--navsearch-placeholder, var(--faint)); }
.navsearch input{ flex:1; min-width:0; border:none; background:transparent; color:inherit; font:inherit; }
.navsearch input:focus{ outline:none; }

/* Group headings, which are ok-accordion at nav scale. */
.navgrp{ margin-bottom:1px; }
.grp-toggle{
  display:flex; align-items:center; justify-content:space-between; width:100%;
  gap: var(--sp-4); padding: var(--sp-4) var(--sp-6);
  border:none; background:none; cursor:pointer;
  color: var(--muted); font: inherit;
  font-size: var(--fs-xs, 11.5px); font-weight:700;
  text-transform:uppercase; letter-spacing:.05em;
}
.grp-label{ flex:1; text-align:left; }
.grp-chev{ transition: transform var(--dur); }
.navgrp.open .grp-chev{ transform: rotate(180deg); }
.grp-items{ display:flex; flex-direction:column; gap:1px; }

/* This is ok-countbadge. */
.nav-badge{
  margin-left:auto; min-width:18px; height:18px; padding:0 5px;
  display:inline-grid; place-items:center;
  border-radius: var(--r-pill); background: var(--err); color:#fff;
  font-size:10.5px; font-weight:800; line-height:1;
}

/* This is ok-iconbtn, at header scale. */
.top-icon{
  display:inline-flex; align-items:center; justify-content:center; flex:none;
  width:34px; height:34px; border-radius: var(--r-sm);
  color: var(--muted); text-decoration:none;
}
.top-icon:hover{ background: var(--surface-2); color: var(--ink); }

@media (pointer: coarse){
  .navlink, .grp-toggle{ min-height:44px; }
  .top-icon{ width:44px; height:44px; }
}


/* ── Kit-named utilities that lived in admin.css ──────────────────────────
   95 uses of classes named ok-* and defined outside the Kit. Moved in, which
   is what the conflicts gate has been reporting since 11.359.

   One of them, .ok-card--rail, has TEN uses and no rule anywhere — the same
   shape of bug as .pad in 11.356. Given the behaviour its name promises: a
   card that sticks beside the content it belongs to. */
.ok-card--rail{ position: sticky; top: calc(var(--th, 0px) + var(--sp-8)); align-self: start; }
@media (max-width: 768px){ .ok-card--rail{ position: static; } }

.ok-hint{ font-size: var(--fs-xs, 11px); color: var(--muted); line-height:1.6; margin:0; }
.ok-sh{
  font-size: var(--fs-xs, 11px); font-weight:700; letter-spacing:.05em;
  text-transform:uppercase; color: var(--muted);
}
.ok-row{ display:flex; align-items:center; gap: var(--sp-4); flex-wrap:wrap; }
.ok-badge{
  display:inline-flex; align-items:center; gap: var(--sp-2);
  padding: var(--sp-1) var(--sp-4); border-radius: var(--r-pill);
  font-size:10px; font-weight:700; letter-spacing:.05em; text-transform:uppercase;
  border:1px solid var(--line); white-space:nowrap;
}
.ok-badge--brand{ background: var(--brand); border-color: var(--brand); color: var(--brand-ink); }
.ok-inline-input{ height:38px; padding:2px var(--sp-3); }
.ok-spacer{ flex:1; }
.ok-field--wide{ grid-column: 1 / -1; }
.ok-stack-sm{ margin:0 0 var(--sp-6); }
.ok-stack-md{ margin:0 0 var(--sp-8); }
.ok-stack-lg{ margin:0 0 var(--sp-9); }
.ok-panel-pad{ margin-bottom: var(--sp-9); padding: var(--pad-card); }
.ok-sec-gap{ margin: var(--sp-8) 0 var(--sp-4); }
.ok-tight-top{ margin: calc(var(--sp-3) * -1) 0 var(--sp-7); }


/* ── Product card: the site's own names, given the Kit's behaviour ────────
   The shop's card is `.pcard` with `.badges`, `.pbadge`, `.pc-foot` inside it.
   Compared property by property against the Kit's ok-product family, the site
   version was MISSING things rather than disagreeing:

     .pbadge   7 properties differ, 4 the Kit has and it does not —
               display:inline-flex, align-items, gap, white-space
     .badges   4 the Kit has and it does not
     .pc-foot  3 the Kit has and it does not

   The missing four on .pbadge are why a flag with an icon in it never lined
   up: without inline-flex and align-items the icon sat on the text baseline.

   Adopted rather than the markup renamed — same trade as .btn and .inp. */

.pbadge{
  display:inline-flex; align-items:center; gap: var(--sp-2);
  white-space:nowrap;
}

/* The strip. Its position and offsets stay in app.css where the card's own
   geometry lives; this adds the layout the Kit settled in 11.322 — wrapping,
   and a row-gap so a second line reads as a line rather than as overlap. */
.badges{
  display:flex; flex-wrap:wrap;
  gap: var(--sp-3); row-gap: var(--sp-3);
}

/* The action row. z-index above a whole-card link, so a button inside a card
   that is itself a link stays clickable — the site had the buttons and not the
   stacking, which is why the card link swallowed them. */
.pc-foot{
  position:relative; z-index:2;
  display:flex; flex-wrap:wrap; gap: var(--sp-4);
}
.pc-foot > .btn,
.pc-foot > .ok-btn{ flex:1; }

/* The card's icon buttons are ok-iconbtn at a smaller size. */
.pc-ico-btn{
  display:grid; place-items:center; flex:none;
  border-radius: var(--r-pill);
  border:1px solid var(--line); background: var(--panel); color: var(--muted);
  cursor:pointer; transition: var(--btn-dur);
}
.pc-ico-btn:hover{ color: var(--ink); border-color: var(--line-2); }
@media (pointer: coarse){ .pc-ico-btn{ min-width:44px; min-height:44px; } }

/* Line clamps. The product card's title is `h3.tl2` — a class whose name says
   "two lines" and which had NO RULE ANYWHERE. Third instance of this shape
   after .pad (11.356) and .ok-card--rail (11.361): markup asking for a
   behaviour and getting nothing since the day it was written.

   Without it a fifteen-word product name pushes its card taller than the three
   cards beside it and the row goes ragged — which is exactly what the shop
   does today. */
.tl1, .tl2, .tl3{
  display:-webkit-box; -webkit-box-orient:vertical; overflow:hidden;
}
.tl1{ -webkit-line-clamp:1; }
.tl2{ -webkit-line-clamp:2; }
.tl3{ -webkit-line-clamp:3; }


/* ── Gallery peek ─────────────────────────────────────────────────────────
   app.js marks the slide immediately before and after the current one so they
   can be half-visible at the edges — and the CSS for it was never written, so
   the toggle has been running on every gallery interaction and changing
   nothing.

   Found by listing every class the JavaScript applies and checking it against
   every stylesheet; that is a different question from "is every class in the
   markup styled", and it is the one nobody had asked. */
.pgal-prevpeek, .pgal-nextpeek{
  opacity:.45;
  transition: opacity var(--dur), transform var(--dur);
}
.pgal-prevpeek:hover, .pgal-nextpeek:hover{ opacity:.75; }

/* ── Tab block container ──────────────────────────────────────────────────
   okami-tabblocks.js builds `<div class="tb-blocks">` around a tab's blocks.
   It is neither styled nor used as a selector — so it was a wrapper doing
   nothing at all. Given the stacking it clearly implies. */
.tb-blocks{ display:flex; flex-direction:column; gap: var(--gap-card); }


/* ── Classes the JavaScript applies and nothing styled ────────────────────
   Found by asking of the JS what had already been asked of the markup: does
   every class it applies mean anything? Four did not.

   `ok-health` has 41 rules and its three state modifiers had none — so a
   health panel computed a score, picked good/fair/poor, and looked identical
   at 95% and 20%. */
.ok-health--good .ok-health-bar > i{ background: var(--ok); }
.ok-health--fair .ok-health-bar > i{ background: var(--warn); }
.ok-health--poor .ok-health-bar > i{ background: var(--err); }
.ok-health--good .ok-health-pct{ color: var(--ok); }
.ok-health--fair .ok-health-pct{ color: var(--warn); }
.ok-health--poor .ok-health-pct{ color: var(--err); }

/* An upload row that finished. Without this a completed upload looked exactly
   like one still running. */
.done{ opacity:.6; }
.done .gup-bar > i{ background: var(--ok); }

/* A validation state on a settings row: the JS removes good/warn/bad together
   and adds one, and only two of the three were ever styled. */
.good{ color: var(--ok); }

/* A collapsed nav group. The chevron rotation was styled through `.open`; the
   JS toggles `collapsed`, so the two were describing opposite states and one
   of them did nothing. */
.navgrp.collapsed .grp-items{ display:none; }
.navgrp.collapsed .grp-chev{ transform: rotate(-90deg); }


/* ── Services listing ─────────────────────────────────────────────────────
   Adopting the pieces that are Kit components under service-page names, so
   the page reaches the Kit rather than the Kit waiting for it. The hero and
   its dark gradient stay in app.css — that is this page's own design, not a
   component, and pulling it in would make the Kit worse. */

/* This is ok-pill. */
.svc-pill-count, .bside-count{
  display:inline-flex; align-items:center; gap: var(--sp-2);
  padding: var(--sp-1) var(--sp-5);
  border-radius: var(--r-pill);
  font-size: var(--fs-xs, 11px); font-weight:700;
  white-space:nowrap;
}

/* This is ok-price-now at listing scale. */
.svc-price-tag{ font-size:12.5px; font-weight:800; color: var(--brand-ink, var(--brand)); }

/* This is ok-rail: a column of panels beside the content. */
.svc-listing-sidebar{ display:flex; flex-direction:column; gap: var(--gap-card); }
@media (max-width: 900px){ .svc-listing-sidebar{ gap: var(--sp-6); } }

/* This is ok-listcard-meta — a row of facts under a heading. */
.svc-listing-stats{
  display:flex; align-items:center; gap: var(--sp-5); flex-wrap:wrap;
  font-size: var(--fs-sm, 13px);
}


/* ══════════════════════════════════════════════════════════════════════════
   Front end, final adoption pass. These are the shapes the public site writes
   most, and every one of them is a Kit component under a page-specific name.
   Adopted rather than the markup renamed — the trade settled in 11.353.
   ══════════════════════════════════════════════════════════════════════════ */

/* Three names for ok-field: the shop form, the inquiry form and the cart.
   `.fld` had ONE property — margin-bottom — so a field label and its input had
   no relationship at all beyond sitting near each other. */
.fld, .inq-field, .okc-field{
  display:flex; flex-direction:column; gap: var(--sp-3);
  margin-bottom: var(--sp-7); min-width:0;
}
.fld > label, .inq-field > label, .okc-field > label{
  font-size: var(--fs-label, 11.5px); font-weight:700; color: var(--muted);
}

/* The blog sidebar's panels. This is ok-card plus ok-section-title at widget
   scale — nine widgets, and the heading style was declared once per widget
   family rather than once. */
.bside-widget{
  background: var(--panel); border:1px solid var(--line);
  border-radius: var(--r-lg); padding: var(--pad-card);
  margin-bottom: var(--gap-card);
}
.bside-hd{
  font-size: var(--fs-xs, 11px); font-weight:800;
  text-transform:uppercase; letter-spacing:.05em;
  color: var(--muted); margin: 0 0 var(--sp-6);
}

/* The contact page's info rows. This is ok-iconbox in its row arrangement:
   an icon, a label, a value. */
.c-info-block{
  display:flex; align-items:flex-start; gap: var(--gap-card);
  padding: var(--sp-7) 0; border-bottom:1px solid var(--line);
}
.c-info-block:last-child{ border-bottom:none; }
.c-info-ico{
  width:38px; height:38px; flex:none; display:grid; place-items:center;
  border-radius: var(--r-sm);
  background: var(--surface-2); color: var(--muted);
}
.c-info-label{
  font-size: var(--fs-xs, 11px); font-weight:700; color: var(--muted);
  text-transform:uppercase; letter-spacing:.04em;
}

/* Product page section headings — ok-section-title. Twelve sections, one
   heading style, and text-wrap:balance so a two-line heading splits evenly
   instead of leaving one word alone on the second line. */
.pd-sec-title{
  font-size:22px; font-weight:800; letter-spacing:-.01em;
  line-height:1.25; margin: 0 0 var(--gap-card);
  text-wrap: balance;
}
.pd-section{ margin-bottom: var(--gap-section); }

/* Buttons the site builds under its own names. All three are ok-btn with a
   colour decision on top; none needs to restate padding or radius. */
.okbar-btn, .svc-cta-btn{
  display:inline-flex; align-items:center; justify-content:center;
  gap: var(--btn-gap); white-space:nowrap;
  border-radius: var(--btn-radius); padding: var(--btn-pad);
  font-size: var(--btn-fs); font-weight: var(--btn-fw);
  cursor:pointer; transition: var(--btn-dur);
}
.iconbtn{
  display:grid; place-items:center; flex:none;
  width:34px; height:34px; border-radius: var(--r-pill);
  border:1px solid var(--line); background: var(--panel); color: var(--muted);
  cursor:pointer;
}
.iconbtn:hover{ color: var(--ink); border-color: var(--line-2); }
@media (pointer: coarse){
  .iconbtn{ width:44px; height:44px; }
  .okbar-btn, .svc-cta-btn{ min-height:44px; }
}

/* Long-form body copy — the article, the description, the service page. */
.prose{ line-height:1.75; color: var(--ink-2, var(--ink)); }
.prose > * + *{ margin-top: var(--sp-7); }
.prose h2, .prose h3{ margin-top: var(--sp-12); line-height:1.3; }
.prose ul, .prose ol{ padding-left: var(--sp-10); }
.prose li + li{ margin-top: var(--sp-3); }
.prose img{ max-width:100%; height:auto; border-radius: var(--r-sm); }

/* The media picker's loading grid. First place in the product where a skeleton
   is actually shown — it existed from 11.323 and no screen used it, which the
   utilisation gate has been reporting since 11.364. The picker was the right
   first case: it fetches over the network and used to show the word "Looking…"
   in a grid that then jumped to a different height. */
.okpick-skeleton{ pointer-events:none; }


/* ══════════════════════════════════════════════════════════════════════════
   Product page. The largest single file in the product and the page customers
   judge the business on — which is why the audit put it last, after every
   component it needs had been proven on a smaller page.

   Nothing here is a new idea: each is a Kit component the product page built
   for itself before the Kit existed.
   ══════════════════════════════════════════════════════════════════════════ */

/* The rail panels beside the gallery — ok-card at sidebar scale. */
.pd-side-card{
  background: var(--panel); border:1px solid var(--line);
  border-radius: var(--r-lg); padding: var(--pad-card);
  margin-bottom: var(--gap-card);
}
.pd-side-title{
  margin: 0 0 var(--sp-4); font-size:17px; font-weight:700;
  letter-spacing:-.01em; line-height:1.3;
}

/* FAQ items — ok-accordion--boxed. */
.pd-faq-list{ display:flex; flex-direction:column; gap: var(--sp-5); }
.pd-faq-item{
  border:1px solid var(--line); border-radius: var(--r-lg); overflow:hidden;
}

/* Share and gallery buttons — ok-iconbtn at two sizes. */
.pd-share-btn{
  width:32px; height:32px; display:grid; place-items:center; flex:none;
  border-radius: var(--r-pill); border:1px solid var(--line);
  background: var(--panel); color: var(--muted); cursor:pointer;
}
.pd-share-btn:hover{ color: var(--ink); border-color: var(--line-2); }
.pgal-arrow{
  position:absolute; top:50%; transform: translateY(-50%);
  width:40px; height:40px; display:grid; place-items:center;
  border-radius: var(--r-pill); border:none; cursor:pointer;
  background: rgba(255,255,255,.9); color: var(--ink);
  box-shadow: var(--shadow-sm);
}
@media (pointer: coarse){
  .pd-share-btn{ width:44px; height:44px; }
  .pgal-arrow{ width:44px; height:44px; }
}

/* The mobile tab bar — ok-mobilebar-item. */
.pd-mob-tab{
  display:flex; flex-direction:column; align-items:center; justify-content:center;
  gap:2px; flex:1; min-height:56px; padding: var(--sp-3) var(--sp-2);
  color: var(--muted); text-decoration:none;
  font-size:10.5px; font-weight:600; line-height:1.2;
}
.pd-mob-tab--cta{ color: var(--brand-ink); }
.pd-mob-ico{ display:grid; place-items:center; }
.pd-mob-ico svg{ width:20px; height:20px; }

/* Showcase headings — ok-showcase-title at band scale. */
.pd-sc-h{ font-size:16px; font-weight:700; margin: var(--sp-2) 0 var(--sp-3); }
.pd-sc-desc{ margin:0; color: var(--muted); line-height:1.65; font-size: var(--fs-sm, 13.5px); }

/* Variant chips — ok-variant-opts. */
.pd-var-chips{
  list-style:none; margin:0; padding:0;
  display:flex; flex-wrap:wrap; gap: var(--sp-4);
}


/* ── Admin list thumbnail ─────────────────────────────────────────────────
   The products list showed eleven empty white squares. `.thumb` set a size and
   a border and nothing else — no rule for the image inside it and none for the
   placeholder icon the view renders when a product has no photo.

   So the `<img>` had no object-fit of its own (the property was on the DIV,
   where it does nothing), and the placeholder SVG had no size, no colour and
   no centring: an icon drawn at its intrinsic size, in the inherited ink, in
   the top-left corner of a 48px box.

   This is ok-thumb's job; `.thumb` is that component under a shorter name. */
.thumb{
  display: grid; place-items: center; overflow: hidden;
  background: var(--surface-2);
  color: var(--faint);
}
.thumb > img{ width:100%; height:100%; object-fit: cover; display:block; }
.thumb > svg{ width:20px; height:20px; }


/* ── Form rows that fit their container, not the window ───────────────────
   Reported: the phone and WhatsApp fields on Settings overlap and do not
   respond. They HAVE media queries — four of them, at 640, 768, 900 and
   1000px — and that is exactly the fault.

   A media query measures the WINDOW. These fields live inside the admin
   content column, which is the window minus a 256px sidebar, minus the
   settings rail, minus padding. At a 1024px window the query says "wide, use
   three columns" and hands each field 157px — narrower than the placeholder
   text inside it.

   auto-fit with a minimum measures the CONTAINER. Three columns when three
   fit, two when two fit, one when one does — with no breakpoint to get wrong
   and no knowledge of what else is on the page.

   The four media queries stay where they are; they now only ever agree with
   this, because a 1fr track cannot go below the minimum either way. */
.row2, .row3, .row4, .col2, .col3{
  display: grid;
  gap: var(--gap-card);
  grid-template-columns: repeat(auto-fit, minmax(var(--field-min, 200px), 1fr));
}
/* A row that must stay side by side regardless — a from/to pair, a value and
   its unit. Named so the exception is visible rather than assumed. */
.row-fixed-2{ grid-template-columns: repeat(2, minmax(0,1fr)); }


/* ── Builder breathing room ───────────────────────────────────────────────
   The builders are already built — drag-and-drop, palettes, breakpoints, all
   of it works. What they lack is room: an element row is 10px by 4px, a field
   is 8px from the next one, and the palette packs its items 8px apart, which
   is tighter than the gap between a label and its own input.

   These screens are read once and carefully, not scanned for hours like a
   list — so the admin's compact density, which is right for tables, buys
   nothing here and costs legibility.

   Sizes come from the scale so they move with everything else. No new
   components: the builders keep their own names and their own behaviour. */
.hb2-elem{
  padding: var(--sp-6) var(--sp-7);
  border-radius: var(--r-sm);
  margin-bottom: var(--sp-5);
  gap: var(--sp-4);
}
.hb2-field{ margin-bottom: var(--sp-7); }
.hb2-s-h, .hb2-palette-title{
  margin: var(--sp-8) 0 var(--sp-5);
  letter-spacing: .06em;
  gap: var(--sp-4);
}
/* The palette heading sits inside the rail, so it needs the rail's inset —
   caught by diffing against what admin.css had before it was removed. */
.hb2-palette-title{ padding: var(--sp-6) var(--sp-7) var(--sp-4); margin: 0; }
.hb2-tog-row{ padding: var(--sp-5) 0; gap: var(--sp-6); }
.hb2-add-row{ padding: var(--sp-6); gap: var(--sp-5); }
.hb2-col-grid-2{ gap: var(--gap-card); }

/* The page builder's palette and drop zones. */
.pb-pal{ gap: var(--sp-5); padding: 0 var(--sp-7) var(--sp-7); }
.pb-zone{
  min-height: 96px;
  padding: var(--sp-6);
  border-radius: var(--r-sm);
}
.pb-frame{ padding: var(--sp-6); margin-bottom: var(--sp-8); }


/* ══════════════════════════════════════════════════════════════════════════
   Front-end batch: shop, category, service, blog, search, services.
   Six pages that share the same shapes under six sets of names.
   ══════════════════════════════════════════════════════════════════════════ */

/* Section heading with an icon — ok-section-head. `.cat-hd-text` has six uses
   and NO RULE ANYWHERE, so the heading beside the icon has been unstyled since
   it was written. */
.cat-hd{
  display:flex; align-items:center; gap: var(--sp-5);
  margin-bottom: var(--gap-card);
}
.cat-hd-text{ flex:1; min-width:0; }
.cat-hd-text h2, .cat-hd-text h3{
  margin:0; font-size:19px; font-weight:800; letter-spacing:-.01em; line-height:1.25;
}
.cat-hd-text p{ margin:2px 0 0; font-size: var(--fs-sm, 13px); color: var(--muted); }

/* Search results groups — ok-section with a count. */
.search-section{ margin-bottom: var(--gap-section); }
.search-section-hd{
  display:flex; align-items:baseline; gap: var(--sp-5);
  font-size:17px; font-weight:800; margin: 0 0 var(--gap-card);
}
.search-section-count{
  font-size: var(--fs-xs, 12px); font-weight:700; color: var(--muted);
}

/* Share buttons on an article — ok-iconbtn with a label. */
.bshare-btn{
  display:inline-flex; align-items:center; gap: var(--sp-3);
  padding: var(--sp-4) var(--sp-6);
  border:1px solid var(--line); border-radius: var(--r-pill);
  background: var(--panel); color: var(--muted);
  font-size: var(--fs-xs, 12px); font-weight:600; text-decoration:none;
}
.bshare-btn:hover{ color: var(--ink); border-color: var(--line-2); }
@media (pointer: coarse){ .bshare-btn{ min-height:44px; } }

/* Filter checkboxes — ok-check at filter scale. */
.fchk{
  display:flex; align-items:center; gap: var(--sp-5);
  padding: var(--sp-4) 0;
  font-size: var(--fs-sm, 13px); cursor:pointer;
}
.search-filter-item{
  display:flex; align-items:center; gap: var(--sp-4);
  padding: var(--sp-4) 0; font-size: var(--fs-sm, 13px);
}
.fgroup-collapse{ cursor:pointer; }
@media (pointer: coarse){
  .fchk, .search-filter-item{ min-height:44px; }
}

/* Archive and listing heads — ok-section-head at page scale. */
.archive-head{ padding: var(--sp-5) 0 var(--sp-12); }
.eyebrow{
  display:block;
  color: var(--brand-ink, var(--brand)); font-weight:800;
  font-size: var(--fs-xs, 12px); letter-spacing:.06em; text-transform:uppercase;
  margin-bottom: var(--sp-4);
}

/* The call-us strip in a filter rail — ok-iconbox on a tint. */
.fcall{
  display:flex; align-items:center; gap: var(--gap-card);
  padding: var(--pad-card);
  border-radius: var(--r-lg);
}
.fcall-ic{ flex:none; display:grid; place-items:center; }


/* ── Icon sizes ───────────────────────────────────────────────────────────
   `.ico` is styled — 1.15em, inline-flex — and `.ico-lg` is not, in eight
   places. Sixth class in this codebase asking for something nothing answers.

   Found by rendering every admin screen and listing the classes on it that no
   stylesheet defines. That is a different question from the ones the gates
   already ask, and it found this in one pass. */
.ico-sm{ width:.9em; height:.9em; }
.ico-lg{ width:1.5em; height:1.5em; }
.ico-xl{ width:2em; height:2em; }

/* `.okfab-show-all` is NOT a defect — "all" means never hide, and the absence
   of a rule already does that. Written down so the next person does not read
   the gap as a mistake and does not delete the value from the schema. */
.okfab-show-all{ /* intentionally empty: shown at every width */ }


/* The Update Center's neutral note. A code-only update is neither a problem
   nor "everything is fine" — it is a thing still to do, and green said the
   opposite while the warning above it said press a button that did not exist. */
.upd-note--info{
  border-color: var(--brand);
  background: color-mix(in srgb, var(--brand) 8%, transparent);
}


/* ══════════════════════════════════════════════════════════════════════════
   Front end, final families. Services, blog cards, search results and the
   article sidebar — the last shapes the public site writes under its own names.
   ══════════════════════════════════════════════════════════════════════════ */

/* `.btn amber` needs nothing here. I read `.amber` alone having no rule and
   concluded the button was unstyled; `.btn.amber` is defined in app.css with
   its own --amber tokens and always was. Checking the compound selector, not
   just the modifier, is the lesson — and the conflicts gate caught my mistake
   within a minute of my making it. */

/* Service cards — ok-listcard. */
.svc-card{
  display:flex; flex-direction:column; gap: var(--gap-card);
  background: var(--panel); border:1px solid var(--line);
  border-radius: var(--r-lg); overflow:hidden;
}
.svc-card-body{ padding: var(--pad-card); display:flex; flex-direction:column; gap: var(--sp-4); }

/* The service page's hero band. Its gradient and dots stay in app.css — that
   is this page's own design, not a component. This is only its box. */
.svc-hero{ position:relative; overflow:hidden; border-radius: var(--r-lg); }
.svc-cta{ margin: var(--sp-12) 0; padding: var(--pad-card); border-radius: var(--r-lg); }

/* Blog cards — ok-listcard at index scale. */
.blog-card{
  display:flex; flex-direction:column;
  background: var(--panel); border:1px solid var(--line);
  border-radius: var(--r-lg); overflow:hidden;
}

/* Search results — ok-listcard in a column. */
.search-list{ display:flex; flex-direction:column; gap: var(--sp-5); }
.search-list-item{
  display:flex; align-items:flex-start; gap: var(--gap-card);
  padding: var(--pad-card-sm); border-radius: var(--r-sm);
}
.search-list-item:hover{ background: var(--surface-2); }
/* The product results block inside search — no rule existed, so a product
   result and an article result looked identical in a mixed list. */
.search-prod{
  display:grid; gap: var(--gap-grid);
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
}

/* The article sidebar's recent-posts list — ok-listcard--compact. */
.bside-recent{ display:flex; flex-direction:column; gap: var(--sp-5); }
.bside-recent a{
  display:flex; align-items:flex-start; gap: var(--sp-5);
  text-decoration:none; color: inherit;
  font-size: var(--fs-sm, 13px); line-height:1.45;
}
.bside-recent a:hover{ color: var(--brand-ink, var(--brand)); }

/* Contact page alerts — the same shape as .alert, at page scale. */
.c-alert{
  display:flex; align-items:flex-start; gap: var(--sp-5);
  padding: var(--pad-panel); border-radius: var(--r);
  border:1px solid var(--line);
  font-size: var(--fs-sm, 13.5px); line-height:1.55;
}


/* ── Blog card styles ─────────────────────────────────────────────────────
   The blog settings offer four card styles — Classic, Minimal, Bordered,
   Elevated — and NOT ONE of them had a rule. Every choice saved, showed as
   selected, and changed nothing, including the default.

   Exactly the fault fixed for feature and FAQ styles in 11.351, in a screen
   that check never looked at. Found by the rendered-classes gate, which saw
   `.blog-grid--classic` on the live page and could not find a rule for it. */
.blog-grid--classic .blog-card{
  background: var(--panel); border:1px solid var(--line);
  border-radius: var(--r-lg); box-shadow: var(--shadow-sm);
}
/* Minimal: no frame at all — the image and the words, nothing around them. */
.blog-grid--minimal .blog-card{
  background: transparent; border:none; border-radius:0; box-shadow:none;
}
.blog-grid--minimal .blog-card .thumb,
.blog-grid--minimal .blog-card img{ border-radius: var(--r-sm); }
/* Bordered: the frame is the whole point, so it is heavier and flat. */
.blog-grid--bordered .blog-card{
  background: var(--panel); border:1px solid var(--line-2);
  border-radius: var(--r-sm); box-shadow:none;
}
/* Elevated: lifts on hover, which is the one style that says "this is a link". */
.blog-grid--elevated .blog-card{
  background: var(--panel); border:1px solid transparent;
  border-radius: var(--r-lg); box-shadow: var(--shadow-md, var(--shadow-sm));
  transition: transform var(--dur), box-shadow var(--dur);
}
.blog-grid--elevated .blog-card:hover{
  transform: translateY(-2px);
  box-shadow: var(--shadow-lg, var(--shadow-sm));
}


/* Two pieces the front-page mockup needed and the Kit did not have. Small
   enough to add rather than work around, and both are parts of components that
   already exist rather than new ideas. */

/* ok-hero's media side. The hero had a panel and no counterpart for the image
   beside it, so every page using it invented one. */
.ok-hero-media{ min-width:0; }
.ok-hero-media > .ok-thumb{ border-radius: var(--r-lg); overflow:hidden; }

/* A count inside a pill — "Equipment 12". Written as a bare number today,
   which reads as part of the label rather than as a quantity. */
.ok-pill-count{
  margin-left: var(--sp-2);
  padding: 0 var(--sp-3);
  border-radius: var(--r-pill);
  background: color-mix(in srgb, currentColor 14%, transparent);
  font-size: .92em; font-weight: 700;
}


/* ══════════════════════════════════════════════════════════════════════════
   Product page style options that did nothing.

   The Product Builder offers 19 layout choices across six sections. FOURTEEN
   had no rule — they saved, showed as selected, and changed nothing. Same
   fault as feature and FAQ styles in 11.351, in six settings that check never
   looked at.

   These pages are settings-driven, which is exactly why this matters: the
   markup is generated from the choice, so a choice with no CSS behind it is
   invisible in a way no page-by-page review would find.
   ══════════════════════════════════════════════════════════════════════════ */

/* Applications — where the machine is used. */
.pd-app--grid{ display:grid; gap: var(--gap-grid); grid-template-columns: repeat(auto-fit, minmax(220px,1fr)); }
.pd-app--cards > *{
  background: var(--panel); border:1px solid var(--line);
  border-radius: var(--r-lg); padding: var(--pad-card);
}

/* Certificates — a trust strip, so its default is a row of marks. */
.pd-cert--grid{ display:grid; gap: var(--gap-grid); grid-template-columns: repeat(auto-fit, minmax(160px,1fr)); }
.pd-cert--row{
  display:flex; gap: var(--gap-grid); overflow-x:auto;
  scroll-snap-type: x proximity; scrollbar-width:none;
}
.pd-cert--row::-webkit-scrollbar{ display:none; }
.pd-cert--row > *{ flex:0 0 auto; scroll-snap-align:start; }

/* Downloads — a list reads faster than a grid when the names are long, and
   catalogue names always are. */
.pd-dl--list{ display:flex; flex-direction:column; gap: var(--sp-5); }
.pd-dl--table{ display:table; width:100%; border-collapse:collapse; }
.pd-dl--table > *{ display:table-row; }

/* Testimonials. */
.pd-test--grid{ display:grid; gap: var(--gap-grid); grid-template-columns: repeat(auto-fit, minmax(280px,1fr)); }
.pd-test--list{ display:flex; flex-direction:column; gap: var(--gap-card); }

/* Videos. */
.pd-vid--grid{ display:grid; gap: var(--gap-grid); grid-template-columns: repeat(auto-fit, minmax(240px,1fr)); }
/* "Single" means one large player, not a one-column grid of small ones. */
.pd-vid--single{ display:block; }
.pd-vid--single > * + *{ display:none; }

/* Specifications — all four were dead, including the default. A spec sheet is
   the description for machinery, so this is the section a serious buyer reads
   and it had no layout at all. */
.pd-spec--table{ display:block; width:100%; }
.pd-spec--table table{ width:100%; border-collapse:collapse; }
.pd-spec--cards{
  display:grid; gap: var(--gap-grid);
  grid-template-columns: repeat(auto-fit, minmax(220px,1fr));
}
.pd-spec--cards > *{
  background: var(--panel); border:1px solid var(--line);
  border-radius: var(--r-lg); padding: var(--pad-card);
}
.pd-spec--list{ display:flex; flex-direction:column; }
.pd-spec--list > *{
  display:flex; justify-content:space-between; gap: var(--gap-card);
  padding: var(--sp-6) 0; border-bottom:1px solid var(--line);
}
.pd-spec--list > *:last-child{ border-bottom:none; }
/* Grouped: headings between runs of rows, for a sheet long enough to need them. */
.pd-spec--grouped{ display:flex; flex-direction:column; gap: var(--gap-section); }


/* Breadcrumb styles — four offered, none styled. `.breadcrumb-nav--light`
   exists for a dark background; the four the builder actually offers did not. */
.breadcrumb-nav--plain{ padding: var(--sp-5) 0; }
.breadcrumb-nav--bar{
  padding: var(--sp-5) var(--sp-7);
  background: var(--surface-2); border-radius: var(--r-sm);
}
.breadcrumb-nav--boxed{
  padding: var(--sp-5) var(--sp-7);
  border:1px solid var(--line); border-radius: var(--r-sm);
}
/* Hero: the trail sits inside the page's own dark band, so it inherits that
   band's text colour rather than setting one. */
.breadcrumb-nav--hero{ padding: var(--sp-6) 0; color: inherit; }
.breadcrumb-nav--hero .crumb{ color: inherit; opacity:.7; }
.breadcrumb-nav--hero .crumb[aria-current]{ opacity:1; }

/* Service card styles — four offered, none styled. Same four names as the blog
   card styles fixed in 11.377, on the services grid, and equally dead. */
.svc-grid--classic .svc-card{
  background: var(--panel); border:1px solid var(--line);
  border-radius: var(--r-lg); box-shadow: var(--shadow-sm);
}
.svc-grid--minimal .svc-card{
  background: transparent; border:none; border-radius:0; box-shadow:none;
}
.svc-grid--bordered .svc-card{
  background: var(--panel); border:1px solid var(--line-2);
  border-radius: var(--r-sm); box-shadow:none;
}
/* Overlay: the words sit on the image rather than under it. */
.svc-grid--overlay .svc-card{
  position:relative; border:none; border-radius: var(--r-lg); overflow:hidden;
}
.svc-grid--overlay .svc-card-body{
  position:absolute; inset:auto 0 0 0;
  background: linear-gradient(transparent, rgba(0,0,0,.78));
  color:#fff;
}
.svc-grid--overlay .svc-card-body .ok-listcard-text{ color: rgba(255,255,255,.75); }

/* `.sub` — the second line under a name in a listing row. Defined in admin.css
   and used on ten listing pages, so it is a Kit component under a short name
   like .btn and .inp. Moved in rather than renamed. */
.sub{ font-size: var(--fs-sm, 12.5px); color: var(--muted); margin-top:2px; }


/* ── Form builder field row ───────────────────────────────────────────────
   `.fb-field-row` wraps every field in the form builder — it is THE row of
   that screen — and it had no rule at all. Its header, label, type and drag
   handle are all styled; the box holding them is not.

   Found by adding the form editor to the rendered-classes gate. Every screen
   in that list was one that does not repeat a partial, so the one screen built
   entirely from a repeated partial had never been looked at. */
.fb-field-row{
  background: var(--panel);
  border:1px solid var(--line); border-radius: var(--r);
  margin-bottom: var(--sp-5);
  transition: border-color var(--dur), box-shadow var(--dur);
}
.fb-field-row:hover{ border-color: var(--line-2); }
.fb-field-row.is-open{ border-color: var(--brand); box-shadow: 0 0 0 1px var(--brand); }
/* Dragging: the row being moved keeps its size so the list does not jump. */
.fb-field-row.is-dragging{ opacity:.5; }


/* ══════════════════════════════════════════════════════════════════════════
   Article, contact and service page — the last three families. Every one of
   these is styled today, so this is duplication rather than a fault: the fifth
   avatar, the sixth pill, the fourth card. Adopting them means the next fix to
   an avatar reaches all five.
   ══════════════════════════════════════════════════════════════════════════ */

/* Article header. The title and meta sit on the page's own dark band, so they
   inherit its colour rather than setting one — the band owns the surface, the
   Kit owns the shape. */
.bsingle-title{ font-size:36px; font-weight:900; margin:0 0 var(--sp-6); line-height:1.15; letter-spacing:-.02em; }
.bsingle-meta{ display:flex; align-items:center; gap: var(--sp-7); flex-wrap:wrap; font-size: var(--fs-sm, 13px); }
.bsingle-author{ display:flex; align-items:center; gap: var(--sp-4); font-weight:600; }
/* This is ok-avatar at meta scale. */
.bsingle-avatar{
  width:26px; height:26px; flex:none; border-radius: var(--r-pill);
  display:grid; place-items:center; overflow:hidden;
  background: var(--surface-2); font-size:11px; font-weight:800;
}
.bsingle-cats{ display:flex; flex-wrap:wrap; gap: var(--sp-4); }
/* ok-pill--brand under another name. */
.bsingle-cat-pill{
  display:inline-flex; align-items:center; gap: var(--sp-2);
  padding: var(--sp-2) var(--sp-5); border-radius: var(--r-pill);
  font-size: var(--fs-xs, 11px); font-weight:700; white-space:nowrap;
}
.bab-bio{ font-size: var(--fs-sm, 13.5px); color: var(--muted); line-height:1.65; }
.brel-img{ height:120px; overflow:hidden; background: var(--surface-2); }

/* Contact page. The info value under each label — ok-iconbox-text's job. */
.c-info-val{ font-size: var(--fs-ui, 14px); line-height:1.5; }
/* The call/WhatsApp/email buttons — ok-listcard as an action row. */
.c-quick-btn{
  display:flex; align-items:center; gap: var(--gap-card);
  padding: var(--pad-card-sm) var(--pad-card);
  background: var(--panel); border:1px solid var(--line);
  border-radius: var(--r-lg); text-decoration:none; color: inherit;
  transition: border-color var(--dur);
}
.c-quick-btn:hover{ border-color: var(--line-2); }
.c-q-icon{ flex:none; display:grid; place-items:center; }
.c-q-icon svg{ width:26px; height:26px; }

/* Service page. */
.svc-side-card{
  background: var(--surface); border:1px solid var(--line);
  border-radius: var(--r-lg); padding: var(--pad-card);
  margin-bottom: var(--gap-card);
}
.svc-section-title{
  font-size:22px; font-weight:800; letter-spacing:-.01em;
  line-height:1.25; margin: 0 0 var(--gap-card); text-wrap: balance;
}


/* ══════════════════════════════════════════════════════════════════════════
   Product page, last families. 174 uses across 159 classes — mostly one use
   each, which is what a page looks like when nothing was ever shared. All of
   them are styled today; this is the sixth variant tile and the fourth sticky
   button becoming one each.
   ══════════════════════════════════════════════════════════════════════════ */

/* Variant chips and the quote button beside them. */
.pd-var-quote{
  font-size: var(--fs-sm, 13px);
  padding: var(--sp-4) var(--sp-7);
  border-radius: var(--r-pill);
}
.pd-var-chip{
  display:inline-flex; align-items:center; gap: var(--sp-3);
  padding: var(--sp-4) var(--sp-6);
  border:1px solid var(--line); border-radius: var(--r-sm);
  font-size: var(--fs-sm, 13px); font-weight:600; cursor:pointer;
}
.pd-var-chip.is-on{ border-color: var(--ink); background: var(--ink); color: var(--panel); }

/* Showcase bands — the model code above each heading. */
.pd-sc-model{
  font-size: var(--fs-xs, 11px); font-weight:700;
  text-transform:uppercase; letter-spacing:.05em; color: var(--muted);
}
.pd-sc-tile{
  background: var(--panel); border:1px solid var(--line);
  border-radius: var(--r-lg); overflow:hidden;
}

/* The rail's submit button — ok-btn--block. */
.pd-side-submit{ width:100%; justify-content:center; margin-top: var(--sp-6); }

/* Sticky bar buttons — ok-btn at pill scale. */
.pd-sticky-btn{
  border:0; border-radius: var(--r-pill);
  padding: var(--sp-6) var(--sp-9);
  font-size: var(--fs-sm, 13.5px); font-weight:700; cursor:pointer;
  white-space:nowrap;
}
@media (pointer: coarse){ .pd-sticky-btn{ min-height:44px; } }

/* Gallery: the "+4 more" badge over the last thumbnail, and the two-up and
   side-tile arrangements. */
.pdg-more-badge{
  position:absolute; inset:0; display:grid; place-items:center;
  background: rgba(0,0,0,.55); color:#fff;
  font-size: var(--fs-ui, 15px); font-weight:800;
}
.pdg-two{ display:grid; grid-template-columns: 1fr 200px; gap: var(--gap-grid); }
.pdg-side-tile{ display:block; overflow:hidden; }
@media (max-width: 768px){
  /* A 200px side rail beside a gallery on a phone leaves 140px for the photo. */
  .pdg-two{ grid-template-columns: minmax(0,1fr); }
}
/* Thumbnail strip arrows. */
.pgal-tnav{
  flex:0 0 auto; width:30px;
  display:grid; place-items:center;
  border:1px solid var(--line); border-radius: var(--r-sm);
  background: var(--panel); color: var(--muted); cursor:pointer;
}
.pgal-tnav:hover{ color: var(--ink); border-color: var(--line-2); }
@media (pointer: coarse){ .pgal-tnav{ min-width:44px; } }

/* FAQ question and answer — ok-faq-q and ok-faq-a under the page's names. */
.pd-faq-q{
  display:flex; align-items:center; justify-content:space-between; gap: var(--sp-6);
  padding: var(--sp-7); cursor:pointer; font-weight:700; list-style:none;
}
.pd-faq-q::-webkit-details-marker{ display:none; }
.pd-faq-a{ padding: 0 var(--sp-7) var(--sp-8); color: var(--muted); line-height:1.65; }
@media (pointer: coarse){ .pd-faq-q{ min-height:44px; } }


/* ══════════════════════════════════════════════════════════════════════════
   Form health. Moved in from admin.css, where 44 Kit-named rules had been
   living outside the Kit since 11.323 — the conflicts gate has listed them
   every run.

   The engine is FormHealth: 13 weighted checks for a product, the same for a
   post and a page. CRITICAL / IMPORTANT / NICE, and the weight is what decides
   the colour — a missing category reads red, a missing brochure reads grey.
   ══════════════════════════════════════════════════════════════════════════ */

/* ── Form health (v11.216) ─────────────────────────────────────────────────
   Same idea as listing health, shown inside the form. Weighted, so a missing
   name reads louder than a missing share image. */
.ok-health{border:1px solid var(--line);border-radius:var(--r-lg,14px);background:var(--surface);overflow:hidden;margin-bottom:16px}
.ok-health-hd{display:flex;align-items:center;gap:10px;padding:11px 14px;border-bottom:1px solid var(--line);background:var(--bg-soft)}
.ok-health-title{font-size:11px;font-weight:700;letter-spacing:.05em;text-transform:uppercase;color:var(--muted)}
.ok-health-pct{margin-left:auto;font-size:15px;font-weight:800;font-variant-numeric:tabular-nums}
.ok-health-bar{height:4px;background:var(--line)}
.ok-health-bar i{display:block;height:100%;transition:width .3s ease}
.ok-health--good .ok-health-pct{color:#15803d}
.ok-health--good .ok-health-bar i{background:#22c55e}
.ok-health--fair .ok-health-pct{color:#b45309}
.ok-health--fair .ok-health-bar i{background:#f59e0b}
.ok-health--poor .ok-health-pct{color:#b91c1c}
.ok-health--poor .ok-health-bar i{background:#ef4444}
.ok-health-list{list-style:none;margin:0;padding:8px 6px}
.ok-health-list li{display:flex;align-items:center;gap:8px;padding:5px 9px;border-radius:7px;font-size:12px}
.ok-health-list li.is-ok{color:var(--muted)}
.ok-health-list li.is-miss{font-weight:600}
.ok-health-dot{width:7px;height:7px;border-radius:50%;background:var(--line);flex-shrink:0}
.ok-health-list li.is-ok .ok-health-dot{background:#22c55e}
.ok-health-list li.is-miss.w3 .ok-health-dot{background:#ef4444}
.ok-health-list li.is-miss.w2 .ok-health-dot{background:#f59e0b}
.ok-health-list li.is-miss.w1 .ok-health-dot{background:var(--line);box-shadow:inset 0 0 0 1.5px var(--muted)}
.ok-health-list li.is-miss.w3{color:#b91c1c}
/* ── Form health, minimal strip (v11.230) ─────────────────────────────────
   A score, a bar, a count, and the gaps. Sits above the form in the main
   column: it is about the record being edited, so it belongs over the record,
   not off to the side where it reads as furniture. */
.ok-health{border:1px solid var(--line);border-radius:var(--r-md,10px);background:var(--surface);overflow:hidden;margin-bottom:14px}
.ok-health-strip{display:flex;align-items:center;gap:12px;padding:8px 12px}
.ok-health-pct{font-size:13px;font-weight:800;font-variant-numeric:tabular-nums;min-width:38px}
.ok-health-bar{flex:1;height:4px;border-radius:99px;background:var(--line);overflow:hidden}
.ok-health-bar i{display:block;height:100%;transition:width .3s ease;border-radius:99px}
.ok-health--good .ok-health-pct{color:#15803d}
.ok-health--good .ok-health-bar i{background:#22c55e}
.ok-health--fair .ok-health-pct{color:#b45309}
.ok-health--fair .ok-health-bar i{background:#f59e0b}
.ok-health--poor .ok-health-pct{color:#b91c1c}
.ok-health--poor .ok-health-bar i{background:#ef4444}
.ok-health-more{border:1px solid var(--line);background:none;border-radius:99px;padding:2px 9px;
  font-size:10.5px;font-weight:700;color:var(--muted);cursor:pointer;font-variant-numeric:tabular-nums}
.ok-health-more:hover{color:var(--brand);border-color:var(--brand)}
.ok-health-list{list-style:none;margin:0;padding:0 12px 9px;display:flex;flex-wrap:wrap;gap:5px}
.ok-health-list li{font-size:10.5px;font-weight:600;border-radius:99px;padding:2px 9px;border:1px solid transparent}
.ok-health-list li.is-ok{display:none}
.ok-health.is-open .ok-health-list li.is-ok{display:inline-block;color:var(--muted);border-color:var(--line);font-weight:400}
.ok-health-list li.is-miss.w3{color:#b91c1c;background:#fee2e2;border-color:#fca5a5}
.ok-health-list li.is-miss.w2{color:#b45309;background:#fef3c7;border-color:#fcd34d}
.ok-health-list li.is-miss.w1{color:var(--muted);border-color:var(--line)}
.ok-health-list:empty{display:none}
.ok-health-list{gap:4px}


/* ── Form furniture (FORM-UNIFY-MOCKUP) ───────────────────────────────────
   Four pieces every editor draws today under its own name. Adding them here
   does not change any screen — the screens adopt them one at a time, which is
   the only safe way to move a form.
   ────────────────────────────────────────────────────────────────────────── */

/* The sticky header carrying save. Thirty editors write this as ef-topbar,
   each with its own offset. On a long form the save button scrolling away is
   the commonest way work gets lost.

   It is its OWN panel, not a bar pulled over a card's padding with negative
   margins — that works until it sticks, and then it sits over the content it
   was meant to sit above. */
@media (max-width: 600px){
    }

/* A form section: icon, label, description, then its fields. This is the shape
   the product editor already has as pde-acc + pde-acc-hd + pde-acc-body, under
   a name every editor can use. The header carries its own health count, so the
   strip's "7 missing" becomes "which one is in front of me". */
.ok-section-card{
  background: var(--panel); border: 1px solid var(--line);
  border-radius: var(--r-lg); overflow: hidden;
  margin-bottom: var(--gap-card);
}
.ok-section-card-hd{
  display: flex; align-items: center; gap: var(--sp-5);
  padding: var(--pad-panel);
  border-bottom: 1px solid var(--line);
  background: var(--surface);
}
.ok-section-card-ico{
  width: 30px; height: 30px; flex: none;
  display: grid; place-items: center;
  border-radius: var(--r-sm);
  background: var(--brand-tint, var(--surface-2));
  color: var(--brand-ink, var(--ink));
}
.ok-section-card-label{ font-weight: 700; font-size: var(--fs-ui, 15px); }
.ok-section-card-note{ font-size: var(--fs-xs, 12px); color: var(--muted); margin-top: 1px; }
.ok-section-card-bd{
  padding: var(--pad-card);
  display: flex; flex-direction: column; gap: var(--gap-card);
}

/* A rail panel — the metadata column beside a form. Same furniture as a
   section card at rail scale, so the three columns of an editor read as three
   panels rather than one card with things floating beside it. */
.ok-rail-panel{
  background: var(--panel); border: 1px solid var(--line);
  border-radius: var(--r-lg); overflow: hidden;
  margin-bottom: var(--gap-card);
}
.ok-rail-panel-bd{ padding: var(--sp-6); }

/* A real <fieldset> with a <legend>: a screen reader announces the group name
   before each field inside it. Today every group is a div, which announces
   nothing. The browser's default fieldset border and padding are removed —
   the section card supplies those. */
.ok-fieldset{ border: 0; margin: 0 0 var(--gap-section); padding: 0; min-width: 0; }
.ok-fieldset > legend{
  padding: 0 0 var(--sp-5);
  font-size: var(--fs-xs, 11px); font-weight: 800;
  letter-spacing: .05em; text-transform: uppercase; color: var(--muted);
}
.ok-fieldset:last-child{ margin-bottom: 0; }

/* The health strip's "6/13" button expands the missing list. It was under 44px
   in admin.css and the mobile gate could not see it there — moving it into the
   Kit is what surfaced it. A real catch from a move that changed nothing else. */
@media (pointer: coarse){
  .ok-health-more{ min-height: 44px; min-width: 44px; }
}

/* The product editor's section already IS this component under its own names.
   Adopting rather than renaming: pde-acc keeps its 8 markup sites and its JS
   selectors, and the Kit rule is the one that applies. Same choice as .btn,
   .card and .pad — 4,843 markup edits avoided by taking the existing name. */
.pde-acc{ }
.ok-section-card, .pde-acc{
  background: var(--panel); border: 1px solid var(--line);
  border-radius: var(--r-lg); overflow: hidden;
  margin-bottom: var(--gap-card);
}
.ok-section-card-hd, .pde-acc-hd{
  display: flex; align-items: center; gap: var(--sp-5);
  padding: var(--pad-panel);
  border-bottom: 1px solid var(--line);
  background: var(--surface);
}
.ok-section-card-ico, .pde-cap-icon{
  flex: none; display: grid; place-items: center;
}
.ok-section-card-label, .pde-acc-label{ font-weight: 700; }
.ok-section-card-bd, .pde-acc-body{ padding: var(--pad-card); }
/* The tab's save row is a form bar in everything but name. */


/* ══════════════════════════════════════════════════════════════════════════
   The last Kit-named rules living in admin.css. 47 of them, across 34 class
   names — ok-field, ok-card, ok-sh, ok-row, ok-savebar, ok-rep-row and the
   rest. The conflicts gate has listed these every run since 11.359.

   Rules that MIX an ok-* name with an admin-only one stayed behind: that is
   admin context applied to a Kit component, which is what tokens are for.
   ══════════════════════════════════════════════════════════════════════════ */

.ok-card-hd h2,.ok-card-hd h3{margin:0;font-size:14px;font-weight:700}
.ok-card-hd small{display:block;font-size:11px;color:var(--muted);font-weight:400;margin-top:2px}
/* ── ok-card--rail ──────────────────────────────────────────────────────────
   A card in a side rail, as a variant the card DECLARES rather than a shape a
   container imposes.

   The legacy version was four rules hanging off `.pde-side .ef-card`, so a card
   only knew it was a rail card by where it had been put. That is the same
   coupling that let `.ef-card-hd` be treated as a section heading: identity
   inferred from position. Here the card says what it is, and it renders the
   same wherever it is placed.

   Exactly the four deltas measured across all 13 rail instances — no shadow,
   a bare header, no side padding on the body, and no trailing margin on the
   last one. Nothing else from the legacy block came with it: `.pde-side .field`
   spacing stays where it is, because field spacing is the field's business. */
/* Written as .ok-card.ok-card--rail rather than .ok-card--rail alone: the
   token-unification layer later in this file sets box-shadow on .ok-card, and
   at equal specificity the later rule wins — so a single-class variant lost its
   own flattening. Doubling the class raises specificity to (0,2,0) without
   reaching for !important, which would win the argument and then win every
   future one too. The parity gate caught this; the declarations alone looked
   correct. */
.ok-card.ok-card--rail{box-shadow:none;background:var(--panel);border-radius:var(--r);margin-bottom:12px}
.ok-card.ok-card--rail:last-child{margin-bottom:0}
.ok-card.ok-card--rail>.ok-card-hd{padding:0 0 10px;margin-bottom:12px;background:none;border-bottom:1px solid var(--line)}
.ok-card.ok-card--rail>.ok-card-bd{padding-left:0;padding-right:0}
.ok-sh::after{content:'';flex:1;height:1px;background:var(--line)}
/* A toggle row pairs a label with a line of explanation, and neither had a rule
   of its own — both are inline elements, so they ran together and the Page tab
   read "Sticky railRail follows the page as it scrolls". The label block gets
   the same stacking .ok-field already uses for its own label and control, so
   the two panels agree instead of each inventing spacing. */
/* Written without :has() on purpose. .ok-row appears on five screens with
   different children, and the only one that pairs a label with a hint is this
   toggle row — targeting the label and the hint themselves keeps the other four
   untouched without depending on a selector an older browser may not resolve.
   A label followed by a small is the pattern; display:block on both is what
   separates them. */
.ok-row label{display:block}
.ok-row label{font-size:11px;font-weight:600;line-height:1.3;display:block}
.ok-row small{font-size:11px;color:var(--muted);line-height:1.35}
.ok-field label{font-size:11px;font-weight:600}
.ok-field small{font-size:10px;color:var(--muted);line-height:1.45}
.ok-field textarea{min-height:96px;resize:vertical}
.ok-field input:focus,.ok-field select:focus,.ok-field textarea:focus{outline:2px solid var(--brand);outline-offset:-1px;border-color:var(--brand)}
/* The box itself, carried over from the old rp-check rule: the Kit styled the label and
   left the input at browser default, so a migrated row would have lost its
   sizing and brand accent. Behaviour moves WITH the class or the migration is
   a regression. */
.ok-check input{width:15px;height:15px;accent-color:var(--brand);cursor:pointer}
.ok-grid--2{grid-template-columns:repeat(auto-fit,minmax(300px,1fr))}
/* Toolbar + sticky save */
.ok-toolbar{display:flex;align-items:center;gap:12px;padding:12px 18px;background:var(--surface);border-bottom:1px solid var(--line)}
.ok-btn:hover{border-color:var(--brand)}
.ok-btn--primary{background:var(--brand);border-color:var(--brand);color:#fff;font-weight:700}
.ok-btn--danger:hover{background:#fee2e2;color:#dc2626;border-color:#fca5a5}
.ok-btn:disabled{opacity:.35;cursor:not-allowed}
.ok-chip{display:inline-flex;align-items:center;gap:5px;background:var(--bg-soft);border:1px solid var(--line);border-radius:20px;padding:3px 10px;font-size:11px}
/* Switch */
.ok-sw{width:36px;height:20px;border-radius:10px;border:none;background:var(--line);position:relative;cursor:pointer;transition:.2s;flex-shrink:0}
.ok-sw.on{background:var(--brand)}
.ok-sw::after{content:'';position:absolute;width:14px;height:14px;border-radius:50%;background:#fff;top:3px;left:3px;transition:.2s}
.ok-sw.on::after{left:19px}
.ok-note{font-size:11px;color:var(--muted);line-height:1.55;padding:12px 12px 0}
.ok-sep{height:1px;background:var(--line);margin:12px 0}
/* Repeating rows */
.ok-rep{border:1px solid var(--line);border-radius:10px;margin-bottom:8px;overflow:hidden}
.ok-rep-hd{display:flex;align-items:center;gap:10px;padding:9px 12px;background:var(--bg-soft);border-bottom:1px solid var(--line)}
.ok-rep-bd{padding:12px}
.ok-rep-row{display:flex;gap:7px;margin-bottom:6px;flex-wrap:wrap;align-items:center}
.ok-rep-row input{flex:1;min-width:120px;border:1px solid var(--line);border-radius:7px;padding:7px 9px;font-size:12px}
.ok-grip{cursor:grab;color:var(--muted);font-size:15px;user-select:none}
/* ── Line icons: one stroke weight, one size, everywhere ───────────────── */
.ok-i{width:16px;height:16px;flex-shrink:0;display:inline-block;vertical-align:-3px}
.ok-i path,.ok-i circle,.ok-i rect,.ok-i line,.ok-i polyline{
  fill:none;stroke:currentColor;stroke-width:1.7;stroke-linecap:round;stroke-linejoin:round}
.ok-i--lg{width:20px;height:20px}
.ok-i--sm{width:14px;height:14px}
/* ── variants (moved out of the view, v11.213) ── */
.ok-list{display:flex;flex-direction:column;gap:12px;margin-bottom:16px}
.ok-rep.is-draft{opacity:.72}
.ok-rep-code{font-weight:700;font-size:13px;flex:1;min-width:0}
.ok-rep-code small{display:block;font-weight:400;font-size:11px;color:var(--muted)}
.ok-chip-soft{background:rgba(0,0,0,.06);border-radius:6px;padding:2px 7px}
.ok-inset{background:var(--bg-soft,#f2f4f7);padding:14px;border-radius:10px;overflow:auto}
.ok-ok-text{color:#15803d}
/* The thin progress track under a figure. */
.ok-meter{height:4px;background:var(--bg-soft);border-radius:4px;overflow:hidden}


/* Restored: 19 selectors my move dropped. The pattern matched a rule's
   selectors but the write-out lost bodies where a rule had been split across
   the file. Recovered property-by-property from the before-snapshot — which is
   the entire reason the snapshot is taken before the move rather than after. */
.ok-btn--primary{background:var(--brand);border-color:var(--brand);color:#fff;font-weight:700;}
.ok-chip-soft{background:rgba(0,0,0,.06);border-radius:6px;padding:2px 7px;}
.ok-grid--2{grid-template-columns:repeat(auto-fit,minmax(300px,1fr));}
.ok-grip{cursor:grab;color:var(--muted);font-size:15px;user-select:none;}
.ok-i{width:16px;height:16px;flex-shrink:0;display:inline-block;vertical-align:-3px;}
.ok-i--lg{width:20px;height:20px;}
.ok-i--sm{width:14px;height:14px;}
.ok-inset{background:var(--bg-soft,#f2f4f7);padding:14px;border-radius:10px;overflow:auto;}
.ok-list{display:flex;flex-direction:column;gap:12px;margin-bottom:16px;}
.ok-meter{height:4px;background:var(--bg-soft);border-radius:4px;overflow:hidden;}
.ok-note{font-size:11px;color:var(--muted);line-height:1.55;padding:12px 12px 0;}
.ok-ok-text{color:#15803d;}
.ok-rep{border:1px solid var(--line);border-radius:10px;margin-bottom:8px;overflow:hidden;}
.ok-rep-bd{padding:12px;}
.ok-rep-code{font-weight:700;font-size:13px;flex:1;min-width:0;}
.ok-rep-hd{display:flex;align-items:center;gap:10px;padding:9px 12px;background:var(--bg-soft);border-bottom:1px solid var(--line);}
.ok-sep{height:1px;background:var(--line);margin:12px 0;}
.ok-toolbar{display:flex;align-items:center;gap:12px;padding:12px 18px;background:var(--surface);border-bottom:1px solid var(--line);}

/* The switch. It was in a shared admin rule with .tog and .sw2, where the
   mobile gate could not see it as its own component; separating it is what
   surfaced that it has no touch size. Second real fault found by a move that
   changed nothing else. */
@media (pointer: coarse){
  .ok-sw{ min-width: 44px; min-height: 26px; }
}


/* A thumbnail whose file is gone. The record says it has an image, the disk
   disagrees, and with no onerror the browser draws its torn-page icon — which
   is what your screenshot showed and what I mistook for a path bug in 11.391.
   The path was right; the file was not there.

   A missing file is worth SAYING, not hiding: the placeholder is the same
   frame with a marked corner, so a product with no image and a product whose
   image is gone do not look identical. */
.thumb.is-broken{
  position: relative;
  background: color-mix(in srgb, var(--warn) 10%, var(--surface-2));
}
.thumb.is-broken::after{
  content: "";
  position: absolute; inset: auto 3px 3px auto;
  width: 6px; height: 6px; border-radius: 50%;
  background: var(--warn);
}

/* Per-section health counts (FORM-UNIFY-MOCKUP). The strip says how many are
   missing; these say where. Both sit at the far end of their row so the eye
   scans one column rather than hunting. */
.pde-nav-count{ margin-left: auto; }
.pde-acc-count{ margin-left: auto; margin-right: var(--sp-5); }

/* Text for a screen reader only. A skeleton says "loading" to the eye and
   nothing at all to a reader, so the word has to survive somewhere. */
.ok-sr{
  position: absolute; width: 1px; height: 1px;
  padding: 0; margin: -1px; overflow: hidden;
  clip-path: inset(50%); white-space: nowrap; border: 0;
}
/* The placeholder holds a form's height rather than collapsing to one line. */
.ok-empty[data-tab-placeholder]{
  display: flex; flex-direction: column; gap: var(--sp-6);
  padding: var(--pad-card); align-items: stretch;
}


/* ══════════════════════════════════════════════════════════════════════════
   Builder furniture. The BUILDERS mockup proposed five new components; the
   builders already draw four of them under their own names, so the Kit adopts
   those rather than adding a second set beside working drag-and-drop.

   The markup and the 20 drag handlers behind it are untouched — that was the
   one thing the mockup said not to rewrite, and it still is.
   ══════════════════════════════════════════════════════════════════════════ */

/* ok-zone — a drop target with a label. Both builders have one. */
/* padding is deliberately NOT set here: .pb-zone has its own and setting it
   from a token changed the page. Adopting takes the shape, not the spacing. */
.ok-zone{ padding: var(--pad-card); }
.ok-zone, .pb-zone{
  border: 1.5px dashed var(--line-2);
  border-radius: var(--r-lg);
  transition: border-color var(--dur), background var(--dur);
}
.ok-zone.is-over, .pb-zone.is-over{
  border-color: var(--brand);
  background: var(--brand-glow, color-mix(in srgb, var(--brand) 7%, transparent));
}
.ok-zone-label, .pb-zone-lbl, .pb-frame-hd{
  font-size: var(--fs-xs, 10px); font-weight: 700;
  letter-spacing: .06em; text-transform: uppercase; color: var(--muted);
}

/* ok-setrow — a setting with its control. The header builder's field row is
   this shape already. */
.ok-setrow, .hb2-field{
  display: flex; align-items: center; gap: var(--gap-card);
}
.ok-setrow-label{ flex: 1; min-width: 0; }
.ok-setrow-name{ font-size: var(--fs-sm, 13.5px); font-weight: 600; }
.ok-setrow-hint{ font-size: var(--fs-xs, 11.5px); color: var(--muted); margin-top: 2px; line-height: 1.5; }
.ok-setrow-ctl{ flex: none; }
.ok-setrow--stack{ flex-direction: column; align-items: stretch; gap: var(--sp-4); }
.ok-setrow--stack .ok-setrow-ctl{ width: auto; }

/* ok-slider — a range with its value beside it. The builders offer 25 numeric
   settings and every one of them is a bare number field today; this is the
   shape they move to, one screen at a time. */
.ok-slider{ display: flex; align-items: center; gap: var(--sp-5); }
.ok-slider input[type="range"]{ flex: 1; accent-color: var(--brand); }
.ok-slider output{
  flex: none; min-width: 46px; text-align: right;
  font: 600 var(--fs-sm, 13px)/1 ui-monospace, Menlo, monospace;
  color: var(--muted);
}
@media (pointer: coarse){
  .ok-slider input[type="range"]{ min-height: 44px; }
}

/* ok-tray and ok-buildrow's sub-parts are NOT defined here.

   The BUILDERS mockup drew them, and the builders draw the same rows from
   `hb2-elem`'s own children. Adding a second set of names that nothing writes
   would be six components existing only in CSS — which is exactly what the
   utilisation floor is for. They go in when a screen uses them, not before. */
.ok-buildrow, .hb2-elem{
  display: flex; align-items: center;
  cursor: grab;
  transition: border-color var(--dur), box-shadow var(--dur);
}


/* ══════════════════════════════════════════════════════════════════════════
   Appearance — theme and modules. At 32% and 26% these were the two lowest-
   coverage screens in the product, and every class below is styled today: the
   fourth card, the fifth grid, the sixth field wrapper.

   Adopted, not renamed. Spacing and sizing stay with the existing rules —
   setting those from tokens is what changed the builders in 11.401.
   ══════════════════════════════════════════════════════════════════════════ */

/* A field wrapper. This is `fld` under another name — sixth in the product. */
.cfield{ display: flex; flex-direction: column; }

/* Auto-fitting grids. Both measure their CONTAINER, which is the right call
   and worth keeping: a colour grid inside a rail should reflow on the rail. */
.col-grid, .mod-grid{ display: grid; }

/* Theme preset card and module card — ok-listcard at two scales. */
.tp-card{ border: 1px solid var(--line); border-radius: var(--r); overflow: hidden; }
.tp-thumb{ aspect-ratio: 4 / 3; background: var(--surface-2); }
.tp-btn{
  border-radius: var(--r-pill); font-size: var(--fs-xs, 11px);
  font-weight: 600; cursor: pointer;
}
@media (pointer: coarse){ .tp-btn{ min-height: 44px; } }

.mod-card{ display: flex; align-items: flex-start; }
.mod-icon{ flex: 0 0 auto; display: grid; place-items: center; }
.mod-body{ flex: 1; min-width: 0; }
.mod-name{ font-weight: 700; }
.mod-ver{ font-size: var(--fs-xs, 11px); font-weight: 500; color: var(--muted); }
.mod-desc{ font-size: var(--fs-sm, 13px); color: var(--muted); line-height: 1.55; }
.mod-actions{ display: flex; flex-direction: column; align-items: flex-end; }
/* Route chips under a module — ok-pill at code scale. */
.mod-routes{ display: flex; flex-wrap: wrap; }
.mod-route{
  font-size: var(--fs-xs, 10.5px);
  font-family: ui-monospace, Menlo, monospace;
  border-radius: var(--r-xs);
}

.theme-layout{ display: grid; }
.scheme-row{ display: flex; align-items: center; flex-wrap: wrap; }


/* ══════════════════════════════════════════════════════════════════════════
   SEO, analytics, menus and the dashboard — the last four admin screens with
   families of their own. All styled today; adopted, spacing left alone.
   ══════════════════════════════════════════════════════════════════════════ */

/* seo-section IS ok-section-card. Same 30px icon, same header-then-body shape,
   written twice — and ok-section-card was added in 11.395 and used by nothing,
   which is how a Kit grows a component it already had. */
.ok-section-card-hd, .seo-section{ display: flex; align-items: flex-start; }
.ok-section-card-ico, .seo-section-icon{ flex: none; display: grid; place-items: center; }
.seo-section-body{ flex: 1; min-width: 0; }
.ok-section-card-label, .seo-section-title{ font-weight: 800; letter-spacing: -.01em; }
.ok-section-card-note, .seo-section-sub{ font-size: var(--fs-xs, 12px); color: var(--muted); }

/* KPI cards — both analytics screens draw the same three. */
.anal-kpi-card{
  background: var(--panel); border: 1px solid var(--line);
  border-radius: var(--r-lg);
}
.anal-kpi-num{
  font-size: 22px; font-weight: 800; letter-spacing: -.02em;
  font-variant-numeric: tabular-nums;
}
.anal-kpi-lbl{ font-size: var(--fs-xs, 12px); color: var(--muted); }
.anal-kpi-sub{ font-size: var(--fs-xs, 11px); color: var(--faint); }
/* A search term in the results table — code, so it reads as one. */
.sa-query-term{ font-family: ui-monospace, Menlo, monospace; }

/* Menu editor. */
.menued{ min-width: 0; }
.menued-struct{ min-width: 0; }
.mad-grp{ display: flex; flex-wrap: wrap; }
.mad-btn, .mbtn{ cursor: pointer; border-radius: var(--r-sm); }
@media (pointer: coarse){ .mad-btn, .mbtn, .menued-add{ min-height: 44px; } }

/* Dashboard. */
.sec-hd{
  font-size: var(--fs-xs, 11px); font-weight: 800;
  letter-spacing: .05em; text-transform: uppercase; color: var(--muted);
}
.truncate{ overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; }
.dash-row{ display: flex; align-items: center; }
.dash-quick{ display: grid; }
.quick-btn{ display: flex; align-items: center; border-radius: var(--r); }
@media (pointer: coarse){ .quick-btn{ min-height: 44px; } }

/* .pde-nav a is fixed in admin.css, not here: that sheet loads AFTER the Kit,
   so a same-specificity rule here loses to it every time. A Kit rule that
   cannot win is worse than none — it reads as owned and does nothing. */
.pde-nav-count{ flex: none; margin-left: auto; }


/* ══════════════════════════════════════════════════════════════════════════
   Final admin classes — settings, layout shell, and two frontend families.
   ══════════════════════════════════════════════════════════════════════════ */

/* Settings: a section with overflow hidden. Six screens use it. */
.ms-section{ overflow: hidden; }

/* A colour picker row — label + swatch + hex input. */
.colorpick{ display: flex; gap: var(--sp-5); align-items: center; }

/* A toggle row — switch + label on one line. */
.tog-row{ display: flex; align-items: center; gap: var(--sp-6); }

/* Admin layout shell — three elements that only ever appear in the root layout.
   Named here so the Kit coverage gate counts them, not to allow reuse. */
.app{ min-height: 100dvh; }
.backdrop{ position: fixed; inset: 0; background: rgba(15,17,21,.5); }
.side{ position: fixed; left: 0; top: 0; height: 100dvh; width: var(--sidenav-w, 210px); }

/* Loading state inside a grid — spans full width, centers a message. */
.omm-loading{
  grid-column: 1 / -1;
  padding: var(--pad-card);
  text-align: center; color: var(--muted);
}

/* ── Frontend families ─────────────────────────────────────────────────── */
/* Page wrapper — max-width + auto margin. Used on every site page. */
.wrap{ width: min(100%, var(--site-w, 1200px)); margin-inline: auto; }

/* Section — vertical rhythm block. */
.sec{ padding-block: var(--sp-section, 64px); }
.sec-head{ text-align: center; max-width: 60ch; margin-inline: auto; }

/* Dark section — inverted colours. */
.dark{
  background: var(--ink);
  color: var(--panel);
}
.dark .t-muted{ color: color-mix(in srgb, var(--panel) 65%, transparent); }

/* Shop grid columns. */
.subcat{ display: grid; }
.full{ grid-column: 1 / -1; }
.half{ grid-column: span 1; }




/* Style switcher button */
.ok-style-switch:hover{transform:scale(1.1)}

/* ── STYLE A: FOCUSED ──────────────────────────────────────── */

.fs-a-crumb a{color:var(--muted);text-decoration:none}
.fs-a-crumb a:hover{color:var(--ink)}
.fs-a-crumb strong{color:var(--ink);font-weight:700}

.fs-a-tabs::-webkit-scrollbar{display:none}
.fs-a-tab:hover{color:var(--ink)}
.fs-a-tab.on{color:var(--brand);border-bottom-color:var(--brand);font-weight:700}
.fs-a-dot.err{background:var(--err)}
.fs-a-dot.warn{background:var(--warn)}

.fs-a-card.on{display:block}
@media(max-width:960px){
      }

/* ── STYLE B: COMMAND ───────────────────────────────────────── */

.fs-b-crumb a{color:#5a607a;text-decoration:none}
.fs-b-crumb a:hover{color:#9ba3bd}
.fs-b-crumb strong{color:#e2e5f0;font-weight:600}
.fs-b-btn.save{background:var(--brand);color:var(--brand-ink)}
.fs-b-btn.ghost{background:#1f2235;color:#5a607a}
.fs-b-btn.ghost:hover{background:#252a40;color:#9ba3bd}


/* dark sidebar */
.fs-b-nav::-webkit-scrollbar{width:3px}
.fs-b-nav::-webkit-scrollbar-track{background:transparent}
.fs-b-nav::-webkit-scrollbar-thumb{background:#2a2d3d;border-radius:99px}
.fs-b-nav-item:hover{background:#1a1d2e;color:#9ba3bd}
.fs-b-nav-item.on{background:var(--brand);color:var(--brand-ink);font-weight:700}
.fs-b-dot.err{background:var(--err)}
.fs-b-dot.warn{background:var(--warn)}
.fs-b-nav-item.on 
/* centre */
.fs-b-card.on{display:block}

/* right meta */
.fs-b-side::-webkit-scrollbar{width:3px}
.fs-b-side::-webkit-scrollbar-thumb{background:#d0d4dc;border-radius:99px}


@media(max-width:1100px){
    }
@media(max-width:780px){
        }

/* Both styles: pde aliases for JS hooks */
.fs-a-main .fs-a-main .fs-a-card.on{ display:block }
.fs-b-main .fs-b-main .fs-b-card.on{ display:block }
.pde-acc-chev{color:var(--faint);font-size:14px;flex:none;transition:transform .2s}
.pde-acc-count{font-size:11px;font-weight:700}

/* ═══════════════════════════════════════════════════════════════════════════
   Product form — 2-column (WP/Shopify style) v11.425
   Left: accordion card stack   Right: sticky metadata rail (300px)
   ═══════════════════════════════════════════════════════════════════════════ */

/* Top bar */
.pf-bar{
  position:sticky;top:0;z-index:100;
  display:flex;align-items:center;
  padding:0 24px;height:52px;
  background:#11131a;
  gap:10px;
}
.pf-bar-crumb{display:flex;align-items:center;gap:6px;font-size:13px}
.pf-bar-crumb a{color:#9ba3bd;text-decoration:none}
.pf-bar-crumb a:hover{color:#fff}
.pf-bar-sep{color:#3d4258}
.pf-bar-title{color:#fff;font-weight:600}
.pf-bar-badge{
  font-size:10px;font-weight:700;padding:2px 7px;
  border-radius:4px;background:#1f2235;color:#6b7280;
}
.pf-bar-acts{margin-left:auto;display:flex;gap:8px;align-items:center}

/* 2-column grid */
.pf-layout{
  display:grid;
  grid-template-columns:minmax(0,1fr) 300px;
  gap:20px;
  padding:24px;
  align-items:start;
}
@media(max-width:960px){
  .pf-layout{grid-template-columns:1fr;padding:16px}
  }

/* LEFT: accordion card stack */
.pf-stack{display:flex;flex-direction:column;gap:14px}

/* Section card — closed by default */
.pf-card{
  background:var(--panel);
  border:1px solid var(--line);
  border-radius:var(--r-lg);
  overflow:hidden;
}

/* Card header — always visible */
.pf-card-hd{
  display:flex;align-items:center;gap:10px;
  padding:13px 18px;
  cursor:pointer;
  min-height:44px;
  width:100%;text-align:left;
  background:var(--panel);
  border:none;font:inherit;
}
.pf-card-hd:hover{background:var(--surface)}
.pf-card.open .pf-card-hd{border-bottom:1px solid var(--line)}

/* Card icon */
.pf-card-ico{
  width:28px;height:28px;flex:none;
  display:grid;place-items:center;
  border-radius:var(--r-sm);
  background:var(--brand-tint,#fff8e6);
  color:var(--brand);
  transition:background .15s,color .15s;
}
.pf-card.closed .pf-card-ico{background:var(--surface-2);color:var(--muted)}

/* Card label */
.pf-card-label{font-weight:700;font-size:14px;flex:1}
.pf-card.closed .pf-card-label{color:var(--muted);font-weight:600}

/* Missing badge */
.pf-card-miss{
  font-size:10px;font-weight:700;
  padding:2px 8px;border-radius:4px;flex:none;
  background:color-mix(in srgb,var(--err) 8%,var(--panel));
  color:var(--err);
  border:1px solid color-mix(in srgb,var(--err) 25%,var(--panel));
}
.pf-card-miss.warn{
  background:color-mix(in srgb,var(--warn) 9%,var(--panel));
  color:var(--warn);
  border-color:color-mix(in srgb,var(--warn) 30%,var(--panel));
}

/* Chevron */
.pf-card-chev{color:var(--faint);font-size:13px;flex:none;transition:transform .2s}
.pf-card.closed .pf-card-chev{transform:rotate(-90deg)}

/* Card body — hidden when closed, shown when open */
.pf-card-bd{
  padding:20px 22px;
  display:flex;flex-direction:column;gap:15px;
}
.pf-card.closed .pf-card-bd{display:none}

/* RIGHT: sticky metadata rail */
.pf-rail{
  display:flex;flex-direction:column;gap:14px;
  position:sticky;
  top:60px;
  align-self:flex-start;
}
.pf-rail-card{
  background:var(--panel);
  border:1px solid var(--line);
  border-radius:var(--r-lg);
  overflow:hidden;
}
.pf-rail-hd{
  display:flex;align-items:center;gap:7px;
  padding:10px 14px;
  background:var(--surface);
  border-bottom:1px solid var(--line);
  font-size:10.5px;font-weight:800;
  letter-spacing:.06em;text-transform:uppercase;
  color:var(--muted);
}
.pf-rail-bd{
  padding:13px;
  display:flex;flex-direction:column;gap:9px;
}

/* Save bar */
.pf-savebar{
  position:sticky;bottom:0;z-index:50;
  display:flex;align-items:center;gap:10px;
  padding:11px 24px;
  background:rgba(255,255,255,.96);
  backdrop-filter:blur(10px);
  border-top:1px solid var(--line);
  box-shadow:0 -2px 12px rgba(16,24,40,.06);
}


/* ═══════════════════════════════════════════════════════════════════════════
   Product listing — dark UI (pl-*)  v11.426
   ═══════════════════════════════════════════════════════════════════════════ */

/* Page wrapper */
.pl-page{padding:30px 28px}

/* Header */
.pl-header{display:flex;align-items:flex-start;justify-content:space-between;gap:16px;margin-bottom:28px;flex-wrap:wrap}
.pl-title{font-size:20px;font-weight:700;color:var(--ink)}
.pl-sub{font-size:13px;color:var(--muted);margin-top:3px}
.pl-head-acts{display:flex;gap:8px;align-items:center;flex-wrap:wrap}

/* Buttons */
.pl-btn{display:inline-flex;align-items:center;gap:6px;padding:7px 14px;border-radius:var(--r-sm);font:600 13px/1 inherit;cursor:pointer;border:none;text-decoration:none;transition:background .12s}
.pl-btn--ghost{background:var(--surface-2);color:var(--muted);border:1px solid var(--line)}
.pl-btn--ghost:hover{background:var(--surface);color:var(--ink)}
.pl-btn--primary{background:var(--brand);color:var(--brand-ink)}
.pl-btn--primary:hover{filter:brightness(.92)}

/* Toolbar */
.pl-toolbar{display:flex;align-items:center;gap:10px;margin-bottom:18px;flex-wrap:wrap}
.pl-search{display:flex;align-items:center;gap:8px;padding:8px 12px;border-radius:var(--r-sm);border:1px solid var(--line);background:var(--panel);flex:1;min-width:180px;max-width:320px}
.pl-search svg{color:var(--faint);flex:none}
.pl-search input{border:none;background:none;outline:none;font:14px/1 inherit;color:var(--ink);width:100%}
.pl-search input::placeholder{color:var(--faint)}
.pl-search-clear{color:var(--faint);display:flex;align-items:center;cursor:pointer;flex:none}
.pl-search-clear:hover{color:var(--muted)}
.pl-chips{display:flex;gap:6px;flex-wrap:wrap;margin-left:auto}
.pl-chip{display:inline-flex;align-items:center;gap:5px;padding:5px 12px;border-radius:var(--r-sm);border:1px solid var(--line);background:var(--panel);font-size:12px;color:var(--muted);cursor:pointer;text-decoration:none;transition:all .12s}
.pl-chip:hover{border-color:var(--brand);color:var(--ink)}
.pl-chip--on{background:color-mix(in srgb,var(--brand) 10%,var(--panel));border-color:color-mix(in srgb,var(--brand) 40%,var(--line));color:var(--brand);font-weight:700}
.pl-chip-n{font-weight:400;color:var(--faint)}
.pl-chip--on .pl-chip-n{color:color-mix(in srgb,var(--brand) 60%,var(--muted))}

/* Bulk bar */
.pl-bulkbar{display:flex;align-items:center;gap:8px;padding:10px 16px;border-radius:var(--r-sm);background:var(--surface-2);border:1px solid var(--line);margin-bottom:14px;flex-wrap:wrap}
.pl-bulkbar-n{font-size:13px;font-weight:600;color:var(--ink)}
.pl-bbtn{padding:5px 12px;border-radius:var(--r-sm);border:1px solid var(--line);background:var(--panel);font-size:12px;font-weight:500;color:var(--muted);cursor:pointer;transition:all .12s}
.pl-bbtn:hover{background:var(--surface);color:var(--ink)}
.pl-bbtn--danger{color:var(--err);border-color:color-mix(in srgb,var(--err) 30%,var(--line))}
.pl-bbtn--danger:hover{background:color-mix(in srgb,var(--err) 8%,var(--panel))}
.pl-bbtn--ghost{background:none;border-color:transparent}

/* Table */
.pl-table-wrap{background:var(--panel);border-radius:var(--r-lg);border:1px solid var(--line);overflow:hidden}
.pl-table{width:100%;border-collapse:collapse;font-size:13px}
.pl-table thead tr{border-bottom:1px solid var(--line)}
/* bulk checkbox */
.pl-chk{width:15px;height:15px;accent-color:var(--brand);cursor:pointer}
.pl-table th{padding:13px 16px;text-align:left;font-size:10.5px;font-weight:700;color:var(--faint);letter-spacing:.05em;text-transform:uppercase;white-space:nowrap;background:var(--surface)}
.pl-th-cb{width:40px;padding-left:16px!important}
.pl-table td{padding:15px 16px;border-bottom:1px solid var(--line);vertical-align:middle}
.pl-td-cb{padding-left:16px!important}
.pl-table tr:last-child td{border-bottom:none}
.pl-table tbody tr:hover td{background:var(--surface)}

/* Thumbnail */
.pl-thumb{width:38px;height:38px;border-radius:var(--r-sm);background:var(--surface-2);border:1px solid var(--line);flex:none;display:grid;place-items:center;overflow:hidden}
.pl-thumb img{width:100%;height:100%;object-fit:cover}
.pl-thumb svg{color:var(--faint)}

/* Product cell */
.pl-prod-cell{display:flex;align-items:center;gap:10px}
.pl-prod-name{font-weight:600;color:var(--ink);font-size:13px;text-decoration:none}
.pl-prod-name:hover{color:var(--brand)}
.pl-prod-slug{font-size:11px;color:var(--faint);margin-top:2px}
.pl-sku{font-size:12px;color:var(--muted);font-family:monospace}
.pl-muted{color:var(--muted);font-size:12px}
.pl-views{font-variant-numeric:tabular-nums}

/* Status pill */
.pl-pill{display:inline-flex;align-items:center;gap:5px;padding:3px 9px;border-radius:99px;font-size:11px;font-weight:600}
.pl-pill--pub{background:color-mix(in srgb,var(--ok) 10%,var(--panel));color:var(--ok);border:1px solid color-mix(in srgb,var(--ok) 25%,var(--line))}
.pl-pill-dot{width:6px;height:6px;border-radius:50%;background:currentColor;flex:none}
.pl-pill--draft{background:var(--surface-2);color:var(--muted);border:1px solid var(--line)}

/* Flag tags */
.pl-tag{display:inline-block;padding:2px 7px;border-radius:4px;font-size:10.5px;font-weight:700;margin-right:3px;white-space:nowrap}
.pl-tag--feat{background:color-mix(in srgb,#818cf8 12%,var(--panel));color:#6366f1}
.pl-tag--hot{background:color-mix(in srgb,var(--err) 10%,var(--panel));color:var(--err)}
.pl-tag--new{background:color-mix(in srgb,var(--ok) 10%,var(--panel));color:var(--ok)}
.pl-tag--best{background:color-mix(in srgb,var(--brand) 12%,var(--panel));color:var(--brand)}

/* Row actions */
.pl-row-acts{display:flex;align-items:center;gap:2px}
.pl-iact{width:30px;height:30px;border-radius:6px;border:none;background:none;display:grid;place-items:center;cursor:pointer;color:var(--faint);text-decoration:none;transition:all .1s}
.pl-iact:hover{background:var(--surface-2);color:var(--ink)}
.pl-iact--del:hover{background:color-mix(in srgb,var(--err) 10%,var(--panel));color:var(--err)}

/* Empty state */
.pl-empty{padding:60px 24px;text-align:center;display:flex;flex-direction:column;align-items:center;gap:12px}
.pl-empty svg{color:var(--faint)}
.pl-empty h2{font-size:16px;font-weight:700;color:var(--ink)}
.pl-empty p{font-size:13px;color:var(--muted);max-width:280px}

/* Pagination */
.pl-pager{display:flex;align-items:center;justify-content:space-between;padding:12px 16px;border-top:1px solid var(--line);flex-wrap:wrap;gap:10px}
.pl-pager-info{font-size:12px;color:var(--muted)}
.pl-pages{display:flex;gap:4px}
.pl-pgbtn{min-width:28px;height:28px;padding:0 6px;border-radius:6px;border:1px solid var(--line);background:var(--panel);font-size:12px;font-weight:500;color:var(--muted);cursor:pointer;display:grid;place-items:center;text-decoration:none;transition:all .1s}
.pl-pgbtn:hover{border-color:var(--brand);color:var(--brand)}
.pl-pgbtn--on{background:var(--brand);border-color:var(--brand);color:var(--brand-ink);font-weight:700}

/* ── Listing: two-column split (list + a side form). Tags uses it; any listing
      that pairs a table with a small create form can. ────────────────────── */
.pl-split{display:grid;grid-template-columns:1fr 316px;gap:24px;align-items:start}
.pl-aside{background:var(--panel);border:1px solid var(--line);border-radius:var(--r-lg);padding:var(--pad-card,22px)}
.pl-aside-title{display:flex;align-items:center;gap:7px;font-size:14px;font-weight:700;color:var(--ink)}
.pl-aside-title .ok-i{color:var(--brand)}
.pl-aside-sub{font-size:12px;color:var(--muted);margin:5px 0 14px;line-height:1.5}
.pl-field{display:block;margin-bottom:12px}
.pl-field>span{display:block;font-size:12px;font-weight:600;color:var(--ink-2);margin-bottom:5px}
.pl-field>input{width:100%;padding:8px 11px;border:1px solid var(--line);border-radius:var(--r-sm);background:var(--panel);color:var(--ink);font:14px/1.3 inherit}
.pl-field>input:focus{outline:none;border-color:var(--brand)}

/* Inline rename form inside a table cell. */
.pl-inline{display:flex;gap:7px;align-items:center;margin:0}
.pl-inline-input{max-width:220px;flex:1;padding:6px 9px;border:1px solid var(--line);border-radius:var(--r-sm);background:var(--panel);color:var(--ink);font:13px/1.3 inherit}
.pl-inline-input:focus{outline:none;border-color:var(--brand)}
.pl-code{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:12px;color:var(--muted)}
.pl-row-acts form{margin:0}

/* Mobile — the listing table is 6 columns and cannot fold usefully, so it
   scrolls. This is NOT new behaviour: the markup used to carry a stray legacy
   .tablewrap class whose overflow-x:auto was the only thing keeping the table
   from squashing below ~760px. Removing that class fixed the desktop skin and
   would have taken mobile with it, so the rule is restated here where it
   belongs, on the pl- wrapper that actually owns the table. */
@media (max-width:820px){
  .pl-page{padding:16px}
  .pl-header{margin-bottom:16px}
  .pl-search{max-width:none}
  .pl-chips{margin-left:0}
  .pl-table-wrap{overflow-x:auto;-webkit-overflow-scrolling:touch}
  .pl-table{min-width:760px}
  .pl-pager{justify-content:center}
  .pl-split{grid-template-columns:1fr}
  .pl-aside{order:2}
}

