1644 lines
49 KiB
CSS
1644 lines
49 KiB
CSS
body { font-family: 'Charter', 'Georgia', serif; -webkit-font-smoothing: antialiased; }
|
||
|
||
/* ============================================================ */
|
||
/* Hero Demo — first-screen live simulator */
|
||
/* Pre-boot: black stage. Post-boot: paper-light reveal. */
|
||
/* ============================================================ */
|
||
.hero-demo {
|
||
position: relative;
|
||
min-height: 100svh;
|
||
padding: 32px 24px 28px;
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
overflow: hidden;
|
||
/* Underlying paper-light surface, revealed once is-booted fades the overlay */
|
||
background: linear-gradient(180deg, #fafaf9 0%, #ffffff 60%);
|
||
color: #1c1917;
|
||
}
|
||
/* Black hero overlay covers the whole stage pre-boot, fades on boot */
|
||
.hero-demo::before {
|
||
content: '';
|
||
position: absolute;
|
||
inset: 0;
|
||
background:
|
||
radial-gradient(ellipse 78% 60% at 50% 48%, #15151a 0%, #08080b 58%, #000 100%);
|
||
z-index: 0;
|
||
pointer-events: none;
|
||
transition: opacity 720ms cubic-bezier(0.4, 0, 0.2, 1);
|
||
}
|
||
.hero-demo::after {
|
||
content: '';
|
||
position: absolute;
|
||
inset: 0;
|
||
background: radial-gradient(circle at 50% 50%, transparent 60%, rgba(0,0,0,0.55) 100%);
|
||
z-index: 0;
|
||
pointer-events: none;
|
||
transition: opacity 720ms cubic-bezier(0.4, 0, 0.2, 1);
|
||
}
|
||
.hero-demo.is-booted::before,
|
||
.hero-demo.is-booted::after {
|
||
opacity: 0;
|
||
}
|
||
.hero-demo > * {
|
||
position: relative;
|
||
z-index: 1;
|
||
}
|
||
|
||
/* Phone shell — pre-boot we hide every trace of the device (bezel, side
|
||
buttons, drop shadow, AND the screen + boot-button background) so the
|
||
power glyph floats in a pure black void. After boot the shell fades in
|
||
to give the iframe a real-device feel on the light bg. */
|
||
.hero-demo #demo-frame {
|
||
box-shadow: none;
|
||
transition: box-shadow 540ms cubic-bezier(0.4, 0, 0.2, 1) 200ms,
|
||
background-color 540ms cubic-bezier(0.4, 0, 0.2, 1) 200ms;
|
||
}
|
||
.hero-demo:not(.is-booted) #demo-frame,
|
||
.hero-demo:not(.is-booted) #demo-boot-btn {
|
||
background: transparent !important;
|
||
}
|
||
.hero-demo.is-booted #demo-frame {
|
||
box-shadow:
|
||
0 0 0 2px #16181c,
|
||
0 0 0 3px rgba(255,255,255,0.04),
|
||
0 30px 60px -15px rgba(0,0,0,0.5),
|
||
0 12px 24px -10px rgba(0,0,0,0.3);
|
||
}
|
||
/* Phone visual scale based on viewport HEIGHT — phone is 800px tall and
|
||
nearly fills 13"/14" laptop viewports, clipping the bottom + crashing
|
||
into the SCROLL hint. Scale down visually so it always fits.
|
||
• Layout box stays 800px (iframe needs that as its design viewport)
|
||
• transform: scale doesn't break iframe coords — browsers remap mouse
|
||
events through the transform automatically
|
||
• Anchored at "center top" so the bottom shifts up, NOT the top — the
|
||
SCROLL hint at the viewport bottom gains room.
|
||
• clamp(0.7, …, 1) — scale never above 1 (no upscaling on tall
|
||
monitors) and never below 0.7 (phone stays readable)
|
||
Formula: (100vh - 200px) / 800px, where 200px is the buffer for brand
|
||
above and SCROLL hint below. */
|
||
.hero-demo .demo-phone-wrap {
|
||
--phone-scale: clamp(0.7, calc((100vh - 100px) / 800px), 1);
|
||
transform-origin: center top;
|
||
transform: scale(var(--phone-scale));
|
||
/* Negative margin-bottom compensates for the phantom layout space below
|
||
the phone: scale visually shrinks the phone but the layout box stays
|
||
800px tall, so without this the hero (and any surrounding flex/grid
|
||
centering) reserves 215px of empty space below the visually-smaller
|
||
phone. Pulling the bottom edge up by (1 - scale) * 800px makes the
|
||
layout match what the user sees. */
|
||
margin-bottom: calc((var(--phone-scale) - 1) * 800px);
|
||
transition:
|
||
transform 360ms cubic-bezier(0.4, 0, 0.2, 1),
|
||
margin-bottom 360ms cubic-bezier(0.4, 0, 0.2, 1);
|
||
}
|
||
|
||
.hero-demo .demo-phone-wrap > div > span {
|
||
opacity: 0;
|
||
transition: opacity 540ms cubic-bezier(0.4, 0, 0.2, 1) 200ms;
|
||
}
|
||
.hero-demo.is-booted .demo-phone-wrap > div > span {
|
||
opacity: 1;
|
||
}
|
||
|
||
/* Boot button label — uppercase spaced caption echoing the SCROLL hint
|
||
typography. Avoids the "loud sentence" feel of the original 16px text. */
|
||
.hero-boot-label {
|
||
margin-top: 14px;
|
||
font: 500 10.5px/1 'Inter', system-ui, sans-serif;
|
||
letter-spacing: 0.34em;
|
||
text-transform: uppercase;
|
||
color: rgba(255, 255, 255, 0.42);
|
||
transition: color 240ms ease;
|
||
}
|
||
.hero-demo #demo-boot-btn:hover .hero-boot-label {
|
||
color: rgba(255, 255, 255, 0.78);
|
||
}
|
||
|
||
/* Boot button — breathing aura + pulsing ring, anchored to the inner circle
|
||
(NOT the button itself: flex centering offsets the button's 50% so a
|
||
button-anchored aura would land above the circle). */
|
||
.hero-demo #demo-boot-btn > span:first-child {
|
||
position: relative;
|
||
isolation: isolate;
|
||
}
|
||
.hero-demo #demo-boot-btn > span:first-child::before {
|
||
content: '';
|
||
position: absolute;
|
||
inset: -22px;
|
||
border-radius: 999px;
|
||
background:
|
||
radial-gradient(circle, rgba(255,255,255,0.12) 0%, rgba(255,255,255,0.03) 45%, transparent 72%);
|
||
filter: blur(2px);
|
||
animation: hero-aura 3.4s ease-in-out infinite;
|
||
pointer-events: none;
|
||
z-index: -1;
|
||
}
|
||
.hero-demo #demo-boot-btn > span:first-child::after {
|
||
content: '';
|
||
position: absolute;
|
||
inset: -2px;
|
||
border-radius: 999px;
|
||
border: 1px solid rgba(255, 255, 255, 0.32);
|
||
opacity: 0;
|
||
animation: hero-ring 2.8s ease-out infinite;
|
||
pointer-events: none;
|
||
}
|
||
.hero-demo #demo-boot-btn:hover > span:first-child::before {
|
||
animation-duration: 2.4s;
|
||
}
|
||
|
||
@keyframes hero-aura {
|
||
0%, 100% { transform: scale(0.9); opacity: 0.45; }
|
||
50% { transform: scale(1.2); opacity: 0.95; }
|
||
}
|
||
@keyframes hero-ring {
|
||
0% { transform: scale(0.94); opacity: 0; }
|
||
20% { opacity: 0.55; }
|
||
100% { transform: scale(1.6); opacity: 0; }
|
||
}
|
||
|
||
/* State Builder is no longer a hero card — its pre-boot fade is now
|
||
handled by .state-dock (vertical pill on phone right edge) and
|
||
.state-drawer (fixed right slide-in). See bottom of file. */
|
||
|
||
/* Hero overlay top bar — brand on the left, resource pills + power-off on
|
||
the right. Both groups stay hidden pre-boot (preserving the pure black
|
||
void) and fade in once the simulator is started. */
|
||
.hero-demo-brand,
|
||
.hero-demo-actions {
|
||
position: absolute;
|
||
top: 22px;
|
||
z-index: 3;
|
||
opacity: 0;
|
||
transform: translateY(-4px);
|
||
pointer-events: none;
|
||
transition:
|
||
opacity 540ms cubic-bezier(0.4, 0, 0.2, 1) 380ms,
|
||
transform 540ms cubic-bezier(0.4, 0, 0.2, 1) 380ms;
|
||
}
|
||
.hero-demo.is-booted .hero-demo-brand,
|
||
.hero-demo.is-booted .hero-demo-actions {
|
||
opacity: 1;
|
||
transform: translateY(0);
|
||
}
|
||
.hero-demo.is-booted .hero-demo-actions { pointer-events: auto; }
|
||
|
||
.hero-demo-brand {
|
||
left: 32px;
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 4px;
|
||
}
|
||
.hero-demo-brand-name {
|
||
font: 700 16px/1 'Inter', system-ui, sans-serif;
|
||
letter-spacing: -0.01em;
|
||
color: #1c1917;
|
||
}
|
||
.hero-demo-brand-meta {
|
||
font: 500 9.5px/1 'Inter', system-ui, sans-serif;
|
||
letter-spacing: 0.24em;
|
||
text-transform: uppercase;
|
||
color: #a8a29e;
|
||
}
|
||
|
||
.hero-demo-actions {
|
||
right: 24px;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
}
|
||
/* Top-right utility buttons — arXiv wordmark, GitHub mark, Power off.
|
||
All borderless to match the same low-key visual weight; brand identity
|
||
carries the meaning (arXiv red wordmark, GitHub octocat). Hover reveals
|
||
a subtle background tint, matching .hero-demo-poweroff. */
|
||
.hero-demo-action {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
padding: 6px 10px;
|
||
color: #57534e;
|
||
background: transparent;
|
||
border: 0;
|
||
border-radius: 6px;
|
||
text-decoration: none;
|
||
cursor: pointer;
|
||
transition: color 200ms ease, background 200ms ease;
|
||
}
|
||
.hero-demo-action:hover {
|
||
color: #1c1917;
|
||
background: rgba(28, 25, 23, 0.04);
|
||
}
|
||
.hero-demo-arxiv-mark {
|
||
font: 800 14px/1 'Inter', system-ui, sans-serif;
|
||
letter-spacing: -0.02em;
|
||
color: #B31B1B;
|
||
transition: color 200ms ease;
|
||
}
|
||
.hero-demo-action-arxiv:hover .hero-demo-arxiv-mark {
|
||
color: #8a1414;
|
||
}
|
||
.hero-demo-action-arxiv:hover {
|
||
background: rgba(179, 27, 27, 0.06);
|
||
}
|
||
|
||
/* Power off — same row as Paper/Code, but lighter (no pill outline),
|
||
to keep it as a tertiary action. state-builder.js toggles .hidden /
|
||
.inline-flex on click. */
|
||
.hero-demo-poweroff {
|
||
display: none;
|
||
align-items: center;
|
||
gap: 5px;
|
||
padding: 6px 10px;
|
||
font: 500 12px/1 'Inter', system-ui, sans-serif;
|
||
letter-spacing: 0;
|
||
color: #78716c;
|
||
background: transparent;
|
||
border: 0;
|
||
border-radius: 6px;
|
||
cursor: pointer;
|
||
transition: color 200ms ease, background 200ms ease;
|
||
}
|
||
.hero-demo-poweroff.inline-flex { display: inline-flex; }
|
||
.hero-demo-poweroff:hover {
|
||
color: #1c1917;
|
||
background: rgba(28, 25, 23, 0.04);
|
||
}
|
||
.hero-demo-poweroff svg { opacity: 0.75; transition: opacity 200ms ease; }
|
||
.hero-demo-poweroff:hover svg { opacity: 1; }
|
||
|
||
/* Hero footer — fixed at viewport bottom. Contains the first-load note
|
||
and the SCROLL hint, stacked. Only fades in after power-on. */
|
||
.hero-demo-foot {
|
||
position: fixed;
|
||
/* On short viewports the phone (scaled to fit) ends up close to the
|
||
viewport bottom — keeping the hint at a fixed 20px gap from the edge
|
||
would crash it into the phone. So the hint slides down (closer to the
|
||
edge) as the viewport shrinks, maintaining a constant 8px gap above
|
||
the phone. Math: phone visual bottom + 8 + hint_height + bottom = vp,
|
||
so bottom = vp/2 - 442 in the scaled regime, clamped to [8, 20]. */
|
||
bottom: max(env(safe-area-inset-bottom), clamp(8px, calc(50vh - 442px), 20px));
|
||
left: 50%;
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
gap: 10px;
|
||
z-index: 60;
|
||
opacity: 0;
|
||
transform: translate(-50%, 14px);
|
||
pointer-events: none;
|
||
transition:
|
||
opacity 540ms cubic-bezier(0.4, 0, 0.2, 1) 600ms,
|
||
transform 540ms cubic-bezier(0.4, 0, 0.2, 1) 600ms;
|
||
}
|
||
.hero-demo.is-booted .hero-demo-foot {
|
||
opacity: 1;
|
||
transform: translate(-50%, 0);
|
||
}
|
||
|
||
|
||
/* Scroll hint is now laid out inside .hero-demo-foot (the footer is
|
||
the fixed-positioned container; hint just stacks below the firstload
|
||
note in flex order). */
|
||
.hero-demo-scroll-hint {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
gap: 5px;
|
||
font: 500 10px/1 'Inter', system-ui, sans-serif;
|
||
letter-spacing: 0.3em;
|
||
text-transform: uppercase;
|
||
color: rgba(28, 25, 23, 0.4);
|
||
pointer-events: none;
|
||
margin-top: 2px;
|
||
}
|
||
.hero-demo-scroll-hint svg {
|
||
/* Arrow shrunk so the whole hint takes less vertical room — gives more
|
||
breathing space above the hint without moving the phone. */
|
||
width: 9px;
|
||
height: 11px;
|
||
animation: hero-arrow 2.4s ease-in-out infinite;
|
||
}
|
||
@keyframes hero-arrow {
|
||
0%, 100% { transform: translateY(0); opacity: 0.55; }
|
||
50% { transform: translateY(5px); opacity: 1; }
|
||
}
|
||
/* Once the user starts scrolling, hide the entire footer so it doesn't
|
||
float over the paper content below. */
|
||
body.hero-scrolled .hero-demo-foot {
|
||
opacity: 0 !important;
|
||
pointer-events: none !important;
|
||
transform: translate(-50%, 14px) !important;
|
||
transition-delay: 0ms !important;
|
||
}
|
||
|
||
@media (max-width: 1040px) {
|
||
.hero-demo {
|
||
padding: 28px 16px 24px;
|
||
}
|
||
}
|
||
|
||
@media (prefers-reduced-motion: reduce) {
|
||
.hero-demo::before,
|
||
.hero-demo::after,
|
||
.hero-demo #demo-boot-btn > span:first-child::before,
|
||
.hero-demo #demo-boot-btn > span:first-child::after,
|
||
.hero-demo .state-dock,
|
||
.state-dock-tab[data-state-dock-tab="session"]::before,
|
||
.state-dock-hint,
|
||
.state-drawer,
|
||
.gesture-guide,
|
||
.gesture-guide-icon .gesture-trail,
|
||
.hero-demo-foot,
|
||
.hero-demo-scroll-hint,
|
||
.hero-demo-scroll-hint svg {
|
||
transition: none !important;
|
||
animation: none !important;
|
||
}
|
||
.gesture-guide-icon .gesture-trail {
|
||
stroke-dashoffset: 0 !important;
|
||
opacity: 1 !important;
|
||
}
|
||
}
|
||
|
||
/* Paper header resources row — ghost pill style: subtle stone-100 fill +
|
||
barely-there 1px border + full rounded. Gives the buttons enough visual
|
||
weight to anchor next to the h1 without competing with it. Hover lifts
|
||
1px and snaps to white + darker border for clear feedback. */
|
||
.paper-actions {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 10px;
|
||
}
|
||
.paper-action {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
padding: 9px 16px;
|
||
font: 500 14px/1 'Inter', system-ui, sans-serif;
|
||
letter-spacing: 0;
|
||
color: #44403c; /* stone-700 */
|
||
background: rgba(245, 245, 244, 0.6); /* stone-100 @ 60% */
|
||
border: 1px solid rgba(28, 25, 23, 0.08);
|
||
border-radius: 999px;
|
||
text-decoration: none;
|
||
cursor: pointer;
|
||
transition:
|
||
color 200ms ease,
|
||
background 200ms ease,
|
||
border-color 200ms ease,
|
||
transform 200ms ease;
|
||
}
|
||
.paper-action:hover {
|
||
color: #1c1917;
|
||
background: #ffffff;
|
||
border-color: rgba(28, 25, 23, 0.22);
|
||
transform: translateY(-1px);
|
||
}
|
||
.paper-action-arxiv-mark {
|
||
font: 800 15px/1 'Inter', system-ui, sans-serif;
|
||
letter-spacing: -0.02em;
|
||
color: #B31B1B;
|
||
transition: color 200ms ease;
|
||
}
|
||
.paper-action-arxiv:hover .paper-action-arxiv-mark {
|
||
color: #8a1414;
|
||
}
|
||
|
||
.skip-link {
|
||
position: absolute;
|
||
left: -9999px;
|
||
top: 0;
|
||
z-index: 1000;
|
||
padding: 0.75rem 1.25rem;
|
||
background: #1c1917;
|
||
color: #fff;
|
||
font-family: 'Inter', system-ui, sans-serif;
|
||
font-size: 0.875rem;
|
||
font-weight: 500;
|
||
border-radius: 0 0 0.5rem 0;
|
||
text-decoration: none;
|
||
}
|
||
.skip-link:focus { left: 0; outline: 2px solid #3b82f6; outline-offset: 2px; }
|
||
h1, h2, h3, .ui { font-family: 'Inter', system-ui, sans-serif; }
|
||
code, pre, .mono { font-family: 'JetBrains Mono', 'SF Mono', monospace; }
|
||
.container-narrow { max-width: 880px; }
|
||
.container-demo { max-width: 1120px; }
|
||
.gradient-bg {
|
||
background: linear-gradient(180deg, #fafaf9 0%, #ffffff 60%);
|
||
}
|
||
.stat-num { font-variant-numeric: tabular-nums; }
|
||
details > summary { cursor: pointer; list-style: none; }
|
||
details > summary::-webkit-details-marker { display: none; }
|
||
details[open] summary .marker { transform: rotate(90deg); }
|
||
.marker { transition: transform 0.15s ease; display: inline-block; }
|
||
/* ------------------------------------------------------------ */
|
||
/* Phone is the only visual anchor in hero. State Builder lives */
|
||
/* in a fixed right-side drawer triggered by the dock attached */
|
||
/* to the phone's right edge. */
|
||
/* ------------------------------------------------------------ */
|
||
.demo-layout {
|
||
position: relative;
|
||
display: flex;
|
||
justify-content: center;
|
||
align-items: center;
|
||
}
|
||
/* The phone wrap's inner relative box is the dock anchor — the
|
||
dock is positioned outside the phone but inside this element
|
||
so transform: scale on .demo-phone-wrap scales them together. */
|
||
.phone-rig { overflow: visible; }
|
||
|
||
/* ============================================================ */
|
||
/* State Dock — vertical icon dock attached to phone right edge */
|
||
/* Glass-morphism pill, fades in post-boot, hides when drawer */
|
||
/* is open (drawer overlaps it on common laptop widths). */
|
||
/* ============================================================ */
|
||
.state-dock {
|
||
position: absolute;
|
||
/* Sit 14px to the right of the phone, vertically centered. */
|
||
left: calc(100% + 14px);
|
||
top: 50%;
|
||
transform: translateY(-50%);
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
gap: 8px;
|
||
padding: 14px 7px;
|
||
border-radius: 999px;
|
||
background: rgba(255, 255, 255, 0.74);
|
||
border: 1px solid rgba(28, 25, 23, 0.08);
|
||
box-shadow:
|
||
0 1px 0 rgba(255, 255, 255, 0.7) inset,
|
||
0 12px 32px -10px rgba(15, 23, 42, 0.18),
|
||
0 4px 12px -6px rgba(15, 23, 42, 0.08);
|
||
backdrop-filter: blur(18px) saturate(140%);
|
||
-webkit-backdrop-filter: blur(18px) saturate(140%);
|
||
z-index: 4;
|
||
/* Pre-boot: hidden alongside the rest of the chrome. */
|
||
opacity: 0;
|
||
pointer-events: none;
|
||
transition:
|
||
opacity 540ms cubic-bezier(0.4, 0, 0.2, 1) 320ms,
|
||
transform 540ms cubic-bezier(0.4, 0, 0.2, 1) 320ms;
|
||
}
|
||
.hero-demo.is-booted .state-dock {
|
||
opacity: 1;
|
||
pointer-events: auto;
|
||
transform: translateY(-50%);
|
||
}
|
||
/* When drawer is open, drawer covers the dock's screen real estate
|
||
on common 1280–1600 viewports. Fade dock out so it doesn't poke
|
||
through behind the drawer's left shadow. */
|
||
body[data-state-drawer-open="true"] .state-dock {
|
||
opacity: 0;
|
||
pointer-events: none;
|
||
transform: translate(8px, -50%);
|
||
transition-delay: 0ms;
|
||
}
|
||
|
||
.state-dock-eyebrow {
|
||
writing-mode: vertical-rl;
|
||
text-orientation: mixed;
|
||
/* writing-mode: vertical-rl reads top-to-bottom on the right side
|
||
of its container, which is what we want — small caps stacking
|
||
downward like an Apple sidebar caption. */
|
||
font: 700 8.5px/1 'Inter', system-ui, sans-serif;
|
||
letter-spacing: 0.36em;
|
||
text-transform: uppercase;
|
||
color: rgba(28, 25, 23, 0.45);
|
||
padding: 4px 0 2px;
|
||
user-select: none;
|
||
}
|
||
.state-dock-divider {
|
||
width: 16px;
|
||
height: 1px;
|
||
background: rgba(28, 25, 23, 0.12);
|
||
margin: 2px 0 4px;
|
||
}
|
||
|
||
.state-dock-tab {
|
||
position: relative;
|
||
width: 36px;
|
||
height: 36px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
padding: 0;
|
||
border: 0;
|
||
border-radius: 12px;
|
||
background: transparent;
|
||
color: #57534e;
|
||
cursor: pointer;
|
||
transition:
|
||
background-color 180ms ease,
|
||
color 180ms ease,
|
||
transform 180ms cubic-bezier(0.4, 0, 0.2, 1);
|
||
}
|
||
.state-dock-tab:hover {
|
||
background: rgba(28, 25, 23, 0.05);
|
||
color: #1c1917;
|
||
transform: scale(1.05);
|
||
}
|
||
.state-dock-tab[aria-selected="true"] {
|
||
background: #1c1917;
|
||
color: #ffffff;
|
||
}
|
||
.state-dock-tab[aria-selected="true"] .state-dock-tab-icon-img {
|
||
/* Image-based icons don't respond to currentColor — keep them
|
||
bright on the dark active pill via a touch of contrast lift. */
|
||
filter: brightness(1.05);
|
||
}
|
||
.state-dock-tab:focus-visible {
|
||
outline: 2px solid rgba(37, 99, 235, 0.55);
|
||
outline-offset: 2px;
|
||
}
|
||
.state-dock-tab-icon {
|
||
width: 22px;
|
||
height: 22px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
.state-dock-tab-icon svg { width: 18px; height: 18px; }
|
||
.state-dock-tab-icon-img {
|
||
border-radius: 6px;
|
||
overflow: hidden;
|
||
}
|
||
.state-dock-tab-icon-img img {
|
||
width: 100%;
|
||
height: 100%;
|
||
object-fit: cover;
|
||
display: block;
|
||
}
|
||
|
||
/* Language tab badge — small pill at the corner of the icon showing
|
||
the current phone locale ("中" / "EN") so visitors can read the
|
||
active language without opening the drawer. */
|
||
.state-dock-tab-badge {
|
||
position: absolute;
|
||
bottom: -3px;
|
||
right: -3px;
|
||
min-width: 18px;
|
||
height: 14px;
|
||
padding: 0 4px;
|
||
border-radius: 7px;
|
||
background: #2563eb;
|
||
color: #ffffff;
|
||
font: 700 9px/14px 'Inter', system-ui, sans-serif;
|
||
letter-spacing: 0.04em;
|
||
text-align: center;
|
||
border: 1.5px solid #fafaf9;
|
||
pointer-events: none;
|
||
user-select: none;
|
||
}
|
||
.state-dock-tab[aria-selected="true"] .state-dock-tab-badge {
|
||
border-color: #1c1917;
|
||
}
|
||
|
||
.studio-language-choices {
|
||
display: grid;
|
||
grid-template-columns: 1fr 1fr;
|
||
gap: 8px;
|
||
}
|
||
.studio-language-choice {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 2px;
|
||
padding: 12px;
|
||
border-radius: 10px;
|
||
border: 1px solid rgba(28, 25, 23, 0.12);
|
||
background: #fafaf9;
|
||
text-align: left;
|
||
cursor: pointer;
|
||
transition: border-color 160ms ease, background-color 160ms ease, color 160ms ease;
|
||
}
|
||
.studio-language-choice:hover {
|
||
border-color: rgba(28, 25, 23, 0.25);
|
||
background: #ffffff;
|
||
}
|
||
.studio-language-choice[aria-checked="true"] {
|
||
border-color: #2563eb;
|
||
background: rgba(37, 99, 235, 0.06);
|
||
color: #1e40af;
|
||
}
|
||
.studio-language-choice:focus-visible {
|
||
outline: 2px solid rgba(37, 99, 235, 0.55);
|
||
outline-offset: 2px;
|
||
}
|
||
.studio-language-choice-name {
|
||
font: 600 13px/1.2 'Inter', system-ui, sans-serif;
|
||
}
|
||
.studio-language-choice-meta {
|
||
font-size: 11px;
|
||
color: rgba(28, 25, 23, 0.55);
|
||
}
|
||
.studio-language-choice[aria-checked="true"] .studio-language-choice-meta {
|
||
color: rgba(30, 64, 175, 0.7);
|
||
}
|
||
|
||
/* First-time pulse: the topmost (Session) tab gently pings to draw
|
||
the eye. Fades permanently after first interaction (sessionStorage
|
||
sets body[data-state-dock-touched="true"]). */
|
||
.state-dock-tab[data-state-dock-tab="session"]::before {
|
||
content: '';
|
||
position: absolute;
|
||
inset: -2px;
|
||
border-radius: 14px;
|
||
border: 1.5px solid rgba(37, 99, 235, 0.7);
|
||
opacity: 0;
|
||
pointer-events: none;
|
||
animation: state-dock-ping 2.4s ease-out 0.6s infinite;
|
||
}
|
||
.hero-demo:not(.is-booted) .state-dock-tab[data-state-dock-tab="session"]::before,
|
||
body[data-state-dock-touched="true"] .state-dock-tab[data-state-dock-tab="session"]::before {
|
||
animation: none;
|
||
opacity: 0;
|
||
}
|
||
@keyframes state-dock-ping {
|
||
0% { transform: scale(0.92); opacity: 0; }
|
||
20% { opacity: 0.85; }
|
||
80% { transform: scale(1.45); opacity: 0; }
|
||
100% { transform: scale(1.45); opacity: 0; }
|
||
}
|
||
|
||
/* Callout below the dock pointing UP at it — only visible until
|
||
first interaction. Reinforces "this is clickable" without
|
||
relying solely on the pulse. */
|
||
.state-dock-hint {
|
||
position: absolute;
|
||
/* Anchor under the phone right edge, slightly indented so it
|
||
reads as belonging to the dock column. */
|
||
left: calc(100% + 6px);
|
||
top: calc(100% + 6px);
|
||
width: 80px;
|
||
text-align: left;
|
||
font: 600 10.5px/1.3 'Inter', system-ui, sans-serif;
|
||
letter-spacing: 0.04em;
|
||
color: rgba(37, 99, 235, 0.82);
|
||
white-space: normal;
|
||
opacity: 0;
|
||
pointer-events: none;
|
||
transition: opacity 540ms ease 700ms;
|
||
animation: state-dock-hint-bob 2.4s ease-in-out 1.6s infinite;
|
||
}
|
||
.state-dock-hint::before {
|
||
content: '↑';
|
||
display: inline-block;
|
||
margin-right: 4px;
|
||
font-size: 12px;
|
||
vertical-align: -1px;
|
||
}
|
||
.hero-demo.is-booted .state-dock-hint {
|
||
opacity: 1;
|
||
}
|
||
body[data-state-dock-touched="true"] .state-dock-hint,
|
||
body[data-state-drawer-open="true"] .state-dock-hint {
|
||
opacity: 0;
|
||
animation: none;
|
||
}
|
||
@keyframes state-dock-hint-bob {
|
||
0%, 100% { transform: translateY(0); }
|
||
50% { transform: translateY(-3px); }
|
||
}
|
||
|
||
/* ============================================================ */
|
||
/* Gesture Guide — left-side companion to .state-dock. Static */
|
||
/* legend explaining the three full-screen gestures the */
|
||
/* simulator listens for (edge-swipe back / swipe-up home / */
|
||
/* swipe-up-and-hold recents). Same glass-morphism + fade-in */
|
||
/* timing as the dock so they read as a matched pair. */
|
||
/* ============================================================ */
|
||
.gesture-guide {
|
||
position: absolute;
|
||
/* Mirror of .state-dock: 14px outside the phone's left edge. */
|
||
right: calc(100% + 14px);
|
||
top: 50%;
|
||
transform: translateY(-50%);
|
||
width: 168px;
|
||
padding: 14px 14px 13px;
|
||
border-radius: 20px;
|
||
background: rgba(255, 255, 255, 0.74);
|
||
border: 1px solid rgba(28, 25, 23, 0.08);
|
||
box-shadow:
|
||
0 1px 0 rgba(255, 255, 255, 0.7) inset,
|
||
0 12px 32px -10px rgba(15, 23, 42, 0.18),
|
||
0 4px 12px -6px rgba(15, 23, 42, 0.08);
|
||
backdrop-filter: blur(18px) saturate(140%);
|
||
-webkit-backdrop-filter: blur(18px) saturate(140%);
|
||
z-index: 4;
|
||
opacity: 0;
|
||
/* Buttons inside (.gesture-guide-icon) need clicks. The container
|
||
starts hidden pre-boot — pointer-events is gated by .is-booted
|
||
below so the buttons don't catch ghost clicks while invisible. */
|
||
pointer-events: none;
|
||
transition:
|
||
opacity 540ms cubic-bezier(0.4, 0, 0.2, 1) 320ms,
|
||
transform 540ms cubic-bezier(0.4, 0, 0.2, 1) 320ms;
|
||
}
|
||
.hero-demo.is-booted .gesture-guide {
|
||
opacity: 1;
|
||
pointer-events: auto;
|
||
transform: translateY(-50%);
|
||
}
|
||
|
||
.gesture-guide-head {
|
||
display: flex;
|
||
align-items: baseline;
|
||
justify-content: space-between;
|
||
gap: 8px;
|
||
padding: 0 2px 9px;
|
||
border-bottom: 1px solid rgba(28, 25, 23, 0.07);
|
||
margin-bottom: 9px;
|
||
}
|
||
.gesture-guide-eyebrow {
|
||
font: 700 9.5px/1 'Inter', system-ui, sans-serif;
|
||
letter-spacing: 0.32em;
|
||
text-transform: uppercase;
|
||
color: #1c1917;
|
||
}
|
||
.gesture-guide-meta {
|
||
font: 600 8.5px/1 'Inter', system-ui, sans-serif;
|
||
letter-spacing: 0.18em;
|
||
text-transform: uppercase;
|
||
color: #2563eb;
|
||
}
|
||
|
||
.gesture-guide-list {
|
||
list-style: none;
|
||
margin: 0;
|
||
padding: 0;
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 4px;
|
||
}
|
||
.gesture-guide-list > li { display: block; }
|
||
|
||
/* Each row is now a real <button> — clicking anywhere on the row
|
||
(icon + label) fires the corresponding hardware-key action via
|
||
scripts/boot-hero.js. Reset native chrome, lay out like a flex
|
||
row with the icon on the left. */
|
||
.gesture-guide-item {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 9px;
|
||
width: 100%;
|
||
padding: 5px 6px;
|
||
border: 0;
|
||
border-radius: 10px;
|
||
background: transparent;
|
||
color: inherit;
|
||
cursor: pointer;
|
||
text-align: left;
|
||
font: inherit;
|
||
-webkit-appearance: none;
|
||
appearance: none;
|
||
transition:
|
||
background-color 160ms ease,
|
||
transform 120ms cubic-bezier(0.4, 0, 0.2, 1);
|
||
}
|
||
.gesture-guide-item:hover {
|
||
background: rgba(28, 25, 23, 0.06);
|
||
}
|
||
.gesture-guide-item:hover .gesture-guide-icon {
|
||
border-color: rgba(28, 25, 23, 0.28);
|
||
box-shadow:
|
||
inset 0 1px 0 rgba(255, 255, 255, 0.95),
|
||
0 2px 6px rgba(15, 23, 42, 0.1);
|
||
}
|
||
.gesture-guide-item:hover .gesture-guide-label {
|
||
color: #1c1917;
|
||
}
|
||
.gesture-guide-item:active {
|
||
background: rgba(28, 25, 23, 0.12);
|
||
transform: scale(0.98);
|
||
transition-duration: 60ms;
|
||
}
|
||
.gesture-guide-item:active .gesture-guide-icon {
|
||
background: #1c1917;
|
||
color: #ffffff;
|
||
border-color: #1c1917;
|
||
box-shadow:
|
||
inset 0 1px 2px rgba(0, 0, 0, 0.45),
|
||
0 0 0 transparent;
|
||
}
|
||
.gesture-guide-item:focus-visible {
|
||
outline: 2px solid rgba(37, 99, 235, 0.55);
|
||
outline-offset: 1px;
|
||
}
|
||
|
||
/* Hint-only variant: not a hardware-key button, just a visual reminder
|
||
that the desktop pager responds to horizontal swipe/drag. Suppress
|
||
pointer-affordances (cursor, hover/active background) so it reads as
|
||
informational text rather than an interactive control. */
|
||
.gesture-guide-item--hint {
|
||
cursor: default;
|
||
}
|
||
.gesture-guide-item--hint:hover,
|
||
.gesture-guide-item--hint:active {
|
||
background: transparent;
|
||
transform: none;
|
||
}
|
||
.gesture-guide-item--hint:hover .gesture-guide-icon,
|
||
.gesture-guide-item--hint:active .gesture-guide-icon {
|
||
background: #ffffff;
|
||
color: #1c1917;
|
||
border-color: rgba(28, 25, 23, 0.14);
|
||
box-shadow:
|
||
inset 0 1px 0 rgba(255, 255, 255, 0.95),
|
||
0 1px 2px rgba(15, 23, 42, 0.06);
|
||
}
|
||
.gesture-guide-item--hint:hover .gesture-guide-label {
|
||
color: #1c1917;
|
||
}
|
||
|
||
.gesture-guide-icon {
|
||
flex: 0 0 auto;
|
||
width: 34px;
|
||
height: 34px;
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
border-radius: 9px;
|
||
/* Solid fill + 1px stone border + soft top highlight reads as a
|
||
physical key, not a tag/badge. Matches the existing app icons in
|
||
the simulator below. */
|
||
background: #ffffff;
|
||
border: 1px solid rgba(28, 25, 23, 0.14);
|
||
box-shadow:
|
||
inset 0 1px 0 rgba(255, 255, 255, 0.95),
|
||
0 1px 2px rgba(15, 23, 42, 0.06);
|
||
color: #1c1917;
|
||
transition: background-color 160ms ease, color 160ms ease,
|
||
border-color 160ms ease, box-shadow 160ms ease;
|
||
}
|
||
.gesture-guide-icon svg {
|
||
width: 24px;
|
||
height: 24px;
|
||
display: block;
|
||
}
|
||
.gesture-guide-text {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 1px;
|
||
min-width: 0;
|
||
}
|
||
.gesture-guide-label {
|
||
font: 600 11.5px/1.2 'Inter', system-ui, sans-serif;
|
||
color: #1c1917;
|
||
transition: color 160ms ease;
|
||
}
|
||
.gesture-guide-desc {
|
||
font: 400 10.5px/1.35 'Inter', system-ui, sans-serif;
|
||
color: #78716c;
|
||
}
|
||
|
||
/* Stroke trails inside each gesture icon — repeatedly "draw" the
|
||
swipe path so the SVG reads as motion, not a static glyph. The
|
||
three items are staggered so the page doesn't pulse in unison. */
|
||
.gesture-guide-icon .gesture-trail {
|
||
stroke-dasharray: 22;
|
||
stroke-dashoffset: 22;
|
||
animation: gesture-trail 2.6s cubic-bezier(0.55, 0, 0.25, 1) infinite;
|
||
}
|
||
.gesture-guide-item:nth-child(1) .gesture-trail { animation-delay: 0ms; }
|
||
.gesture-guide-item:nth-child(2) .gesture-trail { animation-delay: 700ms; }
|
||
.gesture-guide-item:nth-child(3) .gesture-trail { animation-delay: 1400ms; }
|
||
.gesture-guide-item:nth-child(4) .gesture-trail { animation-delay: 2100ms; }
|
||
@keyframes gesture-trail {
|
||
0% { stroke-dashoffset: 22; opacity: 0; }
|
||
18% { opacity: 1; }
|
||
55% { stroke-dashoffset: 0; opacity: 1; }
|
||
80% { stroke-dashoffset: 0; opacity: 0.95; }
|
||
100% { stroke-dashoffset: 0; opacity: 0; }
|
||
}
|
||
|
||
/* Narrow-viewport responsive rules for .gesture-guide are appended
|
||
AFTER the existing .state-dock @media block further down — see
|
||
the trailing @media block at the bottom of this file. CSS cascade
|
||
only honors the last matching rule, and the dock block re-sets
|
||
.state-dock { top: ... } in narrow mode, so the gesture-guide
|
||
override that pushes the dock down has to live after it. */
|
||
|
||
/* ============================================================ */
|
||
/* State Popover — anchored card to the right of the dock. No */
|
||
/* full-screen drawer, no backdrop scrim. Slides in 10px from */
|
||
/* the dock with a fade. Closes on outside click or ESC. */
|
||
/* */
|
||
/* Position math (relative to .demo-layout, which is centered): */
|
||
/* left = 50% + (phone half 180) + (phone↔dock gap 14) */
|
||
/* + (dock width ≈50) + (dock↔popover gap 14) */
|
||
/* = 50% + 258px */
|
||
/* ============================================================ */
|
||
.state-drawer {
|
||
position: absolute;
|
||
left: calc(50% + 258px);
|
||
top: 0;
|
||
width: 380px;
|
||
height: 800px; /* matches phone box height for visual alignment */
|
||
max-height: calc(100svh - 80px);
|
||
background: #fbfbfd;
|
||
border: 1px solid rgba(28, 25, 23, 0.06);
|
||
border-radius: 28px;
|
||
box-shadow:
|
||
0 28px 60px -22px rgba(15, 23, 42, 0.22),
|
||
0 10px 18px -8px rgba(15, 23, 42, 0.10);
|
||
opacity: 0;
|
||
transform: translateX(-10px);
|
||
pointer-events: none;
|
||
z-index: 5;
|
||
display: flex;
|
||
flex-direction: column;
|
||
overflow: hidden;
|
||
transition:
|
||
opacity 280ms cubic-bezier(0.22, 0.61, 0.36, 1),
|
||
transform 320ms cubic-bezier(0.22, 0.61, 0.36, 1);
|
||
}
|
||
.state-drawer[data-open="true"] {
|
||
opacity: 1;
|
||
transform: translateX(0);
|
||
pointer-events: auto;
|
||
}
|
||
|
||
/* Below 1280px the side-anchored popover would overflow the right
|
||
edge AND the vertical dock would visually disconnect from where
|
||
the popover lands. Drop the entire group into a column flow:
|
||
phone → horizontal dock (below phone) → popover (below dock).
|
||
This keeps the click target and the panel that opens on a single
|
||
visual axis. */
|
||
@media (max-width: 1279px) {
|
||
.hero-demo.is-booted {
|
||
padding-top: 76px;
|
||
}
|
||
.hero-demo .demo-phone-wrap {
|
||
--phone-scale: clamp(0.62, calc((100svh - 160px) / 876px), 1);
|
||
margin-bottom: calc((var(--phone-scale) - 1) * 876px);
|
||
}
|
||
.demo-layout {
|
||
flex-direction: column;
|
||
gap: 24px;
|
||
}
|
||
/* Reserve space below the visual phone for the now-horizontal
|
||
dock (which is still position: absolute on .phone-rig at
|
||
top: 100% + 14px). Without this padding the next column-flow
|
||
item (popover) would render on top of the dock. */
|
||
.demo-phone-wrap {
|
||
padding-bottom: 76px;
|
||
}
|
||
.state-drawer {
|
||
position: relative;
|
||
left: auto;
|
||
top: auto;
|
||
width: min(420px, calc(100vw - 32px));
|
||
height: auto;
|
||
max-height: 70svh;
|
||
transform: translateY(-8px);
|
||
}
|
||
.state-drawer[data-open="true"] {
|
||
transform: translateY(0);
|
||
}
|
||
.state-drawer[data-open="false"] {
|
||
display: none;
|
||
}
|
||
}
|
||
|
||
.state-drawer-head {
|
||
position: relative;
|
||
padding: 20px 22px 14px;
|
||
border-bottom: 1px solid rgba(28, 25, 23, 0.06);
|
||
display: flex;
|
||
align-items: flex-start;
|
||
gap: 14px;
|
||
}
|
||
.state-drawer-head .state-builder-board-kicker {
|
||
margin-bottom: 5px;
|
||
}
|
||
.state-drawer-head .state-builder-board-title {
|
||
font-size: 17px;
|
||
line-height: 1.3;
|
||
margin-bottom: 4px;
|
||
}
|
||
.state-drawer-head .state-builder-board-lede {
|
||
font-size: 12.5px;
|
||
line-height: 1.45;
|
||
}
|
||
.state-drawer-close {
|
||
flex: 0 0 auto;
|
||
width: 32px;
|
||
height: 32px;
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
padding: 0;
|
||
border: 0;
|
||
background: rgba(28, 25, 23, 0.04);
|
||
color: #44403c;
|
||
border-radius: 999px;
|
||
cursor: pointer;
|
||
transition: background-color 180ms ease, color 180ms ease, transform 180ms ease;
|
||
}
|
||
.state-drawer-close:hover {
|
||
background: rgba(28, 25, 23, 0.1);
|
||
color: #1c1917;
|
||
transform: rotate(90deg);
|
||
}
|
||
.state-drawer-close:focus-visible {
|
||
outline: 2px solid rgba(37, 99, 235, 0.55);
|
||
outline-offset: 2px;
|
||
}
|
||
|
||
.state-drawer-body {
|
||
flex: 1 1 auto;
|
||
overflow-y: auto;
|
||
padding: 14px 22px 22px;
|
||
-webkit-overflow-scrolling: touch;
|
||
}
|
||
.state-drawer-body .state-builder-board {
|
||
padding-top: 0;
|
||
}
|
||
.state-drawer-body .state-builder-board > .state-builder-board-head {
|
||
/* Kicker/title/lede already render in the drawer head, hide the
|
||
duplicate inside the embedded board (legacy markup keeps it
|
||
for the standalone fallback). */
|
||
display: none;
|
||
}
|
||
|
||
/* Narrow viewports (matches popover breakpoint): dock collapses to
|
||
a horizontal pill below the phone, so dock and popover always
|
||
travel together along the same vertical axis. */
|
||
@media (max-width: 1279px) {
|
||
.state-dock {
|
||
left: 50%;
|
||
top: calc(100% + 14px);
|
||
transform: translate(-50%, 0);
|
||
flex-direction: row;
|
||
padding: 7px 14px;
|
||
gap: 6px;
|
||
}
|
||
.hero-demo.is-booted .state-dock {
|
||
transform: translate(-50%, 0);
|
||
}
|
||
body[data-state-drawer-open="true"] .state-dock {
|
||
/* Keep dock visible under the popover when open — popover
|
||
stacks below it in the column flow, not over it. */
|
||
opacity: 1;
|
||
pointer-events: auto;
|
||
transform: translate(-50%, 0);
|
||
}
|
||
.state-dock-eyebrow {
|
||
writing-mode: horizontal-tb;
|
||
padding: 0 6px 0 2px;
|
||
letter-spacing: 0.28em;
|
||
}
|
||
.state-dock-divider {
|
||
width: 1px;
|
||
height: 18px;
|
||
margin: 0 4px;
|
||
}
|
||
.state-dock-tab {
|
||
width: 34px;
|
||
height: 34px;
|
||
}
|
||
.state-dock-tab-icon { width: 20px; height: 20px; }
|
||
.state-dock-tab-icon svg { width: 16px; height: 16px; }
|
||
.state-dock-hint {
|
||
/* Sits to the right of the horizontal dock, slightly below to
|
||
avoid optical collision with the eyebrow text. */
|
||
left: 50%;
|
||
top: calc(100% + 76px);
|
||
transform: translate(-50%, 0);
|
||
width: auto;
|
||
text-align: center;
|
||
}
|
||
.state-dock-hint::before {
|
||
/* Arrow now points up at the dock above */
|
||
content: '↑';
|
||
}
|
||
}
|
||
|
||
/* ----- Capability board (collapsed state) ----- */
|
||
.state-builder-board {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 10px;
|
||
padding: 6px 4px 0;
|
||
}
|
||
|
||
.state-builder-board-head {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 8px;
|
||
}
|
||
.state-builder-board-kicker {
|
||
color: #2563eb;
|
||
font: 800 10px/1 Inter, system-ui, sans-serif;
|
||
letter-spacing: 0.18em;
|
||
text-transform: uppercase;
|
||
}
|
||
.state-builder-board-title {
|
||
margin: 0;
|
||
font: 700 18px/1.3 Inter, system-ui, sans-serif;
|
||
color: #1c1917;
|
||
}
|
||
.state-builder-board-lede {
|
||
margin: 0;
|
||
font: 400 13.5px/1.55 'Charter', 'Georgia', serif;
|
||
color: #57534e;
|
||
}
|
||
|
||
.state-builder-tabs {
|
||
display: grid;
|
||
grid-template-columns: repeat(6, minmax(0, 1fr));
|
||
gap: 2px;
|
||
padding: 4px 0 0;
|
||
}
|
||
.state-builder-tab {
|
||
position: relative;
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
gap: 6px;
|
||
padding: 4px 2px 6px;
|
||
border: 0;
|
||
background: transparent;
|
||
cursor: pointer;
|
||
color: #78716c;
|
||
font: 600 10.5px/1.25 Inter, system-ui, sans-serif;
|
||
text-align: center;
|
||
transition: color 0.14s ease;
|
||
}
|
||
.state-builder-tab:hover { color: #1c1917; }
|
||
.state-builder-tab[aria-selected="true"] {
|
||
color: #1c1917;
|
||
font-weight: 700;
|
||
}
|
||
.state-builder-tab[aria-selected="true"] .state-builder-tab-icon {
|
||
box-shadow: 0 0 0 1.5px #2563eb;
|
||
}
|
||
.state-builder-tab:focus-visible {
|
||
outline: 2px solid rgba(37, 99, 235, 0.45);
|
||
outline-offset: 2px;
|
||
border-radius: 4px;
|
||
}
|
||
.state-builder-tab-icon {
|
||
width: 30px;
|
||
height: 30px;
|
||
flex: 0 0 auto;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
border-radius: 8px;
|
||
background: #f5f5f4;
|
||
color: #57534e;
|
||
overflow: hidden;
|
||
}
|
||
.state-builder-tab-icon svg { width: 17px; height: 17px; }
|
||
.state-builder-tab-icon-img { background: transparent; }
|
||
.state-builder-tab-icon-img img { width: 100%; height: 100%; object-fit: cover; }
|
||
.state-builder-tab-label { display: block; }
|
||
|
||
.state-builder-panel-shell {
|
||
padding: 4px 0 0;
|
||
}
|
||
|
||
.state-builder-board-foot {
|
||
margin: 8px 4px 0;
|
||
font: 400 11.5px/1.5 'Charter', 'Georgia', serif;
|
||
color: #a8a29e;
|
||
}
|
||
.state-builder-board-foot code {
|
||
font: 500 11px/1.5 'JetBrains Mono', 'SF Mono', monospace;
|
||
color: #78716c;
|
||
background: transparent;
|
||
padding: 0;
|
||
}
|
||
|
||
.studio-action-rail {
|
||
display: flex;
|
||
gap: 8px;
|
||
margin: 9px 0 16px;
|
||
overflow-x: auto;
|
||
scrollbar-width: none;
|
||
}
|
||
.studio-action-rail::-webkit-scrollbar { display: none; }
|
||
.studio-action-tab {
|
||
flex: 0 0 auto;
|
||
border: 0;
|
||
border-bottom: 2px solid transparent;
|
||
border-radius: 0;
|
||
background: transparent;
|
||
padding: 4px 0;
|
||
margin-right: 16px;
|
||
color: #78716c;
|
||
font: 600 12px/1 Inter, system-ui, sans-serif;
|
||
cursor: pointer;
|
||
transition: color 0.14s ease, border-color 0.14s ease;
|
||
}
|
||
.studio-action-tab:hover {
|
||
color: #1c1917;
|
||
}
|
||
.studio-action-tab[aria-selected="true"] {
|
||
color: #1c1917;
|
||
border-bottom-color: #2563eb;
|
||
}
|
||
.studio-action-panel { display: none; }
|
||
.studio-action-panel[data-active="true"] { display: block; }
|
||
#studio-device-location-custom[hidden] { display: none !important; }
|
||
.studio-panel { display: none; }
|
||
.studio-panel[data-active="true"] {
|
||
display: block;
|
||
animation: studio-panel-in 0.18s ease-out;
|
||
}
|
||
.state-builder-panel-shell .studio-action-rail {
|
||
margin: 0 0 14px;
|
||
}
|
||
@keyframes studio-panel-in {
|
||
from { opacity: 0; transform: translateY(4px); }
|
||
to { opacity: 1; transform: translateY(0); }
|
||
}
|
||
.studio-field {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 6px;
|
||
}
|
||
.studio-field label {
|
||
font-size: 11px;
|
||
line-height: 1.2;
|
||
font-weight: 600;
|
||
color: #78716c;
|
||
}
|
||
.studio-input {
|
||
width: 100%;
|
||
border: 1px solid rgba(28,25,23,0.06);
|
||
border-radius: 9px;
|
||
background: transparent;
|
||
padding: 10px 12px;
|
||
font: 600 13px/1.35 Inter, system-ui, sans-serif;
|
||
color: #1c1917;
|
||
outline: none;
|
||
box-shadow: none;
|
||
transition: border-color 0.15s ease, box-shadow 0.15s ease;
|
||
}
|
||
select.studio-input {
|
||
appearance: none;
|
||
background-image: url("data:image/svg+xml,%3Csvg width='18' height='18' viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M7 10L12 15L17 10' stroke='%2378716c' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
|
||
background-position: right 11px center;
|
||
background-repeat: no-repeat;
|
||
background-size: 18px 18px;
|
||
padding-right: 34px;
|
||
}
|
||
input[type="date"].studio-input,
|
||
input[type="datetime-local"].studio-input {
|
||
color-scheme: light;
|
||
}
|
||
textarea.studio-input {
|
||
line-height: 1.5;
|
||
}
|
||
.studio-input:focus {
|
||
border-color: rgba(37,99,235,0.34);
|
||
background: transparent;
|
||
box-shadow:
|
||
0 0 0 2px rgba(37,99,235,0.05);
|
||
}
|
||
.studio-input:hover:not(:focus) {
|
||
border-color: rgba(28,25,23,0.12);
|
||
box-shadow: none;
|
||
}
|
||
.studio-action {
|
||
border-radius: 8px;
|
||
background: #44403c;
|
||
color: #f5f5f4;
|
||
border: 0;
|
||
padding: 9px 14px;
|
||
font: 600 12.5px/1 Inter, system-ui, sans-serif;
|
||
box-shadow: none;
|
||
transition: background 0.14s ease;
|
||
}
|
||
.studio-action:hover {
|
||
background: #57534e;
|
||
box-shadow: none;
|
||
transform: none;
|
||
}
|
||
.studio-action:disabled { background: #a8a29e; transform: none; cursor: not-allowed; }
|
||
.studio-action.secondary {
|
||
background: rgba(255,255,255,0.72);
|
||
color: #1c1917;
|
||
border: 1px solid rgba(214,211,209,0.88);
|
||
box-shadow: none;
|
||
}
|
||
.studio-action.secondary:hover {
|
||
background: #fff;
|
||
border-color: #a8a29e;
|
||
}
|
||
.studio-toggle {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
font: 600 12px/1.2 Inter, system-ui, sans-serif;
|
||
color: #44403c;
|
||
}
|
||
.avatar-picker {
|
||
display: grid;
|
||
grid-template-columns: repeat(6, 1fr);
|
||
gap: 6px;
|
||
}
|
||
.avatar-choice {
|
||
aspect-ratio: 1;
|
||
border-radius: 11px;
|
||
border: 2px solid transparent;
|
||
padding: 2px;
|
||
background: rgba(245,245,244,0.72);
|
||
transition: border-color 0.15s ease, transform 0.15s ease, box-shadow 0.15s ease;
|
||
}
|
||
.avatar-choice:hover {
|
||
transform: translateY(-1px);
|
||
box-shadow: 0 8px 18px -14px rgba(28,25,23,0.7);
|
||
}
|
||
.avatar-choice[aria-pressed="true"] {
|
||
border-color: #2563eb;
|
||
background: #eff6ff;
|
||
}
|
||
.avatar-choice img {
|
||
display: block;
|
||
width: 100%;
|
||
height: 100%;
|
||
border-radius: 9px;
|
||
object-fit: cover;
|
||
}
|
||
.studio-status {
|
||
display: flex;
|
||
align-items: flex-start;
|
||
gap: 8px;
|
||
padding: 0 4px;
|
||
margin-top: 4px;
|
||
font: 400 12px/1.5 'Charter', 'Georgia', serif;
|
||
}
|
||
.studio-status::before {
|
||
content: '';
|
||
width: 6px;
|
||
height: 6px;
|
||
flex: 0 0 auto;
|
||
border-radius: 999px;
|
||
background: currentColor;
|
||
margin-top: 6px;
|
||
}
|
||
.studio-status[data-kind="ok"] { color: #047857; }
|
||
.studio-status[data-kind="warn"] { color: #a16207; }
|
||
.studio-status[data-kind="error"] { color: #b91c1c; }
|
||
.studio-snapshot-meta {
|
||
border-radius: 10px;
|
||
background: rgba(245,245,244,0.7);
|
||
padding: 9px 11px;
|
||
color: #78716c;
|
||
font: 700 10px/1.45 Inter, system-ui, sans-serif;
|
||
}
|
||
|
||
/* ============================================================ */
|
||
/* Reddit case-study trajectory viewer */
|
||
/* ============================================================ */
|
||
.trace-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 1.25rem; max-width: 640px; margin: 0 auto; }
|
||
@media (max-width: 600px) { .trace-grid { grid-template-columns: 1fr; gap: 1rem; } }
|
||
.trace-side { display: flex; flex-direction: column; gap: 0.5rem; }
|
||
.trace-header {
|
||
display: flex; align-items: center; justify-content: flex-start;
|
||
padding: 0 2px 6px;
|
||
}
|
||
.trace-badge {
|
||
padding: 3px 8px; border-radius: 4px;
|
||
font-family: 'Inter', system-ui, sans-serif;
|
||
font-size: 9px; font-weight: 800; letter-spacing: 0.1em;
|
||
text-transform: uppercase;
|
||
}
|
||
.trace-badge-base { background: #1c1917; color: #f5f5f4; }
|
||
.trace-badge-trained { background: #047857; color: #fff; }
|
||
.trace-image-wrap {
|
||
position: relative; border-radius: 10px; overflow: hidden;
|
||
background: #f5f5f4; aspect-ratio: 540 / 1150;
|
||
}
|
||
.trace-image-wrap img { width: 100%; height: 100%; display: block; object-fit: cover; }
|
||
.trace-image-wrap.is-base { border: 1px solid #e7e5e4; }
|
||
.trace-image-wrap.is-trained { border: 1px solid #a7f3d0; background: #ecfdf5; }
|
||
.trace-controls { display: flex; flex-direction: column; gap: 0.5rem; padding: 0 2px; }
|
||
.trace-slider {
|
||
-webkit-appearance: none; appearance: none;
|
||
width: 100%; height: 4px; border-radius: 2px;
|
||
background: #e7e5e4; outline: none; cursor: pointer;
|
||
margin: 0;
|
||
}
|
||
.trace-slider::-webkit-slider-thumb {
|
||
-webkit-appearance: none; appearance: none;
|
||
width: 16px; height: 16px; border-radius: 50%;
|
||
background: #fff; border: 2px solid #44403c;
|
||
cursor: pointer; transition: transform .15s ease;
|
||
}
|
||
.trace-slider::-moz-range-thumb {
|
||
width: 16px; height: 16px; border-radius: 50%;
|
||
background: #fff; border: 2px solid #44403c;
|
||
cursor: pointer; transition: transform .15s ease;
|
||
}
|
||
.trace-slider.is-trained::-webkit-slider-thumb { border-color: #047857; }
|
||
.trace-slider.is-trained::-moz-range-thumb { border-color: #047857; }
|
||
.trace-slider:hover::-webkit-slider-thumb { transform: scale(1.15); }
|
||
.trace-slider:hover::-moz-range-thumb { transform: scale(1.15); }
|
||
.trace-ticks {
|
||
display: flex; justify-content: space-between; align-items: center;
|
||
padding: 0 7px;
|
||
font-family: 'JetBrains Mono', 'SF Mono', monospace;
|
||
font-size: 9px; color: #a8a29e;
|
||
font-variant-numeric: tabular-nums;
|
||
}
|
||
.trace-tick { display: flex; flex-direction: column; align-items: center; flex: 0 0 auto; }
|
||
.trace-tick::before {
|
||
content: ''; width: 1px; height: 5px; background: #d6d3d1; margin-bottom: 2px;
|
||
}
|
||
.trace-action {
|
||
font-family: 'Inter', system-ui, sans-serif;
|
||
font-size: 11px; line-height: 1.35; color: #44403c;
|
||
min-height: 2.7em; padding: 2px 2px 0;
|
||
}
|
||
.trace-action .step-label {
|
||
font-family: 'JetBrains Mono', 'SF Mono', monospace;
|
||
font-weight: 700; font-size: 10px;
|
||
margin-right: 6px;
|
||
color: #1c1917;
|
||
}
|
||
.trace-action.is-trained .step-label { color: #047857; }
|
||
|
||
.app-tile { display: flex; flex-direction: column; align-items: center; gap: 0.5rem; user-select: none; }
|
||
.app-tile .app-icon {
|
||
width: 60px; height: 60px;
|
||
display: flex; align-items: center; justify-content: center;
|
||
transition: transform .18s ease, filter .18s ease;
|
||
position: relative;
|
||
}
|
||
.app-tile .app-icon img {
|
||
width: 100%; height: 100%;
|
||
filter: drop-shadow(0 1px 2px rgba(0,0,0,.08)) drop-shadow(0 2px 4px rgba(0,0,0,.05));
|
||
}
|
||
.app-tile .app-icon.fallback {
|
||
border-radius: 14px;
|
||
box-shadow: 0 1px 2px rgba(0,0,0,.08), 0 2px 6px -2px rgba(0,0,0,.08);
|
||
font-family: 'Inter', system-ui, sans-serif;
|
||
font-weight: 600;
|
||
overflow: visible;
|
||
}
|
||
.app-tile .app-icon.fallback.is-light {
|
||
box-shadow: 0 0 0 1px rgba(0,0,0,.05), 0 1px 2px rgba(0,0,0,.06), 0 2px 6px -2px rgba(0,0,0,.06);
|
||
}
|
||
.app-tile:hover .app-icon { transform: translateY(-2px) scale(1.06); }
|
||
.app-tile:hover .app-icon img {
|
||
filter: drop-shadow(0 4px 10px rgba(0,0,0,.18)) drop-shadow(0 2px 4px rgba(0,0,0,.08));
|
||
}
|
||
.app-tile .app-name {
|
||
font-size: 11px; line-height: 1.15; text-align: center;
|
||
color: #57534e; transition: color .15s ease;
|
||
font-family: 'Inter', system-ui, sans-serif;
|
||
}
|
||
.app-tile:hover .app-name { color: #1c1917; }
|
||
.app-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||
column-gap: 0.5rem;
|
||
row-gap: 1.5rem;
|
||
}
|
||
@media (min-width: 640px) { .app-grid { grid-template-columns: repeat(6, minmax(0, 1fr)); } }
|
||
@media (min-width: 768px) { .app-grid { grid-template-columns: repeat(7, minmax(0, 1fr)); } }
|
||
.app-section-label {
|
||
display: flex; align-items: baseline; gap: 0.625rem; margin-bottom: 1.25rem;
|
||
font-family: 'Inter', system-ui, sans-serif;
|
||
}
|
||
.app-section-label .bar { width: 3px; height: 14px; border-radius: 2px; align-self: center; }
|
||
.app-section-label h3 {
|
||
font-size: 11px; font-weight: 700; letter-spacing: 0.22em;
|
||
text-transform: uppercase; color: #44403c;
|
||
}
|
||
.app-section-label .count { font-size: 11px; color: #a8a29e; font-weight: 500; }
|
||
|
||
/* ============================================================ */
|
||
/* Apps Wall — auto-scrolling marquee */
|
||
/* ============================================================ */
|
||
.apps-marquee-row { margin-bottom: 28px; }
|
||
.apps-marquee-row:last-of-type { margin-bottom: 0; }
|
||
.apps-marquee-meta {
|
||
display: flex; align-items: center; gap: 10px;
|
||
margin: 0 0 14px;
|
||
font-family: 'Inter', system-ui, sans-serif;
|
||
}
|
||
.apps-marquee-meta .bar {
|
||
width: 3px; height: 14px; border-radius: 2px;
|
||
flex: 0 0 auto;
|
||
}
|
||
.apps-marquee-meta-label {
|
||
font-size: 11px; font-weight: 700;
|
||
letter-spacing: 0.22em; text-transform: uppercase;
|
||
color: #44403c;
|
||
}
|
||
.apps-marquee-meta-count {
|
||
font-size: 11px; font-weight: 500; color: #a8a29e;
|
||
}
|
||
|
||
.apps-marquee {
|
||
position: relative;
|
||
overflow: hidden;
|
||
padding: 4px 0 6px;
|
||
-webkit-mask-image: linear-gradient(90deg, transparent 0%, #000 6%, #000 94%, transparent 100%);
|
||
mask-image: linear-gradient(90deg, transparent 0%, #000 6%, #000 94%, transparent 100%);
|
||
}
|
||
.apps-marquee-track {
|
||
display: flex;
|
||
gap: 28px;
|
||
width: max-content;
|
||
animation: apps-scroll-left 30s linear infinite;
|
||
will-change: transform;
|
||
}
|
||
.apps-marquee-track.is-reverse {
|
||
animation-name: apps-scroll-right;
|
||
animation-duration: 30s;
|
||
}
|
||
.apps-marquee:hover .apps-marquee-track,
|
||
.apps-marquee:focus-within .apps-marquee-track {
|
||
animation-play-state: paused;
|
||
}
|
||
@keyframes apps-scroll-left {
|
||
from { transform: translate3d(0, 0, 0); }
|
||
to { transform: translate3d(-50%, 0, 0); }
|
||
}
|
||
@keyframes apps-scroll-right {
|
||
from { transform: translate3d(-50%, 0, 0); }
|
||
to { transform: translate3d(0, 0, 0); }
|
||
}
|
||
.apps-marquee .app-tile {
|
||
flex: 0 0 auto;
|
||
width: 84px;
|
||
}
|
||
@media (prefers-reduced-motion: reduce) {
|
||
.apps-marquee {
|
||
overflow-x: auto;
|
||
-webkit-overflow-scrolling: touch;
|
||
-webkit-mask-image: none;
|
||
mask-image: none;
|
||
}
|
||
.apps-marquee-track {
|
||
animation: none !important;
|
||
transform: none !important;
|
||
}
|
||
}
|
||
|
||
/* ============================================================ */
|
||
/* Gesture Guide — narrow-viewport collapse. */
|
||
/* Placed at end-of-file so it cascades AFTER the .state-dock */
|
||
/* @media block above (which re-sets dock/hint positions). On */
|
||
/* narrow viewports the left-side legend collapses into an */
|
||
/* icon-only horizontal pill directly under the phone — Android */
|
||
/* soft-key bar — and pushes State Dock + hint further down. */
|
||
/* ============================================================ */
|
||
@media (max-width: 1279px) {
|
||
.gesture-guide {
|
||
position: absolute;
|
||
left: 50%;
|
||
right: auto;
|
||
top: calc(100% + 14px);
|
||
transform: translate(-50%, 0);
|
||
width: auto;
|
||
padding: 6px 12px;
|
||
border-radius: 999px;
|
||
}
|
||
.hero-demo.is-booted .gesture-guide {
|
||
transform: translate(-50%, 0);
|
||
}
|
||
/* Drop all explanatory text — only the three keys remain visible. */
|
||
.gesture-guide-head,
|
||
.gesture-guide-text {
|
||
display: none;
|
||
}
|
||
.gesture-guide-list {
|
||
flex-direction: row;
|
||
gap: 6px;
|
||
}
|
||
.gesture-guide-item {
|
||
padding: 2px;
|
||
border-radius: 999px;
|
||
}
|
||
/* Row-level hover/active background made sense when the row also
|
||
held a text label; in icon-only mode the key itself carries all
|
||
the affordance, so the surrounding pill stays transparent. */
|
||
.gesture-guide-item:hover,
|
||
.gesture-guide-item:active {
|
||
background: transparent;
|
||
}
|
||
|
||
/* Push the State Dock and its "Patch state" hint below the new
|
||
gesture-key row so they don't collide. Phone wrap reserves the
|
||
combined height — without it the popover (which renders in
|
||
column flow below) would overlap the dock. */
|
||
.state-dock {
|
||
top: calc(100% + 76px);
|
||
}
|
||
.state-dock-hint {
|
||
top: calc(100% + 138px);
|
||
}
|
||
.demo-phone-wrap {
|
||
padding-bottom: 152px;
|
||
}
|
||
}
|