Compare commits
8 Commits
main
...
akus_schab
| Author | SHA1 | Date | |
|---|---|---|---|
| e78ecbd0d9 | |||
| e5c12b6915 | |||
| d42bde81f6 | |||
| bcbf14eb81 | |||
| f6078542ef | |||
| 695c0fb99e | |||
| 0a47b126cf | |||
| 9cd3278b80 |
9
.vscode/settings.json
vendored
Normal file
9
.vscode/settings.json
vendored
Normal file
@ -0,0 +1,9 @@
|
||||
{
|
||||
"python-envs.pythonProjects": [
|
||||
{
|
||||
"path": ".",
|
||||
"envManager": "ms-python.python:system",
|
||||
"packageManager": "ms-python.python:pip"
|
||||
}
|
||||
]
|
||||
}
|
||||
84
admin.php
84
admin.php
@ -21,6 +21,8 @@ if (empty($_SESSION['admin_csrf'])) {
|
||||
$_SESSION['admin_csrf'] = bin2hex(random_bytes(16));
|
||||
}
|
||||
$csrf = $_SESSION['admin_csrf'];
|
||||
$returnPath = safe_return_path($_GET['return'] ?? null);
|
||||
$adminAction = 'admin.php?return=' . rawurlencode($returnPath);
|
||||
|
||||
function admin_h(?string $value): string {
|
||||
return htmlspecialchars((string)$value, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
|
||||
@ -68,6 +70,63 @@ function git_last_commit(): string {
|
||||
return $out !== '' ? $out : '(unknown)';
|
||||
}
|
||||
|
||||
function git_current_branch(): string {
|
||||
return git_output(['rev-parse', '--abbrev-ref', 'HEAD']);
|
||||
}
|
||||
|
||||
function git_upstream_for_branch(string $branch): ?string {
|
||||
$result = git_run(['rev-parse', '--abbrev-ref', $branch . '@{upstream}']);
|
||||
return $result['code'] === 0 && $result['output'] !== '' ? $result['output'] : null;
|
||||
}
|
||||
|
||||
function git_is_ancestor(string $ancestor, string $descendant): bool {
|
||||
return git_run(['merge-base', '--is-ancestor', $ancestor, $descendant])['code'] === 0;
|
||||
}
|
||||
|
||||
function git_pull_all_tracked_branches(): array {
|
||||
$lines = [];
|
||||
$exitCode = 0;
|
||||
|
||||
$fetch = git_run(['fetch', '--all', '--prune']);
|
||||
$lines[] = '$ git fetch --all --prune';
|
||||
$lines[] = $fetch['output'] ?: '(no output)';
|
||||
if ($fetch['code'] !== 0) return ['code' => $fetch['code'], 'output' => implode("\n", $lines)];
|
||||
|
||||
$current = git_current_branch();
|
||||
foreach (git_local_branches() as $branch) {
|
||||
$upstream = git_upstream_for_branch($branch);
|
||||
if ($upstream === null) {
|
||||
$lines[] = '';
|
||||
$lines[] = $branch . ': no upstream configured, skipped';
|
||||
continue;
|
||||
}
|
||||
|
||||
$lines[] = '';
|
||||
$lines[] = $branch . ' <- ' . $upstream;
|
||||
if ($branch === $current) {
|
||||
$pull = git_run(['merge', '--ff-only', $upstream]);
|
||||
$lines[] = '$ git merge --ff-only ' . $upstream;
|
||||
$lines[] = $pull['output'] ?: '(no output)';
|
||||
if ($pull['code'] !== 0) $exitCode = $pull['code'];
|
||||
continue;
|
||||
}
|
||||
|
||||
if (git_is_ancestor($branch, $upstream)) {
|
||||
$update = git_run(['branch', '-f', $branch, $upstream]);
|
||||
$lines[] = '$ git branch -f ' . $branch . ' ' . $upstream;
|
||||
$lines[] = $update['output'] ?: '(fast-forwarded)';
|
||||
if ($update['code'] !== 0) $exitCode = $update['code'];
|
||||
} elseif (git_is_ancestor($upstream, $branch)) {
|
||||
$lines[] = 'already up to date or ahead';
|
||||
} else {
|
||||
$lines[] = 'diverged, skipped';
|
||||
if ($exitCode === 0) $exitCode = 1;
|
||||
}
|
||||
}
|
||||
|
||||
return ['code' => $exitCode, 'output' => implode("\n", $lines)];
|
||||
}
|
||||
|
||||
function resolve_selected_branch_ref(string $ref, array $localBranches, array $remoteBranches, bool $preferLocalForRemote): ?string {
|
||||
if (str_starts_with($ref, 'local:')) {
|
||||
$branch = substr($ref, 6);
|
||||
@ -94,11 +153,11 @@ if ($allowed && $_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
} else {
|
||||
$action = $_POST['action'] ?? '';
|
||||
if ($action === 'fetch') {
|
||||
$result = git_run(['fetch', '--prune']);
|
||||
$message = 'git fetch --prune';
|
||||
$result = git_run(['fetch', '--all', '--prune']);
|
||||
$message = 'git fetch --all --prune';
|
||||
} elseif ($action === 'pull') {
|
||||
$result = git_run(['pull', '--ff-only']);
|
||||
$message = 'git pull --ff-only';
|
||||
$result = git_pull_all_tracked_branches();
|
||||
$message = 'pull all tracked branches';
|
||||
} elseif ($action === 'push') {
|
||||
$result = git_run(['push']);
|
||||
$message = 'git push';
|
||||
@ -118,6 +177,7 @@ if ($allowed && $_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
}
|
||||
} elseif ($action === 'merge') {
|
||||
$ref = trim((string)($_POST['merge_branch'] ?? ''));
|
||||
git_run(['fetch', '--all', '--prune']);
|
||||
$localBranches = git_local_branches();
|
||||
$remoteBranches = git_remote_branches();
|
||||
$branch = resolve_selected_branch_ref($ref, $localBranches, $remoteBranches, false);
|
||||
@ -164,7 +224,7 @@ $branches = [];
|
||||
$remoteBranches = [];
|
||||
$lastCommit = '';
|
||||
if ($allowed) {
|
||||
$currentBranch = git_output(['rev-parse', '--abbrev-ref', 'HEAD']);
|
||||
$currentBranch = git_current_branch();
|
||||
$status = git_output(['status', '-sb']);
|
||||
$branches = git_local_branches();
|
||||
$remoteBranches = git_remote_branches();
|
||||
@ -203,9 +263,9 @@ if ($allowed) {
|
||||
<body>
|
||||
<div id="app">
|
||||
<header id="topbar">
|
||||
<a class="logo" href="index.php" style="text-decoration:none">REPORT VIEWER <span>Admin</span></a>
|
||||
<a class="logo" href="<?= admin_h($returnPath) ?>" style="text-decoration:none">REPORT VIEWER <span>Admin</span></a>
|
||||
<div class="topbar-actions">
|
||||
<a class="topbar-link" href="index.php">Back</a>
|
||||
<a class="topbar-link" href="<?= admin_h($returnPath) ?>">Back</a>
|
||||
<div class="topbar-user">
|
||||
<?= $user ? admin_h($user['name'] ?: 'FFLogs User') . ' #' . admin_h($user['id']) : 'FFLogs user unavailable' ?>
|
||||
</div>
|
||||
@ -242,14 +302,14 @@ if ($allowed) {
|
||||
<div class="admin-grid section-gap">
|
||||
<div class="card">
|
||||
<div class="card-title">Aktionen</div>
|
||||
<form method="post" class="admin-actions">
|
||||
<form method="post" action="<?= admin_h($adminAction) ?>" class="admin-actions">
|
||||
<input type="hidden" name="csrf" value="<?= admin_h($csrf) ?>">
|
||||
<button class="btn" name="action" value="fetch" type="submit">Fetch</button>
|
||||
<button class="btn btn-gold" name="action" value="pull" type="submit">Pull</button>
|
||||
<button class="btn btn-gold" name="action" value="pull" type="submit">Pull All</button>
|
||||
<button class="btn" name="action" value="push" type="submit">Push</button>
|
||||
</form>
|
||||
|
||||
<form method="post" class="admin-actions section-gap">
|
||||
<form method="post" action="<?= admin_h($adminAction) ?>" class="admin-actions section-gap">
|
||||
<input type="hidden" name="csrf" value="<?= admin_h($csrf) ?>">
|
||||
<input type="hidden" name="action" value="switch">
|
||||
<div class="fg">
|
||||
@ -274,7 +334,7 @@ if ($allowed) {
|
||||
<button class="btn" type="submit">Switch</button>
|
||||
</form>
|
||||
|
||||
<form method="post" class="section-gap">
|
||||
<form method="post" action="<?= admin_h($adminAction) ?>" class="section-gap">
|
||||
<input type="hidden" name="csrf" value="<?= admin_h($csrf) ?>">
|
||||
<input type="hidden" name="action" value="commit">
|
||||
<div class="fg">
|
||||
@ -284,7 +344,7 @@ if ($allowed) {
|
||||
<button class="btn btn-gold section-gap" type="submit">Commit all changes</button>
|
||||
</form>
|
||||
|
||||
<form method="post" class="admin-actions section-gap">
|
||||
<form method="post" action="<?= admin_h($adminAction) ?>" class="admin-actions section-gap">
|
||||
<input type="hidden" name="csrf" value="<?= admin_h($csrf) ?>">
|
||||
<input type="hidden" name="action" value="merge">
|
||||
<div class="fg">
|
||||
|
||||
@ -18,6 +18,7 @@ $startTime = (float)($_POST['start_time'] ?? 0);
|
||||
$endTime = (float)($_POST['end_time'] ?? 0);
|
||||
$language = strtolower(trim($_POST['language'] ?? 'en'));
|
||||
$language = in_array($language, ['en', 'de', 'fr', 'jp'], true) ? $language : 'en';
|
||||
$noCache = filter_var($_POST['no_cache'] ?? false, FILTER_VALIDATE_BOOLEAN);
|
||||
$translate = 'true';
|
||||
|
||||
if (!$reportCode || !$fightId || !$endTime) {
|
||||
@ -27,7 +28,7 @@ if (!$reportCode || !$fightId || !$endTime) {
|
||||
}
|
||||
|
||||
$cacheParts = [$fightId, (int)$startTime, (int)$endTime];
|
||||
$cached = read_cached_log('analysis', $reportCode, $language, $cacheParts);
|
||||
$cached = $noCache ? null : read_cached_log('analysis', $reportCode, $language, $cacheParts);
|
||||
if ($cached !== null) {
|
||||
echo $cached;
|
||||
exit;
|
||||
@ -196,8 +197,11 @@ function resolveMitigations(string $buffStr, array $mitigIdMap, array $buffSourc
|
||||
'buffType' => $mitigIdMap[$id]['buffType'],
|
||||
'extraAbilityGameID' => $mitigIdMap[$id]['extraAbilityGameID'] ?? null,
|
||||
];
|
||||
$source = findBuffSourcePlayer($buffSourceTimeline, $id, $ts, $players);
|
||||
if ($source) $entry['sourcePlayerType'] = $source['type'];
|
||||
$source = findBuffSourceEntry($buffSourceTimeline, $id, $ts, $players);
|
||||
if ($source) {
|
||||
$entry['sourcePlayerType'] = $source['player']['type'];
|
||||
$entry['appliedAt'] = $source['apply'];
|
||||
}
|
||||
$result[] = $entry;
|
||||
}
|
||||
}
|
||||
@ -205,7 +209,7 @@ function resolveMitigations(string $buffStr, array $mitigIdMap, array $buffSourc
|
||||
}
|
||||
|
||||
// Findet den Spieler der einen Buff zum Zeitpunkt $ts gecastet hat (anhand der applybuff-Timeline).
|
||||
function findBuffSourcePlayer(array $sourceTimeline, int $statusId, float $ts, array $players): ?array {
|
||||
function findBuffSourceEntry(array $sourceTimeline, int $statusId, float $ts, array $players): ?array {
|
||||
$best = null;
|
||||
foreach ($sourceTimeline[$statusId] ?? [] as $entry) {
|
||||
if ($entry['apply'] > $ts + 200) continue; // noch nicht aktiv
|
||||
@ -213,7 +217,11 @@ function findBuffSourcePlayer(array $sourceTimeline, int $statusId, float $ts, a
|
||||
if ($best === null || $entry['apply'] > $best['apply']) $best = $entry;
|
||||
}
|
||||
if ($best === null || empty($best['sourceId'])) return null;
|
||||
return $players[$best['sourceId']] ?? null;
|
||||
if (!isset($players[$best['sourceId']])) return null;
|
||||
return [
|
||||
'player' => $players[$best['sourceId']],
|
||||
'apply' => $best['apply'],
|
||||
];
|
||||
}
|
||||
|
||||
// Fallback for shields consumed by a hit: the damage event's buffs field no
|
||||
@ -233,6 +241,7 @@ function shieldsActiveAt(array $shieldTimeline, int $targetId, float $ts, array
|
||||
'dr' => $m['dr'],
|
||||
'buffType' => $m['buffType'],
|
||||
'extraAbilityGameID' => $m['extraAbilityGameID'] ?? null,
|
||||
'appliedAt' => $iv['apply'],
|
||||
];
|
||||
}
|
||||
break;
|
||||
@ -653,10 +662,37 @@ foreach ($clusters as $group) {
|
||||
usort($bossEvents, fn($a, $b) => $a['timestamp'] <=> $b['timestamp']);
|
||||
usort($aoeEvents, fn($a, $b) => $a['timestamp'] <=> $b['timestamp']);
|
||||
|
||||
$mitigationCasts = [];
|
||||
$castSeen = [];
|
||||
foreach ($buffSourceTimeline as $statusId => $entries) {
|
||||
if (!isset($mitigIdMap[$statusId])) continue;
|
||||
$meta = $mitigIdMap[$statusId];
|
||||
foreach ($entries as $entry) {
|
||||
$sourceId = (int)($entry['sourceId'] ?? 0);
|
||||
$apply = (float)($entry['apply'] ?? 0);
|
||||
if ($sourceId <= 0 || $apply <= 0 || !isset($players[$sourceId])) continue;
|
||||
$dedupeKey = $statusId . ':' . $sourceId . ':' . (int)round($apply / 100);
|
||||
if (isset($castSeen[$dedupeKey])) continue;
|
||||
$castSeen[$dedupeKey] = true;
|
||||
$mitigationCasts[] = [
|
||||
'key' => $meta['key'] ?? $meta['name'],
|
||||
'name' => $meta['name'],
|
||||
'buffType' => $meta['buffType'],
|
||||
'sourcePlayerType' => $players[$sourceId]['type'] ?? '',
|
||||
'sourceName' => $players[$sourceId]['name'] ?? '',
|
||||
'timestamp' => $apply,
|
||||
'remove' => $entry['remove'] ?? null,
|
||||
'extraAbilityGameID' => $meta['extraAbilityGameID'] ?? null,
|
||||
];
|
||||
}
|
||||
}
|
||||
usort($mitigationCasts, fn($a, $b) => $a['timestamp'] <=> $b['timestamp']);
|
||||
|
||||
$response = json_encode([
|
||||
'players' => array_values($players),
|
||||
'boss_events' => $bossEvents,
|
||||
'aoe_events' => $aoeEvents,
|
||||
'mitigation_casts' => $mitigationCasts,
|
||||
'fight_start' => (int)$startTime,
|
||||
'mitigation_names' => $mitigationNames,
|
||||
]);
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
declare(strict_types=1);
|
||||
|
||||
const CACHED_LOG_DIR = __DIR__ . '/../cached_logs';
|
||||
const CACHED_LOG_VERSION = 'v12';
|
||||
const CACHED_LOG_VERSION = 'v14';
|
||||
|
||||
function cache_language(string $language): string {
|
||||
$language = strtolower(trim($language));
|
||||
|
||||
@ -15,6 +15,7 @@ if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||
$reportCode = preg_replace('/[^a-zA-Z0-9]/', '', $_POST['report_code'] ?? '');
|
||||
$language = strtolower(trim($_POST['language'] ?? 'en'));
|
||||
$language = in_array($language, ['en', 'de', 'fr', 'jp'], true) ? $language : 'en';
|
||||
$noCache = filter_var($_POST['no_cache'] ?? false, FILTER_VALIDATE_BOOLEAN);
|
||||
|
||||
if (strlen($reportCode) < 1) {
|
||||
http_response_code(400);
|
||||
@ -22,7 +23,7 @@ if (strlen($reportCode) < 1) {
|
||||
exit;
|
||||
}
|
||||
|
||||
$cached = read_cached_log('fight', $reportCode, $language);
|
||||
$cached = $noCache ? null : read_cached_log('fight', $reportCode, $language);
|
||||
if ($cached !== null) {
|
||||
echo $cached;
|
||||
exit;
|
||||
|
||||
@ -98,6 +98,43 @@
|
||||
color: var(--t2);
|
||||
}
|
||||
|
||||
.report-load-row {
|
||||
flex-wrap: nowrap;
|
||||
}
|
||||
|
||||
.report-code-field {
|
||||
flex: 1 1 360px;
|
||||
min-width: 240px;
|
||||
}
|
||||
|
||||
.report-language-field {
|
||||
flex: 0 0 120px;
|
||||
min-width: 100px;
|
||||
}
|
||||
|
||||
.report-fight-field {
|
||||
flex: 1 1 360px;
|
||||
min-width: 260px;
|
||||
}
|
||||
|
||||
.report-fight-label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.report-fight-label a {
|
||||
color: var(--gold);
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
@media (max-width: 1100px) {
|
||||
.report-load-row {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
}
|
||||
|
||||
/* ── Topbar user badge ───────────────────────────────────────────────────────── */
|
||||
.topbar-user {
|
||||
font-size: 13px;
|
||||
|
||||
757
css/planner.css
757
css/planner.css
@ -143,6 +143,14 @@
|
||||
color: var(--t3);
|
||||
}
|
||||
|
||||
.planner-header-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
gap: 10px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
/* ── Job Slots ────────────────────────────────────────────────────────────────── */
|
||||
.job-slots-grid {
|
||||
display: flex;
|
||||
@ -222,6 +230,435 @@
|
||||
padding: 3px 6px;
|
||||
}
|
||||
|
||||
/* ── Compare Modal ──────────────────────────────────────────────────────────── */
|
||||
.planner-compare-modal-box {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: min(1680px, 98vw) !important;
|
||||
height: 96vh !important;
|
||||
max-width: none !important;
|
||||
max-height: 96vh !important;
|
||||
overflow: hidden;
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
.compare-modal-header {
|
||||
flex: 0 0 auto;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 18px;
|
||||
padding: 22px 26px 16px;
|
||||
border-bottom: 1px solid var(--border);
|
||||
background: var(--bgcard);
|
||||
}
|
||||
|
||||
.compare-modal-header .modal-title {
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.compare-modal-subtitle {
|
||||
color: var(--t3);
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.compare-modal-header-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
gap: 8px;
|
||||
flex-wrap: wrap;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.compare-modal-body {
|
||||
flex: 1 1 auto;
|
||||
min-height: 0;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
padding: 18px 26px 14px;
|
||||
}
|
||||
|
||||
.compare-setup-grid {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(360px, 0.8fr) minmax(0, 1.2fr);
|
||||
gap: 14px;
|
||||
align-items: end;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.compare-setup-grid .modal-section {
|
||||
margin: 0;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.compare-setup-grid .name-import-input-row {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.compare-setup-grid .name-import-input-row .btn {
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
.compare-setup-grid input,
|
||||
.compare-setup-grid select {
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.compare-modal-footer {
|
||||
flex: 0 0 auto;
|
||||
position: sticky;
|
||||
bottom: 0;
|
||||
z-index: 8;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
gap: 8px;
|
||||
flex-wrap: wrap;
|
||||
margin-top: 0 !important;
|
||||
padding: 14px 26px 18px;
|
||||
border-top: 1px solid var(--border);
|
||||
background: var(--bgcard);
|
||||
}
|
||||
|
||||
.planner-compare-result { margin-top: 16px; }
|
||||
|
||||
.compare-show-ok-toggle {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
color: var(--t2);
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.compare-show-ok-toggle input {
|
||||
width: auto;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.compare-live-header {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 4;
|
||||
display: grid;
|
||||
grid-template-columns: minmax(180px, 1fr) auto;
|
||||
gap: 14px;
|
||||
align-items: center;
|
||||
padding: 12px;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--r);
|
||||
background: rgba(14,18,27,.96);
|
||||
}
|
||||
|
||||
.compare-live-header strong {
|
||||
display: block;
|
||||
color: var(--t1);
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.compare-live-header span {
|
||||
color: var(--t3);
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.compare-summary {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: flex-end;
|
||||
gap: 7px;
|
||||
}
|
||||
|
||||
.compare-stat {
|
||||
display: inline-flex;
|
||||
min-width: 58px;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 1px;
|
||||
padding: 5px 8px;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--r);
|
||||
background: var(--bg2);
|
||||
color: var(--t2);
|
||||
}
|
||||
|
||||
.compare-stat strong {
|
||||
color: inherit;
|
||||
font-size: 15px;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.compare-stat small {
|
||||
color: inherit;
|
||||
font-size: 10px;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.compare-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 7px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.compare-row {
|
||||
display: grid;
|
||||
grid-template-columns: 92px minmax(0, 1fr);
|
||||
min-height: 68px;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--r);
|
||||
background: rgba(255,255,255,.018);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.compare-row-status {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
gap: 4px;
|
||||
padding: 10px;
|
||||
border-right: 1px solid var(--border);
|
||||
background: rgba(255,255,255,.03);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.compare-row-status strong {
|
||||
color: var(--t1);
|
||||
font-size: 12px;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.compare-row-status span {
|
||||
color: var(--gold);
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.compare-row-body {
|
||||
min-width: 0;
|
||||
padding: 10px 12px;
|
||||
}
|
||||
|
||||
.compare-row-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
margin-bottom: 8px;
|
||||
color: var(--t1);
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.compare-row-title small {
|
||||
color: var(--t3);
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
.compare-row-warning {
|
||||
color: var(--red);
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.compare-columns {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.compare-column-label {
|
||||
margin-bottom: 4px;
|
||||
color: var(--t3);
|
||||
font-size: 10px;
|
||||
font-weight: 700;
|
||||
letter-spacing: .06em;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.compare-chip-list {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
.compare-status-line {
|
||||
display: grid;
|
||||
grid-template-columns: 68px minmax(0, 1fr);
|
||||
align-items: start;
|
||||
gap: 7px;
|
||||
margin-bottom: 5px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.compare-status-line:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.compare-status-line--no-label {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.compare-status-label {
|
||||
align-self: center;
|
||||
padding: 3px 5px;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--r);
|
||||
background: rgba(255,255,255,.025);
|
||||
color: var(--t3);
|
||||
font-size: 10px;
|
||||
font-weight: 700;
|
||||
text-align: center;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.compare-status-label--missing { color: var(--red); border-color: rgba(224,92,92,.45); }
|
||||
.compare-status-label--late { color: #ffb07a; border-color: rgba(255,145,75,.50); }
|
||||
.compare-status-label--early { color: #ffd18a; border-color: rgba(255,190,90,.45); }
|
||||
.compare-status-label--extra { color: var(--blue); border-color: rgba(74,158,255,.45); }
|
||||
.compare-status-label--unknown { color: var(--t3); border-color: rgba(180,190,205,.35); }
|
||||
.compare-status-label--ok { color: #8fd99e; border-color: rgba(88,180,116,.45); }
|
||||
|
||||
.compare-mini-chip {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
max-width: 100%;
|
||||
min-height: 25px;
|
||||
padding: 3px 7px 3px 4px;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--r);
|
||||
background: var(--bg2);
|
||||
color: var(--t2);
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.compare-mini-chip img {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
object-fit: contain;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.compare-mini-chip strong {
|
||||
color: var(--t1);
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.compare-mini-chip span {
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.compare-mini-chip small {
|
||||
color: inherit;
|
||||
font-size: 11px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.compare-stat--ok,
|
||||
.compare-mini-chip--ok,
|
||||
.compare-row--ok .compare-row-status {
|
||||
border-color: rgba(88,180,116,.45);
|
||||
color: #8fd99e;
|
||||
}
|
||||
|
||||
.compare-stat--early,
|
||||
.compare-mini-chip--early,
|
||||
.compare-row--early .compare-row-status {
|
||||
border-color: rgba(255,190,90,.50);
|
||||
color: #ffd18a;
|
||||
}
|
||||
|
||||
.compare-stat--late,
|
||||
.compare-mini-chip--late,
|
||||
.compare-row--late .compare-row-status {
|
||||
border-color: rgba(255,145,75,.55);
|
||||
color: #ffb07a;
|
||||
}
|
||||
|
||||
.compare-stat--missing,
|
||||
.compare-mini-chip--missing,
|
||||
.compare-stat--unmatched,
|
||||
.compare-row--missing .compare-row-status {
|
||||
border-color: rgba(224,92,92,.55);
|
||||
color: var(--red);
|
||||
}
|
||||
|
||||
.compare-stat--extra,
|
||||
.compare-mini-chip--extra,
|
||||
.compare-row--extra .compare-row-status {
|
||||
border-color: rgba(74,158,255,.45);
|
||||
color: var(--blue);
|
||||
}
|
||||
|
||||
.compare-stat--unknown,
|
||||
.compare-mini-chip--unknown,
|
||||
.compare-row--unknown .compare-row-status {
|
||||
border-color: rgba(180,190,205,.35);
|
||||
color: var(--t3);
|
||||
}
|
||||
|
||||
.compare-empty,
|
||||
.compare-loading {
|
||||
color: var(--t3);
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.compare-empty--panel {
|
||||
padding: 18px;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--r);
|
||||
background: var(--bg2);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.compare-error {
|
||||
padding: 10px 12px;
|
||||
border: 1px solid rgba(224,92,92,.45);
|
||||
border-radius: var(--r);
|
||||
color: var(--red);
|
||||
background: rgba(224,92,92,.08);
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
@media (max-width: 760px) {
|
||||
.planner-compare-modal-box {
|
||||
width: 98vw;
|
||||
height: 94vh;
|
||||
}
|
||||
|
||||
.compare-modal-header,
|
||||
.compare-modal-body,
|
||||
.compare-modal-footer {
|
||||
padding-left: 14px;
|
||||
padding-right: 14px;
|
||||
}
|
||||
|
||||
.compare-modal-header {
|
||||
align-items: flex-start;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.compare-modal-header-actions {
|
||||
justify-content: flex-start;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.compare-setup-grid,
|
||||
.compare-live-header,
|
||||
.compare-columns,
|
||||
.compare-row {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.compare-row-status {
|
||||
border-right: none;
|
||||
border-bottom: 1px solid var(--border);
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
|
||||
/* ── Mechanic Cards ──────────────────────────────────────────────────────────── */
|
||||
.mechanic-card {
|
||||
display: grid;
|
||||
@ -711,11 +1148,26 @@
|
||||
|
||||
/* ── Planner Timeline ───────────────────────────────────────────────────────── */
|
||||
.timeline-hint {
|
||||
font-size: 12px;
|
||||
color: var(--t3);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
font-size: 12px;
|
||||
color: var(--t3);
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.timeline-zoom-hint {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
padding: 2px 7px;
|
||||
border: 1px solid rgba(200,168,75,.25);
|
||||
border-radius: var(--r);
|
||||
background: rgba(200,168,75,.08);
|
||||
color: var(--gold);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.timeline-controls {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@ -850,18 +1302,42 @@
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
width: 1px;
|
||||
width: 10px;
|
||||
transform: translateX(-50%);
|
||||
background: rgba(200,168,75,.38);
|
||||
pointer-events: none;
|
||||
background: transparent;
|
||||
pointer-events: auto;
|
||||
cursor: help;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.timeline-hit-line::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 50%;
|
||||
width: 1px;
|
||||
transform: translateX(-50%);
|
||||
background: rgba(200,168,75,.38);
|
||||
}
|
||||
|
||||
.timeline-hit-line--tankbuster {
|
||||
width: 12px;
|
||||
}
|
||||
|
||||
.timeline-hit-line--tankbuster::before {
|
||||
width: 2px;
|
||||
background: rgba(177,112,255,.62);
|
||||
}
|
||||
|
||||
.timeline-hit-line:hover::before {
|
||||
background: var(--gold);
|
||||
}
|
||||
|
||||
.timeline-hit-line--tankbuster:hover::before {
|
||||
background: #b170ff;
|
||||
}
|
||||
|
||||
.timeline-player-row .timeline-track {
|
||||
background-color: rgba(255,255,255,0.015);
|
||||
}
|
||||
@ -993,6 +1469,39 @@
|
||||
opacity: 0.78;
|
||||
}
|
||||
|
||||
.timeline-inactive-gap {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
height: 20px;
|
||||
z-index: 2;
|
||||
pointer-events: auto;
|
||||
cursor: help;
|
||||
}
|
||||
|
||||
.timeline-inactive-gap-fit,
|
||||
.timeline-inactive-gap-late {
|
||||
position: absolute;
|
||||
top: 9px;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
.timeline-inactive-gap-fit {
|
||||
left: 0;
|
||||
border-top: 2px dotted rgba(180,190,205,.42);
|
||||
}
|
||||
|
||||
.timeline-inactive-gap-late {
|
||||
border-top: 2px dotted rgba(224,92,92,.70);
|
||||
}
|
||||
|
||||
.timeline-inactive-gap:hover .timeline-inactive-gap-fit {
|
||||
border-top-color: var(--gold);
|
||||
}
|
||||
|
||||
.timeline-inactive-gap:hover .timeline-inactive-gap-late {
|
||||
border-top-color: var(--red);
|
||||
}
|
||||
|
||||
.timeline-mitigation img {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
@ -1233,11 +1742,229 @@
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.timeline-menu-time {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 7px 9px 9px;
|
||||
margin-bottom: 4px;
|
||||
border-bottom: 1px solid var(--border);
|
||||
color: var(--t3);
|
||||
font-size: 11px;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.timeline-menu-time input {
|
||||
width: 76px !important;
|
||||
padding: 5px 7px !important;
|
||||
font-size: 12px !important;
|
||||
}
|
||||
|
||||
/* ── View Toggle ─────────────────────────────────────────────────────────── */
|
||||
|
||||
.view-toggle-btns {
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
.planner-card-actions {
|
||||
margin-left: auto;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.planner-class-filter-wrap {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
color: var(--t3);
|
||||
font-size: 11px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.planner-class-filter {
|
||||
width: auto;
|
||||
min-width: 88px;
|
||||
padding: 4px 9px;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--r);
|
||||
background: var(--bg2);
|
||||
color: var(--t1);
|
||||
font-size: 12px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.planner-class-filter:hover {
|
||||
border-color: var(--t3);
|
||||
}
|
||||
|
||||
.cactbot-export-modal-box {
|
||||
max-width: 720px;
|
||||
max-height: min(86vh, 780px);
|
||||
overflow-y: auto;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.cactbot-modal-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
gap: 16px;
|
||||
padding: 24px 28px 18px;
|
||||
border-bottom: 1px solid var(--border);
|
||||
}
|
||||
|
||||
.cactbot-modal-header .modal-title {
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.cactbot-modal-subtitle {
|
||||
color: var(--t3);
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.cactbot-export-options {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 10px;
|
||||
padding: 18px 28px 0;
|
||||
}
|
||||
|
||||
.cactbot-option-card,
|
||||
.cactbot-check-card {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--r);
|
||||
background: var(--bg2);
|
||||
cursor: pointer;
|
||||
transition: border-color .15s, background .15s;
|
||||
}
|
||||
|
||||
.cactbot-option-card {
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
.cactbot-option-card:hover,
|
||||
.cactbot-check-card:hover {
|
||||
border-color: var(--borderem);
|
||||
background: var(--bg3);
|
||||
}
|
||||
|
||||
.cactbot-option-card input,
|
||||
.cactbot-check-card input {
|
||||
width: auto;
|
||||
margin: 0;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.cactbot-option-card span,
|
||||
.cactbot-check-card span {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.cactbot-option-card strong,
|
||||
.cactbot-check-card strong {
|
||||
color: var(--t1);
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.cactbot-option-card small,
|
||||
.cactbot-check-card small {
|
||||
color: var(--t3);
|
||||
font-size: 11px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.cactbot-field {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
padding: 16px 28px 0;
|
||||
}
|
||||
|
||||
.cactbot-field label,
|
||||
.cactbot-section-title {
|
||||
color: var(--t2);
|
||||
font-size: 11px;
|
||||
font-weight: 700;
|
||||
letter-spacing: .06em;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.cactbot-field input {
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.cactbot-check-grid,
|
||||
.cactbot-role-grid,
|
||||
.cactbot-player-columns {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(170px, 1fr));
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.cactbot-role-grid,
|
||||
.cactbot-player-columns {
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
.cactbot-player-columns {
|
||||
gap: 10px;
|
||||
align-items: start;
|
||||
}
|
||||
|
||||
.cactbot-player-column {
|
||||
display: flex;
|
||||
min-width: 0;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.cactbot-player-column-title {
|
||||
padding: 0 2px 2px;
|
||||
color: var(--gold);
|
||||
font-size: 11px;
|
||||
font-weight: 700;
|
||||
letter-spacing: .06em;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.cactbot-check-card {
|
||||
padding: 9px 10px;
|
||||
}
|
||||
|
||||
.cactbot-check-card--role {
|
||||
min-height: 40px;
|
||||
}
|
||||
|
||||
.cactbot-section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 9px;
|
||||
padding: 18px 28px 0;
|
||||
}
|
||||
|
||||
.cactbot-modal-actions {
|
||||
position: sticky;
|
||||
bottom: 0;
|
||||
justify-content: flex-end;
|
||||
padding: 18px 28px 24px;
|
||||
margin-top: 8px;
|
||||
border-top: 1px solid var(--border);
|
||||
background: var(--bgcard);
|
||||
}
|
||||
|
||||
@media (max-width: 680px) {
|
||||
.cactbot-export-options,
|
||||
.cactbot-role-grid,
|
||||
.cactbot-player-columns {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
.view-toggle-btn {
|
||||
@ -1264,7 +1991,7 @@
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* ── Meine Spells ────────────────────────────────────────────────────────── */
|
||||
/* ── Simple View Rows ────────────────────────────────────────────────────── */
|
||||
|
||||
.myspells-controls {
|
||||
display: flex;
|
||||
@ -1333,6 +2060,18 @@
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.myspells-ability .badge-remove {
|
||||
opacity: .45;
|
||||
}
|
||||
|
||||
.myspells-ability:hover .badge-remove {
|
||||
opacity: .8;
|
||||
}
|
||||
|
||||
.myspells-ability .badge-remove:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.myspells-ability.myspells-type--debuff { color: var(--orange); border-color: rgba(255,140,0,0.3); }
|
||||
.myspells-ability.myspells-type--shield { color: var(--blue); border-color: rgba(88,166,255,0.3); }
|
||||
.myspells-ability.myspells-type--personal { color: #dbc7ff; border-color: rgba(177,112,255,0.4); }
|
||||
|
||||
1380
js/planner.js
1380
js/planner.js
File diff suppressed because it is too large
Load Diff
@ -1,9 +0,0 @@
|
||||
<div class="card section-gap" id="fight-select-card" style="display:none">
|
||||
<div class="card-title-row">
|
||||
<div class="card-title">Fight auswählen</div>
|
||||
<a id="fflogs-report-link" class="btn btn-sm" href="#" target="_blank" rel="noopener" style="display:none;text-decoration:none;margin-left:auto">FFLogs öffnen</a>
|
||||
</div>
|
||||
<select id="fight-select">
|
||||
<option value="">— Fight auswählen —</option>
|
||||
</select>
|
||||
</div>
|
||||
@ -80,6 +80,51 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Planner Compare Modal -->
|
||||
<div id="planner-compare-modal" class="modal-overlay" style="display:none">
|
||||
<div class="modal-box planner-compare-modal-box">
|
||||
<div class="compare-modal-header">
|
||||
<div>
|
||||
<div class="modal-title">Plan mit FFLogs vergleichen</div>
|
||||
<div class="compare-modal-subtitle">Schnellcheck für live Logs: fehlende, frühe, späte und extra Mitigations.</div>
|
||||
</div>
|
||||
<div class="compare-modal-header-actions">
|
||||
<label class="compare-show-ok-toggle" id="compare-show-ok-wrap" style="display:none">
|
||||
<input type="checkbox" id="compare-show-ok">
|
||||
<span>OK anzeigen</span>
|
||||
</label>
|
||||
<button id="compare-run-btn" class="btn btn-gold" style="display:none">Vergleichen</button>
|
||||
<button id="compare-refresh-btn" class="btn" style="display:none">Refresh live log</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="compare-modal-body">
|
||||
<div class="compare-setup-grid">
|
||||
<div class="modal-section">
|
||||
<div class="modal-label">Report-Code</div>
|
||||
<div class="name-import-input-row">
|
||||
<input type="text" id="compare-report-input" placeholder="Report-Code oder URL…">
|
||||
<button id="compare-load-btn" class="btn btn-sm">Fights laden</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal-section" id="compare-fight-section" style="display:none">
|
||||
<div class="modal-label">Fight</div>
|
||||
<select id="compare-fight-select">
|
||||
<option value="">— Fight auswählen —</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="compare-result" class="planner-compare-result" style="display:none"></div>
|
||||
</div>
|
||||
|
||||
<div class="modal-actions compare-modal-footer">
|
||||
<button id="compare-cancel-btn" class="btn">Schließen</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Name Import Modal (Planner) -->
|
||||
<div id="planner-name-import-modal" class="modal-overlay" style="display:none">
|
||||
<div class="modal-box">
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
<div class="card section-gap">
|
||||
<div class="card-title">Report laden</div>
|
||||
<form id="report-form">
|
||||
<div class="form-row">
|
||||
<div class="fg">
|
||||
<div class="form-row report-load-row">
|
||||
<div class="fg report-code-field">
|
||||
<label>Report Code</label>
|
||||
<input
|
||||
type="text"
|
||||
@ -13,8 +13,8 @@
|
||||
required
|
||||
>
|
||||
</div>
|
||||
<div class="fg">
|
||||
<label>Namen</label>
|
||||
<div class="fg report-language-field">
|
||||
<label>Language</label>
|
||||
<select name="language" id="language-select">
|
||||
<option value="en">EN</option>
|
||||
<option value="de">DE</option>
|
||||
@ -23,7 +23,15 @@
|
||||
</select>
|
||||
</div>
|
||||
<button class="btn btn-gold" type="submit" style="align-self:flex-end">Fetch</button>
|
||||
<a class="btn" href="<?= htmlspecialchars(auth_start_href(), ENT_QUOTES) ?>" style="align-self:flex-end;text-decoration:none">Reconnect</a>
|
||||
<div class="fg report-fight-field" id="fight-select-card" style="display:none">
|
||||
<label class="report-fight-label">
|
||||
Fight
|
||||
<a id="fflogs-report-link" href="#" target="_blank" rel="noopener" style="display:none;text-decoration:none">FFLogs öffnen</a>
|
||||
</label>
|
||||
<select id="fight-select">
|
||||
<option value="">— Fight auswählen —</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
<?php require __DIR__ . '/report-form.php'; ?>
|
||||
|
||||
<div id="analysis-loading" class="loading" style="display:none">
|
||||
<div class="spinner"></div>
|
||||
<span>Analysiere Fight-Daten...</span>
|
||||
@ -5,7 +7,7 @@
|
||||
|
||||
<div id="analysis-empty" class="empty">
|
||||
<div class="empty-icon">📊</div>
|
||||
<h3 id="analysis-empty-msg">Bitte zuerst einen Fight im Report-Tab auswählen</h3>
|
||||
<h3 id="analysis-empty-msg">Bitte zuerst einen Fight auswählen</h3>
|
||||
</div>
|
||||
|
||||
<div id="analysis-content" style="display:none">
|
||||
|
||||
@ -1,4 +1,2 @@
|
||||
<?php require __DIR__ . '/report-form.php'; ?>
|
||||
<?php require __DIR__ . '/fight-select.php'; ?>
|
||||
<?php require __DIR__ . '/event-explorer.php'; ?>
|
||||
<?php require __DIR__ . '/output-card.php'; ?>
|
||||
|
||||
@ -1,14 +1,14 @@
|
||||
<header id="topbar">
|
||||
<div class="logo">REPORT VIEWER <span>Final Fantasy XIV</span></div>
|
||||
<nav class="tabs">
|
||||
<button class="tab active" data-tab="report">⚔ Report</button>
|
||||
<button class="tab" data-tab="analysis">⚖ Analyse</button>
|
||||
<button class="tab" data-tab="planner">☰ Planer</button>
|
||||
<button class="tab active" data-tab="report">⚔ Debug</button>
|
||||
</nav>
|
||||
<?php $fflogsUser = current_fflogs_user(); ?>
|
||||
<div class="topbar-actions">
|
||||
<?php if (is_admin_user()): ?>
|
||||
<a class="topbar-link" href="admin.php">Admin</a>
|
||||
<a class="topbar-link" href="admin.php?return=<?= rawurlencode(current_return_path()) ?>">Admin</a>
|
||||
<?php endif; ?>
|
||||
<div class="topbar-user">
|
||||
<?php if ($fflogsUser): ?>
|
||||
@ -16,5 +16,6 @@
|
||||
<?php endif; ?>
|
||||
Token gültig bis: <?= date('Y-m-d H:i:s', $_SESSION['token_expires']) ?>
|
||||
</div>
|
||||
<a class="topbar-link" href="<?= htmlspecialchars(auth_start_href(), ENT_QUOTES) ?>">Reconnect</a>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user