/* ========================================================================= 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% !important; height: 100% !important; 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 !important; } /* ========================================================================= Blockly theme overrides - pull the workspace into our visual world ========================================================================= */ .blocklyMainBackground { fill: var(--panel) !important; stroke: var(--border) !important; stroke-width: 0 !important; } /* 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 !important; } /* 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) !important; fill-opacity: 1 !important; stroke: var(--border-strong) !important; stroke-width: 0 !important; } /* 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) !important; } /* Block text */ .blocklyText { fill: #fff; font-family: var(--font-sans) !important; } /* 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) !important; } /* ========================================================================= 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; } }