TrdHuy / TrdHuy.github.io

0 stars 0 forks source link

portfolio keyboard implementation #9

Open TrdHuy opened 3 weeks ago

TrdHuy commented 3 weeks ago
<head>
    <link rel="stylesheet" href="./assets/css/index.css">
    <link rel="stylesheet" href="./assets/css/portfolio_autoblocker.css">
    <link rel="preconnect" href="https://fonts.googleapis.com">
    <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
    <link href="https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;600&display=swap" rel="stylesheet">
    <script defer="defer" src="./assets/jscc/horimagecontainer.jscc.js"></script>
    <script defer="defer" src="./assets/jscc/loadingimage.jscc.js"></script>

    <style>
        :root,
        hor-image-container {

            /**
 * colors
 */

            /* gradient */

            --bg-gradient-onyx: linear-gradient(to bottom right,
                    hsl(240, 1%, 25%) 3%,
                    hsl(0, 0%, 19%) 97%);
            --bg-gradient-jet: linear-gradient(to bottom right,
                    hsla(240, 1%, 18%, 0.251) 0%,
                    hsla(240, 2%, 11%, 0) 100%), hsl(240, 2%, 13%);
            --bg-gradient-yellow-1: linear-gradient(to bottom right,
                    hsl(45, 100%, 71%) 0%,
                    hsla(36, 100%, 69%, 0) 50%);
            --bg-gradient-yellow-2: linear-gradient(135deg,
                    hsla(45, 100%, 71%, 0.251) 0%,
                    hsla(35, 100%, 68%, 0) 59.86%), hsl(240, 2%, 13%);
            --border-gradient-onyx: linear-gradient(to bottom right,
                    hsl(0, 0%, 25%) 0%,
                    hsla(0, 0%, 25%, 0) 50%);
            --text-gradient-yellow: linear-gradient(to right,
                    hsl(45, 100%, 72%),
                    hsl(35, 100%, 68%));

            /* solid */

            --jet: hsl(0, 0%, 22%);
            --onyx: hsl(240, 1%, 17%);
            --eerie-black-1: hsl(240, 2%, 13%);
            --eerie-black-2: hsl(240, 2%, 12%);
            --smoky-black: hsl(0, 0%, 7%);
            --white-1: hsl(0, 0%, 100%);
            --white-2: hsl(0, 0%, 98%);
            --orange-yellow-crayola: hsl(45, 100%, 72%);
            --vegas-gold: hsl(45, 54%, 58%);
            --light-gray: hsl(0, 0%, 84%);
            --light-gray-70: hsla(0, 0%, 84%, 0.7);
            --bittersweet-shimmer: hsl(0, 43%, 51%);

            /**
 * typography
 */

            /* font-family */
            --ff-poppins: 'Poppins', sans-serif;

            /* font-size */
            --fs-1: 24px;
            --fs-2: 18px;
            --fs-3: 17px;
            --fs-4: 16px;
            --fs-5: 15px;
            --fs-6: 14px;
            --fs-7: 13px;
            --fs-8: 11px;

            /* font-weight */
            --fw-300: 300;
            --fw-400: 400;
            --fw-500: 500;
            --fw-600: 600;

            /**
 * shadow
 */

            --shadow-1: -4px 8px 24px hsla(0, 0%, 0%, 0.25);
            --shadow-2: 0 16px 30px hsla(0, 0%, 0%, 0.25);
            --shadow-3: 0 16px 40px hsla(0, 0%, 0%, 0.25);
            --shadow-4: 0 25px 50px hsla(0, 0%, 0%, 0.15);
            --shadow-5: 0 24px 80px hsla(0, 0%, 0%, 0.25);

            /**
 * transition
 */

            --transition-1: 0.25s ease;
            --transition-2: 0.5s ease-in-out;

            --spinner-color: var(--vegas-gold);
        }

        .hor-image-container {
            --scroll-bar-track-bg: #292929;
            --disable-wheel-scroll: 1;
            --scroll-bar-height: 4px;
        }

        hor-image-container .fold-device-img {
            height: 350px;
            margin-right: 10px;
            border-radius: 20px;
            border: 3px solid var(--jet);
            pointer-events: none;
            align-self: flex-end;
        }

        hor-image-container .s916-device-img {
            height: 350px;
            margin-right: 10px;
            border-radius: 20px;
            border: 3px solid var(--jet);
            pointer-events: none;
            align-self: flex-end;
        }

        hor-image-container .t976-land-device-img {
            height: 300px;
            margin-right: 10px;
            border-radius: 13px;
            border: 3px solid var(--jet);
            pointer-events: none;
            align-self: flex-end;
        }

        hor-image-container .t976-port-device-img {
            height: 600px;
            margin-right: 10px;
            border-radius: 13px;
            border: 3px solid var(--jet);
            pointer-events: none;
            align-self: flex-end;
            --spinner-container-width: 500px;
        }

        hor-image-container loading-image:last-child {
            margin-right: 0;
        }
    </style>
</head>

<body class="portfolio-container" style="color: azure;">
    <div class="overview overview-container">
        <h1 class="item-title">Samsung Honey Board Overview:</h1>
        <p>HoneyBoard is a versatile keyboard application designed specifically for Samsung devices,
            offering a smooth,
            convenient,
            and intuitive typing experience. With its sleek design and extensive customization options,
            HoneyBoard allows users to personalize their keyboard to fit their unique style and preferences. Whether
            you're typing messages, emails, or notes, HoneyBoard provides a seamless interface that enhances
            productivity and comfort,
            making it the perfect companion for all your Samsung devices.</p>

        <table>
            <tr>
                <th>Release</th>
                <td>The first version of Honey Board started appearing in OneUI 2.1, and has since been continuously
                    updated to improve performance and add new features.</td>
            </tr>
            <tr>
                <th>Main Features</th>
                <td>
                    <ul>
                        <li>AI integration for predictive text and voice recognition, improving typing accuracy.</li>
                        <li>Multilingual support, allowing users to easily switch between languages.</li>
                        <li>Extensive customization options, including theme changes, keyboard size adjustments, and key
                            layout rearrangement.</li>
                        <li>Integration with Samsung Pass, Spotify Plugin, and Samsung's Secure Folder.</li>
                    </ul>
                </td>
            </tr>
            <tr>
                <th>Benefits</th>
                <td>
                    <ul>
                        <li>Enhances typing efficiency with intelligent support tools and high customization.</li>
                        <li>Provides convenience and comfort for users, regardless of the device they are using.</li>
                        <li>Secures personal information with integrations like Samsung Pass and Secure Folder</li>
                    </ul>
                </td>
            </tr>
            <tr>
                <th>User Control</th>
                <ul>
                    <li>Users have full control over their keyboard experience through extensive customization options.
                    </li>
                    <li>Security features ensure that user data is protected, with access to sensitive information
                        restricted to the user.</li>
                </ul>

            </tr>
            <tr>
                <th>Goal</th>
                <td>The primary goal of Samsung Honey Board is to be the optimal typing tool for all Samsung users,
                    helping them improve productivity and user experience while providing a secure and reliable platform
                    for managing personal information.</td>
            </tr>
        </table>
    </div>
    <div style="margin-top: 30px;">
        <h1 class="item-title adaptable-title">Multi-Device Compatibility</h1>
        <p>HoneyBoard is optimized for use across various Samsung devices,
            including smartphones,
            tablets,
            foldable devices (Galaxy Z Fold, Z Flip),
            and wearables like the Galaxy Watch. It ensures a consistent and high-quality typing experience across all
            device types.</p>
        <div>
            <hor-image-container class="hor-image-container" style="width: fit-content;align-self: center;">
                <loading-image class="t976-port-device-img" src="assets/images/portofilo/keyboard/kb_t976_dark_port.jpg"
                    alt="kb_fold_small_scr"></loading-image>
                <loading-image class="t976-land-device-img" src="assets/images/portofilo/keyboard/kb_t976_dark_land.jpg"
                    alt="kb_fold_small_scr"></loading-image>

            </hor-image-container>
        </div>

        <hor-image-container class="hor-image-container">
            <loading-image class="fold-device-img" src="assets/images/portofilo/keyboard/kb_fold_large_scr.jpg"
                alt="kb_fold_large_scr"></loading-image>
            <loading-image class="fold-device-img" src="assets/images/portofilo/keyboard/kb_fold_small_scr.jpg"
                alt="kb_fold_small_scr"></loading-image>
            <loading-image class="s916-device-img" src="assets/images/portofilo/keyboard/kb_s916_dark.jpg"
                style="margin-left: 40px;" alt="kb_fold_small_scr"></loading-image>
        </hor-image-container>

        <h2>2. Integration with Samsung Pass</h2>
        <p>Seamlessly integrates with Samsung Pass,
            allowing users to securely store and autofill passwords directly from the keyboard. Enhances security and
            convenience for logging into apps and websites.</p>
        <h2>3. Spotify Plugin</h2>
        <p>Includes a Spotify plugin that allows users to search for and share music directly from the keyboard without
            leaving the conversation.</p>
        <h2>4. Rich Media Support</h2>
        <p>Offers extensive support for rich media including emojis,
            GIFs,
            and stickers,
            enabling creative expression in messaging.</p>
        <h2>5. AI-Powered Features</h2>
        <p>Features AI-powered functionalities such as predictive text and voice recognition,
            which enhance typing accuracy and efficiency by suggesting words based on context and converting speech to
            text.</p>
        <h2>6. Multilingual Support</h2>
        <p>Provides robust multilingual support,
            allowing users to easily switch between languages and ensuring accurate autocorrection in each language.</p>
        <h2>7. Customization Options</h2>
        <p>Allows users to personalize their keyboard experience through customization options like adjusting keyboard
            size,
            changing themes,
            and rearranging key layouts.</p>
        <h2>8. Accessibility Features</h2>
        <p>Includes accessibility features such as high-contrast themes,
            larger key sizes,
            and voice input options,
            making the keyboard more accessible for users with visual or motor impairments.</p>
        <h2>9. Gesture Typing and Quick Access</h2>
        <p>Supports gesture typing,
            where users can swipe across the keyboard to type,
            and offers quick access shortcuts for functions like copy-paste and undo-redo.</p>
        <h2>10. Secure Folder Integration</h2>
        <p>Integrated with Samsung's Secure Folder, allowing users to manage and input sensitive information securely.
        </p>
        <h2>11. Enhanced Clipboard Management</h2>
        <p>Features advanced clipboard management,
            enabling users to store and quickly access multiple items from the clipboard,
            which is particularly useful for multitasking.</p>
    </div>
</body>
<footer>
    <script type="module" src="https://unpkg.com/ionicons@5.5.2/dist/ionicons/ionicons.esm.js"></script>
    <script nomodule src="https://unpkg.com/ionicons@5.5.2/dist/ionicons/ionicons.js"></script>
    <script src="./assets/js/contract.js"></script>
</footer>
TrdHuy commented 3 weeks ago
class HorImageContainer extends HTMLElement {
    constructor() {
        super();
        const shadowRoot = this.attachShadow({ mode: 'open' });
        document.documentElement.style.setProperty('--scroll-bar-height', '8px');
        document.documentElement.style.setProperty('--scroll-bar-track-bg', '#f1f1f1');
        document.documentElement.style.setProperty('--scroll-bar-thumb-bg', '#888');
        document.documentElement.style.setProperty('--scroll-bar-thumb-border-rad', '10px');
        document.documentElement.style.setProperty('--scroll-bar-track-border-rad', '10px');
        document.documentElement.style.setProperty('--scroll-bar-thumb-bg-hover', '#555');
        document.documentElement.style.setProperty('--scroll-bar-margin', '10px');
        document.documentElement.style.setProperty('--wheel-scroll-velocity', '4');
        document.documentElement.style.setProperty('--disable-wheel-scroll', '0');

        const scrollPanel = document.createElement('div');
        scrollPanel.className = 'scroll-panel';
        scrollPanel.style.position = 'relative';
        scrollPanel.style.width = '100%';
        scrollPanel.style.overflow = 'hidden';
        scrollPanel.style.cursor = 'grab';
        scrollPanel.style.userSelect = 'none';

        this.imageContainer = document.createElement('div');
        const imageContainer = this.imageContainer;
        imageContainer.className = 'image-container';
        imageContainer.style.display = 'flex';
        imageContainer.style.overflowX = 'hidden';
        imageContainer.style.whiteSpace = 'nowrap';
        imageContainer.style.padding = '10px';
        imageContainer.style.scrollBehavior = 'smooth';
        // Nhập nội dung từ slot
        const slot = document.createElement('slot');
        imageContainer.appendChild(slot);

        // Nút mũi tên trái
        const leftButton = document.createElement('button');
        leftButton.className = 'arrow-button arrow-left';
        leftButton.innerHTML = '&#8249;';
        leftButton.style.position = 'absolute';
        leftButton.style.top = '50%';
        leftButton.style.transform = 'translateY(-50%)';
        leftButton.style.backgroundColor = 'rgba(0, 0, 0, 0.5)';
        leftButton.style.color = 'white';
        leftButton.style.border = 'none';
        leftButton.style.padding = '10px';
        leftButton.style.cursor = 'pointer';
        leftButton.style.display = 'none';
        leftButton.style.zIndex = '1';
        leftButton.style.left = '0';
        leftButton.onclick = () => this.scrollLeft();

        // Nút mũi tên phải
        const rightButton = document.createElement('button');
        rightButton.className = 'arrow-button arrow-right';
        rightButton.innerHTML = '&#8250;';
        rightButton.style.position = 'absolute';
        rightButton.style.top = '50%';
        rightButton.style.transform = 'translateY(-50%)';
        rightButton.style.backgroundColor = 'rgba(0, 0, 0, 0.5)';
        rightButton.style.color = 'white';
        rightButton.style.border = 'none';
        rightButton.style.padding = '10px';
        rightButton.style.cursor = 'pointer';
        rightButton.style.display = 'none';
        rightButton.style.zIndex = '1';
        rightButton.style.right = '0';
        rightButton.onclick = () => this.scrollRight();

        // Thêm các phần tử vào panel cuộn
        scrollPanel.appendChild(imageContainer);
        scrollPanel.appendChild(leftButton);
        scrollPanel.appendChild(rightButton);

        const style = document.createElement('style');
        style.textContent = `
:host{display: block;}
.image-container::-webkit-scrollbar{height:var(--scroll-bar-height);}
.image-container::-webkit-scrollbar-track{background:var(--scroll-bar-track-bg);border-radius:var(--scroll-bar-track-border-rad);margin:var(--scroll-bar-margin);}
.image-container::-webkit-scrollbar-thumb{background:var(--scroll-bar-thumb-bg);border-radius:var(--scroll-bar-thumb-border-rad);}
.image-container::-webkit-scrollbar-thumb:hover{background: #555;}`
        // Thêm panel cuộn vào shadow DOM
        shadowRoot.append(style, scrollPanel);

        // Sự kiện kéo để cuộn
        let isDown = false;
        let startX;
        let scrollLeft;

        scrollPanel.addEventListener('mousedown', (e) => {
            isDown = true;
            scrollPanel.classList.add('active');
            startX = e.pageX - imageContainer.offsetLeft;
            scrollLeft = imageContainer.scrollLeft;
            imageContainer.style.pointerEvents = 'none';
        });

        scrollPanel.addEventListener('mouseleave', () => {
            isDown = false;
            scrollPanel.classList.remove('active');
            imageContainer.style.pointerEvents = 'all';
        });

        scrollPanel.addEventListener('mouseup', () => {
            isDown = false;
            scrollPanel.classList.remove('active');
            imageContainer.style.pointerEvents = 'all';
        });

        scrollPanel.addEventListener('mousemove', (e) => {
            if (!isDown) return;
            e.preventDefault();
            const x = e.pageX - imageContainer.offsetLeft;
            const walk = (x - startX) * 2;
            imageContainer.scrollLeft = scrollLeft - walk;
        });

        // Cuộn bằng chuột

        scrollPanel.addEventListener('wheel', (e) => {
            const isDisableWheelScrollProp = getComputedStyle(this).getPropertyValue('--disable-wheel-scroll').trim();
            const isDisableWheelScroll = parseInt(isDisableWheelScrollProp);
            const scrollVelocity = getComputedStyle(this).getPropertyValue('--wheel-scroll-velocity').trim();
            const velocity = parseFloat(scrollVelocity);
            if(isDisableWheelScroll == 0){
                e.preventDefault();
                imageContainer.scrollLeft += e.deltaY * velocity;
            }

        });
        // Hiển thị nút khi hover vào scrollPanel
        scrollPanel.addEventListener('mouseover', () => {
            imageContainer.style.overflowX = 'auto';
            if (imageContainer.scrollWidth > imageContainer.clientWidth) {
                leftButton.style.display = 'block';
                rightButton.style.display = 'block';
            }
        });

        scrollPanel.addEventListener('mouseout', () => {
            imageContainer.style.overflowX = 'hidden';
            leftButton.style.display = 'none';
            rightButton.style.display = 'none';
        });
    }

    scrollLeft() {
        const imageContainer = this.imageContainer;
        imageContainer.scrollBy({
            left: -300,
            behavior: 'smooth'
        });
    }

    scrollRight() {
        const imageContainer = this.imageContainer;
        imageContainer.scrollBy({
            left: 300,
            behavior: 'smooth'
        });
    }

}
customElements.define('hor-image-container', HorImageContainer);
TrdHuy commented 3 weeks ago
class LoadingImage extends HTMLElement {
     constructor() {
          super();
          this.host = this.attachShadow({ mode: 'open' });
          document.documentElement.style.setProperty('--spinner-container-width', '300px');
          document.documentElement.style.setProperty('--spinner-color', '#fff');

          const shadowRoot = this.host;
          shadowRoot.host.style.overflow = 'clip';
          shadowRoot.host.style.display = 'flex';
          shadowRoot.host.style.justifyContent = 'center';
          shadowRoot.host.style.alignItems = 'center';

          // Tạo container
          this.container = document.createElement('div');
          const container = this.container;
          container.className = 'image-container';

          // Tạo spinner
          this.spinerContainer = document.createElement('div');
          this.spinerContainer.style.width = 'var(--spinner-container-width)';
          this.spinerContainer.style.top = '50%';
          this.spinerContainer.style.display = 'flex';
          this.spinerContainer.style.justifyContent = 'center';
          this.spinner = document.createElement('div');
          const spinner = this.spinner;
          spinner.style.border = '4px solid rgba(0, 0, 0, 0.2)';
          spinner.style.borderLeftColor = 'var(--spinner-color)';
          spinner.style.borderRadius = '50%';
          spinner.style.width = '50px';
          spinner.style.height = '50px';
          spinner.style.zIndex = '10';
          spinner.style.animation = 'spin 1s linear infinite';

          // Tạo thẻ img
          this.img = document.createElement('img');
          const img = this.img;
          img.style.maxWidth = '100%';
          img.style.maxHeight = '100%';
          img.style.display = 'none';

          // Đặt thuộc tính src từ thuộc tính của custom element
          this.imgSrc = this.getAttribute('src');
          const altText = this.getAttribute('alt');
          img.alt = altText;

          img.onload = () => {
               spinner.style.display = 'none';
               img.style.display = 'block';

               // Cập nhật tỷ lệ của container dựa trên tỷ lệ của ảnh
               const aspectRatio = img.naturalWidth / img.naturalHeight;
               const parentWidth = 0;
               const parentHeight = shadowRoot.host.clientHeight;
               if (parentWidth != 0 && parentHeight != 0) {
                    if (parentWidth / parentHeight > aspectRatio) {
                         container.style.height = `${parentHeight}px`;
                         container.style.width = `${parentHeight * aspectRatio}px`;
                    } else {
                         container.style.width = `${parentWidth}px`;
                         container.style.height = `${parentWidth / aspectRatio}px`;
                    }
               } else if (parentHeight != 0) {
                    container.style.height = `${parentHeight}px`;
                    container.style.width = `${parentHeight * aspectRatio}px`;
               }

          };

          img.onerror = () => {
               spinner.style.display = 'none';
               console.error('Image failed to load');
          };

          img.style.display = 'none';
          this.spinerContainer.appendChild(spinner);
          container.appendChild(this.spinerContainer);
          container.appendChild(img);

          const style = document.createElement('style');
          style.textContent = `@keyframes spin{0%{transform:rotate(0deg);}100% {transform: rotate(360deg);}}`;

          shadowRoot.append(style, container);
     }

     connectedCallback() {
          const customContainer = this.closest('hor-image-container');
          // Nếu ở bên trong custom-image-container thì apply cơ chế lazy load theo IntersectionObserver 
          if (customContainer) {
               const observer = new IntersectionObserver(entries => {
                    entries.forEach(entry => {
                         if (entry.isIntersecting) {
                              setTimeout(() => {
                                   this.img.src = this.imgSrc;
                              }, 2000); // Delay for demonstration, can be removed
                              observer.unobserve(this);
                         }
                    });
               }, {
                    root: null, // set null thì element sẽ dựa theo kích thước cửa sổ 
                    threshold: 0.1 // 10% of the image is visible
               });

               observer.observe(this);
          } else {
               this.img.src = this.imgSrc;
          }

     }

}
customElements.define('loading-image', LoadingImage);
TrdHuy commented 3 weeks ago

249537670DB03870A206C3933EB9B19BBB515B75D02A6494FF2BC538427ACB02