diff --git a/src/layers/news/cards.js b/src/layers/news/cards.js
index 3e5902ee4..25a355445 100644
--- a/src/layers/news/cards.js
+++ b/src/layers/news/cards.js
@@ -149,14 +149,11 @@ function paint(canvas, view, look, pic) {
}
}
if (pic?.state !== 'ok') {
- // No picture: a tile in the severity colour with the category icon.
- const g = ctx.createLinearGradient(px, PAD, px + pw, PAD + PIC_H);
- g.addColorStop(0, WORLD_OVERLAY_STYLE.background);
- g.addColorStop(1, color);
- ctx.fillStyle = g;
- ctx.globalAlpha = 0.55;
+ // No picture: a plain glass tile with the event's marker.
+ ctx.fillStyle = WORLD_OVERLAY_STYLE.background;
+ ctx.fillRect(px, PAD, pw, PIC_H);
+ ctx.fillStyle = 'rgba(255, 255, 255, 0.04)';
ctx.fillRect(px, PAD, pw, PIC_H);
- ctx.globalAlpha = 1;
paintMarker(ctx, look, px + pw / 2, PAD + PIC_H / 2, 34);
}
ctx.restore();
diff --git a/src/layers/news/index.js b/src/layers/news/index.js
index c097ab149..7e70d652b 100644
--- a/src/layers/news/index.js
+++ b/src/layers/news/index.js
@@ -66,9 +66,11 @@ const FEED_UNAVAILABLE = msg('News feed unavailable');
const REPORT_PREFIX = `${NEWS_PICK_PREFIX}report:`;
const HOTSPOT_PREFIX = `${NEWS_PICK_PREFIX}hotspot:`;
const DEVELOPING_PREFIX = `${NEWS_PICK_PREFIX}developing:`;
-const DEVELOPING_COLOR = '#ffb020';
-// Disputed areas: dotted, in their own colour, apart from ordinary borders.
-const DISPUTED_COLOR = '#f472b6';
+// Outlines in GEV's own colours, apart from the severity ramp: hotspots in
+// the accent, developing places in its lighter tint, disputed areas dotted
+// in the primary text colour.
+const DEVELOPING_COLOR = '#9defff';
+const DISPUTED_COLOR = '#e8eaed';
const HOTSPOT_COLOR = '#00d4ff';
// A filtered feed page that shows almost nothing reads the next page by
// itself, a few times, so a strict severity floor still fills the list.
diff --git a/src/layers/news/model.js b/src/layers/news/model.js
index ff26b0fde..3465ec3c8 100644
--- a/src/layers/news/model.js
+++ b/src/layers/news/model.js
@@ -161,13 +161,15 @@ export const NEWS_CATEGORIES = Object.freeze({
},
});
-// Severity 0–3. Lightness falls as severity rises, so the ramp still reads
-// under colour blindness and in greyscale.
+// Severity 0–3, in colours GEV already uses: its dim and primary text, the
+// military layers' amber and the Cockpit's emergency red. They are drawn on
+// a dark disc, so from minor up lightness falls as severity rises and the
+// ramp still reads under colour blindness and in greyscale.
export const NEWS_SEVERITY = Object.freeze([
- { label: msg('No physical event'), color: '#8a9aa8' },
- { label: msg('Minor incident'), color: '#f2d64b' },
- { label: msg('Significant'), color: '#f28c28' },
- { label: msg('Major'), color: '#d7301f' },
+ { label: msg('No physical event'), color: '#7c878d' },
+ { label: msg('Minor incident'), color: '#e8eaed' },
+ { label: msg('Significant'), color: '#ffb800' },
+ { label: msg('Major'), color: '#ff4444' },
]);
/** The feed's default floor: significant and major events. */
export const NEWS_DEFAULT_MIN_SEVERITY = 2;
@@ -309,7 +311,7 @@ export function ageWords(at, now = Date.now()) {
}
/**
- * How a report's marker looks: fill = severity, icon = category, rim =
+ * How a report's marker looks: colour = severity, icon = category, rim =
* attestation (`confirmed` solid, `claimed` dashed, else thin), size =
* sources, opacity = age (fading to 45 % over a day).
*/
diff --git a/src/layers/news/model.test.mjs b/src/layers/news/model.test.mjs
index 88eb0db73..66b74a9c5 100644
--- a/src/layers/news/model.test.mjs
+++ b/src/layers/news/model.test.mjs
@@ -453,7 +453,7 @@ test('feed cards read like Breaking: headline, meta parts, sides and chips', ()
assert.equal(view.original, 'Удар по Харкову');
assert.equal(view.placedByModel, true);
assert.equal(view.severityLabel, 'Major');
- assert.equal(view.color, '#d7301f');
+ assert.equal(view.color, '#ff4444');
assert.equal(view.category, 'Strike');
assert.equal(view.place, 'Kharkiv');
assert.equal(view.sources, '6 sources');
diff --git a/src/layers/news/sprites.js b/src/layers/news/sprites.js
index ac7c71ac5..7c7aa3a0d 100644
--- a/src/layers/news/sprites.js
+++ b/src/layers/news/sprites.js
@@ -1,5 +1,5 @@
-// Marker sprites for news reports: a disc filled with the severity colour,
-// the category's icon in it, and a rim for attestation. Drawn once per look
+// Marker sprites for news reports: a dark disc with the category's icon and
+// a rim in the severity colour, the rim's form telling attestation. Drawn once per look
// on a canvas and handed to Cesium as a data URL, so its texture atlas keeps
// one copy per look however often the layer re-renders.
@@ -7,8 +7,7 @@ import { NEWS_CATEGORIES, NEWS_SEVERITY } from './model.js';
const CANVAS = 64;
const BODY = 52; // disc diameter inside the canvas; the rest is rim and ring
-const INK = '#0a0a0f';
-const RIM = '#ffffff';
+const INK = 'rgba(10, 10, 15, 0.92)';
const SELECTED = '#00d4ff';
/** Canvas pixels per marker pixel: the billboard is drawn at this ratio. */
@@ -28,30 +27,25 @@ export function paintMarker(ctx, { category, severity, rim }, cx, cy, body) {
ctx.arc(cx, cy, r + 1.5 * k, 0, 2 * Math.PI);
ctx.fillStyle = 'rgba(0, 0, 0, 0.85)';
ctx.fill();
+ // GEV's dark glass disc, as its HUD buttons are; severity colours the rim
+ // and the icon rather than filling the disc.
ctx.beginPath();
ctx.arc(cx, cy, r, 0, 2 * Math.PI);
- ctx.fillStyle = (NEWS_SEVERITY[severity] || NEWS_SEVERITY[0]).color;
+ ctx.fillStyle = INK;
ctx.fill();
+ const color = (NEWS_SEVERITY[severity] || NEWS_SEVERITY[0]).color;
// Attestation is the rim's form, never its colour.
- if (rim === 'confirmed' || rim === 'claimed') {
- ctx.beginPath();
- ctx.arc(cx, cy, r - 3.5 * k, 0, 2 * Math.PI);
- ctx.setLineDash(rim === 'claimed' ? [7 * k, 5 * k] : []);
- ctx.lineWidth = 7 * k;
- ctx.strokeStyle = RIM;
- ctx.stroke();
- ctx.setLineDash([]);
- } else {
- ctx.beginPath();
- ctx.arc(cx, cy, r - 1 * k, 0, 2 * Math.PI);
- ctx.lineWidth = 2 * k;
- ctx.strokeStyle = 'rgba(0, 0, 0, 0.75)';
- ctx.stroke();
- }
+ ctx.beginPath();
+ ctx.arc(cx, cy, r - 3 * k, 0, 2 * Math.PI);
+ ctx.setLineDash(rim === 'claimed' ? [7 * k, 5 * k] : []);
+ ctx.lineWidth = (rim === 'thin' ? 2.5 : 6) * k;
+ ctx.strokeStyle = color;
+ ctx.stroke();
+ ctx.setLineDash([]);
const style = NEWS_CATEGORIES[category] || NEWS_CATEGORIES.other;
- const icon = body * 0.56;
+ const icon = body * 0.5;
ctx.save();
ctx.translate(cx - icon / 2, cy - icon / 2);
ctx.scale(icon / 16, icon / 16);
@@ -59,9 +53,9 @@ export function paintMarker(ctx, { category, severity, rim }, cx, cy, body) {
ctx.lineCap = 'round';
ctx.lineJoin = 'round';
ctx.lineWidth = 1.7;
- ctx.strokeStyle = INK;
+ ctx.strokeStyle = color;
if (style.solid) {
- ctx.fillStyle = INK;
+ ctx.fillStyle = color;
ctx.fill(path);
} else ctx.stroke(path);
ctx.restore();
diff --git a/src/ui/news/NewsCard.vue b/src/ui/news/NewsCard.vue
index 46dbe5fb7..1f2381760 100644
--- a/src/ui/news/NewsCard.vue
+++ b/src/ui/news/NewsCard.vue
@@ -78,7 +78,10 @@ const meta = () =>
:title="item.severityLabel"
>{{ meta() }}{{
+ item.severityLabel
+ }}{{ item.severity >= 2 ? ` · ${meta()}` : meta() }}
{{ item.sides }}
{
);
assert.equal(
first.querySelector('.news-card__meta').textContent,
- 'Strike · Kharkiv · 4 sources · 10 min',
+ 'Major · Strike · Kharkiv · 4 sources · 10 min',
);
assert.equal(
first.querySelector('.news-card__marker').getAttribute('aria-label'),
diff --git a/src/ui/styles/news.css b/src/ui/styles/news.css
index e183548e6..fe35a4f08 100644
--- a/src/ui/styles/news.css
+++ b/src/ui/styles/news.css
@@ -172,16 +172,17 @@
.news-card {
border: 1px solid var(--glass-border);
- border-inline-start: 3px solid var(--news-color, var(--glass-border));
border-radius: 6px;
background: rgba(255, 255, 255, 0.02);
}
.news-card:hover,
.news-card.is-open {
border-color: var(--glass-border-hover);
- border-inline-start-color: var(--news-color);
background: rgba(0, 212, 255, 0.06);
}
+.news-card.is-open {
+ border-color: rgba(0, 212, 255, 0.45);
+}
.news-card.is-unmapped .news-card__title {
color: var(--text-secondary);
}
@@ -254,45 +255,59 @@
color: var(--text-secondary);
font: 10px var(--font-mono);
}
+/* The globe's marker in small: a dark disc, rim and icon in the severity
+ colour (sprites.js). */
.news-card__marker {
flex: none;
display: inline-grid;
place-items: center;
+ box-sizing: border-box;
width: 14px;
height: 14px;
- border: 1px solid rgba(0, 0, 0, 0.8);
+ border: 1.5px solid var(--news-color);
border-radius: 50%;
- background: var(--news-color);
+ background: var(--bg-dark);
}
.news-card__marker svg {
- width: 9px;
- height: 9px;
+ width: 8px;
+ height: 8px;
fill: none;
- stroke: var(--bg-dark);
+ stroke: var(--news-color);
stroke-width: 1.8;
stroke-linecap: round;
stroke-linejoin: round;
}
.news-card__marker.solid svg {
- fill: var(--bg-dark);
+ fill: var(--news-color);
stroke: none;
}
+.news-card__severity {
+ flex: none;
+ color: var(--news-color);
+ font-weight: 600;
+ letter-spacing: 0.08em;
+ text-transform: uppercase;
+}
.news-card__chips {
display: flex;
flex-wrap: wrap;
gap: 4px;
}
+/* GEV's read-only badge (status-badge.css), neutral unless attested. */
.news-chip {
- padding: 1px 6px;
+ padding: 2px 7px;
border: 1px solid var(--glass-border-hover);
border-radius: 999px;
color: var(--text-secondary);
- font: 9px var(--font-mono);
+ font: 8px var(--font-mono);
+ letter-spacing: 0.08em;
+ text-transform: uppercase;
}
.news-chip--official,
.news-chip--confirmed {
- border-color: rgba(0, 212, 255, 0.45);
- color: var(--text-primary);
+ border-color: rgba(0, 212, 255, 0.32);
+ background: rgba(0, 212, 255, 0.09);
+ color: #9defff;
}
.news-chip--claimed {
border-style: dashed;