Snippets Collections
// bad
var leds = stage.selectAll('.led').data(data).enter().append('svg:svg').classed('led', true)
    .attr('width', (radius + margin) * 2).append('svg:g')
    .attr('transform', 'translate(' + (radius + margin) + ',' + (radius + margin) + ')')
    .call(tron.led);

// good
var leds = stage.selectAll('.led')
    .data(data)
  .enter().append('svg:svg')
    .classed('led', true)
    .attr('width', (radius + margin) * 2)
  .append('svg:g')
    .attr('transform', 'translate(' + (radius + margin) + ',' + (radius + margin) + ')')
    .call(tron.led);
var $offCanvasNav = $('.mobile-menu'),
          $offCanvasNavSubMenu = $offCanvasNav.find('.dropdown');
      /*Add Toggle Button With Off Canvas Sub Menu*/
      $offCanvasNavSubMenu.parent().prepend('<span class="menu-expand"><i></i></span>');
      /*Close Off Canvas Sub Menu*/
      $offCanvasNavSubMenu.slideUp();
      /*Category Sub Menu Toggle*/
      $offCanvasNav.on('click', 'li a, li .menu-expand', function(e) {
        console.log('clicked:', e.target, e.currentTarget);
        var $currentTarget = $(e.currentTarget);
        var $this = $(this);
        if (($this.parent().attr('class').match(/\b(menu-item-has-children|has-children|has-sub-menu)\b/)) && ($this.attr('href') === '#' || $this.hasClass('menu-expand'))) {
          e.preventDefault();
          e.stopPropagation();
          if ($this.siblings('ul:visible').length) {
            $this.parent('li').removeClass('active');
            $this.siblings('ul').slideUp();
          } else {
            $this.parent('li').addClass('active');
            $this.closest('li').siblings('li').removeClass('active').find('li').removeClass('active');
            $this.closest('li').siblings('li').find('ul:visible').slideUp();
            $this.siblings('ul').slideDown();
          }
        }
      });
DECLARE
    /*  -------------------------------------------------------------------------------------------
        - L_JSON_RESPONSE:  Variável do tipo CLOB (Character Large Object) que armazenará a resposta JSON da API.
        - L_NEXT_URL:       Variável do tipo VARCHAR2 que armazena a URL da próxima página de resultados da API.
        - L_POKEMON_NAME:   Variável do tipo VARCHAR2 que não é utilizada diretamente no código, mas poderia ser usada para armazenar o nome do Pokémon se necessário.
        - ID:               Variável do tipo NUMBER usada para armazenar o ID do Pokémon extraído da URL.
        -------------------------------------------------------------------------------------------
    */
    L_JSON_RESPONSE CLOB;
    L_NEXT_URL VARCHAR2(4000);
    L_POKEMON_NAME VARCHAR2(100);
    ID  NUMBER;
BEGIN

    -- URL inicial da API para obter os Pokémon
    L_NEXT_URL := 'https://pokeapi.co/api/v2/pokemon/';

    -- Loop para continuar a buscar enquanto houver uma próxima página
    LOOP
        -- Faz a requisição à API
        L_JSON_RESPONSE := APEX_WEB_SERVICE.MAKE_REST_REQUEST(
            P_URL         => L_NEXT_URL,
            P_HTTP_METHOD => 'GET'
        );

        -- Se a resposta não for nula, processa a lista de Pokémon
        IF L_JSON_RESPONSE IS NOT NULL THEN
            -- Loop para processar os nomes dos Pokémon
            FOR C1 IN (
                SELECT JT.name, JT.url
                FROM JSON_TABLE(
                    L_JSON_RESPONSE,
                    '$.results[*]' COLUMNS (
                        name PATH '$.name',
                        url PATH '$.url'
                    )
                ) JT
            ) LOOP

                ID := REGEXP_SUBSTR(C1.url, '(\d+)(/|\?)$', 1, 1, NULL, 1);

                -- Filtrar somente a 1° geração
                IF ID <= 151 THEN

                    -- Insert na tabela base
                    INSERT INTO POKEMON (ID_POKEMON, NAME_POKEMON, URL_POKEMON)
        	        VALUES (ID, C1.name, C1.url);

                END IF;

            END LOOP;

            -- Obtém a URL da próxima página de resultados
            BEGIN
                SELECT JT.next
                INTO L_NEXT_URL
                FROM JSON_TABLE(
                    L_JSON_RESPONSE,
                    '$' COLUMNS (
                        next PATH '$.next'
                    )
                ) JT;

                -- Se a URL for null ou vazia, sai do loop
                IF L_NEXT_URL IS NULL OR L_NEXT_URL = '' OR ID > 151 THEN
                    EXIT;
                END IF;

            EXCEPTION
                WHEN NO_DATA_FOUND THEN
                    -- Se não houver o campo 'next', sai do loop
                    EXIT;
            END;
        ELSE
            DBMS_OUTPUT.PUT_LINE('Resposta vazia recebida.');
            EXIT; -- Se não houver resposta, sai do loop
        END IF;
    END LOOP;

EXCEPTION
    WHEN OTHERS THEN
        DBMS_OUTPUT.PUT_LINE('Erro ao processar a requisição: ' || SQLERRM);
END;
package com.til.tcc.manager.mongo.entity;

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonInclude.Include;
import com.til.tcc.manager.mongo.config.MongoCollections;
import lombok.*;
import org.springframework.data.mongodb.core.index.Indexed;
import org.springframework.data.mongodb.core.mapping.Document;

import java.util.HashMap;
import java.util.Map;
import java.util.Set;

@Data
@JsonInclude(value = Include.NON_NULL)
@Document(collection = MongoCollections.COLLECTION_AUFS_TRANSLATION)
@AllArgsConstructor
@NoArgsConstructor
public class AufsConfigTranslationEntity extends BaseMongoEntity {


    //Default Values
    public static final String IN = "IN";
    public static final String DARK = "dark";
    public static final String LIGHT = "light";
    public static final String INR = "INR";

    //Keys Query Node
    public static final String PC = "pc";
    public static final String PLATFORM = "pfm";
    public static final String KEY = "key";
    public static final String CC = "cc";
    public static final String LID = "langId";
    public static final String FV = "fv";
    public static final String THEME = "theme";
    public static final String IS_DEFAULT = "isDefault";
    public static final String ENABLE = "enable";
    public static final String DELETED = "deleted";
    public static final String ID = "_id";

    //Data Property
    @Indexed
    private String pc;

    @Indexed
    private String pfm;

    @Indexed
    private String client;

    @Indexed
    private String key;

    @Indexed
    private Set<String> cc;

    @Indexed
    private Set<Integer> lid;

    @Indexed
    private Integer fv;

    @Indexed
    private Set<String> theme;

    @Indexed
    private Boolean isDefault;

    @Indexed
    protected Boolean enable = false;

    @Indexed
    protected Boolean deleted = false;

    private Map<String, Object> rootTrans;

    public void setRootTrans(Map<String, Object> rootTrans) {
        if (rootTrans != null) {
            this.rootTrans = new HashMap<>();
            rootTrans.forEach((key, value) -> {
                // Convert dot notation to nested maps
                if (key.contains(".")) {
                    createNestedStructure(key, value);
                } else {
                    this.rootTrans.put(key, value);
                }
            });
        } else {
            this.rootTrans = null;
        }
    }

    /**
     * Converts dot-notated keys into proper nested structure
     * Example: "test1.test2" -> {test1: {test2: value}}
     */
    private void createNestedStructure(String keyPath, Object value) {
        String[] parts = keyPath.split("\\.");
        Map<String, Object> current = this.rootTrans;

        for (int i = 0; i < parts.length - 1; i++) {
            current = (Map<String, Object>) current.computeIfAbsent(parts[i], k -> new HashMap<>());
        }

        current.put(parts[parts.length - 1], value);
    }

    /**
     * Gets a value by dot-notated path
     */
    public Object getByPath(String path) {
        if (rootTrans == null || path == null) return null;

        String[] parts = path.split("\\.");
        Object current = rootTrans;

        for (String part : parts) {
            if (!(current instanceof Map)) return null;
            current = ((Map<?, ?>) current).get(part);
        }

        return current;
    }

    /**
     * Removes a value by dot-notated path
     */
    public void removeByPath(String path) {
        if (rootTrans == null || path == null) return;

        String[] parts = path.split("\\.");
        Map<String, Object> current = rootTrans;

        for (int i = 0; i < parts.length - 1; i++) {
            if (!(current.get(parts[i]) instanceof Map)) return;
            current = (Map<String, Object>) current.get(parts[i]);
        }
        current.remove(parts[parts.length - 1]);
    }

}
package com.til.tcc.manager.mongo.service;


import com.til.tcc.manager.constants.Constants;
import com.til.tcc.manager.mongo.entity.AufsConfigTranslationEntity;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Sort;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.Update;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

@Service
@Slf4j
public class AufsConfigTranslationService {

    @Autowired
    private BaseMongoService baseMongoService;

    @Autowired
    private MongoTemplate mongoTemplate;

    public Map<String, Map<Integer, List<AufsConfigTranslationEntity>>> getTranslationConfigs(String pc) {
        if (pc == null) {
            throw new IllegalArgumentException("Parameter 'pc' cannot be null");
        }
        try {
            if(StringUtils.equalsIgnoreCase(pc, "eis")){
                pc = Constants.pc.es.name();
            }else if (StringUtils.equalsIgnoreCase(pc, "tml")){
                pc = Constants.pc.tamilsamayam.name();
            }else if (StringUtils.equalsIgnoreCase(pc, "mly")){
                pc = Constants.pc.malayalamsamayam.name();
            }else if (StringUtils.equalsIgnoreCase(pc, "tlg")){
                pc = Constants.pc.telugusamayam.name();
            }
            Query query = new Query();
            query.addCriteria(Criteria.where(AufsConfigTranslationEntity.PC).is(pc));
            query.addCriteria(Criteria.where(AufsConfigTranslationEntity.ENABLE).is(true));
            List<AufsConfigTranslationEntity> list = baseMongoService.find(query, AufsConfigTranslationEntity.class);
            list.forEach(x -> x.setRootTrans(null));
            return list.stream()
                    .collect(Collectors.groupingBy(AufsConfigTranslationEntity::getPfm,
                            Collectors.groupingBy(AufsConfigTranslationEntity::getFv, Collectors.toList())));
        } catch (Exception e) {
            log.error("Exception occurred for pc: {} ", pc, e);
            throw new RuntimeException("Failed to retrieve translation configs", e);
        }
    }

    public AufsConfigTranslationEntity getById(String id) {
        try {
            Query query = new Query();
            query.addCriteria(Criteria.where(AufsConfigTranslationEntity.ID).is(id));
            return baseMongoService.findOne(query, AufsConfigTranslationEntity.class);
        } catch (Exception e) {
            log.error("Exception occurred for {} ", id, e);
            throw new RuntimeException("Failed to retrieve translation config", e);
        }
    }

    public int updateById(String id, AufsConfigTranslationEntity updatedEntity) {
        try {
            Query query = new Query(Criteria.where("_id").is(id));
            Update update = new Update();
            if(StringUtils.isNotBlank(updatedEntity.getPfm())){
                update.set("pfm", updatedEntity.getPfm());
            }
            if(StringUtils.isNotBlank(updatedEntity.getPc())){
                update.set("pc", updatedEntity.getPc());
            }
            if(Objects.nonNull(updatedEntity.getFv())){
                update.set("fv", updatedEntity.getFv());
            }
            if(Objects.nonNull(updatedEntity.getEnable())){
                update.set("enable", updatedEntity.getEnable());
            }
            if(updatedEntity.getRootTrans() != null){
                update.set("rootTrans", updatedEntity.getRootTrans());
            }
            return baseMongoService.updateOne(query, update, AufsConfigTranslationEntity.class);

        } catch (Exception e) {
            log.error("Exception occurred for {} ", id, e);
            throw new RuntimeException("Failed to retrieve translation config", e);
        }
    }

    public void updateTranslations(String client, String pfm, Integer fv,
                                   Map<String, Object> updates, Map<String, Object> removals) {

        // 1. Get target feed version (use specified or latest)
        Integer targetFv = (fv != null) ? fv : getLatestFv(client, pfm);
        if (targetFv == null) {
            throw new RuntimeException("No translations found for client: " + client + ", platform: " + pfm);
        }

        // 2. Update the specified document
        updateSpecificDocument(client, pfm, targetFv, updates, removals);

        // 3. Update other language documents (pc ≠ "toi")
        updateOtherLanguageDocuments(client, pfm, updates, removals);
    }

    private void updateSpecificDocument(String client, String pfm, Integer fv,
                                        Map<String, Object> updates, Map<String, Object> removals) {
        Query query = Query.query(Criteria.where("client").is(client)
                .and("pfm").is(pfm)
                .and("fv").is(fv));

        AufsConfigTranslationEntity doc = mongoTemplate.findOne(query, AufsConfigTranslationEntity.class);
        if (doc == null) {
            doc = new AufsConfigTranslationEntity();
            doc.setClient(client);
            doc.setPfm(pfm);
            doc.setFv(fv);
            doc.setRootTrans(new HashMap<>());
        }

        Map<String, Object> rootTrans = doc.getRootTrans() != null
                ? new HashMap<>(doc.getRootTrans())
                : new HashMap<>();

        // Apply updates and removals with deep merge
        applyUpdatesAndRemovals(rootTrans, updates, removals);

        doc.setRootTrans(rootTrans);
        mongoTemplate.save(doc);
    }

    private void updateOtherLanguageDocuments(String client, String pfm,
                                              Map<String, Object> updates, Map<String, Object> removals) {
        Query query = new Query(Criteria.where("client").is(client)
                .and("pfm").is(pfm)
                .and("pc").ne("toi"));
        query.with(new Sort(Sort.Direction.DESC, "fv"));
        List<AufsConfigTranslationEntity> allLangDocs = mongoTemplate.find(query, AufsConfigTranslationEntity.class);

        // Keep only latest fv per unique pc
        Map<String, AufsConfigTranslationEntity> latestPerPc = allLangDocs.stream()
                .collect(Collectors.toMap(
                        AufsConfigTranslationEntity::getPc,
                        Function.identity(),
                        (doc1, doc2) -> doc1.getFv() >= doc2.getFv() ? doc1 : doc2
                ));

        int updateCount = 0;
        for (AufsConfigTranslationEntity doc : latestPerPc.values()) {
            Map<String, Object> rootTrans = doc.getRootTrans() != null
                    ? new HashMap<>(doc.getRootTrans())
                    : new HashMap<>();

            // Apply updates and removals with deep merge
            applyUpdatesAndRemovals(rootTrans, updates, removals);

            doc.setRootTrans(rootTrans);
            mongoTemplate.save(doc);
            updateCount++;
        }

        System.out.println("Updated " + updateCount + " other language documents.");
    }

    private Integer getLatestFv(String client, String pfm) {
        Query query = new Query(Criteria.where("client").is(client)
                .and("pfm").is(pfm))
                .with(new Sort(Sort.Direction.DESC, "fv"))
                .limit(1);
        AufsConfigTranslationEntity doc = mongoTemplate.findOne(query, AufsConfigTranslationEntity.class);
        return doc != null ? doc.getFv() : null;
    }

     //Apply updates and then apply removals to the root translation map with deep merge support
     @SuppressWarnings("unchecked")
    private void applyUpdatesAndRemovals(Map<String, Object> rootTrans,
                                         Map<String, Object> updates,
                                         Map<String, Object> removals) {
        // Apply updates with deep merge
        if (updates != null && !updates.isEmpty()) {
            // First unflatten dotted keys into a nested structure
            Map<String, Object> structuredUpdates = unflattenUpdates(updates);
            // Then merge with existing data
            deepMerge(rootTrans, structuredUpdates);
        }

        // Apply removals
        if (removals != null && !removals.isEmpty()) {
            for (Map.Entry<String, Object> entry : removals.entrySet()) {
                String key = entry.getKey();
                Object value = entry.getValue();

                // Handle removal based on key structure
                if (key.contains(".")) {
                    // For dot notation keys, handle nested removal
                    removeNestedMapValue(rootTrans, key, value);
                } else {
                    // For top-level keys
                    if (value == null) {
                        // Remove entire key if value is null
                        rootTrans.remove(key);
                    } else if (value instanceof Map) {
                        // Handle nested map removal
                        handleNestedMapRemoval(rootTrans, key, (Map<String, Object>) value);
                    } else {
                        // Remove key only if value matches exactly
                        Object currentValue = rootTrans.get(key);
                        if (currentValue != null && currentValue.equals(value)) {
                            rootTrans.remove(key);
                        }
                    }
                }
            }
        }
    }


     //Convert dotted notation keys to nested structure
     @SuppressWarnings("unchecked")
    private Map<String, Object> unflattenUpdates(Map<String, Object> updates) {
        Map<String, Object> result = new HashMap<>();

        for (Map.Entry<String, Object> entry : updates.entrySet()) {
            String key = entry.getKey();
            Object value = entry.getValue();

            if (key.contains(".")) {
                String[] parts = key.split("\\.");
                Map<String, Object> current = result;

                // Navigate to the correct level
                for (int i = 0; i < parts.length - 1; i++) {
                    if (!current.containsKey(parts[i])) {
                        current.put(parts[i], new HashMap<>());
                    }
                    Object next = current.get(parts[i]);
                    if (!(next instanceof Map)) {
                        // Override if not a map
                        Map<String, Object> newMap = new HashMap<>();
                        current.put(parts[i], newMap);
                        current = newMap;
                    } else {
                        current = (Map<String, Object>) next;
                    }
                }

                // Set the value at the deepest level
                current.put(parts[parts.length - 1], value);
            } else {
                // Handle top-level keys normally
                result.put(key, value);
            }
        }

        return result;
    }

     //Deep merge two maps - preserves existing values when not explicitly overwritten
    @SuppressWarnings("unchecked")
    private void deepMerge(Map<String, Object> target, Map<String, Object> source) {
        if (source == null) {
            return;
        }

        for (Map.Entry<String, Object> entry : source.entrySet()) {
            String key = entry.getKey();
            Object value = entry.getValue();

            // If both have a map for the same key, merge them
            if (value instanceof Map && target.containsKey(key) && target.get(key) instanceof Map) {
                deepMerge((Map<String, Object>) target.get(key), (Map<String, Object>) value);
            } else {
                // Otherwise just overwrite/add the value
                target.put(key, value);
            }
        }
    }

     //Handle removal of nested values in a map structure
    @SuppressWarnings("unchecked")
    private void handleNestedMapRemoval(Map<String, Object> rootMap, String key, Map<String, Object> valueToRemove) {
        // If the key doesn't exist in root map, nothing to remove
        if (!rootMap.containsKey(key)) {
            return;
        }

        Object currentValue = rootMap.get(key);

        // If current value is not a map, but removal specifies a map, something is wrong
        if (!(currentValue instanceof Map)) {
            return;
        }

        Map<String, Object> currentMap = (Map<String, Object>) currentValue;

        // If removal map is empty, remove the entire key
        if (valueToRemove.isEmpty()) {
            rootMap.remove(key);
            return;
        }

        // For each entry in the removal map, apply removal logic
        for (Map.Entry<String, Object> entry : valueToRemove.entrySet()) {
            String nestedKey = entry.getKey();
            Object nestedValue = entry.getValue();

            if (!currentMap.containsKey(nestedKey)) {
                continue;
            }

            Object existingNestedValue = currentMap.get(nestedKey);

            if (nestedValue == null) {
                // Remove entire nested key if value is null
                currentMap.remove(nestedKey);
            } else if (nestedValue instanceof Map && existingNestedValue instanceof Map) {
                // Recursively handle nested map removal
                handleNestedMapRemoval(currentMap, nestedKey, (Map<String, Object>) nestedValue);
                // If the nested map is now empty, remove it
                if (((Map<?, ?>) existingNestedValue).isEmpty()) {
                    currentMap.remove(nestedKey);
                }
            } else if (existingNestedValue != null && existingNestedValue.equals(nestedValue)) {
                // Remove if values match exactly
                currentMap.remove(nestedKey);
            }
        }

        // If the map is now empty after removals, remove the parent key
        if (currentMap.isEmpty()) {
            rootMap.remove(key);
        }
    }

    //Remove a value from a nested map structure using dot notation
    @SuppressWarnings("unchecked")
    private void removeNestedMapValue(Map<String, Object> rootMap, String dottedKey, Object valueToRemove) {
        String[] keyParts = dottedKey.split("\\.");

        // Navigate to the parent map that contains the final key
        Map<String, Object> currentMap = rootMap;
        for (int i = 0; i < keyParts.length - 1; i++) {
            Object value = currentMap.get(keyParts[i]);
            if (!(value instanceof Map)) {
                // Path doesn't exist, nothing to remove
                return;
            }
            currentMap = (Map<String, Object>) value;
        }

        String finalKey = keyParts[keyParts.length - 1];

        // Handle removal based on value type
        if (!currentMap.containsKey(finalKey)) {
            return;
        }

        Object currentValue = currentMap.get(finalKey);

        if (valueToRemove == null) {
            // Remove entire key if value to remove is null
            currentMap.remove(finalKey);
        } else if (valueToRemove instanceof Map && currentValue instanceof Map) {
            // Handle nested map removal
            handleNestedMapRemoval(currentMap, finalKey, (Map<String, Object>) valueToRemove);
        } else if (currentValue != null && currentValue.equals(valueToRemove)) {
            // Remove if values match exactly
            currentMap.remove(finalKey);
        }

        // Check if parent maps should be removed (if they're empty)
        if (keyParts.length > 1 && currentMap.isEmpty()) {
            // Remove empty parent maps recursively
            removeEmptyParentMaps(rootMap, keyParts, 0, keyParts.length - 1);
        }
    }

    //Recursively remove empty parent maps
    @SuppressWarnings("unchecked")
    private void removeEmptyParentMaps(Map<String, Object> rootMap, String[] keyParts, int startIdx, int endIdx) {
        if (startIdx >= endIdx) {
            return;
        }

        // Build the path to the current map
        Map<String, Object> currentMap = rootMap;
        for (int i = 0; i < endIdx - 1; i++) {
            Object value = currentMap.get(keyParts[i]);
            if (!(value instanceof Map)) {
                return;
            }
            currentMap = (Map<String, Object>) value;
        }

        // Check if the map at the path is empty
        Object value = currentMap.get(keyParts[endIdx - 1]);
        if (value instanceof Map && ((Map<?, ?>) value).isEmpty()) {
            currentMap.remove(keyParts[endIdx - 1]);
            //Recursively check parent
            removeEmptyParentMaps(rootMap, keyParts, startIdx, endIdx - 1);
        }
    }
}


Mira Yogashala offers transformative yoga teacher training programs in two breathtaking locations—Rishikesh, India, and Bali, Indonesia. Our **100 Hour Yoga Teacher Training in Rishikesh** is perfect for those looking to build a strong foundation in yoga while immersing themselves in the serene beauty of the Himalayas. Whether you seek a peaceful retreat in Rishikesh or a tropical escape in Bali, our courses provide a deep and immersive experience designed to help you grow in your practice and teaching journey.  

Nestled in the foothills of the Himalayas, along the sacred Ganges River, Rishikesh is known as the Yoga Capital of the World—a perfect destination for spiritual growth and self-discovery. Students train in Hatha, Ashtanga, and Vinyasa yoga, along with breathwork, meditation, yoga philosophy, anatomy, and teaching methods. Our experienced instructors, certified by Yoga Alliance, provide personal guidance to help you build confidence and deepen your practice.  

Beyond the classes, you can enjoy temple visits, meditation by the river, and nature walks, making the experience even more special. The fresh air and nourishing food help you refresh your mind and body. If you want a longer, more in-depth training, our 200 Hour Yoga Teacher Training in Bali offers a peaceful and immersive environment where you can explore yoga at a deeper level.  

Whether you choose Rishikesh or Bali, Mira Yogashala provides a nurturing and supportive environment where students can explore, grow, and embrace the true essence of yoga. Our programs are designed to offer authentic teachings, personal attention, and a deep connection to yoga’s spiritual roots.  

Join us for a life-changing experience and embark on a journey of self-discovery, balance, and inner peace with Mira Yogashala.  
For more information visit : https://www.mirayogashala.com/100-hour-yoga-teacher-training-in-rishikesh.php
# Remove DPKG config files for the package
# Replace {package_name} with the name of the package
sudo rm -rf /var/lib/dpkg/info/{package_name}.*

# Remove the package
sudo dpkg --remove --force-remove-reinstreq {package_name}


int findKthLargest(vector<int>& A, int k) {
    nth_element(begin(A), begin(A) + k - 1, end(A), greater<int>());
    return A[k - 1];
}
With the crypto industry evolving rapidly, many startups and entrepreneurs are exploring new ways to enter the market without building products from scratch. One approach that’s gaining popularity is  clone script—but is it really a smart business move?

Honestly, yes—if you approach it the right way.

It lets you launch your own branded crypto wallet app without the massive time or money investment required to build from scratch. You’re essentially starting with a solid foundation that’s already been tested and proven to work.

A good Trust Wallet clone script typically comes with packed features like:

Multi-coin and multi-token support
WalletConnect integration
Built-in staking options
DApp browser support
Private key and seed phrase backup
Biometric authentication (Face ID/Fingerprint)
In-app crypto swaps
Push notifications for transactions
Cross-platform support (Android & iOS)

And the best part? You get a proven product with all the necessary features already built in.

However, not all clone scripts are created equal. Some providers offer flashy websites and promises but fall short when it comes to security, scalability, or real-world functionality. I spent quite a bit of time reviewing different providers, trying out their live demos, reading through client feedback, and asking the right questions.

A standout provider for me was AppcloneX. With over three years in the blockchain industry and 50+ successful projects under their belt, they really knew their stuff. Their Trust Wallet clone script was feature-packed, secure, and highly customizable. On top of that, their post-launch support was exceptional—they walked me through every step, from customization to deployment.

In short, Trust Wallet clone script can be a brilliant business strategy, but only if you partner with the right team. For me approaching AppcloneX is one of the best decision. 

If you’re thinking about this approach or looking to launch your own trust wallet clone script, then you can reach out to them and checking out their live demos.
How Secure Is a Trust Wallet Clone Script?

One of the first questions I had when I started looking into a Trust Wallet clone script was about security. I mean, we’re talking about people’s crypto assets—security isn’t just important, it’s everything.

Naturally, the original Trust Wallet is open-source and has a strong track record in terms of safety. It follows all the best practices—end-to-end encryption, private key control, biometric protection, and seamless integration with DApps. So I wanted to make sure that any clone script I chose would offer the same level of protection.
During my research, I looked into several providers and tested different demo versions. What I quickly learned is that the quality of clone scripts varies widely. Some lacked proper encryption, while others didn’t offer any real security audits or wallet recovery systems.

One provider that really gave me confidence was AppcloneX. Their Trust Wallet clone script was built with security in mind. It included AES encryption, secure private key storage, biometric login, seed phrase backups, and even optional multi-factor authentication. They also emphasized using best practices for both frontend and backend security.

What reassured me even more was that their team took the time to explain how each feature worked and how they could customize or enhance it further based on my needs. That kind of transparency is rare.
To be clear: a Trust Wallet clone can be just as secure as the original—but only if it's developed by a team that prioritizes safety and follows industry standards.

If you’re thinking of going down this path, don’t just look at features. Ask the tough questions about security architecture, audits, and encryption methods. For me, AppcloneX stood out by getting those things right.

If you’re curious to see what their solution actually looks like, I’d recommend checking out their live demo. It gave me a clear picture of the product’s quality and features, and honestly—it helped me make my decision a lot faster.
DashBlox has a lot of features that aren't enabled by default, and if you want to customize the way DashBlox modifies Roblox, you can customize it in DashBlox settings.

If you don't want to customize settings right now, you can always do it later by pressing the gear icon in the top right and selecting 'DashBlox".
document.querySelectorAll('input, textarea').forEach((el) => {
      // Remove inline oncopy and onpaste handlers
        console.log(el)
      el.oncopy = null;
      el.onpaste = null;

      // Or remove attribute values if needed
      el.removeAttribute('oncopy');
      el.removeAttribute('onpaste');
    });
  
import tkinter as tk
from tkinter import ttk, messagebox
from datetime import datetime, timedelta
from tkcalendar import DateEntry

class BookingManagement(ttk.Frame):
    def __init__(self, parent, db):
        super().__init__(parent)
        self.db = db
        self.pack(fill=tk.BOTH, expand=True)
        
        # Create UI elements
        self._create_widgets()
        
        # Populate booking list
        self._populate_booking_list()
        
    def _create_widgets(self):
        """Create UI widgets for booking management"""
        # Title
        title_label = ttk.Label(self, text="Booking Management", font=("Arial", 14, "bold"))
        title_label.grid(row=0, column=0, columnspan=2, pady=10, sticky=tk.W)
        
        # Main content split into two frames
        left_frame = ttk.Frame(self)
        left_frame.grid(row=1, column=0, sticky=tk.NSEW, padx=(0, 10))
        
        right_frame = ttk.Frame(self)
        right_frame.grid(row=1, column=1, sticky=tk.NSEW)
        
        self.columnconfigure(0, weight=1)
        self.columnconfigure(1, weight=1)
        self.rowconfigure(1, weight=1)
        
        # === Left Frame: Booking List ===
        list_label = ttk.Label(left_frame, text="Current Bookings", font=("Arial", 12, "bold"))
        list_label.pack(anchor=tk.W, pady=(0, 10))
        
        # Booking list with scrollbar
        list_frame = ttk.Frame(left_frame)
        list_frame.pack(fill=tk.BOTH, expand=True)
        
        self.booking_tree = ttk.Treeview(list_frame, 
                                      columns=("Guest", "Room", "Check In", "Check Out", "Status"), 
                                      selectmode="browse", show="headings")
        self.booking_tree.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
        
        # Configure columns
        self.booking_tree.heading("Guest", text="Guest")
        self.booking_tree.heading("Room", text="Room")
        self.booking_tree.heading("Check In", text="Check In")
        self.booking_tree.heading("Check Out", text="Check Out")
        self.booking_tree.heading("Status", text="Status")
        
        self.booking_tree.column("Guest", width=150)
        self.booking_tree.column("Room", width=70)
        self.booking_tree.column("Check In", width=100)
        self.booking_tree.column("Check Out", width=100)
        self.booking_tree.column("Status", width=100)
        
        # Add scrollbar
        scrollbar = ttk.Scrollbar(list_frame, orient=tk.VERTICAL, command=self.booking_tree.yview)
        scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
        self.booking_tree.configure(yscrollcommand=scrollbar.set)
        
        # Bind selection event
        self.booking_tree.bind("<<TreeviewSelect>>", self._on_booking_select)
        
        # Filter controls
        filter_frame = ttk.Frame(left_frame)
        filter_frame.pack(fill=tk.X, pady=10)
        
        ttk.Label(filter_frame, text="Status Filter:").pack(side=tk.LEFT, padx=5)
        self.filter_var = tk.StringVar()
        status_cb = ttk.Combobox(filter_frame, textvariable=self.filter_var, width=15)
        status_cb['values'] = ("All", "Confirmed", "Checked In", "Checked Out", "Cancelled")
        status_cb.current(0)
        status_cb.pack(side=tk.LEFT, padx=5)
        status_cb.bind("<<ComboboxSelected>>", self._apply_filter)
        
        refresh_btn = ttk.Button(filter_frame, text="Refresh", command=self._populate_booking_list)
        refresh_btn.pack(side=tk.RIGHT, padx=5)
        
        # === Right Frame: Booking Details & Actions ===
        # Right frame has two sections: Details and New Booking
        notebook = ttk.Notebook(right_frame)
        notebook.pack(fill=tk.BOTH, expand=True)
        
        # First tab: Booking Details
        details_frame = ttk.Frame(notebook)
        notebook.add(details_frame, text="Booking Details")
        
        # Second tab: New Booking
        new_booking_frame = ttk.Frame(notebook)
        notebook.add(new_booking_frame, text="New Booking")
        
        # === Details Frame ===
        # Selected booking details
        info_frame = ttk.LabelFrame(details_frame, text="Booking Information")
        info_frame.pack(fill=tk.X, padx=5, pady=10)
        
        # Guest info
        ttk.Label(info_frame, text="Guest:").grid(row=0, column=0, sticky=tk.W, padx=5, pady=2)
        self.guest_label = ttk.Label(info_frame, text="-")
        self.guest_label.grid(row=0, column=1, sticky=tk.W, padx=5, pady=2)
        
        # Room info
        ttk.Label(info_frame, text="Room:").grid(row=1, column=0, sticky=tk.W, padx=5, pady=2)
        self.room_label = ttk.Label(info_frame, text="-")
        self.room_label.grid(row=1, column=1, sticky=tk.W, padx=5, pady=2)
        
        # Check-in date
        ttk.Label(info_frame, text="Check-in:").grid(row=2, column=0, sticky=tk.W, padx=5, pady=2)
        self.check_in_label = ttk.Label(info_frame, text="-")
        self.check_in_label.grid(row=2, column=1, sticky=tk.W, padx=5, pady=2)
        
        # Check-out date
        ttk.Label(info_frame, text="Check-out:").grid(row=3, column=0, sticky=tk.W, padx=5, pady=2)
        self.check_out_label = ttk.Label(info_frame, text="-")
        self.check_out_label.grid(row=3, column=1, sticky=tk.W, padx=5, pady=2)
        
        # Total amount
        ttk.Label(info_frame, text="Total Amount:").grid(row=4, column=0, sticky=tk.W, padx=5, pady=2)
        self.amount_label = ttk.Label(info_frame, text="-")
        self.amount_label.grid(row=4, column=1, sticky=tk.W, padx=5, pady=2)
        
        # Status
        ttk.Label(info_frame, text="Status:").grid(row=5, column=0, sticky=tk.W, padx=5, pady=2)
        self.status_label = ttk.Label(info_frame, text="-")
        self.status_label.grid(row=5, column=1, sticky=tk.W, padx=5, pady=2)
        
        # Booking date
        ttk.Label(info_frame, text="Booking Date:").grid(row=6, column=0, sticky=tk.W, padx=5, pady=2)
        self.booking_date_label = ttk.Label(info_frame, text="-")
        self.booking_date_label.grid(row=6, column=1, sticky=tk.W, padx=5, pady=2)
        
        # Configure columns
        info_frame.columnconfigure(1, weight=1)
        
        # Action buttons
        action_frame = ttk.Frame(details_frame)
        action_frame.pack(fill=tk.X, padx=5, pady=10)
        
        self.checkin_btn = ttk.Button(action_frame, text="Check In", command=self._check_in, state=tk.DISABLED)
        self.checkin_btn.pack(side=tk.LEFT, padx=5)
        
        self.checkout_btn = ttk.Button(action_frame, text="Check Out", command=self._check_out, state=tk.DISABLED)
        self.checkout_btn.pack(side=tk.LEFT, padx=5)
        
        self.cancel_btn = ttk.Button(action_frame, text="Cancel Booking", command=self._cancel_booking, state=tk.DISABLED)
        self.cancel_btn.pack(side=tk.LEFT, padx=5)
        
        # Payment entry
        payment_frame = ttk.LabelFrame(details_frame, text="Add Payment")
        payment_frame.pack(fill=tk.X, padx=5, pady=10)
        
        ttk.Label(payment_frame, text="Amount:").grid(row=0, column=0, sticky=tk.W, padx=5, pady=5)
        self.payment_amount_var = tk.StringVar()
        self.payment_amount_entry = ttk.Entry(payment_frame, textvariable=self.payment_amount_var, width=15)
        self.payment_amount_entry.grid(row=0, column=1, sticky=tk.W, padx=5, pady=5)
        
        ttk.Label(payment_frame, text="Method:").grid(row=0, column=2, sticky=tk.W, padx=5, pady=5)
        self.payment_method_var = tk.StringVar()
        method_cb = ttk.Combobox(payment_frame, textvariable=self.payment_method_var, width=15)
        method_cb['values'] = ("Cash", "Credit Card", "Debit Card", "Bank Transfer")
        method_cb.current(0)
        method_cb.grid(row=0, column=3, sticky=tk.W, padx=5, pady=5)
        
        self.add_payment_btn = ttk.Button(payment_frame, text="Add Payment", command=self._add_payment, state=tk.DISABLED)
        self.add_payment_btn.grid(row=0, column=4, sticky=tk.W, padx=5, pady=5)
        
        # === New Booking Frame ===
        form_frame = ttk.Frame(new_booking_frame)
        form_frame.pack(fill=tk.BOTH, expand=True, padx=5, pady=10)
        
        # Guest selection
        ttk.Label(form_frame, text="Select Guest:").grid(row=0, column=0, sticky=tk.W, pady=5)
        self.guest_id_var = tk.StringVar()
        self.guest_cb = ttk.Combobox(form_frame, textvariable=self.guest_id_var, width=30)
        self.guest_cb.grid(row=0, column=1, sticky=tk.EW, pady=5, padx=5)
        
        # Room selection
        ttk.Label(form_frame, text="Select Room:").grid(row=1, column=0, sticky=tk.W, pady=5)
        self.room_id_var = tk.StringVar()
        self.room_cb = ttk.Combobox(form_frame, textvariable=self.room_id_var, width=30)
        self.room_cb.grid(row=1, column=1, sticky=tk.EW, pady=5, padx=5)
        
        # Check-in date
        ttk.Label(form_frame, text="Check-in Date:").grid(row=2, column=0, sticky=tk.W, pady=5)
        self.check_in_date = DateEntry(form_frame, width=12, background='darkblue',
                                    foreground='white', borderwidth=2, date_pattern='yyyy-mm-dd')
        self.check_in_date.grid(row=2, column=1, sticky=tk.W, pady=5, padx=5)
        
        # Check-out date
        ttk.Label(form_frame, text="Check-out Date:").grid(row=3, column=0, sticky=tk.W, pady=5)
        self.check_out_date = DateEntry(form_frame, width=12, background='darkblue',
                                     foreground='white', borderwidth=2, date_pattern='yyyy-mm-dd')
        # Set default to check-in + 1 day
        tomorrow = datetime.now() + timedelta(days=1)
        self.check_out_date.set_date(tomorrow)
        self.check_out_date.grid(row=3, column=1, sticky=tk.W, pady=5, padx=5)
        
        # Total amount
        ttk.Label(form_frame, text="Total Amount:").grid(row=4, column=0, sticky=tk.W, pady=5)
        self.total_amount_var = tk.StringVar()
        self.total_amount_entry = ttk.Entry(form_frame, textvariable=self.total_amount_var)
        self.total_amount_entry.grid(row=4, column=1, sticky=tk.EW, pady=5, padx=5)
        
        # Calculate button
        calculate_btn = ttk.Button(form_frame, text="Calculate Total", command=self._calculate_total)
        calculate_btn.grid(row=4, column=2, sticky=tk.W, pady=5)
        
        # Create booking button
        create_btn = ttk.Button(form_frame, text="Create Booking", command=self._create_booking)
        create_btn.grid(row=5, column=1, sticky=tk.E, pady=10, padx=5)
        
        # Configure grid
        form_frame.columnconfigure(1, weight=1)
        
        # Populate dropdowns
        self._populate_dropdowns()
        
        # Selected booking data (hidden)
        self.selected_booking_id = None
    
    def _populate_booking_list(self):
        """Fetch bookings from database and populate the treeview"""
        # Clear the treeview
        for item in self.booking_tree.get_children():
            self.booking_tree.delete(item)
        
        # Get filtered bookings
        status_filter = self.filter_var.get()
        if status_filter and status_filter != "All":
            bookings = self.db.get_bookings(status=status_filter)
        else:
            bookings = self.db.get_bookings()
        
        # Insert bookings into treeview
        for booking in bookings:
            booking_id, first_name, last_name, room_number, check_in, check_out, amount, status = booking
            guest_name = f"{first_name} {last_name}"
            self.booking_tree.insert("", tk.END, values=(guest_name, room_number, check_in, check_out, status), tags=(booking_id,))
        
        # Clear selection and details
        self._clear_selection()
    
    def _apply_filter(self, event=None):
        """Apply filter to booking list"""
        self._populate_booking_list()
    
    def _on_booking_select(self, event=None):
        """Handle booking selection from treeview"""
        selection = self.booking_tree.selection()
        if selection:
            item = selection[0]
            
            # Get booking ID from tags
            booking_id = self.booking_tree.item(item, "tags")[0]
            self.selected_booking_id = booking_id
            
            # Get full booking details from database
            booking = self.db.get_booking(booking_id)
            if booking:
                _, guest_id, first_name, last_name, room_id, room_number, check_in, check_out, booking_date, amount, status = booking
                
                # Update detail labels
                self.guest_label.config(text=f"{first_name} {last_name}")
                self.room_label.config(text=room_number)
                self.check_in_label.config(text=check_in)
                self.check_out_label.config(text=check_out)
                self.amount_label.config(text=f"₹{amount:.2f}")
                self.status_label.config(text=status)
                self.booking_date_label.config(text=booking_date)
                
                # Enable/disable action buttons based on status
                if status == "Confirmed":
                    self.checkin_btn.config(state=tk.NORMAL)
                    self.checkout_btn.config(state=tk.DISABLED)
                    self.cancel_btn.config(state=tk.NORMAL)
                elif status == "Checked In":
                    self.checkin_btn.config(state=tk.DISABLED)
                    self.checkout_btn.config(state=tk.NORMAL)
                    self.cancel_btn.config(state=tk.NORMAL)
                else:
                    self.checkin_btn.config(state=tk.DISABLED)
                    self.checkout_btn.config(state=tk.DISABLED)
                    self.cancel_btn.config(state=tk.DISABLED)
                
                # Enable payment button
                self.add_payment_btn.config(state=tk.NORMAL)
    
    def _clear_selection(self):
        """Clear booking selection and details"""
        # Clear tree selection
        if self.booking_tree.selection():
            self.booking_tree.selection_remove(self.booking_tree.selection()[0])
        
        # Clear detail labels
        self.guest_label.config(text="-")
        self.room_label.config(text="-")
        self.check_in_label.config(text="-")
        self.check_out_label.config(text="-")
        self.amount_label.config(text="-")
        self.status_label.config(text="-")
        self.booking_date_label.config(text="-")
        
        # Disable action buttons
        self.checkin_btn.config(state=tk.DISABLED)
        self.checkout_btn.config(state=tk.DISABLED)
        self.cancel_btn.config(state=tk.DISABLED)
        self.add_payment_btn.config(state=tk.DISABLED)
        
        # Clear payment fields
        self.payment_amount_var.set("")
        self.payment_method_var.set("Cash")
        
        self.selected_booking_id = None
    
    def _populate_dropdowns(self):
        """Populate guest and room dropdown lists"""
        # Populate guest dropdown
        guests = self.db.get_guests()
        guest_list = []
        self.guest_map = {}  # Map display names to IDs
        
        for guest in guests:
            guest_id, first_name, last_name = guest[0:3]
            display_name = f"{first_name} {last_name}"
            guest_list.append(display_name)
            self.guest_map[display_name] = guest_id
        
        self.guest_cb['values'] = guest_list
        
        # Auto-select first guest if available
        if guest_list:
            self.guest_cb.current(0)
        
        # Populate room dropdown (only available rooms)
        rooms = self.db.get_rooms(status="Available")
        room_list = []
        self.room_map = {}  # Map display names to IDs
        
        for room in rooms:
            room_id, room_number, room_type, rate = room[0:4]
            display_name = f"{room_number} - {room_type} (₹{rate:.2f}/night)"
            room_list.append(display_name)
            self.room_map[display_name] = (room_id, rate)
        
        self.room_cb['values'] = room_list
        
        # Auto-select first room if available
        if room_list:
            self.room_cb.current(0)
            
        # If we have both a room and a guest selected, calculate the total automatically
        if guest_list and room_list:
            self._calculate_total()
    
    def _calculate_total(self):
        """Calculate total amount based on selected room and dates"""
        try:
            # Get selected room
            room_display = self.room_id_var.get()
            if not room_display or room_display not in self.room_map:
                messagebox.showerror("Error", "Please select a room")
                return
            
            room_id, rate = self.room_map[room_display]
            
            # Get dates
            check_in = self.check_in_date.get_date()
            check_out = self.check_out_date.get_date()
            
            # Calculate number of nights
            nights = (check_out - check_in).days
            if nights <= 0:
                messagebox.showerror("Error", "Check-out date must be after check-in date")
                return
            
            # Calculate total
            total = nights * rate
            
            # Update total field
            self.total_amount_var.set(f"{total:.2f}")
            
        except Exception as e:
            messagebox.showerror("Error", f"Failed to calculate total: {str(e)}")
    
    def _create_booking(self):
        """Create a new booking"""
        try:
            # Get selected guest
            guest_display = self.guest_id_var.get()
            if not guest_display:
                messagebox.showerror("Error", "Please select a guest")
                return
                
            # Check if guest is in map (only if we have a selection)
            if guest_display and guest_display not in self.guest_map:
                # If no guests exist yet, show a helpful message
                if not self.guest_map:
                    messagebox.showerror("Error", "No guests available. Please add a guest first in Guest Management.")
                    return
                messagebox.showerror("Error", "Please select a valid guest from the dropdown")
                return
            
            guest_id = self.guest_map[guest_display]
            
            # Get selected room
            room_display = self.room_id_var.get()
            if not room_display:
                messagebox.showerror("Error", "Please select a room")
                return
                
            # Check if room is in map (only if we have a selection)
            if room_display and room_display not in self.room_map:
                # If no rooms are available
                if not self.room_map:
                    messagebox.showerror("Error", "No rooms available for booking.")
                    return
                messagebox.showerror("Error", "Please select a valid room from the dropdown")
                return
            
            room_id, _ = self.room_map[room_display]
            
            # Get dates
            check_in = self.check_in_date.get_date().strftime("%Y-%m-%d")
            check_out = self.check_out_date.get_date().strftime("%Y-%m-%d")
            
            # Get total amount
            total_str = self.total_amount_var.get().strip()
            if not total_str:
                messagebox.showerror("Error", "Please calculate the total amount")
                return
            
            try:
                total = float(total_str)
            except ValueError:
                messagebox.showerror("Error", "Invalid total amount")
                return
            
            # Create booking
            booking_id = self.db.add_booking(guest_id, room_id, check_in, check_out, total)
            
            if booking_id:
                messagebox.showinfo("Success", "Booking created successfully")
                self._populate_booking_list()
                self._populate_dropdowns()  # Refresh room list
                
                # Clear form
                self.guest_id_var.set("")
                self.room_id_var.set("")
                self.total_amount_var.set("")
            else:
                messagebox.showerror("Error", "Failed to create booking")
            
        except Exception as e:
            messagebox.showerror("Error", f"Failed to create booking: {str(e)}")
    
    def _check_in(self):
        """Check in a guest for selected booking"""
        if not self.selected_booking_id:
            return
        
        result = self.db.update_booking_status(self.selected_booking_id, "Checked In")
        
        if result:
            messagebox.showinfo("Success", "Guest checked in successfully")
            self._populate_booking_list()
        else:
            messagebox.showerror("Error", "Failed to check in guest")
    
    def _check_out(self):
        """Check out a guest for selected booking"""
        if not self.selected_booking_id:
            return
        
        result = self.db.update_booking_status(self.selected_booking_id, "Checked Out")
        
        if result:
            messagebox.showinfo("Success", "Guest checked out successfully")
            self._populate_booking_list()
            self._populate_dropdowns()  # Refresh room list
        else:
            messagebox.showerror("Error", "Failed to check out guest")
    
    def _cancel_booking(self):
        """Cancel selected booking"""
        if not self.selected_booking_id:
            return
        
        # Confirm cancellation
        if not messagebox.askyesno("Confirm", "Are you sure you want to cancel this booking?"):
            return
        
        result = self.db.update_booking_status(self.selected_booking_id, "Cancelled")
        
        if result:
            messagebox.showinfo("Success", "Booking cancelled successfully")
            self._populate_booking_list()
            self._populate_dropdowns()  # Refresh room list
        else:
            messagebox.showerror("Error", "Failed to cancel booking")
    
    def _add_payment(self):
        """Add payment for selected booking"""
        if not self.selected_booking_id:
            return
        
        # Validate payment amount
        amount_str = self.payment_amount_var.get().strip()
        method = self.payment_method_var.get()
        
        if not amount_str:
            messagebox.showerror("Error", "Please enter payment amount")
            return
        
        try:
            amount = float(amount_str)
            if amount <= 0:
                raise ValueError("Amount must be positive")
        except ValueError:
            messagebox.showerror("Error", "Invalid payment amount")
            return
        
        # Add payment
        result = self.db.add_payment(self.selected_booking_id, amount, method)
        
        if result:
            messagebox.showinfo("Success", f"Payment of ₹{amount:.2f} added successfully")
            self.payment_amount_var.set("")  # Clear payment field
        else:
            messagebox.showerror("Error", "Failed to add payment") 
[
  "y1:r1:R1",
  "y1:r2:R1",
  "y2:r1:R1",
  "y2:r2:R1",
  "r4:p1:R1",
  "r5:p1:R1",
  "r5:b1:R1",
  "b2:p1:R1",
  "y3:b5:R1",
  "y3:b6:R1"
]
{
  "R1": [
    ["y1", "y2", "y3"],
    ["r1", "r2", "r3", "r4", "r5"],
    ["b1", "b2", "b3", "b4", "b5", "b6"]
  ]
}
//Two Button//
.Padding-top:20px;font-size 14px;
padding: 10px 15px;
background-color: #ededed
background-color: transparent;
border: 1px solid #ededed;
weight-width: 40%;
font-weight: 400;
text-transform: inherit;
vertical-align: middle;
transition: 350ms ease;

}

.button-2:hover
text-decoration: underline;
} }

https://www.iperiusremote.com/
"Organization Table (Employee Capacity)"."Reporting To"LIKE concat('%',${system.login.email},'%')
{
	"blocks": [
		{
			"type": "header",
			"text": {
				"type": "plain_text",
				"text": ":xeros-connect: Boost Days - What's on this week! :xeros-connect:"
			}
		},
		{
			"type": "section",
			"text": {
				"type": "mrkdwn",
				"text": "Morning Ahuriri :wave: Happy Monday, let's get ready to dive into another week in the Hawke's Bay office! See below for what's in store :eyes:"
			}
		},
		{
			"type": "divider"
		},
		{
			"type": "header",
			"text": {
				"type": "plain_text",
				"text": ":calendar-date-14: Wednesday, 14th May :camel:",
				"emoji": true
			}
		},
		{
			"type": "section",
			"text": {
				"type": "mrkdwn",
				"text": "\n:coffee: *Café Partnership*: Enjoy coffee and café-style beverages from our cafe partner, *Adoro*, located in our office building *8:00AM - 11:30AM*.\n:breakfast: *Breakfast*: Provided by *Design Cuisine* from *9:30AM-10:30AM* in the Kitchen."
			}
		},
		{
			"type": "header",
			"text": {
				"type": "plain_text",
				"text": ":calendar-date-15: Thursday, 15th May :meow-coffee:",
				"emoji": true
			}
		},
		{
			"type": "section",
			"text": {
				"type": "mrkdwn",
				"text": "\n:coffee: *Café Partnership*: Enjoy coffee and café-style beverages from our cafe partner, *Adoro*, located in our office building *8:00AM - 11:30AM*.\n:wrap: *Lunch*: Provided by *Roam* from *12:30PM-1:30PM* in the Kitchen."
			}
		},
		{
			"type": "divider"
		},
		{
			"type": "section",
			"text": {
				"type": "mrkdwn",
				"text": "*What else?* Stay tuned to this channel for more details, check out the <https://calendar.google.com/calendar/u/0?cid=eGVyby5jb21fbXRhc2ZucThjaTl1b3BpY284dXN0OWlhdDRAZ3JvdXAuY2FsZW5kYXIuZ29vZ2xlLmNvbQ|*Hawkes Bay Social Calendar*>, and get ready to Boost your workdays!\n\nWX Team :party-wx:"
			}
		}
	]
}
<ul id="timer">
  <li>
    <div class="number-container">
      <span id="days" class="number">00</span>
    </div>
    <span class="text">Days</span>
    <div class="circle-container days-anim">
      <div class="glow">
        <img
          src="https://athletesoncall.mmcgbl.dev/wp-content/uploads/2025/05/Ellipse.svg"
          alt=""
        />
      </div>
    </div>
  </li>

  <li>
    <div class="number-container">
      <span id="hours" class="number">00</span>
    </div>
    <span class="text">Hours</span>
    <div class="circle-container hour-anim">
      <div class="glow">
        <img
          src="https://athletesoncall.mmcgbl.dev/wp-content/uploads/2025/05/Ellipse.svg"
          alt=""
        />
      </div>
    </div>
  </li>

  <li>
    <div class="number-container">
      <span id="minutes" class="number">00</span>
    </div>
    <span class="text">Minutes</span>
    <div class="circle-container minu-anim">
      <div class="glow">
        <img
          src="https://athletesoncall.mmcgbl.dev/wp-content/uploads/2025/05/Ellipse.svg"
          alt=""
        />
      </div>
    </div>
  </li>

  <li>
    <div class="number-container">
      <span id="seconds" class="number">00</span>
    </div>
    <span class="text">Seconds</span>
    <div class="circle-container sec-a">
      <div class="glow">
        <img
          src="https://athletesoncall.mmcgbl.dev/wp-content/uploads/2025/05/Ellipse.svg"
          alt=""
        />
      </div>
    </div>
  </li>
</ul>

<script>
  document.addEventListener("DOMContentLoaded", function () {
    let interval;
    const startDate = new Date(2025, 4, 11); 
    const endDate = new Date(2025, 8, 8); 

    function padTo2(num) {
      return num.toString().padStart(2, "0");
    }

    function updateElement(id, value) {
      const el = document.getElementById(id);
      const newVal = padTo2(value);
      if (el.innerText !== newVal) {
        el.classList.add("up");
        setTimeout(() => {
          el.innerText = newVal;
          el.classList.remove("up");
        }, 250);
      }
    }

    function updateCountdown() {
      const now = new Date();

      if (now < startDate) {
        document.getElementById("timer").innerHTML =
          "<li>Countdown hasn't started yet!</li>";
        clearInterval(interval);
        return;
      }

      const remaining = endDate - now;
      if (remaining <= 0) {
        document.getElementById("timer").innerHTML = "<li>Time's up!</li>";
        clearInterval(interval);
        return;
      }

      const totalSeconds = Math.floor(remaining / 1000);
      const days = Math.floor(totalSeconds / (60 * 60 * 24));
      const hours = Math.floor((totalSeconds % (60 * 60 * 24)) / 3600);
      const minutes = Math.floor((totalSeconds % 3600) / 60);
      const seconds = totalSeconds % 60;

      updateElement("days", days);
      updateElement("hours", hours);
      updateElement("minutes", minutes);
      updateElement("seconds", seconds);
    }

    interval = setInterval(updateCountdown, 1000);
    updateCountdown(); 
  });
</script>
To calculate Viral Coefficient (VC), you use this formula:

Viral Coefficient = (Number of Invites per User) × (Conversion Rate)

In your case:

Total active users = 190

Referrals = 4

Converted referrals = 2

Step 1: Find Invites per User
This is:

Number of Referrals / Total Active Users
= 4 / 190 ≈ 0.02105

Step 2: Find Conversion Rate
Converted / Total Referrals
= 2 / 4 = 0.5

Step 3: Multiply
VC = 0.02105 × 0.5 ≈ 0.0105
In recent years, crypto wallets have become an essential gateway for anyone interacting with the blockchain — whether it’s to store assets, send funds, or interact with decentralized apps (dApps). Trust Wallet, in particular, has set itself apart as one of the most popular mobile wallets, boasting millions of users worldwide.
If you’re wondering how to create a crypto wallet like Trust Wallet, this guide will walk you through the key steps, considerations, and best practices.

What is a Crypto Wallet?

At its core, a crypto wallet is a software application that allows users to securely store, send, and receive cryptocurrencies. It interacts with blockchain networks and manages private/public keys — the critical cryptographic elements that control ownership of crypto assets.

There are two main types:

Custodial wallets: Managed by third parties (e.g., exchanges).
Non-custodial wallets: Users control their own private keys (e.g., Trust Wallet, MetaMask).

Trust Wallet is a non-custodial, multi-asset wallet known for:

User-friendly mobile app
Support for thousands of coins and tokens
Built-in DEX (Decentralized Exchange)
NFT support
dApp browser for Web3 interaction

How to Build a Crypto Wallet Like Trust Wallet

Define Your Vision & Features
Decide what your wallet will offer. Key features to consider:

Multi-currency support (Bitcoin, Ethereum, BNB, etc.)
Token management (ERC-20, BEP-20, NFTs)
Backup & recovery (mnemonic phrases, seed phrases)
Security features (PIN, biometric authentication, encryption)
dApp browser / Web3 integration
In-app staking or swapping

Choose Your Technology Stack
Your tech stack will determine scalability, performance, and security:

Frontend: React Native, Flutter (for cross-platform mobile apps)
Backend: Node.js, Go, Python (if you need a backend; many wallets are backend-light)
Blockchain Integration: Use libraries like Web3.js, Ethers.js, or BitcoinJS to interact with blockchains.

Prioritize Security
Security can make or break your wallet’s reputation. Best practices include:

End-to-end encryption for private keys
Secure storage (using device hardware or encrypted storage)
Open-source codebase for transparency
Regular audits and bug bounties

Integrate with Blockchain Networks
To handle transactions, your wallet must connect to blockchain nodes. Options:

Run your own nodes (costly but gives control)
Use third-party API services (e.g., Infura, Alchemy, QuickNode) for scalability

Develop a User-Friendly Interface
Crypto can be intimidating — design your app to make it simple:

Clear navigation
Easy onboarding and wallet setup
Educational tips and warnings (especially around private keys and phishing)

Test Extensively
Before launch, rigorously test:

Transaction speed and reliability
Backup/recovery flow
Cross-device compatibility
Security under various attack scenarios

Stay Compliant
While non-custodial wallets have fewer regulatory requirements, staying informed about KYC/AML laws in your region is still important, especially if you integrate services like swaps, fiat onramps, or staking.

My Research Process

When exploring how to build a wallet like Trust Wallet, I followed a structured research approach:

Analyzed leading wallets: I looked into Trust Wallet, MetaMask, Coinbase Wallet, and Exodus, studying their features, UX, and security models.

Reviewed developer documentation: Open-source wallets often have public GitHub repositories — I explored their architecture.

Tested multiple wallets: I installed and used top wallets to understand strengths and pain points.

Consulted blockchain companies: I reached out to several blockchain companies to better understand the landscape, development challenges, and available solutions. Among them, one standout was AppcloneX , a software provider with over 11 years of collaborative experience in the crypto and blockchain space.

AppcloneX impressed me not only with their deep technical expertise but also with their client-focused approach. They provided me with a demo version of their crypto wallet solution, which allowed me to test features firsthand, assess performance, and get a clear picture of how their technology can accelerate time-to-market.

This helped me evaluate their performance, features, and service quality effectively.

Final Thoughts

Creating a crypto wallet like Trust Wallet is no small feat — but it’s achievable with the right approach. Focus on user security, intuitive design, and reliable blockchain connections. Remember, the wallet space is highly competitive, so innovation (like adding staking, NFTs, or a great dApp experience) can help you stand out.

Mobile Number - +91 9677713864
Whatsapp Number - 9677713864
Email - business@appclonex.com
Website - https://www.appclonex.com/

If you’re curious to get hands-on, AppcloneX trust wallet demo is available to test. This giving you a strong foundation to start your journey.
// Cannot Find - Collapse on xs
.cannot-find-section{
    padding: _res-m(22) 0;
    @include screen-sm-min{
        padding: _res(58) 0;
    }
    @include section-padding;
}
.Advanced-Inventory-Search_1-0-2{
    .advanced-inventory-search.collapse-on-xs{
        padding: 0;
        .row-flex{
            @include screen-xs{
                flex-direction: column;
                gap: _res-m(10);
            }
            @include screen-sm-min{
                flex-wrap: nowrap;
                gap: _res(28);
            }
            .column-auto{
                &.title-container{
                    padding: 0;
                    @include screen-xs{
                        width: 100% !important;
                    }
                    @include screen-sm-min{
                        flex-basis: 12% !important;
                        padding-right: _res(50);
                    }
                    h3{
                        font-size: _res-m(16);
                        font-weight: 500;
                        text-align: left !important;
                        margin: 0;
                        @include _flex($halign: space-between);
                        @include screen-sm-min{
                            font-size: _res(41);
                            font-weight: bold;
                            white-space: wrap;
                            justify-content: flex-start;
                        }
                        &::after{
                            background: url("#{$img-path}/hero-txt-arrow.png") no-repeat center/80%;
                            color: transparent;
                            transform: rotate(45deg);
                            transition: all .3s ease-in-out;
                        }
                        &.icon::after{
                            transform: rotate(225deg);
                        }
                    }
                }
                &.collapse{
                    padding: 0;
                    @include screen-xs{
                        width: 100% !important;
                    }
                    select{
                        border: none;
                        height: _res-m(35);
                        font-size: _res-m(10);
                        margin: 0;
                        padding: 0 _res-m(15);
                        @include screen-sm-min{
                            border-radius: _res(12);
                            height: _res(30,60);
                            font-size: _res(10,16);
                            padding: 0 _res(20);
                        }
                    }
                    &.column-btn{
                        @include screen-sm-min{
                            flex-grow: 0;
                        }
                        #viewResults{
                            @include btn-style;
                            width: 100%;
                            font-size: 0;
                            &::before{
                                content: "Search";
                                font-size: _res-m(14);
                            }
                            @include screen-sm-min{
                                font-size: 0;
                                &::before{
                                    font-size: _res(10,18);
                                }
                            }
                        }
                    }
                    &:nth-child(5), &:last-child{
                        display: none !important;
                    }
                }
            }
        }
    }
}
{
	"blocks": [
		{
			"type": "header",
			"text": {
				"type": "plain_text",
				"text": ":asx::xero: FY25 Half Year Results + Boost Days for next week | Please read! :xero::asx:"
			}
		},
		{
			"type": "divider"
		},
		{
			"type": "section",
			"text": {
				"type": "mrkdwn",
				"text": "Hey Sydney! It's that time a year again where we host half year results in our office and support the live call to the ASX, our Boost Days for next week will run a little differently, however we will continue to run the office as BAU as much as possible."
			}
		},
		{
			"type": "divider"
		},
		{
			"type": "section",
			"text": {
				"type": "mrkdwn",
				"text": "*Please note:*"
			}
		},
		{
			"type": "section",
			"text": {
				"type": "mrkdwn",
				"text": ":dot-blue:*FOH* - Front of house will be closed for the week for the results working group \n :dot-blue:*Staff Entry* - Please use the White Staff entry doors located on each side of the floor \n :dot-blue:*Anzac & Argyle* - These rooms are offline this week to also support the results working group \n :dot-blue:*Live Call* - Our Kitchen/breakout space will be closed between 10:30am - 11:30am. We will have a livestream of the call in Taronga for anyone who'd like to watch! \n :dot-blue:*Filming* - Kitchen will be closed between 10am - 10:20am Friday 15 November for a interview \n",
				"verbatim": false
			}
		},
		{
			"type": "divider"
		},
		{
			"type": "header",
			"text": {
				"type": "plain_text",
				"text": ":star: Boost Days :star:",
				"emoji": true
			}
		},
		{
			"type": "section",
			"text": {
				"type": "mrkdwn",
				"text": "Due to Full Year Results, our Boost Days will be changing for next week! Please see what's on below: \n  "
			}
		},
		{
			"type": "header",
			"text": {
				"type": "plain_text",
				"text": "Monday, 12th May :calendar-date-12:",
				"emoji": true
			}
		},
		{
			"type": "section",
			"text": {
				"type": "plain_text",
				"text": ":coffee: Café Partnership: Head to Naked Duck for your free coffee.\n :breakfast: Breakfast: Provided by Naked Duck from 9am in the kitchen. \n\n ",
				"emoji": true
			}
		},
		{
			"type": "header",
			"text": {
				"type": "plain_text",
				"text": "Wednesday, 14th May :calendar-date-14:",
				"emoji": true
			}
		},
		{
			"type": "section",
			"text": {
				"type": "plain_text",
				"text": ":coffee: Café Partnership: Head to Naked Duck for your free coffee.. \n :lunch: Lunch: Provided by Naked Duck from 12pm in the Kitchen.",
				"emoji": true
			}
		},
		{
			"type": "divider"
		},
		{
			"type": "section",
			"text": {
				"type": "mrkdwn",
				"text": "Stay tuned to this channel for more details, check out the <https://calendar.google.com/calendar/u/0/r?cid=Y185aW90ZWV0cXBiMGZwMnJ0YmtrOXM2cGFiZ0Bncm91cC5jYWxlbmRhci5nb29nbGUuY29t|*Sydney Social Calendar*>, and get ready to Boost your workdays!\n\nLove,\nWX Team :party-wx:"
			}
		}
	]
}
<!DOCTYPE html>
<html lang="vi">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Kiểm tra Thứ hạng Google</title>
    <style>
        body {
            font-family: Arial, sans-serif;
            line-height: 1.6;
            margin: 0;
            padding: 20px;
            color: #333;
        }
        .container {
            max-width: 1200px;
            margin: 0 auto;
        }
        h1 {
            text-align: center;
            margin-bottom: 20px;
        }
        .form-row {
            margin-bottom: 15px;
            display: flex;
            flex-wrap: wrap;
            gap: 10px;
        }
        label {
            display: block;
            margin-bottom: 5px;
            font-weight: bold;
        }
        input[type="text"], textarea, select {
            width: 100%;
            padding: 8px;
            border: 1px solid #ddd;
            border-radius: 4px;
            box-sizing: border-box;
        }
        button {
            background-color: #4CAF50;
            color: white;
            border: none;
            padding: 10px 15px;
            border-radius: 4px;
            cursor: pointer;
            font-size: 16px;
        }
        button:hover {
            background-color: #45a049;
        }
        .loading {
            display: none;
            margin: 20px 0;
            text-align: center;
        }
        .progress-container {
            width: 100%;
            background-color: #f1f1f1;
            border-radius: 4px;
            margin-top: 10px;
        }
        .progress-bar {
            width: 0%;
            height: 30px;
            background-color: #4CAF50;
            text-align: center;
            line-height: 30px;
            color: white;
            border-radius: 4px;
        }
        table {
            width: 100%;
            border-collapse: collapse;
            margin-top: 20px;
        }
        th, td {
            padding: 8px;
            text-align: left;
            border-bottom: 1px solid #ddd;
        }
        th {
            background-color: #f2f2f2;
        }
        tr:hover {
            background-color: #f5f5f5;
        }
        .error {
            color: red;
            margin-top: 10px;
            display: none;
        }
        .success {
            color: green;
            margin-top: 10px;
            display: none;
        }
        .results-actions {
            display: none;
            justify-content: space-between;
            margin-top: 20px;
        }
        .tooltip {
            position: relative;
            display: inline-block;
            cursor: pointer;
        }
        .tooltip .tooltiptext {
            visibility: hidden;
            width: 200px;
            background-color: #555;
            color: #fff;
            text-align: center;
            border-radius: 6px;
            padding: 5px;
            position: absolute;
            z-index: 1;
            bottom: 125%;
            left: 50%;
            margin-left: -100px;
            opacity: 0;
            transition: opacity 0.3s;
        }
        .tooltip:hover .tooltiptext {
            visibility: visible;
            opacity: 1;
        }
        .rank-good {
            background-color: #d4edda;
            color: #155724;
        }
        .rank-medium {
            background-color: #fff3cd;
            color: #856404;
        }
        .rank-bad {
            background-color: #f8d7da;
            color: #721c24;
        }
        .rank-none {
            background-color: #e2e3e5;
            color: #383d41;
        }
        .copy-btn {
            background-color: #007bff;
        }
        .copy-btn:hover {
            background-color: #0069d9;
        }
        .redirect-info {
            font-size: 12px;
            color: #666;
            margin-top: 3px;
        }
        .report-settings {
            background-color: #f9f9f9;
            padding: 15px;
            border-radius: 5px;
            margin-bottom: 15px;
        }
        /* Pháo hoa CSS */
        .pyro {
            position: fixed;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            z-index: 999;
            pointer-events: none;
            display: none;
        }
        .pyro > .before, .pyro > .after {
            position: absolute;
            width: 5px;
            height: 5px;
            border-radius: 50%;
            box-shadow: 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff, 0 0 #fff;
            animation: 1s bang ease-out infinite backwards, 1s gravity ease-in infinite backwards, 5s position linear infinite backwards;
        }
        .pyro > .after {
            animation-delay: 1.25s, 1.25s, 1.25s;
            animation-duration: 1.25s, 1.25s, 6.25s;
        }
        @keyframes bang {
            to {
                box-shadow: -70px -115.67px #00ff73, -28px -99.67px #a6ff00, 58px -31.67px #0051ff, 13px -7.67px #00ffa2, -19px -33.67px #ff00d0, -37px -23.67px #ff8800, 19px -78.67px #ff002f, 56px -87.67px #00ffcc, -29px -45.67px #ff5e00, 1px -66.67px #ff1500, 42px -123.67px #91ff00, 21px -108.67px #b300ff, -23px -3.67px #ffd000, -65px -55.67px #ff4800, -63px -27.67px #00ff88, 46px 0.33px #0055ff, 75px -86.67px #8cff00, -11px -117.67px #00ff4d, 69px -125.67px #ff0033, 82px -36.67px #00ffbb, -39px -92.67px #00ff73, 49px -124.67px #ff0040, 94px -7.67px #ff6600, 82px 22.33px #ff001a, -14px -98.67px #00ffd5, 27px 7.33px #00ff33, -68px -18.67px #0080ff, 89px -42.67px #ff00fb, -88px -93.67px #ff0004, -62px -59.67px #00ff8c, 52px -21.67px #00ff33, 74px 6.33px #ff00bf, -42px -69.67px #00ff8c, -9px -92.67px #00ff8c, 26px -65.67px #ff0004, 57px -51.67px #a2ff00, 47px -89.67px #0099ff, 74px -123.67px #ff0037, -86px -108.67px #ff4000, 76px -25.67px #0400ff, 77px -57.67px #6aff00, -13px -91.67px #00ff95, 52px -66.67px #91ff00, -42px -103.67px #00ff73, -69px -115.67px #ff0037, 89px -38.67px #ff0088, 90px -113.67px #00ff6a, -63px -42.67px #0066ff, -71px -69.67px #0400ff, 0px -53.67px #002bff, 26px -70.67px #ff006a;
            }
        }
        @keyframes gravity {
            to {
                transform: translateY(200px);
                opacity: 0;
            }
        }
        @keyframes position {
            0%, 19.9% {
                margin-top: 10%;
                margin-left: 40%;
            }
            20%, 39.9% {
                margin-top: 40%;
                margin-left: 30%;
            }
            40%, 59.9% {
                margin-top: 20%;
                margin-left: 70%;
            }
            60%, 79.9% {
                margin-top: 30%;
                margin-left: 20%;
            }
            80%, 99.9% {
                margin-top: 30%;
                margin-left: 80%;
            }
        }
        /* Thông báo chúc mừng */
        .celebration {
            position: fixed;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
            background-color: rgba(255, 255, 255, 0.9);
            border: 2px solid #4CAF50;
            border-radius: 10px;
            padding: 20px;
            text-align: center;
            box-shadow: 0 0 20px rgba(0, 0, 0, 0.2);
            z-index: 1000;
            display: none;
        }
        .celebration h2 {
            color: #4CAF50;
            margin-top: 0;
        }
        .celebration p {
            font-size: 18px;
            margin-bottom: 20px;
        }
        .celebration button {
            background-color: #4CAF50;
            color: white;
            border: none;
            padding: 10px 20px;
            border-radius: 5px;
            cursor: pointer;
        }
        .redirect-chain {
            margin-top: 5px;
            font-size: 0.85em;
            color: #666;
        }
        .redirect-chain-item {
            display: block;
            margin-bottom: 3px;
            padding-left: 10px;
            border-left: 2px solid #d35400;
        }
        @media (max-width: 768px) {
            .form-row {
                flex-direction: column;
            }
            .form-row > div {
                width: 100%;
                margin-bottom: 10px;
            }
        }
    </style>
</head>
<body>
    <div class="container">
        <h1>Kiểm tra Thứ hạng Google</h1>
        
        <div class="form-row">
            <div style="flex: 1;">
                <label for="api-key">API Key (Serper.dev):</label>
                <input type="text" id="api-key" placeholder="Nhập API key của bạn" value="85dd0b1bd9a79f29cd3a121a23cc404a759dc00a" hidden>
            </div>
        </div>
        
        <div class="form-row">
            <div style="flex: 1;">
                <label for="domains">Danh sách Domain:</label>
                <textarea id="domains" rows="5" placeholder="Mỗi domain một dòng, ví dụ:&#10;example.com&#10;example.org"></textarea>
            </div>
            <div style="flex: 1;">
                <label for="keywords">Danh sách Từ khóa:</label>
                <textarea id="keywords" rows="5" placeholder="Mỗi từ khóa một dòng, ví dụ:&#10;từ khóa 1&#10;từ khóa 2"></textarea>
            </div>
        </div>
        
        <div class="form-row">
            <div>
                <label for="device">Thiết bị:</label>
                <select id="device">
                    <option value="desktop">Desktop</option>
                    <option value="mobile">Mobile</option>
                </select>
            </div>
            <div>
                <label for="location">Vị trí:</label>
                <select id="location">
                    <option value="">Mặc định (Việt Nam)</option>
                    <option value="1006094">Hà Nội</option>
                    <option value="1006113">TP. Hồ Chí Minh</option>
                    <option value="1006151">Đà Nẵng</option>
                </select>
            </div>
        </div>
        
        <div class="report-settings">
            <label for="report-name">Tên báo cáo:</label>
            <input type="text" id="report-name" placeholder="PIC" value="">
        </div>
        
        <div class="form-row">
            <button onclick="checkRanks()">Kiểm tra Thứ hạng</button>
        </div>
        
        <div id="loading" class="loading">
            <p>Đang kiểm tra thứ hạng... Vui lòng đợi.</p>
            <div class="progress-container">
                <div id="progress" class="progress-bar">0%</div>
            </div>
            <p id="progress-text">0/0</p>
        </div>
        
        <div id="error" class="error"></div>
        <div id="success" class="success"></div>
        
        <div id="results-actions" class="results-actions">
            <button class="copy-btn" onclick="copyAllRanks()">Sao chép dán vào daily ranking</button>
            <button class="copy-btn" onclick="copyFormattedReport()">Sao chép báo cáo Viptalk</button>
        </div>
        
        <div id="results"></div>
    </div>
    
    <!-- Hiệu ứng pháo hoa -->
    <div class="pyro">
        <div class="before"></div>
        <div class="after"></div>
    </div>
    
    <!-- Thông báo chúc mừng -->
    <div id="celebration" class="celebration">
        <h2>🎉 Chúc mừng! 🎉</h2>
        <p id="celebration-message"></p>
        <button onclick="closeCelebration()">Đóng</button>
    </div>
    
    <script>
        // Biến toàn cục để lưu kết quả
        let results = [];
        
        // Kích thước batch mặc định
        const DEFAULT_BATCH_SIZE = 5;
        
        // Đường dẫn API redirect server
        const DEFAULT_REDIRECT_SERVER = "https://red.nguonkienthuc.com";
        
        // Cache kết quả tìm kiếm
        const searchCache = {};
        
        // Hàm làm sạch domain
        function cleanDomain(domain) {
            let cleanedDomain = domain;
            
            // Loại bỏ http://, https://, www. và dấu / ở cuối
            cleanedDomain = cleanedDomain.replace(/^https?:\/\//, '');
            cleanedDomain = cleanedDomain.replace(/^www\./, '');
            cleanedDomain = cleanedDomain.replace(/\/$/, '');
            
            return {
                cleanedDomain: cleanedDomain.toLowerCase(),
                originalDomain: domain
            };
        }
        
        // Hàm chia mảng thành các mảng con có kích thước nhỏ hơn
        function chunkArray(array, size) {
            const chunks = [];
            for (let i = 0; i < array.length; i += size) {
                chunks.push(array.slice(i, i + size));
            }
            return chunks;
        }
        
        // Hàm kiểm tra nhiều chuyển hướng cùng lúc (sử dụng API như phiên bản cũ)
        async function checkMultipleRedirects(domains) {
            try {
                // Đảm bảo mỗi domain có protocol
                const urls = domains.map(domain => {
                    if (!domain.startsWith('http://') && !domain.startsWith('https://')) {
                        return 'https://' + domain;
                    }
                    return domain;
                });
                
                // Gọi API kiểm tra nhiều chuyển hướng
                const response = await fetch(`${DEFAULT_REDIRECT_SERVER}/check-redirects`, {
                    method: 'POST',
                    headers: {
                        'Content-Type': 'application/json'
                    },
                    body: JSON.stringify({ urls, maxRedirects: 10 })
                });
                
                if (!response.ok) {
                    throw new Error(`Lỗi API: ${response.status}`);
                }
                
                return await response.json();
            } catch (error) {
                console.error('Lỗi kiểm tra nhiều chuyển hướng:', error);
                return {};
            }
        }
        
        // Hàm tìm kiếm Google với cache
        async function searchGoogleBatch(queries) {
            const apiKey = document.getElementById('api-key').value.trim();
            if (!apiKey) {
                throw new Error('API key không được để trống');
            }
            
            const device = document.getElementById('device').value;
            const location = document.getElementById('location').value;
            
            // Gom nhóm các từ khóa giống nhau để tránh tìm kiếm trùng lặp
            const uniqueQueries = [];
            const queryMap = new Map(); // Ánh xạ từ từ khóa đến chỉ mục trong uniqueQueries
            
            queries.forEach(query => {
                const cacheKey = `${query.keyword}_${device}_${location || 'default'}`;
                
                // Nếu từ khóa đã có trong cache, bỏ qua
                if (searchCache[cacheKey]) {
                    return;
                }
                
                // Nếu từ khóa chưa được thêm vào uniqueQueries, thêm vào
                if (!queryMap.has(query.keyword)) {
                    queryMap.set(query.keyword, uniqueQueries.length);
                    uniqueQueries.push({
                        keyword: query.keyword,
                        cacheKey: cacheKey
                    });
                }
            });
            
            // Nếu có từ khóa cần tìm kiếm
            if (uniqueQueries.length > 0) {
                const searchQueries = uniqueQueries.map(query => {
                    const queryParams = {
                        q: query.keyword,
                        device: device,
                        gl: "vn",
                        hl: "vi",
                        num: 100
                    };
                    
                    if (location) {
                        queryParams.location = location;
                    }
                    
                    return queryParams;
                });
                
                const myHeaders = new Headers();
                myHeaders.append("X-API-KEY", apiKey);
                myHeaders.append("Content-Type", "application/json");
                
                const requestOptions = {
                    method: "POST",
                    headers: myHeaders,
                    body: JSON.stringify(searchQueries),
                    redirect: "follow"
                };
                
                try {
                    const response = await fetch("https://google.serper.dev/search", requestOptions);
                    
                    if (!response.ok) {
                        const errorText = await response.text();
                        throw new Error(`Lỗi API: ${response.status} - ${errorText}`);
                    }
                    
                    const data = await response.json();
                    
                    // Lưu kết quả vào cache
                    uniqueQueries.forEach((query, index) => {
                        searchCache[query.cacheKey] = data[index];
                    });
                } catch (error) {
                    console.error("Lỗi khi tìm kiếm:", error);
                    throw error;
                }
            }
            
            // Trả về kết quả từ cache cho tất cả queries
            return queries.map(query => {
                const cacheKey = `${query.keyword}_${device}_${location || 'default'}`;
                return searchCache[cacheKey];
            });
        }
        
        // Hàm hiển thị pháo hoa
        function showFireworks() {
            const pyro = document.querySelector('.pyro');
            pyro.style.display = 'block';
            
            // Ẩn pháo hoa sau 3 giây
            setTimeout(() => {
                pyro.style.display = 'none';
            }, 3000);
        }
        
        // Hàm hiển thị thông báo chúc mừng
        function showCelebration(message) {
            const celebration = document.getElementById('celebration');
            const celebrationMessage = document.getElementById('celebration-message');
            
            celebrationMessage.textContent = message;
            celebration.style.display = 'block';
            
            // Hiển thị pháo hoa
            showFireworks();
        }
        
        // Hàm đóng thông báo chúc mừng
        function closeCelebration() {
            document.getElementById('celebration').style.display = 'none';
        }
        
        // Hàm kiểm tra thứ hạng tối ưu
        async function checkRanks() {
            const domainsText = document.getElementById('domains').value.trim();
            const keywordsText = document.getElementById('keywords').value.trim();
            
            if (!domainsText) {
                showError('Vui lòng nhập danh sách domain');
                return;
            }
            
            if (!keywordsText) {
                showError('Vui lòng nhập danh sách từ khóa');
                return;
            }
            
            // Parse danh sách domain và từ khóa
            const domains = domainsText.split('\n')
                .map(domain => domain.trim())
                .filter(domain => domain.length > 0);
                
            const keywords = keywordsText.split('\n')
                .map(keyword => keyword.trim())
                .filter(keyword => keyword.length > 0);
            
            if (domains.length === 0) {
                showError('Không có domain hợp lệ');
                return;
            }
            
            if (keywords.length === 0) {
                showError('Không có từ khóa hợp lệ');
                return;
            }
            
            // Hiển thị loading
            document.getElementById('loading').style.display = 'block';
            document.getElementById('error').style.display = 'none';
            document.getElementById('success').style.display = 'none';
            document.getElementById('results').innerHTML = '';
            document.getElementById('results-actions').style.display = 'none';
            
            // Reset kết quả
            results = [];
            
            // Kiểm tra chuyển hướng cho tất cả domain một lần
            let redirectResults = {};
            try {
                redirectResults = await checkMultipleRedirects(domains);
            } catch (error) {
                console.error('Lỗi khi kiểm tra chuyển hướng:', error);
            }
            
            // Tạo danh sách các domain đã làm sạch để kiểm tra
            const domainInfo = domains.map(domain => {
                const { cleanedDomain } = cleanDomain(domain);
                let domainToCheck = domain;
                if (!domainToCheck.startsWith('http://') && !domainToCheck.startsWith('https://')) {
                    domainToCheck = 'https://' + domainToCheck;
                }
                
                // Thêm thông tin chuyển hướng
                const redirectInfo = redirectResults[domainToCheck] || {};
                
                return {
                    domain: domain,
                    cleanedDomain: cleanedDomain,
                    redirected: redirectInfo.hasRedirect || false,
                    redirectChain: redirectInfo.redirectChain || null,
                    finalUrl: redirectInfo.finalUrl || null,
                    redirectCount: redirectInfo.redirectCount || 0
                };
            });
            
            // PHƯƠNG PHÁP TỐI ƯU: Tạo danh sách các từ khóa duy nhất để tìm kiếm
            const uniqueKeywords = [...new Set(keywords)];
            
            // Tạo ánh xạ từ từ khóa đến các domain cần kiểm tra
            const keywordToDomains = new Map();
            
            // Nếu số lượng domain và từ khóa bằng nhau, giả định mỗi domain đi với một từ khóa
            if (domains.length === keywords.length) {
                for (let i = 0; i < domains.length; i++) {
                    if (!keywordToDomains.has(keywords[i])) {
                        keywordToDomains.set(keywords[i], []);
                    }
                    keywordToDomains.get(keywords[i]).push(domainInfo[i]);
                }
            } else {
                // Nếu số lượng không bằng nhau, kiểm tra mọi domain với mọi từ khóa
                uniqueKeywords.forEach(keyword => {
                    keywordToDomains.set(keyword, domainInfo);
                });
            }
            
            // Tạo danh sách các query để tìm kiếm
            const queries = uniqueKeywords.map(keyword => ({
                keyword: keyword
            }));
            
            // Thực hiện tìm kiếm theo batch
            const batchSize = DEFAULT_BATCH_SIZE;
            const batches = chunkArray(queries, batchSize);
            
            let completed = 0;
            const totalKeywords = uniqueKeywords.length;
            
            try {
                for (const batch of batches) {
                    // Cập nhật tiến trình
                    document.getElementById('progress-text').textContent = `${completed}/${totalKeywords}`;
                    const percent = Math.round((completed / totalKeywords) * 100);
                    document.getElementById('progress').style.width = `${percent}%`;
                    document.getElementById('progress').textContent = `${percent}%`;
                    
                    // Tìm kiếm batch
                    const searchResults = await searchGoogleBatch(batch);
                    
                    // Xử lý kết quả tìm kiếm
                    for (let i = 0; i < batch.length; i++) {
                        const keyword = batch[i].keyword;
                        const searchResult = searchResults[i];
                        
                        // Lấy danh sách domain cần kiểm tra cho từ khóa này
                        const domainsToCheck = keywordToDomains.get(keyword) || [];
                        
                        // Kiểm tra từng domain trong kết quả tìm kiếm
                        for (const domainData of domainsToCheck) {
                            let rank = null;
                            let matchedUrl = null;
                            
                            // Tạo danh sách các domain cần kiểm tra (bao gồm cả domain chuyển hướng)
                            let domainVariants = [domainData.cleanedDomain];
                            
                            // Thêm domain từ chuỗi chuyển hướng
                            if (domainData.redirectChain && domainData.redirectChain.length > 1) {
                                for (let j = 1; j < domainData.redirectChain.length; j++) {
                                    try {
                                        const redirectUrl = new URL(domainData.redirectChain[j].url);
                                        const redirectDomain = redirectUrl.hostname.replace(/^www\./, '');
                                        domainVariants.push(redirectDomain.toLowerCase());
                                    } catch (e) {
                                        console.error("Lỗi xử lý URL chuyển hướng:", e);
                                    }
                                }
                            }
                            
                            // Tìm kiếm trong kết quả Google
                            if (searchResult && searchResult.organic) {
                                for (let j = 0; j < searchResult.organic.length; j++) {
                                    const result = searchResult.organic[j];
                                    try {
                                        const resultUrl = new URL(result.link);
                                        const resultDomain = resultUrl.hostname.replace(/^www\./, '').toLowerCase();
                                        
                                        // Kiểm tra xem domain kết quả có khớp với domain cần kiểm tra không
                                        if (domainVariants.some(domain => resultDomain.includes(domain))) {
                                            rank = j + 1;
                                            matchedUrl = result.link;
                                            break;
                                        }
                                    } catch (e) {
                                        console.error("Lỗi xử lý URL kết quả:", e);
                                    }
                                }
                            }
                            
                            // Thêm kết quả
                            results.push({
                                domain: domainData.domain,
                                cleanedDomain: domainData.cleanedDomain,
                                keyword: keyword,
                                rank: rank,
                                matchedUrl: matchedUrl,
                                redirected: domainData.redirected,
                                redirectChain: domainData.redirectChain,
                                finalUrl: domainData.finalUrl,
                                redirectCount: domainData.redirectCount,
                                date: new Date().toLocaleDateString('vi-VN'),
                                location: document.getElementById('location').options[document.getElementById('location').selectedIndex].text
                            });
                        }
                    }
                    
                    // Hiển thị kết quả sau mỗi batch
                    displayResults();
                    
                    // Cập nhật số lượng hoàn thành
                    completed += batch.length;
                }
                
                // Cập nhật tiến trình thành 100%
                document.getElementById('progress-text').textContent = `${totalKeywords}/${totalKeywords}`;
                document.getElementById('progress').style.width = '100%';
                document.getElementById('progress').textContent = '100%';
                
                // Đếm số từ khóa trong top 10
                const top10Keywords = results.filter(result => result.rank !== null && result.rank <= 6);
                const top10Count = top10Keywords.length;
                
                // Hiển thị thông báo chúc mừng nếu có từ khóa trong top 10
                if (top10Count > 0) {
                    // Tạo thông báo dựa trên số lượng từ khóa trong top 10
                    const message = `Bạn có ${top10Count} từ khóa nằm trong top 6 Google!`;
                    
                    // Hiển thị thông báo chúc mừng
                    showCelebration(message);
                    
                    // Bắn pháo hoa nhiều lần tương ứng với số từ khóa top 10
                    for (let i = 0; i < top10Count; i++) {
                        setTimeout(() => {
                            showFireworks();
                        }, i * 3000); // Mỗi hiệu ứng pháo hoa cách nhau 3 giây
                    }
                }
                
                showSuccess(`Đã kiểm tra thành công ${results.length} kết quả`);
            } catch (error) {
                showError(`Lỗi: ${error.message}`);
            } finally {
                // Ẩn loading
                document.getElementById('loading').style.display = 'none';
                
                // Hiển thị nút sao chép nếu có kết quả
                if (results.length > 0) {
                    document.getElementById('results-actions').style.display = 'flex';
                }
            }
        }
        
        // Hàm hiển thị kết quả
        function displayResults() {
            const resultsDiv = document.getElementById('results');
            
            // Tạo bảng kết quả
            let tableHtml = `
                <table>
                    <thead>
                        <tr>
                            <th>STT</th>
                            <th>Domain</th>
                            <th>Từ khóa</th>
                            <th>Thứ hạng</th>
                            <th>URL khớp</th>
                            <th>Ngày kiểm tra</th>
                            <th>Vị trí</th>
                        </tr>
                    </thead>
                    <tbody>
            `;
            
            // Thêm các dòng kết quả
            results.forEach((result, index) => {
                // Xác định class cho thứ hạng
                let rankClass = 'rank-none';
                let rankDisplay = 'Không tìm thấy';
                
                if (result.rank !== null) {
                    rankDisplay = result.rank;
                    if (result.rank <= 10) {
                        rankClass = 'rank-good';
                    } else if (result.rank <= 20) {
                        rankClass = 'rank-medium';
                    } else {
                        rankClass = 'rank-bad';
                    }
                }
                
                // Tạo thông tin chuyển hướng
                let redirectInfo = '';
                if (result.redirected) {
                    // Tạo tooltip với thông tin chi tiết về chuỗi chuyển hướng
                    let redirectChainHtml = '';
                    if (result.redirectChain && result.redirectChain.length > 0) {
                        redirectChainHtml = '<div class="redirect-chain">';
                        result.redirectChain.forEach((redirect, idx) => {
                            const statusColor = redirect.status === 301 ? '#e74c3c' : '#3498db';
                            redirectChainHtml += `
                                <span class="redirect-chain-item" style="color: ${statusColor}">
                                    ${idx + 1}. ${redirect.url} (${redirect.status})
                                </span>
                            `;
                        });
                        redirectChainHtml += '</div>';
                    }
                    
                    redirectInfo = `
                        <div class="redirect-info">
                            <span class="tooltip">
                                Đã chuyển hướng (${result.redirectCount})
                                <span class="tooltiptext">
                                    ${redirectChainHtml || 'Không có thông tin chi tiết'}
                                </span>
                            </span>
                        </div>
                    `;
                }
                
                // Thêm dòng vào bảng
                tableHtml += `
                    <tr>
                        <td>${index + 1}</td>
                        <td>
                            ${result.domain}
                            ${redirectInfo}
                        </td>
                        <td>${result.keyword}</td>
                        <td class="${rankClass}">${rankDisplay}</td>
                        <td>${result.matchedUrl ? `<a href="${result.matchedUrl}" target="_blank">${result.matchedUrl}</a>` : 'N/A'}</td>
                        <td>${result.date}</td>
                        <td>${result.location}</td>
                    </tr>
                `;
            });
            
            // Đóng bảng
            tableHtml += `
                    </tbody>
                </table>
            `;
            
            // Hiển thị bảng
            resultsDiv.innerHTML = tableHtml;
        }
        
        // Hàm sao chép tất cả thứ hạng thành một cột
        function copyAllRanks() {
            // Tạo một chuỗi chứa tất cả thứ hạng, mỗi thứ hạng trên một dòng
            const ranksList = results.map(result => result.rank ? result.rank.toString() : 'N/A').join('\n');
            
            // Sao chép vào clipboard
            navigator.clipboard.writeText(ranksList)
                .then(() => {
                    showSuccess('Đã sao chép tất cả thứ hạng thành công!');
                })
                .catch(err => {
                    console.error('Lỗi khi sao chép: ', err);
                    showError('Không thể sao chép. Vui lòng thử lại.');
                });
        }

        // Sao chép báo cáo theo định dạng
        // function copyFormattedReport() {
        //     // Lấy ngày hiện tại
        //     const today = new Date();
        //     const day = today.getDate();
        //     const month = today.getMonth() + 1;
            
        //     // Lấy tên báo cáo từ input
        //     const reportName = document.getElementById('report-name').value.trim();
            
        //     // Lấy thông tin khu vực
        //     const locationElement = document.getElementById('location');
        //     const locationText = locationElement.options[locationElement.selectedIndex].text;
        //     const locationInfo = locationElement.value ? ` - ${locationText}` : '';
            
        //     // Bắt đầu với tiêu đề báo cáo
        //     let report = `Ngày ${day}/${month} - [${reportName}]\n============\n\n`;
            
        //     // Nhóm kết quả theo domain (sau khi đã làm sạch)
        //     const domainGroups = {};
            
        //     results.forEach(result => {
        //         const domain = result.cleanedDomain;
        //         if (!domainGroups[domain]) {
        //             domainGroups[domain] = {
        //                 originalDomain: result.domain,
        //                 cleanedDomain: domain,
        //                 keywords: [],
        //                 ranks: [],
        //                 redirected: result.redirected || false,
        //                 finalUrl: result.finalUrl || null,
        //                 redirectChain: result.redirectChain || null,
        //                 has301: false
        //             };
        //         }
                
        //         // Kiểm tra xem có chuyển hướng 301 không
        //         if (result.redirectChain && result.redirectChain.length > 0) {
        //             for (let i = 0; i < result.redirectChain.length; i++) {
        //                 if (result.redirectChain[i].status === 301) {
        //                     domainGroups[domain].has301 = true;
        //                     break;
        //                 }
        //             }
        //         }
                
        //         domainGroups[domain].keywords.push(result.keyword);
        //         domainGroups[domain].ranks.push(result.rank);
        //     });
            
        //     // Tạo báo cáo cho từng domain
        //     for (const domain in domainGroups) {
        //         const group = domainGroups[domain];
                
        //         // Thêm domain vào báo cáo (màu xanh)
        //         // Nếu có chuyển hướng 301, thêm đánh dấu [301]
        //         const redirectMark = group.has301 ? ' [301]' : '';
        //         report += `${domain}${redirectMark}\n`;
                
        //         // Nếu có chuyển hướng, hiển thị URL cuối cùng
        //         if (group.redirected && group.finalUrl) {
        //             // Lấy hostname từ finalUrl
        //             try {
        //                 const finalUrlObj = new URL(group.finalUrl);
        //                 const finalDomain = finalUrlObj.hostname;
        //                 report += `→ ${finalDomain}\n`;
        //             } catch (e) {
        //                 // Nếu không parse được URL, hiển thị toàn bộ finalUrl
        //                 report += `→ ${group.finalUrl}\n`;
        //             }
        //         }
                
        //         // Thêm từ khóa và thứ hạng
        //         for (let i = 0; i < group.keywords.length; i++) {
        //             const keyword = group.keywords[i];
        //             const rank = group.ranks[i] || 'N/A';
        //             report += `${keyword} - top ${rank}\n`;
        //         }
                
        //         report += '\n';
        //     }
            
        //     // Sao chép vào clipboard
        //     navigator.clipboard.writeText(report)
        //         .then(() => {
        //             showSuccess('Đã sao chép báo cáo định dạng thành công!');
        //         })
        //         .catch(err => {
        //             console.error('Lỗi khi sao chép: ', err);
        //             showError('Không thể sao chép. Vui lòng thử lại.');
        //         });
        // }
        // Sao chép báo cáo theo định dạng
function copyFormattedReport() {
    // Lấy ngày hiện tại
    const today = new Date();
    const day = today.getDate();
    const month = today.getMonth() + 1;
    
    // Lấy tên báo cáo từ input
    const reportName = document.getElementById('report-name').value.trim();
    
    // Lấy thông tin khu vực
    const locationElement = document.getElementById('location');
    const locationText = locationElement.options[locationElement.selectedIndex].text;
    const locationInfo = locationElement.value ? ` - ${locationText}` : '';
    
    // Bắt đầu với tiêu đề báo cáo
    let report = `Ngày ${day}/${month} - [${reportName}]\n============\n\n`;
    
    // Lấy danh sách domain và từ khóa ban đầu để giữ nguyên thứ tự
    const domainsText = document.getElementById('domains').value.trim();
    const keywordsText = document.getElementById('keywords').value.trim();
    
    const originalDomains = domainsText.split('\n')
        .map(domain => domain.trim())
        .filter(domain => domain.length > 0);
        
    const originalKeywords = keywordsText.split('\n')
        .map(keyword => keyword.trim())
        .filter(keyword => keyword.length > 0);
    
    // Tạo Map để lưu trữ thông tin domain đã xử lý
    const processedDomains = new Map();
    
    // Nếu số lượng domain và từ khóa bằng nhau, giả định mỗi domain đi với một từ khóa
    if (originalDomains.length === originalKeywords.length) {
        // Lặp qua danh sách domain theo thứ tự ban đầu
        for (let i = 0; i < originalDomains.length; i++) {
            const domain = originalDomains[i];
            const keyword = originalKeywords[i];
            
            // Tìm kết quả tương ứng
            const result = results.find(r => 
                r.domain === domain && r.keyword === keyword);
            
            if (result) {
                const cleanedDomain = result.cleanedDomain;
                
                // Kiểm tra xem domain đã được xử lý chưa
                if (!processedDomains.has(cleanedDomain)) {
                    processedDomains.set(cleanedDomain, {
                        originalDomain: domain,
                        keywords: [],
                        ranks: [],
                        redirected: result.redirected || false,
                        finalUrl: result.finalUrl || null,
                        redirectChain: result.redirectChain || null,
                        has301: false
                    });
                    
                    // Kiểm tra xem có chuyển hướng 301 không
                    if (result.redirectChain && result.redirectChain.length > 0) {
                        for (let j = 0; j < result.redirectChain.length; j++) {
                            if (result.redirectChain[j].status === 301) {
                                processedDomains.get(cleanedDomain).has301 = true;
                                break;
                            }
                        }
                    }
                }
                
                // Thêm từ khóa và thứ hạng vào domain
                const domainData = processedDomains.get(cleanedDomain);
                domainData.keywords.push(keyword);
                domainData.ranks.push(result.rank);
            }
        }
        
        // Tạo báo cáo theo thứ tự domain ban đầu
        const addedDomains = new Set();
        
        for (let i = 0; i < originalDomains.length; i++) {
            const domain = originalDomains[i];
            const { cleanedDomain } = cleanDomain(domain);
            
            // Nếu domain này đã được thêm vào báo cáo, bỏ qua
            if (addedDomains.has(cleanedDomain)) {
                continue;
            }
            
            // Đánh dấu domain đã được thêm vào báo cáo
            addedDomains.add(cleanedDomain);
            
            // Lấy thông tin domain
            const domainData = processedDomains.get(cleanedDomain);
            
            if (domainData) {
                // Thêm domain vào báo cáo
                const redirectMark = domainData.has301 ? ' [301]' : '';
                report += `${cleanedDomain}${redirectMark}\n`;
                
                // Nếu có chuyển hướng, hiển thị URL cuối cùng
                if (domainData.redirected && domainData.finalUrl) {
                    try {
                        const finalUrlObj = new URL(domainData.finalUrl);
                        const finalDomain = finalUrlObj.hostname;
                        report += `→ ${finalDomain}\n`;
                    } catch (e) {
                        report += `→ ${domainData.finalUrl}\n`;
                    }
                }
                
                // Thêm từ khóa và thứ hạng theo thứ tự
                for (let j = 0; j < domainData.keywords.length; j++) {
                    const keyword = domainData.keywords[j];
                    const rank = domainData.ranks[j] || 'N/A';
                    report += `${keyword} - top ${rank}\n`;
                }
                
                report += '\n';
            }
        }
    } else {
        // Nếu số lượng domain và từ khóa không bằng nhau
        // Nhóm kết quả theo domain (giữ nguyên thứ tự xuất hiện đầu tiên)
        const domainOrder = [];
        const domainGroups = {};
        
        results.forEach(result => {
            const domain = result.cleanedDomain;
            
            if (!domainGroups[domain]) {
                domainOrder.push(domain);
                domainGroups[domain] = {
                    originalDomain: result.domain,
                    cleanedDomain: domain,
                    keywords: [],
                    ranks: [],
                    redirected: result.redirected || false,
                    finalUrl: result.finalUrl || null,
                    redirectChain: result.redirectChain || null,
                    has301: false
                };
            }
            
            // Kiểm tra xem có chuyển hướng 301 không
            if (result.redirectChain && result.redirectChain.length > 0) {
                for (let i = 0; i < result.redirectChain.length; i++) {
                    if (result.redirectChain[i].status === 301) {
                        domainGroups[domain].has301 = true;
                        break;
                    }
                }
            }
            
            domainGroups[domain].keywords.push(result.keyword);
            domainGroups[domain].ranks.push(result.rank);
        });
        
        // Tạo báo cáo theo thứ tự domain đã lưu
        for (const domain of domainOrder) {
            const group = domainGroups[domain];
            
            // Thêm domain vào báo cáo
            const redirectMark = group.has301 ? ' [301]' : '';
            report += `${domain}${redirectMark}\n`;
            
            // Nếu có chuyển hướng, hiển thị URL cuối cùng
            if (group.redirected && group.finalUrl) {
                try {
                    const finalUrlObj = new URL(group.finalUrl);
                    const finalDomain = finalUrlObj.hostname;
                    report += `→ ${finalDomain}\n`;
                } catch (e) {
                    report += `→ ${group.finalUrl}\n`;
                }
            }
            
            // Thêm từ khóa và thứ hạng
            for (let i = 0; i < group.keywords.length; i++) {
                const keyword = group.keywords[i];
                const rank = group.ranks[i] || 'N/A';
                report += `${keyword} - top ${rank}\n`;
            }
            
            report += '\n';
        }
    }
    
    // Sao chép vào clipboard
    navigator.clipboard.writeText(report)
        .then(() => {
            showSuccess('Đã sao chép báo cáo định dạng thành công!');
        })
        .catch(err => {
            console.error('Lỗi khi sao chép: ', err);
            showError('Không thể sao chép. Vui lòng thử lại.');
        });
}

        // Hàm hiển thị lỗi
        function showError(message) {
            const errorDiv = document.getElementById('error');
            errorDiv.textContent = message;
            errorDiv.style.display = 'block';
            document.getElementById('success').style.display = 'none';
        }
        
        // Hàm hiển thị thông báo thành công
        function showSuccess(message) {
            const successDiv = document.getElementById('success');
            successDiv.textContent = message;
            successDiv.style.display = 'block';
            document.getElementById('error').style.display = 'none';
        }
    </script>
</body>
</html>
# Step 0: Navigate to your bench directory
cd frappe-bench

# Step 1: Backup your site
bench --site your-site-name backup

# Step 2: Install system dependencies (once)
sudo apt update
sudo apt install -y pkg-config libmariadb-dev

# Step 3: (Optional) Reset current state to avoid merge issues
bench update --reset

# Step 4: Switch ERPNext and Frappe to 'develop'
bench switch-to-branch develop frappe erpnext --upgrade

# Step 5: Install Python and JS requirements
bench setup requirements

# Step 6: Migrate database and build assets
bench --site your-site-name migrate
bench build

# Step 7: Restart bench (especially in production)
bench restart

# Step 8: (Optional) Disable maintenance mode if active
bench set-maintenance-mode off
@media (max-width: 600px) {
  .mr-home { grid-template-columns: repeat(2, 1fr); }
}
@media (max-width: 400px) {
  .mr-home { grid-template-columns: repeat(1, 1fr); }
}
grid-template-columns: repeat(4, 110px);
grid-template-rows: repeat(4, 100px);
grid-auto-flow: dense;
.mr-home .mr-home__image:nth-child(6)
<If check="{Get local=show_excerpt}" value=true>
  <div class="tt-item__content__info">
    <Field excerpt auto="true" />
      </div>
</If>
<Loop type=attachment include="{Loop control=decoration_image}{Field id /}{/Loop}">
  <img src="{Field url size=large}" alt="{Field alt}" class="wp-image-{Field id}" srcset="{Field srcset size=large}" sizes="{Field sizes size=large}" loading="eager" />
</Loop>
<Loop type=learndash_student id=current >
  <If loop type="sfwd-courses" enrolled="true">
    <List available-courses>
      <Loop type="learndash_course" enrolled="true" completion_status="started">
        <Item><Field id /></Item>
      </Loop>
    </List>
    <List course-list>
      <Loop type=learndash_user_activity activity_event=started activity_status="started" orderby=activity_updated>
        <Set logic=course-exists all="true">
          <If check="{Get list=available-courses}" includes value="{Field course_id}">true<Else />false</If>
          <If field=activity_type value=course>true<Else />false</If>
        </Set>
        <If logic=course-exists><Item><Field course_id /></Item></If>
      </Loop>
    </List>
    <Set latest_course_id>
      <If list=course-list>
        <Loop list=course-list count=1><Field /></Loop>
        <Else />
        <Loop type="learndash_course" enrolled="true" completion_status="open,started" count=1><Field id /></Loop>
      </If>
    </Set>
  </If>
</Loop>
/* Bricks initial CSS */
.gform-footer {
    margin-top: 15px !important;
}
.brxe-text-basic ul,
.brxe-text ul {
    padding-left: 18px;
}
#top .gform-theme--foundation .gform_fields {
    row-gap: 15px;
}
.gform_body input::placeholder {
    color: #666;
}
p.gform_required_legend {
    display: none !important;
}
.gform-footer input[type="submit"] {
    text-transform: uppercase !important;
    font-size: 18px !important;
    letter-spacing: 1px !important;
    background-color: var(--primary) !important;
    padding: 12px 30px !important;
    transition: .5s !important;
}
.gform-footer input[type="submit"]:hover {
    background-color: #000 !important;
}
.faq-item.brx-open .faq-icon {
    transform: rotate(90deg);
    color: #fff
}
star

Fri May 16 2025 05:28:12 GMT+0000 (Coordinated Universal Time)

@codejlw #javascript

star

Fri May 16 2025 02:39:19 GMT+0000 (Coordinated Universal Time) https://github.com/TaylorResearchLab/Petagraph

@metaphotonic

star

Thu May 15 2025 14:48:03 GMT+0000 (Coordinated Universal Time)

@StefanoGi

star

Thu May 15 2025 14:44:36 GMT+0000 (Coordinated Universal Time)

@chivchav

star

Thu May 15 2025 09:38:10 GMT+0000 (Coordinated Universal Time)

@rrajatssharma

star

Thu May 15 2025 09:32:32 GMT+0000 (Coordinated Universal Time)

@rrajatssharma

star

Thu May 15 2025 09:20:06 GMT+0000 (Coordinated Universal Time) https://www.coinsclone.com/features-of-cryptocurrency-exchange/

@CharleenStewar #featuresofcryptoexchange #cryptocurrencyexchange

star

Thu May 15 2025 08:36:58 GMT+0000 (Coordinated Universal Time) https://www.mirayogashala.com/100-hour-yoga-teacher-training-in-rishikesh.php

@mirayogshala12 #php

star

Thu May 15 2025 07:05:10 GMT+0000 (Coordinated Universal Time) https://www.beleaftechnologies.com/amazon-clone

@raydensmith #amazonclone #e-commerceclone

star

Thu May 15 2025 06:59:30 GMT+0000 (Coordinated Universal Time)

@jrray ##linux ##terminal

star

Wed May 14 2025 13:40:52 GMT+0000 (Coordinated Universal Time) https://github.com/kiranpalsingh1806/DSA-Code-Snippets

@gohilghanu

star

Wed May 14 2025 12:24:46 GMT+0000 (Coordinated Universal Time) https://www.appclonex.com/trustwallet-clone-script

@riyageorge0895 #trustwalletclone

star

Wed May 14 2025 09:07:47 GMT+0000 (Coordinated Universal Time) https://rishikeshyogkendra.com/7-days-prenatal-postnatal-yoga-course-in-rishikesh.php

@Rskyogkendra #yoga #yttc #yogateacher #yogaschool #yogattc #education #yttcinrishikesh #yogattcinrishikesh

star

Wed May 14 2025 08:57:45 GMT+0000 (Coordinated Universal Time) https://www.mirayogashala.com/200-hour-yoga-teacher-training-in-rishikesh.php

@mirayogashala10

star

Wed May 14 2025 07:01:00 GMT+0000 (Coordinated Universal Time) https://www.roblox.com/

@mdevil1619

star

Tue May 13 2025 13:27:44 GMT+0000 (Coordinated Universal Time)

@reiddd #javascript

star

Tue May 13 2025 12:44:37 GMT+0000 (Coordinated Universal Time) https://dpbosse.net/pannel/PADMINIMORNING/159

@dpbossenet

star

Tue May 13 2025 12:20:42 GMT+0000 (Coordinated Universal Time) https://www.yudiz.com/poker-game-development-company/

@yudizsolutions #pokergamedevelopment #pokergamedevelopmentcompany

star

Tue May 13 2025 11:12:24 GMT+0000 (Coordinated Universal Time)

@dev_shubham14

star

Tue May 13 2025 09:42:38 GMT+0000 (Coordinated Universal Time) https://www.coinsclone.com/crypto-wallet-script/

@CharleenStewar #cryptowallet script

star

Tue May 13 2025 09:20:39 GMT+0000 (Coordinated Universal Time) https://www.beleaftechnologies.com/bc-game-clone-script

@raydensmith #bcgameclone #bcgame

star

Tue May 13 2025 08:51:44 GMT+0000 (Coordinated Universal Time)

@Tilores #entityresolution #cbgc

star

Tue May 13 2025 08:49:47 GMT+0000 (Coordinated Universal Time)

@Tilores #entityresolution #cbgc

star

Tue May 13 2025 08:44:48 GMT+0000 (Coordinated Universal Time)

@Tilores #entityresolution #cbgc

star

Tue May 13 2025 08:41:19 GMT+0000 (Coordinated Universal Time)

@Tilores #entityresolution #cbgc

star

Tue May 13 2025 08:34:12 GMT+0000 (Coordinated Universal Time)

@Tilores #entityresolution #cbgc

star

Mon May 12 2025 22:24:36 GMT+0000 (Coordinated Universal Time)

@Calideebynyc

star

Mon May 12 2025 18:25:31 GMT+0000 (Coordinated Universal Time)

@password

star

Mon May 12 2025 12:40:15 GMT+0000 (Coordinated Universal Time)

@RehmatAli2024

star

Sun May 11 2025 21:25:00 GMT+0000 (Coordinated Universal Time)

@FOHWellington

star

Sun May 11 2025 19:27:26 GMT+0000 (Coordinated Universal Time)

@BilalRaza12

star

Fri May 09 2025 09:44:35 GMT+0000 (Coordinated Universal Time) https://www.coinsclone.com/exodus-wallet-clone-script/

@CharleenStewar #exodus wallet clone script #exodus wallet clone #exodus wallet

star

Fri May 09 2025 06:36:58 GMT+0000 (Coordinated Universal Time)

@IfedayoAwe

star

Thu May 08 2025 17:51:35 GMT+0000 (Coordinated Universal Time) https://www.thiscodeworks.com/extension/initializing?newuser

@mmdyan

star

Thu May 08 2025 14:18:13 GMT+0000 (Coordinated Universal Time) https://www.appclonex.com/trustwallet-clone-script

@riyageorge0895 #trustwalletclone

star

Thu May 08 2025 13:44:11 GMT+0000 (Coordinated Universal Time) https://www.roblox.com/my/account

@Gay

star

Thu May 08 2025 11:33:46 GMT+0000 (Coordinated Universal Time)

@vishalsingh21

star

Thu May 08 2025 07:41:26 GMT+0000 (Coordinated Universal Time) https://www.uniccm.com/blog/the-importance-of-hydrostatic-pressure-in-construction

@dwightjasper

star

Thu May 08 2025 00:47:26 GMT+0000 (Coordinated Universal Time)

@FOHWellington

star

Wed May 07 2025 16:53:12 GMT+0000 (Coordinated Universal Time)

@thanhsonnguyen #html #javascript

star

Wed May 07 2025 16:16:06 GMT+0000 (Coordinated Universal Time)

@Taimoor

star

Wed May 07 2025 12:27:09 GMT+0000 (Coordinated Universal Time)

@Sphynx

star

Wed May 07 2025 12:26:00 GMT+0000 (Coordinated Universal Time)

@Sphynx

star

Wed May 07 2025 12:24:50 GMT+0000 (Coordinated Universal Time)

@Sphynx

star

Wed May 07 2025 12:24:00 GMT+0000 (Coordinated Universal Time)

@Sphynx

star

Wed May 07 2025 12:22:44 GMT+0000 (Coordinated Universal Time)

@Sphynx

star

Wed May 07 2025 12:17:41 GMT+0000 (Coordinated Universal Time)

@Sphynx

star

Wed May 07 2025 09:27:30 GMT+0000 (Coordinated Universal Time)

@omnixima #css

star

Wed May 07 2025 09:26:25 GMT+0000 (Coordinated Universal Time) https://www.coinsclone.com/white-label-digital-asset-exchange/

@CharleenStewar #whitelabeldigitalassetexchange

Save snippets that work with our extensions

Available in the Chrome Web Store Get Firefox Add-on Get VS Code extension