diff --git a/index.html b/index.html index 872a272..a4eeb43 100644 --- a/index.html +++ b/index.html @@ -45,7 +45,7 @@
Current release
-v0.1.2.a.2
+v0.1.2.a.3
Supported families
diff --git a/script.js b/script.js index e293473..be11465 100644 --- a/script.js +++ b/script.js @@ -72,4 +72,46 @@ document.addEventListener('DOMContentLoaded', () => { } }); }); + + // Smooth scroll for anchor links + document.querySelectorAll('a[href^="#"]').forEach(anchor => { + anchor.addEventListener('click', function (e) { + const href = this.getAttribute('href'); + if (href === '#' || href === '#top') return; + + e.preventDefault(); + const target = document.querySelector(href); + if (target) { + target.scrollIntoView({ + behavior: 'smooth', + block: 'start' + }); + } + }); + }); + + // Intersection Observer for fade-in animations + const observerOptions = { + threshold: 0.1, + rootMargin: '0px 0px -50px 0px' + }; + + const observer = new IntersectionObserver((entries) => { + entries.forEach(entry => { + if (entry.isIntersecting) { + entry.target.style.opacity = '1'; + entry.target.style.transform = 'translateY(0)'; + } + }); + }, observerOptions); + + // Observe sections and cards for fade-in effect (exclude hero section) + const animatedElements = document.querySelectorAll('.section:not(.hero), .card, .metric, figure, .install-steps li'); + animatedElements.forEach(el => { + el.style.opacity = '0'; + el.style.transform = 'translateY(20px)'; + el.style.transition = 'opacity 0.6s ease, transform 0.6s ease'; + observer.observe(el); + }); + }); diff --git a/styles.css b/styles.css index f13b16d..5525211 100644 --- a/styles.css +++ b/styles.css @@ -4,12 +4,14 @@ --bg: #05060a; --bg-alt: #0e1018; --card: #121422; - --card-border: rgba(255, 255, 255, 0.08); + --card-border: rgba(255, 255, 255, 0.12); --text: #f5f6fd; --muted: #9aa2c4; --accent: #4dd5ff; --accent-strong: #6d84ff; + --accent-glow: rgba(77, 213, 255, 0.4); --shadow: 0 18px 45px rgba(4, 6, 11, 0.6); + --shadow-glow: 0 0 30px rgba(77, 213, 255, 0.2); } *, @@ -18,15 +20,43 @@ box-sizing: border-box; } +html { + scroll-behavior: smooth; +} + body { margin: 0; min-height: 100vh; - background: radial-gradient(circle at top, rgba(77, 213, 255, 0.12), transparent 55%), - radial-gradient(circle at 20% 20%, rgba(109, 132, 255, 0.25), transparent 35%), - var(--bg); + background: + radial-gradient(circle at 0% 0%, rgba(77, 213, 255, 0.15), transparent 50%), + radial-gradient(circle at 100% 0%, rgba(109, 132, 255, 0.2), transparent 50%), + radial-gradient(circle at 50% 100%, rgba(77, 213, 255, 0.08), transparent 60%), + var(--bg); + background-attachment: fixed; color: var(--text); font-size: 1rem; line-height: 1.6; + position: relative; + overflow-x: hidden; +} + +body::before { + content: ''; + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: + radial-gradient(circle at 20% 30%, rgba(77, 213, 255, 0.03), transparent 50%), + radial-gradient(circle at 80% 70%, rgba(109, 132, 255, 0.03), transparent 50%); + pointer-events: none; + z-index: 0; +} + +body > * { + position: relative; + z-index: 1; } img { @@ -56,6 +86,27 @@ button:focus-visible { display: flex; flex-direction: column; gap: 2rem; + position: relative; + overflow: hidden; +} + +.hero::after { + content: ''; + position: absolute; + top: -50%; + right: -20%; + width: 600px; + height: 600px; + background: radial-gradient(circle, rgba(77, 213, 255, 0.1), transparent 70%); + border-radius: 50%; + filter: blur(60px); + pointer-events: none; + animation: pulse 8s ease-in-out infinite; +} + +@keyframes pulse { + 0%, 100% { transform: scale(1) translate(0, 0); opacity: 0.6; } + 50% { transform: scale(1.2) translate(-10%, 10%); opacity: 0.8; } } .nav { @@ -68,6 +119,12 @@ button:focus-visible { .brand { font-weight: 700; letter-spacing: 0.08em; + font-size: 1.25rem; + background: linear-gradient(135deg, var(--accent), var(--accent-strong)); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; + text-shadow: 0 0 20px var(--accent-glow); } .nav__links { @@ -79,14 +136,32 @@ button:focus-visible { .nav__links a { color: var(--muted); font-weight: 500; - transition: color 0.2s ease; + transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); text-decoration: none; + position: relative; + padding: 0.5rem 0; +} + +.nav__links a::after { + content: ''; + position: absolute; + bottom: 0; + left: 0; + width: 0; + height: 2px; + background: linear-gradient(90deg, var(--accent), var(--accent-strong)); + transition: width 0.3s cubic-bezier(0.4, 0, 0.2, 1); + box-shadow: 0 0 10px var(--accent-glow); } .nav__links a:hover { color: var(--text); } +.nav__links a:hover::after { + width: 100%; +} + .nav__toggle { display: none; background: none; @@ -119,6 +194,11 @@ h1 { font-size: clamp(2.7rem, 5vw, 4.8rem); margin: 0 0 1rem; line-height: 1.05; + background: linear-gradient(135deg, var(--text) 0%, var(--accent) 50%, var(--accent-strong) 100%); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; + filter: drop-shadow(0 0 20px rgba(77, 213, 255, 0.3)); } h2 { @@ -149,8 +229,28 @@ h3 { border-radius: 999px; font-weight: 600; cursor: pointer; - transition: transform 0.2s ease, box-shadow 0.2s ease, border-color 0.2s ease; + transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); text-decoration: none; + position: relative; + overflow: hidden; +} + +.btn::before { + content: ''; + position: absolute; + top: 50%; + left: 50%; + width: 0; + height: 0; + border-radius: 50%; + background: rgba(255, 255, 255, 0.2); + transform: translate(-50%, -50%); + transition: width 0.6s, height 0.6s; +} + +.btn:hover::before { + width: 300px; + height: 300px; } .btn:focus-visible { @@ -163,32 +263,51 @@ h3 { } .btn.primary { - background: linear-gradient(90deg, var(--accent-strong), var(--accent)); + background: linear-gradient(135deg, var(--accent-strong), var(--accent)); color: #05060a; border: none; - box-shadow: 0 15px 40px rgba(77, 213, 255, 0.3); + box-shadow: 0 15px 40px rgba(77, 213, 255, 0.3), 0 0 20px rgba(77, 213, 255, 0.2); + z-index: 1; +} + +.btn.primary:hover { + transform: translateY(-2px) scale(1.02); + box-shadow: 0 20px 50px rgba(77, 213, 255, 0.4), 0 0 30px rgba(77, 213, 255, 0.3); } .btn.ghost { - border-color: rgba(255, 255, 255, 0.2); + border-color: rgba(255, 255, 255, 0.25); color: var(--text); - background: transparent; + background: rgba(255, 255, 255, 0.03); + backdrop-filter: blur(10px); + z-index: 1; } -.btn:hover { - transform: translateY(-1px); - box-shadow: 0 12px 30px rgba(109, 132, 255, 0.25); +.btn.ghost:hover { + transform: translateY(-2px); + border-color: rgba(77, 213, 255, 0.5); + background: rgba(77, 213, 255, 0.1); + box-shadow: 0 10px 30px rgba(77, 213, 255, 0.15); } .install-command { display: block; padding: 1rem 1.25rem; border-radius: 0.75rem; - background: rgba(18, 20, 34, 0.7); + background: rgba(18, 20, 34, 0.8); border: 1px solid var(--card-border); font-family: 'JetBrains Mono', 'Space Grotesk', monospace; font-size: 0.95rem; overflow-x: auto; + backdrop-filter: blur(10px); + box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3), inset 0 1px 0 rgba(255, 255, 255, 0.1); + transition: all 0.3s ease; + position: relative; +} + +.install-command:hover { + border-color: rgba(77, 213, 255, 0.3); + box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3), 0 0 20px rgba(77, 213, 255, 0.1), inset 0 1px 0 rgba(255, 255, 255, 0.1); } .hero__metrics { @@ -201,7 +320,32 @@ h3 { padding: 1.25rem; border-radius: 1rem; border: 1px solid var(--card-border); - background: rgba(5, 6, 10, 0.55); + background: rgba(5, 6, 10, 0.6); + backdrop-filter: blur(10px); + transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); + position: relative; + overflow: hidden; +} + +.metric::before { + content: ''; + position: absolute; + top: 0; + left: -100%; + width: 100%; + height: 100%; + background: linear-gradient(90deg, transparent, rgba(77, 213, 255, 0.1), transparent); + transition: left 0.5s ease; +} + +.metric:hover { + transform: translateY(-4px); + border-color: rgba(77, 213, 255, 0.3); + box-shadow: 0 10px 30px rgba(77, 213, 255, 0.15); +} + +.metric:hover::before { + left: 100%; } .metric__label { @@ -239,6 +383,40 @@ h3 { background: var(--card); box-shadow: var(--shadow); min-height: 10rem; + transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1); + position: relative; + overflow: hidden; + backdrop-filter: blur(10px); +} + +.card::before { + content: ''; + position: absolute; + top: 0; + left: 0; + right: 0; + height: 2px; + background: linear-gradient(90deg, transparent, var(--accent), transparent); + transform: translateX(-100%); + transition: transform 0.6s ease; +} + +.card:hover { + transform: translateY(-6px); + border-color: rgba(77, 213, 255, 0.4); + box-shadow: 0 20px 50px rgba(77, 213, 255, 0.2), var(--shadow); +} + +.card:hover::before { + transform: translateX(100%); +} + +.card h3 { + transition: color 0.3s ease; +} + +.card:hover h3 { + color: var(--accent); } .card p { @@ -262,10 +440,19 @@ h3 { padding: 1.5rem; border-radius: 1rem; border: 1px solid var(--card-border); - background: rgba(5, 6, 10, 0.5); + background: rgba(5, 6, 10, 0.6); + backdrop-filter: blur(10px); counter-increment: install-step; position: relative; padding-left: 4.5rem; + transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); +} + +.install-steps li:hover { + transform: translateX(8px); + border-color: rgba(77, 213, 255, 0.3); + background: rgba(5, 6, 10, 0.7); + box-shadow: 0 10px 30px rgba(77, 213, 255, 0.1); } .install-steps li::before { @@ -281,6 +468,14 @@ h3 { display: grid; place-items: center; font-weight: 600; + transition: all 0.3s ease; + box-shadow: 0 0 15px rgba(77, 213, 255, 0.3); +} + +.install-steps li:hover::before { + background: rgba(77, 213, 255, 0.3); + box-shadow: 0 0 25px rgba(77, 213, 255, 0.5); + transform: scale(1.1); } .callout { @@ -289,6 +484,14 @@ h3 { border-radius: 1rem; background: rgba(255, 196, 87, 0.12); border: 1px solid rgba(255, 196, 87, 0.35); + backdrop-filter: blur(10px); + box-shadow: 0 8px 32px rgba(255, 196, 87, 0.1); + transition: all 0.3s ease; +} + +.callout:hover { + border-color: rgba(255, 196, 87, 0.5); + box-shadow: 0 8px 32px rgba(255, 196, 87, 0.2); } .showcase__grid { @@ -303,7 +506,33 @@ figure { border-radius: 1rem; padding: 1rem; background: rgba(9, 10, 18, 0.8); + backdrop-filter: blur(10px); min-height: 15rem; + transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1); + position: relative; + overflow: hidden; +} + +figure::before { + content: ''; + position: absolute; + top: -50%; + left: -50%; + width: 200%; + height: 200%; + background: radial-gradient(circle, rgba(77, 213, 255, 0.1), transparent 70%); + opacity: 0; + transition: opacity 0.4s ease; +} + +figure:hover { + transform: translateY(-8px) scale(1.02); + border-color: rgba(77, 213, 255, 0.4); + box-shadow: 0 20px 50px rgba(77, 213, 255, 0.2); +} + +figure:hover::before { + opacity: 1; } figcaption { @@ -313,22 +542,55 @@ figcaption { } pre { - background: rgba(5, 6, 10, 0.65); + background: rgba(5, 6, 10, 0.8); padding: 1rem; border-radius: 0.75rem; overflow: auto; font-size: 0.85rem; line-height: 1.3; + border: 1px solid rgba(77, 213, 255, 0.1); + box-shadow: inset 0 2px 10px rgba(0, 0, 0, 0.3); + position: relative; + backdrop-filter: blur(5px); +} + +pre::before { + content: ''; + position: absolute; + top: 0; + left: 0; + right: 0; + height: 1px; + background: linear-gradient(90deg, transparent, rgba(77, 213, 255, 0.3), transparent); } .statusbar-demo { display: flex; justify-content: space-between; - background: linear-gradient(90deg, var(--accent-strong), var(--accent)); + background: linear-gradient(135deg, var(--accent-strong), var(--accent)); color: #05060a; padding: 0.65rem 1rem; border-radius: 0.5rem; font-weight: 600; + box-shadow: 0 4px 20px rgba(77, 213, 255, 0.4), inset 0 1px 0 rgba(255, 255, 255, 0.2); + position: relative; + overflow: hidden; +} + +.statusbar-demo::before { + content: ''; + position: absolute; + top: 0; + left: -100%; + width: 100%; + height: 100%; + background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent); + animation: shimmer 3s infinite; +} + +@keyframes shimmer { + 0% { left: -100%; } + 100% { left: 100%; } } .community__actions { @@ -340,7 +602,7 @@ pre { .footer { padding: 2rem clamp(1.5rem, 5vw, 5rem); - border-top: 1px solid rgba(255, 255, 255, 0.08); + border-top: 1px solid rgba(255, 255, 255, 0.1); display: flex; justify-content: space-between; align-items: center; @@ -348,6 +610,28 @@ pre { gap: 1rem; font-size: 0.9rem; color: var(--muted); + background: rgba(5, 6, 10, 0.5); + backdrop-filter: blur(10px); + position: relative; +} + +.footer::before { + content: ''; + position: absolute; + top: 0; + left: 0; + right: 0; + height: 1px; + background: linear-gradient(90deg, transparent, rgba(77, 213, 255, 0.3), transparent); +} + +.footer a { + transition: all 0.3s ease; +} + +.footer a:hover { + color: var(--accent); + text-shadow: 0 0 10px var(--accent-glow); } @media (max-width: 720px) { @@ -365,9 +649,27 @@ pre { flex-direction: column; padding: 1rem 0 0; gap: 0.75rem; + background: rgba(5, 6, 10, 0.95); + backdrop-filter: blur(20px); + border-radius: 0.5rem; + padding: 1rem; + margin-top: 1rem; + border: 1px solid var(--card-border); } .nav__links.is-open { display: flex; + animation: slideDown 0.3s ease; + } + + @keyframes slideDown { + from { + opacity: 0; + transform: translateY(-10px); + } + to { + opacity: 1; + transform: translateY(0); + } } } diff --git a/terminal.py b/terminal.py index 5676110..4368053 100644 --- a/terminal.py +++ b/terminal.py @@ -16,6 +16,7 @@ import logging import threading import json import shlex +import signal from datetime import datetime import urllib.request import urllib.error @@ -218,6 +219,7 @@ class ZDTTTerminal: self.scroll_region_set = False self.plugin_command_names = set() self.update_check_thread = None + self.resize_lock = threading.Lock() # Lock for resize operations # Setup logging for plugins self.setup_logging() @@ -232,12 +234,50 @@ class ZDTTTerminal: # Load user preferences (status bar color, etc.) self.load_preferences() - # ANSI color codes + # ANSI color codes - Enhanced palette self.COLOR_RESET = '\033[0m' - self.COLOR_GREEN = '\033[92m' - self.COLOR_BLUE = '\033[94m' - self.COLOR_CYAN = '\033[96m' self.COLOR_BOLD = '\033[1m' + self.COLOR_DIM = '\033[2m' + self.COLOR_ITALIC = '\033[3m' + + # Standard colors + self.COLOR_BLACK = '\033[30m' + self.COLOR_RED = '\033[31m' + self.COLOR_GREEN = '\033[32m' + self.COLOR_YELLOW = '\033[33m' + self.COLOR_BLUE = '\033[34m' + self.COLOR_MAGENTA = '\033[35m' + self.COLOR_CYAN = '\033[36m' + self.COLOR_WHITE = '\033[37m' + + # Bright colors + self.COLOR_BRIGHT_BLACK = '\033[90m' + self.COLOR_BRIGHT_RED = '\033[91m' + self.COLOR_BRIGHT_GREEN = '\033[92m' + self.COLOR_BRIGHT_YELLOW = '\033[93m' + self.COLOR_BRIGHT_BLUE = '\033[94m' + self.COLOR_BRIGHT_MAGENTA = '\033[95m' + self.COLOR_BRIGHT_CYAN = '\033[96m' + self.COLOR_BRIGHT_WHITE = '\033[97m' + + # Accent colors (using bright variants for better visibility) + self.COLOR_ACCENT = '\033[96m' # Bright cyan + self.COLOR_ACCENT2 = '\033[94m' # Bright blue + self.COLOR_SUCCESS = '\033[92m' # Bright green + self.COLOR_WARNING = '\033[93m' # Bright yellow + self.COLOR_ERROR = '\033[91m' # Bright red + self.COLOR_INFO = '\033[96m' # Bright cyan + + # Background colors + self.BG_BLACK = '\033[40m' + self.BG_RED = '\033[41m' + self.BG_GREEN = '\033[42m' + self.BG_YELLOW = '\033[43m' + self.BG_BLUE = '\033[44m' + self.BG_MAGENTA = '\033[45m' + self.BG_CYAN = '\033[46m' + self.BG_WHITE = '\033[47m' + self.BG_BRIGHT_CYAN = '\033[106m' self.commands = { 'help': self.cmd_help, @@ -473,35 +513,106 @@ ZDTT Terminal v{self.version} def _render_status_bar(self): """Render a single-line status bar with branding and time.""" - bar_text = self._build_status_bar_text() try: + # Get terminal size first to ensure we don't write beyond bounds + try: + term_size = shutil.get_terminal_size() + max_width = term_size.columns + except Exception: + max_width = 80 # Fallback + + bar_text = self._build_status_bar_text() + + # Ensure bar_text doesn't exceed terminal width (safety check) + # Count visible characters (approximate - ANSI codes don't count) + # This is a rough check, but better than nothing + if len(bar_text) > max_width * 3: # Allow for ANSI codes (rough estimate) + # Rebuild with safer width + bar_text = self._build_status_bar_text() + sys.stdout.write("\033[s") # Save cursor position - sys.stdout.write("\033[1;1H") # Move to first row - sys.stdout.write("\033[2K") # Clear the line + sys.stdout.write("\033[1;1H") # Move to first row, first column + sys.stdout.write("\033[2K") # Clear the entire line + sys.stdout.write("\033[0m") # Reset all attributes sys.stdout.write(bar_text) - sys.stdout.write(self.COLOR_RESET) + sys.stdout.write("\033[0m") # Ensure reset at end + # Move cursor to end of line to prevent wrapping issues + sys.stdout.write(f"\033[{max_width}G") # Move to column max_width sys.stdout.write("\033[u") # Restore cursor sys.stdout.flush() except Exception: - print(bar_text) + # Fallback: just skip rendering if there's an error + pass def _build_status_bar_text(self): - left_text = "ZDTT by ZaneDev" - time_str = datetime.now().strftime("%I:%M:%p").lower() - try: - term_size = shutil.get_terminal_size() - width = max(term_size.columns, len(left_text) + len(time_str) + 4) - except Exception: - width = len(left_text) + len(time_str) + 4 + """Render a single-line status bar with enhanced branding and time.""" + left_text = f"{self.COLOR_BOLD}ZDTT{self.COLOR_RESET} by {self.COLOR_BOLD}ZaneDev{self.COLOR_RESET}" + time_str = datetime.now().strftime("%I:%M %p") + plain_left = "ZDTT by ZaneDev" + plain_time = time_str - padding = max(width - len(left_text) - len(time_str) - 2, 1) - bar_plain = f" {left_text}{' ' * padding}{time_str} " - if len(bar_plain) < width: - bar_plain = bar_plain.ljust(width) + try: + # Always get fresh terminal size to handle resizes + term_size = shutil.get_terminal_size() + width = term_size.columns + # Safety: ensure width is at least 1 + width = max(1, width) + except Exception: + # Fallback to minimum width if we can't get terminal size + width = max(1, len(plain_left) + len(plain_time) + 6) + + # Calculate the minimum content width (plain text only, no ANSI codes) + # Format: " ZDTT by ZaneDev | TIME " + min_content_width = len(plain_left) + len(plain_time) + 5 # 5 = spaces + separator + + # Calculate padding to fill the line + if width < min_content_width: + # Terminal too narrow, use minimum padding + padding = 0 else: - bar_plain = bar_plain[:width] + padding = width - min_content_width + + # Build the content (plain text calculation) + separator = f"{self.COLOR_DIM}│{self.COLOR_RESET}" + bar_content = f" {left_text} {' ' * padding}{separator} {self.COLOR_BRIGHT_WHITE}{time_str}{self.COLOR_RESET} " + + # Calculate actual display length (plain text only) + actual_display_len = len(plain_left) + len(plain_time) + padding + 5 + + # Ensure we fill exactly to terminal width (but never exceed it) + if actual_display_len < width: + # Add trailing spaces to fill exactly to width + trailing_spaces = width - actual_display_len + bar_content = bar_content.rstrip() + ' ' * trailing_spaces + elif actual_display_len > width: + # We exceeded width, recalculate with less padding + padding = max(0, width - min_content_width) + bar_content = f" {left_text} {' ' * padding}{separator} {self.COLOR_BRIGHT_WHITE}{time_str}{self.COLOR_RESET} " + actual_display_len = len(plain_left) + len(plain_time) + padding + 5 + if actual_display_len < width: + trailing_spaces = width - actual_display_len + bar_content = bar_content.rstrip() + ' ' * trailing_spaces + else: + # Still too wide, trim the time if necessary + if width < len(plain_left) + 10: + # Very narrow terminal, just show minimal content + bar_content = f" {left_text} {separator} {self.COLOR_BRIGHT_WHITE}{time_str[:8]}{self.COLOR_RESET} " + bar_content = bar_content[:width] if len(bar_content) > width else bar_content + + # Final safety check: ensure we never exceed terminal width + # This is approximate since ANSI codes don't count, but better than nothing bg_code, fg_code = STATUS_BAR_COLORS.get(self.status_bar_color, ('44', '97')) - return f"\033[{bg_code}m\033[{fg_code}m{bar_plain}" + result = f"\033[{bg_code}m\033[{fg_code}m{bar_content}\033[0m" + + # If the result is suspiciously long, truncate it + # (rough heuristic: ANSI codes add ~30-50 chars, so if result > width*2, it's probably wrong) + if len(result) > width * 2: + # Emergency fallback: simple status bar + simple_bar = f" ZDTT by ZaneDev | {time_str} " + simple_bar = simple_bar[:width] if len(simple_bar) > width else simple_bar.ljust(width) + result = f"\033[{bg_code}m\033[{fg_code}m{simple_bar}\033[0m" + + return result def _set_scroll_region(self): """Reserve the top row for the status bar.""" @@ -525,6 +636,50 @@ ZDTT Terminal v{self.version} sys.stdout.flush() self.scroll_region_set = False + def _handle_resize(self, signum=None, frame=None): + """Handle terminal resize event (SIGWINCH).""" + # Use a lock to prevent race conditions + if not self.resize_lock.acquire(blocking=False): + # If we can't acquire the lock immediately, skip this resize + # (another resize is already being handled) + return + + try: + # Small delay to let terminal settle after resize + time_module.sleep(0.05) + + # Reset scroll region first to clear any corrupted state + self._reset_scroll_region() + + # Update scroll region with new terminal size + self._set_scroll_region() + + # Clear the status bar line completely before redrawing + try: + sys.stdout.write("\033[1;1H") # Move to first row + sys.stdout.write("\033[2K") # Clear the entire line + sys.stdout.write("\033[0m") # Reset attributes + sys.stdout.flush() + except Exception: + pass + + # Force immediate status bar refresh + self._render_status_bar() + + # Ensure cursor is in a safe position + try: + term_size = shutil.get_terminal_size() + sys.stdout.write(f"\033[{term_size.lines};1H") # Move to last line, first column + sys.stdout.flush() + except Exception: + pass + + except Exception: + # Silently fail if resize handling fails + pass + finally: + self.resize_lock.release() + def setup_readline(self): """Setup readline for history and tab completion""" # Setup history @@ -626,7 +781,7 @@ ZDTT Terminal v{self.version} # Show brief status if there were failures if failed_count > 0: - print(f"⚠ {failed_count} plugin(s) failed to load. Check ~/.zdtt/plugin_errors.log") + print(f"{self.COLOR_WARNING}⚠ {failed_count} plugin(s) failed to load. Check ~/.zdtt/plugin_errors.log{self.COLOR_RESET}") def unload_plugin_commands(self): """Remove commands that originated from plugins.""" @@ -668,7 +823,7 @@ ZDTT Terminal v{self.version} f.write(f"{name}={command}\n") except Exception as e: logging.error(f"Failed to save aliases: {e}") - print(f"Error: Failed to save aliases: {e}") + print(f"{self.COLOR_ERROR}Error: Failed to save aliases: {e}{self.COLOR_RESET}") def expand_aliases(self, command_line): """Expand aliases in command line""" @@ -689,7 +844,7 @@ ZDTT Terminal v{self.version} return command_line def get_prompt(self): - """Return the custom prompt string with colors""" + """Return the custom prompt string with enhanced colors""" # Show current directory in prompt cwd = os.getcwd() # Show ~ for home directory @@ -704,63 +859,72 @@ ZDTT Terminal v{self.version} RL_PROMPT_START = '\001' RL_PROMPT_END = '\002' - # Create colorized prompt with readline-safe escape codes - # [username in green @ ZDTT path in blue]=> - prompt = (f"[{RL_PROMPT_START}{self.COLOR_GREEN}{RL_PROMPT_END}{self.username}" + # Create enhanced colorized prompt with gradient-like effect + # [username in bright green @ ZDTT in bright cyan path in bright blue]=> + prompt = (f"{RL_PROMPT_START}{self.COLOR_BRIGHT_CYAN}{RL_PROMPT_END}┌─{RL_PROMPT_START}{self.COLOR_RESET}{RL_PROMPT_END}" + f"[{RL_PROMPT_START}{self.COLOR_BRIGHT_GREEN}{RL_PROMPT_END}{self.username}" f"{RL_PROMPT_START}{self.COLOR_RESET}{RL_PROMPT_END}" - f"@{RL_PROMPT_START}{self.COLOR_CYAN}{RL_PROMPT_END}ZDTT{RL_PROMPT_START}{self.COLOR_RESET}{RL_PROMPT_END} " - f"{RL_PROMPT_START}{self.COLOR_BLUE}{RL_PROMPT_END}{display_path}" - f"{RL_PROMPT_START}{self.COLOR_RESET}{RL_PROMPT_END}]=> ") + f"{RL_PROMPT_START}{self.COLOR_BRIGHT_WHITE}{RL_PROMPT_END}@{RL_PROMPT_START}{self.COLOR_RESET}{RL_PROMPT_END}" + f"{RL_PROMPT_START}{self.COLOR_BRIGHT_CYAN}{RL_PROMPT_END}ZDTT{RL_PROMPT_START}{self.COLOR_RESET}{RL_PROMPT_END} " + f"{RL_PROMPT_START}{self.COLOR_BRIGHT_BLUE}{RL_PROMPT_END}{display_path}" + f"{RL_PROMPT_START}{self.COLOR_RESET}{RL_PROMPT_END}]" + f"{RL_PROMPT_START}{self.COLOR_BRIGHT_CYAN}{RL_PROMPT_END}─{RL_PROMPT_START}{self.COLOR_RESET}{RL_PROMPT_END}\n" + f"{RL_PROMPT_START}{self.COLOR_BRIGHT_CYAN}{RL_PROMPT_END}└─{RL_PROMPT_START}{self.COLOR_BRIGHT_MAGENTA}{RL_PROMPT_END}➜{RL_PROMPT_START}{self.COLOR_RESET}{RL_PROMPT_END} ") return prompt def cmd_help(self, args): - """Display available commands""" - print("\nZDTT Terminal Commands:") - print(" help - Display this help message") - print(" clear - Clear the screen") - print(" echo