@anastops/cli 1.0.0 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/commands/doctor.d.ts.map +1 -1
- package/dist/commands/doctor.js +8 -6
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/init.d.ts +17 -0
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +315 -17
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/ranger.d.ts +7 -2
- package/dist/commands/ranger.d.ts.map +1 -1
- package/dist/commands/ranger.js +99 -21
- package/dist/commands/ranger.js.map +1 -1
- package/dist/commands/uninstall.d.ts +16 -0
- package/dist/commands/uninstall.d.ts.map +1 -0
- package/dist/commands/uninstall.js +206 -0
- package/dist/commands/uninstall.js.map +1 -0
- package/dist/index.js +11 -0
- package/dist/index.js.map +1 -1
- package/package.json +8 -6
- package/ranger-tui/pyproject.toml +51 -0
- package/ranger-tui/ranger_tui/__init__.py +5 -0
- package/ranger-tui/ranger_tui/__main__.py +16 -0
- package/ranger-tui/ranger_tui/__pycache__/__init__.cpython-314.pyc +0 -0
- package/ranger-tui/ranger_tui/__pycache__/__main__.cpython-314.pyc +0 -0
- package/ranger-tui/ranger_tui/__pycache__/accessibility.cpython-314.pyc +0 -0
- package/ranger-tui/ranger_tui/__pycache__/app.cpython-314.pyc +0 -0
- package/ranger-tui/ranger_tui/__pycache__/config.cpython-314.pyc +0 -0
- package/ranger-tui/ranger_tui/__pycache__/theme.cpython-314.pyc +0 -0
- package/ranger-tui/ranger_tui/accessibility.py +499 -0
- package/ranger-tui/ranger_tui/actions/__init__.py +13 -0
- package/ranger-tui/ranger_tui/actions/agent_actions.py +74 -0
- package/ranger-tui/ranger_tui/actions/session_actions.py +110 -0
- package/ranger-tui/ranger_tui/actions/task_actions.py +107 -0
- package/ranger-tui/ranger_tui/app.py +93 -0
- package/ranger-tui/ranger_tui/assets/ranger_head.png +0 -0
- package/ranger-tui/ranger_tui/config.py +100 -0
- package/ranger-tui/ranger_tui/data/__init__.py +16 -0
- package/ranger-tui/ranger_tui/data/__pycache__/__init__.cpython-314.pyc +0 -0
- package/ranger-tui/ranger_tui/data/__pycache__/client.cpython-314.pyc +0 -0
- package/ranger-tui/ranger_tui/data/__pycache__/models.cpython-314.pyc +0 -0
- package/ranger-tui/ranger_tui/data/client.py +858 -0
- package/ranger-tui/ranger_tui/data/models.py +151 -0
- package/ranger-tui/ranger_tui/screens/__init__.py +16 -0
- package/ranger-tui/ranger_tui/screens/__pycache__/__init__.cpython-314.pyc +0 -0
- package/ranger-tui/ranger_tui/screens/__pycache__/dashboard.cpython-314.pyc +0 -0
- package/ranger-tui/ranger_tui/screens/__pycache__/modals.cpython-314.pyc +0 -0
- package/ranger-tui/ranger_tui/screens/__pycache__/session.cpython-314.pyc +0 -0
- package/ranger-tui/ranger_tui/screens/__pycache__/task.cpython-314.pyc +0 -0
- package/ranger-tui/ranger_tui/screens/command_palette.py +357 -0
- package/ranger-tui/ranger_tui/screens/dashboard.py +232 -0
- package/ranger-tui/ranger_tui/screens/help.py +103 -0
- package/ranger-tui/ranger_tui/screens/modals.py +95 -0
- package/ranger-tui/ranger_tui/screens/session.py +289 -0
- package/ranger-tui/ranger_tui/screens/task.py +187 -0
- package/ranger-tui/ranger_tui/styles/ranger.tcss +254 -0
- package/ranger-tui/ranger_tui/theme.py +93 -0
- package/ranger-tui/ranger_tui/widgets/__init__.py +23 -0
- package/ranger-tui/ranger_tui/widgets/__pycache__/__init__.cpython-314.pyc +0 -0
- package/ranger-tui/ranger_tui/widgets/__pycache__/accessible.cpython-314.pyc +0 -0
- package/ranger-tui/ranger_tui/widgets/__pycache__/logo.cpython-314.pyc +0 -0
- package/ranger-tui/ranger_tui/widgets/__pycache__/logo_assets.cpython-314.pyc +0 -0
- package/ranger-tui/ranger_tui/widgets/__pycache__/ranger_image.cpython-314.pyc +0 -0
- package/ranger-tui/ranger_tui/widgets/__pycache__/sidebar.cpython-314.pyc +0 -0
- package/ranger-tui/ranger_tui/widgets/__pycache__/topbar.cpython-314.pyc +0 -0
- package/ranger-tui/ranger_tui/widgets/accessible.py +176 -0
- package/ranger-tui/ranger_tui/widgets/agents_table.py +151 -0
- package/ranger-tui/ranger_tui/widgets/header.py +141 -0
- package/ranger-tui/ranger_tui/widgets/logo.py +258 -0
- package/ranger-tui/ranger_tui/widgets/logo_assets.py +62 -0
- package/ranger-tui/ranger_tui/widgets/metrics_panel.py +121 -0
- package/ranger-tui/ranger_tui/widgets/ranger_image.py +91 -0
- package/ranger-tui/ranger_tui/widgets/sessions_table.py +191 -0
- package/ranger-tui/ranger_tui/widgets/sidebar.py +91 -0
- package/ranger-tui/ranger_tui/widgets/tasks_table.py +189 -0
- package/ranger-tui/ranger_tui/widgets/topbar.py +168 -0
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Agents table widget using Textual DataTable.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from datetime import datetime
|
|
6
|
+
from textual.app import ComposeResult
|
|
7
|
+
from textual.widget import Widget
|
|
8
|
+
from textual.widgets import DataTable
|
|
9
|
+
from textual.reactive import reactive
|
|
10
|
+
|
|
11
|
+
from ranger_tui.data import Agent
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class AgentsTable(Widget):
|
|
15
|
+
"""Table widget for displaying agents."""
|
|
16
|
+
|
|
17
|
+
DEFAULT_CSS = """
|
|
18
|
+
AgentsTable {
|
|
19
|
+
height: 1fr;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
AgentsTable DataTable {
|
|
23
|
+
height: 100%;
|
|
24
|
+
}
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
agents: reactive[list[Agent]] = reactive(list, init=False)
|
|
28
|
+
_selected_row_key: str | None = None
|
|
29
|
+
|
|
30
|
+
def __init__(self, **kwargs):
|
|
31
|
+
super().__init__(**kwargs)
|
|
32
|
+
self.agents = []
|
|
33
|
+
|
|
34
|
+
def compose(self) -> ComposeResult:
|
|
35
|
+
"""Compose the table."""
|
|
36
|
+
table = DataTable(
|
|
37
|
+
id="agents-data-table",
|
|
38
|
+
cursor_type="row",
|
|
39
|
+
zebra_stripes=True,
|
|
40
|
+
)
|
|
41
|
+
yield table
|
|
42
|
+
|
|
43
|
+
def on_mount(self) -> None:
|
|
44
|
+
"""Set up the table columns with proportional widths."""
|
|
45
|
+
# Calculate available width based on parent container
|
|
46
|
+
try:
|
|
47
|
+
table_width = self.app.size.width - 34 # Sidebar(28) + padding(6)
|
|
48
|
+
except Exception:
|
|
49
|
+
table_width = 100
|
|
50
|
+
|
|
51
|
+
table_width = max(table_width, 60)
|
|
52
|
+
usable_width = table_width - 6 # Column separators
|
|
53
|
+
|
|
54
|
+
# Proportional: Status 12%, ID 14%, Name 24%, Role 18%, Provider 12%, Tasks 10%, Tokens 10%
|
|
55
|
+
status_w = max(8, int(usable_width * 0.12))
|
|
56
|
+
id_w = max(10, int(usable_width * 0.14))
|
|
57
|
+
name_w = max(10, int(usable_width * 0.24))
|
|
58
|
+
role_w = max(8, int(usable_width * 0.18))
|
|
59
|
+
provider_w = max(8, int(usable_width * 0.12))
|
|
60
|
+
tasks_w = max(5, int(usable_width * 0.10))
|
|
61
|
+
tokens_w = max(6, int(usable_width * 0.10))
|
|
62
|
+
|
|
63
|
+
table = self.query_one(DataTable)
|
|
64
|
+
table.add_column("Status", key="status", width=status_w)
|
|
65
|
+
table.add_column("ID", key="id", width=id_w)
|
|
66
|
+
table.add_column("Name", key="name", width=name_w)
|
|
67
|
+
table.add_column("Role", key="role", width=role_w)
|
|
68
|
+
table.add_column("Provider", key="provider", width=provider_w)
|
|
69
|
+
table.add_column("Tasks", key="tasks", width=tasks_w)
|
|
70
|
+
table.add_column("Tokens", key="tokens", width=tokens_w)
|
|
71
|
+
|
|
72
|
+
async def update_agents(self, agents: list[Agent]) -> None:
|
|
73
|
+
"""Update the table with new agent data."""
|
|
74
|
+
self.agents = agents
|
|
75
|
+
|
|
76
|
+
table = self.query_one(DataTable)
|
|
77
|
+
|
|
78
|
+
# Remember current selection
|
|
79
|
+
current_key = self._selected_row_key
|
|
80
|
+
|
|
81
|
+
# Clear and repopulate
|
|
82
|
+
table.clear()
|
|
83
|
+
|
|
84
|
+
for agent in agents:
|
|
85
|
+
status_display = self._format_status(agent.status)
|
|
86
|
+
tasks_display = f"{agent.tasks_completed}/{agent.tasks_completed + agent.tasks_failed}"
|
|
87
|
+
tokens_display = f"{agent.tokens_used:,}"
|
|
88
|
+
|
|
89
|
+
table.add_row(
|
|
90
|
+
status_display,
|
|
91
|
+
agent.id,
|
|
92
|
+
agent.name,
|
|
93
|
+
agent.role,
|
|
94
|
+
agent.provider,
|
|
95
|
+
tasks_display,
|
|
96
|
+
tokens_display,
|
|
97
|
+
key=agent.id,
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
# Restore selection if possible
|
|
101
|
+
if current_key:
|
|
102
|
+
try:
|
|
103
|
+
row_index = table.get_row_index(current_key)
|
|
104
|
+
table.cursor_coordinate = (row_index, 0)
|
|
105
|
+
self._selected_row_key = current_key
|
|
106
|
+
except Exception:
|
|
107
|
+
pass
|
|
108
|
+
|
|
109
|
+
def _format_status(self, status: str) -> str:
|
|
110
|
+
"""Format status with color markup."""
|
|
111
|
+
status_colors = {
|
|
112
|
+
"idle": "[dim]○ idle[/]",
|
|
113
|
+
"working": "[cyan]● working[/]",
|
|
114
|
+
"completed": "[green]✓ done[/]",
|
|
115
|
+
"failed": "[red]✗ failed[/]",
|
|
116
|
+
}
|
|
117
|
+
return status_colors.get(status, f"[yellow]{status}[/]")
|
|
118
|
+
|
|
119
|
+
def on_data_table_row_selected(self, event: DataTable.RowSelected) -> None:
|
|
120
|
+
"""Handle row selection."""
|
|
121
|
+
self._selected_row_key = str(event.row_key.value) if event.row_key else None
|
|
122
|
+
|
|
123
|
+
def on_data_table_row_highlighted(self, event: DataTable.RowHighlighted) -> None:
|
|
124
|
+
"""Handle row highlight (cursor movement)."""
|
|
125
|
+
self._selected_row_key = str(event.row_key.value) if event.row_key else None
|
|
126
|
+
|
|
127
|
+
def get_selected_agent_id(self) -> str | None:
|
|
128
|
+
"""Get the currently selected agent ID."""
|
|
129
|
+
table = self.query_one(DataTable)
|
|
130
|
+
if table.cursor_row is not None:
|
|
131
|
+
# Get the ID from the second column (index 1)
|
|
132
|
+
return str(table.get_cell_at((table.cursor_row, 1)))
|
|
133
|
+
return self._selected_row_key
|
|
134
|
+
|
|
135
|
+
@property
|
|
136
|
+
def row_count(self) -> int:
|
|
137
|
+
"""Get the number of rows in the table."""
|
|
138
|
+
table = self.query_one(DataTable)
|
|
139
|
+
return table.row_count
|
|
140
|
+
|
|
141
|
+
@property
|
|
142
|
+
def column_count(self) -> int:
|
|
143
|
+
"""Get the number of columns in the table."""
|
|
144
|
+
table = self.query_one(DataTable)
|
|
145
|
+
return len(table.columns)
|
|
146
|
+
|
|
147
|
+
@property
|
|
148
|
+
def cursor_row(self) -> int | None:
|
|
149
|
+
"""Get the current cursor row."""
|
|
150
|
+
table = self.query_one(DataTable)
|
|
151
|
+
return table.cursor_row
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Header widget with logo and connection status.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from datetime import datetime
|
|
6
|
+
from textual.app import ComposeResult
|
|
7
|
+
from textual.widget import Widget
|
|
8
|
+
from textual.widgets import Static
|
|
9
|
+
from textual.containers import Horizontal
|
|
10
|
+
from textual.reactive import reactive
|
|
11
|
+
|
|
12
|
+
from ranger_tui.config import config
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class Header(Widget):
|
|
16
|
+
"""Header widget with logo and status indicators."""
|
|
17
|
+
|
|
18
|
+
DEFAULT_CSS = """
|
|
19
|
+
Header {
|
|
20
|
+
dock: top;
|
|
21
|
+
height: 3;
|
|
22
|
+
background: $primary;
|
|
23
|
+
color: $text;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
Header .header-content {
|
|
27
|
+
width: 100%;
|
|
28
|
+
height: 100%;
|
|
29
|
+
padding: 0 2;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
Header .logo {
|
|
33
|
+
color: $text;
|
|
34
|
+
text-style: bold;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
Header .logo-name {
|
|
38
|
+
color: $secondary;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
Header .status-bar {
|
|
42
|
+
dock: right;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
Header .status-item {
|
|
46
|
+
margin-left: 2;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
Header .connected {
|
|
50
|
+
color: $success;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
Header .disconnected {
|
|
54
|
+
color: $error;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
Header .environment {
|
|
58
|
+
color: $warning;
|
|
59
|
+
}
|
|
60
|
+
"""
|
|
61
|
+
|
|
62
|
+
mongodb_connected = reactive(False)
|
|
63
|
+
redis_connected = reactive(False)
|
|
64
|
+
last_refresh = reactive(None)
|
|
65
|
+
|
|
66
|
+
def compose(self) -> ComposeResult:
|
|
67
|
+
"""Compose the header layout."""
|
|
68
|
+
with Horizontal(classes="header-content"):
|
|
69
|
+
yield Static(
|
|
70
|
+
"ANASTOPS [bold magenta]RANGER[/]",
|
|
71
|
+
classes="logo",
|
|
72
|
+
markup=True,
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
with Horizontal(classes="status-bar"):
|
|
76
|
+
yield Static(
|
|
77
|
+
f"[dim]{config.environment}[/]",
|
|
78
|
+
classes="status-item environment",
|
|
79
|
+
id="env-status",
|
|
80
|
+
markup=True,
|
|
81
|
+
)
|
|
82
|
+
yield Static(
|
|
83
|
+
"",
|
|
84
|
+
classes="status-item",
|
|
85
|
+
id="refresh-status",
|
|
86
|
+
markup=True,
|
|
87
|
+
)
|
|
88
|
+
yield Static(
|
|
89
|
+
"",
|
|
90
|
+
classes="status-item",
|
|
91
|
+
id="mongo-status",
|
|
92
|
+
markup=True,
|
|
93
|
+
)
|
|
94
|
+
yield Static(
|
|
95
|
+
"",
|
|
96
|
+
classes="status-item",
|
|
97
|
+
id="redis-status",
|
|
98
|
+
markup=True,
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
def on_mount(self) -> None:
|
|
102
|
+
"""Set up watchers when mounted."""
|
|
103
|
+
self.watch(self.app, "mongodb_connected", self._update_mongo_status)
|
|
104
|
+
self.watch(self.app, "redis_connected", self._update_redis_status)
|
|
105
|
+
self.watch(self.app, "last_refresh", self._update_refresh_status)
|
|
106
|
+
|
|
107
|
+
# Initial update
|
|
108
|
+
self._update_mongo_status(getattr(self.app, "mongodb_connected", False))
|
|
109
|
+
self._update_redis_status(getattr(self.app, "redis_connected", False))
|
|
110
|
+
self._update_refresh_status(getattr(self.app, "last_refresh", None))
|
|
111
|
+
|
|
112
|
+
def _update_mongo_status(self, connected: bool) -> None:
|
|
113
|
+
"""Update MongoDB status indicator."""
|
|
114
|
+
status = self.query_one("#mongo-status", Static)
|
|
115
|
+
if connected:
|
|
116
|
+
status.update("[green]● Mongo[/]")
|
|
117
|
+
else:
|
|
118
|
+
status.update("[red]○ Mongo[/]")
|
|
119
|
+
|
|
120
|
+
def _update_redis_status(self, connected: bool) -> None:
|
|
121
|
+
"""Update Redis status indicator."""
|
|
122
|
+
status = self.query_one("#redis-status", Static)
|
|
123
|
+
if connected:
|
|
124
|
+
status.update("[green]● Redis[/]")
|
|
125
|
+
else:
|
|
126
|
+
status.update("[red]○ Redis[/]")
|
|
127
|
+
|
|
128
|
+
def _update_refresh_status(self, last_refresh: datetime | None) -> None:
|
|
129
|
+
"""Update last refresh time."""
|
|
130
|
+
status = self.query_one("#refresh-status", Static)
|
|
131
|
+
if last_refresh:
|
|
132
|
+
age = datetime.now() - last_refresh
|
|
133
|
+
if age.total_seconds() < 60:
|
|
134
|
+
age_str = f"{int(age.total_seconds())}s ago"
|
|
135
|
+
elif age.total_seconds() < 3600:
|
|
136
|
+
age_str = f"{int(age.total_seconds() / 60)}m ago"
|
|
137
|
+
else:
|
|
138
|
+
age_str = f"{int(age.total_seconds() / 3600)}h ago"
|
|
139
|
+
status.update(f"[dim]Updated {age_str}[/]")
|
|
140
|
+
else:
|
|
141
|
+
status.update("[dim]Not refreshed[/]")
|
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Logo widget with ASCII art park ranger for Anastops Ranger TUI.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from textual.widget import Widget
|
|
6
|
+
from textual.app import ComposeResult
|
|
7
|
+
from textual.widgets import Static
|
|
8
|
+
|
|
9
|
+
from ranger_tui.widgets.logo_assets import LOGO_USER_RANGER_SMALL, LOGO_USER_RANGER_LARGE
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
# Compact inline logo for topbar - Clean ASCII art ranger head
|
|
13
|
+
LOGO_COMPACT = """\
|
|
14
|
+
[#4a7c31] ▄███▄ [/][bold cyan]ANASTOPS[/]
|
|
15
|
+
[#4a7c31] ▄███████▄> [/][bold magenta]RANGER[/]
|
|
16
|
+
[#4a7c31] ▀▀[/][#f5d070]▄█████▄[/][#4a7c31]▀▀ [/]
|
|
17
|
+
[#f5d070] █[/][#222]▀[/][#f5d070]███[/][#222]▀[/][#f5d070]█ [/]
|
|
18
|
+
[#f5d070] ▀█[/][#a52a2a]▄[/][#f5d070]█[/][#a52a2a]▄[/][#f5d070]█▀ [/]"""
|
|
19
|
+
|
|
20
|
+
# Medium logo (5 lines) - small ranger with hat
|
|
21
|
+
LOGO_MEDIUM = """\
|
|
22
|
+
[bold green] ▄███████▄[/]
|
|
23
|
+
[bold green] ▀▀▀▀▀▀▀▀▀▀▀[/]
|
|
24
|
+
[yellow] (° °)[/] [bold cyan]ANASTOPS[/]
|
|
25
|
+
[green] ┌┴─┴┐[/] [bold magenta]RANGER[/]
|
|
26
|
+
[green] │ ▪ │[/]"""
|
|
27
|
+
|
|
28
|
+
# Ranger ASCII art - compact version (15 lines)
|
|
29
|
+
LOGO_RANGER = """\
|
|
30
|
+
[bold green] ▄██████▄[/]
|
|
31
|
+
[bold green] ████████████████[/]
|
|
32
|
+
[bold green] ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀[/]
|
|
33
|
+
[yellow] ┌─────┐[/]
|
|
34
|
+
[yellow] │ • • │[/]
|
|
35
|
+
[yellow] │ ▽ │[/]
|
|
36
|
+
[yellow] └──┬──┘[/]
|
|
37
|
+
[green] ┌──┴──┐[/] [dim]╭─────────────────╮[/]
|
|
38
|
+
[green] ╱│ ▪ │╲[/] [dim]│[/] [bold cyan]A N A S T O P S[/] [dim]│[/]
|
|
39
|
+
[green] ╱ │ │ ╲[/] [dim]│[/] [bold magenta]R A N G E R[/] [dim]│[/]
|
|
40
|
+
[green] ╱ └──┬──┘ ╲[/] [dim]╰─────────────────╯[/]
|
|
41
|
+
[green] ╱ │ ╲[/]
|
|
42
|
+
[green] ╱ │ ╲[/]"""
|
|
43
|
+
|
|
44
|
+
# Full ASCII art logo - detailed park ranger
|
|
45
|
+
LOGO_FULL = """\
|
|
46
|
+
[bold green] ▄█▀▀▀▀▀█▄[/]
|
|
47
|
+
[bold green] ▄███████████████▄[/]
|
|
48
|
+
[bold green] █████████████████████[/]
|
|
49
|
+
[bold green] ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀[/]
|
|
50
|
+
[yellow] ╭───────╮[/]
|
|
51
|
+
[yellow] │ ◠ ◠ │[/]
|
|
52
|
+
[yellow] │ ▽ │[/]
|
|
53
|
+
[yellow] ╰───┬───╯[/]
|
|
54
|
+
[green] ┌───┴───┐[/]
|
|
55
|
+
[green] ╱│ ● │╲[/] [bold white]╔═══════════════════╗[/]
|
|
56
|
+
[green] ╱ │ │ ╲[/] [bold white]║[/] [bold cyan]A N A S T O P S[/] [bold white]║[/]
|
|
57
|
+
[green] ╱ └───────┘ ╲[/] [bold white]║[/] [bold magenta]R A N G E R[/] [bold white]║[/]
|
|
58
|
+
[green] ╱ │ ╲[/] [bold white]╚═══════════════════╝[/]
|
|
59
|
+
[green] ╱ │ ╲[/]
|
|
60
|
+
[green] ▕─────────┴─────────▏[/]"""
|
|
61
|
+
|
|
62
|
+
# Extra small ranger for tight spaces
|
|
63
|
+
LOGO_MINI = """\
|
|
64
|
+
[bold green] ▄███▄[/]
|
|
65
|
+
[bold green] ▀▀▀▀▀▀▀[/]
|
|
66
|
+
[yellow] (•‿•)[/]
|
|
67
|
+
[green] ┌┴─┴┐[/]
|
|
68
|
+
[green] │ ▪ │[/]"""
|
|
69
|
+
|
|
70
|
+
# Banner-style logo for wide displays
|
|
71
|
+
LOGO_BANNER = """\
|
|
72
|
+
[bold green] ▄███▄[/] [bold cyan]╔═╗╔╗╔╔═╗╔═╗╔╦╗╔═╗╔═╗╔═╗[/] [bold magenta]╦═╗╔═╗╔╗╔╔═╗╔═╗╦═╗[/]
|
|
73
|
+
[bold green]▀▀▀▀▀▀▀[/] [bold cyan]╠═╣║║║╠═╣╚═╗ ║ ║ ║╠═╝╚═╗[/] [bold magenta]╠╦╝╠═╣║║║║ ╦║╣ ╠╦╝[/]
|
|
74
|
+
[yellow] (•‿•)[/] [bold cyan]╩ ╩╝╚╝╩ ╩╚═╝ ╩ ╚═╝╩ ╚═╝[/] [bold magenta]╩╚═╩ ╩╝╚╝╚═╝╚═╝╩╚═[/]"""
|
|
75
|
+
|
|
76
|
+
# Splash screen with full ranger
|
|
77
|
+
LOGO_SPLASH = """\
|
|
78
|
+
[bold green] ▄█▀▀▀▀▀█▄[/]
|
|
79
|
+
[bold green] ▄███████████████▄[/]
|
|
80
|
+
[bold green] █████████████████████[/]
|
|
81
|
+
[bold green] ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀[/]
|
|
82
|
+
[yellow] ╭───────╮[/]
|
|
83
|
+
[yellow] │ ◠ ◠ │[/]
|
|
84
|
+
[yellow] │ ▽ │[/]
|
|
85
|
+
[yellow] ╰───┬───╯[/]
|
|
86
|
+
[green] ┌───┴───┐[/]
|
|
87
|
+
[green] ╱│ ● │╲[/]
|
|
88
|
+
[green] ╱ │ │ ╲[/]
|
|
89
|
+
[green] ╱ └───────┘ ╲[/]
|
|
90
|
+
[green] ╱ │ ╲[/]
|
|
91
|
+
[green] ╱ │ ╲[/]
|
|
92
|
+
[green] ▕─────────┴─────────▏[/]
|
|
93
|
+
|
|
94
|
+
[bold white] ╔═══════════════════════════════╗[/]
|
|
95
|
+
[bold white] ║[/] [bold cyan]A N A S T O P S[/] [bold white]║[/]
|
|
96
|
+
[bold white] ║[/] [bold magenta]R A N G E R[/] [bold white]║[/]
|
|
97
|
+
[bold white] ╚═══════════════════════════════╝[/]
|
|
98
|
+
[dim] AI Orchestration Dashboard[/]"""
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
class Logo(Widget):
|
|
102
|
+
"""Logo widget displaying ASCII art park ranger."""
|
|
103
|
+
|
|
104
|
+
DEFAULT_CSS = """
|
|
105
|
+
Logo {
|
|
106
|
+
width: auto;
|
|
107
|
+
height: auto;
|
|
108
|
+
padding: 0;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
Logo > Static {
|
|
112
|
+
width: auto;
|
|
113
|
+
height: auto;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
Logo.logo-compact {
|
|
117
|
+
height: auto;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
Logo.logo-medium {
|
|
121
|
+
height: 5;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
Logo.logo-mini {
|
|
125
|
+
height: 5;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
Logo.logo-ranger {
|
|
129
|
+
height: 13;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
Logo.logo-full {
|
|
133
|
+
height: 15;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
Logo.logo-banner {
|
|
137
|
+
height: 3;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
Logo.logo-splash {
|
|
141
|
+
height: 21;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
Logo.logo-user {
|
|
145
|
+
height: auto;
|
|
146
|
+
content-align: center bottom;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
Logo.logo-user_large {
|
|
150
|
+
height: auto;
|
|
151
|
+
}
|
|
152
|
+
"""
|
|
153
|
+
|
|
154
|
+
def __init__(
|
|
155
|
+
self,
|
|
156
|
+
variant: str = "compact",
|
|
157
|
+
name: str | None = None,
|
|
158
|
+
id: str | None = None,
|
|
159
|
+
classes: str | None = None,
|
|
160
|
+
) -> None:
|
|
161
|
+
"""
|
|
162
|
+
Initialize logo widget.
|
|
163
|
+
|
|
164
|
+
Args:
|
|
165
|
+
variant: Logo style - "compact", "medium", "mini", "ranger", "full", "banner", "splash", "user", or "user_large"
|
|
166
|
+
"""
|
|
167
|
+
super().__init__(name=name, id=id, classes=classes)
|
|
168
|
+
self._variant = variant
|
|
169
|
+
self._logos = {
|
|
170
|
+
"compact": LOGO_COMPACT,
|
|
171
|
+
"medium": LOGO_MEDIUM,
|
|
172
|
+
"mini": LOGO_MINI,
|
|
173
|
+
"ranger": LOGO_RANGER,
|
|
174
|
+
"full": LOGO_FULL,
|
|
175
|
+
"banner": LOGO_BANNER,
|
|
176
|
+
"splash": LOGO_SPLASH,
|
|
177
|
+
"user": LOGO_USER_RANGER_SMALL,
|
|
178
|
+
"user_large": LOGO_USER_RANGER_LARGE,
|
|
179
|
+
}
|
|
180
|
+
# Add variant class
|
|
181
|
+
self.add_class(f"logo-{variant}")
|
|
182
|
+
|
|
183
|
+
def compose(self) -> ComposeResult:
|
|
184
|
+
"""Compose the logo display."""
|
|
185
|
+
logo_text = self._logos.get(self._variant, LOGO_COMPACT)
|
|
186
|
+
yield Static(logo_text, markup=True)
|
|
187
|
+
|
|
188
|
+
@property
|
|
189
|
+
def variant(self) -> str:
|
|
190
|
+
"""Get current logo variant."""
|
|
191
|
+
return self._variant
|
|
192
|
+
|
|
193
|
+
def set_variant(self, variant: str) -> None:
|
|
194
|
+
"""Change the logo variant."""
|
|
195
|
+
if variant in self._logos:
|
|
196
|
+
self.remove_class(f"logo-{self._variant}")
|
|
197
|
+
self._variant = variant
|
|
198
|
+
self.add_class(f"logo-{variant}")
|
|
199
|
+
try:
|
|
200
|
+
static = self.query_one(Static)
|
|
201
|
+
static.update(self._logos[variant])
|
|
202
|
+
except Exception:
|
|
203
|
+
pass
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
class LogoBanner(Widget):
|
|
207
|
+
"""Wide banner logo for headers and splash screens."""
|
|
208
|
+
|
|
209
|
+
DEFAULT_CSS = """
|
|
210
|
+
LogoBanner {
|
|
211
|
+
width: 100%;
|
|
212
|
+
height: auto;
|
|
213
|
+
content-align: center middle;
|
|
214
|
+
padding: 1;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
LogoBanner > Static {
|
|
218
|
+
width: auto;
|
|
219
|
+
text-align: center;
|
|
220
|
+
}
|
|
221
|
+
"""
|
|
222
|
+
|
|
223
|
+
def compose(self) -> ComposeResult:
|
|
224
|
+
yield Static(LOGO_BANNER, markup=True)
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
class LogoSplash(Widget):
|
|
228
|
+
"""Full splash screen logo with version info."""
|
|
229
|
+
|
|
230
|
+
DEFAULT_CSS = """
|
|
231
|
+
LogoSplash {
|
|
232
|
+
width: 100%;
|
|
233
|
+
height: auto;
|
|
234
|
+
content-align: center middle;
|
|
235
|
+
padding: 1;
|
|
236
|
+
background: $background;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
LogoSplash > .splash-logo {
|
|
240
|
+
width: auto;
|
|
241
|
+
text-align: center;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
LogoSplash > .splash-version {
|
|
245
|
+
width: 100%;
|
|
246
|
+
text-align: center;
|
|
247
|
+
color: $foreground-muted;
|
|
248
|
+
margin-top: 1;
|
|
249
|
+
}
|
|
250
|
+
"""
|
|
251
|
+
|
|
252
|
+
def __init__(self, version: str = "0.1.0") -> None:
|
|
253
|
+
super().__init__()
|
|
254
|
+
self._version = version
|
|
255
|
+
|
|
256
|
+
def compose(self) -> ComposeResult:
|
|
257
|
+
yield Static(LOGO_SPLASH, classes="splash-logo", markup=True)
|
|
258
|
+
yield Static(f"v{self._version}", classes="splash-version")
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
LOGO_USER_RANGER_SMALL = """\
|
|
2
|
+
[#2c3555]█[/][#16161e]████████████████████████[/][#191b27]█[/]
|
|
3
|
+
[#2c3555]█[/][#16161e]████████████████████████[/][#191b27]█[/]
|
|
4
|
+
[#2c3555]█[/][#16161e]████████████████████████[/][#191b27]█[/]
|
|
5
|
+
[#2c3555]█[/][#16161e]████████[/][#16161e on #20361b]▀[/][#16161e on #1a3318]▀[/][#16161e on #1d4e1b]▀[/][#2c4e21 on #347b23]▀[/][#2e5721 on #31871a]▀[/][#16161e on #326622]▀[/][#16161e]█[/][#16161e on #214319]▀[/][#16161e]████████[/][#191b27]█[/]
|
|
6
|
+
[#2c3555]█[/][#16161e]████████[/][#3b9424 on #245d16]▀[/][#328b19 on #1f5113]▀[/][#36951d on #1c4710]▀[/][#358f1b on #1b4911]▀[/][#348c1d on #1c4b14]▀[/][#35921c on #215016]▀[/][#36911c on #216716]▀[/][#398a22 on #2c6d1c]▀[/][#263524 on #16161e]▀[/][#16161e]█[/][#16161e on #315d24]▀[/][#16161e on #2e6620]▀[/][#16161e]████[/][#191b27]█[/]
|
|
7
|
+
[#2c3555]█[/][#16161e]████[/][#16161e on #263a20]▀[/][#16161e on #29501e]▀[/][#16161e on #215716]▀[/][#2d5923 on #28631d]▀[/][#0e1f09 on #216215]▀[/][#0a1507 on #226614]▀[/][#080d05 on #236212]▀[/][#050706 on #286013]▀[/][#020403 on #185310]▀[/][#000000 on #2c451e]▀[/][#000000 on #434227]▀[/][#000000 on #322821]▀[/][#263128 on #19221b]▀[/][#2d5c21 on #16161e]▀[/][#2c5a20 on #16161e]▀[/][#16161e]█████[/][#191b27]█[/]
|
|
8
|
+
[#2c3555]█[/][#16161e]████████[/][#ad9163 on #dcdbc4]▀[/][#ae926b on #e7dbbd]▀[/][#c6aa6d on #d3be8c]▀[/][#998b7b on #fbf9ff]▀[/][#9f8950 on #d5b97e]▀[/][#aa8c4c on #fed677]▀[/][#ffec8b on #f9d479]▀[/][#5b4d32 on #261d13]▀[/][#000000 on #000100]▀[/][#16161e on #151513]▀[/][#16161e]██████[/][#191b27]█[/]
|
|
9
|
+
[#2c3555]█[/][#16161e]██████[/][#16161e on #3a3733]▀[/][#16161e on #f7d380]▀[/][#5d6066 on #ab9357]▀[/][#b2a599 on #cdb26b]▀[/][#635b45 on #ba9f5c]▀[/][#a2aab0 on #7b725b]▀[/][#d5c3a2 on #d1b872]▀[/][#fbd570 on #f6d273]▀[/][#e8c673 on #dcba6b]▀[/][#150f0d on #0b0606]▀[/][#000000 on #020102]▀[/][#080807 on #040403]▀[/][#16161e]██████[/][#191b27]█[/]
|
|
10
|
+
[#2c3555]█[/][#16161e]███████[/][#8e7a49 on #83754c]▀[/][#ffe382 on #baa661]▀[/][#ffdd7e on #947e48]▀[/][#c8aa56 on #e2c06f]▀[/][#e2be6d on #e4c06a]▀[/][#ffda7e on #d1af60]▀[/][#fed97b on #ffdd7e]▀[/][#deba6c on #d5b265]▀[/][#020001 on #5d4b2d]▀[/][#574b31 on #ffe78a]▀[/][#726642 on #fbd87b]▀[/][#16161e on #86774a]▀[/][#16161e]█████[/][#191b27]█[/]
|
|
11
|
+
[#2c3555]█[/][#16161e]██████[/][#16161e on #7b694d]▀[/][#ffdb80 on #ffe083]▀[/][#bea35c on #c1a25e]▀[/][#b49857 on #f4d179]▀[/][#ffe17f on #ffe488]▀[/][#ffe283 on #9d8654]▀[/][#cba960 on #8b7444]▀[/][#e8c56e on #d6b466]▀[/][#ecc86f on #fed97b]▀[/][#debd70 on #ffdf7e]▀[/][#c0a35d on #edca70]▀[/][#e3c171 on #ffe981]▀[/][#b39f62 on #baa062]▀[/][#16161e]█████[/][#191b27]█[/]
|
|
12
|
+
[#2c3555]█[/][#16161e]██████[/][#786540 on #16161e]▀[/][#ffe484 on #f4cf79]▀[/][#b19655 on #ffee87]▀[/][#a58952 on #b39957]▀[/][#2c261c on #010002]▀[/][#080307 on #483228]▀[/][#ccad6a on #ffe184]▀[/][#d5b358 on #deba63]▀[/][#ffdc7d on #ffdc7c]▀[/][#eac471 on #fddb7d]▀[/][#edc973 on #97814b]▀[/][#e4c678 on #1a1513]▀[/][#16161e]██████[/][#191b27]█[/]
|
|
13
|
+
[#2c3555]█[/][#16161e]███████[/][#c7ab62 on #9a8151]▀[/][#c8aa5d on #c1a259]▀[/][#8b653f on #b8975a]▀[/][#983d56 on #d7a864]▀[/][#c69062 on #f6d176]▀[/][#ffe683 on #ffdf7d]▀[/][#dfbb69 on #e9c56b]▀[/][#ffdb7d on #ffde7c]▀[/][#ffe680 on #ffe786]▀[/][#c5ac69 on #867447]▀[/][#16161e]███████[/][#191b27]█[/]
|
|
14
|
+
[#2c3555]█[/][#16161e]████████[/][#ffdc7e on #ada559]▀[/][#d3ae65 on #6e743c]▀[/][#fff08b on #777c3a]▀[/][#ffdf82 on #768233]▀[/][#ffe083 on #6b8436]▀[/][#f4c874 on #617a36]▀[/][#ffe484 on #55802c]▀[/][#ceac69 on #406c24]▀[/][#16161e on #233f1c]▀[/][#16161e]███████[/][#191b27]█[/]
|
|
15
|
+
[#2c3555]█[/][#16161e]███████[/][#3a7526 on #316123]▀[/][#071505 on #1b3d13]▀[/][#2a7c18 on #2a7419]▀[/][#2c8418 on #246015]▀[/][#2a8119 on #206410]▀[/][#2a8117 on #246912]▀[/][#2b8016 on #267411]▀[/][#298014 on #2a7e15]▀[/][#2b7f19 on #266c14]▀[/][#1f3a1d on #16161e]▀[/][#16161e]███████[/][#191b27]█[/]
|
|
16
|
+
[#2c3555]█[/][#16161e]███████[/][#2f7420 on #16161e]▀[/][#2e7619 on #16161e]▀[/][#32861c on #16161e]▀[/][#2d771a on #16161e]▀[/][#31821a on #16161e]▀[/][#33871c on #16161e]▀[/][#35881d on #16161e]▀[/][#1d6a0a on #16161e]▀[/][#338a19 on #16161e]▀[/][#295b1f on #16161e]▀[/][#16161e]███████[/][#191b27]█[/]
|
|
17
|
+
[#2c3555]█[/][#16161e]████████████████████████[/][#191b27]█[/]
|
|
18
|
+
[#2c3555 on #2c3250]▀[/][#16161e]████████████████████████[/][#191b27]█[/]
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
LOGO_USER_RANGER_LARGE = """\
|
|
22
|
+
[#292e44]██[/][#16161e]███████████████████████████████████████████████████████[/][#191b27]███[/]
|
|
23
|
+
[#292e44]██[/][#16161e]███████████████████████████████████████████████████████[/][#191b27]███[/]
|
|
24
|
+
[#292e44]██[/][#16161e]███████████████████████████████████████████████████████[/][#191b27]███[/]
|
|
25
|
+
[#292e44]██[/][#16161e]███████████████████████████████████████████████████████[/][#191b27]███[/]
|
|
26
|
+
[#292e44]██[/][#16161e]███████████████████████████████████████████████████████[/][#191b27]███[/]
|
|
27
|
+
[#292e44]██[/][#16161e]███████████████████████████████████████████████████████[/][#191b27]███[/]
|
|
28
|
+
[#292e44]██[/][#16161e]███████████████████████████████████████████████████████[/][#191b27]███[/]
|
|
29
|
+
[#292e44]██[/][#16161e]██████████████████████████[/][#16161e on #2c4e21]▀▀[/][#16161e on #2e5721]▀▀[/][#16161e]█████████████████████████[/][#191b27]███[/]
|
|
30
|
+
[#292e44]██[/][#16161e]██████████████████[/][#16161e on #212a23]▀▀[/][#16161e on #20361b]▀▀[/][#16161e on #1a3318]▀▀[/][#16161e on #1d4e1b]▀▀[/][#2c4e21 on #347b23]▀▀[/][#2e5721 on #31871a]▀▀[/][#16161e on #326622]▀▀[/][#16161e on #285d1f]▀▀[/][#16161e]██[/][#16161e on #214319]▀▀[/][#16161e]█████████████████[/][#191b27]███[/]
|
|
31
|
+
[#292e44]██[/][#16161e]██████████████████[/][#212a23 on #294e22]▀▀[/][#20361b on #3b9424]▀▀[/][#1a3318 on #328b19]▀▀[/][#1d4e1b on #36951d]▀▀[/][#347b23 on #358f1b]▀▀[/][#31871a on #348c1d]▀▀[/][#326622 on #35921c]▀▀[/][#285d1f on #37921d]▀▀[/][#16161e on #36911c]▀▀[/][#214319 on #398a22]▀▀[/][#16161e on #263524]▀[/][#16161e]████████████████[/][#191b27]███[/]
|
|
32
|
+
[#292e44]██[/][#16161e]██████████████████[/][#294e22 on #16161e]▀▀[/][#3b9424 on #245d16]▀▀[/][#328b19 on #1f5113]▀▀[/][#36951d on #1c4710]▀▀[/][#358f1b on #1b4911]▀▀[/][#348c1d on #1c4b14]▀▀[/][#35921c on #215016]▀▀[/][#37921d on #235915]▀▀[/][#36911c on #216716]▀▀[/][#398a22 on #2c6d1c]▀▀[/][#263524 on #16161e]▀[/][#16161e]██[/][#16161e on #315d24]▀▀[/][#16161e on #2e6620]▀▀[/][#16161e on #2f562a]▀▀[/][#16161e]████████[/][#191b27]███[/]
|
|
33
|
+
[#292e44]██[/][#16161e]████████████████[/][#16161e on #233722]▀▀[/][#16161e on #1d2a1e]▀▀[/][#245d16 on #183f10]▀▀[/][#1f5113 on #14340e]▀▀[/][#1c4710 on #142a0a]▀▀[/][#1b4911 on #12280c]▀▀[/][#1c4b14 on #0f270c]▀▀[/][#215016 on #12270b]▀▀[/][#235915 on #132c0b]▀▀[/][#216716 on #11340b]▀▀[/][#2c6d1c on #16370f]▀▀[/][#16161e on #1e2323]▀[/][#16161e on #223821]▀▀[/][#315d24 on #305c23]▀▀[/][#2e6620 on #233e20]▀▀[/][#2f562a on #233524]▀▀[/][#16161e]████████[/][#191b27]███[/]
|
|
34
|
+
[#292e44]██[/][#16161e]████████████████[/][#2d5923]██[/][#24401d]██[/][#0e1f09]██[/][#0a1507]██[/][#080d05]██[/][#050706]██[/][#020403]██[/][#000000]████████[/][#263128]█[/][#2d5c21]██[/][#2c5a20]██[/][#16161e]████████████[/][#191b27]███[/]
|
|
35
|
+
[#292e44]██[/][#16161e]██████████[/][#263a20]██[/][#29501e]██[/][#215716]██[/][#28631d]██[/][#26621b]██[/][#216215]██[/][#226614]██[/][#236212]██[/][#286013]██[/][#185310]██[/][#2c451e]██[/][#373f20]██[/][#434227]██[/][#322821]██[/][#19221b]█[/][#16161e]████████████████[/][#191b27]███[/]
|
|
36
|
+
[#292e44]██[/][#16161e]████████████████████[/][#ad9163]██[/][#ae926b]██[/][#c6aa6d]██[/][#998b7b]██[/][#9f8950]██[/][#aa8c4c]██[/][#f9d97a]██[/][#ffec8b]██[/][#5b4d32]██[/][#000000]█[/][#16161e]████████████████[/][#191b27]███[/]
|
|
37
|
+
[#292e44]██[/][#16161e]████████████████████[/][#dcdbc4]██[/][#e7dbbd]██[/][#d3be8c]██[/][#fbf9ff]██[/][#d5b97e]██[/][#fed677]██[/][#ffdd7e]██[/][#f9d479]██[/][#261d13]██[/][#000100]█[/][#151513]██[/][#16161e]██████████████[/][#191b27]███[/]
|
|
38
|
+
[#292e44]██[/][#16161e]████████████████████[/][#5d6066]██[/][#b2a599]██[/][#635b45]██[/][#a2aab0]██[/][#d5c3a2]██[/][#fbd570]██[/][#ffdd7e]██[/][#e8c673]██[/][#150f0d]██[/][#000000]█[/][#080807]██[/][#16161e]██████████████[/][#191b27]███[/]
|
|
39
|
+
[#292e44]██[/][#16161e]██████████████[/][#5f5748]██[/][#ebca7d]██[/][#d1b56f]██[/][#55492f]██[/][#a18d5f]██[/][#7f6b3d]██[/][#4a4f56]██[/][#dac588]██[/][#fad573]██[/][#ffe07f]██[/][#dfbb6b]██[/][#0b0504]██[/][#040304]█[/][#080807]██[/][#16161e]██████████████[/][#191b27]███[/]
|
|
40
|
+
[#292e44]██[/][#16161e]████████████████[/][#ffdd82]██[/][#ffe57f]██[/][#ffdd7e]██[/][#f9d679]██[/][#f4d37b]██[/][#ab955f]██[/][#c7ab5c]██[/][#f4d172]██[/][#ffe181]██[/][#dbb86b]██[/][#0a0607]██[/][#000000]███[/][#16161e]██████████████[/][#191b27]███[/]
|
|
41
|
+
[#292e44]██[/][#16161e]████████████████[/][#ffdd82 on #8e7a49]▀▀[/][#ffe57f on #ffe281]▀▀[/][#ffdd7e on #ffe382]▀▀[/][#f9d679 on #ffdd7e]▀▀[/][#f4d37b on #c8aa56]▀▀[/][#ab955f on #e2be6d]▀▀[/][#c7ab5c on #ffda7e]▀▀[/][#f4d172 on #fed97b]▀▀[/][#ffe181 on #ffe07e]▀▀[/][#dbb86b on #deba6c]▀▀[/][#0a0607 on #020001]▀▀[/][#000000 on #574b31]▀[/][#000000 on #726642]▀▀[/][#16161e]██████████████[/][#191b27]███[/]
|
|
42
|
+
[#292e44]██[/][#16161e]████████████████[/][#8e7a49 on #83754c]▀▀[/][#ffe281 on #b39655]▀▀[/][#ffe382 on #baa661]▀▀[/][#ffdd7e on #947e48]▀▀[/][#c8aa56 on #e2c06f]▀▀[/][#e2be6d on #e4c06a]▀▀[/][#ffda7e on #d1af60]▀▀[/][#fed97b on #ffdd7e]▀▀[/][#ffe07e on #ffe07f]▀▀[/][#deba6c on #d5b265]▀▀[/][#020001 on #5d4b2d]▀▀[/][#574b31 on #ffe78a]▀[/][#726642 on #fbd87b]▀▀[/][#16161e on #86774a]▀▀[/][#16161e]████████████[/][#191b27]███[/]
|
|
43
|
+
[#292e44]██[/][#16161e]████████████████[/][#83754c on #ffdb80]▀▀[/][#b39655 on #fdd87b]▀▀[/][#baa661 on #bea35c]▀▀[/][#947e48 on #b49857]▀▀[/][#e2c06f on #ffe17f]▀▀[/][#e4c06a on #ffe283]▀▀[/][#d1af60 on #cba960]▀▀[/][#ffdd7e on #e8c56e]▀▀[/][#ffe07f on #ffe280]▀▀[/][#d5b265 on #ecc86f]▀▀[/][#5d4b2d on #debd70]▀▀[/][#ffe78a on #c0a35d]▀[/][#fbd87b on #e3c171]▀▀[/][#86774a on #b39f62]▀▀[/][#16161e]████████████[/][#191b27]███[/]
|
|
44
|
+
[#292e44]██[/][#16161e]██████████████[/][#16161e on #7b694d]▀▀[/][#ffdb80 on #ffe083]▀▀[/][#fdd87b on #fcd87a]▀▀[/][#bea35c on #c1a25e]▀▀[/][#b49857 on #f4d179]▀▀[/][#ffe17f on #ffe488]▀▀[/][#ffe283 on #9d8654]▀▀[/][#cba960 on #8b7444]▀▀[/][#e8c56e on #d6b466]▀▀[/][#ffe280 on #ffdc7e]▀▀[/][#ecc86f on #fed97b]▀▀[/][#debd70 on #ffdf7e]▀▀[/][#c0a35d on #edca70]▀[/][#e3c171 on #ffe981]▀▀[/][#b39f62 on #baa062]▀▀[/][#16161e]████████████[/][#191b27]███[/]
|
|
45
|
+
[#292e44]██[/][#16161e]██████████████[/][#7b694d on #786540]▀▀[/][#ffe083 on #ffe484]▀▀[/][#fcd87a on #d8b666]▀▀[/][#c1a25e on #b19655]▀▀[/][#f4d179 on #a58952]▀▀[/][#ffe488 on #2c261c]▀▀[/][#9d8654 on #080307]▀▀[/][#8b7444 on #ccad6a]▀▀[/][#d6b466 on #d5b358]▀▀[/][#ffdc7e on #f8d376]▀▀[/][#fed97b on #ffdc7d]▀▀[/][#ffdf7e on #eac471]▀▀[/][#edca70 on #edc973]▀[/][#ffe981 on #e4c678]▀▀[/][#baa062 on #16161e]▀▀[/][#16161e]████████████[/][#191b27]███[/]
|
|
46
|
+
[#292e44]██[/][#16161e]██████████████[/][#786540 on #16161e]▀▀[/][#ffe484 on #f4cf79]▀▀[/][#d8b666 on #ffdc7c]▀▀[/][#b19655 on #ffee87]▀▀[/][#a58952 on #b39957]▀▀[/][#2c261c on #010002]▀▀[/][#080307 on #483228]▀▀[/][#ccad6a on #ffe184]▀▀[/][#d5b358 on #deba63]▀▀[/][#f8d376 on #ecc86f]▀▀[/][#ffdc7d on #ffdc7c]▀▀[/][#eac471 on #fddb7d]▀▀[/][#edc973 on #97814b]▀[/][#e4c678 on #1a1513]▀▀[/][#16161e]██████████████[/][#191b27]███[/]
|
|
47
|
+
[#292e44]██[/][#16161e]████████████████[/][#f4cf79]██[/][#ffdc7c]██[/][#ffee87]██[/][#b39957]██[/][#010002]██[/][#483228]██[/][#ffe184]██[/][#deba63]██[/][#ecc86f]██[/][#ffdc7c]██[/][#fddb7d]██[/][#97814b]█[/][#1a1513]██[/][#16161e]██████████████[/][#191b27]███[/]
|
|
48
|
+
[#292e44]██[/][#16161e]████████████████[/][#c7ab62]██[/][#ffe885]██[/][#c8aa5d]██[/][#8b653f]██[/][#983d56]██[/][#c69062]██[/][#ffe683]██[/][#dfbb69]██[/][#e6c36b]██[/][#ffdb7d]██[/][#ffe680]██[/][#c5ac69]█[/][#16161e]████████████████[/][#191b27]███[/]
|
|
49
|
+
[#292e44]██[/][#16161e]████████████████[/][#9a8151]██[/][#fff089]██[/][#c1a259]██[/][#b8975a]██[/][#d7a864]██[/][#f6d176]██[/][#ffdf7d]██[/][#e9c56b]██[/][#eac56b]██[/][#ffde7c]██[/][#ffe786]██[/][#867447]█[/][#16161e]████████████████[/][#191b27]███[/]
|
|
50
|
+
[#292e44]██[/][#16161e]██████████████████[/][#fcd77c]██[/][#ffdc7e]██[/][#d3ae65]██[/][#fff08b]██[/][#ffdf82]██[/][#ffe083]██[/][#f4c874]██[/][#efc571]██[/][#ffe484]██[/][#ceac69]██[/][#16161e]█████████████████[/][#191b27]███[/]
|
|
51
|
+
[#292e44]██[/][#16161e]██████████████████[/][#8d804f]██[/][#ada559]██[/][#6e743c]██[/][#777c3a]██[/][#768233]██[/][#6b8436]██[/][#617a36]██[/][#56762d]██[/][#55802c]██[/][#406c24]██[/][#233f1c]█[/][#16161e]████████████████[/][#191b27]███[/]
|
|
52
|
+
[#292e44]██[/][#16161e]████████████████[/][#386f26]██[/][#1c5615]██[/][#052a07]██[/][#258416]██[/][#1c7911]██[/][#1c7c12]██[/][#218114]██[/][#258413]██[/][#298517]██[/][#268313]██[/][#30921a]██[/][#245d1a]█[/][#16161e]████████████████[/][#191b27]███[/]
|
|
53
|
+
[#292e44]██[/][#16161e]████████████████[/][#3c7b26]██[/][#173c12]██[/][#030202]██[/][#2d7419]██[/][#398f1f]██[/][#36861d]██[/][#32801a]██[/][#2e7c17]██[/][#287614]██[/][#2c7c15]██[/][#256c19]██[/][#16161e]█████████████████[/][#191b27]███[/]
|
|
54
|
+
[#292e44]██[/][#16161e]████████████████[/][#316123]██[/][#1a4513]██[/][#1b3d13]██[/][#2a7419]██[/][#246015]██[/][#206410]██[/][#246912]██[/][#267411]██[/][#2b7d17]██[/][#2a7e15]██[/][#266c14]██[/][#16161e]█████████████████[/][#191b27]███[/]
|
|
55
|
+
[#292e44]██[/][#16161e]████████████████[/][#316123 on #2f7420]▀▀[/][#1a4513 on #296d15]▀▀[/][#1b3d13 on #2e7619]▀▀[/][#2a7419 on #32861c]▀▀[/][#246015 on #2d771a]▀▀[/][#206410 on #31821a]▀▀[/][#246912 on #33871c]▀▀[/][#267411 on #35881d]▀▀[/][#2b7d17 on #33841b]▀▀[/][#2a7e15 on #1d6a0a]▀▀[/][#266c14 on #338a19]▀▀[/][#16161e on #295b1f]▀[/][#16161e]████████████████[/][#191b27]███[/]
|
|
56
|
+
[#292e44]██[/][#16161e]████████████████[/][#2f7420 on #16161e]▀▀[/][#296d15 on #16161e]▀▀[/][#2e7619 on #16161e]▀▀[/][#32861c on #16161e]▀▀[/][#2d771a on #16161e]▀▀[/][#31821a on #16161e]▀▀[/][#33871c on #16161e]▀▀[/][#35881d on #16161e]▀▀[/][#33841b on #16161e]▀▀[/][#1d6a0a on #16161e]▀▀[/][#338a19 on #16161e]▀▀[/][#295b1f on #16161e]▀[/][#16161e]████████████████[/][#191b27]███[/]
|
|
57
|
+
[#292e44]██[/][#16161e]███████████████████████████████████████████████████████[/][#191b27]███[/]
|
|
58
|
+
[#292e44]██[/][#16161e]███████████████████████████████████████████████████████[/][#191b27]███[/]
|
|
59
|
+
[#292e44]██[/][#16161e]███████████████████████████████████████████████████████[/][#191b27]███[/]
|
|
60
|
+
[#292e44]██[/][#16161e]███████████████████████████████████████████████████████[/][#191b27]███[/]
|
|
61
|
+
[#292e44 on #2a3047]▀[/][#292e44]█[/][#16161e]█[/][#16161e on #1d2133]▀[/][#16161e]███████████████████████████████████████████████████[/][#16161e on #171720]▀[/][#16161e]█[/][#191b27]███[/]
|
|
62
|
+
"""
|