mirror of
https://github.com/openswarm-ai/openswarm.git
synced 2026-09-07 18:27:45 +02:00
[eric] onboarding: small-detail pass (themed checkbox, chunky torn edge, themed stage, real traffic lights, crossfade transitions)
This commit is contained in:
@@ -55,7 +55,7 @@ const IntroBeat: React.FC<{ c: ClaudeTokens; line: string; sub?: string; onNext:
|
||||
<div style={{ position: 'relative', width: '100%', height: '100%', display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', background: c.bg.inverse, overflow: 'hidden' }}>
|
||||
<motion.div
|
||||
initial={{ scale: 0.35, opacity: 0, x: 0, y: 0 }}
|
||||
animate={{ scale: [0.35, 1, 1.06, 1], opacity: [0, 0.55, 0.5, 0.55], x: [0, 0, 22, 0], y: [0, 0, -14, 0] }}
|
||||
animate={{ scale: [0.35, 1, 1.06, 1], opacity: [0, 0.62, 0.56, 0.62], x: [0, 0, 22, 0], y: [0, 0, -14, 0] }}
|
||||
transition={{ duration: 9, times: [0, 0.16, 0.6, 1], ease: 'easeInOut' }}
|
||||
style={{
|
||||
position: 'absolute', width: 560, height: 560, borderRadius: 999,
|
||||
@@ -173,14 +173,14 @@ const OnboardingV3Root: React.FC = () => {
|
||||
background: c.bg.page,
|
||||
}}
|
||||
>
|
||||
<AnimatePresence mode="wait">
|
||||
<AnimatePresence initial={false}>
|
||||
<motion.div
|
||||
key={beat}
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
exit={{ opacity: 0 }}
|
||||
transition={{ duration: 0.32 }}
|
||||
style={{ width: '100%', height: '100%' }}
|
||||
transition={{ duration: 0.35, ease: 'easeInOut' }}
|
||||
style={{ position: 'absolute', inset: 0 }}
|
||||
>
|
||||
{beat === 'welcome' && <IntroBeat c={c} line="Welcome." onNext={() => setBeat('newos')} />}
|
||||
{beat === 'newos' && <IntroBeat c={c} line="This is your new OS." onNext={() => setBeat('connect')} />}
|
||||
@@ -206,8 +206,8 @@ const OnboardingV3Root: React.FC = () => {
|
||||
transition={{ duration: 0.3 }}
|
||||
style={{ position: 'absolute', top: 13, left: 14, display: 'flex', gap: 7, pointerEvents: 'none' }}
|
||||
>
|
||||
{[0, 1, 2].map((i) => (
|
||||
<span key={i} style={{ width: 11, height: 11, borderRadius: 999, background: 'rgba(255,255,255,0.22)' }} />
|
||||
{['#FF5F57', '#FEBC2E', '#28C840'].map((dot) => (
|
||||
<span key={dot} style={{ width: 11, height: 11, borderRadius: 999, background: dot, opacity: 0.9 }} />
|
||||
))}
|
||||
</motion.div>
|
||||
{finishing && (
|
||||
|
||||
@@ -43,8 +43,8 @@ const BeatApps: React.FC<{
|
||||
}}
|
||||
>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 7, padding: '9px 12px', borderBottom: `1px solid ${c.border.subtle}`, background: c.bg.elevated }}>
|
||||
{[0, 1, 2].map((i) => (
|
||||
<span key={i} style={{ width: 10, height: 10, borderRadius: 999, background: c.border.strong }} />
|
||||
{['#FF5F57', '#FEBC2E', '#28C840'].map((dot) => (
|
||||
<span key={dot} style={{ width: 10, height: 10, borderRadius: 999, background: dot, opacity: 0.85 }} />
|
||||
))}
|
||||
<span style={{ marginLeft: 8, fontSize: '0.72rem', fontWeight: 600, color: c.text.tertiary, letterSpacing: '0.04em' }}>OpenSwarm</span>
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { motion } from 'framer-motion';
|
||||
import { Check } from 'lucide-react';
|
||||
import { useAppDispatch, useAppSelector } from '@/shared/hooks';
|
||||
import { API_BASE } from '@/shared/config';
|
||||
import { fetchModels } from '@/shared/state/modelsSlice';
|
||||
@@ -101,7 +102,6 @@ const BeatConnect: React.FC<{
|
||||
display: 'flex', alignItems: 'center', gap: 14, padding: '13px 14px 13px 16px', textAlign: 'left',
|
||||
borderRadius: c.radius.md, border: `1px solid ${filled ? c.accent.primary : c.border.medium}`,
|
||||
background: c.bg.surface, cursor: connected ? 'default' : 'pointer', fontFamily: 'inherit',
|
||||
boxShadow: c.shadow.sm,
|
||||
}}
|
||||
>
|
||||
<span style={{
|
||||
@@ -135,7 +135,18 @@ const BeatConnect: React.FC<{
|
||||
</motion.div>
|
||||
)}
|
||||
<label style={{ display: 'flex', alignItems: 'flex-start', gap: 9, marginTop: 8, cursor: 'pointer', fontSize: '0.82rem', color: c.text.tertiary, lineHeight: 1.5 }}>
|
||||
<input type="checkbox" checked={scanConsent} onChange={(e) => setScanConsent(e.target.checked)} style={{ marginTop: 2 }} />
|
||||
<button
|
||||
onClick={(e) => { e.preventDefault(); setScanConsent(!scanConsent); }}
|
||||
style={{
|
||||
width: 16, height: 16, marginTop: 2, borderRadius: 5, padding: 0, cursor: 'pointer', flexShrink: 0,
|
||||
border: `1.5px solid ${scanConsent ? c.accent.primary : c.border.strong}`,
|
||||
background: scanConsent ? c.accent.primary : 'transparent',
|
||||
display: 'flex', alignItems: 'center', justifyContent: 'center',
|
||||
transition: 'background 120ms ease, border-color 120ms ease',
|
||||
}}
|
||||
>
|
||||
{scanConsent && <Check size={11} color="#fff" strokeWidth={3.2} />}
|
||||
</button>
|
||||
<span>Take a quick local look around to personalize my setup. Nothing leaves this Mac.</span>
|
||||
</label>
|
||||
<div style={{ display: 'flex', gap: 18, marginTop: 2 }}>
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { motion } from 'framer-motion';
|
||||
import { ArrowLeft } from 'lucide-react';
|
||||
import { useThemeAccent } from '@/shared/styles/ThemeContext';
|
||||
import type { ClaudeTokens } from '@/shared/styles/claudeTokens';
|
||||
|
||||
// Film grain as a data URI so the CSP never phones out; opacity keeps it a texture, not noise.
|
||||
export const GRAIN_URL = "url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2'/%3E%3C/filter%3E%3Crect width='140' height='140' filter='url(%23n)' opacity='0.5'/%3E%3C/svg%3E\")";
|
||||
|
||||
// Arc's torn seam: the dark copy panel ends in a zigzag bite instead of a straight border.
|
||||
const ZIGZAG_CLIP = `polygon(0 0, 100% 0, ${Array.from({ length: 50 }, (unused, i) => `calc(100% - 6px) ${i * 2 + 1}%, 100% ${i * 2 + 2}%`).join(', ')}, 0 100%)`;
|
||||
const ZIGZAG_CLIP = `polygon(0 0, 100% 0, ${Array.from({ length: 30 }, (unused, i) => `calc(100% - 12px) ${i * 3.33 + 1.66}%, 100% ${i * 3.33 + 3.33}%`).join(', ')}, 0 100%)`;
|
||||
|
||||
const SPRING = { type: 'spring' as const, stiffness: 260, damping: 26 };
|
||||
|
||||
@@ -22,6 +23,10 @@ const BeatShell: React.FC<{
|
||||
onBack?: () => void;
|
||||
children: React.ReactNode;
|
||||
}> = ({ c, title, body, nextLabel, nextDisabled, onNext, onBack, children }) => {
|
||||
// Arc's post-theme screens wear the user's color everywhere; the stage drinks the picked stops (or accent) instead of staying flat paper.
|
||||
const { accent, gradient } = useThemeAccent();
|
||||
const stops = gradient ?? (accent ? [accent] : [c.accent.primary]);
|
||||
const stageWash = `linear-gradient(115deg, ${stops.map((hex, i) => `${hex}2e ${stops.length > 1 ? (i / (stops.length - 1)) * 100 : 100}%`).join(', ')}), ${c.bg.secondary}`;
|
||||
// Zen steal: controls stay inert until the entrance lands so a double-click from the prior beat can't fire them.
|
||||
const [armed, setArmed] = useState(false);
|
||||
useEffect(() => {
|
||||
@@ -87,8 +92,10 @@ const BeatShell: React.FC<{
|
||||
style={{
|
||||
position: 'relative', flex: 1, minWidth: 0, display: 'flex', alignItems: 'center', justifyContent: 'center',
|
||||
padding: 36, boxSizing: 'border-box', overflow: 'auto',
|
||||
background: stageWash,
|
||||
}}
|
||||
>
|
||||
<div style={{ position: 'absolute', inset: 0, backgroundImage: GRAIN_URL, opacity: 0.08, pointerEvents: 'none' }} />
|
||||
<div style={{ position: 'relative', width: '100%', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
|
||||
{children}
|
||||
</div>
|
||||
|
||||
@@ -224,7 +224,7 @@ const DashboardCanvas: React.FC<DashboardCanvasProps> = ({
|
||||
position: 'absolute',
|
||||
inset: 0,
|
||||
pointerEvents: 'none',
|
||||
background: `linear-gradient(115deg, ${gradient.map((hex, i) => `${hex}24 ${(i / (gradient.length - 1)) * 100}%`).join(', ')})`,
|
||||
background: `linear-gradient(115deg, ${gradient.map((hex, i) => `${hex}2b ${(i / (gradient.length - 1)) * 100}%`).join(', ')})`,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user