mirror of
https://github.com/BillyOutlast/posthog.com.git
synced 2026-02-04 03:11:21 +01:00
Desktop loading indicator (#13182)
* desktop progress bar
* added homepage loading message
* dark mode hourglass in forums
* inline the lotties
* CSS hourglass spinner
* load a default loading state even faster
* everybody wants their own init file now
* no thank you
* Revert "no thank you"
This reverts commit 80fffc1a05.
* fixes
---------
Co-authored-by: Eli Kinsey <eli@ekinsey.dev>
This commit is contained in:
83
WARP.md
Normal file
83
WARP.md
Normal file
@@ -0,0 +1,83 @@
|
||||
# WARP.md
|
||||
|
||||
This file provides guidance to WARP (warp.dev) when working with code in this repository.
|
||||
|
||||
## Development Commands
|
||||
|
||||
### Core Development
|
||||
- **Start development server**: `yarn start` (runs on port 8001)
|
||||
- **Clean and restart**: `yarn clean && yarn && yarn start` (when things get weird)
|
||||
- **Build production**: `yarn build`
|
||||
- **Serve production build locally**: `gatsby build && gatsby serve`
|
||||
- **Clean build cache**: `gatsby clean`
|
||||
|
||||
### Code Quality
|
||||
- **Format code**: `yarn format` (formats all HTML, JS, TS, TSX, JSON, YML, CSS, SCSS)
|
||||
- **Format docs specifically**: `yarn format:docs` (fixes MDX formatting for changed files)
|
||||
- **Run tests**: `yarn test-redirects` (tests redirect configuration)
|
||||
- **Check links after build**: `yarn check-links-post-build`
|
||||
|
||||
### Content & Assets
|
||||
- **Update SVG sprite**: `yarn update-sprite` (for product feature icons)
|
||||
- **Generate TypeScript types**: `yarn typegen` (for Kea logic)
|
||||
|
||||
### Storybook
|
||||
- **Run Storybook**: `yarn storybook` (port 6006)
|
||||
- **Build Storybook**: `yarn build-storybook`
|
||||
|
||||
## Architecture Overview
|
||||
|
||||
### Desktop OS-Style Website
|
||||
PostHog.com is designed as a desktop operating system experience in the browser:
|
||||
- **Desktop Environment**: The main interface (`src/components/Desktop/index.tsx`) renders a macOS-style desktop with draggable icons
|
||||
- **Window Management**: Pages open in draggable, resizable windows (`src/components/AppWindow/index.tsx`) that can be maximized, minimized, or snapped
|
||||
- **Multi-Window Support**: Users can have multiple windows open simultaneously with a windows panel to manage them
|
||||
- **Global State**: Window management and app state handled by `src/context/App.tsx`
|
||||
|
||||
### Content Architecture
|
||||
- **Gatsby Static Site**: Built with Gatsby 4.25.9, hosted on Vercel
|
||||
- **MDX Content**: All content stored in `/contents/` directory (docs, blog, handbook, tutorials, etc.)
|
||||
- **Multi-Product Structure**: PostHog is multi-product, with product data centralized in hooks (`useProduct.ts`, `useProducts.tsx`)
|
||||
- **Customer Data**: Customer information centralized in `useCustomers.ts`
|
||||
- **Navigation**: All navigation menus defined in `src/navs/index.js`
|
||||
|
||||
### Key Integrations
|
||||
- **Squeak Forum**: In-house community forum integration
|
||||
- **Ashby Jobs**: Job listings via Ashby integration
|
||||
- **Shopify Merch Store**: Headless Shopify integration for merchandise
|
||||
- **Algolia Search**: Site-wide search functionality
|
||||
- **PostHog Analytics**: Self-hosted analytics (naturally)
|
||||
|
||||
### Component Structure
|
||||
- **Radix UI**: Custom Radix components in `/src/components/RadixUI/` with "Radix" prefix for imports
|
||||
- **OS Components**: Custom-built components prefixed with "OS" (OSButton, OSFieldset, OSIcons, OSTable, OSTabs)
|
||||
- **App Components**: Desktop "apps" like ReaderView, Wizard, Explorer for different content types
|
||||
|
||||
### Styling
|
||||
- **Tailwind CSS**: Primary styling system with custom color schemes
|
||||
- **Three Color Schemes**: Primary/secondary/tertiary with light/dark mode support
|
||||
- **Design System**: Consistent spacing, colors defined in `tailwind.config.js`
|
||||
- **Custom CSS**: Additional styles in `src/styles/global.css`
|
||||
|
||||
## Environment Setup
|
||||
- **Node**: Version 22.x required (use `nvm use` if available)
|
||||
- **Package Manager**: Yarn (not npm)
|
||||
- **Port**: Development server runs on 8001 (not default 8000)
|
||||
- **Apple Silicon**: May need to install vips (`brew install vips`) and remove node_modules
|
||||
|
||||
## Working with Content
|
||||
- **Content Location**: All written content in `/contents/` directory
|
||||
- **Product Data**: Check `useProduct.ts` first, then `useProducts.tsx` for product information
|
||||
- **Customer References**: Use `useCustomers.ts` for customer data (names, logos, quotes)
|
||||
- **Navigation Changes**: Modify `src/navs/index.js` but note it's shared with live site
|
||||
|
||||
## API Integrations
|
||||
- **Local PostHog API**: Override schema URL with `POSTHOG_OPEN_API_SPEC_URL` env var
|
||||
- **External Services**: Require API keys for Ashby (jobs), GitHub (contributors), Shopify (merch)
|
||||
- **Development**: Most features work without API keys, but some require environment variables
|
||||
|
||||
## Build & Deployment
|
||||
- **Vercel Hosting**: Deployed via Vercel with branch previews
|
||||
- **Build Process**: Gatsby build with custom post-build processing
|
||||
- **Redirects**: Test redirect configuration with Jest
|
||||
- **Performance**: Uses Cloudinary for image optimization and lazy loading
|
||||
@@ -28,13 +28,20 @@ export const wrapPageElement = ({ element, props: { location } }) => {
|
||||
)
|
||||
}
|
||||
|
||||
export const onRenderBody = function ({ setPreBodyComponents }) {
|
||||
export const onRenderBody = function ({ setPreBodyComponents, setPostBodyComponents }) {
|
||||
setPreBodyComponents([
|
||||
React.createElement('script', {
|
||||
key: 'dark-mode',
|
||||
src: '/scripts/theme-init.js',
|
||||
}),
|
||||
])
|
||||
|
||||
setPostBodyComponents([
|
||||
React.createElement('script', {
|
||||
key: 'initial-loader',
|
||||
src: '/scripts/initial-loader.js',
|
||||
}),
|
||||
])
|
||||
}
|
||||
|
||||
export const onPreRenderHTML = ({ getHeadComponents, replaceHeadComponents }) => {
|
||||
|
||||
90
src/components/Desktop/HourglassSpinner.tsx
Normal file
90
src/components/Desktop/HourglassSpinner.tsx
Normal file
@@ -0,0 +1,90 @@
|
||||
import React from 'react'
|
||||
|
||||
export default function HourglassSpinner({ className = '' }: { className?: string }) {
|
||||
return (
|
||||
<svg
|
||||
className={`hourglass-spinner ${className}`}
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<style>
|
||||
{`
|
||||
.hourglass-spinner {
|
||||
animation: hourglass-flip 2.4s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes hourglass-flip {
|
||||
0%, 40% {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
48%, 90% {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
98%, 100% {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
.sand-top {
|
||||
transform-origin: center bottom;
|
||||
animation: sand-drain-top 2.4s ease-in-out infinite;
|
||||
}
|
||||
|
||||
.sand-bottom {
|
||||
transform-origin: center top;
|
||||
animation: sand-drain-bottom 2.4s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes sand-drain-top {
|
||||
0% {
|
||||
transform: scaleY(1);
|
||||
opacity: 0.6;
|
||||
}
|
||||
40% {
|
||||
transform: scaleY(0);
|
||||
opacity: 0;
|
||||
}
|
||||
48%, 100% {
|
||||
transform: scaleY(0);
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes sand-drain-bottom {
|
||||
0%, 48% {
|
||||
transform: scaleY(0);
|
||||
opacity: 0;
|
||||
}
|
||||
50% {
|
||||
transform: scaleY(1);
|
||||
opacity: 0.6;
|
||||
}
|
||||
90% {
|
||||
transform: scaleY(0);
|
||||
opacity: 0;
|
||||
}
|
||||
100% {
|
||||
transform: scaleY(0);
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
`}
|
||||
</style>
|
||||
{/* Hourglass outline */}
|
||||
<path
|
||||
d="M6 2h12M6 22h12M18 2v3.5c0 1.5-1 2.5-2.5 4L12 13l-3.5-3.5C7 8 6 7 6 5.5V2M18 22v-3.5c0-1.5-1-2.5-2.5-4L12 11l-3.5 3.5C7 16 6 17 6 18.5V22"
|
||||
stroke="currentColor"
|
||||
strokeWidth="1.5"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
{/* Top sand - triangle filling top chamber */}
|
||||
<path className="sand-top" d="M8 4 L16 4 L14.5 7 L9.5 7 Z" fill="currentColor" opacity="0.6" />
|
||||
{/* Bottom sand - triangle filling bottom chamber */}
|
||||
<path className="sand-bottom" d="M8 20 L16 20 L14.5 17 L9.5 17 Z" fill="currentColor" opacity="0.6" />
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
@@ -16,7 +16,9 @@ import { motion } from 'framer-motion'
|
||||
import { DebugContainerQuery } from 'components/DebugContainerQuery'
|
||||
import HedgeHogModeEmbed from 'components/HedgehogMode'
|
||||
import ReactConfetti from 'react-confetti'
|
||||
import ProgressBar from 'components/ProgressBar'
|
||||
import OSButton from 'components/OSButton'
|
||||
import HourglassSpinner from './HourglassSpinner'
|
||||
|
||||
interface Product {
|
||||
name: string
|
||||
@@ -191,6 +193,17 @@ const validateIconPositions = (
|
||||
return true
|
||||
}
|
||||
|
||||
const LOADING_MESSAGES = [
|
||||
'Booting the PostHog experience',
|
||||
'Compiling hedgehog shaders',
|
||||
'Rebuilding webpack',
|
||||
'Hydrating the hedgehogs',
|
||||
'Sourcing and transforming nodes',
|
||||
<>
|
||||
Running <code>yarn serve</code>
|
||||
</>,
|
||||
]
|
||||
|
||||
export default function Desktop() {
|
||||
const productLinks = useProductLinks()
|
||||
const {
|
||||
@@ -209,6 +222,8 @@ export default function Desktop() {
|
||||
})
|
||||
const [rendered, setRendered] = useState(false)
|
||||
const { getWallpaperClasses } = useTheme()
|
||||
const [currentMessageIndex, setCurrentMessageIndex] = useState(0)
|
||||
const [isMessageExiting, setIsMessageExiting] = useState(false)
|
||||
|
||||
function generateInitialPositions(): IconPositions {
|
||||
const positions: IconPositions = {}
|
||||
@@ -265,6 +280,12 @@ export default function Desktop() {
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
// Hide the initial SSR loader once React has hydrated
|
||||
const initialLoader = document.getElementById('initial-loader')
|
||||
if (initialLoader) {
|
||||
initialLoader.style.display = 'none'
|
||||
}
|
||||
|
||||
const savedPositions = localStorage.getItem(STORAGE_KEY)
|
||||
if (savedPositions) {
|
||||
try {
|
||||
@@ -300,6 +321,21 @@ export default function Desktop() {
|
||||
}
|
||||
}, [posthogInstance])
|
||||
|
||||
useEffect(() => {
|
||||
if (rendered) return
|
||||
|
||||
const interval = setInterval(() => {
|
||||
setIsMessageExiting(true)
|
||||
|
||||
setTimeout(() => {
|
||||
setCurrentMessageIndex((prev) => (prev + 1) % LOADING_MESSAGES.length)
|
||||
setIsMessageExiting(false)
|
||||
}, 300)
|
||||
}, 1000)
|
||||
|
||||
return () => clearInterval(interval)
|
||||
}, [rendered, currentMessageIndex])
|
||||
|
||||
const handlePositionChange = (appLabel: string, position: IconPosition) => {
|
||||
const newPositions = { ...iconPositions, [appLabel]: position }
|
||||
setIconPositions(newPositions)
|
||||
@@ -544,6 +580,26 @@ export default function Desktop() {
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{!rendered && (
|
||||
<div
|
||||
data-scheme="secondary"
|
||||
className="fixed top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-full flex items-center justify-center z-50"
|
||||
>
|
||||
<div className="flex items-center justify-center gap-2 w-80 bg-primary/75 backdrop-blur border border-primary rounded p-4">
|
||||
<HourglassSpinner className="text-primary opacity-75" />
|
||||
<div className="flex-1 relative">
|
||||
<strong
|
||||
key={currentMessageIndex}
|
||||
className={`font-medium text-secondary block ${
|
||||
isMessageExiting ? 'animate-slide-up-fade-out' : 'animate-slide-up-fade-in'
|
||||
}`}
|
||||
>
|
||||
{LOADING_MESSAGES[currentMessageIndex]}
|
||||
</strong>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
1669
src/components/Desktop/lottieAnimations.ts
Normal file
1669
src/components/Desktop/lottieAnimations.ts
Normal file
File diff suppressed because it is too large
Load Diff
@@ -18,6 +18,7 @@ import { useUser } from 'hooks/useUser'
|
||||
import { navigate } from 'gatsby'
|
||||
import Lottie from 'lottie-react'
|
||||
import hourglassAnimation from 'images/icons8-hourglass.json'
|
||||
import hourglassAnimationWhite from 'images/icons8-hourglass-white.json'
|
||||
import { useInView } from 'react-intersection-observer'
|
||||
import useTopicsNav from '../../navs/useTopicsNav'
|
||||
import { useWindow } from '../../context/Window'
|
||||
@@ -441,7 +442,12 @@ export default function Inbox(props) {
|
||||
<div className="flex items-center justify-center py-8 h-full">
|
||||
<Lottie
|
||||
animationData={hourglassAnimation}
|
||||
className="size-6 opacity-75"
|
||||
className="size-6 opacity-75 dark:hidden"
|
||||
title="Loading questions..."
|
||||
/>
|
||||
<Lottie
|
||||
animationData={hourglassAnimationWhite}
|
||||
className="size-6 opacity-75 hidden dark:block"
|
||||
title="Loading questions..."
|
||||
/>
|
||||
</div>
|
||||
|
||||
774
src/images/icons8-hourglass-white.json
Normal file
774
src/images/icons8-hourglass-white.json
Normal file
@@ -0,0 +1,774 @@
|
||||
{
|
||||
"nm": "hourglass",
|
||||
"h": 30,
|
||||
"w": 30,
|
||||
"meta": { "g": "@lottiefiles/toolkit-js 0.33.2" },
|
||||
"layers": [
|
||||
{
|
||||
"ty": 4,
|
||||
"nm": "middle Outlines",
|
||||
"sr": 1,
|
||||
"st": 0,
|
||||
"op": 28,
|
||||
"ip": 0,
|
||||
"hasMask": false,
|
||||
"ao": 0,
|
||||
"ks": {
|
||||
"a": { "a": 0, "k": [1, 5.5, 0] },
|
||||
"s": { "a": 0, "k": [100, 100, 100] },
|
||||
"sk": { "a": 0, "k": 0 },
|
||||
"p": { "a": 0, "k": [15, 15.5, 0] },
|
||||
"r": { "a": 0, "k": 0 },
|
||||
"sa": { "a": 0, "k": 0 },
|
||||
"o": { "a": 0, "k": 100 }
|
||||
},
|
||||
"ef": [],
|
||||
"shapes": [
|
||||
{
|
||||
"ty": "gr",
|
||||
"nm": "Group 1",
|
||||
"it": [
|
||||
{
|
||||
"ty": "sh",
|
||||
"nm": "Path 1",
|
||||
"d": 1,
|
||||
"ks": {
|
||||
"a": 0,
|
||||
"k": {
|
||||
"c": false,
|
||||
"i": [
|
||||
[0, 0],
|
||||
[0, 0]
|
||||
],
|
||||
"o": [
|
||||
[0, 0],
|
||||
[0, 0]
|
||||
],
|
||||
"v": [
|
||||
[1, 2.188],
|
||||
[1, 11]
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"ty": "st",
|
||||
"nm": "Stroke 1",
|
||||
"lc": 2,
|
||||
"lj": 2,
|
||||
"ml": 1,
|
||||
"o": { "a": 0, "k": 100 },
|
||||
"w": { "a": 0, "k": 2 },
|
||||
"c": { "a": 0, "k": [1, 1, 1] }
|
||||
},
|
||||
{
|
||||
"ty": "tr",
|
||||
"a": { "a": 0, "k": [0, 0] },
|
||||
"s": { "a": 0, "k": [100, 100] },
|
||||
"sk": { "a": 0, "k": 0 },
|
||||
"p": { "a": 0, "k": [0, 0] },
|
||||
"r": { "a": 0, "k": 0 },
|
||||
"sa": { "a": 0, "k": 0 },
|
||||
"o": { "a": 0, "k": 100 }
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"ty": "tm",
|
||||
"nm": "Trim Paths 1",
|
||||
"e": {
|
||||
"a": 1,
|
||||
"k": [
|
||||
{ "o": { "x": 0.333, "y": 0 }, "i": { "x": 0.667, "y": 1 }, "s": [0], "t": 18 },
|
||||
{ "s": [100], "t": 22 }
|
||||
]
|
||||
},
|
||||
"o": { "a": 0, "k": 0 },
|
||||
"s": {
|
||||
"a": 1,
|
||||
"k": [
|
||||
{ "o": { "x": 0.333, "y": 0 }, "i": { "x": 0.667, "y": 1 }, "s": [0], "t": 18 },
|
||||
{ "o": { "x": 0.333, "y": 0 }, "i": { "x": 0.667, "y": 1 }, "s": [0], "t": 22 },
|
||||
{ "s": [100], "t": 25 }
|
||||
]
|
||||
},
|
||||
"m": 1
|
||||
}
|
||||
],
|
||||
"ind": 1
|
||||
},
|
||||
{
|
||||
"ty": 4,
|
||||
"nm": "hourglass Outlines",
|
||||
"sr": 1,
|
||||
"st": 0,
|
||||
"op": 28,
|
||||
"ip": 0,
|
||||
"hasMask": false,
|
||||
"ao": 0,
|
||||
"ks": {
|
||||
"a": { "a": 0, "k": [12, 16, 0] },
|
||||
"s": {
|
||||
"a": 1,
|
||||
"k": [
|
||||
{ "o": { "x": 0.333, "y": 0 }, "i": { "x": 0.667, "y": 1 }, "s": [100, 100, 100], "t": 3 },
|
||||
{ "o": { "x": 0.333, "y": 0 }, "i": { "x": 0.667, "y": 1 }, "s": [90, 90, 100], "t": 5 },
|
||||
{ "o": { "x": 0.333, "y": 0 }, "i": { "x": 0.667, "y": 1 }, "s": [90, 90, 100], "t": 9 },
|
||||
{ "o": { "x": 0.333, "y": 0 }, "i": { "x": 0.667, "y": 1 }, "s": [100, 100, 100], "t": 11 },
|
||||
{ "s": [100, 100, 100], "t": 18 }
|
||||
]
|
||||
},
|
||||
"sk": { "a": 0, "k": 0 },
|
||||
"p": { "a": 0, "k": [15, 15, 0] },
|
||||
"r": {
|
||||
"a": 1,
|
||||
"k": [
|
||||
{ "o": { "x": 0.333, "y": 0 }, "i": { "x": 0.667, "y": 1 }, "s": [0], "t": 3 },
|
||||
{ "o": { "x": 0.333, "y": 0 }, "i": { "x": 0.667, "y": 1 }, "s": [40], "t": 5 },
|
||||
{ "o": { "x": 0.333, "y": 0 }, "i": { "x": 0.667, "y": 1 }, "s": [193], "t": 9 },
|
||||
{ "o": { "x": 0.333, "y": 0 }, "i": { "x": 0.667, "y": 1 }, "s": [180], "t": 11 },
|
||||
{ "s": [180], "t": 18 }
|
||||
]
|
||||
},
|
||||
"sa": { "a": 0, "k": 0 },
|
||||
"o": { "a": 0, "k": 100 }
|
||||
},
|
||||
"ef": [],
|
||||
"shapes": [
|
||||
{
|
||||
"ty": "gr",
|
||||
"nm": "Group 1",
|
||||
"it": [
|
||||
{
|
||||
"ty": "sh",
|
||||
"nm": "Path 1",
|
||||
"d": 1,
|
||||
"ks": {
|
||||
"a": 0,
|
||||
"k": {
|
||||
"c": true,
|
||||
"i": [
|
||||
[0, 0],
|
||||
[0, 0],
|
||||
[0, 0],
|
||||
[0, 0]
|
||||
],
|
||||
"o": [
|
||||
[0, 0],
|
||||
[0, 0],
|
||||
[0, 0],
|
||||
[0, 0]
|
||||
],
|
||||
"v": [
|
||||
[-7, -2],
|
||||
[7, -2],
|
||||
[7, 2],
|
||||
[-7, 2]
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"ty": "fl",
|
||||
"nm": "Fill 1",
|
||||
"c": { "a": 0, "k": [1, 1, 1] },
|
||||
"r": 1,
|
||||
"o": { "a": 0, "k": 100 }
|
||||
},
|
||||
{
|
||||
"ty": "tr",
|
||||
"a": { "a": 0, "k": [0, 0] },
|
||||
"s": { "a": 0, "k": [100, 100] },
|
||||
"sk": { "a": 0, "k": 0 },
|
||||
"p": { "a": 0, "k": [12, 25] },
|
||||
"r": { "a": 0, "k": 0 },
|
||||
"sa": { "a": 0, "k": 0 },
|
||||
"o": { "a": 0, "k": 100 }
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"ty": "gr",
|
||||
"nm": "Group 2",
|
||||
"it": [
|
||||
{
|
||||
"ty": "sh",
|
||||
"nm": "Path 1",
|
||||
"d": 1,
|
||||
"ks": {
|
||||
"a": 0,
|
||||
"k": {
|
||||
"c": true,
|
||||
"i": [
|
||||
[0, 0],
|
||||
[0, 0],
|
||||
[0, 0],
|
||||
[0, 0]
|
||||
],
|
||||
"o": [
|
||||
[0, 0],
|
||||
[0, 0],
|
||||
[0, 0],
|
||||
[0, 0]
|
||||
],
|
||||
"v": [
|
||||
[-7, -2],
|
||||
[7, -2],
|
||||
[7, 2],
|
||||
[-7, 2]
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"ty": "fl",
|
||||
"nm": "Fill 1",
|
||||
"c": { "a": 0, "k": [1, 1, 1] },
|
||||
"r": 1,
|
||||
"o": { "a": 0, "k": 100 }
|
||||
},
|
||||
{
|
||||
"ty": "tr",
|
||||
"a": { "a": 0, "k": [0, 0] },
|
||||
"s": { "a": 0, "k": [100, 100] },
|
||||
"sk": { "a": 0, "k": 0 },
|
||||
"p": { "a": 0, "k": [12, 7] },
|
||||
"r": { "a": 0, "k": 0 },
|
||||
"sa": { "a": 0, "k": 0 },
|
||||
"o": { "a": 0, "k": 100 }
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"ty": "gr",
|
||||
"nm": "Group 3",
|
||||
"it": [
|
||||
{
|
||||
"ty": "sh",
|
||||
"nm": "Path 1",
|
||||
"d": 1,
|
||||
"ks": {
|
||||
"a": 0,
|
||||
"k": {
|
||||
"c": false,
|
||||
"i": [
|
||||
[0, 0],
|
||||
[0, 0],
|
||||
[0, 0],
|
||||
[0, 5],
|
||||
[0, 0]
|
||||
],
|
||||
"o": [
|
||||
[0, 0],
|
||||
[0, -5],
|
||||
[0, 0],
|
||||
[0, 0],
|
||||
[0, 0]
|
||||
],
|
||||
"v": [
|
||||
[2.5, 11],
|
||||
[2.5, 8],
|
||||
[-2.5, 0],
|
||||
[2.5, -8],
|
||||
[2.5, -11]
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"ty": "st",
|
||||
"nm": "Stroke 1",
|
||||
"lc": 2,
|
||||
"lj": 2,
|
||||
"ml": 1,
|
||||
"o": { "a": 0, "k": 100 },
|
||||
"w": { "a": 0, "k": 2 },
|
||||
"c": { "a": 0, "k": [1, 1, 1] }
|
||||
},
|
||||
{
|
||||
"ty": "tr",
|
||||
"a": { "a": 0, "k": [0, 0] },
|
||||
"s": { "a": 0, "k": [100, 100] },
|
||||
"sk": { "a": 0, "k": 0 },
|
||||
"p": { "a": 0, "k": [16.5, 16.001] },
|
||||
"r": { "a": 0, "k": 0 },
|
||||
"sa": { "a": 0, "k": 0 },
|
||||
"o": { "a": 0, "k": 100 }
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"ty": "gr",
|
||||
"nm": "Group 4",
|
||||
"it": [
|
||||
{
|
||||
"ty": "sh",
|
||||
"nm": "Path 1",
|
||||
"d": 1,
|
||||
"ks": {
|
||||
"a": 0,
|
||||
"k": {
|
||||
"c": false,
|
||||
"i": [
|
||||
[0, 0],
|
||||
[0, 0],
|
||||
[0, 0],
|
||||
[0, -5],
|
||||
[0, 0]
|
||||
],
|
||||
"o": [
|
||||
[0, 0],
|
||||
[0, 5],
|
||||
[0, 0],
|
||||
[0, 0],
|
||||
[0, 0]
|
||||
],
|
||||
"v": [
|
||||
[-2.5, -11],
|
||||
[-2.5, -8],
|
||||
[2.5, 0],
|
||||
[-2.5, 8],
|
||||
[-2.5, 11]
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"ty": "st",
|
||||
"nm": "Stroke 1",
|
||||
"lc": 2,
|
||||
"lj": 2,
|
||||
"ml": 1,
|
||||
"o": { "a": 0, "k": 100 },
|
||||
"w": { "a": 0, "k": 2 },
|
||||
"c": { "a": 0, "k": [1, 1, 1] }
|
||||
},
|
||||
{
|
||||
"ty": "tr",
|
||||
"a": { "a": 0, "k": [0, 0] },
|
||||
"s": { "a": 0, "k": [100, 100] },
|
||||
"sk": { "a": 0, "k": 0 },
|
||||
"p": { "a": 0, "k": [7.5, 16] },
|
||||
"r": { "a": 0, "k": 0 },
|
||||
"sa": { "a": 0, "k": 0 },
|
||||
"o": { "a": 0, "k": 100 }
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"ty": "gr",
|
||||
"nm": "Group 5",
|
||||
"it": [
|
||||
{
|
||||
"ty": "sh",
|
||||
"nm": "Path 1",
|
||||
"d": 1,
|
||||
"ks": {
|
||||
"a": 0,
|
||||
"k": {
|
||||
"c": false,
|
||||
"i": [
|
||||
[0, 0],
|
||||
[0, 0]
|
||||
],
|
||||
"o": [
|
||||
[0, 0],
|
||||
[0, 0]
|
||||
],
|
||||
"v": [
|
||||
[3, 27.001],
|
||||
[21, 27.001]
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"ty": "st",
|
||||
"nm": "Stroke 1",
|
||||
"lc": 2,
|
||||
"lj": 2,
|
||||
"ml": 1,
|
||||
"o": { "a": 0, "k": 100 },
|
||||
"w": { "a": 0, "k": 2 },
|
||||
"c": { "a": 0, "k": [1, 1, 1] }
|
||||
},
|
||||
{
|
||||
"ty": "tr",
|
||||
"a": { "a": 0, "k": [0, 0] },
|
||||
"s": { "a": 0, "k": [100, 100] },
|
||||
"sk": { "a": 0, "k": 0 },
|
||||
"p": { "a": 0, "k": [0, 0] },
|
||||
"r": { "a": 0, "k": 0 },
|
||||
"sa": { "a": 0, "k": 0 },
|
||||
"o": { "a": 0, "k": 100 }
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"ty": "gr",
|
||||
"nm": "Group 6",
|
||||
"it": [
|
||||
{
|
||||
"ty": "sh",
|
||||
"nm": "Path 1",
|
||||
"d": 1,
|
||||
"ks": {
|
||||
"a": 0,
|
||||
"k": {
|
||||
"c": false,
|
||||
"i": [
|
||||
[0, 0],
|
||||
[0, 0]
|
||||
],
|
||||
"o": [
|
||||
[0, 0],
|
||||
[0, 0]
|
||||
],
|
||||
"v": [
|
||||
[3, 5],
|
||||
[21, 5]
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"ty": "st",
|
||||
"nm": "Stroke 1",
|
||||
"lc": 2,
|
||||
"lj": 2,
|
||||
"ml": 1,
|
||||
"o": { "a": 0, "k": 100 },
|
||||
"w": { "a": 0, "k": 2 },
|
||||
"c": { "a": 0, "k": [1, 1, 1] }
|
||||
},
|
||||
{
|
||||
"ty": "tr",
|
||||
"a": { "a": 0, "k": [0, 0] },
|
||||
"s": { "a": 0, "k": [100, 100] },
|
||||
"sk": { "a": 0, "k": 0 },
|
||||
"p": { "a": 0, "k": [0, 0] },
|
||||
"r": { "a": 0, "k": 0 },
|
||||
"sa": { "a": 0, "k": 0 },
|
||||
"o": { "a": 0, "k": 100 }
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"ind": 2
|
||||
},
|
||||
{
|
||||
"ty": 4,
|
||||
"nm": "bottom shape",
|
||||
"sr": 1,
|
||||
"st": 0,
|
||||
"op": 23,
|
||||
"ip": 0,
|
||||
"hasMask": false,
|
||||
"ao": 0,
|
||||
"ks": {
|
||||
"a": { "a": 0, "k": [4.167, 1.844, 0] },
|
||||
"s": { "a": 0, "k": [100, 100, 100] },
|
||||
"sk": { "a": 0, "k": 0 },
|
||||
"p": { "a": 0, "k": [12, 21.594, 0] },
|
||||
"r": { "a": 0, "k": 0 },
|
||||
"sa": { "a": 0, "k": 0 },
|
||||
"o": { "a": 0, "k": 100 }
|
||||
},
|
||||
"ef": [],
|
||||
"shapes": [
|
||||
{
|
||||
"ty": "gr",
|
||||
"nm": "Group 1",
|
||||
"it": [
|
||||
{
|
||||
"ty": "sh",
|
||||
"nm": "Path 1",
|
||||
"d": 1,
|
||||
"ks": {
|
||||
"a": 1,
|
||||
"k": [
|
||||
{
|
||||
"o": { "x": 0.167, "y": 0.167 },
|
||||
"i": { "x": 0.833, "y": 0.833 },
|
||||
"s": [
|
||||
{
|
||||
"c": true,
|
||||
"i": [
|
||||
[0, 0],
|
||||
[1.931, 0],
|
||||
[0.376, -1.819]
|
||||
],
|
||||
"o": [
|
||||
[-0.376, -1.819],
|
||||
[-1.931, 0],
|
||||
[0, 0]
|
||||
],
|
||||
"v": [
|
||||
[3.917, 1.594],
|
||||
[0, -1.594],
|
||||
[-3.917, 1.594]
|
||||
]
|
||||
}
|
||||
],
|
||||
"t": 3
|
||||
},
|
||||
{
|
||||
"o": { "x": 0.167, "y": 0.167 },
|
||||
"i": { "x": 0.833, "y": 0.833 },
|
||||
"s": [
|
||||
{
|
||||
"c": true,
|
||||
"i": [
|
||||
[0, 0],
|
||||
[1.931, 0],
|
||||
[0.376, -1.819]
|
||||
],
|
||||
"o": [
|
||||
[-0.376, -1.819],
|
||||
[-1.931, 0],
|
||||
[0, 0]
|
||||
],
|
||||
"v": [
|
||||
[4.008, 2.243],
|
||||
[0.091, -0.945],
|
||||
[-3.826, 2.243]
|
||||
]
|
||||
}
|
||||
],
|
||||
"t": 5
|
||||
},
|
||||
{
|
||||
"o": { "x": 0.167, "y": 0.167 },
|
||||
"i": { "x": 0.833, "y": 0.833 },
|
||||
"s": [
|
||||
{
|
||||
"c": true,
|
||||
"i": [
|
||||
[0, 0],
|
||||
[1.931, 0],
|
||||
[0.376, -1.819]
|
||||
],
|
||||
"o": [
|
||||
[-0.376, -1.819],
|
||||
[-1.931, 0],
|
||||
[0, 0]
|
||||
],
|
||||
"v": [
|
||||
[3.972, 0.541],
|
||||
[0.055, -2.647],
|
||||
[-3.862, 0.541]
|
||||
]
|
||||
}
|
||||
],
|
||||
"t": 9
|
||||
},
|
||||
{
|
||||
"o": { "x": 0.167, "y": 0.167 },
|
||||
"i": { "x": 0.833, "y": 0.833 },
|
||||
"s": [
|
||||
{
|
||||
"c": true,
|
||||
"i": [
|
||||
[0, 0],
|
||||
[1.471, 0],
|
||||
[0.286, -1.386]
|
||||
],
|
||||
"o": [
|
||||
[-0.286, -1.386],
|
||||
[-1.471, 0],
|
||||
[0, 0]
|
||||
],
|
||||
"v": [
|
||||
[2.918, -0.656],
|
||||
[-0.066, -3.085],
|
||||
[-3.05, -0.656]
|
||||
]
|
||||
}
|
||||
],
|
||||
"t": 11
|
||||
},
|
||||
{
|
||||
"o": { "x": 0.167, "y": 0.167 },
|
||||
"i": { "x": 0.833, "y": 0.833 },
|
||||
"s": [
|
||||
{
|
||||
"c": true,
|
||||
"i": [
|
||||
[0, 0],
|
||||
[1.471, 0],
|
||||
[0.286, -1.386]
|
||||
],
|
||||
"o": [
|
||||
[-0.286, -1.386],
|
||||
[-1.471, 0],
|
||||
[0, 0]
|
||||
],
|
||||
"v": [
|
||||
[2.918, -0.656],
|
||||
[-0.066, -3.085],
|
||||
[-3.05, -0.656]
|
||||
]
|
||||
}
|
||||
],
|
||||
"t": 20
|
||||
},
|
||||
{
|
||||
"s": [
|
||||
{
|
||||
"c": true,
|
||||
"i": [
|
||||
[0, 0],
|
||||
[0.832, 0],
|
||||
[0.162, -0.784]
|
||||
],
|
||||
"o": [
|
||||
[-0.162, -0.784],
|
||||
[-0.832, 0],
|
||||
[0, 0]
|
||||
],
|
||||
"v": [
|
||||
[1.688, -1.991],
|
||||
[0, -3.365],
|
||||
[-1.688, -1.991]
|
||||
]
|
||||
}
|
||||
],
|
||||
"t": 23
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"ty": "fl",
|
||||
"nm": "Fill 1",
|
||||
"c": { "a": 0, "k": [1, 1, 1] },
|
||||
"r": 1,
|
||||
"o": { "a": 0, "k": 100 }
|
||||
},
|
||||
{
|
||||
"ty": "tr",
|
||||
"a": { "a": 0, "k": [0, 0] },
|
||||
"s": { "a": 0, "k": [100, 100] },
|
||||
"sk": { "a": 0, "k": 0 },
|
||||
"p": { "a": 0, "k": [4.167, 1.844] },
|
||||
"r": { "a": 0, "k": 0 },
|
||||
"sa": { "a": 0, "k": 0 },
|
||||
"o": { "a": 0, "k": 100 }
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"ind": 3,
|
||||
"parent": 2
|
||||
},
|
||||
{
|
||||
"ty": 4,
|
||||
"nm": "bottom shape2",
|
||||
"sr": 1,
|
||||
"st": 0,
|
||||
"op": 28,
|
||||
"ip": 22,
|
||||
"hasMask": false,
|
||||
"ao": 0,
|
||||
"ks": {
|
||||
"a": { "a": 0, "k": [4.167, 1.844, 0] },
|
||||
"s": { "a": 0, "k": [100, 100, 100] },
|
||||
"sk": { "a": 0, "k": 0 },
|
||||
"p": { "a": 0, "k": [15, 20.594, 0] },
|
||||
"r": { "a": 0, "k": 0 },
|
||||
"sa": { "a": 0, "k": 0 },
|
||||
"o": { "a": 0, "k": 100 }
|
||||
},
|
||||
"ef": [],
|
||||
"shapes": [
|
||||
{
|
||||
"ty": "gr",
|
||||
"nm": "Group 1",
|
||||
"it": [
|
||||
{
|
||||
"ty": "sh",
|
||||
"nm": "Path 1",
|
||||
"d": 1,
|
||||
"ks": {
|
||||
"a": 1,
|
||||
"k": [
|
||||
{
|
||||
"o": { "x": 0.333, "y": 0 },
|
||||
"i": { "x": 0.667, "y": 1 },
|
||||
"s": [
|
||||
{
|
||||
"c": true,
|
||||
"i": [
|
||||
[0, 0],
|
||||
[0.678, 0],
|
||||
[0.132, -0.639]
|
||||
],
|
||||
"o": [
|
||||
[-0.132, -0.639],
|
||||
[-0.678, 0],
|
||||
[0, 0]
|
||||
],
|
||||
"v": [
|
||||
[1.375, 1.56],
|
||||
[0, 0.44],
|
||||
[-1.375, 1.56]
|
||||
]
|
||||
}
|
||||
],
|
||||
"t": 22
|
||||
},
|
||||
{
|
||||
"s": [
|
||||
{
|
||||
"c": true,
|
||||
"i": [
|
||||
[0, 0],
|
||||
[1.931, 0],
|
||||
[0.376, -1.819]
|
||||
],
|
||||
"o": [
|
||||
[-0.376, -1.819],
|
||||
[-1.931, 0],
|
||||
[0, 0]
|
||||
],
|
||||
"v": [
|
||||
[3.917, 1.594],
|
||||
[0, -1.594],
|
||||
[-3.917, 1.594]
|
||||
]
|
||||
}
|
||||
],
|
||||
"t": 25
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"ty": "fl",
|
||||
"nm": "Fill 1",
|
||||
"c": { "a": 0, "k": [1, 1, 1] },
|
||||
"r": 1,
|
||||
"o": { "a": 0, "k": 100 }
|
||||
},
|
||||
{
|
||||
"ty": "tr",
|
||||
"a": { "a": 0, "k": [0, 0] },
|
||||
"s": { "a": 0, "k": [100, 100] },
|
||||
"sk": { "a": 0, "k": 0 },
|
||||
"p": { "a": 0, "k": [4.167, 1.844] },
|
||||
"r": { "a": 0, "k": 0 },
|
||||
"sa": { "a": 0, "k": 0 },
|
||||
"o": { "a": 0, "k": 100 }
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"ind": 4
|
||||
}
|
||||
],
|
||||
"v": "5.6.5",
|
||||
"fr": 24,
|
||||
"op": 28,
|
||||
"ip": 0,
|
||||
"assets": []
|
||||
}
|
||||
BIN
static/lotties/hourglass-white.lottie
Normal file
BIN
static/lotties/hourglass-white.lottie
Normal file
Binary file not shown.
88
static/scripts/initial-loader.js
Normal file
88
static/scripts/initial-loader.js
Normal file
@@ -0,0 +1,88 @@
|
||||
(function () {
|
||||
try {
|
||||
const body = document.body;
|
||||
const loadingWrapper = document.createElement('div');
|
||||
const loadingStyles = document.createElement('style');
|
||||
loadingStyles.textContent = `
|
||||
#initial-loader {
|
||||
position: fixed;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 9999;
|
||||
pointer-events: none;
|
||||
}
|
||||
#initial-loader-content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 0.5rem;
|
||||
width: 20rem;
|
||||
background: rgba(255, 255, 255, 0.75);
|
||||
backdrop-filter: blur(8px);
|
||||
border: 1px solid rgba(0, 0, 0, 0.1);
|
||||
border-radius: 0.25rem;
|
||||
padding: 1rem;
|
||||
}
|
||||
body.dark #initial-loader-content {
|
||||
background: rgba(30, 31, 35, 0.75);
|
||||
border-color: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
.hourglass-spinner {
|
||||
animation: hourglass-flip 2.4s ease-in-out infinite;
|
||||
opacity: 0.75;
|
||||
}
|
||||
@keyframes hourglass-flip {
|
||||
0%, 40% { transform: rotate(0deg); }
|
||||
48%, 90% { transform: rotate(180deg); }
|
||||
98%, 100% { transform: rotate(360deg); }
|
||||
}
|
||||
.sand-top {
|
||||
transform-origin: center bottom;
|
||||
animation: sand-drain-top 2.4s ease-in-out infinite;
|
||||
}
|
||||
.sand-bottom {
|
||||
transform-origin: center top;
|
||||
animation: sand-drain-bottom 2.4s ease-in-out infinite;
|
||||
}
|
||||
@keyframes sand-drain-top {
|
||||
0% { transform: scaleY(1); opacity: 0.6; }
|
||||
40% { transform: scaleY(0); opacity: 0; }
|
||||
48%, 100% { transform: scaleY(0); opacity: 0; }
|
||||
}
|
||||
@keyframes sand-drain-bottom {
|
||||
0%, 48% { transform: scaleY(0); opacity: 0; }
|
||||
50% { transform: scaleY(1); opacity: 0.6; }
|
||||
90% { transform: scaleY(0); opacity: 0; }
|
||||
100% { transform: scaleY(0); opacity: 0; }
|
||||
}
|
||||
#initial-loader-text {
|
||||
flex: 1;
|
||||
font-weight: 500;
|
||||
color: rgba(0, 0, 0, 0.8);
|
||||
}
|
||||
body.dark #initial-loader-text {
|
||||
color: rgba(255, 255, 255, 0.8);
|
||||
}
|
||||
`;
|
||||
loadingWrapper.id = 'initial-loader';
|
||||
loadingWrapper.innerHTML = `
|
||||
<div id="initial-loader-content">
|
||||
<svg class="hourglass-spinner" width="24" height="24" viewBox="0 0 24 24" fill="none">
|
||||
<path d="M6 2h12M6 22h12M18 2v3.5c0 1.5-1 2.5-2.5 4L12 13l-3.5-3.5C7 8 6 7 6 5.5V2M18 22v-3.5c0-1.5-1-2.5-2.5-4L12 11l-3.5 3.5C7 16 6 17 6 18.5V22" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path class="sand-top" d="M8 4 L16 4 L14.5 7 L9.5 7 Z" fill="currentColor" opacity="0.6"/>
|
||||
<path class="sand-bottom" d="M8 20 L16 20 L14.5 17 L9.5 17 Z" fill="currentColor" opacity="0.6"/>
|
||||
</svg>
|
||||
<div id="initial-loader-text">Loading PostHog...</div>
|
||||
</div>
|
||||
`;
|
||||
body.appendChild(loadingStyles);
|
||||
body.appendChild(loadingWrapper);
|
||||
} catch (error) {
|
||||
console.error('Failed to initialize loading spinner:', error);
|
||||
}
|
||||
})();
|
||||
@@ -362,6 +362,26 @@ module.exports = {
|
||||
'0%, 100%': { 'background-position': '0% 50%' },
|
||||
'50%': { 'background-position': '100% 50%' },
|
||||
},
|
||||
slideUpFadeIn: {
|
||||
from: {
|
||||
opacity: '0',
|
||||
transform: 'translateY(10px)',
|
||||
},
|
||||
to: {
|
||||
opacity: '1',
|
||||
transform: 'translateY(0)',
|
||||
},
|
||||
},
|
||||
slideUpFadeOut: {
|
||||
from: {
|
||||
opacity: '1',
|
||||
transform: 'translateY(0)',
|
||||
},
|
||||
to: {
|
||||
opacity: '0',
|
||||
transform: 'translateY(-10px)',
|
||||
},
|
||||
},
|
||||
},
|
||||
animation: {
|
||||
wiggle: 'wiggle .2s ease-in-out 3',
|
||||
@@ -381,6 +401,8 @@ module.exports = {
|
||||
'spin-slow': 'spin-slow 4s linear infinite',
|
||||
'spin-slow-reverse': 'spin-slow-reverse 4s linear infinite',
|
||||
'gradient-rotate': 'gradient-rotate 3s ease-in-out infinite',
|
||||
'slide-up-fade-in': 'slideUpFadeIn 300ms ease-out forwards',
|
||||
'slide-up-fade-out': 'slideUpFadeOut 300ms ease-in forwards',
|
||||
},
|
||||
containers: {
|
||||
'2xs': '16rem',
|
||||
|
||||
Reference in New Issue
Block a user