diff --git a/packages/opencode/src/cli/cmd/tui/component/bg-pulse.tsx b/packages/opencode/src/cli/cmd/tui/component/bg-pulse.tsx
new file mode 100644
index 0000000000..1c7e78b532
--- /dev/null
+++ b/packages/opencode/src/cli/cmd/tui/component/bg-pulse.tsx
@@ -0,0 +1,93 @@
+import { BoxRenderable, RGBA } from "@opentui/core"
+import { createMemo, createSignal, For, onCleanup, onMount } from "solid-js"
+import { tint, useTheme } from "@tui/context/theme"
+
+const PERIOD = 4600
+const RINGS = 3
+const WIDTH = 4.2
+const TAIL = 10.5
+const AMP = 0.4
+const TAIL_AMP = 0.14
+const BREATH_AMP = 0.06
+const BREATH_SPEED = 0.0008
+// Offset so bg ring emits from GO center at the moment the logo shine arrives there.
+// Logo shine travels ~0.29 of its period to cross from origin to GO center.
+const PHASE_OFFSET = 0.29
+
+export function BgPulse(props: { centerX?: number; centerY?: number }) {
+ const { theme } = useTheme()
+ const [now, setNow] = createSignal(performance.now())
+ const [size, setSize] = createSignal<{ width: number; height: number }>({ width: 0, height: 0 })
+ let box: BoxRenderable | undefined
+
+ const timer = setInterval(() => setNow(performance.now()), 50)
+ onCleanup(() => clearInterval(timer))
+
+ const sync = () => {
+ if (!box) return
+ setSize({ width: box.width, height: box.height })
+ }
+
+ onMount(() => {
+ sync()
+ box?.on("resize", sync)
+ })
+
+ onCleanup(() => {
+ box?.off("resize", sync)
+ })
+
+ const grid = createMemo(() => {
+ const t = now()
+ const w = size().width
+ const h = size().height
+ if (w === 0 || h === 0) return [] as RGBA[][]
+ const cxv = props.centerX ?? w / 2
+ const cyv = props.centerY ?? h / 2
+ const reach = Math.hypot(Math.max(cxv, w - cxv), Math.max(cyv, h - cyv) * 2) + TAIL
+ const rows = [] as RGBA[][]
+ for (let y = 0; y < h; y++) {
+ const row = [] as RGBA[]
+ for (let x = 0; x < w; x++) {
+ const dx = x + 0.5 - cxv
+ const dy = (y + 0.5 - cyv) * 2
+ const dist = Math.hypot(dx, dy)
+ let level = 0
+ for (let i = 0; i < RINGS; i++) {
+ const offset = i / RINGS
+ const phase = (t / PERIOD + offset - PHASE_OFFSET + 1) % 1
+ const envelope = Math.sin(phase * Math.PI)
+ const eased = envelope * envelope * (3 - 2 * envelope)
+ const head = phase * reach
+ const delta = dist - head
+ const crest = Math.abs(delta) < WIDTH ? 0.5 + 0.5 * Math.cos((delta / WIDTH) * Math.PI) : 0
+ const tail = delta < 0 && delta > -TAIL ? (1 + delta / TAIL) ** 2.3 : 0
+ level += (crest * AMP + tail * TAIL_AMP) * eased
+ }
+ const breath = (0.5 + 0.5 * Math.sin(t * BREATH_SPEED)) * BREATH_AMP
+ const strength = Math.min(1, level / RINGS + breath)
+ row.push(tint(theme.backgroundPanel, theme.primary, strength * 0.7))
+ }
+ rows.push(row)
+ }
+ return rows
+ })
+
+ return (
+ (box = item)} width="100%" height="100%">
+
+ {(row) => (
+
+
+ {(color) => (
+
+ {" "}
+
+ )}
+
+
+ )}
+
+
+ )
+}
diff --git a/packages/opencode/src/cli/cmd/tui/component/dialog-go-upsell.tsx b/packages/opencode/src/cli/cmd/tui/component/dialog-go-upsell.tsx
index 2d200ca3b8..da91b1323a 100644
--- a/packages/opencode/src/cli/cmd/tui/component/dialog-go-upsell.tsx
+++ b/packages/opencode/src/cli/cmd/tui/component/dialog-go-upsell.tsx
@@ -1,12 +1,16 @@
-import { RGBA, TextAttributes } from "@opentui/core"
+import { BoxRenderable, RGBA, TextAttributes } from "@opentui/core"
import { useKeyboard } from "@opentui/solid"
import open from "open"
-import { createSignal } from "solid-js"
+import { createSignal, onCleanup, onMount } from "solid-js"
import { selectedForeground, useTheme } from "@tui/context/theme"
import { useDialog, type DialogContext } from "@tui/ui/dialog"
import { Link } from "@tui/ui/link"
+import { GoLogo } from "./logo"
+import { BgPulse } from "./bg-pulse"
const GO_URL = "https://opencode.ai/go"
+const PAD_X = 3
+const PAD_TOP_OUTER = 1
export type DialogGoUpsellProps = {
onClose?: (dontShowAgain?: boolean) => void
@@ -27,62 +31,96 @@ export function DialogGoUpsell(props: DialogGoUpsellProps) {
const dialog = useDialog()
const { theme } = useTheme()
const fg = selectedForeground(theme)
- const [selected, setSelected] = createSignal(0)
+ const [selected, setSelected] = createSignal<"dismiss" | "subscribe">("subscribe")
+ const [center, setCenter] = createSignal<{ x: number; y: number } | undefined>()
+ let content: BoxRenderable | undefined
+ let logoBox: BoxRenderable | undefined
+
+ const sync = () => {
+ if (!content || !logoBox) return
+ setCenter({
+ x: logoBox.x - content.x + logoBox.width / 2,
+ y: logoBox.y - content.y + logoBox.height / 2,
+ })
+ }
+
+ onMount(() => {
+ sync()
+ content?.on("resize", sync)
+ logoBox?.on("resize", sync)
+ })
+
+ onCleanup(() => {
+ content?.off("resize", sync)
+ logoBox?.off("resize", sync)
+ })
useKeyboard((evt) => {
if (evt.name === "left" || evt.name === "right" || evt.name === "tab") {
- setSelected((s) => (s === 0 ? 1 : 0))
+ setSelected((s) => (s === "subscribe" ? "dismiss" : "subscribe"))
return
}
- if (evt.name !== "return") return
- if (selected() === 0) subscribe(props, dialog)
- else dismiss(props, dialog)
+ if (evt.name === "return") {
+ if (selected() === "subscribe") subscribe(props, dialog)
+ else dismiss(props, dialog)
+ }
})
return (
-
-
-
- Free limit reached
-
- dialog.clear()}>
- esc
-
+ (content = item)}>
+
+
-
-
- Subscribe to OpenCode Go to keep going with reliable access to the best open-source models, starting at
- $5/month.
-
-
+
+
+
+ Free limit reached
+
+ dialog.clear()}>
+ esc
+
+
+
+
+ Subscribe to OpenCode Go to keep going with reliable access to the best open-source models, starting at
+ $5/month.
+
+
+
+ (logoBox = item)}>
+
+
-
-
- setSelected(0)}
- onMouseUp={() => subscribe(props, dialog)}
- >
-
- subscribe
-
-
- setSelected(1)}
- onMouseUp={() => dismiss(props, dialog)}
- >
-
+ setSelected("dismiss")}
+ onMouseUp={() => dismiss(props, dialog)}
>
- don't show again
-
+
+ don't show again
+
+
+ setSelected("subscribe")}
+ onMouseUp={() => subscribe(props, dialog)}
+ >
+
+ subscribe
+
+
diff --git a/packages/opencode/src/cli/cmd/tui/component/logo.tsx b/packages/opencode/src/cli/cmd/tui/component/logo.tsx
index e53974871a..4eb82992cb 100644
--- a/packages/opencode/src/cli/cmd/tui/component/logo.tsx
+++ b/packages/opencode/src/cli/cmd/tui/component/logo.tsx
@@ -2,7 +2,24 @@ import { BoxRenderable, MouseButton, MouseEvent, RGBA, TextAttributes } from "@o
import { For, createMemo, createSignal, onCleanup, type JSX } from "solid-js"
import { useTheme, tint } from "@tui/context/theme"
import * as Sound from "@tui/util/sound"
-import { logo } from "@/cli/logo"
+import { go, logo } from "@/cli/logo"
+
+export type LogoShape = {
+ left: string[]
+ right: string[]
+}
+
+const IDLE_PERIOD = 4600
+const IDLE_RINGS = 3
+const IDLE_WIDTH = 1.4
+const IDLE_TAIL = 6.5
+const IDLE_AMP = 0.9
+const IDLE_PEAK = 2.2
+const IDLE_TAIL_AMP = 0.3
+const IDLE_PULSE = 0.0014
+const IDLE_PULSE_AMP = 0.08
+const IDLE_NOISE = 0.14
+const IDLE_ORIGIN = { x: -1.2, y: -0.8 }
// Shadow markers (rendered chars in parens):
// _ = full shadow cell (space with bg=shadow)
@@ -74,9 +91,6 @@ type Frame = {
spark: number
}
-const LEFT = logo.left[0]?.length ?? 0
-const FULL = logo.left.map((line, i) => line + " ".repeat(GAP) + logo.right[i])
-const SPAN = Math.hypot(FULL[0]?.length ?? 0, FULL.length * 2) * 0.94
const NEAR = [
[1, 0],
[1, 1],
@@ -140,7 +154,7 @@ function noise(x: number, y: number, t: number) {
}
function lit(char: string) {
- return char !== " " && char !== "_" && char !== "~"
+ return char !== " " && char !== "_" && char !== "~" && char !== ","
}
function key(x: number, y: number) {
@@ -188,12 +202,12 @@ function route(list: Array<{ x: number; y: number }>) {
return path
}
-function mapGlyphs() {
+function mapGlyphs(full: string[]) {
const cells = [] as Array<{ x: number; y: number }>
- for (let y = 0; y < FULL.length; y++) {
- for (let x = 0; x < (FULL[y]?.length ?? 0); x++) {
- if (lit(FULL[y]?.[x] ?? " ")) cells.push({ x, y })
+ for (let y = 0; y < full.length; y++) {
+ for (let x = 0; x < (full[y]?.length ?? 0); x++) {
+ if (lit(full[y]?.[x] ?? " ")) cells.push({ x, y })
}
}
@@ -237,9 +251,25 @@ function mapGlyphs() {
return { glyph, trace, center }
}
-const MAP = mapGlyphs()
+type LogoContext = {
+ LEFT: number
+ FULL: string[]
+ SPAN: number
+ MAP: ReturnType
+ shape: LogoShape
+}
-function shimmer(x: number, y: number, frame: Frame) {
+function build(shape: LogoShape): LogoContext {
+ const LEFT = shape.left[0]?.length ?? 0
+ const FULL = shape.left.map((line, i) => line + " ".repeat(GAP) + shape.right[i])
+ const SPAN = Math.hypot(FULL[0]?.length ?? 0, FULL.length * 2) * 0.94
+ return { LEFT, FULL, SPAN, MAP: mapGlyphs(FULL), shape }
+}
+
+const DEFAULT = build(logo)
+const GO = build(go)
+
+function shimmer(x: number, y: number, frame: Frame, ctx: LogoContext) {
return frame.list.reduce((best, item) => {
const age = frame.t - item.at
if (age < SHIMMER_IN || age > LIFE) return best
@@ -247,7 +277,7 @@ function shimmer(x: number, y: number, frame: Frame) {
const dy = y * 2 + 1 - item.y
const dist = Math.hypot(dx, dy)
const p = age / LIFE
- const r = SPAN * (1 - (1 - p) ** EXPAND)
+ const r = ctx.SPAN * (1 - (1 - p) ** EXPAND)
const lag = r - dist
if (lag < 0.18 || lag > SHIMMER_OUT) return best
const band = Math.exp(-(((lag - 1.05) / 0.68) ** 2))
@@ -258,19 +288,19 @@ function shimmer(x: number, y: number, frame: Frame) {
}, 0)
}
-function remain(x: number, y: number, item: Release, t: number) {
+function remain(x: number, y: number, item: Release, t: number, ctx: LogoContext) {
const age = t - item.at
if (age < 0 || age > LIFE) return 0
const p = age / LIFE
const dx = x + 0.5 - item.x - 0.5
const dy = y * 2 + 1 - item.y * 2 - 1
const dist = Math.hypot(dx, dy)
- const r = SPAN * (1 - (1 - p) ** EXPAND)
+ const r = ctx.SPAN * (1 - (1 - p) ** EXPAND)
if (dist > r) return 1
return clamp((r - dist) / 1.35 < 1 ? 1 - (r - dist) / 1.35 : 0)
}
-function wave(x: number, y: number, frame: Frame, live: boolean) {
+function wave(x: number, y: number, frame: Frame, live: boolean, ctx: LogoContext) {
return frame.list.reduce((sum, item) => {
const age = frame.t - item.at
if (age < 0 || age > LIFE) return sum
@@ -278,7 +308,7 @@ function wave(x: number, y: number, frame: Frame, live: boolean) {
const dx = x + 0.5 - item.x
const dy = y * 2 + 1 - item.y
const dist = Math.hypot(dx, dy)
- const r = SPAN * (1 - (1 - p) ** EXPAND)
+ const r = ctx.SPAN * (1 - (1 - p) ** EXPAND)
const fade = (1 - p) ** 1.32
const j = 1.02 + noise(x + item.x * 0.7, y + item.y * 0.7, item.at * 0.002 + age * 0.06) * 0.52
const edge = Math.exp(-(((dist - r) / WIDTH) ** 2)) * GAIN * fade * item.force * j
@@ -292,7 +322,7 @@ function wave(x: number, y: number, frame: Frame, live: boolean) {
}, 0)
}
-function field(x: number, y: number, frame: Frame) {
+function field(x: number, y: number, frame: Frame, ctx: LogoContext) {
const held = frame.hold
const rest = frame.release
const item = held ?? rest
@@ -326,11 +356,11 @@ function field(x: number, y: number, frame: Frame) {
Math.max(0, noise(item.x * 3.1, item.y * 2.7, frame.t * 1.7) - 0.72) *
Math.exp(-(dist * dist) / 0.15) *
lerp(0.08, 0.42, body)
- const fade = frame.release && !frame.hold ? remain(x, y, frame.release, frame.t) : 1
+ const fade = frame.release && !frame.hold ? remain(x, y, frame.release, frame.t, ctx) : 1
return (core + shell + ember + ring + fork + glitch + lash + flicker - dim) * fade
}
-function pick(x: number, y: number, frame: Frame) {
+function pick(x: number, y: number, frame: Frame, ctx: LogoContext) {
const held = frame.hold
const rest = frame.release
const item = held ?? rest
@@ -339,26 +369,26 @@ function pick(x: number, y: number, frame: Frame) {
const dx = x + 0.5 - item.x - 0.5
const dy = y * 2 + 1 - item.y * 2 - 1
const dist = Math.hypot(dx, dy)
- const fade = frame.release && !frame.hold ? remain(x, y, frame.release, frame.t) : 1
+ const fade = frame.release && !frame.hold ? remain(x, y, frame.release, frame.t, ctx) : 1
return Math.exp(-(dist * dist) / 1.7) * lerp(0.2, 0.96, rise) * fade
}
-function select(x: number, y: number) {
- const direct = MAP.glyph.get(key(x, y))
+function select(x: number, y: number, ctx: LogoContext) {
+ const direct = ctx.MAP.glyph.get(key(x, y))
if (direct !== undefined) return direct
- const near = NEAR.map(([dx, dy]) => MAP.glyph.get(key(x + dx, y + dy))).find(
+ const near = NEAR.map(([dx, dy]) => ctx.MAP.glyph.get(key(x + dx, y + dy))).find(
(item): item is number => item !== undefined,
)
return near
}
-function trace(x: number, y: number, frame: Frame) {
+function trace(x: number, y: number, frame: Frame, ctx: LogoContext) {
const held = frame.hold
const rest = frame.release
const item = held ?? rest
if (!item || item.glyph === undefined) return 0
- const step = MAP.trace.get(key(x, y))
+ const step = ctx.MAP.trace.get(key(x, y))
if (!step || step.glyph !== item.glyph || step.l < 2) return 0
const age = frame.t - item.at
const rise = held ? ramp(age, HOLD, CHARGE) : rest!.rise
@@ -368,29 +398,70 @@ function trace(x: number, y: number, frame: Frame) {
const dist = Math.min(Math.abs(step.i - head), step.l - Math.abs(step.i - head))
const tail = (head - TAIL + step.l) % step.l
const lag = Math.min(Math.abs(step.i - tail), step.l - Math.abs(step.i - tail))
- const fade = frame.release && !frame.hold ? remain(x, y, frame.release, frame.t) : 1
+ const fade = frame.release && !frame.hold ? remain(x, y, frame.release, frame.t, ctx) : 1
const core = Math.exp(-((dist / 1.05) ** 2)) * lerp(0.8, 2.35, rise)
const glow = Math.exp(-((dist / 1.85) ** 2)) * lerp(0.08, 0.34, rise)
const trail = Math.exp(-((lag / 1.45) ** 2)) * lerp(0.04, 0.42, rise)
return (core + glow + trail) * appear * fade
}
-function bloom(x: number, y: number, frame: Frame) {
+function idle(x: number, y: number, frame: Frame, ctx: LogoContext): { glow: number; peak: number } {
+ const w = ctx.FULL[0]?.length ?? 1
+ const h = ctx.FULL.length * 2
+ const reach = Math.hypot(w, h) + IDLE_TAIL * 2
+ const dx = x + 0.5 - IDLE_ORIGIN.x
+ const dy = y * 2 + 1 - IDLE_ORIGIN.y
+ const dist = Math.hypot(dx, dy)
+ const angle = Math.atan2(dy, dx)
+ const wob1 = noise(x * 0.24, y * 0.38, frame.t * 0.0004) - 0.5
+ const wob2 = noise(x * 0.08, y * 0.11, frame.t * 0.00015) - 0.5
+ const ripple = Math.sin(angle * 4 + frame.t * 0.0015) * 0.35
+ const jitter = (wob1 * 0.65 + wob2 * 0.25 + ripple * 0.1) * IDLE_NOISE * Math.min(dist, 7)
+ const traveled = dist + jitter
+ let glow = 0
+ let peak = 0
+ for (let i = 0; i < IDLE_RINGS; i++) {
+ const offset = i / IDLE_RINGS
+ const phase = (frame.t / IDLE_PERIOD + offset) % 1
+ const envelope = Math.sin(phase * Math.PI)
+ const eased = envelope * envelope * (3 - 2 * envelope)
+ const head = phase * reach
+ const delta = traveled - head
+ const crestHalf = IDLE_WIDTH * 1.6
+ const crest = Math.abs(delta) < crestHalf ? 0.5 + 0.5 * Math.cos((delta / crestHalf) * Math.PI) : 0
+ const sharp = crest * crest
+ const tailRange = IDLE_TAIL * 2.8
+ const tail = delta < 0 && delta > -tailRange ? (1 + delta / tailRange) ** 2.2 : 0
+ glow += (sharp * IDLE_AMP + tail * IDLE_TAIL_AMP) * eased
+ peak += sharp * IDLE_PEAK * eased
+ }
+ const angular = 0.84 + 0.16 * Math.sin(angle * 1.6 + frame.t * 0.0005)
+ const falloff = Math.max(0, 1 - dist / (reach * 0.95))
+ const breath = (0.5 + 0.5 * Math.sin(frame.t * IDLE_PULSE)) * IDLE_PULSE_AMP
+ const rings = IDLE_RINGS
+ return {
+ glow: (glow / rings) * falloff * angular + breath,
+ peak: (peak / rings) * falloff,
+ }
+}
+
+function bloom(x: number, y: number, frame: Frame, ctx: LogoContext) {
const item = frame.glow
if (!item) return 0
- const glyph = MAP.glyph.get(key(x, y))
+ const glyph = ctx.MAP.glyph.get(key(x, y))
if (glyph !== item.glyph) return 0
const age = frame.t - item.at
if (age < 0 || age > GLOW_OUT) return 0
const p = age / GLOW_OUT
const flash = (1 - p) ** 2
- const dx = x + 0.5 - MAP.center.get(item.glyph)!.x
- const dy = y * 2 + 1 - MAP.center.get(item.glyph)!.y
+ const dx = x + 0.5 - ctx.MAP.center.get(item.glyph)!.x
+ const dy = y * 2 + 1 - ctx.MAP.center.get(item.glyph)!.y
const bias = Math.exp(-((Math.hypot(dx, dy) / 2.8) ** 2))
return lerp(item.force, item.force * 0.18, p) * lerp(0.72, 1.1, bias) * flash
}
-export function Logo() {
+export function Logo(props: { shape?: LogoShape; ink?: RGBA; idle?: boolean } = {}) {
+ const ctx = props.shape ? build(props.shape) : DEFAULT
const { theme } = useTheme()
const [rings, setRings] = createSignal([])
const [hold, setHold] = createSignal()
@@ -430,6 +501,7 @@ export function Logo() {
}
if (!live) setRelease(undefined)
if (live || hold() || release() || glow()) return
+ if (props.idle) return
stop()
}
@@ -438,8 +510,13 @@ export function Logo() {
timer = setInterval(tick, 16)
}
+ if (props.idle) {
+ setNow(performance.now())
+ start()
+ }
+
const hit = (x: number, y: number) => {
- const char = FULL[y]?.[x]
+ const char = ctx.FULL[y]?.[x]
return char !== undefined && char !== " "
}
@@ -448,7 +525,7 @@ export function Logo() {
if (last) burst(last.x, last.y)
setNow(t)
if (!last) setRelease(undefined)
- setHold({ x, y, at: t, glyph: select(x, y) })
+ setHold({ x, y, at: t, glyph: select(x, y, ctx) })
hum = false
start()
}
@@ -521,18 +598,21 @@ export function Logo() {
const attrs = bold ? TextAttributes.BOLD : undefined
return Array.from(line).map((char, i) => {
- const h = field(off + i, y, frame)
- const n = wave(off + i, y, frame, lit(char)) + h
- const s = wave(off + i, y, dusk, false) + h
- const p = lit(char) ? pick(off + i, y, frame) : 0
- const e = lit(char) ? trace(off + i, y, frame) : 0
- const b = lit(char) ? bloom(off + i, y, frame) : 0
- const q = shimmer(off + i, y, frame)
+ const h = field(off + i, y, frame, ctx)
+ const pulse = props.idle ? idle(off + i, y, frame, ctx) : { glow: 0, peak: 0 }
+ const peakMix = lit(char) ? Math.min(1, pulse.peak) : 0
+ const inkTinted = peakMix > 0 ? tint(ink, PEAK, peakMix) : ink
+ const n = wave(off + i, y, frame, lit(char), ctx) + h
+ const s = wave(off + i, y, dusk, false, ctx) + h
+ const p = lit(char) ? pick(off + i, y, frame, ctx) : 0
+ const e = lit(char) ? trace(off + i, y, frame, ctx) : 0
+ const b = lit(char) ? bloom(off + i, y, frame, ctx) : 0
+ const q = shimmer(off + i, y, frame, ctx)
if (char === "_") {
return (
+ ▄
+
+ )
+ }
+
if (char === " ") {
return (
-
+
{char}
)
}
return (
-
+
{char}
)
@@ -613,17 +701,27 @@ export function Logo() {
position="absolute"
top={0}
left={0}
- width={FULL[0]?.length ?? 0}
- height={FULL.length}
+ width={ctx.FULL[0]?.length ?? 0}
+ height={ctx.FULL.length}
zIndex={1}
onMouse={mouse}
/>
-
+
{(line, index) => (
- {renderLine(line, index(), theme.textMuted, false, 0, frame(), dusk())}
- {renderLine(logo.right[index()], index(), theme.text, true, LEFT + GAP, frame(), dusk())}
+ {renderLine(line, index(), props.ink ?? theme.textMuted, !!props.ink, 0, frame(), dusk())}
+
+
+ {renderLine(
+ ctx.shape.right[index()],
+ index(),
+ props.ink ?? theme.text,
+ true,
+ ctx.LEFT + GAP,
+ frame(),
+ dusk(),
+ )}
)}
@@ -631,3 +729,9 @@ export function Logo() {
)
}
+
+export function GoLogo() {
+ const { theme } = useTheme()
+ const base = tint(theme.background, theme.text, 0.82)
+ return
+}
diff --git a/packages/opencode/src/cli/logo.ts b/packages/opencode/src/cli/logo.ts
index 44fb93c15b..a58a8cf995 100644
--- a/packages/opencode/src/cli/logo.ts
+++ b/packages/opencode/src/cli/logo.ts
@@ -3,4 +3,9 @@ export const logo = {
right: [" ▄ ", "█▀▀▀ █▀▀█ █▀▀█ █▀▀█", "█___ █__█ █__█ █^^^", "▀▀▀▀ ▀▀▀▀ ▀▀▀▀ ▀▀▀▀"],
}
-export const marks = "_^~"
+export const go = {
+ left: [" ", "█▀▀▀", "█_^█", "▀▀▀▀"],
+ right: [" ", "█▀▀█", "█__█", "▀▀▀▀"],
+}
+
+export const marks = "_^~,"