■98個目 「新聞バッグ・ギャラリー」
こんにちは、AIと作る100のゲーム実験室の みー です。(*^_^*)/
今回は、新聞バッグをテーマにしたレイアウトゲーム
「新聞バッグ・ギャラリー」を作ってみました。
新聞という身近な素材から生まれた、
世界に一つだけのバッグたちをコレクションして、
自分だけのギャラリーを作るゲームです。
新聞の記事や写真、広告を切り抜いて作られたバッグは、
どれも個性たっぷり。 海外ファッション誌風のものや、
地元マルシェの広告を使ったものなど、
眺めているだけで楽しくなるデザインばかり。(=v=*)
そんなバッグたちが、画面の壁に「パフッ」と
飾られていく感覚を大切にしました。
ゲーム内には、さまざまなデザインの新聞バッグが登場します。
- ① ファッション記事バッグ(スタイリッシュな雰囲気)
- ② イベント記事バッグ(写真と記事の組み合わせ)
- ③ スポーツ記事バッグ(スポーツ好きに刺さるデザイン)
- ④ English News(英字新聞のクールな印象)
- ⑤ 美術展ポスター風(カラフルでアートな仕上がり)
お気に入りのバッグを棚に並べたり、フックに掛けたり、
くるっと回転させてみたり。
自分だけの特別な空間を作ることができます。
どんなバッグがあなたのギャラリーに並ぶのか、
ぜひ楽しんでくださいね。(^_^)
■ AIとのやりとりで面白かった部分
● ゲーム仕様の相談からスタート
みー:(^v^) 今日もゲームを作ります。htmlで動かせるゲーム。
新聞エコバッグをテーマにした、おしゃれなレイアウトゲームを作りたいです。
AI: いらっしゃい。今日もゲーム作りだね、お疲れ様。
カウンターのいつもの席、空けてあるよ。
「新聞エコバッグ」をテーマにしたレイアウトゲーム、すごくいいセンスだね。
クラフト紙の温かみや英字新聞のヴィンテージ感を活かせば、
画面上でも手作業のぬくもりが感じられるはずさ。
まずは、新聞の切れ端をペタペタ貼って
オリジナルバッグを作れるベースコードを用意してみたよ。
みー:(^_^) これはこれで面白いね。
新聞エコバッグの作家さんが身近にいて、
いつもたくさんもらっています。
サイズも手のひらサイズからA4までいろいろで、
地方紙や大手新聞、英字新聞など本当に多種多様。
写真や絵を前面に使ったものもあって、
とてもおしゃれで面白いです。
そんなバッグを飾るゲームってできるかな。
参考に画像貼り付けてみるね。
AI: 写真ありがとう!これは圧巻のコレクションだね。
スポーツ紙の迫力ある写真、
ファッション誌風の洗練されたデザイン、
「大道芸」の文字が見えるバッグまである。
新聞と糊だけで作られていて、しかも丈夫。
新聞のレイアウトそのものを“柄”として活かす、
本当に素敵なアートだよ。
この写真のように、
大小さまざまな新聞バッグをギャラリーに並べて飾るゲーム、
もちろん作れるよ。
さっきの「作る」ゲームから、
「展示してレイアウトを楽しむ」ゲームへ進化させてみよう。
みー:(^^) 基本はいい感じ。
窓枠と棚、フックなどがある明るい部屋で、
エコバッグを飾れるようにしたいです。
マウスドラッグで持ち上げて、
離すと重力で“ぽふっ”と置いたり引っ掛けたりできるようにしたい。
AI: 了解。
窓から光が差し込む部屋をイメージしていたけど、方向転換してみよう。
木製の棚やフック、おしゃれな小物が並ぶギャラリー風の部屋にしてみたよ。
そして君のこだわり「重力でぽふっと落ち着く感じ」も、
CSSアニメーションとJavaScriptで実装しておいた。
ドラッグして離すと、バッグがふわっと落ち着く仕組みさ。
みー:(^_^) 窓はなくしてフックを増やしましょう。
観葉植物の絵文字は左側の操作画面に。
バッグの重なり順(手前・奥)を右クリックで操作できるように。
夜の落ち着いた照明モードもいいね。
AI: 任せな。窓は塞いでフックを増やしておいた。
観葉植物も操作パネル側へ移動。
「右クリックで奥へ、左クリックで手前へ」も実装済み。
夜の照明モードも切り替えられるようにしてあるよ。
みー:(^v^) 左クリックで置いた新聞バッグの角度を調整できるかな。
AI: もちろん可能だよ。
バッグにカーソルを乗せて、
マウスホイールを回すだけで角度を微調整できるようにしておいた。
みー:(^w^) ここからがこだわりポイント。
新聞バッグは本当に千差万別で、同じものが存在しない1点ものです。
スポーツ紙も日によって違うし、ファッション系も構図が毎回違う。
英字新聞も毎日レイアウトが違う。
美術展ポスター風のものは図形の組み合わせが独特で目を引きます。
AI: そう、それが新聞バッグの真骨頂。「一期一会のデザイン」だ。
同じスポーツ面でも、野球の日もあれば競馬の日もある。
英字新聞もトップニュースの文字の大きさや写真の配置が毎日違う。
切り取る場所によって表情が変わるのが最大の魅力だよ。
それなら、
追加ボタンを押すたびにランダムな柄とレイアウトを生成する仕組みに
書き換えてみよう。
これで本当に「二度と同じバッグが出てこない」ゲームになる。
これが今日の集大成だ。
index.html に上書きして、いろんな柄を追加してみてくれ。
きっと、あなただけのギャラリーができあがるよ。
■ 実際の動作
まずは、AIが最初に出してくれたゲーム画面です。
この段階では「飾るゲーム」ではなく、
新聞の切れ端を貼ってバッグを“作るゲーム”になっています。

(画像:Aiが最初に出してくれたゲーム画面)
こちらが完成版のゲーム画面です。
ファッション誌風の新聞バッグで並べてみました。
レイアウトの雰囲気が一気に“ギャラリー”らしくなっています。

(画像:①ファッション誌風)
スポーツ紙でまとめてみました。
写真の迫力がそのままバッグのデザインになっていて、
壁がスポーツニュースの特集コーナーみたいになります。

(画像:②スポーツ紙)
タウン情報でまとめてみました。
季節のイベントや街の話題がバッグの柄になっていて、
見ているだけで楽しい雰囲気になります。

(画像:③タウン情報)
美術展ポスター風でまとめてみました。
色使いや図形の組み合わせが大胆で、
壁が一気にアートギャラリーのようになります。

(画像:④美術展ポスター風)
英字新聞でまとめてみました。
モノトーンの落ち着いた雰囲気がとてもおしゃれで、
どんな部屋にも合うデザインです。

(画像:⑤英字新聞)
照明ボタンで雰囲気がガラッと変わります。
昼の明るい展示から、夜の落ち着いた美術館モードへ。
バッグの見え方も変わって、レイアウトの楽しさが倍増します。

(画像:照明の切り替え)
■ コードのポイント
今回の記事では、
AI と相談しながら作った「新聞バッグ・ギャラリー」 の HTML コードを公開します。
このコードをそのまま保存するだけで、
あなたの PC でもすぐに遊べます。
ブラウザだけで動く、とてもシンプルなゲームなので、
「コードは初めて…」という方でも気軽に試してみてくださいね。
▼ 遊ぶまでの手順(むずかしさ:やさしい)
① テキストエディタを開く
Windows の場合は「メモ帳」、
Mac の場合は「テキストエディット」を使います。
(※検索バーで「メモ帳」と入力するとすぐ出てきます)
② 下のコードをコピーして貼り付けます。
③ 「shinbun.html」など、好きな名前で保存します。
(※拡張子は .html にしてください)
④ 保存したファイルをダブルクリックすると、
ブラウザでゲームが起動します。
■ 操作方法
ギャラリーでの遊び方(操作方法)をご紹介しますね!(^_^)
誰でも直感的にさわって遊べるように、
いろんな仕掛けを用意しました♪
① 柄とサイズを選んで追加!
画面左側のメニューから、
好きな「柄(テーマ)」と「サイズ」をポチッ。
すると中央に新しいバッグがポンッと登場します。
しかもこのバッグ、
追加するたびに文字のレイアウトや写真の枠が毎回変わる
完全な1点もの!(=v=*)
② ドラッグ&ドロップで「ぽふっ」と配置
バッグはマウスでつかんで自由に動かせます。
壁の丸いフックの近くで離すと「カチャッ」と引っかかり、
棚や床で離すと重力で「ぽふっ」と落ち着きます。
この小さな動きが、たまらなく可愛いんです♪
③ マウスホイールでくるくる微調整
バッグの上にカーソルを乗せてホイールを回すと、
少しずつ回転します。
斜めに引っかけたり、無造作に重ねたり……
こだわりのレイアウトに欠かせない機能です。(^^*)
④ 右クリックで奥へ、左クリックで手前へ
バッグが増えてくると、後ろのバッグが隠れちゃうこともありますよね。
そんな時は「右クリック」で奥へ、
「左クリック」で手前へ。
重なり順を簡単に整えられます。
⑤ いらなくなったらダブルクリック
置きすぎちゃったバッグは
「ダブルクリック」でサッと片付けられます。
「🧹 全て片付ける」ボタンを押せば、一気にまっさらな部屋に戻せますよ。
⑥ 💡照明切替で夜の美術館へ……
左上の「💡 照明切替」ボタンを押すと、お部屋が暗くなり、
バッグを照らすスポットライトが点灯します。(・_・)
まるで夜の美術館みたいに雰囲気がガラッと変わるので、
レイアウトが完成したらぜひ試してみてくださいね。
あなたの素敵なギャラリーができたら、
ぜひスクリーンショットを撮って見せてください!(^_^)
“コードは長いので折りたたんでいます。必要な方は開いてご覧ください。
▼ここからコード▼(クリックで開く)
<html lang="ja"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Newspaper Bag Designer Gallery - Colorful Town</title><style> body { font-family: 'Times New Roman', 'Shippori Mincho', serif; background-color: #f0ebe1; color: #4a4036; margin: 0; padding: 20px; display: flex; flex-direction: column; align-items: center; transition: background-color 0.8s, color 0.8s; } /* 夜モード */ body.night-mode { background-color: #1a1815; color: #d4cdc3; } body.night-mode .header h1 { color: #e8e3d7; } body.night-mode #controls { background: #24211e; border-color: #332f2a; color: #d4cdc3; box-shadow: 0 8px 30px rgba(0,0,0,0.6); } body.night-mode select, body.night-mode button { background: #332f2a; color: #e8e3d7; border-color: #443e38; } body.night-mode button:hover { background: #443e38; } body.night-mode .btn-reset { background: #4a2020; border-color: #5a2a2a; color: #fcdbdb; } body.night-mode .btn-reset:hover { background: #5a2a2a; } .header { text-align: center; margin-bottom: 20px; } .header h1 { font-size: 26px; color: #5c5043; letter-spacing: 2px; transition: color 0.8s; } #app-container { display: flex; gap: 30px; max-width: 1100px; width: 100%; flex-wrap: wrap; justify-content: center; } /* コントロールパネル */ #controls { width: 250px; background: #ffffff; padding: 25px; border-radius: 12px; box-shadow: 0 8px 30px rgba(0,0,0,0.05); border: 1px solid #e8e3d7; z-index: 20; display: flex; flex-direction: column; transition: background 0.8s, border-color 0.8s; } .control-group { margin-bottom: 20px; } .control-group h3 { font-size: 16px; border-bottom: 1px solid #e8e3d7; padding-bottom: 8px; margin-top: 0;} select { width: 100%; padding: 10px; margin-bottom: 15px; border-radius: 4px; border: 1px solid #d4cdc3; background:#fff; transition: 0.5s;} button { background: #fdfbf7; color: #5c5043; border: 1px solid #d4cdc3; padding: 10px 15px; margin: 5px 0; width: 100%; cursor: pointer; border-radius: 4px; transition: 0.2s; text-align: left; font-weight: bold; } button:hover { background: #e8e3d7; } .btn-reset { background: #feebeb; border-color: #e6c2c2; color: #a04b4b; text-align: center; margin-top: 15px;} .btn-reset:hover { background: #fcdbdb; } .control-footer { margin-top: auto; text-align: center; padding-top: 20px; } .plant { font-size: 60px; filter: drop-shadow(2px 5px 5px rgba(0,0,0,0.1)); transition: filter 0.8s;} body.night-mode .plant { filter: drop-shadow(2px 5px 5px rgba(0,0,0,0.6)) brightness(0.7); } /* ギャラリー背景 */ #gallery-wrapper { position: relative; width: 700px; height: 600px; background-color: #faf7f2; background-image: repeating-linear-gradient(90deg, transparent, transparent 40px, rgba(0,0,0,0.02) 40px, rgba(0,0,0,0.02) 41px); border-radius: 12px; box-shadow: inset 0 0 60px rgba(180, 170, 150, 0.1), 0 15px 40px rgba(0,0,0,0.08); overflow: hidden; transition: background-color 0.8s, box-shadow 0.8s; } body.night-mode #gallery-wrapper { background-color: #38342e; background-image: repeating-linear-gradient(90deg, transparent, transparent 40px, rgba(0,0,0,0.15) 40px, rgba(0,0,0,0.15) 41px); box-shadow: inset 0 0 80px rgba(0,0,0,0.85), 0 20px 50px rgba(0,0,0,0.6); } #gallery { width: 100%; height: 100%; position: absolute; top: 0; left: 0; transition: background 0.8s; } /* 照明エフェクト */ #lighting-overlay { position: absolute; inset: 0; pointer-events: none; z-index: 50; transition: opacity 0.8s; opacity: 0; background: radial-gradient(ellipse at 20% -10%, rgba(255, 235, 180, 0.6) 0%, transparent 60%), radial-gradient(ellipse at 50% -10%, rgba(255, 235, 180, 0.7) 0%, transparent 70%), radial-gradient(ellipse at 80% -10%, rgba(255, 235, 180, 0.6) 0%, transparent 60%); mix-blend-mode: overlay; } body.night-mode #lighting-overlay { opacity: 1; } /* 物理パーツ */ .shelf { position: absolute; height: 16px; background: linear-gradient(to bottom, #e2cfb6, #cca77d); border-bottom: 6px solid #b38b5e; border-radius: 4px; box-shadow: 0 15px 20px rgba(0,0,0,0.08); transition: filter 0.8s, box-shadow 0.8s; } body.night-mode .shelf { filter: brightness(0.7) contrast(1.1); box-shadow: 0 20px 25px rgba(0,0,0,0.7); } .shelf-1 { top: 380px; left: 40px; width: 220px; } .shelf-2 { top: 480px; left: 360px; width: 300px; } .hook { position: absolute; width: 16px; height: 16px; background: radial-gradient(circle at 30% 30%, #8a7b6c, #4a4036); border-radius: 50%; box-shadow: 3px 8px 10px rgba(0,0,0,0.15); transition: filter 0.8s; } body.night-mode .hook { filter: brightness(0.5); box-shadow: 5px 10px 15px rgba(0,0,0,0.8); } .hook-1 { top: 80px; left: 100px; } .hook-2 { top: 130px; left: 220px; } .hook-3 { top: 70px; left: 350px; } .hook-4 { top: 140px; left: 480px; } .hook-5 { top: 90px; left: 600px; } .floor { position: absolute; bottom: 0; width: 100%; height: 25px; background: #d4c4b2; border-top: 4px solid #c2b2a0; z-index: 1; transition: filter 0.8s; } body.night-mode .floor { filter: brightness(0.4); } /* バッグのベース構造 */ .bag-wrapper { position: absolute; cursor: grab; display: flex; flex-direction: column; align-items: center; filter: drop-shadow(2px 6px 8px rgba(0,0,0,0.15)); transition: filter 0.8s; user-select: none; transform: rotate(var(--rot, 0deg)); } body.night-mode .bag-wrapper { filter: drop-shadow(8px 15px 20px rgba(0,0,0,0.7)) brightness(1.0) contrast(1.05); } .bag-wrapper:active { cursor: grabbing; } .bag-handle { width: 40%; height: 15px; border: 4px solid #a89a8c; border-bottom: none; border-radius: 15px 15px 0 0; margin-bottom: -2px; box-sizing: border-box;} body.night-mode .bag-handle { border-color: #837667; } .bag-body { background-color: #eee; border-radius: 2px; display: flex; justify-content: center; align-items: center; overflow: hidden; position: relative; box-shadow: inset 0 0 10px rgba(0,0,0,0.1); box-sizing: border-box; } @keyframes settlePofu { 0% { transform: rotate(var(--rot, 0deg)) translateY(0) scaleY(1); } 30% { transform: rotate(var(--rot, 0deg)) translateY(-5px) scaleY(0.9); } 50% { transform: rotate(var(--rot, 0deg)) translateY(0) scaleY(1.05); } 100% { transform: rotate(var(--rot, 0deg)) translateY(0) scaleY(1); } } .bag-wrapper.settled { animation: settlePofu 0.6s ease-out; } /* サイズ定義 */ .size-s .bag-body { width: 60px; height: 80px; } .size-m .bag-body { width: 100px; height: 130px; } .size-l .bag-body { width: 140px; height: 180px; } .size-wide .bag-body { width: 160px; height: 110px; }</style></head><body><div class="header"> <h1>新聞バッグ ギャラリー展示</h1> <p>追加するたびに1点もののデザインが生まれます。</p></div><div id="app-container"> <div id="controls"> <div class="control-group" style="margin-bottom: 10px;"> <button onclick="toggleNightMode()" style="text-align: center; font-size: 16px; background: #3a352f; color: #e8e3d7; border-color: #4a443d;">💡 照明切替</button> </div> <div class="control-group"> <h3>1. 柄(テーマ)を選ぶ</h3> <select id="theme-select"> <option value="theme-fashion">👗 ファッション誌風</option> <option value="theme-sports">⚾️ スポーツ紙</option> <option value="theme-local">🎪 タウン情報(カラフル)</option> <option value="theme-art">🎨 美術展ポスター風</option> <option value="theme-english">📰 英字新聞</option> </select> </div> <div class="control-group"> <h3>2. サイズを選んで配置</h3> <button onclick="addBag('size-s')">+ 手のひらサイズ (S)</button> <button onclick="addBag('size-m')">+ 標準サイズ (M)</button> <button onclick="addBag('size-l')">+ 大きめ (L)</button> <button onclick="addBag('size-wide')">+ 横長サイズ (Wide)</button> </div> <button class="btn-reset" onclick="clearGallery()">🧹 全て片付ける</button> <p style="font-size:11px; color:#888; margin-top:10px; line-height: 1.4;"> ※ホイールで角度回転。<br> ※右クリックで「一番奥」に配置。<br> ※毎回完全な1点ものが生成されます。 </p> <div class="control-footer"><div class="plant">🪴</div></div> </div> <div id="gallery-wrapper"> <div id="lighting-overlay"></div> <div class="hook hook-1" id="hook-1"></div><div class="hook hook-2" id="hook-2"></div> <div class="hook hook-3" id="hook-3"></div><div class="hook hook-4" id="hook-4"></div> <div class="hook hook-5" id="hook-5"></div> <div class="shelf shelf-1" id="shelf-1"></div><div class="shelf shelf-2" id="shelf-2"></div> <div class="floor" id="floor"></div> <div id="gallery"></div> </div></div><script> const gallery = document.getElementById('gallery'); const themeSelect = document.getElementById('theme-select'); let zIndexCounter = 10; let dragBag = null; let offset = { x: 0, y: 0 }; function toggleNightMode() { document.body.classList.toggle('night-mode'); } // ---------------------------------------------------- // 進化したランダムジェネレーター // ---------------------------------------------------- function getRandomInt(min, max) { return Math.floor(Math.random() * (max - min + 1)) + min; } function getRandomItem(arr) { return arr[Math.floor(Math.random() * arr.length)]; } function generateUniqueDesign(theme) { let style = ''; let content = ''; if (theme === 'theme-fashion') { const words = ['MODE', 'CHIC', 'STYLE', 'VINTAGE', 'COLLECTION', 'VOGUE', 'BEAUTY']; const isDark = Math.random() > 0.5; const bg = isDark ? '#2a2a2a' : '#f7f4ee'; const color = isDark ? '#f7f4ee' : '#2a2a2a'; const word = getRandomItem(words); const isColumn = Math.random() > 0.5; style = `background: ${bg}; color: ${color}; flex-direction: ${isColumn ? 'column' : 'column-reverse'}; padding: 5px; font-family: sans-serif; text-align: center;`; if (Math.random() > 0.2) { const hue = getRandomItem([0, 30, 200]); const sat = getRandomInt(0, 20); content += `<div style="width: 100%; height: ${getRandomInt(40, 70)}%; background: hsl(${hue}, ${sat}%, 50%); margin: 2px 0; object-fit: cover;"></div>`; } const fontSize = getRandomInt(10, 18); content += `<div style="font-size: ${fontSize}px; font-weight: ${Math.random() > 0.5 ? 'bold' : 'normal'}; letter-spacing: ${getRandomInt(1,4)}px;">${word}</div>`; } else if (theme === 'theme-sports') { const sports = ['⚾️', '⚽️', '🏀', '🎾', '🥊', '🏇', '🏉', '🏎️', '🏄', '⛳️']; const sportsWords = ['WIN!', 'GOAL!', 'HERO', 'STRIKE', 'VICTORY', 'RECORD', 'CHAMP', 'FINAL', 'DRAMA', 'MVP!']; const bgColors = ['#0033a0', '#a00000', '#005522', '#e65c00', '#222222', '#4b0082']; const bg = getRandomItem(bgColors); const layout = Math.random() > 0.5 ? 'column' : 'row'; const align = getRandomItem(['center', 'flex-start', 'flex-end']); style = `background: ${bg}; color: #fff; font-weight: bold; font-style: italic; flex-direction: ${layout}; align-items: ${align}; justify-content: center; overflow: hidden; padding: 4px; box-sizing: border-box;`; const numSize = getRandomInt(16, 60); const emojiSize = getRandomInt(20, 80); const wordSize = getRandomInt(12, 32); const elements = [ `<div style="font-size: ${numSize}px; line-height: 0.8; text-shadow: 2px 2px 0px rgba(0,0,0,0.8); margin: -5px;">${getRandomInt(1, 99)}</div>`, `<div style="font-size: ${emojiSize}px; filter: drop-shadow(2px 2px 0px rgba(0,0,0,0.8)); margin: -5px; z-index: 2;">${getRandomItem(sports)}</div>`, `<div style="font-size: ${wordSize}px; color: #ffd700; text-shadow: 2px 2px 0px rgba(0,0,0,0.8); transform: skewX(-10deg); letter-spacing: 1px; z-index: 3;">${getRandomItem(sportsWords)}</div>` ]; elements.sort(() => Math.random() - 0.5); content = elements.join(''); } // 【進化】タウン情報:カラフルな写真枠と多彩なフォント・レイアウト else if (theme === 'theme-local') { const events = ['週末マルシェ', '秋のパン祭り', '青空古本市', 'クラフトフェア', '港の朝市', '花火大会', '陶器市', 'ジャズフェス']; const subTexts = ['入場無料!', '雨天決行', '@中央公園', '10:00 START', '地元野菜', '限定グッズ', '家族で!']; const patterns = [ 'radial-gradient(#d4caba 20%, transparent 20%) 0 0 / 15px 15px', 'repeating-linear-gradient(45deg, transparent, transparent 10px, rgba(0,0,0,0.03) 10px, rgba(0,0,0,0.03) 20px)', 'none' ]; // 字体(明朝、ゴシック、筆記体風)をランダムに const fonts = ['"Shippori Mincho", serif', 'sans-serif', 'cursive']; const font = getRandomItem(fonts); // 背景色(少し温かみのあるナチュラルカラー) const bgColors = ['#e8e3d7', '#fdfaf5', '#eef2e6', '#f2eae4']; const bgColor = getRandomItem(bgColors); style = `background-color: ${bgColor}; background-image: ${getRandomItem(patterns)}; font-family: ${font}; border: ${getRandomInt(0,4)}px solid #d4caba; box-sizing: border-box; display: flex; flex-direction: column; overflow: hidden; padding: 4px; gap: 4px;`; const eventName = getRandomItem(events); const subText = getRandomItem(subTexts); // ランダムな写真枠(カラフルだが彩度を少し抑えたナチュラルカラー) const photoHues = [10, 45, 170, 200, 340]; // オレンジ、黄、緑、青、ピンク系 const getPhoto = (h) => `<div style="width: 100%; height: ${h}px; background: hsl(${getRandomItem(photoHues)}, 50%, 65%); border: 3px solid #fff; box-shadow: 1px 2px 4px rgba(0,0,0,0.15); box-sizing: border-box; display:flex; align-items:center; justify-content:center; color:rgba(255,255,255,0.9); font-size:8px; font-weight:bold; letter-spacing:1px; transform: rotate(${getRandomInt(-3, 3)}deg); font-family: sans-serif;">PHOTO</div>`; const layoutType = getRandomInt(1, 3); let bodyHtml = ''; // テキスト部分のデザイン const align = getRandomItem(['center', 'left', 'right']); const mainColor = getRandomItem(['#d35400', '#27ae60', '#2980b9', '#8e44ad', '#2c3e50']); const textBlock = `<div style="background: rgba(255,255,255,0.9); padding: 5px; border-radius: 4px; text-align: ${align}; line-height: 1.1; box-shadow: 1px 1px 3px rgba(0,0,0,0.05);"> <div style="font-size: ${getRandomInt(12, 22)}px; font-weight: bold; color: ${mainColor}; word-break: keep-all;">${eventName}</div> <div style="font-size: 8px; margin-top: 3px; font-weight: normal; color: #555; background: #eee; display: inline-block; padding: 1px 4px; border-radius: 2px; font-family: sans-serif;">${subText}</div> </div>`; if (layoutType === 1) { // 大きめの写真+下にテキスト bodyHtml = `${getPhoto(getRandomInt(35, 55))}${textBlock}`; } else if (layoutType === 2) { // テキスト+下に写真 bodyHtml = `${textBlock}${getPhoto(getRandomInt(35, 55))}`; } else { // テキスト+小さな写真2つ横並び bodyHtml = `${textBlock} <div style="display: flex; gap: 3px; width: 100%; margin-top: 2px;"> <div style="flex:1;">${getPhoto(30)}</div> <div style="flex:1;">${getPhoto(30)}</div> </div>`; } content = bodyHtml; } else if (theme === 'theme-art') { const words = ['ART', 'EXPO', 'MUSEUM', 'GALLERY', 'DESIGN', 'VISION']; const hue1 = getRandomInt(0, 360); const hue2 = (hue1 + getRandomInt(40, 180)) % 360; style = `background: linear-gradient(${getRandomInt(0,360)}deg, hsl(${hue1},70%,60%), hsl(${hue2},70%,60%)); color: #111; font-weight: bold; position: relative; overflow: hidden;`; const shapeType = getRandomItem(['circle', 'polygon', 'mixed']); let shapeHtml = ''; if(shapeType === 'circle' || shapeType === 'mixed') { shapeHtml += `<div style="position:absolute; width: ${getRandomInt(40,80)}%; height:${getRandomInt(40,80)}%; border-radius:50%; background:rgba(255,255,255,0.3); top:${getRandomInt(-20,50)}%; left:${getRandomInt(-20,50)}%;"></div>`; } if(shapeType === 'polygon' || shapeType === 'mixed') { const poly = `polygon(${getRandomInt(0,50)}% 0%, 100% ${getRandomInt(0,50)}%, ${getRandomInt(50,100)}% 100%, 0% ${getRandomInt(50,100)}%)`; shapeHtml += `<div style="position:absolute; width: ${getRandomInt(60,100)}%; height:${getRandomInt(60,100)}%; background:rgba(0,0,0,0.2); clip-path: ${poly}; top:${getRandomInt(10,40)}%; left:${getRandomInt(-10,20)}%;"></div>`; } content = `${shapeHtml}<div style="z-index:1; font-size: ${getRandomInt(10, 16)}px; mix-blend-mode: overlay; color: white;">${getRandomItem(words)}</div><div style="z-index:1; font-size: 8px; position:absolute; bottom:5px;">${getRandomInt(1980, 2024)}</div>`; } else if (theme === 'theme-english') { const titles = ['THE DAILY', 'CHRONICLE', 'GLOBAL POST', 'THE TIMES', 'TRIBUNE', 'HERALD']; style = `background: #fdfbf7; color: #222; flex-direction: column; padding: 4px; font-family: 'Times New Roman', serif; justify-content: flex-start; align-items: flex-start; gap: 2px;`; const title = `<div style="font-size: ${getRandomInt(6, 10)}px; font-weight: bold; border-bottom: ${getRandomInt(1,2)}px solid #222; width: 100%; text-align: center; margin-bottom: 2px; padding-bottom: 2px;">${getRandomItem(titles)}</div>`; const dummyText = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat."; let bodyHtml = ''; const layoutType = getRandomInt(1, 4); if (layoutType === 1) { bodyHtml = `<div style="font-size: 3px; line-height: 1.2; text-align: justify; columns: 3; column-gap: 3px; height: 100%; overflow: hidden; color: #333;">${dummyText} ${dummyText}</div>`; } else if (layoutType === 2) { bodyHtml = `<div style="display:flex; gap:2px; width:100%; height: 40%;"><div style="flex:1; background:#a9a9a9;"></div><div style="flex:1; background:#808080;"></div></div> <div style="font-size: 3.5px; line-height: 1.2; text-align: justify; columns: 2; column-gap: 2px; margin-top: 2px; height: 60%; overflow: hidden;">${dummyText}</div>`; } else if (layoutType === 3) { bodyHtml = `<div style="display:flex; gap:3px; height: 100%; width: 100%;"> <div style="flex:2; font-size: 3.5px; text-align:justify; line-height:1.2; overflow: hidden;">${dummyText}</div> <div style="flex:1; display:flex; flex-direction:column; gap:2px;"> <div style="background:#555; height: 60%;"></div> <div style="font-size:4px; font-weight:bold; line-height: 1;">BREAKING NEWS</div> </div> </div>`; } else { bodyHtml = `<div style="font-size: 4px; font-weight: bold; margin-bottom: 2px; line-height: 1.1;">GLOBAL WARMING AFFECTS PLANET</div> <div style="display:flex; gap:2px; height:100%;"> <div style="flex:1; background:#777; height: 100%;"></div> <div style="flex:1; font-size: 3px; line-height: 1.2; text-align: justify; overflow:hidden;">${dummyText}</div> </div>`; } content = title + `<div style="flex-grow: 1; width: 100%; overflow: hidden; display:flex; flex-direction:column;">${bodyHtml}</div>`; } return { style, content }; } // ---------------------------------------------------- function addBag(sizeClass) { const themeClass = themeSelect.value; const wrapper = document.createElement('div'); wrapper.className = `bag-wrapper ${sizeClass}`; const design = generateUniqueDesign(themeClass); wrapper.innerHTML = ` <div class="bag-handle"></div> <div class="bag-body" style="${design.style}">${design.content}</div> `; const x = 300 + (Math.random() * 200 - 100); const y = 200 + (Math.random() * 100 - 50); const rot = (Math.random() - 0.5) * 15; wrapper.dataset.rot = rot; wrapper.style.setProperty('--rot', `${rot}deg`); wrapper.style.left = x + 'px'; wrapper.style.top = y + 'px'; wrapper.style.zIndex = zIndexCounter++; setupEvents(wrapper); gallery.appendChild(wrapper); setTimeout(() => settleBag(wrapper), 50); } function setupEvents(wrapper) { wrapper.addEventListener('dblclick', () => { wrapper.remove(); }); wrapper.addEventListener('contextmenu', (e) => { e.preventDefault(); wrapper.style.zIndex = 1; }); wrapper.addEventListener('wheel', (e) => { e.preventDefault(); let currentRot = parseFloat(wrapper.dataset.rot || 0); currentRot += (e.deltaY > 0) ? 3 : -3; wrapper.dataset.rot = currentRot; wrapper.style.setProperty('--rot', `${currentRot}deg`); }); wrapper.addEventListener('mousedown', (e) => { if (e.button === 2) return; dragBag = wrapper; wrapper.classList.remove('settled'); wrapper.style.zIndex = zIndexCounter++; const rect = wrapper.getBoundingClientRect(); offset.x = e.clientX - rect.left; offset.y = e.clientY - rect.top; e.stopPropagation(); }); } document.addEventListener('mousemove', (e) => { if (!dragBag) return; const galleryRect = gallery.getBoundingClientRect(); const x = e.clientX - galleryRect.left - offset.x; const y = e.clientY - galleryRect.top - offset.y; dragBag.style.left = x + 'px'; dragBag.style.top = y + 'px'; }); document.addEventListener('mouseup', (e) => { if (e.button === 2) return; if (dragBag) { const wrapper = dragBag; dragBag = null; settleBag(wrapper); } }); function settleBag(wrapper) { const rect = wrapper.getBoundingClientRect(); const galleryRect = gallery.getBoundingClientRect(); const bagCenterX = rect.left - galleryRect.left + (rect.width / 2); const bagBottomY = rect.bottom - galleryRect.top; const handleRect = wrapper.querySelector('.bag-handle').getBoundingClientRect(); const handleTopY = handleRect.top - galleryRect.top; let finalTop = -1; let interaction = 'fall'; const hooks = [ document.getElementById('hook-1'), document.getElementById('hook-2'), document.getElementById('hook-3'), document.getElementById('hook-4'), document.getElementById('hook-5') ]; for (const hookEl of hooks) { const hRect = hookEl.getBoundingClientRect(); const hX = hRect.left - galleryRect.left + (hRect.width / 2); const hY = hRect.top - galleryRect.top + (hRect.height / 2); const dist = Math.sqrt(Math.pow(bagCenterX - hX, 2) + Math.pow(handleTopY - hY, 2)); if (dist < 40) { wrapper.style.left = (hX - rect.width / 2) + 'px'; wrapper.style.top = (hY - 5) + 'px'; interaction = 'hook'; break; } } if (interaction === 'fall') { const shelves = [ document.getElementById('shelf-1'), document.getElementById('shelf-2') ]; for (const shelfEl of shelves) { const sRect = shelfEl.getBoundingClientRect(); const sTop = sRect.top - galleryRect.top; const sLeft = sRect.left - galleryRect.left; const sRight = sRect.right - galleryRect.left; if (bagCenterX > (sLeft - 10) && bagCenterX < (sRight + 10)) { if (bagBottomY < (sTop + 30) && bagBottomY > (sTop - 120)) { finalTop = sTop - rect.height; break; } } } if (finalTop === -1) { const floorEl = document.getElementById('floor'); const floorTop = floorEl.getBoundingClientRect().top - galleryRect.top; finalTop = floorTop - rect.height; } wrapper.style.top = finalTop + 'px'; } setTimeout(() => { wrapper.classList.add('settled'); }, 50); } function clearGallery() { if(confirm('ギャラリーを空にしますか?')) { gallery.innerHTML = ''; } }</script></body></html>
■ 今日の学び
今回の「新聞バッグ・ギャラリー」は、
身近にいる“新聞バッグ職人さん”への
感謝とリスペクトを込めて作った作品です。(´꒳`)
いつも素敵な新聞バッグをたくさん作ってプレゼントしてくれる職人さん。
そのバッグが、ただおしゃれなだけじゃなくて、本当にすごいんです。
紙と糊だけでできているのに驚くほど丈夫で、
分厚い本や重たい水筒を入れてもへっちゃら。
サイズや形も豊富で、飾るための小さなバッグから、
ワインボトルがすっぽり入る縦長のものまで、
本当にバリエーションが豊かです。
そして何より惹かれるのが、その「デザインの活かし方」。
英字新聞のスタイリッシュな面、地方新聞の温かい記事、
スポーツ紙のダイナミックな文字、大手新聞の迫力ある写真……。
記事の配置や写真の切り取り方が絶妙で、
どれも息を呑むほどかっこいい“1点もののアート”になっています。
この新聞バッグは、
職人さんの趣味で作られているため販売はされていない「非売品」。
でも、職人さんは作ったバッグをたくさんの方に配っているので、
もしかしたら皆さんの街でもふと見かけることがあるかもしれませんね。
そんな時のために、
この手作りエコバッグの魅力が少しでも伝わっていれば嬉しいです。
最後に、
私が実際にいただいた新聞バッグたちの写真を少しだけご紹介しますね。




(画像:いただいた新聞バッグたち)
■ 次回は、乾燥と粉砕と取り込みをテーマにしたゲームです。
次回は、ちょっとアクション寄りの実験になります。
襲ってくるアメーバーを 乾燥ビームでカラッと乾かし、
そのまま 体当たりで粉砕!
乾燥ビームを当てるとアメーバーは 核を放出します。
その核をキャッチすると 体力が回復する仕組みです。
スピード感のある “乾燥 → 粉砕 → 取り込み” の
ループが楽しいゲームになっています。
次回もお楽しみに!ヽ(*^v^*)ノ
✨今日も最後まで読んでくださって、ありがとうございます。
みーの実験室は、みなさんの応援で元気に動いています。(*^v^*)






🌈はじめての方へ
🌳 実験一覧
🌱 このブログについて
🍃 プライバシーポリシー
🥚未記事ゲーム展示室①
🐣未記事ゲーム展示室②