feat(space): 목표 카드를 collapsed rail로 재설계
This commit is contained in:
256
docs/13_space_intent_card_collapsed_expanded_spec.md
Normal file
256
docs/13_space_intent_card_collapsed_expanded_spec.md
Normal file
@@ -0,0 +1,256 @@
|
|||||||
|
# 13. `/space` Intent Card Collapsed / Expanded Spec
|
||||||
|
|
||||||
|
Last Updated: 2026-03-14
|
||||||
|
|
||||||
|
이 문서는 `/space` 좌상단 목표 카드의 **collapsed / expanded 구조**를 정의한다.
|
||||||
|
|
||||||
|
목적은 단순하다.
|
||||||
|
|
||||||
|
- goal은 항상 보여야 한다
|
||||||
|
- 하지만 배경을 계속 크게 가리면 안 된다
|
||||||
|
- 평소에는 조용한 `anchor`처럼 남고, 필요할 때만 `card`처럼 확장돼야 한다
|
||||||
|
|
||||||
|
관련 문서:
|
||||||
|
|
||||||
|
- `10_refocus_system_spec.md`
|
||||||
|
- `11_away_return_recovery_spec.md`
|
||||||
|
- `12_core_loop_execution_roadmap.md`
|
||||||
|
- `../../current_context.md`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. 왜 바꾸는가
|
||||||
|
|
||||||
|
현재 구조의 문제는 이것이다.
|
||||||
|
|
||||||
|
- goal, microStep, 액션이 항상 한 카드 안에 모두 보인다
|
||||||
|
- 읽힘은 확보하지만, 배경의 존재감을 과하게 가져간다
|
||||||
|
- `/space`의 주인공이 scene이어야 하는데, 좌상단 카드가 먼저 읽힌다
|
||||||
|
|
||||||
|
즉 문제는 glass material 자체보다 **항상 큰 상태로 떠 있는 면적**이다.
|
||||||
|
|
||||||
|
premium ambient UI에서는:
|
||||||
|
|
||||||
|
- 정보는 항상 남아야 하지만
|
||||||
|
- 존재감은 항상 같을 필요가 없다
|
||||||
|
|
||||||
|
그래서 목표 카드는 `persistent presence`는 유지하고, `persistent emphasis`는 버려야 한다.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. 한 줄 정의
|
||||||
|
|
||||||
|
> `/space`의 목표 카드는 평소에는 얇은 glass rail로 남고, 사용자가 실제로 결정을 내려야 할 때만 확장된다.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. 상태 정의
|
||||||
|
|
||||||
|
Intent Card는 아래 2개 상태만 가진다.
|
||||||
|
|
||||||
|
### 3.1 Collapsed
|
||||||
|
|
||||||
|
기본 상태.
|
||||||
|
|
||||||
|
보이는 것:
|
||||||
|
|
||||||
|
- goal 1줄
|
||||||
|
- 작은 expand affordance 1개
|
||||||
|
|
||||||
|
보이지 않는 것:
|
||||||
|
|
||||||
|
- microStep 상세
|
||||||
|
- 완료 액션
|
||||||
|
- recovery footer
|
||||||
|
|
||||||
|
역할:
|
||||||
|
|
||||||
|
- 지금 어떤 일 위에 있는지 잃지 않게 하는 기준점
|
||||||
|
|
||||||
|
감정:
|
||||||
|
|
||||||
|
- panel이 아니라 anchor
|
||||||
|
|
||||||
|
### 3.2 Expanded
|
||||||
|
|
||||||
|
필요할 때만 열리는 상태.
|
||||||
|
|
||||||
|
보이는 것:
|
||||||
|
|
||||||
|
- goal
|
||||||
|
- microStep 또는 비어 있을 때의 조용한 helper line
|
||||||
|
- `이번 목표 완료`
|
||||||
|
- microStep 완료 체크
|
||||||
|
|
||||||
|
역할:
|
||||||
|
|
||||||
|
- 지금 이 세션을 어떻게 계속할지 결정하게 하는 짧은 제어 면
|
||||||
|
|
||||||
|
감정:
|
||||||
|
|
||||||
|
- 툴바가 아니라, 잠깐 열리는 soft control surface
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. 상태 전환 규칙
|
||||||
|
|
||||||
|
### Expanded로 전환되는 경우
|
||||||
|
|
||||||
|
- desktop에서 hover
|
||||||
|
- focus 진입
|
||||||
|
- expand affordance 클릭/tap
|
||||||
|
|
||||||
|
### Collapsed로 돌아가는 경우
|
||||||
|
|
||||||
|
- pointer leave
|
||||||
|
- focus out
|
||||||
|
- expand affordance 재클릭
|
||||||
|
- recovery tray가 열릴 때
|
||||||
|
|
||||||
|
중요:
|
||||||
|
|
||||||
|
- `pause / return / next-beat / complete / refocus` tray가 열려 있는 동안에는 목표 카드는 강제로 collapsed 상태를 유지한다
|
||||||
|
- tray가 이미 의사결정 레이어이기 때문에, base card까지 expanded 상태면 배경을 이중으로 가리게 된다
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. 시각 구조
|
||||||
|
|
||||||
|
### Collapsed rail
|
||||||
|
|
||||||
|
- 폭: 약 `18rem ~ 19rem`
|
||||||
|
- 높이: 약 `48px ~ 52px`
|
||||||
|
- radius: `18px` 전후
|
||||||
|
- glass:
|
||||||
|
- 더 얇고 더 투명
|
||||||
|
- large panel처럼 보이면 안 된다
|
||||||
|
- goal:
|
||||||
|
- 1줄 truncate
|
||||||
|
- medium weight
|
||||||
|
- affordance:
|
||||||
|
- 우측에 작은 chevron / expand control
|
||||||
|
- pill/button처럼 보이면 안 된다
|
||||||
|
|
||||||
|
### Expanded card
|
||||||
|
|
||||||
|
- 폭: 약 `21rem ~ 23rem`
|
||||||
|
- 높이: 내용에 따라 자연스럽게
|
||||||
|
- radius: `22px ~ 24px`
|
||||||
|
- glass:
|
||||||
|
- collapsed보다 한 단계 더 선명한 재질
|
||||||
|
- 하지만 tray만큼 무겁지는 않다
|
||||||
|
- 내부 구조:
|
||||||
|
1. goal row
|
||||||
|
2. microStep row
|
||||||
|
3. quiet footer action
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. 정보 구조
|
||||||
|
|
||||||
|
### Goal row
|
||||||
|
|
||||||
|
- 좌측: goal 1줄
|
||||||
|
- 우측: expand/collapse affordance
|
||||||
|
- goal 텍스트는 expanded 상태에서 클릭 시 refocus 진입
|
||||||
|
- collapsed 상태에서는 goal 클릭이 바로 refocus를 여는 대신, rail 자체의 anchor로만 동작해도 괜찮다
|
||||||
|
|
||||||
|
### MicroStep row
|
||||||
|
|
||||||
|
- expanded 상태에서만 노출
|
||||||
|
- 왼쪽: completion glyph
|
||||||
|
- 오른쪽: microStep text
|
||||||
|
- microStep이 없으면:
|
||||||
|
- helper 1줄만 노출
|
||||||
|
- planner-like placeholder 금지
|
||||||
|
|
||||||
|
### Footer action
|
||||||
|
|
||||||
|
- expanded 상태에서만 노출
|
||||||
|
- 우측 정렬된 quiet text action 1개
|
||||||
|
- `이번 목표 완료`
|
||||||
|
- `다시 방향` 상시 버튼은 두지 않는다
|
||||||
|
- refocus는 goal 클릭을 통해 진입한다
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. interaction 원칙
|
||||||
|
|
||||||
|
### Goal 수정
|
||||||
|
|
||||||
|
- expanded 상태에서 goal 텍스트 클릭 -> refocus
|
||||||
|
- collapsed 상태에서는 goal 클릭으로 바로 열지 않거나, 제품 감각을 해치지 않는 범위에서만 허용한다
|
||||||
|
- 핵심은 `expanded`가 먼저 읽히고, `edit`는 그다음이어야 한다
|
||||||
|
|
||||||
|
### microStep 완료
|
||||||
|
|
||||||
|
- expanded 상태에서만 완료 glyph가 노출된다
|
||||||
|
- collapsed 상태에서는 실수 클릭을 막기 위해 숨긴다
|
||||||
|
|
||||||
|
### 목표 완료
|
||||||
|
|
||||||
|
- expanded 상태에서만 footer action 노출
|
||||||
|
- collapsed 상태에서는 보이지 않는다
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. motion 원칙
|
||||||
|
|
||||||
|
### Collapsed -> Expanded
|
||||||
|
|
||||||
|
- 넓어짐 + 내부 row fade-in
|
||||||
|
- card가 “튀어나오는” 느낌보다, rail이 조용히 열리는 느낌이어야 한다
|
||||||
|
- duration은 짧고 부드럽게
|
||||||
|
|
||||||
|
### Expanded -> Collapsed
|
||||||
|
|
||||||
|
- 내부 row가 먼저 약해지고
|
||||||
|
- 폭이 줄어든다
|
||||||
|
- 닫힘은 더 조용해야 한다
|
||||||
|
|
||||||
|
### tray open 시
|
||||||
|
|
||||||
|
- intent card는 먼저 collapsed
|
||||||
|
- 그 아래 tray가 열림
|
||||||
|
- 즉 `card 확장`과 `tray 확장`은 동시에 크게 보이면 안 된다
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. 금지사항
|
||||||
|
|
||||||
|
- 상시 expanded 금지
|
||||||
|
- goal + microStep + footer action 항상 노출 금지
|
||||||
|
- Now chip 재도입 금지
|
||||||
|
- toolbar/pill/button cluster처럼 보이게 만들기 금지
|
||||||
|
- 배경보다 card가 먼저 읽히게 만들기 금지
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 10. 구현 기준
|
||||||
|
|
||||||
|
필수 변경 파일:
|
||||||
|
|
||||||
|
- `src/widgets/space-focus-hud/ui/IntentCapsule.tsx`
|
||||||
|
- `src/widgets/space-focus-hud/ui/SpaceFocusHudWidget.tsx`
|
||||||
|
|
||||||
|
가능한 보조 변경:
|
||||||
|
|
||||||
|
- `src/shared/i18n/messages/space.ts`
|
||||||
|
- `src/widgets/space-focus-hud/ui/overlayStyles.ts`
|
||||||
|
|
||||||
|
구현 포인트:
|
||||||
|
|
||||||
|
- `IntentCapsule` 내부에 local expanded state 추가
|
||||||
|
- hover / focus / toggle button 기반 전환
|
||||||
|
- `showActions === false` 또는 overlay open 상태에서는 강제 collapsed
|
||||||
|
- microStep / footer action은 expanded에서만 렌더
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 11. 완료 기준
|
||||||
|
|
||||||
|
- idle 상태에서 상단 goal UI가 배경을 덜 가린다
|
||||||
|
- expanded 상태에서만 microStep과 완료 액션이 보인다
|
||||||
|
- recovery tray가 열릴 때 base card는 과하게 커져 있지 않다
|
||||||
|
- bright / dark scene 모두에서 읽히지만 scene이 먼저 읽힌다
|
||||||
|
- `/space`가 productivity dashboard처럼 보이지 않는다
|
||||||
@@ -56,6 +56,10 @@ Last Updated: 2026-03-14
|
|||||||
- `Pause` tray는 빠르게 다시 붙잡는 recovery reveal로 조정
|
- `Pause` tray는 빠르게 다시 붙잡는 recovery reveal로 조정
|
||||||
- `Return(focus)`는 짧은 re-entry settle motion으로, `Return(break)`는 더 느슨한 release reveal로 분리
|
- `Return(focus)`는 짧은 re-entry settle motion으로, `Return(break)`는 더 느슨한 release reveal로 분리
|
||||||
- `Goal Complete`도 같은 recovery family 안에서 가장 느린 closure motion을 가지도록 조정
|
- `Goal Complete`도 같은 recovery family 안에서 가장 느린 closure motion을 가지도록 조정
|
||||||
|
- `/space` intent HUD collapsed / expanded 재설계:
|
||||||
|
- 상시 큰 goal 카드 대신 idle에서는 goal 1줄과 작은 expand affordance만 남는 collapsed glass rail 구조로 변경
|
||||||
|
- hover / focus / toggle에서만 expanded card로 열리며, 이때만 microStep과 `이번 목표 완료` 액션이 노출됨
|
||||||
|
- recovery tray(`pause / return / next-beat / complete / refocus`)가 열릴 때는 base card가 강제로 collapsed 상태를 유지하도록 정리
|
||||||
|
|
||||||
- Focus Entry Surface / Execution Surface 재정의:
|
- Focus Entry Surface / Execution Surface 재정의:
|
||||||
- `/app`을 planning home이 아니라 hero-first focus entry surface로 재구성
|
- `/app`을 planning home이 아니라 hero-first focus entry surface로 재구성
|
||||||
|
|||||||
@@ -60,6 +60,10 @@ Last Updated: 2026-03-14
|
|||||||
- `Pause`는 빠르게 다시 붙잡는 recovery reveal로,
|
- `Pause`는 빠르게 다시 붙잡는 recovery reveal로,
|
||||||
- `Return(focus)`는 재진입에 맞는 짧은 settle motion으로,
|
- `Return(focus)`는 재진입에 맞는 짧은 settle motion으로,
|
||||||
- `Return(break)`와 `Goal Complete`는 더 느슨한 release/closure reveal로 분리했다.
|
- `Return(break)`와 `Goal Complete`는 더 느슨한 release/closure reveal로 분리했다.
|
||||||
|
- `/space` 목표 카드를 collapsed / expanded 구조로 재설계했다.
|
||||||
|
- idle에서는 goal 1줄만 남는 얇은 glass rail로 줄였다.
|
||||||
|
- microStep과 `이번 목표 완료`는 expanded 상태에서만 드러난다.
|
||||||
|
- recovery tray가 열리면 base card는 자동으로 collapsed 상태를 유지한다.
|
||||||
- `/app`을 single-goal commitment gate로 다시 줄였다.
|
- `/app`을 single-goal commitment gate로 다시 줄였다.
|
||||||
- 2-step ritual setup을 제거했다.
|
- 2-step ritual setup을 제거했다.
|
||||||
- current session이 있으면 `Resume` UI만 보여주고, `/space`로 이어가기만 제안한다.
|
- current session이 있으면 `Resume` UI만 보여주고, `/space`로 이어가기만 제안한다.
|
||||||
|
|||||||
@@ -38,6 +38,8 @@ export const space = {
|
|||||||
restReminder: '5분이 지났어요. 다음 한 조각으로 돌아와요.',
|
restReminder: '5분이 지났어요. 다음 한 조각으로 돌아와요.',
|
||||||
intentLabel: '이번 세션 목표',
|
intentLabel: '이번 세션 목표',
|
||||||
microStepLabel: '지금 할 한 조각',
|
microStepLabel: '지금 할 한 조각',
|
||||||
|
intentExpandAriaLabel: '목표 카드 펼치기',
|
||||||
|
intentCollapseAriaLabel: '목표 카드 접기',
|
||||||
refocusButton: '다시 방향',
|
refocusButton: '다시 방향',
|
||||||
refocusTitle: '다시 방향 잡기',
|
refocusTitle: '다시 방향 잡기',
|
||||||
refocusDescription: '딱 한 줄만 다듬고 다시 시작해요.',
|
refocusDescription: '딱 한 줄만 다듬고 다시 시작해요.',
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
|
import { useState } from 'react';
|
||||||
import { copy } from '@/shared/i18n';
|
import { copy } from '@/shared/i18n';
|
||||||
import { cn } from '@/shared/lib/cn';
|
import { cn } from '@/shared/lib/cn';
|
||||||
|
|
||||||
@@ -24,39 +25,143 @@ export const IntentCapsule = ({
|
|||||||
onMicroStepDone,
|
onMicroStepDone,
|
||||||
onGoalCompleteRequest,
|
onGoalCompleteRequest,
|
||||||
}: IntentCapsuleProps) => {
|
}: IntentCapsuleProps) => {
|
||||||
|
const [isPinnedExpanded, setPinnedExpanded] = useState(false);
|
||||||
|
const [isHovered, setHovered] = useState(false);
|
||||||
|
const [isFocusWithin, setFocusWithin] = useState(false);
|
||||||
|
|
||||||
const normalizedMicroStep = microStep?.trim() ? microStep.trim() : null;
|
const normalizedMicroStep = microStep?.trim() ? microStep.trim() : null;
|
||||||
|
const isExpanded = showActions && (isPinnedExpanded || isHovered || isFocusWithin);
|
||||||
|
const canInteract = showActions && canRefocus;
|
||||||
const microGlyphClass =
|
const microGlyphClass =
|
||||||
'inline-flex h-5 w-5 items-center justify-center rounded-full border border-white/20 text-white/62 transition-all duration-200 hover:border-white/36 hover:text-white focus-visible:border-white/36 focus-visible:text-white disabled:cursor-default disabled:opacity-30';
|
'inline-flex h-5 w-5 items-center justify-center rounded-full border border-white/18 text-white/62 transition-all duration-200 hover:border-white/32 hover:text-white focus-visible:border-white/32 focus-visible:text-white disabled:cursor-default disabled:opacity-30';
|
||||||
|
|
||||||
|
const handleGoalClick = () => {
|
||||||
|
if (!canInteract) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isExpanded) {
|
||||||
|
setPinnedExpanded(true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
onOpenRefocus();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleToggleExpanded = () => {
|
||||||
|
if (!showActions) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setPinnedExpanded((current) => !current);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="pointer-events-none w-full">
|
<div className="pointer-events-none flex w-full">
|
||||||
<section className="pointer-events-auto relative w-full overflow-hidden rounded-[24px] border border-white/10 bg-[#0f1115]/24 px-5 py-4 text-white shadow-[0_12px_28px_rgba(2,6,23,0.14)] backdrop-blur-[8px] backdrop-saturate-125">
|
<section
|
||||||
|
className={cn(
|
||||||
|
'pointer-events-auto relative overflow-hidden border text-white transition-[width,padding,background-color,border-color,box-shadow] duration-200 ease-out',
|
||||||
|
isExpanded
|
||||||
|
? 'w-[22rem] max-w-full rounded-[24px] border-white/10 bg-[#0f1115]/24 px-5 py-4 shadow-[0_12px_28px_rgba(2,6,23,0.14)] backdrop-blur-[8px] backdrop-saturate-125'
|
||||||
|
: 'w-[18.75rem] max-w-full rounded-[20px] border-white/8 bg-[#0f1115]/16 px-4 py-3 shadow-[0_8px_18px_rgba(2,6,23,0.10)] backdrop-blur-[7px] backdrop-saturate-120',
|
||||||
|
)}
|
||||||
|
onMouseEnter={() => {
|
||||||
|
if (showActions) {
|
||||||
|
setHovered(true);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
onMouseLeave={() => setHovered(false)}
|
||||||
|
onFocusCapture={() => {
|
||||||
|
if (showActions) {
|
||||||
|
setFocusWithin(true);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
onBlurCapture={(event) => {
|
||||||
|
if (!event.currentTarget.contains(event.relatedTarget)) {
|
||||||
|
setFocusWithin(false);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
<div
|
<div
|
||||||
aria-hidden
|
aria-hidden
|
||||||
className="pointer-events-none absolute inset-0 rounded-[24px] bg-[linear-gradient(180deg,rgba(255,255,255,0.08)_0%,rgba(255,255,255,0.025)_24%,rgba(255,255,255,0.01)_100%)]"
|
className={cn(
|
||||||
|
'pointer-events-none absolute inset-0',
|
||||||
|
isExpanded
|
||||||
|
? 'rounded-[24px] bg-[linear-gradient(180deg,rgba(255,255,255,0.08)_0%,rgba(255,255,255,0.025)_24%,rgba(255,255,255,0.01)_100%)]'
|
||||||
|
: 'rounded-[20px] bg-[linear-gradient(180deg,rgba(255,255,255,0.06)_0%,rgba(255,255,255,0.018)_28%,rgba(255,255,255,0.008)_100%)]',
|
||||||
|
)}
|
||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
aria-hidden
|
aria-hidden
|
||||||
className="pointer-events-none absolute inset-x-0 top-0 h-px bg-white/16"
|
className={cn(
|
||||||
|
'pointer-events-none absolute inset-x-0 top-0 h-px',
|
||||||
|
isExpanded ? 'bg-white/16' : 'bg-white/12',
|
||||||
|
)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
<div className="min-w-0">
|
<div className="flex items-center gap-3">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={canRefocus ? onOpenRefocus : undefined}
|
onClick={handleGoalClick}
|
||||||
disabled={!canRefocus || !showActions}
|
disabled={!canInteract}
|
||||||
aria-label={copy.space.focusHud.refocusButton}
|
aria-label={copy.space.focusHud.refocusButton}
|
||||||
className="block min-w-0 w-full max-w-full text-left transition-opacity hover:opacity-90 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-white/14 disabled:cursor-default disabled:hover:opacity-100"
|
className="min-w-0 flex-1 text-left transition-opacity hover:opacity-90 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-white/14 disabled:cursor-default disabled:hover:opacity-100"
|
||||||
|
>
|
||||||
|
<p
|
||||||
|
className={cn(
|
||||||
|
'truncate font-medium tracking-tight text-white/96 transition-[font-size,line-height] duration-200 ease-out',
|
||||||
|
isExpanded ? 'text-[18px] md:text-[20px]' : 'text-[15px] md:text-[16px]',
|
||||||
|
)}
|
||||||
>
|
>
|
||||||
<p className="truncate text-[18px] font-medium tracking-tight text-white/96 md:text-[20px]">
|
|
||||||
{goal}
|
{goal}
|
||||||
</p>
|
</p>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
{showActions ? (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={handleToggleExpanded}
|
||||||
|
aria-label={
|
||||||
|
isExpanded
|
||||||
|
? copy.space.focusHud.intentCollapseAriaLabel
|
||||||
|
: copy.space.focusHud.intentExpandAriaLabel
|
||||||
|
}
|
||||||
|
className={cn(
|
||||||
|
'inline-flex h-8 w-8 shrink-0 items-center justify-center rounded-full border border-white/8 bg-black/10 text-white/56 transition-all duration-200 hover:border-white/14 hover:bg-black/16 hover:text-white/82 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-white/12',
|
||||||
|
isExpanded && 'border-white/12 bg-black/14 text-white/82',
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
aria-hidden
|
||||||
|
viewBox="0 0 16 16"
|
||||||
|
className={cn(
|
||||||
|
'h-3.5 w-3.5 transition-transform duration-200 ease-out',
|
||||||
|
isExpanded ? 'rotate-180' : 'rotate-0',
|
||||||
|
)}
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
strokeWidth="1.6"
|
||||||
|
>
|
||||||
|
<path d="M4.5 6.5 8 10l3.5-3.5" />
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
'overflow-hidden transition-[max-height,opacity,transform,margin] duration-220 ease-out motion-reduce:transition-none',
|
||||||
|
isExpanded
|
||||||
|
? 'mt-3 max-h-40 translate-y-0 opacity-100'
|
||||||
|
: 'max-h-0 -translate-y-1 opacity-0',
|
||||||
|
)}
|
||||||
|
aria-hidden={!isExpanded}
|
||||||
|
>
|
||||||
{normalizedMicroStep ? (
|
{normalizedMicroStep ? (
|
||||||
<div className="mt-3 flex items-center gap-3 border-t border-white/10 pt-3">
|
<div className="flex items-center gap-3 border-t border-white/10 pt-3">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={showActions ? onMicroStepDone : undefined}
|
onClick={showActions ? onMicroStepDone : undefined}
|
||||||
@@ -82,7 +187,7 @@ export const IntentCapsule = ({
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<p className="mt-3 border-t border-white/10 pt-3 text-[14px] text-white/56">
|
<p className="border-t border-white/10 pt-3 text-[14px] leading-[1.5] text-white/56">
|
||||||
{copy.space.focusHud.refocusDescription}
|
{copy.space.focusHud.refocusDescription}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
@@ -99,6 +204,7 @@ export const IntentCapsule = ({
|
|||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -261,6 +261,7 @@ export const SpaceFocusHudWidget = ({
|
|||||||
<>
|
<>
|
||||||
<div className="pointer-events-none fixed left-6 top-6 z-20 w-[min(29rem,calc(100vw-3rem))] md:left-10 md:top-9">
|
<div className="pointer-events-none fixed left-6 top-6 z-20 w-[min(29rem,calc(100vw-3rem))] md:left-10 md:top-9">
|
||||||
<IntentCapsule
|
<IntentCapsule
|
||||||
|
key={isIntentOverlayOpen ? 'intent-locked' : 'intent-interactive'}
|
||||||
goal={normalizedGoal}
|
goal={normalizedGoal}
|
||||||
microStep={microStep}
|
microStep={microStep}
|
||||||
canRefocus={Boolean(hasActiveSession)}
|
canRefocus={Boolean(hasActiveSession)}
|
||||||
|
|||||||
Reference in New Issue
Block a user