OverviewFilesCommitsIssuesDocsReleasesPeers

P2P-Hiverelay / packages / core / cli / banner.js

312 lines · javascript ref:
/**
 * Cypherpunk ASCII banners for the HiveRelay TUI.
 * Pure strings + ANSI escapes — no dependencies.
 */

const ESC = '\x1b['
const RESET = ESC + '0m'
const BOLD = ESC + '1m'

const C = {
  green: ESC + '38;5;46m',
  cyan: ESC + '38;5;51m',
  magenta: ESC + '38;5;201m',
  purple: ESC + '38;5;135m',
  yellow: ESC + '38;5;226m',
  orange: ESC + '38;5;208m',
  red: ESC + '38;5;196m',
  blue: ESC + '38;5;39m',
  pink: ESC + '38;5;213m',
  dim: ESC + '38;5;240m',
  grey: ESC + '38;5;245m',
  white: ESC + '38;5;255m'
}

const useColor = () => process.stdout.isTTY && !process.env.NO_COLOR

function paint (color, text) {
  if (!useColor()) return text
  return color + text + RESET
}

// ─── The big boy — 3D extruded, shown on `start` ──────────────────
//
// Layers (front-to-back):
//   1. Top highlight  — bright white bevel above each letter (▁ chars)
//   2. Main face      — gradient-colored solid block letters
//   3. Depth wall     — dim colored extrusion to lower-right (╲ diagonals)
//   4. Floor shadow   — dim offset ghost below (▀ half-blocks, -2 opacity)
//
// The result simulates a 3D-extruded logo lit from the upper-left, with the
// letters floating slightly above a reflective floor.

// Top highlight — thin bright bevel sitting on top of each letter's face
const MAIN_TOP_BEVEL = '    ▁▁   ▁▁ ▁▁ ▁▁   ▁▁ ▁▁▁▁▁▁ ▁▁▁▁▁▁  ▁▁▁▁▁▁ ▁▁      ▁▁▁▁▁  ▁▁   ▁▁ '

const MAIN_LOGO = [
  '    ██╗  ██╗██╗██╗   ██╗███████╗██████╗ ███████╗██╗      █████╗ ██╗   ██╗',
  '    ██║  ██║██║██║   ██║██╔════╝██╔══██╗██╔════╝██║     ██╔══██╗╚██╗ ██╔╝',
  '    ███████║██║██║   ██║█████╗  ██████╔╝█████╗  ██║     ███████║ ╚████╔╝ ',
  '    ██╔══██║██║╚██╗ ██╔╝██╔══╝  ██╔══██╗██╔══╝  ██║     ██╔══██║  ╚██╔╝  ',
  '    ██║  ██║██║ ╚████╔╝ ███████╗██║  ██║███████╗███████╗██║  ██║   ██║   ',
  '    ╚═╝  ╚═╝╚═╝  ╚═══╝  ╚══════╝╚═╝  ╚═╝╚══════╝╚══════╝╚═╝  ╚═╝   ╚═╝   '
]

// Depth-wall — a single row of slim diagonal strokes under the logo,
// offset slightly right to simulate the extrusion casting downward. The
// characters match the column positions of the bottom of each letter.
const MAIN_DEPTH_WALL = '     ▝▘  ▝▘▝▘  ▝▘   ▝▘▝▘▝▘▝▘ ▝▘▝▘▝▘  ▝▘▝▘▝▘▝▘ ▝▘     ▝▘▝▘▝▘  ▝▘   ▝▘  '

// Floor reflection — half-block ▀ under the main logo, slightly offset
const MAIN_FLOOR = '      ▀▀  ▀▀▀▀  ▀▀   ▀▀▀▀▀▀▀ ▀▀▀▀▀▀  ▀▀▀▀▀▀ ▀▀      ▀▀▀▀▀  ▀▀   ▀▀  '

// Honeycomb hive glyph — three interlocking hexagons
const HIVE_GLYPH = [
  '           ⬡⬢⬡           ',
  '         ⬢     ⬢         ',
  '       ⬡   ▲▲▲   ⬡       ',
  '         ⬢  █  ⬢         ',
  '           ⬡⬢⬡           ',
  '         ⬢     ⬢         ',
  '       ⬡         ⬡       ',
  '         ⬢     ⬢         ',
  '           ⬡⬢⬡           '
]

const TAGLINES = [
  '// always-on p2p infrastructure. no central server. no vendor lock.',
  '// encrypted-at-rest. dht-native. federated. unstoppable.',
  '// your laptop sleeps. your app stays online.',
  '// no backend. no account. just keys and peers.',
  '// the relay is not a server — it is a peer that remembers.',
  '// ctrl+c the author. content persists.',
  '// signed, replicated, discoverable. stateless at the edges.',
  '// WE ARE THE INFRASTRUCTURE.'
]

const MATRIX_RAIN = '01· ▚▚ ·10 ▞▞ 11 ·0· ▚▞ 10· ▞▚ 01 ▞▞ ·1· ▚▚ 10· ▞▞ 00 ▚▞ ·1·'

// ─── Helpers ─────────────────────────────────────────────────────

function pick (arr) { return arr[Math.floor(Math.random() * arr.length)] }

function rainbowLine (line) {
  if (!useColor()) return line
  const palette = [C.cyan, C.blue, C.purple, C.magenta, C.pink, C.magenta, C.purple, C.blue]
  let out = ''
  for (let i = 0; i < line.length; i++) {
    const ch = line[i]
    if (ch === ' ') { out += ' '; continue }
    out += palette[i % palette.length] + ch
  }
  return out + RESET
}

// 3D-extruded logo: top bevel + gradient face + depth wall + floor shadow.
// Drawn as stacked rows. In monochrome terminals it falls back to the flat
// gradient, because the depth effect relies entirely on color contrast.
function logo3D () {
  if (!useColor()) return MAIN_LOGO.join('\n')
  const faceGradient = [C.cyan, C.blue, C.purple, C.magenta, C.pink, C.magenta]
  const depthGradient = [C.dim, C.dim, C.grey, C.grey, C.dim, C.dim]

  const lines = []
  // Top highlight bevel — one row of bright ▁ above the logo
  lines.push(paint(C.white + BOLD, MAIN_TOP_BEVEL))
  // Main face with vertical gradient (cyan → blue → purple → magenta → pink)
  for (let i = 0; i < MAIN_LOGO.length; i++) {
    lines.push(paint(faceGradient[i % faceGradient.length] + BOLD, MAIN_LOGO[i]))
  }
  // Depth wall — single row of diagonal extrusion marks below the logo
  lines.push(paint(depthGradient[0], MAIN_DEPTH_WALL))
  // Floor shadow — dim half-block reflection
  lines.push(paint(C.grey, MAIN_FLOOR))
  return lines.join('\n')
}

// ─── Public banners ──────────────────────────────────────────────

export function mainBanner (version) {
  const tagline = pick(TAGLINES)
  // Cyberpunk frame corners — looks like an old CRT / oscilloscope
  const topFrame = paint(C.cyan, '   ╱═══') + paint(C.dim, '╾──────────────────────────────────────────────────────────╼') + paint(C.cyan, '═══╲')
  const botFrame = paint(C.cyan, '   ╲═══') + paint(C.dim, '╾──────────────────────────────────────────────────────────╼') + paint(C.cyan, '═══╱')
  const lines = [
    '',
    topFrame,
    logo3D(),
    '',
    '         ' + rainbowLine('▚▞▚▞▚▞▚▞  p2p relay infrastructure  ▚▞▚▞▚▞▚▞'),
    '',
    '   ' + paint(C.green, 'p2p-hiverelay v' + version) + '  ' + paint(C.dim, tagline),
    botFrame,
    ''
  ]
  return lines.join('\n')
}

export function setupBanner (version) {
  const lines = [
    '',
    paint(C.cyan + BOLD, '  ╔══════════════════════════════════════════════════════════════╗'),
    paint(C.cyan + BOLD, '  ║') + paint(C.magenta + BOLD, '         H I V E R E L A Y  ·  I N I T I A L I Z E            ') + paint(C.cyan + BOLD, '║'),
    paint(C.cyan + BOLD, '  ║') + paint(C.dim, '              [ cypherpunk p2p infrastructure ]               ') + paint(C.cyan + BOLD, '║'),
    paint(C.cyan + BOLD, '  ╚══════════════════════════════════════════════════════════════╝'),
    '',
    '  ' + paint(C.green, '>') + ' ' + paint(C.white, 'p2p-hiverelay ') + paint(C.dim, 'v' + version) + '  ' + paint(C.cyan, '// no gods. no masters. just peers.'),
    ''
  ]
  return lines.join('\n')
}

export function testnetBanner (version) {
  const lines = [
    '',
    paint(C.yellow, '    ╓─╥──────────────────────────────────────────────╥─╖'),
    paint(C.yellow, '    ║ ') + paint(C.green + BOLD, '⬢') + paint(C.cyan + BOLD, '  H I V E R E L A Y  ') + paint(C.purple, '╳  ') + paint(C.pink + BOLD, 'T E S T N E T') + '  ' + paint(C.green + BOLD, '⬡') + paint(C.yellow, ' ║'),
    paint(C.yellow, '    ║ ') + paint(C.dim, '  three relays · one client · zero external deps') + paint(C.yellow, '  ║'),
    paint(C.yellow, '    ╙─╨──────────────────────────────────────────────╨─╜'),
    '',
    '  ' + paint(C.dim, '[') + paint(C.green, 'ok') + paint(C.dim, '] ') + paint(C.white, 'v' + version) + '  ' + paint(C.magenta, MATRIX_RAIN),
    ''
  ]
  return lines.join('\n')
}

export function statusBanner () {
  const lines = [
    '',
    '  ' + paint(C.green, '⬢') + paint(C.cyan, '⬡') + paint(C.green, '⬢') + ' ' + paint(C.cyan + BOLD, 'HIVERELAY STATUS') + ' ' + paint(C.green, '⬢') + paint(C.cyan, '⬡') + paint(C.green, '⬢'),
    '  ' + paint(C.dim, '────────────────────────'),
    ''
  ]
  return lines.join('\n')
}

export function helpBanner (version) {
  const lines = [
    '',
    paint(C.green + BOLD, '     ┌─────────────────────────────────────────────────────────────┐'),
    paint(C.green + BOLD, '     │  ') + paint(C.cyan + BOLD, 'H   I   V   E   R   E   L   A   Y') + '   ' + paint(C.magenta, 'v' + version) + '  ' + '  '.padEnd(9) + paint(C.green + BOLD, '│'),
    paint(C.green + BOLD, '     │  ') + paint(C.dim, '// peer-to-peer relay infrastructure for the cypherpunk era') + paint(C.green + BOLD, '│'),
    paint(C.green + BOLD, '     └─────────────────────────────────────────────────────────────┘'),
    ''
  ]
  return lines.join('\n')
}

// Operator management console — shown when entering `hiverelay manage` TUI.
// Clears screen and displays a compact 3D logo + connection strip so the
// operator immediately knows what they're configuring.
//
// Mini 3-row font (each letter is 3-rows × 3-cols, separated by 1 space).
// Designed so each glyph is obviously its letter at a glance. Letters that
// are naturally narrow (I, L) are still 3 cols wide for even spacing.
const MINI_FONT = {
  H: ['█ █', '███', '█ █'],
  I: [' █ ', ' █ ', ' █ '],
  V: ['█ █', '█ █', ' █ '],
  E: ['███', '██ ', '███'],
  R: ['██▖', '██▘', '█ █'],
  L: ['█  ', '█  ', '███'],
  A: ['▗█▖', '███', '█ █'],
  Y: ['█ █', '▝█▘', ' █ ']
}

export function manageBanner (host, port, version) {
  const endpoint = `${host}:${port}`

  if (!useColor()) {
    return [
      '',
      '  ╔' + '═'.repeat(68) + '╗',
      '  ║   HIVERELAY MANAGEMENT CONSOLE · v' + version + ' '.repeat(Math.max(0, 30 - version.length)) + '║',
      '  ║   Connected → ' + endpoint.padEnd(52) + '║',
      '  ╚' + '═'.repeat(68) + '╝',
      ''
    ].join('\n')
  }

  // Gradient — one palette entry per letter of HIVERELAY
  const word = 'HIVERELAY'
  const palette = [C.cyan, C.blue, C.purple, C.magenta, C.pink, C.magenta, C.purple, C.blue, C.cyan]

  // Build three rows, each row of the word glued together
  const rows = [[], [], []]
  for (let i = 0; i < word.length; i++) {
    const letter = MINI_FONT[word[i]]
    const color = palette[i] + BOLD
    for (let r = 0; r < 3; r++) {
      rows[r].push(paint(color, letter[r]))
    }
  }
  const titleRows = rows.map(r => r.join(' '))

  const frameTop = paint(C.cyan, '  ╭────') + paint(C.dim, '─'.repeat(60)) + paint(C.cyan, '────╮')
  const frameBot = paint(C.cyan, '  ╰────') + paint(C.dim, '─'.repeat(60)) + paint(C.cyan, '────╯')
  const sep = '    ' // space between logo and label

  const clear = '\x1b[2J\x1b[H'
  return [
    clear,
    '',
    frameTop,
    '  ' + paint(C.cyan, '│ ') + titleRows[0] + sep + paint(C.white + BOLD, 'MANAGEMENT  CONSOLE') + '       ' + paint(C.cyan, '│'),
    '  ' + paint(C.cyan, '│ ') + titleRows[1] + sep + paint(C.dim, '// operator control plane  ') + '   ' + paint(C.cyan, '│'),
    '  ' + paint(C.cyan, '│ ') + titleRows[2] + sep + paint(C.dim, '// ctrl+c to exit · q to back') + ' ' + paint(C.cyan, '│'),
    frameBot,
    '',
    '  ' + paint(C.green, '⬢') + ' ' + paint(C.cyan, 'link ') + paint(C.white + BOLD, endpoint) +
      '   ' + paint(C.green, '⬢') + ' ' + paint(C.cyan, 'version ') + paint(C.magenta, 'v' + version) +
      '   ' + paint(C.green, '⬢') + ' ' + paint(C.dim, pick(TAGLINES)),
    ''
  ].join('\n')
}

// Sub-menu header — used inside each settings page so every screen feels
// part of the same cypherpunk UI.
export function sectionHeader (title, subtitle = '') {
  if (!useColor()) {
    return [
      '',
      '  ── ' + title + ' ' + '─'.repeat(Math.max(4, 60 - title.length)),
      subtitle ? '  // ' + subtitle : null,
      ''
    ].filter(Boolean).join('\n')
  }
  const bar = '─'.repeat(Math.max(4, 60 - title.length))
  return [
    '',
    '  ' + paint(C.cyan + BOLD, '▓▓▓') + ' ' + paint(C.magenta + BOLD, title.toUpperCase()) +
      ' ' + paint(C.cyan, bar),
    subtitle ? '  ' + paint(C.dim, '// ' + subtitle) : null,
    ''
  ].filter(Boolean).join('\n')
}

export function shutdownBanner () {
  if (!useColor()) return '\n  signing off. fnord.\n'
  const lines = [
    '',
    '  ' + paint(C.red, '⬢') + ' ' + paint(C.dim, 'disconnecting from the swarm...'),
    '  ' + paint(C.dim, '// until next time, keep your keys close and your peers closer.'),
    ''
  ]
  return lines.join('\n')
}

export function divider (ch = '─', color = C.dim) {
  const width = Math.min(process.stdout.columns || 78, 78)
  return paint(color, ch.repeat(width))
}

// Mini hex icon — useful inline
export const HEX = useColor() ? paint(C.green, '⬢') : '*'
export const HEX_DIM = useColor() ? paint(C.dim, '⬡') : '.'
export const OK = useColor() ? paint(C.green, '✓') : '[ok]'
export const WARN = useColor() ? paint(C.yellow, '⚠') : '[!]'
export const ERR = useColor() ? paint(C.red, '✗') : '[x]'
export const ARROW = useColor() ? paint(C.cyan, '▶') : '>'

export { C, paint, HIVE_GLYPH, MATRIX_RAIN }