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;