mirror of
https://github.com/hyprwm/hyprland-website.git
synced 2024-12-22 18:29:48 +01:00
add plugins section
This commit is contained in:
parent
e61b27c376
commit
34ebb30e62
9 changed files with 287 additions and 28 deletions
|
@ -1,6 +1,7 @@
|
|||
/* eslint-disable no-useless-escape */
|
||||
import { inview } from 'svelte-inview'
|
||||
import { pick } from 'remeda'
|
||||
import { Observable, debounceTime, share, startWith, throttleTime } from 'rxjs'
|
||||
|
||||
/**
|
||||
* Fade: The initial opacity from 0 to 1.
|
||||
|
@ -100,3 +101,16 @@ export function getBlurredPath(path) {
|
|||
export function getRandom(array) {
|
||||
return array.at(Math.floor(Math.random() * array.length))
|
||||
}
|
||||
|
||||
const fps = 1000 / 60 // 60 frames per second
|
||||
export const mousePosition$ = new Observable((subscriber) => {
|
||||
// eslint-disable-next-line no-undef
|
||||
if (globalThis.document === undefined) {
|
||||
return
|
||||
}
|
||||
document.addEventListener('mousemove', nextPostion)
|
||||
function nextPostion({ clientX, clientY }) {
|
||||
subscriber.next({ clientX, clientY })
|
||||
}
|
||||
return () => document?.removeEventListener('mousemove', nextPostion)
|
||||
}).pipe(throttleTime(fps), share(), startWith({ clientX: 0, clientY: 0 }))
|
||||
|
|
|
@ -1,40 +1,78 @@
|
|||
<script>
|
||||
import { BehaviorSubject, Subject, map, startWith, throttle, throttleTime } from 'rxjs'
|
||||
import { onDestroy } from 'svelte'
|
||||
import {
|
||||
BehaviorSubject,
|
||||
combineLatest,
|
||||
debounceTime,
|
||||
delay,
|
||||
distinctUntilChanged,
|
||||
filter,
|
||||
map,
|
||||
of,
|
||||
startWith,
|
||||
switchMap,
|
||||
timer
|
||||
} from 'rxjs'
|
||||
import { onDestroy, onMount } from 'svelte'
|
||||
import { spring } from 'svelte/motion'
|
||||
import { mousePosition$ } from './Helper.mjs'
|
||||
|
||||
/** The start position of the gradient. */
|
||||
export let startPosition = [-1000, -1000]
|
||||
|
||||
/** @type {HTMLDivElement}*/
|
||||
let wrapperElement = undefined
|
||||
let isMouseOver = false
|
||||
/** @type {import('rxjs').BehaviorSubject<boolean>}*/
|
||||
const isMouseOver$ = new BehaviorSubject(false).pipe(
|
||||
switchMap((isTrue) => {
|
||||
// Prevent elements over the background to disable the movement of the gradient, as the mouse will not be over the background anymore
|
||||
return isTrue ? of(isTrue) : timer(5500).pipe(map(() => false))
|
||||
}),
|
||||
distinctUntilChanged()
|
||||
)
|
||||
|
||||
const gradientSize = 240
|
||||
/** @type {import('rxjs').BehaviorSubject<number>}*/
|
||||
const gradientSize$ = new BehaviorSubject().pipe(
|
||||
// Debounce resize events with some high number for performance
|
||||
debounceTime(1),
|
||||
map(() => wrapperElement.getBoundingClientRect().width * 3),
|
||||
startWith(800)
|
||||
)
|
||||
|
||||
const fps = 1000 / 60 // 60 frames per second
|
||||
/** @type {import('rxjs').Subject<[number,number]>}*/
|
||||
const mouse$ = new Subject()
|
||||
const gradientPosition$ = mouse$.pipe(
|
||||
throttleTime(fps),
|
||||
map(([clientX, clientY]) => {
|
||||
const { x, y } = wrapperElement.getBoundingClientRect()
|
||||
const gradientPosition$ = combineLatest([mousePosition$, gradientSize$, isMouseOver$]).pipe(
|
||||
filter(([_, __, isMouseOver]) => isMouseOver),
|
||||
map(([{ clientX, clientY }, gradientSize]) => {
|
||||
const { x, y } = wrapperElement?.getBoundingClientRect() ?? { x: 0, y: 0 }
|
||||
return [clientX - x - gradientSize * 0.5, clientY - y - gradientSize * 0.5]
|
||||
}),
|
||||
startWith([0, 0])
|
||||
startWith(startPosition)
|
||||
)
|
||||
const gradientWiggle = spring([0, 0])
|
||||
const gradientWiggle = spring(startPosition, { damping: 0.95, stiffness: 0.1 })
|
||||
const subscription = gradientPosition$.subscribe((data) => gradientWiggle.set(data))
|
||||
|
||||
onDestroy(() => {
|
||||
subscription.unsubscribe()
|
||||
})
|
||||
|
||||
let hasJustMounted = true
|
||||
onMount(() => {
|
||||
resizeGradient()
|
||||
|
||||
hasJustMounted = false
|
||||
})
|
||||
|
||||
function resizeGradient() {
|
||||
if (hasJustMounted || !isMouseOver$) return
|
||||
|
||||
gradientSize$.next()
|
||||
}
|
||||
</script>
|
||||
|
||||
<svelte:window on:resize={resizeGradient} />
|
||||
|
||||
<div
|
||||
class={$$props.class + ' wrapper'}
|
||||
on:mouseleave={() => (isMouseOver = false)}
|
||||
on:mousemove={({ clientX, clientY }) => {
|
||||
isMouseOver = true
|
||||
mouse$.next([clientX, clientY])
|
||||
}}
|
||||
on:mouseleave={() => isMouseOver$.next(false)}
|
||||
on:mousemove={() => isMouseOver$.next(true)}
|
||||
aria-hidden
|
||||
bind:this={wrapperElement}
|
||||
>
|
||||
|
@ -42,8 +80,7 @@
|
|||
class="gradient"
|
||||
style:--x={$gradientWiggle.at(0) + 'px'}
|
||||
style:--y={$gradientWiggle.at(1) + 'px'}
|
||||
style:--size={gradientSize + 'px'}
|
||||
class:hidden={!isMouseOver}
|
||||
style:--size={$gradientSize$ + 'px'}
|
||||
></div>
|
||||
|
||||
<svg width="100%" height="100%" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
|
@ -51,11 +88,11 @@
|
|||
id="background-pattern-id"
|
||||
x="0"
|
||||
y="0"
|
||||
width="32"
|
||||
height="32"
|
||||
width="30"
|
||||
height="30"
|
||||
patternUnits="userSpaceOnUse"
|
||||
>
|
||||
<rect x="0.5" y="0.5" width="23" height="23" rx="3.5" stroke="currentColor" />
|
||||
<rect x="0.5" y="0.5" width="30" height="30" rx="0" stroke="currentColor" />
|
||||
</pattern>
|
||||
|
||||
<rect
|
||||
|
@ -72,14 +109,12 @@
|
|||
|
||||
<style lang="postcss">
|
||||
.wrapper {
|
||||
/* mask-image: radial-gradient(closest-side, black 50%, transparent); */
|
||||
/* contain: strict; */
|
||||
mask-image: linear-gradient(black 75%, transparent);
|
||||
contain: strict;
|
||||
user-select: none;
|
||||
}
|
||||
svg {
|
||||
background: theme(colors.black);
|
||||
/* background: theme(colors.black); */
|
||||
/* background-blend-mode: difference; */
|
||||
}
|
||||
|
||||
.gradient {
|
||||
|
@ -89,7 +124,12 @@
|
|||
mix-blend-mode: color-dodge;
|
||||
height: var(--size);
|
||||
width: var(--size);
|
||||
background: radial-gradient(closest-side, theme(colors.cyan.700), transparent);
|
||||
background: radial-gradient(
|
||||
closest-side,
|
||||
theme(colors.cyan.300),
|
||||
theme(colors.blue.950 / 100%) 30%,
|
||||
transparent
|
||||
);
|
||||
opacity: 100%;
|
||||
translate: var(--x) var(--y);
|
||||
z-index: 20;
|
||||
|
|
76
src/lib/components/Video.svelte
Normal file
76
src/lib/components/Video.svelte
Normal file
|
@ -0,0 +1,76 @@
|
|||
<script>
|
||||
import clsx from 'clsx'
|
||||
import PlayIcon from '~icons/mingcute/play-circle-line'
|
||||
import { inview } from 'svelte-inview'
|
||||
|
||||
/** @type {string} */
|
||||
export let src
|
||||
/** @type {string} */
|
||||
export let poster
|
||||
export let loop = true
|
||||
export let muted = true
|
||||
export let autoplay = false
|
||||
export let hidden = false
|
||||
/** @type {string}*/
|
||||
export let videoClass = ''
|
||||
let videoElement
|
||||
let isPaused = !autoplay
|
||||
|
||||
function togglePlay() {
|
||||
videoElement.paused ? videoElement.play() : videoElement.pause()
|
||||
isPaused = videoElement.paused
|
||||
}
|
||||
|
||||
function makeFullscreen() {
|
||||
videoElement.requestFullscreen()
|
||||
}
|
||||
</script>
|
||||
|
||||
<div
|
||||
class="wrapper {$$props.class}"
|
||||
role="banner"
|
||||
use:inview={poster}
|
||||
on:inview_enter
|
||||
on:inview_leave
|
||||
{hidden}
|
||||
>
|
||||
<video
|
||||
bind:this={videoElement}
|
||||
{src}
|
||||
{muted}
|
||||
disablepictureinpicture="true"
|
||||
disableremoteplayback="true"
|
||||
class="rounded-xl {videoClass}"
|
||||
{loop}
|
||||
preload="auto"
|
||||
{poster}
|
||||
on:click={togglePlay}
|
||||
on:dblclick={makeFullscreen}
|
||||
{autoplay}
|
||||
/>
|
||||
<div
|
||||
class={clsx(
|
||||
'z-20 opacity-0 transition-opacity ',
|
||||
isPaused ? 'opacity-100' : 'pointer-events-none'
|
||||
)}
|
||||
>
|
||||
{#if isPaused}
|
||||
<div
|
||||
class="pointer-events-none absolute left-1/2 top-1/2 h-14 w-14 -translate-x-1/2 -translate-y-1/2 rounded-full opacity-80 hover:opacity-100"
|
||||
>
|
||||
<PlayIcon class="h-full w-full" />
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style lang="postcss">
|
||||
.wrapper {
|
||||
position: relative;
|
||||
background: theme(colors.black);
|
||||
}
|
||||
|
||||
video {
|
||||
display: block;
|
||||
}
|
||||
</style>
|
|
@ -5,6 +5,7 @@
|
|||
import Hero from './Hero.svelte'
|
||||
import InstallSlice from './InstallSlice.svelte'
|
||||
import PreviewRiceSlice from './PreviewRiceSlice.svelte'
|
||||
import PluginsSlice from './PluginsSlice.svelte'
|
||||
|
||||
export let data
|
||||
</script>
|
||||
|
@ -21,6 +22,8 @@
|
|||
|
||||
<FeaturesSlice />
|
||||
|
||||
<PluginsSlice />
|
||||
|
||||
<WallOfFameSlice />
|
||||
|
||||
<Community />
|
||||
|
|
126
src/routes/PluginsSlice.svelte
Normal file
126
src/routes/PluginsSlice.svelte
Normal file
|
@ -0,0 +1,126 @@
|
|||
<script>
|
||||
import PatternBackground from '$lib/PatternBackground.svelte'
|
||||
import IconPlugin from '~icons/mingcute/plugin-2-line'
|
||||
import IconIpc from '~icons/mingcute/hexagon-line'
|
||||
import IconLinkOut from '~icons/mingcute/external-link-line'
|
||||
import clsx from 'clsx'
|
||||
import Video from '$lib/components/Video.svelte'
|
||||
import { fade } from 'svelte/transition'
|
||||
|
||||
let activeIndex = 0
|
||||
|
||||
const items = [
|
||||
{
|
||||
icon: IconPlugin,
|
||||
title: 'Plugins.',
|
||||
description:
|
||||
'Customize everything with official and community extensions. Write your own easily with C++',
|
||||
poster: '/videos/hypr_plugins_thumb.webp',
|
||||
src: '/videos/outfoxxed.mp4'
|
||||
},
|
||||
{
|
||||
icon: IconIpc,
|
||||
title: 'Bindings and IPC.',
|
||||
description: 'Control your desktop with your favourite languages or simply via IPC.',
|
||||
poster: '/videos/aylur_thumb.png',
|
||||
src: '/videos/aylur.mp4'
|
||||
}
|
||||
]
|
||||
|
||||
function setActiveItem(index) {
|
||||
activeIndex = index
|
||||
}
|
||||
</script>
|
||||
|
||||
<section class="relative z-0 flex min-h-max w-full flex-col items-center py-20">
|
||||
<div class="mx-auto grid max-w-7xl grid-cols-1 gap-12 px-6 lg:grid-cols-2 lg:gap-24">
|
||||
<div class="z-10 flex flex-col gap-10 px-6">
|
||||
<div class="txt-shadow_ mt-8 flex flex-col gap-6">
|
||||
<h2 class=" text-6xl font-bold">Unlock full power</h2>
|
||||
<p class="text-lg font-bold text-slate-300">
|
||||
Get the latest features Linux offers. Have full controll over your workflow by customizing
|
||||
and extending it how you want.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="flex h-full flex-col gap-4">
|
||||
{#each items as { icon, title, description }, index}
|
||||
{@const isActive = index === activeIndex}
|
||||
<button
|
||||
class={clsx(
|
||||
'flex gap-3 rounded-xl px-4 py-4 outline-0 outline-cyan-400/50 transition-all sm:-ml-4',
|
||||
isActive && 'bg-blue-300/5 shadow-md outline outline-1 backdrop-blur-sm '
|
||||
)}
|
||||
on:mouseenter={() => setActiveItem(index)}
|
||||
>
|
||||
<svelte:component this={icon} class="h-8 w-8 shrink-0 text-primary" />
|
||||
<p
|
||||
class={clsx(
|
||||
'txt-shadow_ text-left text-lg font-medium transition-colors ',
|
||||
isActive ? 'text-slate-300' : 'text-slate-400'
|
||||
)}
|
||||
>
|
||||
<span class="font-bold text-white">{title}</span>
|
||||
{description}
|
||||
</p>
|
||||
</button>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
<div class="-mt-5 hidden gap-1 lg:mt-12 lg:flex lg:flex-col">
|
||||
<a
|
||||
class="txt-shadow_ flex w-max max-w-max shrink-0 items-center gap-3 rounded font-bold text-slate-400 hover:underline"
|
||||
href="https://github.com/hyprland-community/awesome-hyprland"
|
||||
target="_blank"
|
||||
>
|
||||
<div>
|
||||
Checkout <span class="text-cyan-500">Awesome Hyprland</span>
|
||||
for more
|
||||
</div>
|
||||
<IconLinkOut />
|
||||
</a>
|
||||
<p class="font-medium text-slate-400">
|
||||
A list of plugins, bindings and more by the community
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Prevent the video from making the container big on small phones. 300px seem to work well for the text -->
|
||||
<div class="z-10 h-[20rem] min-w-0 sm:h-[25rem] md:h-[30rem] lg:h-[37rem]">
|
||||
{#each items as { src, poster }, index}
|
||||
<Video
|
||||
{src}
|
||||
{poster}
|
||||
autoplay
|
||||
class="z-10 aspect-video h-[inherit] origin-left rounded-lg object-cover object-left shadow-xl shadow-cyan-700/50 outline outline-2 outline-cyan-500 duration-500"
|
||||
hidden={index !== activeIndex}
|
||||
videoClass="h-[inherit] aspect-video"
|
||||
/>
|
||||
{/each}
|
||||
<div
|
||||
class="pt-5 text-sm font-medium text-slate-300 md:text-base [&>a:hover]:text-cyan-300 [&>a:hover]:underline [&>a]:font-bold"
|
||||
>
|
||||
{#if activeIndex === 0}
|
||||
Setup with <a href="https://github.com/outfoxxed/hy3" target="_blank"> hy3</a>, by
|
||||
<a href="https://github.com/outfoxxed/" target="_blank">Outfoxxed</a>, creator of hy3: i3
|
||||
tiling for Hyprland. Other used plugins:
|
||||
<a href="https://github.com/hyprwm/hyprland-plugins" target="_blank">Hyprtrails</a>,
|
||||
<a href="https://github.com/hyprwm/hyprland-plugins" target="_blank">Hyprborders</a>
|
||||
{:else if activeIndex === 1}
|
||||
Setup by <a href="https://github.com/Aylur/dotfiles" target="_blank">Aylur</a>, using
|
||||
<a href="https://github.com/Aylur/ags" target="_blank">Ags</a> to control Hyprland via IPC.
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<PatternBackground class="absolute inset-0 h-[110%] w-full text-slate-800 opacity-40" />
|
||||
</section>
|
||||
|
||||
<style lang="postcss">
|
||||
.txt-shadow_ {
|
||||
text-shadow:
|
||||
0px 0px 12px theme(colors.black / 90%),
|
||||
0px 0px 24px theme(colors.black / 50%);
|
||||
}
|
||||
</style>
|
BIN
static/videos/aylur.mp4
Normal file
BIN
static/videos/aylur.mp4
Normal file
Binary file not shown.
BIN
static/videos/aylur_thumb.png
Normal file
BIN
static/videos/aylur_thumb.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 859 KiB |
BIN
static/videos/hypr_plugins_thumb.webp
Normal file
BIN
static/videos/hypr_plugins_thumb.webp
Normal file
Binary file not shown.
After Width: | Height: | Size: 63 KiB |
BIN
static/videos/outfoxxed.mp4
Normal file
BIN
static/videos/outfoxxed.mp4
Normal file
Binary file not shown.
Loading…
Reference in a new issue