YovStudio

article 第 4 章:カレンダーを調整してみる

公開日: 2025-07-01著者: Yov in YovStudio

今回やること

前回は作って欲しいアプリの仕様をなるべく詳細に伝えてみましたが、ユーザーに優しくないアプリが出来てしまいましたね。
以下の点で特に優しさが足りなかったのは、前回お伝えした通りです。

  • カレンダーが存在することが分からない
  • カレンダーの横軸と縦軸に曜日や月のラベルがないため、表示されている部分がいつなのかわからない
  • いつの気分を入力をしているか分からない
  • 初期表示で顔文字を選択しても何も起きない
  • ライトモードだと😞の色がかなり見えづらい
  • ダークモードへの切り替えやテーマカラー変更が保存されない

今後は ChatGPT と一緒にこれらの課題に対応していく予定です。
今回はまずなんと言ってもこのアプリの主役であるカレンダーまわりの改善に着手していきましょう。

再掲:前回作成したアプリの画像

対応方針

さてどんなプロンプトを作りましょうか。
具体的にどう対応すべきかを指示しないと、また 2 章のような結果になりかねません。ここは課題ごとにどう対応していくか方針を整理し、プロンプトのネタにしましょう。

1. カレンダーが存在することが分からない

気分を入力していない各マスの色と画面の背景色が同じになっているので、そこにカレンダーが存在していることが分からない状況です。
各マスのデフォルトカラーを透明度の高いグレーに変更することでマスを可視化してみましょう。

2. カレンダーの横軸と縦軸に曜日や月のラベルがないため、表示されている部分がいつなのかわからない

カレンダーの縦軸に曜日、横軸に月のラベルを表示してみましょう。

3. いつの気分を入力をしているか分からない

選択しているマスに枠やハイライトを表示することで、どのマスを選択しているか分かるようにしましょう。
また、カレンダー部分ではないですが、あわせて気分入力エリアにも選択した日付を表示しておきましょう。

4. 初期表示で顔文字を選択しても何も起きない

カレンダーとは直接関係しないため、今回は一旦見送ることにします。

5. ライトモードだと😞の色がかなり見えづらい

😞の色がほとんど見えないので、もう少し見やすくなるよう色合いを調整してみましょう。
また、今の気分の色はライトモード用という感じでダークモードの雰囲気と合っていないので、それぞれで自然に見える色を再定義してもらいましょう。

参考:ダークモードでの色合い

6. ダークモードへの切り替えやテーマカラー変更が保存されない

カレンダーとは直接関係しないため、今回は一旦見送ることにします。

今回使うプロンプト

対応方針をベースにプロンプトを作成しました。

今回は改善なので、前回のチャットに継続して送ってみます。

作成済みの「MOOD TRACKER」のWebアプリを改善します。
以下の目的と前提条件に基づき、変更内容をすべて反映してください。

## 目的

カレンダー部分の視認性や使いやすさを改善する

## 前提条件

- コードは HTML / CSS / JavaScript で構成された前回のアプリの続きとして書いてください。
- React や外部ライブラリは使わず、ブラウザ上でローカル実行できるようにしてください。
- コードは省略せず、すべてフルで提供してください。

## 変更内容

1. 【マスの初期表示】
   - 気分が未入力のマスにも、カレンダーの存在が分かるように淡いグレーを表示してください。
   - ライトモード/ダークモード両方で自然に見えるように調整してください(透明度を使ってもOKです)。

2. 【曜日・月のラベル表示】
   - カレンダーの縦軸に曜日(Mon〜Sun)を表示してください。
   - 横軸には各月のラベルを表示してください(1月、2月など)。
   - 月のラベルは週単位の列の上部に表示してください。

3. 【選択中の日付の表示】
   - マスをクリックしたとき、そのマスが選択中であることが視覚的に分かるよう、枠線などでハイライトしてください。
   - その日付(例:2025-07-01)を、気分を選択するエリアに表示してください。

4. 【気分の色の調整】
   - 各気分に対応する5段階の色を、ライトモード・ダークモードそれぞれに最適な配色で再定義してください。
   - 色の見えやすさ(特に 😞 のように薄くなりがちなもの)に配慮してください。

ChatGPT からの返答

今回は 60 秒前後で「コード」と「変更点の詳細」が返ってきました。
返ってきた内容は以下の通りです。

※補足:同じプロンプトでも、ChatGPT は状況によって違った回答を返すことがあります。自分で試してみたら全然違う結果になった、というのもよくあることです。その違いも含めて楽しんでもらえればと思います。

コード

コードは長いので折りたたんでいます。
コード内に含まれるコメントも含め、すべて ChatGPT が作成したものです。

コードはこちら
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>MOOD TRACKER</title>
  <link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined" rel="stylesheet">
  <style>
    :root {
      --bg-light: #f0f0f0;
      --bg-dark: #1e1e2f;
      --text-light: #333;
      --text-dark: #eee;
      --glass-bg: rgba(255, 255, 255, 0.15);
      --glass-border: rgba(255, 255, 255, 0.3);
      --neutral-light: rgba(0, 0, 0, 0.1);
      --neutral-dark: rgba(255, 255, 255, 0.15);
    }

    body {
      margin: 0;
      font-family: sans-serif;
      background: var(--bg-light);
      color: var(--text-light);
      transition: background 0.3s, color 0.3s;
    }
    body.dark {
      background: var(--bg-dark);
      color: var(--text-dark);
    }

    .glass {
      backdrop-filter: blur(12px);
      background: var(--glass-bg);
      border: 1px solid var(--glass-border);
      border-radius: 12px;
      padding: 1rem;
      box-shadow: 0 4px 12px rgba(0,0,0,0.2);
    }

    header {
      display: flex;
      justify-content: space-between;
      align-items: center;
      padding: 1rem;
    }
    .controls {
      display: flex;
      gap: 1rem;
    }
    select, button {
      padding: 0.4rem 0.8rem;
      border-radius: 6px;
      border: none;
      font-size: 1rem;
    }
    .calendar {
      display: flex;
      flex-direction: column;
      gap: 2rem;
      padding: 1rem;
    }
    .year-section {
      display: flex;
      flex-direction: column;
      gap: 0.5rem;
    }
    .week-row {
      display: flex;
      overflow-x: auto;
      gap: 2px;
      position: relative;
    }
    .month-labels {
      display: flex;
      gap: 2px;
      padding-left: 36px;
      font-size: 0.8rem;
      margin-bottom: 4px;
    }
    .month-labels span {
      width: 16px;
      text-align: center;
    }
    .day-col {
      display: flex;
      flex-direction: column;
      gap: 2px;
    }
    .days-with-labels {
      display: flex;
    }
    .day-labels {
      display: flex;
      flex-direction: column;
      gap: 2px;
      margin-right: 4px;
      font-size: 0.75rem;
      padding-top: 16px;
    }
    .day {
      width: 16px;
      height: 16px;
      border-radius: 3px;
      cursor: pointer;
      background: var(--neutral-light);
      box-sizing: border-box;
      transition: transform 0.1s, border 0.2s;
    }
    body.dark .day {
      background: var(--neutral-dark);
    }
    .day:hover {
      transform: scale(1.3);
    }
    .day.selected {
      border: 2px solid #000;
    }
    body.dark .day.selected {
      border: 2px solid #fff;
    }
    .legend {
      display: flex;
      flex-direction: column;
      align-items: center;
      gap: 0.5rem;
      margin: 1rem;
    }
    .mood-row {
      display: flex;
      gap: 0.5rem;
    }
    .mood-icon {
      font-size: 2rem;
      cursor: pointer;
      opacity: 0.6;
    }
    .mood-icon.active {
      opacity: 1;
    }
    .theme-selector {
      margin-left: auto;
    }
    #selectedDateDisplay {
      font-size: 1rem;
      font-weight: bold;
    }
  </style>
</head>
<body>
  <header class="glass">
    <h1>MOOD TRACKER</h1>
    <div class="controls">
      <select id="themeColor">
        <option value="green">Green</option>
        <option value="blue">Blue</option>
        <option value="red">Red</option>
        <option value="purple">Purple</option>
        <option value="orange">Orange</option>
      </select>
      <button id="toggleMode">Dark Mode</button>
    </div>
  </header>

  <div class="calendar" id="calendar"></div>

  <div class="legend glass">
    <div id="selectedDateDisplay">日付を選択してください</div>
    <div class="mood-row">
      <span class="mood-icon" data-mood="1">😞</span>
      <span class="mood-icon" data-mood="2">😐</span>
      <span class="mood-icon" data-mood="3">🙂</span>
      <span class="mood-icon" data-mood="4">😊</span>
      <span class="mood-icon" data-mood="5">😁</span>
    </div>
  </div>

  <script>
    const calendarEl = document.getElementById('calendar');
    const themeSelect = document.getElementById('themeColor');
    const toggleModeBtn = document.getElementById('toggleMode');
    const moodIcons = document.querySelectorAll('.mood-icon');
    const selectedDateDisplay = document.getElementById('selectedDateDisplay');

    let currentTheme = 'green';
    let selectedDate = null;
    let moodData = JSON.parse(localStorage.getItem('moodData') || '{}');

    function getColorScale(mood, theme, isDark) {
      const palette = {
        light: {
          green: ['#e0f2e9','#b8e4d1','#78d2af','#3fbf8a','#149f6a'],
          blue:  ['#e1f4f8','#b2e4f1','#72d2e8','#2cb9db','#0a97c1'],
          red:   ['#ffeaea','#ffbdbd','#ff8a8a','#ff5252','#d32f2f'],
          purple:['#f3e5f5','#dfbdf1','#c18be8','#a44de0','#8e24aa'],
          orange:['#fff3e0','#ffddae','#ffc170','#ffa040','#f57c00']
        },
        dark: {
          green: ['#1a2f25','#246647','#2ca37a','#34dca0','#6fffc8'],
          blue:  ['#12333e','#14627c','#168bb5','#18b1dc','#68e0f5'],
          red:   ['#3d1e1e','#842323','#d33434','#f95858','#ffaaaa'],
          purple:['#2f1e35','#562b63','#813a94','#a856c0','#ce9ef0'],
          orange:['#3f2c1a','#7c4c00','#b36a00','#ff8c1a','#ffc180']
        }
      };
      return palette[isDark ? 'dark' : 'light'][theme][mood - 1];
    }

    function formatDateKey(date) {
      return date.toISOString().split('T')[0];
    }

    function buildCalendar() {
      calendarEl.innerHTML = '';
      let allDates = Object.keys(moodData);
      let years = [...new Set(allDates.map(d => d.split('-')[0]))];
      if (years.length === 0) years = [new Date().getFullYear().toString()];
      years = years.sort((a, b) => b - a);

      const dayLabels = ['Mon','Tue','Wed','Thu','Fri','Sat','Sun'];

      years.forEach(year => {
        const section = document.createElement('div');
        section.className = 'year-section';
        section.innerHTML = `<h2>${year}</h2>`;

        const monthLabels = document.createElement('div');
        monthLabels.className = 'month-labels';

        const weekRow = document.createElement('div');
        weekRow.className = 'week-row';

        const startDate = new Date(`${year}-01-01`);
        const endDate = new Date(`${year}-12-31`);
        const firstMonday = new Date(startDate);
        while (firstMonday.getDay() !== 1) firstMonday.setDate(firstMonday.getDate() - 1);

        const labelDays = document.createElement('div');
        labelDays.className = 'day-labels';
        dayLabels.forEach(d => {
          const label = document.createElement('div');
          label.textContent = d;
          labelDays.appendChild(label);
        });

        const dayWrapper = document.createElement('div');
        dayWrapper.className = 'days-with-labels';
        dayWrapper.appendChild(labelDays);

        let lastMonth = -1;
        for (let d = new Date(firstMonday), i = 0; d <= endDate; d.setDate(d.getDate() + 7), i++) {
          const col = document.createElement('div');
          col.className = 'day-col';

          const monthName = `${d.getMonth() + 1}月`;
          if (lastMonth !== d.getMonth()) {
            const label = document.createElement('span');
            label.textContent = monthName;
            monthLabels.appendChild(label);
            lastMonth = d.getMonth();
          } else {
            monthLabels.appendChild(document.createElement('span'));
          }

          for (let i = 0; i < 7; i++) {
            const cellDate = new Date(d);
            cellDate.setDate(cellDate.getDate() + i);
            const key = formatDateKey(cellDate);
            const day = document.createElement('div');
            day.className = 'day';

            const mood = moodData[key];
            const isDark = document.body.classList.contains('dark');
            if (mood) {
              day.style.background = getColorScale(mood, currentTheme, isDark);
            }
            if (selectedDate === key) {
              day.classList.add('selected');
            }

            day.title = key;
            day.onclick = () => openMoodSelector(key);
            col.appendChild(day);
          }
          dayWrapper.appendChild(col);
        }

        section.appendChild(monthLabels);
        section.appendChild(dayWrapper);
        calendarEl.appendChild(section);
      });
    }

    function openMoodSelector(dateKey) {
      selectedDate = dateKey;
      selectedDateDisplay.textContent = dateKey;
      moodIcons.forEach(icon => {
        icon.onclick = () => {
          const mood = parseInt(icon.dataset.mood);
          moodData[dateKey] = mood;
          localStorage.setItem('moodData', JSON.stringify(moodData));
          buildCalendar();
        };
      });
      buildCalendar();
    }

    themeSelect.onchange = () => {
      currentTheme = themeSelect.value;
      buildCalendar();
    };

    toggleModeBtn.onclick = () => {
      document.body.classList.toggle('dark');
      toggleModeBtn.textContent = document.body.classList.contains('dark') ? 'Light Mode' : 'Dark Mode';
      buildCalendar();
    };

    buildCalendar();
  </script>
</body>
</html>

チャットの回答

以下の改善を加えて、MOOD TRACKERアプリのカレンダー部分の視認性と使いやすさを向上させました:

✅ 変更点の詳細
1. マスの初期表示
  ・気分未入力のマスにも淡いグレー背景を適用(ライトモード/ダークモード両対応)。
2. 曜日・月のラベル
  ・縦軸:Mon〜Sun を表示。
  ・横軸:週の上部に 1月, 2月, ... ラベルを表示。
3. 選択中の日付の表示
  ・マスクリック時に選択状態を枠線でハイライト。
  ・選択中の日付(例:2025-07-01)を気分選択エリア上に表示。
4. 気分の色の再定義
  ・ライトモード/ダークモード別に、5段階ごとの色をテーマごとに調整。
  ・特に淡色の見えやすさに配慮。

ブラウザで .html として保存し開けば、改善後のアプリをそのまま使用できます。さらに調整したい点があれば、お気軽にどうぞ!

カレンダーは改善された

さぁどうなるでしょうか。
このシリーズで気づいたのですが、ChatGPT の出力したコードを動かしてみる瞬間は単純にワクワク感の楽しみもありますが、「ちゃんといい感じになってるだろうか…?」というドキドキもありますね。

とりあえず動きを見たい方は下記リンクからどうぞ。
動作確認用デモページ

実際に手元で動かしてみたい方は、下記からダウンロード後、zip ファイルを展開して中に入っている index.html を実行してみてください。
zipファイルをダウンロード

── おぉ!マスがしっかり並んでカレンダーの存在感が増しましたね!
縦軸と横軸のラベルも実装されていて分かりやすいです。
また、マスを選択するとカレンダーもハイライトされ、気分入力エリアにも日付が表示されるようになっています。
だいぶ人に優しくなってきたのではないでしょうか。

とはいえ、細かい点を見るとまだ見直すべきポイントが残っています。

  • 曜日ラベルの位置がおかしい。「Sun」が一番上にくるべき。
  • 月ラベルの位置がブラウザの幅によってずれてしまう。
  • マスが横のマスとくっついているので、縦方向のように少し余白を入れたい。
  • マスのデフォルト色をもう少し目立たないようにしたい。
  • 相変わらずライトモードの色が見えづらい。(特に😞と😐の色)

小さな優しさの積み重ね

今回の改善では、カレンダーの存在感や見やすさ、操作のしやすさといったユーザー視点の工夫を ChatGPT に伝えてみました。

  • マスのデフォルト色 → カレンダーが見えるようになった
  • 曜日・月ラベル → どのタイミングかが分かるようになった
  • 日付ハイライト → 入力する日が明確になった
  • 気分の色 → モードに応じて自然な配色になった(まだ調整は必要だが)

どれも派手ではないけれど、実際の使いやすさに直結する「優しさ」です。
「優しさ」が加わったことで人が操作しやすくなり、見栄えはともかくとして、アプリとしての完成度はぐっと高まりましたね。

このように、見た目を少し変えただけでも、ユーザーが感じる「分かりやすさ」や「安心感」は大きく変わります。
ChatGPT はプロンプト次第で、こうした「人の気持ちに寄り添うような改善」にも、きちんと応えてくれます。
それは魔法のような一発の指示ではなく、細かい調整を一つひとつ重ねていく── まさに「開発」のリアルそのものかもしれません。

次回は、今回足りなかった部分の補強と残った課題に対応していきます。
見栄えも気にしながら、少しずつ「優しさ」を積み重ねて整えていきましょう。