#author("2025-07-14T12:31:19+02:00","","")
#html{{
<style>
.content {
width: 512px;
max-width: 1152px;
margin-left: 0 !important; /* 余白をなくす */
align-items: flex-start !important;
font-family: "Meiryo", sans-serif;
font-size: 10px;
line-height: 1.2;
display: flex;
flex-direction: column;
gap: 8px;
padding-left: 0; /* 念のため */
}
.main {
width: 100%;
}
.map-area {
width: 100%; /* 親幅に合わせる */
display: flex;
justify-content: center;
margin-bottom: 8px;
}
.map-area img {
width: 100%; /* 親の幅にフィット */
height: auto;
display: block;
margin: 0 auto;
}
.cards-area {
width: 700px; /* ここは全体幅、必要に応じて調整 */
padding: 0;
box-sizing: border-box;
display: grid;
grid-template-columns: repeat(2, 340px); /* 2列でカード幅は340px */
gap: 8px 8px; /* 縦横ともに8pxの隙間 */
justify-content: start; /* 左寄せにして余白減らす */
margin-left: 0; /* 左マージンリセット */
}
.card {
padding-left: 48px; /* テキスト開始を1文字半分ぐらい右に */
position: relative;
display: flex;
align-items: flex-start;
width: 340px;
height: 74px;
margin-bottom: 8px;
background-size: 340px 74px;
background-repeat: no-repeat;
background-position: left center;
/* box-shadowは削除 */
/* box-shadow: 1px 1px 3px rgba(0,0,0,0.15); */
box-sizing: border-box;
/* 他のスタイルはそのまま */
}
.card.mob-red {
background-image: url('./images/mob_red.png');
}
.card.mob-blue {
background-image: url('./images/mob_blue.png');
}
.mob-icon {
width: 48px;
height: 48px;
position: relative;
left: -44px; /* 画像を左に24pxずらす */
margin-top: 8px;
}
.info {
position: absolute;
left: 52px;
top: 7px; /* 11px → 7px にして上に寄せる */
right: 24px;
font-size: 10px;
}
.name {
font-weight: bold;
color: #d94b6a;
font-size: 11px;
margin-bottom: 0; /* 2px → 0 にして名前とテキストを詰める */
}
.line {
white-space: nowrap; /* 折り返さずに1行表示 */
overflow: visible; /* はみ出しも表示 */
text-overflow: clip; /* 省略記号なし */
font-size: 10px;
}
.line .danger {
color: red;
font-weight: bold;
}
.sense-icons {
position: absolute;
bottom: 8px;
right: 24px;
display: flex;
gap: 2px;
}
.sense-icons img {
width: 12px;
height: 14px;
}
.attr-icon {
width: 10px;
height: 10px;
vertical-align: middle;
margin-right: 2px;
}
.map-header {
display: flex;
align-items: center;
padding: 6px 12px;
gap: 8px;
font-weight: bold;
font-size: 18px; /* 14px→18pxに拡大 */
color: #c94c6d;
}
.zone-floor-title.current-floor {
color: #c94c6d;
font-weight: bold;
}
/* 階層ボタンの現在階層もよりわかりやすく */
.floor-buttons img.current {
filter: none; /* マンドラゴラちゃんにはぼかしを適用しない */
}
.floor-buttons {
display: flex;
gap: 6px;
}
.floor-buttons img {
width: 24px;
height: 24px;
cursor: pointer;
transition: filter 0.2s ease;
}
.floor-buttons img:hover {
filter: drop-shadow(0 0 3px #c94c6d);
}
</style>
<div class="content">
<div class="title-bar" id="stageTitle">アポリオン NW #1</div>
<div class="main">
<div class="map-header">
<div class="zone-floor-title" id="zoneFloorTitle">アポリオン NW #1</div>
<div class="floor-buttons" id="floorButtons"></div>
</div>
<div class="map-area">
<img id="mapImage" src="" alt="">
</div>
<div class="cards-area" id="cardsArea"></div>
</div>
</div>
<script>
(() => {
let zoneKey = 'apollyon_nw'; // letにして変更可能に
let floor = 1; // 数値型に
const basePath = '/FFXI/Limbusinfo/';
// ここにゾーンごとの最大階層数を用意(例)
const zoneMaxFloors = {
apollyon_nw: 5,
apollyon_sw: 4,
apollyon_ne: 5,
apollyon_se: 4,
temenos_北塔: 7,
temenos_西塔: 7,
temenos_東塔: 7,
temenos_中央塔: 4,
};
const attrIcons = {
'火': 'Fire-Icon.png',
'氷': 'Ice-Icon.png',
'風': 'Wind-Icon.png',
'土': 'Earth-Icon.png',
'雷': 'Lightning-Icon.png',
'水': 'Water-Icon.png',
'光': 'Light-Icon.png',
'闇': 'Dark-Icon.png'
};
const senseIconMap = {
'インビジ': 'Invisible.png',
'スニーク': 'Sneak.png',
'リンク': 'Link.png',
'生命': 'Low_HP.png' // 生命アイコンを追加(ファイル名は適宜)
};
function renderAttrIcons(text) {
if (!text) return '';
return text.replace(/(火|氷|風|土|雷|水|光|闇)/g, m => {
return `<img class="attr-icon" src="${basePath}images/icon/${attrIcons[m]}" title="${m}">`;
});
}
function createMonsterCard(mob) {
const danger = mob.sense.includes('見破り');
const bgClass = danger ? 'mob-red' : 'mob-blue';
const icons = [];
Object.entries(senseIconMap).forEach(([key, icon]) => {
if (mob.sense.includes(key) || mob.details.includes(key)) {
icons.push(icon);
}
});
// 生命と見破りを赤文字に加工する関数
function colorizeKeywords(text) {
if (!text) return '';
return text
.replace(/生命/g, '<span style="color:red;">生命</span>')
.replace(/見破り/g, '<span style="color:red;">見破り</span>');
}
return `
<div class="card ${bgClass}" style="background-image:url('${basePath}images/${danger ? 'mob_red.png' : 'mob_blue.png'}')">
<img class="mob-icon" src="${basePath}images/mobs/${mob.image}" alt="${mob.name}">
<div class="info">
<div class="name">${mob.name}</div>
<div class="line">【感知】<span class="${danger ? 'danger' : ''}">${colorizeKeywords(renderAttrIcons(mob.sense))}</span></div>
<div class="line">【睡眠】${renderAttrIcons(mob.sleep)} 【弱体】${renderAttrIcons(mob.debuff)}</div>
<div class="line">【弱点】${renderAttrIcons(mob.weakness)} 【耐性】${renderAttrIcons(mob.resistance)}</div>
<div class="line">【詳細】${mob.details}</div>
</div>
<div class="sense-icons">${icons.map(i => `<img src="${basePath}images/icon/${i}">`).join('')}</div>
</div>
`;
}
// 階層ボタンを生成する関数
function buildFloorButtons() {
const floorButtons = document.getElementById('floorButtons');
floorButtons.innerHTML = ''; // クリア
const maxFloor = zoneMaxFloors[zoneKey] || 1;
const zoneFloorTitle = document.getElementById('zoneFloorTitle');
for(let i = 1; i <= maxFloor; i++) {
const img = document.createElement('img');
const numStr = String(i).padStart(2, '0');
if (i === floor) {
img.src = `${basePath}images/icon/Mandragora.png`;
} else {
img.src = `${basePath}images/icon/${numStr}.png`;
}
img.title = `階層 ${i}`;
img.className = (i === floor) ? 'current' : '';
img.onclick = () => {
if (floor !== i) {
floor = i;
loadStage(zoneKey, floor);
buildFloorButtons(); // 強調更新
}
};
floorButtons.appendChild(img);
}
// ゾーンタイトルにも現在階層の強調クラス付与
if(zoneFloorTitle) {
if(!zoneFloorTitle.classList.contains('current-floor')) {
zoneFloorTitle.classList.add('current-floor');
}
}
}
async function loadStage(loadZoneKey = zoneKey, loadFloor = floor) {
zoneKey = loadZoneKey;
floor = loadFloor;
try {
const res = await fetch(`${basePath}data/${zoneKey}_${floor}.json`);
if (!res.ok) throw new Error('データ取得失敗');
const stage = await res.json();
document.getElementById('stageTitle').textContent = stage.title;
document.getElementById('zoneFloorTitle').textContent = stage.title;
document.getElementById('zoneFloorTitle').className = 'current-floor';
document.getElementById('mapImage').src = basePath + 'map/' + stage.map;
const cardsArea = document.getElementById('cardsArea');
cardsArea.innerHTML = stage.monsters.map(createMonsterCard).join('');
buildFloorButtons();
// 音声再生省略(必要なら追加可能)
} catch (e) {
console.error(e);
document.getElementById('stageTitle').textContent = 'データがありません';
document.getElementById('zoneFloorTitle').textContent = 'データがありません';
document.getElementById('mapImage').src = '';
document.getElementById('cardsArea').innerHTML = '';
}
}
loadStage();
})();
</script>
}}
#author("2025-07-15T10:18:46+02:00","","")
#limbusview(zone=apollyon_nw floor=1)