이클립스 흐름 — 배너 진입 · 이벤트 버튼 · 오버레이 · 방어 진행 표시 · 흔들기 감지 통일

This commit is contained in:
Sn-Kinos
2026-10-05 19:05:21 +09:00
parent be623d2e46
commit 576dcfbdfd
27 changed files with 1369 additions and 369 deletions
+52 -26
View File
@@ -48,8 +48,12 @@ class HomeHero extends StatefulWidget {
this.onItem,
this.miniMap,
this.zapButtonKey,
this.eclipseButton,
});
/// 이클립스 이벤트 버튼(`516:88124`). 경보 단계부터 행성 아래 가운데에 뜬다.
final Widget? eclipseButton;
/// 화면 하단에 떠 있는 ZAP 버튼([HomeZap])의 키 — 기여 연출이 이 자리에서 행성으로 날아간다.
final GlobalKey? zapButtonKey;
@@ -181,35 +185,57 @@ class _HomeHeroState extends State<HomeHero> {
builder: (context, box) => SizedBox(
width: double.infinity,
height: math.min(planet, box.maxHeight),
child: widget.unjoined
? _EmptyPlanet(
onCreateNebula: widget.onCreateNebula,
onJoin: widget.onJoin,
)
: growth.isNebula
? _Nebula(
key: _planetKey,
// 탄생은 창립자만(Q-PN-01 A안) — 다른 멤버에게는 안내 · 탭이 없다.
ready: growth.nebulaReady && growth.isFounder,
onBirth: widget.onBirth,
)
: Stack(
clipBehavior: Clip.none,
children: [
Positioned.fill(
child: LivePlanet25D(
onOpenHistory: widget.onOpenHistory,
child: Stack(
clipBehavior: Clip.none,
children: [
Positioned.fill(
child: widget.unjoined
? _EmptyPlanet(
onCreateNebula: widget.onCreateNebula,
onJoin: widget.onJoin,
)
: growth.isNebula
? _Nebula(
key: _planetKey,
fandomId: fandomId,
watch: watchPlanet,
stage: growth.stage,
image: 'assets/figma/planet-home.png',
size: math.min(planet, box.maxHeight),
glow: AppColors.neonViolet,
// 탄생은 창립자만(Q-PN-01 A안) — 다른 멤버에게는 안내 · 탭이 없다.
ready:
growth.nebulaReady &&
growth.isFounder,
onBirth: widget.onBirth,
)
: Stack(
clipBehavior: Clip.none,
children: [
Positioned.fill(
child: LivePlanet25D(
onOpenHistory: widget.onOpenHistory,
key: _planetKey,
fandomId: fandomId,
watch: watchPlanet,
stage: growth.stage,
image:
'assets/figma/planet-home.png',
size: math.min(
planet,
box.maxHeight,
),
glow: AppColors.neonViolet,
),
),
],
),
),
],
),
// 이클립스 이벤트 버튼 — 명세 `516:88125` 「성운(행성) 아래 중앙 정렬」.
// 사본 `Group 563` 은 행성 상자(212) 안 아래쪽에 겹쳐 놓인다.
if (widget.eclipseButton case final button?)
Positioned(
left: 0,
right: 0,
bottom: 0,
child: Center(child: button),
),
],
),
),
),
),
+33 -15
View File
@@ -7,27 +7,45 @@ import '../../../core/theme/tokens.dart';
/// 사본 `Frame 1984081132`(「홈」 `2097:16428`) — 헤더 바로 아래 전폭 띠(높이 22 · 패딩 4/0 ·
/// 바탕 없음), 가운데 한 줄(42dot Medium 12 · 행간 14 흰색). 실시간 소셜 프루프(견적서 4주차)의
/// 표면이다.
///
/// 명세 `516:87664` *동작: 이벤트 발생 시 텍스트 우측에서 좌측으로 1회 슬라이드* — 문구가
/// 바뀔 때마다 오른쪽 밖에서 한 번 미끄러져 들어와 멈춘다.
class LiveTicker extends StatelessWidget {
const LiveTicker({super.key, required this.text});
final String text;
@override
Widget build(BuildContext context) => Container(
height: 22,
padding: const EdgeInsets.symmetric(horizontal: AppSpace.s4),
alignment: Alignment.center,
child: Text(
text,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: const TextStyle(
fontFamily: AppFonts.body,
fontSize: 12,
fontWeight: FontWeight.w500,
height: 14 / 12,
letterSpacing: -0.3,
color: AppColors.onCosmic,
Widget build(BuildContext context) => ClipRect(
child: Container(
height: 22,
padding: const EdgeInsets.symmetric(horizontal: AppSpace.s4),
alignment: Alignment.center,
child: TweenAnimationBuilder<double>(
key: ValueKey(text),
tween: Tween(begin: 1, end: 0),
duration: const Duration(milliseconds: 600),
curve: Curves.easeOutCubic,
// 띠 너비만큼 오른쪽 밖에서 출발한다.
builder: (context, dx, child) => LayoutBuilder(
builder: (context, box) => Transform.translate(
offset: Offset(dx * box.maxWidth, 0),
child: child,
),
),
child: Text(
text,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: const TextStyle(
fontFamily: AppFonts.body,
fontSize: 12,
fontWeight: FontWeight.w500,
height: 14 / 12,
letterSpacing: -0.3,
color: AppColors.onCosmic,
),
),
),
),
);