  :root{
    /* palette pulled from thecoolcatclub.com's brand tokens, so the WMS reads as the same product family */
    --bg:#F8EBCE;
    --surface:#FFFFFF;
    --surface-alt:#FCF7EB;
    --ink:#151A21;
    --muted:#7A7364;
    --border:#ECDFC4;
    --navy:#15382A;
    --navy-light:#385549;
    --amber:#F2A93B;
    --amber-ink:#7A4E06;
    --amber-bg:#FDF1DA;
    --red:#E24B3F;
    --red-bg:#FBE3E1;
    --green:#2F9E52;
    --green-bg:#E6F5EA;
    --pink:#EF7B8C;
    --pink-bg:#FDEBEE;
    --blue:#51AEEC;
    --teal:#04ABAE;
    --orange:#F87630;
    --accent-yellow:#F6E05E;
    --radius:14px;
    --shadow:0 1px 2px rgba(16,24,32,0.06), 0 6px 16px rgba(16,24,32,0.06);
    --font-body:"Bricolage Grotesque",-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif;
    --font-mono:ui-monospace,SFMono-Regular,"SF Mono",Consolas,"Liberation Mono",Menlo,monospace;
  }
  *{box-sizing:border-box;}
  html{overflow-x:hidden;}
  body{margin:0;background:var(--bg);color:var(--ink);font-family:var(--font-body);-webkit-font-smoothing:antialiased;touch-action:manipulation;overflow-x:hidden;overscroll-behavior-x:none;}
  .is-hidden{display:none !important;}
  .app{max-width:1280px;margin:0 auto;padding:0 20px 60px;}

  /* header */
  /* viewport-fit=cover lets the page under the notch/home indicator when installed
     on iOS, so pull the chrome back inside the safe area */
  body{padding-left:env(safe-area-inset-left);padding-right:env(safe-area-inset-right);padding-bottom:env(safe-area-inset-bottom);}
  /* Paints the status-bar strip on every screen (app AND login). It is the PAGE colour,
     not the brand green: with no header on a phone there is nothing green up there any
     more, so cream-on-cream leaves iOS's scroll-edge blur nothing to smear - it is still
     applied, it just has one flat colour to sample and so is invisible. Trying to dodge
     the effect with padding never worked; its falloff reaches ~90pt into the content.
     There is deliberately no exception: nothing green is ever painted at the top of a
     phone screen now (the locked-mode exit bar moved to the bottom in v3.42.2), because
     iOS's effect lightens what it samples - flat green comes back as sage, flat cream
     comes back as cream. */
  body::before{content:'';position:fixed;top:0;left:0;right:0;height:env(safe-area-inset-top);
    background:var(--bg);z-index:999;pointer-events:none;}
  .topbar{display:flex;align-items:center;justify-content:space-between;gap:16px;padding:calc(22px + env(safe-area-inset-top)) 4px 18px;flex-wrap:wrap;}
  /* Brand lockup: logo, then a column where the letter-spaced WMS wordmark sets the
     width and the version badge stretches to exactly match it underneath. */
  .brand{display:flex;align-items:center;gap:12px;}
  /* the logo is a wide wordmark (~4:1, includes "WMS") - size by height, let width follow */
  .brand-mark{width:auto;height:38px;max-width:210px;object-fit:contain;flex:none;}
  /* the wordmark already says WMS - the old text title next to it is now redundant */
  .brand-text h1{display:none;}
  .brand-mark-lg{width:min(320px,85%);height:auto;object-fit:contain;}
  .brand-text{display:flex;flex-direction:column;align-items:stretch;}
  /* the trailing negative margin trims the letter-space after the final S, so the
     wordmark's box ends where its glyphs do and the badge lines up flush */
  .brand h1{margin:0;font-size:19px;letter-spacing:.34em;margin-right:-.34em;color:var(--navy);line-height:1;}
  .version-badge{display:block;text-align:center;margin-top:3px;font-family:var(--font-mono);font-size:9px;font-weight:700;color:var(--muted);background:var(--surface-alt);border:1px solid var(--border);border-radius:20px;padding:1px 6px;letter-spacing:.2px;cursor:default;}
  .hazard-strip{height:4px;border-radius:4px;background:var(--navy);margin-bottom:22px;}
  .topbar-actions{display:flex;align-items:stretch;gap:10px;flex-wrap:wrap;position:relative;}
  .topbar-actions > *{height:40px;box-sizing:border-box;display:inline-flex;align-items:center;white-space:nowrap;}
  .topbar-actions .btn{padding:0 16px;font-size:13.5px;border-radius:10px;}
  .user-badge{font-size:13px;font-weight:600;color:var(--navy);background:var(--surface-alt);padding:0 14px;border-radius:10px;border:1px solid var(--border);gap:7px;}
  .user-badge-icon{flex:none;color:var(--muted);}
  /* Hamburger on EVERY breakpoint (was phone-only, with the buttons spilled
     inline on desktop via display:contents) - the jobs/actions list got long
     enough that the desktop topbar deserves the same dropdown. */
  .topbar-menu-toggle{display:inline-flex;width:40px;height:40px;box-sizing:border-box;align-items:center;justify-content:center;border-radius:10px;border:1px solid var(--border);background:var(--surface);color:var(--navy);cursor:pointer;flex:none;}
  .topbar-menu-toggle:hover{background:var(--surface-alt);}
  /* height:auto matters: as a .topbar-actions child the menu otherwise
     inherits that row's fixed 40px height and its items spill out the bottom */
  .topbar-menu{display:none;height:auto;flex-direction:column;align-items:stretch;gap:2px;position:fixed;background:var(--surface);border:1px solid var(--border);border-radius:14px;box-shadow:0 12px 32px rgba(21,56,42,.18);padding:8px;min-width:224px;z-index:150;}
  .topbar-menu > *{height:auto;}
  /* flat rows instead of the teal pill stack - one visual language for every item */
  .topbar-menu .btn{background:none;border:none;box-shadow:none;color:var(--ink);font-weight:600;font-size:13.5px;
    border-radius:9px;justify-content:flex-start;gap:10px;padding:9px 12px;text-align:left;}
  .topbar-menu .btn:hover{background:var(--surface-alt);}
  .topbar-menu .btn:active{background:var(--border);}
  .topbar-menu .btn svg{width:16px;height:16px;flex:none;color:var(--navy-light);}
  .topbar-menu .js-logout-btn{color:var(--red);}
  .topbar-menu .js-logout-btn svg{color:var(--red);}
  .topbar-menu-label{font-size:10.5px;font-weight:800;letter-spacing:.08em;text-transform:uppercase;color:var(--muted);padding:6px 12px 2px;}
  .topbar-menu > .topbar-menu-sep{height:1px;padding:0;background:var(--border);margin:6px 4px;border-radius:0;}
  /* items cascade in behind the card's own pop */
  .topbar-menu.is-open > *{animation:menuItemIn .55s cubic-bezier(.22,.61,.36,1) both;}
  .topbar-menu.is-open > *:nth-child(1){animation-delay:.04s;} .topbar-menu.is-open > *:nth-child(2){animation-delay:.08s;}
  .topbar-menu.is-open > *:nth-child(3){animation-delay:.12s;} .topbar-menu.is-open > *:nth-child(4){animation-delay:.16s;}
  .topbar-menu.is-open > *:nth-child(5){animation-delay:.20s;} .topbar-menu.is-open > *:nth-child(6){animation-delay:.24s;}
  .topbar-menu.is-open > *:nth-child(7){animation-delay:.28s;} .topbar-menu.is-open > *:nth-child(8){animation-delay:.32s;}
  .topbar-menu.is-open > *:nth-child(9){animation-delay:.36s;} .topbar-menu.is-open > *:nth-child(10){animation-delay:.40s;}
  .topbar-menu.is-open > *:nth-child(11){animation-delay:.44s;} .topbar-menu.is-open > *:nth-child(12){animation-delay:.48s;}
  @keyframes menuItemIn{from{opacity:0;transform:translateY(-4px);}to{opacity:1;transform:none;}}
  @media (prefers-reduced-motion: reduce){
    .topbar-menu.is-open, .topbar-menu.is-open > *, .topbar-menu-backdrop.is-open{animation:none;}
  }
  .topbar-menu.is-open{display:flex;transform-origin:top right;animation:menuIn .42s cubic-bezier(.22,.61,.36,1);}
  @keyframes menuIn{from{opacity:0;transform:scale(.94) translateY(-6px);}to{opacity:1;transform:none;}}
  .topbar-menu > *{box-sizing:border-box;display:inline-flex;align-items:center;justify-content:flex-start;white-space:nowrap;padding:10px 14px;font-size:13.5px;border-radius:10px;}
  .topbar-menu-backdrop{display:none;position:fixed;inset:0;background:rgba(21,26,33,.35);z-index:140;}
  .topbar-menu-backdrop.is-open{display:block;animation:backdropIn .38s ease;}
  @keyframes backdropIn{from{opacity:0;}to{opacity:1;}}

  .btn{font-family:inherit;font-size:14px;font-weight:600;border-radius:10px;padding:10px 16px;border:1px solid transparent;cursor:pointer;transition:transform .12s ease, opacity .3s ease;touch-action:manipulation;}
  .btn:active{transform:scale(.98);}
  .btn-primary{background:var(--navy);color:#fff;}
  .btn-primary:hover{background:var(--navy-light);}
  .btn-ghost{background:transparent;color:var(--navy);border-color:var(--border);}
  .btn-ghost:hover{background:var(--surface-alt);}
  /* Cancel throws the work away, so it is not a neutral ghost button */
  #cancelBtn{color:var(--red);border-color:var(--red);}
  #cancelBtn:hover{background:var(--red-bg);}
  .btn-sm{font-size:12.5px;padding:7px 10px;}
  .btn-block{width:100%;display:block;text-align:center;}
  .btn-big{font-size:17px;padding:16px 26px;border-radius:12px;}
  .mode-exit-btn{background:var(--red);color:#fff;border-color:var(--red);}
  .mode-exit-btn:hover{background:#c73f34;}

  /* locked mode (FLT / Container) — hides everything except the one interface in use.
     .rk-stats is exempt: the Restock tracker's stat strip lives INSIDE a locked mode. */
  .app.locked-mode .stats:not(.rk-stats),
  .app.locked-mode .tabs{display:none;}
  .app.locked-mode .topbar-actions > .btn:not(.mode-exit-btn){display:none;}
  .app.locked-mode .topbar-menu-toggle, .app.locked-mode .topbar-menu{display:none;}
  /* An admin announcement is app-wide chrome, same as the stats strip/tabs above -
     without this it could surface as a colored band over a locked task screen
     (FLT pre-use check, Stocker checklist, etc.), breaking the "hide everything
     except the one interface in use" contract locked mode is built around. */
  .app.locked-mode .announce-bar{display:none;}
  .btn:focus-visible, input:focus-visible, select:focus-visible, .chip:focus-visible, .tab-btn:focus-visible, th button:focus-visible,
  .seg-btn:focus-visible, .mode-tile:focus-visible, .more-row:focus-visible, .fab:focus-visible, .fp-opt:focus-visible,
  .container-tally-card:focus-visible{outline:2px solid var(--navy-light);outline-offset:2px;}

  /* stats - a horizontal strip of large cards inside the Dashboard's Stats section */
  .stats{display:flex;gap:12px;overflow-x:auto;overflow-y:hidden;-webkit-overflow-scrolling:touch;
    scroll-snap-type:x proximity;padding:2px 2px 8px;scrollbar-width:thin;}
  .stats::-webkit-scrollbar{height:6px;}
  .stats::-webkit-scrollbar-thumb{background:var(--border);border-radius:3px;}
  .stat-card{flex:0 0 auto;scroll-snap-align:start;min-width:160px;background:var(--surface-alt);border:1px solid var(--border);border-radius:var(--radius);padding:18px 20px;}
  .stat-num{font-family:var(--font-mono);font-size:30px;font-weight:600;color:var(--navy);line-height:1.1;white-space:nowrap;}
  .stat-label{margin-top:6px;font-size:11px;letter-spacing:.6px;text-transform:uppercase;color:var(--muted);white-space:nowrap;}
  .stat-card.warn .stat-num{color:var(--amber-ink);}
  .stat-card.bad .stat-num{color:var(--red);}
  .stat-card.info .stat-num{color:var(--teal);}
  .stat-card-clickable{cursor:pointer;transition:transform .12s ease, border-color .3s ease;}
  .stat-card-clickable:hover{border-color:var(--red);}
  .stat-card-clickable:active{transform:scale(.98);}

  /* tabs */
  .tabs{display:flex;gap:4px;margin-bottom:18px;border-bottom:1px solid var(--border);flex-wrap:nowrap;overflow-x:auto;overflow-y:hidden;-webkit-overflow-scrolling:touch;scrollbar-width:thin;}
  .tab-btn{flex:none;}
  .tab-btn{font-family:inherit;font-size:13.5px;font-weight:700;padding:10px 16px;border:none;background:transparent;color:var(--muted);cursor:pointer;border-bottom:2px solid transparent;margin-bottom:-1px;}
  .tab-btn.active{color:var(--navy);border-bottom-color:var(--navy);}
  .tab-btn:hover{color:var(--navy);}
  .tab-panel{display:none;}
  .tab-panel.active{display:block;}
  /* Icons and the two phone-only destinations are inert on desktop, where the strip
     stays the text tab bar it has always been. */
  .tab-icon{display:none;}
  .tab-btn-phone{display:none;}
  .subpage-back{display:none;}
  .fab{display:none;}

  /* segmented control - Active / Archived on the Search page */
  .seg-control{display:inline-flex;gap:2px;padding:3px;margin-bottom:14px;background:var(--surface-alt);border:1px solid var(--border);border-radius:10px;}
  .seg-btn{font-family:inherit;font-size:12.5px;font-weight:700;color:var(--muted);background:transparent;border:none;border-radius:8px;padding:7px 16px;cursor:pointer;transition:color .28s ease, background .28s ease;}
  .seg-btn.active{background:var(--surface);color:var(--navy);box-shadow:0 1px 2px rgba(16,24,32,.08);}

  /* Modes page tiles and More page rows. Both are full-width tap targets rather than
     buttons in a row - they're read one at a time on a phone, not scanned. */
  .mode-list, .more-list{display:flex;flex-direction:column;gap:8px;margin-top:14px;}
  .mode-tile{display:flex;align-items:center;gap:12px;width:100%;text-align:left;font-family:inherit;
    background:var(--surface-alt);border:1px solid var(--border);border-radius:12px;padding:14px;cursor:pointer;
    transition:transform .09s ease, border-color .22s ease;}
  .mode-tile:active{transform:scale(.99);}
  .mode-tile:hover{border-color:var(--navy-light);}
  .mode-tile-icon{width:26px;height:26px;flex:none;color:var(--teal);}
  .mode-tile-text{display:flex;flex-direction:column;gap:3px;min-width:0;flex:1;}
  .mode-tile-text strong{font-size:14.5px;color:var(--navy);}
  .mode-tile-text span{font-size:12px;color:var(--muted);}
  .mode-tile-chev{flex:none;font-size:20px;line-height:1;color:var(--muted);}
  /* the More page carries the brand lockup and the signed-in identity, since on a
     phone the topbar that used to show both is gone */
  .more-identity{display:flex;align-items:center;gap:12px;margin-bottom:12px;}
  .more-identity-mark{width:auto;height:30px;max-width:170px;object-fit:contain;flex:none;}
  .more-identity-text strong{display:none;} /* wordmark carries the name */
  .more-identity-text{display:flex;flex-direction:column;align-items:flex-start;gap:4px;}
  .more-identity-text strong{font-size:15px;color:var(--navy);letter-spacing:.02em;}
  .more-user{display:inline-flex;align-items:center;min-height:40px;}
  /* live viewport read-out - the only way to know this device's geometry, since iOS
     reports no make or model */
  .diag{display:block;width:100%;margin-top:12px;text-align:left;font-family:var(--font-mono);
    font-size:10.5px;line-height:1.7;color:var(--muted);background:var(--surface-alt);
    border:1px solid var(--border);border-radius:10px;padding:10px 12px;cursor:pointer;
    white-space:pre;overflow-x:auto;}
  .diag:active{background:var(--border);}
  .more-row{display:flex;align-items:center;justify-content:space-between;gap:10px;width:100%;text-align:left;
    font-family:inherit;font-size:14px;font-weight:600;color:var(--navy);background:var(--surface-alt);
    border:1px solid var(--border);border-radius:10px;padding:14px;min-height:48px;cursor:pointer;}
  .more-row:hover{border-color:var(--navy-light);}
  .more-row:active{transform:scale(.99);}
  .more-row-danger{color:var(--red);}
  .more-row-chev{font-size:20px;line-height:1;color:var(--muted);}
  .subpage-back{font-family:inherit;font-size:13px;font-weight:700;color:var(--navy);background:transparent;
    border:none;padding:6px 2px 10px;cursor:pointer;}

  /* floating "+ Add pallet", phone only - the topbar it used to live in is gone there */
  .fab{position:fixed;right:16px;bottom:calc(60px + var(--bar-gap, max(20px, env(safe-area-inset-bottom,0px))));
    width:56px;height:56px;border-radius:50%;border:none;background:var(--navy);color:#fff;
    align-items:center;justify-content:center;cursor:pointer;z-index:45;
    box-shadow:0 6px 20px rgba(16,24,32,.28);}
  .fab svg{width:26px;height:26px;}
  .fab:active{transform:scale(.94);}
  .fab[hidden]{display:none !important;}

  /* panels (dashboard / picker / map) */
  .panel{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);box-shadow:var(--shadow);padding:18px 20px;margin-bottom:18px;}
  .panel-head h2{margin:0 0 2px;font-size:15px;color:var(--navy);display:flex;align-items:center;gap:8px;flex-wrap:wrap;}
  .panel-sub{margin:0;font-size:12.5px;color:var(--muted);}
  .panel-sub-inline{font-size:11px;font-weight:600;color:var(--muted);background:var(--surface-alt);padding:2px 8px;border-radius:10px;}
  .flavour-search{width:100%;margin:14px 0 4px;padding:9px 12px;border:1px solid var(--border);border-radius:8px;font-size:14px;font-family:inherit;background:var(--surface-alt);}
  .flavour-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(250px,1fr));gap:12px;margin-top:14px;}
  .flavour-card{border:1px solid var(--border);border-radius:10px;padding:14px;background:var(--surface-alt);display:flex;flex-direction:column;gap:10px;}
  /* Fixed card anatomy so every card lines up regardless of name length:
     tag strip on top, photo in a fixed slot next to the name, action pinned
     to the bottom (cards in a grid row stretch to equal height). */
  .fc-tags{display:flex;align-items:center;gap:5px;flex-wrap:wrap;min-height:20px;}
  .fc-name-row{display:flex;align-items:center;gap:10px;}
  .fc-thumb-slot{width:44px;height:44px;flex:none;display:flex;align-items:center;justify-content:center;}
  .fc-name{font-weight:700;font-size:14px;min-width:0;}
  .fc-name .fdisp-name{margin-left:0;overflow-wrap:anywhere;}
  .fc-pick{display:flex;align-items:center;gap:10px;flex-wrap:wrap;}
  .fc-info{flex:1;min-width:0;}
  .fc-action{margin-top:auto;}
  .fc-qty{font-weight:600;font-size:13.5px;}
  .fc-meta{font-size:11.5px;color:var(--muted);margin-top:3px;display:flex;align-items:center;gap:6px;flex-wrap:wrap;}
  /* freshness pills must shrink inside narrow phone-grid cards, not cross the
     viewport edge (pills are nowrap by default) */
  .fc-meta .pill, .pf-card-top .pill{min-width:0;max-width:100%;overflow:hidden;text-overflow:ellipsis;}
  .loc-badge-mid{font-family:var(--font-mono);font-weight:800;font-size:15px;background:var(--navy);color:#fff;border-radius:7px;padding:6px 10px;letter-spacing:.4px;white-space:nowrap;}
  .note-empty{font-size:13px;color:var(--muted);padding:10px 2px;}
  .note-warning{font-size:12.5px;background:var(--red-bg);color:var(--red);padding:8px 10px;border-radius:8px;}

  /* flavour product-photo thumbnails, for quick visual ID next to the flavour name */
  .flavour-thumb{object-fit:contain;background:transparent;border:none;flex:none;vertical-align:middle;}

  /* product-type tag shown in place of the raw (P)/(C)/etc. flavour prefix */
  .ptype-tag{display:inline-block;font-size:10px;font-weight:800;text-transform:uppercase;letter-spacing:.03em;padding:2px 7px;border-radius:5px;vertical-align:middle;border:1px solid transparent;white-space:nowrap;flex:none;}
  /* keeps a space between tags, and between the last tag and the name, in
     flowing-text contexts where the spans sit directly next to each other */
  .ptype-tag + .fdisp-name, .ptype-tag + .ptype-tag{margin-left:5px;}
  /* flex containers already space children with gap - don't double up */
  .fc-tags .ptype-tag + .ptype-tag, .flavour-cell .ptype-tag + .ptype-tag,
  .fp-btn .ptype-tag + .ptype-tag, .fp-opt .ptype-tag + .ptype-tag,
  .pf-flavour-tags .ptype-tag + .ptype-tag{margin-left:0;}
  /* Flavour table cell / card: lay the thumb, tag and name out as a row so the
     name takes the leftover width and wraps within itself. As inline content the
     line could break straight after the tag, stranding it on its own line above
     the name (and splitting names like "Tuna / with Shrimp"). */
  .flavour-cell{display:flex;align-items:center;gap:6px;min-width:0;}
  .flavour-cell .flavour-thumb{flex:none;}
  .flavour-cell .fdisp-name{min-width:0;margin-left:0;}
  .flavour-cell .pill-btn{flex:none;}
  .ptype-pouch{background:var(--pouch-fill);border-color:var(--pouch-border);color:#fff;}
  .ptype-can{background:var(--can-fill);border-color:var(--can-border);color:var(--can-ink);}
  .ptype-chunk{background:var(--chunks-fill);border-color:var(--chunks-border);color:var(--chunks-ink);}
  .ptype-shred{background:var(--shreds-fill);border-color:var(--shreds-border);color:var(--shreds-ink);}
  .ptype-smooth{background:var(--pink-bg);border-color:var(--pink);color:#8A2740;}
  .ptype-dryfood{background:var(--cardboard-fill);border-color:var(--cardboard-border);color:#fff;}
  /* the compact kitten tab - a single K, padded to read as a square tag */
  .ptype-kitten{background:var(--accent-yellow);border-color:#C9AF2E;color:#5C4E07;padding:2px 6px;min-width:20px;text-align:center;cursor:help;}
  /* compact "at pick face" tab - tap/hover for the full wording */
  .pf-tag{background:var(--navy);border-color:var(--navy);color:#fff;cursor:help;}
  .flavour-thumb-sm{width:30px;height:30px;}
  .flavour-thumb-md{width:44px;height:44px;}
  .flavour-thumb-lg{width:64px;height:64px;border-radius:8px;}
  .field-flavour-row{display:flex;align-items:center;gap:10px;}
  .field-flavour-row .field{flex:1;min-width:0;}

  /* picker tab */
  .picker-panel{text-align:center;}
  .picker-intro{padding:26px 10px;}
  .picker-intro h2{margin:0 0 6px;font-size:20px;color:var(--navy);}
  .picker-select{max-width:420px;margin:22px auto 0;}
  .picker-select label{display:block;font-size:13.5px;font-weight:600;color:var(--muted);margin-bottom:8px;}
  .picker-flavour-select{width:100%;font-size:16px;padding:14px;border-radius:10px;border:1px solid var(--border);background:var(--surface-alt);margin-bottom:16px;}
  .picker-select-actions{display:flex;gap:10px;justify-content:center;flex-wrap:wrap;}
  .picker-result{text-align:left;margin-top:6px;}
  .picker-result-head{display:flex;align-items:center;gap:18px;flex-wrap:wrap;background:var(--surface-alt);border:1px solid var(--border);border-radius:12px;padding:20px;}
  .picker-loc-badge{font-family:var(--font-mono);font-weight:800;font-size:30px;background:var(--navy);color:#fff;border-radius:10px;padding:14px 20px;white-space:nowrap;}
  .picker-result-info{flex:1;min-width:200px;}
  .picker-result-info .pr-flavour{font-size:19px;font-weight:700;display:flex;align-items:center;gap:10px;}
  .picker-result-info .pr-meta{font-size:13.5px;color:var(--muted);margin-top:8px;display:flex;gap:12px;flex-wrap:wrap;align-items:center;}
  .picker-note{margin-top:14px;font-size:13.5px;background:var(--amber-bg);color:var(--amber-ink);padding:12px 14px;border-radius:8px;text-align:left;}
  .picker-map{margin-top:18px;}
  .picker-confirm{margin-top:16px;}
  .picker-confirmed{display:flex;align-items:center;gap:14px;background:var(--green-bg);border:1px solid var(--green);border-radius:12px;padding:20px;}
  .picker-confirmed-icon{font-size:30px;color:var(--green);flex:none;line-height:1;}
  .picker-confirmed-text{font-size:14.5px;color:var(--ink);line-height:1.5;}
  .picker-again{margin-top:18px;text-align:center;}

  /* FLT mode: sections sitting under the picker (recent picks + inspection log) */
  .flt-section{margin-top:16px;text-align:left;}
  .flt-section .panel-head{display:flex;align-items:center;justify-content:space-between;gap:12px;flex-wrap:wrap;}
  .flt-history-wrap{margin-top:12px;max-height:320px;overflow-y:auto;}
  .flt-history-wrap table{font-size:13px;}
  .flt-defect-banner{display:flex;align-items:flex-start;gap:12px;margin-top:16px;padding:14px 16px;border-radius:12px;background:var(--red-bg);border:1px solid var(--red);text-align:left;}
  .flt-defect-banner.is-hidden{display:none;}
  .flt-defect-icon{font-size:20px;line-height:1;flex:none;}
  .flt-defect-title{font-weight:800;color:var(--red);font-size:14.5px;}
  .flt-defect-body{font-size:13px;color:var(--ink);margin-top:3px;line-height:1.45;}
  .flt-status-line{margin-top:10px;font-size:13.5px;display:flex;align-items:center;gap:8px;flex-wrap:wrap;}
  /* one row per checklist item: label on the left, pass/fail toggle on the right */
  .flt-check-list{margin-top:10px;border-top:1px solid var(--border);}
  .flt-check-row{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:9px 2px;border-bottom:1px solid var(--border);}
  .flt-check-label{font-size:14px;font-weight:600;color:var(--ink);}
  .flt-check-opts{display:flex;gap:6px;flex:none;}
  .flt-check-opts button{font-family:inherit;font-size:12px;font-weight:700;padding:6px 14px;border-radius:8px;border:1px solid var(--border);background:var(--surface);color:var(--muted);cursor:pointer;}
  .flt-check-opts button.sel-pass{background:var(--green-bg);border-color:var(--green);color:#1D6B36;}
  .flt-check-opts button.sel-fail{background:var(--red-bg);border-color:var(--red);color:var(--red);}
  /* per-item defect report, revealed under a row only when that item is failed */
  .flt-check-item{border-bottom:1px solid var(--border);}
  .flt-check-item .flt-check-row{border-bottom:0;}
  .flt-check-note{padding:0 2px 10px;}
  .flt-check-note textarea{width:100%;font-family:inherit;font-size:13px;padding:8px 10px;border:1px solid var(--red);border-radius:8px;background:var(--red-bg);color:var(--ink);resize:vertical;}
  .flt-check-note textarea::placeholder{color:#B4675E;}
  .flt-check-note.missing textarea{outline:2px solid var(--red);}
  .flt-defect-list{margin:2px 0 0;padding-left:16px;font-size:12.5px;color:var(--muted);}
  .flt-defect-list li{margin-top:2px;}
  .flt-repair-note{font-size:12px;color:var(--muted);margin-top:3px;}
  .flt-repaired-line{color:#1D6B36;}

  /* job-role pickers in the account modal */
  .job-role-list{display:flex;flex-direction:column;gap:8px;}
  .job-role-opt{display:flex;align-items:flex-start;gap:10px;padding:10px 12px;border:1px solid var(--border);border-radius:10px;cursor:pointer;background:var(--surface);}
  .job-role-opt input{width:auto;margin:2px 0 0;flex:none;}
  .job-role-opt.checked{border-color:var(--navy);background:var(--surface-alt);}
  .job-role-name{font-size:14px;font-weight:700;color:var(--ink);}
  .job-role-desc{font-size:12.5px;color:var(--muted);margin-top:1px;}
  .job-role-note{font-size:12.5px;color:var(--muted);}

  /* Horizontal card strip. Used where a list is a glanceable summary rather than
     something to read end to end - it keeps a long list from turning the phone
     view into an endless scroll, and the cut-off card on the right edge is the
     cue that there's more sideways. */
  .card-strip{margin-top:14px;overflow-x:auto;overflow-y:hidden;-webkit-overflow-scrolling:touch;
    scroll-snap-type:x proximity;padding-bottom:4px;}
  .card-strip-track{display:flex;gap:10px;align-items:stretch;width:max-content;padding-right:12px;}
  .card-strip::-webkit-scrollbar{height:6px;}
  .card-strip::-webkit-scrollbar-thumb{background:var(--border);border-radius:3px;}
  .pf-card{scroll-snap-align:start;flex:0 0 auto;width:190px;display:flex;flex-direction:column;gap:8px;
    padding:12px;border:1px solid var(--border);border-radius:12px;background:var(--surface-alt);}
  .pf-card-top{display:flex;align-items:center;justify-content:space-between;gap:6px;flex-wrap:wrap;}
  /* Fixed anatomy, same idea as the dashboard flavour cards: photo in a fixed slot,
     tags on their own line, name below it wrapping within its own column - so a
     short name ("Chicken") and a long one ("Chicken and Beef") sit at the same
     height instead of flowing around the photo differently. */
  .pf-card-flavour{display:flex;align-items:flex-start;gap:8px;font-size:13.5px;font-weight:700;}
  .pf-thumb-slot{width:30px;height:30px;flex:none;display:flex;align-items:center;justify-content:center;}
  .pf-flavour-text{min-width:0;flex:1;display:flex;flex-direction:column;gap:3px;align-items:flex-start;}
  .pf-flavour-tags{display:flex;align-items:center;gap:5px;flex-wrap:wrap;}
  .pf-flavour-tags:empty{display:none;}
  .pf-flavour-text .fdisp-name{overflow-wrap:anywhere;margin-left:0;}
  .pf-card-qty{font-size:12.5px;color:var(--muted);font-weight:600;margin-top:auto;}
  .pf-card-when{font-size:11px;color:var(--muted);white-space:nowrap;}
  .fc-no-pf{font-size:11.5px;padding:8px 6px;}
  .pf-card .btn{width:100%;}
  /* locked field: there's one correct answer, so it's shown rather than asked for */
  .input-locked{background:var(--surface-alt);font-weight:700;letter-spacing:.5px;}

  /* Custom flavour picker. The native <select> stays in the DOM as the value
     holder (so every existing .value read and change listener keeps working) -
     this is purely the visible layer on top of it, which lets each option carry
     its product photo and product-type tag instead of a flat OS menu. */
  select.fp-native{position:absolute;opacity:0;pointer-events:none;width:1px;height:1px;overflow:hidden;}
  .fp{position:relative;width:100%;}
  .fp-btn{width:100%;display:flex;align-items:center;gap:9px;min-height:42px;padding:7px 34px 7px 11px;
    font-family:inherit;font-size:14px;text-align:left;color:var(--ink);background:var(--surface);
    border:1px solid var(--border);border-radius:10px;cursor:pointer;position:relative;}
  .fp-btn:hover{border-color:var(--navy-light);}
  .fp-btn:after{content:'';position:absolute;right:13px;top:50%;width:7px;height:7px;margin-top:-5px;
    border-right:2px solid var(--muted);border-bottom:2px solid var(--muted);transform:rotate(45deg);}
  .fp.is-open .fp-btn{border-color:var(--navy);}
  .fp-btn-label{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
  .fp-btn-placeholder{color:var(--muted);}
  /* z-index stays below .modal-overlay (50) so a picker left open outside a modal
     can never render over one; inside a modal it shares that stacking context and
     still sits above the neighbouring fields */
  .fp-panel{position:absolute;z-index:20;top:calc(100% + 5px);left:0;right:0;background:var(--surface);
    border:1px solid var(--border);border-radius:12px;box-shadow:var(--shadow);display:none;
    max-height:min(380px,60vh);flex-direction:column;overflow:hidden;}
  .fp.is-open .fp-panel{display:flex;animation:fp-in .3s cubic-bezier(.22,.61,.36,1) both;
    transform-origin:top center;}
  .fp.fp-up.is-open .fp-panel{transform-origin:bottom center;}
  @keyframes fp-in{from{opacity:0;transform:translateY(-6px) scale(.985);}to{opacity:1;transform:none;}}
  .fp-btn:after{transition:transform .28s cubic-bezier(.22,.61,.36,1);}
  .fp.is-open .fp-btn:after{transform:rotate(180deg);}
  /* .modal has overflow-y:auto, so a panel opening near the bottom of a modal would
     be clipped rather than scrolled to - flip it above the field in that case */
  .fp.fp-up .fp-panel{top:auto;bottom:calc(100% + 5px);}
  .fp-search-wrap{padding:9px;border-bottom:1px solid var(--border);flex:none;}
  .fp-search{width:100%;font-family:inherit;font-size:14px;padding:9px 11px;border:1px solid var(--border);
    border-radius:8px;background:var(--surface-alt);color:var(--ink);}
  .fp-list{overflow-y:auto;padding:5px;-webkit-overflow-scrolling:touch;}
  .fp-group{font-size:10.5px;font-weight:800;text-transform:uppercase;letter-spacing:.05em;color:var(--muted);
    padding:9px 9px 4px;}
  .fp-opt{display:flex;align-items:center;gap:9px;width:100%;padding:8px 9px;min-height:44px;
    font-family:inherit;font-size:14px;text-align:left;color:var(--ink);background:none;border:0;
    border-radius:8px;cursor:pointer;}
  .fp-opt:hover,.fp-opt.fp-active{background:var(--surface-alt);}
  .fp-opt.fp-selected{background:var(--navy);color:#fff;}
  .fp-opt.fp-selected .ptype-tag{filter:brightness(1.08);}
  .fp-opt-name{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
  .fp-empty{padding:16px 12px;font-size:13px;color:var(--muted);text-align:center;}
  /* the picker's own thumbnail slot keeps a fixed footprint so rows don't jump
     about while product photos load */
  .fp-thumb-slot{width:30px;height:30px;flex:none;display:flex;align-items:center;justify-content:center;}
  .flt-section-actions{display:flex;gap:8px;flex-wrap:wrap;}
  .flt-hours-cell{white-space:nowrap;}
  /* shift rows expand on tap to list what happened during the shift */
  .flt-shift-row{cursor:pointer;}
  .flt-shift-row:hover td{background:var(--surface-alt);}
  .flt-shift-row.expanded td{background:var(--surface-alt);}
  .flt-shift-chevron{display:inline-block;width:14px;color:var(--muted);font-size:11px;margin-right:2px;}
  .flt-events-row td{padding:0 12px 12px;background:var(--surface-alt);}
  .flt-events-box{border:1px solid var(--border);border-radius:10px;background:var(--surface);padding:10px 12px;
    animation:fltEventsIn .45s ease;}
  @keyframes fltEventsIn{from{opacity:0;transform:translateY(-4px);}to{opacity:1;transform:none;}}
  .flt-events-head{font-size:12px;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.04em;margin-bottom:6px;}
  .flt-event-list{list-style:none;margin:0;padding:0;}
  .flt-event-list li{display:flex;flex-wrap:wrap;gap:4px 10px;align-items:baseline;padding:5px 0;border-top:1px solid var(--border);font-size:13px;}
  .flt-event-list li:first-child{border-top:none;}
  .flt-event-time{font-family:var(--font-mono);font-size:11.5px;color:var(--muted);white-space:nowrap;}
  .flt-event-action{font-weight:600;color:var(--ink);}
  .flt-event-detail{color:var(--muted);}
  .flt-hours-used{font-weight:700;color:var(--ink);}
  .flt-hours-range{font-size:12px;color:var(--muted);}

  /* map tab */
  /* compact header: heading + slim flavour picker on one line, chips + count pill
     on the next - the map itself gets everything below (fitMapSvg sizes it to the
     real remaining viewport height, which also survives OS large-text settings) */
  .map-head-row{display:flex;align-items:center;justify-content:space-between;gap:10px;flex-wrap:wrap;}
  .map-head-row h2{margin:0;font-size:15px;color:var(--navy);flex:none;}
  .map-head-row .fp{flex:1 1 150px;max-width:240px;}
  .map-head-row .fp-btn{min-height:34px;padding:4px 28px 4px 9px;font-size:13px;border-radius:8px;}
  .map-head-row .fp-btn .fp-thumb-slot{width:22px;height:22px;}
  .map-controls-row{display:flex;align-items:center;gap:8px 12px;margin-top:14px;margin-bottom:16px;flex-wrap:wrap;}
  .map-controls-row .note-empty{padding:0;margin-left:auto;font-size:12px;}
  /* the aisle chips and the diagram were running into each other */
  #mapTabResult, #mapZoomResult, #stockTakeMapResult, #stockTakeZoomResult{margin-top:4px;}
  /* very small phones: the whole-warehouse overview squeezed its labels below
     legibility (~5px) and bay cells below tappability. Let the overview render at
     a phone-390-equivalent width inside its own horizontal scroll box instead -
     the page body still never scrolls sideways. */
  @media (max-width:400px){
    #mapTabResult, #stockTakeMapResult{overflow-x:auto;-webkit-overflow-scrolling:touch;}
    #mapTabResult .map-overview-svg, #stockTakeMapResult .map-overview-svg{min-width:430px;}
  }
  /* the pick-face/reserve toggle pill is an action - make it look and feel like
     one, and give the "Sure?" confirm state a warning tint */
  .pill[data-action="togglestatus"]{cursor:pointer;min-height:28px;}
  @media (max-width:640px){ .pill[data-action="togglestatus"]{padding:11px 14px;} }
  .pill.pill-confirm{background:var(--red-bg);color:var(--red);}
  .map-location-list{margin-top:16px;display:flex;flex-direction:column;gap:8px;}
  .map-loc-row{display:flex;align-items:center;gap:10px;flex-wrap:wrap;padding:10px 12px;background:var(--surface-alt);border:1px solid var(--border);border-radius:8px;font-size:13px;}
  .map-loc-meta{flex:1;min-width:180px;color:var(--muted);}

  /* map tab / stock take - zoomed bay view (2 row-positions at a time), large + directly tappable */
  /* The aisle reads as ONE continuous run of racking that the buttons pan a camera along,
     rather than a diagram boxed between two arrows. The bay fills the frame edge to edge;
     a gradient on each side fades it out where the run carries on past the viewport, and
     the arrows float over those fades. */
  .bay-pager{position:relative;display:flex;align-items:center;background:var(--surface-alt);
    border:1px solid var(--border);border-radius:12px;padding:10px 0;overflow:hidden;}
  /* The viewport is a window onto the whole aisle; .bay-track holds every bay side by
     side and is what actually moves. Each bay is 82% of the frame, so 9% of the bay
     either side stays visible under the gradient - the racking carries on past the edge
     rather than stopping at it. */
  .bay-viewport{flex:1;min-width:0;overflow:hidden;}
  .bay-track{display:flex;align-items:stretch;width:100%;
    transition:transform .62s cubic-bezier(.22,.61,.36,1);will-change:transform;}
  .bay-slide{flex:0 0 82%;min-width:0;
    /* the bays either side are context, not targets - only the centred one takes taps */
    pointer-events:none;opacity:.55;transition:opacity .62s ease;}
  .bay-slide.is-current{pointer-events:auto;opacity:1;}
  .bay-pager::before, .bay-pager::after{content:'';position:absolute;top:0;bottom:0;width:74px;
    pointer-events:none;z-index:1;}
  .bay-pager::before{left:0;background:linear-gradient(to right, var(--surface-alt) 18%, rgba(252,247,235,0));}
  .bay-pager::after{right:0;background:linear-gradient(to left, var(--surface-alt) 18%, rgba(252,247,235,0));}
  /* no chrome of their own - just a faint chevron sitting in the fade, with a tap
     target much larger than the glyph */
  .bay-nav-btn{position:absolute;top:0;bottom:0;z-index:2;width:56px;
    border:none;background:none;color:var(--muted);font-size:26px;font-weight:400;line-height:1;
    cursor:pointer;touch-action:manipulation;display:flex;align-items:center;justify-content:center;
    opacity:.75;transition:opacity .28s ease, transform .14s ease;}
  .bay-nav-btn:nth-of-type(1){left:0;}
  .bay-nav-btn:nth-of-type(2){right:0;}
  .bay-nav-btn:active{opacity:1;transform:scale(.9);}
  .bay-nav-btn:disabled{opacity:0;pointer-events:none;}
  .bay-caption{text-align:center;font-size:12px;color:var(--muted);margin-top:8px;font-weight:600;}

  /* pallet builder tab */
  :root{
    --can-fill:#FDF0CE; --can-border:#D9BE85; --can-ink:#6B4E17;
    --pouch-fill:#E3A325; --pouch-border:#A66E12; --pouch-ink:#3E2400;
    --chunks-fill:#D9C7A3; --chunks-border:#A88A55; --chunks-ink:#4A3A1A;
    --shreds-fill:#EADFC2; --shreds-border:#C2A96E; --shreds-ink:#5B4623;
    --cardboard-fill:#C9A876; --cardboard-border:#8F6F3F; --cardboard-ink:#3E2E12;
    --layerpad-fill:#DCD6C8; --layerpad-border:#A69F8C; --layerpad-ink:#4A4436;
    --divider-fill:#C9CDD1; --divider-border:#8F959C; --divider-ink:#33373C;
    --pallet-wood:#937460; --pallet-wood-dark:#7A5D4A;
  }
  .builds-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(240px,1fr));gap:12px;margin-top:14px;}
  .build-card{border:1px solid var(--border);border-radius:10px;padding:14px;background:var(--surface-alt);cursor:pointer;transition:transform .09s ease;}
  .build-card:hover{background:var(--surface);border-color:var(--navy);}
  .build-card-name{font-weight:700;font-size:14px;margin-bottom:4px;}
  .build-card-meta{font-size:12px;color:var(--muted);display:flex;gap:8px;flex-wrap:wrap;align-items:center;}
  .builder-setup-row{display:grid;grid-template-columns:2fr 1fr 1fr 1fr 1fr;gap:12px;margin-top:14px;}
  .builder-bulk-panel{background:var(--surface-alt);border:1px solid var(--border);border-radius:10px;padding:16px;margin-top:18px;}
  .builder-bulk-panel h3{margin:0 0 12px;font-size:13px;color:var(--navy);text-transform:uppercase;letter-spacing:.5px;}
  .builder-bulk-row{display:grid;grid-template-columns:repeat(auto-fit,minmax(150px,1fr));gap:10px;}
  /* min-width:0 or a grid item refuses to shrink below its content's intrinsic width -
     which is how a date input ends up overlapping the field beside it */
  .builder-bulk-row > *, .builder-setup-row > *, .pallet-setup-row > *, .field-row > *{min-width:0;}
  .builder-bulk-actions{display:flex;gap:10px;margin-top:12px;flex-wrap:wrap;}
  .builder-stack{margin:20px auto 0;max-width:520px;}
  .builder-stack-band{display:flex;align-items:center;gap:10px;margin-bottom:4px;cursor:pointer;}
  .builder-stack-band-label{font-size:11px;font-weight:700;color:var(--muted);width:56px;text-align:right;flex:none;}
  .builder-stack-band-bar{flex:1;height:26px;border-radius:5px;border:1.5px solid;display:flex;align-items:center;padding:0 4px;gap:2px;position:relative;}
  .builder-stack-band-bar.active-layer{outline:2px solid var(--navy);outline-offset:2px;}
  .builder-stack-cell{flex:1;height:70%;border-radius:2px;opacity:.85;}
  .builder-stack-flag{position:absolute;right:6px;top:50%;transform:translateY(-50%);width:8px;height:8px;border-radius:50%;background:var(--red);}
  .builder-stack-base{height:16px;border-radius:4px;margin-top:2px;}
  .builder-layer-tabs{display:flex;gap:6px;flex-wrap:wrap;margin-top:20px;}
  .builder-layer-tab{font-family:inherit;font-size:12.5px;font-weight:700;padding:7px 14px;border-radius:20px;border:1px solid var(--border);background:var(--surface);color:var(--muted);cursor:pointer;}
  .builder-layer-tab.active{background:var(--navy);border-color:var(--navy);color:#fff;}
  /* A real pallet footprint is a fixed 1200mm x 1000mm — it doesn't get longer as
     you add more rows/cols, the boxes on it just get smaller. So the wrap is a
     fixed-ratio box and the grid always fills it exactly, however many cells. */
  .builder-grid-wrap{margin:14px auto 0;padding:20px;background:var(--pallet-wood);border-radius:12px;max-width:520px;aspect-ratio:1200/1000;display:flex;}
  .builder-grid{display:grid;gap:4px;width:100%;height:100%;}
  .builder-box{background:var(--can-fill);border:2px solid var(--can-border);border-radius:5px;cursor:pointer;display:flex;flex-direction:column;align-items:center;justify-content:center;text-align:center;padding:2px;font-size:9px;font-weight:700;color:var(--can-ink);line-height:1.15;position:relative;transition:transform .09s ease;overflow:hidden;}
  .builder-box:hover{transform:scale(1.04);}
  .builder-box.product-pouch{background:var(--pouch-fill);border-color:var(--pouch-border);color:var(--pouch-ink);}
  .builder-box.product-chunks{background:var(--chunks-fill);border-color:var(--chunks-border);color:var(--chunks-ink);}
  .builder-box.product-shreds{background:var(--shreds-fill);border-color:var(--shreds-border);color:var(--shreds-ink);}
  .builder-box.product-cardboard{background:var(--cardboard-fill);border-color:var(--cardboard-border);color:var(--cardboard-ink);}
  .builder-box.product-layerpad{background:var(--layerpad-fill);border-color:var(--layerpad-border);color:var(--layerpad-ink);}
  .builder-box.product-divider{background:var(--divider-fill);border-color:var(--divider-border);color:var(--divider-ink);}
  .builder-box.flagged{border-color:var(--red);border-width:3px;box-shadow:0 0 0 2px rgba(194,66,47,.25);}
  .builder-box-flag-icon{position:absolute;top:-6px;right:-6px;width:16px;height:16px;border-radius:50%;background:var(--red);color:#fff;font-size:10px;font-weight:800;display:flex;align-items:center;justify-content:center;}
  .builder-box-empty{opacity:.4;font-weight:500;}
  .builder-box-list{margin-top:20px;}
  .builder-box-list-controls{display:flex;gap:10px;margin-bottom:12px;flex-wrap:wrap;align-items:center;}
  .builder-summary{display:flex;gap:16px;flex-wrap:wrap;margin-top:14px;font-size:12.5px;color:var(--muted);}
  .builder-summary strong{color:var(--ink);}

  /* collapsible sections in the pallet edit modal */
  .collapsible-head{display:flex;align-items:center;justify-content:space-between;gap:10px;cursor:pointer;padding:14px 2px;border-top:1px solid var(--border);margin-top:18px;-webkit-user-select:none;user-select:none;}
  .collapsible-head h3{margin:0;font-size:14.5px;color:var(--navy);}
  .collapsible-head .panel-sub{margin:2px 0 0;}
  .collapsible-chevron{color:var(--muted);font-size:12px;transition:transform .34s ease;flex:none;}
  .collapsible-head.collapsed .collapsible-chevron{transform:rotate(-90deg);}
  .collapsible-body.is-hidden{display:none;}
  /* driven by animateSectionToggle() in app.js - height is set inline, this
     just supplies the easing */
  .section-collapsing{transition:height .5s ease, opacity .4s ease;}

  /* team announcement banner - amber notice under the top of the app */
  .announce-bar{flex:none;display:flex;align-items:flex-start;gap:10px;margin:8px 0 0;padding:11px 12px;
    background:var(--amber-bg);border:1px solid var(--amber-ink);border-radius:12px;color:var(--amber-ink);
    animation:contentIn .55s ease;}
  .announce-bar svg{width:18px;height:18px;flex:none;margin-top:1px;}
  .announce-bar-text{flex:1;min-width:0;font-size:13.5px;font-weight:600;color:var(--ink);}
  .announce-bar-meta{display:block;font-size:11.5px;font-weight:500;color:var(--muted);margin-top:2px;}
  .announce-bar-x{background:none;border:none;color:var(--amber-ink);font-size:20px;line-height:1;cursor:pointer;
    padding:0 4px;flex:none;}


  /* shared "content changed" entrance - re-triggered from flashRerender() on
     discrete user actions (filter taps, seg switches, zoom changes) */
  @keyframes contentIn{from{opacity:0;transform:translateY(6px);}to{opacity:1;transform:none;}}
  .anim-rerender{animation:contentIn .55s ease;}
  /* every page switch slides in the same way */
  .tab-panel.active{animation:contentIn .45s ease;}
  @media (prefers-reduced-motion: reduce){
    .anim-rerender, .tab-panel.active{animation:none;}
    .section-collapsing{transition:none;}
  }

  /* read-only pallet details view shown above the (collapsed) edit form */
  .pallet-details-view.is-hidden{display:none;}
  .pallet-details-head{display:flex;align-items:center;gap:10px;flex-wrap:wrap;margin-bottom:14px;}
  .pallet-details-head .loc-badge{font-size:16px;padding:6px 12px;}
  .pallet-details-flavour{font-size:17px;font-weight:700;color:var(--navy);}
  .pallet-details-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(160px,1fr));gap:14px 20px;margin-bottom:6px;}
  .pallet-details-field-label{font-size:11px;text-transform:uppercase;letter-spacing:.04em;color:var(--muted);margin-bottom:3px;}
  .pallet-details-field-value{font-size:14.5px;color:var(--ink);font-weight:600;}
  .pallet-details-notes{margin-top:14px;padding:10px 12px;background:var(--surface-alt);border-radius:8px;font-size:13.5px;color:var(--ink);}
  .pallet-details-flavour-list{margin-top:14px;border-top:1px solid var(--border);padding-top:12px;}
  .pallet-details-flavour-list-title{font-size:11px;text-transform:uppercase;letter-spacing:.04em;color:var(--muted);margin-bottom:8px;}
  .pallet-details-flavour-row{display:flex;align-items:center;gap:10px;padding:5px 0;font-size:13.5px;}
  .pallet-details-flavour-row .flavour-thumb{flex:none;}
  .pallet-details-flavour-row .fl-name{flex:1;}
  .pallet-details-flavour-row .fl-qty{color:var(--muted);}
  /* dashboard section headers - collapsible, no divider line (they top their panels) */
  .dash-head{border-top:none;margin-top:0;padding:2px 0 10px;}
  .dash-head h2{margin:0;font-size:15px;color:var(--navy);display:flex;align-items:center;gap:8px;flex-wrap:wrap;}
  .mobile-sort-row{display:none;}
  .container-link-row{display:flex;align-items:flex-end;gap:12px;flex-wrap:wrap;}
  .container-link-row .field{flex:1;min-width:220px;margin-bottom:0;}

  /* container mode */
  .container-tally-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(160px,1fr));gap:12px;margin-top:18px;}
  .container-tally-card{position:relative;border:1px solid var(--border);border-radius:10px;padding:16px 14px;background:var(--surface-alt);text-align:center;cursor:pointer;-webkit-user-select:none;user-select:none;transition:transform .09s ease, background .18s ease;}
  .container-tally-card:hover{background:var(--surface);border-color:var(--navy);}
  .container-tally-card:active{transform:scale(.97);}
  .container-tally-card.over .container-tally-count{color:var(--red);}
  .container-tally-flavour{font-size:12.5px;font-weight:700;color:var(--navy);margin-bottom:8px;line-height:1.3;min-height:32px;}
  .container-tally-count{font-family:var(--font-mono);font-size:30px;font-weight:800;color:var(--ink);}
  /* tap feedback: the count pops and a "+1" floats up off the card */
  .container-tally-count.tally-pop{animation:tallyPop .5s cubic-bezier(.3,1.6,.5,1);}
  @keyframes tallyPop{0%{transform:scale(1);}45%{transform:scale(1.28);}100%{transform:scale(1);}}
  .tally-float{position:absolute;top:10px;right:12px;font-family:var(--font-mono);font-size:16px;font-weight:800;
    color:var(--green);pointer-events:none;animation:tallyFloat 1.15s ease-out forwards;}
  .tally-float.minus{color:var(--red);}
  @keyframes tallyFloat{0%{opacity:0;transform:translateY(6px);}20%{opacity:1;}100%{opacity:0;transform:translateY(-22px);}}
  @media (prefers-reduced-motion: reduce){
    .container-tally-count.tally-pop, .tally-float{animation:none;}
    .tally-float{display:none;}
  }
  .container-tally-expected{font-size:11px;color:var(--muted);margin-top:4px;}
  .container-tally-minus{position:absolute;top:8px;right:8px;width:24px;height:24px;border-radius:50%;border:1px solid var(--border);background:var(--surface);color:var(--muted);font-size:16px;line-height:1;cursor:pointer;display:flex;align-items:center;justify-content:center;}
  .container-tally-minus:hover{background:var(--red-bg);color:var(--red);border-color:var(--red);}
  .container-tally-bulk-row{display:flex;gap:6px;margin-top:10px;cursor:default;}
  .container-tally-bulk-input{width:0;flex:1;min-width:0;padding:5px 6px;border:1px solid var(--border);border-radius:6px;font-size:12px;text-align:center;font-family:inherit;}
  .container-tally-bulk-btn{font-family:inherit;font-size:11px;font-weight:700;padding:5px 9px;border-radius:6px;border:1px solid var(--border);background:var(--surface);color:var(--navy);cursor:pointer;white-space:nowrap;}
  .container-tally-bulk-btn:hover{background:var(--navy);color:#fff;border-color:var(--navy);}
  .container-summary-table{min-width:0;}
  .container-summary-table td, .container-summary-table th{text-align:right;}
  .container-summary-table td:first-child, .container-summary-table th:first-child{text-align:left;}

  /* ---------- launch splash ----------
     Covers everything (including the status-bar strip) while the app boots, then fades
     itself out. Deliberately short and calm: brand mark settles in, wordmark follows,
     and a hairline bar fills to say something is happening. */
  #splash{position:fixed;inset:0;z-index:2000;background:var(--bg);
    display:flex;align-items:center;justify-content:center;
    transition:opacity .6s ease, visibility .6s;}
  #splash.is-done{opacity:0;visibility:hidden;pointer-events:none;}
  .splash-inner{display:flex;flex-direction:column;align-items:center;gap:18px;
    padding-bottom:6vh;}
  .splash-mark{width:min(72vw,340px);height:auto;object-fit:contain;
    animation:splash-mark 1s cubic-bezier(.22,.9,.3,1) both;}
  .splash-word{display:none;} /* wordmark carries the WMS lettering */
  .splash-word{font-size:20px;font-weight:700;letter-spacing:.42em;margin-right:-.42em;
    color:var(--navy);line-height:1;animation:splash-word .9s cubic-bezier(.22,.9,.3,1) .28s both;}
  .splash-track{width:132px;height:3px;border-radius:3px;background:var(--border);overflow:hidden;
    animation:splash-word .6s ease .5s both;}
  .splash-fill{display:block;height:100%;width:100%;border-radius:3px;background:var(--navy);
    transform-origin:left center;animation:splash-fill 1.9s cubic-bezier(.4,0,.2,1) .55s both;}
  @keyframes splash-mark{from{opacity:0;transform:translateY(10px) scale(.86);}to{opacity:1;transform:none;}}
  @keyframes splash-word{from{opacity:0;transform:translateY(8px);}to{opacity:1;transform:none;}}
  @keyframes splash-fill{from{transform:scaleX(0);}to{transform:scaleX(1);}}

  /* login screen */
  /* Top edge starts BELOW the status-bar inset rather than under it. iOS 26 only
     applies its scroll-edge blur to a scroll container whose content can pass
     beneath the top edge of the display - a scroller that begins below the inset
     never meets that condition, which is why the overflow menu (fixed, non-scrolling,
     clear of the inset) has never been frosted while this screen was. body::before
     still paints the strip itself flat navy above it. */
  #loginScreen{position:fixed;top:env(safe-area-inset-top);left:0;right:0;bottom:0;background:var(--bg);z-index:200;display:flex;align-items:center;justify-content:center;padding:20px;overflow-y:auto;}
  .login-card{background:var(--surface);border:1px solid var(--border);border-radius:16px;box-shadow:var(--shadow);padding:32px 28px;max-width:380px;width:100%;text-align:center;}
  .login-card h2{margin:0 0 6px;font-size:19px;color:var(--navy);}
  .pin-dots{display:flex;gap:14px;justify-content:center;margin:24px 0;}
  .pin-dot{width:16px;height:16px;border-radius:50%;border:2px solid var(--border);background:var(--surface-alt);transition:all .3s ease;}
  .pin-dot.filled{background:var(--navy);border-color:var(--navy);}
  /* wrong PIN: shake the dots so the rejection is felt, not just read */
  .pin-dots.shake{animation:pin-shake .4s ease;}
  .pin-dots.shake .pin-dot{border-color:var(--red);}
  /* "checking…" while the PIN is being verified - pulse the dots, quiet the pad */
  .pin-dots.checking .pin-dot{animation:pinPulse 1s ease-in-out infinite;}
  .pin-dots.checking .pin-dot:nth-child(2){animation-delay:.24s;}
  .pin-dots.checking .pin-dot:nth-child(3){animation-delay:.48s;}
  .pin-dots.checking .pin-dot:nth-child(4){animation-delay:.72s;}
  @keyframes pinPulse{0%,100%{transform:scale(1);opacity:1;}50%{transform:scale(.72);opacity:.55;}}
  .pin-pad-busy{pointer-events:none;opacity:.6;transition:opacity .2s ease;}
  @keyframes pin-shake{
    0%,100%{transform:translateX(0);}
    20%{transform:translateX(-8px);}
    40%{transform:translateX(8px);}
    60%{transform:translateX(-5px);}
    80%{transform:translateX(5px);}
  }
  .pin-pad{display:grid;grid-template-columns:repeat(3,1fr);gap:10px;}
  .pin-pad button{font-family:inherit;font-size:20px;font-weight:600;padding:16px 0;border-radius:10px;border:1px solid var(--border);background:var(--surface-alt);color:var(--ink);cursor:pointer;touch-action:manipulation;}
  .pin-pad button:active{background:var(--border);}
  .pin-pad-blank{visibility:hidden;pointer-events:none;}
  .login-error{margin-top:16px;font-size:13px;color:var(--red);}
  .login-badge-display{display:inline-block;margin:0 auto 14px;}
  /* Version marker on the login screen, so the running build is visible pre-login -
     which is the one moment someone can read it out to you when the app is misbehaving.
     Anchored to the bottom of the screen rather than to a card: it then shows on every
     login card, and a tall one (the PIN pad) can't push it out of view. */
  /* fixed, not absolute: #loginScreen scrolls, and an absolutely positioned child is
     placed against the SCROLLABLE box - so on a phone the badge sat below the fold under
     a tall PIN pad instead of on screen. */
  .version-badge.login-version{position:fixed;z-index:201;left:0;right:0;
    bottom:calc(14px + env(safe-area-inset-bottom,0px));
    width:-moz-max-content;width:max-content;margin:0 auto;text-align:center;
    /* a touch larger than the in-app badge - this one is meant to be read aloud */
    font-size:10.5px;padding:3px 10px;letter-spacing:.4px;}

  /* controls (database tab) */
  .controls{display:flex;align-items:center;justify-content:space-between;gap:12px;margin-bottom:14px;flex-wrap:wrap;}
  #search{flex:1;min-width:200px;padding:9px 12px;border:1px solid var(--border);border-radius:8px;font-size:14px;background:var(--surface);}
  .aisle-filter-select{padding:9px 12px;border:1px solid var(--border);border-radius:8px;font-size:13.5px;font-family:inherit;background:var(--surface);}
  .chips{display:flex;gap:6px;flex-wrap:wrap;}
  .chip{font-family:inherit;font-size:12.5px;font-weight:600;padding:7px 12px;border-radius:20px;border:1px solid var(--border);background:var(--surface);color:var(--muted);cursor:pointer;}
  .chip.active{background:var(--navy);border-color:var(--navy);color:#fff;}

  /* "Group by" control + the header rows it inserts into the database table */
  .group-by-row{display:flex;align-items:center;gap:10px;flex-wrap:wrap;margin:-4px 0 14px;}
  .group-by-label{font-size:11px;text-transform:uppercase;letter-spacing:.04em;color:var(--muted);font-weight:700;}
  tr.group-row td{background:var(--surface-alt);border-top:1px solid var(--border);padding:0;}
  .group-row-btn{width:100%;display:flex;align-items:center;gap:10px;padding:10px 12px;background:none;border:0;font-family:inherit;font-size:13px;font-weight:700;color:var(--navy);cursor:pointer;text-align:left;}
  .group-row-chevron{font-size:10px;color:var(--muted);transition:transform .34s ease;flex:none;}
  .group-row-btn.collapsed .group-row-chevron{transform:rotate(-90deg);}
  .group-row-count{font-weight:600;color:var(--muted);font-size:12px;}
  .group-row-qty{margin-left:auto;font-weight:600;color:var(--muted);font-size:12px;}
  .row-select{width:17px;height:17px;cursor:pointer;accent-color:var(--navy);}
  .row-select-hit{display:inline-flex;align-items:center;justify-content:center;padding:9px;margin:-9px;cursor:pointer;}
  .bulk-actions-bar{display:flex;align-items:center;gap:10px;flex-wrap:wrap;background:var(--surface-alt);border:1px solid var(--border);border-radius:8px;padding:9px 12px;margin-bottom:12px;font-size:13px;font-weight:700;color:var(--navy);}

  /* table */
  .table-wrap{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);box-shadow:var(--shadow);overflow-x:auto;scrollbar-width:thin;}
  /* when a wide table does have to scroll, use the same slim scrollbar as .card-strip
     instead of the browser's chunky default */
  .table-wrap::-webkit-scrollbar{height:6px;width:6px;}
  .table-wrap::-webkit-scrollbar-thumb{background:var(--border);border-radius:3px;}
  /* tables size to their own content (width:100% still fills the panel; .table-wrap
     scrolls if the content genuinely doesn't fit). Only the 13-column Database table
     needs a forced minimum - a blanket 1240px was making 5-column tables like the
     FLT shift log scroll for no reason. */
  table{width:100%;border-collapse:collapse;}
  /* :where() keeps specificity at zero so the phone layout's later `table{min-width:0}`
     still wins on small screens */
  :where(#tab-database) table{min-width:1240px;}
  /* desktop: pin the row-actions column while the wide table scrolls - otherwise
     Edit/Delete sit off-screen by default on ~1280px displays. Phone layout
     re-stacks the table into cards, so scope this to widths that keep the table. */
  @media (min-width:641px){
    #tab-database tbody td:last-child{position:sticky;right:0;background:var(--surface);
      box-shadow:-10px 0 10px -10px rgba(16,24,32,.22);}
    #tab-database thead th:last-child{position:sticky;right:0;z-index:2;background:var(--surface-alt);}
  }
  thead th{position:sticky;top:0;background:var(--surface-alt);text-align:left;padding:12px 14px;font-size:11px;letter-spacing:.6px;text-transform:uppercase;color:var(--muted);border-bottom:1px solid var(--border);white-space:nowrap;}
  thead th button{all:unset;cursor:pointer;display:inline-flex;gap:4px;align-items:center;font:inherit;color:inherit;}
  thead th button:hover{color:var(--navy);}
  tbody td{padding:12px 14px;border-bottom:1px solid var(--border);font-size:14px;vertical-align:middle;}
  /* short, fixed-shape values - letting these wrap turned "06 Feb 2028" into three
     lines and pushed every row to 70px tall */
  tbody td[data-label="Aisle"],
  tbody td[data-label="Date in"],
  tbody td[data-label="BBE"],
  tbody td[data-label="Qty"]{white-space:nowrap;}
  tbody tr:last-child td{border-bottom:none;}
  tbody tr:hover{background:var(--surface-alt);}
  /* location badges match the dark-green style of the Next-pallet cards everywhere
     (also stops iOS painting codes like 1A1 as blue auto-detected links) */
  .loc-badge{font-family:var(--font-mono);font-weight:700;font-size:13px;background:var(--navy);color:#fff;border:1px solid var(--navy);border-radius:6px;padding:3px 8px;letter-spacing:.3px;white-space:nowrap;}
  .mono{font-family:var(--font-mono);font-size:13.5px;color:var(--ink);}
  .muted-cell{color:var(--muted);}
  .notes-cell{max-width:180px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
  .notes-cell.has-info{cursor:pointer;text-decoration:underline dotted var(--muted);text-underline-offset:3px;}
  .pill{display:inline-flex;align-items:center;gap:5px;font-size:12px;font-weight:700;padding:4px 10px;border-radius:20px;white-space:nowrap;}
  .pill.expired{background:var(--red-bg);color:var(--red);}
  .pill.soon{background:var(--amber-bg);color:var(--amber-ink);}
  .pill.ok{background:var(--green-bg);color:var(--green);}
  .pill.unknown{background:var(--surface-alt);color:var(--muted);}
  .pill.loc-reserve{background:var(--surface-alt);color:var(--muted);border:1px solid var(--border);cursor:pointer;}
  .pill.loc-pickface{background:var(--navy);color:#fff;cursor:pointer;}
  .pill-btn{border:none;font:inherit;-webkit-appearance:none;appearance:none;cursor:pointer;}
  .pill-btn:hover{filter:brightness(0.96);}

  /* info popover — tap-to-reveal replacement for hover tooltips */
  .info-popover{position:fixed;z-index:120;max-width:280px;background:var(--navy);color:#fff;padding:10px 14px;border-radius:10px;font-size:13px;line-height:1.45;box-shadow:0 8px 24px rgba(16,24,32,.3);}
  .info-popover.is-hidden{display:none;}
  .row-actions{display:flex;gap:6px;justify-content:flex-end;align-items:center;}
  .icon-btn{border:1px solid var(--border);background:var(--surface);border-radius:7px;width:32px;height:32px;display:inline-flex;align-items:center;justify-content:center;cursor:pointer;color:var(--muted);}
  .icon-btn:hover{color:var(--navy);background:var(--surface-alt);}
  .icon-btn.delete-confirm{background:var(--red);border-color:var(--red);color:#fff;width:auto;padding:0 10px;font-size:12px;font-weight:700;font-family:inherit;}
  .btn.delete-confirm{background:var(--red);border-color:var(--red);color:#fff;}

  .empty-state{padding:60px 20px;text-align:center;color:var(--muted);}
  .empty-state h3{color:var(--ink);margin:0 0 6px;font-size:16px;}
  .empty-state p{margin:0 0 16px;font-size:14px;}

  /* ---------- add-pallet wizard ----------
     Three cards rather than one form. The cards hold no fields of their own - the real
     blocks are moved into .wizard-slot by mountWizard() - so everything here is chrome:
     the progress pips, the card transition, and the one-flavour / mixed choice. */
  /* belt-and-braces with the .modal containment above: pins gesture handling on the
     wizard's own interactive surfaces too, matching every button elsewhere in the app. */
  .wizard, .wizard-viewport, .wizard-card, .wizard-choice-btn, .wizard-loc, .df-sku-btn{touch-action:manipulation;}
  .wizard-pips{display:flex;align-items:center;gap:0;list-style:none;margin:0 0 20px;padding:0;}
  /* the first pip has no connector before it, so it must not claim a flex share or the
     two connectors come out different lengths */
  .wizard-pips li{display:flex;align-items:center;gap:8px;flex:1;min-width:0;color:var(--muted);}
  .wizard-pips li:first-child{flex:0 0 auto;}
  /* the connector between pips, drawn on every item but the first */
  .wizard-pips li + li::before{content:'';height:2px;flex:1;min-width:12px;background:var(--border);
    transition:background .3s ease;}
  .wizard-pips li.is-done + li::before{background:var(--navy);}
  .wizard-pip-dot{flex:none;width:26px;height:26px;border-radius:50%;display:flex;align-items:center;
    justify-content:center;font-family:var(--font-mono);font-size:12px;font-weight:700;
    background:var(--surface-alt);border:1.5px solid var(--border);color:var(--muted);
    transition:background .3s ease, border-color .3s ease, color .3s ease, transform .3s ease;}
  .wizard-pip-label{font-size:11.5px;font-weight:700;letter-spacing:.2px;white-space:nowrap;
    transition:color .3s ease;}
  .wizard-pips li.is-active .wizard-pip-dot{background:var(--navy);border-color:var(--navy);color:#fff;transform:scale(1.08);}
  .wizard-pips li.is-active .wizard-pip-label{color:var(--navy);}
  .wizard-pips li.is-done .wizard-pip-dot{background:var(--green-bg);border-color:var(--green);color:var(--green);}

  .wizard-viewport{position:relative;}
  .wizard-card{display:none;}
  .wizard-card.is-active{display:block;}
  /* forward and back get their own direction, so the stack reads as a sequence rather
     than a set of panels blinking in and out */
  .wizard-card.is-active.slide-fwd{animation:wcard-fwd .52s cubic-bezier(.22,.61,.36,1) both;}
  .wizard-card.is-active.slide-back{animation:wcard-back .52s cubic-bezier(.22,.61,.36,1) both;}
  @keyframes wcard-fwd{from{opacity:0;transform:translateX(26px);}to{opacity:1;transform:none;}}
  @keyframes wcard-back{from{opacity:0;transform:translateX(-26px);}to{opacity:1;transform:none;}}

  .wizard-q{margin:0 0 4px;font-size:19px;color:var(--navy);letter-spacing:-.01em;}
  .wizard-hint{margin:0 0 18px;font-size:13px;color:var(--muted);}
  .wizard-slot:empty{display:none;}
  .wizard-slot{margin-bottom:16px;}
  /* One question per line on a card - the two-up grid the classic form uses is too
     cramped here, and it is what put Flavour and SKU shoulder to shoulder. */
  .wizard-slot .builder-bulk-row{grid-template-columns:1fr;gap:0;}
  /* the fields that are looked up or preset don't need the full width */
  .wizard-slot #build-sku{max-width:220px;}
  .wizard-slot #build-unitqty{max-width:160px;}
  .wizard-slot #build-bbe, .wizard-slot #build-batch{max-width:240px;}

  /* the location code assembling itself as the selects are filled in */
  .wizard-loc{display:flex;align-items:center;justify-content:center;gap:2px;min-height:74px;
    font-family:var(--font-mono);font-size:40px;font-weight:700;color:var(--navy);
    background:var(--surface-alt);border:1px dashed var(--border);border-radius:12px;
    letter-spacing:.06em;}
  .wizard-loc .wizard-loc-part{display:inline-block;animation:rise-in .3s ease-out backwards;}
  .wizard-loc .wizard-loc-part.is-blank{color:var(--border);}
  .wizard-loc-empty{font-family:var(--font-body);font-size:13px;font-weight:600;color:var(--muted);letter-spacing:0;}

  .wizard-choice{display:grid;grid-template-columns:1fr 1fr;gap:10px;margin-bottom:18px;}
  .wizard-choice-btn{display:flex;flex-direction:column;gap:4px;text-align:left;font-family:inherit;
    background:var(--surface-alt);border:1.5px solid var(--border);border-radius:12px;padding:14px;
    cursor:pointer;transition:border-color .2s ease, background .2s ease, transform .09s ease;}
  .wizard-choice-btn:active{transform:scale(.99);}
  .wizard-choice-btn strong{font-size:14.5px;color:var(--navy);}
  .wizard-choice-btn span{font-size:11.5px;color:var(--muted);line-height:1.35;}
  .wizard-choice-btn.is-chosen{border-color:var(--navy);background:var(--surface);
    box-shadow:0 0 0 3px rgba(21,56,42,.08);}
  .wizard-reveal{animation:rise-in .34s ease-out backwards;}

  /* the read-back before committing: what is about to go into the slot */
  .wizard-review{margin-top:6px;background:var(--surface-alt);border:1px solid var(--border);
    border-radius:12px;padding:14px 16px;}
  .wizard-review-row{display:flex;align-items:baseline;justify-content:space-between;gap:14px;
    padding:7px 0;border-bottom:1px solid var(--border);font-size:13.5px;}
  .wizard-review-row:last-child{border-bottom:none;}
  .wizard-review-row dt{color:var(--muted);font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:.4px;}
  .wizard-review-row dd{margin:0;font-weight:700;color:var(--navy);text-align:right;}
  .wizard-review-row dd.is-empty{color:var(--muted);font-weight:600;}

  @media (max-width:640px){
    /* Belt-and-braces with the viewport maximum-scale=1: iOS only auto-zooms a
       focused control whose font is under 16px, so no text-entry control gets one.
       !important on purpose - it must beat every per-control font-size. */
    input[type="text"], input[type="number"], input[type="date"], input[type="search"],
    input[type="password"], input[type="email"], input[type="tel"], select, textarea{
      font-size:16px !important;
    }
    .wizard-pips{margin-bottom:16px;}
    .wizard-pip-label{display:none;}
    .wizard-pips li{flex:1;}
    .wizard-pips li + li::before{min-width:0;}
    .wizard-q{font-size:17px;}
    .wizard-loc{font-size:34px;min-height:64px;}
    .wizard-choice{grid-template-columns:1fr;}
  }

  /* modal */
  .modal-overlay{position:fixed;inset:0;background:rgba(21,26,33,.45);align-items:center;justify-content:center;padding:20px;z-index:50;display:none;}
  .modal-overlay.is-open{display:flex;}
  /* overscroll-behavior:contain (not just -x:none) and touch-action:manipulation
     stop the wizards inside a modal (Add Pallet, Dry Food) from letting a scroll
     or gesture bleed into pinch-zooming/panning the page behind them - the same
     containment every other locked screen already has, just missing here. */
  .modal{background:var(--surface);border-radius:14px;padding:24px;width:100%;max-width:440px;box-shadow:0 20px 60px rgba(0,0,0,.25);max-height:90vh;overflow-y:auto;overflow-x:hidden;overscroll-behavior:contain;touch-action:manipulation;}
  .modal.modal-wide{max-width:640px;}
  .modal.modal-xl{max-width:1200px;}
  .pallet-setup-row{display:grid;grid-template-columns:repeat(4,1fr);gap:12px;align-items:end;}
  .pallet-setup-row .checkbox-field{grid-column:1/-1;padding-bottom:11px;}
  .pallet-setup-row.container-setup-row{grid-template-columns:1.4fr 1fr auto;}
  .pallet-setup-row.container-setup-row .checkbox-field{grid-column:auto;}
  .modal-head{display:flex;align-items:center;justify-content:space-between;margin-bottom:16px;}
  .modal-head h2{margin:0;font-size:17px;color:var(--navy);}
  .modal-close{all:unset;cursor:pointer;font-size:24px;line-height:1;color:var(--muted);padding:2px 8px;border-radius:6px;}
  .modal-close:hover{background:var(--surface-alt);color:var(--ink);}
  .field{margin-bottom:14px;}
  .field label{display:block;font-size:12.5px;font-weight:600;color:var(--muted);margin-bottom:5px;}
  .field input, .field select, .field textarea{width:100%;padding:9px 11px;border:1px solid var(--border);border-radius:8px;font-size:14px;font-family:inherit;background:var(--surface-alt);}
  /* Native date/time pickers: iOS Safari gives these an intrinsic width that
     ignores width:100% and pokes out past the card edge. appearance:none makes
     them lay out like ordinary text fields; they keep the .field input skin. */
  input[type="date"], input[type="datetime-local"], input[type="time"]{
    -webkit-appearance:none;appearance:none;display:block;
    min-width:0;max-width:100%;text-align:left;min-height:42px;
  }
  .field textarea{resize:vertical;min-height:56px;}
  .field input:focus, .field select:focus, .field textarea:focus{background:var(--surface);}
  .field-row{display:grid;grid-template-columns:1fr 1fr;gap:12px;}
  /* Dry Food / Cardboard Bale forms: two columns on desktop, one on a phone */
  .form-grid{display:grid;grid-template-columns:1fr 1fr;gap:0 14px;}
  .form-grid > *{min-width:0;}

  /* ---------- Dry Food 4-step wizard ---------- */
  .df-pips{list-style:none;display:flex;gap:6px;margin:0 0 16px;padding:0;}
  .df-pips li{flex:1;height:4px;border-radius:2px;background:var(--border);transition:background .25s ease;}
  .df-pips li.active{background:var(--navy);}
  .df-pips li.done{background:var(--navy-light);}
  .df-sku-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(150px,1fr));gap:10px;}
  .df-sku-btn{display:flex;flex-direction:column;gap:2px;align-items:flex-start;text-align:left;
    font-family:inherit;padding:12px;border:1.5px solid var(--border);border-radius:12px;
    background:var(--surface-alt);cursor:pointer;transition:transform .1s ease,border-color .15s ease,background .15s ease;}
  .df-sku-btn:active{transform:scale(.97);}
  .df-sku-btn.selected{border-color:var(--navy);background:var(--green-bg);}
  .df-sku-btn:focus-visible{outline:2px solid var(--navy-light);outline-offset:2px;}
  .df-sku-name{font-weight:700;font-size:14px;color:var(--ink);}
  .df-sku-size{font-size:12px;font-weight:600;color:var(--navy-light);}
  .df-sku-sample .df-sku-size{color:var(--amber-ink);}
  .df-sku-code{font-family:var(--font-mono);font-size:10.5px;color:var(--muted);}
  .df-chosen{text-align:center;font-size:14px;color:var(--muted);margin-bottom:14px;}
  .df-chosen .mono{font-family:var(--font-mono);font-size:12px;}
  .df-big-label{display:block;text-align:center;font-weight:700;font-size:16px;color:var(--ink);margin-bottom:10px;}
  .df-qty-row{display:flex;gap:10px;justify-content:center;align-items:stretch;max-width:320px;margin:0 auto;}
  .df-qty-row input{width:110px;text-align:center;font-size:26px !important;font-weight:800;font-family:var(--font-mono);
    padding:10px;border:1.5px solid var(--border);border-radius:12px;background:var(--surface-alt);}
  .df-qty-btn{width:56px;font-size:24px;font-weight:700;border-radius:12px;}
  .df-clean-row{display:flex;gap:10px;}
  .df-clean-btn{flex:1;font-size:15px;padding:14px;border-radius:12px;border-width:1.5px;}
  .df-clean-btn.selected{border-color:var(--navy);background:var(--green-bg);color:var(--navy);font-weight:800;}
  #dfCleanNo.selected{border-color:var(--red);background:var(--red-bg);color:var(--red);}
  .df-review{border:1px solid var(--border);border-radius:12px;background:var(--surface-alt);padding:4px 14px;}
  .df-review-row{display:flex;justify-content:space-between;gap:12px;padding:10px 0;border-top:1px solid var(--border);font-size:14px;}
  .df-review-row:first-child{border-top:none;}
  .df-review-row span{color:var(--muted);}
  .df-review-row strong{text-align:right;}
  .df-review-warn strong{color:var(--red);}
  /* log cards: collapsed = flavour / qty / date; tap to expand full details */
  .df-log-list{display:flex;flex-direction:column;gap:8px;}
  .df-log-card{border:1px solid var(--border);border-radius:12px;background:var(--surface-alt);
    padding:12px 14px;cursor:pointer;-webkit-user-select:none;user-select:none;}
  .df-log-card:focus-visible{outline:2px solid var(--navy-light);outline-offset:2px;}
  .df-log-top{display:flex;align-items:baseline;gap:8px;flex-wrap:wrap;font-size:14px;}
  .df-log-top strong{font-size:15px;color:var(--ink);}
  .df-log-chevron{color:var(--muted);font-size:11px;flex:none;}
  .df-log-qty{font-family:var(--font-mono);font-weight:700;color:var(--navy-light);}
  .df-log-date{margin-left:auto;font-family:var(--font-mono);font-size:11.5px;color:var(--muted);white-space:nowrap;}
  .df-log-details{display:none;margin-top:8px;border-top:1px solid var(--border);padding-top:2px;}
  .df-log-card.expanded .df-log-details{display:block;animation:fltEventsIn .45s ease;}
  /* bale cards: no expand behaviour, everything inline plus the collect button */
  .bale-card{cursor:default;}
  .bale-card-notes{font-size:13px;color:var(--ink);margin-top:6px;}
  .bale-card-row{display:flex;justify-content:space-between;align-items:center;gap:10px;margin-top:8px;font-size:12.5px;}
  .bale-collected{opacity:.75;}

  /* Packer request grid: photo tiles, tap to select */
  .rk-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(104px,1fr));gap:8px;max-height:44vh;overflow-y:auto;
    padding:2px;border:1px solid var(--border);border-radius:12px;background:var(--surface-alt);}
  .rk-tile{display:flex;flex-direction:column;align-items:center;gap:4px;text-align:center;font-family:inherit;
    padding:10px 6px;border:1.5px solid transparent;border-radius:10px;background:var(--surface);cursor:pointer;
    transition:transform .12s ease,border-color .2s ease,background .2s ease;}
  .rk-tile:active{transform:scale(.96);}
  .rk-tile.selected{border-color:var(--navy);background:var(--green-bg);}
  .rk-tile:focus-visible{outline:2px solid var(--navy-light);outline-offset:2px;}
  .rk-tile-name{font-weight:700;font-size:12px;color:var(--ink);line-height:1.2;}
  .rk-tile-type{font-size:10px;font-weight:600;color:var(--muted);text-transform:uppercase;letter-spacing:.04em;}
  .rk-selected-bar{margin-top:12px;border:1px solid var(--border);border-radius:12px;background:var(--surface-alt);
    padding:12px;animation:contentIn .55s ease;}
  /* status pill pinned to the card's right edge, everything on one line */
  .rk-top{flex-wrap:nowrap;align-items:center;}
  .rk-top strong{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
  .rk-status{flex:none;margin-left:auto;}
  .rk-status-row{display:flex;gap:8px;flex-wrap:wrap;margin-bottom:12px;}
  .rk-status-row{align-items:center;}
  .rk-tile{position:relative;}
  .rk-tile-count{position:absolute;top:4px;right:4px;min-width:20px;height:20px;border-radius:10px;
    background:var(--navy);color:#fff;font-size:11px;font-weight:800;display:flex;align-items:center;
    justify-content:center;padding:0 5px;}
  /* admin-disabled flavour: stays visible (per "packers can see it's unavailable"),
     just dimmed and non-interactive, with the admin's note replacing the type tag */
  .rk-tile-lock{display:none;position:absolute;top:4px;left:4px;font-size:12px;line-height:1;}
  .rk-tile.rk-tile-locked{opacity:.55;cursor:not-allowed;}
  .rk-tile.rk-tile-locked:active{transform:none;}
  .rk-tile.rk-tile-locked .rk-tile-lock{display:block;}
  .rk-tile.rk-tile-locked .rk-tile-type{display:none;}
  .rk-tile-lock-note{font-size:9.5px;font-weight:600;color:var(--muted);line-height:1.2;}
  .rk-cart-head{display:flex;align-items:center;justify-content:space-between;gap:10px;margin-bottom:8px;}
  .rk-cart-row{display:flex;align-items:center;justify-content:space-between;gap:10px;padding:7px 0;
    border-top:1px solid var(--border);}
  .rk-cart-row:first-child{border-top:none;}
  .rk-cart-name{flex:1;min-width:0;font-weight:600;font-size:13.5px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
  .rk-cart-controls{display:flex;align-items:center;gap:6px;flex:none;}
  /* gloves-on tap targets for the qty steppers and request cancel */
  .rk-cart-controls .btn{min-width:44px;min-height:40px;}
  .rk-cancel-btn{min-height:40px;}
  .rk-cart-qty{font-family:var(--font-mono);font-weight:800;min-width:24px;text-align:center;}
  .rk-items{flex:1;min-width:0;display:flex;flex-wrap:wrap;gap:4px 10px;align-items:baseline;}
  /* max-width + ellipsis: a single long flavour must shorten, not slide under
     the status pill (.rk-top is deliberately one-line) */
  .rk-item{font-weight:700;font-size:14px;white-space:nowrap;max-width:100%;overflow:hidden;text-overflow:ellipsis;}
  /* "Your job" checklist - the claimed request, front and centre */
  .rk-job-card{border:2px solid var(--navy);border-radius:14px;background:var(--surface-alt);padding:14px;}
  .rk-job-card + .rk-job-card{margin-top:10px;}
  .rk-job-head{display:flex;align-items:baseline;justify-content:space-between;gap:10px;margin-bottom:10px;flex-wrap:wrap;}
  .rk-job-progress{font-weight:800;font-size:15px;color:var(--navy);}
  .rk-job-progress.done{color:var(--green);}
  .rk-checklist{display:flex;flex-direction:column;gap:6px;margin-bottom:12px;}
  .rk-check-row{display:flex;align-items:center;gap:10px;width:100%;text-align:left;font-family:inherit;
    padding:10px 12px;border:1px solid var(--border);border-radius:10px;background:var(--surface);cursor:pointer;
    transition:background .3s ease,border-color .3s ease;}
  .rk-check-row:focus-visible{outline:2px solid var(--navy-light);outline-offset:2px;}
  .rk-check-row.ticked{background:var(--green-bg);border-color:var(--green);}
  .rk-check-row.ticked .rk-check-name{text-decoration:line-through;color:var(--muted);}
  .rk-check-circle{width:24px;height:24px;flex:none;border-radius:50%;border:2px solid var(--border);
    display:flex;align-items:center;justify-content:center;font-weight:800;color:#fff;background:var(--surface);
    transition:background .3s ease,border-color .3s ease;}
  .rk-check-row.ticked .rk-check-circle{background:var(--green);border-color:var(--green);animation:tallyPop .5s cubic-bezier(.3,1.6,.5,1);}
  .rk-check-name{flex:1;min-width:0;font-weight:700;font-size:14.5px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
  /* small phones: let checklist names wrap to two lines - ellipsis alone turns
     '(P) Chicken with Goats Milk' and '(P) Chicken with Liver...' into twins */
  @media (max-width:360px){ .rk-check-name{white-space:normal;} }

  /* admin-flagged priority (v3.65): a red accent so it reads as urgent at a
     glance, distinct from the navy "your job" / amber "waiting" language already
     in use elsewhere on this screen */
  .rk-job-card.rk-priority{border-color:var(--red);}
  .df-log-card.rk-priority{border-color:var(--red);border-width:2px;}
  .rk-priority-badge{flex:none;}
  .rk-queue-actions{display:flex;align-items:center;gap:8px;flex-wrap:wrap;justify-content:flex-end;}

  /* Restock control (admin, v3.65): one row per flavour, grouped like the
     packer's own picker so the two screens read as the same catalogue. */
  .rfs-group + .rfs-group{margin-top:18px;}
  .rfs-group-label{font-size:11px;text-transform:uppercase;letter-spacing:.06em;color:var(--muted);
    font-weight:800;margin-bottom:8px;padding-bottom:4px;border-bottom:1px solid var(--border);}
  .rfs-row{padding:10px 0;border-bottom:1px solid var(--border);}
  .rfs-row:last-child{border-bottom:none;}
  .rfs-row-main{display:flex;align-items:center;gap:10px;}
  .rfs-name{flex:1;min-width:0;font-weight:700;font-size:14px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
  .rfs-toggle{flex:none;font-family:inherit;border:none;cursor:pointer;min-height:32px;}
  .rfs-note-field{margin:8px 0 0 40px;}
  @media (max-width:480px){ .rfs-note-field{margin-left:0;} }
  /* Restock tracker: range switch in the panel head, compact stat cards,
     most-restocked leaderboard bars, day-grouped history */
  .rk-range-seg{margin:6px 0 0;}
  /* chunkier than the Search page's seg: this is a primary control tapped with
     warehouse gloves, so keep each button ~40px tall */
  .rk-range-seg .seg-btn{padding:13px 16px;}
  .rk-stats{margin:4px 0 14px;}
  .rk-stats .stat-card{min-width:118px;padding:12px 16px;}
  .rk-stats .stat-num{font-size:22px;}
  .rk-section-label{font-size:11px;text-transform:uppercase;letter-spacing:.06em;color:var(--muted);font-weight:800;margin-bottom:8px;}
  .rk-top-flavours{margin-bottom:14px;}
  .rk-top-row{display:flex;align-items:center;gap:10px;padding:5px 0;}
  /* fixed name column so every row's bar track starts at the same x — bars are
     only comparable if the tracks align */
  .rk-top-name{flex:0 0 34%;min-width:0;font-weight:700;font-size:13px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
  .rk-top-type{font-size:10px;font-weight:600;color:var(--muted);text-transform:uppercase;letter-spacing:.04em;}
  .rk-top-track{flex:1;min-width:40px;height:8px;border-radius:4px;background:var(--surface-alt);border:1px solid var(--border);overflow:hidden;}
  /* display:block matters: a bare span is inline and silently ignores width/height */
  .rk-top-fill{display:block;height:100%;border-radius:4px;background:var(--navy-light);transition:width .5s ease;}
  .rk-top-count{flex:none;font-family:var(--font-mono);font-size:12px;font-weight:700;color:var(--navy);white-space:nowrap;}
  .rk-day-head{margin:10px 0 2px;font-size:12px;font-weight:800;color:var(--navy);display:flex;align-items:baseline;gap:8px;}
  .rk-day-head:first-child{margin-top:0;}
  .rk-day-count{font-weight:600;color:var(--muted);font-size:11.5px;}
  @media (max-width:640px){ .form-grid{grid-template-columns:1fr;} }
  .field input.field-invalid, .field select.field-invalid{border-color:var(--red);background:var(--red-bg);}
  .field.checkbox-field label{display:flex;align-items:center;gap:8px;font-size:13.5px;font-weight:500;color:var(--ink);cursor:pointer;}
  .field.checkbox-field input{width:auto;margin:0;}
  .modal-actions{display:flex;justify-content:flex-end;gap:8px;margin-top:6px;}
  /* was an inline style="margin-top:20px", which outranked the mobile rule that
     pushes this bar to the bottom of a short modal */
  .modal-actions-spaced{margin-top:20px;}

  .toast{position:fixed;bottom:24px;left:50%;transform:translateX(-50%);background:var(--navy);color:#fff;padding:11px 18px;border-radius:8px;font-size:13.5px;font-weight:600;box-shadow:var(--shadow);z-index:100;display:flex;align-items:center;gap:12px;max-width:calc(100vw - 32px);animation:toast-in .42s ease-out;}
  .toast[hidden]{display:none;}
  @keyframes toast-in{from{opacity:0;transform:translate(-50%,8px);}to{opacity:1;transform:translate(-50%,0);}}
  /* inline-flex so the countdown ring centres against the label instead of
     sitting on the text baseline (which clipped it against the toast edge) */
  .toast-undo-btn{display:inline-flex;align-items:center;gap:6px;line-height:1;background:none;border:none;color:#8FE3D8;font:inherit;font-weight:800;cursor:pointer;padding:2px 0;flex:none;text-transform:uppercase;font-size:12px;letter-spacing:.3px;}

  /* location label printing - #printLabelArea stays empty/hidden on screen; when
     printPalletLabel() fills it and calls window.print(), body.printing-label hides
     everything else so only the label goes to the printer */
  #printLabelArea{display:none;}
  /* 4x4in (101.6mm) thermal label stock. The app's only print path is this label,
     so the page size is set globally rather than via a named page - printing any
     other view isn't a supported flow. */
  @page{size:101.6mm 101.6mm;margin:0;}

  @media print{
    body.printing-label > *:not(#printLabelArea){display:none !important;}
    body.printing-label #printLabelArea{display:block !important;}
    /* Thermal printheads are monochrome and struggle with large solid-black fills
       (slow, prone to smearing, and hard on the head), so this is heavy black on
       bare white rather than the reversed-out block the old label used. */
    .print-label{
      width:101.6mm;height:101.6mm;box-sizing:border-box;padding:2.3mm;
      display:flex;flex-direction:column;overflow:hidden;
      background:#fff;color:#000;
      font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Arial,sans-serif;
      page-break-after:always;
    }
    /* small print, kept up top - it's reference detail, not what you read from a
       distance. No location: pallets get relocated, so a printed one goes stale. */
    .print-label-info{flex:none;padding-bottom:1.5mm;border-bottom:0.4mm solid #000;}
    .print-label-flavour{font-size:4.6mm;font-weight:700;line-height:1.1;}
    .print-label-meta{display:flex;justify-content:space-between;gap:2mm;
      font-size:3.2mm;line-height:1.2;margin-top:1mm;}
    .print-label-meta strong{font-weight:800;}
    /* The hero. Takes every remaining millimetre and is centred in it; the font size
       itself is set inline per label by fitLabelCodeMm() so the code spans the full
       width whatever its characters happen to be. */
    .print-label-code{
      flex:1 1 auto;display:flex;align-items:center;justify-content:center;
      text-align:center;font-weight:900;line-height:.85;letter-spacing:-0.02em;
      overflow:hidden;
    }
  }

  .scan-field-row{display:flex;gap:6px;align-items:stretch;}
  .scan-field-row input{flex:1;min-width:0;}
  .scan-field-row .btn{flex:none;padding:0 12px;}
  .barcode-scanner-video{width:100%;max-height:60vh;border-radius:10px;background:#000;display:block;}

  .builder-grid-wrap{overflow-x:auto;}

  @media (max-width:640px){
    /* App shell: the page never scrolls - the header, hazard strip and tab bar sit
       in fixed flex slots and only the ACTIVE TAB PANEL scrolls internally. This is
       what keeps the chrome pinned through iOS rubber-band overscroll and the
       in-call status bar (position:fixed bars drifted in both). */
    html, body{height:100dvh;overflow:hidden;}
    /* The band below the shell on an installed iOS app is outside the body box, so no
       element can reach it - it is painted from the ROOT element's background. Colouring
       that to match whatever is at the bottom of the screen (the white nav bar, a white
       modal sheet, the green locked-mode bar, the cream PIN screen) is the only thing
       that covers it. setCanvasColor() keeps --canvas in step. Phone only: on desktop a
       page shorter than the viewport would show this below the body box. */
    html{background:var(--canvas, var(--bg));}
    body{padding-bottom:0;}
    /* position:fixed inset:0 rather than height:100dvh. On an installed iOS app dvh came
       up short of the real viewport, leaving a dead band of page background below the tab
       bar; a fixed shell is measured against the same box body::before and the overflow
       menu are anchored to, so the bar actually hugs the bottom edge. Nothing scrolls at
       page level here (html/body are overflow:hidden), so there is no rubber-band for a
       fixed shell to drift against - this is the same construction as #loginScreen.
       The top padding keeps content clear of the status-bar strip. */
    /* Height comes from a MEASURED number (--vvh = window.innerHeight, set by
       syncViewportMetrics()), not a CSS viewport length. 100dvh measured short of the
       real viewport on an installed iOS app, and bottom:0 resolves against whatever
       containing block happens to be in play - a measured pixel value is neither. The
       fallbacks keep it sane before the first measurement and on anything without JS. */
    .app{position:fixed;top:0;left:0;right:0;height:var(--vvh, 100dvh);max-width:none;
      display:flex;flex-direction:column;min-height:0;padding:env(safe-area-inset-top) 10px 0;}
    .topbar, .hazard-strip{flex:none;}
    /* No top bar on a phone at all - everything it held moved into the bottom bar's
       Modes and More pages. It comes back only in a locked mode, where it is the one
       place the Exit button can live; there it paints its own inset, so the shell drops
       the padding to avoid insetting twice. */
    .topbar{display:none;}
    /* A locked mode brings the bar back, but at the BOTTOM - in the same slot the nav
       bar occupies (order:99), where a thumb is anyway. Keeping it off the top edge is
       also what finally settles the blur: iOS lightens whatever it samples up there, so
       a flat green band came back sage while flat cream comes back cream. Nothing green
       is painted at the top of a phone screen any more. */
    /* same reasoning as .tabs: page colour, so the system strip below continues it */
    .app.locked-mode .topbar{display:flex;order:99;flex:none;align-items:center;
      justify-content:space-between;min-height:0;margin:0 -10px;gap:8px;
      padding:8px 12px var(--bar-gap, max(20px, env(safe-area-inset-bottom,0px)));
      background:var(--bg);border-top:1px solid var(--border);}
    /* the mark is dark green again now the bar behind it is cream */
    .app.locked-mode .brand-mark{filter:none;}
    .app.locked-mode .user-badge{color:var(--navy);background:var(--surface-alt);border-color:var(--border);}
    .app.locked-mode .user-badge-icon{color:var(--muted);}
    /* just the mark in a locked mode - the wordmark and build badge are a luxury here */
    .app.locked-mode .brand-text{display:none;}
    .app.locked-mode .brand-mark{width:auto;height:22px;max-width:120px;}
    .app.locked-mode .topbar-actions{gap:8px;}
    .app.locked-mode .topbar-actions > *{height:36px;}
    .app.locked-mode .topbar-actions .btn{padding:0 14px;font-size:13px;}
    .app.locked-mode .user-badge{font-size:11px;padding:0 10px;}
    /* the panel is the top of the screen now that there's no header, so it needs a
       little breathing room of its own above the first card */
    /* overflow-x hidden: with overflow-y auto, a single too-wide child would
       otherwise make the whole page pannable side to side on iOS */
    /* v3.65 briefly added --bar-gap to this padding too, theorising the last card
       in a locked-mode tab was clipped by the home-indicator. Wrong: the locked-mode
       topbar below this panel (order:99, see below) is a normal flex sibling, not an
       overlay - it already reserves --bar-gap for ITSELF. Stacking it here too just
       left a dead gap between the last card and that bar (real user report,
       2026-08-29) with no clipping actually fixed. Back to a flat value. */
    .tab-panel.active{flex:1 1 auto;min-height:0;overflow-y:auto;overflow-x:hidden;-webkit-overflow-scrolling:touch;
      overscroll-behavior:contain;padding-top:10px;padding-bottom:14px;}
    /* one-line topbar: compact brand on the left, all the buttons beside it.
       The user badge is the flexible piece - its name ellipsises before
       anything is pushed to a second line. */
    /* keep the status-bar inset the base rule sets - without it the header slides
       under the iOS status bar and gets frosted by its blur effect */
    /* Solid brand-green header band. iOS 26 frosts the top edge of the app and no
       padding escapes it (the falloff reaches ~90pt down), so instead the whole zone
       is one flat colour - blurring a flat colour is invisible - with the controls
       bottom-aligned low enough in the band to stay clear of the falloff.
       Deliberately NO z-index/isolation here: giving .topbar its own stacking context
       traps the overflow menu beneath its backdrop. */
    .topbar{padding:calc(10px + env(safe-area-inset-top)) 12px 10px;gap:8px;flex-wrap:nowrap;
      background:var(--navy);align-items:flex-end;min-height:88px;margin:0 -10px;}
    .brand h1{color:#fff;}
    /* the wordmark logo is dark green - flip it to white for the green band */
    .brand-mark{filter:brightness(0) invert(1);}
    /* scoped to .topbar: unscoped, these painted the More-page and login badges
       white-on-cream (invisible) - the white treatment only makes sense on the
       green band, which no longer exists outside a locked mode anyway */
    .topbar .version-badge{color:rgba(255,255,255,.85);background:rgba(255,255,255,.14);border-color:rgba(255,255,255,.28);}
    .topbar .user-badge{color:#fff;background:rgba(255,255,255,.14);border-color:rgba(255,255,255,.28);}
    .topbar .user-badge-icon{color:rgba(255,255,255,.72);}
    .topbar-actions .btn-primary{background:#fff;color:var(--navy);}
    .topbar-menu-toggle{background:rgba(255,255,255,.14);border-color:rgba(255,255,255,.3);color:#fff;}
    /* the band replaces the strip as the header's bottom edge */
    .hazard-strip{display:none;}
    .brand{gap:8px;flex:none;}
    .brand-mark{width:auto;height:26px;max-width:140px;}
    .brand h1{font-size:15px;}
    .version-badge{font-size:8px;padding:1px 4px;margin-top:2px;}
    .topbar-actions{gap:6px;flex-wrap:nowrap;min-width:0;justify-content:flex-end;}
    .user-badge{font-size:11.5px;padding:6px 8px;min-width:0;}
    .user-badge span{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
    .btn{padding:9px 12px;font-size:13px;}

    /* dropdown behaviour now lives in the base rules - nothing extra needed here */
    .btn-big{font-size:15px;padding:13px 18px;}
    .hazard-strip{margin-bottom:10px;}

    .stat-card{min-width:132px;padding:14px 16px;}
    .stat-num{font-size:24px;}

    /* bottom nav bar on phone - easier one-handed reach than a top strip. Lives in
       the shell's bottom flex slot (order:99), NOT position:fixed - so overscroll
       and viewport shifts can't move it. */
    /* Five fixed destinations, so the bar divides the width evenly instead of scrolling.
       Forecast / Logs / Accounts are reached from More here, and Archived from Search. */
    /* Snug system-style bar hugging the bottom of the layout viewport.
       Two things make it fit any phone rather than one:
       - the clearance under the buttons is --bar-gap, decided at runtime: 6px where the
         phone already keeps a strip below the page (that strip is the clearance), or
         max(20px, home-indicator inset) where the page runs to the bottom of the display
       - the background is the PAGE colour, not a raised white. The strip the phone keeps
         is painted by the system from the manifest's background_color and no element can
         reach it, so the only way it stops reading as a gap is for the bar above it to be
         the same colour. The top border is what separates the bar from the content. */
    .tabs{position:static;order:99;flex:none;margin:0 -10px;flex-wrap:nowrap;overflow:visible;
      position:relative;border-bottom:none;border-top:none;background:var(--bg);
      padding:6px 6px var(--bar-gap, max(20px, env(safe-area-inset-bottom,0px)));gap:2px;z-index:40;}
    /* No hard rule and no filled block - iOS stopped drawing tab bars as a solid band and
       lets content dissolve into them instead. This is that: a short gradient sitting
       directly above the bar, so the last row of content fades out rather than being cut
       off by a line. */
    .tabs::before{content:'';position:absolute;left:0;right:0;bottom:100%;height:20px;
      background:linear-gradient(to top, var(--bg), rgba(248,235,206,0));pointer-events:none;}
    .tab-btn{position:relative;flex:1 1 0;min-width:0;display:flex;flex-direction:column;align-items:center;
      justify-content:center;gap:3px;padding:5px 2px;min-height:48px;border-radius:12px;
      border-bottom:none;margin-bottom:0;font-size:9.5px;font-weight:700;letter-spacing:.1px;
      line-height:1.2;white-space:nowrap;color:var(--muted);}
    .tab-icon{display:block;width:23px;height:23px;flex:none;position:relative;z-index:1;}
    .tab-label{position:relative;z-index:1;}
    .tab-btn.active{background:transparent;color:var(--navy);border-bottom-color:transparent;}
    .tab-btn.active .tab-icon{stroke-width:2.3;}
    /* the selected state is a pill behind the ICON only, not a block across the button */
    .tab-btn.active::before{content:'';position:absolute;left:50%;top:2px;width:46px;height:30px;
      margin-left:-23px;border-radius:11px;background:rgba(21,56,42,.09);
      animation:rise-in .3s ease-out backwards;}
    .tab-btn-phone{display:flex;}
    .tab-btn-desktop{display:none;}
    .subpage-back{display:inline-flex;align-items:center;}
    .fab{display:flex;}
    .app.locked-mode .fab{display:none;}
    /* room for the last card to scroll clear of the floating Add-pallet button -
       without this the FAB permanently covers the final card's action button */
    #tab-dashboard.active, #tab-database.active{
      padding-bottom:calc(140px + var(--bar-gap, env(safe-area-inset-bottom,0px)));}

    .panel{padding:12px;margin-bottom:12px;border-radius:8px;}
    .flavour-grid{grid-template-columns:1fr 1fr;gap:8px;}
    .builds-grid{grid-template-columns:1fr;}
    .builder-setup-row{grid-template-columns:1fr 1fr;gap:8px;}
    .builder-bulk-row{grid-template-columns:1fr 1fr;}
    .pallet-setup-row{grid-template-columns:1fr 1fr;gap:8px;}
    .pallet-setup-row .checkbox-field{grid-column:1/-1;padding-bottom:0;}
    .pallet-setup-row.container-setup-row{grid-template-columns:1fr 1fr;}

    .controls{flex-direction:column;align-items:stretch;}
    /* The aisle dropdown and the sort row say the same thing as the Group by and
       freshness chips directly beneath them, so on a phone they are just two more bars to
       scroll past. Both stay on desktop, where there is room and no chips row. */
    #aisleFilter, .mobile-sort-row{display:none;}
    .field-row{grid-template-columns:1fr;}

    /* tables become stacked label:value cards on phone - no horizontal scrolling */
    .table-wrap{margin:0;width:100%;border-radius:0;border:none;box-shadow:none;background:transparent;overflow-x:visible;}
    table{width:100%;min-width:0;border-collapse:separate;border-spacing:0;}
    table thead{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;}
    table tbody{display:block;}
    table tr{display:block;background:var(--surface);border:1px solid var(--border);border-radius:10px;box-shadow:var(--shadow);padding:10px 12px;margin-bottom:8px;}
    table td{display:block;padding:5px 0;border:none;font-size:13.5px;}
    table td::before{content:attr(data-label);display:block;font-size:9.5px;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.3px;margin-bottom:2px;}
    table td:not([data-label])::before, table td[data-label=""]::before{content:none;display:none;margin:0;}
    table td:first-child{font-weight:700;padding-top:0;}
    table tr td[colspan]{text-align:center;}
    .notes-cell{max-width:none;white-space:normal;overflow:visible;text-overflow:clip;}
    .row-actions{gap:8px;}
    .icon-btn{width:40px;height:40px;}

    /* thumb-friendly minimum tap targets */
    .chip{padding:10px 14px;font-size:13px;}
    /* the aisle-zoom chips share their row with the count pill - keep them slim
       enough that the pill doesn't wrap onto a second line */
    .map-controls-row .chip{padding:8px 11px;font-size:12px;}
    .loc-badge.pill-btn, .loc-badge-mid.pill-btn{min-height:36px;display:inline-flex;align-items:center;padding:6px 10px;}
    #forecastFileInput{min-height:44px;width:100%;padding:11px 12px;border:1px solid var(--border);border-radius:8px;background:var(--surface);font-size:13px;}

    /* database tab cards - extra compact since there can be hundreds of pallets.
       column order is Location,Aisle,Flavour,Batch,Container,Qty,Date in,BBE,Freshness,
       Pick face,Notes,Actions - Aisle/Container/Date in are dropped here (aisle is already
       the first character of the location code; the other two are low-value at a glance) */
    #tableBody tr{display:flex;flex-wrap:wrap;gap:2px 10px;padding:8px 34px 8px 10px;margin-bottom:6px;position:relative;}
    #tableBody td{flex:0 0 100%;padding:2px 0;font-weight:400;}
    #tableBody td:nth-child(1){order:1;}
    #tableBody td:nth-child(1)::before{display:none;}
    #tableBody td:nth-child(3){order:2;font-weight:700;}
    #tableBody td:nth-child(3)::before{display:none;}
    #tableBody td:nth-child(9){order:3;}
    #tableBody td:nth-child(2), #tableBody td:nth-child(5), #tableBody td:nth-child(7){display:none;}
    #tableBody td:nth-child(4){order:4;flex-basis:calc(50% - 5px);}
    #tableBody td:nth-child(6){order:5;flex-basis:calc(50% - 5px);}
    #tableBody td:nth-child(8){order:6;flex-basis:calc(50% - 5px);}
    #tableBody td:nth-child(10){order:7;flex-basis:calc(50% - 5px);}
    #tableBody td.no-notes{display:none;}
    #tableBody td:nth-child(11){order:8;}
    #tableBody td:nth-child(12){order:9;padding-top:6px;}
    #tableBody td:nth-child(13){position:absolute;top:0;right:0;flex-basis:auto;padding:0;}
    #tableBody td:nth-child(13) .row-select-hit{padding:12px;margin:0;}
    #tableBody td:nth-child(13) .row-select{width:20px;height:20px;}

    /* Group-by header rows are still <tr>s, so without this they inherit the pallet
       card's inner padding (including the 34px reserved for the checkbox) and render
       as a stranded grey strip floating inside a white card. Let the header fill its
       card edge to edge instead. */
    #tableBody tr.group-row{padding:0;overflow:hidden;background:var(--surface-alt);}
    #tableBody tr.group-row td{padding:0;border-top:none;}
    #tableBody tr.group-row .group-row-btn{padding:12px 14px;min-height:44px;}

    /* Pick face (dashboard) and Archived share the same 8-column shape:
       Location,Aisle,Flavour,Batch,Qty,BBE,Freshness|Archived,Actions.
       Without this they fall back to one full-width row per field, so a single
       pallet filled most of a phone screen. Aisle is dropped - it's already the
       first character of the location code. */
    #archivedBody tr{display:flex;flex-wrap:wrap;gap:2px 10px;padding:8px 10px;margin-bottom:6px;}
    #archivedBody td{flex:0 0 100%;padding:2px 0;font-weight:400;}
    #archivedBody td:nth-child(1){order:1;}
    #archivedBody td:nth-child(1)::before{display:none;}
    #archivedBody td:nth-child(2){display:none;}
    #archivedBody td:nth-child(3){order:2;font-weight:700;}
    #archivedBody td:nth-child(3)::before{display:none;}
    #archivedBody td:nth-child(7){order:3;flex-basis:calc(50% - 5px);}
    #archivedBody td:nth-child(5){order:4;flex-basis:calc(50% - 5px);}
    #archivedBody td:nth-child(6){order:5;flex-basis:calc(50% - 5px);}
    #archivedBody td:nth-child(4){order:6;flex-basis:calc(50% - 5px);}
    #archivedBody td:nth-child(8){order:7;padding-top:6px;}

    /* let these flow with the page instead of trapping a second scroll area
       inside one that already scrolls */
    .flt-history-wrap{max-height:none;overflow-y:visible;}


    /* Accounts: Name,Role,Job roles,PIN,Training,Created,Actions.
       Was one full-width row per field (~330px an account). */
    #accountsBody tr{display:flex;flex-wrap:wrap;gap:2px 10px;padding:8px 10px;margin-bottom:6px;}
    #accountsBody td{flex:0 0 100%;padding:2px 0;}
    #accountsBody td:nth-child(1){order:1;font-weight:700;}
    #accountsBody td:nth-child(1)::before{display:none;}
    #accountsBody td:nth-child(2){order:2;flex-basis:calc(50% - 5px);}
    #accountsBody td:nth-child(4){order:3;flex-basis:calc(50% - 5px);}
    #accountsBody td:nth-child(3){order:4;}
    #accountsBody td:nth-child(5){order:5;flex-basis:calc(50% - 5px);}
    #accountsBody td:nth-child(6){order:6;flex-basis:calc(50% - 5px);}
    #accountsBody td:nth-child(7){order:7;padding-top:6px;}

    /* Logs: When,Who,Action,Details,Undo */
    #logsBody tr{display:flex;flex-wrap:wrap;gap:2px 10px;padding:8px 10px;margin-bottom:6px;}
    #logsBody td{flex:0 0 100%;padding:2px 0;}
    #logsBody td:nth-child(1){order:1;flex-basis:calc(55% - 5px);}
    #logsBody td:nth-child(2){order:2;flex-basis:calc(45% - 5px);}
    #logsBody td:nth-child(3){order:3;font-weight:700;}
    #logsBody td:nth-child(4){order:4;}
    #logsBody td:nth-child(5){order:5;}

    /* Forecast: SKU,Stock units,Stock cartons,Avg weekly,Weeks left,Run-out,Trend */
    #forecastSections tbody tr{display:flex;flex-wrap:wrap;gap:2px 10px;padding:8px 10px;margin-bottom:6px;}
    #forecastSections tbody td{flex:0 0 100%;padding:2px 0;}
    #forecastSections tbody td:nth-child(1){order:1;font-weight:700;}
    #forecastSections tbody td:nth-child(1)::before{display:none;}
    #forecastSections tbody td:nth-child(2),
    #forecastSections tbody td:nth-child(3),
    #forecastSections tbody td:nth-child(4),
    #forecastSections tbody td:nth-child(5),
    #forecastSections tbody td:nth-child(6){flex-basis:calc(50% - 5px);}
    #forecastSections tbody td:nth-child(7){order:9;}

    /* Box list inside the pallet modal: Position,Flavour,SKU,Qty,BBE,Batch,Notes,Actions */
    #boxListBody tr{display:flex;flex-wrap:wrap;gap:2px 10px;padding:8px 10px;margin-bottom:6px;}
    #boxListBody td{flex:0 0 100%;padding:2px 0;}
    #boxListBody td:nth-child(1){order:1;flex-basis:calc(50% - 5px);font-weight:700;}
    #boxListBody td:nth-child(4){order:2;flex-basis:calc(50% - 5px);}
    #boxListBody td:nth-child(2){order:3;font-weight:700;}
    #boxListBody td:nth-child(2)::before{display:none;}
    #boxListBody td:nth-child(3){order:4;flex-basis:calc(50% - 5px);}
    #boxListBody td:nth-child(5){order:5;flex-basis:calc(50% - 5px);}
    #boxListBody td:nth-child(6){order:6;flex-basis:calc(50% - 5px);}
    #boxListBody td:nth-child(7){order:7;}
    #boxListBody td:nth-child(8){order:8;padding-top:6px;}

    /* FLT mode tables: keep the label/value stack but let the short columns pair up */
    #fltInspectionBody tr{display:flex;flex-wrap:wrap;gap:2px 10px;padding:8px 10px;margin-bottom:6px;}
    #fltInspectionBody td{flex:0 0 100%;padding:2px 0;}
    #fltInspectionBody td:nth-child(2), #fltInspectionBody td:nth-child(3){flex-basis:calc(50% - 5px);}

    .pill{padding:6px 12px;font-size:12.5px;}

    .bulk-actions-bar{padding:8px 10px;font-size:12px;}
    .bulk-actions-bar .btn{padding:8px 10px;font-size:12px;}
    #exportCsvBtn{width:100%;}

    .container-tally-grid{grid-template-columns:repeat(3,1fr);gap:8px;}
    .container-tally-card{padding:12px 8px;}
    .container-tally-count{font-size:24px;}

    /* Full-screen sheet, but its top edge stops at the status-bar inset instead of
       running under it - same reason as #loginScreen: a scroller that never has
       content passing beneath the top edge doesn't trigger the iOS 26 scroll-edge
       blur. The strip above stays flat navy via body::before. */
    .modal-overlay{padding:0;align-items:stretch;top:env(safe-area-inset-top);
      height:calc(var(--vvh, 100dvh) - env(safe-area-inset-top));bottom:auto;}
    /* 100% of the overlay, NOT 100dvh. The overlay is position:fixed from the status-bar
       inset to bottom:0, which is the same box .app is anchored to and does reach the
       bottom of the display; dvh measured short of it on an installed iOS app and left
       the action bar floating above a band of page background. */
    .modal{max-width:100%;width:100%;height:100%;max-height:100%;border-radius:0;padding:16px;
      display:flex;flex-direction:column;}
    /* The action bar is sticky, but sticky only pins once content overflows - on a
       short modal it sat mid-screen with dead space beneath. Letting the content
       wrapper grow and pushing the bar down with margin-top:auto puts it at the
       bottom in both cases. */
    /* :not(.modal-actions) matters - some modals (Move to pick face, Record a repair)
       have the action bar as the last child directly, and growing it into a full-height
       column stretched its buttons down the whole screen. Where it IS the last child,
       .modal being a flex column plus margin-top:auto already pins it to the bottom. */
    .modal > *:last-child:not(.modal-actions){flex:1 1 auto;display:flex;flex-direction:column;min-height:0;}
    .modal-actions, .modal-actions-spaced{margin-top:auto;}
    /* never let the bar stretch its buttons vertically */
    .modal-actions{align-items:flex-start;flex:none;}

    /* pin the save/cancel bar to the bottom of the modal so it never needs scrolling to reach */
    /* bottom:-16px, not 0: sticky pins to the scrollport's padding box, which left
       the modal's own 16px bottom padding visible as a strip content scrolled through */
    .modal-actions{position:sticky;bottom:-16px;margin-left:-16px;margin-right:-16px;margin-bottom:-16px;
      padding:12px 16px calc(12px + var(--bar-gap, env(safe-area-inset-bottom,0px)));background:var(--surface);
      border-top:1px solid var(--border);box-shadow:0 -4px 12px rgba(16,24,32,.08);z-index:5;
      /* the pallet modal has five buttons; without wrapping they squeeze until every
         label breaks over two lines ("Delete / pallet"). Let the row wrap instead. */
      flex-wrap:wrap;justify-content:stretch;}
    .modal-actions .btn{white-space:nowrap;flex:1 1 auto;min-height:44px;padding:10px 12px;}
    /* keep the primary action on its own full-width row, where a thumb expects it */
    .modal-actions .btn-primary{flex:1 0 100%;order:99;}

    .picker-loc-badge{font-size:24px;padding:12px 16px;}
    .picker-result-head{padding:14px;gap:12px;}

    .toast{bottom:calc(52px + var(--bar-gap, max(20px, env(safe-area-inset-bottom,0px))));}

    /* Installed (standalone) app: hug the bottom. In a browser the full safe-area
       inset matters because browser chrome sits below; installed, the bar can sit
       under the home indicator with just enough clearance to stay tappable. */
    @media (display-mode: standalone){
      .tabs{padding-bottom:max(8px, calc(env(safe-area-inset-bottom, 0px) - 16px));}
      .toast{bottom:calc(56px + max(8px, calc(env(safe-area-inset-bottom, 0px) - 16px)));}
    }
  }

  /* respect the OS-level "reduce motion" setting: strip the decorative movement
     (state changes still happen, just without the animation) */
  /* ---------- motion & loading states ----------
     Deliberately restrained: this is a working tool, so animation is only used to
     explain a change (something arrived, something is pending, something is leaving),
     never for decoration. Everything here is switched off by the reduced-motion
     block below.
     Timings were raised ~1.6x in v3.42.0 and again ~1.4x in v3.46.0 - the first pass read as snappy to the point
     of being abrupt, so transitions now settle rather than snap. Keep them in step
     with each other if you change one. */

  /* content arriving - panels and cards fade up as they render */
  /* Entrance animations use fill-mode BACKWARDS, not both: a both-filled
     transform/opacity animation keeps the element a stacking context forever
     (Chrome), which trapped the flavour-picker dropdown UNDER later sibling
     panels (seen live on the FLT picker, 2026-08-29). backwards looks identical
     but releases the context the moment the entrance finishes. */
  @keyframes rise-in{from{opacity:0;transform:translateY(6px);}to{opacity:1;transform:none;}}
  /* switching tabs flips display:none -> block, which restarts these, so the whole
     page eases in rather than snapping */
  @keyframes page-in{from{opacity:0;transform:translateY(8px);}to{opacity:1;transform:none;}}
  .tab-panel.active{animation:page-in .58s cubic-bezier(.22,.61,.36,1) backwards;}
  .tab-btn{transition:background .38s ease, color .38s ease;}
  /* modals ease up instead of appearing instantly */
  @keyframes modal-in{from{opacity:0;transform:translateY(14px) scale(.99);}to{opacity:1;transform:none;}}
  @keyframes overlay-in{from{opacity:0;}to{opacity:1;}}
  .modal-overlay.is-open{animation:overlay-in .38s ease-out both;}
  .modal-overlay.is-open > .modal{animation:modal-in .55s cubic-bezier(.22,.61,.36,1) both;}
  /* symmetric exits: .is-closing is added by animateClose() and wins over the
     .is-open entrance because it comes later at equal specificity. pointer-events
     off so a mid-fade overlay can't swallow taps. */
  @keyframes overlay-out{to{opacity:0;}}
  @keyframes modal-out{to{opacity:0;transform:translateY(10px) scale(.985);}}
  .modal-overlay.is-closing{animation:overlay-out .38s ease both;pointer-events:none;}
  .modal-overlay.is-closing > .modal{animation:modal-out .38s ease both;}
  .topbar-menu.is-closing{animation:menuOut .32s ease both;pointer-events:none;}
  .topbar-menu.is-closing > *{animation:none;}
  @keyframes menuOut{to{opacity:0;transform:scale(.95) translateY(-6px);}}
  .topbar-menu-backdrop.is-closing{animation:backdropOut .32s ease both;pointer-events:none;}
  @keyframes backdropOut{to{opacity:0;}}
  .fp.is-open.fp-closing .fp-panel{animation:fp-out .28s ease both;pointer-events:none;}
  @keyframes fp-out{to{opacity:0;transform:translateY(-6px) scale(.985);}}
  .toast.toast-hide{animation:toastOut .38s ease both;}
  @keyframes toastOut{to{opacity:0;transform:translate(-50%,8px);}}
  @media (prefers-reduced-motion: reduce){
    .modal-overlay.is-closing, .modal-overlay.is-closing > .modal, .topbar-menu.is-closing,
    .topbar-menu-backdrop.is-closing, .fp.is-open.fp-closing .fp-panel, .toast.toast-hide{animation:none;}
  }
  .tab-panel.active > .panel{animation:rise-in .5s ease-out backwards;}
  .tab-panel.active > .panel:nth-child(2){animation-delay:.1s;}
  .tab-panel.active > .panel:nth-child(3){animation-delay:.2s;}
  .tab-panel.active > .panel:nth-child(4){animation-delay:.3s;}
  .flavour-card, .pf-card, .build-card, .stat-card{animation:rise-in .44s ease-out backwards;}

  /* the countdown ring on the toast's Undo button */
  @keyframes ring-drain{from{stroke-dashoffset:0;}to{stroke-dashoffset:57;}}
  .toast-countdown{width:18px;height:18px;flex:none;transform:rotate(-90deg);}
  .toast-countdown circle{fill:none;stroke:#8FE3D8;stroke-width:2.5;
    stroke-dasharray:57;animation:ring-drain linear forwards;}

  /* skeletons - same shape as the real content so nothing jumps when it lands */
  @keyframes shimmer{from{background-position:-320px 0;}to{background-position:320px 0;}}
  .skel{background:linear-gradient(90deg,var(--surface-alt) 0%,#F2EADA 50%,var(--surface-alt) 100%);
    background-size:320px 100%;animation:shimmer 1.1s linear infinite;border-radius:8px;}
  .skel-card{border:1px solid var(--border);border-radius:10px;padding:14px;background:var(--surface-alt);}
  .skel-line{height:11px;margin-bottom:8px;}
  .skel-line.w40{width:40%;} .skel-line.w60{width:60%;} .skel-line.w80{width:80%;}
  .skel-thumb{width:44px;height:44px;border-radius:8px;}
  .skel-row{display:flex;align-items:center;gap:10px;padding:12px 14px;border-bottom:1px solid var(--border);}

  @media (prefers-reduced-motion: reduce){
    *, *::before, *::after{
      animation-duration:.01ms !important;
      animation-iteration-count:1 !important;
      transition-duration:.01ms !important;
    }
  }

  @media (max-width:400px){
    .container-tally-grid{grid-template-columns:repeat(2,1fr);}
    /* Deliberately NOT dropping to one column here. With 14+ flavours a single
       column made this panel ~2,500px of scrolling on its own; two-up halves that
       at the cost of a slightly tighter card. */
    .flavour-grid{grid-template-columns:1fr 1fr;gap:6px;}
    .flavour-card{padding:10px;gap:7px;}
    .fc-name{font-size:13px;}
    .fc-meta{font-size:10.5px;}
    .flavour-card .flavour-thumb-md{width:30px;height:30px;}
    .flavour-card .fc-thumb-slot{width:30px;height:30px;}
  }
