stage-31 polish: featured banner header + clickable news cards w/ hover
Two enhancements from the screenshot annotations:
1. The featured card now has a proper banner header above the hero —
gradient strip with "⌖ Featured Threat" label, a pulsing severity
dot, severity/TLP badges, source + ingest time. The header is
sev-themed (red border + glow for CRITICAL, amber for HIGH), and an
"Open case →" CTA at the bottom of the hero with an arrow that
nudges right on hover.
2. News items are now full-card clickable (transparent overlay link)
with rich severity-tinted hover transitions:
- Lifts (translateY -1px), tinted background + border that matches
severity (red CRITICAL, amber HIGH, pale MEDIUM/LOW), per-kind
accents for enforced/submitted/rejected/failed items.
- A right-side arrow (→) slides in on hover signaling "click to open".
- Case glyph SVG gets a subtle drop-shadow + 1.04x scale on hover.
- 180ms ease transitions across background/border/transform/shadow.
- Respects prefers-reduced-motion (turns animations off).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -927,3 +927,100 @@ body.wide .net-grid { grid-template-columns: repeat(auto-fill, minmax(320px, 1fr
|
||||
.news-item.sev-low { border-left-color: var(--muted); }
|
||||
.news-item .news-icon { background: transparent; border: none; padding: 0; }
|
||||
.news-item .news-icon .case-glyph-svg { display: block; border: 1px solid var(--border); border-radius: 7px; }
|
||||
|
||||
/* ── featured section header (banner above the hero) ───────── */
|
||||
.featured-section { margin: 0 0 22px; }
|
||||
.featured-section-head {
|
||||
display: flex; align-items: center; justify-content: space-between;
|
||||
gap: 12px; flex-wrap: wrap;
|
||||
padding: 10px 16px; margin-bottom: 1px;
|
||||
background: linear-gradient(90deg, rgba(30,200,255,0.10), rgba(15,17,21,0.4));
|
||||
border: 1px solid var(--border); border-bottom: none;
|
||||
border-radius: 14px 14px 0 0;
|
||||
position: relative;
|
||||
}
|
||||
.featured-section-head.sev-critical { background: linear-gradient(90deg, rgba(248,113,113,0.16), rgba(15,17,21,0.4)); border-color: var(--red); }
|
||||
.featured-section-head.sev-high { background: linear-gradient(90deg, rgba(251,191,36,0.14), rgba(15,17,21,0.4)); border-color: var(--amber); }
|
||||
.featured-section-title { display: inline-flex; align-items: center; gap: 10px; }
|
||||
.featured-bracket {
|
||||
font-family: var(--font-display); color: var(--accent); font-size: 18px;
|
||||
text-shadow: 0 0 12px var(--accent-glow);
|
||||
}
|
||||
.featured-section-head.sev-critical .featured-bracket { color: var(--red); text-shadow: 0 0 12px rgba(248,113,113,0.5); }
|
||||
.featured-section-head.sev-high .featured-bracket { color: var(--amber); text-shadow: 0 0 12px rgba(251,191,36,0.5); }
|
||||
.featured-section-label {
|
||||
font-family: var(--font-display); font-size: 13px; font-weight: 600;
|
||||
letter-spacing: 0.22em; text-transform: uppercase; color: #eaf6ff;
|
||||
}
|
||||
.featured-section-pulse {
|
||||
display: inline-block; width: 7px; height: 7px; border-radius: 50%;
|
||||
background: var(--accent); box-shadow: 0 0 10px var(--accent);
|
||||
animation: featured-pulse 1.8s ease-in-out infinite;
|
||||
}
|
||||
.featured-section-head.sev-critical .featured-section-pulse { background: var(--red); box-shadow: 0 0 10px var(--red); }
|
||||
.featured-section-head.sev-high .featured-section-pulse { background: var(--amber); box-shadow: 0 0 10px var(--amber); }
|
||||
@keyframes featured-pulse { 0%,100% { opacity: 0.5; transform: scale(1); } 50% { opacity: 1; transform: scale(1.3); } }
|
||||
.featured-section-meta { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; font-size: 12px; }
|
||||
.featured-section-meta .muted { color: var(--muted); font-family: ui-monospace, Menlo, monospace; }
|
||||
.featured { border-radius: 0 0 14px 14px; margin: 0; }
|
||||
.featured-cta {
|
||||
display: inline-flex; align-items: center; gap: 6px; margin-top: 8px;
|
||||
font-family: var(--font-display); font-size: 11px; letter-spacing: 0.16em;
|
||||
color: var(--accent); text-transform: uppercase;
|
||||
opacity: 0.85; transition: gap 0.2s, opacity 0.2s;
|
||||
}
|
||||
.featured-link:hover .featured-cta, .featured:hover .featured-cta { opacity: 1; gap: 12px; }
|
||||
.featured-arrow { transition: transform 0.25s ease; }
|
||||
.featured:hover .featured-arrow { transform: translateX(4px); }
|
||||
|
||||
/* ── news-item: clickable card with severity-tinted hover ─── */
|
||||
.news-item {
|
||||
position: relative;
|
||||
display: grid; grid-template-columns: 44px 1fr auto; gap: 12px;
|
||||
padding: 12px 14px; padding-left: 14px;
|
||||
margin: 6px 0;
|
||||
background: rgba(28,34,48,0.0);
|
||||
border: 1px solid transparent;
|
||||
border-left: 3px solid transparent;
|
||||
border-radius: 8px;
|
||||
transition: background 0.18s ease, border-color 0.18s ease, transform 0.18s ease, box-shadow 0.22s ease;
|
||||
}
|
||||
.news-item.is-link { cursor: pointer; }
|
||||
/* full-card click target (sits under the content, lets text + chip be selectable on real <a>'s) */
|
||||
.news-cardlink {
|
||||
position: absolute; inset: 0; border-radius: inherit;
|
||||
z-index: 1; text-indent: -9999px; overflow: hidden;
|
||||
}
|
||||
.news-item > .news-icon, .news-item > .news-body, .news-item > .news-arrow { position: relative; z-index: 2; pointer-events: none; }
|
||||
.news-item .news-meta a { pointer-events: auto; } /* explicit links stay clickable */
|
||||
|
||||
.news-arrow {
|
||||
align-self: center; font-family: var(--font-display);
|
||||
color: var(--accent); font-size: 18px;
|
||||
opacity: 0; transform: translateX(-6px);
|
||||
transition: opacity 0.18s ease, transform 0.18s ease;
|
||||
}
|
||||
.news-item.is-link:hover .news-arrow { opacity: 1; transform: translateX(0); }
|
||||
|
||||
/* severity tints on hover */
|
||||
.news-item:hover { transform: translateY(-1px); box-shadow: 0 6px 18px rgba(0,0,0,0.25); }
|
||||
.news-item.sev-critical:hover { background: rgba(248,113,113,0.06); border-color: rgba(248,113,113,0.55); box-shadow: 0 6px 22px rgba(248,113,113,0.18); }
|
||||
.news-item.sev-high:hover { background: rgba(251,191,36,0.06); border-color: rgba(251,191,36,0.55); box-shadow: 0 6px 22px rgba(251,191,36,0.16); }
|
||||
.news-item.sev-medium:hover { background: rgba(253,230,138,0.05); border-color: rgba(253,230,138,0.42); }
|
||||
.news-item.sev-low:hover { background: rgba(125,133,151,0.06); border-color: var(--border); }
|
||||
.news-item.news-kind-enforced:hover { background: rgba(30,200,255,0.06); border-color: var(--accent); box-shadow: 0 6px 22px var(--accent-glow); }
|
||||
.news-item.news-kind-submitted:hover { background: rgba(74,222,128,0.05); border-color: rgba(74,222,128,0.4); }
|
||||
.news-item.news-kind-rejected:hover, .news-item.news-kind-failed:hover { background: rgba(248,113,113,0.05); border-color: rgba(248,113,113,0.4); }
|
||||
/* the glyph svg inside the icon — subtle highlight on hover */
|
||||
.news-item:hover .case-glyph-svg { filter: drop-shadow(0 0 8px var(--accent-glow)); transform: scale(1.04); transition: filter 0.18s, transform 0.18s; }
|
||||
.news-icon-glyph {
|
||||
display: grid; place-items: center; width: 36px; height: 36px;
|
||||
background: var(--panel-2); border: 1px solid var(--border); border-radius: 7px;
|
||||
font-size: 16px; color: var(--muted);
|
||||
}
|
||||
|
||||
.news-case-id { color: var(--muted); font-family: ui-monospace, Menlo, monospace; }
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.news-item, .news-arrow, .featured-arrow, .featured-cta, .case-glyph-svg, .featured-section-pulse { transition: none; animation: none; }
|
||||
}
|
||||
|
||||
@@ -38,18 +38,24 @@
|
||||
</section>
|
||||
|
||||
{% if featured %}
|
||||
<section class="featured sev-{{ featured.classification.severity.value }}">
|
||||
<a class="featured-link" href="/cases/{{ featured.case_id }}">
|
||||
<section class="featured-section">
|
||||
<header class="featured-section-head sev-{{ featured.classification.severity.value }}">
|
||||
<div class="featured-section-title">
|
||||
<span class="featured-bracket">⌖</span>
|
||||
<span class="featured-section-label">Featured Threat</span>
|
||||
<span class="featured-section-pulse"></span>
|
||||
</div>
|
||||
<div class="featured-section-meta">
|
||||
<span class="sev-badge">{{ featured.classification.severity.value if featured.classification.severity else '—' }}</span>
|
||||
<span class="tlp-badge tlp-{{ featured.classification.tlp.value }}">{{ featured.classification.tlp.value }}</span>
|
||||
<span class="muted">{{ featured.source_metadata.feed or 'unknown' }} · {{ featured.ingested_at.strftime('%Y-%m-%d %H:%M') }}</span>
|
||||
</div>
|
||||
</header>
|
||||
<a class="featured sev-{{ featured.classification.severity.value }}" href="/cases/{{ featured.case_id }}">
|
||||
<div class="featured-hero">{{ featured_hero|safe }}</div>
|
||||
<div class="featured-overlay">
|
||||
<div class="featured-tag">⌖ FEATURED THREAT</div>
|
||||
<h2 class="featured-title">{{ featured.summary }}</h2>
|
||||
<div class="featured-meta">
|
||||
<span class="sev-badge">{{ featured.classification.severity.value if featured.classification.severity else '—' }}</span>
|
||||
<span class="tlp-badge tlp-{{ featured.classification.tlp.value }}">{{ featured.classification.tlp.value }}</span>
|
||||
<span class="muted">from {{ featured.source_metadata.feed or 'unknown' }}</span>
|
||||
<span class="muted">ingested {{ featured.ingested_at.strftime('%Y-%m-%d %H:%M') }}</span>
|
||||
</div>
|
||||
<h3 class="featured-title">{{ featured.summary }}</h3>
|
||||
<div class="featured-cta">Open case <span class="featured-arrow">→</span></div>
|
||||
</div>
|
||||
</a>
|
||||
</section>
|
||||
@@ -71,12 +77,14 @@
|
||||
<h3 class="bucket-head">{{ b.label }} <span class="bucket-count">{{ b.items|length }}</span></h3>
|
||||
<ol class="news-list">
|
||||
{% for i in b.items %}
|
||||
<li class="news-item news-kind-{{ i.kind }}{% if i.severity %} sev-{{ i.severity }}{% endif %}">
|
||||
{% set has_link = i.case_id %}
|
||||
<li class="news-item news-kind-{{ i.kind }}{% if i.severity %} sev-{{ i.severity }}{% endif %}{% if has_link %} is-link{% endif %}">
|
||||
{% if has_link %}<a class="news-cardlink" href="/cases/{{ i.case_id }}" aria-label="Open {{ i.case_id }}"></a>{% endif %}
|
||||
<div class="news-icon">
|
||||
{% if i.kind == 'case' and i.case_id and case_glyphs.get(i.case_id) %}
|
||||
{{ case_glyphs[i.case_id]|safe }}
|
||||
{% else %}
|
||||
{{ i.icon }}
|
||||
<span class="news-icon-glyph">{{ i.icon }}</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="news-body">
|
||||
@@ -87,9 +95,10 @@
|
||||
<div class="news-sub">{{ i.body }}</div>
|
||||
<div class="news-meta">
|
||||
<time>{{ i.timestamp.strftime('%H:%M' if b.label == 'Today' else '%Y-%m-%d %H:%M') }}</time>
|
||||
{% if i.case_id %} · <a href="/cases/{{ i.case_id }}">{{ i.case_id }}</a>{% endif %}
|
||||
{% if i.case_id %} · <span class="news-case-id">{{ i.case_id }}</span>{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% if has_link %}<span class="news-arrow" aria-hidden="true">→</span>{% endif %}
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ol>
|
||||
|
||||
Reference in New Issue
Block a user