/* Shared artwork styles + animation keyframes (used by the wizard and the admin preview) */
.art-svg { max-width:100%; max-height:100%; }
.art-img { max-width:100%; max-height:100%; object-fit:contain; }
.art.zoomable .art-img { cursor:zoom-in; }
.art-zoom-preview { position:fixed; z-index:10000; display:none; padding:10px; box-sizing:border-box;
  pointer-events:none; background:#fff; border:2px solid #d9e2f3; border-radius:16px;
  box-shadow:0 20px 55px rgba(34,49,80,.28); }
.art-zoom-preview.show { display:block; }
.art-zoom-preview img { width:100%; height:100%; object-fit:contain; border-radius:9px; }
.tb { transform-box:fill-box; transform-origin:center; }

@keyframes kfEmerge { 0%{transform:translateX(-34px)} 40%,100%{transform:translateX(0)} }
@keyframes kfFall { 0%,55%{transform:translate(0,0) rotate(0);opacity:1} 82%{transform:translate(12px,30px) rotate(34deg);opacity:0} 100%{opacity:0} }
@keyframes kfFallLate { 0%,72%{transform:translate(0,0) rotate(0);opacity:1} 92%{transform:translate(12px,30px) rotate(34deg);opacity:0} 100%{opacity:0} }
@keyframes kfPulse { 0%,100%{opacity:.04;transform:scale(.8)} 50%{opacity:.3;transform:scale(1.12)} }
@keyframes kfSpin { to{transform:rotate(360deg)} }
@keyframes kfMarch { to{stroke-dashoffset:-22} }
@keyframes kfBlade { 0%,40%{transform:translateY(0)} 48%,56%{transform:translateY(13px)} 66%,100%{transform:translateY(0)} }
@keyframes kfCutFall { 0%{opacity:0} 6%{opacity:1} 8%,56%{transform:translate(0,0) rotate(0);opacity:1} 84%{transform:translate(12px,30px) rotate(24deg);opacity:0} 100%{opacity:0} }
@keyframes kfPeel { 0%{transform:translate(0,0);opacity:0} 10%{opacity:1} 60%{transform:translate(28px,-30px) rotate(-10deg);opacity:1} 80%{transform:translate(42px,-46px) rotate(-14deg);opacity:0} 100%{opacity:0} }
@keyframes kfHover { 0%{transform:translate(0,0);opacity:0} 12%{opacity:1;transform:translate(16px,-20px)} 72%{transform:translate(16px,-24px);opacity:1} 90%{opacity:0} 100%{opacity:0} }
@keyframes kfScan { 0%,100%{transform:translateX(0)} 50%{transform:translateX(84px)} }
@keyframes kfScanV { 0%,100%{transform:translateY(-7px)} 50%{transform:translateY(9px)} }
/* Usage scenarios: animate the actual print → inspect → apply sequence. */
@keyframes kfScenePass { 0%,12%{opacity:0;transform:scale(.7)} 24%,76%{opacity:1;transform:scale(1)} 90%,100%{opacity:0;transform:scale(.7)} }
@keyframes kfSceneLabLabel { 0%,12%{transform:translateX(0);opacity:1} 36%,74%{transform:translateX(58px);opacity:1} 88%,100%{transform:translateX(58px);opacity:0} }
@keyframes kfSceneLabResult { 0%,42%{opacity:0} 55%,82%{opacity:1} 94%,100%{opacity:0} }
@keyframes kfSceneManualApply { 0%,14%{transform:translate(0,-8px);opacity:0} 24%{transform:translate(0,0);opacity:1} 64%,74%{transform:translate(77px,8px);opacity:1} 84%,100%{transform:translate(77px,8px);opacity:0} }
@keyframes kfSceneAppliedLabel { 0%,66%{opacity:0} 74%,94%{opacity:1} 100%{opacity:0} }
@keyframes kfSceneRobotSource { 0%,14%{opacity:1;transform:translateX(0)} 20%,82%{opacity:0;transform:translateX(0)} 86%{opacity:1;transform:translateX(-15px)} 96%,100%{opacity:1;transform:translateX(0)} }
@keyframes kfSceneRobotCarry { 0%,12%{opacity:0} 18%,62%{opacity:1} 70%,100%{opacity:0} }
@keyframes kfSceneRobotApplied { 0%,62%{opacity:0} 70%,94%{opacity:1} 100%{opacity:0} }
/* Defective-label handling: one shared timeline per option, from detection to the
   chosen disposition and then resuming production. */
@keyframes kfRejectAlarm { 0%,100%{opacity:.45} 50%{opacity:1} }
@keyframes kfRejectStoppedLabel { 0%,55%{opacity:1} 64%,100%{opacity:0} }
@keyframes kfRejectHandRemove { 0%,34%{opacity:0;transform:translate(0,-12px)} 44%,58%{opacity:1;transform:translate(0,0)} 76%{opacity:1;transform:translate(18px,-20px)} 88%,100%{opacity:0;transform:translate(18px,-20px)} }
@keyframes kfRejectRecordFlow { 0%{opacity:0;transform:translateX(-12px)} 10%{opacity:1} 88%{opacity:1;transform:translateX(100px)} 100%{opacity:0;transform:translateX(112px)} }
@keyframes kfRejectNextFlow { 0%,34%{opacity:0;transform:translateX(-14px)} 44%{opacity:1} 96%{opacity:1;transform:translateX(70px)} 100%{opacity:0;transform:translateX(76px)} }
@keyframes kfRejectLog { 0%,14%{opacity:0;transform:translate(0,0)} 24%{opacity:1} 68%{opacity:1;transform:translate(63px,-22px)} 82%,100%{opacity:0;transform:translate(63px,-22px)} }
@keyframes kfRejectVoidCycle { 0%,18%{opacity:1;transform:translateX(0)} 32%,50%{opacity:1;transform:translateX(-28px)} 64%{opacity:1;transform:translateX(0)} 92%{opacity:1;transform:translateX(98px)} 100%{opacity:0;transform:translateX(108px)} }
@keyframes kfRejectNgMark { 0%,34%{opacity:1} 40%,100%{opacity:0} }
@keyframes kfRejectVoidXs { 0%,34%{opacity:0} 40%,100%{opacity:1} }
@keyframes kfRejectHeadStamp { 0%,26%{opacity:0;transform:translateY(-5px)} 34%{opacity:1;transform:translateY(0)} 42%,48%{opacity:1;transform:translateY(3px)} 56%,100%{opacity:0;transform:translateY(-5px)} }
@keyframes kfRejectReplacementAfterVoid { 0%,68%{opacity:0;transform:translateX(-18px)} 74%{opacity:1;transform:translateX(0)} 94%{opacity:1;transform:translateX(72px)} 100%{opacity:0;transform:translateX(82px)} }
@keyframes kfRejectGapAfterVoid { 0%,68%{opacity:0;transform:translateX(0)} 74%{opacity:1} 94%{opacity:1;transform:translateX(68px)} 100%{opacity:0;transform:translateX(78px)} }
/* Seamless continuous label belt: shift by exactly one label pitch (17) and loop */
@keyframes kfBeltStrip { from{transform:translateX(0)} to{transform:translateX(17px)} }
@keyframes kfDrip { 0%,20%{transform:translateY(-6px);opacity:0} 30%{opacity:1} 70%{transform:translateY(14px);opacity:1} 80%,100%{opacity:0} }
@keyframes kfGloss { 0%{transform:translateX(0)} 55%,100%{transform:translateX(240px)} }
@keyframes kfBob { 0%,100%{transform:translateY(0)} 50%{transform:translateY(-6px)} }
@keyframes kfDrop { 0%{transform:translateY(-30px);opacity:0} 25%{opacity:1} 50%{transform:translateY(0)} 88%{opacity:1;transform:translateY(0)} 100%{opacity:0} }
@keyframes kfSuck { 0%,10%{transform:translateY(0)} 45%{transform:translateY(22px)} 75%,100%{transform:translateY(0)} }
@keyframes kfConvey { 0%{transform:translateX(-24px);opacity:0} 12%{opacity:1} 88%{opacity:1} 100%{transform:translateX(92px);opacity:0} }
@keyframes kfFlex { 0%,100%{transform:rotate(0)} 50%{transform:rotate(-3deg)} }
@keyframes kfPlace { 0%,100%{transform:translateY(-14px)} 45%,60%{transform:translateY(0)} }
@keyframes kfWebOut { 0%{transform:translateX(-105px)} 55%,100%{transform:translateX(0)} }
@keyframes kfWebOut1 { 0%{transform:translateX(-50px)} 45%,100%{transform:translateX(0)} }
@keyframes kfWebOutS { 0%,35%{transform:translateX(-48px)} 80%,100%{transform:translateX(0)} }
@keyframes kfStripFall { 0%,58%{transform:none;opacity:1} 88%{transform:translate(16px,34px) rotate(24deg);opacity:0} 100%{opacity:0} }
@keyframes kfTearDown { 0%,58%{transform:rotate(0);opacity:1} 74%{transform:rotate(40deg);opacity:1} 86%{transform:rotate(48deg);opacity:0} 100%{transform:rotate(48deg);opacity:0} }
@keyframes kfHandGrab { 0%,42%{opacity:0} 52%,82%{opacity:1} 90%,100%{opacity:0} }
@keyframes kfSnap { 0%,56%{opacity:0} 62%,74%{opacity:1} 82%,100%{opacity:0} }
@keyframes kfHandTear { 0%,50%{opacity:0;transform:translate(0,-10px)} 58%{opacity:1;transform:translate(0,2px)} 86%{transform:translate(18px,34px) rotate(16deg);opacity:0} 100%{opacity:0} }
@keyframes kfPeelWait { 0%{opacity:0;transform:translate(-8px,10px)} 12%{opacity:1;transform:translate(0,0) rotate(-8deg)} 50%{transform:translate(0,-2px) rotate(-8deg);opacity:1} 82%{transform:translate(30px,-32px) rotate(-16deg);opacity:0} 100%{opacity:0} }
@keyframes kfHandPeel { 0%,44%{opacity:0;transform:translate(4px,-12px)} 54%{opacity:1;transform:translate(0,0)} 82%{transform:translate(30px,-34px);opacity:0} 100%{opacity:0} }
@keyframes kfJobFly { 0%{transform:translate(0,0);opacity:0} 8%{opacity:1} 26%{transform:translate(40px,34px);opacity:1} 34%{opacity:0} 100%{opacity:0} }
@keyframes kfJobFly2 { 0%{transform:translate(-16px,0);opacity:0} 12%{opacity:1} 30%{transform:translate(36px,14px);opacity:1} 38%{opacity:0} 100%{opacity:0} }
@keyframes kfPulseSoft { 0%,100%{opacity:1} 50%{opacity:.45} }
@keyframes kfRobotArm { 0%,18%{transform:rotate(-24deg)} 52%,68%{transform:rotate(26deg)} 96%,100%{transform:rotate(-24deg)} }
@keyframes kfRobotLbl { 0%,18%{transform:translate(0,0);opacity:1} 52%{transform:translate(112px,24px);opacity:1} 60%,72%{transform:translate(118px,28px);opacity:1} 82%{opacity:0} 98%{opacity:0;transform:translate(0,0)} 100%{opacity:1} }
/* Robot receiving-platform motion: approach the printer exit, receive one label,
   then carry it back to the robot pickup position. */
@keyframes kfRobotPlatformX { 0%,12%{transform:translateX(0)} 34%,62%{transform:translateX(42px)} 84%,100%{transform:translateX(0)} }
@keyframes kfRobotPlatformY { 0%,12%{transform:translateX(0)} 34%,62%{transform:translateX(-117px)} 84%,100%{transform:translateX(0)} }
@keyframes kfRobotPlatformZ { 0%,12%{transform:translateY(0)} 34%,62%{transform:translateY(-42px)} 84%,100%{transform:translateY(0)} }
/* Keep the side-view label visible across the loop boundary, then let the robot
   remove it before the next platform approach. This avoids an end-of-cycle flash. */
@keyframes kfRobotPlatformLabel { 0%{opacity:1} 8%,44%{opacity:0} 50%,100%{opacity:1} }
@keyframes kfRobotPlatformPrint { 0%,34%{opacity:0;transform:scaleX(.05)} 39%{opacity:1;transform:scaleX(.18)} 51%{opacity:1;transform:scaleX(1)} 56%,100%{opacity:0;transform:scaleX(1)} }
/* In the front view the complete label is visible at once; it never grows from
   a short line. The second phase keeps that full line on the returning platform. */
@keyframes kfRobotPlatformFrontPrint { 0%,38%{opacity:0} 39%,55%{opacity:1} 56%,100%{opacity:0} }
@keyframes kfRobotPlatformFrontLabel { 0%,49%{opacity:0} 50%,91%{opacity:1} 92%,100%{opacity:0} }
/* Tamp applicator: the cylinder ROD stays attached to the machine and telescopes
   (scaleY) while the pad+label descend in sync — the rod never detaches. */
@keyframes kfTamp { 0%,14%{transform:translateY(0)} 40%,52%{transform:translateY(22px)} 74%,100%{transform:translateY(0)} }
@keyframes kfTampRod { 0%,14%{transform:scaleY(1)} 40%,52%{transform:scaleY(2.4)} 74%,100%{transform:scaleY(1)} }
@keyframes kfTampLbl { 0%,42%{opacity:1} 48%,100%{opacity:0} }
@keyframes kfStuckLbl { 0%,43%{opacity:0} 49%,96%{opacity:1} 100%{opacity:0} }
/* Gantry pick & place: suction head picks a label off the platform and places it on the box */
@keyframes kfPnp { 0%{transform:translate(0,0)} 8%{transform:translate(0,22px)} 16%{transform:translate(0,0)}
  44%{transform:translate(126px,0)} 54%{transform:translate(126px,20px)} 60%{transform:translate(126px,20px)}
  68%{transform:translate(126px,0)} 92%{transform:translate(0,0)} 100%{transform:translate(0,0)} }
@keyframes kfPickedLbl { 0%,10%{opacity:0} 16%,52%{opacity:1} 56%,100%{opacity:0} }
@keyframes kfPlacedLbl { 0%,54%{opacity:0} 58%,96%{opacity:1} 100%{opacity:0} }
/* Articulated robot arm (rotation via SVG animateTransform about the joint points —
   CSS transform-box gives wrong pivots for nested SVG groups). Label fades only. */
@keyframes kfArmPickLbl { 0%,12%{opacity:0} 17%,48%{opacity:1} 53%,100%{opacity:0} }
@keyframes kfArmPlaceLbl { 0%,50%{opacity:0} 56%,95%{opacity:1} 100%{opacity:0} }
/* Printed label waiting on the receiving platform at the printer exit: present at
   start, taken by the arm (~12%), a fresh one prints out near the end of the cycle. */
@keyframes kfPrintLbl { 0%,11%{opacity:1;transform:translateX(0)} 15%,84%{opacity:0;transform:translateX(0)}
  86%{opacity:1;transform:translateX(-16px)} 96%,100%{opacity:1;transform:translateX(0)} }
@keyframes kfEject { 0%,45%{transform:translate(0,0);opacity:1} 70%{transform:translate(28px,-22px) rotate(20deg);opacity:0} 100%{transform:translate(0,0);opacity:0} }
/* RFID electromagnetic waves radiating outward */
@keyframes kfRadiate { 0%{transform:scale(.4);opacity:0} 25%{opacity:.9} 80%{transform:scale(1.15);opacity:0} 100%{opacity:0} }
/* Void & reprint: the NG label sits at the exit, is voided, retracts into the
   printer; then a fresh correct label prints out and moves down the line. */
@keyframes kfVoidRetract { 0%,32%{transform:translateX(0);opacity:1} 44%{transform:translateX(-32px);opacity:0} 100%{transform:translateX(-32px);opacity:0} }
@keyframes kfReprintOut { 0%,46%{transform:translateX(-30px);opacity:0} 56%{transform:translateX(0);opacity:1} 88%{transform:translateX(64px);opacity:1} 100%{transform:translateX(64px);opacity:0} }
@keyframes kfRetractArrow { 0%,30%{opacity:0} 36%,44%{opacity:1} 50%,100%{opacity:0} }
/* Print via driver: Print button is clicked, then the printer prints the label */
@keyframes kfBtnPress { 0%,18%{transform:translateY(0)} 24%,30%{transform:translateY(2px)} 36%,100%{transform:translateY(0)} }
@keyframes kfClickDot { 0%,20%{opacity:0;transform:scale(1.6)} 26%{opacity:.55;transform:scale(.6)} 40%,100%{opacity:0} }
/* Directly send printer commands: highlight steps down the ZPL lines; the printer keeps printing labels out of its slot */
@keyframes kfLineStep { from{transform:translateY(0)} to{transform:translateY(42px)} }
@keyframes kfPrintOut { 0%{transform:translateX(-2px);opacity:0} 12%{opacity:1} 68%{transform:translateX(16px);opacity:1} 90%,100%{transform:translateX(22px);opacity:0} }
/* Printer-stored format: tap panel keys in sequence, then the printer prints a label out of its front slot */
@keyframes kfTapOnce { 0%{opacity:0} 4%{opacity:.9} 14%{opacity:0} 100%{opacity:0} }
@keyframes kfPsPrint { 0%,42%{opacity:0;transform:translateY(-8px)} 52%{opacity:1;transform:translateY(0)} 86%{opacity:1;transform:translateY(10px)} 96%,100%{opacity:0;transform:translateY(14px)} }
/* Data return: local manual export, continuous MES packets, and IO/PLC actuation */
@keyframes kfDataResultToPc { 0%,8%{transform:translateX(0);opacity:0} 15%{opacity:1} 34%,100%{transform:translateX(17px);opacity:0} }
@keyframes kfDataManualClick { 0%,38%{transform:translate(7px,7px);opacity:0} 48%,58%{transform:translate(0,0);opacity:1} 68%,100%{opacity:0} }
@keyframes kfDataManualExport { 0%,54%{transform:translateX(0);opacity:0} 62%{opacity:1} 84%{transform:translateX(27px);opacity:1} 92%,100%{transform:translateX(27px);opacity:0} }
@keyframes kfDataMesUpload { 0%{transform:translate(0,0);opacity:0} 10%{opacity:1} 78%{transform:translate(76px,-31px);opacity:1} 90%,100%{transform:translate(76px,-31px);opacity:0} }
@keyframes kfDataMesLive { 0%,100%{opacity:.45;transform:scale(.85)} 50%{opacity:1;transform:scale(1.12)} }
@keyframes kfDataIoSignal { 0%,6%{transform:translateX(0);opacity:0} 14%{opacity:1} 28%{transform:translateX(24px);opacity:1} 34%,100%{transform:translateX(24px);opacity:0} }
@keyframes kfDataNgLed { 0%,26%{opacity:.25} 34%,48%{opacity:1} 58%,100%{opacity:.25} }
@keyframes kfDataPlcPulse { 0%,30%{transform:translateX(0);opacity:0} 38%{opacity:1} 52%{transform:translateX(27px);opacity:1} 58%,100%{transform:translateX(27px);opacity:0} }
@keyframes kfDataActuatorPulse { 0%,52%{transform:translateX(0);opacity:0} 60%{opacity:1} 72%{transform:translateX(39px);opacity:1} 78%,100%{transform:translateX(39px);opacity:0} }
@keyframes kfDataPusher { 0%,68%{transform:translateX(0)} 76%,84%{transform:translateX(-12px)} 94%,100%{transform:translateX(0)} }
/* Enterprise management: fleet control, authentication, unique allocation, central storage and template distribution */
@keyframes kfEntConsoleScan { 0%,100%{transform:translateY(0);opacity:.2} 50%{transform:translateY(31px);opacity:.65} }
@keyframes kfEntPacket { 0%{transform:translate(0,0);opacity:0} 12%{opacity:1} 72%{transform:translate(var(--tx),var(--ty));opacity:1} 86%,100%{transform:translate(var(--tx),var(--ty));opacity:0} }
@keyframes kfEntAuthRequest { 0%,5%{transform:translateX(0);opacity:0} 14%{opacity:1} 46%{transform:translateX(80px);opacity:1} 56%,100%{transform:translateX(80px);opacity:0} }
@keyframes kfEntAuthGrant { 0%,50%{transform:translateX(0);opacity:0} 60%{opacity:1} 88%{transform:translateX(-80px);opacity:1} 96%,100%{transform:translateX(-80px);opacity:0} }
@keyframes kfEntCounter { 0%,46%{opacity:0} 50%,100%{opacity:1} }
@keyframes kfEntReadyPulse { 0%,64%{opacity:0;transform:scale(.55)} 76%{opacity:1;transform:scale(1.15)} 88%,100%{opacity:.75;transform:scale(1)} }
