小説投稿サイト

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <title>小説投稿サイト</title>
  <style>
    body {
      font-family: sans-serif;
      padding: 20px;
      max-width: 800px;
      margin: auto;
      background: #f2f2f2;
    }

    h1 {
      text-align: center;
    }

    form {
      background: white;
      padding: 20px;
      border-radius: 10px;
      margin-bottom: 30px;
      box-shadow: 0 0 10px rgba(0,0,0,0.1);
    }

    input, textarea {
      width: 100%;
      margin-bottom: 10px;
      padding: 10px;
      border-radius: 5px;
      border: 1px solid #ccc;
    }

    button {
      padding: 10px 20px;
      background-color: #007bff;
      color: white;
      border: none;
      border-radius: 5px;
      cursor: pointer;
    }

    .post {
      background: white;
      padding: 15px;
      border-left: 5px solid #007bff;
      margin-bottom: 20px;
      border-radius: 5px;
    }

    .post h2 {
      margin: 0 0 10px;
    }

    .meta {
      color: gray;
      font-size: 0.9em;
      margin-bottom: 10px;
    }

    .delete-btn {
      background-color: #dc3545;
      color: white;
      border: none;
      padding: 5px 10px;
      border-radius: 4px;
      float: right;
      cursor: pointer;
    }
  </style>
</head>
<body>
  <h1>小説投稿サイト</h1>

  <form id="novelForm">
    <input type="text" id="author" placeholder="著者名" required>
    <input type="text" id="title" placeholder="タイトル" required>
    <textarea id="content" rows="8" placeholder="本文" required></textarea>
    <button type="submit">投稿する</button>
  </form>

  <div id="postList"></div>

  <script>
    const form = document.getElementById('novelForm');
    const postList = document.getElementById('postList');
    let posts = JSON.parse(localStorage.getItem('novels')) || [];

    function saveAndRender() {
      localStorage.setItem('novels', JSON.stringify(posts));
      renderPosts();
    }

    function renderPosts() {
      postList.innerHTML = '';
      [...posts].reverse().forEach((post, index) => {
        const div = document.createElement('div');
        div.className = 'post';
        div.innerHTML = `
          <button class="delete-btn" onclick="deletePost(${index})">削除</button>
          <h2>${post.title}</h2>
          <div class="meta">著者: ${post.author} | 投稿日: ${post.date}</div>
          <p>${post.content.replace(/\n/g, '<br>')}</p>
        `;
        postList.appendChild(div);
      });
    }

    form.addEventListener('submit', e => {
      e.preventDefault();
      const title = document.getElementById('title').value;
      const content = document.getElementById('content').value;
      const author = document.getElementById('author').value;
      const date = new Date().toLocaleString();

      posts.push({ title, content, author, date });
      form.reset();
      saveAndRender();
    });

    window.deletePost = function(index) {
      posts.splice(posts.length - 1 - index, 1); // reverseしてるため
      saveAndRender();
    }

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

Vooglebrowser

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <title>Voogleブラウザ</title>
  <meta property="og:title" content="Voogleブラウザ">
  <meta property="og:description" content="次世代タブブラウザ Voogle">
  <meta property="og:url" content="https://voogle.onrender.com/">
  <meta property="og:type" content="website">
  <meta property="og:image" content="https://voogle.onrender.com/ogp.png">
  <link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
  <style>
    body.dark {
      background-color: #222;
      color: #fff;
    }
    header {
      background-color: #4285F4;
      padding: 15px;
      color: white;
      font-size: 24px;
      text-align: center;
    }
    #tabs {
      display: flex;
      background-color: #e0e0e0;
      padding: 10px;
      overflow-x: auto;
    }
    .tab {
      padding: 8px 16px;
      background-color: white;
      border-radius: 20px;
      margin-right: 10px;
      cursor: grab;
      font-weight: bold;
      position: relative;
      user-select: none;
    }
    .tab.active {
      background-color: #34A853;
      color: white;
    }
    .tab .close {
      position: absolute;
      top: 0;
      right: 5px;
      cursor: pointer;
      font-family: 'Material Icons';
    }
    #navbar {
      display: flex;
      padding: 10px;
      background-color: #f1f1f1;
      gap: 10px;
    }
    #url {
      flex: 1;
      padding: 10px;
      border-radius: 20px;
      border: 1px solid #ccc;
    }
    button {
      padding: 10px 20px;
      background-color: #4285F4;
      color: white;
      border: none;
      border-radius: 20px;
      cursor: pointer;
      display: flex;
      align-items: center;
      gap: 5px;
    }
    #spinner {
      display: none;
      position: absolute;
      top: 50%;
      left: 50%;
      transform: translate(-50%, -50%);
      font-size: 24px;
    }
    iframe {
      width: 100%;
      height: calc(100% - 240px);
      border: none;
    }
    footer {
      padding: 10px;
      background-color: #e0e0e0;
      text-align: center;
      font-size: 12px;
    }
  </style>
</head>
<body>

<header>
  <span class="material-icons" style="vertical-align: middle; margin-right: 10px;">language</span>
  <a href="https://voogle.onrender.com/">Voogleブラウザ</a>
</header>

<div id="tabs"></div>

<div id="navbar">
  <input type="text" id="url" placeholder="https://example.com">
  <button id="go"><span class="material-icons">arrow_forward</span>移動</button>
  <button id="newTab"><span class="material-icons">add</span>タブ追加</button>
  <button id="darkModeToggle"><span class="material-icons">dark_mode</span>ダークモード</button>
</div>

<div id="spinner">読み込み中...</div>

<iframe id="viewer"></iframe>

<footer>
  &copy; 2025 Voogle Inc.
</footer>

<script>
  const tabs = document.getElementById('tabs');
  const viewer = document.getElementById('viewer');
  const urlInput = document.getElementById('url');
  const goButton = document.getElementById('go');
  const newTabButton = document.getElementById('newTab');
  const darkModeToggle = document.getElementById('darkModeToggle');
  const spinner = document.getElementById('spinner');

  let tabData = [];
  let activeTab = -1;

  function switchTab(index) {
    activeTab = index;
    document.querySelectorAll('.tab').forEach((tab, i) => {
      tab.classList.toggle('active', i === index);
    });
    loadPage(tabData[index].url);
  }

  function addTab(url = 'https://voogle.onrender.com/') {
    tabData.push({ url });
    const index = tabData.length - 1;

    const tab = document.createElement('div');
    tab.className = 'tab';
    tab.setAttribute('draggable', true);
    tab.innerHTML = `タブ ${index + 1} <span class="close">close</span>`;
    tab.onclick = () => switchTab(index);
    tab.querySelector('.close').onclick = (e) => {
      e.stopPropagation();
      tabs.removeChild(tab);
      tabData.splice(index, 1);
      if (activeTab === index) {
        viewer.src = '';
        urlInput.value = '';
      }
    };
    tabs.appendChild(tab);
    switchTab(index);
  }

  function loadPage(url) {
    spinner.style.display = 'block';
    viewer.src = url;
    urlInput.value = url;
    viewer.onload = () => {
      spinner.style.display = 'none';
      document.title = viewer.contentDocument.title || 'Voogleブラウザ';
    };
  }

  goButton.onclick = () => {
    if (activeTab >= 0) {
      let url = urlInput.value.trim();
      if (!url.startsWith('http')) {
        url = 'https://' + url;
      }
      tabData[activeTab].url = url;
      loadPage(url);
    }
  };

  newTabButton.onclick = () => addTab();

  darkModeToggle.onclick = () => {
    document.body.classList.toggle('dark');
    localStorage.setItem('darkMode', document.body.classList.contains('dark'));
  };

  if (localStorage.getItem('darkMode') === 'true') {
    document.body.classList.add('dark');
  }

  addTab();
</script>

</body>
</html>

消費型オタクと生産型オタクの違い

消費型オタク生産型オタクの違いは、趣味への関わり方にあります。

消費型オタク

  • 主に 作品を楽しむことが中心 のオタク。
  • アニメ、漫画、ゲーム、小説などを 視聴・購読・プレイ して楽しむ。
  • グッズやフィギュア、Blu-rayなどを 購入 する。
  • SNSや掲示板で 感想を共有 する。
  • コスプレやイベント参加など、体験型の楽しみ方をすることも。

  • アニメをリアルタイムで視聴し、円盤やグッズを買う。
  • 推しのキャラクターのフィギュアやタペストリーを集める。
  • ゲームをプレイして感想をツイートする。

生産型オタク

  • 自ら創作活動を行う オタク。
  • イラスト、小説、同人誌、漫画、動画、音楽、ゲーム制作などの 二次創作・オリジナル作品を作る
  • 3Dモデリング、プログラミング、AI技術などを活かして創作することも。
  • YouTube、Pixiv、ニコニコ動画、BOOTHなどで 作品を発表・販売 する。

  • 好きなアニメのキャラを描いてSNSに投稿する。
  • 同人誌や同人ゲームを作成してイベント(例:コミケ)で頒布する。
  • VTuberのLive2Dモデルを作成する。
  • AIを使ってオリジナルの物語を生成する。

ハイブリッド型(両方)

最近は、消費しながら創作する ハイブリッド型 のオタクも多いです。

  • 最初は消費型→生産型に移行する 例も多い。
    • 例:「好きな作品の二次創作イラストを描き始めたら、いつの間にか生産型になっていた」
  • 逆に、生産型だけど他の作品も 消費してインスピレーションを得る こともある。

どちらが良い・悪いという話ではない

  • 消費型は市場を支える → コンテンツが売れないと創作活動も続かない。
  • 生産型は新しいコンテンツを生み出す → 二次創作が原作の人気を支えることも。
  • どちらもオタク文化の重要な一部。

結論:「消費型も生産型も、それぞれの楽しみ方がある!」

ARTBOOK

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>ARTBOOK - アートSNS</title>
    <style>
        body {
            font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
            margin: 0;
            padding: 0;
            background-color: #f9fafb;
            color: #333;
        }
        header {
            background-color: #4a5568;
            color: white;
            padding: 1.5rem;
            text-align: center;
            box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
        }
        header h1 {
            margin: 0;
            font-size: 2.5rem;
        }
        header p {
            margin: 0.5rem 0;
            font-size: 1.2rem;
        }
        header nav {
            margin-top: 1rem;
        }
        header nav a {
            color: white;
            text-decoration: none;
            margin: 0 1rem;
            font-weight: bold;
            font-size: 1.1rem;
            padding: 0.5rem 1rem;
            border-radius: 5px;
            transition: background-color 0.3s;
        }
        header nav a:hover {
            background-color: #2d3748;
        }
        .container {
            max-width: 1200px;
            margin: 2rem auto;
            padding: 1rem;
        }
        .form-group {
            margin-bottom: 1rem;
        }
        .form-group label {
            display: block;
            margin-bottom: 0.5rem;
            font-weight: bold;
        }
        .form-group input, .form-group textarea {
            width: 100%;
            padding: 0.8rem;
            border: 1px solid #d2d6dc;
            border-radius: 6px;
            background-color: #f7fafc;
            font-size: 1rem;
        }
        .form-group button {
            background-color: #3182ce;
            color: white;
            border: none;
            padding: 0.8rem 1.5rem;
            font-size: 1rem;
            border-radius: 6px;
            cursor: pointer;
            transition: background-color 0.3s;
        }
        .form-group button:hover {
            background-color: #2b6cb0;
        }
        .profile {
            text-align: center;
        }
        .profile img {
            width: 150px;
            height: 150px;
            border-radius: 50%;
            object-fit: cover;
            margin-bottom: 1rem;
        }
        footer {
            text-align: center;
            padding: 1.5rem;
            background-color: #4a5568;
            color: white;
            margin-top: 2rem;
        }
        footer p {
            margin: 0;
            font-size: 1rem;
        }
    </style>
</head>
<body>
    <header>
        <h1>ARTBOOK</h1>
        <p>あなたのアートをシェアしよう!</p>
        <nav>
            <a href="#home" onclick="navigateTo('home')">ホーム</a>
            <a href="#gallery" onclick="navigateTo('gallery')">作品一覧</a>
            <a href="#post" onclick="navigateTo('post')">投稿する</a>
            <a href="#profile" onclick="navigateTo('profile')">プロフィール</a>
        </nav>
    </header>

    <div id="content" class="container">
        <!-- コンテンツがここに動的に挿入されます -->
    </div>

    <footer>
        <p>&copy; 2025 ARTBOOK. All Rights Reserved.</p>
    </footer>

    <script>
        let posts = JSON.parse(localStorage.getItem('posts')) || [];
        let profile = JSON.parse(localStorage.getItem('profile')) || {
            name: "未設定",
            bio: "ここに自己紹介が表示されます",
            image: "https://via.placeholder.com/150"
        };

        function navigateTo(section) {
            const content = document.getElementById('content');
            content.innerHTML = '';

            if (section === 'home') {
                content.innerHTML = `<h2>ホーム</h2><p>ようこそ、ARTBOOKへ!最新のアート作品をご覧ください。</p>`;
            } else if (section === 'gallery') {
                content.innerHTML = `<h2>作品一覧</h2><div id='gallery'>${renderPosts()}</div>`;
            } else if (section === 'post') {
                content.innerHTML = `
                    <h2>投稿する</h2>
                    <form id="postForm">
                        <div class="form-group">
                            <label for="title">タイトル</label>
                            <input type="text" id="title" name="title" required>
                        </div>
                        <div class="form-group">
                            <label for="description">説明</label>
                            <textarea id="description" name="description" rows="4" required></textarea>
                        </div>
                        <div class="form-group">
                            <label for="upload">画像アップロード</label>
                            <input type="file" id="upload" name="upload" accept="image/*" required>
                        </div>
                        <div class="form-group">
                            <button type="button" onclick="submitPost()">投稿する</button>
                        </div>
                    </form>`;
            } else if (section === 'profile') {
                content.innerHTML = `
                    <h2>プロフィール</h2>
                    <div class="profile">
                        <img src="${profile.image}" alt="プロフィール画像">
                        <h3>${profile.name}</h3>
                        <p>${profile.bio}</p>
                        <form id="profileForm">
                            <div class="form-group">
                                <label for="name">名前</label>
                                <input type="text" id="name" value="${profile.name}" required>
                            </div>
                            <div class="form-group">
                                <label for="bio">自己紹介</label>
                                <textarea id="bio" rows="4" required>${profile.bio}</textarea>
                            </div>
                            <div class="form-group">
                                <label for="profileImage">プロフィール画像</label>
                                <input type="file" id="profileImage" accept="image/*">
                            </div>
                            <div class="form-group">
                                <button type="button" onclick="updateProfile()">更新する</button>
                            </div>
                        </form>
                    </div>`;
            }
        }

        function submitPost() {
            const title = document.getElementById('title').value;
            const description = document.getElementById('description').value;
            const upload = document.getElementById('upload').files[0];

            if (title && description && upload) {
                const reader = new FileReader();

                reader.onload = function(event) {
                    posts.push({ title, description, image: event.target.result });
                    localStorage.setItem('posts', JSON.stringify(posts));
                    alert('投稿が完了しました!');
                    navigateTo('gallery');
                };

                reader.readAsDataURL(upload);
            } else {
                alert('すべてのフィールドを入力してください。');
            }
        }

        function updateProfile() {
            const name = document.getElementById('name').value;
            const bio = document.getElementById('bio').value;
            const profileImage = document.getElementById('profileImage').files[0];

            if (name && bio) {
                if (profileImage) {
                    const reader = new FileReader();
                    reader.onload = function(event) {
                        profile.image = event.target.result;
                        saveProfile(name, bio);
                    };
                    reader.readAsDataURL(profileImage);
                } else {
                    saveProfile(name, bio);
                }
            } else {
                alert('すべてのフィールドを入力してください。');
            }
        }

        function saveProfile(name, bio) {
            profile.name = name;
            profile.bio = bio;
            localStorage.setItem('profile', JSON.stringify(profile));
            alert('プロフィールが更新されました!');
            navigateTo('profile');
        }

        function renderPosts() {
            if (posts.length === 0) {
                return '<p>まだ投稿がありません。</p>';
            }

            return posts.map(post => `
                <div class="art-card">
                    <img src="${post.image}" alt="${post.title}">
                    <h2>${post.title}</h2>
                    <p>${post.description}</p>
                </div>`).join('');
        }

        // 初期表示
        navigateTo('home');
    </script>
</body>
</html>

MyCarousel

index.html

<!DOCTYPE html>
<html lang="ja">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>My Carousel</title>
    <link rel="stylesheet" href="css/style.css">
</head>

<body>
    <section class="carousel">
        <div class="container">
            <ul>
                <li><img src="img/pic1.png"></li>
                <li><img src="img/pic2.png"></li>
                <li><img src="img/pic3.png"></li>
                <li><img src="img/pic4.png"></li>
            </ul>

            <button id="prev">&laquo;</button>
            <button id="next">&raquo;</button>
        </div>

        <nav>
        </nav>
    </section>

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

</html>

js/main.js

'use strict';

{
    const next = document.getElementById('next');
    const prev = document.getElementById('prev');
    const ul = document.querySelector('ul');
    const slides = ul.children;
    const dots = [];
    let currentIndex = 0;

    function updateButtons() {
        prev.classList.remove('hidden');
        next.classList.remove('hidden');

        if (currentIndex === 0) {
            prev.classList.add('hidden');
        }
        if (currentIndex === slides.length - 1) {
            next.classList.add('hidden');
        }
    }

    function moveSlides() {
        const slideWidth = slides[0].getBoundingClientRect().width;
        ul.style.transform = `translateX(${-1 * slideWidth * currentIndex}px)`;
    }

    function setupDots() {
        for (let i = 0; i < slides.length; i++) {
            const button = document.createElement('button');
            button.addEventListener('click', () => {
                currentIndex = i;
                updateDots();
                updateButtons();
                moveSlides();
            });
            dots.push(button);
            document.querySelector('nav').appendChild(button);
        }

        dots[0].classList.add('current');
    }

    function updateDots() {
        dots.forEach(dot => {
            dot.classList.remove('current');
        });
        dots[currentIndex].classList.add('current');
    }

    updateButtons();
    setupDots();

    next.addEventListener('click', () => {
        currentIndex++;
        updateButtons();
        updateDots();
        moveSlides();
    });

    prev.addEventListener('click', () => {
        currentIndex--;
        updateButtons();
        updateDots();
        moveSlides();
    });

    window.addEventListener('resize', () => {
        moveSlides();
    });
}

css/style.css

.carousel {
    width: 80%;
    margin: 16px auto;
}

.container {
    width: 100%;
    height: 220px;
    overflow: hidden;
    position: relative;
}

ul {
    list-style: none;
    margin: 0;
    padding: 0;
    height: 100%;
    display: flex;
    transition: transform .3s;
}

li {
    height: 100%;
    min-width: 100%;
}

li img {
    width: 100%;
    height: 100%;
    object-fit: cover;
}

#prev,
#next {
    position: absolute;
    top: 50%;
    transform: translateY(-50%);
    border: none;
    background: rgba(0, 0, 0, .8);
    color: #fff;
    font-size: 24px;
    padding: 0 8px 4px;
    cursor: pointer;
}

#prev:hover,
#next:hover {
    opacity: .8;
}

#prev {
    left: 0;
}

#next {
    right: 0;
}

.hidden {
    display: none;
}

nav {
    margin-top: 16px;
    text-align: center;
}

nav button+button {
    margin-left: 8px;
}

nav button {
    border: none;
    width: 16px;
    height: 16px;
    background: #ddd;
    border-radius: 50%;
    cursor: pointer;
}

nav .current {
    background: #999;
}

MyTabMenu

index.html

<!DOCTYPE html>
<html lang="ja">

<head>
    <meta charset="utf-8">
    <title>Tab Menu</title>
    <link rel="stylesheet" href="css/styles.css">
</head>

<body>
    <div class="container">
        <ul class="menu">
            <li><a href="#" class="active" data-id="about">サイトの概要</a></li>
            <li><a href="#" data-id="service">サービス内容</a></li>
            <li><a href="#" data-id="contact">お問い合わせ</a></li>
        </ul>

        <section class="content active" id="about">
            サイトの概要。サイトの概要。サイトの概要。サイトの概要。サイトの概要。サイトの概要。サイトの概要。サイトの概要。サイトの概要。サイトの概要。サイトの概要。サイトの概要。
        </section>

        <section class="content" id="service">
            サービス内容。サービス内容。サービス内容。サービス内容。サービス内容。サービス内容。サービス内容。サービス内容。サービス内容。サービス内容。サービス内容。サービス内容。
        </section>

        <section class="content" id="contact">
            お問い合わせ。お問い合わせ。お問い合わせ。お問い合わせ。お問い合わせ。お問い合わせ。お問い合わせ。お問い合わせ。お問い合わせ。お問い合わせ。お問い合わせ。お問い合わせ。
        </section>
    </div>

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

</html>

css/styles.css

body {
    font-size: 14px;
}

.container {
    margin: 30px auto;
    width: 500px;
}

.menu {
    list-style: none;
    padding: 0;
    margin: 0;
    display: flex;
}

.menu li a {
    display: inline-block;
    width: 100px;
    text-align: center;
    padding: 8px 0;
    color: #333;
    text-decoration: none;
    border-radius: 4px 4px 0 0;
}

.menu li a.active {
    background: #333;
    color: #fff;
}

.menu li a:not(.active):hover {
    opacity: 0.5;
    transition: opacity 0.4s;
}

.content.active {
    background: #333;
    color: #fff;
    min-height: 150px;
    padding: 12px;
    display: block;
}

.content {
    display: none;
}

js/main.js

'use strict';

{
    const menuItems = document.querySelectorAll('.menu li a');
    const contents = document.querySelectorAll('.content');

    menuItems.forEach(clickedItem => {
        clickedItem.addEventListener('click', e => {
            e.preventDefault();

            menuItems.forEach(item => {
                item.classList.remove('active');
            });
            clickedItem.classList.add('active');

            contents.forEach(content => {
                content.classList.remove('active');
            });
            document.getElementById(clickedItem.dataset.id).classList.add('active');
        });
    });
}

WEBサービスのアイディア

  • ここにいくつかのWEBサービスアイデアを提案します:
  • タスク自動化サービス:
  • 各種のタスク(例:ファイル管理、データバックアップ、レポート作成など)を自動化するクラウドサービス。ユーザーが日常的な作業を簡単にスクリプトやAIを使って自動化できる。
  • AI学習サポートプラットフォーム:
  • 受験生や学習者向けに、個別の学習プランを作成し、進捗を自動的に管理するサービス。AIがユーザーの理解度に応じた問題を提案したり、弱点克服のためのアドバイスを提供する。
  • ローカルコミュニティSNS:
  • 地域ごとのローカルなコミュニティSNS。近所でのイベントやニュース、フリーマーケット情報など、地域に特化した情報を交換できる。
  • オンラインスキルシェアプラットフォーム:
  • ユーザーが自身の専門知識やスキルを他のユーザーに教えたり、学んだりできるプラットフォーム。講師として登録でき、動画コンテンツやライブ講義を提供できる。
  • デジタルライフオーガナイザー:
  • ユーザーのオンラインアカウント、サブスクリプション、パスワードなどを一括で管理し、期限が近づいたら通知を送るサービス。セキュリティに配慮したデータ管理機能も持つ。
  • 趣味特化型Q&Aサイト:
  • 趣味(例:写真撮影、DIY、園芸、料理など)に特化したQ&Aサイト。ユーザーが同じ趣味を持つ仲間と意見交換や質問ができる。
  • AIパーソナルトレーナー:
  • フィットネスや食事管理をAIがサポートするサービス。日々の運動プランや食事の提案、進捗管理を行い、目標に合わせて調整。
  • ライティング支援ツール:
  • 小説、ブログ、エッセイなど、文章作成に特化したAI支援ツール。文法チェックやアイデア生成、構成アドバイスなどを提供。
  • クラウドベースのプロジェクト管理ツール:
  • チームでのプロジェクト管理を効率化するため、タスクの進捗管理、ファイル共有、コミュニケーションを一元化するツール。特にリモートワーク向けの機能が充実。
  • キャリアアドバイス&マッチングプラットフォーム:
  • AIが個人のスキルや経験に基づいてキャリアアドバイスを提供し、適切な企業やプロジェクトとマッチングしてくれるプラットフォーム。

ELDER

<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ELDER</title>
<link href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css" rel="stylesheet">
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.4/css/all.min.css" rel="stylesheet">
<style>
  body {
    padding-top: 20px;
    background-color: #f8f9fa;
  }
  .card {
    margin-bottom: 20px;
  }
  .profile-img {
    width: 50px;
    height: 50px;
    object-fit: cover;
    border-radius: 50%;
  }
  .comment-section {
    margin-top: 20px;
  }
  .navbar {
    margin-bottom: 20px;
  }
  .form-error {
    color: red;
    font-size: 0.9em;
  }
</style>
</head>
<body>

<nav class="navbar navbar-expand-lg navbar-light bg-light">
  <a class="navbar-brand" href="#">SNS</a>
  <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
    <span class="navbar-toggler-icon"></span>
  </button>
  <div class="collapse navbar-collapse" id="navbarNav">
    <ul class="navbar-nav ml-auto">
      <li class="nav-item">
        <a class="nav-link" href="#" onclick="showLoginForm()">ログイン</a>
      </li>
      <li class="nav-item">
        <a class="nav-link" href="#" onclick="showRegisterForm()">登録</a>
      </li>
      <li class="nav-item">
        <a class="nav-link" href="#" onclick="showProfile()" style="display: none;" id="navProfile">プロフィール</a>
      </li>
      <li class="nav-item">
        <a class="nav-link" href="#" onclick="logout()" style="display: none;" id="navLogout">ログアウト</a>
      </li>
    </ul>
  </div>
</nav>

<div class="container">
  <h1 class="text-center mb-4">ソーシャルネットワーキングサービス</h1>

  <!-- ログインフォーム -->
  <div id="loginForm" class="card w-50 mx-auto">
    <div class="card-body">
      <h2 class="card-title text-center mb-4">ログイン</h2>
      <div class="form-group">
        <input type="text" id="loginUsername" class="form-control" placeholder="ユーザー名">
        <span id="loginUsernameError" class="form-error" style="display: none;">ユーザー名を入力してください</span>
      </div>
      <div class="form-group">
        <input type="password" id="loginPassword" class="form-control" placeholder="パスワード">
        <span id="loginPasswordError" class="form-error" style="display: none;">パスワードを入力してください</span>
      </div>
      <button onclick="login()" class="btn btn-primary btn-block">ログイン</button>
      <button onclick="showRegisterForm()" class="btn btn-secondary btn-block">登録</button>
    </div>
  </div>

  <!-- 登録フォーム -->
  <div id="registerForm" class="card w-50 mx-auto" style="display: none;">
    <div class="card-body">
      <h2 class="card-title text-center mb-4">登録</h2>
      <div class="form-group">
        <input type="text" id="registerName" class="form-control" placeholder="氏名">
        <span id="registerNameError" class="form-error" style="display: none;">氏名を入力してください</span>
      </div>
      <div class="form-group">
        <input type="text" id="registerUsername" class="form-control" placeholder="ユーザー名">
        <span id="registerUsernameError" class="form-error" style="display: none;">ユーザー名を入力してください</span>
      </div>
      <div class="form-group">
        <input type="password" id="registerPassword" class="form-control" placeholder="パスワード">
        <span id="registerPasswordError" class="form-error" style="display: none;">パスワードを入力してください</span>
      </div>
      <div class="form-group">
        <input type="password" id="registerConfirmPassword" class="form-control" placeholder="パスワードを確認">
        <span id="registerConfirmPasswordError" class="form-error" style="display: none;">パスワードが一致しません</span>
      </div>
      <button onclick="register()" class="btn btn-primary btn-block">登録</button>
      <button onclick="showLoginForm()" class="btn btn-secondary btn-block">ログインに戻る</button>
    </div>
  </div>

  <!-- プロフィール -->
  <div id="profile" class="card w-50 mx-auto" style="display: none;">
    <div class="card-body">
      <h2 class="card-title text-center mb-4">プロフィール</h2>
      <div class="text-center mb-3">
        <img id="profileImg" class="profile-img" src="default_profile_img.jpg" alt="プロフィール画像">
      </div>
      <p class="text-center mb-2"><strong>氏名:</strong> <span id="profileName"></span></p>
      <p class="text-center mb-4"><strong>ユーザー名:</strong> <span id="profileUsername"></span></p>
      <div class="form-group">
        <input type="file" id="profileImageInput" accept="image/*" class="form-control-file">
      </div>
      <button onclick="uploadProfileImage()" class="btn btn-primary btn-block"><i class="fas fa-upload"></i> プロフィール画像をアップロード</button>
      <button id="followButton" onclick="toggleFollow()" class="btn btn-primary btn-block"></button>
      <button id="messageButton" onclick="openDirectMessageModal()" class="btn btn-primary btn-block">ダイレクトメッセージを送信</button>
      <button onclick="logout()" class="btn btn-danger btn-block"><i class="fas fa-sign-out-alt"></i> ログアウト</button>
    </div>
  </div>

  <!-- 投稿フォーム -->
  <div id="postForm" class="card w-75 mx-auto" style="display: none;">
    <div class="card-body">
      <h2 class="card-title text-center mb-4">投稿を作成</h2>
      <div class="form-group">
        <textarea id="postContent" class="form-control" rows="3" placeholder="今何を考えていますか?"></textarea>
      </div>
      <button onclick="createPost()" class="btn btn-primary btn-block">投稿</button>
    </div>
  </div>

  <!-- FEED登録フォーム -->
  <div id="feedForm" class="card w-75 mx-auto" style="display: none;">
    <div class="card-body">
      <h2 class="card-title text-center mb-4">FEEDを登録</h2>
      <div class="form-group">
        <input type="text" id="feedUrl" class="form-control" placeholder="RSSフィードのURL">
        <span id="feedUrlError" class="form-error" style="display: none;">RSSフィードのURLを入力してください</span>
      </div>
      <button onclick="registerFeed()" class="btn btn-primary btn-block">登録</button>
    </div>
  </div>

  <!-- タイムライン -->
  <div id="timeline" class="w-75 mx-auto mt-4"></div>
</div>

<!-- リプライモーダル -->
<div class="modal fade" id="replyModal" tabindex="-1" role="dialog" aria-labelledby="replyModalLabel" aria-hidden="true">
  <div class="modal-dialog" role="document">
    <div class="modal-content">
      <div class="modal-header">
        <h5 class="modal-title" id="replyModalLabel">投稿に返信</h5>
        <button type="button" class="close" data-dismiss="modal" aria-label="閉じる">
          <span aria-hidden="true">&times;</span>
        </button>
      </div>
      <div class="modal-body">
        <textarea id="replyContent" class="form-control" rows="3" placeholder="返信をここに書いてください..."></textarea>
      </div>
      <div class="modal-footer">
        <button type="button" class="btn btn-secondary" data-dismiss="modal">閉じる</button>
        <button type="button" onclick="postReply()" class="btn btn-primary">返信</button>
      </div>
    </div>
  </div>
</div>

<!-- ダイレクトメッセージモーダル -->
<div class="modal fade" id="directMessageModal" tabindex="-1" role="dialog" aria-labelledby="directMessageModalLabel" aria-hidden="true">
  <div class="modal-dialog" role="document">
    <div class="modal-content">
      <div class="modal-header">
        <h5 class="modal-title" id="directMessageModalLabel">ダイレクトメッセージを送信</h5>
        <button type="button" class="close" data-dismiss="modal" aria-label="閉じる">
          <span aria-hidden="true">&times;</span>
        </button>
      </div>
      <div class="modal-body">
        <select id="recipient" class="form-control">
          <option value="">送信先を選択</option>
          <!-- ユーザーリストがここに追加される -->
        </select>
        <textarea id="directMessageContent" class="form-control mt-2" rows="3" placeholder="メッセージをここに書いてください..."></textarea>
      </div>
      <div class="modal-footer">
        <button type="button" class="btn btn-secondary" data-dismiss="modal">閉じる</button>
        <button type="button" onclick="sendDirectMessage()" class="btn btn-primary">送信</button>
      </div>
    </div>
  </div>
</div>

<script src="https://code.jquery.com/jquery-3.5.1.slim.min.js"></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>
<script>
  let currentUser = null; // 現在のログインユーザー
  let users = []; // ユーザーの配列
  let posts = []; // 投稿の配列
  let feeds = []; // フィードの配列

  // ローカルストレージにユーザー情報を保存
  function saveUsersToLocalStorage() {
    localStorage.setItem('users', JSON.stringify(users));
  }

  // ローカルストレージに投稿情報を保存
  function savePostsToLocalStorage() {
    localStorage.setItem('posts', JSON.stringify(posts));
  }

  // ローカルストレージにフィード情報を保存
  function saveFeedsToLocalStorage() {
    localStorage.setItem('feeds', JSON.stringify(feeds));
  }

  // ローカルストレージからユーザー情報を読み込み
  function loadUsersFromLocalStorage() {
    const storedUsers = localStorage.getItem('users');
    if (storedUsers) {
      users = JSON.parse(storedUsers);
    }
  }

  // ローカルストレージから投稿情報を読み込み
  function loadPostsFromLocalStorage() {
    const storedPosts = localStorage.getItem('posts');
    if (storedPosts) {
      posts = JSON.parse(storedPosts);
    }
  }

  // ローカルストレージからフィード情報を読み込み
  function loadFeedsFromLocalStorage() {
    const storedFeeds = localStorage.getItem('feeds');
    if (storedFeeds) {
      feeds = JSON.parse(storedFeeds);
    }
  }

  function showLoginForm() {
    hideAll();
    document.getElementById('loginForm').style.display = 'block';
  }

  function showRegisterForm() {
    hideAll();
    document.getElementById('registerForm').style.display = 'block';
  }

  function validateLoginForm() {
    let valid = true;
    const username = document.getElementById('loginUsername').value;
    const password = document.getElementById('loginPassword').value;
    
    if (!username) {
      document.getElementById('loginUsernameError').style.display = 'block';
      valid = false;
    } else {
      document.getElementById('loginUsernameError').style.display = 'none';
    }
    
    if (!password) {
      document.getElementById('loginPasswordError').style.display = 'block';
      valid = false;
    } else {
      document.getElementById('loginPasswordError').style.display = 'none';
    }
    
    return valid;
  }

  function login() {
    if (!validateLoginForm()) {
      return;
    }

    const username = document.getElementById('loginUsername').value;
    const password = document.getElementById('loginPassword').value;
    const user = users.find(u => u.username === username && u.password === password);
    if (user) {
      currentUser = user;
      showProfile();
    } else {
      alert('ユーザー名またはパスワードが間違っています。');
    }
  }

  function logout() {
    currentUser = null;
    hideAll();
    document.getElementById('loginForm').style.display = 'block';
    document.getElementById('navProfile').style.display = 'none';
    document.getElementById('navLogout').style.display = 'none';
  }

  function validateRegisterForm() {
    let valid = true;
    const name = document.getElementById('registerName').value;
    const username = document.getElementById('registerUsername').value;
    const password = document.getElementById('registerPassword').value;
    const confirmPassword = document.getElementById('registerConfirmPassword').value;
    
    if (!name) {
      document.getElementById('registerNameError').style.display = 'block';
      valid = false;
    } else {
      document.getElementById('registerNameError').style.display = 'none';
    }
    
    if (!username) {
      document.getElementById('registerUsernameError').style.display = 'block';
      valid = false;
    } else {
      document.getElementById('registerUsernameError').style.display = 'none';
    }
    
    if (!password) {
      document.getElementById('registerPasswordError').style.display = 'block';
      valid = false;
    } else {
      document.getElementById('registerPasswordError').style.display = 'none';
    }
    
    if (password !== confirmPassword) {
      document.getElementById('registerConfirmPasswordError').style.display = 'block';
      valid = false;
    } else {
      document.getElementById('registerConfirmPasswordError').style.display = 'none';
    }
    
    return valid;
  }

  function register() {
    if (!validateRegisterForm()) {
      return;
    }

    const name = document.getElementById('registerName').value;
    const username = document.getElementById('registerUsername').value;
    const password = document.getElementById('registerPassword').value;

    users.push({ name, username, password, following: false });
    saveUsersToLocalStorage(); // ユーザー情報を保存
    alert('登録が完了しました!ログインしてください。');
    showLoginForm();
  }

  function showProfile() {
    hideAll();
    document.getElementById('profile').style.display = 'block';
    document.getElementById('profileName').textContent = currentUser.name;
    document.getElementById('profileUsername').textContent = currentUser.username;
    document.getElementById('postForm').style.display = 'block';
    document.getElementById('feedForm').style.display = 'block';
    loadProfileImage(); // プロフィール画像をロード
    displayTimeline(); // タイムラインを表示
    updateFollowButton(); // フォローボタンの表示を更新
    loadDirectMessageRecipients(); // ダイレクトメッセージの送信先をロード
    document.getElementById('navProfile').style.display = 'block';
    document.getElementById('navLogout').style.display = 'block';
  }

  // プロフィール画像をロード
  function loadProfileImage() {
    const profileImg = document.getElementById('profileImg');
    // プロフィール画像が設定されている場合はその画像を表示
    if (currentUser.profileImage) {
      profileImg.src = currentUser.profileImage;
    } else {
      // デフォルトのプロフィール画像を表示
      profileImg.src = 'default_profile_img.jpg';
    }
  }

  // プロフィール画像をアップロード
  function uploadProfileImage() {
    const input = document.getElementById('profileImageInput');
    const file = input.files[0];
    if (file) {
      // FileReaderを使用して画像を読み込む
      const reader = new FileReader();
      reader.onload = function(e) {
        // 読み込んだ画像をプロフィール画像として設定
        currentUser.profileImage = e.target.result;
        // プロフィール画像を更新して表示
        loadProfileImage();
        saveUsersToLocalStorage(); // プロフィール画像を保存
      }
      reader.readAsDataURL(file);
    }
  }

  // フォローボタンの更新
  function updateFollowButton() {
    const followButton = document.getElementById('followButton');
    if (currentUser.following) {
      followButton.textContent = 'フォロー解除';
    } else {
      followButton.textContent = 'フォロー';
    }
    saveUsersToLocalStorage(); // フォロー状態を保存
  }

  // フォローの切り替え
  function toggleFollow() {
    currentUser.following = !currentUser.following;
    updateFollowButton();
  }

  // ダイレクトメッセージの送信先をロード
  function loadDirectMessageRecipients() {
    const select = document.getElementById('recipient');
    select.innerHTML = '<option value="">送信先を選択</option>';
    users.forEach(user => {
      if (user !== currentUser) {
        const option = document.createElement('option');
        option.value = user.username;
        option.textContent = user.name;
        select.appendChild(option);
      }
    });
  }

  // ダイレクトメッセージのモーダルを開く
  function openDirectMessageModal() {
    $('#directMessageModal').modal('show');
  }

  // ダイレクトメッセージを送信
  function sendDirectMessage() {
    const recipientUsername = document.getElementById('recipient').value;
    const messageContent = document.getElementById('directMessageContent').value;
    if (recipientUsername && messageContent.trim() !== '') {
      const recipient = users.find(user => user.username === recipientUsername);
      if (recipient) {
        alert(`メッセージを${recipient.name}に送信しました: ${messageContent}`);
        $('#directMessageModal').modal('hide');
      } else {
        alert('送信先が見つかりません。');
      }
    } else {
      alert('送信先を選択し、メッセージを入力してください。');
    }
  }

  function createPost() {
    const postContent = document.getElementById('postContent').value;
    if (postContent.trim() !== '') {
      const post = {
        id: Date.now(),
        content: postContent,
        author: currentUser.name,
        authorUsername: currentUser.username,
        authorProfileImage: currentUser.profileImage, // プロフィール画像を追加
        likes: 0,
        comments: []
      };
      posts.unshift(post); // 最新の投稿を先頭に追加
      savePostsToLocalStorage(); // 投稿を保存
      displayTimeline();
      document.getElementById('postContent').value = ''; // 投稿後、入力欄を空にする
    }
  }

  function registerFeed() {
    const feedUrl = document.getElementById('feedUrl').value;
    if (feedUrl.trim() !== '') {
      feeds.push(feedUrl);
      saveFeedsToLocalStorage(); // フィード情報を保存
      alert('RSSフィードが登録されました!');
      document.getElementById('feedUrl').value = ''; // 登録後、入力欄を空にする
    } else {
      document.getElementById('feedUrlError').style.display = 'block';
    }
  }

  function displayTimeline() {
    const timeline = document.getElementById('timeline');
    timeline.innerHTML = '';
    posts.forEach(post => {
      const postElement = document.createElement('div');
      postElement.classList.add('card', 'mb-3');
      postElement.innerHTML = `
        <div class="card-body">
          <div class="d-flex align-items-center mb-3">
            <img src="${post.authorProfileImage || 'default_profile_img.jpg'}" class="profile-img mr-2" alt="プロフィール画像">
            <div>
              <strong>${post.author}</strong> (@${post.authorUsername})
            </div>
          </div>
          <p class="card-text">${post.content}</p>
          ${post.link ? `<a href="${post.link}" target="_blank" class="btn btn-link">続きを読む</a>` : ''}
          <div class="d-flex justify-content-between">
            <div>
              <button onclick="likePost(${post.id})" class="btn btn-primary btn-sm"><i class="fas fa-thumbs-up"></i> いいね (${post.likes})</button>
              <button onclick="toggleComments(${post.id})" class="btn btn-secondary btn-sm"><i class="fas fa-comments"></i> コメント (${post.comments.length})</button>
              <button onclick="replyToPost(${post.id})" class="btn btn-info btn-sm"><i class="fas fa-reply"></i> 返信</button>
            </div>
            <small class="text-muted">${new Date(post.id).toLocaleString()}</small>
          </div>
          <div id="comments-${post.id}" class="comment-section mt-3" style="display: none;">
            ${post.comments.map(comment => `
              <div class="card mb-2">
                <div class="card-body">
                  <p><strong>${comment.author}</strong>: ${comment.content}</p>
                </div>
              </div>
            `).join('')}
          </div>
        </div>
      `;
      timeline.appendChild(postElement);
    });
  }

  function likePost(postId) {
    const post = posts.find(p => p.id === postId);
    post.likes++;
    savePostsToLocalStorage(); // いいねを保存
    displayTimeline();
  }

  function toggleComments(postId) {
    const commentSection = document.getElementById(`comments-${postId}`);
    commentSection.style.display = commentSection.style.display === 'none' ? 'block' : 'none';
  }

  function replyToPost(postId) {
    const modal = document.getElementById('replyModal');
    modal.dataset.postId = postId;
    $('#replyModal').modal('show');
  }

  function postReply() {
    const postId = parseInt(document.getElementById('replyModal').dataset.postId);
    const post = posts.find(p => p.id === postId);
    const replyContent = document.getElementById('replyContent').value;
    if (replyContent.trim() !== '') {
      post.comments.push({ author: currentUser.name, content: replyContent });
      $('#replyModal').modal('hide');
      savePostsToLocalStorage(); // コメントを保存
      displayTimeline();
    } else {
      alert('返信を入力してください。');
    }
  }

  function fetchFeeds() {
    feeds.forEach(feedUrl => {
      fetch(`https://api.rss2json.com/v1/api.json?rss_url=${encodeURIComponent(feedUrl)}`)
        .then(response => response.json())
        .then(data => {
          data.items.forEach(item => {
            const post = {
              id: Date.now() + Math.random(), // 重複しないIDを生成
              content: item.title,
              link: item.link, // リンクを追加
              author: 'RSSフィード',
              authorUsername: 'rssfeed',
              authorProfileImage: 'default_profile_img.jpg', // プロフィール画像を設定
              likes: 0,
              comments: []
            };
            posts.unshift(post); // 最新の投稿を先頭に追加
            savePostsToLocalStorage(); // 投稿を保存
          });
          displayTimeline(); // タイムラインを表示
        })
        .catch(error => console.error('Error fetching RSS feed:', error));
    });
  }

  function hideAll() {
    document.getElementById('loginForm').style.display = 'none';
    document.getElementById('registerForm').style.display = 'none';
    document.getElementById('profile').style.display = 'none';
    document.getElementById('postForm').style.display = 'none';
    document.getElementById('feedForm').style.display = 'none';
  }

  // 初期データをローカルストレージから読み込む
  loadUsersFromLocalStorage();
  loadPostsFromLocalStorage();
  loadFeedsFromLocalStorage();

  // 初期ログイン画面表示
  hideAll();
  document.getElementById('loginForm').style.display = 'block';

  // 1時間ごとにRSSフィードをフェッチして自動投稿
  setInterval(fetchFeeds, 5); // 3600000ミリ秒 = 1時間

</script>

</body>
</html>

RSSの機能を使えるようにしました