Thứ Năm, 20 tháng 8, 2026

smooth scroll

Smooth Scroll

 
<button class="smooth-scroll">

    Liên hệ với nhà cung cấp

</button>

// +---------------------+

// | Click div.smooth-scroll chạy xuống div.inquiry-msg-wrapper

// +---------------------+

<div class="inquiry-msg-wrapper">

    Content suppliers    

</div>


**** CSS

html {

  scroll-behavior: smooth;

}

.btn-inquiry {

  cursor: pointer;

  transition: transform 0.2s ease, opacity 0.2s ease;

}


.btn-inquiry:hover {

  opacity: 0.9;

  transform: translateY(-2px); 

  /* Hiệu ứng nhích nhẹ nút khi di chuột */

}


**** JavaScript

$(document).ready(function() {

    $('.smooth-scroll').on('click', function(e) {

        e.preventDefault();        

        // Lấy vị trí top của phần tử mục tiêu

        var targetOffset = $('.inquiry-msg-wrapper').offset().top;        

        // Nếu menu của bạn là menu cố định (fixed header), hãy trừ đi chiều cao menu tại đây (ví dụ: - 70)

        var headerHeight = 0; 

        // Thực hiện hiệu ứng cuộn mượt trong 800ms

        $('html, body').animate({

            scrollTop: targetOffset - headerHeight

        }, 800);

    });

});

360° Virtual Tour


 

Để làm hình ảnh panorama 360 độ và tích hợp trình xem tương tác (360° Virtual Tour) giống trong ảnh lên website, bạn cần thực hiện theo 2 bước chính dưới đây.

Bước 1: Chụp và chuẩn bị ảnh 360 độ (Equirectangular)

  • Chụp bằng điện thoại/máy ảnh: Sử dụng ứng dụng chụp Panorama 360 (Google Street View, Polycam, Google Camera) hoặc chụp từ flycam/máy ảnh panorama chuyên dụng (Insta360, Ricoh Theta).

  • Định dạng ảnh: Xuất ảnh dưới dạng panorama toàn cảnh góc nhìn 360° × 180° (thường có tỉ lệ 2:1, định dạng .jpg hoặc .png).

Bước 2: Nhúng tính năng tương tác 360° vào Website

Bạn có thể chọn 1 trong 2 cách triển khai tùy thuộc vào nền tảng website của bạn:

Cách 1: Sử dụng thư viện JavaScript (Dành cho mã nguồn tự viết / HTML-JS) Dùng thư viện mã nguồn mở Pannellum (miễn phí, siêu nhẹ và phổ biến nhất).

  1. Thêm thư viện vào tệp HTML của bạn:

<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/pannellum@2.5.6/build/pannellum.css"/>
<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/pannellum@2.5.6/build/pannellum.js"></script>
  1. Tạo thẻ div chứa khung nhìn và thêm đoạn script cấu hình:

<div id="panorama" style="width: 100%; height: 400px;"></div>

<script>
pannellum.viewer('panorama', {
    "type": "equirectangular",
    "panorama": "duong-dan-den-anh-360.jpg",
    "autoLoad": true,
    "autoRotate": -2
});
</script>

Cách 2: Sử dụng Plugin hoặc Nền tảng có sẵn (Không cần viết code)

  • Website WordPress: Cài đặt các plugin như WP VR - 360 Panorama Tour hoặc iPanorama 360. Bạn chỉ cần tải ảnh 360 lên, plugin sẽ tự tạo trình xem tương tác và cung cấp Shortcode để chèn vào bất kỳ đâu trên trang.

  • Nền tảng nhúng đám mây: Tải ảnh lên các dịch vụ như Kuula.co hoặc Momento360, sau đó sao chép đoạn mã <iframe> được tạo tự động và dán vào website.

Chủ Nhật, 9 tháng 8, 2026

Fancybox - Swiper - Zoomin


- Demo : ketsathalan

<!DOCTYPE html>
<html lang="vi">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Product Gallery Zoom & Fancybox</title>

  <!-- Swiper CSS -->
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/swiper@11/swiper-bundle.min.css" />
  <!-- Fancybox CSS -->
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fancyapps/ui@5.0/dist/fancybox/fancybox.css" />

  <style>
    * {
      box-sizing: border-box;
      margin: 0;
      padding: 0;
    }

    body {
      font-family: Arial, sans-serif;
      padding: 40px;
      background-color: #f5f5f5;
    }

    .product-gallery-wrapper {
      display: flex;
      gap: 20px;
      max-width: 1000px;
      margin: 0 auto;
      position: relative;
    }

    /* Container bên trái chứa Ảnh lớn + Thumbnails */
    .gallery-left {
      width: 450px;
      position: relative;
    }

    /* Khung ảnh chính */
    .main-image-container {
      width: 100%;
      height: 450px;
      border: 1px solid #ddd;
      position: relative;
      overflow: hidden;
      cursor: pointer;
      background: #fff;
    }

    .main-image-container img {
      width: 100%;
      height: 100%;
      object-fit: cover;
      display: block;
    }

    /* Khung Lens di chuyển trên ảnh chính */
    .zoom-lens {
      position: absolute;
      width: 150px;
      height: 150px;
      border: 1px solid #999;
      background-color: rgba(255, 255, 255, 0.4);
      display: none;
      pointer-events: none; /* Cho phép click xuyên qua lens */
    }

    /* Khung hiển thị Zoom kết quả (Bên phải) */
    .zoom-result {
      position: absolute;
      top: 0;
      left: 470px; /* Nằm ngay cạnh gallery-left */
      width: 450px;
      height: 450px;
      border: 1px solid #ccc;
      background-repeat: no-repeat;
      background-color: #fff;
      display: none;
      z-index: 99;
      box-shadow: 0 4px 10px rgba(0,0,0,0.15);
    }

    /* Slider ảnh thu nhỏ (Thumbnails) */
    .thumb-slider-container {
      margin-top: 15px;
      position: relative;
      padding: 0 30px; /* Dành khoảng trống cho nút Prev/Next */
    }

    .swiper-slide {
      border: 2px solid transparent;
      border-radius: 4px;
      overflow: hidden;
      cursor: pointer;
      height: 70px;
      opacity: 0.7;
      transition: all 0.2s ease;
    }

    .swiper-slide-thumb-active,
    .swiper-slide:hover {
      border-color: #d71921;
      opacity: 1;
    }

    .swiper-slide img {
      width: 100%;
      height: 100%;
      object-fit: cover;
    }

    /* Nút di chuyển Prev / Next */
    .swiper-button-next,
    .swiper-button-prev {
      width: 24px;
      height: 24px;
      color: #333;
    }

    .swiper-button-next::after,
    .swiper-button-prev::after {
      font-size: 14px;
      font-weight: bold;
    }

    .swiper-button-prev { left: 0; }
    .swiper-button-next { right: 0; }
  </style>
</head>
<body>

  <div class="product-gallery-wrapper">
    <!-- Cột bên trái: Ảnh chính & Slider thu nhỏ -->
    <div class="gallery-left">
      <!-- Ảnh chính -->
      <div class="main-image-container" id="mainImgContainer">
        <div class="zoom-lens" id="zoomLens"></div>
        <img id="mainImage" src="https://picsum.photos/id/1018/1000/1000" alt="Sản phẩm chính">
      </div>

      <!-- Thumbnails Carousel (Swiper) -->
      <div class="thumb-slider-container">
        <div class="swiper thumbSwiper">
          <div class="swiper-wrapper">
            <div class="swiper-slide">
              <img src="https://picsum.photos/id/1018/1000/1000" alt="Ảnh 1">
            </div>
            <div class="swiper-slide">
              <img src="https://picsum.photos/id/1015/1000/1000" alt="Ảnh 2">
            </div>
            <div class="swiper-slide">
              <img src="https://picsum.photos/id/1019/1000/1000" alt="Ảnh 3">
            </div>
            <div class="swiper-slide">
              <img src="https://picsum.photos/id/1025/1000/1000" alt="Ảnh 4">
            </div>
            <div class="swiper-slide">
              <img src="https://picsum.photos/id/1039/1000/1000" alt="Ảnh 5">
            </div>
            <!-- Ảnh thứ 6 và 7 để kích hoạt chuyển slider -->
            <div class="swiper-slide">
              <img src="https://picsum.photos/id/1043/1000/1000" alt="Ảnh 6">
            </div>
            <div class="swiper-slide">
              <img src="https://picsum.photos/id/1050/1000/1000" alt="Ảnh 7">
            </div>
          </div>
        </div>
        <!-- Nút điều hướng -->
        <div class="swiper-button-next"></div>
        <div class="swiper-button-prev"></div>
      </div>
    </div>

    <!-- Khung hiển thị Zoom bên phải -->
    <div class="zoom-result" id="zoomResult"></div>
  </div>

  <!-- Swiper JS -->
  <script src="https://cdn.jsdelivr.net/npm/swiper@11/swiper-bundle.min.js"></script>
  <!-- Fancybox JS -->
  <script src="https://cdn.jsdelivr.net/npm/@fancyapps/ui@5.0/dist/fancybox/fancybox.umd.js"></script>

  <script>
    // 1. Cấu hình Swiper Slider cho thumbnails (Hiển thị 5 ảnh, vượt quá 5 sẽ scroll/next)
    const swiper = new Swiper('.thumbSwiper', {
      slidesPerView: 5,
      spaceBetween: 8,
      navigation: {
        nextEl: '.swiper-button-next',
        prevEl: '.swiper-button-prev',
      },
    });

    const mainImgContainer = document.getElementById('mainImgContainer');
    const mainImage = document.getElementById('mainImage');
    const zoomLens = document.getElementById('zoomLens');
    const zoomResult = document.getElementById('zoomResult');
    const slides = document.querySelectorAll('.thumbSwiper .swiper-slide');

    // Mặc định active slide đầu tiên
    slides[0].classList.add('swiper-slide-thumb-active');

    // Danh sách URL ảnh dùng cho Fancybox
    let galleryItems = Array.from(slides).map(slide => {
      return { src: slide.querySelector('img').src, type: "image" };
    });

    let currentActiveIndex = 0;

    // 2. Chuyển đổi ảnh chính khi click vào thumbnail
    slides.forEach((slide, index) => {
      slide.addEventListener('click', () => {
        document.querySelector('.swiper-slide-thumb-active')?.classList.remove('swiper-slide-thumb-active');
        slide.classList.add('swiper-slide-thumb-active');
        
        currentActiveIndex = index;
        mainImage.src = slide.querySelector('img').src;
        updateZoomBackground();
      });
    });

    // 3. Xử lý hiệu ứng Zoom khi Hover chuột
    function updateZoomBackground() {
      zoomResult.style.backgroundImage = `url('${mainImage.src}')`;
    }
    updateZoomBackground();

    mainImgContainer.addEventListener('mouseenter', () => {
      zoomLens.style.display = 'block';
      zoomResult.style.display = 'block';
      
      // Tính toán tỷ lệ zoom dựa trên kích thước thực tế của ảnh
      const cx = zoomResult.offsetWidth / zoomLens.offsetWidth;
      const cy = zoomResult.offsetHeight / zoomLens.offsetHeight;
      
      zoomResult.style.backgroundSize = `${mainImage.width * cx}px ${mainImage.height * cy}px`;
    });

    mainImgContainer.addEventListener('mouseleave', () => {
      zoomLens.style.display = 'none';
      zoomResult.style.display = 'none';
    });

    mainImgContainer.addEventListener('mousemove', moveLens);

    function moveLens(e) {
      e.preventDefault();
      const rect = mainImgContainer.getBoundingClientRect();
      
      // Lấy tọa độ chuột tương đối trong khung ảnh
      let x = e.clientX - rect.left - (zoomLens.offsetWidth / 2);
      let y = e.clientY - rect.top - (zoomLens.offsetHeight / 2);

      // Giới hạn không cho lens ra ngoài khung ảnh
      if (x > mainImgContainer.offsetWidth - zoomLens.offsetWidth) {
        x = mainImgContainer.offsetWidth - zoomLens.offsetWidth;
      }
      if (x < 0) { x = 0; }
      if (y > mainImgContainer.offsetHeight - zoomLens.offsetHeight) {
        y = mainImgContainer.offsetHeight - zoomLens.offsetHeight;
      }
      if (y < 0) { y = 0; }

      zoomLens.style.left = x + 'px';
      zoomLens.style.top = y + 'px';

      // Tính tỷ lệ và di chuyển background của ô zoom kết quả
      const cx = zoomResult.offsetWidth / zoomLens.offsetWidth;
      const cy = zoomResult.offsetHeight / zoomLens.offsetHeight;

      zoomResult.style.backgroundPosition = `-${x * cx}px -${y * cy}px`;
    }

    // 4. Xử lý Click vào ảnh chính để mở Fancybox Lightbox
    mainImgContainer.addEventListener('click', () => {
      Fancybox.show(galleryItems, {
        startIndex: currentActiveIndex
      });
    });
  </script>
</body>
</html>


 

Thứ Hai, 3 tháng 8, 2026

Day - Date - Month - Year PHP

 

$thoigian  = $value["thoigian"]; ("2026-07-31 14:41:39")

$timestamp = strtotime($thoigian);

$days = array(

    'Sunday'    => 'Chủ nhật',

    'Monday'    => 'Thứ Hai',

    'Tuesday'   => 'Thứ Ba',

    'Wednesday' => 'Thứ Tư',

    'Thursday'  => 'Thứ Năm',

    'Friday'    => 'Thứ Sáu',

    'Saturday'  => 'Thứ Bảy'

);

$day = $days[date('l', $timestamp)];

<div class="pdnews-times">

    <span><img src="/themes/'.$ThemeSel.'/images/icon-blog2.png"> '.$day.',</span>

    <span class="news_home_content_short_time">

        '.date('d/m/Y', $timestamp).'

    </span>

</div>

Thứ Ba, 28 tháng 7, 2026

Chọn vị trí để IN (print) ra nội dung của div

 ** HTML

<div id="printArea">

    <div class="no-print">Nội dung left</div>


    <div class="content-right">Nội dung phải</div>

    <!--Nội dung phải được in ra-->

</div>


** CSS

<style type="text/css">

    @media print{

        .certificate-container{

            margin:0 !important;

            padding:8mm !important;

            height:auto !important;

            min-height:auto !important;

        }

        .certificate-inner-border{

            padding:6mm !important;

        }

        p,h1,h2,h3,h4,h5,h6{

            margin-top:2px !important;

            margin-bottom:2px !important;

        }

        .no-print{

            display:none!important;

        }

        body *{

            visibility:hidden;

        }

        #printArea,

        #printArea *{

            visibility:visible;

        }


        #printArea{

            position:absolute;

            left:0;

            top:0;

            width:100%;

        }

    }

</style>


** JS

<script type="text/javascript">

    function printDiv(){

        window.print();

    }

</script>


Thứ Hai, 20 tháng 7, 2026

Nhập số tự chuyển thành "period / dot" và "comma"

** HTML
<input type='text' name='currency' id='currency' class='form-control'>

Nhập 25500 -> thành 25.500

** PHP

Code php lấy lại giá trị: 25500

$currency = str_replace('.', '', $_POST['currency']);

** JS

<script type="text/javascript">

    $("#currency").on("input", function () {

        // Chỉ giữ lại số

        let value = $(this).val().replace(/\D/g, "");

        // Format 25.500

        if (value !== "") {

            value = Number(value).toLocaleString("vi-VN");

        }

        $(this).val(value);

    });

</script> 

Thứ Ba, 14 tháng 7, 2026

explode - implode - uploads Images code php

 ##┌────────────────────────────────────────────────┐     

  ## Implode

##└────────────────────────────────────────────────┘

function ConvertImplode($post){    

    if(empty($post) || !is_array($post)){

        return '';

    }

    $post = array_map('trim', $post);

    $post = array_filter($post);

    return implode('**', $post);

}

$maymoc1 = ConvertImplode($_POST['maymoc1']);

$maymoc2 = ConvertImplode($_POST['maymoc2']);

$maymoc3 = ConvertImplode($_POST['maymoc3']);

$maymoc4 = ConvertImplode($_POST['maymoc4']);


##┌────────────────────────────────────────────────┐     

  ## upload-image

##└────────────────────────────────────────────────┘

function uploadVerifiedImage($file, $path){

    if (!isset($file['tmp_name']) || $file['error'] != 0) {

        return '';

    }

    $ext = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION));

    $allow = array('jpg','jpeg','png','gif','webp');

    if (!in_array($ext, $allow)) {

        return '';

    }

    $filename = time().'_'.mt_rand(1000,9999).'.'.$ext;

    if (move_uploaded_file($file['tmp_name'], $path.$filename)) {

        return $filename;

    }

    return '';

}

$path = "uploads/verified/";

if (!is_dir($path)) {
        mkdir($path, 0777, true);
}

$gate_image = uploadVerifiedImage($_FILES['gate_image'], $path);

$production_image = uploadVerifiedImage($_FILES['production_image'], $path);

$machinery_image = uploadVerifiedImage($_FILES['machinery_image'], $path);

$certificate_file = uploadVerifiedImage($_FILES['certificate_file'], $path);

$video_file = uploadVerifiedImage($_FILES['video_file'], $path);  


if($gate_image != ''){

     $input_data['gate_image'] = $gate_image;

}

if($production_image != ''){

     $input_data['production_image'] = $production_image;

}

if($machinery_image != ''){

        $input_data['machinery_image'] = $machinery_image;

}

if($certificate_file != ''){

     $input_data['certificate_file'] = $certificate_file;

}

if($video_file != ''){

     $input_data['video_file'] = $video_file;

}    

##┌────────────────────────────────────────────────┐     

  ## Note: RESULT

##└────────────────────────────────────────────────┘

$input_data = array(

    'bangke1'  => $bangke1,

    'bangke2'  => $bangke2,

);

$xsql = "SELECT id FROM api_Vfactory WHERE shopid=".$shopid;

$rs   = $db->sql_query($xsql);

if($db->sql_numrows($rs)){

    // UPDATE

    $sql = "UPDATE api_Vfactory SET "

        .$db->sql_build_array('UPDATE', $input_data)

        ." WHERE shopid=".$shopid;

}else{

    // INSERT

    $input_data['shopid'] = $shopid;

    $input_data['created_at'] = time();

    $sql = "INSERT INTO api_Vfactory "

        .$db->sql_build_array('INSERT', $input_data);

}


##┌────────────────────────────────────────────────┐     

  ## Note: explode

##└────────────────────────────────────────────────┘ 

function parseData($value){

    return !empty($value) ? explode('**', $value) : array();

}

$data = data__Vfactory($shopid);

extract($data[0]);

$bangke1 = parseData($bangke1);

$bangke2 = parseData($bangke2);

foreach ($bangke1 as $key => $value) {

    <tr>

        <td>'.$value.'</td>

        <td>'.$bangke2[$key].'</td>

    </tr>

}

Thứ Tư, 8 tháng 7, 2026

Hiệu ứng Popup - Detail Product

 



## HTML

<!DOCTYPE html>
<html lang="vi">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Slide Popup Lightbox</title>
    <link rel="stylesheet" href="style.css">
</head>
<body>

<!-- Danh sách các hình ảnh hiển thị trên trang -->
<div class="image-gallery">
    <div class="gallery-item">
        <img src="1.jpg" alt="máy giặt bong bóng" class="popup-trigger"
             data-title-vi="máy giặt bong bóng" 
             data-date="May 19,2021" 
             data-title-en="Bubble Washing Machine" 
             data-desc="The bubbling cleaning machine is designed for materials with high shape requirements. It is...">
        <p>máy giặt bong bóng</p>
    </div>
    <div class="gallery-item">
        <img src="2.jpg" alt="máy chiết rót nước sốt" class="popup-trigger"
             data-title-vi="máy chiết rót nước sốt" 
             data-date="June 10,2022" 
             data-title-en="Sauce Filling Machine" 
             data-desc="Description for sauce filling machine goes here...">
        <p>máy chiết rót nước sốt</p>
    </div>
    <!-- Thêm các hình ảnh khác tại đây -->
</div>

<!-- Cấu trúc MODAL POPUP (Giống như trong ảnh z7975352149535_20ea4773eb8dace4aa835ec268dc0eab.jpg) -->
<div id="lightboxModal" class="modal">
    <!-- Nút đóng góc trên bên phải bên ngoài hoặc sát mép -->
    <span class="close-btn">&times;</span>
    
    <div class="modal-content">
        <!-- Phần Header của Popup -->
        <div class="modal-header">
            <h3 id="modalTitleVi">máy giặt bong bóng</h3>
            <span id="modalDate">May 19,2021</span>
        </div>

        <!-- Phần Thân chứa Ảnh và Nút chuyển đổi -->
        <div class="modal-body">
            <button class="nav-btn prev-btn">&#10094;</button>
            <div class="image-container">
                <img id="modalImage" src="" alt="Popup Image">
            </div>
            <button class="nav-btn next-btn">&#10095;</button>
        </div>

        <!-- Phần Footer chứa Mô tả -->
        <div class="modal-footer">
            <h4 id="modalTitleEn">Bubble Washing Machine</h4>
            <div class="desc-scroll">
                <p id="modalDesc">The bubbling cleaning machine is designed for materials with high shape requirements. It is...</p>
            </div>
        </div>
    </div>
</div>

<script src="script.js"></script>

<style type="text/css">
    
    /* Gallery ngoài trang */
body {
    font-family: Arial, sans-serif;
    background-color: #f4f4f4;
    margin: 0;
    padding: 20px;
}
.image-gallery {
    display: flex;
    gap: 20px;
    flex-wrap: wrap;
}
.gallery-item {
    background: #fff;
    padding: 10px;
    border: 1px solid #ddd;
    text-align: center;
    cursor: pointer;
    width: 200px;
}
.gallery-item img {
    width: 100%;
    height: auto;
}

/* MODAL BACKGROUND */
.modal {
    display: none; /* Ẩn mặc định */
    position: fixed;
    z-index: 9999;
    left: 0;
    top: 0;
    width: 100%;
    height: 100%;
    overflow: auto;
    background-color: rgba(0, 0, 0, 0.5); /* Nền tối mờ phía sau */
    justify-content: center;
    align-items: center;
}

/* NÚT ĐÓNG (X) */
.close-btn {
    position: absolute;
    top: 20px;
    right: 30px;
    color: #fff;
    font-size: 40px;
    font-weight: bold;
    cursor: pointer;
    background: rgba(0,0,0,0.3);
    width: 45px;
    height: 45px;
    display: flex;
    align-items: center;
    justify-content: center;
    border-radius: 4px;
}
.close-btn:hover {
    background: rgba(0,0,0,0.6);
}

/* KHUNG POPUP CHÍNH */
.modal-content {
    background-color: #fff;
    width: 90%;
    max-width: 800px;
    border-radius: 4px;
    display: flex;
    flex-direction: column;
    position: relative;
    box-shadow: 0 4px 20px rgba(0,0,0,0.3);
    animation: fadeIn 0.3s ease;
}

@keyframes fadeIn {
    from { opacity: 0; transform: scale(0.95); }
    to { opacity: 1; transform: scale(1); }
}

/* HEADER POPUP */
.modal-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    padding: 15px 25px;
    border-bottom: 1px solid #eee;
}
.modal-header h3 {
    margin: 0;
    font-size: 16px;
    color: #333;
    font-weight: bold;
}
.modal-header span {
    font-size: 13px;
    color: #888;
}

/* THÂN POPUP CHỨA ẢNH VÀ DI CHUYỂN */
.modal-body {
    display: flex;
    align-items: center;
    justify-content: space-between;
    position: relative;
    padding: 10px 0;
    background: #fff;
}
.image-container {
    width: 100%;
    display: flex;
    justify-content: center;
    align-items: center;
    height: 400px; /* Chiều cao cố định cho vùng chứa ảnh */
}
.image-container img {
    max-width: 100%;
    max-height: 100%;
    object-fit: contain;
}

/* NÚT NEXT / PREV TRÁI PHẢI */
.nav-btn {
    background: rgba(0, 0, 0, 0.4);
    color: white;
    border: none;
    padding: 15px 12px;
    cursor: pointer;
    font-size: 20px;
    position: absolute;
    z-index: 10;
    transition: 0.2s;
}
.nav-btn:hover {
    background: rgba(0, 0, 0, 0.7);
}
.prev-btn { left: 0; border-radius: 0 4px 4px 0; }
.next-btn { right: 0; border-radius: 4px 0 0 4px; }

/* FOOTER POPUP CHỨA MÔ TẢ */
.modal-footer {
    padding: 20px 25px;
    border-top: 1px solid #eee;
    background: #fff;
}
.modal-footer h4 {
    margin: 0 0 10px 0;
    font-size: 16px;
    color: #444;
}


/* Tạo thanh cuộn cho nội dung dài giống hình ảnh mẫu */
.desc-scroll {
    max-height: 80px;
    overflow-y: auto;
    padding-right: 10px;
}
.desc-scroll p {
    margin: 0;
    font-size: 13px;
    color: #666;
    line-height: 1.5;
}
/* Tùy biến thanh cuộn đẹp hơn */
.desc-scroll::-webkit-scrollbar {
    width: 6px;
}
.desc-scroll::-webkit-scrollbar-thumb {
    background: #ccc;
    border-radius: 4px;
}
</style>

<script type="text/javascript">
    document.addEventListener("DOMContentLoaded", function () {
    const modal = document.getElementById("lightboxModal");
    const triggers = document.querySelectorAll(".popup-trigger");
    const closeBtn = document.querySelector(".close-btn");
    
    const modalImg = document.getElementById("modalImage");
    const modalTitleVi = document.getElementById("modalTitleVi");
    const modalDate = document.getElementById("modalDate");
    const modalTitleEn = document.getElementById("modalTitleEn");
    const modalDesc = document.getElementById("modalDesc");
    
    const prevBtn = document.querySelector(".prev-btn");
    const nextBtn = document.querySelector(".next-btn");

    let currentIndex = 0;

    // Hàm cập nhật nội dung cho Modal dựa trên Index của ảnh
    function updateModal(index) {
        const targetImg = triggers[index];
        
        modalImg.src = targetImg.src;
        modalTitleVi.textContent = targetImg.getAttribute("data-title-vi");
        modalDate.textContent = targetImg.getAttribute("data-date");
        modalTitleEn.textContent = targetImg.getAttribute("data-title-en");
        modalDesc.textContent = targetImg.getAttribute("data-desc");
        
        currentIndex = index;
    }

    // Lắng nghe sự kiện click vào từng bức ảnh
    triggers.forEach((trigger, index) => {
        trigger.addEventListener("click", function () {
            modal.style.display = "flex";
            updateModal(index);
        });
    });

    // Nút quay lại (Trái)
    prevBtn.addEventListener("click", function (e) {
        e.stopPropagation(); // Tránh lỗi tắt modal ngoài ý muốn
        let newIndex = currentIndex - 1;
        if (newIndex < 0) {
            newIndex = triggers.length - 1; // Quay về ảnh cuối cùng nếu vượt quá đầu danh sách
        }
        updateModal(newIndex);
    });

    // Nút kế tiếp (Phải)
    nextBtn.addEventListener("click", function (e) {
        e.stopPropagation();
        let newIndex = currentIndex + 1;
        if (newIndex >= triggers.length) {
            newIndex = 0; // Quay về ảnh đầu tiên nếu vượt quá cuối danh sách
        }
        updateModal(newIndex);
    });

    // Đóng modal khi bấm nút X
    closeBtn.addEventListener("click", function () {
        modal.style.display = "none";
    });

    // Đóng modal khi click ra vùng ngoài khung trắng
    modal.addEventListener("click", function (e) {
        if (e.target === modal) {
            modal.style.display = "none";
        }
    });
    
    // Hỗ trợ bấm nút điều hướng bằng bàn phím (Trái/Phải/Esc)
    document.addEventListener("keydown", function(e) {
        if (modal.style.display === "flex") {
            if (e.key === "ArrowLeft") {
                prevBtn.click();
            } else if (e.key === "ArrowRight") {
                nextBtn.click();
            } else if (e.key === "Escape") {
                modal.style.display = "none";
            }
        }
    });
});
</script>
</body>
</html>

## Demo

Link: https://vietb2b.com/nha-cung-cap-6-profile


Thứ Hai, 6 tháng 7, 2026

Hover Button css đẹp

 

## HTML

<div class="box-send-contact">

    <button class="button dark">Gửi thông tin</button>

</div>


## CSS

.button.dark {

    color: #fff;

    border-color: #0d2445;

}

.box-send-contact button {

    display: block;

    margin: 2rem auto 3rem auto;

}

.button:before {

    position: absolute;

    content: '';

    display: block;

    left: -2px;

    top: 0;

    right: -2px;

    bottom: 0;

    transform: scale(1, 1);

    transform-origin: left center;

    z-index: -1;

    background-color: #ffffff;

    transition: transform 0.45s cubic-bezier(0.785, 0.135, 0.15, 0.86);

}

.button.dark:before {

    background-color: #0d2445;

}

.button {

    font-weight: 600;

    position: relative;

    display: inline-block;

    padding: 1rem 2.8rem;

    line-height: normal;

    border: 1px solid #ffffff;

    border-radius: 50px;

    text-transform: uppercase;

    font-size: 1.2rem;

    text-align: center;

    letter-spacing: 1px;

    background-color: transparent;

    transition: color 0.45s cubic-bezier(0.785, 0.135, 0.15, 0.86), border 0.45s cubic-bezier(0.785, 0.135, 0.15, 0.86);

    z-index: 1;

    color: #0d2445;

    overflow: hidden;

}

.button:hover:before {

    transform-origin: right center;

    transform: scale(0, 1);

}

button:focus:not(:focus-visible) {

    outline: 0;

}

button.dark:hover {

    color: #0d2445;

}

Thứ Tư, 1 tháng 7, 2026

CSS clamp

 height: clamp(280px, 23vw, 450px);

có nghĩa là chiều cao sẽ tự co giãn theo kích thước màn hình nhưng luôn nằm trong khoảng từ 280px đến 450px.

Cú pháp của clamp() là:

clamp(MIN, PREFERRED, MAX)
  • 280px → Giá trị nhỏ nhất (không bao giờ thấp hơn).
  • 23vw → Giá trị mong muốn (23% chiều rộng của cửa sổ trình duyệt).
  • 450px → Giá trị lớn nhất (không bao giờ vượt quá).

Ví dụ

Chiều rộng màn hình23vwKết quả
320px73.6px280px (vì nhỏ hơn min)
768px176.6px280px
1200px276px280px
1400px322px322px
1600px368px368px
2200px506px450px (vì vượt max)

Khi nào dùng?

Ví dụ cho ảnh sản phẩm:

.dmsp-image {
height: clamp(280px, 23vw, 450px);
}
  • Điện thoại → cao 280px.
  • Tablet → khoảng 280–320px.
  • Desktop → khoảng 320–450px.
  • Màn hình siêu rộng → tối đa 450px, không bị quá cao.

Một số giá trị thường dùng

/* Luôn từ 250px đến 400px */
height: clamp(250px, 20vw, 400px);

/* Phù hợp card sản phẩm */
height: clamp(280px, 25vw, 420px);

/* Banner */
height: clamp(300px, 35vw, 700px);

clamp() rất hữu ích vì bạn không cần viết nhiều @media để thay đổi chiều cao theo từng kích thước màn hình.

Thứ Hai, 15 tháng 6, 2026

Click More - Less HTML+JS+CSS

 

** CSS
.filter-list li:nth-child(n+11) {
    display: none;
}
.filter-list.show-all li {
    display: block !important;
}

** JS
<script type="text/javascript">
            
    $(document).on("click", ".moreless", function(e){
        e.stopPropagation();

        var parent = $(this).closest(".filter-section");
        var sublist = parent.find(".filter-list");
        var icon = $(this).find("i");

        if(sublist.hasClass("show-all")){
            sublist.removeClass("show-all");
            $(this).find("span").text("More");

            // icon xuống
            icon.removeClass("fa-angle-up").addClass("fa-angle-down");
        } else {
            sublist.addClass("show-all");
            $(this).find("span").text("Less");

            // icon lên
            icon.removeClass("fa-angle-down").addClass("fa-angle-up");
        }
    });
</script>

** HTML
echo'<div class="filter-section">

    <div class="filter-title">Danh Mục</div>

    <ul class="filter-list">';
        foreach ($data as $key => $value) {
            echo'<li>
                <a href="/'.mod_rewrite($value["title"]).'-'.$value["mid"].'-nsp">'.$value["title"].'</a>
            </li>';
        }
    echo'</ul>

    <button class="moreless toggle-btn"><span>More</span> <i class="ft-icon fa fa-angle-down"></i>
    </button>

</div>';




Thứ Tư, 10 tháng 6, 2026

Icon trong select css đẹp

**** HTML



<div class="select-wrap">

    <select name="customer_shipping_phuongxa" id="customer_shipping_phuongxa">

        <option value="">Chọn Phường / Xã</option>

    </select>

</div> 

**** CSS
.appline .select-wrap{

    position:relative;

    width:100%;

}

.appline .select-wrap select{

    -webkit-appearance:none;

    -moz-appearance:none;

    appearance:none;


    background:#fff;

    border:1px solid #d9d9d9;

    box-sizing:border-box;

    border-radius:16px !important;

    height:40px !important;

    width:100%;

    padding:5px 45px 5px 20px !important;

    transition:all .2s;

    font-size:14px;

    cursor:pointer;

}

.appline .select-wrap select::-ms-expand{

    display:none;

}

.appline .select-wrap:after{

    content:"";

    position:absolute;

    top:50%;

    right:18px;

    margin-top:-2px;

    width:0;

    height:0;

    border-style:solid;

    border-width:5px 4px 0 4px;

    border-color:#888 transparent transparent transparent;

    pointer-events:none;

}

Thứ Tư, 27 tháng 5, 2026

htmlspecialchars show hết lỗi dấu nháy - bird" s

$title_en="bird's nest products";

<input class="form-control" id="txt_name_en" name="txt_name_en" placeholder="Tên sản phẩm - Tiếng anh (*)" value="bird" s="" nest="" products'="" maxlength="80" required="">

và sử dụng hàm htmlspecialchars là OK.

htmlspecialchars($title_en, ENT_QUOTES)

Thứ Năm, 16 tháng 4, 2026

lấy giá trị form - code php ajax

 <form id="#send-supplier-fast">

<a id="tensanpham" href="'.$link.'" target="_blank" class="link">'.$title.'</a>            

<input type="text" placeholder="Số lượng"  name="soluong" oninput="this.value = this.value.replace(/[^0-9]/g, \'\')" required>

<textarea class="input-textarea" id="content" name="content" required></textarea> 

<button type="submit" id="inquirySend"></button>    

</form>

$(document).on('submit', '#send-supplier-fast', function (e) {

    e.preventDefault();

    const form = $(this);

    const btn  = $('#inquirySend');

    let formData = new FormData();

    // lấy data thường

    formData.append('soluong', $('input[name=soluong]').val());

    formData.append('donvitinh', $('select[name=donvitinh]').val());

    formData.append('content', $('#content').val());

    formData.append('senderMail', $('#senderMail').val());

    formData.append('pid', $('input[name=pid]').val());

    // formData.append('title', $('input[name=title]').val());

    formData.append('title', $('#tensanpham').text().trim());

    formData.append('userid', $('input[name=userid]').val());

    // chỉ append file từ selectedFiles

    selectedFiles.forEach(function(file) {

        if (file && file.size > 0) {

            formData.append('files[]', file);

        }

    });

    btn.prop('disabled', true).text('Đang gửi...');

    $.ajax({

        url: '/send-supplier-product',

        type: 'POST',

        data: formData,

        processData: false,

        contentType: false,

        dataType: 'json',

        success: function (res) {

            // console.log(res);

            if (res && res.status === 'success') {

                showNotify('✅ Gửi yêu cầu thành công!');

                form[0].reset();            

                window.location.href = '/?sendmail=' + res.mail_data.mail_id+ '&token=' + res.mail_data.token;

            } else {

                showNotify('❌ ' + (res.message || 'Có lỗi xảy ra'), 'error');

            }

        },

        error: function () {

            showNotify('❌ Lỗi server', 'error');

        },

        complete: function () {

            btn.prop('disabled', false).text('Gửi yêu cầu');

        }

    });

});

Chỉ cho nhập số vào - code php

 <input type="text" name="khoahoc" class="form-control" oninput="this.value = this.value.replace(/[^0-9]/g, \'\')" required>

Thứ Ba, 14 tháng 4, 2026

SEND EMAIL SMTP - KEIEIJUKU - send supplier

 require_once 'classes/class.phpmailer.php';

require_once 'classes/class.smtp.php';

<!-- take include/data/mail_config.php 

$SuppEmail = email_mail

$mailHost

$mailUsername

$mailPassword

$mailsetFrom 

-->

===== MAIL GỬI CHO SUPPLIER =====

$mail = new PHPMailer();

$mail->CharSet = 'UTF-8';

$mail->isSMTP();


$mail->SMTPKeepAlive = true;

$mail->SMTPDebug = 0;

// $mail->Timeout = 10;

$mail->Host       = $mailHost;

$mail->SMTPAuth   = true;

$mail->Username   = $mailUsername;

$mail->Password   = $mailPassword;

$mail->SMTPSecure = 'ssl';

$mail->Port       = 465;


$mail->setFrom($mailsetFrom, 'KEIEIJUKU KBN');

$mail->addAddress($supplier);

$mail->addReplyTo($customer);

$mail->isHTML(true);

$mail->Subject = $subject;

$mail->Body    = $body;

// ===== MAIL GỬI CHO KHÁCH (NGAY LẬP TỨC) =====

$mail2 = new PHPMailer();

$mail2->CharSet = 'UTF-8';

$mail2->isSMTP();

$mail2->SMTPKeepAlive = true;

$mail2->SMTPDebug = 0;

// $mail2->Timeout = 3;


$mail2->Host       = $mailHost;

$mail2->SMTPAuth   = true;

$mail2->Username   = $mailUsername;

$mail2->Password   = $mailPassword;

$mail2->SMTPSecure = 'ssl';

$mail2->Port       = 465;


$mail2->setFrom($mailsetFrom, 'KEIEIJUKU KBN');

$mail2->addAddress($customer);


$mail2->isHTML(true);

$mail2->Subject = 'Xác nhận đã gửi yêu cầu từ website KEIEIJUKU KBN';

$mail2->Body    = $body;

## Send-Mail

$start = microtime(true);

// gửi mail supplier trước

$ok1 = $mail->send();

$time1 = microtime(true);

// chỉ gửi mail khách nếu mail 1 OK

$ok2 = false;

if($ok1){

    $ok2 = $mail2->send();

}

$time2 = microtime(true);

## ------------------------- ##

// tính thời gian

$time_supplier = round($time1 - $start, 3);

$time_customer = round($time2 - $time1, 3);

$total_time    = round($time2 - $start, 3);

// ghi log

file_put_contents('debug_time.txt',

    "Supplier: {$time_supplier}s\n" .

    "Customer: {$time_customer}s\n" .

    "Total: {$total_time}s\n\n",

    FILE_APPEND

);

Thứ Sáu, 10 tháng 4, 2026

Accept - nhận đuôi file trong upload-file-input

 <input type="file" name="pic_ncc" accept=".jpeg,.jpg,.png">

OR

 <input type="file" name="pic_ncc" accept="image/*">

--> post - action - save

$image_nlh = null;

if (isset($_FILES['pic_ncc']) && $_FILES['pic_ncc']['error'] == 0) {

    $ext = strtolower(pathinfo($_FILES['pic_ncc']['name'], PATHINFO_EXTENSION));

    $allow_ext = array('jpg','png','jpeg');

    if (in_array($ext, $allow_ext)) {

        $new_name = 'picncc_'.time().'.'.$ext;

        if (move_uploaded_file($_FILES['pic_ncc']['tmp_name'], $upload_dir . $new_name)) {

            $image_nlh = $new_name;

        }

    }

}


/**************************/

<input type="file" id="images-file"  accept=".jpeg,.jpg,.png" name="images_file[]" multiple>

--> post - action - save

if (isset($_FILES['images_file']) && !empty($_FILES['images_file']['name'][0])) {

    for ($i = 0; $i < count($_FILES['images_file']['name']); $i++) {

        if ($_FILES['images_file']['error'][$i] == 0) {

            $name = $_FILES['images_file']['name'][$i];

            $tmp  = $_FILES['images_file']['tmp_name'][$i];

            $ext  = strtolower(pathinfo($name, PATHINFO_EXTENSION));

            $allow_ext = array('jpg','png');

            if (!in_array($ext, $allow_ext)) continue;

            $new_name = 'img_' . time() . '_' . $i . '.' . $ext;

            if (move_uploaded_file($tmp, $upload_banner . $new_name)) {

                $images_saved[] = $new_name;

            }

        }

    }

}

$images_db = !empty($images_saved) ? implode('**', $images_saved) : null;