From f3e206bf37805c569e155d8371c5be4715b39ca3 Mon Sep 17 00:00:00 2001 From: Denis Groshev Date: Wed, 4 Nov 2026 10:20:41 +0100 Subject: [PATCH] Draw news in GEV's own colours The news layer brought its own ColorBrewer yellow, orange and red, solid coloured marker discs, a coloured edge on every card, an amber developing ring and pink disputed areas, which read as foreign next to GEV's cyan glass. Severity now uses colours GEV already has (dim and primary text, the military layers' amber, the Cockpit's emergency red), drawn as GEV's dark disc with a severity rim and icon; attestation is still the rim's form. Cards lose the coloured edge and name significant and major events in a mono badge; attestation chips follow the status badge; outlines stay in the accent family with disputed areas dotted in the primary text colour. --- src/layers/news/cards.js | 11 ++++------ src/layers/news/index.js | 8 ++++--- src/layers/news/model.js | 16 ++++++++------ src/layers/news/model.test.mjs | 2 +- src/layers/news/sprites.js | 40 +++++++++++++++------------------- src/ui/news/NewsCard.vue | 5 ++++- src/ui/newsPanel.test.mjs | 2 +- src/ui/styles/news.css | 39 +++++++++++++++++++++++---------- 8 files changed, 68 insertions(+), 55 deletions(-) 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;