+ <>
+
- {points.map(({ rich, cost }, i) => (
-
- ))}
+
![nat.org screenshot](/blog/curse/nat.org.png)
+
+
+
+
+ >
+ )
+}
+
+function Axis({ cost, rich }: { cost?: string; rich?: string }) {
+ return (
+ <>
+
+
+
+ Richness
+
+
+ >
+ )
+}
+
+function Point({
+ rich,
+ cost,
+ pop,
+ index,
+ className,
+ extraClassName,
+}: {
+ rich: number
+ cost: number
+ name: string
+ pop: any[]
+ className?: string
+ extraClassName: string
+ index: number
+}) {
+ const extra = pop.map(({ rich, cost }, i) => (
+
+
+
+ ))
+ return (
+ <>
+ {extra}
+
+ >
+ )
+}
+
+function RandomOpacity({ children }: { children: React.ReactNode }) {
+ const opacity = React.useMemo(() => Math.random() * 0.5 + 0.2, [])
+ return
{children}
+}
+
+export function Layout(props: unknown) {
+ const { steps } = parseProps(props, Block.extend({ steps: z.array(Block) }))
+ return (
+ <>
+
TODO: small screen
+
+
+
+ {steps.map((step, i) => (
+
+
+ {step.children}
+
+
+ ))}
+
+
+ >
+ )
+}
+
+export function RainbowList({ children }: { children: React.ReactNode }) {
+ return (
+
+ {children}
)
}
diff --git a/apps/web/public/blog/curse/nat.org.png b/apps/web/public/blog/curse/nat.org.png
new file mode 100644
index 00000000..bdc59e1b
Binary files /dev/null and b/apps/web/public/blog/curse/nat.org.png differ
diff --git a/apps/web/public/blog/curse/tailwindcss.com.png b/apps/web/public/blog/curse/tailwindcss.com.png
new file mode 100644
index 00000000..221acaa1
Binary files /dev/null and b/apps/web/public/blog/curse/tailwindcss.com.png differ
diff --git a/apps/web/tailwind.config.ts b/apps/web/tailwind.config.ts
index eb7ebf0e..8c840492 100644
--- a/apps/web/tailwind.config.ts
+++ b/apps/web/tailwind.config.ts
@@ -42,10 +42,15 @@ const config = {
from: { height: "var(--radix-accordion-content-height)" },
to: { height: "0" },
},
+ appear: {
+ from: { opacity: "0" },
+ to: { opacity: "1" },
+ },
},
animation: {
"accordion-down": "accordion-down 0.2s ease-out",
"accordion-up": "accordion-up 0.2s ease-out",
+ appear: "appear 0.2s ease-out",
},
},
},
diff --git a/packages/codehike/src/utils/scroller.tsx b/packages/codehike/src/utils/scroller.tsx
index 33bbc945..5bc384db 100644
--- a/packages/codehike/src/utils/scroller.tsx
+++ b/packages/codehike/src/utils/scroller.tsx
@@ -6,6 +6,8 @@ const ObserverContext = React.createContext
(
const useIsomorphicLayoutEffect =
typeof window !== "undefined" ? useLayoutEffect : useEffect
+export type MarginConfig = string | { top: number; height: number }
+
export function Scroller({
onIndexChange,
triggerPosition = "50%",
@@ -14,26 +16,48 @@ export function Scroller({
}: {
onIndexChange: (index: number) => void
triggerPosition?: TriggerPosition
- rootMargin?: string
+ rootMargin?: MarginConfig
children: React.ReactNode
}) {
const [observer, setObserver] = React.useState()
const vh = useWindowHeight()
+ const ratios = React.useRef>({})
useIsomorphicLayoutEffect(() => {
const windowHeight = vh || 0
const handleIntersect: IntersectionObserverCallback = (entries) => {
+ let enteringIndex = -1
entries.forEach((entry) => {
+ const index = +entry.target.getAttribute("data-index")!
+ ratios.current[index] = entry.intersectionRatio
if (entry.intersectionRatio > 0) {
- // get entry.target index
- const index = entry.target.getAttribute("data-index")
- onIndexChange(+index!)
+ enteringIndex = index
}
})
+
+ if (enteringIndex >= 0) {
+ // on enter
+ onIndexChange(enteringIndex)
+ } else {
+ // on exit (go tos the higher intersection)
+ const sorted = Object.entries(ratios.current).sort(
+ ([, a], [, b]) => b - a,
+ )
+ const [index] = sorted[0]
+ if (ratios.current[+index] > 0) {
+ onIndexChange(+index)
+ }
+ }
}
- const observer = newIntersectionObserver(
- handleIntersect,
- rootMargin || defaultRootMargin(windowHeight, triggerPosition),
- )
+ const rm = !rootMargin
+ ? defaultRootMargin(windowHeight, triggerPosition)
+ : typeof rootMargin === "string"
+ ? rootMargin
+ : `${-rootMargin.top}px 0px ${-(
+ windowHeight -
+ rootMargin.top -
+ rootMargin.height
+ )}px`
+ const observer = newIntersectionObserver(handleIntersect, rm)
setObserver(observer)
return () => observer.disconnect()
@@ -50,11 +74,16 @@ function newIntersectionObserver(
handleIntersect: IntersectionObserverCallback,
rootMargin: string,
) {
- return new IntersectionObserver(handleIntersect, {
- rootMargin,
- threshold: 0.000001,
- root: null,
- })
+ try {
+ return new IntersectionObserver(handleIntersect, {
+ rootMargin,
+ threshold: 0.000001,
+ root: null,
+ })
+ } catch (e) {
+ // console.error({ rootMargin })
+ throw e
+ }
}
export function ObservedDiv({
diff --git a/packages/codehike/src/utils/selection.tsx b/packages/codehike/src/utils/selection.tsx
index ed0da519..35653019 100644
--- a/packages/codehike/src/utils/selection.tsx
+++ b/packages/codehike/src/utils/selection.tsx
@@ -1,6 +1,6 @@
"use client"
import React from "react"
-import { ObservedDiv, Scroller } from "./scroller.js"
+import { MarginConfig, ObservedDiv, Scroller } from "./scroller.js"
const StepsContext = React.createContext<{
selectedIndex: number
@@ -15,7 +15,7 @@ export function SelectionProvider({
rootMargin,
...rest
}: React.HTMLAttributes & {
- rootMargin?: string
+ rootMargin?: MarginConfig
}) {
const [selectedIndex, selectIndex] = React.useState(0)
return (