diff --git a/src/lib/assets/css/background-3d.css b/src/lib/assets/css/background-3d.css new file mode 100644 index 0000000..c8b4ad8 --- /dev/null +++ b/src/lib/assets/css/background-3d.css @@ -0,0 +1,13 @@ +.scene-container { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + z-index: -1; + pointer-events: none; +} + +.scene-container :global(canvas) { + display: block; +} diff --git a/src/lib/assets/css/index.css b/src/lib/assets/css/index.css new file mode 100644 index 0000000..0f2b2a9 --- /dev/null +++ b/src/lib/assets/css/index.css @@ -0,0 +1,34 @@ +/* TUI Components CSS Index */ +/* This file can be imported to get all TUI component styles at once */ + +/* TUI Core Components */ +@import './tui-header.css'; +@import './tui-footer.css'; +@import './tui-body.css'; + +/* TUI Display Components */ +@import './tui-button.css'; +@import './tui-link.css'; +@import './tui-card.css'; +@import './tui-card-grid.css'; +@import './tui-progress.css'; +@import './tui-accordion.css'; +@import './tui-table.css'; +@import './tui-tooltip.css'; + +/* TUI Form Components */ +@import './tui-input.css'; +@import './tui-textarea.css'; +@import './tui-checkbox.css'; +@import './tui-radio.css'; +@import './tui-select.css'; +@import './tui-toggle.css'; + +/* Main Components */ +@import './terminal.css'; +@import './terminal-tui.css'; +@import './terminal-page.css'; +@import './model-viewer.css'; +@import './navbar.css'; +@import './navbar-waybar.css'; +@import './background-3d.css'; diff --git a/src/lib/assets/css/model-viewer.css b/src/lib/assets/css/model-viewer.css new file mode 100644 index 0000000..cb2dc9a --- /dev/null +++ b/src/lib/assets/css/model-viewer.css @@ -0,0 +1,172 @@ +.model-viewer { + display: flex; + flex-direction: column; + background: var(--viewer-bg); + border: 1px solid var(--viewer-border); + border-radius: 8px; + overflow: hidden; + transition: all 0.3s ease; +} + +.model-viewer.fullscreen { + position: fixed; + inset: 60px 0 0 0; + z-index: 100; + border-radius: 0; + border: none; +} + +.viewer-header { + display: flex; + align-items: center; + justify-content: space-between; + padding: 0.5rem 0.75rem; + background: color-mix(in srgb, var(--viewer-bg) 80%, black); + border-bottom: 1px solid var(--viewer-border); +} + +.header-left { + display: flex; + align-items: center; + gap: 0.5rem; + color: var(--viewer-primary); + font-size: 0.85rem; + font-weight: 500; +} + +.header-controls { + display: flex; + gap: 0.25rem; +} + +.control-btn { + display: flex; + align-items: center; + justify-content: center; + width: 28px; + height: 28px; + background: transparent; + border: 1px solid transparent; + border-radius: 4px; + color: var(--viewer-muted); + cursor: pointer; + transition: all 0.2s ease; +} + +.control-btn:hover { + background: var(--viewer-border); + color: var(--viewer-text); + border-color: var(--viewer-border); +} + +.control-btn.active { + background: var(--viewer-primary); + color: var(--viewer-bg); + border-color: var(--viewer-primary); +} + +.control-btn.small { + width: 24px; + height: 24px; +} + +/* Extended controls panel */ +.controls-panel { + display: flex; + flex-wrap: wrap; + gap: 1rem; + padding: 0.5rem 0.75rem; + background: color-mix(in srgb, var(--viewer-bg) 90%, black); + border-bottom: 1px solid var(--viewer-border); +} + +.control-group { + display: flex; + align-items: center; + gap: 0.5rem; +} + +.control-label { + font-size: 0.7rem; + color: var(--viewer-muted); + text-transform: uppercase; + letter-spacing: 0.05em; +} + +.control-buttons { + display: flex; + align-items: center; + gap: 0.25rem; +} + +.control-value { + font-size: 0.7rem; + color: var(--viewer-text); + min-width: 2.5rem; + text-align: center; + font-family: 'JetBrains Mono', monospace; +} + +.canvas-container { + flex: 1; + min-height: 300px; + position: relative; + background: radial-gradient( + circle at center, + color-mix(in srgb, var(--viewer-primary) 5%, transparent), + transparent 70% + ); + outline: none; +} + +.canvas-container:focus { + box-shadow: inset 0 0 0 2px var(--viewer-primary); +} + +.canvas-container :global(canvas) { + display: block; +} + +.loading-overlay, +.error-overlay { + position: absolute; + inset: 0; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 0.75rem; + color: var(--viewer-muted); + font-size: 0.9rem; +} + +.error-overlay { + color: #f38ba8; +} + +:global(.spin) { + animation: spin 1s linear infinite; +} + +@keyframes spin { + from { transform: rotate(0deg); } + to { transform: rotate(360deg); } +} + +.viewer-footer { + display: flex; + align-items: center; + justify-content: center; + gap: 1.5rem; + padding: 0.5rem; + background: color-mix(in srgb, var(--viewer-bg) 80%, black); + border-top: 1px solid var(--viewer-border); +} + +.hint { + display: flex; + align-items: center; + gap: 0.35rem; + color: var(--viewer-muted); + font-size: 0.75rem; +} diff --git a/src/lib/assets/css/navbar-waybar.css b/src/lib/assets/css/navbar-waybar.css new file mode 100644 index 0000000..9f92cd5 --- /dev/null +++ b/src/lib/assets/css/navbar-waybar.css @@ -0,0 +1,377 @@ +/* Waybar main container */ +.waybar { + position: fixed; + top: 0; + left: 0; + right: 0; + z-index: 1000; + height: var(--navbar-height); + display: flex; + align-items: center; + justify-content: space-between; + padding: 0 0.75rem; + background: var(--bar-bg); + border-bottom: 1px solid var(--bar-border); + font-family: 'JetBrains Mono', 'Fira Code', monospace; + font-size: 0.8rem; +} + +/* Bar sections */ +.bar-left, +.bar-center, +.bar-right { + display: flex; + align-items: center; + gap: 0.5rem; +} + +.bar-left { + flex: 1; +} + +.bar-right { + flex: 1; + justify-content: flex-end; +} + +/* Desktop-only items */ +.desktop-only { + display: flex; +} + +@media (max-width: 768px) { + .desktop-only { + display: none; + } +} + +/* Module base style */ +.module { + display: flex; + align-items: center; + gap: 0.5rem; + padding: 0.35rem 0.6rem; + background: var(--bar-bg-module); + border-radius: 6px; + transition: all 0.15s ease; +} + +/* Launcher / Logo */ +.launcher { + padding: 0.4rem; + border-radius: 6px; +} + +.launcher:hover { + background: color-mix(in srgb, var(--bar-primary) 20%, transparent); +} + +/* Mobile menu toggle button */ +.mobile-menu-toggle { + display: none; + align-items: center; + justify-content: center; + width: 36px; + height: 36px; + padding: 0; + background: var(--bar-bg-module); + border: 1px solid var(--bar-border); + border-radius: 6px; + color: var(--bar-text); + cursor: pointer; + transition: all 0.15s ease; +} + +.mobile-menu-toggle:hover { + background: color-mix(in srgb, var(--bar-primary) 20%, transparent); + border-color: var(--bar-primary); +} + +@media (max-width: 768px) { + .mobile-menu-toggle { + display: flex; + } +} + +/* Workspaces */ +.workspaces { + gap: 0.25rem; + background: transparent; + padding: 0; +} + +.workspace { + display: flex; + align-items: center; + gap: 0.35rem; + padding: 0.35rem 0.6rem; + color: var(--bar-muted); + text-decoration: none; + border-radius: 4px; + transition: all 0.15s ease; + font-size: 0.75rem; +} + +.workspace:hover { + color: var(--bar-text); + background: var(--bar-bg-module); +} + +.workspace.active { + color: var(--bar-primary); + background: color-mix(in srgb, var(--bar-primary) 15%, transparent); +} + +.workspace.external { + color: var(--bar-accent); +} + +.workspace.external:hover { + background: color-mix(in srgb, var(--bar-accent) 15%, transparent); +} + +.ws-name { + font-weight: 500; +} + +/* Window title */ +.window-title { + color: var(--bar-text); + font-size: 0.75rem; +} + +.title-icon { + color: var(--bar-primary); +} + +.title-text { + color: var(--bar-muted); +} + +/* Clock */ +.clock { + gap: 0.75rem; +} + +.time { + color: var(--bar-text); + font-weight: 600; +} + +.date { + color: var(--bar-muted); +} + +/* Theme selector */ +.theme-selector { + position: relative; + background: transparent; + padding: 0; +} + +.theme-trigger { + display: flex; + align-items: center; + justify-content: center; + width: 32px; + height: 32px; + padding: 0; + background: var(--bar-bg-module); + border: 1px solid var(--bar-border); + border-radius: 6px; + color: var(--bar-text); + cursor: pointer; + transition: all 0.15s ease; +} + +.theme-trigger:hover { + background: color-mix(in srgb, var(--bar-primary) 20%, transparent); + border-color: var(--bar-primary); +} + +.theme-dropdown { + position: absolute; + top: calc(100% + 0.5rem); + right: 0; + min-width: 160px; + background: var(--bar-bg); + border: 1px solid var(--bar-border); + border-radius: 8px; + padding: 0.5rem; + box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4); + z-index: 1001; +} + +.dropdown-header { + padding: 0.25rem 0.5rem; + font-size: 0.65rem; + text-transform: uppercase; + letter-spacing: 0.05em; + color: var(--bar-muted); +} + +.dropdown-divider { + height: 1px; + background: var(--bar-border); + margin: 0.5rem 0; +} + +.theme-option { + display: flex; + align-items: center; + gap: 0.5rem; + width: 100%; + padding: 0.5rem 0.75rem; + background: transparent; + border: none; + border-radius: 4px; + color: var(--bar-text); + cursor: pointer; + font-family: inherit; + font-size: 0.8rem; + text-align: left; + transition: all 0.15s ease; +} + +.theme-option:hover { + background: color-mix(in srgb, var(--bar-primary) 15%, transparent); +} + +.theme-option.active { + color: var(--bar-primary); +} + +:global(.check) { + margin-left: auto; + color: var(--bar-primary); +} + +/* Backdrop */ +.backdrop { + position: fixed; + inset: 0; + background: transparent; + z-index: 999; + border: none; + cursor: default; +} + +/* Mobile menu dropdown */ +.mobile-menu { + position: fixed; + top: var(--navbar-height); + left: 0; + right: 0; + background: var(--bar-bg); + border-bottom: 1px solid var(--bar-border); + z-index: 998; + padding: 1rem; + font-family: 'JetBrains Mono', 'Fira Code', monospace; + box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4); +} + +.mobile-nav-links { + display: flex; + flex-direction: column; + gap: 0.5rem; +} + +.mobile-nav-link { + display: flex; + align-items: center; + gap: 0.75rem; + padding: 0.75rem 1rem; + color: var(--bar-text); + text-decoration: none; + border-radius: 6px; + transition: all 0.15s ease; + font-size: 0.9rem; +} + +.mobile-nav-link:hover { + background: var(--bar-bg-module); +} + +.mobile-nav-link.active { + color: var(--bar-primary); + background: color-mix(in srgb, var(--bar-primary) 15%, transparent); +} + +.mobile-nav-link.external { + color: var(--bar-accent); +} + +.mobile-menu-divider { + height: 1px; + background: var(--bar-border); + margin: 1rem 0; +} + +.mobile-theme-section { + display: flex; + flex-direction: column; + gap: 0.75rem; +} + +.mobile-section-header { + font-size: 0.7rem; + text-transform: uppercase; + letter-spacing: 0.05em; + color: var(--bar-muted); + padding: 0 0.5rem; +} + +.mobile-theme-options { + display: flex; + gap: 0.5rem; +} + +.mobile-theme-btn { + flex: 1; + display: flex; + flex-direction: column; + align-items: center; + gap: 0.35rem; + padding: 0.75rem 0.5rem; + background: var(--bar-bg-module); + border: 1px solid var(--bar-border); + border-radius: 6px; + color: var(--bar-text); + cursor: pointer; + font-family: inherit; + font-size: 0.75rem; + transition: all 0.15s ease; +} + +.mobile-theme-btn:hover { + background: color-mix(in srgb, var(--bar-primary) 15%, transparent); + border-color: var(--bar-primary); +} + +.mobile-theme-btn.active { + color: var(--bar-primary); + border-color: var(--bar-primary); + background: color-mix(in srgb, var(--bar-primary) 20%, transparent); +} + +.mobile-mode-btn { + display: flex; + align-items: center; + justify-content: center; + gap: 0.5rem; + padding: 0.75rem; + background: var(--bar-bg-module); + border: 1px solid var(--bar-border); + border-radius: 6px; + color: var(--bar-text); + cursor: pointer; + font-family: inherit; + font-size: 0.8rem; + transition: all 0.15s ease; + width: 100%; +} + +.mobile-mode-btn:hover { + background: color-mix(in srgb, var(--bar-primary) 15%, transparent); + border-color: var(--bar-primary); +} diff --git a/src/lib/assets/css/navbar.css b/src/lib/assets/css/navbar.css new file mode 100644 index 0000000..09e3bb2 --- /dev/null +++ b/src/lib/assets/css/navbar.css @@ -0,0 +1,331 @@ +.navbar { + position: fixed; + top: 0; + left: 0; + right: 0; + z-index: 1000; + background: var(--nav-bg); + border-bottom: 1px solid var(--nav-border); + backdrop-filter: blur(10px); + font-family: 'JetBrains Mono', 'Fira Code', 'Consolas', monospace; +} + +.nav-container { + max-width: 1400px; + margin: 0 auto; + padding: 0.75rem 1.5rem; + display: flex; + align-items: center; + justify-content: space-between; + gap: 2rem; +} + +.nav-brand { + display: flex; + align-items: center; + gap: 0.25rem; + font-size: 0.9rem; + font-weight: 600; +} + +.terminal-prompt { + display: flex; + align-items: center; + gap: 0; +} + +.terminal-prompt .user { + color: var(--nav-accent); +} + +.terminal-prompt .separator { + color: var(--nav-text); +} + +.terminal-prompt .path { + color: var(--nav-primary); +} + +.terminal-prompt .symbol { + color: var(--nav-text); + margin-left: 0.25rem; +} + +.cursor { + width: 8px; + height: 1em; + background: var(--nav-primary); + animation: blink 1s step-end infinite; + margin-left: 0.25rem; + vertical-align: text-bottom; +} + +@keyframes blink { + 0%, 100% { opacity: 1; } + 50% { opacity: 0; } +} + +.mobile-toggle { + display: none; + background: none; + border: none; + padding: 0.5rem; + cursor: pointer; +} + +.hamburger { + display: block; + width: 24px; + height: 2px; + background: var(--nav-text); + position: relative; + transition: all 0.3s ease; +} + +.hamburger::before, +.hamburger::after { + content: ''; + position: absolute; + width: 24px; + height: 2px; + background: var(--nav-text); + transition: all 0.3s ease; +} + +.hamburger::before { + top: -7px; +} + +.hamburger::after { + top: 7px; +} + +.hamburger.open { + background: transparent; +} + +.hamburger.open::before { + top: 0; + transform: rotate(45deg); +} + +.hamburger.open::after { + top: 0; + transform: rotate(-45deg); +} + +.nav-links { + display: flex; + align-items: center; + gap: 1.5rem; + flex: 1; +} + +.nav-link { + color: var(--nav-text); + text-decoration: none; + font-size: 0.85rem; + padding: 0.5rem 0.75rem; + border-radius: 4px; + transition: all 0.2s ease; + display: flex; + align-items: center; + position: relative; + overflow: hidden; +} + +.nav-link::before { + content: ''; + position: absolute; + bottom: 0; + left: 0; + width: 0; + height: 2px; + background: var(--nav-primary); + transition: width 0.3s ease; +} + +.nav-link:hover::before { + width: 100%; +} + +.nav-link:hover { + color: var(--nav-primary); +} + +.link-prefix { + color: var(--nav-muted); + margin-right: 2px; +} + +.nav-controls { + display: flex; + align-items: center; + gap: 1rem; +} + +.theme-selector { + position: relative; +} + +.theme-button { + display: flex; + align-items: center; + gap: 0.5rem; + padding: 0.5rem 0.75rem; + background: transparent; + border: 1px solid var(--nav-border); + border-radius: 6px; + color: var(--nav-text); + cursor: pointer; + font-family: inherit; + font-size: 0.8rem; + transition: all 0.2s ease; +} + +.theme-button:hover { + border-color: var(--nav-primary); + background: color-mix(in srgb, var(--nav-primary) 10%, transparent); +} + +.theme-icon { + font-size: 1rem; + display: flex; + align-items: center; +} + +.theme-label { + text-transform: capitalize; +} + +:global(.dropdown-arrow) { + transition: transform 0.2s ease; +} + +:global(.dropdown-arrow.open) { + transform: rotate(180deg); +} + +.theme-dropdown { + position: absolute; + top: calc(100% + 0.5rem); + right: 0; + min-width: 180px; + background: var(--nav-bg); + border: 1px solid var(--nav-border); + border-radius: 8px; + padding: 0.5rem; + box-shadow: 0 10px 40px rgba(0, 0, 0, 0.3); + z-index: 1001; +} + +.theme-option { + display: flex; + align-items: center; + gap: 0.75rem; + width: 100%; + padding: 0.625rem 0.75rem; + background: transparent; + border: none; + border-radius: 6px; + color: var(--nav-text); + cursor: pointer; + font-family: inherit; + font-size: 0.85rem; + text-align: left; + transition: all 0.2s ease; +} + +.theme-option:hover { + background: color-mix(in srgb, var(--nav-primary) 15%, transparent); +} + +.theme-option.active { + background: color-mix(in srgb, var(--nav-primary) 20%, transparent); + color: var(--nav-primary); +} + +.option-icon { + font-size: 1.1rem; + display: flex; + align-items: center; +} + +.option-label { + flex: 1; +} + +:global(.check-mark) { + color: var(--nav-primary); +} + +.mode-toggle { + display: flex; + align-items: center; + justify-content: center; + width: 40px; + height: 40px; + background: transparent; + border: 1px solid var(--nav-border); + border-radius: 8px; + cursor: pointer; + transition: all 0.2s ease; + color: var(--nav-text); +} + +.mode-toggle:hover { + border-color: var(--nav-primary); + background: color-mix(in srgb, var(--nav-primary) 10%, transparent); + color: var(--nav-primary); +} + +.backdrop { + position: fixed; + inset: 0; + background: transparent; + z-index: 999; + border: none; + cursor: default; +} + +@media (max-width: 768px) { + .nav-container { + flex-wrap: wrap; + padding: 0.75rem 1rem; + } + + .mobile-toggle { + display: block; + order: 2; + } + + .nav-links { + order: 4; + width: 100%; + flex-direction: column; + align-items: flex-start; + gap: 0.5rem; + max-height: 0; + overflow: hidden; + transition: max-height 0.3s ease, padding 0.3s ease; + } + + .nav-links.expanded { + max-height: 200px; + padding-top: 1rem; + } + + .nav-controls { + order: 3; + margin-left: auto; + margin-right: 1rem; + } + + .theme-label { + display: none; + } + + .theme-button { + padding: 0.5rem; + } +} diff --git a/src/lib/assets/css/terminal-page.css b/src/lib/assets/css/terminal-page.css new file mode 100644 index 0000000..badfba1 --- /dev/null +++ b/src/lib/assets/css/terminal-page.css @@ -0,0 +1,223 @@ +.terminal-page { + font-family: 'JetBrains Mono', 'Fira Code', 'Consolas', monospace; + background: var(--terminal-bg); + border: 1px solid var(--terminal-border); + border-radius: 12px; + overflow: hidden; + box-shadow: + 0 25px 50px -12px rgba(0, 0, 0, 0.4), + 0 0 0 1px rgba(255, 255, 255, 0.05) inset; + animation: terminalFadeIn 0.5s ease-out; + display: flex; + flex-direction: column; + height: 100%; +} + +@keyframes terminalFadeIn { + from { + opacity: 0; + transform: translateY(20px) scale(0.98); + } + to { + opacity: 1; + transform: translateY(0) scale(1); + } +} + +.terminal-header { + display: flex; + align-items: center; + padding: 0.75rem 1rem; + background: var(--terminal-bg-light); + border-bottom: 1px solid var(--terminal-border); + flex-shrink: 0; +} + +.terminal-buttons { + display: flex; + gap: 8px; +} + +.terminal-buttons .btn { + width: 12px; + height: 12px; + border-radius: 50%; + cursor: pointer; + transition: opacity 0.2s; +} + +.terminal-buttons .btn:hover { + opacity: 0.8; +} + +.terminal-buttons .close { + background: #ff5f56; +} + +.terminal-buttons .minimize { + background: #ffbd2e; +} + +.terminal-buttons .maximize { + background: #27ca40; +} + +.terminal-title { + flex: 1; + text-align: center; + font-size: 0.8rem; + color: var(--terminal-muted); + display: flex; + align-items: center; + justify-content: center; + gap: 0.5rem; +} + +.terminal-icon { + font-size: 1rem; +} + +.terminal-spacer { + width: 52px; +} + +.terminal-body { + padding: 1rem 1.25rem; + flex: 1; + overflow-y: auto; + font-size: 0.9rem; + line-height: 1.6; +} + +.terminal-line { + display: flex; + align-items: flex-start; + flex-wrap: wrap; + margin-bottom: 0.25rem; + animation: lineSlideIn 0.2s ease-out; + min-height: 1.6em; +} + +.terminal-line.blank { + min-height: 0.8em; +} + +@keyframes lineSlideIn { + from { + opacity: 0; + transform: translateX(-10px); + } + to { + opacity: 1; + transform: translateX(0); + } +} + +.prompt { + display: inline-flex; + margin-right: 0.5rem; + flex-shrink: 0; +} + +.prompt .user { + color: var(--terminal-user); +} + +.prompt .separator { + color: var(--terminal-text); +} + +.prompt .path { + color: var(--terminal-path); +} + +.prompt .symbol { + color: var(--terminal-text); + margin-left: 0.25rem; +} + +.content { + color: var(--terminal-text); + word-break: break-word; + white-space: pre-wrap; +} + +.terminal-line.output .content { + color: var(--terminal-muted); +} + +.terminal-line.error .content { + color: #f38ba8; +} + +.terminal-line.success .content { + color: #a6e3a1; +} + +.terminal-line.info .content { + color: var(--terminal-primary); +} + +.terminal-line.header .content { + color: var(--terminal-accent); + font-weight: 600; + font-size: 1rem; +} + +.header-text { + color: var(--terminal-accent); + font-weight: 600; +} + +.terminal-image { + display: flex; + flex-direction: column; + gap: 0.5rem; + padding: 0.5rem 0; +} + +.terminal-image img { + border-radius: 8px; + border: 1px solid var(--terminal-border); + background: var(--terminal-bg-light); + object-fit: contain; +} + +.image-caption { + color: var(--terminal-muted); + font-size: 0.8rem; + font-style: italic; +} + +.cursor { + display: inline-block; + width: 8px; + height: 1em; + background: var(--terminal-primary); + animation: cursorBlink 1s step-end infinite; + margin-left: 2px; + vertical-align: text-bottom; +} + +@keyframes cursorBlink { + 0%, 100% { opacity: 1; } + 50% { opacity: 0; } +} + +/* Scrollbar styling */ +.terminal-body::-webkit-scrollbar { + width: 8px; +} + +.terminal-body::-webkit-scrollbar-track { + background: transparent; +} + +.terminal-body::-webkit-scrollbar-thumb { + background: var(--terminal-border); + border-radius: 4px; +} + +.terminal-body::-webkit-scrollbar-thumb:hover { + background: var(--terminal-muted); +} diff --git a/src/lib/assets/css/terminal-tui.css b/src/lib/assets/css/terminal-tui.css new file mode 100644 index 0000000..e632d69 --- /dev/null +++ b/src/lib/assets/css/terminal-tui.css @@ -0,0 +1,73 @@ +.tui-terminal { + font-family: 'JetBrains Mono', 'Fira Code', 'Consolas', monospace; + background: var(--terminal-bg); + border: 2px solid var(--terminal-border); + border-radius: 8px; + overflow: hidden; + position: relative; + display: flex; + flex-direction: column; + width: 95%; + margin: 0 auto; + height: calc(100vh - var(--navbar-height) - 80px); + max-height: calc(100vh - var(--navbar-height) - 80px); + animation: tuiFadeIn 0.4s ease-out; +} + +.tui-terminal:focus-within .tui-border-glow { + opacity: 1; +} + +@keyframes tuiFadeIn { + from { + opacity: 0; + transform: scale(0.98); + } + to { + opacity: 1; + transform: scale(1); + } +} + +/* Hyprland-style animated border glow */ +.tui-border-glow { + position: absolute; + inset: -2px; + border-radius: 10px; + background: linear-gradient( + 45deg, + var(--terminal-primary), + var(--terminal-accent), + var(--terminal-primary), + var(--terminal-accent) + ); + background-size: 400% 400%; + animation: borderGlow 8s ease infinite; + opacity: 0.5; + z-index: -1; + filter: blur(4px); + transition: opacity 0.3s ease; +} + +@keyframes borderGlow { + 0%, 100% { background-position: 0% 50%; } + 50% { background-position: 100% 50%; } +} + +.tui-content { + display: flex; + flex-direction: column; + height: 100%; + background: var(--terminal-bg); + position: relative; + z-index: 1; +} + +/* Responsive */ +@media (max-width: 768px) { + .tui-terminal { + width: 95%; + height: calc(100vh - var(--navbar-height) - 60px); + max-height: calc(100vh - var(--navbar-height) - 60px); + } +} diff --git a/src/lib/assets/css/terminal.css b/src/lib/assets/css/terminal.css new file mode 100644 index 0000000..4417fd9 --- /dev/null +++ b/src/lib/assets/css/terminal.css @@ -0,0 +1,183 @@ +.terminal { + font-family: 'JetBrains Mono', 'Fira Code', 'Consolas', monospace; + background: var(--terminal-bg); + border: 1px solid var(--terminal-border); + border-radius: 12px; + overflow: hidden; + box-shadow: + 0 25px 50px -12px rgba(0, 0, 0, 0.4), + 0 0 0 1px rgba(255, 255, 255, 0.05) inset; + animation: terminalFadeIn 0.5s ease-out; +} + +@keyframes terminalFadeIn { + from { + opacity: 0; + transform: translateY(20px) scale(0.98); + } + to { + opacity: 1; + transform: translateY(0) scale(1); + } +} + +.terminal-header { + display: flex; + align-items: center; + padding: 0.75rem 1rem; + background: color-mix(in srgb, var(--terminal-bg) 80%, black); + border-bottom: 1px solid var(--terminal-border); +} + +.terminal-buttons { + display: flex; + gap: 8px; +} + +.terminal-buttons .btn { + width: 12px; + height: 12px; + border-radius: 50%; + cursor: pointer; + transition: opacity 0.2s; +} + +.terminal-buttons .btn:hover { + opacity: 0.8; +} + +.terminal-buttons .close { + background: #ff5f56; +} + +.terminal-buttons .minimize { + background: #ffbd2e; +} + +.terminal-buttons .maximize { + background: #27ca40; +} + +.terminal-title { + flex: 1; + text-align: center; + font-size: 0.8rem; + color: var(--terminal-muted); + display: flex; + align-items: center; + justify-content: center; + gap: 0.5rem; +} + +.terminal-icon { + font-size: 1rem; +} + +.terminal-spacer { + width: 52px; +} + +.terminal-body { + padding: 1rem 1.25rem; + min-height: 200px; + max-height: 400px; + overflow-y: auto; + font-size: 0.9rem; + line-height: 1.6; +} + +.terminal-line { + display: flex; + align-items: center; + flex-wrap: wrap; + margin-bottom: 0.25rem; + animation: lineSlideIn 0.2s ease-out; +} + +@keyframes lineSlideIn { + from { + opacity: 0; + transform: translateX(-10px); + } + to { + opacity: 1; + transform: translateX(0); + } +} + +.prompt { + display: inline-flex; + margin-right: 0.5rem; + flex-shrink: 0; +} + +.prompt .user { + color: var(--terminal-user); +} + +.prompt .separator { + color: var(--terminal-text); +} + +.prompt .path { + color: var(--terminal-path); +} + +.prompt .symbol { + color: var(--terminal-text); + margin-left: 0.25rem; +} + +.content { + color: var(--terminal-text); + word-break: break-word; +} + +.terminal-line.output .content { + color: var(--terminal-muted); +} + +.terminal-line.error .content { + color: #ff6b6b; +} + +.terminal-line.success .content { + color: #51cf66; +} + +.terminal-line.info .content { + color: var(--terminal-primary); +} + +.cursor { + display: inline-block; + width: 8px; + height: 1em; + background: var(--terminal-primary); + animation: cursorBlink 1s step-end infinite; + margin-left: 2px; + vertical-align: text-bottom; +} + +@keyframes cursorBlink { + 0%, 100% { opacity: 1; } + 50% { opacity: 0; } +} + +/* Scrollbar styling */ +.terminal-body::-webkit-scrollbar { + width: 8px; +} + +.terminal-body::-webkit-scrollbar-track { + background: transparent; +} + +.terminal-body::-webkit-scrollbar-thumb { + background: var(--terminal-border); + border-radius: 4px; +} + +.terminal-body::-webkit-scrollbar-thumb:hover { + background: var(--terminal-muted); +} diff --git a/src/lib/assets/css/tui-accordion.css b/src/lib/assets/css/tui-accordion.css new file mode 100644 index 0000000..343ffee --- /dev/null +++ b/src/lib/assets/css/tui-accordion.css @@ -0,0 +1,69 @@ +.tui-accordion { + margin: 0.5rem 0; + border: 1px solid var(--terminal-border); + border-radius: 6px; + overflow: hidden; +} + +.accordion-item { + border-bottom: 1px solid var(--terminal-border); +} + +.accordion-item:last-child { + border-bottom: none; +} + +.accordion-header { + display: flex; + align-items: center; + gap: 0.5rem; + width: 100%; + padding: 0.6rem 0.75rem; + background: transparent; + border: none; + color: var(--terminal-text); + font-family: inherit; + font-size: 0.85rem; + font-weight: 500; + text-align: left; + cursor: pointer; + transition: background 0.15s ease; +} + +.accordion-header:hover { + background: rgba(255, 255, 255, 0.03); +} + +.accordion-item.open .accordion-header { + background: rgba(0, 0, 0, 0.1); + border-bottom: 1px solid var(--terminal-border); +} + +:global(.accordion-icon) { + color: var(--accordion-accent); + transition: transform 0.2s ease; +} + +.accordion-title { + flex: 1; +} + +.accordion-content { + padding: 0.75rem; + color: var(--terminal-muted); + font-size: 0.85rem; + line-height: 1.6; + white-space: pre-wrap; + animation: slideDown 0.2s ease-out; +} + +@keyframes slideDown { + from { + opacity: 0; + transform: translateY(-5px); + } + to { + opacity: 1; + transform: translateY(0); + } +} diff --git a/src/lib/assets/css/tui-body.css b/src/lib/assets/css/tui-body.css new file mode 100644 index 0000000..21523a3 --- /dev/null +++ b/src/lib/assets/css/tui-body.css @@ -0,0 +1,227 @@ +.tui-body { + flex: 1; + padding: 1rem 1.25rem 2rem 1.25rem; + overflow-y: auto; + overflow-x: hidden; + font-size: 0.9rem; + line-height: 1.7; + min-height: 0; +} + +/* Inline group wrapper - groups consecutive inline elements */ +.tui-inline-group { + display: flex; + flex-wrap: wrap; + gap: 0.5rem; + margin-bottom: 0.2rem; + animation: lineSlideIn 0.15s ease-out; +} + +.inline-content { + display: inline; + white-space: pre-wrap; +} + +.inline-content.output { + color: var(--terminal-muted); +} + +.inline-content.info { + color: var(--terminal-primary); +} + +.inline-content.success { + color: #a6e3a1; +} + +.inline-content.error { + color: #f38ba8; +} + +.inline-content.warning { + color: #f9e2af; +} + +/* Lines */ +.tui-line { + display: flex; + align-items: flex-start; + flex-wrap: wrap; + margin-bottom: 0.2rem; + animation: lineSlideIn 0.15s ease-out; + min-height: 1.7em; +} + +.tui-line.blank { + min-height: 0.5em; +} + +@keyframes lineSlideIn { + from { + opacity: 0; + transform: translateX(-5px); + } + to { + opacity: 1; + transform: translateX(0); + } +} + +/* Prompt styling */ +.prompt { + display: inline-flex; + margin-right: 0.5rem; + flex-shrink: 0; +} + +.prompt .user { + color: var(--terminal-user); +} + +.prompt .at { + color: var(--terminal-muted); +} + +.prompt .host { + color: var(--terminal-accent); +} + +.prompt .separator { + color: var(--terminal-muted); +} + +.prompt .path { + color: var(--terminal-path); +} + +.prompt .symbol { + color: var(--terminal-muted); + margin-left: 0.25rem; +} + +/* Content */ +.content { + color: var(--terminal-text); + word-break: break-word; + white-space: pre-wrap; +} + +.tui-line.output .content { + color: var(--terminal-muted); +} + +.tui-line.error .content { + color: #f38ba8; +} + +.tui-line.success .content { + color: #a6e3a1; +} + +.tui-line.info .content { + color: var(--terminal-primary); +} + +.tui-line.warning .content { + color: #f9e2af; +} + +.header-text { + color: var(--terminal-accent); + font-weight: 600; + font-size: 1rem; + display: flex; + align-items: center; + gap: 0.5rem; +} + +:global(.header-icon) { + opacity: 0.7; +} + +:global(.inline-icon) { + display: inline-block; + vertical-align: middle; + margin: 0 0.15em; +} + +/* Divider */ +.tui-divider { + display: flex; + align-items: center; + gap: 1rem; + margin: 0.75rem 0; +} + +.divider-line { + flex: 1; + height: 1px; + background: linear-gradient( + to right, + transparent, + var(--terminal-border), + transparent + ); +} + +.divider-text { + color: var(--terminal-muted); + font-size: 0.75rem; + text-transform: uppercase; + letter-spacing: 0.1em; +} + +/* Images */ +.tui-image { + display: flex; + flex-direction: column; + gap: 0.5rem; + padding: 0.5rem 0; +} + +.tui-image img { + border-radius: 6px; + border: 1px solid var(--terminal-border); + background: var(--terminal-bg-light); + object-fit: contain; +} + +.image-caption { + color: var(--terminal-muted); + font-size: 0.8rem; + font-style: italic; +} + +/* Cursor */ +.cursor { + display: inline-block; + width: 8px; + height: 1em; + background: var(--terminal-primary); + animation: cursorBlink 1s step-end infinite; + margin-left: 2px; + vertical-align: text-bottom; +} + +@keyframes cursorBlink { + 0%, 100% { opacity: 1; } + 50% { opacity: 0; } +} + +/* Scrollbar */ +.tui-body::-webkit-scrollbar { + width: 6px; +} + +.tui-body::-webkit-scrollbar-track { + background: transparent; +} + +.tui-body::-webkit-scrollbar-thumb { + background: var(--terminal-border); + border-radius: 3px; +} + +.tui-body::-webkit-scrollbar-thumb:hover { + background: var(--terminal-muted); +} diff --git a/src/lib/assets/css/tui-button.css b/src/lib/assets/css/tui-button.css new file mode 100644 index 0000000..071726f --- /dev/null +++ b/src/lib/assets/css/tui-button.css @@ -0,0 +1,60 @@ +.tui-button { + display: flex; + align-items: center; + gap: 0.5rem; + width: 100%; + padding: 0.5rem 0.75rem; + margin: 0.2rem 0; + background: transparent; + border: 1px solid transparent; + border-radius: 4px; + color: var(--btn-color); + font-family: inherit; + font-size: 0.9rem; + text-align: left; + cursor: pointer; + transition: all 0.15s ease; +} + +/* Inline button styles */ +.tui-button.inline { + width: auto; + display: inline-flex; + margin: 0; + padding: 0.35rem 0.6rem; + border: 1px solid color-mix(in srgb, var(--btn-color) 40%, transparent); +} + +.tui-button.inline .btn-indicator { + display: none; +} + +.tui-button:hover, +.tui-button.selected { + background: color-mix(in srgb, var(--btn-color) 15%, transparent); + border-color: var(--btn-color); +} + +.btn-indicator { + color: var(--btn-color); + font-size: 0.8rem; + width: 1rem; +} + +.btn-text { + flex: 1; +} + +:global(.btn-arrow) { + opacity: 0.5; +} + +.tui-button.selected :global(.btn-arrow) { + opacity: 1; +} + +:global(.inline-icon) { + display: inline-block; + vertical-align: middle; + margin: 0 0.15em; +} diff --git a/src/lib/assets/css/tui-card-grid.css b/src/lib/assets/css/tui-card-grid.css new file mode 100644 index 0000000..2e780e3 --- /dev/null +++ b/src/lib/assets/css/tui-card-grid.css @@ -0,0 +1,222 @@ +.tui-card-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); + gap: 1rem; + padding: 0.5rem 0; + width: 100%; +} + +.tui-card { + background: var(--terminal-bg); + border: 1px solid var(--terminal-border); + border-radius: 8px; + overflow: hidden; + display: flex; + flex-direction: column; + transition: all 0.2s ease; + font-family: 'JetBrains Mono', monospace; +} + +.tui-card:hover { + border-color: var(--terminal-primary); + transform: translateY(-2px); + box-shadow: 0 8px 24px rgba(0, 0, 0, 0.3); +} + +.tui-card.featured { + border-color: var(--terminal-accent); +} + +.card-image { + position: relative; + width: 100%; + height: 140px; + overflow: hidden; + background: var(--terminal-bg-light); +} + +.card-image img { + width: 100%; + height: 100%; + object-fit: cover; + transition: transform 0.3s ease; +} + +.tui-card:hover .card-image img { + transform: scale(1.05); +} + +.card-header-badge { + padding: 0.5rem 0.75rem 0; +} + +.featured-badge { + position: absolute; + top: 0.5rem; + right: 0.5rem; + background: rgba(0, 0, 0, 0.75); + color: var(--terminal-accent); + padding: 0.15rem 0.4rem; + border-radius: 3px; + font-size: 0.65rem; + font-weight: 600; + backdrop-filter: blur(4px); +} + +.card-header-badge .featured-badge { + position: static; + display: inline-block; +} + +.card-body { + padding: 0.75rem; + display: flex; + flex-direction: column; + gap: 0.4rem; + flex: 1; +} + +.card-title { + margin: 0; + font-size: 0.9rem; + font-weight: 600; + color: var(--terminal-text); + line-height: 1.3; +} + +.card-meta { + display: flex; + align-items: center; + gap: 0.3rem; + font-size: 0.7rem; + color: var(--terminal-primary); +} + +.meta-icon { + font-size: 0.75rem; +} + +.hackathon-name { + font-weight: 500; +} + +.year { + color: var(--terminal-muted); +} + +.card-location { + display: flex; + align-items: center; + gap: 0.3rem; + font-size: 0.65rem; + color: var(--terminal-muted); +} + +.awards { + display: flex; + flex-direction: column; + gap: 0.2rem; + margin: 0.25rem 0; +} + +.award { + display: flex; + align-items: center; + gap: 0.3rem; + font-size: 0.7rem; +} + +.award-icon { + font-size: 0.75rem; +} + +.award-text { + color: #a6e3a1; + font-weight: 500; +} + +.card-desc { + margin: 0; + font-size: 0.7rem; + color: var(--terminal-muted); + line-height: 1.4; + display: -webkit-box; + -webkit-line-clamp: 2; + line-clamp: 2; + -webkit-box-orient: vertical; + overflow: hidden; +} + +.tags { + display: flex; + flex-wrap: wrap; + gap: 0.25rem; + margin-top: auto; +} + +.tag { + background: color-mix(in srgb, var(--terminal-primary) 15%, transparent); + color: var(--terminal-primary); + padding: 0.1rem 0.35rem; + border-radius: 3px; + font-size: 0.55rem; + font-weight: 500; + text-transform: lowercase; +} + +.warning { + font-size: 0.6rem; + color: #f9e2af; + padding: 0.2rem 0.35rem; + background: rgba(249, 226, 175, 0.1); + border-radius: 3px; + width: fit-content; +} + +.card-actions { + display: flex; + flex-wrap: wrap; + gap: 0.35rem; + margin-top: 0.5rem; + padding-top: 0.5rem; + border-top: 1px solid var(--terminal-border); +} + +.action-btn { + display: inline-flex; + align-items: center; + gap: 0.25rem; + padding: 0.25rem 0.5rem; + background: transparent; + border: 1px solid var(--terminal-border); + border-radius: 4px; + color: var(--terminal-text); + font-size: 0.65rem; + font-weight: 500; + text-decoration: none; + transition: all 0.15s ease; + font-family: inherit; +} + +.action-btn:hover { + background: var(--terminal-primary); + border-color: var(--terminal-primary); + color: var(--terminal-bg); +} + +.action-btn.primary { + background: var(--terminal-primary); + border-color: var(--terminal-primary); + color: var(--terminal-bg); +} + +.action-btn.primary:hover { + background: var(--terminal-accent); + border-color: var(--terminal-accent); +} + +@media (max-width: 600px) { + .tui-card-grid { + grid-template-columns: 1fr; + } +} diff --git a/src/lib/assets/css/tui-card.css b/src/lib/assets/css/tui-card.css new file mode 100644 index 0000000..922d30c --- /dev/null +++ b/src/lib/assets/css/tui-card.css @@ -0,0 +1,58 @@ +.tui-card { + border: 1px solid var(--terminal-border); + border-radius: 6px; + background: color-mix(in srgb, var(--terminal-bg) 80%, var(--card-accent) 5%); + margin: 0.5rem 0; + overflow: hidden; + transition: border-color 0.2s ease; +} + +.tui-card:hover { + border-color: var(--card-accent); +} + +.card-header { + display: flex; + align-items: center; + gap: 0.5rem; + padding: 0.6rem 0.75rem; + border-bottom: 1px solid var(--terminal-border); + background: rgba(0, 0, 0, 0.1); +} + +:global(.card-icon) { + color: var(--card-accent); +} + +.card-title { + font-weight: 600; + color: var(--terminal-text); + font-size: 0.85rem; +} + +.card-body { + padding: 0.75rem; +} + +.card-image { + width: 100%; + max-height: 200px; + object-fit: cover; + border-radius: 4px; + margin-bottom: 0.5rem; +} + +.card-content { + color: var(--terminal-muted); + font-size: 0.85rem; + line-height: 1.5; + white-space: pre-wrap; +} + +.card-footer { + padding: 0.5rem 0.75rem; + border-top: 1px solid var(--terminal-border); + font-size: 0.75rem; + color: var(--terminal-muted); + background: rgba(0, 0, 0, 0.05); +} diff --git a/src/lib/assets/css/tui-checkbox.css b/src/lib/assets/css/tui-checkbox.css new file mode 100644 index 0000000..679c400 --- /dev/null +++ b/src/lib/assets/css/tui-checkbox.css @@ -0,0 +1,55 @@ +.tui-checkbox { + display: flex; + align-items: center; + gap: 0.5rem; + margin: 0.35rem 0; + padding: 0.35rem 0.5rem; + font-size: 0.9rem; + cursor: pointer; + border-radius: 4px; + transition: all 0.15s ease; + user-select: none; +} + +.tui-checkbox.inline { + display: inline-flex; + margin: 0 0.75rem 0 0; +} + +.tui-checkbox:hover:not(.disabled) { + background: color-mix(in srgb, var(--checkbox-color) 10%, transparent); +} + +.tui-checkbox:focus-visible { + outline: 1px solid var(--checkbox-color); + outline-offset: 2px; +} + +.tui-checkbox.disabled { + opacity: 0.5; + cursor: not-allowed; +} + +.checkbox-box { + font-family: inherit; + font-weight: bold; + color: var(--terminal-muted); + transition: color 0.15s ease; +} + +.tui-checkbox.checked .checkbox-box, +.checkbox-box.indeterminate { + color: var(--checkbox-color); +} + +:global(.checkbox-icon) { + color: var(--checkbox-color); +} + +.checkbox-label { + color: var(--terminal-text); +} + +.tui-checkbox.disabled .checkbox-label { + color: var(--terminal-muted); +} diff --git a/src/lib/assets/css/tui-footer.css b/src/lib/assets/css/tui-footer.css new file mode 100644 index 0000000..3e76510 --- /dev/null +++ b/src/lib/assets/css/tui-footer.css @@ -0,0 +1,59 @@ +.tui-statusbar { + display: flex; + align-items: center; + justify-content: space-between; + padding: 0.4rem 0.75rem; + font-size: 0.75rem; + background: var(--terminal-bg-light); + border-color: var(--terminal-border); +} + +.tui-statusbar.bottom { + border-top: 1px solid var(--terminal-border); +} + +.status-left, .status-right { + display: flex; + align-items: center; + gap: 0.5rem; + color: var(--terminal-muted); +} + +.status-center { + color: var(--terminal-primary); + font-weight: 600; +} + +.skip-btn { + display: flex; + align-items: center; + gap: 0.3rem; + padding: 0.15rem 0.5rem; + background: var(--terminal-border); + border: 1px solid transparent; + border-radius: 3px; + color: var(--terminal-muted); + font-family: inherit; + font-size: 0.7rem; + cursor: pointer; + transition: all 0.15s ease; +} + +.skip-btn:hover { + background: var(--terminal-primary); + color: var(--terminal-bg); + border-color: var(--terminal-primary); +} + +.typing-indicator { + animation: pulse 1s ease-in-out infinite; +} + +@keyframes pulse { + 0%, 100% { opacity: 1; } + 50% { opacity: 0.5; } +} + +.line-count { + color: var(--terminal-muted); +} diff --git a/src/lib/assets/css/tui-header.css b/src/lib/assets/css/tui-header.css new file mode 100644 index 0000000..973a074 --- /dev/null +++ b/src/lib/assets/css/tui-header.css @@ -0,0 +1,38 @@ +.tui-statusbar { + display: flex; + align-items: center; + justify-content: space-between; + padding: 0.4rem 0.75rem; + font-size: 0.75rem; + background: var(--terminal-bg-light); + border-color: var(--terminal-border); +} + +.tui-statusbar.top { + border-bottom: 1px solid var(--terminal-border); +} + +.status-left, .status-right { + display: flex; + align-items: center; + gap: 0.5rem; + color: var(--terminal-muted); +} + +.status-center { + color: var(--terminal-primary); + font-weight: 600; +} + +.hint { + padding: 0.1rem 0.4rem; + background: var(--terminal-border); + border-radius: 3px; + font-size: 0.7rem; +} + +@media (max-width: 768px) { + .hint { + display: none; + } +} diff --git a/src/lib/assets/css/tui-input.css b/src/lib/assets/css/tui-input.css new file mode 100644 index 0000000..24dac23 --- /dev/null +++ b/src/lib/assets/css/tui-input.css @@ -0,0 +1,96 @@ +.tui-input { + margin: 0.5rem 0; + font-size: 0.9rem; +} + +.tui-input.inline { + display: inline-flex; + flex-direction: column; + margin: 0 0.5rem 0 0; +} + +.input-label { + display: flex; + align-items: center; + gap: 0.35rem; + margin-bottom: 0.35rem; + color: var(--terminal-muted); + font-size: 0.85rem; +} + +:global(.label-icon) { + color: var(--input-color); +} + +.input-wrapper { + display: flex; + align-items: center; + gap: 0.35rem; + padding: 0.5rem 0.75rem; + background: color-mix(in srgb, var(--terminal-bg) 80%, black); + border: 1px solid var(--terminal-muted); + border-radius: 4px; + transition: all 0.15s ease; +} + +.input-wrapper:focus-within { + border-color: var(--input-color); + box-shadow: 0 0 0 1px color-mix(in srgb, var(--input-color) 30%, transparent); +} + +.tui-input.error .input-wrapper { + border-color: #f38ba8; +} + +.tui-input.disabled .input-wrapper { + opacity: 0.5; + cursor: not-allowed; +} + +.input-prompt { + color: var(--input-color); + font-weight: bold; + user-select: none; +} + +.input-affix { + color: var(--terminal-muted); + font-size: 0.85rem; +} + +.input-affix.prefix { + margin-right: 0.25rem; +} + +.input-affix.suffix { + margin-left: 0.25rem; +} + +input { + flex: 1; + min-width: 0; + background: transparent; + border: none; + color: var(--terminal-text); + font-family: inherit; + font-size: inherit; + outline: none; +} + +input::placeholder { + color: var(--terminal-muted); + opacity: 0.6; +} + +input:disabled { + cursor: not-allowed; +} + +.input-error { + display: flex; + align-items: center; + gap: 0.35rem; + margin-top: 0.35rem; + color: #f38ba8; + font-size: 0.8rem; +} diff --git a/src/lib/assets/css/tui-link.css b/src/lib/assets/css/tui-link.css new file mode 100644 index 0000000..5da3408 --- /dev/null +++ b/src/lib/assets/css/tui-link.css @@ -0,0 +1,45 @@ +.tui-link { + display: inline-flex; + align-items: center; + gap: 0.25rem; +} + +:global(.link-icon) { + color: var(--link-color); + opacity: 0.8; +} + +.link-text { + background: none; + border: none; + padding: 0; + margin: 0; + font-family: inherit; + font-size: inherit; + color: var(--link-color); + text-decoration: underline; + text-decoration-style: dotted; + text-underline-offset: 2px; + cursor: pointer; + transition: all 0.15s ease; +} + +.link-text:hover { + text-decoration-style: solid; + filter: brightness(1.2); +} + +:global(.link-external) { + color: var(--link-color); + opacity: 0.5; +} + +.tui-link:hover :global(.link-external) { + opacity: 0.8; +} + +:global(.inline-icon) { + display: inline-block; + vertical-align: middle; + margin: 0 0.15em; +} diff --git a/src/lib/assets/css/tui-progress.css b/src/lib/assets/css/tui-progress.css new file mode 100644 index 0000000..885f340 --- /dev/null +++ b/src/lib/assets/css/tui-progress.css @@ -0,0 +1,104 @@ +.tui-progress { + margin: 0.5rem 0; + font-size: 0.85rem; +} + +/* Inline progress styles */ +.tui-progress.inline { + display: inline-flex; + align-items: center; + gap: 0.5rem; + margin: 0; + min-width: 120px; +} + +.tui-progress.inline .progress-label { + margin-bottom: 0; + white-space: nowrap; +} + +.tui-progress.inline .progress-bar { + flex: 1; + min-width: 80px; + height: 0.8rem; +} + +.tui-progress.inline .progress-value { + margin-top: 0; + white-space: nowrap; +} + +.progress-label { + color: var(--terminal-text); + margin-bottom: 0.25rem; +} + +.progress-bar { + position: relative; + height: 1.2rem; + background: var(--terminal-border); + border-radius: 3px; + overflow: hidden; +} + +.progress-fill { + position: absolute; + top: 0; + left: 0; + height: 100%; + background: linear-gradient(90deg, var(--progress-color), color-mix(in srgb, var(--progress-color) 80%, white 20%)); + border-radius: 3px; + transition: width 0.3s ease; +} + +.progress-glow { + position: absolute; + right: 0; + top: 0; + height: 100%; + width: 20px; + background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.3)); + animation: shimmer 1.5s infinite; +} + +@keyframes shimmer { + 0% { opacity: 0; } + 50% { opacity: 1; } + 100% { opacity: 0; } +} + +.progress-blocks { + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + display: flex; + gap: 2px; + padding: 3px; +} + +.block { + flex: 1; + background: rgba(0, 0, 0, 0.2); + border-radius: 1px; + transition: background 0.2s; +} + +.block.filled { + background: transparent; +} + +.progress-value { + text-align: right; + color: var(--progress-color); + font-size: 0.75rem; + margin-top: 0.25rem; + font-weight: 600; +} + +:global(.inline-icon) { + display: inline-block; + vertical-align: middle; + margin: 0 0.15em; +} diff --git a/src/lib/assets/css/tui-radio.css b/src/lib/assets/css/tui-radio.css new file mode 100644 index 0000000..3a879ab --- /dev/null +++ b/src/lib/assets/css/tui-radio.css @@ -0,0 +1,87 @@ +.tui-radio-group { + margin: 0.5rem 0; + font-size: 0.9rem; +} + +.tui-radio-group.inline { + display: inline-flex; + flex-direction: column; + margin: 0 0.75rem 0 0; +} + +.tui-radio-group.disabled { + opacity: 0.5; +} + +.radio-label { + display: flex; + align-items: center; + gap: 0.35rem; + margin-bottom: 0.5rem; + color: var(--terminal-muted); + font-size: 0.85rem; +} + +:global(.label-icon) { + color: var(--radio-color); +} + +.radio-options { + display: flex; + flex-direction: column; + gap: 0.25rem; +} + +.radio-options.horizontal { + flex-direction: row; + flex-wrap: wrap; + gap: 0.75rem; +} + +.radio-option { + display: flex; + align-items: center; + gap: 0.5rem; + padding: 0.35rem 0.5rem; + cursor: pointer; + border-radius: 4px; + transition: all 0.15s ease; + user-select: none; +} + +.radio-option:hover:not(.option-disabled) { + background: color-mix(in srgb, var(--radio-color) 10%, transparent); +} + +.radio-option:focus-visible { + outline: 1px solid var(--radio-color); + outline-offset: 2px; +} + +.radio-option.option-disabled { + opacity: 0.5; + cursor: not-allowed; +} + +.radio-symbol { + font-family: inherit; + font-weight: bold; + color: var(--terminal-muted); + transition: color 0.15s ease; +} + +.radio-option.selected .radio-symbol { + color: var(--radio-color); +} + +:global(.option-icon) { + color: var(--radio-color); +} + +.option-label { + color: var(--terminal-text); +} + +.radio-option.option-disabled .option-label { + color: var(--terminal-muted); +} diff --git a/src/lib/assets/css/tui-select.css b/src/lib/assets/css/tui-select.css new file mode 100644 index 0000000..9a2d038 --- /dev/null +++ b/src/lib/assets/css/tui-select.css @@ -0,0 +1,162 @@ +.tui-select { + position: relative; + margin: 0.5rem 0; + font-size: 0.9rem; +} + +.tui-select.inline { + display: inline-flex; + flex-direction: column; + margin: 0 0.5rem 0 0; +} + +.select-label { + display: flex; + align-items: center; + gap: 0.35rem; + margin-bottom: 0.35rem; + color: var(--terminal-muted); + font-size: 0.85rem; +} + +:global(.label-icon) { + color: var(--select-color); +} + +.select-trigger { + display: flex; + align-items: center; + gap: 0.35rem; + padding: 0.5rem 0.75rem; + background: color-mix(in srgb, var(--terminal-bg) 80%, black); + border: 1px solid var(--terminal-muted); + border-radius: 4px; + cursor: pointer; + transition: all 0.15s ease; +} + +.tui-select.open .select-trigger, +.select-trigger:focus-visible { + border-color: var(--select-color); + box-shadow: 0 0 0 1px color-mix(in srgb, var(--select-color) 30%, transparent); +} + +.tui-select.error .select-trigger { + border-color: #f38ba8; +} + +.tui-select.disabled .select-trigger { + opacity: 0.5; + cursor: not-allowed; +} + +.select-prompt { + color: var(--select-color); + font-weight: bold; +} + +.select-value { + flex: 1; + color: var(--terminal-text); +} + +.select-value.placeholder { + color: var(--terminal-muted); + opacity: 0.6; +} + +.select-arrow { + color: var(--terminal-muted); + font-size: 0.7rem; +} + +.select-dropdown { + position: absolute; + top: 100%; + left: 0; + right: 0; + margin-top: 0.25rem; + background: var(--terminal-bg); + border: 1px solid var(--select-color); + border-radius: 4px; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); + z-index: 100; + overflow: hidden; +} + +.select-search { + display: flex; + align-items: center; + gap: 0.35rem; + padding: 0.5rem 0.75rem; + border-bottom: 1px solid var(--terminal-muted); +} + +.select-search input { + flex: 1; + background: transparent; + border: none; + color: var(--terminal-text); + font-family: inherit; + font-size: inherit; + outline: none; +} + +.select-search input::placeholder { + color: var(--terminal-muted); +} + +.select-options { + max-height: 200px; + overflow-y: auto; +} + +.select-option { + display: flex; + align-items: center; + gap: 0.5rem; + padding: 0.5rem 0.75rem; + cursor: pointer; + transition: background 0.1s ease; +} + +.select-option.highlighted { + background: color-mix(in srgb, var(--select-color) 15%, transparent); +} + +.select-option.selected { + color: var(--select-color); +} + +.select-option.option-disabled { + opacity: 0.5; + cursor: not-allowed; +} + +:global(.option-icon) { + color: var(--select-color); +} + +.option-label { + flex: 1; +} + +:global(.check-icon) { + color: var(--select-color); +} + +.select-empty { + padding: 0.75rem; + text-align: center; + color: var(--terminal-muted); + font-style: italic; +} + +.select-error { + display: flex; + align-items: center; + gap: 0.35rem; + margin-top: 0.35rem; + color: #f38ba8; + font-size: 0.8rem; +} diff --git a/src/lib/assets/css/tui-table.css b/src/lib/assets/css/tui-table.css new file mode 100644 index 0000000..7d61c12 --- /dev/null +++ b/src/lib/assets/css/tui-table.css @@ -0,0 +1,49 @@ +.tui-table-wrapper { + margin: 0.5rem 0; + border: 1px solid var(--terminal-border); + border-radius: 6px; + overflow: hidden; +} + +.table-title { + padding: 0.5rem 0.75rem; + font-weight: 600; + font-size: 0.85rem; + color: var(--terminal-text); + background: rgba(0, 0, 0, 0.1); + border-bottom: 1px solid var(--terminal-border); +} + +.tui-table { + width: 100%; + border-collapse: collapse; + font-size: 0.8rem; +} + +th { + padding: 0.5rem 0.75rem; + text-align: left; + font-weight: 600; + color: var(--table-accent); + background: rgba(0, 0, 0, 0.15); + border-bottom: 1px solid var(--terminal-border); + white-space: nowrap; +} + +td { + padding: 0.4rem 0.75rem; + color: var(--terminal-text); + border-bottom: 1px solid var(--terminal-border); +} + +tr:last-child td { + border-bottom: none; +} + +tr.alt { + background: rgba(0, 0, 0, 0.03); +} + +tr:hover { + background: color-mix(in srgb, var(--table-accent) 5%, transparent); +} diff --git a/src/lib/assets/css/tui-textarea.css b/src/lib/assets/css/tui-textarea.css new file mode 100644 index 0000000..1f4a673 --- /dev/null +++ b/src/lib/assets/css/tui-textarea.css @@ -0,0 +1,113 @@ +.tui-textarea { + margin: 0.5rem 0; + font-size: 0.9rem; +} + +.tui-textarea.inline { + display: inline-flex; + flex-direction: column; + margin: 0 0.5rem 0 0; +} + +.textarea-label { + display: flex; + align-items: center; + gap: 0.35rem; + margin-bottom: 0.35rem; + color: var(--terminal-muted); + font-size: 0.85rem; +} + +:global(.label-icon) { + color: var(--input-color); +} + +.textarea-wrapper { + display: flex; + background: color-mix(in srgb, var(--terminal-bg) 80%, black); + border: 1px solid var(--terminal-muted); + border-radius: 4px; + transition: all 0.15s ease; + overflow: hidden; +} + +.tui-textarea.focused .textarea-wrapper { + border-color: var(--input-color); + box-shadow: 0 0 0 1px color-mix(in srgb, var(--input-color) 30%, transparent); +} + +.tui-textarea.error .textarea-wrapper { + border-color: #f38ba8; +} + +.tui-textarea.disabled .textarea-wrapper { + opacity: 0.5; + cursor: not-allowed; +} + +.line-numbers { + display: flex; + flex-direction: column; + padding: 0.5rem 0; + background: color-mix(in srgb, var(--terminal-bg) 60%, black); + border-right: 1px solid var(--terminal-muted); + user-select: none; +} + +.line-num { + padding: 0 0.5rem; + color: var(--terminal-muted); + font-size: 0.8rem; + line-height: 1.5; + text-align: right; + min-width: 2rem; +} + +textarea { + flex: 1; + min-width: 0; + padding: 0.5rem 0.75rem; + background: transparent; + border: none; + color: var(--terminal-text); + font-family: inherit; + font-size: inherit; + line-height: 1.5; + resize: vertical; + outline: none; +} + +textarea::placeholder { + color: var(--terminal-muted); + opacity: 0.6; +} + +textarea:disabled { + cursor: not-allowed; + resize: none; +} + +.textarea-footer { + display: flex; + justify-content: space-between; + align-items: center; + margin-top: 0.35rem; +} + +.textarea-error { + display: flex; + align-items: center; + gap: 0.35rem; + color: #f38ba8; + font-size: 0.8rem; +} + +.char-count { + margin-left: auto; + color: var(--terminal-muted); + font-size: 0.75rem; +} + +.char-count.warning { + color: #f9e2af; +} diff --git a/src/lib/assets/css/tui-toggle.css b/src/lib/assets/css/tui-toggle.css new file mode 100644 index 0000000..1e04a4c --- /dev/null +++ b/src/lib/assets/css/tui-toggle.css @@ -0,0 +1,94 @@ +.tui-toggle { + display: flex; + align-items: center; + gap: 0.75rem; + margin: 0.35rem 0; + padding: 0.35rem 0.5rem; + font-size: 0.9rem; + cursor: pointer; + border-radius: 4px; + transition: all 0.15s ease; + user-select: none; +} + +.tui-toggle.inline { + display: inline-flex; + margin: 0 0.75rem 0 0; +} + +.tui-toggle:hover:not(.disabled) { + background: color-mix(in srgb, var(--toggle-color) 10%, transparent); +} + +.tui-toggle:focus-visible { + outline: 1px solid var(--toggle-color); + outline-offset: 2px; +} + +.tui-toggle.disabled { + opacity: 0.5; + cursor: not-allowed; +} + +:global(.toggle-icon) { + color: var(--toggle-color); +} + +.toggle-label { + color: var(--terminal-text); +} + +.toggle-track { + display: flex; + align-items: center; + gap: 0.25rem; + padding: 0.25rem 0.5rem; + background: color-mix(in srgb, var(--terminal-bg) 80%, black); + border: 1px solid var(--terminal-muted); + border-radius: 4px; + font-size: 0.75rem; + transition: all 0.15s ease; +} + +.tui-toggle.checked .toggle-track { + border-color: var(--toggle-color); + background: color-mix(in srgb, var(--toggle-color) 15%, transparent); +} + +.toggle-off-label, +.toggle-on-label { + color: var(--terminal-muted); + font-weight: bold; + text-transform: uppercase; + min-width: 2rem; + text-align: center; + transition: color 0.15s ease; +} + +.tui-toggle:not(.checked) .toggle-off-label { + color: var(--terminal-text); +} + +.tui-toggle.checked .toggle-on-label { + color: var(--toggle-color); +} + +.toggle-switch { + display: flex; + align-items: center; + justify-content: center; + width: 2.5rem; + position: relative; +} + +.toggle-knob { + font-size: 1rem; + color: var(--terminal-muted); + transition: all 0.2s ease; + transform: translateX(-0.5rem); +} + +.tui-toggle.checked .toggle-knob { + color: var(--toggle-color); + transform: translateX(0.5rem); +} diff --git a/src/lib/assets/css/tui-tooltip.css b/src/lib/assets/css/tui-tooltip.css new file mode 100644 index 0000000..d50cd20 --- /dev/null +++ b/src/lib/assets/css/tui-tooltip.css @@ -0,0 +1,78 @@ +.tui-tooltip-trigger { + position: relative; + display: inline-flex; + align-items: center; + gap: 0.25rem; + cursor: help; +} + +.trigger-text { + border-bottom: 1px dotted var(--tooltip-color); +} + +.tooltip-indicator { + font-size: 0.7rem; + color: var(--tooltip-color); + opacity: 0.7; +} + +.tooltip { + position: fixed; + z-index: 99999; + padding: 0.5rem 0.75rem; + background: var(--terminal-bg); + border: 1px solid var(--tooltip-color); + border-radius: 4px; + font-size: 0.8rem; + color: var(--terminal-text); + white-space: nowrap; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.4); + animation: tooltipFadeIn 0.15s ease-out; + pointer-events: none; +} + +@keyframes tooltipFadeIn { + from { + opacity: 0; + transform: scale(0.95); + } + to { + opacity: 1; + transform: scale(1); + } +} + +/* Arrow styles - simplified for fixed positioning */ +.tooltip-arrow { + position: absolute; + width: 8px; + height: 8px; + background: var(--terminal-bg); + border: 1px solid var(--tooltip-color); + border-top: none; + border-left: none; +} + +.tooltip.top .tooltip-arrow { + bottom: -5px; + left: 50%; + transform: translateX(-50%) rotate(45deg); +} + +.tooltip.bottom .tooltip-arrow { + top: -5px; + left: 50%; + transform: translateX(-50%) rotate(-135deg); +} + +.tooltip.left .tooltip-arrow { + right: -5px; + top: 50%; + transform: translateY(-50%) rotate(-45deg); +} + +.tooltip.right .tooltip-arrow { + left: -5px; + top: 50%; + transform: translateY(-50%) rotate(135deg); +} diff --git a/src/lib/components/Background3D.svelte b/src/lib/components/Background3D.svelte index 0d3e9a2..30dce5c 100644 --- a/src/lib/components/Background3D.svelte +++ b/src/lib/components/Background3D.svelte @@ -4,6 +4,7 @@ import ParticleField from './ParticleField.svelte'; import { themeColors, mode } from '$lib/stores/theme'; import * as THREE from 'three'; + import '$lib/assets/css/background-3d.css'; let bgColor = $derived($mode === 'dark' ? $themeColors.background : $themeColors.background); @@ -35,19 +36,3 @@ - - diff --git a/src/lib/components/ModelViewer.svelte b/src/lib/components/ModelViewer.svelte index 63e767d..6afe89b 100644 --- a/src/lib/components/ModelViewer.svelte +++ b/src/lib/components/ModelViewer.svelte @@ -1,6 +1,7 @@ + + + + diff --git a/src/lib/components/tui/TuiFooter.svelte b/src/lib/components/tui/TuiFooter.svelte index e7edae5..eafaf25 100644 --- a/src/lib/components/tui/TuiFooter.svelte +++ b/src/lib/components/tui/TuiFooter.svelte @@ -1,5 +1,6 @@
- + {user.username}@{user.hostname} {title} @@ -22,43 +31,4 @@
- diff --git a/src/lib/components/tui/TuiInput.svelte b/src/lib/components/tui/TuiInput.svelte new file mode 100644 index 0000000..5df0324 --- /dev/null +++ b/src/lib/components/tui/TuiInput.svelte @@ -0,0 +1,197 @@ + + +
+ {#if line.content} + + {/if} + +
+ + {#if prefix} + {prefix} + {/if} + + {#if suffix} + {suffix} + {/if} +
+ + {#if hasError && errorMessage} +
+ + {errorMessage} +
+ {/if} +
+ + diff --git a/src/lib/components/tui/TuiLink.svelte b/src/lib/components/tui/TuiLink.svelte index 69549d9..3632ad1 100644 --- a/src/lib/components/tui/TuiLink.svelte +++ b/src/lib/components/tui/TuiLink.svelte @@ -2,6 +2,7 @@ import Icon from '@iconify/svelte'; import { getButtonStyle, parseColorText, getSegmentStyle } from './utils'; import type { TerminalLine } from './types'; + import '$lib/assets/css/tui-link.css'; export let line: TerminalLine; export let onClick: () => void; diff --git a/src/lib/components/tui/TuiProgress.svelte b/src/lib/components/tui/TuiProgress.svelte index c66854c..4ad70f2 100644 --- a/src/lib/components/tui/TuiProgress.svelte +++ b/src/lib/components/tui/TuiProgress.svelte @@ -2,6 +2,7 @@ import Icon from '@iconify/svelte'; import { getButtonStyle, parseColorText, getSegmentStyle } from './utils'; import type { TerminalLine } from './types'; + import '$lib/assets/css/tui-progress.css'; export let line: TerminalLine; export let inline: boolean = false; diff --git a/src/lib/components/tui/TuiRadio.svelte b/src/lib/components/tui/TuiRadio.svelte new file mode 100644 index 0000000..6d72afa --- /dev/null +++ b/src/lib/components/tui/TuiRadio.svelte @@ -0,0 +1,188 @@ + + +
+ {#if line.content} +
+ {#if line.icon} + + {/if} + {#each labelSegments as segment} + {#if segment.icon} + + {:else if getSegmentStyle(segment)} + {segment.text} + {:else} + {segment.text} + {/if} + {/each} +
+ {/if} + +
+ {#each options as option} + {@const isSelected = value === option.value} + {@const optionSegments = parseColorText(option.label)} + + {/each} +
+
+ + diff --git a/src/lib/components/tui/TuiSelect.svelte b/src/lib/components/tui/TuiSelect.svelte new file mode 100644 index 0000000..150c8ab --- /dev/null +++ b/src/lib/components/tui/TuiSelect.svelte @@ -0,0 +1,393 @@ + + + + +
+ {#if line.content} + + {/if} + +
+ + + {displayValue || placeholder} + + {isOpen ? '▲' : '▼'} +
+ + {#if isOpen} +
+ {#if searchable} + + {/if} +
+ {#each filteredOptions as option, i} + {@const optionSegments = parseColorText(option.label)} +
!option.disabled && handleSelect(option.value)} + on:keydown={(e) => e.key === 'Enter' && !option.disabled && handleSelect(option.value)} + on:mouseenter={() => highlightedIndex = i} + > + {#if option.icon} + + {/if} + + {#each optionSegments as segment} + {#if segment.icon} + + {:else if getSegmentStyle(segment)} + {segment.text} + {:else} + {segment.text} + {/if} + {/each} + + {#if value === option.value} + + {/if} +
+ {:else} +
No options found
+ {/each} +
+
+ {/if} + + {#if hasError && errorMessage} +
+ + {errorMessage} +
+ {/if} +
+ + diff --git a/src/lib/components/tui/TuiTable.svelte b/src/lib/components/tui/TuiTable.svelte index f8bde93..9327ab9 100644 --- a/src/lib/components/tui/TuiTable.svelte +++ b/src/lib/components/tui/TuiTable.svelte @@ -1,6 +1,7 @@ + +
+ {#if line.content} + + {/if} + +
+
+ {#each Array(Math.max(rows, value.split('\n').length)) as _, i} + {i + 1} + {/each} +
+ +
+ + +
+ + diff --git a/src/lib/components/tui/TuiToggle.svelte b/src/lib/components/tui/TuiToggle.svelte new file mode 100644 index 0000000..a15b711 --- /dev/null +++ b/src/lib/components/tui/TuiToggle.svelte @@ -0,0 +1,175 @@ + + +
+ {#if line.icon} + + {/if} + + {#if line.content} + + {#each labelSegments as segment} + {#if segment.icon} + + {:else if getSegmentStyle(segment)} + {segment.text} + {:else} + {segment.text} + {/if} + {/each} + + {/if} + +
+ {#if showLabels} + {offLabel} + {/if} + + {checked ? '●' : '○'} + + {#if showLabels} + {onLabel} + {/if} +
+
+ + diff --git a/src/lib/components/tui/TuiTooltip.svelte b/src/lib/components/tui/TuiTooltip.svelte index e784ccc..4bc5d4a 100644 --- a/src/lib/components/tui/TuiTooltip.svelte +++ b/src/lib/components/tui/TuiTooltip.svelte @@ -1,6 +1,7 @@ @@ -143,12 +150,7 @@
- +