@tailwind base; @tailwind components; @tailwind utilities; @layer base { :root { --radius: 0.75rem; --success: 142 72% 29%; --success-foreground: 138 60% 94%; /* Mobile chrome dimensions — referenced by layout utility classes below */ --top-bar-height: 2.5rem; /* MobileTopBar h-10 = 40px */ --bottom-nav-height: 2.75rem; /* MobileBottomNav h-11 = 44px */ } .dark { --success: 142 60% 22%; --success-foreground: 138 60% 94%; } } @layer base { * { @apply border-border; scrollbar-width: none; } *::-webkit-scrollbar { display: none; } body { @apply bg-background text-foreground; } } @layer utilities { /* ── Safe-area inset utilities ──────────────────────────────────────────── Use var(--safe-area-inset-*, …) as the outer wrapper so that Capacitor's SystemBars plugin (which injects --safe-area-inset-* CSS variables on Android) takes precedence when available. The inner env(safe-area-inset-*, 0px) is the standard fallback for iOS / web. */ .safe-area-top { padding-top: var(--safe-area-inset-top, env(safe-area-inset-top, 0px)); } .safe-area-bottom { padding-bottom: var(--safe-area-inset-bottom, env(safe-area-inset-bottom, 0px)); } .safe-area-inset-top { top: var(--safe-area-inset-top, env(safe-area-inset-top, 0px)); } .safe-area-inset-bottom { bottom: var(--safe-area-inset-bottom, env(safe-area-inset-bottom, 0px)); } /* FAB bottom offset: clears bottom nav + safe area inset on mobile */ .bottom-fab { bottom: calc(1.5rem + var(--bottom-nav-height) + var(--safe-area-inset-bottom, env(safe-area-inset-bottom, 0px))); } /* Position above mobile bottom nav + safe area + arc overhang (28px) */ .bottom-mobile-nav { bottom: calc(var(--bottom-nav-height) + 28px + var(--safe-area-inset-bottom, env(safe-area-inset-bottom, 0px))); } /* Bottom overscroll padding for the center column: clears the mobile bottom nav + safe area + generous extra space so content can be scrolled well past the bottom bar */ .pb-overscroll { padding-bottom: calc(10vh + var(--bottom-nav-height) + var(--safe-area-inset-bottom, env(safe-area-inset-bottom, 0px))); } @media (min-width: 900px) { .pb-overscroll { padding-bottom: 10vh; } } /* Mobile top bar height + safe area inset for sticky elements */ .top-mobile-bar { top: calc(var(--top-bar-height) + var(--safe-area-inset-top, env(safe-area-inset-top, 0px))); } /* New-posts pill: just below the SubHeaderBar on both mobile and desktop */ .new-posts-pill { top: calc(var(--top-bar-height) + var(--safe-area-inset-top, env(safe-area-inset-top, 0px)) + 3.5rem); } @media (min-width: 900px) { .new-posts-pill { top: 4rem; } } /* Slide the sub-header bar out of view together with the mobile top bar. Must clear its own height (100%) + top bar + safe area + arc overhang (20px). */ @media (max-width: 899px) { .nav-hidden-slide { transform: translateY(calc(-100% - var(--top-bar-height) - 20px - var(--safe-area-inset-top, env(safe-area-inset-top, 0px)))); } } /* Negative margin to pull content area up behind the mobile top bar (only when it's visible) */ @media (max-width: 899px) { .-mt-mobile-bar { margin-top: calc(-1 * var(--top-bar-height) - var(--safe-area-inset-top, env(safe-area-inset-top, 0px))); padding-top: calc(var(--top-bar-height) + var(--safe-area-inset-top, env(safe-area-inset-top, 0px))); } } /* AI chat height on mobile: full viewport minus top bar, extends behind bottom nav. Padding-bottom keeps input above the nav. */ .ai-chat-height { height: calc(100dvh - var(--top-bar-height) - var(--safe-area-inset-top, env(safe-area-inset-top, 0px))); padding-bottom: calc(var(--bottom-nav-height) + var(--safe-area-inset-bottom, env(safe-area-inset-bottom, 0px))); } /* Live stream page height on mobile: full viewport minus top bar, bottom nav, and safe-area insets */ .livestream-height { height: calc(100dvh - var(--top-bar-height) - var(--bottom-nav-height) - var(--safe-area-inset-bottom, env(safe-area-inset-bottom, 0px))); max-height: calc(100dvh - var(--top-bar-height) - var(--bottom-nav-height) - var(--safe-area-inset-bottom, env(safe-area-inset-bottom, 0px))); } /* Vine feed slide height: full viewport on mobile (top bar + bottom nav are both hidden for an immersive experience). Tab bar floats over video. */ .vine-slide-height { height: 100dvh; } } @keyframes spin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } } /* 3-burst ping: pulses 3 times then stops */ @keyframes ping-3 { 0% { transform: scale(1); opacity: 0.6; } 11% { transform: scale(2); opacity: 0; } 22% { transform: scale(1); opacity: 0; } 33% { transform: scale(1); opacity: 0.6; } 44% { transform: scale(2); opacity: 0; } 55% { transform: scale(1); opacity: 0; } 66% { transform: scale(1); opacity: 0.6; } 77% { transform: scale(2); opacity: 0; } 100% { transform: scale(1); opacity: 0; } } .animate-ping-3 { animation: ping-3 4s ease-out forwards; } /* Slow continuous pulse for nagging buttons */ @keyframes pulse-slow { 0%, 100% { transform: scale(1); opacity: 0.5; } 50% { transform: scale(1.8); opacity: 0; } } .animate-pulse-slow { animation: pulse-slow 2.5s ease-in-out infinite; } /* Eerie sigil glow pulse */ @keyframes sigil-glow { 0%, 100% { opacity: 0.12; filter: drop-shadow(0 0 4px hsl(var(--primary) / 0.3)); } 50% { opacity: 0.28; filter: drop-shadow(0 0 12px hsl(var(--primary) / 0.6)) drop-shadow(0 0 24px hsl(var(--primary) / 0.25)); } } .animate-sigil-glow { animation: sigil-glow 4s ease-in-out infinite; } /* Eyeball blink animation - eyelid sweeps down then back up */ @keyframes eyelid-blink { 0% { transform: translateY(-100%); } 50% { transform: translateY(0%); } 100% { transform: translateY(-100%); } } .animate-eyelid-blink { animation: eyelid-blink 0.5s cubic-bezier(0.4, 0, 0.6, 1) forwards; } /* Landing hero staggered fade-in */ @keyframes landing-hero-fade-in { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: translateY(0); } } .landing-hero-fade { opacity: 0; animation: landing-hero-fade-in 0.5s cubic-bezier(0.16, 1, 0.3, 1) forwards; } @keyframes nudge-cancel-fade-in { from { opacity: 0; } to { opacity: 0.6; } } /* ── NIP-62 Request to Vanish ───────────────────────────────────────────────── */ /* Animated caution stripes — red/dark diagonal bands that scroll */ .vanish-stripes { background: repeating-linear-gradient( -45deg, hsl(0 72% 51% / 0.7), hsl(0 72% 51% / 0.7) 10px, hsl(0 72% 51% / 0.25) 10px, hsl(0 72% 51% / 0.25) 20px ); background-size: 28.28px 28.28px; animation: vanish-stripe-scroll 1.5s linear infinite; } :is(.dark) .vanish-stripes { background: repeating-linear-gradient( -45deg, hsl(0 72% 51% / 0.6), hsl(0 72% 51% / 0.6) 10px, hsl(0 72% 51% / 0.15) 10px, hsl(0 72% 51% / 0.15) 20px ); background-size: 28.28px 28.28px; animation: vanish-stripe-scroll 1.5s linear infinite; } @keyframes vanish-stripe-scroll { from { background-position: 0 0; } to { background-position: 28.28px 0; } } /* Glitch text effect on the /// symbol */ .vanish-glitch-text { position: relative; animation: vanish-glitch 3s ease-in-out infinite; } @keyframes vanish-glitch { 0%, 90%, 100% { opacity: 1; transform: translate(0); } 92% { opacity: 0.8; transform: translate(-2px, 1px); } 94% { opacity: 0.6; transform: translate(2px, -1px); } 96% { opacity: 0.9; transform: translate(-1px, -1px); } 98% { opacity: 1; transform: translate(1px, 0); } } /* Ring pulse animation for the icon */ .vanish-ring-pulse { animation: vanish-ring 3s ease-out infinite; } .vanish-ring-pulse-delayed { animation: vanish-ring 3s ease-out 1.5s infinite; } @keyframes vanish-ring { 0% { transform: scale(1); opacity: 0.4; } 50% { transform: scale(1.15); opacity: 0; } 100% { transform: scale(1); opacity: 0; } } /* Slow blink for the status dot */ .vanish-blink { animation: vanish-blink-kf 2s ease-in-out infinite; } @keyframes vanish-blink-kf { 0%, 40%, 100% { opacity: 1; } 50%, 90% { opacity: 0.2; } } /* ── Wii Mail envelope grid ─────────────────────────────────────────────────── */ /* Staggered entrance: envelopes pop in one by one */ @keyframes envelope-entrance { 0% { opacity: 0; transform: scale(0.6) translateY(20px) rotate(-4deg); } 60% { opacity: 1; transform: scale(1.05) translateY(-2px) rotate(1deg); } 80% { transform: scale(0.97) translateY(1px) rotate(-0.5deg); } 100% { opacity: 1; transform: scale(1) translateY(0) rotate(0deg); } } .envelope-card { animation: envelope-entrance 0.4s cubic-bezier(0.34, 1.56, 0.64, 1) both; animation-delay: var(--entrance-delay, 0ms); transform-origin: center bottom; cursor: pointer; } /* Hover wobble — playful Wii-style oscillation */ @keyframes envelope-wobble { 0% { transform: rotate(0deg); } 15% { transform: rotate(-5deg); } 30% { transform: rotate(4deg); } 45% { transform: rotate(-3deg); } 60% { transform: rotate(2deg); } 75% { transform: rotate(-1deg); } 100% { transform: rotate(0deg); } } .envelope-body { transition: transform 0.15s ease; } .envelope-card:hover .envelope-body, .envelope-card:focus-visible .envelope-body { animation: envelope-wobble 0.5s ease-in-out; } .envelope-card:active .envelope-body { transform: scale(0.92); animation: none; } /* Envelope skeleton shimmer */ @keyframes envelope-skeleton-shimmer { 0% { opacity: 0.4; } 50% { opacity: 0.7; } 100% { opacity: 0.4; } } .envelope-skeleton { animation: envelope-skeleton-shimmer 1.5s ease-in-out infinite; } /* Reduced motion: disable envelope animations */ @media (prefers-reduced-motion: reduce) { .envelope-card { animation: none; opacity: 1; } .envelope-card:hover .envelope-body, .envelope-card:focus-visible .envelope-body { animation: none; } } /* Drawn SVG stickers — ensure inline SVGs fill their container */ [data-sticker] svg, .sticker-svg-wrap svg { width: 100%; height: 100%; display: block; } /* Letter send animation keyframes */ @keyframes letter-send-fade-up { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: translateY(0); } } @keyframes letter-send-avatar-in { 0% { transform: scale(0); opacity: 0; } 50% { transform: scale(1.15); opacity: 1; } 70% { transform: scale(0.92); } 85% { transform: scale(1.04); } 100% { transform: scale(1); opacity: 1; } } @keyframes letter-send-burst { 0% { transform: translate(0, 0) rotate(0deg) scale(0); opacity: 0; } 15% { opacity: 1; transform: translate(0, 0) rotate(0deg) scale(1); } 60% { opacity: 0.8; transform: translate(var(--burst-tx), var(--burst-ty)) rotate(var(--burst-rot)) scale(1); } 100% { opacity: 0; transform: translate(var(--burst-tx), var(--burst-ty)) rotate(var(--burst-rot)) scale(0.4); } } /* Reduced motion: disable all vanish animations */ @media (prefers-reduced-motion: reduce) { .vanish-stripes { animation: none; } .vanish-glitch-text { animation: none; } .vanish-ring-pulse, .vanish-ring-pulse-delayed { animation: none; opacity: 0; } .vanish-blink { animation: none; } } /* ========================================== Blobbi Reaction Animations Shared animations for all Blobbi stages (egg, baby, adult) ========================================== */ @keyframes blobbi-gentle-sway { 0%, 100% { transform: rotate(-2deg); } 50% { transform: rotate(2deg); } } @keyframes blobbi-bounce { 0%, 100% { transform: translateY(0) rotate(0deg); } 25% { transform: translateY(-3px) rotate(1.5deg); } 50% { transform: translateY(0) rotate(0deg); } 75% { transform: translateY(-3px) rotate(-1.5deg); } } .animate-blobbi-sway { /* Baby/adult sway is livelier than egg (1.2s vs 1.8s) */ animation: blobbi-gentle-sway 1.2s ease-in-out infinite; } .animate-blobbi-bounce { /* Baby/adult sing bounce - smoother and cuter than before (0.5s) */ animation: blobbi-bounce 0.5s ease-in-out infinite; } @media (prefers-reduced-motion: reduce) { .animate-blobbi-sway, .animate-blobbi-bounce { animation: none !important; } } /* ─── Floating Music Notes ───────────────────────────────────────────────────── */ @keyframes float-up { 0% { opacity: 0; transform: translateY(0) scale(0.8); } 15% { opacity: 1; transform: translateY(-10px) scale(1); } 85% { opacity: 0.8; transform: translateY(-60px) scale(1); } 100% { opacity: 0; transform: translateY(-80px) scale(0.6); } } .animate-float-up { animation: float-up 3s ease-out infinite; } @media (prefers-reduced-motion: reduce) { .animate-float-up { animation: none !important; opacity: 0 !important; } } /* ─── Blobbi Eye Animation ───────────────────────────────────────────────────── */ /** * Eye animation for Blobbi characters. * * Features: * - Mouse tracking: eyes follow cursor instantly * - Natural blinking: random intervals (2-5s), fast close, slow open * * NO CSS transitions - all animation is controlled by JavaScript RAF loop. * Using transitions causes delayed visual updates. * * Transform combines: translate(x y) scale(1 blinkY) * - translate: mouse tracking position * - scale: vertical squash for blink effect (1 = open, 0.1 = closed) */ .blobbi-eye { /* SVG transform properties - scale from center of each eye element */ transform-box: fill-box; transform-origin: center center; /* NO transition - instant updates only */ } @media (prefers-reduced-motion: reduce) { .blobbi-eye { /* Disable all eye animations for users who prefer reduced motion */ transform: none !important; } } /* ── Milkdown Editor Styles ─────────────────────────────────────────────────── */ .milkdown-editor .milkdown { @apply outline-none; } .milkdown-editor .editor { @apply outline-none min-h-[400px] p-3; font-size: 1.125rem; line-height: 1.75; } .milkdown-editor .ProseMirror { @apply outline-none min-h-[400px]; } .milkdown-editor .ProseMirror:focus { @apply outline-none; } /* Headings */ .milkdown-editor h1 { @apply text-3xl font-bold mt-6 mb-4; } .milkdown-editor h2 { @apply text-2xl font-semibold mt-5 mb-3; } .milkdown-editor h3 { @apply text-xl font-semibold mt-4 mb-2; } .milkdown-editor h4 { @apply text-lg font-medium mt-3 mb-2; } /* Inline styles */ .milkdown-editor strong { @apply font-bold; } .milkdown-editor em { @apply italic; } .milkdown-editor del { @apply line-through text-muted-foreground; } .milkdown-editor code { @apply bg-muted px-1.5 py-0.5 rounded text-sm font-mono; } /* Block elements */ .milkdown-editor p { @apply my-1.5; } .milkdown-editor blockquote { @apply border-l-4 border-primary/40 pl-4 my-4 italic text-muted-foreground; } .milkdown-editor pre { @apply bg-muted rounded-lg p-4 my-4 overflow-x-auto; } .milkdown-editor pre code { @apply bg-transparent p-0 font-mono; font-size: 0.875rem; } /* Lists */ .milkdown-editor ul { @apply list-disc list-inside my-3 space-y-1; } .milkdown-editor ol { @apply list-decimal list-inside my-3 space-y-1; } .milkdown-editor li { @apply pl-1; } .milkdown-editor li p { @apply inline my-0; } /* Task lists (GFM) */ .milkdown-editor ul.task-list { @apply list-none pl-0; } .milkdown-editor li.task-list-item { @apply flex items-start gap-2 pl-0; } .milkdown-editor li.task-list-item input[type="checkbox"] { @apply mt-1.5 h-4 w-4 rounded border-border; } /* Links */ .milkdown-editor a { @apply text-primary underline underline-offset-2 hover:text-primary/80 transition-colors; } /* Horizontal rule */ .milkdown-editor hr { @apply my-6 border-border; } /* Images */ .milkdown-editor img { @apply max-w-full h-auto rounded-lg my-4; } /* Tables (GFM) */ .milkdown-editor table { @apply w-full border-collapse my-4; } .milkdown-editor th, .milkdown-editor td { @apply border border-border px-3 py-2 text-left; } .milkdown-editor th { @apply bg-muted font-semibold; } .milkdown-editor tr:nth-child(even) { @apply bg-muted/30; } /* Upload placeholder */ .milkdown-upload-placeholder { @apply inline-flex items-center gap-2 px-3 py-1.5 bg-muted/50 rounded-md text-sm text-muted-foreground; } .milkdown-upload-placeholder::before { content: ''; @apply w-4 h-4 border-2 border-muted-foreground/30 border-t-primary rounded-full animate-spin; } /* Placeholder — only when unfocused AND content is empty. */ .milkdown-editor .ProseMirror:not(:focus):not(.has-content) > p:first-child::before { @apply text-muted-foreground pointer-events-none float-left h-0; content: var(--ph, ''); } /* Milkdown content area */ .milkdown-editor .milkdown-content .ProseMirror { @apply min-h-[350px]; } /* Blobbi idle animations — speed/intensity driven by happiness via inline style */ @keyframes blobbi-bob { 0%, 100% { transform: translateY(0); } 50% { transform: translateY(-8px); } } @keyframes blobbi-sway { 0%, 100% { transform: rotate(0deg); } 25% { transform: rotate(1.5deg); } 75% { transform: rotate(-1.5deg); } } /* Hatch ceremony shake animations */ @keyframes blobbi-shake-light { 0%, 100% { transform: rotate(0deg); } 25% { transform: rotate(-3deg); } 75% { transform: rotate(3deg); } } @keyframes blobbi-shake-medium { 0%, 100% { transform: rotate(0deg); } 20% { transform: rotate(-6deg); } 40% { transform: rotate(5deg); } 60% { transform: rotate(-4deg); } 80% { transform: rotate(6deg); } } @keyframes blobbi-shake-heavy { 0%, 100% { transform: rotate(0deg) scale(1); } 15% { transform: rotate(-8deg) scale(1.02); } 30% { transform: rotate(7deg) scale(0.98); } 45% { transform: rotate(-9deg) scale(1.03); } 60% { transform: rotate(8deg) scale(0.97); } 75% { transform: rotate(-7deg) scale(1.02); } 90% { transform: rotate(9deg) scale(1); } } @keyframes blobbi-flash { 0% { opacity: 1; } 100% { opacity: 0; } } @keyframes blobbi-fade-to-white { 0% { opacity: 0; } 100% { opacity: 1; } }