Files
shinalok 800f42eab1 add habit leveling/XP system, explicit fail marking for missed habits, and scrollable habit tabs
- Habits and the account now accumulate XP and levels from check streaks, with
  streak/level-up celebration banners and push notifications.
- The "yesterday missed" banner on /today now lets a habit be explicitly marked
  failed (not just checked done), backed by a new HabitLog.status column so
  completion-rate/streak calculations never count a failed day as done.
- The habit management tabs scroll horizontally on narrow screens instead of
  wrapping to two lines, with a pure-CSS edge shadow indicating more content.
2026-07-20 18:18:56 +09:00

66 lines
2.3 KiB
HTML

{% if celebrate_streak %}
<div class="celebration-banner" x-data="{ show: true }" x-init="setTimeout(() => show = false, 4000)" x-show="show" x-transition>
🎉 <strong>{{ celebrate_habit_name }}</strong> {{ celebrate_streak }}일 연속 달성! 축하해요!
</div>
{% endif %}
{% if celebrate_level_up %}
<div class="celebration-banner" x-data="{ show: true }" x-init="setTimeout(() => show = false, 4000)" x-show="show" x-transition>
<strong>{{ celebrate_habit_name }}</strong> 레벨 {{ celebrate_level_up }}로 올랐어요!
</div>
{% endif %}
<div class="card account-level-card">
<span class="badge level-badge">{{ level_tier_emoji(account_level.level) }} 계정 Lv.{{ account_level.level }}</span>
<div class="xp-bar-wrap">
<div class="xp-bar"><div class="xp-bar-fill" style="width: {{ account_level.progress_pct }}%;"></div></div>
<span class="xp-bar-label">{{ account_level.xp_into_level }}/{{ account_level.xp_for_next_level }} xp</span>
</div>
</div>
<div
class="card"
style="display:flex; align-items:center; justify-content:space-between; flex-wrap:wrap; gap:8px;"
x-data="{ pushState: 'checking' }"
x-init="habitPush.getSubscriptionState().then(s => pushState = s)"
>
<h2 style="margin:0;">오늘</h2>
<div style="display:flex; align-items:center; gap:8px;">
<span class="badge">{{ checked_count }}/{{ total_count }} 완료</span>
<button
type="button"
class="btn btn-secondary"
x-show="pushState === 'unsubscribed'"
x-cloak
@click="habitPush.subscribePush().then(ok => { if (ok) pushState = 'subscribed'; })"
>알림 켜기</button>
<span class="badge" x-show="pushState === 'subscribed'" x-cloak>🔔 알림 켜짐</span>
</div>
</div>
{% if yesterday_missed %}
<h2>어제 놓친 습관</h2>
<div class="card">
{% for item in yesterday_missed %}
{% include "partials/yesterday_item.html" %}
{% endfor %}
</div>
{% endif %}
<h2>형성 습관</h2>
<div class="card">
{% for item in build_items %}
{% include "partials/today_item.html" %}
{% else %}
<div class="empty-state">오늘 예정된 형성 습관이 없어요.</div>
{% endfor %}
</div>
<h2>중단 습관</h2>
<div class="card">
{% for item in quit_items %}
{% include "partials/today_item.html" %}
{% else %}
<div class="empty-state">오늘 예정된 중단 습관이 없어요.</div>
{% endfor %}
</div>