[GitHub Copilot] HTML 占いアプリを作ってみる

"GitHub Copilot" の練習に HTML 占いアプリを作ってみました。こちらで紹介します。

 

 

1. HTML 占いアプリ を作ってみる

"GitHub Copilot" と "Microsoft Visual Studio Code" を使って、簡単な HTML 占いアプリを作ってみました。

Agent モードで "GPT-5 mini" を使用し、5分強 という短時間の作業で完成することができました。

人によるソースコード直接編集を一切行っておりません。背景画像 ("fortune_teller.png") だけ事前準備して開始しています。

HTML 占いアプリ

 

"GitHub Copilot" を使って "HTML 占いアプリ" を作成する全作業を以下のビデオ紹介します。

 

"GitHub Copilot" が作成した全コードを以下で紹介します。

"README.md"

# おみくじ占い (簡易)

これはシンプルな HTML/CSS/JS で作られた占いアプリのサンプルです。

使い方:

- `index.html`, `style.css`, `script.js` を同じディレクトリに置いてブラウザで `index.html` を開いてください。
- または簡易サーバーで提供するには、Node.js があれば次を実行できます。

```powershell
# 簡易サーバー (PowerShell)
python -m http.server 8000
# ブラウザで http://localhost:8000 を開く
```

機能:

- 名前(任意)を入力して今日の運勢を占う
- ラッキーナンバーの表示
- 結果のコピー / シェア

履歴:

- 占った結果はブラウザの localStorage に保存されます(表示はページ内の「履歴」セクション)。
- 履歴は「履歴を消去」ボタンで削除できます。

 

"index.html"

<!doctype html>
<html lang="ja">
<head>
  <meta charset="utf-8" />
  <meta name="viewport" content="width=device-width,initial-scale=1" />
  <title>おみくじ占い</title>
  <link rel="stylesheet" href="style.css" />
</head>
<body>
  <main class="container">
    <h1>今日の運勢を占う</h1>

    <form id="fortuneForm">
      <label for="name">あなたの名前(任意)</label>
      <input id="name" name="name" type="text" placeholder="例: 太郎" autocomplete="off" />
      <div class="controls">
        <button type="submit" id="drawBtn">占う</button>
      </div>
    </form>

    <section id="result" class="result" aria-live="polite" hidden>
      <div class="card" id="card">
        <h2 id="fortuneTitle"></h2>
        <p id="fortuneDesc"></p>
        <p id="luckyNumber"></p>
        <div class="actions">
          <button id="shareBtn">結果をコピー</button>
          <button id="againBtn">もう一度</button>
        </div>
      </div>
    </section>

    <section id="historySection" class="history">
      <h3>履歴</h3>
      <p class="history-empty" id="historyEmpty">まだ履歴がありません。</p>
      <ul id="historyList" class="history-list" aria-live="polite"></ul>
      <div class="history-actions">
        <button id="clearHistoryBtn" class="clear">履歴を消去</button>
      </div>
    </section>

    <footer class="footer">ローカルで開くか、簡易サーバーで動かしてください。</footer>
  </main>

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

 

"style.css"

:root{
  --bg:#f8f7ff;
  --card:#ffffff;
  --accent:#6b5b95;
  --muted:#666;
}
*{box-sizing:border-box}
html,body,#root{height:100%}
.body-bg{
  position:fixed;inset:0;z-index:-1;background-size:cover;background-position:center;filter:brightness(0.95);
}
body{
  margin:0;padding:24px;font-family:system-ui,-apple-system,"Segoe UI",Roboto,"Hiragino Kaku Gothic ProN",Meiryo,sans-serif;color:#222;min-height:100vh;position:relative;
  /* 背景画像 + 暗めのオーバーレイでテキストを読みやすくする */
  background-image:linear-gradient(rgba(6,6,10,0.36),rgba(6,6,10,0.12)), url('fortune_teller.png');
  background-size:cover;
  background-position:center;
}
.container{max-width:680px;margin:24px auto}
.container h1{font-size:1.6rem;margin:0 0 16px;color:#fff;text-shadow:0 2px 8px rgba(0,0,0,0.45)}
form{display:grid;gap:12px;background:var(--card);padding:16px;border-radius:10px;box-shadow:0 6px 18px rgba(102,102,102,0.08)}
label{font-size:0.9rem;color:var(--muted)}
input[type=text]{padding:10px 12px;border-radius:8px;border:1px solid #ddd;font-size:1rem}
.controls{display:flex;gap:8px}
button{background:var(--accent);color:#fff;border:0;padding:10px 14px;border-radius:8px;cursor:pointer}
button[disabled]{opacity:0.6;cursor:not-allowed}
.result{margin-top:16px}
.card{background:linear-gradient(180deg,#fff,#fbfbff);padding:16px;border-radius:10px;border:1px solid #eee}
.card h2{margin:0 0 8px}
.card p{margin:6px 0}
.actions{display:flex;gap:8px;margin-top:12px}
.footer{margin-top:20px;color:rgba(255,255,255,0.85);font-size:0.9rem}

.history{margin-top:18px;background:rgba(255,255,255,0.06);padding:12px;border-radius:8px;color:#fff}
.history h3{margin:0 0 8px}
.history-list{list-style:none;padding:0;margin:0;max-height:220px;overflow:auto}
.history-item{padding:8px;border-bottom:1px dashed rgba(255,255,255,0.06)}
.history-item .date{display:block;color:rgba(255,255,255,0.7);font-size:0.85rem}
.history-item .desc{margin-top:6px;color:rgba(255,255,255,0.95)}
.history-item .ln{margin-top:6px;font-weight:600}
.history-actions{margin-top:8px}
.clear{background:transparent;border:1px solid rgba(255,255,255,0.14);color:#fff;padding:8px 10px;border-radius:8px}
.history-empty{color:rgba(255,255,255,0.8);margin:6px 0}

@media (max-width:520px){
  .container{padding:8px}
  h1{font-size:1.3rem}
}

 

"script.js"

// 占いロジック
const fortunes = [
  { title: '大吉', desc: '今日は何をやっても上手くいきます。自信を持って進んでください。'},
  { title: '中吉', desc: '良い運気です。小さなチャレンジが実を結びます。'},
  { title: '小吉', desc: '穏やかな一日。急がず着実に行動を。'},
  { title: '凶', desc: '注意が必要です。慌てずリスクを確認しましょう。'},
  { title: '大凶', desc: '思いがけない出来事に注意。無理は禁物です。'}
];

function randomInt(min,max){
  return Math.floor(Math.random()*(max-min+1))+min;
}

document.addEventListener('DOMContentLoaded',()=>{
  const form = document.getElementById('fortuneForm');
  const result = document.getElementById('result');
  const drawBtn = document.getElementById('drawBtn');
  const againBtn = document.getElementById('againBtn');
  const shareBtn = document.getElementById('shareBtn');
  const fortuneTitle = document.getElementById('fortuneTitle');
  const fortuneDesc = document.getElementById('fortuneDesc');
  const luckyNumber = document.getElementById('luckyNumber');
  const historyList = document.getElementById('historyList');
  const historyEmpty = document.getElementById('historyEmpty');
  const clearHistoryBtn = document.getElementById('clearHistoryBtn');

  // 履歴: localStorage を使って保存
  const HISTORY_KEY = 'fortune_history_v1';

  function loadHistory(){
    const raw = localStorage.getItem(HISTORY_KEY);
    try{
      const arr = raw? JSON.parse(raw) : [];
      return Array.isArray(arr)? arr : [];
    }catch(e){
      console.error('履歴の読み込みでエラー', e);
      return [];
    }
  }

  function saveHistory(arr){
    try{ localStorage.setItem(HISTORY_KEY, JSON.stringify(arr)); }catch(e){ console.error(e); }
  }

  function renderHistory(){
    const arr = loadHistory();
    historyList.innerHTML = '';
    if(arr.length === 0){ historyEmpty.hidden = false; return; }
    historyEmpty.hidden = true;
    arr.slice().reverse().forEach(item => {
      const li = document.createElement('li');
      li.className = 'history-item';
      li.innerHTML = `<strong>${item.title}</strong> <span class="date">${item.date}</span><div class="desc">${item.desc}</div><div class="ln">${item.lucky}</div>`;
      historyList.appendChild(li);
    });
  }

  clearHistoryBtn.addEventListener('click', ()=>{
    if(!confirm('履歴を本当に消去しますか?')) return;
    saveHistory([]);
    renderHistory();
  });


  form.addEventListener('submit',(e)=>{
    e.preventDefault();
    drawBtn.disabled = true;
    // シンプルな演出
    drawBtn.textContent = '占っています...';
    setTimeout(()=>{
      const idx = randomInt(0,fortunes.length-1);
      const f = fortunes[idx];
      const name = (document.getElementById('name').value || '').trim();
      fortuneTitle.textContent = `${f.title}${name? ' — '+name : ''}`;
      fortuneDesc.textContent = f.desc;
      const num = randomInt(1,99);
      luckyNumber.textContent = `ラッキーナンバー: ${num}`;
      result.hidden = false;
      drawBtn.textContent = '占う';
      drawBtn.disabled = false;
      // スクロールして結果へ
      result.scrollIntoView({behavior:'smooth'});
      
      // 履歴に保存
      try{
        const arr = loadHistory();
        arr.push({
          title: fortuneTitle.textContent,
          desc: fortuneDesc.textContent,
          lucky: luckyNumber.textContent,
          date: new Date().toLocaleString()
        });
        saveHistory(arr);
        renderHistory();
      }catch(e){ console.error('履歴保存失敗', e); }
    },700);
  });

  againBtn.addEventListener('click',()=>{
    result.hidden = true;
    document.getElementById('name').focus();
  });

  shareBtn.addEventListener('click',async ()=>{
    const title = fortuneTitle.textContent || '';
    const desc = fortuneDesc.textContent || '';
    const num = luckyNumber.textContent || '';
    const text = `今日の運勢: ${title}\n${desc}\n${num}`;
    try{
      if(navigator.share){
        await navigator.share({title:'今日の運勢',text});
      } else if(navigator.clipboard){
        await navigator.clipboard.writeText(text);
        alert('結果をクリップボードにコピーしました。');
      } else {
        // フォールバック
        const ta = document.createElement('textarea');
        ta.value = text; document.body.appendChild(ta); ta.select();
        document.execCommand('copy'); document.body.removeChild(ta);
        alert('結果をコピーしました。');
      }
    }catch(err){
      console.error(err);
      alert('共有に失敗しました。');
    }
  });
});

 

 

ライセンス

本ページの情報は、特記無い限り下記 MIT ライセンスで提供されます。

The MIT License (MIT)

  Copyright 2025 Kinoshita Hidetoshi

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

 

 

参考

 


 

変更履歴

2025-11-03 - 新規作成

 

Programming Items トップページ

プライバシーポリシー