/* ============================================================
   Catalog Template — plain CSS, no build step, no external fonts.
   Mobile first: phone styles are the defaults, wider screens add
   to them further down.

   Layout of this file
     1. Design tokens (colour, type scale, spacing, radius, shadow)
     2. Base + reset
     3. App bar, staff menu, footer
     4. Browse bar (chips, search, sort/filter sheets)
     5. Product grid + cards
     6. Product detail
     7. Staff screens (panels, forms, tables, master cards)
     8. Buttons, action colours, tags
     9. Feedback (flash, notice, toast, empty)
    10. Tablet / desktop
    11. Motion (at the very end of the file)
   ============================================================ */

/* ============================================================
   1. Design tokens
   ============================================================ */
:root{
  /* --- Surfaces --- */
  --bg:#f4f7fb;              /* page */
  --bg-deep:#e8eef7;         /* wells, image placeholders */
  --card:#fff;
  --card-2:#fbfcfe;          /* nested surface inside a card */

  /* --- Ink. Darkened for contrast: 15.6:1 and 5.9:1 on white. --- */
  --ink:#0d1729;
  --ink-2:#2b3a55;           /* secondary heading / strong body */
  --muted:#54637c;           /* meta text — passes AA at 15px */
  --faint:#93a1b5;           /* placeholders only, never body copy */

  /* --- Lines --- */
  --line:#dfe6f0;
  --line-2:#c9d5e6;          /* stronger rule, form borders */

  /* --- Accent (blue) --- */
  --accent:#1d4ed8;
  --accent-600:#1e40af;
  --accent-700:#1b3a9c;
  --accent-soft:#eef4ff;
  --accent-line:#c7dbfe;
  --accent-grad:linear-gradient(140deg,#2563eb 0%,#1d4ed8 55%,#1b3fa8 100%);

  /* --- Money (green) --- */
  --price:#047857;
  --price-deep:#065f46;
  --price-soft:#e7f7f0;

  /* --- WhatsApp --- */
  --wa:#25d366;
  --wa-ink:#07361e;
  --wa-grad:linear-gradient(140deg,#37dd77 0%,#22c55e 55%,#16a34a 100%);

  /* --- Category badge (warm, sits on photos) --- */
  --badge-grad:linear-gradient(140deg,#f59e0b 0%,#d97706 60%,#b45309 100%);

  /* --- Type scale --- */
  --font:-apple-system,BlinkMacSystemFont,"Segoe UI Variable Text","Segoe UI",
         Roboto,"Helvetica Neue",Arial,"Noto Sans",sans-serif,
         "Apple Color Emoji","Segoe UI Emoji";
  --font-mono:ui-monospace,SFMono-Regular,"SF Mono",Menlo,Consolas,
              "Liberation Mono",monospace;
  --t-2xs:11px;
  --t-xs:12px;
  --t-sm:13px;
  --t-base:15px;
  --t-md:16px;
  --t-lg:18px;
  --t-xl:21px;
  --t-2xl:25px;
  --t-3xl:31px;

  /* --- Spacing scale (4px rhythm) --- */
  --s1:4px; --s2:8px; --s3:12px; --s4:16px; --s5:22px; --s6:30px; --s7:42px;

  /* --- Radius --- */
  --r-xs:8px; --r-sm:10px; --r:13px; --r-lg:16px; --r-xl:20px; --r-pill:999px;

  /* --- Elevation. Two-layer shadows read as real depth. --- */
  --sh-1:0 1px 2px rgba(13,23,41,.06), 0 1px 3px rgba(13,23,41,.05);
  --sh-2:0 2px 4px rgba(13,23,41,.05), 0 6px 16px rgba(13,23,41,.08);
  --sh-3:0 4px 8px rgba(13,23,41,.06), 0 16px 34px rgba(13,23,41,.12);
  --sh-4:0 8px 18px rgba(13,23,41,.10), 0 26px 56px rgba(13,23,41,.18);
  --sh-up:0 -2px 6px rgba(13,23,41,.05), 0 -10px 28px rgba(13,23,41,.10);
  --ring:0 0 0 3px rgba(37,99,235,.22);

  --appbar-h:56px;
  --ease:cubic-bezier(.4,.14,.3,1);
}

/* ============================================================
   2. Base
   ============================================================ */
*{box-sizing:border-box}
html{-webkit-text-size-adjust:100%}
body{
  margin:0;background:var(--bg);color:var(--ink);
  font-family:var(--font);font-size:var(--t-base);line-height:1.55;
  font-weight:450;
  -webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;
  font-feature-settings:"kern" 1,"liga" 1;
  -webkit-tap-highlight-color:transparent;
}
a{color:inherit;text-decoration:none}
img{max-width:100%}
h1,h2,h3{line-height:1.2;letter-spacing:-.018em;font-weight:800;color:var(--ink)}
b,strong{font-weight:700}

/* One visible focus ring for the whole app — keyboard and screen-reader users
   never lose their place, mouse users never see it. */
:focus-visible{outline:2px solid var(--accent);outline-offset:2px;border-radius:6px}
input:focus-visible,select:focus-visible,textarea:focus-visible{outline:none}

.wrap{max-width:1180px;margin:0 auto;padding:0 var(--s3)}
.wrap.narrow{max-width:460px}

/* Page head */
main{padding-top:var(--s4)}
.page-head{display:flex;flex-wrap:wrap;align-items:center;gap:var(--s2) var(--s3);
  margin-bottom:var(--s4)}
.page-head h1{margin:0;font-size:var(--t-xl)}
.count{color:var(--muted);font-size:var(--t-sm);font-weight:600}

@media (prefers-reduced-motion:reduce){
  *,*::before,*::after{animation-duration:.001ms !important;transition-duration:.001ms !important}
}

/* ============================================================
   3. App bar, staff menu, footer
   ============================================================ */
.appbar{
  position:sticky;top:0;z-index:40;
  background:rgba(255,255,255,.94);
  -webkit-backdrop-filter:saturate(180%) blur(12px);
  backdrop-filter:saturate(180%) blur(12px);
  border-bottom:1px solid rgba(13,23,41,.07);
  box-shadow:0 1px 3px rgba(13,23,41,.05), 0 6px 18px rgba(13,23,41,.05);
}
.appbar-in{display:flex;align-items:center;justify-content:space-between;
  height:var(--appbar-h);gap:var(--s2)}
.brand{display:flex;align-items:center;gap:var(--s2);min-width:0;
  padding:4px 6px 4px 0;border-radius:var(--r-sm)}
.brand-mark{width:32px;height:32px;flex:0 0 32px;border-radius:10px;
  background:var(--accent-grad);color:#fff;font-weight:800;font-size:var(--t-md);
  display:flex;align-items:center;justify-content:center;
  box-shadow:0 2px 6px rgba(29,78,216,.32), inset 0 1px 0 rgba(255,255,255,.28)}
.brand-text{font-weight:800;font-size:var(--t-lg);letter-spacing:-.028em;
  white-space:nowrap;overflow:hidden;text-overflow:ellipsis;color:var(--ink)}
.appbar-actions{display:flex;align-items:center;gap:7px}

.icon-btn{width:42px;height:42px;border-radius:50%;border:0;cursor:pointer;
  display:flex;align-items:center;justify-content:center;
  background:#e9eef6;color:var(--ink-2);
  transition:transform .16s var(--ease),box-shadow .16s var(--ease),
             background-color .16s var(--ease)}
.icon-btn:active{transform:scale(.92)}
.icon-btn.wa{background:var(--wa-grad);color:#fff;
  box-shadow:0 2px 8px rgba(22,163,74,.34)}
.icon-btn.call{background:var(--accent-grad);color:#fff;
  box-shadow:0 2px 8px rgba(29,78,216,.30)}
.icon-btn.add{background:var(--accent-grad);color:#fff;
  box-shadow:0 2px 8px rgba(29,78,216,.30)}
@media (hover:hover){
  /* Only the plain buttons go grey on hover.
     `.icon-btn:hover` is (0,2,0) — the same specificity as `.icon-btn.wa` —
     and it sits later in the file, so without the :not() it won the cascade
     and painted grey straight over the green and blue gradients. The
     brightness rule underneath then simply brightened the grey. */
  .icon-btn:not(.wa):not(.call):not(.add):hover{background:#dde5f1;color:var(--ink)}
  .icon-btn.wa:hover,.icon-btn.call:hover,.icon-btn.add:hover{filter:brightness(1.08)}
}

/* Staff menu (never shown to customers) */
.staff-menu{background:var(--card);border-top:1px solid var(--line);
  box-shadow:var(--sh-3)}
.staff-menu .wrap{display:flex;flex-direction:column;
  padding-top:var(--s2);padding-bottom:var(--s3)}
.staff-who{font-size:var(--t-2xs);color:var(--accent);text-transform:uppercase;
  letter-spacing:.09em;font-weight:800;padding:var(--s2) 2px var(--s1)}
.staff-menu a{padding:14px 4px;font-weight:650;font-size:var(--t-md);
  color:var(--ink-2);border-bottom:1px solid var(--line);min-height:48px;
  display:flex;align-items:center;border-radius:var(--r-xs)}
.staff-menu a:last-child{border-bottom:0}
.staff-menu a:active{background:var(--accent-soft);color:var(--accent)}
.staff-menu a.danger{color:#b91c1c}

.foot{margin-top:var(--s7);padding:var(--s5) 0 var(--s6);
  border-top:1px solid var(--line);color:var(--muted);font-size:var(--t-sm)}

/* ============================================================
   4. Browse bar
   ============================================================ */
.browse{position:sticky;top:var(--appbar-h);z-index:30;
  background:rgba(244,247,251,.93);
  -webkit-backdrop-filter:saturate(180%) blur(10px);
  backdrop-filter:saturate(180%) blur(10px);
  margin:0 calc(var(--s3) * -1) var(--s3);
  padding:var(--s2) var(--s3) var(--s2);
  border-bottom:1px solid var(--line)}

.chips-row{display:flex;gap:7px;overflow-x:auto;padding-bottom:var(--s2);
  scrollbar-width:none;-webkit-overflow-scrolling:touch}
.chips-row::-webkit-scrollbar{display:none}
.chip-tab{flex:0 0 auto;padding:9px 15px;border-radius:var(--r-pill);
  background:var(--card);color:var(--ink-2);
  font-size:var(--t-sm);font-weight:700;letter-spacing:-.005em;
  white-space:nowrap;border:1px solid var(--line);min-height:38px;
  display:inline-flex;align-items:center;
  box-shadow:0 1px 2px rgba(13,23,41,.04);
  transition:transform .15s var(--ease),box-shadow .15s var(--ease),
             color .15s var(--ease),border-color .15s var(--ease)}
.chip-tab:active{transform:scale(.96)}
.chip-tab.on{background:var(--accent-grad);color:#fff;border-color:transparent;
  box-shadow:0 2px 8px rgba(29,78,216,.34)}
@media (hover:hover){
  .chip-tab:hover{border-color:var(--accent-line);color:var(--accent)}
  .chip-tab.on:hover{color:#fff}
}

.search-row{display:flex;gap:7px;align-items:stretch}
.search-box{flex:1;min-width:0;display:flex;align-items:center;gap:var(--s2);
  background:var(--card);border:1px solid var(--line-2);border-radius:var(--r);
  padding:0 12px;color:var(--muted);
  box-shadow:0 1px 2px rgba(13,23,41,.04);
  transition:border-color .15s var(--ease),box-shadow .15s var(--ease)}
.search-box:focus-within{border-color:var(--accent);box-shadow:var(--ring);
  color:var(--accent)}
.search-box input{flex:1;min-width:0;border:0;outline:0;background:none;
  font-family:inherit;font-size:var(--t-md);font-weight:500;
  padding:12px 0;color:var(--ink)}
.search-box input::placeholder{color:var(--faint);font-weight:450}

.tool{display:flex;align-items:center;gap:5px;padding:0 12px;min-height:46px;
  border:1px solid var(--line-2);border-radius:var(--r);background:var(--card);
  color:var(--ink-2);font-family:inherit;font-size:var(--t-2xs);font-weight:800;
  text-transform:uppercase;letter-spacing:.06em;cursor:pointer;white-space:nowrap;
  box-shadow:0 1px 2px rgba(13,23,41,.04);
  transition:transform .15s var(--ease),border-color .15s var(--ease),
             color .15s var(--ease),background-color .15s var(--ease)}
.tool:active{transform:scale(.96)}
.tool.on{border-color:var(--accent);color:var(--accent);background:var(--accent-soft);
  box-shadow:0 1px 2px rgba(29,78,216,.14)}
@media (hover:hover){ .tool:hover{border-color:var(--accent);color:var(--accent)} }

/* z-index matters: the product thumbs below are position:relative, so without
   it the open sheet paints underneath them on wide screens. */
.sheet{position:absolute;z-index:50;left:var(--s3);right:var(--s3);margin-top:var(--s2);
  background:var(--card);border:1px solid var(--line);border-radius:var(--r-lg);
  overflow:hidden;box-shadow:var(--sh-4);max-height:62vh;overflow-y:auto;
  animation:sheet-in .16s var(--ease)}
@keyframes sheet-in{from{opacity:0;transform:translateY(-6px)}to{opacity:1;transform:none}}
.sheet-title{padding:var(--s3) var(--s4) var(--s1);font-size:var(--t-2xs);
  font-weight:800;color:var(--muted);text-transform:uppercase;letter-spacing:.08em}
.sheet-item{display:flex;align-items:center;min-height:48px;
  padding:12px var(--s4);border-bottom:1px solid var(--line);
  font-size:var(--t-md);font-weight:600;color:var(--ink-2)}
.sheet-item:last-child{border-bottom:0}
.sheet-item.on{color:var(--accent);background:var(--accent-soft);font-weight:750;
  box-shadow:inset 3px 0 0 var(--accent)}
.sheet-item.clear{color:#b91c1c;font-weight:700}
@media (hover:hover){
  /* Same trap as .icon-btn: a bare :hover here is (0,2,0), matching
     .sheet-item.on, and being later it wiped the accent tint off the option
     you had chosen the moment you pointed at it. The selected row deepens
     instead. */
  .sheet-item:not(.on):hover{background:var(--bg)}
  .sheet-item.on:hover{background:#e3ecfd}
}

.result-line{display:flex;align-items:center;justify-content:space-between;
  gap:var(--s3);color:var(--muted);font-size:var(--t-sm);font-weight:600;
  margin:2px 0 var(--s3)}
.result-line span{min-width:0}

/* Filter bar (staff pages) */
.filters{display:flex;flex-wrap:wrap;gap:var(--s2);margin-bottom:var(--s4)}
.filters input,.filters select{
  padding:12px 13px;border:1px solid var(--line-2);border-radius:var(--r);
  background:var(--card);color:var(--ink);
  font-family:inherit;font-size:var(--t-md);min-width:130px;min-height:46px;
  box-shadow:0 1px 2px rgba(13,23,41,.04)}
.filters input{flex:1 1 200px;min-width:150px}
/* A tick box in a filter row is not a text field. The rule above would give it
   a 150px minimum width and a 46px box — which is the giant empty square that
   turned up beside "Include products already in a group". */
.filters input[type=checkbox]{
  flex:0 0 auto;width:16px;height:16px;min-width:0;min-height:0;
  padding:0;border:0;border-radius:0;box-shadow:none;
  accent-color:var(--accent);cursor:pointer}
/* Height still matches the fields beside it so the row lines up; only the
   lettering and the tick come down to the size of a filter, not a button. */
.filters .chip{flex:0 0 auto;gap:7px;padding:9px 14px;font-size:var(--t-sm)}

/* ============================================================
   5. Product grid + cards
   ============================================================ */
.grid{display:grid;gap:var(--s3);grid-template-columns:repeat(2,minmax(0,1fr))}

.card{background:var(--card);border:1px solid var(--line);border-radius:var(--r-lg);
  overflow:hidden;display:flex;flex-direction:column;box-shadow:var(--sh-1)}
.thumb{position:relative;aspect-ratio:1/1;
  background:linear-gradient(150deg,#f2f6fc,#e6ecf6);
  display:flex;align-items:center;justify-content:center;overflow:hidden}
.thumb img{width:100%;height:100%;object-fit:cover;display:block}
.thumb .ph{color:var(--faint);font-size:var(--t-2xs);letter-spacing:.1em;
  text-transform:uppercase;font-weight:700}
.card-body{padding:11px 11px 12px;display:flex;flex-direction:column;gap:1px;flex:1}
.art{font-weight:750;font-size:var(--t-base);line-height:1.3;color:var(--ink);
  letter-spacing:-.012em}
.meta{color:var(--muted);font-size:var(--t-xs);font-weight:550}
.rate{margin-top:auto;padding-top:var(--s2);font-weight:800;
  font-size:var(--t-lg);color:var(--price);letter-spacing:-.02em;
  font-variant-numeric:tabular-nums}

/* Art no and price on one line, remark under it.
   The line carries the margin-top:auto that used to sit on .rate, so the pair
   still hugs the bottom of the card; the price inside it must give that up or
   the two would sit on different baselines. */
.card-line{margin-top:auto;padding-top:var(--s2);display:flex;
  align-items:baseline;justify-content:space-between;gap:var(--s2)}
.card-line .meta{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.card-line .rate{margin-top:0;padding-top:0;flex:0 0 auto}
/* The remark: small, at most two lines, and darker than the art no above it
   rather than a different hue. On a grid of a dozen near-identical cardigans
   it is often the only line that tells them apart, so it should not read as
   more metadata — but a coloured line on every card is louder than the point
   deserves. Weight and depth do the work instead. 11.4:1 on white. */
.rmk{margin-top:3px;color:var(--ink-2);font-size:var(--t-2xs);font-weight:650;
  line-height:1.35;overflow:hidden;display:-webkit-box;-webkit-line-clamp:2;
  -webkit-box-orient:vertical}
.tag{align-self:flex-start;font-size:var(--t-2xs);padding:3px 9px;
  border-radius:var(--r-pill);background:var(--accent-soft);color:var(--accent);
  font-weight:700;border:1px solid var(--accent-line);white-space:nowrap}

/* Category badge sitting on the photo. Deepened so white text clears AA.
   Transparent border in the base rule so the frosted version below is exactly
   the same size — only the colours change.

   Bottom left, not top left: a garment photo carries its subject in the upper
   half, so a label up there lands on the product. Down here it sits over the
   quiet end of the frame, and it is clear of the staff buttons top right. */
.cat-badge{position:absolute;bottom:8px;left:8px;z-index:1;
  background:var(--badge-grad);color:#fff;border:1px solid transparent;
  font-size:var(--t-2xs);font-weight:800;letter-spacing:.05em;
  text-transform:uppercase;padding:4px 9px;border-radius:var(--r-xs);
  max-width:76%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;
  box-shadow:0 2px 6px rgba(120,53,15,.32), inset 0 1px 0 rgba(255,255,255,.24)}

/* Frosted glass: the photo shows through the badge instead of being covered
   by a solid slab. The blur behind it and the text shadow are what keep white
   text readable once the colour is no longer opaque — a plain alpha drop on
   its own turns the label to mush over a pale photo. Browsers without
   backdrop-filter keep the solid badge above, which is the safe fallback. */
@supports ((-webkit-backdrop-filter:blur(1px)) or (backdrop-filter:blur(1px))){
  .cat-badge{
    background:linear-gradient(140deg,
      rgba(245,158,11,.80) 0%, rgba(217,119,6,.84) 60%, rgba(180,83,9,.88) 100%);
    border-color:rgba(255,255,255,.34);
    -webkit-backdrop-filter:blur(9px) saturate(150%);
    backdrop-filter:blur(9px) saturate(150%);
    text-shadow:0 1px 2px rgba(88,38,7,.5);
    box-shadow:0 2px 8px rgba(120,53,15,.24), inset 0 1px 0 rgba(255,255,255,.3)}
}

a.card{transition:box-shadow .18s var(--ease),transform .18s var(--ease),
                  border-color .18s var(--ease)}
a.card:active{transform:scale(.975)}
@media (hover:hover){
  a.card:hover{box-shadow:var(--sh-3);transform:translateY(-3px);
    border-color:var(--accent-line)}
}

/* Pagination */
.pager{display:flex;gap:6px;justify-content:center;margin-top:var(--s6);
  flex-wrap:wrap}
.pager a,.pager span{display:inline-flex;align-items:center;justify-content:center;
  min-width:44px;min-height:44px;padding:0 14px;border-radius:var(--r-sm);
  border:1px solid var(--line);background:var(--card);font-size:var(--t-sm);
  font-weight:700;color:var(--ink-2);box-shadow:0 1px 2px rgba(13,23,41,.04)}
.pager .on{background:var(--accent-grad);color:#fff;border-color:transparent;
  box-shadow:0 2px 8px rgba(29,78,216,.30)}
.pager .off{color:var(--faint);box-shadow:none;background:transparent}
@media (hover:hover){ .pager a:hover{border-color:var(--accent);color:var(--accent)} }

/* ============================================================
   6. Product detail
   ============================================================ */
.crumb{margin:0 0 var(--s3);font-size:var(--t-sm);font-weight:650;color:var(--muted)}
.crumb a{color:var(--accent)}
.crumb a:hover{text-decoration:underline}

.detail-page{padding-bottom:96px}      /* room for the fixed action bar */
.pcard{background:var(--card);border:1px solid var(--line);border-radius:var(--r-xl);
  overflow:hidden;box-shadow:var(--sh-2)}
.pshot{background:linear-gradient(160deg,#f2f6fc,#e6ecf6);display:block}
.pshot img{width:100%;height:auto;display:block;cursor:zoom-in}
.pshot-empty{aspect-ratio:1/1;display:flex;align-items:center;justify-content:center;
  color:var(--faint);font-size:var(--t-sm);letter-spacing:.1em;
  text-transform:uppercase;font-weight:700}
.pstrip{display:flex;gap:var(--s2);padding:var(--s2) var(--s3);overflow-x:auto;
  scrollbar-width:none;border-top:1px solid var(--line)}
.pstrip::-webkit-scrollbar{display:none}
.shot{flex:0 0 62px;width:62px;height:62px;padding:0;border:1px solid var(--line);
  border-radius:var(--r-sm);overflow:hidden;background:var(--card);cursor:pointer;
  transition:border-color .15s var(--ease),box-shadow .15s var(--ease),
             transform .15s var(--ease)}
.shot:active{transform:scale(.94)}
.shot.on{border-color:var(--accent);box-shadow:0 0 0 2px var(--accent-line)}
.shot img{width:100%;height:100%;object-fit:cover;display:block}

.pbody{padding:var(--s4) var(--s4) var(--s5)}
.pbody h1{margin:0;font-size:var(--t-2xl);font-weight:800;letter-spacing:-.026em}
.p-art{color:var(--muted);font-size:var(--t-sm);margin-top:var(--s1);font-weight:600}
.p-art span{font-family:var(--font-mono);color:var(--ink);font-weight:700;
  background:var(--bg-deep);border-radius:6px;padding:1px 7px;font-size:var(--t-xs);
  letter-spacing:0}
.p-name{color:var(--muted);font-size:var(--t-base);margin-top:2px}
.p-price{font-size:var(--t-3xl);font-weight:800;color:var(--price);
  letter-spacing:-.03em;margin:var(--s3) 0 var(--s1);
  font-variant-numeric:tabular-nums;line-height:1.1}

.spec-tiles{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));
  gap:var(--s2);margin-top:var(--s4)}
.spec{background:var(--card-2);border:1px solid var(--line);
  border-radius:var(--r);padding:10px 12px;font-size:var(--t-sm);min-width:0}
.spec span{display:block;color:var(--muted);font-size:var(--t-2xs);
  text-transform:uppercase;letter-spacing:.07em;font-weight:700;margin-bottom:2px}
.spec b{font-size:var(--t-base);color:var(--ink);font-weight:700;
  overflow-wrap:anywhere;letter-spacing:-.01em}
.p-remarks{margin-top:var(--s4);padding-top:var(--s3);
  border-top:1px solid var(--line);font-size:var(--t-base);line-height:1.6;
  color:var(--ink-2)}
.p-remarks span{color:var(--muted);font-weight:700;text-transform:uppercase;
  font-size:var(--t-2xs);letter-spacing:.07em}

/* More in category — horizontal rail with snap */
.more{margin-top:var(--s5)}
.more-head{display:flex;align-items:baseline;justify-content:space-between;
  gap:var(--s3);margin:0 2px var(--s3)}
.more-head h2{margin:0;font-size:var(--t-lg);font-weight:800}
.swipe-hint{font-size:var(--t-xs);color:var(--muted);font-weight:600;
  white-space:nowrap}
.more-rail{display:flex;gap:var(--s3);overflow-x:auto;padding:2px 0 var(--s2);
  scroll-snap-type:x mandatory;scrollbar-width:none}
.more-rail::-webkit-scrollbar{display:none}
.mini-card{flex:0 0 138px;width:138px;background:var(--card);
  border:1px solid var(--line);border-radius:var(--r-lg);overflow:hidden;
  scroll-snap-align:start;box-shadow:var(--sh-1);
  transition:box-shadow .18s var(--ease),transform .18s var(--ease)}
.mini-card:active{transform:scale(.975)}
@media (hover:hover){ .mini-card:hover{box-shadow:var(--sh-2);transform:translateY(-2px)} }
.mini-card .thumb{aspect-ratio:1/1}
.mini-body{padding:8px 9px 10px}
.mini-body .art{font-size:var(--t-sm);font-weight:750;white-space:nowrap;
  overflow:hidden;text-overflow:ellipsis}
.mini-body .meta{font-size:var(--t-2xs);white-space:nowrap;overflow:hidden;
  text-overflow:ellipsis}
.mini-body .rate{font-size:var(--t-base);padding-top:var(--s1)}

/* Fixed action bar (customer) */
.action-bar{position:fixed;left:0;right:0;bottom:0;z-index:45;display:flex;
  gap:var(--s2);padding:10px var(--s3) calc(10px + env(safe-area-inset-bottom));
  background:rgba(255,255,255,.92);border-top:1px solid var(--line);
  -webkit-backdrop-filter:saturate(180%) blur(14px);
  backdrop-filter:saturate(180%) blur(14px);
  box-shadow:var(--sh-up)}
.ab-btn{flex:1;display:flex;align-items:center;justify-content:center;gap:8px;
  min-height:50px;border-radius:var(--r);font-weight:800;font-size:var(--t-md);
  letter-spacing:-.01em;
  transition:transform .15s var(--ease),filter .15s var(--ease)}
.ab-btn:active{transform:scale(.97)}
.ab-call{flex:0 0 33%;background:#e9eef6;color:var(--ink);
  border:1px solid var(--line-2)}
.ab-wa{background:var(--wa-grad);color:var(--wa-ink);
  box-shadow:0 3px 12px rgba(22,163,74,.36), inset 0 1px 0 rgba(255,255,255,.30)}
@media (hover:hover){
  .ab-call:hover{background:#dde5f1}
  .ab-wa:hover{filter:brightness(1.05)}
}

/* Older detail markup kept working (gallery / info / specs / related) */
.detail{display:grid;gap:var(--s4);grid-template-columns:minmax(0,1fr)}
.gallery .thumb.main{border:1px solid var(--line);border-radius:var(--r-lg);
  background:var(--card)}
.strip{display:flex;gap:7px;margin-top:var(--s3);overflow-x:auto;padding-bottom:var(--s1)}
.info h1{margin:var(--s2) 0 2px;font-size:var(--t-2xl)}
.info .sub{margin:0;color:var(--muted)}
.info .price{margin:var(--s3) 0;font-size:var(--t-2xl);font-weight:800;color:var(--price)}
.btn-wa{display:inline-flex;align-items:center;gap:var(--s2);
  background:var(--wa-grad);color:var(--wa-ink);text-decoration:none;
  width:100%;justify-content:center;padding:14px;font-size:var(--t-md)}
.btn-wa:hover{filter:brightness(1.05)}
.wa-hint{margin:var(--s2) 0 0;font-size:var(--t-xs);color:var(--muted)}
.specs{display:grid;grid-template-columns:auto 1fr;gap:7px var(--s4);margin:0;
  padding-top:var(--s4);border-top:1px solid var(--line);font-size:var(--t-base)}
.specs dt{color:var(--muted)}
.specs dd{margin:0;font-weight:650}
.remarks{margin-top:var(--s5);padding-top:var(--s4);border-top:1px solid var(--line)}
.remarks h2{margin:0 0 var(--s2);font-size:var(--t-xs);color:var(--muted);
  text-transform:uppercase;letter-spacing:.07em}
.remarks p{margin:0;font-size:var(--t-base)}
.related{margin-top:var(--s6)}
.related h2{font-size:var(--t-lg);margin:0 0 var(--s3)}

/* ============================================================
   7. Staff screens
   ============================================================ */
/* Tabs — scroll sideways instead of wrapping into a wall */
.tabs{display:flex;gap:2px;margin-bottom:var(--s5);
  border-bottom:1px solid var(--line);overflow-x:auto;scrollbar-width:none;
  -webkit-overflow-scrolling:touch}
.tabs::-webkit-scrollbar{display:none}
.tabs a{padding:11px 15px;font-size:var(--t-sm);font-weight:700;color:var(--muted);
  border-bottom:2.5px solid transparent;margin-bottom:-1px;white-space:nowrap;
  border-radius:var(--r-xs) var(--r-xs) 0 0;
  transition:color .15s var(--ease),background-color .15s var(--ease)}
.tabs a:hover{color:var(--ink);background:rgba(29,78,216,.05)}
.tabs a.on{color:var(--accent);border-bottom-color:var(--accent);
  background:var(--accent-soft)}

/* Panels */
.panel{background:var(--card);border:1px solid var(--line);border-radius:var(--r-lg);
  padding:var(--s4);margin-bottom:var(--s4);box-shadow:var(--sh-1)}
.panel h2{margin:0 0 var(--s1);font-size:var(--t-lg);letter-spacing:-.022em}
.panel .hint{margin:0 0 var(--s4);color:var(--muted);font-size:var(--t-sm);
  line-height:1.5}
.panel .hint code{background:var(--bg-deep);border-radius:5px;padding:1px 6px;
  font-family:var(--font-mono);font-size:var(--t-xs);color:var(--ink-2)}
.danger-zone{border-color:#f3b8b8;background:linear-gradient(#fffafa,#fff)}

/* Toggle chips */
.chips{display:flex;flex-wrap:wrap;gap:var(--s2)}
.chip{display:inline-flex;align-items:center;gap:8px;padding:10px 15px;
  cursor:pointer;border:1px solid var(--line-2);border-radius:var(--r-pill);
  font-size:var(--t-base);font-weight:700;color:var(--ink-2);background:var(--card);
  user-select:none;min-height:46px;
  transition:border-color .15s var(--ease),background-color .15s var(--ease),
             color .15s var(--ease)}
.chip input{accent-color:var(--accent);width:18px;height:18px;margin:0}
.chip.on{border-color:var(--accent);background:var(--accent-soft);color:var(--accent);
  box-shadow:0 1px 3px rgba(29,78,216,.14)}
@media (hover:hover){ .chip:hover{border-color:var(--accent)} }

/* Form fields */
.fields{display:grid;gap:var(--s4);grid-template-columns:1fr}
.fields label,.cell{display:flex;flex-direction:column;gap:6px;
  font-size:var(--t-sm);color:var(--muted);font-weight:700;
  letter-spacing:.005em}
.fields input,.fields select,.fields textarea,.cell input,.stack input,
.add-master input,.rename-form input,.m-rename input[type=text],.m-order,
.mini-select,.mini-input{
  border:1px solid var(--line-2);border-radius:var(--r);background:var(--card);
  font-family:inherit;color:var(--ink);font-weight:500;
  box-shadow:0 1px 2px rgba(13,23,41,.04);
  transition:border-color .15s var(--ease),box-shadow .15s var(--ease)}
.fields input,.fields select,.fields textarea,.cell input,.stack input{
  padding:12px 13px;width:100%;font-size:var(--t-md);min-height:48px}
.fields textarea{resize:vertical;line-height:1.55;min-height:96px}
.fields input:focus,.fields select:focus,.fields textarea:focus,.cell input:focus,
.stack input:focus,.filters input:focus,.filters select:focus,
.add-master input:focus,.rename-form input:focus,.m-rename input:focus,
.m-order:focus,.mini-select:focus,.mini-input:focus{
  outline:none;border-color:var(--accent);box-shadow:var(--ring)}
.fields input::placeholder,.stack input::placeholder,
.add-master input::placeholder,.filters input::placeholder{color:var(--faint)}
.fields .hidden{display:none}
.fields .wide{grid-column:1 / -1}
.fields .self-end{justify-content:flex-end}
.field-hint{font-weight:500;font-size:var(--t-xs);color:var(--muted);line-height:1.45}

/* Select + "New" button pairs on the upload form */
.pick:not(input){display:flex;gap:6px;align-items:stretch}
.pick:not(input) select{flex:1;min-width:0}
.new-btn{flex:0 0 auto;padding:0 14px;min-height:48px;
  border:1.5px dashed var(--accent);border-radius:var(--r);
  background:var(--accent-soft);color:var(--accent);
  font-family:inherit;font-size:var(--t-sm);font-weight:800;cursor:pointer;
  white-space:nowrap;transition:background-color .15s var(--ease),color .15s var(--ease)}
.new-btn.on{background:var(--accent-grad);color:#fff;border-style:solid;
  border-color:transparent;box-shadow:0 2px 8px rgba(29,78,216,.28)}
.new-input{margin-top:6px}

/* One row per image: small image left, changing columns right */
.rows{display:flex;flex-direction:column;gap:var(--s3);margin-top:var(--s4)}
.row{display:flex;align-items:center;gap:var(--s3);padding:var(--s3);
  border:1px solid var(--line);border-radius:var(--r-lg);background:var(--card-2)}
.shot-sm{width:60px;height:60px;flex:0 0 60px;object-fit:cover;
  border-radius:var(--r-sm);background:var(--bg-deep);display:block;
  border:1px solid var(--line)}
.row-main{flex:1;min-width:0}
.cells{display:grid;gap:var(--s2);grid-template-columns:1fr}
.row-label{font-size:var(--t-2xs);text-transform:uppercase;letter-spacing:.07em;
  font-weight:800;color:var(--muted)}
.cell input{padding:11px 12px;font-size:var(--t-md);min-height:46px}
.row .file{margin-top:6px;font-size:var(--t-xs);color:var(--muted);
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.row .x{flex:0 0 auto;width:40px;height:40px;border:1px solid var(--line-2);
  border-radius:var(--r-sm);background:var(--card);color:var(--muted);
  font-size:19px;line-height:1;cursor:pointer;
  transition:border-color .15s var(--ease),color .15s var(--ease),
             background-color .15s var(--ease)}
.row .x:hover{border-color:#fca5a5;color:#b91c1c;background:#fef2f2}
#picker{font-family:inherit;font-size:var(--t-base);width:100%;padding:12px 0}
.actions{display:flex;flex-wrap:wrap;gap:var(--s3);align-items:center;
  margin:var(--s5) 0 var(--s2)}
.actions .btn{flex:1 1 auto}

/* Manage table */
.table-wrap{overflow-x:auto;background:var(--card);border:1px solid var(--line);
  border-radius:var(--r-lg);box-shadow:var(--sh-1)}
.tbl{width:100%;border-collapse:collapse;font-size:var(--t-sm);min-width:640px}
.tbl th{text-align:left;padding:12px 13px;font-size:var(--t-2xs);
  text-transform:uppercase;letter-spacing:.07em;font-weight:800;color:var(--muted);
  background:var(--card-2);border-bottom:1px solid var(--line-2);white-space:nowrap}
.tbl td{padding:12px 13px;border-bottom:1px solid var(--line);vertical-align:middle}
.tbl tbody tr:nth-child(even) td{background:rgba(244,247,251,.55)}
.tbl tbody tr:last-child td{border-bottom:0}
@media (hover:hover){ .tbl tbody tr:hover td{background:var(--accent-soft)} }
.tbl .num{text-align:right;font-variant-numeric:tabular-nums;font-weight:700}
.tbl .strong{font-weight:750;color:var(--ink);white-space:nowrap}
.tbl .strong:hover{color:var(--accent);text-decoration:underline}
.tbl tr.dim td:not(.row-acts){opacity:.5}
.ph-sm{display:flex;align-items:center;justify-content:center;color:var(--faint);
  background:linear-gradient(150deg,#f2f6fc,#e6ecf6);border-radius:var(--r-sm);
  border:1px solid var(--line)}
.row-acts{white-space:nowrap;text-align:right}
.row-acts form{display:inline-block;margin:0}
.row-acts>*+*{margin-left:6px}
.pick-col{width:38px}
.pick-col input{width:20px;height:20px}
.tbl input[type=checkbox]{accent-color:var(--accent)}

/* Compact "⋮" row menu */
.more-menu{position:relative;display:inline-block}
.dots{width:40px;height:40px;border:1px solid var(--line-2);border-radius:var(--r-sm);
  background:var(--card);color:var(--ink-2);font-size:20px;line-height:1;cursor:pointer}
.dots:hover{border-color:var(--accent);color:var(--accent)}
.menu-pop{position:absolute;right:0;top:44px;z-index:20;min-width:168px;
  background:var(--card);border:1px solid var(--line);border-radius:var(--r);
  overflow:hidden;box-shadow:var(--sh-4)}
.menu-pop a,.menu-pop button{display:flex;align-items:center;gap:10px;width:100%;
  text-align:left;padding:13px 15px;border:0;border-bottom:1px solid var(--line);
  background:none;font-family:inherit;font-size:var(--t-base);font-weight:650;
  cursor:pointer;color:var(--ink-2);min-height:48px}
.menu-pop a:last-child,.menu-pop button:last-child{border-bottom:0}
.menu-pop a:hover,.menu-pop button:hover{background:var(--bg)}
.menu-pop .i-edit{color:var(--info)}
.menu-pop .i-view{color:var(--alt)}
.menu-pop .i-hide{color:var(--warn)}
.menu-pop .i-show{color:var(--ok)}
.menu-pop .i-del{color:var(--bad)}

.dupe-note{margin:calc(var(--s2) * -1) 0 var(--s4);font-size:var(--t-sm);
  color:var(--muted)}
.dupe-note a{color:var(--accent);font-weight:700;text-decoration:underline}
.warn{font-size:var(--t-xs);font-weight:700;color:var(--warn)}
.warn a{text-decoration:underline}

/* Masters: brands / categories / sizes as cards */
.add-master{display:flex;flex-direction:column;align-items:stretch;gap:var(--s2);
  margin-bottom:var(--s4)}
.add-master input{flex:0 0 auto;width:100%;padding:12px 13px;
  font-size:var(--t-md);min-height:48px}
.add-master .btn{width:100%}
.m-cards{display:grid;gap:var(--s3);grid-template-columns:1fr}
.m-card{border:1px solid var(--line);border-radius:var(--r-lg);
  padding:var(--s3) var(--s3) var(--s3);background:var(--card-2);
  box-shadow:0 1px 2px rgba(13,23,41,.04)}
.m-card.off{opacity:.62}
.m-top{display:flex;align-items:center;justify-content:space-between;gap:var(--s2)}
.m-name{font-weight:800;font-size:var(--t-md);min-width:0;overflow-wrap:anywhere;
  letter-spacing:-.02em;color:var(--ink)}
.m-uses{color:var(--muted);font-size:var(--t-xs);margin-top:1px;font-weight:600}
.m-acts{margin-top:var(--s3);display:flex;flex-direction:column;gap:var(--s2)}
.m-rename{display:flex;gap:6px}
.m-rename input[type=text]{flex:1;min-width:0;padding:11px 12px;
  font-size:var(--t-md);min-height:46px}
.m-order{width:70px;padding:11px 8px;font-size:var(--t-md);min-height:46px;
  text-align:center}
.m-buttons{display:flex;flex-wrap:wrap;gap:6px;align-items:center}
.m-buttons form{display:flex;gap:5px;align-items:center;margin:0}
.m-merge .mini-select{max-width:118px}
.rename-form,.merge-form{display:flex;gap:6px;align-items:center}
.rename-form input{flex:1;min-width:120px;padding:10px 12px;font-size:var(--t-md)}
.mini-select{padding:10px 7px;font-size:var(--t-base);max-width:118px;
  min-height:44px;font-weight:600}
.mini-input{width:140px;padding:10px 11px;font-size:var(--t-base);min-height:44px}

/* Edit page images */
.img-grid{display:grid;gap:var(--s2);grid-template-columns:repeat(3,minmax(0,1fr));
  margin-bottom:var(--s4)}
.img-tile{position:relative;border:1px solid var(--line);border-radius:var(--r);
  overflow:hidden;background:var(--card);box-shadow:var(--sh-1)}
.img-tile img{width:100%;aspect-ratio:1/1;object-fit:cover;display:block}
.cover-badge{position:absolute;top:8px;left:8px;background:var(--accent-grad);
  color:#fff;font-size:var(--t-2xs);font-weight:800;padding:3px 9px;
  border-radius:var(--r-pill);box-shadow:0 2px 6px rgba(29,78,216,.35);
  letter-spacing:.03em}
.img-actions{display:flex;gap:5px;padding:7px;justify-content:center;flex-wrap:wrap}
.img-actions form{margin:0}
.img-tile[draggable]{cursor:grab}
.img-tile.dragging{opacity:.4;cursor:grabbing}
.img-tile.over{outline:2px dashed var(--accent);outline-offset:-2px}
.add-images{display:flex;flex-direction:column;align-items:stretch;gap:var(--s3);
  padding-top:var(--s4);border-top:1px solid var(--line)}
.add-images input{flex:0 0 auto;width:100%}
.add-images .btn{width:100%}

/* Auth */
.who{font-size:var(--t-sm);font-weight:700;color:var(--ink)}
.stack{display:flex;flex-direction:column;gap:var(--s3)}
.stack label{display:flex;flex-direction:column;gap:6px;font-size:var(--t-sm);
  color:var(--muted);font-weight:700}
.stack .actions{margin:var(--s2) 0 0}
.reset-form{display:inline-flex;gap:5px;align-items:center}

/* Settings page */
.set-preview{display:flex;align-items:center;gap:var(--s3)}
.set-preview .brand-mark{width:48px;height:48px;flex-basis:48px;font-size:24px;
  border-radius:14px}

/* Sticky staff bars */
.bulk-bar,.submit-bar{position:fixed;left:0;right:0;bottom:0;z-index:45;
  display:flex;align-items:center;gap:var(--s2);
  padding:10px var(--s3) calc(10px + env(safe-area-inset-bottom));
  background:rgba(255,255,255,.94);border-top:1px solid var(--line);
  -webkit-backdrop-filter:saturate(180%) blur(14px);
  backdrop-filter:saturate(180%) blur(14px);
  box-shadow:var(--sh-up)}
.bb-count,.sb-count{flex:1;min-width:0;font-size:var(--t-sm);font-weight:700;
  color:var(--muted)}
.submit-bar .btn{flex:0 0 auto}
#bulk-form,form#bulk-form,#manage-form,form#manage-form{padding-bottom:84px}

/* ============================================================
   8. Buttons, action colours, tags
   ============================================================ */
input,select,textarea,button{font-family:inherit}
/* iOS zooms the page when a focused input is under 16px. */
input[type=text],input[type=search],input[type=number],input[type=password],
input[type=email],input[type=tel],select,textarea{font-size:16px}

.btn{display:inline-flex;align-items:center;justify-content:center;gap:7px;
  padding:13px 20px;border:0;border-radius:var(--r);background:var(--accent-grad);
  color:#fff;font-family:inherit;font-size:var(--t-base);font-weight:750;
  letter-spacing:-.008em;cursor:pointer;min-height:48px;
  box-shadow:0 2px 8px rgba(29,78,216,.28), inset 0 1px 0 rgba(255,255,255,.18);
  transition:transform .15s var(--ease),box-shadow .15s var(--ease),
             filter .15s var(--ease)}
.btn:active{transform:translateY(1px) scale(.985);
  box-shadow:0 1px 4px rgba(29,78,216,.30)}
@media (hover:hover){ .btn:hover{filter:brightness(1.07);
  box-shadow:0 4px 14px rgba(29,78,216,.34)} }
.btn:disabled{opacity:.45;cursor:not-allowed;box-shadow:none;filter:none}
.btn svg{display:block}
.btn-ghost{background:var(--card);color:var(--ink-2);border:1px solid var(--line-2);
  box-shadow:0 1px 2px rgba(13,23,41,.05)}
.btn-ghost:hover{filter:none;background:var(--bg);border-color:var(--accent);
  color:var(--accent);box-shadow:0 1px 3px rgba(13,23,41,.08)}
.btn-add{padding:10px 15px;min-height:44px;font-size:var(--t-sm);white-space:nowrap}
.page-head .btn-add{margin-left:auto}

/* Link-styled submit button (manage.php) */
.as-link{border:0;background:none;padding:0;font-family:inherit;
  font-size:inherit;font-weight:700;color:var(--accent);cursor:pointer;
  text-decoration:underline}

/* Each action keeps the same colour everywhere:
   green = show/save, amber = hide, red = delete, blue = edit/view,
   violet = merge. */
:root{
  --ok:#047857;      --ok-bg:#ecfdf5;      --ok-line:#9ae6c4;
  --warn:#b45309;    --warn-bg:#fffbeb;    --warn-line:#f8d488;
  --bad:#b91c1c;     --bad-bg:#fef2f2;     --bad-line:#f9bcbc;
  --info:#1d4ed8;    --info-bg:#eff6ff;    --info-line:#b6d2fd;
  --alt:#6d28d9;     --alt-bg:#f5f3ff;     --alt-line:#d3c8fd;
}
.mini{display:inline-flex;align-items:center;justify-content:center;gap:6px;
  padding:9px 13px;border-radius:var(--r-sm);border:1px solid var(--line-2);
  background:var(--card);color:var(--ink-2);font-family:inherit;
  font-size:var(--t-sm);font-weight:700;cursor:pointer;min-height:44px;
  transition:background-color .15s var(--ease),color .15s var(--ease),
             border-color .15s var(--ease),transform .15s var(--ease)}
.mini:active{transform:scale(.95)}
.mini:hover{color:var(--ink);border-color:var(--line-2)}
.mini svg{display:block}
.mini.danger:hover{border-color:var(--bad-line);color:var(--bad);background:var(--bad-bg)}
.mini[disabled]{opacity:.35;cursor:not-allowed}

.mini.act-ok   {color:var(--ok);   background:var(--ok-bg);   border-color:var(--ok-line)}
.mini.act-warn {color:var(--warn); background:var(--warn-bg); border-color:var(--warn-line)}
.mini.act-bad  {color:var(--bad);  background:var(--bad-bg);  border-color:var(--bad-line)}
.mini.act-info {color:var(--info); background:var(--info-bg); border-color:var(--info-line)}
.mini.act-alt  {color:var(--alt);  background:var(--alt-bg);  border-color:var(--alt-line)}
.mini.act-ok:hover  {background:var(--ok);   color:#fff;border-color:var(--ok)}
.mini.act-warn:hover{background:var(--warn); color:#fff;border-color:var(--warn)}
.mini.act-bad:hover {background:var(--bad);  color:#fff;border-color:var(--bad)}
.mini.act-info:hover{background:var(--info); color:#fff;border-color:var(--info)}
.mini.act-alt:hover {background:var(--alt);  color:#fff;border-color:var(--alt)}

/* Icon-only: square, no label text */
.mini.ico{width:44px;height:44px;padding:0}
.mini.ico svg{width:19px;height:19px}

/* Bulk bar buttons are solid, so the destructive one is unmistakable */
.bulk-bar .mini{border:0;color:#fff;font-weight:750;padding:11px 15px;
  font-size:var(--t-sm);box-shadow:var(--sh-1)}
.bulk-bar .act-ok{background:var(--ok)}
.bulk-bar .act-warn{background:var(--warn)}
.bulk-bar .act-bad{background:var(--bad)}
.bulk-bar .act-ok:hover{background:#036a4d;color:#fff}
.bulk-bar .act-warn:hover{background:#9a4708;color:#fff}
.bulk-bar .act-bad:hover{background:#a11616;color:#fff}

.btn-danger{background:var(--bad);
  box-shadow:0 2px 8px rgba(185,28,28,.28), inset 0 1px 0 rgba(255,255,255,.18)}
.btn-danger:hover{box-shadow:0 4px 14px rgba(185,28,28,.34)}

.tag-off{background:#eef1f6;color:var(--muted);border-color:var(--line-2)}
.tag-warn{background:var(--warn-bg);color:var(--warn);border-color:var(--warn-line)}

/* ============================================================
   9. Feedback: empty, flash, notice, toast
   ============================================================ */
.empty{background:var(--card);border:2px dashed var(--line-2);
  border-radius:var(--r-xl);padding:var(--s7) var(--s5);text-align:center;
  color:var(--muted);font-size:var(--t-md);line-height:1.6}
.empty p{margin:0 0 var(--s3)}
.empty p:last-child{margin-bottom:0}
.empty a:not(.btn){color:var(--accent);font-weight:700;text-decoration:underline}

.flash{border-radius:var(--r);padding:14px 16px;margin-bottom:var(--s4);
  font-size:var(--t-base);font-weight:600;line-height:1.5;
  display:flex;flex-wrap:wrap;align-items:center;gap:var(--s3);
  box-shadow:var(--sh-1)}
.flash.ok{background:var(--ok-bg);border:1px solid var(--ok-line);
  border-left:4px solid var(--ok);color:var(--price-deep)}
.flash.bad{background:var(--bad-bg);border:1px solid var(--bad-line);
  border-left:4px solid var(--bad);color:#911616;display:block}
.flash ul{margin:var(--s2) 0 0;padding-left:20px}
.flash li{margin-bottom:3px}

.notice{background:linear-gradient(#fffdf5,#fffbeb);border:1px solid #f6dc9a;
  border-left:4px solid #d97706;border-radius:var(--r-lg);padding:var(--s5);
  margin:var(--s5) 0;box-shadow:var(--sh-1)}
.notice h2{margin:0 0 var(--s2);font-size:var(--t-lg);color:#7c3f06}
.notice p{margin:0 0 var(--s2)}
.notice p:last-child{margin-bottom:0}
.notice a{color:var(--accent);font-weight:700;text-decoration:underline}
.notice code{background:#fff;border:1px solid #f6dc9a;border-radius:6px;
  padding:1px 6px;font-family:var(--font-mono);font-size:var(--t-sm)}

/* Undo toast — replaces the "are you sure?" dialog for reversible deletes */
.toast{position:fixed;left:var(--s3);right:var(--s3);bottom:var(--s4);z-index:60;
  display:flex;align-items:center;gap:var(--s3);padding:14px 16px;
  border-radius:var(--r-lg);
  background:linear-gradient(150deg,#182545,#0d1729);
  color:#fff;font-size:var(--t-base);font-weight:650;
  box-shadow:0 12px 34px rgba(13,23,41,.40), inset 0 1px 0 rgba(255,255,255,.10);
  animation:toast-in .2s var(--ease)}
.toast span{flex:1;min-width:0}
.toast form{margin:0}
.toast-btn{border:0;background:rgba(125,211,252,.14);color:#8fd8fb;
  font-family:inherit;font-size:var(--t-sm);font-weight:800;text-transform:uppercase;
  letter-spacing:.06em;cursor:pointer;padding:9px 14px;border-radius:var(--r-xs);
  min-height:40px;transition:background-color .15s var(--ease)}
.toast-btn:hover{background:rgba(125,211,252,.26);color:#bae6fd}
.toast.gone{opacity:0;transform:translateY(10px);pointer-events:none;
  transition:opacity .22s,transform .22s}
@keyframes toast-in{from{opacity:0;transform:translateY(12px)}to{opacity:1;transform:none}}

/* Hide the bars while scrolling down, bring them back on scroll up. The phone's
   own address bar hides on the same gesture, so the screen fills with products. */
.appbar{transition:transform .24s var(--ease)}
.browse{transition:top .24s var(--ease)}
body.bars-off .appbar{transform:translateY(-100%)}
body.bars-off .browse{top:0}

/* ============================================================
   9b. Product groups
   ------------------------------------------------------------
   None of this reaches a screen until an admin switches grouping
   on in Manage, so the catalog is untouched by default.
   ============================================================ */

/* Heading between two grids — "More products", "In this group". A rule under
   it and more room above than below is what makes it read as the start of a
   new section rather than a bigger paragraph. */
.sec-head{display:flex;align-items:baseline;gap:var(--s2);
  margin:var(--s6) 0 var(--s3);padding-bottom:var(--s2);
  border-bottom:1px solid var(--line);
  font-size:var(--t-lg);color:var(--ink);letter-spacing:-.022em}
.sec-head .count{font-weight:600}

/* --- The group card in the catalog grid --- */
/* An ordinary product card with a count where the category badge would be, so
   groups and loose products sit in one grid without a seam. */
.g-count-badge{position:absolute;top:8px;right:8px;z-index:1;
  background:rgba(13,23,41,.74);color:#fff;border:1px solid rgba(255,255,255,.22);
  font-size:var(--t-2xs);font-weight:800;letter-spacing:.04em;white-space:nowrap;
  padding:4px 9px;border-radius:var(--r-xs);
  box-shadow:0 2px 6px rgba(13,23,41,.28)}
@supports ((-webkit-backdrop-filter:blur(1px)) or (backdrop-filter:blur(1px))){
  .g-count-badge{background:rgba(13,23,41,.52);
    -webkit-backdrop-filter:blur(9px) saturate(150%);
    backdrop-filter:blur(9px) saturate(150%)}
}
/* A second edge peeking out below: a group is more than one thing, and that
   reads before any label does. */
.g-cover{position:relative;margin-bottom:5px}
.g-cover::after{content:"";position:absolute;left:10px;right:10px;bottom:-5px;height:5px;
  background:var(--card-2);border:1px solid var(--line);border-top:0;
  border-radius:0 0 var(--r-lg) var(--r-lg)}

/* Inside a group: the title strip above the listing. The way back out is a
   real link — the installed app has no address bar to press Back in. */
.in-group{display:flex;flex-wrap:wrap;align-items:center;gap:var(--s2) var(--s3);
  margin-bottom:var(--s3)}
.in-group h1{margin:0;font-size:var(--t-xl);letter-spacing:-.024em}
.back-mini{flex:0 0 auto}

/* --- Manage: the switch for the whole feature --- */
/* A capped top edge, the same move `.master-sec` uses for its own colour —
   here it just marks "this panel is a switch", not a list, before you read
   a word of it. */
.group-switch,
.g-settings{border-top:3px solid var(--accent)}
.group-switch summary,
.g-settings summary{display:flex;align-items:center;gap:var(--s2);cursor:pointer;
  list-style:none;font-weight:800;font-size:var(--t-md);color:var(--ink)}
.group-switch summary::-webkit-details-marker,
.g-settings summary::-webkit-details-marker{display:none}
.group-switch[open] summary,
.g-settings[open] summary{margin-bottom:var(--s3)}
.gs-title{display:inline-flex;align-items:center;gap:8px}
.gs-acts{display:flex;flex-wrap:wrap;gap:var(--s2);margin:0}
.gs-note{margin:var(--s3) 0 0}

/* Import is two steps and the second one is a panel further down the page.
   This line sits under the Preview button and says so. */
.step-note{margin:var(--s3) 0 0;padding-top:var(--s3);border-top:1px solid var(--line)}
.panel h2 .count{font-weight:600}

/* Fetching the photos an import named: how far through it is. */
.fetch-bar{height:8px;border-radius:var(--r-pill);background:var(--bg-deep);
  overflow:hidden;margin:var(--s3) 0}
.fetch-bar span{display:block;height:100%;background:var(--accent-grad);
  border-radius:var(--r-pill);transition:width .3s var(--ease)}
.fail-list{margin:0 0 var(--s3);padding-left:18px;font-size:var(--t-sm);
  color:var(--muted);line-height:1.7}
.fail-list code{font-family:var(--font-mono);font-size:var(--t-xs);
  overflow-wrap:anywhere;color:var(--ink-2)}

/* The group control in Manage's bulk bar. On a phone it takes a line of its
   own — a dropdown, a text box and three buttons will not share one. */
.bulk-bar{flex-wrap:wrap;row-gap:8px}
.bb-group{display:flex;align-items:center;gap:6px;flex:1 1 100%;flex-wrap:wrap}
.bb-group .mini-input{flex:1 1 120px;width:auto}
/* Room to scroll past the taller bar. Browsers without :has() keep the 84px
   above, which is one row short — no worse than before this control existed. */
#manage-form:has(.bb-group){padding-bottom:150px}

/* --- Groups page: one card per group --- */
/* Same shape as the group card the catalog shows a customer (`.g-cover` +
   `.g-count-badge`, above) rather than a plain product thumb — a staff
   member sees the same "this is a stack of things" cue as everyone else. */
/* A section and the groups inside it are drawn as one block, the section first
   and its subgroups behind it — the arrangement on screen is the arrangement a
   customer will walk through. */
.g-section{margin-bottom:var(--s4)}
.g-section.has-kids{border:1px solid var(--line);border-radius:var(--r-lg);
  padding:var(--s3);background:var(--card-2)}
.g-sub{border-style:dashed}
.g-sub .thumb{aspect-ratio:16/10}

/* Breadcrumb back to the section, on a subgroup's own page. */
.g-crumb{display:inline-flex;align-items:center;gap:6px;font-size:var(--t-sm);
  font-weight:700;color:var(--muted)}
.g-crumb a{color:var(--accent);text-decoration:underline}

.g-cards{display:grid;gap:var(--s3);grid-template-columns:repeat(2,minmax(0,1fr))}
.g-card{background:var(--card);border:1px solid var(--line);border-radius:var(--r-lg);
  overflow:hidden;display:flex;flex-direction:column;box-shadow:var(--sh-1);
  transition:box-shadow .18s var(--ease),transform .18s var(--ease),
             border-color .18s var(--ease)}
.g-card:active{transform:scale(.98)}
@media (hover:hover){
  .g-card:hover{box-shadow:var(--sh-3);transform:translateY(-3px);
    border-color:var(--accent-line)}
}
.g-card.off{opacity:.62}
.g-card-link{display:flex;flex-direction:column;flex:1;min-width:0}
.g-card-body{padding:11px 12px 12px;display:flex;flex-direction:column;gap:4px;flex:1}
/* Name and its on/off state read as one line, the way a master card pairs
   them — `.m-top` already does exactly this layout, nothing group-specific
   about it. */
.g-card-body .m-top{width:100%}
.g-card-body .art{flex:1 1 auto;min-width:0;overflow:hidden;
  text-overflow:ellipsis;white-space:nowrap}
.g-card-acts{display:flex;flex-wrap:wrap;gap:6px;align-items:center;padding:0 12px 12px}

/* --- Members and the picker share one row --- */
/* A line each, not a card each: a hundred products fit on one screenful, which
   is what building a group out of a range actually needs. */
.p-list{border:1px solid var(--line);border-radius:var(--r-lg);overflow:hidden;
  background:var(--card);box-shadow:var(--sh-1)}
/* Rhythm from an alternating tint rather than a rule under every row — a
   hundred rows read as stripes to scan, not a hundred thin lines to parse. */
.p-row{display:flex;align-items:center;gap:var(--s3);padding:9px 12px;
  min-height:58px;background:var(--card)}
.p-row:nth-child(even){background:var(--card-2)}
.p-row.off{opacity:.6}
/* The cover product gets the same left accent bar a selected picker row
   gets below — one visual language for "this row is the one that matters". */
.p-row.is-cover{background:var(--accent-soft);box-shadow:inset 3px 0 0 var(--accent)}

.p-thumb{position:relative;flex:0 0 42px;width:42px;height:42px;border-radius:var(--r-xs);
  overflow:hidden;background:var(--bg-deep);display:flex;align-items:center;
  justify-content:center}
.p-thumb img{width:100%;height:100%;object-fit:cover;display:block}
.p-noimg{color:var(--faint);font-weight:700;font-size:var(--t-sm)}
.p-cover{position:absolute;right:0;bottom:0;background:var(--accent);color:#fff;
  line-height:0;padding:2px;border-radius:var(--r-xs) 0 0 0}

.p-main{flex:1 1 auto;min-width:0;display:flex;flex-direction:column;gap:1px}
.p-art{font-weight:750;font-size:var(--t-base);color:var(--ink);letter-spacing:-.012em;
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.p-meta{color:var(--muted);font-size:var(--t-xs);font-weight:550;
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.p-tags{flex:0 0 auto;display:flex;gap:5px;align-items:center}
.p-rate{flex:0 0 auto;font-weight:800;color:var(--price);font-size:var(--t-base);
  font-variant-numeric:tabular-nums;letter-spacing:-.02em}
.p-acts{flex:0 0 auto;display:flex;gap:4px;align-items:center}
.p-acts form{margin:0}

/* The whole picker row is the checkbox's label, so a thumb anywhere on the line
   ticks it — a hundred products go in without aiming at a hundred small boxes.
   Shift-click takes a run. */
.p-pick{cursor:pointer;-webkit-user-select:none;user-select:none}
.p-pick > input{flex:0 0 auto;width:20px;height:20px;margin:0;
  accent-color:var(--accent);cursor:pointer}
/* Same inset bar as a group's cover row — ticked is ticked, at a glance,
   without hunting for the checkbox itself. */
.p-pick.on{background:var(--accent-soft);box-shadow:inset 3px 0 0 var(--accent)}
@media (hover:hover){ .p-pick:hover{background:var(--bg)} .p-pick.on:hover{background:var(--accent-soft)} }

.pick-head{display:flex;flex-wrap:wrap;align-items:center;gap:var(--s2) var(--s3);
  margin-bottom:var(--s3)}
.pick-head .count{flex:1 1 auto;min-width:0}
.pick-add{flex:0 0 auto}
/* Same disabled treatment as every other button in the app — the grayscale
   filter this used to add on top just muddied the accent gradient further. */
.pick-add[disabled]{opacity:.45;cursor:not-allowed;box-shadow:none;filter:none}

/* On a phone the row keeps the four things you pick by — photo, art no, what
   it is, and the price — on one line. Status tags and the row's own action
   buttons move to a second line rather than squeezing everything down until
   nothing is a comfortable tap target.

   The break between the two lines is a generated empty item (`::after`) with
   `flex-basis:100%`, placed by `order` right after the price. An item that
   claims the full row width always wraps whatever comes next, so tags and
   actions land together on line two regardless of how long the art no or
   price text runs — no fixed pixel width to get wrong on a narrow phone. */
@media (max-width:519px){
  .p-row{gap:var(--s2);padding:9px;flex-wrap:wrap}
  .p-thumb{order:1}
  .p-main{order:2}
  .p-rate{order:3}
  /* A member row carries three buttons as well, which will not share a line
     with the name and the price at this width without dropping under the 44px
     a thumb needs. Give them a line of their own.
     Picker rows have no buttons, so they must NOT wrap — a hundred of them is
     the whole point of the list, and a wasted second line each doubles the
     scrolling. :not() rather than :has() so old phones get this right too. */
  .p-row:not(.p-pick)::after{content:"";flex-basis:100%;height:0;order:4}
  .p-tags{order:5;padding-left:54px}
  .p-acts{order:6;margin-left:auto}
  /* On a picker row the tags stay on the line and give way to the art no,
     which is what you are reading. */
  .p-pick .p-tags{order:4;padding-left:0;flex:0 1 auto;min-width:0;overflow:hidden}
}

/* Rename row on a group's page — the same shape as a master's rename. */
.g-rename{display:flex;gap:6px;margin-bottom:var(--s3)}
.g-rename input{flex:1;min-width:0;padding:11px 12px;font-size:var(--t-md);
  min-height:46px}

/* Renaming a group, out in the open rather than behind a fold — it is one of
   the two things this page exists to change. */
.g-name-edit{display:flex;flex-wrap:wrap;gap:var(--s2);align-items:center}
.g-name-edit label{flex:0 0 auto;font-size:var(--t-sm);font-weight:700;color:var(--muted)}
.g-name-edit input{flex:1 1 200px;min-width:0;padding:11px 12px;font-size:var(--t-md);
  min-height:46px}
.g-name-edit .btn{flex:0 0 auto}

/* Choosing the cover: every photo in the group, one tap each. "Automatic"
   is a tile of its own so that letting the group choose is a choice you make,
   not the absence of one. */
.cover-hint{margin:var(--s4) 0 var(--s2)}
.cover-pick{display:flex;gap:var(--s2);overflow-x:auto;padding-bottom:var(--s2);
  scrollbar-width:thin;-webkit-overflow-scrolling:touch}
.cp-tile{margin:0;flex:0 0 auto}
.cp-tile button{display:flex;flex-direction:column;align-items:center;gap:5px;
  width:88px;padding:6px;cursor:pointer;position:relative;
  background:var(--card);border:2px solid var(--line);border-radius:var(--r);
  color:var(--muted);font-size:var(--t-2xs);font-weight:700;
  transition:border-color .15s var(--ease),background-color .15s var(--ease)}
.cp-shot{width:100%;aspect-ratio:1/1;border-radius:var(--r-xs);overflow:hidden;
  background:var(--bg-deep);display:flex;align-items:center;justify-content:center}
.cp-shot img{width:100%;height:100%;object-fit:cover;display:block}
.cp-blank{color:var(--faint)}
.cp-art{max-width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.cp-mark{position:absolute;top:4px;right:4px;background:var(--accent);color:#fff;
  border-radius:50%;width:20px;height:20px;display:flex;align-items:center;
  justify-content:center;box-shadow:0 1px 3px rgba(13,23,41,.3)}
/* The chosen tile keeps its tint on hover — a later plain :hover would
   otherwise win on equal specificity (see the trap noted in MEMORY.md). */
.cp-tile.on button{border-color:var(--accent);background:var(--accent-soft);
  color:var(--accent)}
@media (hover:hover){
  .cp-tile:not(.on) button:hover{border-color:var(--accent-line);background:var(--bg)}
}

.tag-alt{background:var(--alt-bg);color:var(--alt);border-color:var(--alt-line)}

/* On a phone the hamburger menu already lists every staff page, so the tab
   strip is pure noise; bring it back on wider screens. */
@media (max-width:639px){ .tabs{display:none} }

/* ============================================================
   10. Tablet and desktop
   ============================================================ */
@media (min-width:640px){
  .wrap{padding:0 var(--s4)}
  main{padding-top:var(--s5)}
  .page-head h1{font-size:var(--t-2xl)}

  .browse{margin:0 calc(var(--s4) * -1) var(--s4);padding:10px var(--s4) var(--s2)}
  .sheet{left:var(--s4);right:auto;min-width:300px;max-width:420px}

  .grid{grid-template-columns:repeat(auto-fill,minmax(200px,1fr));gap:var(--s4)}

  .pbody{padding:var(--s5)}
  .pbody h1{font-size:var(--t-3xl)}
  .spec-tiles{grid-template-columns:repeat(4,minmax(0,1fr))}
  .shot{flex-basis:74px;width:74px;height:74px}
  .detail{grid-template-columns:minmax(0,1.1fr) minmax(0,1fr);gap:var(--s6)}

  .action-bar,.submit-bar,.bulk-bar{justify-content:flex-end}
  .ab-btn{flex:0 0 auto;padding:0 26px}
  .ab-call{flex:0 0 auto}
  .btn-wa{width:auto}

  .panel{padding:var(--s5)}
  .fields{grid-template-columns:repeat(auto-fit,minmax(220px,1fr))}
  .cells{grid-template-columns:repeat(auto-fit,minmax(160px,1fr))}
  .img-grid{grid-template-columns:repeat(auto-fill,minmax(150px,1fr));gap:var(--s3)}
  .shot-sm{width:68px;height:68px;flex-basis:68px}
  .add-images,.add-master{flex-direction:row;align-items:center}
  .add-images .btn,.add-master .btn{width:auto}
  .add-master input{flex:1 1 240px;width:auto}
  .actions .btn{flex:0 0 auto}
  .m-cards{grid-template-columns:repeat(auto-fill,minmax(330px,1fr))}
  .g-cards{grid-template-columns:repeat(auto-fill,minmax(250px,1fr))}
  .bb-group{flex:0 1 auto}
  .bb-group .mini-input{flex:0 0 auto;width:170px}
  .mini{min-height:38px;padding:7px 12px}
  .mini.ico{width:38px;height:38px}
  .mini-select,.m-merge .mini-select{max-width:170px;min-height:38px;padding:8px 10px}
  .mini-input{min-height:38px;padding:8px 11px}
  .tbl{min-width:720px;font-size:var(--t-sm)}
  .toast{left:auto;right:24px;min-width:340px;max-width:470px}
}
@media (min-width:900px){
  .browse{position:static;background:none;border-bottom:0;margin:0 0 var(--s4);
    padding:0;-webkit-backdrop-filter:none;backdrop-filter:none}
  .sheet{position:absolute}
  .grid{gap:var(--s4)}
  .more-rail{scroll-snap-type:none}
}
@media (min-width:1024px){
  .grid{grid-template-columns:repeat(auto-fill,minmax(215px,1fr))}
}

/* Footer line doubles as the hidden staff entrance (five taps) — it must still
   look like an ordinary copyright, so only the cursor and tap behaviour change. */
#foot-mark span{cursor:default;-webkit-user-select:none;user-select:none}

/* Required vs optional fields.
   Red asterisk = must be filled in; grey "optional" chip = can be left blank. */
.req{color:var(--bad);font-weight:800;font-size:1.05em;line-height:1}
.opt{color:var(--muted);background:var(--bg-deep);border-radius:var(--r-pill);
  padding:1px 7px;font-size:var(--t-2xs);font-weight:700;text-transform:uppercase;
  letter-spacing:.05em;vertical-align:1px}
.legend{margin:-2px 0 var(--s3)}
.legend .req{margin-right:2px}
.row-label .req,.row-label .opt{margin-left:3px}
.row-label .opt{padding:0 5px;font-size:9.5px}

/* Field labels flow as text, so the caption and its required/optional marker
   stay on one line — as a flex column each was becoming its own row. */
.fields label{display:block}
.fields label > input,
.fields label > select,
.fields label > textarea,
.fields label > .new-input{display:block;width:100%;margin-top:5px}
.fields label > .pick{display:flex;width:100%;margin-top:5px}
/* Must come last: a display rule above would otherwise beat the hidden
   attribute and reveal the "+ New" boxes that should stay closed. */
.fields label > [hidden]{display:none}
.fields label > .field-hint{display:block;margin-top:4px}
.fields .self-end{display:flex;align-items:flex-end}
.fields .self-end > .btn{width:100%}

/* ============================================================
   Brands / Categories / Sizes / Remarks each get their own colour,
   so the sections stay apart at a glance. One --sec variable per
   section drives the bar, heading, cards and Add button.
   ============================================================ */
.master-brand   {--sec:#1d4ed8; --sec-soft:#eef4ff; --sec-line:#c7dbfe}  /* blue   */
.master-category{--sec:#7c3aed; --sec-soft:#f5f3ff; --sec-line:#ddd6fe}  /* violet */
.master-size    {--sec:#0f766e; --sec-soft:#ecfdf5; --sec-line:#a7f3d0}  /* teal   */
.master-remark  {--sec:#b45309; --sec-soft:#fffbeb; --sec-line:#fde68a}  /* amber  */

.master-sec{border-top:3px solid var(--sec);position:relative}
.master-sec > h2{color:var(--sec)}
.master-sec > h2 .count{color:var(--sec);opacity:.75}
.master-sec .m-card{background:var(--sec-soft);border-color:var(--sec-line);
  border-left:3px solid var(--sec)}
.master-sec .m-card.off{background:var(--bg);border-left-color:var(--line-2)}
.master-sec .add-master .btn{background:var(--sec)}
.master-sec .m-name{color:var(--sec)}
.master-sec .m-card.off .m-name{color:var(--muted)}

/* Sizes carry an extra "order" box, and on a 375px screen the name + order +
   save row pushed the whole page sideways. Let the name shrink and keep the
   order box narrow; the card can never be wider than its column. */
.m-card{max-width:100%;overflow-x:hidden}
.m-rename{flex-wrap:nowrap;min-width:0}
.m-rename input[type=text]{flex:1 1 auto;min-width:0}
.m-order{flex:0 0 56px;width:56px;padding-left:6px;padding-right:6px;text-align:center}

/* ============================================================
   Collapsible master sections. Closed by default, so the page opens
   as four headings instead of a long wall of cards.
   ============================================================ */
details.master-sec{padding:0;overflow:hidden}
details.master-sec > summary{
  list-style:none;cursor:pointer;user-select:none;-webkit-user-select:none;
  display:flex;align-items:center;gap:var(--s2);
  padding:var(--s3) var(--s4);min-height:52px;
  font-weight:800;font-size:var(--t-lg);letter-spacing:-.02em;color:var(--sec);
  background:var(--sec-soft);
}
details.master-sec > summary::-webkit-details-marker{display:none}
details.master-sec > summary:hover{filter:brightness(.985)}
details.master-sec > summary .sec-title{flex:1;min-width:0}
details.master-sec > summary .count{
  background:var(--sec);color:#fff;border-radius:var(--r-pill);
  padding:1px 9px;font-size:var(--t-xs);font-weight:800;opacity:1}
details.master-sec > summary .chev{flex:0 0 auto;transition:transform .18s var(--ease)}
details.master-sec[open] > summary .chev{transform:rotate(180deg)}
details.master-sec[open] > summary{border-bottom:1px solid var(--sec-line)}

/* Everything after the summary is the body, so it needs the padding the
   panel used to provide. */
details.master-sec > *:not(summary){margin-left:var(--s4);margin-right:var(--s4)}
details.master-sec > .add-master{margin-top:var(--s4)}
details.master-sec > .m-cards{margin-bottom:var(--s4)}
details.master-sec > .empty{margin-bottom:var(--s4)}

/* ============================================================
   Import row. It used to reuse the "chip" style, which made the
   checkbox a huge tick and wrapped the label over three lines.
   ============================================================ */
.import-form{display:grid;gap:var(--s2);align-items:center}
.file-input{width:100%;padding:11px 12px;font-size:var(--t-sm);
  border:1px dashed var(--line-2);border-radius:var(--r-sm);background:var(--card);
  color:var(--ink-2)}
.file-input::file-selector-button{
  margin-right:10px;padding:7px 12px;border:0;border-radius:var(--r-xs);
  background:var(--accent-soft);color:var(--accent);font:inherit;font-size:var(--t-sm);
  font-weight:700;cursor:pointer}
.file-input::file-selector-button:hover{background:var(--accent-line)}
.check-line{display:flex;align-items:center;gap:9px;min-height:34px;
  font-size:var(--t-sm);font-weight:600;color:var(--ink-2);cursor:pointer}
.check-line input{flex:0 0 18px;width:18px;height:18px;margin:0;accent-color:var(--accent)}
.check-line span{line-height:1.3}
.import-form .btn{justify-self:start}

@media (min-width:720px){
  /* file | checkbox | button, all on one line */
  .import-form{grid-template-columns:minmax(0,1fr) auto auto;gap:var(--s3)}
}

/* "No results, here's the closest" banner */
.no-match{background:var(--warn-bg);border:1px solid var(--warn-line);
  border-left:4px solid var(--warn);border-radius:var(--r);
  padding:var(--s3) var(--s4);margin-bottom:var(--s3);font-size:var(--t-sm)}
.no-match strong{display:block;color:var(--warn);font-size:var(--t-base)}
.no-match > span{color:var(--ink-2)}
.did-you-mean{display:flex;flex-wrap:wrap;align-items:center;gap:6px;margin-top:var(--s2)}
.dym-label{font-size:var(--t-xs);font-weight:700;color:var(--muted);
  text-transform:uppercase;letter-spacing:.05em}
.dym-chip{display:inline-flex;align-items:center;min-height:32px;padding:4px 12px;
  border-radius:var(--r-pill);background:var(--card);border:1px solid var(--warn-line);
  color:var(--warn);font-size:var(--t-sm);font-weight:700}
.dym-chip:hover{background:var(--warn);color:#fff;border-color:var(--warn)}

/* ============================================================
   Staff shortcuts on a product card. The card is an <article> so the
   buttons can sit beside the link rather than inside it — a button
   nested in an <a> is invalid and every tap would open the product.
   ============================================================ */
.card{position:relative}
.card-link{display:flex;flex-direction:column;height:100%;color:inherit}
.card-actions{position:absolute;top:7px;right:7px;z-index:3;display:flex;gap:6px}
.card-actions .ca-form{margin:0;display:block}
.ca-btn{width:34px;height:34px;border:0;border-radius:50%;cursor:pointer;
  display:flex;align-items:center;justify-content:center;
  background:rgba(255,255,255,.94);color:var(--ink-2);
  -webkit-backdrop-filter:blur(6px);backdrop-filter:blur(6px);
  box-shadow:0 2px 6px rgba(13,23,41,.22);
  transition:transform .15s var(--ease),background-color .15s var(--ease),
             color .15s var(--ease)}
.ca-btn:active{transform:scale(.9)}
.ca-edit:hover{background:var(--info);color:#fff}
.ca-hide:hover{background:var(--warn);color:#fff}

/* Fingers are wide — give them a bit more room on a phone. */
@media (max-width:420px){
  .ca-btn{width:36px;height:36px}
}
.tag-owner{background:var(--alt-bg);color:var(--alt);font-weight:800}

/* The Images help text is one line; the server limits hide behind a toggle. */
.hint.one-line{margin:0 0 var(--s3);line-height:1.5}
.hint-toggle{border:0;background:none;padding:0 0 0 4px;font:inherit;
  font-size:var(--t-sm);font-weight:700;color:var(--accent);cursor:pointer;
  text-decoration:underline;text-underline-offset:2px}
.hint-toggle.on{color:var(--ink-2)}
.limits-line{margin:-6px 0 var(--s3);padding:var(--s2) var(--s3);
  background:var(--bg-deep);border-radius:var(--r-xs)}
.warn-inline{color:var(--warn)}

/* Cover photo: obvious badge, and a labelled button on the others. */
.cover-badge{display:inline-flex;align-items:center;gap:4px}
.cover-badge svg{display:block}
.set-cover{padding:6px 9px;font-size:var(--t-2xs);gap:4px;white-space:nowrap}
.inline-ico{display:inline-flex;vertical-align:-2px}
.inline-ico svg{display:block}

/* ============================================================
   Listing code, upload / updated stamps, and the first-run empty state
   ============================================================ */

/* The 16-character listing code. Monospace and letter-spaced so it can be
   read off a screen and typed back in without guessing at the characters. */
.lcode{font-family:var(--font-mono);font-size:var(--t-xs);letter-spacing:.06em;
  color:var(--ink-2);font-weight:600;overflow-wrap:anywhere}
.tbl .lcode{display:block;margin-top:3px;color:var(--muted);font-size:var(--t-2xs)}
.tbl .nowrap{white-space:nowrap}
.tbl .faint{color:var(--faint)}

/* Edit page: code and dates, above the fields and not editable. */
.idstrip{display:flex;flex-wrap:wrap;gap:var(--s2) var(--s5);
  background:var(--card-2);border:1px solid var(--line);border-radius:var(--r);
  padding:var(--s3) var(--s4);margin-bottom:var(--s4)}
.idbit{min-width:0}
.idbit span{display:block;color:var(--muted);font-size:var(--t-2xs);
  text-transform:uppercase;letter-spacing:.07em;font-weight:800;margin-bottom:2px}
.idbit b{font-size:var(--t-sm);color:var(--ink);font-weight:700}

/* Product page: the reference line under the remarks. */
.p-ref{margin-top:var(--s3);padding-top:var(--s3);border-top:1px solid var(--line);
  display:flex;flex-wrap:wrap;gap:var(--s1) var(--s4);
  color:var(--muted);font-size:var(--t-xs)}
.p-ref .lcode{font-size:var(--t-xs);color:var(--ink-2)}

/* Nothing in the catalog yet: staff get the two ways to fix that. */
.empty-start{display:flex;flex-direction:column;align-items:center;gap:var(--s2)}
.es-icon{width:64px;height:64px;border-radius:50%;display:flex;
  align-items:center;justify-content:center;color:var(--accent);
  background:var(--accent-soft);border:1px solid var(--accent-line);
  margin-bottom:var(--s1)}
.es-title{margin:0;font-size:var(--t-lg);font-weight:800;color:var(--ink);
  letter-spacing:-.02em}
.es-sub{margin:0;max-width:44ch;color:var(--muted);font-size:var(--t-sm);
  line-height:1.6}
.es-sub strong{color:var(--ink-2)}
.es-acts{display:flex;flex-wrap:wrap;gap:var(--s2);justify-content:center;
  margin:var(--s3) 0 0}
.es-note{margin:var(--s2) 0 0;font-size:var(--t-xs);color:var(--faint)}
.es-note a{color:var(--muted)}

/* "+ Add product" and "Bulk" travel together above the grid. */
.add-pair{display:inline-flex;gap:var(--s2);margin-left:auto}

/* ============================================================
   Drop zone for photos (bulk.php, product-add.php)
   ------------------------------------------------------------
   The file input is stretched invisibly across the box, so the whole
   area is clickable, accepts a native drop, and still tab-focuses.
   ============================================================ */
.drop{position:relative;display:flex;flex-direction:column;align-items:center;
  justify-content:center;gap:2px;text-align:center;
  padding:var(--s6) var(--s4);border:2px dashed var(--line-2);
  border-radius:var(--r-lg);background:var(--card-2);color:var(--muted);
  transition:border-color .15s var(--ease),background-color .15s var(--ease)}
.drop-ico{color:var(--accent);margin-bottom:var(--s1)}
.drop-main{font-size:var(--t-base);font-weight:750;color:var(--ink-2)}
.drop-sub{font-size:var(--t-xs);color:var(--muted)}
/* Keyed on the type as well as the old page's id: the rule used to match
   `#picker` alone, so any dropzone whose input was named anything else
   showed the browser's native "No file chosen" button straight through the
   middle of the box. */
.drop #picker,
.drop input[type=file],
.dropzone input[type=file]{position:absolute;left:0;top:0;width:100%;height:100%;
  margin:0;padding:0;border:0;opacity:0;cursor:pointer}
.drop:focus-within{border-color:var(--accent);box-shadow:var(--ring)}
@media (hover:hover){ .drop:hover{border-color:var(--accent)} }

/* Dragging files anywhere over the page lights this up. */
.drop.over{border-color:var(--accent);border-style:solid;background:var(--accent-soft)}
.drop.over .drop-main{color:var(--accent)}
.drop.over .drop-ico{transform:translateY(-2px)}

/* What a drop left out — never leave that unsaid. */
.drop-note{margin:var(--s2) 0 0;color:var(--warn);font-weight:650}

/* ============================================================
   Back button (product.php)
   ------------------------------------------------------------
   Was a small text link. It is the most-used control on the page, so it
   is a real button now: 44px tall, its own surface, thumb-sized.
   ============================================================ */
.back-row{display:flex;align-items:center;gap:var(--s2);margin:0 0 var(--s3)}
/* Highlighted, not plain: this is the way out of a product or an edit and
   the control people reach for most, so it carries the accent rather than
   disappearing into the page as another white pill. */
.btn-back{display:inline-flex;align-items:center;gap:6px;min-height:44px;
  padding:0 16px 0 12px;border-radius:var(--r-pill);
  background:var(--accent-soft);border:1.5px solid var(--accent-line);
  box-shadow:0 1px 2px rgba(29,78,216,.10);
  color:var(--accent);font-size:var(--t-sm);font-weight:800;line-height:1;
  white-space:nowrap;
  transition:border-color .15s var(--ease),background-color .15s var(--ease),
             color .15s var(--ease),transform .12s var(--ease),
             box-shadow .15s var(--ease)}
.btn-back svg{display:block;flex:0 0 auto}
.btn-back:active{transform:scale(.97)}
@media (hover:hover){
  .btn-back:hover{background:var(--accent-grad);border-color:transparent;
    color:#fff;box-shadow:0 2px 8px rgba(29,78,216,.28)}
}
/* The secondary link beside it stays quiet and sits at the far end, so the
   highlight above reads as the one obvious way back. */
.btn-back-alt{margin-left:auto;background:var(--card);color:var(--muted);
  border-color:var(--line-2);font-weight:700;box-shadow:var(--sh-1)}
@media (hover:hover){
  .btn-back-alt:hover{background:var(--accent-soft);color:var(--accent);
    border-color:var(--accent-line);box-shadow:none}
}

/* ============================================================
   Choosing the cover photo
   ------------------------------------------------------------
   It used to be a bare star squeezed between the arrows in the button
   row. It is a labelled tap target on the photo now, in the same corner
   the Cover badge occupies, so the two read as one control: whichever
   photo shows the filled badge is the cover.
   ============================================================ */
.cover-pick{position:absolute;top:8px;left:8px;margin:0;z-index:2}
.cover-dot{display:inline-flex;align-items:center;gap:5px;
  min-height:34px;padding:0 12px;border-radius:var(--r-pill);cursor:pointer;
  font-family:inherit;font-size:var(--t-2xs);font-weight:800;line-height:1;
  letter-spacing:.02em;color:#fff;
  background:rgba(13,23,41,.5);border:1px solid rgba(255,255,255,.42);
  -webkit-backdrop-filter:blur(7px);backdrop-filter:blur(7px);
  box-shadow:0 2px 6px rgba(13,23,41,.28);
  transition:background-color .15s var(--ease),border-color .15s var(--ease),
             transform .12s var(--ease)}
.cover-dot svg{display:block}
.cover-dot:active{transform:scale(.94)}
@media (hover:hover){
  .cover-dot:hover{background:var(--accent);border-color:rgba(255,255,255,.7)}
}

/* Same choice on the add page, before anything is saved: the cover is
   whichever photo is first, so this just moves one to the front. */
.cov-row{margin-top:6px}
.cov-tag{display:inline-flex;align-items:center;gap:4px;padding:4px 10px;
  border-radius:var(--r-pill);background:var(--accent-soft);color:var(--accent);
  border:1px solid var(--accent-line);
  font-size:var(--t-2xs);font-weight:800;line-height:1}
.cov-set{display:inline-flex;align-items:center;min-height:32px;padding:0 11px;
  border-radius:var(--r-pill);cursor:pointer;
  background:var(--card);border:1px solid var(--line-2);color:var(--muted);
  font-family:inherit;font-size:var(--t-2xs);font-weight:750;line-height:1;
  transition:border-color .15s var(--ease),color .15s var(--ease),
             background-color .15s var(--ease)}
@media (hover:hover){
  .cov-set:hover{border-color:var(--accent);color:var(--accent);
    background:var(--accent-soft)}
}

/* ============================================================
   Multiline remarks
   ------------------------------------------------------------
   A remark is a note, not a label: washing instructions and packing
   notes run to several lines. Every box that takes one is a textarea,
   so they all need the same borders, focus ring and sizing the single
   line inputs beside them already have.
   ============================================================ */
.cell textarea,.new-input,.add-master textarea,.m-rename textarea{
  border:1px solid var(--line-2);border-radius:var(--r);background:var(--card);
  font-family:inherit;color:var(--ink);font-weight:500;
  box-shadow:0 1px 2px rgba(13,23,41,.04);
  resize:vertical;line-height:1.5;
  transition:border-color .15s var(--ease),box-shadow .15s var(--ease)}
.cell textarea:focus,.add-master textarea:focus,.m-rename textarea:focus{
  outline:none;border-color:var(--accent);box-shadow:var(--ring)}
.cell textarea::placeholder,.add-master textarea::placeholder{color:var(--faint)}

/* One row of the bulk grid: shorter than a full field, still two lines. */
.cell textarea{padding:10px 12px;width:100%;font-size:var(--t-md);min-height:62px}

/* The "+ New" reveal under a picker — same width as the select above it. */
textarea.new-input{padding:11px 13px;width:100%;font-size:var(--t-md);
  min-height:74px}

/* Masters page: adding and renaming a saved remark. */
.add-master textarea{flex:0 0 auto;width:100%;padding:12px 13px;
  font-size:var(--t-md);min-height:62px}
.m-rename textarea{flex:1 1 auto;min-width:0;padding:11px 12px;
  font-size:var(--t-md);min-height:58px}
/* The name already comes through nl2br, so no white-space rule here — the
   two together would double every line break. */

/* ============================================================
   Coming back from an edit
   ------------------------------------------------------------
   Saving closes the edit view and returns to the listing, landing on
   the product just changed: #p<id> scrolls to it, ?hl=<id> rings it.
   ============================================================ */

/* The app bar and the browse bar are both sticky, so an anchor would park
   the card underneath them. Reserve their height above the scroll target —
   measured at 112px on the catalog, so this clears them with a gap. */
.card,.tbl tr{scroll-margin-top:calc(var(--appbar-h) + 72px)}

.card.hl{border-color:var(--accent);
  box-shadow:0 0 0 3px var(--accent-line),var(--sh-2);
  animation:hl-land 2.6s var(--ease) both}
.tbl tr.hl td{background:var(--accent-soft) !important;
  box-shadow:inset 0 0 0 1px var(--accent-line);
  animation:hl-land 2.6s var(--ease) both}

/* Bright for a moment, then settles into a quiet ring that stays put —
   long enough to catch the eye, not so long that it nags. */
@keyframes hl-land{
  0%   {box-shadow:0 0 0 3px var(--accent),0 0 0 10px rgba(29,78,216,.22)}
  35%  {box-shadow:0 0 0 3px var(--accent),0 0 0 14px rgba(29,78,216,0)}
  100% {box-shadow:0 0 0 3px var(--accent-line),var(--sh-2)}
}
@media (prefers-reduced-motion:reduce){
  .card.hl,.tbl tr.hl td{animation:none}
}

/* ============================================================
   install.php — the report of what ran
   ============================================================ */
.steps{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;
  gap:var(--s2)}
.step{border:1px solid var(--line);border-radius:var(--r);background:var(--card-2);
  padding:var(--s3) var(--s3) var(--s3) var(--s4);border-left-width:4px}
.step.ok{border-left-color:var(--ok)}
.step.bad{border-left-color:var(--bad);background:var(--bad-bg)}
.step-head{display:flex;align-items:center;gap:8px;
  font-weight:800;font-size:var(--t-base);color:var(--ink)}
.step-mark{display:inline-flex;align-items:center;justify-content:center;
  width:22px;height:22px;flex:0 0 22px;border-radius:50%;color:#fff}
.step.ok .step-mark{background:var(--ok)}
.step.bad .step-mark{background:var(--bad)}
.step-mark svg{display:block}
.step-lines{margin:6px 0 0;padding-left:30px;color:var(--muted);
  font-size:var(--t-sm);line-height:1.55}
.step-lines li{margin-bottom:2px}
.step.bad .step-lines{color:#911616;white-space:pre-line}
.notice ol{margin:0 0 var(--s2);padding-left:20px}
.notice li{margin-bottom:4px}
/* A step that finished but could not do one part of its job. */
.step.warn{border-left-color:var(--warn);background:var(--warn-bg)}
.step.warn .step-mark{background:var(--warn)}
.step-lines .line-bad{color:var(--bad);font-weight:650}
.step.warn .step-lines .line-bad{color:#8a4708}

/* ============================================================
   Sign in: the button and "stay signed in" share a row
   ------------------------------------------------------------
   The tick used to sit above the button as a full-width chip. Worse,
   `.stack input` sizes every input in this form to 100% wide and 48px
   tall — which caught the checkbox and turned it into a huge blue
   square. Both are fixed here.
   ============================================================ */
.signin-row{display:flex;align-items:center;gap:var(--s3);flex-wrap:wrap;
  margin:var(--s4) 0 0}
.signin-row .btn{flex:0 0 auto}
.remember{display:inline-flex;align-items:center;gap:8px;min-height:44px;
  cursor:pointer;user-select:none;
  font-size:var(--t-sm);font-weight:700;color:var(--ink-2);line-height:1.3}
/* Beats .stack input on specificity, so the tick stays a tick. */
.remember input[type=checkbox]{flex:0 0 auto;
  width:17px;height:17px;min-height:0;padding:0;margin:0;
  accent-color:var(--accent);box-shadow:none;border-radius:4px}
@media (hover:hover){ .remember:hover{color:var(--accent)} }

/* ============================================================
   Activity log (csv.php, owner only)
   ------------------------------------------------------------
   A short feed of who did what. Only the newest ACTIVITY_KEEP entries
   exist at all, so this is never a long list.
   ============================================================ */
.act-list{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;
  gap:1px;background:var(--line);border:1px solid var(--line);
  border-radius:var(--r);overflow:hidden}
.act{display:flex;align-items:baseline;flex-wrap:wrap;gap:var(--s1) var(--s2);
  padding:10px var(--s3);background:var(--card);font-size:var(--t-sm);
  border-left:3px solid var(--line-2)}
.act-ok{border-left-color:var(--ok)}
.act-info{border-left-color:var(--info)}
.act-warn{border-left-color:var(--warn)}
.act-bad{border-left-color:var(--bad)}
.act-alt{border-left-color:var(--alt)}
.act-who{font-weight:800;color:var(--ink);white-space:nowrap}
.act-you{font-size:var(--t-2xs);font-weight:800;text-transform:uppercase;
  letter-spacing:.06em;color:var(--accent);background:var(--accent-soft);
  border-radius:var(--r-pill);padding:1px 6px;margin-left:2px}
.act-what{color:var(--ink-2);font-weight:650}
.act-detail{color:var(--muted);min-width:0;overflow-wrap:anywhere}
.act-when{margin-left:auto;color:var(--faint);font-size:var(--t-xs);
  white-space:nowrap;font-variant-numeric:tabular-nums}

/* ============================================================
   Multi-vendor rebuild — components the original catalog had no
   need for. Built on the token layer above; no raw colours here
   except where a token genuinely does not exist.
   ============================================================ */

/* ---- Admin tables -------------------------------------------
   The staff list, the master lists, the vendor list. Note this is
   NOT `.rows`, which is a flex column of image cards — these are
   real tables with real columns, and they scroll sideways inside
   their own wrapper rather than pushing the page wide. */
.table{width:100%;border-collapse:collapse;margin-top:var(--s4);
  font-size:var(--t-base);display:block;overflow-x:auto}
.table thead th{text-align:left;padding:var(--s2) var(--s3);
  font-size:var(--t-xs);text-transform:uppercase;letter-spacing:.07em;
  font-weight:800;color:var(--muted);border-bottom:1px solid var(--line-2);
  white-space:nowrap}
.table td{padding:var(--s3);border-bottom:1px solid var(--line);
  vertical-align:middle}
.table tr:last-child td{border-bottom:0}
.table td .meta{color:var(--muted);font-size:var(--t-sm)}
.table input[type=text],.table input[type=number],
.table input[type=password],.table textarea,.table select{
  min-height:42px;padding:9px 11px;font-size:var(--t-sm)}

/* A switched-off brand, hidden product or disabled account. Dimmed
   rather than removed — it is still there and still editable. */
.is-hidden{opacity:.55}
.is-hidden td:first-child{border-left:3px solid var(--line-2)}

/* ---- Pills --------------------------------------------------
   Small secondary actions that sit inside a row or on a photo,
   where a full .btn would swamp everything around it. */
.pill{display:inline-flex;align-items:center;justify-content:center;gap:5px;
  padding:7px 12px;min-height:36px;border:1px solid var(--line-2);
  border-radius:var(--r-pill);background:var(--card);color:var(--ink-2);
  font-family:inherit;font-size:var(--t-sm);font-weight:700;cursor:pointer;
  transition:background .15s var(--ease),border-color .15s var(--ease),
             transform .12s var(--ease)}
.pill:active{transform:scale(.96)}
/* Scoped with :not() so the variants below keep their own colour —
   a plain .pill:hover later in the file would otherwise beat
   .pill.danger, which has the same specificity. */
.pill:not(.on):not(.danger):hover{background:var(--bg-deep);border-color:var(--line-2)}
.pill.on{background:var(--info-bg);border-color:var(--accent);color:var(--accent)}
.pill.danger{color:var(--bad);border-color:var(--bad-line)}
.pill.danger:hover{background:var(--bad-bg)}
.pill.nudge{padding:7px 10px;font-size:var(--t-base);line-height:1}

/* Several small forms side by side in one table cell. Each button
   is its own form (they POST to different places), so they need
   laying out as if they were not. */
.row-actions{display:flex;flex-wrap:wrap;gap:var(--s2);align-items:center}
.row-actions form{display:inline}
.inline-form{display:flex;flex-wrap:wrap;gap:var(--s2);align-items:center}
.inline-form input,.inline-form textarea,.inline-form select{flex:1;min-width:140px}
.narrow-num{flex:0 0 76px;min-width:0;text-align:center}

/* ---- Editable photo strip -----------------------------------
   The reorderable tiles on the product edit page. `.shot` above is
   the read-only viewer version; this one is draggable and carries
   its own controls. */
.shot-edit{flex:0 0 auto;width:104px;display:flex;flex-direction:column;
  gap:6px;padding:6px;border:1px solid var(--line);border-radius:var(--r-lg);
  background:var(--card-2);cursor:grab}
.shot-edit:active{cursor:grabbing}
.shot-edit img{width:100%;height:88px;object-fit:cover;display:block;
  border-radius:var(--r-sm);background:var(--bg-deep);pointer-events:none}
.shot-edit.dragging{opacity:.4;border-color:var(--accent);
  box-shadow:0 0 0 2px var(--accent-line)}
.shot-actions{display:flex;flex-wrap:wrap;gap:4px;justify-content:center}
.shot-actions .pill{padding:5px 8px;min-height:30px;font-size:var(--t-xs)}

/* ---- Bulk listing -------------------------------------------- */
.dropzone{position:relative;margin-top:var(--s4);padding:var(--s6) var(--s4);
  border:2px dashed var(--line-2);border-radius:var(--r-lg);
  background:var(--bg-deep);text-align:center;transition:
  border-color .15s var(--ease),background .15s var(--ease)}
.dropzone.over{border-color:var(--accent);background:var(--info-bg)}
.dropzone p{margin:4px 0;color:var(--muted);font-size:var(--t-base)}
.dropzone strong{color:var(--ink-2)}
/* The input is a real file input stretched invisibly across the box,
   so tapping and the keyboard work exactly as they would otherwise. */
.dropzone input[type=file]{position:absolute;inset:0;width:100%;height:100%;
  opacity:0;cursor:pointer}

.bulk-row{display:flex;flex-wrap:wrap;align-items:center;gap:var(--s2);
  padding:var(--s3);margin-top:var(--s2);border:1px solid var(--line);
  border-radius:var(--r-lg);background:var(--card-2)}
.bulk-thumb{width:56px;height:56px;flex:0 0 56px;object-fit:cover;
  border-radius:var(--r-sm);border:1px solid var(--line);background:var(--bg-deep)}
.bulk-row input{flex:1 1 120px;min-width:0;min-height:44px}
.bulk-name{flex:1 1 100%;font-size:var(--t-xs);color:var(--faint);
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap}

/* ---- Odds and ends ------------------------------------------- */
/* .flash had ok and bad; grouping and import needed a third note
   that is neither a success nor a failure. */
.flash.warn{background:var(--warn-bg);border:1px solid var(--warn-line);
  border-left:4px solid var(--warn);color:#7c3d06;display:block;
  font-size:var(--t-base);font-weight:600}
.flash.warn p{margin:0 0 6px}
.flash.warn p:last-child{margin-bottom:0}

/* The two-level group list. Deliberately plain: the nesting is the
   information, and anything decorative competes with it. */
.tree{list-style:none;margin:var(--s4) 0 0;padding:0}
.tree li{padding:var(--s2) 0;border-bottom:1px solid var(--line)}
.tree li:last-child{border-bottom:0}
.tree ul{list-style:none;margin:var(--s2) 0 0;padding-left:var(--s4);
  border-left:2px solid var(--line)}
.tree a{font-weight:700;color:var(--ink-2)}
.tree .meta{margin-left:var(--s2);color:var(--muted);font-size:var(--t-sm)}

/* Tapped five times to reach the sign-in page from inside the
   installed app, which has no address bar and no Sign in link. */
.copy{margin:0;text-align:center;font-size:var(--t-xs);color:var(--faint);
  padding:var(--s5) 0;user-select:none}

/* A checkbox inside .filters needs its own rule: `.filters input`
   styles text fields (min-width, min-height, padding, border) and
   hits checkboxes too, which is what made the "include grouped"
   tick a giant empty square in the original. */
.filters input[type=checkbox]{min-width:0;min-height:0;width:18px;height:18px;
  padding:0;border-radius:4px;flex:0 0 auto}
.filters .remember{display:flex;align-items:center;gap:var(--s2);
  font-size:var(--t-sm);color:var(--muted);min-width:0}


/* The browse bar's sheets are absolutely positioned, so their container
   has to be the thing they should hang from. */
.browse .wrap{position:relative}

/* Two fields answered in one go, side by side once there is room —
   stacking them pushes Save off a phone screen for no reason. */
.field-pair{display:grid;gap:var(--s3);grid-template-columns:1fr}
@media (min-width:560px){ .field-pair{grid-template-columns:1fr 1fr} }
.field-pair label{margin:0}

/* Fields answered as one short task, laid out across rather than as a
   column of full-width boxes that makes four inputs look like four jobs. */
.field-row{display:grid;gap:var(--s3);grid-template-columns:1fr;align-items:end}
@media (min-width:720px){ .field-row{grid-template-columns:repeat(4,minmax(0,1fr))} }
.field-row label{display:flex;flex-direction:column;gap:6px;margin:0;
  font-size:var(--t-sm);color:var(--muted);font-weight:700}
.field-end{display:flex;align-items:flex-end}
.field-end .btn{width:100%}

/* Collapsible master sections: the coloured bar carries the name, a count
   and a chevron that turns when it opens. */
.m-summary{display:flex;align-items:center;gap:var(--s3);cursor:pointer;
  list-style:none;padding:2px 0}
.m-summary::-webkit-details-marker{display:none}
.m-summary h2{margin:0;flex:1;min-width:0}
.m-count{flex:0 0 auto;min-width:26px;height:26px;padding:0 8px;border-radius:var(--r-pill);
  background:var(--sec,var(--accent));color:#fff;font-size:var(--t-xs);font-weight:800;
  display:inline-flex;align-items:center;justify-content:center}
.m-chev{flex:0 0 auto;width:11px;height:11px;border-right:2.5px solid var(--sec,var(--muted));
  border-bottom:2.5px solid var(--sec,var(--muted));transform:rotate(45deg);
  margin-bottom:4px;transition:transform .18s var(--ease)}
details[open] > .m-summary .m-chev{transform:rotate(-135deg);margin-bottom:-2px}
.master-sec .add-master{flex-direction:row;align-items:stretch}
.master-sec .add-master input,.master-sec .add-master textarea{flex:1;width:auto}
.master-sec .add-master .btn{width:auto;flex:0 0 auto}
.ok-pill{color:var(--ok);border-color:var(--ok-line)}
.ok-pill:hover{background:var(--ok-bg)}

/* Manage: the listing code under the art no, and the bulk bar's buttons
   keeping the app's action colours — green show, amber hide, red delete. */
.lcode{font-family:var(--font-mono);font-size:var(--t-2xs);color:var(--faint);
  letter-spacing:.02em}
.faint{color:var(--faint)}
.btn-show{background:var(--ok);box-shadow:none}
.btn-hide{background:var(--warn);box-shadow:none}
.btn-del{background:var(--bad);box-shadow:none}

/* A heading that stands outside a panel — "Subgroups", "In this group" —
   with its count beside it. */
.sec-head{display:flex;align-items:baseline;gap:var(--s2);margin:var(--s5) 0 var(--s2);
  padding-bottom:var(--s2);border-bottom:1px solid var(--line)}
.sec-head h2{margin:0;font-size:var(--t-lg)}

/* The group's cover product, marked in its member list. */
.row.is-cover{border-left:3px solid var(--accent);background:var(--accent-soft)}
.row .rate{margin:0;padding:0;flex:0 0 auto}
.row form{margin:0}

/* The group-switch and settings panels use a summary bar like the master
   sections, so the chevron behaves the same way. */
.group-switch summary h2,.g-settings summary h2{margin:0;flex:1;min-width:0;
  font-size:var(--t-lg)}

/* Coming back from an edit: ring the product you just saved, so landing in
   the right place is visible rather than merely true. Fades on its own —
   it is confirmation, not a permanent state. */
.hl{outline:2.5px solid var(--accent);outline-offset:2px;
  box-shadow:0 0 0 6px var(--accent-soft);animation:hl-fade 2.4s var(--ease) 1.6s forwards}
tr.hl td{background:var(--accent-soft)}
@keyframes hl-fade{to{outline-color:transparent;box-shadow:none}}

/* ---- Uploading ----------------------------------------------
   Compression is the wait: a 4 MB phone photo is decoded, walked
   down a ladder of sizes and re-encoded before it is stored. That
   is seconds, not milliseconds, and an unexplained pause is what
   makes people press Save twice. */
.uploading{position:fixed;inset:0;z-index:80;display:flex;align-items:center;
  justify-content:center;padding:var(--s4);
  background:rgba(13,23,41,.55);
  -webkit-backdrop-filter:blur(3px);backdrop-filter:blur(3px);
  animation:fade-in .18s var(--ease)}
.uploading[hidden]{display:none}
@keyframes fade-in{from{opacity:0}to{opacity:1}}

.uploading-box{background:var(--card);border-radius:var(--r-xl,20px);
  padding:var(--s6) var(--s5);text-align:center;max-width:340px;width:100%;
  box-shadow:var(--sh-4)}
.uploading-main{margin:var(--s4) 0 var(--s1);font-size:var(--t-md);
  font-weight:800;color:var(--ink)}
.uploading-sub{margin:0;font-size:var(--t-sm);color:var(--muted);line-height:1.5}

.spinner{width:40px;height:40px;margin:0 auto;border-radius:50%;
  border:3px solid var(--line);border-top-color:var(--accent);
  animation:spin .8s linear infinite}
@keyframes spin{to{transform:rotate(360deg)}}

/* Shown while photos are still being chosen, so the advice arrives in
   time to act on rather than after the wait it explains. */
.size-note{margin-top:var(--s2);font-size:var(--t-xs);line-height:1.45;
  color:var(--warn);font-weight:650}
.size-note.ok{color:var(--ok)}

@media (prefers-reduced-motion:reduce){
  .spinner{animation-duration:2.4s}
  .uploading{animation:none}
}

/* ============================================================
   UI audit — additions only, nothing above is renamed.
   ============================================================ */

/* ---- Text for screen readers only --------------------------
   A table's tick and photo columns need headings that would be
   noise on screen: an unheaded column of checkboxes is the one a
   screen reader cannot explain. Clipped rather than
   display:none, which would take it out of the reading order
   too. */
.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;
  overflow:hidden;clip:rect(0 0 0 0);clip-path:inset(50%);white-space:nowrap;
  border:0}

/* ---- Sign out is a form, not a link ------------------------
   /logout answers POST and nothing else, so the old <a> was a 405
   for anyone whose JavaScript had not run. The button carries the
   look the tab strip and the staff menu already had; only the
   element underneath changed. */
.tabs .tab-form{margin:0;display:flex}
.tabs button{padding:11px 15px;border:0;background:none;cursor:pointer;
  font-family:inherit;font-size:var(--t-sm);font-weight:700;color:var(--muted);
  border-bottom:2.5px solid transparent;margin-bottom:-1px;white-space:nowrap;
  border-radius:var(--r-xs) var(--r-xs) 0 0;
  transition:color .15s var(--ease),background-color .15s var(--ease)}
.tabs button:hover{color:var(--ink);background:rgba(29,78,216,.05)}

.staff-menu .tab-form{margin:0;display:flex}
.staff-menu button{flex:1;padding:14px 4px;border:0;background:none;
  cursor:pointer;text-align:left;font-family:inherit;font-size:var(--t-md);
  font-weight:650;color:var(--ink-2);min-height:48px;
  display:flex;align-items:center;border-radius:var(--r-xs)}
.staff-menu button:active{background:var(--accent-soft)}
.staff-menu button.danger{color:#b91c1c}

/* The tab strip is display:none below 640px — which is nearly every
   visit — and nothing rendered the menu it says replaces it, so a
   signed-in shopkeeper on a phone could not reach Manage, Add
   product or even Sign out. The menu is real now; the hamburger
   that opens it stands down once the tabs are back. */
.staff-menu[hidden]{display:none}
.staff-menu a[aria-current],.staff-menu button[aria-current]{color:var(--accent)}
@media (min-width:640px){ #staff-toggle{display:none} }

/* The platform panel puts its one tab inside the app bar rather than in
   a strip below it, so the phone rule that hides the vendor tab strip was
   taking its Sign out with it — and the platform has no hamburger to put
   it back. Only the in-bar copy is exempted. */
.appbar .tabs{display:flex;margin:0;border-bottom:0}

/* ---- A row's leading line outside a table ------------------
   `.strong` was only ever defined as `.tbl .strong`, so the art no
   heading each member row of a group carries came out as plain body
   text. Same weight as the table version, without the nowrap a
   table column needs. */
.row .strong{font-weight:750;color:var(--ink);letter-spacing:-.012em}

/* ---- Room under the fixed Save bar -------------------------
   `#bulk-form` and `#manage-form` reserve this above; the product
   form never did, so the Save bar sat over the foot of the page.
   Two rules rather than one because the Delete panel is a sibling
   of the form, not a child of it — whichever of the two ends the
   page is the one that needs the clearance. */
#product-form{padding-bottom:84px}
.danger-zone{margin-bottom:84px}

/* ============================================================
   11. Motion
   ------------------------------------------------------------
   Everything above already moves a little. This section settles on
   one vocabulary for it, then fills the places where something
   changes on screen with no motion at all to say so.

   Three durations, one curve — --ease, already defined in section 1:

     --m-tap    the finger is still on the glass. Press feedback and
                nothing else. Slower than this reads as lag on a
                four-year-old Android, which is most of the phones
                this catalog is opened on.
     --m-state  something already on screen becoming something else:
                selected, open, tinted, turned.
     --m-enter  something arriving or leaving. The longest we go, and
                still short enough that nobody waits on it.

   Only transform and opacity are animated here. Nothing in this
   section animates width, height, top, left or box-shadow on a thing
   that repeats in a list — a grid of forty cards on a midrange phone
   cannot afford a layout or a paint per frame, and the shadow is the
   most expensive of those to repaint.

   Reduced motion is handled once at the bottom of the section rather
   than rule by rule, because the blanket at the top of the file
   already covers most of it. What it cannot cover is written out
   there, with the reason.
   ============================================================ */
:root{
  --m-tap:150ms;
  --m-state:180ms;
  --m-enter:220ms;
}

/* ---- Unifying what was already here -------------------------------
   These restate existing rules. They are the only rewrites in this
   section, and each one is the same kind of change that was wearing a
   different number or a different curve somewhere else. */

/* Press feedback ran at .12s in three places and .18s in three others
   while eight more used .15s. --m-tap is 150ms because that is the
   number the file already agreed on most often — unifying downward
   would have meant editing fourteen rules to save nobody 30ms. The
   non-transform halves of each transition are unchanged apart from
   landing on --m-state. */
a.card,.mini-card{transition:box-shadow var(--m-state) var(--ease),
  transform var(--m-tap) var(--ease),border-color var(--m-state) var(--ease)}
.g-card{transition:box-shadow var(--m-state) var(--ease),
  transform var(--m-tap) var(--ease),border-color var(--m-state) var(--ease)}
.btn-back{transition:border-color var(--m-state) var(--ease),
  background-color var(--m-state) var(--ease),color var(--m-state) var(--ease),
  transform var(--m-tap) var(--ease),box-shadow var(--m-state) var(--ease)}
.cover-dot{transition:background-color var(--m-state) var(--ease),
  border-color var(--m-state) var(--ease),transform var(--m-tap) var(--ease)}
/* `background` also transitions background-image, which on a gradient
   means interpolating a whole image every frame. These two only ever
   change the colour, so they should only ever be asked for the colour. */
.pill{transition:background-color var(--m-state) var(--ease),
  border-color var(--m-state) var(--ease),transform var(--m-tap) var(--ease)}
.dropzone{transition:border-color var(--m-state) var(--ease),
  background-color var(--m-state) var(--ease)}

/* The undo toast arrived on --ease and left on the browser default, so
   it was noticeably lazier going than coming. One curve both ways. */
.toast{animation:toast-in var(--m-enter) var(--ease)}
.toast.gone{transition:opacity var(--m-enter) var(--ease),
  transform var(--m-enter) var(--ease)}

/* Same keyframe, two durations. The sheet and the row menu are the same
   gesture — a small panel dropping out from under the control that
   opened it — so they run at the same speed on the same frames. */
.sheet{animation:sheet-in var(--m-state) var(--ease)}

/* Two highlight systems ended up on the same element. `.card.hl` rings
   with hl-land while the later, generic `.hl` paints an outline and runs
   hl-fade; the animations sort themselves out on specificity but the
   outline does not, so a card you had just saved came back wearing both
   a ring and a square. The card keeps the ring. */
.card.hl{outline:none}

/* ---- Keyframes ----------------------------------------------------
   `fade-in` and `sheet-in` already exist further up and are reused here
   rather than duplicated under new names. */
@keyframes fade-up{from{opacity:0;transform:translateY(8px)}to{opacity:1;transform:none}}
@keyframes bar-up{from{opacity:0;transform:translateY(100%)}to{opacity:1;transform:none}}
@keyframes pop-in{from{opacity:0;transform:scale(.96)}to{opacity:1;transform:none}}
@keyframes shimmer{from{transform:translateX(-140%)}to{transform:translateX(140%)}}

/* ---- The page arriving ---------------------------------------------
   A fade and nothing else. It must NOT get a transform: `.bulk-bar` and
   `.submit-bar` are position:fixed and they sit INSIDE <main>, and a
   transformed ancestor becomes the containing block for a fixed
   descendant — the bottom bar would come unstuck from the bottom of the
   screen for as long as the animation ran, then snap back. Opacity makes
   a stacking context but not a containing block, so it is safe.

   The rise lives on the content below instead, which is the better
   division anyway: the chrome settles, the goods move. */
main{animation:fade-in var(--m-enter) var(--ease) both}

/* ---- Lists filling in ----------------------------------------------
   Cards do not fall from the sky one after another. They fade up
   together with a hair between them — just enough that the grid reads as
   filling in rather than blinking into place.

   The stagger stops after six. Six is three rows on a phone, which is
   everything you can see; past that a delay only means the last row of a
   forty-product page arrives visibly late for nobody's benefit. */
.grid > *,.g-cards > *,.img-grid > *,.bulk-row{
  animation:fade-up var(--m-enter) var(--ease) both}
.grid > :nth-child(2){animation-delay:30ms}
.grid > :nth-child(3){animation-delay:60ms}
.grid > :nth-child(4){animation-delay:90ms}
.grid > :nth-child(5){animation-delay:120ms}
.grid > :nth-child(6){animation-delay:150ms}
/* A card you have just come back to already has something to say —
   hl-land wins on specificity and the entrance is skipped. That is the
   right way round: you did not arrive at this page, you returned to this
   product. */

/* ---- Waiting for a photo -------------------------------------------
   On a patchy connection the card lands seconds before its photo does.
   The sweep says "a picture is coming" instead of leaving a flat grey
   square that might be all there ever is.

   Two limits, both deliberate. It only runs where there is actually an
   <img> to wait for — a product with no photo shows the `.ph` label and
   must not be made to look like it is loading something. And it stops
   after six passes: an endless sweep on twenty-four thumbnails is
   twenty-four composited layers kept alive for as long as the page is
   open, which is how you flatten a cheap phone's battery while someone
   is only reading prices. Six passes is about seven seconds, longer than
   any photo this app serves takes to arrive.

   Deliberately not on `.p-thumb`: those are 42px and there are a hundred
   of them on a picker screen. A shimmer that small is noise.

   linear, not --ease — a sweep that decelerates looks like it is
   hesitating. The spinner above already uses linear for the same reason.

   Browsers without :has() get the flat gradient placeholder they had
   before, which is the correct fallback rather than a broken one. */
.thumb:has(> img)::after,
.pshot:has(img)::before{
  content:"";position:absolute;inset:0;z-index:0;pointer-events:none;
  background:linear-gradient(100deg,
    rgba(255,255,255,0) 32%,rgba(255,255,255,.5) 50%,rgba(255,255,255,0) 68%);
  animation:shimmer 1.15s linear 6}
/* The photo has to sit above the sweep, or it finishes loading and the
   sweep carries on crossing it. */
.thumb img{position:relative;z-index:1}
.pshot{position:relative;overflow:hidden}
.pshot > *{position:relative;z-index:1}

/* ---- The gallery swapping photos ------------------------------------
   Tapping a thumbnail changes the stage image's src in script. There is
   no class on the stage image to hang a cross-fade off, and this file
   does not get to touch the script — so the strip carries the swap
   instead. The chosen thumbnail lifts and holds its ring, which answers
   the tap immediately even while the full-size photo is still coming
   down the wire; the shimmer above covers the gap it leaves behind.

   `.shot:active` is restated after `.shot.on` on purpose. They have the
   same specificity, so without the reorder the lifted thumbnail would
   have stopped responding to being pressed the moment it was chosen. */
.shot.on{transform:translateY(-2px)}
.shot:active{transform:scale(.94)}

/* ---- Panels, menus and bars opening ---------------------------------
   All of these appear by having their `hidden` attribute dropped, which
   restarts the animation every time — no class juggling needed. */
.menu-pop{animation:sheet-in var(--m-state) var(--ease)}
.uploading-box{animation:pop-in var(--m-state) var(--ease) both}

/* The bottom bars push up from off-screen rather than materialising.
   translateY on a fixed element does not extend the scrollable area, so
   the half that starts below the fold costs nothing. */
.bulk-bar,.submit-bar,.action-bar{
  animation:bar-up var(--m-enter) var(--ease) both}

/* ---- Flash messages -------------------------------------------------
   Rendered by the server on the next page load, so this is arrival only.
   Leaving is the undo toast's job and it already had it. */
.flash{animation:fade-up var(--m-enter) var(--ease) both}

/* ---- Touch targets that had no answer -------------------------------
   Everything with a border already responded to a press. These are the
   full-width rows, where a scale would look like the row shrinking away
   from the finger, so they flash a surface colour instead. */
.sheet-item{transition:background-color var(--m-tap) var(--ease),
  color var(--m-tap) var(--ease)}
.sheet-item:active{background:var(--bg-deep)}
.menu-pop a,.menu-pop button{transition:background-color var(--m-tap) var(--ease)}
.menu-pop a:active,.menu-pop button:active{background:var(--bg-deep)}
/* The pager is small and separate enough to take the press scale the
   rest of the buttons use. */
.pager a{transition:transform var(--m-tap) var(--ease),
  border-color var(--m-state) var(--ease),color var(--m-state) var(--ease)}
.pager a:active{transform:scale(.94)}

/* ---- Reduced motion -------------------------------------------------
   The blanket at the top of the file flattens every duration to nothing,
   which is the right answer for transitions. Four things it cannot say
   are spelled out here.

   1. It does not touch animation-delay. A stagger of six delays with a
      zero duration is not stillness — it is six cards popping in one
      after another, which is exactly what was being asked for less of.
      These entrances carry no information, so they are removed outright
      rather than shortened. */
@media (prefers-reduced-motion:reduce){
  main,.flash,.grid > *,.g-cards > *,.img-grid > *,.bulk-row,
  .bulk-bar,.submit-bar,.action-bar,.uploading-box{animation:none}

  /* 2. A loop at .001ms is a strobe, not a stillness. The sweep has to
        go by display, not by animation:none — with no animation the
        gradient simply parks in the middle of the frame and leaves a
        permanent white streak across every thumbnail. */
  .thumb:has(> img)::after,.pshot:has(img)::before{display:none}

  /* 3. The spinner needs !important. The blanket's own !important beats
        the plain 2.4s rule further up, so the one element on the page
        that is genuinely meant to keep turning was being spun at a
        thousand frames a second on the cheapest phone in the room. This
        is what that 2.4s rule was already trying to say. */
  .spinner{animation-duration:2.4s !important}

  /* 4. An animation that ends somewhere different from where it started
        is carrying meaning, not decoration. hl-fade runs `forwards` and
        finishes with the ring gone, so collapsing it does not calm the
        highlight down — it deletes it, and seeing which product you just
        saved is the entire point of coming back from an edit. Stop the
        animation and let the ring stay put. */
  .hl{animation:none}
}
