/* crabidy web client — pure modern CSS (architecture/web-client.md). One accent variable (crab orange-red), light and dark themes via color-scheme + light-dark(); the theme toggle stamps data-theme on , otherwise the OS decides. No frameworks, no external requests — everything ships in the bundle. */ :root { color-scheme: light dark; /* The crab. Every accent tone derives from this one value. */ --accent: oklch(0.62 0.19 35); --accent-strong: color-mix(in oklch, var(--accent) 85%, black); --accent-soft: color-mix(in oklch, var(--accent) 14%, transparent); --on-accent: oklch(0.99 0.005 60); --bg: light-dark(oklch(0.98 0.005 60), oklch(0.17 0.01 260)); --bg-raised: light-dark(oklch(1 0 0), oklch(0.21 0.012 260)); --fg: light-dark(oklch(0.25 0.015 260), oklch(0.92 0.005 60)); --fg-dim: light-dark(oklch(0.52 0.012 260), oklch(0.68 0.008 60)); --danger: light-dark(oklch(0.54 0.2 25), oklch(0.68 0.19 25)); --border: color-mix(in oklch, var(--fg) 14%, transparent); --shadow: 0 8px 32px light-dark(rgb(0 0 0 / 0.14), rgb(0 0 0 / 0.55)); } :root[data-theme="light"] { color-scheme: light; } :root[data-theme="dark"] { color-scheme: dark; } * { box-sizing: border-box; } body { margin: 0; font: 15px/1.45 system-ui, sans-serif; background: var(--bg); color: var(--fg); overscroll-behavior: none; } button { font: inherit; color: inherit; background: var(--accent); color: var(--on-accent); border: none; border-radius: 6px; padding: 0.3rem 0.75rem; cursor: pointer; &:hover { background: var(--accent-strong); } &:disabled { opacity: 0.35; cursor: default; } &.ghost { background: transparent; color: var(--fg-dim); padding: 0.25rem 0.5rem; &:hover:not(:disabled) { background: var(--accent-soft); color: var(--fg); } &.active { color: var(--accent); background: var(--accent-soft); } } &.danger { background: var(--danger); color: var(--on-accent); } &.ghost.danger { background: transparent; color: var(--danger); &:hover:not(:disabled) { background: color-mix(in oklch, var(--danger) 15%, transparent); } } } input { font: inherit; color: var(--fg); background: var(--bg); border: 1px solid var(--border); border-radius: 6px; padding: 0.4rem 0.6rem; &:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; } } /* ---- frame ---------------------------------------------------------- */ .shell { display: grid; grid-template-rows: auto 1fr auto; block-size: 100dvh; } .topbar { display: flex; align-items: center; gap: 0.75rem; padding: 0.4rem 0.9rem; border-block-end: 1px solid var(--border); background: var(--bg-raised); & .brand { color: var(--accent); font-weight: 700; font-size: 1.05rem; letter-spacing: 0.02em; } & .conn { font-size: 0.85rem; color: var(--fg-dim); &.offline { color: var(--danger); } } & .topbar-actions { margin-inline-start: auto; display: flex; gap: 0.25rem; } } /* ---- panes ----------------------------------------------------------- */ .panes { display: grid; grid-template-columns: 3fr 2fr; min-block-size: 0; } .pane { display: grid; grid-template-rows: auto 1fr auto; min-block-size: 0; border-inline-end: 1px solid var(--border); /* The focused pane shows it like the TUI's highlighted border. */ box-shadow: inset 0 2px 0 transparent; &:last-child { border-inline-end: none; } &.focused { box-shadow: inset 0 2px 0 var(--accent); } } .toolbar { display: flex; align-items: center; gap: 0.15rem; padding: 0.35rem 0.6rem; border-block-end: 1px solid var(--border); overflow-x: auto; & .path { font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } & .spacer { flex: 1; } } .list { margin: 0; padding: 0.25rem 0; list-style: none; overflow-y: auto; min-block-size: 0; & li { display: flex; align-items: center; gap: 0.4rem; padding: 0.28rem 0.75rem; cursor: pointer; border-inline-start: 3px solid transparent; & .title { flex: 1; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } & .badge { color: var(--fg-dim); font-size: 0.8rem; } &:hover { background: var(--accent-soft); } &.selected { background: var(--accent-soft); border-inline-start-color: var(--accent); } &.marked .title { color: var(--accent); font-weight: 600; &::before { content: "* "; } } /* Skipped tracks carry no audio (incremental captures). */ &.skipped .title { color: var(--danger); } &.node .title { color: color-mix(in oklch, var(--fg) 80%, var(--accent)); } &.current .title { color: var(--accent); font-weight: 700; } & .row-action { visibility: hidden; } &:hover .row-action, &.selected .row-action { visibility: visible; } } } .capture-lines { padding: 0.2rem 0.75rem 0.4rem; font-size: 0.85rem; color: var(--fg-dim); & .capture-line.error { color: var(--danger); } } /* ---- transport -------------------------------------------------------- */ .transport { display: grid; grid-template-columns: auto 1fr auto; align-items: center; gap: 1rem; padding: 0.5rem 0.9rem; border-block-start: 1px solid var(--border); background: var(--bg-raised); & .controls { display: flex; align-items: center; gap: 0.1rem; & .big { font-size: 1.3rem; color: var(--accent); } } & .now-playing { min-inline-size: 0; & .np-title { display: block; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; font-weight: 600; } } & .progress { display: flex; align-items: center; gap: 0.5rem; & .time { font-size: 0.8rem; color: var(--fg-dim); font-variant-numeric: tabular-nums; } & .gauge { flex: 1; block-size: 6px; border-radius: 3px; background: var(--accent-soft); overflow: hidden; & .gauge-fill { block-size: 100%; background: var(--accent); border-radius: 3px; transition: width 0.4s linear; } } } & .volume { display: flex; align-items: center; gap: 0.4rem; & input[type="range"] { inline-size: 7rem; accent-color: var(--accent); padding: 0; border: none; background: transparent; } } } /* ---- overlays ---------------------------------------------------------- */ .overlay { position: fixed; inset: 0; display: grid; place-items: center; background: rgb(0 0 0 / 0.4); backdrop-filter: blur(2px); } .dialog { display: grid; gap: 0.7rem; min-inline-size: min(26rem, 90vw); max-block-size: 85dvh; overflow-y: auto; padding: 1.1rem 1.3rem; border-radius: 10px; background: var(--bg-raised); box-shadow: var(--shadow); & label { color: var(--fg-dim); font-size: 0.9rem; } & .dialog-actions { display: flex; justify-content: flex-end; gap: 0.5rem; } &.danger-dialog { border-inline-start: 4px solid var(--danger); } } .help { min-inline-size: min(52rem, 94vw); & h2 { margin: 0; color: var(--accent); } & .help-columns { display: grid; grid-template-columns: repeat(auto-fit, minmax(18rem, 1fr)); gap: 0.5rem 2rem; & h3 { margin: 0.4rem 0 0.2rem; font-size: 0.9rem; color: var(--fg-dim); text-transform: uppercase; letter-spacing: 0.06em; } & table { border-collapse: collapse; inline-size: 100%; & td { padding: 0.12rem 0.4rem 0.12rem 0; vertical-align: top; } & .key { font-family: ui-monospace, monospace; color: var(--accent); white-space: nowrap; } } } } .toast { position: fixed; inset-block-end: 4.5rem; inset-inline-start: 50%; translate: -50% 0; padding: 0.5rem 1rem; border-radius: 8px; background: var(--danger); color: var(--on-accent); box-shadow: var(--shadow); } /* ---- phone ------------------------------------------------------------- */ @media (max-width: 700px) { /* One pane at a time; Tab (or tapping a pane edge) switches — the unfocused pane collapses to a slim strip acting as its tab. */ .panes { grid-template-columns: 1fr; grid-template-rows: 1fr auto; } .pane:not(.focused) { grid-template-rows: auto; max-block-size: 2.4rem; overflow: hidden; border-block-start: 1px solid var(--border); opacity: 0.75; } .transport { grid-template-columns: 1fr; gap: 0.4rem; & .volume { justify-content: flex-end; } } }