@hdcodedev/snowfall 1.0.0 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -1
- package/dist/SnowfallProvider.d.mts +9 -5
- package/dist/SnowfallProvider.d.ts +9 -5
- package/dist/SnowfallProvider.js +13 -5
- package/dist/SnowfallProvider.js.map +1 -1
- package/dist/SnowfallProvider.mjs +13 -5
- package/dist/SnowfallProvider.mjs.map +1 -1
- package/dist/utils/snowfall/dom.js +10 -1
- package/dist/utils/snowfall/dom.js.map +1 -1
- package/dist/utils/snowfall/dom.mjs +10 -1
- package/dist/utils/snowfall/dom.mjs.map +1 -1
- package/dist/utils/snowfall/physics.js +13 -7
- package/dist/utils/snowfall/physics.js.map +1 -1
- package/dist/utils/snowfall/physics.mjs +13 -7
- package/dist/utils/snowfall/physics.mjs.map +1 -1
- package/dist/utils/snowfall/types.d.mts +2 -0
- package/dist/utils/snowfall/types.d.ts +2 -0
- package/dist/utils/snowfall/types.js.map +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -7,13 +7,17 @@ interface PhysicsConfig {
|
|
|
7
7
|
WIND_STRENGTH: number;
|
|
8
8
|
ACCUMULATION: {
|
|
9
9
|
SIDE_RATE: number;
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
TOP_RATE: number;
|
|
11
|
+
BOTTOM_RATE: number;
|
|
12
12
|
};
|
|
13
13
|
MAX_DEPTH: {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
14
|
+
TOP: number;
|
|
15
|
+
BOTTOM: number;
|
|
16
|
+
SIDE: number;
|
|
17
|
+
};
|
|
18
|
+
FLAKE_SIZE: {
|
|
19
|
+
MIN: number;
|
|
20
|
+
MAX: number;
|
|
17
21
|
};
|
|
18
22
|
}
|
|
19
23
|
declare const DEFAULT_PHYSICS: PhysicsConfig;
|
|
@@ -7,13 +7,17 @@ interface PhysicsConfig {
|
|
|
7
7
|
WIND_STRENGTH: number;
|
|
8
8
|
ACCUMULATION: {
|
|
9
9
|
SIDE_RATE: number;
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
TOP_RATE: number;
|
|
11
|
+
BOTTOM_RATE: number;
|
|
12
12
|
};
|
|
13
13
|
MAX_DEPTH: {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
14
|
+
TOP: number;
|
|
15
|
+
BOTTOM: number;
|
|
16
|
+
SIDE: number;
|
|
17
|
+
};
|
|
18
|
+
FLAKE_SIZE: {
|
|
19
|
+
MIN: number;
|
|
20
|
+
MAX: number;
|
|
17
21
|
};
|
|
18
22
|
}
|
|
19
23
|
declare const DEFAULT_PHYSICS: PhysicsConfig;
|
package/dist/SnowfallProvider.js
CHANGED
|
@@ -32,13 +32,17 @@ const DEFAULT_PHYSICS = {
|
|
|
32
32
|
WIND_STRENGTH: 0.8,
|
|
33
33
|
ACCUMULATION: {
|
|
34
34
|
SIDE_RATE: 1.2,
|
|
35
|
-
|
|
36
|
-
|
|
35
|
+
TOP_RATE: 1.9,
|
|
36
|
+
BOTTOM_RATE: 1.2
|
|
37
37
|
},
|
|
38
38
|
MAX_DEPTH: {
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
39
|
+
TOP: 50,
|
|
40
|
+
BOTTOM: 25,
|
|
41
|
+
SIDE: 8
|
|
42
|
+
},
|
|
43
|
+
FLAKE_SIZE: {
|
|
44
|
+
MIN: 0.5,
|
|
45
|
+
MAX: 2.5
|
|
42
46
|
}
|
|
43
47
|
};
|
|
44
48
|
const SnowfallContext = (0, import_react.createContext)(void 0);
|
|
@@ -59,6 +63,10 @@ function SnowfallProvider({ children }) {
|
|
|
59
63
|
MAX_DEPTH: {
|
|
60
64
|
...prev.MAX_DEPTH,
|
|
61
65
|
...config.MAX_DEPTH || {}
|
|
66
|
+
},
|
|
67
|
+
FLAKE_SIZE: {
|
|
68
|
+
...prev.FLAKE_SIZE,
|
|
69
|
+
...config.FLAKE_SIZE || {}
|
|
62
70
|
}
|
|
63
71
|
}));
|
|
64
72
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/SnowfallProvider.tsx"],"sourcesContent":["'use client';\n\nimport React, { createContext, useContext, useState, ReactNode } from 'react';\n\nexport interface PhysicsConfig {\n MAX_FLAKES: number;\n MELT_SPEED: number;\n WIND_STRENGTH: number;\n ACCUMULATION: {\n SIDE_RATE: number;\n
|
|
1
|
+
{"version":3,"sources":["../src/SnowfallProvider.tsx"],"sourcesContent":["'use client';\n\nimport React, { createContext, useContext, useState, ReactNode } from 'react';\n\nexport interface PhysicsConfig {\n MAX_FLAKES: number;\n MELT_SPEED: number;\n WIND_STRENGTH: number;\n ACCUMULATION: {\n SIDE_RATE: number;\n TOP_RATE: number;\n BOTTOM_RATE: number;\n };\n MAX_DEPTH: {\n TOP: number;\n BOTTOM: number;\n SIDE: number;\n };\n FLAKE_SIZE: {\n MIN: number;\n MAX: number;\n };\n}\n\nexport const DEFAULT_PHYSICS: PhysicsConfig = {\n MAX_FLAKES: 500,\n MELT_SPEED: 0.00005,\n WIND_STRENGTH: 0.8,\n ACCUMULATION: {\n SIDE_RATE: 1.2,\n TOP_RATE: 1.9,\n BOTTOM_RATE: 1.2,\n },\n MAX_DEPTH: {\n TOP: 50,\n BOTTOM: 25,\n SIDE: 8,\n },\n FLAKE_SIZE: {\n MIN: 0.5,\n MAX: 2.5,\n }\n};\n\ninterface SnowfallContextType {\n isEnabled: boolean;\n toggleSnow: () => void;\n physicsConfig: PhysicsConfig;\n updatePhysicsConfig: (config: Partial<PhysicsConfig>) => void;\n resetPhysics: () => void;\n}\n\nconst SnowfallContext = createContext<SnowfallContextType | undefined>(undefined);\n\nexport function SnowfallProvider({ children }: { children: ReactNode }) {\n const [isEnabled, setIsEnabled] = useState(true);\n const [physicsConfig, setPhysicsConfig] = useState<PhysicsConfig>(DEFAULT_PHYSICS);\n\n const toggleSnow = () => {\n setIsEnabled((prev) => !prev);\n };\n\n const updatePhysicsConfig = (config: Partial<PhysicsConfig>) => {\n setPhysicsConfig((prev) => ({\n ...prev,\n ...config,\n ACCUMULATION: {\n ...prev.ACCUMULATION,\n ...(config.ACCUMULATION || {}),\n },\n MAX_DEPTH: {\n ...prev.MAX_DEPTH,\n ...(config.MAX_DEPTH || {}),\n },\n FLAKE_SIZE: {\n ...prev.FLAKE_SIZE,\n ...(config.FLAKE_SIZE || {}),\n },\n }));\n };\n\n const resetPhysics = () => {\n setPhysicsConfig(DEFAULT_PHYSICS);\n };\n\n return (\n <SnowfallContext.Provider value={{\n isEnabled,\n toggleSnow,\n physicsConfig,\n updatePhysicsConfig,\n resetPhysics\n }}>\n {children}\n </SnowfallContext.Provider>\n );\n}\n\nexport function useSnowfall() {\n const context = useContext(SnowfallContext);\n if (context === undefined) {\n throw new Error('useSnowfall must be used within a SnowfallProvider');\n }\n return context;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAsFQ;AApFR,mBAAsE;AAsB/D,MAAM,kBAAiC;AAAA,EAC1C,YAAY;AAAA,EACZ,YAAY;AAAA,EACZ,eAAe;AAAA,EACf,cAAc;AAAA,IACV,WAAW;AAAA,IACX,UAAU;AAAA,IACV,aAAa;AAAA,EACjB;AAAA,EACA,WAAW;AAAA,IACP,KAAK;AAAA,IACL,QAAQ;AAAA,IACR,MAAM;AAAA,EACV;AAAA,EACA,YAAY;AAAA,IACR,KAAK;AAAA,IACL,KAAK;AAAA,EACT;AACJ;AAUA,MAAM,sBAAkB,4BAA+C,MAAS;AAEzE,SAAS,iBAAiB,EAAE,SAAS,GAA4B;AACpE,QAAM,CAAC,WAAW,YAAY,QAAI,uBAAS,IAAI;AAC/C,QAAM,CAAC,eAAe,gBAAgB,QAAI,uBAAwB,eAAe;AAEjF,QAAM,aAAa,MAAM;AACrB,iBAAa,CAAC,SAAS,CAAC,IAAI;AAAA,EAChC;AAEA,QAAM,sBAAsB,CAAC,WAAmC;AAC5D,qBAAiB,CAAC,UAAU;AAAA,MACxB,GAAG;AAAA,MACH,GAAG;AAAA,MACH,cAAc;AAAA,QACV,GAAG,KAAK;AAAA,QACR,GAAI,OAAO,gBAAgB,CAAC;AAAA,MAChC;AAAA,MACA,WAAW;AAAA,QACP,GAAG,KAAK;AAAA,QACR,GAAI,OAAO,aAAa,CAAC;AAAA,MAC7B;AAAA,MACA,YAAY;AAAA,QACR,GAAG,KAAK;AAAA,QACR,GAAI,OAAO,cAAc,CAAC;AAAA,MAC9B;AAAA,IACJ,EAAE;AAAA,EACN;AAEA,QAAM,eAAe,MAAM;AACvB,qBAAiB,eAAe;AAAA,EACpC;AAEA,SACI,4CAAC,gBAAgB,UAAhB,EAAyB,OAAO;AAAA,IAC7B;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACJ,GACK,UACL;AAER;AAEO,SAAS,cAAc;AAC1B,QAAM,cAAU,yBAAW,eAAe;AAC1C,MAAI,YAAY,QAAW;AACvB,UAAM,IAAI,MAAM,oDAAoD;AAAA,EACxE;AACA,SAAO;AACX;","names":[]}
|
|
@@ -7,13 +7,17 @@ const DEFAULT_PHYSICS = {
|
|
|
7
7
|
WIND_STRENGTH: 0.8,
|
|
8
8
|
ACCUMULATION: {
|
|
9
9
|
SIDE_RATE: 1.2,
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
TOP_RATE: 1.9,
|
|
11
|
+
BOTTOM_RATE: 1.2
|
|
12
12
|
},
|
|
13
13
|
MAX_DEPTH: {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
14
|
+
TOP: 50,
|
|
15
|
+
BOTTOM: 25,
|
|
16
|
+
SIDE: 8
|
|
17
|
+
},
|
|
18
|
+
FLAKE_SIZE: {
|
|
19
|
+
MIN: 0.5,
|
|
20
|
+
MAX: 2.5
|
|
17
21
|
}
|
|
18
22
|
};
|
|
19
23
|
const SnowfallContext = createContext(void 0);
|
|
@@ -34,6 +38,10 @@ function SnowfallProvider({ children }) {
|
|
|
34
38
|
MAX_DEPTH: {
|
|
35
39
|
...prev.MAX_DEPTH,
|
|
36
40
|
...config.MAX_DEPTH || {}
|
|
41
|
+
},
|
|
42
|
+
FLAKE_SIZE: {
|
|
43
|
+
...prev.FLAKE_SIZE,
|
|
44
|
+
...config.FLAKE_SIZE || {}
|
|
37
45
|
}
|
|
38
46
|
}));
|
|
39
47
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/SnowfallProvider.tsx"],"sourcesContent":["'use client';\n\nimport React, { createContext, useContext, useState, ReactNode } from 'react';\n\nexport interface PhysicsConfig {\n MAX_FLAKES: number;\n MELT_SPEED: number;\n WIND_STRENGTH: number;\n ACCUMULATION: {\n SIDE_RATE: number;\n
|
|
1
|
+
{"version":3,"sources":["../src/SnowfallProvider.tsx"],"sourcesContent":["'use client';\n\nimport React, { createContext, useContext, useState, ReactNode } from 'react';\n\nexport interface PhysicsConfig {\n MAX_FLAKES: number;\n MELT_SPEED: number;\n WIND_STRENGTH: number;\n ACCUMULATION: {\n SIDE_RATE: number;\n TOP_RATE: number;\n BOTTOM_RATE: number;\n };\n MAX_DEPTH: {\n TOP: number;\n BOTTOM: number;\n SIDE: number;\n };\n FLAKE_SIZE: {\n MIN: number;\n MAX: number;\n };\n}\n\nexport const DEFAULT_PHYSICS: PhysicsConfig = {\n MAX_FLAKES: 500,\n MELT_SPEED: 0.00005,\n WIND_STRENGTH: 0.8,\n ACCUMULATION: {\n SIDE_RATE: 1.2,\n TOP_RATE: 1.9,\n BOTTOM_RATE: 1.2,\n },\n MAX_DEPTH: {\n TOP: 50,\n BOTTOM: 25,\n SIDE: 8,\n },\n FLAKE_SIZE: {\n MIN: 0.5,\n MAX: 2.5,\n }\n};\n\ninterface SnowfallContextType {\n isEnabled: boolean;\n toggleSnow: () => void;\n physicsConfig: PhysicsConfig;\n updatePhysicsConfig: (config: Partial<PhysicsConfig>) => void;\n resetPhysics: () => void;\n}\n\nconst SnowfallContext = createContext<SnowfallContextType | undefined>(undefined);\n\nexport function SnowfallProvider({ children }: { children: ReactNode }) {\n const [isEnabled, setIsEnabled] = useState(true);\n const [physicsConfig, setPhysicsConfig] = useState<PhysicsConfig>(DEFAULT_PHYSICS);\n\n const toggleSnow = () => {\n setIsEnabled((prev) => !prev);\n };\n\n const updatePhysicsConfig = (config: Partial<PhysicsConfig>) => {\n setPhysicsConfig((prev) => ({\n ...prev,\n ...config,\n ACCUMULATION: {\n ...prev.ACCUMULATION,\n ...(config.ACCUMULATION || {}),\n },\n MAX_DEPTH: {\n ...prev.MAX_DEPTH,\n ...(config.MAX_DEPTH || {}),\n },\n FLAKE_SIZE: {\n ...prev.FLAKE_SIZE,\n ...(config.FLAKE_SIZE || {}),\n },\n }));\n };\n\n const resetPhysics = () => {\n setPhysicsConfig(DEFAULT_PHYSICS);\n };\n\n return (\n <SnowfallContext.Provider value={{\n isEnabled,\n toggleSnow,\n physicsConfig,\n updatePhysicsConfig,\n resetPhysics\n }}>\n {children}\n </SnowfallContext.Provider>\n );\n}\n\nexport function useSnowfall() {\n const context = useContext(SnowfallContext);\n if (context === undefined) {\n throw new Error('useSnowfall must be used within a SnowfallProvider');\n }\n return context;\n}\n"],"mappings":";AAsFQ;AApFR,SAAgB,eAAe,YAAY,gBAA2B;AAsB/D,MAAM,kBAAiC;AAAA,EAC1C,YAAY;AAAA,EACZ,YAAY;AAAA,EACZ,eAAe;AAAA,EACf,cAAc;AAAA,IACV,WAAW;AAAA,IACX,UAAU;AAAA,IACV,aAAa;AAAA,EACjB;AAAA,EACA,WAAW;AAAA,IACP,KAAK;AAAA,IACL,QAAQ;AAAA,IACR,MAAM;AAAA,EACV;AAAA,EACA,YAAY;AAAA,IACR,KAAK;AAAA,IACL,KAAK;AAAA,EACT;AACJ;AAUA,MAAM,kBAAkB,cAA+C,MAAS;AAEzE,SAAS,iBAAiB,EAAE,SAAS,GAA4B;AACpE,QAAM,CAAC,WAAW,YAAY,IAAI,SAAS,IAAI;AAC/C,QAAM,CAAC,eAAe,gBAAgB,IAAI,SAAwB,eAAe;AAEjF,QAAM,aAAa,MAAM;AACrB,iBAAa,CAAC,SAAS,CAAC,IAAI;AAAA,EAChC;AAEA,QAAM,sBAAsB,CAAC,WAAmC;AAC5D,qBAAiB,CAAC,UAAU;AAAA,MACxB,GAAG;AAAA,MACH,GAAG;AAAA,MACH,cAAc;AAAA,QACV,GAAG,KAAK;AAAA,QACR,GAAI,OAAO,gBAAgB,CAAC;AAAA,MAChC;AAAA,MACA,WAAW;AAAA,QACP,GAAG,KAAK;AAAA,QACR,GAAI,OAAO,aAAa,CAAC;AAAA,MAC7B;AAAA,MACA,YAAY;AAAA,QACR,GAAG,KAAK;AAAA,QACR,GAAI,OAAO,cAAc,CAAC;AAAA,MAC9B;AAAA,IACJ,EAAE;AAAA,EACN;AAEA,QAAM,eAAe,MAAM;AACvB,qBAAiB,eAAe;AAAA,EACpC;AAEA,SACI,oBAAC,gBAAgB,UAAhB,EAAyB,OAAO;AAAA,IAC7B;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACJ,GACK,UACL;AAER;AAEO,SAAS,cAAc;AAC1B,QAAM,UAAU,WAAW,eAAe;AAC1C,MAAI,YAAY,QAAW;AACvB,UAAM,IAAI,MAAM,oDAAoD;AAAA,EACxE;AACA,SAAO;AACX;","names":[]}
|
|
@@ -84,7 +84,16 @@ const getAccumulationSurfaces = () => {
|
|
|
84
84
|
const isBottomRole = BOTTOM_ROLES.includes(el.getAttribute("role") || "");
|
|
85
85
|
const isBottomSurface = isBottomTag || isBottomRole || manualOverride === import_constants.VAL_BOTTOM;
|
|
86
86
|
if (isFullPageWrapper && !isBottomSurface && !isManuallyIncluded) return;
|
|
87
|
-
|
|
87
|
+
let isFixed = false;
|
|
88
|
+
let currentEl = el;
|
|
89
|
+
while (currentEl && currentEl !== document.body) {
|
|
90
|
+
const style = window.getComputedStyle(currentEl);
|
|
91
|
+
if (style.position === "fixed" || style.position === "sticky") {
|
|
92
|
+
isFixed = true;
|
|
93
|
+
break;
|
|
94
|
+
}
|
|
95
|
+
currentEl = currentEl.parentElement;
|
|
96
|
+
}
|
|
88
97
|
if (shouldAccumulate(el)) {
|
|
89
98
|
let type = getElementType(el);
|
|
90
99
|
if (manualOverride === import_constants.VAL_BOTTOM) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/utils/snowfall/dom.ts"],"sourcesContent":["import { SnowAccumulation, ElementSurface, SnowfallSurface } from './types';\nimport {\n ATTR_SNOWFALL, VAL_IGNORE, VAL_TOP, VAL_BOTTOM,\n TAG_HEADER, TAG_FOOTER, ROLE_BANNER, ROLE_CONTENTINFO\n} from './constants';\n\nconst BOTTOM_TAGS = [TAG_HEADER, TAG_FOOTER];\nconst BOTTOM_ROLES = [ROLE_BANNER, ROLE_CONTENTINFO];\n\nconst AUTO_DETECT_TAGS = ['header', 'footer', 'article', 'section', 'aside', 'nav'];\nconst AUTO_DETECT_ROLES = ['[role=\"banner\"]', '[role=\"contentinfo\"]', '[role=\"main\"]'];\nconst AUTO_DETECT_CLASSES = [\n '.card', '[class*=\"card\"]', '[class*=\"Card\"]',\n '[class*=\"bg-\"]', '[class*=\"shadow-\"]', '[class*=\"rounded-\"]'\n];\n\nexport const getElementType = (el: Element): SnowfallSurface => {\n const tagName = el.tagName.toLowerCase();\n if (BOTTOM_TAGS.includes(tagName)) return VAL_BOTTOM;\n\n const role = el.getAttribute('role');\n if (role && BOTTOM_ROLES.includes(role)) return VAL_BOTTOM;\n\n return VAL_TOP;\n};\n\nconst shouldAccumulate = (el: Element): boolean => {\n // Explicit opt-out\n if (el.getAttribute(ATTR_SNOWFALL) === VAL_IGNORE) return false;\n\n // Explicit opt-in\n if (el.hasAttribute(ATTR_SNOWFALL)) return true;\n\n // Heuristics\n const styles = window.getComputedStyle(el);\n const rect = el.getBoundingClientRect();\n\n const isVisible = styles.display !== 'none' &&\n styles.visibility !== 'hidden' &&\n parseFloat(styles.opacity) > 0.1;\n if (!isVisible) return false;\n\n // Check for visual prominence\n const bgColor = styles.backgroundColor;\n const hasBackground = bgColor !== 'rgba(0, 0, 0, 0)' && bgColor !== 'transparent';\n const hasBorder = parseFloat(styles.borderWidth) > 0 || styles.borderStyle !== 'none';\n const hasBoxShadow = styles.boxShadow !== 'none';\n const hasBorderRadius = parseFloat(styles.borderRadius) > 0;\n\n return hasBackground || hasBorder || hasBoxShadow || hasBorderRadius;\n};\n\nexport const getAccumulationSurfaces = (): { el: Element; type: SnowfallSurface; isFixed: boolean }[] => {\n const surfaces: { el: Element; type: SnowfallSurface; isFixed: boolean }[] = [];\n const seen = new Set<Element>();\n\n const candidates = document.querySelectorAll(\n [\n `[${ATTR_SNOWFALL}]`,\n ...AUTO_DETECT_TAGS,\n ...AUTO_DETECT_ROLES,\n ...AUTO_DETECT_CLASSES\n ].join(', ')\n );\n\n candidates.forEach(el => {\n if (seen.has(el)) return;\n\n const rect = el.getBoundingClientRect();\n\n // Manual override check first\n const manualOverride = el.getAttribute(ATTR_SNOWFALL);\n if (manualOverride === VAL_IGNORE) return;\n\n // If manually opted in, skip some heuristic checks but keep basic visibility/size sanity\n const isManuallyIncluded = manualOverride !== null;\n\n const styles = window.getComputedStyle(el);\n const isVisible = styles.display !== 'none' &&\n styles.visibility !== 'hidden' &&\n parseFloat(styles.opacity) > 0.1;\n\n if (!isVisible && !isManuallyIncluded) return;\n\n // Skip really small elements unless manually forced\n const hasSize = rect.width >= 100 && rect.height >= 50;\n if (!hasSize && !isManuallyIncluded) return;\n\n // HEURISTIC: Skip full-page wrappers\n const isFullPageWrapper = rect.top <= 10 && rect.height >= window.innerHeight * 0.9;\n\n\n const isBottomTag = BOTTOM_TAGS.includes(el.tagName.toLowerCase());\n const isBottomRole = BOTTOM_ROLES.includes(el.getAttribute('role') || '');\n const isBottomSurface = isBottomTag || isBottomRole ||\n manualOverride === VAL_BOTTOM;\n\n if (isFullPageWrapper && !isBottomSurface && !isManuallyIncluded) return;\n\n
|
|
1
|
+
{"version":3,"sources":["../../../src/utils/snowfall/dom.ts"],"sourcesContent":["import { SnowAccumulation, ElementSurface, SnowfallSurface } from './types';\nimport {\n ATTR_SNOWFALL, VAL_IGNORE, VAL_TOP, VAL_BOTTOM,\n TAG_HEADER, TAG_FOOTER, ROLE_BANNER, ROLE_CONTENTINFO\n} from './constants';\n\nconst BOTTOM_TAGS = [TAG_HEADER, TAG_FOOTER];\nconst BOTTOM_ROLES = [ROLE_BANNER, ROLE_CONTENTINFO];\n\nconst AUTO_DETECT_TAGS = ['header', 'footer', 'article', 'section', 'aside', 'nav'];\nconst AUTO_DETECT_ROLES = ['[role=\"banner\"]', '[role=\"contentinfo\"]', '[role=\"main\"]'];\nconst AUTO_DETECT_CLASSES = [\n '.card', '[class*=\"card\"]', '[class*=\"Card\"]',\n '[class*=\"bg-\"]', '[class*=\"shadow-\"]', '[class*=\"rounded-\"]'\n];\n\nexport const getElementType = (el: Element): SnowfallSurface => {\n const tagName = el.tagName.toLowerCase();\n if (BOTTOM_TAGS.includes(tagName)) return VAL_BOTTOM;\n\n const role = el.getAttribute('role');\n if (role && BOTTOM_ROLES.includes(role)) return VAL_BOTTOM;\n\n return VAL_TOP;\n};\n\nconst shouldAccumulate = (el: Element): boolean => {\n // Explicit opt-out\n if (el.getAttribute(ATTR_SNOWFALL) === VAL_IGNORE) return false;\n\n // Explicit opt-in\n if (el.hasAttribute(ATTR_SNOWFALL)) return true;\n\n // Heuristics\n const styles = window.getComputedStyle(el);\n const rect = el.getBoundingClientRect();\n\n const isVisible = styles.display !== 'none' &&\n styles.visibility !== 'hidden' &&\n parseFloat(styles.opacity) > 0.1;\n if (!isVisible) return false;\n\n // Check for visual prominence\n const bgColor = styles.backgroundColor;\n const hasBackground = bgColor !== 'rgba(0, 0, 0, 0)' && bgColor !== 'transparent';\n const hasBorder = parseFloat(styles.borderWidth) > 0 || styles.borderStyle !== 'none';\n const hasBoxShadow = styles.boxShadow !== 'none';\n const hasBorderRadius = parseFloat(styles.borderRadius) > 0;\n\n return hasBackground || hasBorder || hasBoxShadow || hasBorderRadius;\n};\n\nexport const getAccumulationSurfaces = (): { el: Element; type: SnowfallSurface; isFixed: boolean }[] => {\n const surfaces: { el: Element; type: SnowfallSurface; isFixed: boolean }[] = [];\n const seen = new Set<Element>();\n\n const candidates = document.querySelectorAll(\n [\n `[${ATTR_SNOWFALL}]`,\n ...AUTO_DETECT_TAGS,\n ...AUTO_DETECT_ROLES,\n ...AUTO_DETECT_CLASSES\n ].join(', ')\n );\n\n candidates.forEach(el => {\n if (seen.has(el)) return;\n\n const rect = el.getBoundingClientRect();\n\n // Manual override check first\n const manualOverride = el.getAttribute(ATTR_SNOWFALL);\n if (manualOverride === VAL_IGNORE) return;\n\n // If manually opted in, skip some heuristic checks but keep basic visibility/size sanity\n const isManuallyIncluded = manualOverride !== null;\n\n const styles = window.getComputedStyle(el);\n const isVisible = styles.display !== 'none' &&\n styles.visibility !== 'hidden' &&\n parseFloat(styles.opacity) > 0.1;\n\n if (!isVisible && !isManuallyIncluded) return;\n\n // Skip really small elements unless manually forced\n const hasSize = rect.width >= 100 && rect.height >= 50;\n if (!hasSize && !isManuallyIncluded) return;\n\n // HEURISTIC: Skip full-page wrappers\n const isFullPageWrapper = rect.top <= 10 && rect.height >= window.innerHeight * 0.9;\n\n\n const isBottomTag = BOTTOM_TAGS.includes(el.tagName.toLowerCase());\n const isBottomRole = BOTTOM_ROLES.includes(el.getAttribute('role') || '');\n const isBottomSurface = isBottomTag || isBottomRole ||\n manualOverride === VAL_BOTTOM;\n\n if (isFullPageWrapper && !isBottomSurface && !isManuallyIncluded) return;\n\n let isFixed = false;\n let currentEl: Element | null = el;\n while (currentEl && currentEl !== document.body) {\n const style = window.getComputedStyle(currentEl);\n if (style.position === 'fixed' || style.position === 'sticky') {\n isFixed = true;\n break;\n }\n currentEl = currentEl.parentElement;\n }\n\n if (shouldAccumulate(el)) {\n // Determine type: manual override takes precedence\n let type: SnowfallSurface = getElementType(el);\n\n if (manualOverride === VAL_BOTTOM) {\n type = VAL_BOTTOM;\n } else if (manualOverride === VAL_TOP) {\n type = VAL_TOP;\n }\n\n surfaces.push({ el, type, isFixed });\n seen.add(el);\n }\n });\n\n console.log(`[Snowfall] Auto-detection found ${surfaces.length} surfaces`);\n return surfaces;\n};\n\nexport const getElementRects = (accumulationMap: Map<Element, SnowAccumulation>): ElementSurface[] => {\n const elementRects: ElementSurface[] = [];\n\n for (const [el, acc] of accumulationMap.entries()) {\n if (!el.isConnected) continue;\n const rect = el.getBoundingClientRect();\n // Convert viewport coordinates to absolute page coordinates\n const absoluteRect = {\n left: rect.left + window.scrollX,\n right: rect.right + window.scrollX,\n top: rect.top + window.scrollY,\n bottom: rect.bottom + window.scrollY,\n width: rect.width,\n height: rect.height,\n x: rect.x, // Note: these are strictly viewport relative in DOMRect usually, \n // but we just need consistent absolute coords for physics\n y: rect.y,\n toJSON: rect.toJSON\n };\n // We cast because we constructed a compatible object, though strictly DOMRect has readonly properties\n elementRects.push({ el, rect: absoluteRect as unknown as DOMRect, acc });\n }\n return elementRects;\n};\n"],"mappings":"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","names":[]}
|
|
@@ -68,7 +68,16 @@ const getAccumulationSurfaces = () => {
|
|
|
68
68
|
const isBottomRole = BOTTOM_ROLES.includes(el.getAttribute("role") || "");
|
|
69
69
|
const isBottomSurface = isBottomTag || isBottomRole || manualOverride === VAL_BOTTOM;
|
|
70
70
|
if (isFullPageWrapper && !isBottomSurface && !isManuallyIncluded) return;
|
|
71
|
-
|
|
71
|
+
let isFixed = false;
|
|
72
|
+
let currentEl = el;
|
|
73
|
+
while (currentEl && currentEl !== document.body) {
|
|
74
|
+
const style = window.getComputedStyle(currentEl);
|
|
75
|
+
if (style.position === "fixed" || style.position === "sticky") {
|
|
76
|
+
isFixed = true;
|
|
77
|
+
break;
|
|
78
|
+
}
|
|
79
|
+
currentEl = currentEl.parentElement;
|
|
80
|
+
}
|
|
72
81
|
if (shouldAccumulate(el)) {
|
|
73
82
|
let type = getElementType(el);
|
|
74
83
|
if (manualOverride === VAL_BOTTOM) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/utils/snowfall/dom.ts"],"sourcesContent":["import { SnowAccumulation, ElementSurface, SnowfallSurface } from './types';\nimport {\n ATTR_SNOWFALL, VAL_IGNORE, VAL_TOP, VAL_BOTTOM,\n TAG_HEADER, TAG_FOOTER, ROLE_BANNER, ROLE_CONTENTINFO\n} from './constants';\n\nconst BOTTOM_TAGS = [TAG_HEADER, TAG_FOOTER];\nconst BOTTOM_ROLES = [ROLE_BANNER, ROLE_CONTENTINFO];\n\nconst AUTO_DETECT_TAGS = ['header', 'footer', 'article', 'section', 'aside', 'nav'];\nconst AUTO_DETECT_ROLES = ['[role=\"banner\"]', '[role=\"contentinfo\"]', '[role=\"main\"]'];\nconst AUTO_DETECT_CLASSES = [\n '.card', '[class*=\"card\"]', '[class*=\"Card\"]',\n '[class*=\"bg-\"]', '[class*=\"shadow-\"]', '[class*=\"rounded-\"]'\n];\n\nexport const getElementType = (el: Element): SnowfallSurface => {\n const tagName = el.tagName.toLowerCase();\n if (BOTTOM_TAGS.includes(tagName)) return VAL_BOTTOM;\n\n const role = el.getAttribute('role');\n if (role && BOTTOM_ROLES.includes(role)) return VAL_BOTTOM;\n\n return VAL_TOP;\n};\n\nconst shouldAccumulate = (el: Element): boolean => {\n // Explicit opt-out\n if (el.getAttribute(ATTR_SNOWFALL) === VAL_IGNORE) return false;\n\n // Explicit opt-in\n if (el.hasAttribute(ATTR_SNOWFALL)) return true;\n\n // Heuristics\n const styles = window.getComputedStyle(el);\n const rect = el.getBoundingClientRect();\n\n const isVisible = styles.display !== 'none' &&\n styles.visibility !== 'hidden' &&\n parseFloat(styles.opacity) > 0.1;\n if (!isVisible) return false;\n\n // Check for visual prominence\n const bgColor = styles.backgroundColor;\n const hasBackground = bgColor !== 'rgba(0, 0, 0, 0)' && bgColor !== 'transparent';\n const hasBorder = parseFloat(styles.borderWidth) > 0 || styles.borderStyle !== 'none';\n const hasBoxShadow = styles.boxShadow !== 'none';\n const hasBorderRadius = parseFloat(styles.borderRadius) > 0;\n\n return hasBackground || hasBorder || hasBoxShadow || hasBorderRadius;\n};\n\nexport const getAccumulationSurfaces = (): { el: Element; type: SnowfallSurface; isFixed: boolean }[] => {\n const surfaces: { el: Element; type: SnowfallSurface; isFixed: boolean }[] = [];\n const seen = new Set<Element>();\n\n const candidates = document.querySelectorAll(\n [\n `[${ATTR_SNOWFALL}]`,\n ...AUTO_DETECT_TAGS,\n ...AUTO_DETECT_ROLES,\n ...AUTO_DETECT_CLASSES\n ].join(', ')\n );\n\n candidates.forEach(el => {\n if (seen.has(el)) return;\n\n const rect = el.getBoundingClientRect();\n\n // Manual override check first\n const manualOverride = el.getAttribute(ATTR_SNOWFALL);\n if (manualOverride === VAL_IGNORE) return;\n\n // If manually opted in, skip some heuristic checks but keep basic visibility/size sanity\n const isManuallyIncluded = manualOverride !== null;\n\n const styles = window.getComputedStyle(el);\n const isVisible = styles.display !== 'none' &&\n styles.visibility !== 'hidden' &&\n parseFloat(styles.opacity) > 0.1;\n\n if (!isVisible && !isManuallyIncluded) return;\n\n // Skip really small elements unless manually forced\n const hasSize = rect.width >= 100 && rect.height >= 50;\n if (!hasSize && !isManuallyIncluded) return;\n\n // HEURISTIC: Skip full-page wrappers\n const isFullPageWrapper = rect.top <= 10 && rect.height >= window.innerHeight * 0.9;\n\n\n const isBottomTag = BOTTOM_TAGS.includes(el.tagName.toLowerCase());\n const isBottomRole = BOTTOM_ROLES.includes(el.getAttribute('role') || '');\n const isBottomSurface = isBottomTag || isBottomRole ||\n manualOverride === VAL_BOTTOM;\n\n if (isFullPageWrapper && !isBottomSurface && !isManuallyIncluded) return;\n\n
|
|
1
|
+
{"version":3,"sources":["../../../src/utils/snowfall/dom.ts"],"sourcesContent":["import { SnowAccumulation, ElementSurface, SnowfallSurface } from './types';\nimport {\n ATTR_SNOWFALL, VAL_IGNORE, VAL_TOP, VAL_BOTTOM,\n TAG_HEADER, TAG_FOOTER, ROLE_BANNER, ROLE_CONTENTINFO\n} from './constants';\n\nconst BOTTOM_TAGS = [TAG_HEADER, TAG_FOOTER];\nconst BOTTOM_ROLES = [ROLE_BANNER, ROLE_CONTENTINFO];\n\nconst AUTO_DETECT_TAGS = ['header', 'footer', 'article', 'section', 'aside', 'nav'];\nconst AUTO_DETECT_ROLES = ['[role=\"banner\"]', '[role=\"contentinfo\"]', '[role=\"main\"]'];\nconst AUTO_DETECT_CLASSES = [\n '.card', '[class*=\"card\"]', '[class*=\"Card\"]',\n '[class*=\"bg-\"]', '[class*=\"shadow-\"]', '[class*=\"rounded-\"]'\n];\n\nexport const getElementType = (el: Element): SnowfallSurface => {\n const tagName = el.tagName.toLowerCase();\n if (BOTTOM_TAGS.includes(tagName)) return VAL_BOTTOM;\n\n const role = el.getAttribute('role');\n if (role && BOTTOM_ROLES.includes(role)) return VAL_BOTTOM;\n\n return VAL_TOP;\n};\n\nconst shouldAccumulate = (el: Element): boolean => {\n // Explicit opt-out\n if (el.getAttribute(ATTR_SNOWFALL) === VAL_IGNORE) return false;\n\n // Explicit opt-in\n if (el.hasAttribute(ATTR_SNOWFALL)) return true;\n\n // Heuristics\n const styles = window.getComputedStyle(el);\n const rect = el.getBoundingClientRect();\n\n const isVisible = styles.display !== 'none' &&\n styles.visibility !== 'hidden' &&\n parseFloat(styles.opacity) > 0.1;\n if (!isVisible) return false;\n\n // Check for visual prominence\n const bgColor = styles.backgroundColor;\n const hasBackground = bgColor !== 'rgba(0, 0, 0, 0)' && bgColor !== 'transparent';\n const hasBorder = parseFloat(styles.borderWidth) > 0 || styles.borderStyle !== 'none';\n const hasBoxShadow = styles.boxShadow !== 'none';\n const hasBorderRadius = parseFloat(styles.borderRadius) > 0;\n\n return hasBackground || hasBorder || hasBoxShadow || hasBorderRadius;\n};\n\nexport const getAccumulationSurfaces = (): { el: Element; type: SnowfallSurface; isFixed: boolean }[] => {\n const surfaces: { el: Element; type: SnowfallSurface; isFixed: boolean }[] = [];\n const seen = new Set<Element>();\n\n const candidates = document.querySelectorAll(\n [\n `[${ATTR_SNOWFALL}]`,\n ...AUTO_DETECT_TAGS,\n ...AUTO_DETECT_ROLES,\n ...AUTO_DETECT_CLASSES\n ].join(', ')\n );\n\n candidates.forEach(el => {\n if (seen.has(el)) return;\n\n const rect = el.getBoundingClientRect();\n\n // Manual override check first\n const manualOverride = el.getAttribute(ATTR_SNOWFALL);\n if (manualOverride === VAL_IGNORE) return;\n\n // If manually opted in, skip some heuristic checks but keep basic visibility/size sanity\n const isManuallyIncluded = manualOverride !== null;\n\n const styles = window.getComputedStyle(el);\n const isVisible = styles.display !== 'none' &&\n styles.visibility !== 'hidden' &&\n parseFloat(styles.opacity) > 0.1;\n\n if (!isVisible && !isManuallyIncluded) return;\n\n // Skip really small elements unless manually forced\n const hasSize = rect.width >= 100 && rect.height >= 50;\n if (!hasSize && !isManuallyIncluded) return;\n\n // HEURISTIC: Skip full-page wrappers\n const isFullPageWrapper = rect.top <= 10 && rect.height >= window.innerHeight * 0.9;\n\n\n const isBottomTag = BOTTOM_TAGS.includes(el.tagName.toLowerCase());\n const isBottomRole = BOTTOM_ROLES.includes(el.getAttribute('role') || '');\n const isBottomSurface = isBottomTag || isBottomRole ||\n manualOverride === VAL_BOTTOM;\n\n if (isFullPageWrapper && !isBottomSurface && !isManuallyIncluded) return;\n\n let isFixed = false;\n let currentEl: Element | null = el;\n while (currentEl && currentEl !== document.body) {\n const style = window.getComputedStyle(currentEl);\n if (style.position === 'fixed' || style.position === 'sticky') {\n isFixed = true;\n break;\n }\n currentEl = currentEl.parentElement;\n }\n\n if (shouldAccumulate(el)) {\n // Determine type: manual override takes precedence\n let type: SnowfallSurface = getElementType(el);\n\n if (manualOverride === VAL_BOTTOM) {\n type = VAL_BOTTOM;\n } else if (manualOverride === VAL_TOP) {\n type = VAL_TOP;\n }\n\n surfaces.push({ el, type, isFixed });\n seen.add(el);\n }\n });\n\n console.log(`[Snowfall] Auto-detection found ${surfaces.length} surfaces`);\n return surfaces;\n};\n\nexport const getElementRects = (accumulationMap: Map<Element, SnowAccumulation>): ElementSurface[] => {\n const elementRects: ElementSurface[] = [];\n\n for (const [el, acc] of accumulationMap.entries()) {\n if (!el.isConnected) continue;\n const rect = el.getBoundingClientRect();\n // Convert viewport coordinates to absolute page coordinates\n const absoluteRect = {\n left: rect.left + window.scrollX,\n right: rect.right + window.scrollX,\n top: rect.top + window.scrollY,\n bottom: rect.bottom + window.scrollY,\n width: rect.width,\n height: rect.height,\n x: rect.x, // Note: these are strictly viewport relative in DOMRect usually, \n // but we just need consistent absolute coords for physics\n y: rect.y,\n toJSON: rect.toJSON\n };\n // We cast because we constructed a compatible object, though strictly DOMRect has readonly properties\n elementRects.push({ el, rect: absoluteRect as unknown as DOMRect, acc });\n }\n return elementRects;\n};\n"],"mappings":"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","names":[]}
|
|
@@ -28,7 +28,8 @@ var import_dom = require("./dom");
|
|
|
28
28
|
var import_constants = require("./constants");
|
|
29
29
|
const createSnowflake = (canvasWidth, config, isBackground = false) => {
|
|
30
30
|
if (isBackground) {
|
|
31
|
-
const
|
|
31
|
+
const sizeRatio = Math.random();
|
|
32
|
+
const radius = config.FLAKE_SIZE.MIN * 0.6 + sizeRatio * (config.FLAKE_SIZE.MAX - config.FLAKE_SIZE.MIN) * 0.4;
|
|
32
33
|
return {
|
|
33
34
|
x: Math.random() * canvasWidth,
|
|
34
35
|
y: window.scrollY - 5,
|
|
@@ -37,10 +38,13 @@ const createSnowflake = (canvasWidth, config, isBackground = false) => {
|
|
|
37
38
|
wind: (Math.random() - 0.5) * (config.WIND_STRENGTH * 0.625),
|
|
38
39
|
opacity: Math.random() * 0.2 + 0.2,
|
|
39
40
|
wobble: Math.random() * Math.PI * 2,
|
|
40
|
-
wobbleSpeed: Math.random() * 0.015 + 5e-3
|
|
41
|
+
wobbleSpeed: Math.random() * 0.015 + 5e-3,
|
|
42
|
+
sizeRatio,
|
|
43
|
+
isBackground: true
|
|
41
44
|
};
|
|
42
45
|
} else {
|
|
43
|
-
const
|
|
46
|
+
const sizeRatio = Math.random();
|
|
47
|
+
const radius = config.FLAKE_SIZE.MIN + sizeRatio * (config.FLAKE_SIZE.MAX - config.FLAKE_SIZE.MIN);
|
|
44
48
|
return {
|
|
45
49
|
x: Math.random() * canvasWidth,
|
|
46
50
|
y: window.scrollY - 5,
|
|
@@ -49,7 +53,9 @@ const createSnowflake = (canvasWidth, config, isBackground = false) => {
|
|
|
49
53
|
wind: (Math.random() - 0.5) * config.WIND_STRENGTH,
|
|
50
54
|
opacity: Math.random() * 0.3 + 0.5,
|
|
51
55
|
wobble: Math.random() * Math.PI * 2,
|
|
52
|
-
wobbleSpeed: Math.random() * 0.02 + 0.01
|
|
56
|
+
wobbleSpeed: Math.random() * 0.02 + 0.01,
|
|
57
|
+
sizeRatio,
|
|
58
|
+
isBackground: false
|
|
53
59
|
};
|
|
54
60
|
}
|
|
55
61
|
};
|
|
@@ -75,7 +81,7 @@ const initializeAccumulation = (accumulationMap, config) => {
|
|
|
75
81
|
return;
|
|
76
82
|
}
|
|
77
83
|
const height = Math.ceil(rect.height);
|
|
78
|
-
const baseMax = isBottom ? config.MAX_DEPTH.
|
|
84
|
+
const baseMax = isBottom ? config.MAX_DEPTH.BOTTOM : config.MAX_DEPTH.TOP;
|
|
79
85
|
const styles = window.getComputedStyle(el);
|
|
80
86
|
const borderRadius = parseFloat(styles.borderTopLeftRadius) || 0;
|
|
81
87
|
let maxHeights = new Array(width);
|
|
@@ -103,7 +109,7 @@ const initializeAccumulation = (accumulationMap, config) => {
|
|
|
103
109
|
maxHeights,
|
|
104
110
|
leftSide: existing?.leftSide.length === height ? existing.leftSide : new Array(height).fill(0),
|
|
105
111
|
rightSide: existing?.rightSide.length === height ? existing.rightSide : new Array(height).fill(0),
|
|
106
|
-
maxSideHeight: isBottom ? 0 : config.MAX_DEPTH.
|
|
112
|
+
maxSideHeight: isBottom ? 0 : config.MAX_DEPTH.SIDE,
|
|
107
113
|
borderRadius,
|
|
108
114
|
type,
|
|
109
115
|
isFixed
|
|
@@ -167,7 +173,7 @@ const updateSnowflakes = (snowflakes, elementRects, config, dt, canvasWidth, can
|
|
|
167
173
|
if (shouldAccumulate) {
|
|
168
174
|
const baseSpread = Math.ceil(flake.radius);
|
|
169
175
|
const spread = baseSpread + Math.floor(Math.random() * 2);
|
|
170
|
-
const accumRate = isBottom ? config.ACCUMULATION.
|
|
176
|
+
const accumRate = isBottom ? config.ACCUMULATION.BOTTOM_RATE : config.ACCUMULATION.TOP_RATE;
|
|
171
177
|
const centerOffset = Math.floor(Math.random() * 3) - 1;
|
|
172
178
|
for (let dx = -spread; dx <= spread; dx++) {
|
|
173
179
|
if (Math.random() < 0.15) continue;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/utils/snowfall/physics.ts"],"sourcesContent":["import { PhysicsConfig } from '../../SnowfallProvider';\nimport { Snowflake, SnowAccumulation, ElementSurface } from './types';\nimport { getAccumulationSurfaces } from './dom';\nimport { VAL_BOTTOM } from './constants';\n\nexport const createSnowflake = (\n canvasWidth: number,\n config: PhysicsConfig,\n isBackground: boolean = false\n): Snowflake => {\n if (isBackground) {\n const radius = Math.random() * 0.8 + 0.3;\n return {\n x: Math.random() * canvasWidth,\n y: window.scrollY - 5,\n radius,\n speed: radius * 0.3 + Math.random() * 0.2 + 0.2,\n wind: (Math.random() - 0.5) * (config.WIND_STRENGTH * 0.625),\n opacity: Math.random() * 0.2 + 0.2,\n wobble: Math.random() * Math.PI * 2,\n wobbleSpeed: Math.random() * 0.015 + 0.005,\n };\n } else {\n const radius = Math.random() * 2 + 0.5;\n return {\n x: Math.random() * canvasWidth,\n y: window.scrollY - 5,\n radius,\n speed: radius * 0.5 + Math.random() * 0.3 + 0.5,\n wind: (Math.random() - 0.5) * config.WIND_STRENGTH,\n opacity: Math.random() * 0.3 + 0.5,\n wobble: Math.random() * Math.PI * 2,\n wobbleSpeed: Math.random() * 0.02 + 0.01,\n };\n }\n};\n\nexport const initializeAccumulation = (\n accumulationMap: Map<Element, SnowAccumulation>,\n config: PhysicsConfig\n) => {\n const elements = getAccumulationSurfaces();\n // Prune disconnected elements\n for (const [el] of accumulationMap.entries()) {\n if (!el.isConnected) {\n accumulationMap.delete(el);\n }\n }\n\n elements.forEach(({ el, type, isFixed }) => {\n const existing = accumulationMap.get(el);\n const rect = el.getBoundingClientRect();\n const width = Math.ceil(rect.width);\n const isBottom = type === VAL_BOTTOM;\n\n if (existing && existing.heights.length === width) {\n existing.type = type;\n existing.isFixed = isFixed;\n if (existing.borderRadius !== undefined) {\n const styleBuffer = window.getComputedStyle(el); // Potentially slow in loop, strictly necessary?\n existing.borderRadius = parseFloat(styleBuffer.borderTopLeftRadius) || 0;\n }\n return;\n }\n\n const height = Math.ceil(rect.height);\n const baseMax = isBottom ? config.MAX_DEPTH.HEADER_TOP : config.MAX_DEPTH.CARD_TOP;\n\n const styles = window.getComputedStyle(el);\n const borderRadius = parseFloat(styles.borderTopLeftRadius) || 0;\n\n let maxHeights = new Array(width);\n for (let i = 0; i < width; i++) {\n let edgeFactor = 1.0;\n if (!isBottom && borderRadius > 0) {\n if (i < borderRadius) {\n edgeFactor = Math.pow(i / borderRadius, 1.2);\n } else if (i > width - borderRadius) {\n edgeFactor = Math.pow((width - i) / borderRadius, 1.2);\n }\n }\n maxHeights[i] = baseMax * edgeFactor * (0.85 + Math.random() * 0.15);\n }\n\n // Smooth maxHeights\n const smoothPasses = 4;\n for (let p = 0; p < smoothPasses; p++) {\n const smoothed = [...maxHeights];\n for (let i = 1; i < width - 1; i++) {\n smoothed[i] = (maxHeights[i - 1] + maxHeights[i] + maxHeights[i + 1]) / 3;\n }\n maxHeights = smoothed;\n }\n\n accumulationMap.set(el, {\n heights: existing?.heights.length === width ? existing.heights : new Array(width).fill(0),\n maxHeights,\n leftSide: existing?.leftSide.length === height ? existing.leftSide : new Array(height).fill(0),\n rightSide: existing?.rightSide.length === height ? existing.rightSide : new Array(height).fill(0),\n maxSideHeight: isBottom ? 0 : config.MAX_DEPTH.CARD_SIDE,\n borderRadius,\n type,\n isFixed,\n });\n });\n};\n\nconst accumulateSide = (\n sideArray: number[],\n rectHeight: number,\n localY: number,\n maxSideHeight: number,\n borderRadius: number,\n config: PhysicsConfig\n) => {\n const spread = 4;\n const addHeight = config.ACCUMULATION.SIDE_RATE * (0.8 + Math.random() * 0.4);\n\n for (let dy = -spread; dy <= spread; dy++) {\n const y = localY + dy;\n if (y >= 0 && y < sideArray.length) {\n const inTop = y < borderRadius;\n const inBottom = y > rectHeight - borderRadius;\n if (borderRadius > 0 && (inTop || inBottom)) continue;\n\n const normalizedDist = Math.abs(dy) / spread;\n const falloff = (Math.cos(normalizedDist * Math.PI) + 1) / 2;\n sideArray[y] = Math.min(maxSideHeight, sideArray[y] + addHeight * falloff);\n }\n }\n};\n\nexport const updateSnowflakes = (\n snowflakes: Snowflake[],\n elementRects: ElementSurface[],\n config: PhysicsConfig,\n dt: number,\n canvasWidth: number,\n canvasHeight: number\n) => {\n for (let i = snowflakes.length - 1; i >= 0; i--) {\n const flake = snowflakes[i];\n\n flake.wobble += flake.wobbleSpeed * dt;\n flake.x += (flake.wind + Math.sin(flake.wobble) * 0.5) * dt;\n flake.y += (flake.speed + Math.cos(flake.wobble * 0.5) * 0.1) * dt;\n\n let landed = false;\n\n for (const { rect, acc } of elementRects) {\n const isBottom = acc.type === VAL_BOTTOM;\n\n // Side collisions\n if (!landed && acc.maxSideHeight > 0 && !isBottom) {\n const isInVerticalBounds = flake.y >= rect.top && flake.y <= rect.bottom;\n\n if (isInVerticalBounds) {\n const localY = Math.floor(flake.y - rect.top);\n const borderRadius = acc.borderRadius;\n\n const isInTopCorner = localY < borderRadius;\n const isInBottomCorner = localY > rect.height - borderRadius;\n const isCorner = borderRadius > 0 && (isInTopCorner || isInBottomCorner);\n\n if (flake.x >= rect.left - 5 && flake.x < rect.left + 3) {\n if (!isCorner) {\n accumulateSide(acc.leftSide, rect.height, localY, acc.maxSideHeight, borderRadius, config);\n landed = true;\n }\n }\n\n if (!landed && flake.x > rect.right - 3 && flake.x <= rect.right + 5) {\n if (!isCorner) {\n accumulateSide(acc.rightSide, rect.height, localY, acc.maxSideHeight, borderRadius, config);\n landed = true;\n }\n }\n\n if (landed) break;\n }\n }\n\n // Top accumulation\n if (flake.x >= rect.left && flake.x <= rect.right) {\n const localX = Math.floor(flake.x - rect.left);\n const currentHeight = acc.heights[localX] || 0;\n const maxHeight = acc.maxHeights[localX] || 5;\n\n const surfaceY = isBottom ? rect.bottom - currentHeight : rect.top - currentHeight;\n\n if (flake.y >= surfaceY && flake.y < surfaceY + 10 && currentHeight < maxHeight) {\n const shouldAccumulate = isBottom ? Math.random() < 0.15 : true;\n\n if (shouldAccumulate) {\n const baseSpread = Math.ceil(flake.radius);\n const spread = baseSpread + Math.floor(Math.random() * 2);\n const accumRate = isBottom ? config.ACCUMULATION.TOP_HEADER_RATE : config.ACCUMULATION.TOP_CARD_RATE;\n const centerOffset = Math.floor(Math.random() * 3) - 1;\n\n for (let dx = -spread; dx <= spread; dx++) {\n if (Math.random() < 0.15) continue;\n const idx = localX + dx + centerOffset;\n if (idx >= 0 && idx < acc.heights.length) {\n const dist = Math.abs(dx);\n const pixelMax = acc.maxHeights[idx] || 5;\n\n const normDist = dist / spread;\n const falloff = (Math.cos(normDist * Math.PI) + 1) / 2;\n const baseAdd = 0.3 * falloff;\n\n const randomFactor = 0.8 + Math.random() * 0.4;\n const addHeight = baseAdd * randomFactor * accumRate;\n\n if (acc.heights[idx] < pixelMax && addHeight > 0) {\n acc.heights[idx] = Math.min(pixelMax, acc.heights[idx] + addHeight);\n }\n }\n }\n\n if (isBottom) {\n landed = true;\n break;\n }\n }\n\n if (!isBottom) {\n landed = true;\n break;\n }\n }\n }\n }\n\n if (landed || flake.y > canvasHeight + 10 || flake.x < -20 || flake.x > canvasWidth + 20) {\n snowflakes.splice(i, 1);\n }\n }\n};\n\nexport const meltAndSmoothAccumulation = (\n elementRects: ElementSurface[],\n config: PhysicsConfig,\n dt: number\n) => {\n for (const { acc } of elementRects) {\n const meltRate = config.MELT_SPEED * dt;\n const len = acc.heights.length;\n\n // Smooth\n if (len > 2) {\n for (let i = 1; i < len - 1; i++) {\n if (acc.heights[i] > 0.05) {\n const avg = (acc.heights[i - 1] + acc.heights[i + 1]) / 2;\n acc.heights[i] = acc.heights[i] * 0.99 + avg * 0.01;\n }\n }\n }\n\n // Melt\n for (let i = 0; i < acc.heights.length; i++) {\n if (acc.heights[i] > 0) acc.heights[i] = Math.max(0, acc.heights[i] - meltRate);\n }\n for (let i = 0; i < acc.leftSide.length; i++) {\n if (acc.leftSide[i] > 0) acc.leftSide[i] = Math.max(0, acc.leftSide[i] - meltRate);\n if (acc.rightSide[i] > 0) acc.rightSide[i] = Math.max(0, acc.rightSide[i] - meltRate);\n }\n }\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAEA,iBAAwC;AACxC,uBAA2B;AAEpB,MAAM,kBAAkB,CAC3B,aACA,QACA,eAAwB,UACZ;AACZ,MAAI,cAAc;AACd,UAAM,SAAS,KAAK,OAAO,IAAI,MAAM;AACrC,WAAO;AAAA,MACH,GAAG,KAAK,OAAO,IAAI;AAAA,MACnB,GAAG,OAAO,UAAU;AAAA,MACpB;AAAA,MACA,OAAO,SAAS,MAAM,KAAK,OAAO,IAAI,MAAM;AAAA,MAC5C,OAAO,KAAK,OAAO,IAAI,QAAQ,OAAO,gBAAgB;AAAA,MACtD,SAAS,KAAK,OAAO,IAAI,MAAM;AAAA,MAC/B,QAAQ,KAAK,OAAO,IAAI,KAAK,KAAK;AAAA,MAClC,aAAa,KAAK,OAAO,IAAI,QAAQ;AAAA,IACzC;AAAA,EACJ,OAAO;AACH,UAAM,SAAS,KAAK,OAAO,IAAI,IAAI;AACnC,WAAO;AAAA,MACH,GAAG,KAAK,OAAO,IAAI;AAAA,MACnB,GAAG,OAAO,UAAU;AAAA,MACpB;AAAA,MACA,OAAO,SAAS,MAAM,KAAK,OAAO,IAAI,MAAM;AAAA,MAC5C,OAAO,KAAK,OAAO,IAAI,OAAO,OAAO;AAAA,MACrC,SAAS,KAAK,OAAO,IAAI,MAAM;AAAA,MAC/B,QAAQ,KAAK,OAAO,IAAI,KAAK,KAAK;AAAA,MAClC,aAAa,KAAK,OAAO,IAAI,OAAO;AAAA,IACxC;AAAA,EACJ;AACJ;AAEO,MAAM,yBAAyB,CAClC,iBACA,WACC;AACD,QAAM,eAAW,oCAAwB;AAEzC,aAAW,CAAC,EAAE,KAAK,gBAAgB,QAAQ,GAAG;AAC1C,QAAI,CAAC,GAAG,aAAa;AACjB,sBAAgB,OAAO,EAAE;AAAA,IAC7B;AAAA,EACJ;AAEA,WAAS,QAAQ,CAAC,EAAE,IAAI,MAAM,QAAQ,MAAM;AACxC,UAAM,WAAW,gBAAgB,IAAI,EAAE;AACvC,UAAM,OAAO,GAAG,sBAAsB;AACtC,UAAM,QAAQ,KAAK,KAAK,KAAK,KAAK;AAClC,UAAM,WAAW,SAAS;AAE1B,QAAI,YAAY,SAAS,QAAQ,WAAW,OAAO;AAC/C,eAAS,OAAO;AAChB,eAAS,UAAU;AACnB,UAAI,SAAS,iBAAiB,QAAW;AACrC,cAAM,cAAc,OAAO,iBAAiB,EAAE;AAC9C,iBAAS,eAAe,WAAW,YAAY,mBAAmB,KAAK;AAAA,MAC3E;AACA;AAAA,IACJ;AAEA,UAAM,SAAS,KAAK,KAAK,KAAK,MAAM;AACpC,UAAM,UAAU,WAAW,OAAO,UAAU,aAAa,OAAO,UAAU;AAE1E,UAAM,SAAS,OAAO,iBAAiB,EAAE;AACzC,UAAM,eAAe,WAAW,OAAO,mBAAmB,KAAK;AAE/D,QAAI,aAAa,IAAI,MAAM,KAAK;AAChC,aAAS,IAAI,GAAG,IAAI,OAAO,KAAK;AAC5B,UAAI,aAAa;AACjB,UAAI,CAAC,YAAY,eAAe,GAAG;AAC/B,YAAI,IAAI,cAAc;AAClB,uBAAa,KAAK,IAAI,IAAI,cAAc,GAAG;AAAA,QAC/C,WAAW,IAAI,QAAQ,cAAc;AACjC,uBAAa,KAAK,KAAK,QAAQ,KAAK,cAAc,GAAG;AAAA,QACzD;AAAA,MACJ;AACA,iBAAW,CAAC,IAAI,UAAU,cAAc,OAAO,KAAK,OAAO,IAAI;AAAA,IACnE;AAGA,UAAM,eAAe;AACrB,aAAS,IAAI,GAAG,IAAI,cAAc,KAAK;AACnC,YAAM,WAAW,CAAC,GAAG,UAAU;AAC/B,eAAS,IAAI,GAAG,IAAI,QAAQ,GAAG,KAAK;AAChC,iBAAS,CAAC,KAAK,WAAW,IAAI,CAAC,IAAI,WAAW,CAAC,IAAI,WAAW,IAAI,CAAC,KAAK;AAAA,MAC5E;AACA,mBAAa;AAAA,IACjB;AAEA,oBAAgB,IAAI,IAAI;AAAA,MACpB,SAAS,UAAU,QAAQ,WAAW,QAAQ,SAAS,UAAU,IAAI,MAAM,KAAK,EAAE,KAAK,CAAC;AAAA,MACxF;AAAA,MACA,UAAU,UAAU,SAAS,WAAW,SAAS,SAAS,WAAW,IAAI,MAAM,MAAM,EAAE,KAAK,CAAC;AAAA,MAC7F,WAAW,UAAU,UAAU,WAAW,SAAS,SAAS,YAAY,IAAI,MAAM,MAAM,EAAE,KAAK,CAAC;AAAA,MAChG,eAAe,WAAW,IAAI,OAAO,UAAU;AAAA,MAC/C;AAAA,MACA;AAAA,MACA;AAAA,IACJ,CAAC;AAAA,EACL,CAAC;AACL;AAEA,MAAM,iBAAiB,CACnB,WACA,YACA,QACA,eACA,cACA,WACC;AACD,QAAM,SAAS;AACf,QAAM,YAAY,OAAO,aAAa,aAAa,MAAM,KAAK,OAAO,IAAI;AAEzE,WAAS,KAAK,CAAC,QAAQ,MAAM,QAAQ,MAAM;AACvC,UAAM,IAAI,SAAS;AACnB,QAAI,KAAK,KAAK,IAAI,UAAU,QAAQ;AAChC,YAAM,QAAQ,IAAI;AAClB,YAAM,WAAW,IAAI,aAAa;AAClC,UAAI,eAAe,MAAM,SAAS,UAAW;AAE7C,YAAM,iBAAiB,KAAK,IAAI,EAAE,IAAI;AACtC,YAAM,WAAW,KAAK,IAAI,iBAAiB,KAAK,EAAE,IAAI,KAAK;AAC3D,gBAAU,CAAC,IAAI,KAAK,IAAI,eAAe,UAAU,CAAC,IAAI,YAAY,OAAO;AAAA,IAC7E;AAAA,EACJ;AACJ;AAEO,MAAM,mBAAmB,CAC5B,YACA,cACA,QACA,IACA,aACA,iBACC;AACD,WAAS,IAAI,WAAW,SAAS,GAAG,KAAK,GAAG,KAAK;AAC7C,UAAM,QAAQ,WAAW,CAAC;AAE1B,UAAM,UAAU,MAAM,cAAc;AACpC,UAAM,MAAM,MAAM,OAAO,KAAK,IAAI,MAAM,MAAM,IAAI,OAAO;AACzD,UAAM,MAAM,MAAM,QAAQ,KAAK,IAAI,MAAM,SAAS,GAAG,IAAI,OAAO;AAEhE,QAAI,SAAS;AAEb,eAAW,EAAE,MAAM,IAAI,KAAK,cAAc;AACtC,YAAM,WAAW,IAAI,SAAS;AAG9B,UAAI,CAAC,UAAU,IAAI,gBAAgB,KAAK,CAAC,UAAU;AAC/C,cAAM,qBAAqB,MAAM,KAAK,KAAK,OAAO,MAAM,KAAK,KAAK;AAElE,YAAI,oBAAoB;AACpB,gBAAM,SAAS,KAAK,MAAM,MAAM,IAAI,KAAK,GAAG;AAC5C,gBAAM,eAAe,IAAI;AAEzB,gBAAM,gBAAgB,SAAS;AAC/B,gBAAM,mBAAmB,SAAS,KAAK,SAAS;AAChD,gBAAM,WAAW,eAAe,MAAM,iBAAiB;AAEvD,cAAI,MAAM,KAAK,KAAK,OAAO,KAAK,MAAM,IAAI,KAAK,OAAO,GAAG;AACrD,gBAAI,CAAC,UAAU;AACX,6BAAe,IAAI,UAAU,KAAK,QAAQ,QAAQ,IAAI,eAAe,cAAc,MAAM;AACzF,uBAAS;AAAA,YACb;AAAA,UACJ;AAEA,cAAI,CAAC,UAAU,MAAM,IAAI,KAAK,QAAQ,KAAK,MAAM,KAAK,KAAK,QAAQ,GAAG;AAClE,gBAAI,CAAC,UAAU;AACX,6BAAe,IAAI,WAAW,KAAK,QAAQ,QAAQ,IAAI,eAAe,cAAc,MAAM;AAC1F,uBAAS;AAAA,YACb;AAAA,UACJ;AAEA,cAAI,OAAQ;AAAA,QAChB;AAAA,MACJ;AAGA,UAAI,MAAM,KAAK,KAAK,QAAQ,MAAM,KAAK,KAAK,OAAO;AAC/C,cAAM,SAAS,KAAK,MAAM,MAAM,IAAI,KAAK,IAAI;AAC7C,cAAM,gBAAgB,IAAI,QAAQ,MAAM,KAAK;AAC7C,cAAM,YAAY,IAAI,WAAW,MAAM,KAAK;AAE5C,cAAM,WAAW,WAAW,KAAK,SAAS,gBAAgB,KAAK,MAAM;AAErE,YAAI,MAAM,KAAK,YAAY,MAAM,IAAI,WAAW,MAAM,gBAAgB,WAAW;AAC7E,gBAAM,mBAAmB,WAAW,KAAK,OAAO,IAAI,OAAO;AAE3D,cAAI,kBAAkB;AAClB,kBAAM,aAAa,KAAK,KAAK,MAAM,MAAM;AACzC,kBAAM,SAAS,aAAa,KAAK,MAAM,KAAK,OAAO,IAAI,CAAC;AACxD,kBAAM,YAAY,WAAW,OAAO,aAAa,kBAAkB,OAAO,aAAa;AACvF,kBAAM,eAAe,KAAK,MAAM,KAAK,OAAO,IAAI,CAAC,IAAI;AAErD,qBAAS,KAAK,CAAC,QAAQ,MAAM,QAAQ,MAAM;AACvC,kBAAI,KAAK,OAAO,IAAI,KAAM;AAC1B,oBAAM,MAAM,SAAS,KAAK;AAC1B,kBAAI,OAAO,KAAK,MAAM,IAAI,QAAQ,QAAQ;AACtC,sBAAM,OAAO,KAAK,IAAI,EAAE;AACxB,sBAAM,WAAW,IAAI,WAAW,GAAG,KAAK;AAExC,sBAAM,WAAW,OAAO;AACxB,sBAAM,WAAW,KAAK,IAAI,WAAW,KAAK,EAAE,IAAI,KAAK;AACrD,sBAAM,UAAU,MAAM;AAEtB,sBAAM,eAAe,MAAM,KAAK,OAAO,IAAI;AAC3C,sBAAM,YAAY,UAAU,eAAe;AAE3C,oBAAI,IAAI,QAAQ,GAAG,IAAI,YAAY,YAAY,GAAG;AAC9C,sBAAI,QAAQ,GAAG,IAAI,KAAK,IAAI,UAAU,IAAI,QAAQ,GAAG,IAAI,SAAS;AAAA,gBACtE;AAAA,cACJ;AAAA,YACJ;AAEA,gBAAI,UAAU;AACV,uBAAS;AACT;AAAA,YACJ;AAAA,UACJ;AAEA,cAAI,CAAC,UAAU;AACX,qBAAS;AACT;AAAA,UACJ;AAAA,QACJ;AAAA,MACJ;AAAA,IACJ;AAEA,QAAI,UAAU,MAAM,IAAI,eAAe,MAAM,MAAM,IAAI,OAAO,MAAM,IAAI,cAAc,IAAI;AACtF,iBAAW,OAAO,GAAG,CAAC;AAAA,IAC1B;AAAA,EACJ;AACJ;AAEO,MAAM,4BAA4B,CACrC,cACA,QACA,OACC;AACD,aAAW,EAAE,IAAI,KAAK,cAAc;AAChC,UAAM,WAAW,OAAO,aAAa;AACrC,UAAM,MAAM,IAAI,QAAQ;AAGxB,QAAI,MAAM,GAAG;AACT,eAAS,IAAI,GAAG,IAAI,MAAM,GAAG,KAAK;AAC9B,YAAI,IAAI,QAAQ,CAAC,IAAI,MAAM;AACvB,gBAAM,OAAO,IAAI,QAAQ,IAAI,CAAC,IAAI,IAAI,QAAQ,IAAI,CAAC,KAAK;AACxD,cAAI,QAAQ,CAAC,IAAI,IAAI,QAAQ,CAAC,IAAI,OAAO,MAAM;AAAA,QACnD;AAAA,MACJ;AAAA,IACJ;AAGA,aAAS,IAAI,GAAG,IAAI,IAAI,QAAQ,QAAQ,KAAK;AACzC,UAAI,IAAI,QAAQ,CAAC,IAAI,EAAG,KAAI,QAAQ,CAAC,IAAI,KAAK,IAAI,GAAG,IAAI,QAAQ,CAAC,IAAI,QAAQ;AAAA,IAClF;AACA,aAAS,IAAI,GAAG,IAAI,IAAI,SAAS,QAAQ,KAAK;AAC1C,UAAI,IAAI,SAAS,CAAC,IAAI,EAAG,KAAI,SAAS,CAAC,IAAI,KAAK,IAAI,GAAG,IAAI,SAAS,CAAC,IAAI,QAAQ;AACjF,UAAI,IAAI,UAAU,CAAC,IAAI,EAAG,KAAI,UAAU,CAAC,IAAI,KAAK,IAAI,GAAG,IAAI,UAAU,CAAC,IAAI,QAAQ;AAAA,IACxF;AAAA,EACJ;AACJ;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../../src/utils/snowfall/physics.ts"],"sourcesContent":["import { PhysicsConfig } from '../../SnowfallProvider';\nimport { Snowflake, SnowAccumulation, ElementSurface } from './types';\nimport { getAccumulationSurfaces } from './dom';\nimport { VAL_BOTTOM } from './constants';\n\nexport const createSnowflake = (\n canvasWidth: number,\n config: PhysicsConfig,\n isBackground: boolean = false\n): Snowflake => {\n if (isBackground) {\n const sizeRatio = Math.random();\n const radius = config.FLAKE_SIZE.MIN * 0.6 + sizeRatio * (config.FLAKE_SIZE.MAX - config.FLAKE_SIZE.MIN) * 0.4;\n return {\n x: Math.random() * canvasWidth,\n y: window.scrollY - 5,\n radius,\n speed: radius * 0.3 + Math.random() * 0.2 + 0.2,\n wind: (Math.random() - 0.5) * (config.WIND_STRENGTH * 0.625),\n opacity: Math.random() * 0.2 + 0.2,\n wobble: Math.random() * Math.PI * 2,\n wobbleSpeed: Math.random() * 0.015 + 0.005,\n sizeRatio,\n isBackground: true\n };\n } else {\n const sizeRatio = Math.random();\n const radius = config.FLAKE_SIZE.MIN + sizeRatio * (config.FLAKE_SIZE.MAX - config.FLAKE_SIZE.MIN);\n return {\n x: Math.random() * canvasWidth,\n y: window.scrollY - 5,\n radius,\n speed: radius * 0.5 + Math.random() * 0.3 + 0.5,\n wind: (Math.random() - 0.5) * config.WIND_STRENGTH,\n opacity: Math.random() * 0.3 + 0.5,\n wobble: Math.random() * Math.PI * 2,\n wobbleSpeed: Math.random() * 0.02 + 0.01,\n sizeRatio,\n isBackground: false\n };\n }\n};\n\nexport const initializeAccumulation = (\n accumulationMap: Map<Element, SnowAccumulation>,\n config: PhysicsConfig\n) => {\n const elements = getAccumulationSurfaces();\n // Prune disconnected elements\n for (const [el] of accumulationMap.entries()) {\n if (!el.isConnected) {\n accumulationMap.delete(el);\n }\n }\n\n elements.forEach(({ el, type, isFixed }) => {\n const existing = accumulationMap.get(el);\n const rect = el.getBoundingClientRect();\n const width = Math.ceil(rect.width);\n const isBottom = type === VAL_BOTTOM;\n\n if (existing && existing.heights.length === width) {\n existing.type = type;\n existing.isFixed = isFixed;\n if (existing.borderRadius !== undefined) {\n const styleBuffer = window.getComputedStyle(el); // Potentially slow in loop, strictly necessary?\n existing.borderRadius = parseFloat(styleBuffer.borderTopLeftRadius) || 0;\n }\n return;\n }\n\n const height = Math.ceil(rect.height);\n const baseMax = isBottom ? config.MAX_DEPTH.BOTTOM : config.MAX_DEPTH.TOP;\n\n const styles = window.getComputedStyle(el);\n const borderRadius = parseFloat(styles.borderTopLeftRadius) || 0;\n\n let maxHeights = new Array(width);\n for (let i = 0; i < width; i++) {\n let edgeFactor = 1.0;\n if (!isBottom && borderRadius > 0) {\n if (i < borderRadius) {\n edgeFactor = Math.pow(i / borderRadius, 1.2);\n } else if (i > width - borderRadius) {\n edgeFactor = Math.pow((width - i) / borderRadius, 1.2);\n }\n }\n maxHeights[i] = baseMax * edgeFactor * (0.85 + Math.random() * 0.15);\n }\n\n // Smooth maxHeights\n const smoothPasses = 4;\n for (let p = 0; p < smoothPasses; p++) {\n const smoothed = [...maxHeights];\n for (let i = 1; i < width - 1; i++) {\n smoothed[i] = (maxHeights[i - 1] + maxHeights[i] + maxHeights[i + 1]) / 3;\n }\n maxHeights = smoothed;\n }\n\n accumulationMap.set(el, {\n heights: existing?.heights.length === width ? existing.heights : new Array(width).fill(0),\n maxHeights,\n leftSide: existing?.leftSide.length === height ? existing.leftSide : new Array(height).fill(0),\n rightSide: existing?.rightSide.length === height ? existing.rightSide : new Array(height).fill(0),\n maxSideHeight: isBottom ? 0 : config.MAX_DEPTH.SIDE,\n borderRadius,\n type,\n isFixed,\n });\n });\n};\n\nconst accumulateSide = (\n sideArray: number[],\n rectHeight: number,\n localY: number,\n maxSideHeight: number,\n borderRadius: number,\n config: PhysicsConfig\n) => {\n const spread = 4;\n const addHeight = config.ACCUMULATION.SIDE_RATE * (0.8 + Math.random() * 0.4);\n\n for (let dy = -spread; dy <= spread; dy++) {\n const y = localY + dy;\n if (y >= 0 && y < sideArray.length) {\n const inTop = y < borderRadius;\n const inBottom = y > rectHeight - borderRadius;\n if (borderRadius > 0 && (inTop || inBottom)) continue;\n\n const normalizedDist = Math.abs(dy) / spread;\n const falloff = (Math.cos(normalizedDist * Math.PI) + 1) / 2;\n sideArray[y] = Math.min(maxSideHeight, sideArray[y] + addHeight * falloff);\n }\n }\n};\n\nexport const updateSnowflakes = (\n snowflakes: Snowflake[],\n elementRects: ElementSurface[],\n config: PhysicsConfig,\n dt: number,\n canvasWidth: number,\n canvasHeight: number\n) => {\n for (let i = snowflakes.length - 1; i >= 0; i--) {\n const flake = snowflakes[i];\n\n flake.wobble += flake.wobbleSpeed * dt;\n flake.x += (flake.wind + Math.sin(flake.wobble) * 0.5) * dt;\n flake.y += (flake.speed + Math.cos(flake.wobble * 0.5) * 0.1) * dt;\n\n let landed = false;\n\n for (const { rect, acc } of elementRects) {\n const isBottom = acc.type === VAL_BOTTOM;\n\n // Side collisions\n if (!landed && acc.maxSideHeight > 0 && !isBottom) {\n const isInVerticalBounds = flake.y >= rect.top && flake.y <= rect.bottom;\n\n if (isInVerticalBounds) {\n const localY = Math.floor(flake.y - rect.top);\n const borderRadius = acc.borderRadius;\n\n const isInTopCorner = localY < borderRadius;\n const isInBottomCorner = localY > rect.height - borderRadius;\n const isCorner = borderRadius > 0 && (isInTopCorner || isInBottomCorner);\n\n if (flake.x >= rect.left - 5 && flake.x < rect.left + 3) {\n if (!isCorner) {\n accumulateSide(acc.leftSide, rect.height, localY, acc.maxSideHeight, borderRadius, config);\n landed = true;\n }\n }\n\n if (!landed && flake.x > rect.right - 3 && flake.x <= rect.right + 5) {\n if (!isCorner) {\n accumulateSide(acc.rightSide, rect.height, localY, acc.maxSideHeight, borderRadius, config);\n landed = true;\n }\n }\n\n if (landed) break;\n }\n }\n\n // Top accumulation\n if (flake.x >= rect.left && flake.x <= rect.right) {\n const localX = Math.floor(flake.x - rect.left);\n const currentHeight = acc.heights[localX] || 0;\n const maxHeight = acc.maxHeights[localX] || 5;\n\n const surfaceY = isBottom ? rect.bottom - currentHeight : rect.top - currentHeight;\n\n if (flake.y >= surfaceY && flake.y < surfaceY + 10 && currentHeight < maxHeight) {\n const shouldAccumulate = isBottom ? Math.random() < 0.15 : true;\n\n if (shouldAccumulate) {\n const baseSpread = Math.ceil(flake.radius);\n const spread = baseSpread + Math.floor(Math.random() * 2);\n const accumRate = isBottom ? config.ACCUMULATION.BOTTOM_RATE : config.ACCUMULATION.TOP_RATE;\n const centerOffset = Math.floor(Math.random() * 3) - 1;\n\n for (let dx = -spread; dx <= spread; dx++) {\n if (Math.random() < 0.15) continue;\n const idx = localX + dx + centerOffset;\n if (idx >= 0 && idx < acc.heights.length) {\n const dist = Math.abs(dx);\n const pixelMax = acc.maxHeights[idx] || 5;\n\n const normDist = dist / spread;\n const falloff = (Math.cos(normDist * Math.PI) + 1) / 2;\n const baseAdd = 0.3 * falloff;\n\n const randomFactor = 0.8 + Math.random() * 0.4;\n const addHeight = baseAdd * randomFactor * accumRate;\n\n if (acc.heights[idx] < pixelMax && addHeight > 0) {\n acc.heights[idx] = Math.min(pixelMax, acc.heights[idx] + addHeight);\n }\n }\n }\n\n if (isBottom) {\n landed = true;\n break;\n }\n }\n\n if (!isBottom) {\n landed = true;\n break;\n }\n }\n }\n }\n\n if (landed || flake.y > canvasHeight + 10 || flake.x < -20 || flake.x > canvasWidth + 20) {\n snowflakes.splice(i, 1);\n }\n }\n};\n\nexport const meltAndSmoothAccumulation = (\n elementRects: ElementSurface[],\n config: PhysicsConfig,\n dt: number\n) => {\n for (const { acc } of elementRects) {\n const meltRate = config.MELT_SPEED * dt;\n const len = acc.heights.length;\n\n // Smooth\n if (len > 2) {\n for (let i = 1; i < len - 1; i++) {\n if (acc.heights[i] > 0.05) {\n const avg = (acc.heights[i - 1] + acc.heights[i + 1]) / 2;\n acc.heights[i] = acc.heights[i] * 0.99 + avg * 0.01;\n }\n }\n }\n\n // Melt\n for (let i = 0; i < acc.heights.length; i++) {\n if (acc.heights[i] > 0) acc.heights[i] = Math.max(0, acc.heights[i] - meltRate);\n }\n for (let i = 0; i < acc.leftSide.length; i++) {\n if (acc.leftSide[i] > 0) acc.leftSide[i] = Math.max(0, acc.leftSide[i] - meltRate);\n if (acc.rightSide[i] > 0) acc.rightSide[i] = Math.max(0, acc.rightSide[i] - meltRate);\n }\n }\n};\n"],"mappings":"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","names":[]}
|
|
@@ -2,7 +2,8 @@ import { getAccumulationSurfaces } from "./dom";
|
|
|
2
2
|
import { VAL_BOTTOM } from "./constants";
|
|
3
3
|
const createSnowflake = (canvasWidth, config, isBackground = false) => {
|
|
4
4
|
if (isBackground) {
|
|
5
|
-
const
|
|
5
|
+
const sizeRatio = Math.random();
|
|
6
|
+
const radius = config.FLAKE_SIZE.MIN * 0.6 + sizeRatio * (config.FLAKE_SIZE.MAX - config.FLAKE_SIZE.MIN) * 0.4;
|
|
6
7
|
return {
|
|
7
8
|
x: Math.random() * canvasWidth,
|
|
8
9
|
y: window.scrollY - 5,
|
|
@@ -11,10 +12,13 @@ const createSnowflake = (canvasWidth, config, isBackground = false) => {
|
|
|
11
12
|
wind: (Math.random() - 0.5) * (config.WIND_STRENGTH * 0.625),
|
|
12
13
|
opacity: Math.random() * 0.2 + 0.2,
|
|
13
14
|
wobble: Math.random() * Math.PI * 2,
|
|
14
|
-
wobbleSpeed: Math.random() * 0.015 + 5e-3
|
|
15
|
+
wobbleSpeed: Math.random() * 0.015 + 5e-3,
|
|
16
|
+
sizeRatio,
|
|
17
|
+
isBackground: true
|
|
15
18
|
};
|
|
16
19
|
} else {
|
|
17
|
-
const
|
|
20
|
+
const sizeRatio = Math.random();
|
|
21
|
+
const radius = config.FLAKE_SIZE.MIN + sizeRatio * (config.FLAKE_SIZE.MAX - config.FLAKE_SIZE.MIN);
|
|
18
22
|
return {
|
|
19
23
|
x: Math.random() * canvasWidth,
|
|
20
24
|
y: window.scrollY - 5,
|
|
@@ -23,7 +27,9 @@ const createSnowflake = (canvasWidth, config, isBackground = false) => {
|
|
|
23
27
|
wind: (Math.random() - 0.5) * config.WIND_STRENGTH,
|
|
24
28
|
opacity: Math.random() * 0.3 + 0.5,
|
|
25
29
|
wobble: Math.random() * Math.PI * 2,
|
|
26
|
-
wobbleSpeed: Math.random() * 0.02 + 0.01
|
|
30
|
+
wobbleSpeed: Math.random() * 0.02 + 0.01,
|
|
31
|
+
sizeRatio,
|
|
32
|
+
isBackground: false
|
|
27
33
|
};
|
|
28
34
|
}
|
|
29
35
|
};
|
|
@@ -49,7 +55,7 @@ const initializeAccumulation = (accumulationMap, config) => {
|
|
|
49
55
|
return;
|
|
50
56
|
}
|
|
51
57
|
const height = Math.ceil(rect.height);
|
|
52
|
-
const baseMax = isBottom ? config.MAX_DEPTH.
|
|
58
|
+
const baseMax = isBottom ? config.MAX_DEPTH.BOTTOM : config.MAX_DEPTH.TOP;
|
|
53
59
|
const styles = window.getComputedStyle(el);
|
|
54
60
|
const borderRadius = parseFloat(styles.borderTopLeftRadius) || 0;
|
|
55
61
|
let maxHeights = new Array(width);
|
|
@@ -77,7 +83,7 @@ const initializeAccumulation = (accumulationMap, config) => {
|
|
|
77
83
|
maxHeights,
|
|
78
84
|
leftSide: existing?.leftSide.length === height ? existing.leftSide : new Array(height).fill(0),
|
|
79
85
|
rightSide: existing?.rightSide.length === height ? existing.rightSide : new Array(height).fill(0),
|
|
80
|
-
maxSideHeight: isBottom ? 0 : config.MAX_DEPTH.
|
|
86
|
+
maxSideHeight: isBottom ? 0 : config.MAX_DEPTH.SIDE,
|
|
81
87
|
borderRadius,
|
|
82
88
|
type,
|
|
83
89
|
isFixed
|
|
@@ -141,7 +147,7 @@ const updateSnowflakes = (snowflakes, elementRects, config, dt, canvasWidth, can
|
|
|
141
147
|
if (shouldAccumulate) {
|
|
142
148
|
const baseSpread = Math.ceil(flake.radius);
|
|
143
149
|
const spread = baseSpread + Math.floor(Math.random() * 2);
|
|
144
|
-
const accumRate = isBottom ? config.ACCUMULATION.
|
|
150
|
+
const accumRate = isBottom ? config.ACCUMULATION.BOTTOM_RATE : config.ACCUMULATION.TOP_RATE;
|
|
145
151
|
const centerOffset = Math.floor(Math.random() * 3) - 1;
|
|
146
152
|
for (let dx = -spread; dx <= spread; dx++) {
|
|
147
153
|
if (Math.random() < 0.15) continue;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/utils/snowfall/physics.ts"],"sourcesContent":["import { PhysicsConfig } from '../../SnowfallProvider';\nimport { Snowflake, SnowAccumulation, ElementSurface } from './types';\nimport { getAccumulationSurfaces } from './dom';\nimport { VAL_BOTTOM } from './constants';\n\nexport const createSnowflake = (\n canvasWidth: number,\n config: PhysicsConfig,\n isBackground: boolean = false\n): Snowflake => {\n if (isBackground) {\n const radius = Math.random() * 0.8 + 0.3;\n return {\n x: Math.random() * canvasWidth,\n y: window.scrollY - 5,\n radius,\n speed: radius * 0.3 + Math.random() * 0.2 + 0.2,\n wind: (Math.random() - 0.5) * (config.WIND_STRENGTH * 0.625),\n opacity: Math.random() * 0.2 + 0.2,\n wobble: Math.random() * Math.PI * 2,\n wobbleSpeed: Math.random() * 0.015 + 0.005,\n };\n } else {\n const radius = Math.random() * 2 + 0.5;\n return {\n x: Math.random() * canvasWidth,\n y: window.scrollY - 5,\n radius,\n speed: radius * 0.5 + Math.random() * 0.3 + 0.5,\n wind: (Math.random() - 0.5) * config.WIND_STRENGTH,\n opacity: Math.random() * 0.3 + 0.5,\n wobble: Math.random() * Math.PI * 2,\n wobbleSpeed: Math.random() * 0.02 + 0.01,\n };\n }\n};\n\nexport const initializeAccumulation = (\n accumulationMap: Map<Element, SnowAccumulation>,\n config: PhysicsConfig\n) => {\n const elements = getAccumulationSurfaces();\n // Prune disconnected elements\n for (const [el] of accumulationMap.entries()) {\n if (!el.isConnected) {\n accumulationMap.delete(el);\n }\n }\n\n elements.forEach(({ el, type, isFixed }) => {\n const existing = accumulationMap.get(el);\n const rect = el.getBoundingClientRect();\n const width = Math.ceil(rect.width);\n const isBottom = type === VAL_BOTTOM;\n\n if (existing && existing.heights.length === width) {\n existing.type = type;\n existing.isFixed = isFixed;\n if (existing.borderRadius !== undefined) {\n const styleBuffer = window.getComputedStyle(el); // Potentially slow in loop, strictly necessary?\n existing.borderRadius = parseFloat(styleBuffer.borderTopLeftRadius) || 0;\n }\n return;\n }\n\n const height = Math.ceil(rect.height);\n const baseMax = isBottom ? config.MAX_DEPTH.HEADER_TOP : config.MAX_DEPTH.CARD_TOP;\n\n const styles = window.getComputedStyle(el);\n const borderRadius = parseFloat(styles.borderTopLeftRadius) || 0;\n\n let maxHeights = new Array(width);\n for (let i = 0; i < width; i++) {\n let edgeFactor = 1.0;\n if (!isBottom && borderRadius > 0) {\n if (i < borderRadius) {\n edgeFactor = Math.pow(i / borderRadius, 1.2);\n } else if (i > width - borderRadius) {\n edgeFactor = Math.pow((width - i) / borderRadius, 1.2);\n }\n }\n maxHeights[i] = baseMax * edgeFactor * (0.85 + Math.random() * 0.15);\n }\n\n // Smooth maxHeights\n const smoothPasses = 4;\n for (let p = 0; p < smoothPasses; p++) {\n const smoothed = [...maxHeights];\n for (let i = 1; i < width - 1; i++) {\n smoothed[i] = (maxHeights[i - 1] + maxHeights[i] + maxHeights[i + 1]) / 3;\n }\n maxHeights = smoothed;\n }\n\n accumulationMap.set(el, {\n heights: existing?.heights.length === width ? existing.heights : new Array(width).fill(0),\n maxHeights,\n leftSide: existing?.leftSide.length === height ? existing.leftSide : new Array(height).fill(0),\n rightSide: existing?.rightSide.length === height ? existing.rightSide : new Array(height).fill(0),\n maxSideHeight: isBottom ? 0 : config.MAX_DEPTH.CARD_SIDE,\n borderRadius,\n type,\n isFixed,\n });\n });\n};\n\nconst accumulateSide = (\n sideArray: number[],\n rectHeight: number,\n localY: number,\n maxSideHeight: number,\n borderRadius: number,\n config: PhysicsConfig\n) => {\n const spread = 4;\n const addHeight = config.ACCUMULATION.SIDE_RATE * (0.8 + Math.random() * 0.4);\n\n for (let dy = -spread; dy <= spread; dy++) {\n const y = localY + dy;\n if (y >= 0 && y < sideArray.length) {\n const inTop = y < borderRadius;\n const inBottom = y > rectHeight - borderRadius;\n if (borderRadius > 0 && (inTop || inBottom)) continue;\n\n const normalizedDist = Math.abs(dy) / spread;\n const falloff = (Math.cos(normalizedDist * Math.PI) + 1) / 2;\n sideArray[y] = Math.min(maxSideHeight, sideArray[y] + addHeight * falloff);\n }\n }\n};\n\nexport const updateSnowflakes = (\n snowflakes: Snowflake[],\n elementRects: ElementSurface[],\n config: PhysicsConfig,\n dt: number,\n canvasWidth: number,\n canvasHeight: number\n) => {\n for (let i = snowflakes.length - 1; i >= 0; i--) {\n const flake = snowflakes[i];\n\n flake.wobble += flake.wobbleSpeed * dt;\n flake.x += (flake.wind + Math.sin(flake.wobble) * 0.5) * dt;\n flake.y += (flake.speed + Math.cos(flake.wobble * 0.5) * 0.1) * dt;\n\n let landed = false;\n\n for (const { rect, acc } of elementRects) {\n const isBottom = acc.type === VAL_BOTTOM;\n\n // Side collisions\n if (!landed && acc.maxSideHeight > 0 && !isBottom) {\n const isInVerticalBounds = flake.y >= rect.top && flake.y <= rect.bottom;\n\n if (isInVerticalBounds) {\n const localY = Math.floor(flake.y - rect.top);\n const borderRadius = acc.borderRadius;\n\n const isInTopCorner = localY < borderRadius;\n const isInBottomCorner = localY > rect.height - borderRadius;\n const isCorner = borderRadius > 0 && (isInTopCorner || isInBottomCorner);\n\n if (flake.x >= rect.left - 5 && flake.x < rect.left + 3) {\n if (!isCorner) {\n accumulateSide(acc.leftSide, rect.height, localY, acc.maxSideHeight, borderRadius, config);\n landed = true;\n }\n }\n\n if (!landed && flake.x > rect.right - 3 && flake.x <= rect.right + 5) {\n if (!isCorner) {\n accumulateSide(acc.rightSide, rect.height, localY, acc.maxSideHeight, borderRadius, config);\n landed = true;\n }\n }\n\n if (landed) break;\n }\n }\n\n // Top accumulation\n if (flake.x >= rect.left && flake.x <= rect.right) {\n const localX = Math.floor(flake.x - rect.left);\n const currentHeight = acc.heights[localX] || 0;\n const maxHeight = acc.maxHeights[localX] || 5;\n\n const surfaceY = isBottom ? rect.bottom - currentHeight : rect.top - currentHeight;\n\n if (flake.y >= surfaceY && flake.y < surfaceY + 10 && currentHeight < maxHeight) {\n const shouldAccumulate = isBottom ? Math.random() < 0.15 : true;\n\n if (shouldAccumulate) {\n const baseSpread = Math.ceil(flake.radius);\n const spread = baseSpread + Math.floor(Math.random() * 2);\n const accumRate = isBottom ? config.ACCUMULATION.TOP_HEADER_RATE : config.ACCUMULATION.TOP_CARD_RATE;\n const centerOffset = Math.floor(Math.random() * 3) - 1;\n\n for (let dx = -spread; dx <= spread; dx++) {\n if (Math.random() < 0.15) continue;\n const idx = localX + dx + centerOffset;\n if (idx >= 0 && idx < acc.heights.length) {\n const dist = Math.abs(dx);\n const pixelMax = acc.maxHeights[idx] || 5;\n\n const normDist = dist / spread;\n const falloff = (Math.cos(normDist * Math.PI) + 1) / 2;\n const baseAdd = 0.3 * falloff;\n\n const randomFactor = 0.8 + Math.random() * 0.4;\n const addHeight = baseAdd * randomFactor * accumRate;\n\n if (acc.heights[idx] < pixelMax && addHeight > 0) {\n acc.heights[idx] = Math.min(pixelMax, acc.heights[idx] + addHeight);\n }\n }\n }\n\n if (isBottom) {\n landed = true;\n break;\n }\n }\n\n if (!isBottom) {\n landed = true;\n break;\n }\n }\n }\n }\n\n if (landed || flake.y > canvasHeight + 10 || flake.x < -20 || flake.x > canvasWidth + 20) {\n snowflakes.splice(i, 1);\n }\n }\n};\n\nexport const meltAndSmoothAccumulation = (\n elementRects: ElementSurface[],\n config: PhysicsConfig,\n dt: number\n) => {\n for (const { acc } of elementRects) {\n const meltRate = config.MELT_SPEED * dt;\n const len = acc.heights.length;\n\n // Smooth\n if (len > 2) {\n for (let i = 1; i < len - 1; i++) {\n if (acc.heights[i] > 0.05) {\n const avg = (acc.heights[i - 1] + acc.heights[i + 1]) / 2;\n acc.heights[i] = acc.heights[i] * 0.99 + avg * 0.01;\n }\n }\n }\n\n // Melt\n for (let i = 0; i < acc.heights.length; i++) {\n if (acc.heights[i] > 0) acc.heights[i] = Math.max(0, acc.heights[i] - meltRate);\n }\n for (let i = 0; i < acc.leftSide.length; i++) {\n if (acc.leftSide[i] > 0) acc.leftSide[i] = Math.max(0, acc.leftSide[i] - meltRate);\n if (acc.rightSide[i] > 0) acc.rightSide[i] = Math.max(0, acc.rightSide[i] - meltRate);\n }\n }\n};\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../../src/utils/snowfall/physics.ts"],"sourcesContent":["import { PhysicsConfig } from '../../SnowfallProvider';\nimport { Snowflake, SnowAccumulation, ElementSurface } from './types';\nimport { getAccumulationSurfaces } from './dom';\nimport { VAL_BOTTOM } from './constants';\n\nexport const createSnowflake = (\n canvasWidth: number,\n config: PhysicsConfig,\n isBackground: boolean = false\n): Snowflake => {\n if (isBackground) {\n const sizeRatio = Math.random();\n const radius = config.FLAKE_SIZE.MIN * 0.6 + sizeRatio * (config.FLAKE_SIZE.MAX - config.FLAKE_SIZE.MIN) * 0.4;\n return {\n x: Math.random() * canvasWidth,\n y: window.scrollY - 5,\n radius,\n speed: radius * 0.3 + Math.random() * 0.2 + 0.2,\n wind: (Math.random() - 0.5) * (config.WIND_STRENGTH * 0.625),\n opacity: Math.random() * 0.2 + 0.2,\n wobble: Math.random() * Math.PI * 2,\n wobbleSpeed: Math.random() * 0.015 + 0.005,\n sizeRatio,\n isBackground: true\n };\n } else {\n const sizeRatio = Math.random();\n const radius = config.FLAKE_SIZE.MIN + sizeRatio * (config.FLAKE_SIZE.MAX - config.FLAKE_SIZE.MIN);\n return {\n x: Math.random() * canvasWidth,\n y: window.scrollY - 5,\n radius,\n speed: radius * 0.5 + Math.random() * 0.3 + 0.5,\n wind: (Math.random() - 0.5) * config.WIND_STRENGTH,\n opacity: Math.random() * 0.3 + 0.5,\n wobble: Math.random() * Math.PI * 2,\n wobbleSpeed: Math.random() * 0.02 + 0.01,\n sizeRatio,\n isBackground: false\n };\n }\n};\n\nexport const initializeAccumulation = (\n accumulationMap: Map<Element, SnowAccumulation>,\n config: PhysicsConfig\n) => {\n const elements = getAccumulationSurfaces();\n // Prune disconnected elements\n for (const [el] of accumulationMap.entries()) {\n if (!el.isConnected) {\n accumulationMap.delete(el);\n }\n }\n\n elements.forEach(({ el, type, isFixed }) => {\n const existing = accumulationMap.get(el);\n const rect = el.getBoundingClientRect();\n const width = Math.ceil(rect.width);\n const isBottom = type === VAL_BOTTOM;\n\n if (existing && existing.heights.length === width) {\n existing.type = type;\n existing.isFixed = isFixed;\n if (existing.borderRadius !== undefined) {\n const styleBuffer = window.getComputedStyle(el); // Potentially slow in loop, strictly necessary?\n existing.borderRadius = parseFloat(styleBuffer.borderTopLeftRadius) || 0;\n }\n return;\n }\n\n const height = Math.ceil(rect.height);\n const baseMax = isBottom ? config.MAX_DEPTH.BOTTOM : config.MAX_DEPTH.TOP;\n\n const styles = window.getComputedStyle(el);\n const borderRadius = parseFloat(styles.borderTopLeftRadius) || 0;\n\n let maxHeights = new Array(width);\n for (let i = 0; i < width; i++) {\n let edgeFactor = 1.0;\n if (!isBottom && borderRadius > 0) {\n if (i < borderRadius) {\n edgeFactor = Math.pow(i / borderRadius, 1.2);\n } else if (i > width - borderRadius) {\n edgeFactor = Math.pow((width - i) / borderRadius, 1.2);\n }\n }\n maxHeights[i] = baseMax * edgeFactor * (0.85 + Math.random() * 0.15);\n }\n\n // Smooth maxHeights\n const smoothPasses = 4;\n for (let p = 0; p < smoothPasses; p++) {\n const smoothed = [...maxHeights];\n for (let i = 1; i < width - 1; i++) {\n smoothed[i] = (maxHeights[i - 1] + maxHeights[i] + maxHeights[i + 1]) / 3;\n }\n maxHeights = smoothed;\n }\n\n accumulationMap.set(el, {\n heights: existing?.heights.length === width ? existing.heights : new Array(width).fill(0),\n maxHeights,\n leftSide: existing?.leftSide.length === height ? existing.leftSide : new Array(height).fill(0),\n rightSide: existing?.rightSide.length === height ? existing.rightSide : new Array(height).fill(0),\n maxSideHeight: isBottom ? 0 : config.MAX_DEPTH.SIDE,\n borderRadius,\n type,\n isFixed,\n });\n });\n};\n\nconst accumulateSide = (\n sideArray: number[],\n rectHeight: number,\n localY: number,\n maxSideHeight: number,\n borderRadius: number,\n config: PhysicsConfig\n) => {\n const spread = 4;\n const addHeight = config.ACCUMULATION.SIDE_RATE * (0.8 + Math.random() * 0.4);\n\n for (let dy = -spread; dy <= spread; dy++) {\n const y = localY + dy;\n if (y >= 0 && y < sideArray.length) {\n const inTop = y < borderRadius;\n const inBottom = y > rectHeight - borderRadius;\n if (borderRadius > 0 && (inTop || inBottom)) continue;\n\n const normalizedDist = Math.abs(dy) / spread;\n const falloff = (Math.cos(normalizedDist * Math.PI) + 1) / 2;\n sideArray[y] = Math.min(maxSideHeight, sideArray[y] + addHeight * falloff);\n }\n }\n};\n\nexport const updateSnowflakes = (\n snowflakes: Snowflake[],\n elementRects: ElementSurface[],\n config: PhysicsConfig,\n dt: number,\n canvasWidth: number,\n canvasHeight: number\n) => {\n for (let i = snowflakes.length - 1; i >= 0; i--) {\n const flake = snowflakes[i];\n\n flake.wobble += flake.wobbleSpeed * dt;\n flake.x += (flake.wind + Math.sin(flake.wobble) * 0.5) * dt;\n flake.y += (flake.speed + Math.cos(flake.wobble * 0.5) * 0.1) * dt;\n\n let landed = false;\n\n for (const { rect, acc } of elementRects) {\n const isBottom = acc.type === VAL_BOTTOM;\n\n // Side collisions\n if (!landed && acc.maxSideHeight > 0 && !isBottom) {\n const isInVerticalBounds = flake.y >= rect.top && flake.y <= rect.bottom;\n\n if (isInVerticalBounds) {\n const localY = Math.floor(flake.y - rect.top);\n const borderRadius = acc.borderRadius;\n\n const isInTopCorner = localY < borderRadius;\n const isInBottomCorner = localY > rect.height - borderRadius;\n const isCorner = borderRadius > 0 && (isInTopCorner || isInBottomCorner);\n\n if (flake.x >= rect.left - 5 && flake.x < rect.left + 3) {\n if (!isCorner) {\n accumulateSide(acc.leftSide, rect.height, localY, acc.maxSideHeight, borderRadius, config);\n landed = true;\n }\n }\n\n if (!landed && flake.x > rect.right - 3 && flake.x <= rect.right + 5) {\n if (!isCorner) {\n accumulateSide(acc.rightSide, rect.height, localY, acc.maxSideHeight, borderRadius, config);\n landed = true;\n }\n }\n\n if (landed) break;\n }\n }\n\n // Top accumulation\n if (flake.x >= rect.left && flake.x <= rect.right) {\n const localX = Math.floor(flake.x - rect.left);\n const currentHeight = acc.heights[localX] || 0;\n const maxHeight = acc.maxHeights[localX] || 5;\n\n const surfaceY = isBottom ? rect.bottom - currentHeight : rect.top - currentHeight;\n\n if (flake.y >= surfaceY && flake.y < surfaceY + 10 && currentHeight < maxHeight) {\n const shouldAccumulate = isBottom ? Math.random() < 0.15 : true;\n\n if (shouldAccumulate) {\n const baseSpread = Math.ceil(flake.radius);\n const spread = baseSpread + Math.floor(Math.random() * 2);\n const accumRate = isBottom ? config.ACCUMULATION.BOTTOM_RATE : config.ACCUMULATION.TOP_RATE;\n const centerOffset = Math.floor(Math.random() * 3) - 1;\n\n for (let dx = -spread; dx <= spread; dx++) {\n if (Math.random() < 0.15) continue;\n const idx = localX + dx + centerOffset;\n if (idx >= 0 && idx < acc.heights.length) {\n const dist = Math.abs(dx);\n const pixelMax = acc.maxHeights[idx] || 5;\n\n const normDist = dist / spread;\n const falloff = (Math.cos(normDist * Math.PI) + 1) / 2;\n const baseAdd = 0.3 * falloff;\n\n const randomFactor = 0.8 + Math.random() * 0.4;\n const addHeight = baseAdd * randomFactor * accumRate;\n\n if (acc.heights[idx] < pixelMax && addHeight > 0) {\n acc.heights[idx] = Math.min(pixelMax, acc.heights[idx] + addHeight);\n }\n }\n }\n\n if (isBottom) {\n landed = true;\n break;\n }\n }\n\n if (!isBottom) {\n landed = true;\n break;\n }\n }\n }\n }\n\n if (landed || flake.y > canvasHeight + 10 || flake.x < -20 || flake.x > canvasWidth + 20) {\n snowflakes.splice(i, 1);\n }\n }\n};\n\nexport const meltAndSmoothAccumulation = (\n elementRects: ElementSurface[],\n config: PhysicsConfig,\n dt: number\n) => {\n for (const { acc } of elementRects) {\n const meltRate = config.MELT_SPEED * dt;\n const len = acc.heights.length;\n\n // Smooth\n if (len > 2) {\n for (let i = 1; i < len - 1; i++) {\n if (acc.heights[i] > 0.05) {\n const avg = (acc.heights[i - 1] + acc.heights[i + 1]) / 2;\n acc.heights[i] = acc.heights[i] * 0.99 + avg * 0.01;\n }\n }\n }\n\n // Melt\n for (let i = 0; i < acc.heights.length; i++) {\n if (acc.heights[i] > 0) acc.heights[i] = Math.max(0, acc.heights[i] - meltRate);\n }\n for (let i = 0; i < acc.leftSide.length; i++) {\n if (acc.leftSide[i] > 0) acc.leftSide[i] = Math.max(0, acc.leftSide[i] - meltRate);\n if (acc.rightSide[i] > 0) acc.rightSide[i] = Math.max(0, acc.rightSide[i] - meltRate);\n }\n }\n};\n"],"mappings":"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","names":[]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/utils/snowfall/types.ts"],"sourcesContent":["export interface Snowflake {\n x: number;\n y: number;\n radius: number;\n speed: number;\n wind: number;\n opacity: number;\n wobble: number;\n wobbleSpeed: number;\n}\n\nexport type SnowfallSurface = 'top' | 'bottom';\n\nexport interface SnowAccumulation {\n heights: number[];\n maxHeights: number[]; // Max height per pixel column\n leftSide: number[];\n rightSide: number[];\n maxSideHeight: number;\n borderRadius: number;\n type: SnowfallSurface;\n isFixed: boolean;\n}\n\nexport interface ElementSurface {\n el: Element;\n rect: DOMRect;\n acc: SnowAccumulation;\n}\n"],"mappings":";;;;;;;;;;;;;;AAAA;AAAA;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../../src/utils/snowfall/types.ts"],"sourcesContent":["export interface Snowflake {\n x: number;\n y: number;\n radius: number;\n speed: number;\n wind: number;\n opacity: number;\n wobble: number;\n wobbleSpeed: number;\n sizeRatio: number;\n isBackground: boolean;\n}\n\nexport type SnowfallSurface = 'top' | 'bottom';\n\nexport interface SnowAccumulation {\n heights: number[];\n maxHeights: number[]; // Max height per pixel column\n leftSide: number[];\n rightSide: number[];\n maxSideHeight: number;\n borderRadius: number;\n type: SnowfallSurface;\n isFixed: boolean;\n}\n\nexport interface ElementSurface {\n el: Element;\n rect: DOMRect;\n acc: SnowAccumulation;\n}\n"],"mappings":";;;;;;;;;;;;;;AAAA;AAAA;","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hdcodedev/snowfall",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.1",
|
|
4
4
|
"description": "Realistic snowfall effect for React with physics-based accumulation on surfaces. Features melting, wind, and smart surface detection.",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"module": "./dist/index.mjs",
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
"url": "https://github.com/hdcodedev/snowfall/issues"
|
|
42
42
|
},
|
|
43
43
|
"homepage": "https://github.com/hdcodedev/snowfall#readme",
|
|
44
|
-
"license": "
|
|
44
|
+
"license": "Apache-2.0",
|
|
45
45
|
"peerDependencies": {
|
|
46
46
|
"react": "^18.0.0 || ^19.0.0",
|
|
47
47
|
"react-dom": "^18.0.0 || ^19.0.0"
|