Spaces:
Running
Running
| /* ========================================================================= | |
| Reachy Blocks - Modern dev-tool stylesheet | |
| - Neutral grayscale base, single orange accent. | |
| - Inspired by Linear / Vercel / Stripe: a credible dev-tool aesthetic | |
| that doesn't fight Blockly's playful blocks. | |
| - All visual tokens go through CSS variables so theming stays consistent. | |
| ========================================================================= */ | |
| :root { | |
| /* ---------- Surface ---------- */ | |
| --bg: #0A0B0D; /* page */ | |
| --panel: #131418; /* primary surface */ | |
| --panel-elevated: #1B1D22; /* hovered / elevated cards */ | |
| --panel-input: #0E0F12; /* deeper recessed bg for inputs */ | |
| --border: #26282E; | |
| --border-strong: #34373F; | |
| /* ---------- Text ---------- */ | |
| --text: #E8E9EB; | |
| --text-secondary: #9298A0; | |
| --text-muted: #60656E; | |
| /* ---------- Brand / accent ---------- */ | |
| --accent: #FF6B35; /* keep Pollen-coral */ | |
| --accent-hover: #FF8557; | |
| --accent-dim: rgba(255, 107, 53, 0.12); | |
| --accent-ring: rgba(255, 107, 53, 0.35); | |
| /* ---------- Semantic ---------- */ | |
| --success: #10B981; | |
| --success-dim: rgba(16, 185, 129, 0.14); | |
| --danger: #EF4444; | |
| --danger-dim: rgba(239, 68, 68, 0.14); | |
| --warning: #F59E0B; | |
| /* ---------- Typography ---------- */ | |
| --font-sans: 'Inter', system-ui, -apple-system, sans-serif; | |
| --font-mono: ui-monospace, 'SF Mono', Menlo, Monaco, monospace; | |
| --text-xs: 11px; | |
| --text-sm: 12px; | |
| --text-base: 13px; | |
| --text-md: 14px; | |
| --text-lg: 16px; | |
| --text-xl: 20px; | |
| --text-display: 24px; | |
| /* ---------- Spacing scale (multiples of 4) ---------- */ | |
| --s-1: 4px; | |
| --s-2: 8px; | |
| --s-3: 12px; | |
| --s-4: 16px; | |
| --s-5: 20px; | |
| --s-6: 24px; | |
| --s-8: 32px; | |
| /* ---------- Radius ---------- */ | |
| --r-sm: 4px; | |
| --r: 8px; | |
| --r-lg: 12px; | |
| --r-xl: 16px; | |
| /* ---------- Shadows ---------- */ | |
| --shadow-sm: 0 1px 2px rgba(0,0,0,0.3); | |
| --shadow: 0 4px 12px rgba(0,0,0,0.4); | |
| --shadow-lg: 0 12px 32px rgba(0,0,0,0.5), 0 0 0 1px rgba(255,255,255,0.04); | |
| /* ---------- Motion ---------- */ | |
| --ease: cubic-bezier(0.4, 0, 0.2, 1); | |
| --t-fast: 120ms; | |
| --t-med: 200ms; | |
| } | |
| * { box-sizing: border-box; margin: 0; padding: 0; } | |
| html, body { | |
| height: 100%; | |
| background: var(--bg); | |
| color: var(--text); | |
| font-family: var(--font-sans); | |
| font-size: var(--text-md); | |
| line-height: 1.5; | |
| -webkit-font-smoothing: antialiased; | |
| -moz-osx-font-smoothing: grayscale; | |
| overflow: hidden; /* layout sizes itself; no body scroll */ | |
| } | |
| a { color: var(--accent); text-decoration: none; } | |
| /* ========================================================================= | |
| Buttons | |
| ========================================================================= */ | |
| .btn { | |
| height: 36px; | |
| padding: 0 14px; | |
| border: 1px solid transparent; | |
| border-radius: var(--r); | |
| background: var(--panel-elevated); | |
| color: var(--text); | |
| font: 600 var(--text-base) / 1 var(--font-sans); | |
| cursor: pointer; | |
| transition: background var(--t-fast) var(--ease), | |
| border-color var(--t-fast) var(--ease), | |
| opacity var(--t-fast) var(--ease); | |
| display: inline-flex; | |
| align-items: center; | |
| justify-content: center; | |
| gap: var(--s-2); | |
| user-select: none; | |
| } | |
| .btn:hover:not(:disabled) { background: var(--border); } | |
| .btn:focus-visible { | |
| outline: none; | |
| box-shadow: 0 0 0 3px var(--accent-ring); | |
| } | |
| .btn:disabled { opacity: 0.4; cursor: not-allowed; } | |
| .btn-primary { | |
| background: var(--accent); | |
| color: #fff; | |
| border-color: var(--accent); | |
| } | |
| .btn-primary:hover:not(:disabled) { background: var(--accent-hover); border-color: var(--accent-hover); } | |
| .btn-secondary { | |
| background: var(--panel-elevated); | |
| color: var(--text); | |
| border-color: var(--border); | |
| } | |
| .btn-danger { | |
| background: transparent; | |
| color: var(--text-secondary); | |
| border-color: var(--border); | |
| } | |
| .btn-danger:hover:not(:disabled) { | |
| color: var(--danger); | |
| border-color: var(--danger); | |
| background: var(--danger-dim); | |
| } | |
| .btn-run { | |
| background: var(--success); | |
| color: #fff; | |
| border-color: var(--success); | |
| } | |
| .btn-run:hover:not(:disabled) { background: #0FA974; border-color: #0FA974; } | |
| .btn-stop { | |
| background: transparent; | |
| color: var(--text-secondary); | |
| border-color: var(--border); | |
| } | |
| .btn-stop:not(:disabled):hover { | |
| color: var(--danger); | |
| border-color: var(--danger); | |
| } | |
| .btn-icon { | |
| height: 32px; | |
| width: 32px; | |
| padding: 0; | |
| border-radius: var(--r); | |
| background: transparent; | |
| color: var(--text-secondary); | |
| border: 1px solid var(--border); | |
| font: 400 16px / 1 var(--font-sans); | |
| cursor: pointer; | |
| transition: color var(--t-fast) var(--ease), border-color var(--t-fast) var(--ease); | |
| display: inline-flex; | |
| align-items: center; | |
| justify-content: center; | |
| } | |
| .btn-icon:hover { color: var(--text); border-color: var(--border-strong); } | |
| /* AI Generate modal. */ | |
| .aigen-modal-card { | |
| max-width: 580px; | |
| max-height: 88vh; | |
| overflow: hidden; | |
| gap: 0; | |
| padding: 0; | |
| } | |
| .aigen-modal-card > h3 { padding: var(--s-5) var(--s-5) 0; } | |
| .aigen-modal-card > .settings-blurb { padding: 0 var(--s-5); } | |
| .aigen-textarea { | |
| margin: var(--s-3) var(--s-5) 0; | |
| padding: 12px; | |
| background: var(--panel-input); | |
| border: 1px solid var(--border); | |
| border-radius: var(--r); | |
| color: var(--text); | |
| font-size: var(--text-md); | |
| font-family: var(--font-sans); | |
| line-height: 1.5; | |
| min-height: 86px; | |
| max-height: 200px; | |
| resize: vertical; | |
| transition: border-color var(--t-fast) var(--ease); | |
| } | |
| .aigen-textarea:focus { | |
| outline: none; | |
| border-color: var(--accent); | |
| box-shadow: 0 0 0 3px var(--accent-ring); | |
| } | |
| .aigen-status { | |
| padding: 0 var(--s-5); | |
| margin-top: var(--s-2); | |
| min-height: 18px; | |
| } | |
| .aigen-preview { | |
| margin: var(--s-3) var(--s-5) 0; | |
| background: var(--panel-input); | |
| border: 1px solid var(--border); | |
| border-radius: var(--r-lg); | |
| padding: var(--s-3) var(--s-4); | |
| display: flex; | |
| flex-direction: column; | |
| gap: var(--s-2); | |
| overflow-y: auto; | |
| max-height: 30vh; | |
| } | |
| .aigen-preview-head { | |
| font: 600 11px / 1 var(--font-sans); | |
| color: var(--text-muted); | |
| text-transform: uppercase; | |
| letter-spacing: 0.08em; | |
| } | |
| .aigen-preview-summary { | |
| color: var(--text); | |
| font-size: var(--text-md); | |
| line-height: 1.5; | |
| font-family: var(--font-mono); | |
| word-break: break-word; | |
| } | |
| .aigen-preview-details summary { | |
| color: var(--accent); | |
| font-size: var(--text-sm); | |
| cursor: pointer; | |
| user-select: none; | |
| } | |
| .aigen-preview-json { | |
| background: var(--bg); | |
| border: 1px solid var(--border); | |
| border-radius: var(--r-sm); | |
| padding: var(--s-3); | |
| margin-top: var(--s-2); | |
| color: var(--text-secondary); | |
| font-family: var(--font-mono); | |
| font-size: var(--text-sm); | |
| line-height: 1.4; | |
| max-height: 220px; | |
| overflow: auto; | |
| white-space: pre; | |
| } | |
| .aigen-preview-meta { | |
| color: var(--text-muted); | |
| font-size: var(--text-sm); | |
| font-family: var(--font-mono); | |
| } | |
| .aigen-footer { | |
| display: flex; | |
| gap: var(--s-2); | |
| padding: var(--s-3) var(--s-5) var(--s-4); | |
| border-top: 1px solid var(--border); | |
| margin-top: var(--s-3); | |
| background: var(--panel); | |
| flex-wrap: wrap; | |
| } | |
| .aigen-footer .btn { flex: 1; min-width: 110px; } | |
| /* Examples button in header + its dropdown menu. */ | |
| .examples-wrap { | |
| position: relative; | |
| display: inline-flex; | |
| } | |
| .btn-examples { | |
| height: 30px; | |
| padding: 0 12px; | |
| border: 1px solid var(--border); | |
| border-radius: var(--r); | |
| background: var(--panel-elevated); | |
| color: var(--text); | |
| font: 600 var(--text-sm) / 1 var(--font-sans); | |
| cursor: pointer; | |
| transition: background var(--t-fast) var(--ease), border-color var(--t-fast) var(--ease); | |
| display: inline-flex; | |
| align-items: center; | |
| gap: 4px; | |
| } | |
| .btn-examples:hover { background: var(--border); border-color: var(--border-strong); } | |
| /* Small "beta" pill inside the Build with AI button. */ | |
| .beta-pill { | |
| display: inline-block; | |
| padding: 2px 6px; | |
| margin-left: 4px; | |
| background: var(--accent-dim); | |
| color: var(--accent); | |
| border: 1px solid color-mix(in srgb, var(--accent) 35%, transparent); | |
| border-radius: 999px; | |
| font: 700 9px / 1 var(--font-sans); | |
| text-transform: uppercase; | |
| letter-spacing: 0.06em; | |
| vertical-align: middle; | |
| transform: translateY(-0.5px); | |
| } | |
| .examples-menu { | |
| position: absolute; | |
| top: calc(100% + 6px); | |
| right: 0; | |
| min-width: 280px; | |
| background: var(--panel); | |
| border: 1px solid var(--border-strong); | |
| border-radius: var(--r-lg); | |
| box-shadow: var(--shadow-lg); | |
| padding: 4px; | |
| display: flex; | |
| flex-direction: column; | |
| gap: 2px; | |
| z-index: 80; | |
| animation: examples-fade var(--t-fast) var(--ease); | |
| } | |
| @keyframes examples-fade { | |
| from { opacity: 0; transform: translateY(-4px); } | |
| to { opacity: 1; transform: translateY(0); } | |
| } | |
| .examples-menu-item { | |
| text-align: left; | |
| background: transparent; | |
| color: var(--text); | |
| border: 0; | |
| padding: 10px 12px; | |
| border-radius: var(--r-sm); | |
| cursor: pointer; | |
| display: flex; | |
| flex-direction: column; | |
| gap: 2px; | |
| transition: background var(--t-fast) var(--ease); | |
| } | |
| .examples-menu-item:hover { background: var(--panel-elevated); } | |
| .examples-menu-name { | |
| font: 600 var(--text-md) / 1.2 var(--font-sans); | |
| color: var(--text); | |
| letter-spacing: -0.005em; | |
| } | |
| .examples-menu-desc { | |
| font: 400 var(--text-sm) / 1.4 var(--font-sans); | |
| color: var(--text-muted); | |
| } | |
| .btn-logout { | |
| height: 30px; | |
| padding: 0 12px; | |
| background: transparent; | |
| color: var(--text-muted); | |
| border: 1px solid var(--border); | |
| border-radius: var(--r); | |
| font: 500 var(--text-sm) / 1 var(--font-sans); | |
| cursor: pointer; | |
| transition: color var(--t-fast) var(--ease), border-color var(--t-fast) var(--ease); | |
| } | |
| .btn-logout:hover { color: var(--text-secondary); border-color: var(--border-strong); } | |
| .btn-hf { | |
| background: #FFD21E; | |
| color: #0A0B0D; | |
| border: none; | |
| padding: 12px 24px; | |
| border-radius: var(--r); | |
| font-size: var(--text-md); | |
| font-weight: 700; | |
| cursor: pointer; | |
| display: inline-flex; | |
| align-items: center; | |
| gap: var(--s-2); | |
| transition: transform var(--t-fast) var(--ease), filter var(--t-fast) var(--ease); | |
| } | |
| .btn-hf:hover { filter: brightness(1.05); } | |
| /* ========================================================================= | |
| Header | |
| ========================================================================= */ | |
| .header { | |
| height: 52px; | |
| padding: 0 var(--s-4); | |
| background: var(--panel); | |
| border-bottom: 1px solid var(--border); | |
| display: flex; | |
| align-items: center; | |
| justify-content: space-between; | |
| position: sticky; | |
| top: 0; | |
| z-index: 10; | |
| } | |
| .logo { | |
| display: flex; | |
| align-items: center; | |
| gap: var(--s-3); | |
| min-width: 0; | |
| } | |
| .logo img { | |
| width: 28px; | |
| height: 28px; | |
| border-radius: var(--r-sm); | |
| } | |
| .logo-text { | |
| font: 700 var(--text-md) / 1 var(--font-sans); | |
| color: var(--text); | |
| letter-spacing: -0.01em; | |
| white-space: nowrap; | |
| } | |
| .logo-text span { | |
| color: var(--text-muted); | |
| font-weight: 400; | |
| margin-left: var(--s-2); | |
| font-size: var(--text-sm); | |
| } | |
| .user-section { | |
| display: flex; | |
| align-items: center; | |
| gap: var(--s-2); | |
| } | |
| .user-badge { | |
| height: 30px; | |
| display: inline-flex; | |
| align-items: center; | |
| padding: 0 12px; | |
| background: var(--panel-input); | |
| border: 1px solid var(--border); | |
| border-radius: var(--r); | |
| font: 500 var(--text-sm) / 1 var(--font-sans); | |
| color: var(--text-secondary); | |
| } | |
| /* ========================================================================= | |
| Login view (shown before HF auth) | |
| ========================================================================= */ | |
| .login-view { | |
| height: 100vh; | |
| height: 100dvh; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| padding: var(--s-5); | |
| background: | |
| radial-gradient(circle at 20% 0%, rgba(255,107,53,0.08), transparent 50%), | |
| radial-gradient(circle at 80% 100%, rgba(16,185,129,0.05), transparent 50%), | |
| var(--bg); | |
| } | |
| .login-card { | |
| background: var(--panel); | |
| border: 1px solid var(--border); | |
| padding: var(--s-8); | |
| border-radius: var(--r-xl); | |
| text-align: center; | |
| max-width: 400px; | |
| width: 100%; | |
| box-shadow: var(--shadow-lg); | |
| } | |
| .login-logo { | |
| width: 64px; | |
| height: 64px; | |
| margin-bottom: var(--s-5); | |
| border-radius: var(--r-lg); | |
| } | |
| .login-card h2 { | |
| color: var(--text); | |
| font-size: var(--text-xl); | |
| font-weight: 700; | |
| margin-bottom: var(--s-2); | |
| letter-spacing: -0.02em; | |
| } | |
| .login-card p { | |
| color: var(--text-secondary); | |
| font-size: var(--text-md); | |
| line-height: 1.55; | |
| margin-bottom: var(--s-6); | |
| } | |
| /* ========================================================================= | |
| Main 2-panel layout - workspace + stage | |
| ========================================================================= */ | |
| .rb-container { | |
| display: grid; | |
| grid-template-columns: 1fr 380px; | |
| gap: var(--s-3); | |
| padding: var(--s-3); | |
| height: calc(100vh - 52px); | |
| height: calc(100dvh - 52px); | |
| background: var(--bg); | |
| } | |
| .rb-workspace { | |
| background: var(--panel); | |
| border: 1px solid var(--border); | |
| border-radius: var(--r-lg); | |
| overflow: hidden; | |
| position: relative; | |
| min-height: 0; | |
| box-shadow: var(--shadow-sm); | |
| display: flex; /* sidebar + blockly canvas */ | |
| } | |
| #blocklyDiv { | |
| position: relative; | |
| flex: 1; | |
| height: 100%; | |
| min-width: 0; | |
| } | |
| /* ========================================================================= | |
| Category pills (Scratch 2.0+ style) - rendered as an HTML overlay at | |
| the top-left of #blocklyDiv. Sits above Blockly's flat flyout, giving | |
| the visual feel of ONE palette panel: pills row at top, blocks under. | |
| Width matches the flyout (260px) so pills + flyout read as one column. | |
| ========================================================================= */ | |
| .rb-pills { | |
| position: absolute; | |
| top: 0; | |
| left: 0; | |
| width: 260px; | |
| box-sizing: border-box; | |
| padding: 10px 10px 8px 10px; | |
| display: flex; | |
| flex-wrap: wrap; | |
| gap: 5px; | |
| background: var(--panel-elevated); | |
| border-right: 1px solid var(--border); | |
| border-bottom: 1px solid var(--border); | |
| z-index: 10; /* above the flyout SVG */ | |
| } | |
| .rb-pill { | |
| height: 26px; | |
| padding: 0 10px 0 8px; | |
| display: inline-flex; | |
| align-items: center; | |
| gap: 6px; | |
| border: 1px solid var(--border); | |
| border-radius: 13px; | |
| background: var(--panel); | |
| color: var(--text-secondary); | |
| font: 600 11px / 1 var(--font-sans); | |
| cursor: pointer; | |
| transition: background var(--t-fast) var(--ease), | |
| color var(--t-fast) var(--ease), | |
| border-color var(--t-fast) var(--ease); | |
| } | |
| .rb-pill:hover { | |
| border-color: var(--cat-color, var(--border-strong)); | |
| color: var(--text); | |
| } | |
| .rb-pill.selected { | |
| background: var(--cat-color, var(--accent)); | |
| color: #fff; | |
| border-color: transparent; | |
| } | |
| .rb-pill-bead { | |
| width: 9px; | |
| height: 9px; | |
| border-radius: 50%; | |
| background: var(--cat-color, var(--accent)); | |
| flex-shrink: 0; | |
| } | |
| .rb-pill.selected .rb-pill-bead { background: #fff; } | |
| .rb-pill-label { white-space: nowrap; } | |
| .rb-stage { | |
| display: flex; | |
| flex-direction: column; | |
| gap: var(--s-3); | |
| overflow-y: auto; | |
| min-height: 0; | |
| padding-right: 2px; /* sit clear of scrollbar */ | |
| } | |
| /* Custom thin scrollbar */ | |
| .rb-stage::-webkit-scrollbar { width: 6px; } | |
| .rb-stage::-webkit-scrollbar-track { background: transparent; } | |
| .rb-stage::-webkit-scrollbar-thumb { | |
| background: var(--border); | |
| border-radius: 3px; | |
| } | |
| .rb-stage::-webkit-scrollbar-thumb:hover { background: var(--border-strong); } | |
| /* ========================================================================= | |
| Stage - camera, status, controls | |
| ========================================================================= */ | |
| .stage-video-wrap { | |
| position: relative; | |
| background: var(--panel-input); | |
| border: 1px solid var(--border); | |
| border-radius: var(--r-lg); | |
| overflow: hidden; | |
| aspect-ratio: 16 / 9; | |
| } | |
| .stage-video-wrap video { | |
| width: 100%; | |
| height: 100%; | |
| object-fit: cover; | |
| background: | |
| radial-gradient(circle at center, rgba(255,107,53,0.04), transparent 60%), | |
| var(--panel-input); | |
| display: none; /* shown only while streaming */ | |
| } | |
| .stage-video-wrap.streaming video { display: block; } | |
| /* 3D simulation viewport - fills the camera tile when no robot streaming. */ | |
| .sim-container { | |
| position: absolute; | |
| inset: 0; | |
| background: var(--panel-input); | |
| overflow: hidden; | |
| } | |
| .stage-video-wrap.streaming .sim-container { display: none; } | |
| .sim-container canvas { | |
| display: block; | |
| width: 100% ; | |
| height: 100% ; | |
| cursor: grab; | |
| } | |
| .sim-container canvas:active { cursor: grabbing; } | |
| /* Browser-compat warning bar (shown only if features are missing). */ | |
| .compat-bar { | |
| position: fixed; | |
| top: 0; | |
| left: 0; | |
| right: 0; | |
| background: var(--warning); | |
| color: #1a1a1a; | |
| padding: 10px 16px; | |
| font: 600 var(--text-base) / 1.4 var(--font-sans); | |
| z-index: 200; | |
| display: flex; | |
| align-items: center; | |
| justify-content: space-between; | |
| gap: var(--s-3); | |
| box-shadow: 0 2px 8px rgba(0,0,0,0.4); | |
| } | |
| .compat-bar-dismiss { | |
| background: transparent; | |
| border: 0; | |
| color: #1a1a1a; | |
| font-size: 20px; | |
| cursor: pointer; | |
| line-height: 1; | |
| padding: 0 8px; | |
| opacity: 0.7; | |
| } | |
| .compat-bar-dismiss:hover { opacity: 1; } | |
| /* First-visit welcome card - floating bottom-right. */ | |
| .welcome-banner { | |
| position: fixed; | |
| right: var(--s-4); | |
| bottom: var(--s-4); | |
| width: 320px; | |
| background: var(--panel); | |
| border: 1px solid var(--border-strong); | |
| border-radius: var(--r-lg); | |
| padding: var(--s-4); | |
| z-index: 90; | |
| box-shadow: var(--shadow-lg); | |
| display: flex; | |
| flex-direction: column; | |
| gap: var(--s-3); | |
| animation: welcome-rise var(--t-med) var(--ease); | |
| } | |
| @keyframes welcome-rise { | |
| from { transform: translateY(12px); opacity: 0; } | |
| to { transform: translateY(0); opacity: 1; } | |
| } | |
| .welcome-title { | |
| font: 700 var(--text-lg) / 1.2 var(--font-sans); | |
| color: var(--text); | |
| letter-spacing: -0.01em; | |
| } | |
| .welcome-body { | |
| font: 400 var(--text-md) / 1.5 var(--font-sans); | |
| color: var(--text-secondary); | |
| } | |
| .welcome-body strong { color: var(--text); font-weight: 600; } | |
| .welcome-dismiss { align-self: flex-end; } | |
| /* Sim loading overlay - shown while STL meshes download on first visit. */ | |
| .sim-loading { | |
| position: absolute; | |
| inset: 0; | |
| display: flex; | |
| flex-direction: column; | |
| align-items: center; | |
| justify-content: center; | |
| gap: var(--s-3); | |
| background: var(--panel-input); | |
| z-index: 3; | |
| pointer-events: none; | |
| } | |
| .sim-loading-spinner { | |
| width: 32px; | |
| height: 32px; | |
| border: 3px solid var(--border); | |
| border-top-color: var(--accent); | |
| border-radius: 50%; | |
| animation: rb-spin 0.9s linear infinite; | |
| } | |
| @keyframes rb-spin { | |
| to { transform: rotate(360deg); } | |
| } | |
| .sim-loading-text { | |
| color: var(--text-secondary); | |
| font-size: var(--text-sm); | |
| font-weight: 500; | |
| letter-spacing: 0.01em; | |
| } | |
| /* Reset-view button - bottom-right of the stage tile, only when sim visible. */ | |
| .sim-reset-btn { | |
| position: absolute; | |
| right: 8px; | |
| bottom: 8px; | |
| width: 28px; | |
| height: 28px; | |
| border-radius: var(--r); | |
| background: rgba(0, 0, 0, 0.55); | |
| border: 1px solid var(--border); | |
| color: var(--text-secondary); | |
| cursor: pointer; | |
| font-size: 14px; | |
| line-height: 1; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| transition: color var(--t-fast) var(--ease), border-color var(--t-fast) var(--ease); | |
| z-index: 4; | |
| backdrop-filter: blur(6px); | |
| } | |
| .sim-reset-btn:hover { color: var(--text); border-color: var(--border-strong); } | |
| .stage-video-wrap.streaming .sim-reset-btn { display: none; } | |
| .stage-overlay-top { | |
| position: absolute; | |
| inset: 0 0 auto 0; | |
| padding: var(--s-3); | |
| background: linear-gradient(to bottom, rgba(0,0,0,0.6), transparent); | |
| display: flex; | |
| align-items: flex-start; | |
| justify-content: space-between; | |
| gap: var(--s-2); | |
| } | |
| .connection-badge { | |
| height: 26px; | |
| display: inline-flex; | |
| align-items: center; | |
| gap: var(--s-2); | |
| padding: 0 10px; | |
| background: rgba(0,0,0,0.55); | |
| backdrop-filter: blur(8px); | |
| border-radius: 999px; | |
| font: 500 var(--text-sm) / 1 var(--font-sans); | |
| color: var(--text); | |
| } | |
| .status-indicator { | |
| width: 6px; | |
| height: 6px; | |
| border-radius: 50%; | |
| background: var(--text-muted); | |
| transition: background var(--t-med) var(--ease), box-shadow var(--t-med) var(--ease); | |
| } | |
| .status-indicator.connected { | |
| background: var(--success); | |
| box-shadow: 0 0 8px var(--success); | |
| } | |
| .status-indicator.connecting { | |
| background: var(--warning); | |
| animation: pulse 1.2s ease-in-out infinite; | |
| } | |
| @keyframes pulse { | |
| 0%, 100% { opacity: 1; } | |
| 50% { opacity: 0.45; } | |
| } | |
| .robot-name { | |
| height: 26px; | |
| display: inline-flex; | |
| align-items: center; | |
| padding: 0 10px; | |
| background: rgba(0,0,0,0.55); | |
| backdrop-filter: blur(8px); | |
| border-radius: 999px; | |
| font: 500 var(--text-sm) / 1 var(--font-sans); | |
| color: var(--text); | |
| } | |
| .stage-controls, | |
| .stage-runrow { | |
| display: flex; | |
| gap: var(--s-2); | |
| } | |
| .stage-controls .btn, | |
| .stage-runrow .btn { | |
| flex: 1; | |
| min-width: 0; | |
| } | |
| .btn-run, .btn-stop { | |
| height: 44px; /* slightly taller - primary call-to-action */ | |
| font-size: var(--text-md); | |
| } | |
| /* ========================================================================= | |
| Stage sections (run status, robot picker) | |
| ========================================================================= */ | |
| .stage-section { | |
| background: var(--panel); | |
| border: 1px solid var(--border); | |
| border-radius: var(--r-lg); | |
| padding: var(--s-3) var(--s-4); | |
| } | |
| .stage-section-header { | |
| font: 600 11px / 1 var(--font-sans); | |
| color: var(--text-muted); | |
| text-transform: uppercase; | |
| letter-spacing: 0.08em; | |
| margin-bottom: var(--s-2); | |
| display: flex; | |
| align-items: center; | |
| gap: 6px; | |
| } | |
| .stage-section-header .stage-section-sub { | |
| font-weight: 500; | |
| text-transform: none; | |
| letter-spacing: 0; | |
| color: var(--text-muted); | |
| background: var(--panel-input); | |
| border: 1px solid var(--border); | |
| padding: 1px 6px; | |
| border-radius: 999px; | |
| font-size: 10px; | |
| } | |
| .stage-section-note { | |
| margin-top: var(--s-2); | |
| color: var(--text-muted); | |
| font-size: var(--text-sm); | |
| line-height: 1.45; | |
| } | |
| /* Push Run Status to the bottom of the stage column. */ | |
| .stage-section-runstatus { | |
| margin-top: auto; | |
| } | |
| /* "AI in use" summary card - shows current chat / listen / speak | |
| service + model so the user always knows what's about to run. */ | |
| .run-models { | |
| background: var(--panel); | |
| border: 1px solid var(--border); | |
| border-radius: var(--r); | |
| padding: var(--s-2) var(--s-3); | |
| display: flex; | |
| flex-direction: column; | |
| gap: 6px; | |
| } | |
| .run-models-title { | |
| font: 700 11px / 1 var(--font-sans); | |
| letter-spacing: 0.08em; | |
| text-transform: uppercase; | |
| color: var(--text-muted); | |
| } | |
| .run-models-list { | |
| list-style: none; | |
| padding: 0; | |
| margin: 0; | |
| display: flex; | |
| flex-direction: column; | |
| gap: 4px; | |
| } | |
| .run-models-list li { | |
| display: grid; | |
| grid-template-columns: 18px 50px 1fr auto; | |
| align-items: baseline; | |
| gap: 8px; | |
| font: 500 var(--text-sm) / 1.4 var(--font-sans); | |
| color: var(--text-secondary); | |
| } | |
| .run-models-icon { | |
| font-size: 13px; | |
| text-align: center; | |
| } | |
| .run-models-label { | |
| color: var(--text); | |
| font-weight: 600; | |
| } | |
| .run-models-provider { | |
| color: var(--text-secondary); | |
| } | |
| .run-models-model { | |
| color: var(--text-muted); | |
| font-family: var(--font-mono); | |
| font-size: 11px; | |
| text-align: right; | |
| white-space: nowrap; | |
| overflow: hidden; | |
| text-overflow: ellipsis; | |
| max-width: 180px; | |
| } | |
| /* Inline warning banner shown above Run Status when the program uses | |
| blocks (askAI / say / listen) whose required API key isn't set. */ | |
| .run-warning { | |
| background: color-mix(in srgb, #F59E0B 14%, var(--panel)); | |
| border: 1px solid color-mix(in srgb, #F59E0B 45%, var(--border)); | |
| border-radius: var(--r); | |
| padding: var(--s-3); | |
| display: flex; | |
| flex-direction: column; | |
| gap: var(--s-2); | |
| } | |
| .run-warning-head { | |
| display: flex; | |
| align-items: center; | |
| gap: 8px; | |
| } | |
| .run-warning-icon { | |
| width: 18px; | |
| height: 18px; | |
| border-radius: 50%; | |
| background: #F59E0B; | |
| color: #1B1D22; | |
| font: 800 12px / 18px var(--font-sans); | |
| text-align: center; | |
| flex-shrink: 0; | |
| } | |
| .run-warning-title { | |
| color: #FCD34D; | |
| font: 700 var(--text-base) / 1.3 var(--font-sans); | |
| } | |
| .run-warning-list { | |
| list-style: none; | |
| padding: 0; | |
| margin: 0; | |
| display: flex; | |
| flex-direction: column; | |
| gap: 6px; | |
| } | |
| .run-warning-list li { | |
| position: relative; | |
| padding-left: 14px; | |
| color: var(--text-secondary); | |
| font: 500 var(--text-sm) / 1.45 var(--font-sans); | |
| } | |
| .run-warning-list li::before { | |
| content: ''; | |
| position: absolute; | |
| left: 2px; | |
| top: 8px; | |
| width: 5px; | |
| height: 5px; | |
| border-radius: 50%; | |
| background: #F59E0B; | |
| } | |
| .run-warning-item-label { | |
| color: var(--text); | |
| font-weight: 700; | |
| } | |
| .run-warning-item-detail { | |
| color: var(--text-secondary); | |
| } | |
| .run-warning-btn { | |
| align-self: flex-start; | |
| margin-top: 2px; | |
| height: 28px; | |
| padding: 0 12px; | |
| border-radius: var(--r-sm); | |
| background: #F59E0B; | |
| color: #1B1D22; | |
| border: 0; | |
| font: 700 var(--text-sm) / 1 var(--font-sans); | |
| cursor: pointer; | |
| white-space: nowrap; | |
| } | |
| .run-warning-btn:hover { filter: brightness(1.1); } | |
| .run-status-text { | |
| color: var(--text-secondary); | |
| font-size: var(--text-base); | |
| line-height: 1.45; | |
| word-break: break-word; | |
| font-family: var(--font-mono); | |
| } | |
| .robot-list { | |
| display: flex; | |
| flex-direction: column; | |
| gap: var(--s-2); | |
| } | |
| .robot-card { | |
| padding: var(--s-3) var(--s-3); | |
| background: var(--panel-input); | |
| border: 1px solid var(--border); | |
| border-radius: var(--r); | |
| cursor: pointer; | |
| transition: border-color var(--t-fast) var(--ease), background var(--t-fast) var(--ease); | |
| } | |
| .robot-card:hover { background: var(--panel-elevated); border-color: var(--border-strong); } | |
| .robot-card.selected { | |
| border-color: var(--accent); | |
| background: var(--accent-dim); | |
| } | |
| .robot-card .name { | |
| font-weight: 600; | |
| font-size: var(--text-base); | |
| color: var(--text); | |
| } | |
| .robot-card .id { | |
| font-size: var(--text-xs); | |
| color: var(--text-muted); | |
| font-family: var(--font-mono); | |
| margin-top: 2px; | |
| } | |
| /* ========================================================================= | |
| Modals (Settings, Share) | |
| ========================================================================= */ | |
| .modal { | |
| position: fixed; | |
| inset: 0; | |
| background: rgba(8, 9, 11, 0.6); | |
| backdrop-filter: blur(6px); | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| z-index: 100; | |
| padding: var(--s-4); | |
| animation: modalFade var(--t-med) var(--ease); | |
| } | |
| @keyframes modalFade { | |
| from { opacity: 0; } | |
| to { opacity: 1; } | |
| } | |
| .modal-card { | |
| background: var(--panel); | |
| border: 1px solid var(--border); | |
| border-radius: var(--r-xl); | |
| padding: var(--s-6); | |
| max-width: 460px; | |
| width: 100%; | |
| display: flex; | |
| flex-direction: column; | |
| gap: var(--s-3); | |
| box-shadow: var(--shadow-lg); | |
| animation: modalRise var(--t-med) var(--ease); | |
| } | |
| @keyframes modalRise { | |
| from { transform: translateY(8px); opacity: 0; } | |
| to { transform: translateY(0); opacity: 1; } | |
| } | |
| .modal-card h3 { | |
| color: var(--text); | |
| font-size: var(--text-lg); | |
| font-weight: 700; | |
| letter-spacing: -0.01em; | |
| margin: 0; | |
| } | |
| .settings-label { | |
| display: block; | |
| color: var(--text-muted); | |
| font-size: 10px; | |
| font-weight: 700; | |
| text-transform: uppercase; | |
| letter-spacing: 0.09em; | |
| margin: 0 0 6px; | |
| } | |
| .settings-input { | |
| display: block; | |
| width: 100%; | |
| height: 38px; | |
| padding: 0 12px; | |
| background: var(--panel-input); | |
| border: 1px solid var(--border); | |
| border-radius: var(--r); | |
| color: var(--text); | |
| font-size: var(--text-md); | |
| font-family: var(--font-mono); | |
| transition: border-color var(--t-fast) var(--ease), box-shadow var(--t-fast) var(--ease); | |
| } | |
| .settings-input:focus { | |
| outline: none; | |
| border-color: var(--accent); | |
| box-shadow: 0 0 0 3px var(--accent-ring); | |
| } | |
| /* Wider Settings modal - two sections now (AI + TTS provider). */ | |
| .settings-modal-card { | |
| max-width: 540px; | |
| max-height: 86vh; | |
| padding: 0; | |
| overflow: hidden; /* inner sections scroll, header/footer sticky */ | |
| gap: 0; | |
| } | |
| .settings-modal-card h3 { | |
| padding: var(--s-4) var(--s-5) 0; | |
| } | |
| .settings-blurb { | |
| padding: 0 var(--s-5); | |
| color: var(--text-secondary); | |
| font-size: var(--text-sm); | |
| line-height: 1.5; | |
| } | |
| /* Scrollable content region. Header (h3 + blurb) and footer (buttons) sit | |
| outside this so they're always visible while the form scrolls. */ | |
| .settings-body { | |
| flex: 1 1 auto; | |
| overflow-y: auto; | |
| padding: var(--s-4) var(--s-5); | |
| display: flex; | |
| flex-direction: column; | |
| gap: var(--s-4); | |
| } | |
| .settings-section { | |
| background: var(--panel-input); | |
| border: 1px solid var(--border-strong); | |
| border-radius: var(--r-lg); | |
| padding: var(--s-4); | |
| display: flex; | |
| flex-direction: column; | |
| gap: var(--s-3); | |
| } | |
| .settings-section-title { | |
| display: flex; | |
| flex-direction: column; | |
| gap: 2px; | |
| margin-bottom: var(--s-1); | |
| } | |
| .settings-section-title-main { | |
| font: 700 var(--text-lg) / 1.2 var(--font-sans); | |
| color: var(--text); | |
| letter-spacing: -0.015em; | |
| } | |
| .settings-section-sub { | |
| display: block; | |
| color: var(--text-muted); | |
| font-weight: 400; | |
| font-size: var(--text-sm); | |
| letter-spacing: 0; | |
| } | |
| .settings-label-sub { | |
| color: var(--text-muted); | |
| font-weight: 400; | |
| font-size: 10px; | |
| text-transform: none; | |
| letter-spacing: 0; | |
| margin-left: 4px; | |
| } | |
| /* A label + control pair, vertically stacked. */ | |
| .settings-field { display: flex; flex-direction: column; } | |
| .settings-info { | |
| background: var(--panel); | |
| border: 1px solid var(--border); | |
| border-left: 3px solid var(--accent); | |
| border-radius: var(--r); | |
| padding: var(--s-3); | |
| color: var(--text-secondary); | |
| font-size: var(--text-sm); | |
| line-height: 1.55; | |
| } | |
| .settings-info a { | |
| color: var(--accent); | |
| text-decoration: underline; | |
| text-underline-offset: 2px; | |
| } | |
| .settings-info a:hover { color: var(--accent-hover); } | |
| .settings-info code { | |
| background: var(--panel-elevated); | |
| padding: 1px 5px; | |
| border-radius: 3px; | |
| font-family: var(--font-mono); | |
| font-size: 0.9em; | |
| color: var(--text); | |
| } | |
| .settings-select { | |
| display: block; | |
| width: 100%; | |
| height: 38px; | |
| padding: 0 36px 0 12px; | |
| background-color: var(--panel-input); | |
| border: 1px solid var(--border); | |
| border-radius: var(--r); | |
| color: var(--text); | |
| font-size: var(--text-md); | |
| font-family: var(--font-sans); | |
| cursor: pointer; | |
| -webkit-appearance: none; | |
| -moz-appearance: none; | |
| appearance: none; | |
| background-image: | |
| linear-gradient(45deg, transparent 50%, var(--text-secondary) 50%), | |
| linear-gradient(135deg, var(--text-secondary) 50%, transparent 50%); | |
| background-position: right 16px top 17px, right 11px top 17px; | |
| background-size: 5px 5px, 5px 5px; | |
| background-repeat: no-repeat; | |
| } | |
| .settings-select:focus { | |
| outline: none; | |
| border-color: var(--accent); | |
| box-shadow: 0 0 0 3px var(--accent-ring); | |
| } | |
| .settings-select option { | |
| background: var(--panel); | |
| color: var(--text); | |
| } | |
| .settings-footer { | |
| flex: 0 0 auto; | |
| display: flex; | |
| gap: var(--s-2); | |
| padding: var(--s-3) var(--s-5) var(--s-4); | |
| border-top: 1px solid var(--border); | |
| background: var(--panel); | |
| } | |
| .settings-footer .btn { flex: 1; } | |
| .settings-footer .btn-secondary.btn-close { flex: 0 0 auto; padding: 0 var(--s-4); } | |
| #settingsStatus { padding: 0 var(--s-5) var(--s-1); margin-top: var(--s-1); } | |
| .share-textarea { | |
| width: 100%; | |
| padding: 10px 12px; | |
| background: var(--panel-input); | |
| border: 1px solid var(--border); | |
| border-radius: var(--r); | |
| color: var(--text); | |
| font-size: var(--text-sm); | |
| font-family: var(--font-mono); | |
| min-height: 110px; | |
| max-height: 240px; | |
| resize: vertical; | |
| line-height: 1.5; | |
| transition: border-color var(--t-fast) var(--ease); | |
| } | |
| .share-textarea:focus { | |
| outline: none; | |
| border-color: var(--accent); | |
| box-shadow: 0 0 0 3px var(--accent-ring); | |
| } | |
| /* ========================================================================= | |
| Utilities | |
| ========================================================================= */ | |
| .hidden { display: none ; } | |
| /* ========================================================================= | |
| Blockly theme overrides - pull the workspace into our visual world | |
| ========================================================================= */ | |
| .blocklyMainBackground { | |
| fill: var(--panel) ; | |
| stroke: var(--border) ; | |
| stroke-width: 0 ; | |
| } | |
| /* We use a flat flyoutToolbox (no category UI), so any leftover | |
| toolbox container Blockly emits should be hidden. CSS belt-and-braces. */ | |
| .blocklyToolboxDiv { | |
| display: none ; | |
| } | |
| /* Style the always-open flyout as a permanent panel - a distinct lighter | |
| strip between the sidebar and the workspace, à la Scratch 1.4. */ | |
| .blocklyFlyout .blocklyFlyoutBackground { | |
| fill: var(--panel-elevated) ; | |
| fill-opacity: 1 ; | |
| stroke: var(--border-strong) ; | |
| stroke-width: 0 ; | |
| } | |
| /* Width clipping for wide flyout blocks is handled by Blockly itself | |
| once flyout.getWidth() is locked in JS (see initBlockly). No CSS | |
| overrides on .blocklyFlyout - they interact poorly with SVG. */ | |
| /* Per-category color tokens - used by .rb-pill (bead + selected state) | |
| and any future styling that needs the matching hue. */ | |
| .rb-cat-motion { --cat-color: hsl(216, 90%, 60%); } | |
| .rb-cat-sound { --cat-color: hsl(330, 80%, 62%); } | |
| .rb-cat-ai { --cat-color: hsl(265, 75%, 65%); } | |
| .rb-cat-sensing { --cat-color: hsl(190, 75%, 55%); } | |
| .rb-cat-control { --cat-color: hsl( 30, 90%, 58%); } | |
| .rb-cat-math { --cat-color: hsl(160, 70%, 50%); } | |
| .rb-cat-text { --cat-color: hsl( 50, 90%, 55%); } | |
| .rb-cat-variables { --cat-color: hsl( 5, 80%, 60%); } | |
| .rb-cat-functions { --cat-color: hsl(290, 70%, 65%); } | |
| /* (duplicate flyout-bg rule removed - see earlier .blocklyFlyout block) */ | |
| .blocklyFlyoutScrollbar .blocklyScrollbarHandle { | |
| fill: var(--border-strong) ; | |
| } | |
| /* Block text */ | |
| .blocklyText { | |
| fill: #fff; | |
| font-family: var(--font-sans) ; | |
| } | |
| /* Trashcan (Blockly's built-in - we keep this one because it has | |
| drag-drop-to-delete behavior tied to Blockly internals). Bump | |
| the default visibility so it matches our custom controls. */ | |
| .blocklyTrash > image { | |
| opacity: 0.7; | |
| transition: opacity var(--t-fast) var(--ease), transform var(--t-fast) var(--ease); | |
| filter: drop-shadow(0 2px 4px rgba(0, 0, 0, 0.45)); | |
| } | |
| .blocklyTrash:hover > image { | |
| opacity: 1; | |
| transform: scale(1.06); | |
| } | |
| /* Custom zoom controls — small floating group bottom-right of workspace. */ | |
| .rb-zoom-controls { | |
| position: absolute; | |
| right: 12px; | |
| bottom: 14px; | |
| display: flex; | |
| flex-direction: column; | |
| gap: 2px; | |
| padding: 3px; | |
| background: rgba(19, 20, 24, 0.85); | |
| border: 1px solid var(--border-strong); | |
| border-radius: var(--r); | |
| backdrop-filter: blur(6px); | |
| box-shadow: var(--shadow); | |
| z-index: 4; | |
| } | |
| .rb-zoom-btn { | |
| width: 30px; | |
| height: 30px; | |
| background: transparent; | |
| border: 0; | |
| border-radius: var(--r-sm); | |
| color: var(--text-secondary); | |
| cursor: pointer; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| transition: background var(--t-fast) var(--ease), color var(--t-fast) var(--ease); | |
| padding: 0; | |
| } | |
| .rb-zoom-btn:hover { background: var(--panel-elevated); color: var(--text); } | |
| .rb-zoom-btn:active { background: var(--border); } | |
| .rb-zoom-btn svg { display: block; } | |
| /* Trash variant - shifts to danger tint on hover so the destructive | |
| action telegraphs itself before click. */ | |
| .rb-zoom-btn-danger:hover { | |
| background: var(--danger-dim); | |
| color: var(--danger); | |
| } | |
| /* Workspace scrollbars */ | |
| .blocklyScrollbarHandle { | |
| fill: var(--border) ; | |
| } | |
| /* ========================================================================= | |
| Responsive - narrow viewports stack stage above workspace | |
| ========================================================================= */ | |
| @media (max-width: 820px) { | |
| .rb-container { | |
| grid-template-columns: 1fr; | |
| grid-template-rows: auto 1fr; | |
| height: auto; | |
| min-height: 100vh; | |
| min-height: 100dvh; | |
| } | |
| .rb-stage { order: -1; max-height: 50vh; } | |
| .rb-workspace { min-height: 60vh; } | |
| .header { padding: 0 var(--s-3); } | |
| .logo-text span { display: none; } | |
| } | |