diff --git a/packages/docusaurus-playground/package.json b/packages/docusaurus-playground/package.json index fdd6e60..2d0ddea 100644 --- a/packages/docusaurus-playground/package.json +++ b/packages/docusaurus-playground/package.json @@ -4,7 +4,7 @@ "private": true, "scripts": { "docusaurus": "docusaurus", - "start": "docusaurus start", + "start": "docusaurus start -p 3002", "build": "docusaurus build", "swizzle": "docusaurus swizzle", "deploy": "docusaurus deploy", diff --git a/packages/docusaurus-playground/src/css/custom.scss b/packages/docusaurus-playground/src/css/custom.scss index 5a4260f..f7edaf4 100644 --- a/packages/docusaurus-playground/src/css/custom.scss +++ b/packages/docusaurus-playground/src/css/custom.scss @@ -1,2 +1,7 @@ :root { } + +body, +html { + overflow-x: hidden; +} diff --git a/packages/docusaurus-playground/src/pages/index.mdx b/packages/docusaurus-playground/src/pages/index.mdx index 0e1e87e..d78fde3 100644 --- a/packages/docusaurus-playground/src/pages/index.mdx +++ b/packages/docusaurus-playground/src/pages/index.mdx @@ -8,22 +8,40 @@ import { FeatureList, Showcase, HeroModel, + HeroInfo, } from '../components/mdx' - - - - Build on Waku - - - Run Waku - - - + + + The web3 managing standard + {/* + + Waku is a decentralized communications network that enables private, censorship-resistant messaging for web3 applications. + + * */} + + WAKU IS A DECENTRALIZED COMMUNICATIONS NETWORK. + + + + Build on Waku + + + Run Waku + + + + , 'title'> & { size?: 'large' | 'medium' | 'small' - title?: React.ReactNode - description?: React.ReactNode } export const Hero: React.FC = ({ size = 'medium', - title, - description, className, children, ...props @@ -24,8 +19,6 @@ export const Hero: React.FC = ({ className={clsx(className, 'mdx-hero', `mdx-hero--${size}`)} {...props} > - {title && {title}} - {description && {description}} {children} diff --git a/packages/logos-docusaurus-theme/src/client/components/mdx/HeroActions/HeroActions.scss b/packages/logos-docusaurus-theme/src/client/components/mdx/HeroActions/HeroActions.scss index 6ffbca7..23efa06 100644 --- a/packages/logos-docusaurus-theme/src/client/components/mdx/HeroActions/HeroActions.scss +++ b/packages/logos-docusaurus-theme/src/client/components/mdx/HeroActions/HeroActions.scss @@ -4,4 +4,7 @@ flex-direction: row; gap: 0 1rem; align-items: center; + + position: relative; + z-index: 98; } diff --git a/packages/logos-docusaurus-theme/src/client/components/mdx/HeroDescription/HeroDescription.tsx b/packages/logos-docusaurus-theme/src/client/components/mdx/HeroDescription/HeroDescription.tsx index 3b8d72b..f49fdff 100644 --- a/packages/logos-docusaurus-theme/src/client/components/mdx/HeroDescription/HeroDescription.tsx +++ b/packages/logos-docusaurus-theme/src/client/components/mdx/HeroDescription/HeroDescription.tsx @@ -6,25 +6,27 @@ import './HeroDescription.scss' export type HeroDescriptionProps = TypographyProps & { size?: 'small' | 'medium' | 'large' + uppercase?: boolean } export const HeroDescription: React.FC = ({ size: sizeProp, className, + uppercase = false, children, ...props }) => { const ctx = useHero() - const size = sizeProp ?? (ctx ? ctx.size : 'medium') return ( diff --git a/packages/logos-docusaurus-theme/src/client/components/mdx/HeroInfo/HeroInfo.scss b/packages/logos-docusaurus-theme/src/client/components/mdx/HeroInfo/HeroInfo.scss new file mode 100644 index 0000000..19ffee5 --- /dev/null +++ b/packages/logos-docusaurus-theme/src/client/components/mdx/HeroInfo/HeroInfo.scss @@ -0,0 +1,17 @@ +.mdx-hero-info { + padding: var(--logos-hero-info-padding-bottom) 0 + var(--logos-hero-info-padding-bottom) 0; + height: var(--logos-hero-info-height); + + display: flex; + flex-direction: column; + justify-content: end; + + border-bottom: 1px solid rgb(var(--lsd-surface-secondary)); + margin-bottom: calc(100vh - var(--logos-hero-info-height)); + + transition: margin-bottom 0.05s ease-in-out; +} + +.mdx-hero-info--large { +} diff --git a/packages/logos-docusaurus-theme/src/client/components/mdx/HeroInfo/HeroInfo.tsx b/packages/logos-docusaurus-theme/src/client/components/mdx/HeroInfo/HeroInfo.tsx new file mode 100644 index 0000000..3d24048 --- /dev/null +++ b/packages/logos-docusaurus-theme/src/client/components/mdx/HeroInfo/HeroInfo.tsx @@ -0,0 +1,61 @@ +import clsx from 'clsx' +import React, { PropsWithChildren, useEffect, useRef, useState } from 'react' +import { useHero } from '../Hero/Hero.context' +import './HeroInfo.scss' +import { useScrollY } from '../../../lib/useScrollY' +import { calcScrollThreshold, mapFloat } from '../../../lib/ui.utils' + +export type HeroInfoProps = PropsWithChildren< + React.HTMLAttributes +> & { + size?: 'medium' | 'large' | 'small' +} + +const calcMaxOffsetY = () => { + return window.innerHeight * 0.33 +} +export const HeroInfo: React.FC = ({ + size: sizeProp, + className, + children, + ...props +}) => { + const ctx = useHero() + const size = sizeProp ? sizeProp : ctx ? ctx.size : 'medium' + const scrollY = useScrollY() + const ref = useRef() + const [initialMarginBottom, setInitialMarginBottom] = useState( + null, + ) + + const getMarginBottom = () => { + const offsetY = mapFloat( + scrollY, + 0, + calcScrollThreshold(), + 0, + calcMaxOffsetY(), + ) + return initialMarginBottom ? initialMarginBottom - offsetY : 0 + } + + useEffect(() => { + if (typeof window === 'undefined' || !(ref && ref.current)) return + setInitialMarginBottom( + parseFloat(window.getComputedStyle(ref.current).marginBottom), + ) + }, []) + + return ( +
+ {children} +
+ ) +} diff --git a/packages/logos-docusaurus-theme/src/client/components/mdx/HeroInfo/index.ts b/packages/logos-docusaurus-theme/src/client/components/mdx/HeroInfo/index.ts new file mode 100644 index 0000000..d33697a --- /dev/null +++ b/packages/logos-docusaurus-theme/src/client/components/mdx/HeroInfo/index.ts @@ -0,0 +1 @@ +export * from './HeroInfo' diff --git a/packages/logos-docusaurus-theme/src/client/components/mdx/HeroModel/HeroModel.scss b/packages/logos-docusaurus-theme/src/client/components/mdx/HeroModel/HeroModel.scss index 84707a8..84d3404 100644 --- a/packages/logos-docusaurus-theme/src/client/components/mdx/HeroModel/HeroModel.scss +++ b/packages/logos-docusaurus-theme/src/client/components/mdx/HeroModel/HeroModel.scss @@ -1,55 +1,51 @@ .mdx-hero-model { - border-top: 1px solid rgb(var(--lsd-surface-secondary)); - margin-top: 2rem; - height: 452px; + width: 100vw; + transform: translateX( + calc(-1 * calc(calc(100vw - 1440px + calc(var(--content-padding)) * 2) / 2)) + ); + position: absolute; + top: calc(var(--logos-hero-info-height) - 10px); - & > div { - position: absolute; + .mdx-hero-model--inner { + position: relative; width: 100%; height: 1400px; - z-index: -1; - top: 200px; + z-index: 0; left: 0; - } - - & > div:first-child { + top: 0; + //transition: translate 0.6s ease-in-out; canvas { pointer-events: none; } } - - & > div:last-child { - pointer-events: none; - background-image: linear-gradient( - 179.61deg, - rgba(0, 0, 0, 0) 0.34%, - #000000 70% - ); - opacity: 1; - } -} - -.mdx-hero-model--floating { } .mdx-hero-model--cropped { - overflow: hidden; - & > div { - position: relative; - top: -200px; - z-index: 0; +} +.mdx-hero-model--floating { + .mdx-hero-model--inner { canvas { pointer-events: unset !important; } } - - & > div:last-child { - display: none; - } } .mdx-hero-model--3d { + overflow: visible !important; + + .mdx-hero-model--inner { + position: relative; + } + + .mdx-hero-model--inner { + > div { + position: absolute; + top: 50%; + left: 50%; + transform: scale(1.2) translate(-50%, -50%); + } + } } .mdx-hero-model--ascii { @@ -59,11 +55,19 @@ border: none !important; font-family: monospace; } - - tr { - //text-align: center; - //font-size: 12px; - //letter-spacing: 1px; - } } } + +.mdx-hero-model--shade { + width: 100%; + height: 600px; + position: absolute; + background: linear-gradient( + 0deg, + rgb(0, 0, 0) 37%, + rgb(0, 0, 0) 0%, + rgba(0, 0, 0, 0) 100% + ); + bottom: 500px; + left: 0; +} diff --git a/packages/logos-docusaurus-theme/src/client/components/mdx/HeroModel/HeroModel.tsx b/packages/logos-docusaurus-theme/src/client/components/mdx/HeroModel/HeroModel.tsx index 35fd08e..7babd53 100644 --- a/packages/logos-docusaurus-theme/src/client/components/mdx/HeroModel/HeroModel.tsx +++ b/packages/logos-docusaurus-theme/src/client/components/mdx/HeroModel/HeroModel.tsx @@ -2,62 +2,150 @@ import { OrbitControls, useGLTF } from '@react-three/drei' import { Canvas, useFrame, useThree } from '@react-three/fiber' import clsx from 'clsx' import React, { + PropsWithChildren, Suspense, useEffect, useLayoutEffect, useMemo, useRef, + useState, } from 'react' import * as THREE from 'three' import { AsciiEffect } from 'three-stdlib' import './HeroModel.scss' +import { + calcScrollThreshold, + isTouchDevice, + mapFloat, + random, +} from '../../../lib/ui.utils' +import { useScrollY } from '../../../lib/useScrollY' + +let ROTATE_SPEED = 0.1 +const MIN_ROTATE_SPEED = 0.1 +const MAX_ROTATE_SPEED = 1 + +const MAX_Z = 20 * 1.4 // or another value that suits your needs +const MIN_Z = 0.2 * 1.4 +const MIN_Y = -0.25 +const MAX_Y = 0.25 +const MIN_X = -0.25 +const MAX_X = 0.25 + +const MIN_ZOOM = 0.22 +const RESIZE_SPEED_FACTOR = 0.9 +const INITIAL_ZOOM = 3 +const calcZoom = (scrollY = 0, initZ = INITIAL_ZOOM) => { + return Math.max( + mapFloat( + scrollY, + 0, + calcScrollThreshold() * RESIZE_SPEED_FACTOR, + initZ, + MIN_ZOOM, + ), + MIN_ZOOM, + ) +} + +const calcRotateSpeed = (scrollY = 0, speed) => { + const sFactor = mapFloat(scrollY, 0, window.innerHeight, 1, 10) + return Math.max(Math.min(speed * sFactor, MAX_ROTATE_SPEED), MIN_ROTATE_SPEED) +} function Model({ url, ...props }) { // useGLTF suspends the component, it literally stops processing - const { camera } = useThree() const { scene } = useGLTF(url) as any - - const size = useMemo(() => { - const box = new THREE.Box3().setFromObject(scene) - return box.getSize(new THREE.Vector3()) - }, [scene]) - - useLayoutEffect(() => { - if (!camera) return - const c = camera as THREE.PerspectiveCamera - const fov = c.fov * (Math.PI / 180) - const distance = Math.abs(Math.max(size.x, size.y) / Math.sin(fov / 2)) - c.position.setZ(distance) - }, [size, camera]) - + // useLayoutEffect(() => { + // if (!camera) return + // const c = camera as THREE.PerspectiveCamera + // const fov = c.fov * (Math.PI / 180) + // const distance = Math.abs(Math.max(size.x, size.y) / Math.sin(fov / 2)) + // c.position.setZ(distance) + // }, [size, camera]) return ( ) } -function Rotate(props) { - const ref = useRef() - useFrame((state) => (ref.current.rotation.y = state.clock.elapsedTime * 0.1)) +type RotateProps = PropsWithChildren<{ + initialZ?: number + rotateSpeed?: number + enableZoom?: boolean + enableRotateOnScroll?: boolean +}> - return +function Movements({ + initialZ, + rotateSpeed = ROTATE_SPEED, + enableZoom = true, + enableRotateOnScroll = true, + ...props +}: RotateProps) { + const ref = useRef() + const scrollY = useScrollY() + + useFrame((state, delta) => { + // const f = mapFloat(scrollY, 0, 1000, 1, 2); + // ref.current.rotation.x += delta * (ROTATE_SPEED); + // ref.current.rotation.y += delta * (ROTATE_SPEED); + const speed = enableRotateOnScroll + ? calcRotateSpeed(scrollY, rotateSpeed) + : rotateSpeed + ref.current.rotation.y += delta * speed + }) + + return ( + + ) } -function useAsciiEffect({ - renderIndex = 1, - bgColor = 'black', - fgColor = 'white', - // characters = ' .:-+*=%@#', - characters = ' l.o.g.o.s', - invert = false, - color = false, - resolution = 0.2, -}) { +type AsciiConfigs = { + characters?: string + invert?: boolean + color?: boolean + bgColor?: string + fgColor?: string + resolution?: number + renderIndex?: number +} + +const defaultAsciiConfigs = { + renderIndex: 1, + bgColor: 'rgb(var(--lsd-surface-primary))', + fgColor: 'rgb(var(--lsd-text-primary))', + characters: ' l.o.g.o.s', + invert: false, + color: false, + resolution: 0.2, +} + +type AsciiEffectProps = { + config?: AsciiConfigs + initialZ?: number +} + +function useAsciiEffect({ config = {}, initialZ }: AsciiEffectProps) { + const { + renderIndex = defaultAsciiConfigs.renderIndex, + characters = defaultAsciiConfigs.characters, + invert = defaultAsciiConfigs.invert, + color = defaultAsciiConfigs.color, + resolution = defaultAsciiConfigs.resolution, + bgColor = defaultAsciiConfigs.bgColor, + fgColor = defaultAsciiConfigs.fgColor, + } = config + // Reactive state - const { size, gl, scene, camera, viewport } = useThree() + const { gl, size, scene, camera, viewport } = useThree() const effect = useMemo(() => { const effect = new AsciiEffect(gl, characters, { @@ -72,10 +160,9 @@ function useAsciiEffect({ return effect }, [characters, invert, color, resolution]) - // Styling useLayoutEffect(() => { effect.domElement.style.color = fgColor - effect.domElement.style.backgroundColor = bgColor + // effect.domElement.style.backgroundColor = bgColor }, [fgColor, bgColor]) // Append on mount, remove on unmount @@ -90,9 +177,6 @@ function useAsciiEffect({ // Set size useEffect(() => { - camera.position.setX(2) - camera.position.setY(2) - effect.setSize(size.width, size.height) }, [effect, size]) @@ -102,15 +186,8 @@ function useAsciiEffect({ }, renderIndex) } -const AsciiRenderer = ({ - fgColor, - bgColor, -}: { - fgColor: string - bgColor: string -}) => { - useAsciiEffect({ fgColor, bgColor }) - +const AsciiRenderer = (props: AsciiEffectProps) => { + useAsciiEffect(props) return <> } @@ -118,6 +195,14 @@ export type HeroModelProps = React.HTMLAttributes & { modelUrl: string layout?: 'floating' | 'cropped' renderer?: '3d' | 'ascii' + asciiConfig?: AsciiConfigs + initialZ?: number + initialX?: number + initialY?: number + rotateSpeed?: number + enableZoom?: boolean + enableRotateOnScroll?: boolean + withParallelEffect?: boolean } export const HeroModel: React.FC = ({ @@ -126,8 +211,30 @@ export const HeroModel: React.FC = ({ modelUrl, className, children, + asciiConfig, + initialX, + initialY, + initialZ, + rotateSpeed = ROTATE_SPEED, + enableZoom, + enableRotateOnScroll, + withParallelEffect, ...props }) => { + const scrollY = useScrollY() + + const initialPosition = useMemo(() => { + if (typeof window === 'undefined') return new THREE.Vector3(0, 0, 0) + if (renderer === 'ascii') { + return new THREE.Vector3( + initialX || random(MIN_X, MAX_X), + initialY || random(MIN_Y, MAX_Y), + 0, + ) + } + // if not ascii then use the max z + return new THREE.Vector3(3.1, initialY || random(MIN_Y, MAX_Y), 0) + }, [initialX, initialY, initialZ, renderer]) return (
= ({ )} {...props} > -
+
- - + + }> - + - + - + {!isTouchDevice() && } {renderer === 'ascii' && ( - + )}
-
+
) } diff --git a/packages/logos-docusaurus-theme/src/client/components/mdx/index.ts b/packages/logos-docusaurus-theme/src/client/components/mdx/index.ts index 7153566..44f7474 100644 --- a/packages/logos-docusaurus-theme/src/client/components/mdx/index.ts +++ b/packages/logos-docusaurus-theme/src/client/components/mdx/index.ts @@ -7,3 +7,4 @@ export * from './HeroDescription' export * from './HeroModel' export * from './HeroTitle' export * from './Showcase' +export * from './HeroInfo' diff --git a/packages/logos-docusaurus-theme/src/client/css/custom.scss b/packages/logos-docusaurus-theme/src/client/css/custom.scss index e091ade..415e159 100644 --- a/packages/logos-docusaurus-theme/src/client/css/custom.scss +++ b/packages/logos-docusaurus-theme/src/client/css/custom.scss @@ -298,6 +298,11 @@ a code { --ifm-navbar-item-padding-vertical: 8px; --ifm-navbar-item-padding-horizontal: 12px; + + --logos-navbar-height: 64px; + --logos-hero-info-height: 420px; + --logos-hero-info-padding-top: 32px; + --logos-hero-info-padding-bottom: 32px; } .card, @@ -314,18 +319,23 @@ a code { .sub1 { font-size: var(--ifm-h1-font-size); } + .sub2 { font-size: var(--ifm-h2-font-size); } + .sub3 { font-size: var(--ifm-h3-font-size); } + .sub4 { font-size: var(--ifm-h4-font-size); } + .sub5 { font-size: var(--ifm-h5-font-size); } + .sub6 { font-size: var(--ifm-h6-font-size); } @@ -841,6 +851,10 @@ small { } } + .main-wrapper > div { + display: block; + } + .navbar__left-items { display: none; } diff --git a/packages/logos-docusaurus-theme/src/client/lib/ui.utils.ts b/packages/logos-docusaurus-theme/src/client/lib/ui.utils.ts new file mode 100644 index 0000000..5c9a235 --- /dev/null +++ b/packages/logos-docusaurus-theme/src/client/lib/ui.utils.ts @@ -0,0 +1,21 @@ +export const random = (min, max) => { + return Math.random() * (max - min) + min +} + +export const mapFloat = (value, inMin, inMax, outMin, outMax) => { + return ((value - inMin) * (outMax - outMin)) / (inMax - inMin) + outMin +} + +export const calcScrollThreshold = () => { + return window.innerHeight * 0.4 +} + +export const isTouchDevice = () => { + if (typeof window === 'undefined') return false + return ( + 'ontouchstart' in window || + navigator.maxTouchPoints > 0 || + // @ts-ignore + (navigator.msMaxTouchPoints && navigator.msMaxTouchPoints > 0) + ) +} diff --git a/packages/logos-docusaurus-theme/src/client/lib/useScrollY.ts b/packages/logos-docusaurus-theme/src/client/lib/useScrollY.ts new file mode 100644 index 0000000..719a0b5 --- /dev/null +++ b/packages/logos-docusaurus-theme/src/client/lib/useScrollY.ts @@ -0,0 +1,10 @@ +import { useWindowEventListener } from './useWindowEventListener' +import { useState } from 'react' + +export const useScrollY = () => { + const [scrollY, setScrollY] = useState(0) + useWindowEventListener('scroll', () => { + setScrollY(window.scrollY) + }) + return scrollY +}