/* * static/css/components.css * Reusable UI components: buttons, inputs, badges, status indicators, * instruction cards, and animations. */ /* ── Status indicator (LED dot) ─────────────────────────────────────────── */ .led { display: inline-flex; align-items: center; gap: var(--space-2); font-size: var(--text-xs); font-weight: 700; letter-spacing: 0.1em; text-transform: uppercase; } .led__dot { width: 8px; height: 8px; border-radius: 50%; flex-shrink: 0; } .led--green .led__dot { background: var(--green); box-shadow: 0 0 6px var(--green), 0 0 12px var(--green-glow); } .led--amber .led__dot { background: var(--amber); box-shadow: 0 0 6px var(--amber), 0 0 12px var(--amber-glow); } .led--red .led__dot { background: var(--red); box-shadow: 0 0 6px var(--red), 0 0 12px var(--red-glow); } .led--muted .led__dot { background: var(--text-muted); box-shadow: none; } .led--cyan .led__dot { background: var(--cyan); box-shadow: 0 0 6px var(--cyan), 0 0 12px var(--cyan-glow); } .led--green .led__label { color: var(--green); } .led--amber .led__label { color: var(--amber); } .led--red .led__label { color: var(--red); } .led--muted .led__label { color: var(--text-muted); } .led--cyan .led__label { color: var(--cyan); } /* Pulse for "connected" / "active" */ .led--pulse .led__dot { animation: pulse 2.4s ease-in-out infinite; } @keyframes pulse { 0%, 100% { opacity: 1; transform: scale(1); } 50% { opacity: 0.5; transform: scale(0.85); } } /* ── Queue count badge ───────────────────────────────────────────────────── */ .badge { display: inline-flex; align-items: center; justify-content: center; min-width: 20px; height: 20px; padding: 0 var(--space-2); border-radius: 10px; font-size: var(--text-xs); font-weight: 700; line-height: 1; } .badge--cyan { background: var(--cyan-glow); color: var(--cyan); border: 1px solid rgba(0,212,255,0.3); } .badge--amber { background: var(--amber-glow); color: var(--amber); border: 1px solid rgba(255,190,11,0.3); } .badge--muted { background: var(--bg-overlay); color: var(--text-muted); border: 1px solid var(--border-subtle); } /* ── Buttons ─────────────────────────────────────────────────────────────── */ .btn { display: inline-flex; align-items: center; justify-content: center; gap: var(--space-2); padding: var(--space-2) var(--space-4); border: 1px solid transparent; border-radius: var(--radius-md); font-family: var(--font-ui); font-size: var(--text-sm); font-weight: 600; letter-spacing: 0.04em; cursor: pointer; text-decoration: none; transition: background var(--duration-fast) var(--ease-in-out), border-color var(--duration-fast) var(--ease-in-out), box-shadow var(--duration-fast) var(--ease-in-out), transform var(--duration-fast) var(--ease-in-out); user-select: none; white-space: nowrap; } .btn:disabled { opacity: 0.4; cursor: not-allowed; pointer-events: none; } .btn:active:not(:disabled) { transform: scale(0.97); } /* Primary – cyan */ .btn--primary { background: var(--cyan); border-color: var(--cyan); color: #000; } .btn--primary:hover { background: #1de9ff; box-shadow: 0 0 16px var(--cyan-glow); } /* Ghost */ .btn--ghost { background: transparent; border-color: var(--border-muted); color: var(--text-secondary); } .btn--ghost:hover { background: var(--bg-hover); border-color: var(--border-strong); color: var(--text-primary); } /* Danger */ .btn--danger { background: transparent; border-color: var(--red-dim); color: var(--red); } .btn--danger:hover { background: var(--red-glow); border-color: var(--red); } /* Icon-only */ .btn--icon { padding: var(--space-2); width: 32px; height: 32px; border-radius: var(--radius-sm); } /* Small */ .btn--sm { padding: var(--space-1) var(--space-3); font-size: var(--text-xs); height: 28px; } /* ── Form elements ──────────────────────────────────────────────────────── */ .input, .textarea { width: 100%; background: var(--bg-overlay); border: 1px solid var(--border-muted); border-radius: var(--radius-md); color: var(--text-primary); font-family: var(--font-mono); font-size: var(--text-base); padding: var(--space-3) var(--space-4); transition: border-color var(--duration-fast) var(--ease-in-out), box-shadow var(--duration-fast) var(--ease-in-out); outline: none; resize: vertical; } .input::placeholder, .textarea::placeholder { color: var(--text-muted); } .input:focus, .textarea:focus { border-color: var(--cyan-dim); box-shadow: 0 0 0 3px rgba(0,212,255,0.08); } .textarea { min-height: 88px; line-height: 1.6; } .form-group { display: flex; flex-direction: column; gap: var(--space-2); } .form-row { display: flex; gap: var(--space-3); align-items: flex-end; } .form-row .textarea { flex: 1; } /* ── Instruction card ─────────────────────────────────────────────────────── */ .instruction-card { padding: var(--space-4) var(--space-5); border-bottom: 1px solid var(--border-subtle); display: grid; grid-template-columns: 1fr auto; gap: var(--space-2) var(--space-4); align-items: start; transition: background var(--duration-fast) var(--ease-in-out); animation: card-in var(--duration-slow) var(--ease-out-expo) both; } @keyframes card-in { from { opacity: 0; transform: translateY(-8px); } to { opacity: 1; transform: translateY(0); } } .instruction-card:last-child { border-bottom: none; } .instruction-card:hover { background: var(--bg-hover); } .instruction-card__meta { grid-column: 1 / -1; display: flex; align-items: center; gap: var(--space-4); flex-wrap: wrap; } .instruction-card__pos { font-size: var(--text-xs); font-weight: 700; font-family: var(--font-mono); color: var(--cyan); background: var(--cyan-glow); border: 1px solid rgba(0,212,255,0.25); border-radius: var(--radius-sm); padding: 1px 6px; letter-spacing: 0.08em; } .instruction-card__content { font-family: var(--font-mono); font-size: var(--text-base); line-height: 1.65; color: var(--text-primary); word-break: break-word; white-space: pre-wrap; } .instruction-card__time { font-size: var(--text-xs); color: var(--text-muted); font-family: var(--font-mono); } .instruction-card__actions { display: flex; gap: var(--space-2); align-items: center; flex-shrink: 0; } /* Edit mode */ .instruction-card__edit-area { grid-column: 1 / -1; display: flex; gap: var(--space-3); align-items: flex-end; margin-top: var(--space-2); } .instruction-card__edit-area .textarea { flex: 1; min-height: 72px; } /* Consumed card */ .instruction-card--consumed { opacity: 0.45; } .instruction-card--consumed .instruction-card__content { text-decoration: line-through; text-decoration-color: var(--amber-dim); color: var(--text-secondary); } .instruction-card--consumed .instruction-card__pos { background: var(--amber-glow); border-color: rgba(255,190,11,0.2); color: var(--amber); } .instruction-card--consumed:hover { opacity: 0.65; } .instruction-card__consumed-by { font-size: var(--text-xs); color: var(--amber); font-family: var(--font-mono); } /* ── Empty state ─────────────────────────────────────────────────────────── */ .empty-state { padding: var(--space-10) var(--space-6); text-align: center; color: var(--text-muted); font-size: var(--text-sm); letter-spacing: 0.04em; } .empty-state__icon { font-size: 1.5rem; margin-bottom: var(--space-3); opacity: 0.4; } /* ── Status panel items ──────────────────────────────────────────────────── */ .stat-row { display: flex; align-items: baseline; justify-content: space-between; padding: var(--space-2) 0; border-bottom: 1px solid var(--border-subtle); } .stat-row:last-child { border-bottom: none; } .stat-label { font-size: var(--text-xs); font-weight: 700; letter-spacing: 0.1em; text-transform: uppercase; color: var(--text-muted); } .stat-value { font-family: var(--font-mono); font-size: var(--text-sm); color: var(--text-primary); } .stat-value--cyan { color: var(--cyan); } .stat-value--amber { color: var(--amber); } /* ── Config form ─────────────────────────────────────────────────────────── */ .config-field { display: flex; flex-direction: column; gap: var(--space-1); margin-bottom: var(--space-4); } .config-field:last-child { margin-bottom: 0; } .config-label { font-size: var(--text-xs); font-weight: 700; letter-spacing: 0.1em; text-transform: uppercase; color: var(--text-secondary); } .config-hint { font-size: var(--text-xs); color: var(--text-muted); margin-top: var(--space-1); } .input--sm { padding: var(--space-2) var(--space-3); font-size: var(--text-sm); } /* ── Toast notification ──────────────────────────────────────────────────── */ #toast-container { position: fixed; bottom: var(--space-6); right: var(--space-6); z-index: 9999; display: flex; flex-direction: column; gap: var(--space-2); pointer-events: none; } .toast { background: var(--bg-overlay); border: 1px solid var(--border-strong); border-radius: var(--radius-md); padding: var(--space-3) var(--space-5); font-size: var(--text-sm); color: var(--text-primary); box-shadow: var(--shadow-lg); animation: toast-in var(--duration-normal) var(--ease-out-expo) both; pointer-events: auto; max-width: 320px; } .toast--success { border-color: var(--green-dim); color: var(--green); } .toast--error { border-color: var(--red-dim); color: var(--red); } .toast--info { border-color: var(--cyan-dim); color: var(--cyan); } @keyframes toast-in { from { opacity: 0; transform: translateY(12px) scale(0.95); } to { opacity: 1; transform: translateY(0) scale(1); } } @keyframes toast-out { from { opacity: 1; transform: translateY(0) scale(1); } to { opacity: 0; transform: translateY(4px) scale(0.97); } } /* ── Theme toggle (header) ────────────────────────────────────────────────── */ #theme-toggle { color: var(--text-secondary); border-color: transparent; transition: color var(--duration-fast) var(--ease-in-out), background var(--duration-fast) var(--ease-in-out); } #theme-toggle:hover { color: var(--text-primary); background: var(--bg-hover); border-color: var(--border-subtle); } /* ── SVG icons ───────────────────────────────────────────────────────────── */ .icon { width: 16px; height: 16px; flex-shrink: 0; stroke-width: 2; stroke: currentColor; fill: none; stroke-linecap: round; stroke-linejoin: round; } .icon--sm { width: 12px; height: 12px; } /* ── Page load animation ─────────────────────────────────────────────────── */ .fade-in { animation: fade-in var(--duration-slow) var(--ease-out-expo) both; } .fade-in--delay-1 { animation-delay: 60ms; } .fade-in--delay-2 { animation-delay: 120ms; } .fade-in--delay-3 { animation-delay: 200ms; } .fade-in--delay-4 { animation-delay: 300ms; } @keyframes fade-in { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: translateY(0); } } /* ── Loading spinner ─────────────────────────────────────────────────────── */ .spinner { display: inline-block; width: 14px; height: 14px; border: 2px solid var(--border-muted); border-top-color: var(--cyan); border-radius: 50%; animation: spin 0.7s linear infinite; } @keyframes spin { to { transform: rotate(360deg); } } /* ── Auth overlay ─────────────────────────────────────────────────────────── */ .auth-overlay { position: fixed; inset: 0; z-index: 9000; display: flex; align-items: center; justify-content: center; background: color-mix(in srgb, var(--bg-void) 44%, transparent); backdrop-filter: blur(2px); -webkit-backdrop-filter: blur(2px); } .auth-card { width: 100%; max-width: 380px; margin: var(--space-4); padding: var(--space-8); background: var(--surface-raised); border: 1px solid var(--border); border-radius: var(--radius-lg); box-shadow: 0 32px 64px rgba(0, 0, 0, 0.48); display: flex; flex-direction: column; align-items: center; gap: var(--space-4); text-align: center; } .auth-card__icon { display: flex; align-items: center; justify-content: center; width: 64px; height: 64px; border-radius: 50%; background: color-mix(in srgb, var(--cyan) 10%, transparent); border: 1px solid color-mix(in srgb, var(--cyan) 25%, transparent); margin-bottom: var(--space-2); } .auth-card__title { font-family: var(--font-display); font-size: var(--text-lg); font-weight: 700; color: var(--text-primary); margin: 0; } .auth-card__desc { font-size: var(--text-sm); color: var(--text-muted); margin: 0; line-height: 1.6; } .auth-card__desc code { font-family: var(--font-mono); font-size: 0.9em; color: var(--cyan); background: color-mix(in srgb, var(--cyan) 10%, transparent); padding: 1px 5px; border-radius: 3px; } .auth-card__form { width: 100%; display: flex; flex-direction: column; gap: var(--space-3); } .auth-card__error { font-size: var(--text-xs); color: var(--red); margin: 0; } .confirm-card { max-width: 420px; } .confirm-card__icon--danger { color: var(--red); background: color-mix(in srgb, var(--red) 12%, transparent); border-color: color-mix(in srgb, var(--red) 30%, transparent); } .confirm-card__actions { width: 100%; display: grid; grid-template-columns: 1fr 1fr; gap: var(--space-3); } .confirm-card__actions .btn { width: 100%; } /* ── Quick shortcuts ─────────────────────────────────────────────────────── */ .shortcuts-container { margin-top: var(--space-3); border-top: 1px solid var(--border-subtle); padding-top: var(--space-3); display: flex; flex-direction: column; gap: var(--space-2); } .shortcuts-header { display: flex; align-items: center; justify-content: space-between; gap: var(--space-3); } .shortcuts-label { font-family: var(--font-ui); font-size: var(--text-xs); font-weight: 700; letter-spacing: 0.12em; text-transform: uppercase; color: var(--text-muted); flex-shrink: 0; } .shortcut-edit-toggle { display: inline-flex; align-items: center; gap: var(--space-1); padding: 2px var(--space-2); background: none; border: 1px solid var(--border-subtle); border-radius: var(--radius-sm); font-family: var(--font-ui); font-size: var(--text-xs); font-weight: 600; letter-spacing: 0.06em; color: var(--text-muted); cursor: pointer; transition: border-color 150ms, color 150ms; } .shortcut-edit-toggle:hover { border-color: var(--border-strong); color: var(--text-secondary); } .shortcuts-rail { display: flex; flex-wrap: wrap; gap: var(--space-2); min-height: 28px; } /* Individual shortcut chip */ .shortcut-chip { position: relative; display: inline-flex; align-items: center; gap: var(--space-1); max-width: 220px; padding: 4px var(--space-3); background: var(--bg-overlay); border: 1px solid var(--border-muted); border-radius: 4px; font-family: var(--font-mono); font-size: var(--text-xs); font-weight: 400; color: var(--text-secondary); cursor: pointer; white-space: nowrap; overflow: hidden; transition: border-color 140ms ease, color 140ms ease, background 140ms ease, transform 80ms ease; user-select: none; } .shortcut-chip:hover { border-color: var(--cyan-dim); color: var(--text-primary); background: var(--bg-hover); } .shortcut-chip:active { transform: scale(0.95); } .shortcut-chip__text { overflow: hidden; text-overflow: ellipsis; flex: 1; min-width: 0; } /* Delete handle (shown in edit mode) */ .shortcut-chip__del { display: none; /* toggled via JS */ align-items: center; justify-content: center; width: 14px; height: 14px; flex-shrink: 0; border-radius: 50%; background: var(--red-dim); color: var(--red); transition: background 120ms; } .shortcut-chip:hover .shortcut-chip__del { background: var(--red); color: #fff; } /* Edit handle (shown in edit mode) */ .shortcut-chip__edit { display: none; /* toggled via JS */ align-items: center; justify-content: center; width: 14px; height: 14px; flex-shrink: 0; border-radius: 50%; background: var(--border-muted); color: var(--text-secondary); transition: background 120ms; } .shortcut-chip:hover .shortcut-chip__edit { background: var(--cyan-dim); color: var(--cyan); } /* Inline chip edit row */ .shortcut-inline-edit { display: inline-flex; align-items: center; gap: var(--space-2); animation: chip-fire 160ms ease; } .shortcut-inline-input { width: 180px; padding: 3px var(--space-2); font-size: var(--text-xs); height: 26px; } /* Firing state (just clicked) */ .shortcut-chip--firing { border-color: var(--cyan); color: var(--cyan); animation: chip-fire 280ms ease; } /* Sent confirmation flash */ .shortcut-chip--sent { border-color: var(--green); color: var(--green); animation: chip-sent 600ms ease; } /* Add chip */ .shortcut-chip--add { border-style: dashed; border-color: var(--border-subtle); color: var(--text-muted); gap: var(--space-1); padding: 4px var(--space-2); } .shortcut-chip--add:hover { border-color: var(--cyan-dim); color: var(--cyan); } /* Chip animations */ @keyframes chip-fire { 0% { transform: scale(1); } 40% { transform: scale(0.93); } 100% { transform: scale(1); } } @keyframes chip-sent { 0% { box-shadow: 0 0 0 0 var(--green-glow); } 50% { box-shadow: 0 0 0 6px transparent; } 100% { box-shadow: none; } } @keyframes chip-vanish { to { opacity: 0; transform: scale(0.8); width: 0; padding: 0; margin: 0; } } /* Add shortcut inline form */ .shortcut-add-form { display: flex; flex-direction: column; gap: var(--space-2); padding-top: var(--space-2); border-top: 1px solid var(--border-subtle); margin-top: var(--space-1); animation: fade-in-up 180ms ease; } .shortcut-add-actions { display: flex; gap: var(--space-2); }