Full Code of dukelec/cdpnp for AI

master 3f8303045c0f cached
51 files
1.5 MB
727.2k tokens
138 symbols
1 requests
Download .txt
Showing preview only (1,599K chars total). Download the full file or copy to clipboard to get everything.
Repository: dukelec/cdpnp
Branch: master
Commit: 3f8303045c0f
Files: 51
Total size: 1.5 MB

Directory structure:
gitextract__75ccm0y/

├── .gitignore
├── .gitmodules
├── License
├── Readme.md
├── cd_ws.py
├── doc/
│   └── shortcuts.md
├── html/
│   ├── calibration.js
│   ├── dev_cmd.js
│   ├── index.html
│   ├── index.js
│   ├── input_ctrl.js
│   ├── libs/
│   │   └── bulma-0.9.4.css
│   ├── pos_list.js
│   ├── preload_ctrl.js
│   └── utils/
│       ├── cd_ws.js
│       ├── helper.js
│       ├── idb.js
│       ├── lang.js
│       └── trans/
│           ├── zh_cn.js
│           └── zh_hk.js
├── pnp_cv.py
├── pnp_main.py
├── pnp_xyz.py
├── start.sh
├── tmp/
│   └── .gitkeep
├── tools/
│   ├── cdbus_gui_configs/
│   │   ├── cdcam_21.mpk
│   │   ├── cdcam_22.mpk
│   │   ├── cdpump_11.mpk
│   │   ├── cdpump_v2_11.mpk
│   │   ├── cdstep_01.mpk
│   │   ├── cdstep_02.mpk
│   │   ├── cdstep_03.mpk
│   │   ├── cdstep_04.mpk
│   │   └── cdstep_05.mpk
│   ├── csv_conv_ad.py
│   ├── csv_conv_allegro.py
│   ├── firmware/
│   │   ├── bootloader/
│   │   │   ├── cdcam_bl_v1_e11ff15.hex
│   │   │   ├── cdcam_bl_v2_v3_fb06e57.hex
│   │   │   ├── cdpump_bl_v1_476ead2.hex
│   │   │   ├── cdpump_bl_v2_6e45f85.hex
│   │   │   └── cdstep_bl_all_v3_ee1d4bc.hex
│   │   ├── cdcam_v1_e11ff15.hex
│   │   ├── cdcam_v2_v3_fb06e57.hex
│   │   ├── cdpump_v1_476ead2.hex
│   │   ├── cdpump_v2_6e45f85.hex
│   │   ├── cdstep_r_v3_72abac5.hex
│   │   ├── cdstep_v3_ee1d4bc.hex
│   │   └── cdstep_z_v3_efbb9a9.hex
│   ├── fw_cfg_update.py
│   └── pos.md
└── web_serve.py

================================================
FILE CONTENTS
================================================

================================================
FILE: .gitignore
================================================
#
# NOTE! Don't add files that are generated in specific
# subdirectories here. Add them in the ".gitignore" file
# in that subdirectory instead.
#
# NOTE! Please use 'git ls-files -i --exclude-standard'
# command after changing this file, to see if there are
# any tracked files which get ignored after the change.
#
# Normal rules
#
.*
*.pyc

/build/
/dist/
/tmp/

#
# git files that we don't want to ignore even it they are dot-files
#
!.gitignore
!.gitmodules
!.gitkeep
!.mailmap

*.orig
*~
\#*#



================================================
FILE: .gitmodules
================================================
[submodule "pycdnet"]
	path = pycdnet
	url = ../pycdnet


================================================
FILE: License
================================================
MIT License

Copyright (c) 2021 DUKELEC

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


================================================
FILE: Readme.md
================================================
CDPnP
=======================================

CDPnP is a compact, desktop, semi-automatic SMT prototyping machine.

Traditional SMT machines are cumbersome to configure. When making prototypes,
setup often takes several hours, while the placement process only takes a few minutes.

This semi-automatic machine is easy to set up: simply import the position file in KiCad .csv format (other software formats are also supported),
place one or more PCBs, and use the camera to mark two reference points on each PCB. No component setup is required.

For ICs with very dense pins, especially BGA packages,
it is recommended to manually check the position before placement and use the keyboard to adjust the position and angle.
Placing them by hand can make alignment difficult and may result in misplacement due to hand tremors.

[Discussions](https://github.com/dukelec/cdpnp/discussions) for this project are now enabled. Feel free to join and get more information.


### Hardware

The hardware is modular in design, with a CDBUS (RS-485) bus connecting all modules to the PC, including two cameras (10 Mbps by default).

The machine has four degrees of freedom: X, Y, Z, and R (rotation), each with a homing switch. It is controlled by multiple stepper motor controllers.

The Y-axis is controlled by two motor controllers, synchronized via multicasting.

The Z-axis contains a strain gauge-based force sensor, with sensor data relayed through the R-axis controller, as the sensor is located closest to the R-axis.

The machine automatically picks up components from predefined search areas and places them onto the PCB in the corresponding positions.  
However, it does not recognize the component models on the tray, so the user must place the correct components according to the instructions.

Once the machine finishes placing components of the same model, it automatically pauses and waits for the user to replace the components in the search area.

While the machine is placing components, the user can prepare the next few models on separate trays in advance and quickly replace the trays for loading,
enabling parallel operation between the machine and the operator.

<img src="doc/hardware.jpg">

<img src="doc/work.jpg">

The stepper motor controllers, cameras, and others are open-source projects available at: https://github.com/dukelec/cdbus_doc


### User Interface

The list of components can be dragged and sorted.

Click on a component to move the camera automatically to its position.

Press the run button to start the semi-automatic SMT placement process.

<img src="doc/software.jpg">  

It also supports more than two PCBs, with additional blank items automatically hidden in the software.

The following images show the recognition of 0402, 0201, and SOT23 footprints for testing purposes only.
In practice, only components of the same model can be placed at one time.


<img src="doc/cv.jpg">  


#### Download this GUI tool:
`git clone --recurse-submodules https://github.com/dukelec/cdpnp.git`

#### Update:
`git pull --recurse-submodules`


#### Dependence:
Python version >= 3.8  
`pip3 install pythoncrc websockets pyserial u-msgpack-python aiohttp opencv-python scipy`

#### Usage:
Run `pnp_main.py` or `start.sh`, then open the following URL in your web browser: http://localhost:8900

Test without hardware: `./pnp_main.py --debug --dev None`

App shortcuts: [doc/shortcuts.md](doc/shortcuts.md)



================================================
FILE: cd_ws.py
================================================
#!/usr/bin/env python3
#
# Software License Agreement (MIT License)
#
# Author: Duke Fong <d@d-l.io>
#

# Packet format:
#   {
#       src: (addr, port), dst: (addr, port),
#       dat: ...
#   }
#
# The addr and port format: any string (or number)
# The dat format: dict for convention

import umsgpack
import asyncio


class CDWebSocket():
    def __init__(self, ns, port):
        self.ns = ns
        self.port = port
        self.recv_q = asyncio.Queue()
        assert self.port not in self.ns.sockets
        self.ns.sockets[self.port] = self
    
    def delete(self):
        #await self.recv_q.join()
        del self.ns.sockets[self.port]
    
    async def sendto(self, dat, s_addr):
        msg = umsgpack.packb({'src': (self.ns.addr, self.port), 'dst': s_addr, 'dat': dat})
        if s_addr[0] in self.ns.connections:
            await self.ns.connections[s_addr[0]].send(msg)
            return None
        elif self.ns.def_route and (self.ns.def_route in self.ns.connections):
            await self.ns.connections[self.ns.def_route].send(msg)
            return None
        else:
            return 'no route'
    
    async def recvfrom(self, timeout=None):
        # throw asyncio.TimeoutError if timeout
        return await asyncio.wait_for(self.recv_q.get(), timeout=timeout)


class CDWebSocketNS():
    def __init__(self, addr, def_route=None):
        self.addr = addr
        self.def_route = def_route
        self.connections = {} # id: ws
        self.sockets = {}     # port: CDWebSocket


# cd_ws_def_ns = CDWebSocketNS('server')



================================================
FILE: doc/shortcuts.md
================================================

### CDPnP App Shortcuts

```
<-   : Move left     : Left arrow
->   : Move right    : Right arrow
^    : Move forward  : Up arrow
v    : Move backward : Down arrow
Up   : Move up       : ' (")
Down : Move down     : / (?)
CW   : CW rotate     : , (<)
CCW  : CCW rotate    : . (>)
       Set move step : 1, 2, 3, 4
       Toggle pause  : Space
```


================================================
FILE: html/calibration.js
================================================
/*
 * Software License Agreement (MIT License)
 *
 * Author: Duke Fong <d@d-l.io>
 */

import { L } from './utils/lang.js'
import { readable_float, sleep } from './utils/helper.js';
import { get_camera_cfg, get_motor_pos, set_motor_pos, check_suck_pressure, set_pump, get_cv_cur,
         z_keep_high, enable_force, cam_comp_snap, set_camera_cfg, set_vision_cfg } from './dev_cmd.js';
import { set_step, get_step_safe, set_comp_search, get_comp_search } from './pos_list.js';
import { csa_to_page_input, input_change } from './input_ctrl.js';
import { csa, cal_grab_ofs, rotate_vector } from './index.js';


document.getElementById('btn_cali_offset').onclick = async function() {
    if (!document.getElementById('camera_detect').value) {
        alert(L("please set camera vision detect method!"));
        return;
    }
    if (!document.getElementById('btn_stop').disabled) {
        alert(L("please stop smt first!"));
        return;
    }
    if (document.getElementById('camera_dev').value != '1' || !document.getElementById('camera_en').checked) {
        console.log("auto enable camera before run task");
        document.getElementById('camera_dev').value = 1;
        document.getElementById('camera_en').checked = true;
        await document.getElementById('camera_dev').onchange();
    }
    document.getElementById('btn_run').disabled = true;
    document.getElementById('btn_stop').disabled = false;
    csa.stop = false;
    csa.comp_height = null;
    document.getElementById('cur_height').innerText = `--`;
    
    let z_middle = Math.min(csa.cur_pos[2] + csa.cam_dz, -2);
    if (csa.cur_pos[2] < z_middle) {
        csa.cur_pos[2] = z_middle;
        await set_motor_pos(100);
    }
    
    set_step(1);
    let cali_cnt = 0;
    let cali_dat = [];
    
    while (true) {
        let step = get_step_safe();
        let search = get_comp_search();
        console.log(`step: ${step}, search: ${search}, cali_cnt: ${cali_cnt}`);
        
        if (csa.stop)
            break;
        if (document.getElementById('pause_en').checked) {
            console.log(`enter wait`);
            while (document.getElementById('pause_en').checked)
                await sleep(100);
            console.log(`exit wait`);
            continue;
        }
        
        if (step == 0 || step == 4 || step == 5) {
            set_step(1);
            continue;
        }
        
        if (step == 1) { // goto_comp
            console.log('fsm goto_comp');
            document.getElementById('camera_light1').checked = false;
            await document.getElementById('camera_light1').onchange();
            await z_keep_high(70);
            if (cali_dat.length) {
                csa.cur_pos[0] = cali_dat[cali_dat.length-1][0];
                csa.cur_pos[1] = cali_dat[cali_dat.length-1][1];
            } else {
                csa.cur_pos[0] = csa.comp_search[search][0];
                csa.cur_pos[1] = csa.comp_search[search][1];
            }
            csa.cur_pos[3] = 0;
            if (csa.cur_pos[2] != csa.comp_top_z) {
                await set_motor_pos(70);
                csa.cur_pos[2] = csa.comp_top_z;
                await set_motor_pos(100);
            } else {
                await set_motor_pos(100);
            }
            set_step(2);
            continue;
        }
        
        if (step == 2) { // snap
            console.log('fsm snap');
            document.getElementById('camera_light1').checked = false;
            await document.getElementById('camera_light1').onchange();
            let ret = await cam_comp_snap();
            if (ret < 0) {
                if (++search >= csa.comp_search.length)
                    search = 0;
                set_comp_search(search);
                set_step(1);
            } else {
                set_step(3);
                console.log(`cali_cnt: ${cali_cnt}, csa.cur_pos: ${csa.cur_pos}`);
                cali_dat.push([csa.cur_pos[0], csa.cur_pos[1]]);
                if (++cali_cnt >= 2)
                    break;
            }
            continue;
        }
        
        if (step == 3) { // pickup
            console.log('fsm pickup');
            csa.cur_pos[0] -= csa.grab_ofs[0];
            csa.cur_pos[1] -= csa.grab_ofs[1];
            csa.cur_pos[3] = 0;
            if (csa.comp_height != null)
                csa.cur_pos[2] = csa.comp_base_z + csa.comp_height + 1; // 1mm space
            await set_motor_pos(100);
            await sleep(800);
            await enable_force();
            csa.cur_pos[2] = csa.comp_base_z - 1;
            await set_motor_pos(100, csa.motor_speed >= 0.6 ? 12000 : 6000);
            await set_pump(csa.pump_hw_ver == 'v1' ? 2 : -70);
            if (csa.comp_height == null) {
                await get_motor_pos();
                csa.comp_height = Math.max(parseFloat((csa.cur_pos[2] - csa.comp_base_z).toFixed(3)), 0);
                document.getElementById('cur_height').innerText = `${csa.comp_height}`;
            }
            await sleep(600);
            await z_keep_high(70, 260000);
            if (await check_suck_pressure())
                break;
            csa.cur_pos[3] = 180;
            await set_motor_pos(100);
            set_step(6);
            continue;
        }
        
        if (step == 6) { // putdown
            console.log('fsm putdown');
            if (csa.comp_height != null)
                csa.cur_pos[2] = csa.comp_base_z + csa.comp_height + 1; // 1mm space
            await set_motor_pos(100);

            await sleep(800);
            await enable_force();
            csa.cur_pos[2] = csa.pcb_base_z - 1;
            await set_motor_pos(100, csa.motor_speed >= 0.6 ? 12000 : 6000);
            await set_pump(csa.pump_hw_ver == 'v1' ? 1 : 0);

            await sleep(500);
            await z_keep_high(70);
            set_step(1);
            await set_pump(0);
        }
        
    }
    console.log('cali_offset finished');
    csa.stop = true;
    document.getElementById('btn_run').disabled = false;
    document.getElementById('btn_stop').disabled = true;
    csa.comp_height = null;
    document.getElementById('cur_height').innerText = `--`;
    csa.cur_pos[3] = 0;
    document.getElementById('btn_pld_clear').onclick();
    await set_motor_pos();
    set_step(1);
    
    if (cali_cnt < 2)
        return;
    
    let delta_x = (cali_dat[1][0] - cali_dat[0][0]) / 2;
    let delta_y = (cali_dat[1][1] - cali_dat[0][1]) / 2;
    
    let is_confirm = confirm(`Add offset: ${readable_float(delta_x)}, ${readable_float(delta_y)}`);
    if (is_confirm) {
        csa.grab_ofs[0] += delta_x;
        csa.grab_ofs[1] += delta_y;
        csa_to_page_input();
        await input_change();
        console.log('offset added');
    }
};


document.getElementById('btn_cali_nozzle').onclick = async function() {
    if (!document.getElementById('camera_detect').value.startsWith('cali_nozzle')) {
        alert(L("please set camera vision detect method for nozzle calibration!"));
        return;
    }
    console.log("auto enable camera before run task");
    document.getElementById('camera_dev').value = 2;
    document.getElementById('camera_en').checked = true;
    document.getElementById('camera_light1').checked = false;
    document.getElementById('camera_light2').checked = true;
    await document.getElementById('camera_dev').onchange();
    
    await set_camera_cfg(document.getElementById('camera_detect').value, csa.nozzle_expos);
    await set_vision_cfg();
    
    await window.btn_goto_xyz('user_pos0'); // goto position for calibration
    
    if (document.getElementById('pause_en').checked)
        return;
    let ret = await cam_comp_snap();
    console.log(`csa.cur_pos at 0: ${csa.cur_pos}`);
    let x0 = csa.cur_pos[0];
    let y0 = csa.cur_pos[1];
    
    csa.cur_pos[3] = 180;
    await set_motor_pos(100);
    
    if (document.getElementById('pause_en').checked)
        return;
    ret = await cam_comp_snap();
    console.log(`csa.cur_pos at 180: ${csa.cur_pos}`);
    let x180 = csa.cur_pos[0];
    let y180 = csa.cur_pos[1];
    
    console.log('cali_nozzle finished');
    let delta_x = (x180 - x0) / 2;
    let delta_y = (y180 - y0) / 2;
    
    let is_confirm = confirm(`Update nozzle cali data: ${readable_float(delta_x)}, ${readable_float(delta_y)} and the cali pos`);
    if (is_confirm) {
        csa.nozzle_cali[0] = delta_x;
        csa.nozzle_cali[1] = delta_y;
        csa.user_pos[0][1][0] = x0 + delta_x;
        csa.user_pos[0][1][1] = y0 + delta_y;
        csa_to_page_input();
        await input_change();
        console.log('nozzle cali data updated');
        
    }
    if (document.getElementById('pause_en').checked)
        return;
    csa.cur_pos[0] = csa.user_pos[0][1][0];
    csa.cur_pos[1] = csa.user_pos[0][1][1];
    csa.cur_pos[3] = 0;
    await set_motor_pos();
};


document.getElementById('btn_update_vision').onclick = async function() {
    let debug_en = document.getElementById('vision_debug_en').checked;
    await set_camera_cfg(document.getElementById('camera_detect').value, csa.nozzle_expos);
    await set_vision_cfg(debug_en);
};


document.getElementById('btn_cali_cam1').onclick = async function() {
    if (!document.getElementById('camera_detect').value) {
        alert(L("please set camera vision detect method!"));
        return;
    }
    if (!document.getElementById('btn_stop').disabled) {
        alert(L("please stop smt first!"));
        return;
    }
    if (document.getElementById('camera_dev').value != '1' || !document.getElementById('camera_en').checked) {
        console.log("auto enable camera before run task");
        document.getElementById('camera_dev').value = 1;
        document.getElementById('camera_en').checked = true;
        await document.getElementById('camera_dev').onchange();
    }
    document.getElementById('btn_run').disabled = true;
    document.getElementById('btn_stop').disabled = false;
    csa.stop = false;
    csa.comp_height = null;
    document.getElementById('cur_height').innerText = `--`;
    
    let z_middle = Math.min(csa.cur_pos[2] + csa.cam_dz, -2);
    if (csa.cur_pos[2] < z_middle) {
        csa.cur_pos[2] = z_middle;
        await set_motor_pos(100);
    }
    
    set_step(1);
    let angle_avg = null;
    
    while (true) {
        let step = get_step_safe();
        let search = get_comp_search();
        console.log(`step: ${step}, search: ${search}`);
        
        if (csa.stop)
            break;
        if (document.getElementById('pause_en').checked) {
            console.log(`enter wait`);
            while (document.getElementById('pause_en').checked)
                await sleep(100);
            console.log(`exit wait`);
            continue;
        }
        
        if (step != 1 && step != 2) {
            set_step(1);
            continue;
        }
        
        if (step == 1) { // goto_comp
            console.log('fsm goto_comp');
            document.getElementById('camera_light1').checked = false;
            await document.getElementById('camera_light1').onchange();
            await z_keep_high(70);
            csa.cur_pos[0] = csa.comp_search[search][0];
            csa.cur_pos[1] = csa.comp_search[search][1];
            csa.cur_pos[3] = 0;
            if (csa.cur_pos[2] != csa.comp_top_z) {
                await set_motor_pos(70);
                csa.cur_pos[2] = csa.comp_top_z;
                await set_motor_pos(100);
            } else {
                await set_motor_pos(100);
            }
            set_step(2);
            continue;
        }
        
        if (step == 2) { // snap
            console.log('fsm snap');
            document.getElementById('camera_light1').checked = false;
            await document.getElementById('camera_light1').onchange();
            let ret = await cam_comp_snap();
            if (ret < 0) {
                if (++search >= csa.comp_search.length)
                    search = 0;
                set_comp_search(search);
                set_step(1);
            } else {
                set_step(1);
                let x_bk = csa.cur_pos[0];
                let y_bk = csa.cur_pos[1];
                console.log(`csa.cur_pos: ${csa.cur_pos}`);
                
                let cv = await get_cv_cur();
                console.log(`cv 0,0: ${cv[0]}, ${cv[1]}`);

                csa.cur_pos[0] = x_bk + 4;
                csa.cur_pos[1] = y_bk;
                await set_motor_pos(100);
                await sleep(1000);
                cv = await get_cv_cur();
                console.log(`cv 4,0: ${cv[0]}, ${cv[1]}`);
                let x1 = [cv[0], -cv[1]];

                csa.cur_pos[0] = x_bk - 4;
                csa.cur_pos[1] = y_bk;
                await set_motor_pos(100);
                await sleep(1000);
                cv = await get_cv_cur();
                console.log(`cv -4,0: ${cv[0]}, ${cv[1]}`);
                let x2 = [cv[0], -cv[1]];

                csa.cur_pos[0] = x_bk;
                csa.cur_pos[1] = y_bk - 4;
                await set_motor_pos(100);
                await sleep(1000);
                cv = await get_cv_cur();
                console.log(`cv 0,-4: ${cv[0]}, ${cv[1]}`);
                let y1 = [cv[0], -cv[1]];

                csa.cur_pos[0] = x_bk;
                csa.cur_pos[1] = y_bk + 4;
                await set_motor_pos(100);
                await sleep(1000);
                cv = await get_cv_cur();
                console.log(`cv 0,4: ${cv[0]}, ${cv[1]}`);
                let y2 = [cv[0], -cv[1]];
                
                csa.cur_pos[0] = x_bk;
                csa.cur_pos[1] = y_bk;
                await set_motor_pos(100);
                
                let xv = [x2[0]-x1[0], x2[1]-x1[1]];
                let yv = [y2[0]-y1[0], y2[1]-y1[1]];
                
                let xa = Math.atan2(xv[1], xv[0]) / Math.PI * 180;
                let ya = Math.atan2(yv[1], yv[0]) / Math.PI * 180 - 90;
                angle_avg = (xa + ya) / 2;
                
                console.log(`xv: ${xv}`);
                console.log(`yv: ${yv}`);
                console.log(`xa: ${xa}`);
                console.log(`ya: ${ya}`);
                console.log(`avg: ${angle_avg}`);
                break;
            }
            continue;
        }
    }
    console.log('cali_offset finished');
    csa.stop = true;
    document.getElementById('btn_run').disabled = false;
    document.getElementById('btn_stop').disabled = true;
    csa.comp_height = null;
    document.getElementById('cur_height').innerText = `--`;
    csa.cur_pos[3] = 0;
    document.getElementById('btn_pld_clear').onclick();
    await set_motor_pos();
    set_step(1);
    
    if (angle_avg != null) {
        let is_confirm = confirm(`Update cam1 angle err: ${readable_float(angle_avg)}`);
        if (is_confirm) {
            csa.cam_angle[0] = angle_avg;
            csa_to_page_input();
            await input_change();
            console.log('cam1 angle updated');
        }
    }
};


document.getElementById('btn_cali_cam2').onclick = async function() {
    if (!document.getElementById('camera_detect').value) {
        alert(L("please set camera vision detect method!"));
        return;
    }
    if (!document.getElementById('btn_stop').disabled) {
        alert(L("please stop smt first!"));
        return;
    }
    if (document.getElementById('camera_dev').value != '1' || !document.getElementById('camera_en').checked) {
        console.log("auto enable camera before run task");
        document.getElementById('camera_dev').value = 1;
        document.getElementById('camera_en').checked = true;
        await document.getElementById('camera_dev').onchange();
    }
    document.getElementById('btn_run').disabled = true;
    document.getElementById('btn_stop').disabled = false;
    csa.stop = false;
    csa.comp_height = null;
    document.getElementById('cur_height').innerText = `--`;
    
    let z_middle = Math.min(csa.cur_pos[2] + csa.cam_dz, -2);
    if (csa.cur_pos[2] < z_middle) {
        csa.cur_pos[2] = z_middle;
        await set_motor_pos(100);
    }
    
    set_step(1);
    let angle_avg = null;
    
    while (true) {
        let step = get_step_safe();
        let search = get_comp_search();
        console.log(`step: ${step}, search: ${search}`);
        
        if (csa.stop)
            break;
        if (document.getElementById('pause_en').checked) {
            console.log(`enter wait`);
            while (document.getElementById('pause_en').checked)
                await sleep(100);
            console.log(`exit wait`);
            continue;
        }
        
        if (step == 0 || step == 5) {
            set_step(1);
            continue;
        }
        
        if (step == 1) { // goto_comp
            console.log('fsm goto_comp');
            document.getElementById('camera_light1').checked = false;
            await document.getElementById('camera_light1').onchange();
            await z_keep_high(70);
            csa.cur_pos[0] = csa.comp_search[search][0];
            csa.cur_pos[1] = csa.comp_search[search][1];
            csa.cur_pos[3] = 0;
            if (csa.cur_pos[2] != csa.comp_top_z) {
                await set_motor_pos(70);
                csa.cur_pos[2] = csa.comp_top_z;
                await set_motor_pos(100);
            } else {
                await set_motor_pos(100);
            }
            set_step(2);
            continue;
        }
        
        if (step == 2) { // snap
            console.log('fsm snap');
            document.getElementById('camera_light1').checked = false;
            await document.getElementById('camera_light1').onchange();
            let ret = await cam_comp_snap();
            if (ret < 0) {
                if (++search >= csa.comp_search.length)
                    search = 0;
                set_comp_search(search);
                set_step(1);
            } else {
                set_step(3);
                console.log(`csa.cur_pos: ${csa.cur_pos}`);
            }
            continue;
        }
        
        if (step == 3) { // pickup
            console.log('fsm pickup');
            let grab_ofs = cal_grab_ofs(0);
            csa.cur_pos[0] -= grab_ofs[0];
            csa.cur_pos[1] -= grab_ofs[1];
            csa.cur_pos[3] = 0;
            if (csa.comp_height != null)
                csa.cur_pos[2] = csa.comp_base_z + csa.comp_height + 1; // 1mm space
            await set_motor_pos(100);
            if (csa.comp_height != null && document.getElementById('less_detect').checked) {
                csa.cur_pos[2] = csa.comp_base_z + csa.comp_height - 0.5; // -0.5mm space
                await set_motor_pos(100);
            } else {
                await sleep(800);
                await enable_force();
                csa.cur_pos[2] = csa.comp_base_z - 1;
                await set_motor_pos(100, csa.motor_speed >= 0.6 ? 12000 : 6000);
            }
            await set_pump(csa.pump_hw_ver == 'v1' ? 2 : -70);
            if (csa.comp_height == null) {
                await get_motor_pos();
                csa.comp_height = Math.max(parseFloat((csa.cur_pos[2] - csa.comp_base_z).toFixed(3)), 0);
                document.getElementById('cur_height').innerText = `${csa.comp_height}`;
            }
            await sleep(600);
            await z_keep_high(70, 260000);
            
            set_step(4); // 2nd check
            continue;
        }

        if (step == 4) { // 2nd check
            console.log('fsm check');
            let detect_bk = document.getElementById('camera_detect').value;
            document.getElementById('camera_dev').value = 2;
            document.getElementById('camera_light2').checked = true;
            document.getElementById('camera_detect').value = "";
            await document.getElementById('camera_dev').onchange();
            await set_camera_cfg("cali_pad");
            
            let cali_pos = csa.user_pos[0][1];
            csa.cur_pos[3] = -csa.cv_cur_r;
            let err = rotate_vector(csa.cur_pos[3], csa.nozzle_cali);
            
            let z_middle = Math.min(Math.max(cali_pos[2], csa.cur_pos[2]) + csa.comp_height, -2);
            if (csa.cur_pos[2] < z_middle) {
                csa.cur_pos[2] = z_middle;
                await set_motor_pos(70);
            }
            csa.cur_pos[0] = cali_pos[0] - err[0];
            csa.cur_pos[1] = cali_pos[1] - err[1];
            await set_motor_pos(70);
            csa.cur_pos[2] = cali_pos[2] + csa.comp_height;
            await set_motor_pos(100);
            
            while (document.getElementById('pause_en').checked)
                await sleep(100);
            if (csa.stop)
                break;
            
            let ret = await cam_comp_snap();
            csa.cur_pos[3] -= csa.cv_cur_r;
            await set_motor_pos(100);
            
            if (!document.getElementById('putdown_en').checked) {
                document.getElementById('pause_en').checked = true;
            } else {
                await sleep(800);
            }
            while (document.getElementById('pause_en').checked)
                await sleep(100);
            if (csa.stop)
                break;

            let x_bk = csa.cur_pos[0];
            let y_bk = csa.cur_pos[1];
            console.log(`csa.cur_pos: ${csa.cur_pos}`);
            
            let cv = await get_cv_cur();
            console.log(`cv 0,0: ${cv[0]}, ${cv[1]}`);

            csa.cur_pos[0] = x_bk - 4;
            csa.cur_pos[1] = y_bk;
            await set_motor_pos(100);
            await sleep(1000);
            cv = await get_cv_cur();
            console.log(`cv -4,0: ${cv[0]}, ${cv[1]}`);
            let x1 = [cv[0], -cv[1]];

            csa.cur_pos[0] = x_bk + 4;
            csa.cur_pos[1] = y_bk;
            await set_motor_pos(100);
            await sleep(1000);
            cv = await get_cv_cur();
            console.log(`cv 4,0: ${cv[0]}, ${cv[1]}`);
            let x2 = [cv[0], -cv[1]];

            csa.cur_pos[0] = x_bk;
            csa.cur_pos[1] = y_bk + 4;
            await set_motor_pos(100);
            await sleep(1000);
            cv = await get_cv_cur();
            console.log(`cv 0,4: ${cv[0]}, ${cv[1]}`);
            let y1 = [cv[0], -cv[1]];

            csa.cur_pos[0] = x_bk;
            csa.cur_pos[1] = y_bk - 4;
            await set_motor_pos(100);
            await sleep(1000);
            cv = await get_cv_cur();
            console.log(`cv 0,-4: ${cv[0]}, ${cv[1]}`);
            let y2 = [cv[0], -cv[1]];
            
            csa.cur_pos[0] = x_bk;
            csa.cur_pos[1] = y_bk;
            await set_motor_pos(100);
            await sleep(1000);
            
            let xv = [x2[0]-x1[0], x2[1]-x1[1]];
            let yv = [y2[0]-y1[0], y2[1]-y1[1]];
            
            let xa = Math.atan2(xv[1], xv[0]) / Math.PI * 180;
            let ya = Math.atan2(yv[1], yv[0]) / Math.PI * 180 - 90;
            angle_avg = (xa + ya) / 2;
            
            console.log(`xv: ${xv}`);
            console.log(`yv: ${yv}`);
            console.log(`xa: ${xa}`);
            console.log(`ya: ${ya}`);
            console.log(`avg: ${angle_avg}`);

            document.getElementById('camera_dev').value = 1;
            document.getElementById('camera_detect').value = detect_bk;
            document.getElementById('camera_light2').checked = false;
            await document.getElementById('camera_dev').onchange();
            
            csa.cur_pos[0] = csa.comp_search[search][0] - csa.grab_ofs[0];
            csa.cur_pos[1] = csa.comp_search[search][1] - csa.grab_ofs[1];
            csa.cur_pos[3] = 0;
            await set_motor_pos(100);
            
            set_step(6);
            continue;
        }

        if (step == 6) { // putdown
            console.log('fsm putdown');
            if (csa.comp_height != null)
                csa.cur_pos[2] = csa.comp_base_z + csa.comp_height + 1; // 1mm space
            await set_motor_pos(100);

            await sleep(800);
            await enable_force();
            csa.cur_pos[2] = csa.pcb_base_z - 1;
            await set_motor_pos(100, csa.motor_speed >= 0.6 ? 12000 : 6000);
            await set_pump(csa.pump_hw_ver == 'v1' ? 1 : 0);
            await sleep(500);
            await z_keep_high(70);
            set_step(1);
            await set_pump(0);
            
            csa.cur_pos[0] = csa.comp_search[search][0];
            csa.cur_pos[1] = csa.comp_search[search][1];
            csa.cur_pos[3] = 0;
            await set_motor_pos(100);
            break;
        }
        
    }
    console.log('cali_offset finished');
    csa.stop = true;
    document.getElementById('btn_run').disabled = false;
    document.getElementById('btn_stop').disabled = true;
    csa.comp_height = null;
    document.getElementById('cur_height').innerText = `--`;
    csa.cur_pos[3] = 0;
    document.getElementById('btn_pld_clear').onclick();
    await set_motor_pos();
    set_step(1);
    
    if (angle_avg != null) {
        let is_confirm = confirm(`Update cam2 angle err: ${readable_float(angle_avg)}`);
        if (is_confirm) {
            csa.cam_angle[1] = angle_avg;
            csa_to_page_input();
            await input_change();
            console.log('cam2 angle updated');
        }
    }
};


export { };


================================================
FILE: html/dev_cmd.js
================================================
/*
 * Software License Agreement (MIT License)
 *
 * Author: Duke Fong <d@d-l.io>
 */

import { L } from './utils/lang.js'
import { sleep } from './utils/helper.js';
import { csa_to_page_pos } from './input_ctrl.js';
import { csa, cmd_sock } from './index.js';


function update_aux() {
    let dx = csa.cur_pos[0] - csa.old_pos[0];
    let dy = csa.cur_pos[1] - csa.old_pos[1];
    let dz = csa.cur_pos[2] - csa.old_pos[2];
    let dr = csa.cur_pos[3] - csa.old_pos[3];
    csa.aux_pos = [csa.aux_pos[0] + dx, csa.aux_pos[1] + dy, csa.aux_pos[2] + dz, csa.aux_pos[3] + dr];
    csa.old_pos = [csa.cur_pos[0], csa.cur_pos[1], csa.cur_pos[2], csa.cur_pos[3]];
}

async function get_camera_cfg() {
    cmd_sock.flush();
    await cmd_sock.sendto({'action': 'get_camera_cfg'}, ['server', 'dev']);
    let dat = await cmd_sock.recvfrom(500);
    console.log('get_camera_cfg ret', dat);
    document.getElementById('camera_en').checked = !!dat[0].enable;
    document.getElementById('camera_light1').checked = !!dat[0].light1;
    document.getElementById('camera_light2').checked = !!dat[0].light2;
    document.getElementById('camera_dev').value = dat[0].dev;
    document.getElementById('camera_detect').value = dat[0].detect;
}

async function set_camera_cfg(_detect=null, exposure=20) {
    let dev = Number(document.getElementById('camera_dev').value);
    let detect = _detect == null ? document.getElementById('camera_detect').value : _detect;
    let light1 = document.getElementById('camera_light1').checked;
    let light2 = document.getElementById('camera_light2').checked;
    cmd_sock.flush();
    await cmd_sock.sendto({'action': 'set_camera_cfg', 'dev': dev, 'detect': detect,
                           'light1': light1, 'light2': light2, 'expos': exposure}, ['server', 'dev']);
    let dat = await cmd_sock.recvfrom(500);
    console.log(`set_camera_cfg ${dev}, ${detect} ret`, dat);
}

async function set_vision_cfg(debug=false) {
    cmd_sock.flush();
    await cmd_sock.sendto({'action': 'set_vision_cfg', 'nozzle_thresh': csa.nozzle_thresh, 'debug': debug}, ['server', 'dev']);
    let dat = await cmd_sock.recvfrom(500);
    console.log(`set_vision_cfg ret`, dat);
}

async function get_motor_pos() {
    cmd_sock.flush();
    await cmd_sock.sendto({'action': 'get_motor_pos'}, ['server', 'dev']);
    let dat = await cmd_sock.recvfrom(500);
    console.log('get_cur_pos ret', dat);
    csa.cur_pos = dat[0];
    update_aux();
    csa_to_page_pos();
}

async function set_motor_pos(wait=0, speed=600000) {
    console.log(`set_motor_pos: ${csa.cur_pos}, wait: ${wait}`);
    if (wait > 0)
        wait = 100; // not use curve path
    if (wait < 0)
        wait *= -1; // force wait by percent
    if (speed == 600000)
        speed = Math.round(speed * csa.motor_speed);
    csa.cur_pos[0] = Math.min(Math.max(csa.cur_pos[0], 2), 300)
    csa.cur_pos[1] = Math.min(Math.max(csa.cur_pos[1], 2), 249)
    csa.cur_pos[2] = Math.min(Math.max(csa.cur_pos[2], -92), -2)
    update_aux();
    csa_to_page_pos();
    cmd_sock.flush();
    await cmd_sock.sendto({'action': 'set_motor_pos', 'pos': csa.cur_pos, 'wait': wait, 'speed': speed}, ['server', 'dev']);
    let dat = await cmd_sock.recvfrom(wait ? 300000 : 2000);
    console.log('set_motor_pos ret', dat);
}

async function get_pump_hw_ver() {
    cmd_sock.flush();
    await cmd_sock.sendto({'action': 'get_pump_hw_ver'}, ['server', 'dev']);
    let dat = await cmd_sock.recvfrom(2000);
    console.log(`get_pump_hw_ver ret`, dat);
    return dat ? dat[0] : null;
}

async function get_pump_pressure() {
    cmd_sock.flush();
    await cmd_sock.sendto({'action': 'get_pump_pressure'}, ['server', 'dev']);
    let dat = await cmd_sock.recvfrom(2000);
    console.log(`get_pump_pressure ret`, dat);
    return dat ? dat[0] : null;
}

async function check_suck_pressure(should_empty=false, threshold=-30) {
    if (csa.pump_hw_ver == 'v1')
        return false;
    let val = await get_pump_pressure();
    if (should_empty) {
        if (val > threshold)
            return false;
        alert(L("suck not empty!"));
    } else {
        if (val <= threshold)
            return false;
        alert(L("suck empty!"));
    }
    return true;
}

// hw_v1: 0: off, 1: valve on, 2: pump on
// hw_v2: target pressure
async function set_pump(val) {
    cmd_sock.flush();
    await cmd_sock.sendto({'action': 'set_pump', 'val': val}, ['server', 'dev']);
    let dat = await cmd_sock.recvfrom(2000);
    console.log(`set_pump ${val} ret`, dat);
    document.getElementById('pump_en').checked = val ? true : false;
    csa.pump_suck_on = val < 0;
}

async function pcb2xyz(pcb, cam, x, y) {
    cmd_sock.flush();
    await cmd_sock.sendto({'action': 'pcb2xyz', 'pcb': pcb, 'cam': cam, 'x': x, 'y': y}, ['server', 'dev']);
    let dat = await cmd_sock.recvfrom(1000);
    console.log('pcb2xyz ret', dat ? dat[0] : null);
    return dat ? dat[0] : null;
}

async function z_keep_high(wait=0, speed=600000) {
    let min_z = Math.max(csa.pcb_top_z, csa.comp_top_z);
    if (document.getElementById('pump_en').checked && csa.comp_height != null)
        min_z += csa.comp_height;
    if (csa.cur_pos[2] < min_z) {
        csa.cur_pos[2] = min_z;
        await set_motor_pos(wait, speed);
    }
}

async function enable_force() {
    cmd_sock.flush();
    await cmd_sock.sendto({'action': 'enable_force'}, ['server', 'dev']);
    let dat = await cmd_sock.recvfrom(1000);
    console.log('enable_force ret', dat);
    return dat ? dat[0] : null;
}

async function get_cv_cur() {
    cmd_sock.flush();
    await cmd_sock.sendto({'action': 'get_cv_cur'}, ['server', 'dev']);
    let dat = await cmd_sock.recvfrom(1000);
    console.log('get_cv_cur ret', dat);
    return dat ? dat[0] : null;
}

// 10mm / 344 pixel
let DIV_MM2PIXEL = 10/344;

async function cam_comp_snap(times=3, max_err=0.02) {
    let dev = Number(document.getElementById('camera_dev').value);
    let sign = dev == 1 ? 1 : -1;
    let cam_width = dev == 1 ? 600 : 800;
    let cam_height = dev == 1 ? 800 : 600;
    let cv = null;
    
    await sleep(600);
    for (let i = 0; i < times; i++) {
        cv = await get_cv_cur();
        if (cv) {
            let dx = (cv[0] - cam_width/2) * DIV_MM2PIXEL
            let dy = (cv[1] - cam_height/2) * DIV_MM2PIXEL
            console.log(`cv i: ${i}, dx: ${dx}, dy: ${dy}`)
            csa.cur_pos[0] += dx * sign
            csa.cur_pos[1] += dy * sign
            await set_motor_pos(100);
            if (times == 1) // for preload_ctrl
                return null;
            if (Math.abs(dx) < max_err && Math.abs(dy) < max_err) {
                console.log('cv breaks at:', i)
                i = times; // breaks
            }
        }
        await sleep(600);
    }
    cv = await get_cv_cur();
    csa.cv_cur_r = cv ? cv[2] : null; // [-89, 90]
    return cv ? 0 : -1;
}


export {
    get_camera_cfg, set_camera_cfg, get_motor_pos, set_motor_pos, get_pump_hw_ver, get_pump_pressure, check_suck_pressure, set_pump,
    pcb2xyz, z_keep_high, enable_force, get_cv_cur, cam_comp_snap, set_vision_cfg
};


================================================
FILE: html/index.html
================================================
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1">
<!--link rel="icon" type="image/png" href="./img/icon.png" /-->
<title>CDPNP Tools - V3.12</title>

<link rel="stylesheet" href="./libs/bulma-0.9.4.css">
<!-- fix sub border missing by override bulma css -->
<style>
.content table tbody tr:last-child td {
    border-bottom-width: 1px;
}
.content table tbody tr:last-child > td {
    border-bottom-width: 0;
}
</style>

</head>
<body>

<section class="section">
    <div class="container">
        <h1 class="title is-size-4">CDPnP</h1>
        <button class="button is-small" id="btn_load_csv">${L('Load')} CSV</button>
        <button class="button is-small" id="btn_export_prj">${L('Export')} ${L('Project')}</button>
        <button class="button is-small" id="btn_import_prj">${L('Import')} ${L('Project')}</button> &nbsp;|&nbsp;
        <button class="button is-small" id="btn_reset_cfg">${L('Reset')} ${L('Config')}</button>
        <button class="button is-small" id="btn_export_cfg">${L('Export')} ${L('Config')}</button>
        <button class="button is-small" id="btn_import_cfg">${L('Import')} ${L('Config')}</button> &nbsp;|&nbsp;
        <button class="button is-small" id="btn_save_cfg">${L('Save')} ${L('Config')} & ${L('Project')}</button>
        <br><br>
        
        <span style="display: inline-block; min-width: 70px;">${L('Cur pos')}:</span> <span id="cur_pos">--</span>
        <br>
        <span style="display: inline-block; min-width: 70px;">${L('Aux pos')}:</span> <span id="aux_pos">--</span>
        &nbsp;
        <button class="button is-small" id="btn_reset_aux">${L('Reset aux')}</button>
        <br><br>
        
        <button class="button is-small" onclick="move_button([-1, 0, 0, 0])"><-</button>
        <button class="button is-small" onclick="move_button([ 1, 0, 0, 0])">-></button>
        <button class="button is-small" onclick="move_button([0, -1, 0, 0])">^</button>
        <button class="button is-small" onclick="move_button([0,  1, 0, 0])">v</button>
        <button class="button is-small" onclick="move_button([0, 0,  1, 0])">${L('Up')}</button>
        <button class="button is-small" onclick="move_button([0, 0, -1, 0])">${L('Down')}</button>
        <button class="button is-small" onclick="move_button([0, 0, 0, -1])">${L('CCW')}</button>
        <button class="button is-small" onclick="move_button([0, 0, 0,  1])">${L('CW')}</button>
        &nbsp;
        <select id="move_speed">
            <option value="1">x1</option>
            <option value="2">x2</option>
            <option value="3" selected>x3</option>
            <option value="4">x4</option>
        </select> <span>${L('Step size')}</span>
        &nbsp;
        <select id="motor_speed" onchange="input_change()">
            <option value="0.1">10%</option>
            <option value="0.2">20%</option>
            <option value="0.3">30%</option>
            <option value="0.4">40%</option>
            <option value="0.5" selected>50%</option>
            <option value="0.6">60%</option>
            <option value="0.7">70%</option>
            <option value="0.8">80%</option>
            <option value="0.9">90%</option>
            <option value="1">100%</option>
        </select> <span>${L('Motor speed')}</span>
        &nbsp;
        <label class="checkbox"><input type="checkbox" id="less_detect" checked> <span>${L('Less detect')}</span></label>
        <br>
        <br>
        <button class="button is-small" onclick="btn_select_step(0)" id="btn_step0">${L('Show target')}</button> >
        <button class="button is-small" onclick="btn_select_step(1)" id="btn_step1">${L('Goto comp')}</button> >
        <button class="button is-small" onclick="btn_select_step(2)" id="btn_step2">${L('Snap')}</button> >
        <button class="button is-small" onclick="btn_select_step(3)" id="btn_step3">${L('Pickup')}</button> >
        <button class="button is-small" onclick="btn_select_step(4)" id="btn_step4">${L('Check')}</button> >
        <button class="button is-small" onclick="btn_select_step(5)" id="btn_step5">${L('Goto pcb')}</button> >
        <button class="button is-small" onclick="btn_select_step(6)" id="btn_step6">${L('Put down')}</button>
        &nbsp;|&nbsp;
        <label class="checkbox"><input type="checkbox" id="pause_en"> <span>${L('Pause')}</span></label>
        <button class="button is-small" id="btn_stop" disabled>${L('Stop')}</button>
        <button class="button is-small" id="btn_run">${L('Run')}</button>
        <br>
        
        
        <br>
        <label class="checkbox"><input type="checkbox" id="pump_en"> <span>${L('Enable pump')}</span></label>
        &nbsp;
        <span>${L('Camera')}</span>
        <select id="camera_dev">
            <option value="1" selected>1</option>
            <option value="2">2</option>
        </select>
        <select id="camera_detect">
            <option value="default" selected>${L('Default')}</option>
            <option value="limit_angle">${L('Limit angle')}</option>
            <option value="">-- ${L('None')} --</option>
            <option value="cali_nozzle_13_4">${L('Cali nozzle')} 502</option> <!-- Φ13mm ±4mm -->
            <option value="cali_nozzle_22_4">${L('Cali nozzle')} 503</option>
            <option value="cali_nozzle_37_6">${L('Cali nozzle')} 504</option>
            <option value="cali_nozzle_49_6">${L('Cali nozzle')} 506</option>
        </select>
        <label class="checkbox"><input type="checkbox" id="camera_en"> <span>${L('Enable')}</span></label>
        <label class="checkbox"><input type="checkbox" id="camera_light1">_<input type="checkbox" id="camera_light2"> <span>${L('Light')}</span></label>
        <button class="button is-small" onclick="camera_update_bg()" title="${L('Use the current camera image as background')}">${L('New BG')}</button>
        <button class="button is-small" onclick="camera_remove_bg()">${L('Del BG')}</button>
        &nbsp;
        <label class="checkbox"><input type="checkbox" id="show_target"> <span>${L('Show target')}</span></label>
        <label class="checkbox"><input type="checkbox" id="check2_en"> <span>${L('2nd check')}</span></label>
        <label class="checkbox"><input type="checkbox" id="putdown_en" checked> <span>${L('Put down')}</span></label>
        
        <br>
        <span style="display: inline-block; min-width: 138px;" title="${L('Distance from the camera center to the nozzle rotation center')}">
            ${L('Grab offset')}:</span>
        <input type="text" id="grab_ofs" onchange="input_change()">
        <button class="button is-small goto_btn" onclick="btn_grab_ofs(null, 1)"><-</button>
        <button class="button is-small goto_btn" onclick="btn_grab_ofs(null, -1)">-></button>
        <button class="button is-small goto_btn" id="btn_cali_offset">${L('Calibration')}</button>
        <br>
        <span style="display: inline-block; min-width: 138px;" title="${L('Offset from the nozzle rotation center to nozzle at 0 degrees')}">
            ${L('Nozzle cali')} (0°):</span>
        <input type="text" id="nozzle_cali" onchange="input_change()">
        <button class="button is-small goto_btn" onclick="btn_grab_ofs(0, 1)"><-</button>
        <button class="button is-small goto_btn" onclick="btn_grab_ofs(0, -1)">-></button>
        <button class="button is-small goto_btn" id="btn_cali_nozzle">${L('Calibration')}</button>
        <span>${L('Exposure')}</span>: <input type="text" size="5" id="nozzle_expos" onchange="input_change()">
        <span>${L('Threshold')}</span>: <input type="text" size="5" id="nozzle_thresh" onchange="input_change()">
        <label class="checkbox"><input type="checkbox" id="vision_debug_en"> <span>${L('Debug')}</span></label>
        <button class="button is-small goto_btn" id="btn_update_vision">${L('Set')}</button>
        <br>
        
        <span style="display: inline-block; min-width: 138px;">
            ${L('Cam angle err')}:</span>
        <input type="text" id="cam1_angle" onchange="input_change()">
        <button class="button is-small goto_btn" id="btn_cali_cam1">${L('Cali cam')}1</button>
        <input type="text" id="cam2_angle" onchange="input_change()">
        <button class="button is-small goto_btn" id="btn_cali_cam2">${L('Cali cam')}2</button>
        
        <div id="input_search"></div>
        <span style="display: inline-block; min-width: 138px;" title="${L('Height of nozzle to target when focusing (better not modify this)')}">
            ${L('Camera delta z')}:</span>
        <input type="text" id="comp_cam_dz" onchange="input_change()">
        <br>
        <span style="display: inline-block; min-width: 138px;" title="${L('Tray surface height')}">${L('Comp base z')}:</span>
        <input type="text" id="comp_base_z" onchange="input_change()">
        <button class="button is-small goto_btn" onclick="btn_goto_z('comp_base_z')">${L('Goto')}</button>
        <button class="button is-small" onclick="btn_update_z('comp_base_z')">${L('Update')}</button> &nbsp;|&nbsp;
        <button class="button is-small goto_btn" onclick="btn_goto_z('comp_top_z')">${L('Comp base')} + cam dz</button> &nbsp;|&nbsp;
        <button class="button is-small goto_btn" onclick="btn_detect_z()">${L('Detect bottom')}</button>
        <br>
        <span style="display: inline-block; min-width: 138px;" title="${L('PCB surface height')}">${L('PCB base z')}:</span>
        <input type="text" id="pcb_base_z" onchange="input_change()">
        <button class="button is-small goto_btn" onclick="btn_goto_z('pcb_base_z')">${L('Goto')}</button>
        <button class="button is-small" onclick="btn_update_z('pcb_base_z')">${L('Update')}</button> &nbsp;|&nbsp;
        <button class="button is-small goto_btn" onclick="btn_goto_z('pcb_top_z')">${L('PCB base')} + cam dz</button> &nbsp;|&nbsp;
        <button class="button is-small goto_btn" onclick="btn_goto_z('inc_camera_dz')">${L('Cur')} + cam dz</button>
        <button class="button is-small goto_btn" onclick="btn_goto_z('dec_camera_dz')">${L('Cur')} - cam dz</button>
        
        <div id="input_fiducial">
            <span style="display: inline-block; min-width: 138px; padding-bottom: 0.1em;" title="${L('Fiducial coordinates in PCB file')}">
                ${L('Fiducial pcb')}:</span>
            <input type="text" id="fiducial_pcb0" onchange="input_change()">
            <input type="text" id="fiducial_pcb1" onchange="input_change()">
        </div>
        <div id="input_user"></div>
    </div>

    <br>
    <div class="container">
        <h1 class="title is-size-4"><span>${L('Preload')}</span> <input type="checkbox" id="preload_en" onchange="input_change()" checked></h1>
        <div class="content" style="font-size: 12px;">
            
            <span style="display: inline-block; min-width: 100px;">${L('Preload search')}:</span>
            <input type="text" id="pld_search" onchange="input_change()">
            <button class="button is-small goto_btn" onclick="btn_goto_xy('pld_search')">${L('Goto')}</button>
            <button class="button is-small" onclick="btn_update_xy('pld_search')">${L('Update')}</button>
            <br>
            <span style="display: inline-block; min-width: 100px;">${L('Preload base z')}:</span>
            <input type="text" id="pld_base_z" onchange="input_change()">
            <button class="button is-small goto_btn" onclick="btn_goto_z('pld_base_z')">${L('Goto')}</button>
            <button class="button is-small" onclick="btn_update_z('pld_base_z')">${L('Update')}</button> &nbsp;|&nbsp;
            <button class="button is-small goto_btn" onclick="btn_goto_z('pld_top_z')">${L('Preload base')} + cam dz</button>
            <br><br>
            
            <span>${L('Comp offset')}</span>: <input type="text" id="pld_comp_offset" onchange="input_change()" size="2" value="2"> mm &nbsp;|&nbsp;
            
            <span>${L('Comp space')}</span>: <input type="text" id="pld_comp_space" onchange="input_change()" size="2" value="1"> <span>${L('unit')}</span> (4mm) &nbsp;|&nbsp;
            
            <span>${L('Start at')}</span>: <input type="text" id="pld_start_at" onchange="input_change()" size="2" value="0.5"> <span>${L('unit')}</span> &nbsp;|&nbsp;
            
            <span>${L('Amount')}</span>: <input type="text" id="pld_amount" size="2" value="--"> pcs
            <br><br>
            
            <span>${L('Target grid')}</span>: <input type="text" id="pld_tgt_grid" onchange="input_change()" size="2" value="2, 1.5"> mm &nbsp;|&nbsp;
            
            <span>${L('Rotate')}</span>:
            <select id="pld_rotate" onchange="input_change()">
                <option value="0" selected>0</option>
                <option value="90">90</option>
                <option value="-90">-90</option>
                <option value="180">180</option>
            </select> &nbsp;|&nbsp;
            
            <span>${L('Count')}</span>: <input type="text" id="pld_count" size="2" value="0"> &nbsp;
            <button class="button is-small" id="btn_pld_clear">${L('Clear')}</button>  &nbsp;|&nbsp;
            <button class="button is-small" id="btn_pld_stop" disabled>${L('Stop')}</button>
            <button class="button is-small" id="btn_pld_run">${L('Run')}</button>
            
        </div>
    </div>

    <br>
    <div class="container">
        <h1 class="title is-size-4"><span>${L('Components')}</span></h1>
        <span>
            ${L('Current')}: <span id="cur_progress">-- / --</span> &nbsp;|&nbsp;
                     <span id="cur_comp">-- -- --</span> <span id="cur_board">--</span> &nbsp;|&nbsp;
                     ${L('Height')}: <span id="cur_height">--</span>
            <br>
            ${L('Next')}: <span id="next_comp">-- -- --</span> &nbsp;|&nbsp; ${L('Amount')}: <span id="next_total">--</span>
        </span>
        <br>
        
        <div class="content" style="font-size: 12px;">
              <table style="width: 80em;">
                  <thead>
                      <tr>
                          <td style="width: 20em;">${L('Footprint')}</td> <td>${L('Offset')}</td>
                          <td style="width: 20em;">${L('Value')}</td> <td style="width: 10em;">${L('Reference')}</td>
                          <td style="width: 7em;">X</td> <td style="width: 7em;">Y</td> <td style="width: 7em;">R</td>
                      </tr>
                  </thead>
                  <tbody class="js-sortable-table" id="pos_list">
                  </tbody>
              </table>
        </div>
    </div>

    <br>
    <div class="container" disabled>
        <h1 class="title is-size-4"><span>${L('Offsets')}</span></h1>
        <div class="content" style="font-size: 12px;">
            <textarea rows="5" cols="60" id="offset_config" onchange="input_change()"
                      placeholder="wildcard: grab_offs_x, y | pcb_offs_x, y"></textarea>
            <br>
            <button class="button is-small" id="offset_apply">${L('Apply')}</button> <span>(${L('or refresh page')})</span>
            
        </div>
    </div>
    
</section>

<input id="input_file" type="file" style="display:none;">
</body>
<script src="./libs/html5sortable-3dac3ba.min.js" defer></script>
<script src="./libs/msgpack-ygoe-9045d01.min.js" defer></script>
<script type="module" src="./index.js"></script>
</html>


================================================
FILE: html/index.js
================================================
/*
 * Software License Agreement (MIT License)
 *
 * Author: Duke Fong <d@d-l.io>
 */

import { L } from './utils/lang.js'
import { sleep, blob2dat, cpy, deep_merge } from './utils/helper.js';
import { CDWebSocket, CDWebSocketNS } from './utils/cd_ws.js';
import { Idb } from './utils/idb.js';
import { search_comp_parents, search_next_comp, select_comp, move_to_comp, get_comp_values, pos_to_page,
         set_board, get_board_safe, set_step, get_step_safe, set_comp_search, get_comp_search, get_comp_safe } from './pos_list.js';
import { input_init, csa_to_page_input } from './input_ctrl.js';
import { get_camera_cfg, get_motor_pos, set_motor_pos, get_pump_hw_ver, check_suck_pressure, set_pump, pcb2xyz,
         z_keep_high, enable_force, cam_comp_snap, set_camera_cfg } from './dev_cmd.js';

let csa_dft = {
    cur_pos: [0, 0, 0, 0],
    old_pos: [0, 0, 0, 0],
    aux_pos: [0, 0, 0, 0],
    
    grab_ofs:    [33.53, 6.45], // camera to grab rotation center
    nozzle_cali: [-0.1, -0.05], // error vector at 0 degree
    cam_angle: [0, 0],          // cam1 and cam2 err angle
    comp_search: [[55, 142], [65, 142]],
    cam_dz: 7,
    comp_base_z: -89.9,
    pcb_base_z: -88.9,
    fiducial_pcb: [[-26.375, 21.35], [-6.3, 4.75]],
    fiducial_cam: [[[89.67, 175.0], [109.9, 158.6]], [[120.7, 175.3], [140.8, 158.9]]],
    
    user_pos: [
        ['Calibration', [27.95, 10.87, -29.57]],
        ['Idle', [132.0, 62.0, -45.0]]
    ],
    
    comp_height: null,
    motor_speed: 0.5,
    
    nozzle_expos: 5000,
    nozzle_thresh: 199,
    
    offset_config: "",
    
    pld_search: [47, 142],
    pld_base_z: -89.9,
    pld_comp_offset: 3.5,
    pld_comp_space: 0.5,
    pld_start_at: -0.5,
    pld_tgt_grid: [2.0, 2.0],
    pld_rotate: 0,
    pld_enable: 0,
    
    pump_hw_ver: null,
    pump_suck_on: false
};

let csa = {};
deep_merge(csa, csa_dft);

let csa_need_save = ['grab_ofs', 'nozzle_cali', 'cam_angle', 'comp_search', 'cam_dz', 'comp_base_z', 'pcb_base_z', 'fiducial_pcb', 'fiducial_cam',
                     'user_pos', 'motor_speed', 'nozzle_expos', 'nozzle_thresh',
                     'offset_config', 'pld_search', 'pld_base_z', 'pld_comp_offset', 'pld_comp_space', 'pld_start_at', 'pld_tgt_grid', 'pld_rotate', 'pld_enable'];
let csa_prj_export = ['pcb_base_z', 'fiducial_pcb', 'fiducial_cam', 'offset_config'];
let csa_cfg_export = ['grab_ofs', 'nozzle_cali', 'cam_angle', 'comp_search', 'cam_dz', 'comp_base_z', 'pcb_base_z', 'user_pos'];

let db = null;
let ws_ns = new CDWebSocketNS('/');
let cmd_sock = new CDWebSocket(ws_ns, 'cmd');


//         |  (90 degree)
//         |
//         |
// --------+-------->  x (0 degree)
//         |
//         |
//         |
//         v  y (-90 degree)

function rotate_vector(angle, vector) {
    let rad = -angle * Math.PI / 180;
    let new_vector = [
        Math.cos(rad) * vector[0] - Math.sin(rad) * vector[1],
        Math.sin(rad) * vector[0] + Math.cos(rad) * vector[1]
    ];
    return new_vector;
}

function cal_grab_ofs(angle, err=null) {
    if (err == null)
        err = [csa.nozzle_cali[0], csa.nozzle_cali[1]];
    let err_at_angle = rotate_vector(angle, err);
    return [csa.grab_ofs[0] + err_at_angle[0], csa.grab_ofs[1] + err_at_angle[1]];
}


document.getElementById('btn_run').onclick = async function() {
    let comp = get_comp_safe();
    if (!comp) {
        alert(L("list empty!"));
        return;
    }
    if (!document.getElementById('camera_detect').value) {
        alert(L("please set camera vision detect method!"));
        return;
    }
    if (document.getElementById('camera_dev').value != '1' || !document.getElementById('camera_en').checked) {
        console.log("auto enable camera before run task");
        document.getElementById('camera_dev').value = 1;
        document.getElementById('camera_en').checked = true;
        await document.getElementById('camera_dev').onchange();
    }
    document.getElementById('btn_run').disabled = true;
    document.getElementById('btn_stop').disabled = false;
    csa.stop = false;
    let parents_pre = null;
    
    let z_middle = Math.min(csa.cur_pos[2] + csa.cam_dz, -2);
    if (csa.cur_pos[2] < z_middle) {
        csa.cur_pos[2] = z_middle;
        await set_motor_pos(100);
    }
    
    // error vector and nozzle angle before putdown
    let nozzle_err_vector = null;
    let nozzle_err_angle = null;
    
    while (true) {
        let comp = get_comp_safe();
        if (!comp)
            break;
        let board = get_board_safe();
        let step = get_step_safe();
        let search = get_comp_search();
        console.log(`comp: ${comp}, board: ${board}, step: ${step}, search: ${search}`);
        
        let parents = search_comp_parents(comp);
        if (parents_pre && parents_pre[0] != parents[0]) {
            csa.comp_height = null;
            document.getElementById('cur_height').innerText = `--`;
        }
        if (parents_pre && (parents_pre[0] != parents[0] || parents_pre[1] != parents[1])) {
            document.getElementById('pause_en').checked = true;
            //document.getElementById('camera_light1').checked = true;
            await set_camera_cfg("");
            set_board(board);
            await move_to_comp(comp);
            if (csa.pump_suck_on && !(await check_suck_pressure(true)))
                await set_pump(0);
        }
        console.log(`parents: ${parents_pre} -> ${parents}`);
        
        if (csa.stop)
            break;
        if (document.getElementById('pause_en').checked) {
            console.log(`enter wait`);
            if (step == 3) {
                console.log(`exit pickup before pause`);
                set_step(1);
            }
            while (document.getElementById('pause_en').checked)
                await sleep(100);
            console.log(`exit wait`);
            parents_pre = null;
            continue;
        }
        
        let comp_val = get_comp_values(comp);
        let comp_xyz = await pcb2xyz(csa.fiducial_pcb, csa.fiducial_cam[board], comp_val[0], comp_val[1]);
        let [,,comp_offsets] = search_comp_parents(comp);
        
        if (step == 0) { // show_target
            console.log('fsm show target');
            //document.getElementById('camera_light1').checked = true;
            await set_camera_cfg("");
            await z_keep_high(70);
            csa.cur_pos[0] = comp_xyz[0];
            csa.cur_pos[1] = comp_xyz[1];
            if (csa.cur_pos[2] != csa.pcb_top_z) {
                await set_motor_pos(70);
                csa.cur_pos[2] = csa.pcb_top_z;
                await set_motor_pos(100);
            } else {
                await set_motor_pos(100);
            }
            await sleep(600);
            set_step(1);
            continue;
        }
        
        if (step == 1) { // goto_comp
            console.log('fsm goto_comp');
            document.getElementById('camera_light1').checked = false;
            await document.getElementById('camera_light1').onchange();
            await z_keep_high(70);
            csa.cur_pos[0] = csa.comp_search[search][0];
            csa.cur_pos[1] = csa.comp_search[search][1];
            csa.cur_pos[3] = 0;
            if (csa.cur_pos[2] != csa.comp_top_z) {
                await set_motor_pos(70);
                csa.cur_pos[2] = csa.comp_top_z;
                await set_motor_pos(100);
            } else {
                await set_motor_pos(100);
            }
            if (csa.pump_suck_on) {
                if (!(await check_suck_pressure(true))) {
                    await set_pump(0);
                } else {
                    document.getElementById('pause_en').checked = true;
                    continue;
                }
            }
            set_step(2);
            continue;
        }
        
        if (step == 2) { // snap
            console.log('fsm snap');
            document.getElementById('camera_light1').checked = false;
            await document.getElementById('camera_light1').onchange();
            let ret = await cam_comp_snap();
            if (ret < 0) {
                if (++search >= csa.comp_search.length)
                    search = 0;
                set_comp_search(search);
                set_step(1);
            } else {
                csa.cv_cur_r -= csa.cam_angle[0];
                set_step(3);
            }
            continue;
        }
        
        if (step == 3) { // pickup
            console.log('fsm pickup');
            let grab_ofs = cal_grab_ofs(0);
            csa.cur_pos[0] -= grab_ofs[0];
            csa.cur_pos[1] -= grab_ofs[1];
            csa.cur_pos[3] = 0;
            if (csa.comp_height != null)
                csa.cur_pos[2] = csa.comp_base_z + csa.comp_height + 1; // 1mm space
            if (comp_offsets[0][0] != 0 || comp_offsets[0][1] != 0) {
                let rad = -csa.cv_cur_r * Math.PI / 180;
                let offset = [
                    Math.cos(rad) * comp_offsets[0][0] - Math.sin(rad) * comp_offsets[0][1],
                    Math.sin(rad) * comp_offsets[0][0] + Math.cos(rad) * comp_offsets[0][1]
                ];
                csa.cur_pos[0] += offset[0];
                csa.cur_pos[1] += offset[1];
            }
            await set_motor_pos(100);
            if (csa.comp_height != null && document.getElementById('less_detect').checked) {
                csa.cur_pos[2] = csa.comp_base_z + csa.comp_height - 0.5; // -0.5mm space
                await set_motor_pos(100);
            } else {
                await sleep(800);
                await enable_force();
                csa.cur_pos[2] = csa.comp_base_z - 1;
                await set_motor_pos(100, csa.motor_speed >= 0.6 ? 12000 : 6000);
            }
            await set_pump(csa.pump_hw_ver == 'v1' ? 2 : -70);
            if (csa.comp_height == null) {
                await get_motor_pos();
                csa.comp_height = Math.max(parseFloat((csa.cur_pos[2] - csa.comp_base_z).toFixed(3)), 0);
                document.getElementById('cur_height').innerText = `${csa.comp_height}`;
            }
            await sleep(600);
            await z_keep_high(70, 260000);
            if (await check_suck_pressure()) {
                await set_pump(0);
                document.getElementById('pause_en').checked = true;
                set_step(1);
                continue;
            }
            
            if (document.getElementById('check2_en').checked) {
                set_step(4);
            } else {
                // nozzle angle and nozzle_err_vector when comp rotate to 0 degrees
                nozzle_err_angle = -csa.cv_cur_r;
                nozzle_err_vector = rotate_vector(nozzle_err_angle, csa.nozzle_cali)
                set_step(5);
            }
            continue;
        }
        
        if (step == 4) { // 2nd check
            console.log('fsm check');
            let detect_bk = document.getElementById('camera_detect').value;
            document.getElementById('camera_dev').value = 2;
            document.getElementById('camera_light2').checked = true;
            document.getElementById('camera_detect').value = "";
            await document.getElementById('camera_dev').onchange();
            await set_camera_cfg("cali_pad");
            
            let cali_pos = csa.user_pos[0][1];
            csa.cur_pos[3] = -csa.cv_cur_r;
            let err = rotate_vector(csa.cur_pos[3], csa.nozzle_cali);
            
            let z_middle = Math.min(Math.max(cali_pos[2], csa.cur_pos[2]) + csa.comp_height, -2);
            if (csa.cur_pos[2] < z_middle) {
                csa.cur_pos[2] = z_middle;
                await set_motor_pos(70);
            }
            csa.cur_pos[0] = cali_pos[0] - err[0];
            csa.cur_pos[1] = cali_pos[1] - err[1];
            await set_motor_pos(70);
            csa.cur_pos[2] = cali_pos[2] + csa.comp_height;
            await set_motor_pos(100);
            
            while (document.getElementById('pause_en').checked)
                await sleep(100);
            if (csa.stop)
                break;
            
            await cam_comp_snap(3, 0.2);
            csa.cur_pos[3] -= csa.cv_cur_r;
            await set_motor_pos(100);
            await cam_comp_snap();
            
            if (!document.getElementById('putdown_en').checked) {
                document.getElementById('pause_en').checked = true;
            } else {
                await sleep(800);
            }
            while (document.getElementById('pause_en').checked)
                await sleep(100);
            if (csa.stop)
                break;
            
            nozzle_err_vector = [cali_pos[0] - csa.cur_pos[0], cali_pos[1] - csa.cur_pos[1]];
            nozzle_err_angle = csa.cur_pos[3];
            
            nozzle_err_angle += csa.cam_angle[1];
            nozzle_err_vector = rotate_vector(csa.cam_angle[1], nozzle_err_vector);

            document.getElementById('camera_dev').value = 1;
            document.getElementById('camera_detect').value = detect_bk;
            document.getElementById('camera_light2').checked = false;
            await document.getElementById('camera_dev').onchange();
            set_step(5);
            continue;
        }
        
        if (step == 5) { // goto_pcb
            console.log('fsm goto_pcb');
            await z_keep_high(70);
            // optimize the rotation angle for faster speed
            if (nozzle_err_angle != null) {
                let rad = (comp_val[2] + nozzle_err_angle + comp_xyz[2]) * Math.PI / 180;
                let tgt_angle = Math.atan2(Math.sin(rad), Math.cos(rad)) * 180 / Math.PI;
                if (document.getElementById('camera_detect').value == 'default' &&
                        Math.abs(tgt_angle) > 90 && !document.getElementById('check2_en').checked) {
                    console.log('  rotate 180, before:', tgt_angle);
                    tgt_angle = tgt_angle > 90 ? tgt_angle - 180 : tgt_angle + 180;
                }
                nozzle_err_vector = rotate_vector(tgt_angle - nozzle_err_angle, nozzle_err_vector);
                csa.cur_pos[3] = tgt_angle;
            } else {
                nozzle_err_vector = [0, 0];
            }
            
            csa.cur_pos[0] = comp_xyz[0] - csa.grab_ofs[0] - nozzle_err_vector[0];
            csa.cur_pos[1] = comp_xyz[1] - csa.grab_ofs[1] - nozzle_err_vector[1];
            await set_motor_pos(70);
            set_step(6);
            continue;
        }
        
        if (step == 6) { // putdown
            console.log('fsm putdown');
            if (csa.comp_height != null)
                csa.cur_pos[2] = csa.pcb_base_z + csa.comp_height + 1; // 1mm space
            await set_motor_pos(100);
            if (!document.getElementById('putdown_en').checked) {
                document.getElementById('pause_en').checked = true;
                while (document.getElementById('pause_en').checked)
                    await sleep(100);
                // manual select comp during putdown pause
                await set_pump(csa.pump_hw_ver == 'v1' ? 1 : 0);
                await sleep(500);
                await z_keep_high(70);
                await set_pump(0);
                if (get_comp_safe() != comp || get_board_safe() != board)
                    continue;
            } else {
                if (csa.comp_height != null && document.getElementById('less_detect').checked) {
                    csa.cur_pos[2] = csa.pcb_base_z + csa.comp_height - 0.5; // -0.5mm space
                    await set_motor_pos(100);
                } else {
                    await sleep(800);
                    await enable_force();
                    csa.cur_pos[2] = csa.pcb_base_z - 1;
                    await set_motor_pos(100, csa.motor_speed >= 0.6 ? 12000 : 6000);
                }
                //await set_pump(csa.pump_hw_ver == 'v1' ? 1 : 30); // blow
                await set_pump(csa.pump_hw_ver == 'v1' ? 1 : 0);
                await sleep(500);
                await z_keep_high(70);
                await set_pump(0);
                if (csa.pump_hw_ver != 'v1')
                    await set_pump(-70); // check nozzle empty
            }
            set_step(Number(!document.getElementById('show_target').checked));
        }
        
        if (++board >= csa.fiducial_cam.length) {
            set_board(0);
            let next = search_next_comp(comp);
            select_comp(next);
            parents_pre = parents;
            if (!next)
                break;
        } else {
            set_board(board);
            select_comp(comp); // update progress
        }
    }
    console.log('all comp finished');
    csa.cur_pos[3] = 0;
    document.getElementById('btn_pld_clear').onclick();
    await set_motor_pos();
    if (csa.pump_hw_ver != 'v1' && csa.pump_suck_on) {
        await sleep(1000);
        if (!(await check_suck_pressure(true)))
            await set_pump(0);
    }
    csa.comp_height = null;
    document.getElementById('cur_height').innerText = `--`;
    csa.stop = true;
    document.getElementById('btn_run').disabled = false;
    document.getElementById('btn_stop').disabled = true;
};

document.getElementById('btn_stop').onclick = function() {
    csa.stop = true;
    document.getElementById('pause_en').checked = false;
    document.getElementById('btn_stop').disabled = true;
    set_step(1);
};


function init_ws() {
    let ws_url = 'ws://' + window.location.hostname + ':8900';
    let ws = new WebSocket(ws_url);
    
    ws.onopen = async function(evt) {
        console.log("ws onopen");
        ws_ns.connections['server'] = ws;
        await get_motor_pos();
        await get_camera_cfg();
        csa.pump_hw_ver = await get_pump_hw_ver();
    }
    ws.onmessage = async function(evt) {
        let dat = await blob2dat(evt.data);
        var msg = msgpack.deserialize(dat);
        //console.log("Received dat", msg);
        var sock = ws_ns.sockets[msg['dst'][1]];
        sock.recv_q.put([msg['dat'], msg['src']]);
    }
    ws.onerror = function(evt) {
        console.log("ws onerror: ", evt);
        document.body.style.backgroundColor = "gray";
    }
    ws.onclose = function(evt) {
        delete ws_ns.connections['server'];
        console.log('ws disconnected');
        document.body.style.backgroundColor = "gray";
    }
}


window.addEventListener('load', async function() {
    console.log("load app");
    
    // apply translation
    for (let tag of ['button', 'span', 'option', 'td']) {
        let elems = document.getElementsByTagName(tag);
        for (let e of elems) {
            e.innerHTML = eval("`" + e.innerHTML + "`");
            if (e.title)
                e.title = eval("`" + e.title + "`");
        }
    }
    
    db = await new Idb();
    init_ws();
    
    let csa_pre = await db.get('tmp', 'csa');
    if (csa_pre)
        cpy(csa, csa_pre, csa_need_save);
    let pos = await db.get('tmp', 'list');
    if (pos)
        pos_to_page(pos);
    input_init();
    csa_to_page_input();
});

export {
    csa_dft, csa, cmd_sock, db, csa_need_save, csa_prj_export, csa_cfg_export, cal_grab_ofs, rotate_vector
};


================================================
FILE: html/input_ctrl.js
================================================
/*
 * Software License Agreement (MIT License)
 *
 * Author: Duke Fong <d@d-l.io>
 */

import { L } from './utils/lang.js'
import { read_file, download, readable_float, cpy, sleep, wildcard_test } from './utils/helper.js';
import { set_camera_cfg, get_motor_pos, set_motor_pos, set_pump, enable_force } from './dev_cmd.js';
import { csa_dft, csa, cmd_sock, db, csa_need_save, csa_prj_export, csa_cfg_export, cal_grab_ofs } from './index.js';
import { pld_csa_to_page, pld_csa_from_page } from './preload_ctrl.js';
import { pos_to_page, pos_from_page } from './pos_list.js';
import { } from './calibration.js';


function disable_goto_btn(val) {
    let btn = document.getElementsByClassName('goto_btn');
    for (let b of btn)
        b.disabled = val;
    document.getElementById('pos_list').style.backgroundColor = val ? '#f0f0f0' : '';
}

function auto_hide() {
    let skip_hide = true;
    for (let i = 0; i < 8; i++) {
        if (i < csa.comp_search.length) {
            document.getElementById(`search_grp${i}`).style.display = '';
        } else {
            document.getElementById(`search_grp${i}`).style.display = skip_hide ? '' : 'none';
            skip_hide = false;
        }
    }
    skip_hide = true;
    for (let i = 0; i < 10; i++) {
        if (i < csa.fiducial_cam.length) {
            document.getElementById(`fiducial_grp${i}`).style.display = '';
        } else {
            document.getElementById(`fiducial_grp${i}`).style.display = skip_hide ? '' : 'none';
            skip_hide = false;
        }
    }
    skip_hide = true;
    for (let i = 0; i < 8; i++) {
        if (i < csa.user_pos.length) {
            document.getElementById(`user_grp${i}`).style.display = '';
        } else {
            document.getElementById(`user_grp${i}`).style.display = skip_hide ? '' : 'none';
            skip_hide = false;
        }
    }
}

function csa_to_page_pos()
{
    document.getElementById('cur_pos').innerHTML =
        `${readable_float(csa.cur_pos[0])}, ${readable_float(csa.cur_pos[1])},
         ${readable_float(csa.cur_pos[2])}, ${readable_float(csa.cur_pos[3])}`;
    document.getElementById('aux_pos').innerHTML = 
        `${readable_float(csa.aux_pos[0])}, ${readable_float(csa.aux_pos[1])},
         ${readable_float(csa.aux_pos[2])}, ${readable_float(csa.aux_pos[3])}`;
}

function csa_to_page_input()
{
    document.getElementById('motor_speed').value = csa.motor_speed;
    document.getElementById('nozzle_expos').value = csa.nozzle_expos;
    document.getElementById('nozzle_thresh').value = csa.nozzle_thresh;
    document.getElementById('grab_ofs').value =
        `${readable_float(csa.grab_ofs[0])}, ${readable_float(csa.grab_ofs[1])}`;
    document.getElementById('nozzle_cali').value =
        `${readable_float(csa.nozzle_cali[0])}, ${readable_float(csa.nozzle_cali[1])}`;
    document.getElementById('cam1_angle').value = `${readable_float(csa.cam_angle[0])}`;
    document.getElementById('cam2_angle').value = `${readable_float(csa.cam_angle[1])}`;
    
    for (let i = 0; i < 8; i++) {
        if (i < csa.comp_search.length) {
            document.getElementById(`comp_search${i}`).value = 
                `${readable_float(csa.comp_search[i][0])}, ${readable_float(csa.comp_search[i][1])}`;
        } else {
            document.getElementById(`comp_search${i}`).value = '';
        }
    }
    
    document.getElementById('comp_cam_dz').value = `${readable_float(csa.cam_dz)}`;
    document.getElementById('comp_base_z').value = `${readable_float(csa.comp_base_z)}`;
    document.getElementById('pcb_base_z').value = `${readable_float(csa.pcb_base_z)}`;
    csa.pcb_top_z = csa.pcb_base_z + csa.cam_dz;
    csa.comp_top_z = csa.comp_base_z + csa.cam_dz;
    
    document.getElementById('fiducial_pcb0').value =
        `${readable_float(csa.fiducial_pcb[0][0])}, ${readable_float(csa.fiducial_pcb[0][1])}`;
    document.getElementById('fiducial_pcb1').value =
        `${readable_float(csa.fiducial_pcb[1][0])}, ${readable_float(csa.fiducial_pcb[1][1])}`;
    
    for (let i = 0; i < 10; i++) {
        if (i < csa.fiducial_cam.length) {
            document.getElementById(`fiducial_cam${i}_0`).value = 
                `${readable_float(csa.fiducial_cam[i][0][0])}, ${readable_float(csa.fiducial_cam[i][0][1])}`;
            document.getElementById(`fiducial_cam${i}_1`).value = 
                `${readable_float(csa.fiducial_cam[i][1][0])}, ${readable_float(csa.fiducial_cam[i][1][1])}`;
        } else {
            document.getElementById(`fiducial_cam${i}_0`).value = '';
            document.getElementById(`fiducial_cam${i}_1`).value = '';
        }
    }
    
    for (let i = 0; i < 8; i++) {
        if (i < csa.user_pos.length) {
            document.getElementById(`user_pos${i}`).value = 
                `${readable_float(csa.user_pos[i][1][0])}, ${readable_float(csa.user_pos[i][1][1])}, ${readable_float(csa.user_pos[i][1][2])}`;
            document.getElementById(`user_name${i}`).value = csa.user_pos[i][0];
        } else {
            document.getElementById(`user_pos${i}`).value = '';
            document.getElementById(`user_name${i}`).value = '';
        }
    }
    
    document.getElementById('offset_config').value = csa.offset_config;
    pld_csa_to_page();
}

function csa_from_page_input()
{
    csa.motor_speed = Number(document.getElementById('motor_speed').value);
    csa.nozzle_expos = Number(document.getElementById('nozzle_expos').value);
    csa.nozzle_thresh = Number(document.getElementById('nozzle_thresh').value);
    let xy_str;
    xy_str = document.getElementById('grab_ofs').value;
    csa.grab_ofs = [Number(xy_str.split(',')[0]), Number(xy_str.split(',')[1])];
    xy_str = document.getElementById('nozzle_cali').value;
    csa.nozzle_cali = [Number(xy_str.split(',')[0]), Number(xy_str.split(',')[1])];
    csa.cam_angle = [Number(document.getElementById('cam1_angle').value),
                     Number(document.getElementById('cam2_angle').value)];
    
    csa.comp_search = [];
    for (let i = 0; ; i++) {
        if (!document.getElementById(`comp_search${i}`))
            break;
        xy_str = document.getElementById(`comp_search${i}`).value;
        if (xy_str)
            csa.comp_search.push([Number(xy_str.split(',')[0]), Number(xy_str.split(',')[1])]);
        else
            break;
    }
    
    csa.cam_dz = Number(document.getElementById('comp_cam_dz').value);
    csa.comp_base_z = Number(document.getElementById('comp_base_z').value);
    csa.pcb_base_z = Number(document.getElementById('pcb_base_z').value);
    csa.pcb_top_z = csa.pcb_base_z + csa.cam_dz;
    csa.comp_top_z = csa.comp_base_z + csa.cam_dz;
    
    xy_str = document.getElementById('fiducial_pcb0').value;
    csa.fiducial_pcb[0] = [Number(xy_str.split(',')[0]), Number(xy_str.split(',')[1])];
    xy_str = document.getElementById('fiducial_pcb1').value;
    csa.fiducial_pcb[1] = [Number(xy_str.split(',')[0]), Number(xy_str.split(',')[1])];
    
    csa.fiducial_cam = [];
    for (let i = 0; ; i++) {
        if (!document.getElementById(`fiducial_cam${i}_0`))
            break;
        xy_str = document.getElementById(`fiducial_cam${i}_0`).value;
        let xy_str2 = document.getElementById(`fiducial_cam${i}_1`).value;
        if (xy_str && xy_str2)
            csa.fiducial_cam.push([[ Number(xy_str.split(',')[0]),  Number(xy_str.split(',')[1])],
                                   [Number(xy_str2.split(',')[0]), Number(xy_str2.split(',')[1])]]);
        else
            break;
    }
    
    csa.user_pos = [];
    for (let i = 0; ; i++) {
        if (!document.getElementById(`user_pos${i}`))
            break;
        let xyz_str = document.getElementById(`user_pos${i}`).value;
        let name_str = document.getElementById(`user_name${i}`).value;
        if (xyz_str)
            csa.user_pos.push([name_str, [Number(xyz_str.split(',')[0]), Number(xyz_str.split(',')[1]), Number(xyz_str.split(',')[2])]]);
        else
            break;
    }
    
    csa.offset_config = document.getElementById('offset_config').value;
    pld_csa_from_page();
}

async function save_cfg() {
    let save = {'cfg_ver': 1};
    cpy(save, csa, csa_need_save);
    await db.set('tmp', 'csa', save);
    await db.set('tmp', 'list', pos_from_page());
    console.log('config saved');
    document.getElementById('btn_save_cfg').style.background = '';
}

async function input_change() {
    console.log('input_change');
    csa_from_page_input();
    auto_hide();
    document.getElementById('btn_save_cfg').style.background = 'yellow';
}
window.input_change = input_change;


window.btn_update_xy = async function(name) {
    let xy = `${readable_float(csa.cur_pos[0])}, ${readable_float(csa.cur_pos[1])}`;
    document.getElementById(name).value = xy;
    await input_change();
};
window.btn_update_xyz = async function(name) {
    let xyz = `${readable_float(csa.cur_pos[0])}, ${readable_float(csa.cur_pos[1])}, ${readable_float(csa.cur_pos[2])}`;
    document.getElementById(name).value = xyz;
    await input_change();
    if (name == "user_pos0")
        document.getElementById('btn_reset_aux').onclick();
};
window.btn_update_z = async function(name) {
    let z = `${readable_float(csa.cur_pos[2])}`;
    document.getElementById(name).value = z;
    await input_change();
};
window.btn_goto_xy = async function(name) {
    let xy_str = document.getElementById(name).value;
    if (!xy_str)
        return;
    disable_goto_btn(true);
    let z = name.startsWith('comp_search') ? csa.comp_top_z : (name == 'pld_search' ? csa.pld_top_z : csa.pcb_top_z);
    let z_middle = Math.min(Math.max(z, csa.cur_pos[2]) + csa.cam_dz, -2);
    if (csa.cur_pos[2] < z_middle) {
        csa.cur_pos[2] = z_middle;
        await set_motor_pos(100);
    }
    csa.cur_pos[0] = Number(xy_str.split(',')[0]);
    csa.cur_pos[1] = Number(xy_str.split(',')[1]);
    csa.cur_pos[3] = 0;
    await set_motor_pos(100);
    csa.cur_pos[2] = z;
    await set_motor_pos(100);
    disable_goto_btn(false);
};
window.btn_goto_xyz = async function(name) {
    let xyz_str = document.getElementById(name).value;
    if (!xyz_str)
        return;
    disable_goto_btn(true);
    let z = Number(xyz_str.split(',')[2]);
    let z_middle = Math.min(Math.max(z, csa.cur_pos[2]) + csa.cam_dz, -2);
    if (csa.cur_pos[2] < z_middle) {
        csa.cur_pos[2] = z_middle;
        await set_motor_pos(100);
    }
    csa.cur_pos[0] = Number(xyz_str.split(',')[0]);
    csa.cur_pos[1] = Number(xyz_str.split(',')[1]);
    csa.cur_pos[3] = 0;
    await set_motor_pos(100);
    csa.cur_pos[2] = z;
    await set_motor_pos(100);
    if (name == "user_pos0")
        document.getElementById('btn_reset_aux').onclick();
    disable_goto_btn(false);
};
window.btn_goto_z = async function(name) {
    disable_goto_btn(true);
    if (name == 'inc_camera_dz') {
        csa.cur_pos[2] = csa.cur_pos[2] + csa.cam_dz;
    } else if (name == 'dec_camera_dz') {
        csa.cur_pos[2] = csa.cur_pos[2] - csa.cam_dz;
    } else if (name == 'pcb_top_z') {
        csa.cur_pos[2] = csa.pcb_base_z + csa.cam_dz;
    } else if (name == 'comp_top_z') {
        csa.cur_pos[2] = csa.pcb_base_z + csa.cam_dz;
    } else if (name == 'pld_top_z') {
        csa.cur_pos[2] = csa.pld_base_z + csa.cam_dz;
    } else {
        csa.cur_pos[2] = Number(document.getElementById(name).value);
    }
    await set_motor_pos(100);
    disable_goto_btn(false);
};
window.btn_grab_ofs = async function(type, dir=1) {
    disable_goto_btn(true);
    let origin_z = csa.cur_pos[2];
    csa.cur_pos[2] = Math.min(csa.cur_pos[2] + csa.cam_dz, -2);
    await set_motor_pos(100);
    let grab_ofs = type == 0 ? cal_grab_ofs(0) : csa.grab_ofs;
    csa.cur_pos[0] -= dir * grab_ofs[0];
    csa.cur_pos[1] -= dir * grab_ofs[1];
    csa.cur_pos[3] = 0;
    await set_motor_pos(100);
    csa.cur_pos[2] = origin_z;
    await set_motor_pos(100);
    disable_goto_btn(false);
};
window.btn_detect_z = async function() {
    disable_goto_btn(true);
    if (!document.getElementById('pump_en').checked) {
        console.log('detect bottom z... (fast)');
        await enable_force();
        csa.cur_pos[2] = -92;
        await set_motor_pos(100, 12000);
        await get_motor_pos();
        console.log('detect bottom z done (fast)');
        csa.cur_pos[2] += 1;
        await set_motor_pos(100);
        await sleep(800);
    }
    console.log('detect bottom z... (slow)');
    await enable_force();
    csa.cur_pos[2] = -92;
    await set_motor_pos(100, 2000);
    await get_motor_pos();
    console.log('detect bottom z done (slow)');
    disable_goto_btn(false);
};


document.getElementById('pump_en').onchange = async function() {
    let pump_en = document.getElementById('pump_en').checked;
    if (pump_en) {
        await set_pump(csa.pump_hw_ver == 'v1' ? 2 : -70);
    } else {
        await set_pump(csa.pump_hw_ver == 'v1' ? 1 : 0);
        await sleep(500);
        await set_pump(0);
    }
};

async function set_camera_en(enable) {
    cmd_sock.flush();
    await cmd_sock.sendto({'action': 'set_camera', 'val': enable}, ['server', 'dev']);
    let dat = await cmd_sock.recvfrom(500);
    console.log(`camera_en ${camera_en} ret`, dat);
    await set_camera_cfg();
};
async function _set_camera_cfg() {
    await set_camera_cfg();
}
async function set_camera_dev() {
    if (document.getElementById('camera_en').checked) {
        await set_camera_en(false);
        await set_camera_cfg();
        await set_camera_en(true);
    } else {
        await set_camera_cfg();
    }
}
document.getElementById('camera_en').onchange = async function() {
    await set_camera_en(document.getElementById('camera_en').checked);
};
document.getElementById('camera_light1').onchange = _set_camera_cfg
document.getElementById('camera_light2').onchange = _set_camera_cfg
document.getElementById('camera_detect').onchange = _set_camera_cfg;
document.getElementById('camera_dev').onchange = set_camera_dev;

async function camera_update_bg()
{
    cmd_sock.flush();
    await cmd_sock.sendto({'action': 'update_camera_bg'}, ['server', 'dev']);
    let dat = await cmd_sock.recvfrom(500);
    console.log(`update_camera_bg ret`, dat);
}
async function camera_remove_bg()
{
    cmd_sock.flush();
    await cmd_sock.sendto({'action': 'remove_camera_bg'}, ['server', 'dev']);
    let dat = await cmd_sock.recvfrom(500);
    console.log(`remove_camera_bg ret`, dat);
}
window.camera_update_bg = camera_update_bg;
window.camera_remove_bg = camera_remove_bg;

document.getElementById('btn_reset_aux').onclick = function() {
    csa.aux_pos = [0, 0, 0, 0];
    csa_to_page_pos();
};

async function move_button(val)
{
    let speed_pow = Number(document.getElementById('move_speed').value - 1);
    let div = Math.pow(10, speed_pow) / 100;
    let dx = val[0] * div;
    let dy = val[1] * div;
    let dz = val[2] * div;
    let dr = val[3] * (div < 10 ? div * 10 : 45);
    csa.cur_pos = [csa.cur_pos[0] + dx, csa.cur_pos[1] + dy, csa.cur_pos[2] + dz, csa.cur_pos[3] + dr];
    await set_motor_pos();
}
window.move_button = move_button;

window.addEventListener('keydown', async function(e) {
    if (document.activeElement.type == 'text' || document.activeElement.type == 'textarea')
        return;
    console.log(e.keyCode);
    if (e.keyCode == 32) { // space
        e.preventDefault();
        document.getElementById('pause_en').checked = !document.getElementById('pause_en').checked;
        document.getElementById('btn_pld_clear').onclick();
        return;
    }
    if (e.keyCode >= 49 && e.keyCode <= 52) { // 1, 2, 3, 4
        e.preventDefault();
        document.getElementById('move_speed').value = String.fromCharCode(e.keyCode);
        return;
    }
    let val = [0, 0, 0, 0];
    if (e.keyCode == 37) // left
        val[0] = -1;
    else if (e.keyCode == 39) // right
        val[0] = 1;
    else if (e.keyCode == 38) // up
        val[1] = -1;
    else if (e.keyCode == 40) // down
        val[1] = 1;
    else if (e.keyCode == 33 || e.keyCode == 222) // page up, "'"
        val[2] = 1;
    else if (e.keyCode == 34 || e.keyCode == 191) // page down, "/"
        val[2] = -1;
    else if (e.keyCode == 188) // "<"
        val[3] = -1;
    else if (e.keyCode == 190) // ">"
        val[3] = 1;
    else
        return;
    e.preventDefault();
    move_button(val);
});

document.getElementById('btn_save_cfg').onclick = async function() {
    await save_cfg();
    alert(L('Saved.'));
};

document.getElementById('btn_reset_cfg').onclick = async function() {
    cpy(csa, csa_dft, csa_cfg_export);
    csa_to_page_input();
    await input_change();
    alert(L('Ok, please save config and refresh page.'));
};

document.getElementById('btn_import_cfg').onclick = async function() {
    //let input = document.createElement('input');
    //cpy(input, {type: 'file', accept: '*.json'}, ['type', 'accept']);
    let input = document.getElementById('input_file');
    input.accept = '.json';
    input.onchange = async function () {
        var files = this.files;
        if (files && files.length) {
            let file = files[0];
            let data = await read_file(file);
            let data_str = new TextDecoder().decode(data);
            let cfg = JSON.parse(data_str);
            if (!cfg || !cfg.version || !cfg.version.startsWith('cdpnp.cfg')) {
                alert(L('Format error'));
                this.value = '';
                return;
            }
            console.log('import cfg dat:', cfg);
            cpy(csa, cfg.csa, csa_cfg_export);
            csa_to_page_input();
            await input_change();
            alert(L('Import config succeeded'));
        }
        this.value = '';
    };
    input.click();
};

document.getElementById('btn_export_cfg').onclick = async function() {
    let c = await db.get('tmp', 'csa');
    let exp_dat = { version: 'cdpnp.cfg v0', csa: {}};
    cpy(exp_dat.csa, csa, csa_cfg_export);
    console.info('export cfg data:', exp_dat);
    let file_dat = JSON.stringify(exp_dat, null, 4);
    download(file_dat, 'cdpnp.cfg.json');
};

document.getElementById('btn_import_prj').onclick = async function() {
    //let input = document.createElement('input');
    //cpy(input, {type: 'file', accept: '*.json'}, ['type', 'accept']);
    let input = document.getElementById('input_file');
    input.accept = '.json';
    input.onchange = async function () {
        var files = this.files;
        if (files && files.length) {
            let file = files[0];
            let data = await read_file(file);
            let data_str = new TextDecoder().decode(data);
            let prj = JSON.parse(data_str);
            if (!prj || !prj.version || !prj.version.startsWith('cdpnp.prj')) {
                alert(L('Format error'));
                this.value = '';
                return;
            }
            console.log('import prj dat:', prj);
            cpy(csa, prj.csa, csa_prj_export);
            csa_to_page_input();
            await input_change();
            pos_to_page(prj.list);
            alert(L('Import project succeeded'));
        }
        this.value = '';
    };
    input.click();
};

document.getElementById('btn_export_prj').onclick = async function() {
    let c = await db.get('tmp', 'csa');
    let l = await db.get('tmp', 'list');
    let exp_dat = { version: 'cdpnp.prj v0', csa: {}, list: l};
    cpy(exp_dat.csa, csa, csa_prj_export);
    console.info('export prj data:', exp_dat);
    let file_dat = JSON.stringify(exp_dat, null, 4);
    download(file_dat, 'cdpnp.prj.json');
};


function offset_apply() {
    let lines = csa.offset_config.split("\n");
    let offsets = {};
    for (let i = 0; i < lines.length; i++) {
        let line = lines[i];
        if (!line || line.startsWith("//"))
            continue;
        let wildcard = line.split(":")[0];
        let xy_str1 = line.split(":")[1].split("|")[0];
        let xy_str2 = line.split(":")[1].split("|")[1];
        let offset1 = [Number(xy_str1.split(',')[0]), Number(xy_str1.split(',')[1])];
        let offset2 = [Number(xy_str2.split(',')[0]), Number(xy_str2.split(',')[1])];
        offsets[wildcard] = `${offset1[0]}, ${offset1[1]} | ${offset2[0]}, ${offset2[1]}`;
    }
    console.log(`offsets:`, offsets);
    
    let footprint_list = pos_list.getElementsByClassName('list_footprint');
    for (let elm of footprint_list) {
        let subs = elm.querySelectorAll('td');
        subs[1].innerText = '--';
    }
    for (let elm of footprint_list) {
        let subs = elm.querySelectorAll('td');
        for (let wildcard in offsets) {
            if (wildcard_test(subs[0].innerText, wildcard))
                subs[1].innerText = offsets[wildcard];
        }
    }
};

document.getElementById('offset_apply').onclick = async function() {
    offset_apply();
    alert(L("Apply OK."));
};


function input_init() {
    let search = document.getElementById('input_search');
    let fiducial = document.getElementById('input_fiducial');
    let user = document.getElementById('input_user');
    for (let i = 0; i < 8; i++) {
        search.insertAdjacentHTML('beforeend', `
            <div id="search_grp${i}">
                <span style="display: inline-block; min-width: 138px;">${L('Comp search')} #${i}:</span>
                <input type="text" id="comp_search${i}" onchange="input_change()">
                <button class="button is-small goto_btn" onclick="btn_goto_xy('comp_search${i}')">${L('Goto')}</button>
                <button class="button is-small" onclick="btn_update_xy('comp_search${i}')">${L('Update')}</button>
                <button class="button is-small" onclick="btn_select_search(${i})" id="btn_comp_search${i}">${L('Select')}</button>
            </div>`);
    }
    for (let i = 0; i < 10; i++) {
        fiducial.insertAdjacentHTML('beforeend', `
            <div id="fiducial_grp${i}">
                <span style="display: inline-block; min-width: 138px;">${L('Fiducial cam')} #${i}:</span>
                <input type="text" id="fiducial_cam${i}_0" onchange="input_change()">
                <button class="button is-small goto_btn" onclick="btn_goto_xy('fiducial_cam${i}_0')">${L('Goto')}</button>
                <button class="button is-small" onclick="btn_update_xy('fiducial_cam${i}_0')">${L('Update')}</button>
                <input type="text" id="fiducial_cam${i}_1" onchange="input_change()">
                <button class="button is-small goto_btn" onclick="btn_goto_xy('fiducial_cam${i}_1')">${L('Goto')}</button>
                <button class="button is-small" onclick="btn_update_xy('fiducial_cam${i}_1')">${L('Update')}</button>
                <button class="button is-small" onclick="btn_select_board(${i})" id="btn_board${i}">${L('Select')}</button>
            </div>`);
    }
    for (let i = 0; i < 8; i++) {
        fiducial.insertAdjacentHTML('beforeend', `
            <div id="user_grp${i}">
                <span style="display: inline-block; min-width: 138px;">${L('User pos')} #${i}:</span>
                <input type="text" id="user_name${i}" onchange="input_change()" placeholder="name">
                <input type="text" id="user_pos${i}" onchange="input_change()">
                <button class="button is-small goto_btn" onclick="btn_goto_xyz('user_pos${i}')">${L('Goto')}</button>
                <button class="button is-small" onclick="btn_update_xyz('user_pos${i}')">${L('Update')}</button>
            </div>`);
    }
    document.getElementById('user_name0').disabled = true;
    document.getElementById('user_name1').disabled = true;
    auto_hide();
}

export {
    input_init, csa_to_page_pos, csa_to_page_input, csa_from_page_input, disable_goto_btn, offset_apply, input_change
};


================================================
FILE: html/libs/bulma-0.9.4.css
================================================
/*! bulma.io v0.9.4 | MIT License | github.com/jgthms/bulma */
/* Bulma Utilities */
.button, .input, .textarea, .select select, .file-cta,
.file-name, .pagination-previous,
.pagination-next,
.pagination-link,
.pagination-ellipsis {
  -moz-appearance: none;
  -webkit-appearance: none;
  align-items: center;
  border: 1px solid transparent;
  border-radius: 4px;
  box-shadow: none;
  display: inline-flex;
  font-size: 1rem;
  height: 2.5em;
  justify-content: flex-start;
  line-height: 1.5;
  padding-bottom: calc(0.5em - 1px);
  padding-left: calc(0.75em - 1px);
  padding-right: calc(0.75em - 1px);
  padding-top: calc(0.5em - 1px);
  position: relative;
  vertical-align: top;
}

.button:focus, .input:focus, .textarea:focus, .select select:focus, .file-cta:focus,
.file-name:focus, .pagination-previous:focus,
.pagination-next:focus,
.pagination-link:focus,
.pagination-ellipsis:focus, .is-focused.button, .is-focused.input, .is-focused.textarea, .select select.is-focused, .is-focused.file-cta,
.is-focused.file-name, .is-focused.pagination-previous,
.is-focused.pagination-next,
.is-focused.pagination-link,
.is-focused.pagination-ellipsis, .button:active, .input:active, .textarea:active, .select select:active, .file-cta:active,
.file-name:active, .pagination-previous:active,
.pagination-next:active,
.pagination-link:active,
.pagination-ellipsis:active, .is-active.button, .is-active.input, .is-active.textarea, .select select.is-active, .is-active.file-cta,
.is-active.file-name, .is-active.pagination-previous,
.is-active.pagination-next,
.is-active.pagination-link,
.is-active.pagination-ellipsis {
  outline: none;
}

.button[disabled], .input[disabled], .textarea[disabled], .select select[disabled], .file-cta[disabled],
.file-name[disabled], .pagination-previous[disabled],
.pagination-next[disabled],
.pagination-link[disabled],
.pagination-ellipsis[disabled],
fieldset[disabled] .button,
fieldset[disabled] .input,
fieldset[disabled] .textarea,
fieldset[disabled] .select select,
.select fieldset[disabled] select,
fieldset[disabled] .file-cta,
fieldset[disabled] .file-name,
fieldset[disabled] .pagination-previous,
fieldset[disabled] .pagination-next,
fieldset[disabled] .pagination-link,
fieldset[disabled] .pagination-ellipsis {
  cursor: not-allowed;
}

.button, .file, .breadcrumb, .pagination-previous,
.pagination-next,
.pagination-link,
.pagination-ellipsis, .tabs, .is-unselectable {
  -webkit-touch-callout: none;
  -webkit-user-select: none;
  -moz-user-select: none;
  -ms-user-select: none;
  user-select: none;
}

.select:not(.is-multiple):not(.is-loading)::after, .navbar-link:not(.is-arrowless)::after {
  border: 3px solid transparent;
  border-radius: 2px;
  border-right: 0;
  border-top: 0;
  content: " ";
  display: block;
  height: 0.625em;
  margin-top: -0.4375em;
  pointer-events: none;
  position: absolute;
  top: 50%;
  transform: rotate(-45deg);
  transform-origin: center;
  width: 0.625em;
}

.box:not(:last-child), .content:not(:last-child), .notification:not(:last-child), .progress:not(:last-child), .table:not(:last-child), .table-container:not(:last-child), .title:not(:last-child),
.subtitle:not(:last-child), .block:not(:last-child), .breadcrumb:not(:last-child), .level:not(:last-child), .message:not(:last-child), .pagination:not(:last-child), .tabs:not(:last-child) {
  margin-bottom: 1.5rem;
}

.delete, .modal-close {
  -webkit-touch-callout: none;
  -webkit-user-select: none;
  -moz-user-select: none;
  -ms-user-select: none;
  user-select: none;
  -moz-appearance: none;
  -webkit-appearance: none;
  background-color: rgba(10, 10, 10, 0.2);
  border: none;
  border-radius: 9999px;
  cursor: pointer;
  pointer-events: auto;
  display: inline-block;
  flex-grow: 0;
  flex-shrink: 0;
  font-size: 0;
  height: 20px;
  max-height: 20px;
  max-width: 20px;
  min-height: 20px;
  min-width: 20px;
  outline: none;
  position: relative;
  vertical-align: top;
  width: 20px;
}

.delete::before, .modal-close::before, .delete::after, .modal-close::after {
  background-color: white;
  content: "";
  display: block;
  left: 50%;
  position: absolute;
  top: 50%;
  transform: translateX(-50%) translateY(-50%) rotate(45deg);
  transform-origin: center center;
}

.delete::before, .modal-close::before {
  height: 2px;
  width: 50%;
}

.delete::after, .modal-close::after {
  height: 50%;
  width: 2px;
}

.delete:hover, .modal-close:hover, .delete:focus, .modal-close:focus {
  background-color: rgba(10, 10, 10, 0.3);
}

.delete:active, .modal-close:active {
  background-color: rgba(10, 10, 10, 0.4);
}

.is-small.delete, .is-small.modal-close {
  height: 16px;
  max-height: 16px;
  max-width: 16px;
  min-height: 16px;
  min-width: 16px;
  width: 16px;
}

.is-medium.delete, .is-medium.modal-close {
  height: 24px;
  max-height: 24px;
  max-width: 24px;
  min-height: 24px;
  min-width: 24px;
  width: 24px;
}

.is-large.delete, .is-large.modal-close {
  height: 32px;
  max-height: 32px;
  max-width: 32px;
  min-height: 32px;
  min-width: 32px;
  width: 32px;
}

.button.is-loading::after, .loader, .select.is-loading::after, .control.is-loading::after {
  -webkit-animation: spinAround 500ms infinite linear;
          animation: spinAround 500ms infinite linear;
  border: 2px solid #dbdbdb;
  border-radius: 9999px;
  border-right-color: transparent;
  border-top-color: transparent;
  content: "";
  display: block;
  height: 1em;
  position: relative;
  width: 1em;
}

.image.is-square img,
.image.is-square .has-ratio, .image.is-1by1 img,
.image.is-1by1 .has-ratio, .image.is-5by4 img,
.image.is-5by4 .has-ratio, .image.is-4by3 img,
.image.is-4by3 .has-ratio, .image.is-3by2 img,
.image.is-3by2 .has-ratio, .image.is-5by3 img,
.image.is-5by3 .has-ratio, .image.is-16by9 img,
.image.is-16by9 .has-ratio, .image.is-2by1 img,
.image.is-2by1 .has-ratio, .image.is-3by1 img,
.image.is-3by1 .has-ratio, .image.is-4by5 img,
.image.is-4by5 .has-ratio, .image.is-3by4 img,
.image.is-3by4 .has-ratio, .image.is-2by3 img,
.image.is-2by3 .has-ratio, .image.is-3by5 img,
.image.is-3by5 .has-ratio, .image.is-9by16 img,
.image.is-9by16 .has-ratio, .image.is-1by2 img,
.image.is-1by2 .has-ratio, .image.is-1by3 img,
.image.is-1by3 .has-ratio, .modal, .modal-background, .is-overlay, .hero-video {
  bottom: 0;
  left: 0;
  position: absolute;
  right: 0;
  top: 0;
}

.navbar-burger {
  -moz-appearance: none;
  -webkit-appearance: none;
  appearance: none;
  background: none;
  border: none;
  color: currentColor;
  font-family: inherit;
  font-size: 1em;
  margin: 0;
  padding: 0;
}

/* Bulma Base */
/*! minireset.css v0.0.6 | MIT License | github.com/jgthms/minireset.css */
html,
body,
p,
ol,
ul,
li,
dl,
dt,
dd,
blockquote,
figure,
fieldset,
legend,
textarea,
pre,
iframe,
hr,
h1,
h2,
h3,
h4,
h5,
h6 {
  margin: 0;
  padding: 0;
}

h1,
h2,
h3,
h4,
h5,
h6 {
  font-size: 100%;
  font-weight: normal;
}

ul {
  list-style: none;
}

button,
input,
select,
textarea {
  margin: 0;
}

html {
  box-sizing: border-box;
}

*, *::before, *::after {
  box-sizing: inherit;
}

img,
video {
  height: auto;
  max-width: 100%;
}

iframe {
  border: 0;
}

table {
  border-collapse: collapse;
  border-spacing: 0;
}

td,
th {
  padding: 0;
}

td:not([align]),
th:not([align]) {
  text-align: inherit;
}

html {
  background-color: white;
  font-size: 16px;
  -moz-osx-font-smoothing: grayscale;
  -webkit-font-smoothing: antialiased;
  min-width: 300px;
  overflow-x: hidden;
  overflow-y: scroll;
  text-rendering: optimizeLegibility;
  -webkit-text-size-adjust: 100%;
     -moz-text-size-adjust: 100%;
          text-size-adjust: 100%;
}

article,
aside,
figure,
footer,
header,
hgroup,
section {
  display: block;
}

body,
button,
input,
optgroup,
select,
textarea {
  font-family: BlinkMacSystemFont, -apple-system, "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", "Helvetica", "Arial", sans-serif;
}

code,
pre {
  -moz-osx-font-smoothing: auto;
  -webkit-font-smoothing: auto;
  font-family: monospace;
}

body {
  color: #4a4a4a;
  font-size: 1em;
  font-weight: 400;
  line-height: 1.5;
}

a {
  color: #485fc7;
  cursor: pointer;
  text-decoration: none;
}

a strong {
  color: currentColor;
}

a:hover {
  color: #363636;
}

code {
  background-color: whitesmoke;
  color: #da1039;
  font-size: 0.875em;
  font-weight: normal;
  padding: 0.25em 0.5em 0.25em;
}

hr {
  background-color: whitesmoke;
  border: none;
  display: block;
  height: 2px;
  margin: 1.5rem 0;
}

img {
  height: auto;
  max-width: 100%;
}

input[type="checkbox"],
input[type="radio"] {
  vertical-align: baseline;
}

small {
  font-size: 0.875em;
}

span {
  font-style: inherit;
  font-weight: inherit;
}

strong {
  color: #363636;
  font-weight: 700;
}

fieldset {
  border: none;
}

pre {
  -webkit-overflow-scrolling: touch;
  background-color: whitesmoke;
  color: #4a4a4a;
  font-size: 0.875em;
  overflow-x: auto;
  padding: 1.25rem 1.5rem;
  white-space: pre;
  word-wrap: normal;
}

pre code {
  background-color: transparent;
  color: currentColor;
  font-size: 1em;
  padding: 0;
}

table td,
table th {
  vertical-align: top;
}

table td:not([align]),
table th:not([align]) {
  text-align: inherit;
}

table th {
  color: #363636;
}

@-webkit-keyframes spinAround {
  from {
    transform: rotate(0deg);
  }
  to {
    transform: rotate(359deg);
  }
}

@keyframes spinAround {
  from {
    transform: rotate(0deg);
  }
  to {
    transform: rotate(359deg);
  }
}

/* Bulma Elements */
.box {
  background-color: white;
  border-radius: 6px;
  box-shadow: 0 0.5em 1em -0.125em rgba(10, 10, 10, 0.1), 0 0px 0 1px rgba(10, 10, 10, 0.02);
  color: #4a4a4a;
  display: block;
  padding: 1.25rem;
}

a.box:hover, a.box:focus {
  box-shadow: 0 0.5em 1em -0.125em rgba(10, 10, 10, 0.1), 0 0 0 1px #485fc7;
}

a.box:active {
  box-shadow: inset 0 1px 2px rgba(10, 10, 10, 0.2), 0 0 0 1px #485fc7;
}

.button {
  background-color: white;
  border-color: #dbdbdb;
  border-width: 1px;
  color: #363636;
  cursor: pointer;
  justify-content: center;
  padding-bottom: calc(0.5em - 1px);
  padding-left: 1em;
  padding-right: 1em;
  padding-top: calc(0.5em - 1px);
  text-align: center;
  white-space: nowrap;
}

.button strong {
  color: inherit;
}

.button .icon, .button .icon.is-small, .button .icon.is-medium, .button .icon.is-large {
  height: 1.5em;
  width: 1.5em;
}

.button .icon:first-child:not(:last-child) {
  margin-left: calc(-0.5em - 1px);
  margin-right: 0.25em;
}

.button .icon:last-child:not(:first-child) {
  margin-left: 0.25em;
  margin-right: calc(-0.5em - 1px);
}

.button .icon:first-child:last-child {
  margin-left: calc(-0.5em - 1px);
  margin-right: calc(-0.5em - 1px);
}

.button:hover, .button.is-hovered {
  border-color: #b5b5b5;
  color: #363636;
}

.button:focus, .button.is-focused {
  border-color: #485fc7;
  color: #363636;
}

.button:focus:not(:active), .button.is-focused:not(:active) {
  box-shadow: 0 0 0 0.125em rgba(72, 95, 199, 0.25);
}

.button:active, .button.is-active {
  border-color: #4a4a4a;
  color: #363636;
}

.button.is-text {
  background-color: transparent;
  border-color: transparent;
  color: #4a4a4a;
  text-decoration: underline;
}

.button.is-text:hover, .button.is-text.is-hovered, .button.is-text:focus, .button.is-text.is-focused {
  background-color: whitesmoke;
  color: #363636;
}

.button.is-text:active, .button.is-text.is-active {
  background-color: #e8e8e8;
  color: #363636;
}

.button.is-text[disabled],
fieldset[disabled] .button.is-text {
  background-color: transparent;
  border-color: transparent;
  box-shadow: none;
}

.button.is-ghost {
  background: none;
  border-color: transparent;
  color: #485fc7;
  text-decoration: none;
}

.button.is-ghost:hover, .button.is-ghost.is-hovered {
  color: #485fc7;
  text-decoration: underline;
}

.button.is-white {
  background-color: white;
  border-color: transparent;
  color: #0a0a0a;
}

.button.is-white:hover, .button.is-white.is-hovered {
  background-color: #f9f9f9;
  border-color: transparent;
  color: #0a0a0a;
}

.button.is-white:focus, .button.is-white.is-focused {
  border-color: transparent;
  color: #0a0a0a;
}

.button.is-white:focus:not(:active), .button.is-white.is-focused:not(:active) {
  box-shadow: 0 0 0 0.125em rgba(255, 255, 255, 0.25);
}

.button.is-white:active, .button.is-white.is-active {
  background-color: #f2f2f2;
  border-color: transparent;
  color: #0a0a0a;
}

.button.is-white[disabled],
fieldset[disabled] .button.is-white {
  background-color: white;
  border-color: white;
  box-shadow: none;
}

.button.is-white.is-inverted {
  background-color: #0a0a0a;
  color: white;
}

.button.is-white.is-inverted:hover, .button.is-white.is-inverted.is-hovered {
  background-color: black;
}

.button.is-white.is-inverted[disabled],
fieldset[disabled] .button.is-white.is-inverted {
  background-color: #0a0a0a;
  border-color: transparent;
  box-shadow: none;
  color: white;
}

.button.is-white.is-loading::after {
  border-color: transparent transparent #0a0a0a #0a0a0a !important;
}

.button.is-white.is-outlined {
  background-color: transparent;
  border-color: white;
  color: white;
}

.button.is-white.is-outlined:hover, .button.is-white.is-outlined.is-hovered, .button.is-white.is-outlined:focus, .button.is-white.is-outlined.is-focused {
  background-color: white;
  border-color: white;
  color: #0a0a0a;
}

.button.is-white.is-outlined.is-loading::after {
  border-color: transparent transparent white white !important;
}

.button.is-white.is-outlined.is-loading:hover::after, .button.is-white.is-outlined.is-loading.is-hovered::after, .button.is-white.is-outlined.is-loading:focus::after, .button.is-white.is-outlined.is-loading.is-focused::after {
  border-color: transparent transparent #0a0a0a #0a0a0a !important;
}

.button.is-white.is-outlined[disabled],
fieldset[disabled] .button.is-white.is-outlined {
  background-color: transparent;
  border-color: white;
  box-shadow: none;
  color: white;
}

.button.is-white.is-inverted.is-outlined {
  background-color: transparent;
  border-color: #0a0a0a;
  color: #0a0a0a;
}

.button.is-white.is-inverted.is-outlined:hover, .button.is-white.is-inverted.is-outlined.is-hovered, .button.is-white.is-inverted.is-outlined:focus, .button.is-white.is-inverted.is-outlined.is-focused {
  background-color: #0a0a0a;
  color: white;
}

.button.is-white.is-inverted.is-outlined.is-loading:hover::after, .button.is-white.is-inverted.is-outlined.is-loading.is-hovered::after, .button.is-white.is-inverted.is-outlined.is-loading:focus::after, .button.is-white.is-inverted.is-outlined.is-loading.is-focused::after {
  border-color: transparent transparent white white !important;
}

.button.is-white.is-inverted.is-outlined[disabled],
fieldset[disabled] .button.is-white.is-inverted.is-outlined {
  background-color: transparent;
  border-color: #0a0a0a;
  box-shadow: none;
  color: #0a0a0a;
}

.button.is-black {
  background-color: #0a0a0a;
  border-color: transparent;
  color: white;
}

.button.is-black:hover, .button.is-black.is-hovered {
  background-color: #040404;
  border-color: transparent;
  color: white;
}

.button.is-black:focus, .button.is-black.is-focused {
  border-color: transparent;
  color: white;
}

.button.is-black:focus:not(:active), .button.is-black.is-focused:not(:active) {
  box-shadow: 0 0 0 0.125em rgba(10, 10, 10, 0.25);
}

.button.is-black:active, .button.is-black.is-active {
  background-color: black;
  border-color: transparent;
  color: white;
}

.button.is-black[disabled],
fieldset[disabled] .button.is-black {
  background-color: #0a0a0a;
  border-color: #0a0a0a;
  box-shadow: none;
}

.button.is-black.is-inverted {
  background-color: white;
  color: #0a0a0a;
}

.button.is-black.is-inverted:hover, .button.is-black.is-inverted.is-hovered {
  background-color: #f2f2f2;
}

.button.is-black.is-inverted[disabled],
fieldset[disabled] .button.is-black.is-inverted {
  background-color: white;
  border-color: transparent;
  box-shadow: none;
  color: #0a0a0a;
}

.button.is-black.is-loading::after {
  border-color: transparent transparent white white !important;
}

.button.is-black.is-outlined {
  background-color: transparent;
  border-color: #0a0a0a;
  color: #0a0a0a;
}

.button.is-black.is-outlined:hover, .button.is-black.is-outlined.is-hovered, .button.is-black.is-outlined:focus, .button.is-black.is-outlined.is-focused {
  background-color: #0a0a0a;
  border-color: #0a0a0a;
  color: white;
}

.button.is-black.is-outlined.is-loading::after {
  border-color: transparent transparent #0a0a0a #0a0a0a !important;
}

.button.is-black.is-outlined.is-loading:hover::after, .button.is-black.is-outlined.is-loading.is-hovered::after, .button.is-black.is-outlined.is-loading:focus::after, .button.is-black.is-outlined.is-loading.is-focused::after {
  border-color: transparent transparent white white !important;
}

.button.is-black.is-outlined[disabled],
fieldset[disabled] .button.is-black.is-outlined {
  background-color: transparent;
  border-color: #0a0a0a;
  box-shadow: none;
  color: #0a0a0a;
}

.button.is-black.is-inverted.is-outlined {
  background-color: transparent;
  border-color: white;
  color: white;
}

.button.is-black.is-inverted.is-outlined:hover, .button.is-black.is-inverted.is-outlined.is-hovered, .button.is-black.is-inverted.is-outlined:focus, .button.is-black.is-inverted.is-outlined.is-focused {
  background-color: white;
  color: #0a0a0a;
}

.button.is-black.is-inverted.is-outlined.is-loading:hover::after, .button.is-black.is-inverted.is-outlined.is-loading.is-hovered::after, .button.is-black.is-inverted.is-outlined.is-loading:focus::after, .button.is-black.is-inverted.is-outlined.is-loading.is-focused::after {
  border-color: transparent transparent #0a0a0a #0a0a0a !important;
}

.button.is-black.is-inverted.is-outlined[disabled],
fieldset[disabled] .button.is-black.is-inverted.is-outlined {
  background-color: transparent;
  border-color: white;
  box-shadow: none;
  color: white;
}

.button.is-light {
  background-color: whitesmoke;
  border-color: transparent;
  color: rgba(0, 0, 0, 0.7);
}

.button.is-light:hover, .button.is-light.is-hovered {
  background-color: #eeeeee;
  border-color: transparent;
  color: rgba(0, 0, 0, 0.7);
}

.button.is-light:focus, .button.is-light.is-focused {
  border-color: transparent;
  color: rgba(0, 0, 0, 0.7);
}

.button.is-light:focus:not(:active), .button.is-light.is-focused:not(:active) {
  box-shadow: 0 0 0 0.125em rgba(245, 245, 245, 0.25);
}

.button.is-light:active, .button.is-light.is-active {
  background-color: #e8e8e8;
  border-color: transparent;
  color: rgba(0, 0, 0, 0.7);
}

.button.is-light[disabled],
fieldset[disabled] .button.is-light {
  background-color: whitesmoke;
  border-color: whitesmoke;
  box-shadow: none;
}

.button.is-light.is-inverted {
  background-color: rgba(0, 0, 0, 0.7);
  color: whitesmoke;
}

.button.is-light.is-inverted:hover, .button.is-light.is-inverted.is-hovered {
  background-color: rgba(0, 0, 0, 0.7);
}

.button.is-light.is-inverted[disabled],
fieldset[disabled] .button.is-light.is-inverted {
  background-color: rgba(0, 0, 0, 0.7);
  border-color: transparent;
  box-shadow: none;
  color: whitesmoke;
}

.button.is-light.is-loading::after {
  border-color: transparent transparent rgba(0, 0, 0, 0.7) rgba(0, 0, 0, 0.7) !important;
}

.button.is-light.is-outlined {
  background-color: transparent;
  border-color: whitesmoke;
  color: whitesmoke;
}

.button.is-light.is-outlined:hover, .button.is-light.is-outlined.is-hovered, .button.is-light.is-outlined:focus, .button.is-light.is-outlined.is-focused {
  background-color: whitesmoke;
  border-color: whitesmoke;
  color: rgba(0, 0, 0, 0.7);
}

.button.is-light.is-outlined.is-loading::after {
  border-color: transparent transparent whitesmoke whitesmoke !important;
}

.button.is-light.is-outlined.is-loading:hover::after, .button.is-light.is-outlined.is-loading.is-hovered::after, .button.is-light.is-outlined.is-loading:focus::after, .button.is-light.is-outlined.is-loading.is-focused::after {
  border-color: transparent transparent rgba(0, 0, 0, 0.7) rgba(0, 0, 0, 0.7) !important;
}

.button.is-light.is-outlined[disabled],
fieldset[disabled] .button.is-light.is-outlined {
  background-color: transparent;
  border-color: whitesmoke;
  box-shadow: none;
  color: whitesmoke;
}

.button.is-light.is-inverted.is-outlined {
  background-color: transparent;
  border-color: rgba(0, 0, 0, 0.7);
  color: rgba(0, 0, 0, 0.7);
}

.button.is-light.is-inverted.is-outlined:hover, .button.is-light.is-inverted.is-outlined.is-hovered, .button.is-light.is-inverted.is-outlined:focus, .button.is-light.is-inverted.is-outlined.is-focused {
  background-color: rgba(0, 0, 0, 0.7);
  color: whitesmoke;
}

.button.is-light.is-inverted.is-outlined.is-loading:hover::after, .button.is-light.is-inverted.is-outlined.is-loading.is-hovered::after, .button.is-light.is-inverted.is-outlined.is-loading:focus::after, .button.is-light.is-inverted.is-outlined.is-loading.is-focused::after {
  border-color: transparent transparent whitesmoke whitesmoke !important;
}

.button.is-light.is-inverted.is-outlined[disabled],
fieldset[disabled] .button.is-light.is-inverted.is-outlined {
  background-color: transparent;
  border-color: rgba(0, 0, 0, 0.7);
  box-shadow: none;
  color: rgba(0, 0, 0, 0.7);
}

.button.is-dark {
  background-color: #363636;
  border-color: transparent;
  color: #fff;
}

.button.is-dark:hover, .button.is-dark.is-hovered {
  background-color: #2f2f2f;
  border-color: transparent;
  color: #fff;
}

.button.is-dark:focus, .button.is-dark.is-focused {
  border-color: transparent;
  color: #fff;
}

.button.is-dark:focus:not(:active), .button.is-dark.is-focused:not(:active) {
  box-shadow: 0 0 0 0.125em rgba(54, 54, 54, 0.25);
}

.button.is-dark:active, .button.is-dark.is-active {
  background-color: #292929;
  border-color: transparent;
  color: #fff;
}

.button.is-dark[disabled],
fieldset[disabled] .button.is-dark {
  background-color: #363636;
  border-color: #363636;
  box-shadow: none;
}

.button.is-dark.is-inverted {
  background-color: #fff;
  color: #363636;
}

.button.is-dark.is-inverted:hover, .button.is-dark.is-inverted.is-hovered {
  background-color: #f2f2f2;
}

.button.is-dark.is-inverted[disabled],
fieldset[disabled] .button.is-dark.is-inverted {
  background-color: #fff;
  border-color: transparent;
  box-shadow: none;
  color: #363636;
}

.button.is-dark.is-loading::after {
  border-color: transparent transparent #fff #fff !important;
}

.button.is-dark.is-outlined {
  background-color: transparent;
  border-color: #363636;
  color: #363636;
}

.button.is-dark.is-outlined:hover, .button.is-dark.is-outlined.is-hovered, .button.is-dark.is-outlined:focus, .button.is-dark.is-outlined.is-focused {
  background-color: #363636;
  border-color: #363636;
  color: #fff;
}

.button.is-dark.is-outlined.is-loading::after {
  border-color: transparent transparent #363636 #363636 !important;
}

.button.is-dark.is-outlined.is-loading:hover::after, .button.is-dark.is-outlined.is-loading.is-hovered::after, .button.is-dark.is-outlined.is-loading:focus::after, .button.is-dark.is-outlined.is-loading.is-focused::after {
  border-color: transparent transparent #fff #fff !important;
}

.button.is-dark.is-outlined[disabled],
fieldset[disabled] .button.is-dark.is-outlined {
  background-color: transparent;
  border-color: #363636;
  box-shadow: none;
  color: #363636;
}

.button.is-dark.is-inverted.is-outlined {
  background-color: transparent;
  border-color: #fff;
  color: #fff;
}

.button.is-dark.is-inverted.is-outlined:hover, .button.is-dark.is-inverted.is-outlined.is-hovered, .button.is-dark.is-inverted.is-outlined:focus, .button.is-dark.is-inverted.is-outlined.is-focused {
  background-color: #fff;
  color: #363636;
}

.button.is-dark.is-inverted.is-outlined.is-loading:hover::after, .button.is-dark.is-inverted.is-outlined.is-loading.is-hovered::after, .button.is-dark.is-inverted.is-outlined.is-loading:focus::after, .button.is-dark.is-inverted.is-outlined.is-loading.is-focused::after {
  border-color: transparent transparent #363636 #363636 !important;
}

.button.is-dark.is-inverted.is-outlined[disabled],
fieldset[disabled] .button.is-dark.is-inverted.is-outlined {
  background-color: transparent;
  border-color: #fff;
  box-shadow: none;
  color: #fff;
}

.button.is-primary {
  background-color: #00d1b2;
  border-color: transparent;
  color: #fff;
}

.button.is-primary:hover, .button.is-primary.is-hovered {
  background-color: #00c4a7;
  border-color: transparent;
  color: #fff;
}

.button.is-primary:focus, .button.is-primary.is-focused {
  border-color: transparent;
  color: #fff;
}

.button.is-primary:focus:not(:active), .button.is-primary.is-focused:not(:active) {
  box-shadow: 0 0 0 0.125em rgba(0, 209, 178, 0.25);
}

.button.is-primary:active, .button.is-primary.is-active {
  background-color: #00b89c;
  border-color: transparent;
  color: #fff;
}

.button.is-primary[disabled],
fieldset[disabled] .button.is-primary {
  background-color: #00d1b2;
  border-color: #00d1b2;
  box-shadow: none;
}

.button.is-primary.is-inverted {
  background-color: #fff;
  color: #00d1b2;
}

.button.is-primary.is-inverted:hover, .button.is-primary.is-inverted.is-hovered {
  background-color: #f2f2f2;
}

.button.is-primary.is-inverted[disabled],
fieldset[disabled] .button.is-primary.is-inverted {
  background-color: #fff;
  border-color: transparent;
  box-shadow: none;
  color: #00d1b2;
}

.button.is-primary.is-loading::after {
  border-color: transparent transparent #fff #fff !important;
}

.button.is-primary.is-outlined {
  background-color: transparent;
  border-color: #00d1b2;
  color: #00d1b2;
}

.button.is-primary.is-outlined:hover, .button.is-primary.is-outlined.is-hovered, .button.is-primary.is-outlined:focus, .button.is-primary.is-outlined.is-focused {
  background-color: #00d1b2;
  border-color: #00d1b2;
  color: #fff;
}

.button.is-primary.is-outlined.is-loading::after {
  border-color: transparent transparent #00d1b2 #00d1b2 !important;
}

.button.is-primary.is-outlined.is-loading:hover::after, .button.is-primary.is-outlined.is-loading.is-hovered::after, .button.is-primary.is-outlined.is-loading:focus::after, .button.is-primary.is-outlined.is-loading.is-focused::after {
  border-color: transparent transparent #fff #fff !important;
}

.button.is-primary.is-outlined[disabled],
fieldset[disabled] .button.is-primary.is-outlined {
  background-color: transparent;
  border-color: #00d1b2;
  box-shadow: none;
  color: #00d1b2;
}

.button.is-primary.is-inverted.is-outlined {
  background-color: transparent;
  border-color: #fff;
  color: #fff;
}

.button.is-primary.is-inverted.is-outlined:hover, .button.is-primary.is-inverted.is-outlined.is-hovered, .button.is-primary.is-inverted.is-outlined:focus, .button.is-primary.is-inverted.is-outlined.is-focused {
  background-color: #fff;
  color: #00d1b2;
}

.button.is-primary.is-inverted.is-outlined.is-loading:hover::after, .button.is-primary.is-inverted.is-outlined.is-loading.is-hovered::after, .button.is-primary.is-inverted.is-outlined.is-loading:focus::after, .button.is-primary.is-inverted.is-outlined.is-loading.is-focused::after {
  border-color: transparent transparent #00d1b2 #00d1b2 !important;
}

.button.is-primary.is-inverted.is-outlined[disabled],
fieldset[disabled] .button.is-primary.is-inverted.is-outlined {
  background-color: transparent;
  border-color: #fff;
  box-shadow: none;
  color: #fff;
}

.button.is-primary.is-light {
  background-color: #ebfffc;
  color: #00947e;
}

.button.is-primary.is-light:hover, .button.is-primary.is-light.is-hovered {
  background-color: #defffa;
  border-color: transparent;
  color: #00947e;
}

.button.is-primary.is-light:active, .button.is-primary.is-light.is-active {
  background-color: #d1fff8;
  border-color: transparent;
  color: #00947e;
}

.button.is-link {
  background-color: #485fc7;
  border-color: transparent;
  color: #fff;
}

.button.is-link:hover, .button.is-link.is-hovered {
  background-color: #3e56c4;
  border-color: transparent;
  color: #fff;
}

.button.is-link:focus, .button.is-link.is-focused {
  border-color: transparent;
  color: #fff;
}

.button.is-link:focus:not(:active), .button.is-link.is-focused:not(:active) {
  box-shadow: 0 0 0 0.125em rgba(72, 95, 199, 0.25);
}

.button.is-link:active, .button.is-link.is-active {
  background-color: #3a51bb;
  border-color: transparent;
  color: #fff;
}

.button.is-link[disabled],
fieldset[disabled] .button.is-link {
  background-color: #485fc7;
  border-color: #485fc7;
  box-shadow: none;
}

.button.is-link.is-inverted {
  background-color: #fff;
  color: #485fc7;
}

.button.is-link.is-inverted:hover, .button.is-link.is-inverted.is-hovered {
  background-color: #f2f2f2;
}

.button.is-link.is-inverted[disabled],
fieldset[disabled] .button.is-link.is-inverted {
  background-color: #fff;
  border-color: transparent;
  box-shadow: none;
  color: #485fc7;
}

.button.is-link.is-loading::after {
  border-color: transparent transparent #fff #fff !important;
}

.button.is-link.is-outlined {
  background-color: transparent;
  border-color: #485fc7;
  color: #485fc7;
}

.button.is-link.is-outlined:hover, .button.is-link.is-outlined.is-hovered, .button.is-link.is-outlined:focus, .button.is-link.is-outlined.is-focused {
  background-color: #485fc7;
  border-color: #485fc7;
  color: #fff;
}

.button.is-link.is-outlined.is-loading::after {
  border-color: transparent transparent #485fc7 #485fc7 !important;
}

.button.is-link.is-outlined.is-loading:hover::after, .button.is-link.is-outlined.is-loading.is-hovered::after, .button.is-link.is-outlined.is-loading:focus::after, .button.is-link.is-outlined.is-loading.is-focused::after {
  border-color: transparent transparent #fff #fff !important;
}

.button.is-link.is-outlined[disabled],
fieldset[disabled] .button.is-link.is-outlined {
  background-color: transparent;
  border-color: #485fc7;
  box-shadow: none;
  color: #485fc7;
}

.button.is-link.is-inverted.is-outlined {
  background-color: transparent;
  border-color: #fff;
  color: #fff;
}

.button.is-link.is-inverted.is-outlined:hover, .button.is-link.is-inverted.is-outlined.is-hovered, .button.is-link.is-inverted.is-outlined:focus, .button.is-link.is-inverted.is-outlined.is-focused {
  background-color: #fff;
  color: #485fc7;
}

.button.is-link.is-inverted.is-outlined.is-loading:hover::after, .button.is-link.is-inverted.is-outlined.is-loading.is-hovered::after, .button.is-link.is-inverted.is-outlined.is-loading:focus::after, .button.is-link.is-inverted.is-outlined.is-loading.is-focused::after {
  border-color: transparent transparent #485fc7 #485fc7 !important;
}

.button.is-link.is-inverted.is-outlined[disabled],
fieldset[disabled] .button.is-link.is-inverted.is-outlined {
  background-color: transparent;
  border-color: #fff;
  box-shadow: none;
  color: #fff;
}

.button.is-link.is-light {
  background-color: #eff1fa;
  color: #3850b7;
}

.button.is-link.is-light:hover, .button.is-link.is-light.is-hovered {
  background-color: #e6e9f7;
  border-color: transparent;
  color: #3850b7;
}

.button.is-link.is-light:active, .button.is-link.is-light.is-active {
  background-color: #dce0f4;
  border-color: transparent;
  color: #3850b7;
}

.button.is-info {
  background-color: #3e8ed0;
  border-color: transparent;
  color: #fff;
}

.button.is-info:hover, .button.is-info.is-hovered {
  background-color: #3488ce;
  border-color: transparent;
  color: #fff;
}

.button.is-info:focus, .button.is-info.is-focused {
  border-color: transparent;
  color: #fff;
}

.button.is-info:focus:not(:active), .button.is-info.is-focused:not(:active) {
  box-shadow: 0 0 0 0.125em rgba(62, 142, 208, 0.25);
}

.button.is-info:active, .button.is-info.is-active {
  background-color: #3082c5;
  border-color: transparent;
  color: #fff;
}

.button.is-info[disabled],
fieldset[disabled] .button.is-info {
  background-color: #3e8ed0;
  border-color: #3e8ed0;
  box-shadow: none;
}

.button.is-info.is-inverted {
  background-color: #fff;
  color: #3e8ed0;
}

.button.is-info.is-inverted:hover, .button.is-info.is-inverted.is-hovered {
  background-color: #f2f2f2;
}

.button.is-info.is-inverted[disabled],
fieldset[disabled] .button.is-info.is-inverted {
  background-color: #fff;
  border-color: transparent;
  box-shadow: none;
  color: #3e8ed0;
}

.button.is-info.is-loading::after {
  border-color: transparent transparent #fff #fff !important;
}

.button.is-info.is-outlined {
  background-color: transparent;
  border-color: #3e8ed0;
  color: #3e8ed0;
}

.button.is-info.is-outlined:hover, .button.is-info.is-outlined.is-hovered, .button.is-info.is-outlined:focus, .button.is-info.is-outlined.is-focused {
  background-color: #3e8ed0;
  border-color: #3e8ed0;
  color: #fff;
}

.button.is-info.is-outlined.is-loading::after {
  border-color: transparent transparent #3e8ed0 #3e8ed0 !important;
}

.button.is-info.is-outlined.is-loading:hover::after, .button.is-info.is-outlined.is-loading.is-hovered::after, .button.is-info.is-outlined.is-loading:focus::after, .button.is-info.is-outlined.is-loading.is-focused::after {
  border-color: transparent transparent #fff #fff !important;
}

.button.is-info.is-outlined[disabled],
fieldset[disabled] .button.is-info.is-outlined {
  background-color: transparent;
  border-color: #3e8ed0;
  box-shadow: none;
  color: #3e8ed0;
}

.button.is-info.is-inverted.is-outlined {
  background-color: transparent;
  border-color: #fff;
  color: #fff;
}

.button.is-info.is-inverted.is-outlined:hover, .button.is-info.is-inverted.is-outlined.is-hovered, .button.is-info.is-inverted.is-outlined:focus, .button.is-info.is-inverted.is-outlined.is-focused {
  background-color: #fff;
  color: #3e8ed0;
}

.button.is-info.is-inverted.is-outlined.is-loading:hover::after, .button.is-info.is-inverted.is-outlined.is-loading.is-hovered::after, .button.is-info.is-inverted.is-outlined.is-loading:focus::after, .button.is-info.is-inverted.is-outlined.is-loading.is-focused::after {
  border-color: transparent transparent #3e8ed0 #3e8ed0 !important;
}

.button.is-info.is-inverted.is-outlined[disabled],
fieldset[disabled] .button.is-info.is-inverted.is-outlined {
  background-color: transparent;
  border-color: #fff;
  box-shadow: none;
  color: #fff;
}

.button.is-info.is-light {
  background-color: #eff5fb;
  color: #296fa8;
}

.button.is-info.is-light:hover, .button.is-info.is-light.is-hovered {
  background-color: #e4eff9;
  border-color: transparent;
  color: #296fa8;
}

.button.is-info.is-light:active, .button.is-info.is-light.is-active {
  background-color: #dae9f6;
  border-color: transparent;
  color: #296fa8;
}

.button.is-success {
  background-color: #48c78e;
  border-color: transparent;
  color: #fff;
}

.button.is-success:hover, .button.is-success.is-hovered {
  background-color: #3ec487;
  border-color: transparent;
  color: #fff;
}

.button.is-success:focus, .button.is-success.is-focused {
  border-color: transparent;
  color: #fff;
}

.button.is-success:focus:not(:active), .button.is-success.is-focused:not(:active) {
  box-shadow: 0 0 0 0.125em rgba(72, 199, 142, 0.25);
}

.button.is-success:active, .button.is-success.is-active {
  background-color: #3abb81;
  border-color: transparent;
  color: #fff;
}

.button.is-success[disabled],
fieldset[disabled] .button.is-success {
  background-color: #48c78e;
  border-color: #48c78e;
  box-shadow: none;
}

.button.is-success.is-inverted {
  background-color: #fff;
  color: #48c78e;
}

.button.is-success.is-inverted:hover, .button.is-success.is-inverted.is-hovered {
  background-color: #f2f2f2;
}

.button.is-success.is-inverted[disabled],
fieldset[disabled] .button.is-success.is-inverted {
  background-color: #fff;
  border-color: transparent;
  box-shadow: none;
  color: #48c78e;
}

.button.is-success.is-loading::after {
  border-color: transparent transparent #fff #fff !important;
}

.button.is-success.is-outlined {
  background-color: transparent;
  border-color: #48c78e;
  color: #48c78e;
}

.button.is-success.is-outlined:hover, .button.is-success.is-outlined.is-hovered, .button.is-success.is-outlined:focus, .button.is-success.is-outlined.is-focused {
  background-color: #48c78e;
  border-color: #48c78e;
  color: #fff;
}

.button.is-success.is-outlined.is-loading::after {
  border-color: transparent transparent #48c78e #48c78e !important;
}

.button.is-success.is-outlined.is-loading:hover::after, .button.is-success.is-outlined.is-loading.is-hovered::after, .button.is-success.is-outlined.is-loading:focus::after, .button.is-success.is-outlined.is-loading.is-focused::after {
  border-color: transparent transparent #fff #fff !important;
}

.button.is-success.is-outlined[disabled],
fieldset[disabled] .button.is-success.is-outlined {
  background-color: transparent;
  border-color: #48c78e;
  box-shadow: none;
  color: #48c78e;
}

.button.is-success.is-inverted.is-outlined {
  background-color: transparent;
  border-color: #fff;
  color: #fff;
}

.button.is-success.is-inverted.is-outlined:hover, .button.is-success.is-inverted.is-outlined.is-hovered, .button.is-success.is-inverted.is-outlined:focus, .button.is-success.is-inverted.is-outlined.is-focused {
  background-color: #fff;
  color: #48c78e;
}

.button.is-success.is-inverted.is-outlined.is-loading:hover::after, .button.is-success.is-inverted.is-outlined.is-loading.is-hovered::after, .button.is-success.is-inverted.is-outlined.is-loading:focus::after, .button.is-success.is-inverted.is-outlined.is-loading.is-focused::after {
  border-color: transparent transparent #48c78e #48c78e !important;
}

.button.is-success.is-inverted.is-outlined[disabled],
fieldset[disabled] .button.is-success.is-inverted.is-outlined {
  background-color: transparent;
  border-color: #fff;
  box-shadow: none;
  color: #fff;
}

.button.is-success.is-light {
  background-color: #effaf5;
  color: #257953;
}

.button.is-success.is-light:hover, .button.is-success.is-light.is-hovered {
  background-color: #e6f7ef;
  border-color: transparent;
  color: #257953;
}

.button.is-success.is-light:active, .button.is-success.is-light.is-active {
  background-color: #dcf4e9;
  border-color: transparent;
  color: #257953;
}

.button.is-warning {
  background-color: #ffe08a;
  border-color: transparent;
  color: rgba(0, 0, 0, 0.7);
}

.button.is-warning:hover, .button.is-warning.is-hovered {
  background-color: #ffdc7d;
  border-color: transparent;
  color: rgba(0, 0, 0, 0.7);
}

.button.is-warning:focus, .button.is-warning.is-focused {
  border-color: transparent;
  color: rgba(0, 0, 0, 0.7);
}

.button.is-warning:focus:not(:active), .button.is-warning.is-focused:not(:active) {
  box-shadow: 0 0 0 0.125em rgba(255, 224, 138, 0.25);
}

.button.is-warning:active, .button.is-warning.is-active {
  background-color: #ffd970;
  border-color: transparent;
  color: rgba(0, 0, 0, 0.7);
}

.button.is-warning[disabled],
fieldset[disabled] .button.is-warning {
  background-color: #ffe08a;
  border-color: #ffe08a;
  box-shadow: none;
}

.button.is-warning.is-inverted {
  background-color: rgba(0, 0, 0, 0.7);
  color: #ffe08a;
}

.button.is-warning.is-inverted:hover, .button.is-warning.is-inverted.is-hovered {
  background-color: rgba(0, 0, 0, 0.7);
}

.button.is-warning.is-inverted[disabled],
fieldset[disabled] .button.is-warning.is-inverted {
  background-color: rgba(0, 0, 0, 0.7);
  border-color: transparent;
  box-shadow: none;
  color: #ffe08a;
}

.button.is-warning.is-loading::after {
  border-color: transparent transparent rgba(0, 0, 0, 0.7) rgba(0, 0, 0, 0.7) !important;
}

.button.is-warning.is-outlined {
  background-color: transparent;
  border-color: #ffe08a;
  color: #ffe08a;
}

.button.is-warning.is-outlined:hover, .button.is-warning.is-outlined.is-hovered, .button.is-warning.is-outlined:focus, .button.is-warning.is-outlined.is-focused {
  background-color: #ffe08a;
  border-color: #ffe08a;
  color: rgba(0, 0, 0, 0.7);
}

.button.is-warning.is-outlined.is-loading::after {
  border-color: transparent transparent #ffe08a #ffe08a !important;
}

.button.is-warning.is-outlined.is-loading:hover::after, .button.is-warning.is-outlined.is-loading.is-hovered::after, .button.is-warning.is-outlined.is-loading:focus::after, .button.is-warning.is-outlined.is-loading.is-focused::after {
  border-color: transparent transparent rgba(0, 0, 0, 0.7) rgba(0, 0, 0, 0.7) !important;
}

.button.is-warning.is-outlined[disabled],
fieldset[disabled] .button.is-warning.is-outlined {
  background-color: transparent;
  border-color: #ffe08a;
  box-shadow: none;
  color: #ffe08a;
}

.button.is-warning.is-inverted.is-outlined {
  background-color: transparent;
  border-color: rgba(0, 0, 0, 0.7);
  color: rgba(0, 0, 0, 0.7);
}

.button.is-warning.is-inverted.is-outlined:hover, .button.is-warning.is-inverted.is-outlined.is-hovered, .button.is-warning.is-inverted.is-outlined:focus, .button.is-warning.is-inverted.is-outlined.is-focused {
  background-color: rgba(0, 0, 0, 0.7);
  color: #ffe08a;
}

.button.is-warning.is-inverted.is-outlined.is-loading:hover::after, .button.is-warning.is-inverted.is-outlined.is-loading.is-hovered::after, .button.is-warning.is-inverted.is-outlined.is-loading:focus::after, .button.is-warning.is-inverted.is-outlined.is-loading.is-focused::after {
  border-color: transparent transparent #ffe08a #ffe08a !important;
}

.button.is-warning.is-inverted.is-outlined[disabled],
fieldset[disabled] .button.is-warning.is-inverted.is-outlined {
  background-color: transparent;
  border-color: rgba(0, 0, 0, 0.7);
  box-shadow: none;
  color: rgba(0, 0, 0, 0.7);
}

.button.is-warning.is-light {
  background-color: #fffaeb;
  color: #946c00;
}

.button.is-warning.is-light:hover, .button.is-warning.is-light.is-hovered {
  background-color: #fff6de;
  border-color: transparent;
  color: #946c00;
}

.button.is-warning.is-light:active, .button.is-warning.is-light.is-active {
  background-color: #fff3d1;
  border-color: transparent;
  color: #946c00;
}

.button.is-danger {
  background-color: #f14668;
  border-color: transparent;
  color: #fff;
}

.button.is-danger:hover, .button.is-danger.is-hovered {
  background-color: #f03a5f;
  border-color: transparent;
  color: #fff;
}

.button.is-danger:focus, .button.is-danger.is-focused {
  border-color: transparent;
  color: #fff;
}

.button.is-danger:focus:not(:active), .button.is-danger.is-focused:not(:active) {
  box-shadow: 0 0 0 0.125em rgba(241, 70, 104, 0.25);
}

.button.is-danger:active, .button.is-danger.is-active {
  background-color: #ef2e55;
  border-color: transparent;
  color: #fff;
}

.button.is-danger[disabled],
fieldset[disabled] .button.is-danger {
  background-color: #f14668;
  border-color: #f14668;
  box-shadow: none;
}

.button.is-danger.is-inverted {
  background-color: #fff;
  color: #f14668;
}

.button.is-danger.is-inverted:hover, .button.is-danger.is-inverted.is-hovered {
  background-color: #f2f2f2;
}

.button.is-danger.is-inverted[disabled],
fieldset[disabled] .button.is-danger.is-inverted {
  background-color: #fff;
  border-color: transparent;
  box-shadow: none;
  color: #f14668;
}

.button.is-danger.is-loading::after {
  border-color: transparent transparent #fff #fff !important;
}

.button.is-danger.is-outlined {
  background-color: transparent;
  border-color: #f14668;
  color: #f14668;
}

.button.is-danger.is-outlined:hover, .button.is-danger.is-outlined.is-hovered, .button.is-danger.is-outlined:focus, .button.is-danger.is-outlined.is-focused {
  background-color: #f14668;
  border-color: #f14668;
  color: #fff;
}

.button.is-danger.is-outlined.is-loading::after {
  border-color: transparent transparent #f14668 #f14668 !important;
}

.button.is-danger.is-outlined.is-loading:hover::after, .button.is-danger.is-outlined.is-loading.is-hovered::after, .button.is-danger.is-outlined.is-loading:focus::after, .button.is-danger.is-outlined.is-loading.is-focused::after {
  border-color: transparent transparent #fff #fff !important;
}

.button.is-danger.is-outlined[disabled],
fieldset[disabled] .button.is-danger.is-outlined {
  background-color: transparent;
  border-color: #f14668;
  box-shadow: none;
  color: #f14668;
}

.button.is-danger.is-inverted.is-outlined {
  background-color: transparent;
  border-color: #fff;
  color: #fff;
}

.button.is-danger.is-inverted.is-outlined:hover, .button.is-danger.is-inverted.is-outlined.is-hovered, .button.is-danger.is-inverted.is-outlined:focus, .button.is-danger.is-inverted.is-outlined.is-focused {
  background-color: #fff;
  color: #f14668;
}

.button.is-danger.is-inverted.is-outlined.is-loading:hover::after, .button.is-danger.is-inverted.is-outlined.is-loading.is-hovered::after, .button.is-danger.is-inverted.is-outlined.is-loading:focus::after, .button.is-danger.is-inverted.is-outlined.is-loading.is-focused::after {
  border-color: transparent transparent #f14668 #f14668 !important;
}

.button.is-danger.is-inverted.is-outlined[disabled],
fieldset[disabled] .button.is-danger.is-inverted.is-outlined {
  background-color: transparent;
  border-color: #fff;
  box-shadow: none;
  color: #fff;
}

.button.is-danger.is-light {
  background-color: #feecf0;
  color: #cc0f35;
}

.button.is-danger.is-light:hover, .button.is-danger.is-light.is-hovered {
  background-color: #fde0e6;
  border-color: transparent;
  color: #cc0f35;
}

.button.is-danger.is-light:active, .button.is-danger.is-light.is-active {
  background-color: #fcd4dc;
  border-color: transparent;
  color: #cc0f35;
}

.button.is-small {
  font-size: 0.75rem;
}

.button.is-small:not(.is-rounded) {
  border-radius: 2px;
}

.button.is-normal {
  font-size: 1rem;
}

.button.is-medium {
  font-size: 1.25rem;
}

.button.is-large {
  font-size: 1.5rem;
}

.button[disabled],
fieldset[disabled] .button {
  background-color: white;
  border-color: #dbdbdb;
  box-shadow: none;
  opacity: 0.5;
}

.button.is-fullwidth {
  display: flex;
  width: 100%;
}

.button.is-loading {
  color: transparent !important;
  pointer-events: none;
}

.button.is-loading::after {
  position: absolute;
  left: calc(50% - (1em * 0.5));
  top: calc(50% - (1em * 0.5));
  position: absolute !important;
}

.button.is-static {
  background-color: whitesmoke;
  border-color: #dbdbdb;
  color: #7a7a7a;
  box-shadow: none;
  pointer-events: none;
}

.button.is-rounded {
  border-radius: 9999px;
  padding-left: calc(1em + 0.25em);
  padding-right: calc(1em + 0.25em);
}

.buttons {
  align-items: center;
  display: flex;
  flex-wrap: wrap;
  justify-content: flex-start;
}

.buttons .button {
  margin-bottom: 0.5rem;
}

.buttons .button:not(:last-child):not(.is-fullwidth) {
  margin-right: 0.5rem;
}

.buttons:last-child {
  margin-bottom: -0.5rem;
}

.buttons:not(:last-child) {
  margin-bottom: 1rem;
}

.buttons.are-small .button:not(.is-normal):not(.is-medium):not(.is-large) {
  font-size: 0.75rem;
}

.buttons.are-small .button:not(.is-normal):not(.is-medium):not(.is-large):not(.is-rounded) {
  border-radius: 2px;
}

.buttons.are-medium .button:not(.is-small):not(.is-normal):not(.is-large) {
  font-size: 1.25rem;
}

.buttons.are-large .button:not(.is-small):not(.is-normal):not(.is-medium) {
  font-size: 1.5rem;
}

.buttons.has-addons .button:not(:first-child) {
  border-bottom-left-radius: 0;
  border-top-left-radius: 0;
}

.buttons.has-addons .button:not(:last-child) {
  border-bottom-right-radius: 0;
  border-top-right-radius: 0;
  margin-right: -1px;
}

.buttons.has-addons .button:last-child {
  margin-right: 0;
}

.buttons.has-addons .button:hover, .buttons.has-addons .button.is-hovered {
  z-index: 2;
}

.buttons.has-addons .button:focus, .buttons.has-addons .button.is-focused, .buttons.has-addons .button:active, .buttons.has-addons .button.is-active, .buttons.has-addons .button.is-selected {
  z-index: 3;
}

.buttons.has-addons .button:focus:hover, .buttons.has-addons .button.is-focused:hover, .buttons.has-addons .button:active:hover, .buttons.has-addons .button.is-active:hover, .buttons.has-addons .button.is-selected:hover {
  z-index: 4;
}

.buttons.has-addons .button.is-expanded {
  flex-grow: 1;
  flex-shrink: 1;
}

.buttons.is-centered {
  justify-content: center;
}

.buttons.is-centered:not(.has-addons) .button:not(.is-fullwidth) {
  margin-left: 0.25rem;
  margin-right: 0.25rem;
}

.buttons.is-right {
  justify-content: flex-end;
}

.buttons.is-right:not(.has-addons) .button:not(.is-fullwidth) {
  margin-left: 0.25rem;
  margin-right: 0.25rem;
}

@media screen and (max-width: 768px) {
  .button.is-responsive.is-small {
    font-size: 0.5625rem;
  }
  .button.is-responsive,
  .button.is-responsive.is-normal {
    font-size: 0.65625rem;
  }
  .button.is-responsive.is-medium {
    font-size: 0.75rem;
  }
  .button.is-responsive.is-large {
    font-size: 1rem;
  }
}

@media screen and (min-width: 769px) and (max-width: 1023px) {
  .button.is-responsive.is-small {
    font-size: 0.65625rem;
  }
  .button.is-responsive,
  .button.is-responsive.is-normal {
    font-size: 0.75rem;
  }
  .button.is-responsive.is-medium {
    font-size: 1rem;
  }
  .button.is-responsive.is-large {
    font-size: 1.25rem;
  }
}

.container {
  flex-grow: 1;
  margin: 0 auto;
  position: relative;
  width: auto;
}

.container.is-fluid {
  max-width: none !important;
  padding-left: 32px;
  padding-right: 32px;
  width: 100%;
}

@media screen and (min-width: 1024px) {
  .container {
    max-width: 960px;
  }
}

@media screen and (max-width: 1215px) {
  .container.is-widescreen:not(.is-max-desktop) {
    max-width: 1152px;
  }
}

@media screen and (max-width: 1407px) {
  .container.is-fullhd:not(.is-max-desktop):not(.is-max-widescreen) {
    max-width: 1344px;
  }
}

@media screen and (min-width: 1216px) {
  .container:not(.is-max-desktop) {
    max-width: 1152px;
  }
}

@media screen and (min-width: 1408px) {
  .container:not(.is-max-desktop):not(.is-max-widescreen) {
    max-width: 1344px;
  }
}

.content li + li {
  margin-top: 0.25em;
}

.content p:not(:last-child),
.content dl:not(:last-child),
.content ol:not(:last-child),
.content ul:not(:last-child),
.content blockquote:not(:last-child),
.content pre:not(:last-child),
.content table:not(:last-child) {
  margin-bottom: 1em;
}

.content h1,
.content h2,
.content h3,
.content h4,
.content h5,
.content h6 {
  color: #363636;
  font-weight: 600;
  line-height: 1.125;
}

.content h1 {
  font-size: 2em;
  margin-bottom: 0.5em;
}

.content h1:not(:first-child) {
  margin-top: 1em;
}

.content h2 {
  font-size: 1.75em;
  margin-bottom: 0.5714em;
}

.content h2:not(:first-child) {
  margin-top: 1.1428em;
}

.content h3 {
  font-size: 1.5em;
  margin-bottom: 0.6666em;
}

.content h3:not(:first-child) {
  margin-top: 1.3333em;
}

.content h4 {
  font-size: 1.25em;
  margin-bottom: 0.8em;
}

.content h5 {
  font-size: 1.125em;
  margin-bottom: 0.8888em;
}

.content h6 {
  font-size: 1em;
  margin-bottom: 1em;
}

.content blockquote {
  background-color: whitesmoke;
  border-left: 5px solid #dbdbdb;
  padding: 1.25em 1.5em;
}

.content ol {
  list-style-position: outside;
  margin-left: 2em;
  margin-top: 1em;
}

.content ol:not([type]) {
  list-style-type: decimal;
}

.content ol:not([type]).is-lower-alpha {
  list-style-type: lower-alpha;
}

.content ol:not([type]).is-lower-roman {
  list-style-type: lower-roman;
}

.content ol:not([type]).is-upper-alpha {
  list-style-type: upper-alpha;
}

.content ol:not([type]).is-upper-roman {
  list-style-type: upper-roman;
}

.content ul {
  list-style: disc outside;
  margin-left: 2em;
  margin-top: 1em;
}

.content ul ul {
  list-style-type: circle;
  margin-top: 0.5em;
}

.content ul ul ul {
  list-style-type: square;
}

.content dd {
  margin-left: 2em;
}

.content figure {
  margin-left: 2em;
  margin-right: 2em;
  text-align: center;
}

.content figure:not(:first-child) {
  margin-top: 2em;
}

.content figure:not(:last-child) {
  margin-bottom: 2em;
}

.content figure img {
  display: inline-block;
}

.content figure figcaption {
  font-style: italic;
}

.content pre {
  -webkit-overflow-scrolling: touch;
  overflow-x: auto;
  padding: 1.25em 1.5em;
  white-space: pre;
  word-wrap: normal;
}

.content sup,
.content sub {
  font-size: 75%;
}

.content table {
  width: 100%;
}

.content table td,
.content table th {
  border: 1px solid #dbdbdb;
  border-width: 0 0 1px;
  padding: 0.5em 0.75em;
  vertical-align: top;
}

.content table th {
  color: #363636;
}

.content table th:not([align]) {
  text-align: inherit;
}

.content table thead td,
.content table thead th {
  border-width: 0 0 2px;
  color: #363636;
}

.content table tfoot td,
.content table tfoot th {
  border-width: 2px 0 0;
  color: #363636;
}

.content table tbody tr:last-child td,
.content table tbody tr:last-child th {
  border-bottom-width: 0;
}

.content .tabs li + li {
  margin-top: 0;
}

.content.is-small {
  font-size: 0.75rem;
}

.content.is-normal {
  font-size: 1rem;
}

.content.is-medium {
  font-size: 1.25rem;
}

.content.is-large {
  font-size: 1.5rem;
}

.icon {
  align-items: center;
  display: inline-flex;
  justify-content: center;
  height: 1.5rem;
  width: 1.5rem;
}

.icon.is-small {
  height: 1rem;
  width: 1rem;
}

.icon.is-medium {
  height: 2rem;
  width: 2rem;
}

.icon.is-large {
  height: 3rem;
  width: 3rem;
}

.icon-text {
  align-items: flex-start;
  color: inherit;
  display: inline-flex;
  flex-wrap: wrap;
  line-height: 1.5rem;
  vertical-align: top;
}

.icon-text .icon {
  flex-grow: 0;
  flex-shrink: 0;
}

.icon-text .icon:not(:last-child) {
  margin-right: 0.25em;
}

.icon-text .icon:not(:first-child) {
  margin-left: 0.25em;
}

div.icon-text {
  display: flex;
}

.image {
  display: block;
  position: relative;
}

.image img {
  display: block;
  height: auto;
  width: 100%;
}

.image img.is-rounded {
  border-radius: 9999px;
}

.image.is-fullwidth {
  width: 100%;
}

.image.is-square img,
.image.is-square .has-ratio, .image.is-1by1 img,
.image.is-1by1 .has-ratio, .image.is-5by4 img,
.image.is-5by4 .has-ratio, .image.is-4by3 img,
.image.is-4by3 .has-ratio, .image.is-3by2 img,
.image.is-3by2 .has-ratio, .image.is-5by3 img,
.image.is-5by3 .has-ratio, .image.is-16by9 img,
.image.is-16by9 .has-ratio, .image.is-2by1 img,
.image.is-2by1 .has-ratio, .image.is-3by1 img,
.image.is-3by1 .has-ratio, .image.is-4by5 img,
.image.is-4by5 .has-ratio, .image.is-3by4 img,
.image.is-3by4 .has-ratio, .image.is-2by3 img,
.image.is-2by3 .has-ratio, .image.is-3by5 img,
.image.is-3by5 .has-ratio, .image.is-9by16 img,
.image.is-9by16 .has-ratio, .image.is-1by2 img,
.image.is-1by2 .has-ratio, .image.is-1by3 img,
.image.is-1by3 .has-ratio {
  height: 100%;
  width: 100%;
}

.image.is-square, .image.is-1by1 {
  padding-top: 100%;
}

.image.is-5by4 {
  padding-top: 80%;
}

.image.is-4by3 {
  padding-top: 75%;
}

.image.is-3by2 {
  padding-top: 66.6666%;
}

.image.is-5by3 {
  padding-top: 60%;
}

.image.is-16by9 {
  padding-top: 56.25%;
}

.image.is-2by1 {
  padding-top: 50%;
}

.image.is-3by1 {
  padding-top: 33.3333%;
}

.image.is-4by5 {
  padding-top: 125%;
}

.image.is-3by4 {
  padding-top: 133.3333%;
}

.image.is-2by3 {
  padding-top: 150%;
}

.image.is-3by5 {
  padding-top: 166.6666%;
}

.image.is-9by16 {
  padding-top: 177.7777%;
}

.image.is-1by2 {
  padding-top: 200%;
}

.image.is-1by3 {
  padding-top: 300%;
}

.image.is-16x16 {
  height: 16px;
  width: 16px;
}

.image.is-24x24 {
  height: 24px;
  width: 24px;
}

.image.is-32x32 {
  height: 32px;
  width: 32px;
}

.image.is-48x48 {
  height: 48px;
  width: 48px;
}

.image.is-64x64 {
  height: 64px;
  width: 64px;
}

.image.is-96x96 {
  height: 96px;
  width: 96px;
}

.image.is-128x128 {
  height: 128px;
  width: 128px;
}

.notification {
  background-color: whitesmoke;
  border-radius: 4px;
  position: relative;
  padding: 1.25rem 2.5rem 1.25rem 1.5rem;
}

.notification a:not(.button):not(.dropdown-item) {
  color: currentColor;
  text-decoration: underline;
}

.notification strong {
  color: currentColor;
}

.notification code,
.notification pre {
  background: white;
}

.notification pre code {
  background: transparent;
}

.notification > .delete {
  right: 0.5rem;
  position: absolute;
  top: 0.5rem;
}

.notification .title,
.notification .subtitle,
.notification .content {
  color: currentColor;
}

.notification.is-white {
  background-color: white;
  color: #0a0a0a;
}

.notification.is-black {
  background-color: #0a0a0a;
  color: white;
}

.notification.is-light {
  background-color: whitesmoke;
  color: rgba(0, 0, 0, 0.7);
}

.notification.is-dark {
  background-color: #363636;
  color: #fff;
}

.notification.is-primary {
  background-color: #00d1b2;
  color: #fff;
}

.notification.is-primary.is-light {
  background-color: #ebfffc;
  color: #00947e;
}

.notification.is-link {
  background-color: #485fc7;
  color: #fff;
}

.notification.is-link.is-light {
  background-color: #eff1fa;
  color: #3850b7;
}

.notification.is-info {
  background-color: #3e8ed0;
  color: #fff;
}

.notification.is-info.is-light {
  background-color: #eff5fb;
  color: #296fa8;
}

.notification.is-success {
  background-color: #48c78e;
  color: #fff;
}

.notification.is-success.is-light {
  background-color: #effaf5;
  color: #257953;
}

.notification.is-warning {
  background-color: #ffe08a;
  color: rgba(0, 0, 0, 0.7);
}

.notification.is-warning.is-light {
  background-color: #fffaeb;
  color: #946c00;
}

.notification.is-danger {
  background-color: #f14668;
  color: #fff;
}

.notification.is-danger.is-light {
  background-color: #feecf0;
  color: #cc0f35;
}

.progress {
  -moz-appearance: none;
  -webkit-appearance: none;
  border: none;
  border-radius: 9999px;
  display: block;
  height: 1rem;
  overflow: hidden;
  padding: 0;
  width: 100%;
}

.progress::-webkit-progress-bar {
  background-color: #ededed;
}

.progress::-webkit-progress-value {
  background-color: #4a4a4a;
}

.progress::-moz-progress-bar {
  background-color: #4a4a4a;
}

.progress::-ms-fill {
  background-color: #4a4a4a;
  border: none;
}

.progress.is-white::-webkit-progress-value {
  background-color: white;
}

.progress.is-white::-moz-progress-bar {
  background-color: white;
}

.progress.is-white::-ms-fill {
  background-color: white;
}

.progress.is-white:indeterminate {
  background-image: linear-gradient(to right, white 30%, #ededed 30%);
}

.progress.is-black::-webkit-progress-value {
  background-color: #0a0a0a;
}

.progress.is-black::-moz-progress-bar {
  background-color: #0a0a0a;
}

.progress.is-black::-ms-fill {
  background-color: #0a0a0a;
}

.progress.is-black:indeterminate {
  background-image: linear-gradient(to right, #0a0a0a 30%, #ededed 30%);
}

.progress.is-light::-webkit-progress-value {
  background-color: whitesmoke;
}

.progress.is-light::-moz-progress-bar {
  background-color: whitesmoke;
}

.progress.is-light::-ms-fill {
  background-color: whitesmoke;
}

.progress.is-light:indeterminate {
  background-image: linear-gradient(to right, whitesmoke 30%, #ededed 30%);
}

.progress.is-dark::-webkit-progress-value {
  background-color: #363636;
}

.progress.is-dark::-moz-progress-bar {
  background-color: #363636;
}

.progress.is-dark::-ms-fill {
  background-color: #363636;
}

.progress.is-dark:indeterminate {
  background-image: linear-gradient(to right, #363636 30%, #ededed 30%);
}

.progress.is-primary::-webkit-progress-value {
  background-color: #00d1b2;
}

.progress.is-primary::-moz-progress-bar {
  background-color: #00d1b2;
}

.progress.is-primary::-ms-fill {
  background-color: #00d1b2;
}

.progress.is-primary:indeterminate {
  background-image: linear-gradient(to right, #00d1b2 30%, #ededed 30%);
}

.progress.is-link::-webkit-progress-value {
  background-color: #485fc7;
}

.progress.is-link::-moz-progress-bar {
  background-color: #485fc7;
}

.progress.is-link::-ms-fill {
  background-color: #485fc7;
}

.progress.is-link:indeterminate {
  background-image: linear-gradient(to right, #485fc7 30%, #ededed 30%);
}

.progress.is-info::-webkit-progress-value {
  background-color: #3e8ed0;
}

.progress.is-info::-moz-progress-bar {
  background-color: #3e8ed0;
}

.progress.is-info::-ms-fill {
  background-color: #3e8ed0;
}

.progress.is-info:indeterminate {
  background-image: linear-gradient(to right, #3e8ed0 30%, #ededed 30%);
}

.progress.is-success::-webkit-progress-value {
  background-color: #48c78e;
}

.progress.is-success::-moz-progress-bar {
  background-color: #48c78e;
}

.progress.is-success::-ms-fill {
  background-color: #48c78e;
}

.progress.is-success:indeterminate {
  background-image: linear-gradient(to right, #48c78e 30%, #ededed 30%);
}

.progress.is-warning::-webkit-progress-value {
  background-color: #ffe08a;
}

.progress.is-warning::-moz-progress-bar {
  background-color: #ffe08a;
}

.progress.is-warning::-ms-fill {
  background-color: #ffe08a;
}

.progress.is-warning:indeterminate {
  background-image: linear-gradient(to right, #ffe08a 30%, #ededed 30%);
}

.progress.is-danger::-webkit-progress-value {
  background-color: #f14668;
}

.progress.is-danger::-moz-progress-bar {
  background-color: #f14668;
}

.progress.is-danger::-ms-fill {
  background-color: #f14668;
}

.progress.is-danger:indeterminate {
  background-image: linear-gradient(to right, #f14668 30%, #ededed 30%);
}

.progress:indeterminate {
  -webkit-animation-duration: 1.5s;
          animation-duration: 1.5s;
  -webkit-animation-iteration-count: infinite;
          animation-iteration-count: infinite;
  -webkit-animation-name: moveIndeterminate;
          animation-name: moveIndeterminate;
  -webkit-animation-timing-function: linear;
          animation-timing-function: linear;
  background-color: #ededed;
  background-image: linear-gradient(to right, #4a4a4a 30%, #ededed 30%);
  background-position: top left;
  background-repeat: no-repeat;
  background-size: 150% 150%;
}

.progress:indeterminate::-webkit-progress-bar {
  background-color: transparent;
}

.progress:indeterminate::-moz-progress-bar {
  background-color: transparent;
}

.progress:indeterminate::-ms-fill {
  animation-name: none;
}

.progress.is-small {
  height: 0.75rem;
}

.progress.is-medium {
  height: 1.25rem;
}

.progress.is-large {
  height: 1.5rem;
}

@-webkit-keyframes moveIndeterminate {
  from {
    background-position: 200% 0;
  }
  to {
    background-position: -200% 0;
  }
}

@keyframes moveIndeterminate {
  from {
    background-position: 200% 0;
  }
  to {
    background-position: -200% 0;
  }
}

.table {
  background-color: white;
  color: #363636;
}

.table td,
.table th {
  border: 1px solid #dbdbdb;
  border-width: 0 0 1px;
  padding: 0.5em 0.75em;
  vertical-align: top;
}

.table td.is-white,
.table th.is-white {
  background-color: white;
  border-color: white;
  color: #0a0a0a;
}

.table td.is-black,
.table th.is-black {
  background-color: #0a0a0a;
  border-color: #0a0a0a;
  color: white;
}

.table td.is-light,
.table th.is-light {
  background-color: whitesmoke;
  border-color: whitesmoke;
  color: rgba(0, 0, 0, 0.7);
}

.table td.is-dark,
.table th.is-dark {
  background-color: #363636;
  border-color: #363636;
  color: #fff;
}

.table td.is-primary,
.table th.is-primary {
  background-color: #00d1b2;
  border-color: #00d1b2;
  color: #fff;
}

.table td.is-link,
.table th.is-link {
  background-color: #485fc7;
  border-color: #485fc7;
  color: #fff;
}

.table td.is-info,
.table th.is-info {
  background-color: #3e8ed0;
  border-color: #3e8ed0;
  color: #fff;
}

.table td.is-success,
.table th.is-success {
  background-color: #48c78e;
  border-color: #48c78e;
  color: #fff;
}

.table td.is-warning,
.table th.is-warning {
  background-color: #ffe08a;
  border-color: #ffe08a;
  color: rgba(0, 0, 0, 0.7);
}

.table td.is-danger,
.table th.is-danger {
  background-color: #f14668;
  border-color: #f14668;
  color: #fff;
}

.table td.is-narrow,
.table th.is-narrow {
  white-space: nowrap;
  width: 1%;
}

.table td.is-selected,
.table th.is-selected {
  background-color: #00d1b2;
  color: #fff;
}

.table td.is-selected a,
.table td.is-selected strong,
.table th.is-selected a,
.table th.is-selected strong {
  color: currentColor;
}

.table td.is-vcentered,
.table th.is-vcentered {
  vertical-align: middle;
}

.table th {
  color: #363636;
}

.table th:not([align]) {
  text-align: left;
}

.table tr.is-selected {
  background-color: #00d1b2;
  color: #fff;
}

.table tr.is-selected a,
.table tr.is-selected strong {
  color: currentColor;
}

.table tr.is-selected td,
.table tr.is-selected th {
  border-color: #fff;
  color: currentColor;
}

.table thead {
  background-color: transparent;
}

.table thead td,
.table thead th {
  border-width: 0 0 2px;
  color: #363636;
}

.table tfoot {
  background-color: transparent;
}

.table tfoot td,
.table tfoot th {
  border-width: 2px 0 0;
  color: #363636;
}

.table tbody {
  background-color: transparent;
}

.table tbody tr:last-child td,
.table tbody tr:last-child th {
  border-bottom-width: 0;
}

.table.is-bordered td,
.table.is-bordered th {
  border-width: 1px;
}

.table.is-bordered tr:last-child td,
.table.is-bordered tr:last-child th {
  border-bottom-width: 1px;
}

.table.is-fullwidth {
  width: 100%;
}

.table.is-hoverable tbody tr:not(.is-selected):hover {
  background-color: #fafafa;
}

.table.is-hoverable.is-striped tbody tr:not(.is-selected):hover {
  background-color: #fafafa;
}

.table.is-hoverable.is-striped tbody tr:not(.is-selected):hover:nth-child(even) {
  background-color: whitesmoke;
}

.table.is-narrow td,
.table.is-narrow th {
  padding: 0.25em 0.5em;
}

.table.is-striped tbody tr:not(.is-selected):nth-child(even) {
  background-color: #fafafa;
}

.table-container {
  -webkit-overflow-scrolling: touch;
  overflow: auto;
  overflow-y: hidden;
  max-width: 100%;
}

.tags {
  align-items: center;
  display: flex;
  flex-wrap: wrap;
  justify-content: flex-start;
}

.tags .tag {
  margin-bottom: 0.5rem;
}

.tags .tag:not(:last-child) {
  margin-right: 0.5rem;
}

.tags:last-child {
  margin-bottom: -0.5rem;
}

.tags:not(:last-child) {
  margin-bottom: 1rem;
}

.tags.are-medium .tag:not(.is-normal):not(.is-large) {
  font-size: 1rem;
}

.tags.are-large .tag:not(.is-normal):not(.is-medium) {
  font-size: 1.25rem;
}

.tags.is-centered {
  justify-content: center;
}

.tags.is-centered .tag {
  margin-right: 0.25rem;
  margin-left: 0.25rem;
}

.tags.is-right {
  justify-content: flex-end;
}

.tags.is-right .tag:not(:first-child) {
  margin-left: 0.5rem;
}

.tags.is-right .tag:not(:last-child) {
  margin-right: 0;
}

.tags.has-addons .tag {
  margin-right: 0;
}

.tags.has-addons .tag:not(:first-child) {
  margin-left: 0;
  border-top-left-radius: 0;
  border-bottom-left-radius: 0;
}

.tags.has-addons .tag:not(:last-child) {
  border-top-right-radius: 0;
  border-bottom-right-radius: 0;
}

.tag:not(body) {
  align-items: center;
  background-color: whitesmoke;
  border-radius: 4px;
  color: #4a4a4a;
  display: inline-flex;
  font-size: 0.75rem;
  height: 2em;
  justify-content: center;
  line-height: 1.5;
  padding-left: 0.75em;
  padding-right: 0.75em;
  white-space: nowrap;
}

.tag:not(body) .delete {
  margin-left: 0.25rem;
  margin-right: -0.375rem;
}

.tag:not(body).is-white {
  background-color: white;
  color: #0a0a0a;
}

.tag:not(body).is-black {
  background-color: #0a0a0a;
  color: white;
}

.tag:not(body).is-light {
  background-color: whitesmoke;
  color: rgba(0, 0, 0, 0.7);
}

.tag:not(body).is-dark {
  background-color: #363636;
  color: #fff;
}

.tag:not(body).is-primary {
  background-color: #00d1b2;
  color: #fff;
}

.tag:not(body).is-primary.is-light {
  background-color: #ebfffc;
  color: #00947e;
}

.tag:not(body).is-link {
  background-color: #485fc7;
  color: #fff;
}

.tag:not(body).is-link.is-light {
  background-color: #eff1fa;
  color: #3850b7;
}

.tag:not(body).is-info {
  background-color: #3e8ed0;
  color: #fff;
}

.tag:not(body).is-info.is-light {
  background-color: #eff5fb;
  color: #296fa8;
}

.tag:not(body).is-success {
  background-color: #48c78e;
  color: #fff;
}

.tag:not(body).is-success.is-light {
  background-color: #effaf5;
  color: #257953;
}

.tag:not(body).is-warning {
  background-color: #ffe08a;
  color: rgba(0, 0, 0, 0.7);
}

.tag:not(body).is-warning.is-light {
  background-color: #fffaeb;
  color: #946c00;
}

.tag:not(body).is-danger {
  background-color: #f14668;
  color: #fff;
}

.tag:not(body).is-danger.is-light {
  background-color: #feecf0;
  color: #cc0f35;
}

.tag:not(body).is-normal {
  font-size: 0.75rem;
}

.tag:not(body).is-medium {
  font-size: 1rem;
}

.tag:not(body).is-large {
  font-size: 1.25rem;
}

.tag:not(body) .icon:first-child:not(:last-child) {
  margin-left: -0.375em;
  margin-right: 0.1875em;
}

.tag:not(body) .icon:last-child:not(:first-child) {
  margin-left: 0.1875em;
  margin-right: -0.375em;
}

.tag:not(body) .icon:first-child:last-child {
  margin-left: -0.375em;
  margin-right: -0.375em;
}

.tag:not(body).is-delete {
  margin-left: 1px;
  padding: 0;
  position: relative;
  width: 2em;
}

.tag:not(body).is-delete::before, .tag:not(body).is-delete::after {
  background-color: currentColor;
  content: "";
  display: block;
  left: 50%;
  position: absolute;
  top: 50%;
  transform: translateX(-50%) translateY(-50%) rotate(45deg);
  transform-origin: center center;
}

.tag:not(body).is-delete::before {
  height: 1px;
  width: 50%;
}

.tag:not(body).is-delete::after {
  height: 50%;
  width: 1px;
}

.tag:not(body).is-delete:hover, .tag:not(body).is-delete:focus {
  background-color: #e8e8e8;
}

.tag:not(body).is-delete:active {
  background-color: #dbdbdb;
}

.tag:not(body).is-rounded {
  border-radius: 9999px;
}

a.tag:hover {
  text-decoration: underline;
}

.title,
.subtitle {
  word-break: break-word;
}

.title em,
.title span,
.subtitle em,
.subtitle span {
  font-weight: inherit;
}

.title sub,
.subtitle sub {
  font-size: 0.75em;
}

.title sup,
.subtitle sup {
  font-size: 0.75em;
}

.title .tag,
.subtitle .tag {
  vertical-align: middle;
}

.title {
  color: #363636;
  font-size: 2rem;
  font-weight: 600;
  line-height: 1.125;
}

.title strong {
  color: inherit;
  font-weight: inherit;
}

.title:not(.is-spaced) + .subtitle {
  margin-top: -1.25rem;
}

.title.is-1 {
  font-size: 3rem;
}

.title.is-2 {
  font-size: 2.5rem;
}

.title.is-3 {
  font-size: 2rem;
}

.title.is-4 {
  font-size: 1.5rem;
}

.title.is-5 {
  font-size: 1.25rem;
}

.title.is-6 {
  font-size: 1rem;
}

.title.is-7 {
  font-size: 0.75rem;
}

.subtitle {
  color: #4a4a4a;
  font-size: 1.25rem;
  font-weight: 400;
  line-height: 1.25;
}

.subtitle strong {
  color: #363636;
  font-weight: 600;
}

.subtitle:not(.is-spaced) + .title {
  margin-top: -1.25rem;
}

.subtitle.is-1 {
  font-size: 3rem;
}

.subtitle.is-2 {
  font-size: 2.5rem;
}

.subtitle.is-3 {
  font-size: 2rem;
}

.subtitle.is-4 {
  font-size: 1.5rem;
}

.subtitle.is-5 {
  font-size: 1.25rem;
}

.subtitle.is-6 {
  font-size: 1rem;
}

.subtitle.is-7 {
  font-size: 0.75rem;
}

.heading {
  display: block;
  font-size: 11px;
  letter-spacing: 1px;
  margin-bottom: 5px;
  text-transform: uppercase;
}

.number {
  align-items: center;
  background-color: whitesmoke;
  border-radius: 9999px;
  display: inline-flex;
  font-size: 1.25rem;
  height: 2em;
  justify-content: center;
  margin-right: 1.5rem;
  min-width: 2.5em;
  padding: 0.25rem 0.5rem;
  text-align: center;
  vertical-align: top;
}

/* Bulma Form */
.input, .textarea, .select select {
  background-color: white;
  border-color: #dbdbdb;
  border-radius: 4px;
  color: #363636;
}

.input::-moz-placeholder, .textarea::-moz-placeholder, .select select::-moz-placeholder {
  color: rgba(54, 54, 54, 0.3);
}

.input::-webkit-input-placeholder, .textarea::-webkit-input-placeholder, .select select::-webkit-input-placeholder {
  color: rgba(54, 54, 54, 0.3);
}

.input:-moz-placeholder, .textarea:-moz-placeholder, .select select:-moz-placeholder {
  color: rgba(54, 54, 54, 0.3);
}

.input:-ms-input-placeholder, .textarea:-ms-input-placeholder, .select select:-ms-input-placeholder {
  color: rgba(54, 54, 54, 0.3);
}

.input:hover, .textarea:hover, .select select:hover, .is-hovered.input, .is-hovered.textarea, .select select.is-hovered {
  border-color: #b5b5b5;
}

.input:focus, .textarea:focus, .select select:focus, .is-focused.input, .is-focused.textarea, .select select.is-focused, .input:active, .textarea:active, .select select:active, .is-active.input, .is-active.textarea, .select select.is-active {
  border-color: #485fc7;
  box-shadow: 0 0 0 0.125em rgba(72, 95, 199, 0.25);
}

.input[disabled], .textarea[disabled], .select select[disabled],
fieldset[disabled] .input,
fieldset[disabled] .textarea,
fieldset[disabled] .select select,
.select fieldset[disabled] select {
  background-color: whitesmoke;
  border-color: whitesmoke;
  box-shadow: none;
  color: #7a7a7a;
}

.input[disabled]::-moz-placeholder, .textarea[disabled]::-moz-placeholder, .select select[disabled]::-moz-placeholder,
fieldset[disabled] .input::-moz-placeholder,
fieldset[disabled] .textarea::-moz-placeholder,
fieldset[disabled] .select select::-moz-placeholder,
.select fieldset[disabled] select::-moz-placeholder {
  color: rgba(122, 122, 122, 0.3);
}

.input[disabled]::-webkit-input-placeholder, .textarea[disabled]::-webkit-input-placeholder, .select select[disabled]::-webkit-input-placeholder,
fieldset[disabled] .input::-webkit-input-placeholder,
fieldset[disabled] .textarea::-webkit-input-placeholder,
fieldset[disabled] .select select::-webkit-input-placeholder,
.select fieldset[disabled] select::-webkit-input-placeholder {
  color: rgba(122, 122, 122, 0.3);
}

.input[disabled]:-moz-placeholder, .textarea[disabled]:-moz-placeholder, .select select[disabled]:-moz-placeholder,
fieldset[disabled] .input:-moz-placeholder,
fieldset[disabled] .textarea:-moz-placeholder,
fieldset[disabled] .select select:-moz-placeholder,
.select fieldset[disabled] select:-moz-placeholder {
  color: rgba(122, 122, 122, 0.3);
}

.input[disabled]:-ms-input-placeholder, .textarea[disabled]:-ms-input-placeholder, .select select[disabled]:-ms-input-placeholder,
fieldset[disabled] .input:-ms-input-placeholder,
fieldset[disabled] .textarea:-ms-input-placeholder,
fieldset[disabled] .select select:-ms-input-placeholder,
.select fieldset[disabled] select:-ms-input-placeholder {
  color: rgba(122, 122, 122, 0.3);
}

.input, .textarea {
  box-shadow: inset 0 0.0625em 0.125em rgba(10, 10, 10, 0.05);
  max-width: 100%;
  width: 100%;
}

.input[readonly], .textarea[readonly] {
  box-shadow: none;
}

.is-white.input, .is-white.textarea {
  border-color: white;
}

.is-white.input:focus, .is-white.textarea:focus, .is-white.is-focused.input, .is-white.is-focused.textarea, .is-white.input:active, .is-white.textarea:active, .is-white.is-active.input, .is-white.is-active.textarea {
  box-shadow: 0 0 0 0.125em rgba(255, 255, 255, 0.25);
}

.is-black.input, .is-black.textarea {
  border-color: #0a0a0a;
}

.is-black.input:focus, .is-black.textarea:focus, .is-black.is-focused.input, .is-black.is-focused.textarea, .is-black.input:active, .is-black.textarea:active, .is-black.is-active.input, .is-black.is-active.textarea {
  box-shadow: 0 0 0 0.125em rgba(10, 10, 10, 0.25);
}

.is-light.input, .is-light.textarea {
  border-color: whitesmoke;
}

.is-light.input:focus, .is-light.textarea:focus, .is-light.is-focused.input, .is-light.is-focused.textarea, .is-light.input:active, .is-light.textarea:active, .is-light.is-active.input, .is-light.is-active.textarea {
  box-shadow: 0 0 0 0.125em rgba(245, 245, 245, 0.25);
}

.is-dark.input, .is-dark.textarea {
  border-color: #363636;
}

.is-dark.input:focus, .is-dark.textarea:focus, .is-dark.is-focused.input, .is-dark.is-focused.textarea, .is-dark.input:active, .is-dark.textarea:active, .is-dark.is-active.input, .is-dark.is-active.textarea {
  box-shadow: 0 0 0 0.125em rgba(54, 54, 54, 0.25);
}

.is-primary.input, .is-primary.textarea {
  border-color: #00d1b2;
}

.is-primary.input:focus, .is-primary.textarea:focus, .is-primary.is-focused.input, .is-primary.is-focused.textarea, .is-primary.input:active, .is-primary.textarea:active, .is-primary.is-active.input, .is-primary.is-active.textarea {
  box-shadow: 0 0 0 0.125em rgba(0, 209, 178, 0.25);
}

.is-link.input, .is-link.textarea {
  border-color: #485fc7;
}

.is-link.input:focus, .is-link.textarea:focus, .is-link.is-focused.input, .is-link.is-focused.textarea, .is-link.input:active, .is-link.textarea:active, .is-link.is-active.input, .is-link.is-active.textarea {
  box-shadow: 0 0 0 0.125em rgba(72, 95, 199, 0.25);
}

.is-info.input, .is-info.textarea {
  border-color: #3e8ed0;
}

.is-info.input:focus, .is-info.textarea:focus, .is-info.is-focused.input, .is-info.is-focused.textarea, .is-info.input:active, .is-info.textarea:active, .is-info.is-active.input, .is-info.is-active.textarea {
  box-shadow: 0 0 0 0.125em rgba(62, 142, 208, 0.25);
}

.is-success.input, .is-success.textarea {
  border-color: #48c78e;
}

.is-success.input:focus, .is-success.textarea:focus, .is-success.is-focused.input, .is-success.is-focused.textarea, .is-success.input:active, .is-success.textarea:active, .is-success.is-active.input, .is-success.is-active.textarea {
  box-shadow: 0 0 0 0.125em rgba(72, 199, 142, 0.25);
}

.is-warning.input, .is-warning.textarea {
  border-color: #ffe08a;
}

.is-warning.input:focus, .is-warning.textarea:focus, .is-warning.is-focused.input, .is-warning.is-focused.textarea, .is-warning.input:active, .is-warning.textarea:active, .is-warning.is-active.input, .is-warning.is-active.textarea {
  box-shadow: 0 0 0 0.125em rgba(255, 224, 138, 0.25);
}

.is-danger.input, .is-danger.textarea {
  border-color: #f14668;
}

.is-danger.input:focus, .is-danger.textarea:focus, .is-danger.is-focused.input, .is-danger.is-focused.textarea, .is-danger.input:active, .is-danger.textarea:active, .is-danger.is-active.input, .is-danger.is-active.textarea {
  box-shadow: 0 0 0 0.125em rgba(241, 70, 104, 0.25);
}

.is-small.input, .is-small.textarea {
  border-radius: 2px;
  font-size: 0.75rem;
}

.is-medium.input, .is-medium.textarea {
  font-size: 1.25rem;
}

.is-large.input, .is-large.textarea {
  font-size: 1.5rem;
}

.is-fullwidth.input, .is-fullwidth.textarea {
  display: block;
  width: 100%;
}

.is-inline.input, .is-inline.textarea {
  display: inline;
  width: auto;
}

.input.is-rounded {
  border-radius: 9999px;
  padding-left: calc(calc(0.75em - 1px) + 0.375em);
  padding-right: calc(calc(0.75em - 1px) + 0.375em);
}

.input.is-static {
  background-color: transparent;
  border-color: transparent;
  box-shadow: none;
  padding-left: 0;
  padding-right: 0;
}

.textarea {
  display: block;
  max-width: 100%;
  min-width: 100%;
  padding: calc(0.75em - 1px);
  resize: vertical;
}

.textarea:not([rows]) {
  max-height: 40em;
  min-height: 8em;
}

.textarea[rows] {
  height: initial;
}

.textarea.has-fixed-size {
  resize: none;
}

.checkbox, .radio {
  cursor: pointer;
  display: inline-block;
  line-height: 1.25;
  position: relative;
}

.checkbox input, .radio input {
  cursor: pointer;
}

.checkbox:hover, .radio:hover {
  color: #363636;
}

.checkbox[disabled], .radio[disabled],
fieldset[disabled] .checkbox,
fieldset[disabled] .radio,
.checkbox input[disabled],
.radio input[disabled] {
  color: #7a7a7a;
  cursor: not-allowed;
}

.radio + .radio {
  margin-left: 0.5em;
}

.select {
  display: inline-block;
  max-width: 100%;
  position: relative;
  vertical-align: top;
}

.select:not(.is-multiple) {
  height: 2.5em;
}

.select:not(.is-multiple):not(.is-loading)::after {
  border-color: #485fc7;
  right: 1.125em;
  z-index: 4;
}

.select.is-rounded select {
  border-radius: 9999px;
  padding-left: 1em;
}

.select select {
  cursor: pointer;
  display: block;
  font-size: 1em;
  max-width: 100%;
  outline: none;
}

.select select::-ms-expand {
  display: none;
}

.select select[disabled]:hover,
fieldset[disabled] .select select:hover {
  border-color: whitesmoke;
}

.select select:not([multiple]) {
  padding-right: 2.5em;
}

.select select[multiple] {
  height: auto;
  padding: 0;
}

.select select[multiple] option {
  padding: 0.5em 1em;
}

.select:not(.is-multiple):not(.is-loading):hover::after {
  border-color: #363636;
}

.select.is-white:not(:hover)::after {
  border-color: white;
}

.select.is-white select {
  border-color: white;
}

.select.is-white select:hover, .select.is-white select.is-hovered {
  border-color: #f2f2f2;
}

.select.is-white select:focus, .select.is-white select.is-focused, .select.is-white select:active, .select.is-white select.is-active {
  box-shadow: 0 0 0 0.125em rgba(255, 255, 255, 0.25);
}

.select.is-black:not(:hover)::after {
  border-color: #0a0a0a;
}

.select.is-black select {
  border-color: #0a0a0a;
}

.select.is-black select:hover, .select.is-black select.is-hovered {
  border-color: black;
}

.select.is-black select:focus, .select.is-black select.is-focused, .select.is-black select:active, .select.is-black select.is-active {
  box-shadow: 0 0 0 0.125em rgba(10, 10, 10, 0.25);
}

.select.is-light:not(:hover)::after {
  border-color: whitesmoke;
}

.select.is-light select {
  border-color: whitesmoke;
}

.select.is-light select:hover, .select.is-light select.is-hovered {
  border-color: #e8e8e8;
}

.select.is-light select:focus, .select.is-light select.is-focused, .select.is-light select:active, .select.is-light select.is-active {
  box-shadow: 0 0 0 0.125em rgba(245, 245, 245, 0.25);
}

.select.is-dark:not(:hover)::after {
  border-color: #363636;
}

.select.is-dark select {
  border-color: #363636;
}

.select.is-dark select:hover, .select.is-dark select.is-hovered {
  border-color: #292929;
}

.select.is-dark select:focus, .select.is-dark select.is-focused, .select.is-dark select:active, .select.is-dark select.is-active {
  box-shadow: 0 0 0 0.125em rgba(54, 54, 54, 0.25);
}

.select.is-primary:not(:hover)::after {
  border-color: #00d1b2;
}

.select.is-primary select {
  border-color: #00d1b2;
}

.select.is-primary select:hover, .select.is-primary select.is-hovered {
  border-color: #00b89c;
}

.select.is-primary select:focus, .select.is-primary select.is-focused, .select.is-primary select:active, .select.is-primary select.is-active {
  box-shadow: 0 0 0 0.125em rgba(0, 209, 178, 0.25);
}

.select.is-link:not(:hover)::after {
  border-color: #485fc7;
}

.select.is-link select {
  border-color: #485fc7;
}

.select.is-link select:hover, .select.is-link select.is-hovered {
  border-color: #3a51bb;
}

.select.is-link select:focus, .select.is-link select.is-focused, .select.is-link select:active, .select.is-link select.is-active {
  box-shadow: 0 0 0 0.125em rgba(72, 95, 199, 0.25);
}

.select.is-info:not(:hover)::after {
  border-color: #3e8ed0;
}

.select.is-info select {
  border-color: #3e8ed0;
}

.select.is-info select:hover, .select.is-info select.is-hovered {
  border-color: #3082c5;
}

.select.is-info select:focus, .select.is-info select.is-focused, .select.is-info select:active, .select.is-info select.is-active {
  box-shadow: 0 0 0 0.125em rgba(62, 142, 208, 0.25);
}

.select.is-success:not(:hover)::after {
  border-color: #48c78e;
}

.select.is-success select {
  border-color: #48c78e;
}

.select.is-success select:hover, .select.is-success select.is-hovered {
  border-color: #3abb81;
}

.select.is-success select:focus, .select.is-success select.is-focused, .select.is-success select:active, .select.is-success select.is-active {
  box-shadow: 0 0 0 0.125em rgba(72, 199, 142, 0.25);
}

.select.is-warning:not(:hover)::after {
  border-color: #ffe08a;
}

.select.is-warning select {
  border-color: #ffe08a;
}

.select.is-warning select:hover, .select.is-warning select.is-hovered {
  border-color: #ffd970;
}

.select.is-warning select:focus, .select.is-warning select.is-focused, .select.is-warning select:active, .select.is-warning select.is-active {
  box-shadow: 0 0 0 0.125em rgba(255, 224, 138, 0.25);
}

.select.is-danger:not(:hover)::after {
  border-color: #f14668;
}

.select.is-danger select {
  border-color: #f14668;
}

.select.is-danger select:hover, .select.is-danger select.is-hovered {
  border-color: #ef2e55;
}

.select.is-danger select:focus, .select.is-danger select.is-focused, .select.is-danger select:active, .select.is-danger select.is-active {
  box-shadow: 0 0 0 0.125em rgba(241, 70, 104, 0.25);
}

.select.is-small {
  border-radius: 2px;
  font-size: 0.75rem;
}

.select.is-medium {
  font-size: 1.25rem;
}

.select.is-large {
  font-size: 1.5rem;
}

.select.is-disabled::after {
  border-color: #7a7a7a !important;
  opacity: 0.5;
}

.select.is-fullwidth {
  width: 100%;
}

.select.is-fullwidth select {
  width: 100%;
}

.select.is-loading::after {
  margin-top: 0;
  position: absolute;
  right: 0.625em;
  top: 0.625em;
  transform: none;
}

.select.is-loading.is-small:after {
  font-size: 0.75rem;
}

.select.is-loading.is-medium:after {
  font-size: 1.25rem;
}

.select.is-loading.is-large:after {
  font-size: 1.5rem;
}

.file {
  align-items: stretch;
  display: flex;
  justify-content: flex-start;
  position: relative;
}

.file.is-white .file-cta {
  background-color: white;
  border-color: transparent;
  color: #0a0a0a;
}

.file.is-white:hover .file-cta, .file.is-white.is-hovered .file-cta {
  background-color: #f9f9f9;
  border-color: transparent;
  color: #0a0a0a;
}

.file.is-white:focus .file-cta, .file.is-white.is-focused .file-cta {
  border-color: transparent;
  box-shadow: 0 0 0.5em rgba(255, 255, 255, 0.25);
  color: #0a0a0a;
}

.file.is-white:active .file-cta, .file.is-white.is-active .file-cta {
  background-color: #f2f2f2;
  border-color: transparent;
  color: #0a0a0a;
}

.file.is-black .file-cta {
  background-color: #0a0a0a;
  border-color: transparent;
  color: white;
}

.file.is-black:hover .file-cta, .file.is-black.is-hovered .file-cta {
  background-color: #040404;
  border-color: transparent;
  color: white;
}

.file.is-black:focus .file-cta, .file.is-black.is-focused .file-cta {
  border-color: transparent;
  box-shadow: 0 0 0.5em rgba(10, 10, 10, 0.25);
  color: white;
}

.file.is-black:active .file-cta, .file.is-black.is-active .file-cta {
  background-color: black;
  border-color: transparent;
  color: white;
}

.file.is-light .file-cta {
  background-color: whitesmoke;
  border-color: transparent;
  color: rgba(0, 0, 0, 0.7);
}

.file.is-light:hover .file-cta, .file.is-light.is-hovered .file-cta {
  background-color: #eeeeee;
  border-color: transparent;
  color: rgba(0, 0, 0, 0.7);
}

.file.is-light:focus .file-cta, .file.is-light.is-focused .file-cta {
  border-color: transparent;
  box-shadow: 0 0 0.5em rgba(245, 245, 245, 0.25);
  color: rgba(0, 0, 0, 0.7);
}

.file.is-light:active .file-cta, .file.is-light.is-active .file-cta {
  background-color: #e8e8e8;
  border-color: transparent;
  color: rgba(0, 0, 0, 0.7);
}

.file.is-dark .file-cta {
  background-color: #363636;
  border-color: transparent;
  color: #fff;
}

.file.is-dark:hover .file-cta, .file.is-dark.is-hovered .file-cta {
  background-color: #2f2f2f;
  border-color: transparent;
  color: #fff;
}

.file.is-dark:focus .file-cta, .file.is-dark.is-focused .file-cta {
  border-color: transparent;
  box-shadow: 0 0 0.5em rgba(54, 54, 54, 0.25);
  color: #fff;
}

.file.is-dark:active .file-cta, .file.is-dark.is-active .file-cta {
  background-color: #292929;
  border-color: transparent;
  color: #fff;
}

.file.is-primary .file-cta {
  background-color: #00d1b2;
  border-color: transparent;
  color: #fff;
}

.file.is-primary:hover .file-cta, .file.is-primary.is-hovered .file-cta {
  background-color: #00c4a7;
  border-color: transparent;
  color: #fff;
}

.file.is-primary:focus .file-cta, .file.is-primary.is-focused .file-cta {
  border-color: transparent;
  box-shadow: 0 0 0.5em rgba(0, 209, 178, 0.25);
  color: #fff;
}

.file.is-primary:active .file-cta, .file.is-primary.is-active .file-cta {
  background-color: #00b89c;
  border-color: transparent;
  color: #fff;
}

.file.is-link .file-cta {
  background-color: #485fc7;
  border-color: transparent;
  color: #fff;
}

.file.is-link:hover .file-cta, .file.is-link.is-hovered .file-cta {
  background-color: #3e56c4;
  border-color: transparent;
  color: #fff;
}

.file.is-link:focus .file-cta, .file.is-link.is-focused .file-cta {
  border-color: transparent;
  box-shadow: 0 0 0.5em rgba(72, 95, 199, 0.25);
  color: #fff;
}

.file.is-link:active .file-cta, .file.is-link.is-active .file-cta {
  background-color: #3a51bb;
  border-color: transparent;
  color: #fff;
}

.file.is-info .file-cta {
  background-color: #3e8ed0;
  border-color: transparent;
  color: #fff;
}

.file.is-info:hover .file-cta, .file.is-info.is-hovered .file-cta {
  background-color: #3488ce;
  border-color: transparent;
  color: #fff;
}

.file.is-info:focus .file-cta, .file.is-info.is-focused .file-cta {
  border-color: transparent;
  box-shadow: 0 0 0.5em rgba(62, 142, 208, 0.25);
  color: #fff;
}

.file.is-info:active .file-cta, .file.is-info.is-active .file-cta {
  background-color: #3082c5;
  border-color: transparent;
  color: #fff;
}

.file.is-success .file-cta {
  background-color: #48c78e;
  border-color: transparent;
  color: #fff;
}

.file.is-success:hover .file-cta, .file.is-success.is-hovered .file-cta {
  background-color: #3ec487;
  border-color: transparent;
  color: #fff;
}

.file.is-success:focus .file-cta, .file.is-success.is-focused .file-cta {
  border-color: transparent;
  box-shadow: 0 0 0.5em rgba(72, 199, 142, 0.25);
  color: #fff;
}

.file.is-success:active .file-cta, .file.is-success.is-active .file-cta {
  background-color: #3abb81;
  border-color: transparent;
  color: #fff;
}

.file.is-warning .file-cta {
  background-color: #ffe08a;
  border-color: transparent;
  color: rgba(0, 0, 0, 0.7);
}

.file.is-warning:hover .file-cta, .file.is-warning.is-hovered .file-cta {
  background-color: #ffdc7d;
  border-color: transparent;
  color: rgba(0, 0, 0, 0.7);
}

.file.is-warning:focus .file-cta, .file.is-warning.is-focused .file-cta {
  border-color: transparent;
  box-shadow: 0 0 0.5em rgba(255, 224, 138, 0.25);
  color: rgba(0, 0, 0, 0.7);
}

.file.is-warning:active .file-cta, .file.is-warning.is-active .file-cta {
  background-color: #ffd970;
  border-color: transparent;
  color: rgba(0, 0, 0, 0.7);
}

.file.is-danger .file-cta {
  background-color: #f14668;
  border-color: transparent;
  color: #fff;
}

.file.is-danger:hover .file-cta, .file.is-danger.is-hovered .file-cta {
  background-color: #f03a5f;
  border-color: transparent;
  color: #fff;
}

.file.is-danger:focus .file-cta, .file.is-danger.is-focused .file-cta {
  border-color: transparent;
  box-shadow: 0 0 0.5em rgba(241, 70, 104, 0.25);
  color: #fff;
}

.file.is-danger:active .file-cta, .file.is-danger.is-active .file-cta {
  background-color: #ef2e55;
  border-color: transparent;
  color: #fff;
}

.file.is-small {
  font-size: 0.75rem;
}

.file.is-normal {
  font-size: 1rem;
}

.file.is-medium {
  font-size: 1.25rem;
}

.file.is-medium .file-icon .fa {
  font-size: 21px;
}

.file.is-large {
  font-size: 1.5rem;
}

.file.is-large .file-icon .fa {
  font-size: 28px;
}

.file.has-name .file-cta {
  border-bottom-right-radius: 0;
  border-top-right-radius: 0;
}

.file.has-name .file-name {
  border-bottom-left-radius: 0;
  border-top-left-radius: 0;
}

.file.has-name.is-empty .file-cta {
  border-radius: 4px;
}

.file.has-name.is-empty .file-name {
  display: none;
}

.file.is-boxed .file-label {
  flex-direction: column;
}

.file.is-boxed .file-cta {
  flex-direction: column;
  height: auto;
  padding: 1em 3em;
}

.file.is-boxed .file-name {
  border-width: 0 1px 1px;
}

.file.is-boxed .file-icon {
  height: 1.5em;
  width: 1.5em;
}

.file.is-boxed .file-icon .fa {
  font-size: 21px;
}

.file.is-boxed.is-small .file-icon .fa {
  font-size: 14px;
}

.file.is-boxed.is-medium .file-icon .fa {
  font-size: 28px;
}

.file.is-boxed.is-large .file-icon .fa {
  font-size: 35px;
}

.file.is-boxed.has-name .file-cta {
  border-radius: 4px 4px 0 0;
}

.file.is-boxed.has-name .file-name {
  border-radius: 0 0 4px 4px;
  border-width: 0 1px 1px;
}

.file.is-centered {
  justify-content: center;
}

.file.is-fullwidth .file-label {
  width: 100%;
}

.file.is-fullwidth .file-name {
  flex-grow: 1;
  max-width: none;
}

.file.is-right {
  justify-content: flex-end;
}

.file.is-right .file-cta {
  border-radius: 0 4px 4px 0;
}

.file.is-right .file-name {
  border-radius: 4px 0 0 4px;
  border-width: 1px 0 1px 1px;
  order: -1;
}

.file-label {
  align-items: stretch;
  display: flex;
  cursor: pointer;
  justify-content: flex-start;
  overflow: hidden;
  position: relative;
}

.file-label:hover .file-cta {
  background-color: #eeeeee;
  color: #363636;
}

.file-label:hover .file-name {
  border-color: #d5d5d5;
}

.file-label:active .file-cta {
  background-color: #e8e8e8;
  color: #363636;
}

.file-label:active .file-name {
  border-color: #cfcfcf;
}

.file-input {
  height: 100%;
  left: 0;
  opacity: 0;
  outline: none;
  position: absolute;
  top: 0;
  width: 100%;
}

.file-cta,
.file-name {
  border-color: #dbdbdb;
  border-radius: 4px;
  font-size: 1em;
  padding-left: 1em;
  padding-right: 1em;
  white-space: nowrap;
}

.file-cta {
  background-color: whitesmoke;
  color: #4a4a4a;
}

.file-name {
  border-color: #dbdbdb;
  border-style: solid;
  border-width: 1px 1px 1px 0;
  display: block;
  max-width: 16em;
  overflow: hidden;
  text-align: inherit;
  text-overflow: ellipsis;
}

.file-icon {
  align-items: center;
  display: flex;
  height: 1em;
  justify-content: center;
  margin-right: 0.5em;
  width: 1em;
}

.file-icon .fa {
  font-size: 14px;
}

.label {
  color: #363636;
  display: block;
  font-size: 1rem;
  font-weight: 700;
}

.label:not(:last-child) {
  margin-bottom: 0.5em;
}

.label.is-small {
  font-size: 0.75rem;
}

.label.is-medium {
  font-size: 1.25rem;
}

.label.is-large {
  font-size: 1.5rem;
}

.help {
  display: block;
  font-size: 0.75rem;
  margin-top: 0.25rem;
}

.help.is-white {
  color: white;
}

.help.is-black {
  color: #0a0a0a;
}

.help.is-light {
  color: whitesmoke;
}

.help.is-dark {
  color: #363636;
}

.help.is-primary {
  color: #00d1b2;
}

.help.is-link {
  color: #485fc7;
}

.help.is-info {
  color: #3e8ed0;
}

.help.is-success {
  color: #48c78e;
}

.help.is-warning {
  color: #ffe08a;
}

.help.is-danger {
  color: #f14668;
}

.field:not(:last-child) {
  margin-bottom: 0.75rem;
}

.field.has-addons {
  display: flex;
  justify-content: flex-start;
}

.field.has-addons .control:not(:last-child) {
  margin-right: -1px;
}

.field.has-addons .control:not(:first-child):not(:last-child) .button,
.field.has-addons .control:not(:first-child):not(:last-child) .input,
.field.has-addons .control:not(:first-child):not(:last-child) .select select {
  border-radius: 0;
}

.field.has-addons .control:first-child:not(:only-child) .button,
.field.has-addons .control:first-child:not(:only-child) .input,
.field.has-addons .control:first-child:not(:only-child) .select select {
  border-bottom-right-radius: 0;
  border-top-right-radius: 0;
}

.field.has-addons .control:last-child:not(:only-child) .button,
.field.has-addons .control:last-child:not(:only-child) .input,
.field.has-addons .control:last-child:not(:only-child) .select select {
  border-bottom-left-radius: 0;
  border-top-left-radius: 0;
}

.field.has-addons .control .button:not([disabled]):hover, .field.has-addons .control .button:not([disabled]).is-hovered,
.field.has-addons .control .input:not([disabled]):hover,
.field.has-addons .control .input:not([disabled]).is-hovered,
.field.has-addons .control .select select:not([disabled]):hover,
.field.has-addons .control .select select:not([disabled]).is-hovered {
  z-index: 2;
}

.field.has-addons .control .button:not([disabled]):focus, .field.has-addons .control .button:not([disabled]).is-focused, .field.has-addons .control .button:not([disabled]):active, .field.has-addons .control .button:not([disabled]).is-active,
.field.has-addons .control .input:not([disabled]):focus,
.field.has-addons .control .input:not([disabled]).is-focused,
.field.has-addons .control .input:not([disabled]):active,
.field.has-addons .control .input:not([disabled]).is-active,
.field.has-addons .control .select select:not([disabled]):focus,
.field.has-addons .control .select select:not([disabled]).is-focused,
.field.has-addons .control .select select:not([disabled]):active,
.field.has-addons .control .select select:not([disabled]).is-active {
  z-index: 3;
}

.field.has-addons .control .button:not([disabled]):focus:hover, .field.has-addons .control .button:not([disabled]).is-focused:hover, .field.has-addons .control .button:not([disabled]):active:hover, .field.has-addons .control .button:not([disabled]).is-active:hover,
.field.has-addons .control .input:not([disabled]):focus:hover,
.field.has-addons .control .input:not([disabled]).is-focused:hover,
.field.has-addons .control .input:not([disabled]):active:hover,
.field.has-addons .control .input:not([disabled]).is-active:hover,
.field.has-addons .control .select select:not([disabled]):focus:hover,
.field.has-addons .control .select select:not([disabled]).is-focused:hover,
.field.has-addons .control .select select:not([disabled]):active:hover,
.field.has-addons .control .select select:not([disabled]).is-active:hover {
  z-index: 4;
}

.field.has-addons .control.is-expanded {
  flex-grow: 1;
  flex-shrink: 1;
}

.field.has-addons.has-addons-centered {
  justify-content: center;
}

.field.has-addons.has-addons-right {
  justify-content: flex-end;
}

.field.has-addons.has-addons-fullwidth .control {
  flex-grow: 1;
  flex-shrink: 0;
}

.field.is-grouped {
  display: flex;
  justify-content: flex-start;
}

.field.is-grouped > .control {
  flex-shrink: 0;
}

.field.is-grouped > .control:not(:last-child) {
  margin-bottom: 0;
  margin-right: 0.75rem;
}

.field.is-grouped > .control.is-expanded {
  flex-grow: 1;
  flex-shrink: 1;
}

.field.is-grouped.is-grouped-centered {
  justify-content: center;
}

.field.is-grouped.is-grouped-right {
  justify-content: flex-end;
}

.field.is-grouped.is-grouped-multiline {
  flex-wrap: wrap;
}

.field.is-grouped.is-grouped-multiline > .control:last-child, .field.is-grouped.is-grouped-multiline > .control:not(:last-child) {
  margin-bottom: 0.75rem;
}

.field.is-grouped.is-grouped-multiline:last-child {
  margin-bottom: -0.75rem;
}

.field.is-grouped.is-grouped-multiline:not(:last-child) {
  margin-bottom: 0;
}

@media screen and (min-width: 769px), print {
  .field.is-horizontal {
    display: flex;
  }
}

.field-label .label {
  font-size: inherit;
}

@media screen and (max-width: 768px) {
  .field-label {
    margin-bottom: 0.5rem;
  }
}

@media screen and (min-width: 769px), print {
  .field-label {
    flex-basis: 0;
    flex-grow: 1;
    flex-shrink: 0;
    margin-right: 1.5rem;
    text-align: right;
  }
  .field-label.is-small {
    font-size: 0.75rem;
    padding-top: 0.375em;
  }
  .field-label.is-normal {
    padding-top: 0.375em;
  }
  .field-label.is-medium {
    font-size: 1.25rem;
    padding-top: 0.375em;
  }
  .field-label.is-large {
    font-size: 1.5rem;
    padding-top: 0.375em;
  }
}

.field-body .field .field {
  margin-bottom: 0;
}

@media screen and (min-width: 769px), print {
  .field-body {
    display: flex;
    flex-basis: 0;
    flex-grow: 5;
    flex-shrink: 1;
  }
  .field-body .field {
    margin-bottom: 0;
  }
  .field-body > .field {
    flex-shrink: 1;
  }
  .field-body > .field:not(.is-narrow) {
    flex-grow: 1;
  }
  .field-body > .field:not(:last-child) {
    margin-right: 0.75rem;
  }
}

.control {
  box-sizing: border-box;
  clear: both;
  font-size: 1rem;
  position: relative;
  text-align: inherit;
}

.control.has-icons-left .input:focus ~ .icon,
.control.has-icons-left .select:focus ~ .icon, .control.has-icons-right .input:focus ~ .icon,
.control.has-icons-right .select:focus ~ .icon {
  color: #4a4a4a;
}

.control.has-icons-left .input.is-small ~ .icon,
.control.has-icons-left .select.is-small ~ .icon, .control.has-icons-right .input.is-small ~ .icon,
.control.has-icons-right .select.is-small ~ .icon {
  font-size: 0.75rem;
}

.control.has-icons-left .input.is-medium ~ .icon,
.control.has-icons-left .select.is-medium ~ .icon, .control.has-icons-right .input.is-medium ~ .icon,
.control.has-icons-right .select.is-medium ~ .icon {
  font-size: 1.25rem;
}

.control.has-icons-left .input.is-large ~ .icon,
.control.has-icons-left .select.is-large ~ .icon, .control.has-icons-right .input.is-large ~ .icon,
.control.has-icons-right .select.is-large ~ .icon {
  font-size: 1.5rem;
}

.control.has-icons-left .icon, .control.has-icons-right .icon {
  color: #dbdbdb;
  height: 2.5em;
  pointer-events: none;
  posi
Download .txt
gitextract__75ccm0y/

├── .gitignore
├── .gitmodules
├── License
├── Readme.md
├── cd_ws.py
├── doc/
│   └── shortcuts.md
├── html/
│   ├── calibration.js
│   ├── dev_cmd.js
│   ├── index.html
│   ├── index.js
│   ├── input_ctrl.js
│   ├── libs/
│   │   └── bulma-0.9.4.css
│   ├── pos_list.js
│   ├── preload_ctrl.js
│   └── utils/
│       ├── cd_ws.js
│       ├── helper.js
│       ├── idb.js
│       ├── lang.js
│       └── trans/
│           ├── zh_cn.js
│           └── zh_hk.js
├── pnp_cv.py
├── pnp_main.py
├── pnp_xyz.py
├── start.sh
├── tmp/
│   └── .gitkeep
├── tools/
│   ├── cdbus_gui_configs/
│   │   ├── cdcam_21.mpk
│   │   ├── cdcam_22.mpk
│   │   ├── cdpump_11.mpk
│   │   ├── cdpump_v2_11.mpk
│   │   ├── cdstep_01.mpk
│   │   ├── cdstep_02.mpk
│   │   ├── cdstep_03.mpk
│   │   ├── cdstep_04.mpk
│   │   └── cdstep_05.mpk
│   ├── csv_conv_ad.py
│   ├── csv_conv_allegro.py
│   ├── firmware/
│   │   ├── bootloader/
│   │   │   ├── cdcam_bl_v1_e11ff15.hex
│   │   │   ├── cdcam_bl_v2_v3_fb06e57.hex
│   │   │   ├── cdpump_bl_v1_476ead2.hex
│   │   │   ├── cdpump_bl_v2_6e45f85.hex
│   │   │   └── cdstep_bl_all_v3_ee1d4bc.hex
│   │   ├── cdcam_v1_e11ff15.hex
│   │   ├── cdcam_v2_v3_fb06e57.hex
│   │   ├── cdpump_v1_476ead2.hex
│   │   ├── cdpump_v2_6e45f85.hex
│   │   ├── cdstep_r_v3_72abac5.hex
│   │   ├── cdstep_v3_ee1d4bc.hex
│   │   └── cdstep_z_v3_efbb9a9.hex
│   ├── fw_cfg_update.py
│   └── pos.md
└── web_serve.py
Download .txt
SYMBOL INDEX (138 symbols across 15 files)

FILE: cd_ws.py
  class CDWebSocket (line 21) | class CDWebSocket():
    method __init__ (line 22) | def __init__(self, ns, port):
    method delete (line 29) | def delete(self):
    method sendto (line 33) | async def sendto(self, dat, s_addr):
    method recvfrom (line 44) | async def recvfrom(self, timeout=None):
  class CDWebSocketNS (line 49) | class CDWebSocketNS():
    method __init__ (line 50) | def __init__(self, addr, def_route=None):

FILE: html/dev_cmd.js
  function update_aux (line 13) | function update_aux() {
  function get_camera_cfg (line 22) | async function get_camera_cfg() {
  function set_camera_cfg (line 34) | async function set_camera_cfg(_detect=null, exposure=20) {
  function set_vision_cfg (line 46) | async function set_vision_cfg(debug=false) {
  function get_motor_pos (line 53) | async function get_motor_pos() {
  function set_motor_pos (line 63) | async function set_motor_pos(wait=0, speed=600000) {
  function get_pump_hw_ver (line 82) | async function get_pump_hw_ver() {
  function get_pump_pressure (line 90) | async function get_pump_pressure() {
  function check_suck_pressure (line 98) | async function check_suck_pressure(should_empty=false, threshold=-30) {
  function set_pump (line 116) | async function set_pump(val) {
  function pcb2xyz (line 125) | async function pcb2xyz(pcb, cam, x, y) {
  function z_keep_high (line 133) | async function z_keep_high(wait=0, speed=600000) {
  function enable_force (line 143) | async function enable_force() {
  function get_cv_cur (line 151) | async function get_cv_cur() {
  constant DIV_MM2PIXEL (line 160) | let DIV_MM2PIXEL = 10/344;
  function cam_comp_snap (line 162) | async function cam_comp_snap(times=3, max_err=0.02) {

FILE: html/index.js
  function rotate_vector (line 81) | function rotate_vector(angle, vector) {
  function cal_grab_ofs (line 90) | function cal_grab_ofs(angle, err=null) {
  function init_ws (line 445) | function init_ws() {

FILE: html/input_ctrl.js
  function disable_goto_btn (line 16) | function disable_goto_btn(val) {
  function auto_hide (line 23) | function auto_hide() {
  function csa_to_page_pos (line 53) | function csa_to_page_pos()
  function csa_to_page_input (line 63) | function csa_to_page_input()
  function csa_from_page_input (line 122) | function csa_from_page_input()
  function save_cfg (line 186) | async function save_cfg() {
  function input_change (line 195) | async function input_change() {
  function set_camera_en (line 327) | async function set_camera_en(enable) {
  function _set_camera_cfg (line 334) | async function _set_camera_cfg() {
  function set_camera_dev (line 337) | async function set_camera_dev() {
  function camera_update_bg (line 354) | async function camera_update_bg()
  function camera_remove_bg (line 361) | async function camera_remove_bg()
  function move_button (line 376) | async function move_button(val)
  function offset_apply (line 516) | function offset_apply() {
  function input_init (line 552) | function input_init() {

FILE: html/pos_list.js
  function get_comp_values (line 14) | function get_comp_values(comp)
  function search_comp_parents (line 38) | function search_comp_parents(comp, set_color=false, color='')
  function search_next_comp (line 91) | function search_next_comp(comp)
  function search_current_comp (line 111) | function search_current_comp()
  function search_first_comp (line 124) | function search_first_comp()
  function get_comp_progress (line 133) | function get_comp_progress(comp)
  function select_comp (line 150) | function select_comp(comp) {
  function move_to_comp (line 198) | async function move_to_comp(comp) {
  function pos_to_page (line 233) | function pos_to_page(pos) {
  function pos_from_page (line 289) | function pos_from_page() {
  function csv_to_pos (line 311) | function csv_to_pos(csv)
  function set_board (line 373) | function set_board(idx) {
  function get_board_safe (line 380) | function get_board_safe() {
  function set_step (line 389) | function set_step(idx) {
  function get_step_safe (line 395) | function get_step_safe() {
  function set_comp_search (line 404) | function set_comp_search(idx) {
  function get_comp_search (line 410) | function get_comp_search() {
  function get_comp_safe (line 419) | function get_comp_safe() {

FILE: html/preload_ctrl.js
  function pld_get_grid (line 14) | function pld_get_grid(idx)
  function pld_csa_to_page (line 38) | function pld_csa_to_page()
  function pld_csa_from_page (line 55) | function pld_csa_from_page()

FILE: html/utils/cd_ws.js
  class CDWebSocket (line 16) | class CDWebSocket {
    method constructor (line 17) | constructor(ns, port) {
    method delete_ (line 25) | delete_() {
    method sendto (line 29) | async sendto(dat, s_addr) {
    method recvfrom (line 37) | async recvfrom(timeout=null) {
    method flush (line 41) | flush() {
  class CDWebSocketNS (line 47) | class CDWebSocketNS {
    method constructor (line 48) | constructor(addr, def_route=null) {

FILE: html/utils/helper.js
  function sleep (line 7) | function sleep(ms) {
  function read_file (line 11) | async function read_file(file) {
  function load_img (line 23) | async function load_img(img, url) {
  function date2num (line 33) | function date2num() {
  function timestamp (line 39) | function timestamp() {
  function sha256 (line 45) | async function sha256(dat) {
  function aes256 (line 50) | async function aes256(dat, key, type='encrypt') {
  function dat2hex (line 60) | function dat2hex(dat, join='', le=false) {
  function hex2dat (line 67) | function hex2dat(hex, le=false) {
  function dat2str (line 75) | function dat2str(dat) {
  function str2dat (line 79) | function str2dat(str) {
  function val2hex (line 84) | function val2hex(val, fixed=4, prefix=false, upper=false, float=false) {
  function cpy (line 102) | function cpy(dst, src, list, map = {}) {
  class Queue (line 113) | class Queue {
    method constructor (line 114) | constructor() {
    method put (line 119) | put(t) {
    method get (line 125) | async get(timeout=null) {
    method size (line 147) | size() {
    method flush (line 150) | flush() {
  function download_url (line 158) | function download_url(data, fileName) {
  function download (line 169) | function download(data, fileName='dat.bin', mimeType='application/octet-...
  function escape_html (line 177) | function escape_html(unsafe) {
  function readable_size (line 186) | function readable_size(bytes, fixed=3, si=true) {
  function readable_float (line 202) | function readable_float(num, fixed=6, double=true) {
  function blob2dat (line 221) | async function blob2dat(blob) {
  function deep_merge (line 232) | function deep_merge(target, ...sources) {
  function csv_parser (line 247) | function csv_parser(str, delimiter ){
  function wildcard_test (line 274) | function wildcard_test(str, wildcard) {

FILE: html/utils/idb.js
  class Idb (line 7) | class Idb {
    method constructor (line 8) | constructor(db_name = 'cd', store_list = ['var', 'tmp']) {
    method trans (line 28) | trans(store_name, type) {
    method get (line 39) | async get(store_name, key) {
    method set (line 45) | async set(store_name, key, value) {
    method del (line 50) | async del(store_name, key) {
    method clear (line 55) | async clear(store_name) {
    method keys (line 60) | async keys(store_name) {
    method stores (line 67) | stores() {

FILE: html/utils/lang.js
  function L (line 26) | function L(ori, mark=null) {

FILE: pnp_cv.py
  function cv_get_pos (line 41) | def cv_get_pos(img, detect):
  function cv_get_circle (line 125) | def cv_get_circle(img, detect):
  function cv_get_pad (line 181) | def cv_get_pad(img, detect):
  function pic_rx (line 286) | def pic_rx():
  function pnp_cv_start (line 370) | def pnp_cv_start(detect='default', local=True):

FILE: pnp_main.py
  function equations (line 74) | def equations(p):
  function pcb2xyz (line 82) | def pcb2xyz(p, pcb):
  function update_coeff (line 88) | def update_coeff():
  function dev_service (line 96) | async def dev_service():
  function open_brower (line 207) | async def open_brower():
  function start_loop (line 215) | def start_loop():

FILE: pnp_xyz.py
  function dbg_echo (line 31) | def dbg_echo():
  function cd_info_rd (line 38) | def cd_info_rd(dev_addr, timeout=0.8, retry=3):
  function cd_reg_rw (line 53) | def cd_reg_rw(dev_addr, reg_addr, write=None, read=0, timeout=0.8, retry...
  function set_pump (line 74) | def set_pump(val):
  function get_pump_pressure (line 82) | def get_pump_pressure():
  function enable_motor (line 90) | def enable_motor():
  function load_pos (line 105) | def load_pos(mask=0xf):
  function wait_stop (line 151) | def wait_stop():
  function wait_finish (line 160) | def wait_finish(axis, wait):
  function enable_force (line 173) | def enable_force():
  function cal_accel (line 178) | def cal_accel(v):
  function goto_pos (line 185) | def goto_pos(pos, wait=0, s_speed=260000):
  function detect_origin (line 241) | def detect_origin():
  function xyz_init (line 276) | def xyz_init():

FILE: tools/fw_cfg_update.py
  function invoke_cmd_r (line 41) | def invoke_cmd_r(cmd):
  function invoke_cmd (line 48) | def invoke_cmd(cmd):

FILE: web_serve.py
  function http_file_server (line 22) | async def http_file_server(path, request):
  function ws_handler (line 51) | async def ws_handler(ws, path):
  function start_web (line 76) | async def start_web(addr='localhost', port=8900):
Condensed preview — 51 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (1,665K chars).
[
  {
    "path": ".gitignore",
    "chars": 501,
    "preview": "#\n# NOTE! Don't add files that are generated in specific\n# subdirectories here. Add them in the \".gitignore\" file\n# in t"
  },
  {
    "path": ".gitmodules",
    "chars": 56,
    "preview": "[submodule \"pycdnet\"]\n\tpath = pycdnet\n\turl = ../pycdnet\n"
  },
  {
    "path": "License",
    "chars": 1064,
    "preview": "MIT License\n\nCopyright (c) 2021 DUKELEC\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof"
  },
  {
    "path": "Readme.md",
    "chars": 3427,
    "preview": "CDPnP\n=======================================\n\nCDPnP is a compact, desktop, semi-automatic SMT prototyping machine.\n\nTra"
  },
  {
    "path": "cd_ws.py",
    "chars": 1565,
    "preview": "#!/usr/bin/env python3\n#\n# Software License Agreement (MIT License)\n#\n# Author: Duke Fong <d@d-l.io>\n#\n\n# Packet format:"
  },
  {
    "path": "doc/shortcuts.md",
    "chars": 348,
    "preview": "\n### CDPnP App Shortcuts\n\n```\n<-   : Move left     : Left arrow\n->   : Move right    : Right arrow\n^    : Move forward  "
  },
  {
    "path": "html/calibration.js",
    "chars": 25448,
    "preview": "/*\n * Software License Agreement (MIT License)\n *\n * Author: Duke Fong <d@d-l.io>\n */\n\nimport { L } from './utils/lang.j"
  },
  {
    "path": "html/dev_cmd.js",
    "chars": 7086,
    "preview": "/*\n * Software License Agreement (MIT License)\n *\n * Author: Duke Fong <d@d-l.io>\n */\n\nimport { L } from './utils/lang.j"
  },
  {
    "path": "html/index.html",
    "chars": 15420,
    "preview": "<!DOCTYPE html>\n<html>\n<head>\n<meta charset=\"utf-8\" />\n<meta name=\"viewport\" content=\"width=device-width, initial-scale="
  },
  {
    "path": "html/index.js",
    "chars": 19217,
    "preview": "/*\n * Software License Agreement (MIT License)\n *\n * Author: Duke Fong <d@d-l.io>\n */\n\nimport { L } from './utils/lang.j"
  },
  {
    "path": "html/input_ctrl.js",
    "chars": 23694,
    "preview": "/*\n * Software License Agreement (MIT License)\n *\n * Author: Duke Fong <d@d-l.io>\n */\n\nimport { L } from './utils/lang.j"
  },
  {
    "path": "html/libs/bulma-0.9.4.css",
    "chars": 245487,
    "preview": "/*! bulma.io v0.9.4 | MIT License | github.com/jgthms/bulma */\n/* Bulma Utilities */\n.button, .input, .textarea, .select"
  },
  {
    "path": "html/pos_list.js",
    "chars": 15608,
    "preview": "/*\n * Software License Agreement (MIT License)\n *\n * Author: Duke Fong <d@d-l.io>\n */\n\nimport { L } from './utils/lang.j"
  },
  {
    "path": "html/preload_ctrl.js",
    "chars": 8577,
    "preview": "/*\n * Software License Agreement (MIT License)\n *\n * Author: Duke Fong <d@d-l.io>\n */\n\nimport { L } from './utils/lang.j"
  },
  {
    "path": "html/utils/cd_ws.js",
    "chars": 1318,
    "preview": "/*\n * Software License Agreement (MIT License)\n *\n * Author: Duke Fong <d@d-l.io>\n */\n\nimport { Queue } from './helper.j"
  },
  {
    "path": "html/utils/helper.js",
    "chars": 8097,
    "preview": "/*\r\n * Software License Agreement (MIT License)\r\n *\r\n * Author: Duke Fong <d@d-l.io>\r\n */\r\n\r\nfunction sleep(ms) {\r\n    r"
  },
  {
    "path": "html/utils/idb.js",
    "chars": 2247,
    "preview": "/*\r\n * Software License Agreement (MIT License)\r\n *\r\n * Author: Duke Fong <d@d-l.io>\r\n */\r\n\r\nclass Idb {\r\n    constructo"
  },
  {
    "path": "html/utils/lang.js",
    "chars": 902,
    "preview": "/*\r\n * Software License Agreement (MIT License)\r\n *\r\n * Author: Duke Fong <d@d-l.io>\r\n */\r\n\r\nimport { trans_zh_hk }  fro"
  },
  {
    "path": "html/utils/trans/zh_cn.js",
    "chars": 3227,
    "preview": "/*\r\n * Copyright (c) 2019, Kudo, Inc.\r\n * All rights reserved.\r\n *\r\n * Author: Duke Fong <d@d-l.io>\r\n */\r\n\r\n// cat zh_hk"
  },
  {
    "path": "html/utils/trans/zh_hk.js",
    "chars": 3227,
    "preview": "/*\r\n * Copyright (c) 2019, Kudo, Inc.\r\n * All rights reserved.\r\n *\r\n * Author: Duke Fong <d@d-l.io>\r\n */\r\n\r\n// cat zh_cn"
  },
  {
    "path": "pnp_cv.py",
    "chars": 13612,
    "preview": "#!/usr/bin/env python3\n\n# This programs calculates the orientation of an object.\n# The input is an image, and the output"
  },
  {
    "path": "pnp_main.py",
    "chars": 8103,
    "preview": "#!/usr/bin/env python3\n# Software License Agreement (BSD License)\n#\n# Copyright (c) 2017, DUKELEC, Inc.\n# All rights res"
  },
  {
    "path": "pnp_xyz.py",
    "chars": 11722,
    "preview": "#!/usr/bin/env python3\n# Software License Agreement (BSD License)\n#\n# Copyright (c) 2017, DUKELEC, Inc.\n# All rights res"
  },
  {
    "path": "start.sh",
    "chars": 390,
    "preview": "#!/bin/bash\n\nwhich gnome-terminal 2> /dev/null && XTERM=gnome-terminal\nwhich xfce4-terminal 2> /dev/null && XTERM=xfce4-"
  },
  {
    "path": "tmp/.gitkeep",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "tools/csv_conv_ad.py",
    "chars": 2682,
    "preview": "#!/usr/bin/env python3\n# Software License Agreement (MIT License)\n#\n# Copyright (c) 2023, DUKELEC, Inc.\n# All rights res"
  },
  {
    "path": "tools/csv_conv_allegro.py",
    "chars": 2217,
    "preview": "#!/usr/bin/env python3\n# Software License Agreement (MIT License)\n#\n# Copyright (c) 2023, DUKELEC, Inc.\n# All rights res"
  },
  {
    "path": "tools/firmware/bootloader/cdcam_bl_v1_e11ff15.hex",
    "chars": 66343,
    "preview": ":020000040800F2\r\n:1000000000900020893D00082507000835070008FA\r\n:1000100000000000000000000000000000000000E0\r\n:100020000000"
  },
  {
    "path": "tools/firmware/bootloader/cdcam_bl_v2_v3_fb06e57.hex",
    "chars": 66343,
    "preview": ":020000040800F2\r\n:1000000000900020893D00082507000835070008FA\r\n:1000100000000000000000000000000000000000E0\r\n:100020000000"
  },
  {
    "path": "tools/firmware/bootloader/cdpump_bl_v1_476ead2.hex",
    "chars": 65512,
    "preview": ":020000040800F2\r\n:1000000000200020613C0008ED060008FD06000805\r\n:1000100000000000000000000000000000000000E0\r\n:100020000000"
  },
  {
    "path": "tools/firmware/bootloader/cdpump_bl_v2_6e45f85.hex",
    "chars": 66388,
    "preview": ":020000040800F2\r\n:1000000000900020993D00083507000845070008CA\r\n:1000100000000000000000000000000000000000E0\r\n:100020000000"
  },
  {
    "path": "tools/firmware/bootloader/cdstep_bl_all_v3_ee1d4bc.hex",
    "chars": 66388,
    "preview": ":020000040800F2\r\n:1000000000900020993D00083507000845070008CA\r\n:1000100000000000000000000000000000000000E0\r\n:100020000000"
  },
  {
    "path": "tools/firmware/cdcam_v1_e11ff15.hex",
    "chars": 93179,
    "preview": ":020000040800F2\r\n:10600000009000206DB60008C1680008D168000843\r\n:106010000000000000000000000000000000000080\r\n:106020000000"
  },
  {
    "path": "tools/firmware/cdcam_v2_v3_fb06e57.hex",
    "chars": 129315,
    "preview": ":020000040800F2\r\n:10600000009000206DC00008A5680008B568000871\r\n:106010000000000000000000000000000000000080\r\n:106020000000"
  },
  {
    "path": "tools/firmware/cdpump_v1_476ead2.hex",
    "chars": 81140,
    "preview": ":020000040800F2\r\n:106000000020002095A9000895670008A5670008F2\r\n:106010000000000000000000000000000000000080\r\n:106020000000"
  },
  {
    "path": "tools/firmware/cdpump_v2_6e45f85.hex",
    "chars": 115971,
    "preview": ":020000040800F2\r\n:10600000009000207DC30008A5690008B56900085C\r\n:106010000000000000000000000000000000000080\r\n:106020000000"
  },
  {
    "path": "tools/firmware/cdstep_r_v3_72abac5.hex",
    "chars": 136880,
    "preview": ":020000040800F2\r\n:10600000009000208DCB0008B16A0008C16A00082A\r\n:106010000000000000000000000000000000000080\r\n:106020000000"
  },
  {
    "path": "tools/firmware/cdstep_v3_ee1d4bc.hex",
    "chars": 134249,
    "preview": ":020000040800F2\r\n:106000000090002039C90008B16A0008C16A000880\r\n:106010000000000000000000000000000000000080\r\n:106020000000"
  },
  {
    "path": "tools/firmware/cdstep_z_v3_efbb9a9.hex",
    "chars": 136941,
    "preview": ":020000040800F2\r\n:106000000090002071CB0008B16A0008C16A000846\r\n:106010000000000000000000000000000000000080\r\n:106020000000"
  },
  {
    "path": "tools/fw_cfg_update.py",
    "chars": 3516,
    "preview": "#!/usr/bin/env python3\n# Software License Agreement (MIT License)\n#\n# Copyright (c) 2023, DUKELEC, Inc.\n# All rights res"
  },
  {
    "path": "tools/pos.md",
    "chars": 306,
    "preview": "\nkicad bottom layer:\n - fiducial pcb x * -1\n - enable `Use negative X coordinates for footprints on bottom layer` in `Ge"
  },
  {
    "path": "web_serve.py",
    "chars": 2453,
    "preview": "#!/usr/bin/env python3\n#\n# Software License Agreement (MIT License)\n#\n# Author: Duke Fong <d@d-l.io>\n#\n\nimport os\nimport"
  }
]

// ... and 9 more files (download for full content)

About this extraction

This page contains the full source code of the dukelec/cdpnp GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 51 files (1.5 MB), approximately 727.2k tokens, and a symbol index with 138 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!