Files
Termix/src/ui/index.css
T
a89ccc6b07 v26.10.3 (#1692)
* feat: shell controller, TabStage, layout and theme plugins with repair view

* feat: move the classic layout into its own bundled plugin

* feat: move color themes into a bundled themes plugin

* fix: plugin tests and type checks work with a linked sdk

* fix: home tab, layout switch and missing layout fallbacks for looks

* chore: add classic layout and themes to the readme

* chore: drop example plugin links from the plugin guide

* feat: plugin filters by type, paged browse, hide plugins needing newer termix

* feat: move desktop, status, import, audit and branding features into plugins

* feat: host lock api so a plugin can load hosts from a config file (#1581)

* chore: add hosts config to the plugin table

* feat: login policy hook for plugins and optional https redirect (#1584)

* chore: add login security to the plugin table

* feat: api key socket tickets so a master can open remote sessions (#1654)

* feat: sync moves to a plugin, desktop gets multiple accounts (#1580, #1654)

* chore: add sync to the plugin table

* feat: move the sync screens and wizard out of core into the plugin (#1580)

* fix: add a neutral linked desktop hook for plugins (#1580)

* fix: show the linked account's name and admin flag in the app (#1580)

* feat: ssh transport providers, core ssh relay and plugin folders api

* feat: support FIDO2 security keys through ssh agent

* feat: add companion panels beside host tabs

* feat: mTLS client certificates for desktop server links

* feat: native web view for plugins in the desktop app

* fix: detect the desktop link window by Electron's default user agent

Fixes #1675

* fix: changelog for desktop link fix

* fix: keep text primary keys in the plugin data export

* fix: ship sharp binaries for Windows arm64 builds

* feat: client cert on the sync link types (sdk 1.0.18)

* chore: update translations

* feat: themed title bar for the desktop app (#1690)

* chore: changelog for the themed title bar

* fix: draw the title bar in the sidebar color

* feat: per host layout for the terminal panel bar

* chore: sdk 1.0.19 for the panel bar layout

* chore: bundle all 43 plugins for 26.10.3

* chore: update translations

---------

Co-authored-by: SirHephaistos <60616553+SirHephaistos@users.noreply.github.com>
Co-authored-by: Giovanny Aranda <garanda21@gmail.com>
2026-10-20 16:03:29 -05:00

583 lines
13 KiB
CSS

/* Plugin utilities (termix-plugin build) rank above base, below core's own. */
@layer properties, termix-plugin-properties, theme, base, components,
termix-plugin-utilities, utilities;
@import "tailwindcss";
@import "tw-animate-css";
@import "./theme.css";
@import "@fontsource-variable/jetbrains-mono";
@import "@fontsource/jetbrains-mono/400.css";
@import "@fontsource/jetbrains-mono/400-italic.css";
@import "@fontsource/jetbrains-mono/700.css";
@import "@fontsource/jetbrains-mono/700-italic.css";
@import "@fontsource/fira-code/400.css";
@import "@fontsource/fira-code/700.css";
@import "@fontsource/source-code-pro/400.css";
@import "@fontsource/source-code-pro/400-italic.css";
@import "@fontsource/source-code-pro/700.css";
@import "@fontsource/source-code-pro/700-italic.css";
@font-face {
font-family: "Caskaydia Cove Nerd Font Mono";
src: url("/fonts/CaskaydiaCoveNerdFontMono-Regular.ttf") format("truetype");
font-weight: 400;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: "Caskaydia Cove Nerd Font Mono";
src: url("/fonts/CaskaydiaCoveNerdFontMono-Bold.ttf") format("truetype");
font-weight: 700;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: "Caskaydia Cove Nerd Font Mono";
src: url("/fonts/CaskaydiaCoveNerdFontMono-Italic.ttf") format("truetype");
font-weight: 400;
font-style: italic;
font-display: swap;
}
@font-face {
font-family: "Caskaydia Cove Nerd Font Mono";
src: url("/fonts/CaskaydiaCoveNerdFontMono-BoldItalic.ttf") format("truetype");
font-weight: 700;
font-style: italic;
font-display: swap;
}
:root {
--accent-brand: #f59145;
--motion-duration-micro: 110ms;
--motion-duration-panel: 220ms;
--motion-duration-context: 340ms;
--motion-ease-enter: cubic-bezier(0.16, 1, 0.3, 1);
--motion-ease-exit: cubic-bezier(0.4, 0, 1, 1);
--background: oklch(1 0 0);
--foreground: oklch(0.1802 0.0043 128.73);
--card: oklch(1 0 0);
--card-foreground: oklch(0.1802 0.0043 128.73);
--popover: oklch(1 0 0);
--popover-foreground: oklch(0.1802 0.0043 128.73);
--primary: oklch(0.2115 0.0042 128.68);
--primary-foreground: oklch(0.985 0 0);
--secondary: oklch(0.97 0 0);
--secondary-foreground: oklch(0.2115 0.0042 128.68);
--muted: oklch(0.97 0 0);
--muted-foreground: oklch(0.44 0 0);
--accent: oklch(0.97 0 0);
--accent-foreground: oklch(0.2115 0.0042 128.68);
--destructive: oklch(0.577 0.245 27.325);
--border: oklch(0.922 0 0);
--input: oklch(0.922 0 0);
--ring: oklch(0.708 0 0);
--chart-1: oklch(0.87 0 0);
--chart-2: oklch(0.556 0 0);
--chart-3: oklch(0.439 0 0);
--chart-4: oklch(0.371 0 0);
--chart-5: oklch(0.269 0 0);
--radius: 0.625rem;
--surface: oklch(0.97 0 0);
--sidebar: oklch(0.985 0 0);
--sidebar-foreground: oklch(0.1802 0.0043 128.73);
--sidebar-primary: oklch(0.2115 0.0042 128.68);
--sidebar-primary-foreground: oklch(0.985 0 0);
--sidebar-accent: oklch(0.97 0 0);
--sidebar-accent-foreground: oklch(0.2115 0.0042 128.68);
--sidebar-border: oklch(0.922 0 0);
--sidebar-ring: oklch(0.708 0 0);
--warning: oklch(0.795 0.162 86.05);
--terminal-bg: #ffffff;
--surface-dim: oklch(0.945 0 0);
--bg-base: var(--background);
--border-base: var(--border);
--foreground-secondary: var(--muted-foreground);
--canvas: var(--background);
--overlay: oklch(0 0 0 / 50%);
}
.dark {
--background: oklch(0.155 0.004 128.73);
--foreground: oklch(0.985 0 0);
--card: oklch(0.21 0.004 128.68);
--card-foreground: oklch(0.985 0 0);
--popover: oklch(0.21 0.004 128.68);
--popover-foreground: oklch(0.985 0 0);
--primary: oklch(0.922 0 0);
--primary-foreground: oklch(0.2115 0.0042 128.68);
--secondary: oklch(0.269 0 0);
--secondary-foreground: oklch(0.985 0 0);
--surface: oklch(0.185 0.004 128.73);
--surface-dim: oklch(0.165 0.003 128.73);
--muted: oklch(0.255 0 0);
--muted-foreground: oklch(0.72 0 0);
--accent: oklch(0.255 0 0);
--accent-foreground: oklch(0.985 0 0);
--destructive: oklch(0.704 0.191 22.216);
--border: oklch(1 0 0 / 12%);
--input: oklch(1 0 0 / 15%);
--ring: oklch(0.556 0 0);
--chart-1: oklch(0.87 0 0);
--chart-2: oklch(0.556 0 0);
--chart-3: oklch(0.439 0 0);
--chart-4: oklch(0.371 0 0);
--chart-5: oklch(0.269 0 0);
--sidebar: oklch(0.195 0.004 128.68);
--sidebar-foreground: oklch(0.985 0 0);
--sidebar-primary: oklch(0.488 0.243 264.376);
--sidebar-primary-foreground: oklch(0.985 0 0);
--sidebar-accent: oklch(0.255 0 0);
--sidebar-accent-foreground: oklch(0.985 0 0);
--sidebar-border: oklch(1 0 0 / 12%);
--sidebar-ring: oklch(0.556 0 0);
--warning: oklch(0.852 0.156 91.94);
--terminal-bg: #0c0d0b;
}
@layer base {
* {
/* Tailwind v4 defaults border-color to currentColor, so any bare "border"
utility picked up the text color and rendered near-white. */
@apply border-border outline-none;
scrollbar-width: thin;
scrollbar-color: oklch(1 0 0 / 20%) transparent;
}
*::-webkit-scrollbar {
width: 6px;
height: 6px;
}
*::-webkit-scrollbar-track {
background: transparent;
}
*::-webkit-scrollbar-thumb {
background: oklch(1 0 0 / 20%);
border-radius: 3px;
}
*::-webkit-scrollbar-thumb:hover {
background: oklch(1 0 0 / 35%);
}
*::-webkit-scrollbar-corner {
background: transparent;
}
html,
body,
#root {
height: 100dvh;
@apply overflow-hidden;
}
body {
@apply bg-background text-foreground font-mono;
}
html {
@apply font-mono;
}
}
/* Icons sized with the size prop carry width/height attributes in px, which
the interface size setting cannot reach. These restate them in CSS, where
they become rem like every other length. A size class on the icon wins. */
@layer base {
svg.lucide[width="8"] {
width: 8px;
height: 8px;
}
svg.lucide[width="9"] {
width: 9px;
height: 9px;
}
svg.lucide[width="10"] {
width: 10px;
height: 10px;
}
svg.lucide[width="11"] {
width: 11px;
height: 11px;
}
svg.lucide[width="12"] {
width: 12px;
height: 12px;
}
svg.lucide[width="13"] {
width: 13px;
height: 13px;
}
svg.lucide[width="14"] {
width: 14px;
height: 14px;
}
svg.lucide[width="15"] {
width: 15px;
height: 15px;
}
svg.lucide[width="16"] {
width: 16px;
height: 16px;
}
svg.lucide[width="17"] {
width: 17px;
height: 17px;
}
svg.lucide[width="18"] {
width: 18px;
height: 18px;
}
svg.lucide[width="19"] {
width: 19px;
height: 19px;
}
svg.lucide[width="20"] {
width: 20px;
height: 20px;
}
svg.lucide[width="21"] {
width: 21px;
height: 21px;
}
svg.lucide[width="22"] {
width: 22px;
height: 22px;
}
svg.lucide[width="23"] {
width: 23px;
height: 23px;
}
svg.lucide[width="24"] {
width: 24px;
height: 24px;
}
svg.lucide[width="25"] {
width: 25px;
height: 25px;
}
svg.lucide[width="26"] {
width: 26px;
height: 26px;
}
svg.lucide[width="27"] {
width: 27px;
height: 27px;
}
svg.lucide[width="28"] {
width: 28px;
height: 28px;
}
svg.lucide[width="29"] {
width: 29px;
height: 29px;
}
svg.lucide[width="30"] {
width: 30px;
height: 30px;
}
svg.lucide[width="31"] {
width: 31px;
height: 31px;
}
svg.lucide[width="32"] {
width: 32px;
height: 32px;
}
svg.lucide[width="36"] {
width: 36px;
height: 36px;
}
svg.lucide[width="40"] {
width: 40px;
height: 40px;
}
svg.lucide[width="48"] {
width: 48px;
height: 48px;
}
svg.lucide[width="64"] {
width: 64px;
height: 64px;
}
svg.lucide[width="96"] {
width: 96px;
height: 96px;
}
}
/* Font size scale applied to html element */
html.fs-xs {
font-size: 12px;
}
html.fs-sm {
font-size: 13px;
}
html.fs-md {
font-size: 14px;
}
html.fs-lg {
font-size: 17px;
}
html.fs-xl {
font-size: 20px;
}
@supports (padding-bottom: env(safe-area-inset-bottom)) {
.safe-bottom {
padding-bottom: env(safe-area-inset-bottom);
}
}
@keyframes blink {
0%,
100% {
opacity: 1;
}
50% {
opacity: 0;
}
}
/* Motion communicates state and hierarchy. Keep it off the terminal glyph layer. */
.motion-interactive {
transition-duration: var(--motion-duration-micro);
transition-timing-function: var(--motion-ease-enter);
}
.motion-meter {
transition: width var(--motion-duration-panel) var(--motion-ease-enter);
}
.motion-panel-enter {
animation: motion-panel-enter var(--motion-duration-panel)
var(--motion-ease-enter) both;
}
.motion-context-enter {
animation: motion-context-enter var(--motion-duration-context)
var(--motion-ease-enter) both;
}
.motion-fade-enter {
animation: motion-fade-enter var(--motion-duration-context)
var(--motion-ease-enter) both;
}
.motion-workspace-enter {
animation: motion-workspace-enter var(--motion-duration-panel)
var(--motion-ease-enter) both;
}
.motion-workspace-layout {
animation: motion-workspace-layout var(--motion-duration-context)
var(--motion-ease-enter) both;
}
.motion-transfer-phase {
animation: motion-transfer-phase var(--motion-duration-micro)
var(--motion-ease-enter) both;
}
.transfer-progress-track {
position: relative;
height: 5px;
overflow: hidden;
background: color-mix(in oklab, var(--primary) 16%, transparent);
}
.transfer-progress-fill {
width: 100%;
height: 100%;
background: var(--accent-brand);
transform: scaleX(var(--transfer-progress, 0));
transform-origin: left center;
transition: transform var(--motion-duration-panel) var(--motion-ease-enter);
}
.transfer-progress-indeterminate {
width: 32%;
transform: translateX(-110%);
animation: transfer-progress-flow 1.1s var(--motion-ease-enter) infinite;
}
.transfer-progress-stalled {
background: var(--destructive);
animation: none;
}
.transfer-progress-indeterminate.transfer-progress-stalled {
transform: translateX(105%);
}
.host-status-stripe {
position: relative;
isolation: isolate;
}
.host-status-stripe[data-locking="true"]::after {
content: "";
position: absolute;
inset: 0 -3px;
z-index: -1;
background: inherit;
opacity: 0;
transform: scaleX(1);
animation: host-signal-lock var(--motion-duration-context)
var(--motion-ease-enter) both;
}
@keyframes motion-panel-enter {
from {
opacity: 0;
transform: translateY(6px) scale(0.99);
}
to {
opacity: 1;
transform: translateY(0) scale(1);
}
}
@keyframes motion-context-enter {
from {
opacity: 0;
transform: scale(0.995);
filter: saturate(0.65);
}
to {
opacity: 1;
transform: scale(1);
filter: saturate(1);
}
}
@keyframes motion-fade-enter {
from {
opacity: 0;
}
}
@keyframes motion-workspace-enter {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
@keyframes motion-workspace-layout {
from {
opacity: 0.72;
transform: scale(0.996);
}
to {
opacity: 1;
transform: scale(1);
}
}
@keyframes motion-transfer-phase {
from {
opacity: 0.45;
transform: translateX(-3px);
}
to {
opacity: 1;
transform: translateX(0);
}
}
@keyframes transfer-progress-flow {
to {
transform: translateX(420%);
}
}
@keyframes host-signal-lock {
0% {
opacity: 0;
transform: scaleX(3.2);
}
38% {
opacity: 0.48;
}
100% {
opacity: 0;
transform: scaleX(1);
}
}
@media (prefers-reduced-motion: reduce) {
:root {
--motion-duration-micro: 1ms;
--motion-duration-panel: 1ms;
--motion-duration-context: 1ms;
}
.motion-panel-enter,
.motion-context-enter,
.motion-fade-enter,
.motion-workspace-enter,
.motion-workspace-layout,
.motion-transfer-phase,
.transfer-progress-indeterminate,
.host-status-stripe[data-locking="true"]::after {
animation: none;
}
.motion-meter {
transition: none;
}
.transfer-progress-indeterminate {
transform: translateX(105%);
}
*,
*::before,
*::after {
scroll-behavior: auto !important;
animation-duration: 1ms !important;
animation-iteration-count: 1 !important;
transition-duration: 1ms !important;
}
}
/* A settings search result, marked briefly after the page opens. */
.settings-search-hit {
outline: 1px solid var(--accent-brand);
outline-offset: 2px;
transition: outline-color 1.2s ease;
}
/* Cards and rows the settings page search filtered out. */
[data-search-hidden] {
display: none !important;
}
::highlight(settings-search) {
background-color: color-mix(in oklab, var(--accent-brand) 35%, transparent);
color: inherit;
}
/*
* The title bar the desktop app draws on top of its window. The window has
* no native frame, so this strip is what moves the window around: the whole
* bar drags, everything inside it that takes clicks does not.
*/
.termix-titlebar {
-webkit-app-region: drag;
user-select: none;
}
.termix-titlebar button,
.termix-titlebar a,
.termix-titlebar input,
.termix-titlebar [role="button"] {
-webkit-app-region: no-drag;
}
/*
* Layouts size themselves to the viewport with h-dvh. Under the app-drawn
* title bar the usable height is shorter, so trim it while the bar shows.
*/
.h-dvh {
height: calc(100dvh - var(--termix-titlebar-height, 0px));
}