ひろばニュース|2026/05/22~2026/05/28号

みかん

今週のニュースです!新着などのお知らせだよ!

みかんの新着おんがく紹介①|フリーBGM『Chill Time(チルタイム)』『ソードランチャー』
フリーBGM:Chill Time(チルタイム)
フリーBGM|ループ対応、用途:Youtube動画向け、配信・雑談BGM、時間:2分01秒、BPM:55、キー:D#、ジャンル:ゆったり、あかるい、楽器:オルゴール|ゆったりとしたオルゴール曲です!配信や雑談、寝る前、落ち着いたシーンなら何にでも合わせやすい一曲です!
フリーBGM:ソードランチャー
フリーBGM|ループ対応、用途:Youtube動画向け、ゲーム・アプリ向け、時間:1分00秒、BPM:318、キー:F#、ジャンル:あかるい、楽器:ギター、ピアノ|超ハイスピードの戦闘・バトル向けのギターバッキング系BGMです!タイムアタック系やピンチなときのシーンなどにぴったり!
みかん

今週は2曲投稿しました!どちらもYoutubeなどの動画にぴったりなのでぜひ一度聞いてみてください!

みかんの新着おんがく紹介②|今週の15秒&30秒BGMはこちら!
15秒フリーBGM:コックピット
15秒フリーBGM|ループ対応、用途:Shorts・Tiktok向け、ゲーム・アプリ向け、15秒CM向け、時間:0分15秒、BPM:119、キー:C#m、ジャンル:みらい、楽器:シンセサイザー|15秒BGM第50弾!宇宙船のコックピットで、軌道に乗った頃の時間をイメージしました!宇宙系・神秘系のシーン、何となくキラキラしたおしゃれな印象にしたい場面などにぴったり!
30秒フリーBGM:プラネタリウム
30秒フリーBGM|ループ対応、用途:Youtube動画向け、Shorts・Tiktok向け、ゲーム・アプリ向け、時間:0分30秒、BPM:63、キー:G#m、ジャンル:ゆったり、おしゃれ、みらい、楽器:シンセサイザー|30秒BGM第26弾!プラネタリウムみたいな夜の空間をイメージした1曲です!天体観測や星を眺めるシーン、あとはそれこそプラネタリウムのBGMにぴったり!
みかん

今週も合計5曲以上の15秒&30秒BGMを追加しました!他にもあるのでぜひ見てみてください!

いろはの新着セットリスト|『グランドパレード』×『食器』
フリーBGM:グランドパレード
フリーBGM|ループ対応、用途:Youtube動画向け、ゲーム・アプリ向け、時間:2分41秒、BPM:125、キー:E、ジャンル:おしゃれ、あかるい、楽器:ストリングス|オーケストラ風に仕上げた、パレード系な一曲です!感謝祭やフェスティバル、フィナーレのシーンなどの楽しくて盛り上がる場面にぴったりです!
フリー効果音:食器
フリー効果音|ジャンル:かんきょう、料理で卵とかを軽くかき混ぜてるときの効果音です!木製の容器と菜箸の当たる音なので料理シーンやご飯シーンにぴったり!
いろは

今週のセットリスト案のテーマは『パーティー』です。豪華なオーケストラが響くホールで食事会、のような食器の音などが入っているとリアルになりますわね。

ことはの新着ソースコード|『おんがく素材チャット』アップデートのお知らせ!
// --- 1. バックエンド:タグ&投稿データ取得API ---
add_action('rest_api_init', function () {
    register_rest_route('mikan/v1', '/sounds', array(
        'methods' => 'GET',
        'callback' => function() {
            $tags = get_terms(array(
                'taxonomy'   => 'post_tag',
                'hide_empty' => true,
                'orderby'    => 'count',
                'order'      => 'DESC',
            ));

            $result = array();
            foreach ($tags as $tag) {
                $posts = get_posts(array(
                    'post_type' => 'post',
                    'tag_id' => $tag->term_id,
                    'posts_per_page' => 10
                ));
                
                $sounds = array();
                foreach ($posts as $p) {
                    $sounds[] = array(
                        'title' => $p->post_title,
                        'url' => get_permalink($p->ID)
                    );
                }
                $result[] = array('name' => $tag->name, 'sounds' => $sounds);
            }
            return $result;
        },
        'permission_callback' => '__return_true',
    ));
});

// --- 2. フロントエンド:UI・CSS・ロジック ---
add_action('wp_footer', function () {
?>
<style>
    .mikan-loader {
        border: 4px solid #f3f3f3;
        border-top: 4px solid #ff9800;
        border-radius: 50%;
        width: 30px;
        height: 30px;
        animation: mikan-spin 1s linear infinite;
        margin: 20px auto;
    }
    @keyframes mikan-spin {
        0% { transform: rotate(0deg); }
        100% { transform: rotate(360deg); }
    }
    #m-btn:hover { transform: scale(1.1); }
    #m-btn { transition: transform 0.2s; }
    /* フェードインアニメーション */
    @keyframes fadeIn {
        from { opacity: 0; transform: translateY(10px); }
        to { opacity: 1; transform: translateY(0); }
    }
</style>

<div id="mikan-bot" style="position:fixed; bottom:10px; right:10px; z-index:9999; font-family:sans-serif;">
    <div id="m-tooltip" style="display:none; position:absolute; bottom:75px; right:0; background:white; color:#333; padding:8px 12px; border-radius:10px; border:2px solid #ff9800; font-size:12px; white-space:nowrap; box-shadow:0 4px 10px rgba(0,0,0,0.1); font-weight:bold;">チャットボットで探してみる?</div>
    
    <button id="m-btn" style="background:#D1F2E5; border:none; border-radius:50%; width:60px; height:60px; cursor:pointer; box-shadow:0 4px 10px rgba(0,0,0,0.3); padding:0; overflow:hidden; display:flex; align-items:center; justify-content:center;">
        <img src="https://mikanmusicsquare.com/wp-content/uploads/2025/11/cropped-favicon.png" style="width:70%; height:70%; object-fit:contain;" alt="🍊">
    </button>
    
    <div id="m-window" style="display:none; width:320px; height:480px; background:white; border-radius:12px; flex-direction:column; overflow:hidden; box-shadow:0 8px 30px rgba(0,0,0,0.2); border:1px solid #eee;">
        <div style="background:#ff9800; color:white; padding:15px; font-weight:bold; display:flex; justify-content:space-between;">
            <span>おんがく素材チャット</span>
            <span id="m-close" style="cursor:pointer; font-size:20px;">×</span>
        </div>
        
        <div id="m-body" style="flex:1; padding:15px; overflow-y:auto; background:#fff; scroll-behavior: smooth;">
            <div id="m-msg">こんにちは!みかんBotです!気になるタグを選んでね。</div>
            <div id="m-tags" style="margin-top:10px; min-height:50px;"></div>
            
            <div id="m-results"></div>
            
            <hr style="margin:20px 0; border:0; border-top:1px dashed #ddd;">
            
            <div id="m-cart-area">
                <strong style="font-size:14px;">🧺キープ中のリスト</strong>
                <div id="m-cart-list" style="font-size:12px; margin-top:10px; color:#666;"></div>
            </div>
        </div>
    </div>
</div>

<script>
(function() {
    const btn = document.getElementById('m-btn');
    const win = document.getElementById('m-window');
    const body = document.getElementById('m-body'); // スクロール対象
    const tagsDiv = document.getElementById('m-tags');
    const resultsDiv = document.getElementById('m-results');
    const cartDiv = document.getElementById('m-cart-list');
    const close = document.getElementById('m-close');
    const tooltip = document.getElementById('m-tooltip');

    let cart = JSON.parse(localStorage.getItem('mikan_keep_list') || '[]');

    const saveCart = () => {
        localStorage.setItem('mikan_keep_list', JSON.stringify(cart));
        renderCart();
    };

 // --- JavaScript内の renderCart 関数を以下に差し替え ---

const renderCart = () => {
    const cartDiv = document.getElementById('m-cart-list');
    
    if (cart.length === 0) {
        cartDiv.innerHTML = '<div style="color:#999; text-align:center; padding:10px;">カゴは空っぽだよ。</div>';
        return;
    }

    // リスト部分のHTML生成
    let html = cart.map((item, i) => `
        <div style="display:flex; justify-content:space-between; align-items:center; margin-bottom:5px; background:#f9f9f9; padding:5px 8px; border-radius:4px; border:1px solid #eee; animation: fadeIn 0.3s;">
            <span style="font-size:11px; color:#333; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; flex:1;">${item.title}</span>
            <span onclick="window.mikanRemove(${i})" style="cursor:pointer; color:#ff4444; font-weight:bold; padding:0 5px; font-size:14px;">✕</span>
        </div>
    `).join('');

    // ☆--- アップデート部分!【まとめてダウンロードボタンの追加】---☆
    html += `
        <div style="margin-top:10px; padding-top:10px; border-top: 1px solid #eee;">
            <button id="m-batch-dl" style="width:100%; background:#007b43; color:white; border:none; border-radius:6px; padding:10px; cursor:pointer; font-weight:bold; font-size:13px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); transition: 0.2s;">
                🧺 キープした曲をまとめてDL
            </button>
            <div style="font-size:9px; color:#999; text-align:center; margin-top:5px;">※ZIPを作成してダウンロードします</div>
        </div>
    `;

    cartDiv.innerHTML = html;

const dlBtn = document.getElementById('m-batch-dl');
if (dlBtn) {
    dlBtn.onclick = () => {
        if (cart.length === 0) return;
        
        // カート内の記事URLを連結
        const urls = cart.map(item => item.url).join(',');
        
        const downloadUrl = `/download.php?urls=${encodeURIComponent(urls)}`;
        
        // ダウンロード開始
        window.location.href = downloadUrl;
    };
}
    
};

    window.mikanAdd = (title, url) => {
        if (!cart.some(i => i.url === url)) {
            cart.push({title, url});
            saveCart();
        }
    };

    window.mikanRemove = (index) => {
        cart.splice(index, 1);
        saveCart();
    };

    btn.onclick = () => {
        const isOpening = win.style.display === 'none';
        win.style.display = isOpening ? 'flex' : 'none';
        tooltip.style.display = 'none';

        if (isOpening && !tagsDiv.hasChildNodes()) {
            tagsDiv.innerHTML = '<div class="mikan-loader"></div>';

            fetch('/wp-json/mikan/v1/sounds')
                .then(res => res.json())
                .then(data => {
                    tagsDiv.innerHTML = '';
                    data.forEach(t => {
                        const b = document.createElement('button');
                        b.innerText = t.name;
                        b.style = "margin:3px; padding:6px 12px; border:1px solid #ff9800; background:white; border-radius:20px; cursor:pointer; font-size:12px; transition:0.2s;";
                        b.onmouseover = () => b.style.background = '#fff4e5';
                        b.onmouseout = () => b.style.background = 'white';
                        
                        b.onclick = () => {
                            let html = `<div style="margin-top:20px; border-left:3px solid #ff9800; padding-left:10px; animation: fadeIn 0.4s ease-out;"><strong>${t.name}の検索結果:</strong><br>`;
                            if(t.sounds.length === 0) {
                                html += '<span style="font-size:11px;">該当する曲がありませんでした。</span>';
                            } else {
                                t.sounds.forEach(s => {
                                    html += `<div style="margin:8px 0; border-bottom:1px solid #eee; padding-bottom:5px;">
                                        <div style="font-size:13px; font-weight:bold;">・${s.title}</div>
                                        <div style="margin-top:3px;">
                                            <button onclick="window.mikanAdd('${s.title.replace(/'/g, "\\'")}','${s.url}')" style="font-size:10px; cursor:pointer; background:#ff9800; color:white; border:none; border-radius:4px; padding:2px 6px;">🧺キープ</button>
                                            <a href="${s.url}" style="font-size:10px; margin-left:10px; color:#666;">詳細を見る</a>
                                        </div>
                                    </div>`;
                                });
                            }
                            resultsDiv.innerHTML = html + '</div>';

                            // タグボタンの直下まで自動スクロール
                            setTimeout(() => {
                                body.scrollTo({
                                    top: resultsDiv.offsetTop - 20,
                                    behavior: 'smooth'
                                });
                            }, 100);
                        };
                        tagsDiv.appendChild(b);
                    });
                })
                .catch(err => {
                    tagsDiv.innerHTML = '<div style="font-size:11px; color:red; text-align:center;">エラーが発生しました。</div>';
                });
        }
    };

    btn.onmouseenter = () => { if(win.style.display === 'none') tooltip.style.display = 'block'; };
    btn.onmouseleave = () => tooltip.style.display = 'none';
    close.onclick = () => win.style.display = 'none';

    window.addEventListener('scroll', function() {
        const chatbot = document.getElementById('mikan-bot');
        chatbot.style.transition = 'all 0.3s ease-out';
        chatbot.style.bottom = (window.scrollY > 100) ? '60px' : '10px';
    });

    renderCart();
})();
</script>
<?php
});
ことは

今週の金曜日にて『おんがく素材チャット』に『まとめてDLボタン』を追加しました。バックエンドのRustを含め以下の3ステップになっていますのでぜひ一読ください。

1.PHP(ボットのUI)がリクエストを受け取り、サーバー内のRustを起動。
2.Rustがサーバーのメモリを直接叩き、指定された音源ファイルをまとめてZIP化。サーバー内にファイルをアップロードしているため、cargo build --releaseを使用してバイナリ化で実行ファイルのメモリ容量を最小限化。
3.Rustが生成したデータをPHP側に返して、ユーザーのブラウザにダウンロードさせる。

タイトルとURLをコピーしました