cct-cli 0.7.9.0__py3-none-any.whl
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.
- calc_terminal/__init__.py +14 -0
- calc_terminal/__main__.py +14 -0
- calc_terminal/activity.py +1334 -0
- calc_terminal/agent.py +3387 -0
- calc_terminal/agent_runtime.py +519 -0
- calc_terminal/ai_context.py +447 -0
- calc_terminal/ai_modes.py +752 -0
- calc_terminal/ai_personalization.py +286 -0
- calc_terminal/ai_preview_feedback.py +213 -0
- calc_terminal/aicore.py +2572 -0
- calc_terminal/anim.py +367 -0
- calc_terminal/app.py +3685 -0
- calc_terminal/art.py +639 -0
- calc_terminal/atomsim.py +368 -0
- calc_terminal/attachments.py +743 -0
- calc_terminal/benchmark_system.py +414 -0
- calc_terminal/browser/__init__.py +36 -0
- calc_terminal/browser/browser_state.py +346 -0
- calc_terminal/browser/devserver.py +176 -0
- calc_terminal/browser/engine.py +494 -0
- calc_terminal/browser/navigation.py +84 -0
- calc_terminal/browser/preview.py +429 -0
- calc_terminal/browser/preview_entry.py +95 -0
- calc_terminal/browser/project_detector.py +144 -0
- calc_terminal/browser/server.py +449 -0
- calc_terminal/browser/state.py +75 -0
- calc_terminal/browser/watcher.py +99 -0
- calc_terminal/browser_gui/__init__.py +1 -0
- calc_terminal/browser_gui/__main__.py +3 -0
- calc_terminal/browser_gui/launcher.py +173 -0
- calc_terminal/browser_gui/playwright_browser.py +117 -0
- calc_terminal/browser_gui/qt_browser.py +1501 -0
- calc_terminal/browser_gui/webview_browser.py +57 -0
- calc_terminal/capabilities/__init__.py +35 -0
- calc_terminal/capabilities/adapters/__init__.py +33 -0
- calc_terminal/capabilities/adapters/bioinformatics.py +204 -0
- calc_terminal/capabilities/adapters/browser_adapter.py +205 -0
- calc_terminal/capabilities/adapters/filesystem.py +206 -0
- calc_terminal/capabilities/adapters/git_adapter.py +202 -0
- calc_terminal/capabilities/adapters/jupyter_adapter.py +138 -0
- calc_terminal/capabilities/adapters/ml_frameworks.py +158 -0
- calc_terminal/capabilities/adapters/platforms.py +200 -0
- calc_terminal/capabilities/adapters/python_exec.py +93 -0
- calc_terminal/capabilities/adapters/quantum_adapter.py +150 -0
- calc_terminal/capabilities/adapters/scientific_comp.py +123 -0
- calc_terminal/capabilities/adapters/structural_bio.py +161 -0
- calc_terminal/capabilities/adapters/terminal.py +99 -0
- calc_terminal/capabilities/bus.py +178 -0
- calc_terminal/capabilities/discovery.py +207 -0
- calc_terminal/capabilities/schema.py +221 -0
- calc_terminal/cat.ico +0 -0
- calc_terminal/cat_browser.py +2018 -0
- calc_terminal/chat_store.py +703 -0
- calc_terminal/cli.py +1178 -0
- calc_terminal/code_editor.py +640 -0
- calc_terminal/collaboration.py +723 -0
- calc_terminal/commands_data.py +139 -0
- calc_terminal/compatibility_engine.py +352 -0
- calc_terminal/compute/__init__.py +31 -0
- calc_terminal/compute/fabric.py +350 -0
- calc_terminal/config.py +227 -0
- calc_terminal/core/__init__.py +41 -0
- calc_terminal/core/checkpoint.py +156 -0
- calc_terminal/core/input/__init__.py +45 -0
- calc_terminal/core/mode_registry.py +300 -0
- calc_terminal/core/project_graph.py +172 -0
- calc_terminal/core/recovery.py +129 -0
- calc_terminal/core/security_layer.py +112 -0
- calc_terminal/core/task_graph.py +202 -0
- calc_terminal/core/unified_runtime.py +184 -0
- calc_terminal/core/verification.py +257 -0
- calc_terminal/customization.py +1566 -0
- calc_terminal/derivations.py +153 -0
- calc_terminal/device_control.py +263 -0
- calc_terminal/diagnostics/__init__.py +27 -0
- calc_terminal/diagnostics/doctor_engine.py +382 -0
- calc_terminal/diagnostics/self_test.py +247 -0
- calc_terminal/doctor.py +519 -0
- calc_terminal/easter_eggs.py +274 -0
- calc_terminal/editor/__init__.py +1 -0
- calc_terminal/editor/actions.py +263 -0
- calc_terminal/editor/commands.py +160 -0
- calc_terminal/editor/shortcuts.py +226 -0
- calc_terminal/engine.py +259 -0
- calc_terminal/errors.py +120 -0
- calc_terminal/event_stream.py +146 -0
- calc_terminal/eventbus.py +133 -0
- calc_terminal/extensions.py +733 -0
- calc_terminal/fallback_cli.py +1321 -0
- calc_terminal/first_run.py +265 -0
- calc_terminal/fomoji_auth.py +1043 -0
- calc_terminal/formulas.py +82 -0
- calc_terminal/fs_cache.py +121 -0
- calc_terminal/fs_watcher.py +277 -0
- calc_terminal/game.py +193 -0
- calc_terminal/gen1.py +5 -0
- calc_terminal/generators.py +245 -0
- calc_terminal/gestures/__init__.py +42 -0
- calc_terminal/gestures/bindings.py +175 -0
- calc_terminal/gestures/manager.py +477 -0
- calc_terminal/goodbye.py +363 -0
- calc_terminal/gpu3d.py +290 -0
- calc_terminal/graphs.py +358 -0
- calc_terminal/hardware_analyzer.py +440 -0
- calc_terminal/host/__init__.py +30 -0
- calc_terminal/host/browser_manager.py +187 -0
- calc_terminal/host/desktop.py +1386 -0
- calc_terminal/host/launcher.py +395 -0
- calc_terminal/host/terminal.py +279 -0
- calc_terminal/identity.py +216 -0
- calc_terminal/input/__init__.py +54 -0
- calc_terminal/input/capabilities.py +258 -0
- calc_terminal/input/focus.py +87 -0
- calc_terminal/input/gestures.py +64 -0
- calc_terminal/input/pointer.py +114 -0
- calc_terminal/input/touch.py +345 -0
- calc_terminal/keys.py +84 -0
- calc_terminal/live_automation.py +165 -0
- calc_terminal/mathtext.py +433 -0
- calc_terminal/mcp.py +386 -0
- calc_terminal/memory.py +337 -0
- calc_terminal/memory_v2.py +479 -0
- calc_terminal/metrics.py +333 -0
- calc_terminal/mode_detection.py +146 -0
- calc_terminal/model.py +2431 -0
- calc_terminal/model_router.py +665 -0
- calc_terminal/models/__init__.py +0 -0
- calc_terminal/models/active_state.py +187 -0
- calc_terminal/models/dynamic_registry.py +584 -0
- calc_terminal/models/manager.py +781 -0
- calc_terminal/models/model_metadata.json +3526 -0
- calc_terminal/models/profiles.py +194 -0
- calc_terminal/models/registry.py +265 -0
- calc_terminal/models/schema.py +197 -0
- calc_terminal/models/validator.py +287 -0
- calc_terminal/models/verification_engine.py +368 -0
- calc_terminal/native_picker.py +215 -0
- calc_terminal/ollama_catalog.py +279 -0
- calc_terminal/ollama_download.py +233 -0
- calc_terminal/orchestrator.py +304 -0
- calc_terminal/package_research.py +322 -0
- calc_terminal/packages.py +1024 -0
- calc_terminal/pc_specs.py +116 -0
- calc_terminal/permissions.py +334 -0
- calc_terminal/pet.py +106 -0
- calc_terminal/pipeline.py +505 -0
- calc_terminal/platform/__init__.py +491 -0
- calc_terminal/platform/desktop.py +491 -0
- calc_terminal/platform/web.py +781 -0
- calc_terminal/preview/__init__.py +1 -0
- calc_terminal/preview/dev_server.py +303 -0
- calc_terminal/preview/diagnostics.py +131 -0
- calc_terminal/preview/live_reload.py +66 -0
- calc_terminal/preview/manager.py +129 -0
- calc_terminal/project_stats.py +209 -0
- calc_terminal/projects.py +328 -0
- calc_terminal/providers/__init__.py +0 -0
- calc_terminal/providers/adapters/__init__.py +80 -0
- calc_terminal/providers/adapters/anthropic_adapter.py +127 -0
- calc_terminal/providers/adapters/base.py +106 -0
- calc_terminal/providers/adapters/chinese_adapters.py +420 -0
- calc_terminal/providers/adapters/gemini_adapter.py +101 -0
- calc_terminal/providers/adapters/ollama_adapter.py +83 -0
- calc_terminal/providers/adapters/openai_adapter.py +159 -0
- calc_terminal/providers/adapters/other_adapters.py +246 -0
- calc_terminal/providers/anthropic_provider.py +172 -0
- calc_terminal/providers/auto_update.py +416 -0
- calc_terminal/providers/base_provider.py +105 -0
- calc_terminal/providers/discovery_manager.py +207 -0
- calc_terminal/providers/gemini_provider.py +178 -0
- calc_terminal/providers/lifecycle.py +767 -0
- calc_terminal/providers/ollama_adapter.py +707 -0
- calc_terminal/providers/openai_provider.py +254 -0
- calc_terminal/providers/provider_manager.py +1827 -0
- calc_terminal/providers/providers.json +4075 -0
- calc_terminal/reactionsim.py +279 -0
- calc_terminal/registry.py +337 -0
- calc_terminal/report.py +162 -0
- calc_terminal/research/__init__.py +45 -0
- calc_terminal/research/artifact_intel.py +126 -0
- calc_terminal/research/data_lineage.py +123 -0
- calc_terminal/research/experiment_ledger.py +303 -0
- calc_terminal/research/reproducibility.py +131 -0
- calc_terminal/resilience/__init__.py +47 -0
- calc_terminal/resilience/agent_state.py +121 -0
- calc_terminal/resilience/capability_matcher.py +174 -0
- calc_terminal/resilience/circuit_breaker.py +158 -0
- calc_terminal/resilience/failover_engine.py +230 -0
- calc_terminal/resilience/health_monitor.py +192 -0
- calc_terminal/resilience/ollama_adapter.py +125 -0
- calc_terminal/resilience/orchestrator.py +312 -0
- calc_terminal/resilience/types.py +134 -0
- calc_terminal/sandbox.py +98 -0
- calc_terminal/scires.py +558 -0
- calc_terminal/security_scanner.py +126 -0
- calc_terminal/session.py +294 -0
- calc_terminal/sim3d.py +206 -0
- calc_terminal/solver.py +276 -0
- calc_terminal/sound.py +127 -0
- calc_terminal/task_reports.py +287 -0
- calc_terminal/terminal_host.py +201 -0
- calc_terminal/terminal_identity.py +411 -0
- calc_terminal/test_ai_mode_reliability.py +344 -0
- calc_terminal/test_browser.py +368 -0
- calc_terminal/test_code_editor_upgrade.py +485 -0
- calc_terminal/test_customization.py +1148 -0
- calc_terminal/test_customization_ui.py +612 -0
- calc_terminal/test_dynamic_registry.py +304 -0
- calc_terminal/test_extensions.py +436 -0
- calc_terminal/test_overhaul.py +557 -0
- calc_terminal/test_project_detect.py +255 -0
- calc_terminal/test_root_cause_fix.py +527 -0
- calc_terminal/test_stability.py +532 -0
- calc_terminal/test_terminal_identity.py +132 -0
- calc_terminal/test_v079_speed.py +460 -0
- calc_terminal/theme.py +1107 -0
- calc_terminal/timeline.py +139 -0
- calc_terminal/todos.py +246 -0
- calc_terminal/tool_call_normalizer.py +419 -0
- calc_terminal/tui.py +104 -0
- calc_terminal/ui/__init__.py +8 -0
- calc_terminal/ui/activity_panel.py +231 -0
- calc_terminal/ui/activity_stream_panel.py +238 -0
- calc_terminal/ui/animations.py +122 -0
- calc_terminal/ui/app.py +7271 -0
- calc_terminal/ui/attach_panel.py +597 -0
- calc_terminal/ui/attachments.py +424 -0
- calc_terminal/ui/backup_panel.py +810 -0
- calc_terminal/ui/browser_shell.py +887 -0
- calc_terminal/ui/cat_agent.py +357 -0
- calc_terminal/ui/chats_panel.py +899 -0
- calc_terminal/ui/command_palette.py +125 -0
- calc_terminal/ui/command_palette_modal.py +166 -0
- calc_terminal/ui/composer.py +1141 -0
- calc_terminal/ui/context_menu.py +197 -0
- calc_terminal/ui/conversation.py +1435 -0
- calc_terminal/ui/customization_panel.py +1229 -0
- calc_terminal/ui/dashboard.py +404 -0
- calc_terminal/ui/design_system.py +557 -0
- calc_terminal/ui/diff_panel.py +213 -0
- calc_terminal/ui/editor.py +2102 -0
- calc_terminal/ui/empty_state.py +302 -0
- calc_terminal/ui/events.py +487 -0
- calc_terminal/ui/extensions_panel.py +815 -0
- calc_terminal/ui/footer.py +166 -0
- calc_terminal/ui/gestures_panel.py +383 -0
- calc_terminal/ui/goodbye_screen.py +100 -0
- calc_terminal/ui/header.py +1034 -0
- calc_terminal/ui/help_panel.py +254 -0
- calc_terminal/ui/live_activities.py +914 -0
- calc_terminal/ui/mcp_panel.py +570 -0
- calc_terminal/ui/memory_center.py +524 -0
- calc_terminal/ui/mode_colors_panel.py +525 -0
- calc_terminal/ui/nav_screens.py +747 -0
- calc_terminal/ui/ollama_panel.py +536 -0
- calc_terminal/ui/palette.py +221 -0
- calc_terminal/ui/permission_panel.py +269 -0
- calc_terminal/ui/personalization_panel.py +517 -0
- calc_terminal/ui/personalize_center.py +1568 -0
- calc_terminal/ui/preview_panel.py +441 -0
- calc_terminal/ui/resizers.py +402 -0
- calc_terminal/ui/sidebar.py +1285 -0
- calc_terminal/ui/statusbar.py +168 -0
- calc_terminal/ui/theme_css.py +1396 -0
- calc_terminal/ui/thinking.py +226 -0
- calc_terminal/ui/timeline_panel.py +102 -0
- calc_terminal/ui/todo_panel.py +193 -0
- calc_terminal/ui/viewport.py +136 -0
- calc_terminal/ui/vision_panel.py +489 -0
- calc_terminal/ui/welcome_modal.py +343 -0
- calc_terminal/ui/widgets.py +160 -0
- calc_terminal/ui/workspace.py +831 -0
- calc_terminal/viewers/__init__.py +1 -0
- calc_terminal/viewers/document_viewer.py +252 -0
- calc_terminal/viewers/image_viewer.py +241 -0
- calc_terminal/viewers/pdf_viewer.py +203 -0
- calc_terminal/viewers/presentation_viewer.py +164 -0
- calc_terminal/viewers/registry.py +120 -0
- calc_terminal/viewers/spreadsheet_viewer.py +204 -0
- calc_terminal/vision/__init__.py +89 -0
- calc_terminal/vision/analysis.py +194 -0
- calc_terminal/vision/annotations.py +297 -0
- calc_terminal/vision/capture.py +171 -0
- calc_terminal/vision/context.py +231 -0
- calc_terminal/vision/correlation.py +169 -0
- calc_terminal/vision/cursor.py +258 -0
- calc_terminal/vision/events.py +66 -0
- calc_terminal/vision/frame_pipeline.py +259 -0
- calc_terminal/vision/priority.py +218 -0
- calc_terminal/vision/provider.py +180 -0
- calc_terminal/vision/safety.py +149 -0
- calc_terminal/vision/session.py +281 -0
- calc_terminal/vision/verify.py +162 -0
- calc_terminal/vision.py +514 -0
- calc_terminal/vscode_integration.py +113 -0
- calc_terminal/web/__init__.py +8 -0
- calc_terminal/web/cat_runtime.py +710 -0
- calc_terminal/web/server.py +2891 -0
- calc_terminal/web/static/css/app.css +3152 -0
- calc_terminal/web/static/icons/badge-72.png +0 -0
- calc_terminal/web/static/icons/cat.ico +0 -0
- calc_terminal/web/static/icons/icon-128.png +0 -0
- calc_terminal/web/static/icons/icon-144.png +0 -0
- calc_terminal/web/static/icons/icon-152.png +0 -0
- calc_terminal/web/static/icons/icon-192.png +0 -0
- calc_terminal/web/static/icons/icon-384.png +0 -0
- calc_terminal/web/static/icons/icon-512.png +0 -0
- calc_terminal/web/static/icons/icon-72.png +0 -0
- calc_terminal/web/static/icons/icon-96.png +0 -0
- calc_terminal/web/static/icons/icon.svg +34 -0
- calc_terminal/web/static/icons/new-project.png +0 -0
- calc_terminal/web/static/icons/open-project.png +0 -0
- calc_terminal/web/static/index.html +734 -0
- calc_terminal/web/static/js/app.js +2403 -0
- calc_terminal/web/static/manifest.json +88 -0
- calc_terminal/web/static/sw.js +230 -0
- calc_terminal/workflow_engine.py +769 -0
- calc_terminal/workspace.py +593 -0
- calc_terminal/workspace_index.py +385 -0
- cct_cli-0.7.9.0.dist-info/METADATA +210 -0
- cct_cli-0.7.9.0.dist-info/RECORD +325 -0
- cct_cli-0.7.9.0.dist-info/WHEEL +5 -0
- cct_cli-0.7.9.0.dist-info/entry_points.txt +4 -0
- cct_cli-0.7.9.0.dist-info/licenses/LICENSE +21 -0
- cct_cli-0.7.9.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,1396 @@
|
|
|
1
|
+
"""
|
|
2
|
+
CCT UI theme bridge — theme.py is the single palette source; this module
|
|
3
|
+
is the ONLY place that turns it into Textual CSS variables. There is no
|
|
4
|
+
second, hand-authored palette here (that was the old composer.py's
|
|
5
|
+
COMPOSER_CSS bug — a flat black-and-blue palette that drifted from the
|
|
6
|
+
rest of the app's Tokyo Night colors). Every color a Textual widget
|
|
7
|
+
uses comes from theme.py's module-level RGB tuples via `css_variables()`
|
|
8
|
+
below, so `/theme light` restyles the primary UI exactly the same way
|
|
9
|
+
it already restyles every print()-based panel.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
if __name__ == "__main__":
|
|
13
|
+
print("This is a library file and is not meant to be run directly.")
|
|
14
|
+
import sys
|
|
15
|
+
sys.exit(1)
|
|
16
|
+
|
|
17
|
+
from .. import theme
|
|
18
|
+
from .. import ai_modes
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def _patch_rich_divide_line():
|
|
22
|
+
"""Defend against Rich/Textual ValueError: range() arg 3 must not be zero
|
|
23
|
+
when widgets are rendered with width <= line_pad * 2."""
|
|
24
|
+
try:
|
|
25
|
+
import rich._wrap
|
|
26
|
+
import rich.cells
|
|
27
|
+
|
|
28
|
+
orig_divide_line = getattr(rich._wrap, "_cat_orig_divide_line", None)
|
|
29
|
+
if orig_divide_line is None:
|
|
30
|
+
orig_divide_line = rich._wrap.divide_line
|
|
31
|
+
rich._wrap._cat_orig_divide_line = orig_divide_line
|
|
32
|
+
|
|
33
|
+
def safe_divide_line(text, width, fold=True):
|
|
34
|
+
if width <= 0:
|
|
35
|
+
return []
|
|
36
|
+
return orig_divide_line(text, width, fold=fold)
|
|
37
|
+
|
|
38
|
+
rich._wrap.divide_line = safe_divide_line
|
|
39
|
+
|
|
40
|
+
orig_chop_cells = getattr(rich.cells, "_cat_orig_chop_cells", None)
|
|
41
|
+
if orig_chop_cells is None:
|
|
42
|
+
orig_chop_cells = rich.cells.chop_cells
|
|
43
|
+
rich.cells._cat_orig_chop_cells = orig_chop_cells
|
|
44
|
+
|
|
45
|
+
def safe_chop_cells(text, width, unicode_version="auto"):
|
|
46
|
+
if width <= 0:
|
|
47
|
+
return [text] if text else []
|
|
48
|
+
return orig_chop_cells(text, width, unicode_version=unicode_version)
|
|
49
|
+
|
|
50
|
+
rich.cells.chop_cells = safe_chop_cells
|
|
51
|
+
|
|
52
|
+
try:
|
|
53
|
+
import textual.content
|
|
54
|
+
if hasattr(textual.content, "divide_line"):
|
|
55
|
+
textual.content.divide_line = rich._wrap.divide_line
|
|
56
|
+
except Exception:
|
|
57
|
+
pass
|
|
58
|
+
except Exception:
|
|
59
|
+
pass
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
_patch_rich_divide_line()
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def _hex(rgb):
|
|
66
|
+
r, g, b = rgb
|
|
67
|
+
return f"#{r:02x}{g:02x}{b:02x}"
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
# Textual CSS variable name -> theme.py attribute name. One row per
|
|
71
|
+
# variable; add a color here, not a second copy of its value anywhere
|
|
72
|
+
# else. `panel`/`element`/`border*` reuse the OC_* set theme.py already
|
|
73
|
+
# maintains for exactly this "OpenCode-style" surface (dark AND light
|
|
74
|
+
# variants already defined there — see theme.py's _THEMES dict).
|
|
75
|
+
_VARIABLE_SOURCE = {
|
|
76
|
+
"surface": "OC_BG_PANEL",
|
|
77
|
+
"surface-alt": "OC_BG_ELEMENT",
|
|
78
|
+
"panel": "OC_BG_PANEL",
|
|
79
|
+
"panel-alt": "OC_BG_ELEMENT",
|
|
80
|
+
"border": "OC_BORDER",
|
|
81
|
+
"border-active": "OC_BORDER_ACTIVE",
|
|
82
|
+
"accent": "OC_PRIMARY",
|
|
83
|
+
"accent-secondary": "OC_SECONDARY",
|
|
84
|
+
"accent-highlight": "OC_ACCENT",
|
|
85
|
+
"text": "TEXT",
|
|
86
|
+
"text-muted": "DIM",
|
|
87
|
+
"text-faint": "FAINT",
|
|
88
|
+
"success": "GREEN",
|
|
89
|
+
"warning": "ORANGE",
|
|
90
|
+
"error": "RED",
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
_CSS_VAR_CACHE = {}
|
|
95
|
+
_LAST_THEME_MODE = None
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def invalidate_css_cache():
|
|
99
|
+
global _LAST_THEME_MODE, _CSS_VAR_CACHE
|
|
100
|
+
_LAST_THEME_MODE = None
|
|
101
|
+
_CSS_VAR_CACHE.clear()
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def bevel_colors(hex_color):
|
|
105
|
+
"""Computes a lighter top-left highlight and a darker bottom-right shadow from a hex color."""
|
|
106
|
+
try:
|
|
107
|
+
c = str(hex_color).lstrip("#")
|
|
108
|
+
if len(c) == 3:
|
|
109
|
+
c = "".join(ch * 2 for ch in c)
|
|
110
|
+
r = int(c[0:2], 16)
|
|
111
|
+
g = int(c[2:4], 16)
|
|
112
|
+
b = int(c[4:6], 16)
|
|
113
|
+
# Highlight: blend +35% towards white
|
|
114
|
+
hr = min(255, int(r + (255 - r) * 0.35))
|
|
115
|
+
hg = min(255, int(g + (255 - g) * 0.35))
|
|
116
|
+
hb = min(255, int(b + (255 - b) * 0.35))
|
|
117
|
+
# Shadow: blend -45% towards dark black/navy
|
|
118
|
+
sr = max(0, int(r * 0.45))
|
|
119
|
+
sg = max(0, int(g * 0.45))
|
|
120
|
+
sb = max(0, int(b * 0.45))
|
|
121
|
+
return f"#{hr:02x}{hg:02x}{hb:02x}", f"#{sr:02x}{sg:02x}{sb:02x}"
|
|
122
|
+
except Exception:
|
|
123
|
+
return hex_color or "#38bdf8", hex_color or "#0f172a"
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def css_variables():
|
|
127
|
+
"""Returns {css_variable_name: '#rrggbb'} built from theme.py's
|
|
128
|
+
CURRENT palette (dark or light — whichever `theme.set_theme()` last
|
|
129
|
+
selected). Pass this from `CCTApp.get_css_variables()`.
|
|
130
|
+
|
|
131
|
+
On top of the dark/light base palette, the active AI mode
|
|
132
|
+
(ai_modes.current_mode() — spec section 3) repaints `accent` and
|
|
133
|
+
`accent-highlight` with that mode's color. This is the one thing
|
|
134
|
+
that makes "Agent mode changes... accent color / borders / status
|
|
135
|
+
indicators" (and the same for Build/Plan) true everywhere those two
|
|
136
|
+
variables are already used (BrandHeader's mark, focused-panel
|
|
137
|
+
borders, footer badges) — without a second, mode-specific palette
|
|
138
|
+
to hand-maintain alongside theme.py's dark/light one.
|
|
139
|
+
|
|
140
|
+
v0.7.9.0 LIGHT-MODE AUDIT: Textual's built-in defaults for a whole
|
|
141
|
+
family of component variables are DARK-MODE values (#E0E0E0 text on
|
|
142
|
+
buttons, reverse-video button focus, pale selection washes). In
|
|
143
|
+
dark they're invisible because our own rules override most of them;
|
|
144
|
+
in light they leak through as washed-out / glowing / unreadable
|
|
145
|
+
highlights. When the active theme is light we now emit explicit,
|
|
146
|
+
contrast-correct values for that family — dark emits nothing, so
|
|
147
|
+
dark mode stays byte-for-byte what it was."""
|
|
148
|
+
global _LAST_THEME_MODE, _CSS_VAR_CACHE
|
|
149
|
+
cur_theme = getattr(theme, "CURRENT_THEME_NAME", "tokyo-night")
|
|
150
|
+
cur_mode = ai_modes.current_mode() if hasattr(ai_modes, "current_mode") else "notebook"
|
|
151
|
+
current_key = (cur_theme, cur_mode)
|
|
152
|
+
if _LAST_THEME_MODE == current_key and _CSS_VAR_CACHE:
|
|
153
|
+
return _CSS_VAR_CACHE
|
|
154
|
+
|
|
155
|
+
out = {}
|
|
156
|
+
for var_name, attr_name in _VARIABLE_SOURCE.items():
|
|
157
|
+
rgb = getattr(theme, attr_name, None)
|
|
158
|
+
if rgb is not None:
|
|
159
|
+
out[var_name] = _hex(rgb)
|
|
160
|
+
bg = theme._TERMINAL_BG.get(theme.CURRENT_THEME_NAME, (0, 0, 0))
|
|
161
|
+
out["app-background"] = _hex(bg)
|
|
162
|
+
out["background"] = out["app-background"]
|
|
163
|
+
mode_accent = ai_modes.accent_hex()
|
|
164
|
+
out["accent"] = mode_accent
|
|
165
|
+
hi, sh = bevel_colors(mode_accent)
|
|
166
|
+
out["accent-highlight"] = hi
|
|
167
|
+
out["accent-shadow"] = sh
|
|
168
|
+
out["border-active"] = mode_accent
|
|
169
|
+
s_alt_rgb = getattr(theme, "OC_BG_ELEMENT", (30, 30, 46))
|
|
170
|
+
s_rgb = getattr(theme, "OC_BG_PANEL", (24, 24, 37))
|
|
171
|
+
out["surface-highlight"] = blend_hex(s_alt_rgb, (255, 255, 255), 0.18)
|
|
172
|
+
out["surface-shadow"] = blend_hex(s_rgb, (0, 0, 0), 0.45)
|
|
173
|
+
out["surface-dark"] = blend_hex(s_rgb, (0, 0, 0), 0.35)
|
|
174
|
+
out["surface-active"] = blend_hex(s_alt_rgb, (255, 255, 255), 0.08)
|
|
175
|
+
out["surface-light"] = out["surface-highlight"]
|
|
176
|
+
out["accent-light"] = out["accent-highlight"]
|
|
177
|
+
out["accent-muted"] = out["accent-shadow"]
|
|
178
|
+
out["accent-dark"] = out["accent-shadow"]
|
|
179
|
+
# Standard Textual core variable fallbacks (for Switch, DataTable, etc.)
|
|
180
|
+
out["foreground"] = out.get("text", "#e0e0e0")
|
|
181
|
+
out["primary"] = out.get("accent", "#38bdf8")
|
|
182
|
+
out["secondary"] = out.get("accent-secondary", "#818cf8")
|
|
183
|
+
out["border-blurred"] = out.get("border", "#30363d")
|
|
184
|
+
out["screen-selection-background"] = out.get("accent", "#38bdf8") + "33"
|
|
185
|
+
out["screen-selection-foreground"] = "#ffffff"
|
|
186
|
+
|
|
187
|
+
# v0.7.8 FEATURE 3 (Mode Gradients): the active mode's two-stop
|
|
188
|
+
# gradient, exposed for glass/shimmer accents (composer badge,
|
|
189
|
+
# welcome hero, header mark). `accent` stays the flat solid color
|
|
190
|
+
# for borders/bubbles; the gradient is used where a subtle wash is
|
|
191
|
+
# wanted — never heavy.
|
|
192
|
+
g_start, g_end = ai_modes.gradient()
|
|
193
|
+
out["accent-gradient-start"] = g_start
|
|
194
|
+
out["accent-gradient-end"] = g_end
|
|
195
|
+
|
|
196
|
+
# v0.7.9.5 THEME CATALOG: every component-level variable below is now
|
|
197
|
+
# derived from the ACTIVE THEME OBJECT (theme.get_theme_obj()) instead
|
|
198
|
+
# of hardcoded GitHub-Light hexes, and emitted whenever the theme is
|
|
199
|
+
# light. Dark themes keep Textual's own dark-mode defaults (unchanged
|
|
200
|
+
# behavior from the v0.7.9.0 audit). Because each theme's schema
|
|
201
|
+
# carries tuned selection/cursor/heading colors, ANY of the 22 themes
|
|
202
|
+
# gets correct, contrast-checked chrome — no theme can inherit another
|
|
203
|
+
# theme's colors anymore.
|
|
204
|
+
if theme.is_light():
|
|
205
|
+
t = theme.get_theme_obj()
|
|
206
|
+
text_hex = t.hex("text")
|
|
207
|
+
muted_hex = t.hex("text_muted")
|
|
208
|
+
blue_hex = t.hex("accent_alt")
|
|
209
|
+
cursor_bg = blend_hex(t.text, t.background, 0.12)
|
|
210
|
+
out.update({
|
|
211
|
+
# Buttons: Textual's default label color is #E0E0E0 (near-
|
|
212
|
+
# white), which vanishes on light surfaces.
|
|
213
|
+
"button-foreground": text_hex,
|
|
214
|
+
# Focus: replace the default `b reverse` full-block invert
|
|
215
|
+
# with plain bold — the active border already marks focus.
|
|
216
|
+
"button-focus-text-style": "bold",
|
|
217
|
+
# Text/inputs: subtle, readable selection — a soft solid
|
|
218
|
+
# wash with dark text, never a bright glowing block, and it
|
|
219
|
+
# cannot bleed into unselected neighbors.
|
|
220
|
+
"input-selection-background": t.hex("selection_background"),
|
|
221
|
+
"input-selection-foreground": t.hex("selection_text"),
|
|
222
|
+
"screen-selection-background": t.hex("selection_background"),
|
|
223
|
+
"screen-selection-foreground": t.hex("selection_text"),
|
|
224
|
+
# Cursors: clearly visible without being loud.
|
|
225
|
+
"input-cursor-background": cursor_bg,
|
|
226
|
+
"input-cursor-foreground": t.hex("background"),
|
|
227
|
+
"block-cursor-background": blue_hex,
|
|
228
|
+
"block-cursor-foreground": "auto 87%",
|
|
229
|
+
"block-cursor-blurred-background": blue_hex + "44",
|
|
230
|
+
"block-cursor-blurred-foreground": text_hex,
|
|
231
|
+
# Footer key caps: amber readable on light surfaces.
|
|
232
|
+
"footer-key-foreground": t.hex("warning"),
|
|
233
|
+
# Blurred borders stop assuming a near-black chrome color.
|
|
234
|
+
"border-blurred": t.hex("border"),
|
|
235
|
+
# Markdown headings in assistant bubbles: darker, WCAG-safe.
|
|
236
|
+
"markdown-h1-color": blue_hex,
|
|
237
|
+
"markdown-h2-color": blue_hex,
|
|
238
|
+
"markdown-h3-color": blue_hex,
|
|
239
|
+
"markdown-h4-color": text_hex,
|
|
240
|
+
"markdown-h5-color": text_hex,
|
|
241
|
+
# In light mode, surface tones must be soft and adaptive, never dark/black
|
|
242
|
+
"surface-highlight": blend_hex(t.background, (255, 255, 255), 0.60),
|
|
243
|
+
"accent-highlight": blue_hex,
|
|
244
|
+
"accent-light": blue_hex,
|
|
245
|
+
"surface-shadow": t.hex("border"),
|
|
246
|
+
"surface-dark": t.hex("border"),
|
|
247
|
+
"surface-active": blend_hex(t.background, t.text, 0.08),
|
|
248
|
+
"surface-alt": blend_hex(t.background, t.text, 0.04),
|
|
249
|
+
})
|
|
250
|
+
_LAST_THEME_MODE = current_key
|
|
251
|
+
_CSS_VAR_CACHE = out
|
|
252
|
+
return out
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
def blend_hex(a, b, t):
|
|
256
|
+
"""Blend two RGB tuples (t=0 → a, t=1 → b) into a '#rrggbb' string."""
|
|
257
|
+
r1, g1, b1 = a
|
|
258
|
+
r2, g2, b2 = b
|
|
259
|
+
return "#{:02x}{:02x}{:02x}".format(
|
|
260
|
+
int(round(r1 + (r2 - r1) * t)),
|
|
261
|
+
int(round(g1 + (g2 - g1) * t)),
|
|
262
|
+
int(round(b1 + (b2 - b1) * t)))
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
def gradient_hex(key=None):
|
|
266
|
+
"""Direct (start_hex, end_hex) lookup for the active (or named) AI
|
|
267
|
+
mode's gradient, for Rich-markup callers outside CSS."""
|
|
268
|
+
return ai_modes.gradient(key)
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
def current_hex(role):
|
|
272
|
+
"""One-off lookup for widgets that need a raw hex string outside CSS
|
|
273
|
+
var resolution (e.g. an f-string building Rich markup). `role` is
|
|
274
|
+
one of the keys in `_VARIABLE_SOURCE` above, or 'app-background'."""
|
|
275
|
+
return css_variables().get(role, "#888888")
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
# Shared layout/spacing rules every ui/ widget file composes with. Pure
|
|
279
|
+
# structure — no literal colors — every color reference below is a Textual
|
|
280
|
+
# `$variable` resolved at runtime from css_variables(), so switching
|
|
281
|
+
# `/theme light` <-> dark restyles this without touching a single rule here.
|
|
282
|
+
BASE_CSS = """
|
|
283
|
+
Screen { background: $app-background; }
|
|
284
|
+
|
|
285
|
+
.cct-panel {
|
|
286
|
+
background: $surface;
|
|
287
|
+
border: heavy;
|
|
288
|
+
border-top: heavy $surface-highlight;
|
|
289
|
+
border-left: heavy $surface-highlight;
|
|
290
|
+
border-bottom: heavy $surface-dark;
|
|
291
|
+
border-right: heavy $surface-dark;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
.cct-panel:focus-within {
|
|
295
|
+
border: heavy;
|
|
296
|
+
border-top: heavy #ffffff;
|
|
297
|
+
border-left: heavy #ffffff;
|
|
298
|
+
border-bottom: heavy $border-active;
|
|
299
|
+
border-right: heavy $border-active;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
.cct-card-3d, .cct-panel-3d {
|
|
303
|
+
background: $surface;
|
|
304
|
+
border: heavy;
|
|
305
|
+
border-top: heavy $surface-highlight;
|
|
306
|
+
border-left: heavy $surface-highlight;
|
|
307
|
+
border-bottom: heavy $surface-dark;
|
|
308
|
+
border-right: heavy $surface-dark;
|
|
309
|
+
padding: 1 2;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
.cct-text { color: $text; }
|
|
313
|
+
.cct-text-muted { color: $text-muted; }
|
|
314
|
+
.cct-text-faint { color: $text-faint; }
|
|
315
|
+
.cct-success { color: $success; }
|
|
316
|
+
.cct-warning { color: $warning; }
|
|
317
|
+
.cct-error { color: $error; }
|
|
318
|
+
|
|
319
|
+
/* Tooltip: themed, 3D raised border with legible contrast (fixes black-box hover bug) */
|
|
320
|
+
Tooltip {
|
|
321
|
+
background: $surface-alt;
|
|
322
|
+
color: $text;
|
|
323
|
+
border: heavy $border-active;
|
|
324
|
+
padding: 0 1;
|
|
325
|
+
text-style: none;
|
|
326
|
+
max-width: 60;
|
|
327
|
+
opacity: 0.98;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
/* Toast: themed 3D raised notification card with beveled lighting */
|
|
331
|
+
Toast, Toast.-information, Toast.-warning, Toast.-error {
|
|
332
|
+
background: $surface;
|
|
333
|
+
color: $text;
|
|
334
|
+
border: heavy;
|
|
335
|
+
border-top: heavy #ffffff;
|
|
336
|
+
border-left: heavy #ffffff;
|
|
337
|
+
border-bottom: heavy $surface-dark;
|
|
338
|
+
border-right: heavy $surface-dark;
|
|
339
|
+
padding: 1 2;
|
|
340
|
+
}
|
|
341
|
+
Toast.-warning {
|
|
342
|
+
border-top: heavy #ffffff;
|
|
343
|
+
border-left: heavy #ffffff;
|
|
344
|
+
border-bottom: heavy $warning;
|
|
345
|
+
border-right: heavy $warning;
|
|
346
|
+
}
|
|
347
|
+
Toast.-error {
|
|
348
|
+
border-top: heavy #ffffff;
|
|
349
|
+
border-left: heavy #ffffff;
|
|
350
|
+
border-bottom: heavy $error;
|
|
351
|
+
border-right: heavy $error;
|
|
352
|
+
}
|
|
353
|
+
Toast.-information {
|
|
354
|
+
border-top: heavy #ffffff;
|
|
355
|
+
border-left: heavy #ffffff;
|
|
356
|
+
border-bottom: heavy $accent-shadow;
|
|
357
|
+
border-right: heavy $accent-shadow;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
/* ==================================================== v0.7.8.1 ======
|
|
361
|
+
Shared DESIGN SYSTEM — button + popup conventions every dialog in
|
|
362
|
+
ui/ follows, so no panel ever ships its own ad-hoc sizes again.
|
|
363
|
+
------------------------------------------------------------------ */
|
|
364
|
+
|
|
365
|
+
/* ---- Button system (v0.7.8.61): ONE shared definition for every
|
|
366
|
+
button in the app. Bare `Button` rules override Textual's own
|
|
367
|
+
default "hkey/tall" half-block design (▔▁ rails / ▊▔▎ boxes) at
|
|
368
|
+
equal specificity — app CSS is merged after widget CSS, so the tie
|
|
369
|
+
resolves to this block. Variants drop in via Textual's emitted
|
|
370
|
+
`.-primary` / `.-error` / `.-success` classes. The clickable region,
|
|
371
|
+
border, background, and text all share the same 3-cell footprint:
|
|
372
|
+
no button-specific positioning, spacing hacks, or fractional sizes.
|
|
373
|
+
|
|
374
|
+
v0.7.8.61 changes (washed-out / "highlighted" button look):
|
|
375
|
+
- `text-style: not bold` — Textual's default Button CSS force-bolds
|
|
376
|
+
every label (`Button.-style-default { text-style: bold }`), which
|
|
377
|
+
made ALL buttons read as selected/highlighted. The base resets it;
|
|
378
|
+
focus and the primary variant re-bold explicitly.
|
|
379
|
+
- `background: $surface-alt` — the old `$surface` background equals
|
|
380
|
+
the panel color, so buttons melted into their cards. `$surface-alt`
|
|
381
|
+
sits one step lighter and reads as a raised control everywhere.
|
|
382
|
+
- hover: accent wash + active border; focus: active border + bold,
|
|
383
|
+
with Textual's default `background-tint` cleared so focus never
|
|
384
|
+
smears the button.
|
|
385
|
+
- `.-active` (pressed): the button sinks one row (`offset-y: 1`) and
|
|
386
|
+
darkens via `tint` — a real press, not a palette swap.
|
|
387
|
+
- disabled buttons dim without losing their label.
|
|
388
|
+
The `Button:ansi.-style-default/-style-flat` duplicates keep this
|
|
389
|
+
block winning over Textual's widget defaults even in setups where
|
|
390
|
+
the `:ansi` pseudo-class matches. ---- */
|
|
391
|
+
Button, Button:ansi.-style-default, Button:ansi.-style-flat, .cct-btn {
|
|
392
|
+
height: 3; min-height: 3; max-height: 3; padding: 0 2;
|
|
393
|
+
min-width: 10;
|
|
394
|
+
border: heavy;
|
|
395
|
+
border-top: heavy $surface-highlight;
|
|
396
|
+
border-left: heavy $surface-highlight;
|
|
397
|
+
border-bottom: heavy $surface-dark;
|
|
398
|
+
border-right: heavy $surface-dark;
|
|
399
|
+
background: $surface-alt;
|
|
400
|
+
color: $text;
|
|
401
|
+
text-style: bold;
|
|
402
|
+
content-align: center middle;
|
|
403
|
+
transition: background 80ms, color 80ms, border 80ms, offset 80ms;
|
|
404
|
+
}
|
|
405
|
+
Button:hover, .cct-btn:hover {
|
|
406
|
+
background: $accent 24%;
|
|
407
|
+
border: heavy;
|
|
408
|
+
border-top: heavy #ffffff;
|
|
409
|
+
border-left: heavy $accent-highlight;
|
|
410
|
+
border-bottom: heavy $accent;
|
|
411
|
+
border-right: heavy $accent;
|
|
412
|
+
color: $text;
|
|
413
|
+
text-style: bold;
|
|
414
|
+
offset-y: 0;
|
|
415
|
+
}
|
|
416
|
+
Button:focus, .cct-btn:focus {
|
|
417
|
+
border: heavy;
|
|
418
|
+
border-top: heavy #ffffff;
|
|
419
|
+
border-left: heavy $accent-highlight;
|
|
420
|
+
border-bottom: heavy $accent;
|
|
421
|
+
border-right: heavy $accent;
|
|
422
|
+
text-style: bold;
|
|
423
|
+
background-tint: transparent;
|
|
424
|
+
}
|
|
425
|
+
Button:disabled, .cct-btn:disabled { opacity: 0.45; text-style: not bold; }
|
|
426
|
+
Button.-active, .cct-btn.-active {
|
|
427
|
+
offset-y: 1;
|
|
428
|
+
border: heavy;
|
|
429
|
+
border-top: heavy $surface-dark;
|
|
430
|
+
border-left: heavy $surface-dark;
|
|
431
|
+
border-bottom: heavy $surface-highlight;
|
|
432
|
+
border-right: heavy $surface-highlight;
|
|
433
|
+
tint: $app-background 20%;
|
|
434
|
+
}
|
|
435
|
+
.chats-filter-btn.-active,
|
|
436
|
+
Screen .chats-filter-btn.-active {
|
|
437
|
+
offset-y: 0;
|
|
438
|
+
offset-x: 0;
|
|
439
|
+
offset: 0 0;
|
|
440
|
+
}
|
|
441
|
+
Button.cct-dash-action, .cct-dash-action,
|
|
442
|
+
Button.cct-dash-action.-style-default,
|
|
443
|
+
Button.cct-dash-action:ansi.-style-default,
|
|
444
|
+
Button.cct-dash-action:ansi.-style-flat,
|
|
445
|
+
Screen Button.cct-dash-action {
|
|
446
|
+
height: 3; min-height: 3; max-height: 3; padding: 0;
|
|
447
|
+
min-width: 14; max-width: 24; width: 1fr; margin: 0 1;
|
|
448
|
+
background: $surface-alt;
|
|
449
|
+
color: #ffffff;
|
|
450
|
+
text-style: bold;
|
|
451
|
+
content-align: center middle;
|
|
452
|
+
border: heavy;
|
|
453
|
+
border-top: heavy #ffffff;
|
|
454
|
+
border-left: heavy #ffffff;
|
|
455
|
+
border-bottom: heavy $accent-shadow;
|
|
456
|
+
border-right: heavy $accent-shadow;
|
|
457
|
+
transition: background 80ms, color 80ms, border 80ms, offset 80ms;
|
|
458
|
+
}
|
|
459
|
+
Button.cct-dash-action:hover, .cct-dash-action:hover,
|
|
460
|
+
Button.cct-dash-action:focus, .cct-dash-action:focus,
|
|
461
|
+
Screen Button.cct-dash-action:hover, Screen Button.cct-dash-action:focus {
|
|
462
|
+
background: $accent 35%;
|
|
463
|
+
color: #ffffff;
|
|
464
|
+
border: heavy;
|
|
465
|
+
border-top: heavy #ffffff;
|
|
466
|
+
border-left: heavy #ffffff;
|
|
467
|
+
border-bottom: heavy $accent-highlight;
|
|
468
|
+
border-right: heavy $accent-highlight;
|
|
469
|
+
text-style: bold;
|
|
470
|
+
offset-y: 0;
|
|
471
|
+
}
|
|
472
|
+
Button.cct-dash-action.-active, .cct-dash-action.-active,
|
|
473
|
+
Screen Button.cct-dash-action.-active {
|
|
474
|
+
offset-y: 1;
|
|
475
|
+
background: $accent 50%;
|
|
476
|
+
color: #ffffff;
|
|
477
|
+
border: heavy;
|
|
478
|
+
border-top: heavy $accent-shadow;
|
|
479
|
+
border-left: heavy $accent-shadow;
|
|
480
|
+
border-bottom: heavy #ffffff;
|
|
481
|
+
border-right: heavy #ffffff;
|
|
482
|
+
}
|
|
483
|
+
.cct-btn-3d {
|
|
484
|
+
height: 3; min-height: 3; max-height: 3; padding: 0 2;
|
|
485
|
+
min-width: 10;
|
|
486
|
+
border: heavy;
|
|
487
|
+
border-top: heavy $accent-highlight;
|
|
488
|
+
border-left: heavy $accent-highlight;
|
|
489
|
+
border-bottom: heavy $accent-shadow;
|
|
490
|
+
border-right: heavy $accent-shadow;
|
|
491
|
+
background: $surface-alt;
|
|
492
|
+
color: $text;
|
|
493
|
+
text-style: bold;
|
|
494
|
+
content-align: center middle;
|
|
495
|
+
transition: background 80ms, color 80ms, border 80ms, offset 80ms;
|
|
496
|
+
}
|
|
497
|
+
.cct-btn-3d:hover {
|
|
498
|
+
background: $accent 24%;
|
|
499
|
+
border: heavy;
|
|
500
|
+
border-top: heavy #ffffff;
|
|
501
|
+
border-left: heavy #ffffff;
|
|
502
|
+
border-bottom: heavy $accent;
|
|
503
|
+
border-right: heavy $accent;
|
|
504
|
+
color: #ffffff;
|
|
505
|
+
text-style: bold;
|
|
506
|
+
offset-y: 0;
|
|
507
|
+
}
|
|
508
|
+
.cct-btn-3d:focus {
|
|
509
|
+
border: heavy;
|
|
510
|
+
border-top: heavy #ffffff;
|
|
511
|
+
border-left: heavy #ffffff;
|
|
512
|
+
border-bottom: heavy $accent-highlight;
|
|
513
|
+
border-right: heavy $accent-highlight;
|
|
514
|
+
text-style: bold;
|
|
515
|
+
background-tint: transparent;
|
|
516
|
+
}
|
|
517
|
+
.cct-btn-3d.-active {
|
|
518
|
+
offset-y: 1;
|
|
519
|
+
border: heavy;
|
|
520
|
+
border-top: heavy $accent-shadow;
|
|
521
|
+
border-left: heavy $accent-shadow;
|
|
522
|
+
border-bottom: heavy $accent-highlight;
|
|
523
|
+
border-right: heavy $accent-highlight;
|
|
524
|
+
tint: $app-background 20%;
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
/* Universal 3D Skeuomorphic Button Keycaps */
|
|
528
|
+
Button.cct-btn, Button.cct-modal-btn, Button.cct-btn-sm, Button.cct-btn-primary {
|
|
529
|
+
border: tall;
|
|
530
|
+
border-top: tall $surface-highlight;
|
|
531
|
+
border-left: tall $surface-highlight;
|
|
532
|
+
border-bottom: tall $surface-shadow;
|
|
533
|
+
border-right: tall $surface-shadow;
|
|
534
|
+
background: $surface;
|
|
535
|
+
color: $text;
|
|
536
|
+
text-style: bold;
|
|
537
|
+
transition: background 80ms, color 80ms, border 80ms, offset 60ms;
|
|
538
|
+
}
|
|
539
|
+
Button.cct-btn:hover, Button.cct-modal-btn:hover, Button.cct-btn-sm:hover {
|
|
540
|
+
border: tall;
|
|
541
|
+
border-top: tall $accent-highlight;
|
|
542
|
+
border-left: tall $accent-highlight;
|
|
543
|
+
border-bottom: tall $accent-shadow;
|
|
544
|
+
border-right: tall $accent-shadow;
|
|
545
|
+
background: $surface-alt;
|
|
546
|
+
color: #ffffff;
|
|
547
|
+
}
|
|
548
|
+
Button.cct-btn:focus, Button.cct-modal-btn:focus, Button.cct-btn-sm:focus {
|
|
549
|
+
border: tall;
|
|
550
|
+
border-top: tall $accent-highlight;
|
|
551
|
+
border-left: tall $accent-highlight;
|
|
552
|
+
border-bottom: tall $accent-shadow;
|
|
553
|
+
border-right: tall $accent-shadow;
|
|
554
|
+
background: $surface-alt;
|
|
555
|
+
color: #ffffff;
|
|
556
|
+
}
|
|
557
|
+
Button.cct-btn.-active, Button.cct-modal-btn.-active, Button.cct-btn-sm.-active {
|
|
558
|
+
offset-y: 1;
|
|
559
|
+
border: tall;
|
|
560
|
+
border-top: tall $surface-shadow;
|
|
561
|
+
border-left: tall $surface-shadow;
|
|
562
|
+
border-bottom: tall $surface-highlight;
|
|
563
|
+
border-right: tall $surface-highlight;
|
|
564
|
+
background: $surface-dark;
|
|
565
|
+
}
|
|
566
|
+
Button.cct-btn-primary, Button.-primary {
|
|
567
|
+
border: tall;
|
|
568
|
+
border-top: tall $accent-highlight;
|
|
569
|
+
border-left: tall $accent-highlight;
|
|
570
|
+
border-bottom: tall $accent-shadow;
|
|
571
|
+
border-right: tall $accent-shadow;
|
|
572
|
+
background: $accent;
|
|
573
|
+
color: #ffffff;
|
|
574
|
+
}
|
|
575
|
+
Button.cct-btn-primary:hover, Button.-primary:hover {
|
|
576
|
+
background: $accent-highlight;
|
|
577
|
+
color: #ffffff;
|
|
578
|
+
}
|
|
579
|
+
Button.cct-btn-primary.-active, Button.-primary.-active {
|
|
580
|
+
offset-y: 1;
|
|
581
|
+
border-top: tall $accent-shadow;
|
|
582
|
+
border-left: tall $accent-shadow;
|
|
583
|
+
border-bottom: tall $accent-highlight;
|
|
584
|
+
border-right: tall $accent-highlight;
|
|
585
|
+
background: $accent-shadow;
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
/* Universal 3D Skeuomorphic Sunken Tray Inputs / Search Bars */
|
|
589
|
+
Input.cct-search-input, .cct-input-3d, #search-input, #model-search, #pc-search Input, #ext-search Input, #cust-search Input, #mcp-search Input {
|
|
590
|
+
border: tall;
|
|
591
|
+
border-top: tall $surface-shadow;
|
|
592
|
+
border-left: tall $surface-shadow;
|
|
593
|
+
border-bottom: tall $surface-highlight;
|
|
594
|
+
border-right: tall $surface-highlight;
|
|
595
|
+
background: $app-background;
|
|
596
|
+
color: $text;
|
|
597
|
+
transition: border 80ms, background 80ms;
|
|
598
|
+
}
|
|
599
|
+
Input.cct-search-input:focus, .cct-input-3d:focus, #search-input:focus, #model-search:focus, #pc-search Input:focus, #ext-search Input:focus, #cust-search Input:focus, #mcp-search Input:focus {
|
|
600
|
+
border: tall;
|
|
601
|
+
border-top: tall $surface-shadow;
|
|
602
|
+
border-left: tall $surface-shadow;
|
|
603
|
+
border-bottom: tall $accent-highlight;
|
|
604
|
+
border-right: tall $accent-highlight;
|
|
605
|
+
background: $app-background;
|
|
606
|
+
}
|
|
607
|
+
Button.cct-ctrl, .cct-ctrl,
|
|
608
|
+
Button.cct-ctrl:ansi.-style-default,
|
|
609
|
+
Button.cct-ctrl:ansi.-style-flat {
|
|
610
|
+
height: 1; min-height: 1; max-height: 1;
|
|
611
|
+
min-width: 3; max-width: 3; width: 3; padding: 0; margin: 0;
|
|
612
|
+
background: transparent; color: $text-faint;
|
|
613
|
+
border: none;
|
|
614
|
+
content-align: center middle;
|
|
615
|
+
transition: color 80ms, background 80ms, offset 60ms;
|
|
616
|
+
}
|
|
617
|
+
Button.cct-ctrl:hover, .cct-ctrl:hover {
|
|
618
|
+
color: $error; background: transparent; border: none;
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
#cct-right-controls {
|
|
622
|
+
dock: right;
|
|
623
|
+
width: 12;
|
|
624
|
+
min-width: 12;
|
|
625
|
+
max-width: 12;
|
|
626
|
+
height: 1;
|
|
627
|
+
min-height: 1;
|
|
628
|
+
max-height: 1;
|
|
629
|
+
layout: horizontal;
|
|
630
|
+
align-vertical: middle;
|
|
631
|
+
align-horizontal: right;
|
|
632
|
+
margin: 0;
|
|
633
|
+
padding: 0;
|
|
634
|
+
overflow: hidden;
|
|
635
|
+
background: transparent;
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
Button.cct-icon-btn,
|
|
639
|
+
Button.cct-icon-btn:hover,
|
|
640
|
+
Button.cct-icon-btn:focus,
|
|
641
|
+
Button.cct-icon-btn.-active,
|
|
642
|
+
Button#btn-permissions,
|
|
643
|
+
Button#btn-permissions:hover,
|
|
644
|
+
Button#btn-permissions:focus,
|
|
645
|
+
Button#btn-permissions.-active,
|
|
646
|
+
Button#btn-attach,
|
|
647
|
+
Button#btn-attach:hover,
|
|
648
|
+
Button#btn-attach:focus,
|
|
649
|
+
Button#btn-attach.-active,
|
|
650
|
+
Button.cct-icon-send,
|
|
651
|
+
Button.cct-icon-send:hover,
|
|
652
|
+
Button.cct-icon-send:focus,
|
|
653
|
+
Button.cct-icon-send.-active,
|
|
654
|
+
Button#btn-send,
|
|
655
|
+
Button#btn-send:hover,
|
|
656
|
+
Button#btn-send:focus,
|
|
657
|
+
Button#btn-send.-active {
|
|
658
|
+
border: none;
|
|
659
|
+
offset: 0 0;
|
|
660
|
+
offset-x: 0;
|
|
661
|
+
offset-y: 0;
|
|
662
|
+
transition: none;
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
Button.cct-icon-btn, .cct-icon-btn,
|
|
666
|
+
Button.cct-icon-btn:ansi.-style-default,
|
|
667
|
+
Button.cct-icon-btn:ansi.-style-flat,
|
|
668
|
+
Button#btn-permissions,
|
|
669
|
+
Button#btn-permissions:ansi.-style-default {
|
|
670
|
+
height: 1; min-height: 1; max-height: 1;
|
|
671
|
+
min-width: 3; max-width: 3; width: 3; padding: 0; margin: 0;
|
|
672
|
+
background: transparent; color: $text;
|
|
673
|
+
border: none;
|
|
674
|
+
text-style: bold;
|
|
675
|
+
content-align: center middle;
|
|
676
|
+
}
|
|
677
|
+
Button#btn-attach,
|
|
678
|
+
Button#btn-attach:ansi.-style-default {
|
|
679
|
+
height: 1; min-height: 1; max-height: 1;
|
|
680
|
+
min-width: 4; max-width: 4; width: 4; padding: 0; margin: 0 0 0 1;
|
|
681
|
+
background: transparent; color: $text;
|
|
682
|
+
border: none;
|
|
683
|
+
text-style: bold;
|
|
684
|
+
content-align: center middle;
|
|
685
|
+
}
|
|
686
|
+
Button.cct-icon-btn:hover, Button#btn-permissions:hover, Button#btn-attach:hover {
|
|
687
|
+
color: $accent-highlight; background: $surface-highlight 40%; text-style: bold; border: none;
|
|
688
|
+
}
|
|
689
|
+
Button.cct-icon-btn:focus, Button#btn-permissions:focus, Button#btn-attach:focus {
|
|
690
|
+
color: $accent-highlight; background: $surface-highlight 40%; text-style: bold; border: none;
|
|
691
|
+
}
|
|
692
|
+
Button.cct-icon-btn.-active, Button#btn-permissions.-active, Button#btn-attach.-active {
|
|
693
|
+
background: $surface-highlight 70%; color: #ffffff; border: none; offset: 0 0;
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
Button.cct-icon-send, .cct-icon-send,
|
|
697
|
+
Button.cct-icon-send:ansi.-style-default,
|
|
698
|
+
Button.cct-icon-send:ansi.-style-flat,
|
|
699
|
+
Button#btn-send,
|
|
700
|
+
Button#btn-send:ansi.-style-default {
|
|
701
|
+
height: 1; min-height: 1; max-height: 1;
|
|
702
|
+
min-width: 3; max-width: 3; width: 3; padding: 0; margin: 0 0 0 1;
|
|
703
|
+
background: transparent; color: $accent;
|
|
704
|
+
border: none;
|
|
705
|
+
text-style: bold;
|
|
706
|
+
content-align: center middle;
|
|
707
|
+
}
|
|
708
|
+
Button.cct-icon-send:hover, Button#btn-send:hover {
|
|
709
|
+
color: #ffffff; background: $accent 60%; text-style: bold; border: none;
|
|
710
|
+
}
|
|
711
|
+
Button.cct-icon-send:focus, Button#btn-send:focus {
|
|
712
|
+
color: $accent-highlight; background: $surface-highlight 40%; text-style: bold; border: none;
|
|
713
|
+
}
|
|
714
|
+
Button.cct-icon-send.-active, Button#btn-send.-active {
|
|
715
|
+
background: $surface-highlight 70%; color: #ffffff; border: none; offset: 0 0;
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
/* Interrupt Button state during AI Streaming */
|
|
719
|
+
#cct-composer Button#btn-send.cct-btn-interrupt,
|
|
720
|
+
#cct-composer.cct-mode-notebook Button#btn-send.cct-btn-interrupt,
|
|
721
|
+
#cct-composer.cct-mode-research Button#btn-send.cct-btn-interrupt,
|
|
722
|
+
#cct-composer.cct-mode-plan Button#btn-send.cct-btn-interrupt,
|
|
723
|
+
#cct-composer.cct-mode-build Button#btn-send.cct-btn-interrupt,
|
|
724
|
+
#cct-composer.cct-mode-debugger Button#btn-send.cct-btn-interrupt,
|
|
725
|
+
#cct-composer.cct-mode-agent Button#btn-send.cct-btn-interrupt,
|
|
726
|
+
Button.cct-btn-interrupt, Button#btn-send.cct-btn-interrupt {
|
|
727
|
+
background: transparent;
|
|
728
|
+
color: #ef4444;
|
|
729
|
+
text-style: bold;
|
|
730
|
+
}
|
|
731
|
+
#cct-composer Button#btn-send.cct-btn-interrupt:hover,
|
|
732
|
+
Button.cct-btn-interrupt:hover, Button#btn-send.cct-btn-interrupt:hover {
|
|
733
|
+
background: #ef4444 25%;
|
|
734
|
+
color: #f87171;
|
|
735
|
+
text-style: bold;
|
|
736
|
+
}
|
|
737
|
+
#cct-composer Button#btn-send.cct-btn-interrupt:focus,
|
|
738
|
+
Button.cct-btn-interrupt:focus, Button#btn-send.cct-btn-interrupt:focus {
|
|
739
|
+
background: #ef4444 25%;
|
|
740
|
+
color: #f87171;
|
|
741
|
+
text-style: bold;
|
|
742
|
+
}
|
|
743
|
+
#cct-composer Button#btn-send.cct-btn-interrupt.-active,
|
|
744
|
+
Button.cct-btn-interrupt.-active, Button#btn-send.cct-btn-interrupt.-active {
|
|
745
|
+
background: #ef4444 45%;
|
|
746
|
+
color: #fca5a5;
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
/* AI Mode Dynamic 3D Chassis & Skeuomorphic Accent Theming */
|
|
750
|
+
#cct-composer,
|
|
751
|
+
#cct-composer:focus-within,
|
|
752
|
+
#cct-composer.cct-mode-notebook,
|
|
753
|
+
#cct-composer.cct-mode-research,
|
|
754
|
+
#cct-composer.cct-mode-plan,
|
|
755
|
+
#cct-composer.cct-mode-build,
|
|
756
|
+
#cct-composer.cct-mode-debugger,
|
|
757
|
+
#cct-composer.cct-mode-agent {
|
|
758
|
+
background: $app-background;
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
#cct-composer.cct-mode-notebook {
|
|
762
|
+
border: heavy;
|
|
763
|
+
border-top: heavy #89b4fa;
|
|
764
|
+
border-left: heavy #89b4fa;
|
|
765
|
+
border-bottom: heavy #3d59a1;
|
|
766
|
+
border-right: heavy #3d59a1;
|
|
767
|
+
}
|
|
768
|
+
#cct-composer.cct-mode-notebook:focus-within {
|
|
769
|
+
border: heavy;
|
|
770
|
+
border-top: heavy #b4befe;
|
|
771
|
+
border-left: heavy #b4befe;
|
|
772
|
+
border-bottom: heavy #5875b7;
|
|
773
|
+
border-right: heavy #5875b7;
|
|
774
|
+
}
|
|
775
|
+
#cct-composer.cct-mode-notebook #cct-prompt-glyph {
|
|
776
|
+
color: #7aa2f7;
|
|
777
|
+
}
|
|
778
|
+
#cct-composer.cct-mode-notebook #cct-prompt-row,
|
|
779
|
+
#cct-composer.cct-mode-notebook #cct-prompt-row:focus-within {
|
|
780
|
+
border: none;
|
|
781
|
+
}
|
|
782
|
+
#cct-composer.cct-mode-notebook Button#btn-send,
|
|
783
|
+
#cct-composer.cct-mode-notebook Button.cct-icon-send {
|
|
784
|
+
background: transparent;
|
|
785
|
+
color: #7aa2f7;
|
|
786
|
+
}
|
|
787
|
+
#cct-composer.cct-mode-notebook Button#btn-send:hover,
|
|
788
|
+
#cct-composer.cct-mode-notebook Button.cct-icon-send:hover {
|
|
789
|
+
background: #7aa2f7 25%;
|
|
790
|
+
color: #89b4fa;
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
#cct-composer.cct-mode-research {
|
|
794
|
+
border: heavy;
|
|
795
|
+
border-top: heavy #ff85c0;
|
|
796
|
+
border-left: heavy #ff85c0;
|
|
797
|
+
border-bottom: heavy #9e366a;
|
|
798
|
+
border-right: heavy #9e366a;
|
|
799
|
+
}
|
|
800
|
+
#cct-composer.cct-mode-research:focus-within {
|
|
801
|
+
border: heavy;
|
|
802
|
+
border-top: heavy #ffb3d9;
|
|
803
|
+
border-left: heavy #ffb3d9;
|
|
804
|
+
border-bottom: heavy #b8437d;
|
|
805
|
+
border-right: heavy #b8437d;
|
|
806
|
+
}
|
|
807
|
+
#cct-composer.cct-mode-research #cct-prompt-glyph {
|
|
808
|
+
color: #ff69b4;
|
|
809
|
+
}
|
|
810
|
+
#cct-composer.cct-mode-research #cct-prompt-row,
|
|
811
|
+
#cct-composer.cct-mode-research #cct-prompt-row:focus-within {
|
|
812
|
+
border: none;
|
|
813
|
+
}
|
|
814
|
+
#cct-composer.cct-mode-research Button#btn-send,
|
|
815
|
+
#cct-composer.cct-mode-research Button.cct-icon-send {
|
|
816
|
+
background: transparent;
|
|
817
|
+
color: #ff69b4;
|
|
818
|
+
}
|
|
819
|
+
#cct-composer.cct-mode-research Button#btn-send:hover,
|
|
820
|
+
#cct-composer.cct-mode-research Button.cct-icon-send:hover {
|
|
821
|
+
background: #ff69b4 25%;
|
|
822
|
+
color: #ff85c0;
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
#cct-composer.cct-mode-plan {
|
|
826
|
+
border: heavy;
|
|
827
|
+
border-top: heavy #a6e3a1;
|
|
828
|
+
border-left: heavy #a6e3a1;
|
|
829
|
+
border-bottom: heavy #567e30;
|
|
830
|
+
border-right: heavy #567e30;
|
|
831
|
+
}
|
|
832
|
+
#cct-composer.cct-mode-plan:focus-within {
|
|
833
|
+
border: heavy;
|
|
834
|
+
border-top: heavy #c5f3be;
|
|
835
|
+
border-left: heavy #c5f3be;
|
|
836
|
+
border-bottom: heavy #6e9d40;
|
|
837
|
+
border-right: heavy #6e9d40;
|
|
838
|
+
}
|
|
839
|
+
#cct-composer.cct-mode-plan #cct-prompt-glyph {
|
|
840
|
+
color: #9ece6a;
|
|
841
|
+
}
|
|
842
|
+
#cct-composer.cct-mode-plan #cct-prompt-row,
|
|
843
|
+
#cct-composer.cct-mode-plan #cct-prompt-row:focus-within {
|
|
844
|
+
border: none;
|
|
845
|
+
}
|
|
846
|
+
#cct-composer.cct-mode-plan Button#btn-send,
|
|
847
|
+
#cct-composer.cct-mode-plan Button.cct-icon-send {
|
|
848
|
+
background: transparent;
|
|
849
|
+
color: #9ece6a;
|
|
850
|
+
}
|
|
851
|
+
#cct-composer.cct-mode-plan Button#btn-send:hover,
|
|
852
|
+
#cct-composer.cct-mode-plan Button.cct-icon-send:hover {
|
|
853
|
+
background: #9ece6a 25%;
|
|
854
|
+
color: #a6e3a1;
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
#cct-composer.cct-mode-build {
|
|
858
|
+
border: heavy;
|
|
859
|
+
border-top: heavy #f0d3a7;
|
|
860
|
+
border-left: heavy #f0d3a7;
|
|
861
|
+
border-bottom: heavy #966b33;
|
|
862
|
+
border-right: heavy #966b33;
|
|
863
|
+
}
|
|
864
|
+
#cct-composer.cct-mode-build:focus-within {
|
|
865
|
+
border: heavy;
|
|
866
|
+
border-top: heavy #fae3b0;
|
|
867
|
+
border-left: heavy #fae3b0;
|
|
868
|
+
border-bottom: heavy #b88642;
|
|
869
|
+
border-right: heavy #b88642;
|
|
870
|
+
}
|
|
871
|
+
#cct-composer.cct-mode-build #cct-prompt-glyph {
|
|
872
|
+
color: #e0af68;
|
|
873
|
+
}
|
|
874
|
+
#cct-composer.cct-mode-build #cct-prompt-row,
|
|
875
|
+
#cct-composer.cct-mode-build #cct-prompt-row:focus-within {
|
|
876
|
+
border: none;
|
|
877
|
+
}
|
|
878
|
+
#cct-composer.cct-mode-build Button#btn-send,
|
|
879
|
+
#cct-composer.cct-mode-build Button.cct-icon-send {
|
|
880
|
+
background: transparent;
|
|
881
|
+
color: #e0af68;
|
|
882
|
+
}
|
|
883
|
+
#cct-composer.cct-mode-build Button#btn-send:hover,
|
|
884
|
+
#cct-composer.cct-mode-build Button.cct-icon-send:hover {
|
|
885
|
+
background: #e0af68 25%;
|
|
886
|
+
color: #fab387;
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
#cct-composer.cct-mode-debugger {
|
|
890
|
+
border: heavy;
|
|
891
|
+
border-top: heavy #f38ba8;
|
|
892
|
+
border-left: heavy #f38ba8;
|
|
893
|
+
border-bottom: heavy #991b1b;
|
|
894
|
+
border-right: heavy #991b1b;
|
|
895
|
+
}
|
|
896
|
+
#cct-composer.cct-mode-debugger:focus-within {
|
|
897
|
+
border: heavy;
|
|
898
|
+
border-top: heavy #fca5a5;
|
|
899
|
+
border-left: heavy #fca5a5;
|
|
900
|
+
border-bottom: heavy #b91c1c;
|
|
901
|
+
border-right: heavy #b91c1c;
|
|
902
|
+
}
|
|
903
|
+
#cct-composer.cct-mode-debugger #cct-prompt-glyph {
|
|
904
|
+
color: #dc143c;
|
|
905
|
+
}
|
|
906
|
+
#cct-composer.cct-mode-debugger #cct-prompt-row,
|
|
907
|
+
#cct-composer.cct-mode-debugger #cct-prompt-row:focus-within {
|
|
908
|
+
border: none;
|
|
909
|
+
}
|
|
910
|
+
#cct-composer.cct-mode-debugger Button#btn-send,
|
|
911
|
+
#cct-composer.cct-mode-debugger Button.cct-icon-send {
|
|
912
|
+
background: transparent;
|
|
913
|
+
color: #dc143c;
|
|
914
|
+
}
|
|
915
|
+
#cct-composer.cct-mode-debugger Button#btn-send:hover,
|
|
916
|
+
#cct-composer.cct-mode-debugger Button.cct-icon-send:hover {
|
|
917
|
+
background: #dc143c 25%;
|
|
918
|
+
color: #f38ba8;
|
|
919
|
+
}
|
|
920
|
+
|
|
921
|
+
#cct-composer.cct-mode-agent {
|
|
922
|
+
border: heavy;
|
|
923
|
+
border-top: heavy #cba6f7;
|
|
924
|
+
border-left: heavy #cba6f7;
|
|
925
|
+
border-bottom: heavy #7a58b8;
|
|
926
|
+
border-right: heavy #7a58b8;
|
|
927
|
+
}
|
|
928
|
+
#cct-composer.cct-mode-agent:focus-within {
|
|
929
|
+
border: heavy;
|
|
930
|
+
border-top: heavy #e0caff;
|
|
931
|
+
border-left: heavy #e0caff;
|
|
932
|
+
border-bottom: heavy #946fd4;
|
|
933
|
+
border-right: heavy #946fd4;
|
|
934
|
+
}
|
|
935
|
+
#cct-composer.cct-mode-agent #cct-prompt-glyph {
|
|
936
|
+
color: #bb9af7;
|
|
937
|
+
}
|
|
938
|
+
#cct-composer.cct-mode-agent #cct-prompt-row,
|
|
939
|
+
#cct-composer.cct-mode-agent #cct-prompt-row:focus-within {
|
|
940
|
+
border: none;
|
|
941
|
+
}
|
|
942
|
+
#cct-composer.cct-mode-agent Button#btn-send,
|
|
943
|
+
#cct-composer.cct-mode-agent Button.cct-icon-send {
|
|
944
|
+
background: transparent;
|
|
945
|
+
color: #bb9af7;
|
|
946
|
+
}
|
|
947
|
+
#cct-composer.cct-mode-agent Button#btn-send:hover,
|
|
948
|
+
#cct-composer.cct-mode-agent Button.cct-icon-send:hover {
|
|
949
|
+
background: #bb9af7 25%;
|
|
950
|
+
color: #cba6f7;
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
/* 3D Toggle Switch */
|
|
954
|
+
Button.cct-toggle-3d {
|
|
955
|
+
height: 1; min-height: 1; max-height: 1;
|
|
956
|
+
width: 9; min-width: 9; max-width: 9;
|
|
957
|
+
padding: 0; margin: 0;
|
|
958
|
+
content-align: center middle;
|
|
959
|
+
text-style: bold;
|
|
960
|
+
border: none;
|
|
961
|
+
transition: background 100ms, color 100ms, offset 60ms;
|
|
962
|
+
}
|
|
963
|
+
Button.cct-toggle-3d.-on {
|
|
964
|
+
background: #047857;
|
|
965
|
+
color: #ecfdf5;
|
|
966
|
+
}
|
|
967
|
+
Button.cct-toggle-3d.-on:hover {
|
|
968
|
+
background: #059669;
|
|
969
|
+
color: #ffffff;
|
|
970
|
+
}
|
|
971
|
+
Button.cct-toggle-3d.-off {
|
|
972
|
+
background: #1e293b;
|
|
973
|
+
color: #94a3b8;
|
|
974
|
+
}
|
|
975
|
+
Button.cct-toggle-3d.-off:hover {
|
|
976
|
+
background: #334155;
|
|
977
|
+
color: #f1f5f9;
|
|
978
|
+
}
|
|
979
|
+
Button.cct-toggle-3d.-active {
|
|
980
|
+
offset-y: 1;
|
|
981
|
+
}
|
|
982
|
+
Button.-primary, .cct-btn.-primary, Button.cct-btn-primary {
|
|
983
|
+
background: $accent 30%;
|
|
984
|
+
color: $text;
|
|
985
|
+
border-top: tall $accent-highlight;
|
|
986
|
+
border-left: tall $accent-highlight;
|
|
987
|
+
border-bottom: tall $accent;
|
|
988
|
+
border-right: tall $accent;
|
|
989
|
+
text-style: bold;
|
|
990
|
+
}
|
|
991
|
+
Button.-primary:hover, .cct-btn.-primary:hover, Button.cct-btn-primary:hover {
|
|
992
|
+
background: $accent 50%;
|
|
993
|
+
border-top: tall #ffffff;
|
|
994
|
+
border-left: tall $accent-highlight;
|
|
995
|
+
border-bottom: tall $accent;
|
|
996
|
+
border-right: tall $accent;
|
|
997
|
+
color: #ffffff;
|
|
998
|
+
offset-y: -1;
|
|
999
|
+
}
|
|
1000
|
+
Button.-primary:focus, .cct-btn.-primary:focus, Button.cct-btn-primary:focus {
|
|
1001
|
+
background: $accent 45%;
|
|
1002
|
+
border-top: tall #ffffff;
|
|
1003
|
+
border-left: tall $accent-highlight;
|
|
1004
|
+
border-bottom: tall $accent;
|
|
1005
|
+
border-right: tall $accent;
|
|
1006
|
+
color: #ffffff;
|
|
1007
|
+
text-style: bold;
|
|
1008
|
+
}
|
|
1009
|
+
Button.-primary.-active, .cct-btn.-primary.-active, Button.cct-btn-primary.-active {
|
|
1010
|
+
offset-y: 1;
|
|
1011
|
+
border-top: tall $accent;
|
|
1012
|
+
border-left: tall $accent;
|
|
1013
|
+
border-bottom: tall $accent-highlight;
|
|
1014
|
+
border-right: tall $accent-highlight;
|
|
1015
|
+
}
|
|
1016
|
+
Button.-error, .cct-btn.-error { background: $surface-alt; color: $error; border: tall $error #7f1d1d; text-style: bold; }
|
|
1017
|
+
Button.-error:hover, .cct-btn.-error:hover { background: $error 25%; color: #ffffff; border: tall #fca5a5 $error; }
|
|
1018
|
+
Button.-error:focus, .cct-btn.-error:focus { background: $error 15%; border: tall #fca5a5 $error; text-style: bold; }
|
|
1019
|
+
Button.-error.-active, .cct-btn.-error.-active { background: $error; color: #ffffff; border: tall #7f1d1d #fca5a5; offset-y: 1; }
|
|
1020
|
+
Button.-success, .cct-btn.-success { background: $surface-alt; color: $success; border: tall $success #14532d; text-style: bold; }
|
|
1021
|
+
Button.-success:hover, .cct-btn.-success:hover { background: $success 25%; color: #ffffff; border: tall #86efac $success; }
|
|
1022
|
+
Button.-success:focus, .cct-btn.-success:focus { background: $success 15%; border: tall #86efac $success; text-style: bold; }
|
|
1023
|
+
Button.-success.-active, .cct-btn.-success.-active { background: $success; color: #ffffff; border: tall #14532d #86efac; offset-y: 1; }
|
|
1024
|
+
Button.cct-btn-sm, .cct-btn-sm { min-width: 8; padding: 0 1; }
|
|
1025
|
+
Button.cct-btn-cta, .cct-btn-cta { min-width: 20; padding: 0 3; }
|
|
1026
|
+
|
|
1027
|
+
/* Shared form field look: same tall-nothing-to-hide rule — every
|
|
1028
|
+
Input app-wide gets the real single-line rounded border instead of
|
|
1029
|
+
Textual's default 3-cell "tall" block box. */
|
|
1030
|
+
Input {
|
|
1031
|
+
height: 3;
|
|
1032
|
+
border: tall;
|
|
1033
|
+
border-top: tall $surface-shadow;
|
|
1034
|
+
border-left: tall $surface-shadow;
|
|
1035
|
+
border-bottom: tall $surface-highlight;
|
|
1036
|
+
border-right: tall $surface-highlight;
|
|
1037
|
+
background: $app-background;
|
|
1038
|
+
color: $text;
|
|
1039
|
+
padding: 0 1;
|
|
1040
|
+
}
|
|
1041
|
+
Input:focus {
|
|
1042
|
+
border: tall;
|
|
1043
|
+
border-top: tall $accent-shadow;
|
|
1044
|
+
border-left: tall $accent-shadow;
|
|
1045
|
+
border-bottom: tall $accent-highlight;
|
|
1046
|
+
border-right: tall $accent-highlight;
|
|
1047
|
+
}
|
|
1048
|
+
|
|
1049
|
+
/* ---- Popup system: every centered modal uses .cct-popup (the dimmed
|
|
1050
|
+
full-screen overlay) + .cct-popup-box (the card). Fade + slide-up
|
|
1051
|
+
open animation, sharp 3D border, never wider than the terminal, and
|
|
1052
|
+
`Esc`/`Enter` handling lives in each Screen (shared convention). */
|
|
1053
|
+
.cct-popup {
|
|
1054
|
+
align: center middle;
|
|
1055
|
+
background: $app-background 65%;
|
|
1056
|
+
}
|
|
1057
|
+
.cct-popup-box {
|
|
1058
|
+
width: auto; height: auto; max-width: 100%; max-height: 100%;
|
|
1059
|
+
background: $surface;
|
|
1060
|
+
border: heavy;
|
|
1061
|
+
border-top: heavy $surface-highlight;
|
|
1062
|
+
border-left: heavy $surface-highlight;
|
|
1063
|
+
border-bottom: heavy $surface-shadow;
|
|
1064
|
+
border-right: heavy $surface-shadow;
|
|
1065
|
+
padding: 0;
|
|
1066
|
+
opacity: 0; offset-y: 1;
|
|
1067
|
+
transition: opacity 140ms, offset 160ms;
|
|
1068
|
+
}
|
|
1069
|
+
.cct-popup-box.open { opacity: 1; offset-y: 0; }
|
|
1070
|
+
|
|
1071
|
+
/* Title bar shared by every popup card. */
|
|
1072
|
+
.cct-popup-titlebar {
|
|
1073
|
+
height: 3; padding: 1 2 0 2;
|
|
1074
|
+
border-bottom: solid $border;
|
|
1075
|
+
}
|
|
1076
|
+
.cct-popup-title { text-style: bold; width: 1fr; }
|
|
1077
|
+
.cct-popup-subtitle { color: $text-faint; padding: 1 2 0 2; }
|
|
1078
|
+
.cct-popup-hint { color: $text-faint; padding: 0 2 1 2; }
|
|
1079
|
+
.cct-popup-close, Button.cct-popup-close,
|
|
1080
|
+
Button.cct-popup-close:ansi.-style-default,
|
|
1081
|
+
Button.cct-popup-close:ansi.-style-flat,
|
|
1082
|
+
Button.cct-popup-close:focus,
|
|
1083
|
+
Button#hc-close,
|
|
1084
|
+
Button#hc-close:ansi.-style-default,
|
|
1085
|
+
Button#hc-close:ansi.-style-flat,
|
|
1086
|
+
Button#ollama-close,
|
|
1087
|
+
Button#ollama-close:ansi.-style-default,
|
|
1088
|
+
Button#mcp-close,
|
|
1089
|
+
Button#mcp-close:ansi.-style-default,
|
|
1090
|
+
Button#bpp-close,
|
|
1091
|
+
Button#bpp-close:ansi.-style-default,
|
|
1092
|
+
Button#act-close,
|
|
1093
|
+
Button#act-close:ansi.-style-default,
|
|
1094
|
+
Button#pp-close,
|
|
1095
|
+
Button#pp-close:ansi.-style-default,
|
|
1096
|
+
Button#pc-close,
|
|
1097
|
+
Button#pc-close:ansi.-style-default,
|
|
1098
|
+
Button#ap-close,
|
|
1099
|
+
Button#ap-close:ansi.-style-default,
|
|
1100
|
+
Button#apf-close,
|
|
1101
|
+
Button#apf-close:ansi.-style-default,
|
|
1102
|
+
Button#amf-close,
|
|
1103
|
+
Button#amf-close:ansi.-style-default {
|
|
1104
|
+
width: 3; min-width: 3; max-width: 3;
|
|
1105
|
+
height: 1; min-height: 1; max-height: 1;
|
|
1106
|
+
padding: 0; margin: 0;
|
|
1107
|
+
background: transparent; color: $text-faint; border: none;
|
|
1108
|
+
content-align: center middle;
|
|
1109
|
+
}
|
|
1110
|
+
.cct-popup-close:hover, Button.cct-popup-close:hover,
|
|
1111
|
+
Button.cct-popup-close:hover:ansi.-style-default,
|
|
1112
|
+
Button#hc-close:hover,
|
|
1113
|
+
Button#ollama-close:hover,
|
|
1114
|
+
Button#mcp-close:hover,
|
|
1115
|
+
Button#bpp-close:hover,
|
|
1116
|
+
Button#act-close:hover,
|
|
1117
|
+
Button#pp-close:hover,
|
|
1118
|
+
Button#pc-close:hover,
|
|
1119
|
+
Button#ap-close:hover,
|
|
1120
|
+
Button#apf-close:hover,
|
|
1121
|
+
Button#amf-close:hover {
|
|
1122
|
+
color: $error;
|
|
1123
|
+
background: transparent;
|
|
1124
|
+
border: none;
|
|
1125
|
+
}
|
|
1126
|
+
.cct-popup-actions {
|
|
1127
|
+
height: 5; padding: 0 2 1 2; border-top: solid $border;
|
|
1128
|
+
align-horizontal: right;
|
|
1129
|
+
}
|
|
1130
|
+
.cct-popup-actions Button { margin-left: 1; }
|
|
1131
|
+
|
|
1132
|
+
/* Form fields inside popups — consistent height and spacing. */
|
|
1133
|
+
.cct-popup-box Input {
|
|
1134
|
+
height: 3;
|
|
1135
|
+
border: tall;
|
|
1136
|
+
border-top: tall $surface-shadow;
|
|
1137
|
+
border-left: tall $surface-shadow;
|
|
1138
|
+
border-bottom: tall $surface-highlight;
|
|
1139
|
+
border-right: tall $surface-highlight;
|
|
1140
|
+
background: $app-background;
|
|
1141
|
+
color: $text; margin: 0 0 0 0; padding: 0 1;
|
|
1142
|
+
}
|
|
1143
|
+
.cct-popup-box Input:focus {
|
|
1144
|
+
border: tall;
|
|
1145
|
+
border-top: tall $accent-shadow;
|
|
1146
|
+
border-left: tall $accent-shadow;
|
|
1147
|
+
border-bottom: tall $accent-highlight;
|
|
1148
|
+
border-right: tall $accent-highlight;
|
|
1149
|
+
}
|
|
1150
|
+
.cct-field-label { color: $text-faint; height: 2; padding-top: 1; }
|
|
1151
|
+
|
|
1152
|
+
/* ====================================================================
|
|
1153
|
+
CAT Customization — Dynamic UI & Button Styles (Section 11)
|
|
1154
|
+
Shapes: minimal, rounded, pill, square, ghost, outlined, filled, compact, large
|
|
1155
|
+
Sizes: small, medium, large, compact
|
|
1156
|
+
==================================================================== */
|
|
1157
|
+
.cust-btn-shape-minimal Button, .cust-btn-shape-minimal Button:ansi.-style-default { border: none; background: transparent; }
|
|
1158
|
+
.cust-btn-shape-minimal Button:hover { background: $accent 15%; border: none; }
|
|
1159
|
+
.cust-btn-shape-rounded Button, .cust-btn-shape-rounded Button:ansi.-style-default { border: round $border; padding: 0 1; min-width: 10; min-height: 3; height: 3; }
|
|
1160
|
+
.cust-btn-shape-pill Button, .cust-btn-shape-pill Button:ansi.-style-default { border: round $border; padding: 0 1; min-width: 10; min-height: 3; height: 3; }
|
|
1161
|
+
.cust-btn-shape-square Button, .cust-btn-shape-square Button:ansi.-style-default { border: solid $border; padding: 0 1; min-width: 10; min-height: 3; height: 3; }
|
|
1162
|
+
.cust-btn-shape-ghost Button, .cust-btn-shape-ghost Button:ansi.-style-default { border: none; background: transparent; }
|
|
1163
|
+
.cust-btn-shape-ghost Button:hover { background: $accent 12%; }
|
|
1164
|
+
.cust-btn-shape-outlined Button, .cust-btn-shape-outlined Button:ansi.-style-default { border: solid $border; background: transparent; padding: 0 1; min-width: 10; min-height: 3; height: 3; }
|
|
1165
|
+
.cust-btn-shape-outlined Button:hover { background: $accent 15%; }
|
|
1166
|
+
.cust-btn-shape-filled Button, .cust-btn-shape-filled Button:ansi.-style-default { border: none; background: $surface-alt; }
|
|
1167
|
+
.cust-btn-shape-compact Button, .cust-btn-shape-compact Button:ansi.-style-default { height: 1; min-height: 1; min-width: 8; padding: 0 1; border: none; }
|
|
1168
|
+
.cust-btn-shape-large Button, .cust-btn-shape-large Button:ansi.-style-default { height: 4; min-height: 4; min-width: 16; padding: 1 3; }
|
|
1169
|
+
|
|
1170
|
+
.cust-btn-size-small Button, .cust-btn-size-small Button:ansi.-style-default { height: 1; min-height: 1; min-width: 8; padding: 0 1; border: none; }
|
|
1171
|
+
.cust-btn-size-medium Button, .cust-btn-size-medium Button:ansi.-style-default { height: 3; min-height: 3; min-width: 12; padding: 0 1; }
|
|
1172
|
+
.cust-btn-size-large Button, .cust-btn-size-large Button:ansi.-style-default { height: 4; min-height: 4; min-width: 16; padding: 1 3; }
|
|
1173
|
+
.cust-btn-size-compact Button, .cust-btn-size-compact Button:ansi.-style-default { height: 1; min-height: 1; min-width: 8; padding: 0 1; border: none; }
|
|
1174
|
+
|
|
1175
|
+
/* Preserve 3D Action & Dashboard Buttons from flat customization overrides */
|
|
1176
|
+
Button.cct-dash-action, Button.cct-dash-action.-style-default,
|
|
1177
|
+
Screen Button.cct-dash-action, Screen.cust-btn-shape-rounded Button.cct-dash-action,
|
|
1178
|
+
Screen.cust-btn-shape-pill Button.cct-dash-action, Screen.cust-btn-shape-square Button.cct-dash-action,
|
|
1179
|
+
Screen.cust-btn-shape-ghost Button.cct-dash-action, Screen.cust-btn-shape-outlined Button.cct-dash-action,
|
|
1180
|
+
Screen.cust-btn-shape-filled Button.cct-dash-action, Screen.cust-btn-shape-minimal Button.cct-dash-action,
|
|
1181
|
+
Button#cct-open-folder-btn, Screen Button#cct-open-folder-btn {
|
|
1182
|
+
border: heavy;
|
|
1183
|
+
border-top: heavy #ffffff;
|
|
1184
|
+
border-left: heavy #ffffff;
|
|
1185
|
+
border-bottom: heavy $accent-shadow;
|
|
1186
|
+
border-right: heavy $accent-shadow;
|
|
1187
|
+
}
|
|
1188
|
+
|
|
1189
|
+
/* ====================================================================
|
|
1190
|
+
CAT Touchscreen & Touch-Friendly UI Mode Rules (.cct-touch-mode)
|
|
1191
|
+
==================================================================== */
|
|
1192
|
+
.cct-touch-mode Button, .cct-touch-mode .cct-btn, .cct-touch-mode .cct-ctrl {
|
|
1193
|
+
min-height: 3;
|
|
1194
|
+
min-width: 10;
|
|
1195
|
+
padding: 0 2;
|
|
1196
|
+
}
|
|
1197
|
+
.cct-touch-mode Button.cct-icon-btn {
|
|
1198
|
+
min-width: 4;
|
|
1199
|
+
min-height: 1;
|
|
1200
|
+
padding: 0 1;
|
|
1201
|
+
}
|
|
1202
|
+
.cct-touch-mode .cct-badge {
|
|
1203
|
+
padding: 0 1;
|
|
1204
|
+
min-width: 6;
|
|
1205
|
+
}
|
|
1206
|
+
.cct-touch-mode .cct-rws-row,
|
|
1207
|
+
.cct-touch-mode .cct-setting-row,
|
|
1208
|
+
.cct-touch-mode .cct-nav-item,
|
|
1209
|
+
.cct-touch-mode .cct-sidebar-project-row {
|
|
1210
|
+
min-height: 2;
|
|
1211
|
+
padding: 0 1;
|
|
1212
|
+
}
|
|
1213
|
+
.cct-touch-mode .cct-filetree {
|
|
1214
|
+
padding: 0 1;
|
|
1215
|
+
}
|
|
1216
|
+
.cct-touch-mode Button:hover,
|
|
1217
|
+
.cct-touch-mode Button:focus,
|
|
1218
|
+
.cct-touch-mode .cct-btn:hover,
|
|
1219
|
+
.cct-touch-mode .cct-btn:focus,
|
|
1220
|
+
.cct-touch-mode .cct-ctrl:hover,
|
|
1221
|
+
.cct-touch-mode .cct-ctrl:focus {
|
|
1222
|
+
background: $accent 30%;
|
|
1223
|
+
border: solid $accent;
|
|
1224
|
+
}
|
|
1225
|
+
.cct-touch-mode #cct-explorer-resizer,
|
|
1226
|
+
.cct-touch-mode #cct-rightpane-resizer {
|
|
1227
|
+
background: $border;
|
|
1228
|
+
}
|
|
1229
|
+
.cct-touch-mode #cct-explorer-resizer:hover,
|
|
1230
|
+
.cct-touch-mode #cct-explorer-resizer.dragging,
|
|
1231
|
+
.cct-touch-mode #cct-rightpane-resizer:hover,
|
|
1232
|
+
.cct-touch-mode #cct-rightpane-resizer.dragging {
|
|
1233
|
+
background: $accent;
|
|
1234
|
+
}
|
|
1235
|
+
.cct-touch-mode .cct-tb-pill {
|
|
1236
|
+
padding: 0 1;
|
|
1237
|
+
min-width: 3;
|
|
1238
|
+
}
|
|
1239
|
+
.cct-touch-mode .cct-tb-pill:hover,
|
|
1240
|
+
.cct-touch-mode .cct-tb-pill:focus {
|
|
1241
|
+
color: $accent;
|
|
1242
|
+
text-style: bold;
|
|
1243
|
+
}
|
|
1244
|
+
.cct-touch-mode .cct-tb-close {
|
|
1245
|
+
min-width: 4;
|
|
1246
|
+
padding: 0 1;
|
|
1247
|
+
}
|
|
1248
|
+
.cct-touch-mode .cct-bv-btn {
|
|
1249
|
+
min-width: 4;
|
|
1250
|
+
padding: 0 1;
|
|
1251
|
+
}
|
|
1252
|
+
.cct-touch-mode .cct-bv-btn:hover,
|
|
1253
|
+
.cct-touch-mode .cct-bv-btn:focus {
|
|
1254
|
+
color: $accent;
|
|
1255
|
+
background: $surface-alt;
|
|
1256
|
+
}
|
|
1257
|
+
"""
|
|
1258
|
+
|
|
1259
|
+
|
|
1260
|
+
def fit_dialog(screen, box_id, scroll_id, fill_ids=()):
|
|
1261
|
+
"""Size a centered dialog card to its content, capped to the terminal.
|
|
1262
|
+
|
|
1263
|
+
Textual CSS alone cannot express "card is content-sized, but when the
|
|
1264
|
+
terminal is too short the middle scrolls and the footer stays
|
|
1265
|
+
visible": an `1fr` middle always inflates an auto-height card to its
|
|
1266
|
+
max-height, and an auto middle overflows the capped card. So each
|
|
1267
|
+
dialog releases its card to natural auto sizing, waits for that
|
|
1268
|
+
layout to actually land, and then imposes ONE of two consistent
|
|
1269
|
+
states:
|
|
1270
|
+
|
|
1271
|
+
Release — every id this helper may have forced before (the card,
|
|
1272
|
+
its scroll area, and the fill ids) is reset back to its natural
|
|
1273
|
+
CSS height. The next layout passes therefore measure real natural
|
|
1274
|
+
sizes, never heights distorted by a previous fit.
|
|
1275
|
+
|
|
1276
|
+
Apply — v0.7.8.5: the card's natural total height is its own
|
|
1277
|
+
*measured region* in the released layout — Textual already stacks
|
|
1278
|
+
every child (header, scroll area, footer) at natural heights, so
|
|
1279
|
+
re-deriving the total from chrome/fixed/content sums is both
|
|
1280
|
+
unnecessary and wrong (that old math double-counted the scroll
|
|
1281
|
+
area's padding, so every dialog landed 1-2 rows short and the
|
|
1282
|
+
footer buttons were pushed past the box border and clipped).
|
|
1283
|
+
The card height is set to that measured natural total, capped at
|
|
1284
|
+
`vh - 4` and floored at 6. When capped, the scroll area switches
|
|
1285
|
+
to `1fr` (so the footer stays visible in the card) and every id
|
|
1286
|
+
in `fill_ids` switches from `auto` to `100%` (used by two-column
|
|
1287
|
+
dialogs whose columns must fill the card and scroll internally
|
|
1288
|
+
instead of overflowing it).
|
|
1289
|
+
|
|
1290
|
+
Textual never applies a changed CSS height instantly: the released
|
|
1291
|
+
box keeps its previous region for one layout pass, and content that
|
|
1292
|
+
mounts *after* a fit call (row lists rebuilt in on_mount/on_resize
|
|
1293
|
+
handlers) only lands a frame later. A single measure-then-set cycle
|
|
1294
|
+
can therefore snapshot a stale region and freeze the dialog at a
|
|
1295
|
+
too-small height with its footer pushed past the box border. So
|
|
1296
|
+
apply() loops: it waits until the released region no longer matches
|
|
1297
|
+
a height this helper previously imposed, measures, writes the new
|
|
1298
|
+
heights, and then runs one more release+measure pass — repeating
|
|
1299
|
+
only while consecutive measures disagree. Every cycle starts from
|
|
1300
|
+
the same released baseline, so fits converge to the stable natural
|
|
1301
|
+
size and cannot shrink or inflate the card frame by frame.
|
|
1302
|
+
"""
|
|
1303
|
+
import os
|
|
1304
|
+
|
|
1305
|
+
# Per-screen memory of the height the last fit imposed on each box,
|
|
1306
|
+
# used to tell "the released layout hasn't landed yet" (region still
|
|
1307
|
+
# equals what we forced) apart from "the layout is stable at natural".
|
|
1308
|
+
memory = getattr(screen, "_cct_fit_memory", None)
|
|
1309
|
+
if memory is None:
|
|
1310
|
+
memory = {}
|
|
1311
|
+
try:
|
|
1312
|
+
screen._cct_fit_memory = memory
|
|
1313
|
+
except Exception:
|
|
1314
|
+
pass
|
|
1315
|
+
|
|
1316
|
+
def _q(ident):
|
|
1317
|
+
return screen.query_one(f"#{ident}")
|
|
1318
|
+
|
|
1319
|
+
def remember(value):
|
|
1320
|
+
memory[box_id] = value
|
|
1321
|
+
|
|
1322
|
+
def release():
|
|
1323
|
+
wanted = [box_id, scroll_id, *fill_ids]
|
|
1324
|
+
for ident in wanted:
|
|
1325
|
+
try:
|
|
1326
|
+
_q(ident).styles.height = "auto"
|
|
1327
|
+
except Exception:
|
|
1328
|
+
pass
|
|
1329
|
+
|
|
1330
|
+
def apply(attempts=0):
|
|
1331
|
+
if attempts > 14:
|
|
1332
|
+
return # cannot settle; leave whatever is on screen
|
|
1333
|
+
try:
|
|
1334
|
+
box = _q(box_id)
|
|
1335
|
+
scroll = _q(scroll_id)
|
|
1336
|
+
except Exception:
|
|
1337
|
+
return
|
|
1338
|
+
if not (box.is_attached and scroll.is_attached):
|
|
1339
|
+
return
|
|
1340
|
+
vh = screen.size.height
|
|
1341
|
+
# Short terminals: panels may hide chrome (subtitles/hints) via
|
|
1342
|
+
# the shared .cct-compact class; re-measure once it takes effect.
|
|
1343
|
+
want_compact = vh < 28
|
|
1344
|
+
if want_compact != box.has_class("cct-compact"):
|
|
1345
|
+
box.set_class(want_compact, "cct-compact")
|
|
1346
|
+
screen.call_after_refresh(lambda: apply(attempts))
|
|
1347
|
+
return
|
|
1348
|
+
|
|
1349
|
+
last_imposed = memory.get(box_id)
|
|
1350
|
+
if last_imposed is not None and box.region.height == last_imposed:
|
|
1351
|
+
# Still showing the previous fit — the released layout
|
|
1352
|
+
# hasn't landed yet. Wait one more frame (bounded; equal
|
|
1353
|
+
# values are also the stable end-state, so giving up here is
|
|
1354
|
+
# harmless).
|
|
1355
|
+
screen.call_after_refresh(lambda: apply(attempts + 1))
|
|
1356
|
+
return
|
|
1357
|
+
|
|
1358
|
+
natural_total = box.region.height
|
|
1359
|
+
if natural_total < 2:
|
|
1360
|
+
# Layout not ready (first frame); try again after the next one.
|
|
1361
|
+
screen.call_after_refresh(lambda: apply(attempts + 1))
|
|
1362
|
+
return
|
|
1363
|
+
capped = natural_total > vh - 4
|
|
1364
|
+
target = max(6, min(natural_total, vh - 4))
|
|
1365
|
+
if os.environ.get("CCT_FIT_DEBUG"):
|
|
1366
|
+
children = ", ".join(
|
|
1367
|
+
f"{c.id or type(c).__name__}={c.region.height}" for c in box.children)
|
|
1368
|
+
print(f"[fit] {box_id}@{vh}: box={box.region} natural={natural_total} "
|
|
1369
|
+
f"target={target} capped={capped} children=[{children}]")
|
|
1370
|
+
if target == last_imposed:
|
|
1371
|
+
# The released layout agrees with the previous fit: stable.
|
|
1372
|
+
# The styles below may still hold the release() reset from
|
|
1373
|
+
# the confirmation cycle above — re-assert the settled state
|
|
1374
|
+
# and stop.
|
|
1375
|
+
scroll.styles.height = "1fr" if capped else "auto"
|
|
1376
|
+
for child_id in fill_ids:
|
|
1377
|
+
try:
|
|
1378
|
+
_q(child_id).styles.height = "100%" if capped else "auto"
|
|
1379
|
+
except Exception:
|
|
1380
|
+
pass
|
|
1381
|
+
return
|
|
1382
|
+
box.styles.height = target
|
|
1383
|
+
scroll.styles.height = "1fr" if capped else "auto"
|
|
1384
|
+
for child_id in fill_ids:
|
|
1385
|
+
try:
|
|
1386
|
+
_q(child_id).styles.height = "100%" if capped else "auto"
|
|
1387
|
+
except Exception:
|
|
1388
|
+
pass
|
|
1389
|
+
remember(target)
|
|
1390
|
+
# Content may still be landing; confirm with one more
|
|
1391
|
+
# release + measure cycle before calling it settled.
|
|
1392
|
+
release()
|
|
1393
|
+
screen.call_after_refresh(lambda: apply(attempts + 1))
|
|
1394
|
+
|
|
1395
|
+
release()
|
|
1396
|
+
screen.call_after_refresh(apply)
|