본문 바로가기
개발 일지

주문 페이지 - 크기 수정 / Pagination

by 만식 2024. 5. 24.

 

 

오류 개선 - menu.html

오류 : Unresolved attribute reference 'any' for class 'Sequence’

 

수정 전

        if faces.any():
            print("faces>>>>>>>", faces)
            break

문제의 원인은 faces가 비어있는지 확인하는 방법에서 발생합니다. faces는 Numpy 배열이므로 faces.any() 메서드는 사용할 수 없습니다. 대신 faces가 비어있는지 확인하기 위해 Numpy 배열의 크기를 확인하는 방법을 사용해야 합니다.

 

수정 후

        if len(faces) > 0:
            print("faces>>>>>>>", faces)
            break

 

탬플릿 메뉴 추천 후 음성 안내 ( 완료 )

음성안내 후 다시 음성 받을 준비 ( 완료 )

→ 마이크 입력을 계속해서 넣지 않으면 마이크가 꺼져버림 → 고연령 ( 마이크입력시간 길게 + 필요 없는 말은 필터링해서 메뉴 반영 x ) → 저연령 ( 질문 마스코트를 만들어서 버튼식으로 입력을 받기 )

메뉴에 담기는 모션 수정 필요

얼굴인식 후에 마이크 입력 단에 오류가 발생 수정 必

 

<!DOCTYPE html>
<html lang="ko">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Silver Lining</title>
    <link href="https://maxcdn.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css" rel="stylesheet">
    <style>
        /* 기존 스타일 */

        .menu-item,
        .selected-item {
            display: flex;
            justify-content: center;
            align-items: center;
            padding: 10px;
            margin: 10px;
            border: 1px solid #ddd;
            border-radius: 5px;
            flex-direction: column;
            transition: transform 0.2s;
        }

        .menu-item img {
            width: 250px;
            height: 250px;
        }

        .menu-item:hover {
            transform: scale(1.05);
        }

        .selected-item span {
            margin: 0 5px;
        }

        .actions button {
            margin: 5px;
        }

        .card-body {
            text-align: center;
        }

        .fly-to-cart {
            position: absolute;
            z-index: 1000;
            transition: transform 1s ease-in-out;
        }

        #selectedItemsList {
            display: flex;
            flex-direction: column;
            gap: 10px;
            align-items: center;
        }

        .selected-item {
            display: flex;
            align-items: center;
            padding: 10px;
            border: 1px solid #ddd;
            border-radius: 5px;
            flex-direction: row;
            width: 600px;
            margin: 5px 0;
        }

        .selected-item img {
            width: 50px;
            height: 50px;
            margin-right: 10px;
        }
    </style>
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-cookie/1.4.1/jquery.cookie.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
</head>
<body>
<div class="container mt-5">
    <h2 class="text-center mb-4">Silver Lining</h2>
    <form id="speechForm">
        {% csrf_token %}
    </form>
    <p id="transcription"></p>

    <div class="button d-flex flex-wrap justify-content-center" id="menuContainer">
    </div>

    <div class="selected-items mt-4">
        <h3 class="text-center">선택한 상품</h3>
        <div id="selectedItemsList"></div>
    </div>
    <div class="total-price mt-3">
        <h3 class="text-center">총 금액: <span id="totalPrice">0원</span></h3>
    </div>
    <div class="actions text-center">
        <button class="btn btn-danger" onclick="clearItems()">전체삭제</button>
        <button class="btn btn-success" id="submitOrderBtn">결제하기</button>
    </div>
</div>

<script>
    window.addEventListener('load', function () {
        const hashtags = "";
        updateMenus(hashtags)
        const welcomeMessage = "반갑습니다. 원하시는 메뉴를 추천해 드리겠습니다. 필요한 것이 있다면 말씀해주세요.";
        speak(welcomeMessage, startSpeechRecognition);
    });

    const startButton = document.getElementById('startButton');
    const transcription = document.getElementById('transcription');

    function getCsrfToken() {
        const csrfTokenElement = document.querySelector('input[name="csrfmiddlewaretoken"]');
        if (csrfTokenElement) {
            return csrfTokenElement.value;
        } else {
            console.error('CSRF 토큰을 찾을 수 없습니다.');
            return null;
        }
    }

    function speak(text, callback) {
        const synth = window.speechSynthesis;
        const utterance = new SpeechSynthesisUtterance(text);
        utterance.lang = 'ko-KR';
        utterance.onend = function () {
            console.log("음성 안내가 끝났습니다.");
            if (callback) {
                callback();
            }
        };
        synth.speak(utterance);
    }

    function startSpeechRecognition() {
        if (!('webkitSpeechRecognition' in window)) {
            alert("음성 인식이 지원되지 않는 브라우저입니다.");
        } else {
            const recognition = new webkitSpeechRecognition();
            recognition.lang = 'ko-KR';
            recognition.start();
            recognition.onresult = function (event) {
                const transcript = event.results[0][0].transcript;
                transcription.textContent = transcript;
                const csrfToken = getCsrfToken();
                axios.post('{% url "orders:aibot" %}', {inputText: transcript}, {
                    headers: {
                        'X-CSRFToken': csrfToken
                    }
                })
                    .then(function (response) {
                        const responseText = response.data.responseText;
                        const hashtags = response.data.hashtags;
                        console.log('서버 응답:', responseText);
                        updateMenus(hashtags); // 메뉴 업데이트 먼저 실행
                        speak(responseText, startSpeechRecognition); // 그 다음에 음성 안내 실행
                    })
                    .catch(function (error) {
                        console.error('에러:', error);
                    });
            };
            recognition.onend = function () {
                startButton.textContent = '음성 입력 다시 시작';
            };
        }
    }

    function updateMenus(hashtags) {
        $.ajax({
            url: '/orders/get_menus/',
            data: {hashtags: hashtags},
            dataType: 'json',
            success: function (data) {
                const menus = data.menus;
                const menuContainer = $('#menuContainer');
                menuContainer.empty();
                menus.forEach(menu => {
                    const menuItem = `
                        <div class="menu-item card" onclick="addItem('${menu.food_name}', ${menu.price}, '${menu.img_url}', this)">
                            <img src="${menu.img_url}" alt="${menu.food_name}" class="card-img-top">
                            <div class="card-body text-center">
                                <h5 class="card-title text-primary">${menu.food_name}</h5>
                                <p class="card-text text-muted">${menu.price}원</p>
                            </div>
                        </div>
                    `;
                    menuContainer.append(menuItem);
                });
            },
            error: function (error) {
                console.error('메뉴 업데이트 중 오류 발생:', error);
            }
        });
    }

    const selectedItems = {};

    function addItem(name, price, imgUrl, element) {
        if (!selectedItems[name]) {
            selectedItems[name] = {price: price, count: 1, imgUrl: imgUrl};
        } else {
            selectedItems[name].count += 1;
        }
        updateSelectedItemsList();
        flyToCart(element, document.getElementById('selectedItemsList'));
    }

    function updateSelectedItemsList() {
        const selectedItemsList = document.getElementById('selectedItemsList');
        selectedItemsList.innerHTML = '';
        let totalPrice = 0;
        for (const [name, item] of Object.entries(selectedItems)) {
            const itemElement = document.createElement('div');
            itemElement.classList.add('selected-item');
            itemElement.innerHTML = `
                <img src="${item.imgUrl}" alt="${name}">
                <span>${name}</span>
                <span>${item.price}원</span>
                <span>${item.count}개</span>
                <button class="btn btn-danger btn-sm" onclick="removeItem('${name}')">삭제</button>
            `;
            selectedItemsList.appendChild(itemElement);
            totalPrice += item.price * item.count;
        }
        document.getElementById('totalPrice').textContent = `${totalPrice}원`;
    }

    function removeItem(name) {
        if (selectedItems[name]) {
            delete selectedItems[name];
            updateSelectedItemsList();
        }
    }

    function clearItems() {
        for (const key in selectedItems) {
            delete selectedItems[key];
        }
        updateSelectedItemsList();
    }

    function flyToCart(element, targetElement) {
        const imgToDrag = element.querySelector("img");
        if (imgToDrag) {
            const imgClone = imgToDrag.cloneNode(true);
            const rect = imgToDrag.getBoundingClientRect();
            imgClone.style.position = 'absolute';
            imgClone.style.top = rect.top + 'px';
            imgClone.style.left = rect.left + 'px';
            imgClone.style.width = '100px';
            imgClone.style.height = '100px';
            imgClone.classList.add('fly-to-cart');
            document.body.appendChild(imgClone);

            const targetRect = targetElement.getBoundingClientRect();
            setTimeout(() => {
                imgClone.style.transform = `translate(${targetRect.left - rect.left}px, ${targetRect.top - rect.top}px) scale(0.5)`;
            }, 10);

            setTimeout(() => {
                imgClone.remove();
            }, 1000);
        }
    }

    document.getElementById('submitOrderBtn').addEventListener('click', function () {
        const selectedItemsArray = Object.entries(selectedItems).map(([name, item]) => {
            return {name: name, count: item.count};
        });

        const totalPrice = calculateTotalPrice(selectedItems);

        $.ajax({
            url: '/orders/submit_order/',
            cache: false,
            dataType: 'json',
            type: 'POST',
            contentType: 'application/json',
            data: JSON.stringify({items: selectedItemsArray, total_price: totalPrice}),
            beforeSend: function (xhr) {
                xhr.setRequestHeader('X-CSRFToken', $.cookie('csrftoken'));
            },
            success: function (data) {
                console.log('주문이 성공적으로 처리되었습니다.');
                window.location.href = '/orders/order_complete/' + data.order_number + '/';
            },
            error: function (error) {
                console.error('주문 처리 중 오류가 발생했습니다:', error);
            }
        });
    });

    function calculateTotalPrice(selectedItems) {
        let totalPrice = 0;
        for (const item of Object.values(selectedItems)) {
            totalPrice += item.price * item.count;
        }
        return totalPrice;
    }
</script>
<script src="https://cdn.jsdelivr.net/npm/@popperjs/core@2.5.4/dist/umd/popper.min.js"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js"></script>
</body>
</html>

 

페이지네이션 - javascript로 변환

Template를 가져올때 javascript로 가져올 수 있도록 변환

기존에 음료 종류가 많아서 한 페이지에 담기 어려움이 있어, 버튼 형식의 1,2 페이지로 나누어서 적용

 

function updateMenus(hashtags, page = 1) {
        $.ajax({
            url: '/orders/get_menus/',
            data: {hashtags: hashtags, page: page},
            dataType: 'json',
            success: function (data) {
                const menus = data.menus;
                const menuContainer = $('#menuContainer');
                menuContainer.empty();
                menus.forEach(menu => {
                    const menuItem = `
                        <div class="menu-item card" onclick="addItem('${menu.food_name}', ${menu.price}, '${menu.img_url}', this)">
                            <img src="${menu.img_url}" alt="${menu.food_name}" class="card-img-top">
                            <div class="card-body text-center">
                                <h5 class="card-title text-primary">${menu.food_name}</h5>
                                <p class="card-text text-muted">${menu.price}원</p>
                            </div>
                        </div>
                    `;
                    menuContainer.append(menuItem);
                });
                console.log(data.page_count)
                updatePaginationButtons(data.page_count, page);
            },
            error: function (error) {
                console.error('메뉴 업데이트 중 오류 발생:', error);
            }
        });
    }

    function updatePaginationButtons(totalPages, currentPage) {
        const paginationButtons = $('#paginationButtons');
        paginationButtons.empty();

        if (currentPage > 1) {
            const prevButton = `<button class="btn btn-outline-primary mr-1" onclick="changePage(${currentPage - 1})">이전</button>`;
            paginationButtons.append(prevButton);
        }

        for (let i = 1; i <= totalPages; i++) {
            const button = `<button class="btn btn-outline-primary mr-1" onclick="changePage(${i})">${i}</button>`;
            paginationButtons.append(button);
        }

        if (currentPage < totalPages) {
            const nextButton = `<button class="btn btn-outline-primary" onclick="changePage(${currentPage + 1})">다음</button>`;
            paginationButtons.append(nextButton);
        }
    }

    function changePage(pageNumber) {
        const hashtags = "";
        updateMenus(hashtags, pageNumber);
    }
def get_menus(request):
    hashtags = request.GET.get('hashtags', None)
    if hashtags:
        menus = Menu.objects.filter(hashtags__hashtag=hashtags)
    else:
        menus = Menu.objects.all()

    # 페이지네이션 설정
    paginator = Paginator(menus, 9)  # 페이지 당 9개의 메뉴

    page_number = request.GET.get('page')
    try:
        menus = paginator.page(page_number)
    except PageNotAnInteger:
        # 페이지 번호가 정수가 아닌 경우, 첫 번째 페이지를 반환
        menus = paginator.page(1)
    except EmptyPage:
        # 페이지가 비어있는 경우, 마지막 페이지를 반환
        menus = paginator.page(paginator.num_pages)

    menu_list = [
        {
            'food_name': menu.food_name,
            'price': menu.price,
            'img_url': menu.img.url if menu.img else ''
        } for menu in menus
    ]

    # 총 페이지 수 계산
    total_pages = paginator.num_pages

    return JsonResponse({'menus': menu_list, 'page_count': total_pages})

 

음성AI - 버튼이 없어도 음성호출

 

  • 버튼 형식의 음성인식 대신, 음성만 호출
  • 기존에는 AI가 추천음료를 말한다음, template상 음료가 변경되었다면, 추천 음료를 먼저 보여주고 설명해 주는 AI형식으로 개선
  • 기존 한번의 음성인식 기능으로 한 번의 주문만 받았다면, 여러 번의 음성대화형식으로 나오게끔 개선

<div class="container mt-5">
    <h2 class="text-center mb-4">Silver Lining</h2>
    <form id="speechForm">
        {% csrf_token %}
    </form>
    <p id="transcription"></p>

    <div class="button d-flex flex-wrap justify-content-center" id="menuContainer">
    </div>

    <div class="selected-items mt-4">
        <h3 class="text-center">선택한 상품 🛒</h3>
        <div id="selectedItemsList"></div>
    </div>
    <div class="total-price mt-3">
        <h3 class="text-center">총 금액: <span id="totalPrice">0원</span></h3>
    </div>
    <div class="actions text-center">
        <button class="btn btn-danger" onclick="clearItems()">전체삭제</button>
        <button class="btn btn-success" id="submitOrderBtn">결제하기</button>
    </div>
</div>

<script>
    window.addEventListener('load', function () {
        const hashtags = "";
        updateMenus(hashtags)
        const welcomeMessage = "반갑습니다. 원하시는 메뉴를 추천해 드리겠습니다. 필요한 것이 있다면 말씀해주세요.";
        speak(welcomeMessage, startSpeechRecognition);
    });

    const startButton = document.getElementById('startButton');
    const transcription = document.getElementById('transcription');

    function getCsrfToken() {
        const csrfTokenElement = document.querySelector('input[name="csrfmiddlewaretoken"]');
        if (csrfTokenElement) {
            return csrfTokenElement.value;
        } else {
            console.error('CSRF 토큰을 찾을 수 없습니다.');
            return null;
        }
    }

    function speak(text, callback) {
        const synth = window.speechSynthesis;
        const utterance = new SpeechSynthesisUtterance(text);
        utterance.lang = 'ko-KR';
        utterance.onend = function () {
            console.log("음성 안내가 끝났습니다.");
            if (callback) {
                callback();
            }
        };
        synth.speak(utterance);
    }

    function startSpeechRecognition() {
        if (!('webkitSpeechRecognition' in window)) {
            alert("음성 인식이 지원되지 않는 브라우저입니다.");
        } else {
            const recognition = new webkitSpeechRecognition();
            recognition.lang = 'ko-KR';
            recognition.start();
            recognition.onresult = function (event) {
                const transcript = event.results[0][0].transcript;
                transcription.textContent = transcript;
                const csrfToken = getCsrfToken();
                axios.post('{% url "orders:aibot" %}', {inputText: transcript}, {
                    headers: {
                        'X-CSRFToken': csrfToken
                    }
                })
                    .then(function (response) {
                        const responseText = response.data.responseText;
                        const hashtags = response.data.hashtags;
                        console.log('서버 응답:', responseText);
                        updateMenus(hashtags); // 메뉴 업데이트 먼저 실행
                        speak(responseText, startSpeechRecognition); // 그 다음에 음성 안내 실행
                    })
                    .catch(function (error) {
                        console.error('에러:', error);
                    });
            };
            recognition.onend = function () {
                startButton.textContent = '음성 입력 다시 시작';
            };
        }
    }

    function updateMenus(hashtags) {
        $.ajax({
            url: '/orders/get_menus/',
            data: {hashtags: hashtags},
            dataType: 'json',
            success: function (data) {
                const menus = data.menus;
                const menuContainer = $('#menuContainer');
                menuContainer.empty();
                menus.forEach(menu => {
                    const menuItem = `
                        <div class="menu-item card" onclick="addItem('${menu.food_name}', ${menu.price}, '${menu.img_url}', this)">
                            <img src="${menu.img_url}" alt="${menu.food_name}" class="card-img-top">
                            <div class="card-body text-center">
                                <h5 class="card-title text-primary">${menu.food_name}</h5>
                                <p class="card-text text-muted">${menu.price}원</p>
                            </div>
                        </div>
                    `;
                    menuContainer.append(menuItem);
                });
            },
            error: function (error) {
                console.error('메뉴 업데이트 중 오류 발생:', error);
            }
        });
    }

    const selectedItems = {};

    function addItem(name, price, imgUrl, element) {
        if (!selectedItems[name]) {
            selectedItems[name] = {price: price, count: 1, imgUrl: imgUrl};
        } else {
            selectedItems[name].count += 1;
        }
        updateSelectedItemsList();
        flyToCart(element, document.getElementById('selectedItemsList'));
    }

    function updateSelectedItemsList() {
        const selectedItemsList = document.getElementById('selectedItemsList');
        selectedItemsList.innerHTML = '';
        let totalPrice = 0;
        for (const [name, item] of Object.entries(selectedItems)) {
            const itemElement = document.createElement('div');
            itemElement.classList.add('selected-item');
            itemElement.innerHTML = `
                <img src="${item.imgUrl}" alt="${name}">
                <span>${name}</span>
                <span>${item.price}원</span>
                <span>${item.count}개</span>
                <button class="btn btn-danger btn-sm" onclick="removeItem('${name}')">삭제</button>
            `;
            selectedItemsList.appendChild(itemElement);
            totalPrice += item.price * item.count;
        }
        document.getElementById('totalPrice').textContent = `${totalPrice}원`;
    }

    function removeItem(name) {
        if (selectedItems[name]) {
            delete selectedItems[name];
            updateSelectedItemsList();
        }
    }

 

디테일 추가 : 장바구니에 음료가 담길 때에 장바구니로 이미지가 이동하면서 크기가 점점 작아짐

function flyToCart(element, targetElement) {
    const imgToDrag = element.querySelector("img");
    if (imgToDrag) {
        const imgClone = imgToDrag.cloneNode(true);
        let rect = imgToDrag.getBoundingClientRect();
        imgClone.style.position = 'absolute';
        imgClone.style.top = rect.top + 'px';
        imgClone.style.left = rect.left + 'px';
        imgClone.style.width = '250px'; // 초기 이미지 크기
        imgClone.style.height = '250px'; // 초기 이미지 크기
        imgClone.classList.add('fly-to-cart');
        document.body.appendChild(imgClone);

        // 🛒 아이콘 위치 설정
        const cartIconRect = targetElement.getBoundingClientRect();

        // 카트 아이콘 중앙 위치 계산
        const cartCenterX = cartIconRect.left + cartIconRect.width / 2;
        const cartCenterY = cartIconRect.top + cartIconRect.height / 2;

        // 이미지 이동 속도 계산
        const dx = (cartCenterX - rect.left) / 120; // x 방향 이동 속도
        const dy = (cartCenterY - rect.top) / 120; // y 방향 이동 속도

        // 이미지 크기 감소 속도 계산
        const dw = (250 - 100) / 120; // 이미지 크기 감소 속도

        // 이미지 이동 및 크기 조절 함수
        function moveImage() {
            rect = imgClone.getBoundingClientRect();
            if ((dx > 0 && rect.left < cartCenterX) || (dx < 0 && rect.left > cartCenterX) ||
                (dy > 0 && rect.top < cartCenterY) || (dy < 0 && rect.top > cartCenterY)) {
                imgClone.style.left = (rect.left + dx) + 'px';
                imgClone.style.top = (rect.top + dy) + 'px';

                // 이미지 크기 조절
                const newWidth = parseFloat(imgClone.style.width) - dw;
                imgClone.style.width = newWidth + 'px';
                imgClone.style.height = newWidth + 'px';

                requestAnimationFrame(moveImage);
            } else {
                imgClone.remove();
            }
        }

        // 이미지 이동 시작
        moveImage();
    }
}

 

주문하기 - 얼굴인식 안내

 

주문하기를 누르면, 얼굴이 인식되기까지의 시간동안 로딩중과 같은 문구를 적용

 

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Order Page</title>
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css">
    <style>
        body {
            font-family: Arial, sans-serif;
            text-align: center;
            background-color: #f8f9fa;
        }

        h1 {
            color: #ef4040;
            margin-top: 70px;
            font-size: 70px;
        }

        p {
            font-size: 45px;
            margin: 20px;
        }

        .button-container {
            display: flex;
            flex-direction: column;
            align-items: center;
            margin-top: 70px;
        }

        button {
            width: 700px;
            height: 600px;
            font-size: 120px;
            font-weight: bold;
            padding: 40px 80px;
            color: white;
            background-color: #ef4040;
            border: none;
            border-radius: 20px;
            cursor: pointer;
        }

        button:hover {
            background-color: #d93636;
        }

        .spinner-container {
            display: none;
        }

        .status {
            font-size: 50px;
            margin-top: 100px;
            display: none;
        }
    </style>
</head>
<body>
<h1>여기에서 주문하세요!</h1>
<p>화면을 터치해 주세요</p>
<div class="button-container">
    <form id="order-form" method="post" action="{% url 'orders:face_recognition' %}">
        {% csrf_token %}
        <button type="submit" id="order-button">주문하기</button>
        <div class="spinner-container" id="spinner">
            <div class="spinner-border" style="width: 15rem; height: 15rem;" role="status">
                <span class="visually-hidden"></span>
            </div>
        </div>
        <div class="status" id="status">얼굴 인식을 진행하고 있습니다</div>
    </form>
</div>

<script>
    document.getElementById("order-form").addEventListener("submit", function () {
        document.getElementById("spinner").style.display = "block";
        document.getElementById("status").style.display = "block";
        document.getElementById("order-button").style.display = "none";
    });
</script>
</body>
</html>

 

 

주문 페이지 - 크기 수정 / Pagination 

고연령층도 간편하고 쉽게 찾을 수 있게, 글자 크기와 버튼 크기 수정 (세로기준)

 /* 기존 스타일 */
        body {
            font-size: 2rem;
        }

        .h2, h2 {
            font-size: 4rem;
            background-color: #ef4040;
            color: #f8f9fa;
            padding: 20px;
        }

        .h3, h3 {
            font-size: 2.75rem;
        }

        .h5, h5 {
            font-size: 1.5rem;
        }

        .menu-item {
            display: flex;
            justify-content: center;
            align-items: center;
            padding: 10px;
            margin: 10px;
            border: 1px solid #ddd;
            border-radius: 5px;
            flex-direction: column;
            transition: transform 0.2s;
            width: 280px;
        }

 

→ 추가 문제 : 장바구니에 담긴 상품들의 종류의 양이 많을 시, 페이지를 넘어버리는 점 (장바구니의 스크롤을 만든다.)

<style>
#selectedItemsList {
            height: 350px;
            overflow-y: auto;
            flex-grow: 1;
            padding: 10px;
            border-radius: 5px;
        }

        .scroll-button {
            margin: 5px 0;
            height: 155px;
            width: 50px;
            display: flex;
            justify-content: center;
            align-items: center;
        }

        .selected-item {
            display: flex;
            align-items: center;
            padding: 10px;
            border: 1px solid #ddd;
            border-radius: 5px;
            flex-direction: row;
            width: 850px;
            margin: 5px 0;
            justify-content: space-between;
        }

        .btn-custom {
            padding: 30px 95px;
            font-size: 3.5rem;
        }

        .btn-page {
            padding: .375rem 1.75rem;
            font-size: 3rem;
        }

        .scroll-button {
            margin: 10px 0;
            padding: 10px 20px;
            font-size: 1.5rem;
        }

        .selected-item img {
            width: 50px;
            height: 50px;
            margin-right: 10px;
        }
</style>

        
        <body>
        <div class="pagination justify-content-center" id="paginationButtons">
    </div>
    <h3 class="text-center">선택한 상품 🛒</h3>
    <div class="selected-items mt-4 d-flex">
        <div id="selectedItemsList" class="flex-grow-1"></div>
        <div class="d-flex flex-column justify-content-center align-items-center">
            <button class="btn btn-primary scroll-button mb-2" onclick="scrollSelectedItemsList(-100)">
                <i class="fas fa-arrow-up"></i>
            </button>
            <button class="btn btn-primary scroll-button" onclick="scrollSelectedItemsList(100)">
                <i class="fas fa-arrow-down"></i>
            </button>
        </div>
    </div>
    </body>