Claudeに生成してもらったReactアプリをClaudeにHTML+CSS+JavaScriptのみに変換してもらった

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/52151/e9d3a7e6-01f6-7313-9096-2af371450e9e.png)

抽選の結果、『**石**』さんがプレゼン者に選ばれました

# はじめに

前回の記事[「ClaudeのGPT-4o超え」って意外と本当かもと驚いた！！！](https://qiita.com/yanagih/items/cfa0636e9ab9ce415609)で生成したルーレット・アプリは、Reactを使用したアプリでした。

せっかく生成したもらっておいて失礼ですが笑、Reactを使うまでもないシンプルなアプリなので、シンプルに、HTML+CSS+JavaScriptだけを使ったアプリに書き換えてください、とお願いしてみました。

:::note info
文句ひとつ言わず、数秒でやってくれました。
:::

:::note alert
もし、私がこんな依頼を受けたなら、もう、文句ブーブーです :sunglasses:
ていうか、何時間かかるんだろう:sweat_smile:
:::

# レッツ・トライ！
早速始めます。

## プロンプト1

Claudeに生成してもらったReactをHTML+CSS+JavaScriptのみに変換するためのプロンプトを入力します。

```plaintext: prompt
ソースコードをHTML+CSS+JavaScriptのみで書き換えてください

```
何やらエラーが発生しました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/52151/2b0db215-b7c7-d993-f9d5-183f57a99813.png)

無料枠の回数制限は確実に残っている(理由:本日使用1回目ですので)と思いましたが、よく見ると、**length limit**とありますので、コンテキストサイズがオーバーしたようです。

ソースコード自体はそれほど大きくないのですが、これまでのやり取りが全てコンテキストに含まれているようで、その結果、**length limit**に引っかかったようです。

ならばと思い、次に試したのは、こんなプロンプトです。

## プロンプト2
そうならばと思い、次に試したのは、こんなプロンプトです。
```plaintext: prompt
以下のソースコードをHTML+CSS+JavaScriptのみで書き換えてください

import React, { useState, useEffect, useRef } from 'react';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';

const PresenterRoulette = () => {
  const [presenters, setPresenters] = useState([]);
  const [newPresenter, setNewPresenter] = useState('');
  
(以下省略)
```
指示プロンプトの下に、生成されたソースコードを全てペーストしてみました。

結果は、こちらの通り、15秒前後でコンバートしてくれました。すごすぎる。。。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/52151/cc33c3ed-8ee3-d68d-58dd-81d916be439a.png)

## プロンプト3
実は、ちょっと気になったことがありました。
プレゼン者を入力した後に、いつもの癖で、enterキーを押してしまうのですが、このアプリでは、enterキーでは追加してくれず、必ず[追加]ボタンを押す必要があります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/52151/9156ccc1-a7a3-22a7-721d-8e944f6a6f69.png)
細かいことですが、enterキーでもプレゼン者リストに追加されるようにお願いしてみます。

```plaintext: prompt
プレゼン者の追加は、追加ボタンのほかに、キーボードのenterキーも有効にしてください
```
あっという間に、コードを修正してくれました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/52151/ee843d16-3214-e21f-9e4e-faf568d4546f.png)

それでは実際に試してみます。
enterキーでプレゼン者リストに追加されました。
<iframe width="476" height="847" src="https://www.youtube.com/embed/GnJu2MZcgeA" title="HTML Only" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>

# 生成されたHTML

生成されたHTML+CSS+JavaScriptを共有いたします。
ライブラリを必要としませんので、すぐに試せますね。
**Simple is best.**

```html:html
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>プレゼン者ルーレットアプリ</title>
    <style>
        body {
            font-family: Arial, sans-serif;
            max-width: 400px;
            margin: 0 auto;
            padding: 20px;
        }
        h1 {
            font-size: 24px;
            margin-bottom: 20px;
        }
        .input-container {
            display: flex;
            margin-bottom: 20px;
        }
        input {
            flex-grow: 1;
            margin-right: 10px;
            padding: 5px;
        }
        button {
            padding: 5px 10px;
            background-color: #4CAF50;
            color: white;
            border: none;
            cursor: pointer;
        }
        button:disabled {
            background-color: #cccccc;
            cursor: not-allowed;
        }
        ul {
            list-style-type: disc;
            padding-left: 20px;
        }
        canvas {
            display: block;
            margin: 0 auto;
        }
        #result {
            text-align: center;
            padding: 15px;
            background-color: #e6f3ff;
            border-radius: 5px;
            margin-top: 20px;
        }
        #selectedPresenter {
            font-size: 24px;
            color: #0066cc;
            font-weight: bold;
        }
    </style>
</head>
<body>
    <h1>プレゼン者ルーレットアプリ</h1>
    <div class="input-container">
        <input type="text" id="newPresenter" placeholder="プレゼン者の名前を入力">
        <button onclick="addPresenter()">追加</button>
    </div>
    <div>
        <h2>プレゼン者リスト:</h2>
        <ul id="presenterList"></ul>
    </div>
    <canvas id="roulette" width="300" height="300"></canvas>
    <button id="spinButton" onclick="spinRoulette()">ルーレットを回す</button>
    <div id="result" style="display: none;">
        <h2>選ばれたプレゼン者:</h2>
        <p id="selectedPresenter"></p>
    </div>

    <script>
        const presenters = [];
        let isSpinning = false;
        const canvas = document.getElementById('roulette');
        const ctx = canvas.getContext('2d');
        const spinButton = document.getElementById('spinButton');
        const newPresenterInput = document.getElementById('newPresenter');

        // Enterキーのイベントリスナーを追加
        newPresenterInput.addEventListener('keypress', function(event) {
            if (event.key === 'Enter') {
                addPresenter();
            }
        });

        function addPresenter() {
            const newPresenter = newPresenterInput.value.trim();
            if (newPresenter !== '') {
                presenters.push(newPresenter);
                newPresenterInput.value = '';
                updatePresenterList();
                drawRoulette(0);
            }
        }

        function updatePresenterList() {
            const list = document.getElementById('presenterList');
            list.innerHTML = '';
            presenters.forEach(presenter => {
                const li = document.createElement('li');
                li.textContent = presenter;
                list.appendChild(li);
            });
        }

        function spinRoulette() {
            if (presenters.length > 0 && !isSpinning) {
                isSpinning = true;
                spinButton.disabled = true;
                spinButton.textContent = 'ルーレット回転中...';
                const minSpins = 5;
                const maxSpins = 10;
                const spins = minSpins + Math.random() * (maxSpins - minSpins);
                const minDuration = 4000;
                const maxDuration = 8000;
                const duration = minDuration + Math.random() * (maxDuration - minDuration);
                let currentRotation = 0;
                const startTime = Date.now();

                const sliceAngle = 360 / presenters.length;
                const additionalRotation = Math.random() * 360;

                function animate() {
                    const elapsedTime = Date.now() - startTime;
                    const progress = Math.min(elapsedTime / duration, 1);
                    const easeOut = 1 - Math.pow(1 - progress, 3);
                    
                    currentRotation = easeOut * spins * 360 + additionalRotation;
                    drawRoulette(currentRotation);

                    if (progress < 1) {
                        requestAnimationFrame(animate);
                    } else {
                        isSpinning = false;
                        spinButton.disabled = false;
                        spinButton.textContent = 'ルーレットを回す';
                        selectPresenter(currentRotation);
                    }
                }

                animate();
            }
        }

        function selectPresenter(finalRotation) {
            const normalizedRotation = finalRotation % 360;
            const sliceAngle = 360 / presenters.length;
            const selectedIndex = (presenters.length - Math.floor(normalizedRotation / sliceAngle) - 1) % presenters.length;
            const selectedPresenter = presenters[selectedIndex];
            
            document.getElementById('result').style.display = 'block';
            document.getElementById('selectedPresenter').textContent = selectedPresenter;
        }

        function drawRoulette(rotation) {
            const centerX = canvas.width / 2;
            const centerY = canvas.height / 2;
            const radius = Math.min(centerX, centerY) - 10;

            ctx.clearRect(0, 0, canvas.width, canvas.height);
            ctx.save();
            ctx.translate(centerX, centerY);
            ctx.rotate((rotation * Math.PI) / 180);

            const sliceAngle = (2 * Math.PI) / presenters.length;
            const colors = ['#FF6B6B', '#4ECDC4', '#45B7D1', '#FFA07A', '#98D8C8'];

            presenters.forEach((presenter, index) => {
                const startAngle = index * sliceAngle;
                const endAngle = startAngle + sliceAngle;

                ctx.beginPath();
                ctx.moveTo(0, 0);
                ctx.arc(0, 0, radius, startAngle, endAngle);
                ctx.closePath();
                ctx.fillStyle = colors[index % colors.length];
                ctx.fill();

                ctx.save();
                ctx.rotate(startAngle + sliceAngle / 2);
                ctx.textAlign = 'right';
                ctx.fillStyle = '#000';
                ctx.font = 'bold 28px Arial';
                ctx.fillText(presenter, radius - 30, 0);
                ctx.restore();
            });

            ctx.restore();

            // Draw arrow
            ctx.beginPath();
            ctx.moveTo(centerX + radius, centerY);
            ctx.lineTo(centerX + radius + 20, centerY - 10);
            ctx.lineTo(centerX + radius + 20, centerY + 10);
            ctx.closePath();
            ctx.fillStyle = '#333';
            ctx.fill();
        }

        // 初期描画
        drawRoulette(0);
    </script>
</body>
</html>
```

# 終わりに
もう、ただただ、便利です。

# 参考

https://support.anthropic.com/en/collections/4078533-api-prompt-design

https://claude.ai/new