Snippets Collections
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
}
The Betfair Clone Script provides end-to-end betting solutions, making it a reliable choice for modern betting businesses.  A Betfair clone script is a systematic way of creating a betting platform that is similar to Betfair.  Business owners regard it as trustworthy for betting actions. Addus Technologies provides scalable, API-integrated solutions that attract business owners and serious investors in today's digital betting industry.
I recently used My Assignment Help Australia and was genuinely impressed with the quality and professionalism. The team delivered well-researched content right on time, which really helped me meet my deadline. Their support system is responsive, and the writers clearly understand Australian academic standards. Highly recommend it to any student needing dependable assignment help!
  
  https://myassignmenthelp.expert/
#!/bin/bash

SOURCE_PAGE_ID=280606
CSV_FILE="cities.csv"

tail -n +2 "$CSV_FILE" | while IFS=',' read -r city state
do
  slug_city=$(echo "$city" | tr '[:upper:]' '[:lower:]' | tr ' ' '-')
  post_title="Web App Development Company $city"
  post_slug="web-app-development-company-${slug_city}"

  new_page_id=$(wp post create --from-post=$SOURCE_PAGE_ID --post_type=page --post_status=draft \
    --post_title="$post_title" --post_name="$post_slug" --porcelain)

  echo "✅ Created: $post_title → Page ID $new_page_id"

  # Fetch base _elementor_data in JSON
  base_data=$(wp post meta get $SOURCE_PAGE_ID _elementor_data --format=json)

  # Replace placeholders in JSON safely
  updated_data=$(echo "$base_data" | sed "s/{City}/$city/g" | sed "s/{State}/$state/g")

  # Save to temporary JSON file
  temp_file=$(mktemp /tmp/elementor_data.XXXXXX.json)
  echo "$updated_data" > "$temp_file"

  # Pass JSON via stdin (avoids shell arg limit)
  wp post meta update "$new_page_id" _elementor_data --format=json < "$temp_file"
  rm "$temp_file"

  # Update Elementor mode
  wp post meta update $new_page_id _elementor_edit_mode builder
  wp post meta update $new_page_id _elementor_template_type page

  # Update Yoast SEO
  wp post meta update $new_page_id _yoast_wpseo_title "Web App Development Company $city, $state | MMC Global"
  wp post meta update $new_page_id _yoast_wpseo_metadesc "Leading web app development company in $city, $state that delivers problem-solving and high-performing custom solutions for all businesses."
  wp post meta update $new_page_id _yoast_wpseo_focuskw "Web App Development $city"

  # Open Graph / Twitter
  wp post meta update $new_page_id _yoast_wpseo_opengraph-title "Web App Development Company $city, $state | MMC Global"
  wp post meta update $new_page_id _yoast_wpseo_opengraph-description "Leading web app development company in $city, $state that delivers problem-solving and high-performing custom solutions for all businesses."
  wp post meta update $new_page_id _yoast_wpseo_twitter-title "Web App Development Company $city, $state | MMC Global"
  wp post meta update $new_page_id _yoast_wpseo_twitter-description "Leading web app development company in $city, $state that delivers problem-solving and high-performing custom solutions for all businesses."

done
/*
Theme Name:   ThemeName
Theme URI:    https://www.hartmann-kommunikation.de
Description:  WordPress Theme fuer AuftraggeberName
Author:       Sebastian Hartmann
Author URI:   https://www.hartmann-kommunikation.de
Template:     (parent)themename
Version:      0.1
License:      single commercial
License URI:  https://www.hartmann-kommunikation.de
Tags:         tag
Text Domain:  themename
*/


/*===========================================================
/* Basics
=============================================================*/
/* Semantic Content Template */
<!DOCTYPE html> 
<html itemscope itemtype="https://schema.org/Article" 
lang="" dir=""> 
<head> 
<title itemprop="name"></title> 
<meta itemprop="description" name="description" 
content=""> 
<meta itemprop="author" name="author" content=""> 
</head> 
<body> 
<article> 
<header> 
<h1 itemprop="headline"></h1> 
<time itemprop="datePublished" datetime=""></time> 
<p><a itemprop="author" href=""></a></p> 
</header> 
<div itemprop="about"></div> 
<div itemprop="articleBody"> 
<!-- The main body of the article goes here --> 
</div> 
<footer> 
<time itemprop="dateModified" datetime=""></time> 
<section itemscope 
itemtype="http://schema.org/WebPage"> 
<!-- Section heading--> 
<h2></h2> 
<p><a itemprop="relatedLink" href=""></a></p> 
</section> 
</footer> 
</article> 
</body> 
</html> 
<!-- Schema Artikel -->
<html itemscope itemtype="http://schema.org/Article">
<head>
<meta itemprop="name" content="Just a test article...">
<meta itemprop="description" content="Just testing the way that articles appear in content...">
<meta itemprop="image" content="http://wheresgus.com/schema/article.png">
<meta itemprop="url" content="http://wheresgus.com/schema/article.html">
<meta itemprop="author" content="Gus Class">
<meta itemprop="contentRating" content="MPAA G">
<meta itemprop="copyrightYear" content="2012">
<meta itemprop="dateCreated" content="20120703">
<meta itemprop="datePublished" content="20120703">
</head>

<body>
<p>Note how this content is now ignored because schema markup is specified.</p>
</body>
</html>
<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>_title_</title>
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
</head>

<body>
  <header>
    _headerContent_
  </header>

  <main>
    _mainContent_
  </main>

  <footer>
    _footerContent_
  </footer>

  <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
  <script>if (!window.jQuery) { document.write("<script src=\"https://code.jquery.com/jquery-3.2.1.min.js\"><\/script>"); }</script>
  <script src="_scriptFile_"></script>
</body>
</html>
/*sticky text panel v builderu*/

.mce-panel .mce-stack-layout-item.mce-first{
    position: sticky!important;
    top: -60px;
}
.et-fb-modal--expanded .mce-panel .mce-stack-layout-item.mce-first{
    top: -24px!important;
}
<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>_title_</title>

  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta name="author" content="_author_" >
  <meta name="designer" content="_designer_" >
  <meta name="description" content="_description_" >
  <meta name="keywords" content="_keywords_" >

  <meta name="application-name" content="_appName_">
  <meta name="apple-mobile-web-app-title" content="_appName_">
  <meta name="msapplication-config" content="_browserConfigFile_">
  <meta name="theme-color" content="_themeColor_">
  <link rel="apple-touch-icon" href="_appleIconFile_" sizes="180x180">
  <link rel="icon" type="image/png" href="_faviconFile32x32_" sizes="32x32">
  <link rel="icon" type="image/png" href="_faviconFile16x16_" sizes="16x16">
  <link rel="manifest" href="_manifestFile_">
  <link rel="mask-icon" href="_maskIconFile_">
  <link rel="shortcut icon" href="_faviconIcoFile_">

  <link rel="stylesheet" type="text/css" href="_styleFile_" >
</head>

<body>
  <header>
    _headerContent_
  </header>

  <main>
    _mainContent_
  </main>

  <footer>
    _footerContent_
  </footer>

  <script src="_scriptFile_"></script>
  <noscript>
    _noJavaScriptSupportMessage_
  </noscript>
</body>
</html>
<?php
/*
Plugin Name: Functionality Plugin for websitename
Plugin URI: https://www.hartmann-kommunikation.de/
Description: Moves some functions away from functions.php
Author: Sebastian Hartmann
Version: 1.0
Author URI: https://www.hartmann-kommunikation.de/
License: single commercial, © Sebastian Hartmann, 2018
*/

// functions hier einfügen

?>
<?php

/**
 * Storefront automatically loads the core CSS even if using a child theme as it is more efficient
 * than @importing it in the child theme style.css file.
 *
 * Uncomment the line below if you'd like to disable the Storefront Core CSS.
 *
 * If you don't plan to dequeue the Storefront Core CSS you can remove the subsequent line and as well
 * as the sf_child_theme_dequeue_style() function declaration.
 */
//add_action( 'wp_enqueue_scripts', 'sf_child_theme_dequeue_style', 999 );

/**
 * Dequeue the Storefront Parent theme core CSS
 */
function sf_child_theme_dequeue_style() {
    wp_dequeue_style( 'storefront-style' );
    wp_dequeue_style( 'storefront-woocommerce-style' );
}

/**
 * Note: DO NOT! alter or remove the code above this text and only add your custom PHP functions below this text.
 */


https://www.peeayecreative.com/how-to-add-text-or-symbols-before-and-after-the-divi-number-counter/
/*Menu znevididelneni pred scrollem - zviditelneni sticky menu po scrollu*****do sekce s menu vlozit tridu: pa-header-hide-before-scroll......a nastavit menu na sticky*/
/*CSS to set the header hidden until scroll*/
header.et-l.et-l--header {
	height: 0;
}

.pa-header-hide-before-scroll {
	height: 0px;
	transform: translateX(0px) translateY(-300px);
	transition: transform 800ms ease 0ms, height 800ms ease 0ms;
	visibility: hidden;
}

.pa-scroll-header {
	height: auto !important;
	transform: translateX(0px) translateY(0px) !important;
	transition: transform 800ms ease 0ms, height 800ms ease 0ms;
	visibility: visible;
}

.pa-header-hide-before-scroll .et_pb_menu .et_pb_menu__wrap {
	display: none !important;
}

.pa-header-hide-before-scroll.pa-scroll-header .et_pb_menu .et_pb_menu__wrap {
	display: flex !important;
}


//Jquery vlozit do scripts.js
    jQuery(document).ready(function() {
        jQuery(window).scroll(function() {
            var scroll = jQuery(window).scrollTop();
            if (scroll >= 1) {
                jQuery(".pa-header-hide-before-scroll").addClass("pa-scroll-header");
            } else {
                jQuery(".pa-header-hide-before-scroll").removeClass("pa-scroll-header");
            }
        });
    });
/*zvetseni vysky HTML textu v builderu*/
.et-db #et-boc .et-l .et-fb-option--tiny-mce .et-fb-tinymce-html-input {
	height: 400px;
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

star

Wed May 07 2025 08:39:44 GMT+0000 (Coordinated Universal Time) https://www.addustechnologies.com/blog/betfair-clone-script

@Seraphina

star

Wed May 07 2025 07:17:25 GMT+0000 (Coordinated Universal Time) https://dpbosse.net/pannel/FASTMUMBAI/161

@dpbossenet

star

Wed May 07 2025 04:55:53 GMT+0000 (Coordinated Universal Time) https://myassignmenthelp.expert/

@sgold7593

star

Tue May 06 2025 17:28:38 GMT+0000 (Coordinated Universal Time) https://winscript.cc/

@Curable1600 #windows

star

Tue May 06 2025 13:09:15 GMT+0000 (Coordinated Universal Time) https://appticz.com/taxi-booking-clone-script

@davidscott

star

Tue May 06 2025 11:39:19 GMT+0000 (Coordinated Universal Time)

@BilalRaza12

star

Tue May 06 2025 09:53:07 GMT+0000 (Coordinated Universal Time)

@Sebhart #wordpress #css

star

Tue May 06 2025 09:52:05 GMT+0000 (Coordinated Universal Time)

@Sebhart #markup #html

star

Tue May 06 2025 09:51:09 GMT+0000 (Coordinated Universal Time)

@Sebhart #markup #html

star

Tue May 06 2025 09:48:40 GMT+0000 (Coordinated Universal Time)

@Sebhart #markup #html

star

Tue May 06 2025 09:47:52 GMT+0000 (Coordinated Universal Time)

@hedviga

star

Tue May 06 2025 09:47:51 GMT+0000 (Coordinated Universal Time)

@Sebhart #markup #html

star

Tue May 06 2025 09:46:44 GMT+0000 (Coordinated Universal Time)

@Sebhart #php #wordpress #functions #plugin

star

Tue May 06 2025 09:45:18 GMT+0000 (Coordinated Universal Time)

@Sebhart #php #wordpress #functions #storefront #woocommerce #theme

star

Tue May 06 2025 09:43:54 GMT+0000 (Coordinated Universal Time)

@hedviga

star

Tue May 06 2025 09:40:27 GMT+0000 (Coordinated Universal Time)

@hedviga

star

Tue May 06 2025 09:36:17 GMT+0000 (Coordinated Universal Time)

@hedviga

Save snippets that work with our extensions

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