fix: sticky header opaque + banner visible + missing button styles

Three things from screenshot feedback against 4.6.20:

1) VODs visible through/above the sticky profile header. Root cause
   was a stack: the 0.10/0.04 alpha gradient over var(--bg-card)
   pushed the resulting background just barely under "opaque" in
   some renderers AND .content has padding-top: 25px which let
   VODs scroll through the area above the sticky element when
   top: 0 was used. Fix: drop the gradient (banner-bg + ::before
   pseudo handle the visual interest now), use straight
   var(--bg-card), set top: -25px to negate .contents padding so
   the header pins flush with the visible top edge, bump z-index
   to 100, add isolation:isolate to force a new stacking context
   so VODs cannot escape upward through the header.

2) Banner not visible. Was being suppressed by a 0.78-0.92 alpha
   dimming gradient applied via background-image alongside the
   banner URL — readable for text but visually killed the banner.
   Moved the gradient into a ::before pseudo at z-index 1 with
   gentler 0.55-0.78 alpha, dropped banner-bg blur from 18px to
   10px, took opacity from 0.55 back up to 1.0. Banner now
   actually shows behind the content the way twitch.tv does.

3) Stray un-styled buttons. Scan turned up a handful of action
   buttons rolling their own inline styles (.vodBulkAddBtn /
   MarkBtn / UnmarkBtn / ClearBtn, .vodFilterClearBtn,
   .btnStreamerBulkRemove, .clipDialogConfirmBtn) plus a missing
   .queue-detail-btn rule that was leaving every "View chat",
   "View events", "Open file", "Show in folder" button defaulting
   to the browsers gray fallback. Added three reusable classes
   (.btn-pill default/primary/success/danger, .btn-icon, plus the
   missing .queue-detail-btn) and swapped the inline styles for
   the classes. Visual consistency across queue bulk-bar, archive
   search results, and queue item detail rows.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
xRangerDE 2026-05-11 01:04:22 +02:00
parent bd54ba9cfb
commit 30776c02b9
3 changed files with 164 additions and 17 deletions

View File

@ -131,7 +131,7 @@
<!-- Button --> <!-- Button -->
<div style="text-align: center;"> <div style="text-align: center;">
<button class="btn-primary" id="clipDialogConfirmBtn" style="background: #00c853; padding: 12px 30px; border: none; border-radius: 4px; color: white; font-weight: 600; cursor: pointer;" onclick="confirmClipDialog()">Zur Queue hinzufugen</button> <button class="btn-pill success" id="clipDialogConfirmBtn" style="padding: 12px 30px;" onclick="confirmClipDialog()">Zur Queue hinzufugen</button>
</div> </div>
</div> </div>
</div> </div>
@ -241,7 +241,7 @@
<div class="section-title" id="streamerSectionTitle" style="display:flex; align-items:center; gap:6px; justify-content:space-between;"> <div class="section-title" id="streamerSectionTitle" style="display:flex; align-items:center; gap:6px; justify-content:space-between;">
<span id="streamerSectionTitleText">Streamer</span> <span id="streamerSectionTitleText">Streamer</span>
<button id="btnStreamerBulkRemove" type="button" onclick="bulkRemoveStreamers()" title="Bulk remove" style="display:none; background:transparent; border:1px solid var(--border-soft); border-radius:4px; padding:2px 8px; color:var(--text-secondary); font-size:11px; cursor:pointer;">x</button> <button id="btnStreamerBulkRemove" class="btn-icon" type="button" onclick="bulkRemoveStreamers()" title="Bulk remove" style="display:none;">x</button>
</div> </div>
<input type="text" id="streamerListFilter" placeholder="Filter..." oninput="onStreamerListFilterChange()" style="display:none; width:calc(100% - 16px); margin:0 8px 8px; background:var(--bg-card); border:1px solid var(--border-soft); border-radius:4px; padding:4px 8px; color:var(--text); font-size:12px;"> <input type="text" id="streamerListFilter" placeholder="Filter..." oninput="onStreamerListFilterChange()" style="display:none; width:calc(100% - 16px); margin:0 8px 8px; background:var(--bg-card); border:1px solid var(--border-soft); border-radius:4px; padding:4px 8px; color:var(--text); font-size:12px;">
<div class="streamers" id="streamerList"></div> <div class="streamers" id="streamerList"></div>
@ -283,7 +283,7 @@
<div id="streamerProfileHeader" class="streamer-profile-header" style="display:none;"></div> <div id="streamerProfileHeader" class="streamer-profile-header" style="display:none;"></div>
<div class="vod-filter-row" style="display:flex; align-items:center; gap:8px; margin-bottom:12px; flex-wrap:wrap;"> <div class="vod-filter-row" style="display:flex; align-items:center; gap:8px; margin-bottom:12px; flex-wrap:wrap;">
<input type="text" id="vodFilterInput" placeholder="Filter VODs..." oninput="onVodFilterInput()" style="flex:1; min-width:180px; background: var(--bg-card); border:1px solid var(--border-soft); border-radius:6px; padding:8px 12px; color: var(--text); font-size:13px;"> <input type="text" id="vodFilterInput" placeholder="Filter VODs..." oninput="onVodFilterInput()" style="flex:1; min-width:180px; background: var(--bg-card); border:1px solid var(--border-soft); border-radius:6px; padding:8px 12px; color: var(--text); font-size:13px;">
<button id="vodFilterClearBtn" onclick="clearVodFilter()" title="Clear filter" style="display:none; background:transparent; border:1px solid var(--border-soft); border-radius:6px; padding:8px 12px; color: var(--text-secondary); cursor:pointer;">x</button> <button id="vodFilterClearBtn" class="btn-icon" onclick="clearVodFilter()" title="Clear filter" style="display:none;">x</button>
<label id="vodSortLabel" for="vodSortSelect" style="color: var(--text-secondary); font-size:12px; margin-left:8px;">Sort:</label> <label id="vodSortLabel" for="vodSortSelect" style="color: var(--text-secondary); font-size:12px; margin-left:8px;">Sort:</label>
<select id="vodSortSelect" onchange="onVodSortChange()" style="background: var(--bg-card); border:1px solid var(--border-soft); border-radius:6px; padding:7px 10px; color: var(--text); font-size:13px;"> <select id="vodSortSelect" onchange="onVodSortChange()" style="background: var(--bg-card); border:1px solid var(--border-soft); border-radius:6px; padding:7px 10px; color: var(--text); font-size:13px;">
<option value="date_desc">Newest first</option> <option value="date_desc">Newest first</option>
@ -301,10 +301,10 @@
<div id="vodBulkBar" class="vod-bulk-bar" style="display:none; align-items:center; gap:10px; padding:8px 12px; background: rgba(145, 70, 255, 0.12); border:1px solid rgba(145, 70, 255, 0.4); border-radius:6px; margin-bottom:12px; flex-wrap:wrap;"> <div id="vodBulkBar" class="vod-bulk-bar" style="display:none; align-items:center; gap:10px; padding:8px 12px; background: rgba(145, 70, 255, 0.12); border:1px solid rgba(145, 70, 255, 0.4); border-radius:6px; margin-bottom:12px; flex-wrap:wrap;">
<span id="vodBulkCount" style="color: var(--text); font-size:13px; font-weight:600;">0 selected</span> <span id="vodBulkCount" style="color: var(--text); font-size:13px; font-weight:600;">0 selected</span>
<span style="flex:1;"></span> <span style="flex:1;"></span>
<button id="vodBulkAddBtn" type="button" onclick="bulkAddSelectedVodsToQueue()" style="background:var(--accent); border:none; border-radius:6px; padding:6px 14px; color:#fff; font-size:13px; font-weight:600; cursor:pointer;">+ Queue</button> <button id="vodBulkAddBtn" class="btn-pill primary" type="button" onclick="bulkAddSelectedVodsToQueue()">+ Queue</button>
<button id="vodBulkMarkBtn" type="button" onclick="bulkMarkSelectedDownloaded(true)" style="background:transparent; border:1px solid var(--border-soft); border-radius:6px; padding:6px 12px; color:var(--text-secondary); font-size:13px; cursor:pointer;">Mark as downloaded</button> <button id="vodBulkMarkBtn" class="btn-pill" type="button" onclick="bulkMarkSelectedDownloaded(true)">Mark as downloaded</button>
<button id="vodBulkUnmarkBtn" type="button" onclick="bulkMarkSelectedDownloaded(false)" style="background:transparent; border:1px solid var(--border-soft); border-radius:6px; padding:6px 12px; color:var(--text-secondary); font-size:13px; cursor:pointer;">Unmark</button> <button id="vodBulkUnmarkBtn" class="btn-pill" type="button" onclick="bulkMarkSelectedDownloaded(false)">Unmark</button>
<button id="vodBulkClearBtn" type="button" onclick="clearVodSelection()" style="background:transparent; border:1px solid var(--border-soft); border-radius:6px; padding:6px 12px; color:var(--text-secondary); font-size:13px; cursor:pointer;">Clear</button> <button id="vodBulkClearBtn" class="btn-pill" type="button" onclick="clearVodSelection()">Clear</button>
</div> </div>
<div class="vod-grid" id="vodGrid"> <div class="vod-grid" id="vodGrid">
<div class="empty-state"> <div class="empty-state">

View File

@ -111,8 +111,10 @@ function renderStreamerProfileCard(p: StreamerProfile): void {
</div>`; </div>`;
// Banner-as-background — set inline so the URL stays per-streamer. // Banner-as-background — set inline so the URL stays per-streamer.
// The darkening gradient is handled by the .streamer-profile-header::before
// pseudo so the banner itself stays bright and unfiltered here.
const bannerStyle = p.bannerUrl const bannerStyle = p.bannerUrl
? `background-image: linear-gradient(135deg, rgba(14,14,16,0.78) 0%, rgba(14,14,16,0.92) 100%), url("${p.bannerUrl.replace(/"/g, '%22')}");` ? `background-image: url("${p.bannerUrl.replace(/"/g, '%22')}");`
: ''; : '';
// Live preview block — only when currently live. Big card with // Live preview block — only when currently live. Big card with

View File

@ -978,6 +978,139 @@ body {
cursor: pointer; cursor: pointer;
} }
/* ============================================
COMPACT / UTILITY BUTTONS
============================================
.btn-pill small action buttons used in toolbars + bulk-bars.
Comes in default (transparent), primary (purple), success (green).
Replaces the inline-style blocks the renderer was rolling for each
bulk action button. */
.btn-pill {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 6px;
background: transparent;
color: var(--text-secondary);
border: 1px solid var(--border-soft);
border-radius: 6px;
padding: 6px 12px;
font-size: 13px;
font-weight: 500;
cursor: pointer;
transition: background 0.15s, color 0.15s, border-color 0.15s, transform 0.15s;
line-height: 1.2;
}
.btn-pill:hover:not(:disabled) {
background: rgba(255, 255, 255, 0.06);
color: var(--text);
border-color: rgba(255, 255, 255, 0.18);
}
.btn-pill:active:not(:disabled) {
transform: translateY(1px);
}
.btn-pill:disabled {
opacity: 0.45;
cursor: not-allowed;
}
.btn-pill.primary {
background: var(--accent);
color: #fff;
border-color: var(--accent);
font-weight: 600;
}
.btn-pill.primary:hover:not(:disabled) {
background: var(--accent-hover);
border-color: var(--accent-hover);
color: #fff;
box-shadow: 0 4px 14px rgba(145, 70, 255, 0.35);
}
.btn-pill.success {
background: #00c853;
color: #fff;
border-color: #00c853;
font-weight: 600;
}
.btn-pill.success:hover:not(:disabled) {
background: #00e676;
border-color: #00e676;
box-shadow: 0 4px 14px rgba(0, 200, 83, 0.35);
}
.btn-pill.danger {
background: transparent;
color: #ff6b6b;
border-color: rgba(255, 107, 107, 0.4);
}
.btn-pill.danger:hover:not(:disabled) {
background: rgba(255, 107, 107, 0.12);
border-color: rgba(255, 107, 107, 0.7);
color: #ff8a8a;
}
/* .btn-icon square X-close button for filter clears, inline removals.
1.6em wide, transparent base, hover only colours the X. */
.btn-icon {
display: inline-flex;
align-items: center;
justify-content: center;
background: transparent;
border: 1px solid var(--border-soft);
border-radius: 6px;
padding: 6px 10px;
color: var(--text-secondary);
cursor: pointer;
font-size: 12px;
font-weight: 500;
transition: background 0.15s, color 0.15s, border-color 0.15s;
line-height: 1;
}
.btn-icon:hover:not(:disabled) {
background: rgba(255, 70, 70, 0.10);
border-color: rgba(255, 70, 70, 0.45);
color: #ff6b6b;
}
/* .queue-detail-btn tiny chip-style action button used in queue item
detail rows AND in the archive search results list. Was previously
rendering with browser defaults (gray flat button). */
.queue-detail-btn {
display: inline-flex;
align-items: center;
justify-content: center;
background: rgba(145, 70, 255, 0.10);
color: var(--text);
border: 1px solid rgba(145, 70, 255, 0.30);
border-radius: 5px;
padding: 4px 10px;
margin-right: 6px;
margin-bottom: 4px;
font-size: 11px;
font-weight: 500;
cursor: pointer;
transition: background 0.15s, border-color 0.15s, color 0.15s, transform 0.12s;
}
.queue-detail-btn:hover {
background: rgba(145, 70, 255, 0.22);
border-color: rgba(145, 70, 255, 0.6);
color: #fff;
transform: translateY(-1px);
}
.queue-detail-btn:active {
transform: translateY(0);
}
/* Clips */ /* Clips */
.clip-input { .clip-input {
max-width: 600px; max-width: 600px;
@ -1949,23 +2082,35 @@ body.theme-light .modal {
for instant familiarity, but trimmed for the desktop-app context. */ for instant familiarity, but trimmed for the desktop-app context. */
.streamer-profile-header { .streamer-profile-header {
position: sticky; position: sticky;
top: 0; top: -25px; /* negate the .content top padding so the header pins flush with the visible top edge */
z-index: 20; z-index: 100;
display: block; display: block;
padding: 0; padding: 0;
margin-top: -2px;
margin-bottom: 14px; margin-bottom: 14px;
background: linear-gradient(135deg, rgba(145, 70, 255, 0.10) 0%, rgba(0, 200, 83, 0.04) 100%), var(--bg-card); background: var(--bg-card);
border: 1px solid var(--border-soft); border: 1px solid var(--border-soft);
border-radius: 12px; border-radius: 12px;
overflow: hidden; overflow: hidden;
animation: profile-fade-in 0.32s ease-out; animation: profile-fade-in 0.32s ease-out;
backdrop-filter: blur(6px); isolation: isolate; /* new stacking context so VODs below cannot leak above */
-webkit-backdrop-filter: blur(6px); box-shadow: 0 6px 22px rgba(0, 0, 0, 0.35);
}
/* Dimming gradient sits ABOVE the banner-bg but BELOW the content row.
Gives the banner room to breathe while keeping name + bio readable. */
.streamer-profile-header::before {
content: '';
position: absolute;
inset: 0;
background: linear-gradient(135deg, rgba(15, 15, 18, 0.55) 0%, rgba(15, 15, 18, 0.78) 100%);
z-index: 1;
pointer-events: none;
} }
.streamer-profile-row { .streamer-profile-row {
position: relative; position: relative;
z-index: 1; z-index: 2;
display: flex; display: flex;
gap: 18px; gap: 18px;
align-items: center; align-items: center;
@ -1977,11 +2122,11 @@ body.theme-light .modal {
inset: 0; inset: 0;
background-size: cover; background-size: cover;
background-position: center; background-position: center;
filter: blur(18px) saturate(1.2); filter: blur(10px) saturate(1.35);
opacity: 0.55; opacity: 1;
pointer-events: none; pointer-events: none;
z-index: 0; z-index: 0;
transform: scale(1.1); /* avoid the blur edge bleed */ transform: scale(1.12); /* hide the blur edge bleed inside the rounded corner clip */
} }
@keyframes profile-fade-in { @keyframes profile-fade-in {