Back to City Breaker
SOURCE / PINNED RELEASE

Made of little things.

City Breaker

Release
393802aabdd1…
Author-recorded commit
d57116fff26b…
License
LICENSE
Author’s source reference
nostr://npub1sj2vketepqw9v6fmge5fdzuj7atj9sv6qv2jss05n2z6rl64lcfqumdsnt/wss%3A%2F%2Fgit.napplet.soy%2F/n-cf8b858653e

Archive hash verified: 81221cc4d1015fd3…. The source-to-build association is the author’s claim; it has not been independently rebuilt.

src/ui/icons.ts
// Inline SVG pictograms for the war-table planner.
const svg = (body: string, vb = '0 0 40 40') => `<svg viewBox="${vb}" class="ico" aria-hidden="true">${body}</svg>`;

const bombBody = (cx: number, cy: number, w: number, h: number, fill = '#d8d0b8') =>
  `<ellipse cx="${cx}" cy="${cy}" rx="${w}" ry="${h}" fill="${fill}"/><path d="M${cx - w * 0.9} ${cy - h - 3} h${w * 1.8} l-${w * 0.4} 4 h-${w} z" fill="${fill}"/>`;

/** A picture for each weapon. */
export function munIcon(m: string): string {
  switch (m) {
    case 'FAB-250': return svg(bombBody(20, 22, 4, 9));
    case 'FAB-500': return svg(bombBody(20, 22, 6, 12));
    case 'FAB-1500': return svg(bombBody(20, 22, 9, 15));
    case 'RBK-500': return svg(`${bombBody(15, 20, 5, 10)}<g fill="#ffd36a"><circle cx="28" cy="14" r="2"/><circle cx="32" cy="22" r="2"/><circle cx="27" cy="29" r="2"/><circle cx="33" cy="32" r="2"/></g>`);
    case 'ZAB-500': return svg(`${bombBody(16, 22, 5, 11)}<path d="M30 33c-6 0-7-6-3-10 0 3 2 4 3 3-1-4 1-8 4-10-1 4 4 6 3 11-1 4-3 6-7 6z" fill="#ff7a2a"/>`);
    case 'FAB-500-LD': return svg(`${bombBody(15, 22, 5, 11)}<circle cx="30" cy="22" r="8" fill="none" stroke="#ffd36a" stroke-width="2"/><path d="M30 17v5l3 3" stroke="#ffd36a" stroke-width="2" fill="none"/>`);
    case 'UPAB-1500': return svg(`${bombBody(17, 23, 7, 12, '#bfe0c8')}<rect x="26" y="8" width="10" height="8" rx="1" fill="none" stroke="#8cffaa" stroke-width="2"/><path d="M31 16v4" stroke="#8cffaa" stroke-width="2"/>`);
    case 'S-24': return svg(`<path d="M8 30 L28 10 l4 0 0 4 L12 34z" fill="#d8d0b8"/><path d="M8 30l-4 6 6-4z" fill="#ff7a2a"/>`);
    case 'KSR-11': return svg(`<path d="M6 30 L26 10 l5 0 0 5 L11 35z" fill="#ffb347"/><path d="M30 26a6 6 0 0 1 6 6M30 21a11 11 0 0 1 11 11" stroke="#ff6a5a" stroke-width="2" fill="none"/>`);
    case 'K-10S':
    case 'Kh-22': return svg(`<path d="M4 33 L28 9 l7 0 0 7 L11 40z" fill="#ffb347"/><path d="M22 11 l6 -4 M30 19 l4 -6" stroke="#ffb347" stroke-width="3"/>`);
    default: return svg('<circle cx="20" cy="20" r="6" fill="#888"/>');
  }
}

/** When a flight arrives: early arrows, on-time dot, late arrows. */
export function timingIcon(offset: number): string {
  if (offset === 0) return svg('<circle cx="20" cy="20" r="7" fill="#ffd36a"/>');
  const early = offset < 0;
  const n = Math.min(3, Math.round(Math.abs(offset) / 12));
  let body = '';
  for (let i = 0; i < n; i++) {
    const x = 8 + i * 9;
    body += early ? `<path d="M${x + 9} 12 l-9 8 9 8z" fill="#5fd3ff"/>` : `<path d="M${x} 12 l9 8 -9 8z" fill="#ff9a6a"/>`;
  }
  return svg(body);
}

export const ICONS = {
  chaff: svg('<path d="M8 30 l24 -20" stroke="#bfe9f2" stroke-width="3"/><g fill="#e8f0ff"><rect x="10" y="8" width="2" height="6"/><rect x="18" y="16" width="2" height="6"/><rect x="26" y="24" width="2" height="6"/><rect x="30" y="10" width="2" height="6"/><rect x="14" y="26" width="2" height="6"/></g>'),
  drone: svg('<path d="M20 6 l4 14 12 6 -12 2 -4 6 -4-6 -12-2 12-6z" fill="#d8cfa0"/>'),
  tanker: svg('<path d="M6 18 h20 l8 -6 v14 l-8 -6 h-20z" fill="#9fb4b8"/><path d="M14 20 q2 10 12 12" stroke="#ffd36a" stroke-width="2" fill="none"/><circle cx="27" cy="32" r="2" fill="#ffd36a"/>'),
  star: svg('<path d="M20 5 l4.5 9.5 10.5 1.5 -7.6 7.4 1.8 10.4 -9.2-4.9 -9.2 4.9 1.8-10.4 -7.6-7.4 10.5-1.5z" fill="#ffd36a"/>'),
  trash: svg('<path d="M12 12 h16 l-2 22 h-12z M10 10 h20 M16 7 h8" stroke="#ff9a8a" stroke-width="3" fill="none"/>'),
  minus: svg('<path d="M10 20 h20" stroke="#e8f8fc" stroke-width="5" stroke-linecap="round"/>'),
  plus: svg('<path d="M10 20 h20 M20 10 v20" stroke="#e8f8fc" stroke-width="5" stroke-linecap="round"/>'),
  direct: svg('<path d="M36 20 H6" stroke="#ffb347" stroke-width="3"/><path d="M6 20 l8 -6 v12z" fill="#ffb347"/><path d="M22 6 v28" stroke="#ff4a3a" stroke-width="2" stroke-dasharray="3 3"/>'),
  north: svg('<path d="M36 26 C30 4 12 4 6 20" stroke="#7dffa0" stroke-width="3" fill="none"/><path d="M6 20 l2 -9 6 6z" fill="#7dffa0"/><path d="M22 14 v24" stroke="#ff4a3a" stroke-width="2" stroke-dasharray="3 3"/>'),
  south: svg('<path d="M36 14 C30 36 12 36 6 20" stroke="#7dffa0" stroke-width="3" fill="none"/><path d="M6 20 l8 -2 -6 -7z" fill="#7dffa0"/><path d="M22 2 v24" stroke="#ff4a3a" stroke-width="2" stroke-dasharray="3 3"/>'),
  target: svg('<path d="M20 4 l16 16 -16 16 -16 -16z" fill="none" stroke="#ffd36a" stroke-width="3"/><circle cx="20" cy="20" r="3" fill="#ffd36a"/>'),
  radar: svg('<path d="M10 34 l10 -26 10 26" stroke="#ff6a5a" stroke-width="3" fill="none"/><path d="M8 10 h24" stroke="#ff6a5a" stroke-width="3"/>'),
  skull: svg('<path d="M20 6c-8 0-12 5-12 11 0 4 2 6 4 7v5h16v-5c2-1 4-3 4-7 0-6-4-11-12-11z" fill="#ff6a5a"/><circle cx="15" cy="18" r="3" fill="#2a0806"/><circle cx="25" cy="18" r="3" fill="#2a0806"/>'),
  eye: svg('<path d="M4 20 q16 -16 32 0 q-16 16 -32 0z" fill="none" stroke="#ffd36a" stroke-width="2.5"/><circle cx="20" cy="20" r="5" fill="#ffd36a"/>'),
  fuel: svg('<rect x="8" y="8" width="18" height="26" rx="2" fill="none" stroke="#ffd36a" stroke-width="3"/><rect x="12" y="12" width="10" height="6" fill="#ffd36a"/><path d="M26 14 l6 4 v12" stroke="#ffd36a" stroke-width="3" fill="none"/>'),
  stop: svg('<circle cx="20" cy="20" r="15" fill="#c4231c"/><path d="M12 20 h16" stroke="#fff" stroke-width="5"/>'),
  warn: svg('<path d="M20 5 l17 30 h-34z" fill="#ffb347"/><path d="M20 15 v10 M20 29 v2" stroke="#2a1606" stroke-width="3.5"/>'),
  people: svg('<g fill="#9fc4d4"><circle cx="13" cy="13" r="5"/><circle cx="27" cy="13" r="5"/><path d="M5 34 c0-9 16-9 16 0z M19 34 c0-9 16-9 16 0z"/></g>'),
  list: svg('<path d="M8 11 h24 M8 20 h24 M8 29 h24" stroke="#e8f8fc" stroke-width="3.5" stroke-linecap="round"/>'),
};