This commit is contained in:
VDawg 2025-10-23 19:35:55 +02:00 committed by GitHub
commit 970fa6c160
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
67 changed files with 2610 additions and 1664 deletions

View file

@ -1,6 +1,10 @@
module.exports = {
root: true,
extends: ['eslint:recommended', 'plugin:svelte/recommended', 'prettier'],
extends: [
'eslint:recommended',
'plugin:svelte/recommended',
'prettier'
],
parserOptions: {
sourceType: 'module',
ecmaVersion: 'latest',

View file

@ -1,3 +1,6 @@
{
"recommendations": ["bradlc.vscode-tailwindcss", "svelte.svelte-vscode"]
"recommendations": [
"bradlc.vscode-tailwindcss",
"svelte.svelte-vscode"
]
}

View file

@ -3,6 +3,7 @@
"version": "1.1.0",
"description": "Website for Hyprland - Hyprland - A wayland compositor with the looks.",
"repository": "github:hyprwm/hyprland-website",
"type": "module",
"scripts": {
"dev": "vite dev",
"build": "vite build",
@ -13,7 +14,7 @@
"keywords": [
"hyprland"
],
"author": "",
"author": "VDawg",
"license": "BSD-3-Clause",
"private": "true",
"os": [
@ -27,26 +28,25 @@
"@iconify/json": "^2.2.220",
"@interactjs/types": "^1.10.27",
"@sveltejs/adapter-static": "^3.0.2",
"@sveltejs/kit": "^2.5.17",
"@sveltejs/vite-plugin-svelte": "^3.1.1",
"@sveltejs/kit": "^2.39.1",
"@sveltejs/vite-plugin-svelte": "^4.0.4",
"autoprefixer": "^10.4.19",
"eslint": "^9.5.0",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-svelte": "^2.40.0",
"eslint-plugin-svelte": "^2.46.1",
"mdsvex": "^0.11.2",
"postcss": "^8.4.38",
"prettier": "^3.3.2",
"prettier-plugin-svelte": "^3.2.4",
"prettier-plugin-svelte": "^3.4.0",
"simplex-noise": "^4.0.1",
"svelte": "^4.2.18",
"svelte-check": "^3.8.1",
"svelte": "^5.38.10",
"svelte-check": "^4.3.1",
"tailwindcss": "^3.4.4",
"tailwindcss-animate": "^1.0.7",
"typescript": "^5.4.5",
"typescript": "^5.9.2",
"unplugin-icons": "^0.19.0",
"vite": "^5.3.1"
"vite": "^5.4.20"
},
"type": "module",
"dependencies": {
"@fontsource-variable/inter": "^5.2.5",
"@fontsource/ibm-plex-mono": "^5.0.13",

906
pnpm-lock.yaml generated

File diff suppressed because it is too large Load diff

View file

@ -3,7 +3,7 @@ module.exports = {
singleQuote: true,
semi: false,
trailingComma: 'none',
printWidth: 100,
printWidth: 70,
plugins: ['prettier-plugin-svelte', 'prettier-plugin-tailwindcss'],
tailwindFunctions: ['clsx']
}

View file

@ -6,7 +6,10 @@ import { spawnSync } from 'node:child_process'
// This script should be run from the root of the application
const root = new URL('..', import.meta.url)
const imageDirectories = ['static/ricing_competitions', 'static/plugins-data/logos']
const imageDirectories = [
'static/ricing_competitions',
'static/plugins-data/logos'
]
const generatedPrefix = 'generated_'
// This value seems to work well
const maxBrightness = 65535
@ -23,12 +26,16 @@ const filePaths = await globby(imageDirectories, {
}).then((filePaths) =>
filePaths
.filter((filePath) => {
const isGenerated = getFileNameWithoutExtension(filePath).startsWith(generatedPrefix)
const isGenerated =
getFileNameWithoutExtension(filePath).startsWith(
generatedPrefix
)
if (isGenerated) return false
const fileName = getFileNameWithoutExtension(filePath)
const fileDirectory = filePath.split('/').slice(0, -2).join('/') + '/'
const fileDirectory =
filePath.split('/').slice(0, -2).join('/') + '/'
const generatedFilePath = `${fileDirectory}${generatedPrefix}${fileName}.webp`
const isAlreadyBlurred = filePaths.includes(generatedFilePath)
@ -41,19 +48,29 @@ const filePaths = await globby(imageDirectories, {
for (const filePathUrl of filePaths) {
const extension = filePathUrl.pathname.split('.').at(-1)
const generatedFileName =
generatedPrefix + getFileNameWithoutExtension(filePathUrl.href) + '.webp'
const outPath = decodeURIComponent(new URL('.', filePathUrl).pathname + generatedFileName)
generatedPrefix +
getFileNameWithoutExtension(filePathUrl.href) +
'.webp'
const outPath = decodeURIComponent(
new URL('.', filePathUrl).pathname + generatedFileName
)
const filePath = decodeURIComponent(filePathUrl.pathname)
const currentBrightness = Number(
exec(`convert "${filePath}" -colorspace Gray -format "%[mean]" info: `)
exec(
`convert "${filePath}" -colorspace Gray -format "%[mean]" info: `
)
)
// Boost the brightness if the image is very dark
const brightnessIncrease = Math.max(1 - (currentBrightness / brightnessThreshold) * 50, 0)
const brightnessIncrease = Math.max(
1 - (currentBrightness / brightnessThreshold) * 50,
0
)
// Nessecary to do it like that for Zx
const svgCommands = extension === 'svg' ? '-background none -resize 2500x2500' : ''
const svgCommands =
extension === 'svg' ? '-background none -resize 2500x2500' : ''
exec(
`magick convert ${svgCommands} -brightness-contrast ${brightnessIncrease}x40 -modulate 100,1000,100 "${filePath}" "${outPath}"`
@ -81,7 +98,8 @@ function exec(command) {
const stdError = stderr.toString().trim()
if (stdError && !stdError.includes('WARNING')) throw new Error(stdError)
if (stdError && !stdError.includes('WARNING'))
throw new Error(stdError)
return stdout
} catch (error) {

View file

@ -1,15 +1,30 @@
<!doctype html>
<html lang="en">
<head>
<head>
<meta charset="utf-8" />
<link rel="icon" href="%sveltekit.assets%/favicon.ico" />
<meta name="viewport" content="width=device-width" />
<meta property="og:image" content="/imgs/og-img.png" />
<link rel="alternate" type="application/rss+xml" title="Hyprland News" href="/rss.xml" />
<link
rel="alternate"
type="application/rss+xml"
title="Hyprland News"
href="/rss.xml"
/>
<style></style>
%sveltekit.head%
</head>
%sveltekit.head% </head
>
<body data-sveltekit-preload-data="hover">
<div style="display: contents">%sveltekit.body%</div>
</body>
</html>

View file

@ -18,6 +18,7 @@ If you want to support the development for 5€ + tax a month, and also get a fe
please check out [the pricing page](https://account.hypr.land/pricing)
You will get:
- Member-only forum access, with dev Q&A, support from me, and more
- Premium desktop experience, which is a set of preconfigured dotfiles with a one-click install and update
- And of course, support the continued development.
@ -28,4 +29,4 @@ where you can find answers, ask questions, and interact with the community.
Also, thank you for all the support, guys. You are awesome!
Cheers,
vax.
vax.

View file

@ -1,5 +1,5 @@
---
title: Hyprland 0.46.0 is upon us!
title: Hyprland 0.46.0 is upon us!
date: 1734392927
author:
name: Vaxry
@ -22,26 +22,28 @@ Some stuff has changed:
Window/layer rule regexes now require a full match (not any match) to trigger.
For example, in the case of `jeremy`:
- `jeremy`: OK
- `^(jeremy)$`: OK
- `jer`: Used to match, now won't. You'll need to do `.*jer.*` to make it act like before. (Consider _not_ doing that though, make a better regex.)
- `jeremy`: OK
- `^(jeremy)$`: OK
- `jer`: Used to match, now won't. You'll need to do `.*jer.*` to make it act like before. (Consider _not_ doing that though, make a better regex.)
## New stuff
Tons of new stuff, you can think of all of these as Christmas presents.
- Color handling has been moved to OkLab from sRGB. This means gradients and color transitions will now look more natural.
- XWayland Drag and Drop is back! You can now drag stuff from your Wayland clients to X11 clients.
- New update screen! Whenever you update, you'll get a small popup reassuring everything went well and reminding you to check the release notes.
- Window and layer rule handling has been improved and optimized, and regex handling is now done via RE2 from Google, which is faster and generally better.
- `cursor:warp_on_change_workspace` now accepts `force` to bypass `cursor:no_warps`.
- hyprctl: `clients` got `inhibitingIdle`, `monitors` got `directScanout`, `plugins list` got `-j` support.
- `cursor:warp_back_after_non_mouse_input` added, allowing you to keep touch / tablet input from messing with your mouse input.
- `lockdead_screen_delay` has been added if your lockscreen can't appear fast enough to avoid the "lockscreen dead" from flashing for a moment.
- You can now blur IME popups with `decoration:blur:input_methods`
- Version requests now also show linked versions of hypr* deps.
- New windowrules for mouse and touchpad scroll factors.
- Added some new festive splashes for xmas and new years :)
- And more!
- Color handling has been moved to OkLab from sRGB. This means gradients and color transitions will now look more natural.
- XWayland Drag and Drop is back! You can now drag stuff from your Wayland clients to X11 clients.
- New update screen! Whenever you update, you'll get a small popup reassuring everything went well and reminding you to check the release notes.
- Window and layer rule handling has been improved and optimized, and regex handling is now done via RE2 from Google, which is faster and generally better.
- `cursor:warp_on_change_workspace` now accepts `force` to bypass `cursor:no_warps`.
- hyprctl: `clients` got `inhibitingIdle`, `monitors` got `directScanout`, `plugins list` got `-j` support.
- `cursor:warp_back_after_non_mouse_input` added, allowing you to keep touch / tablet input from messing with your mouse input.
- `lockdead_screen_delay` has been added if your lockscreen can't appear fast enough to avoid the "lockscreen dead" from flashing for a moment.
- You can now blur IME popups with `decoration:blur:input_methods`
- Version requests now also show linked versions of hypr\* deps.
- New windowrules for mouse and touchpad scroll factors.
- Added some new festive splashes for xmas and new years :)
- And more!
## Fixes

View file

@ -41,6 +41,7 @@ a bunch of things, and we hope to get more and more tests there as time goes on.
### Other
Other new stuff include amongst others:
- New `monitorv2` syntax for monitor configs that are less cluttered
- tons of small optimizations (thanks Tom!)
- new `ext_workspace_v1` support
@ -52,10 +53,10 @@ Other new stuff include amongst others:
- new permission management for keyboards
- and more...
### Fixes
Tons of fixes, including:
- fixes for some `hyprpm` and hyprland crashes.
- fixed some minor blur artifacts on popups
- snap now respects outer gaps

View file

@ -12,7 +12,7 @@ Hey hey people, vaxry here. It's time for a new Hyprland update, after 2 months
## Breaking changes
- The gesture system has been reworked and is now way more flexible. Thus, the old `gestures:workspace_swipe`,
`gestures:workspace_swipe_fingers` and `gestures:workspace_swipe_min_fingers` are gone.
`gestures:workspace_swipe_fingers` and `gestures:workspace_swipe_min_fingers` are gone.
- `animations:first_launch_animation` is gone, use the new `monitorAdded` animation leaf.
@ -40,7 +40,6 @@ Check the new [gestures wiki page](https://wiki.hypr.land/Configuring/Gestures/)
- Screensharing now forces 8-bit by default (fixes chromium / firefox screensharing on wide color gamut displays)
- New `novrr` windowrule.
## Fixes
Tons of fixes as usual:
@ -60,4 +59,4 @@ Tons of fixes as usual:
If you are one of these people that enjoy reading, check the release on [Github](https://github.com/hyprwm/Hyprland/releases/tag/v0.51.0).
Cheers,
vax
vax

View file

@ -1,418 +1,280 @@
[
{
"image": "https://cdn.discordapp.com/avatars/372809091208445953/a_33fd25e26c0ba17c05566bc3179c6476.gif",
"class": "outline-red-500",
"coordinates": [
187,
296
],
"size": 172
},
{
"image": "https://cdn.discordapp.com/avatars/444952344308744203/9ee39cd422568dad9e70319df27b2560.webp",
"class": "outline-yellow-500",
"coordinates": [
735,
441
],
"size": 164
},
{
"image": "https://cdn.discordapp.com/avatars/419880181101232129/d5ec2616075dc0fddb3de528c9113628.webp",
"class": "outline-orange-500",
"coordinates": [
391,
615
],
"size": 149
},
{
"image": "https://cdn.discordapp.com/avatars/378704069726044170/415dcb2ef8d1ef635e35e1d04d523cba.webp",
"class": "outline-amber-500",
"coordinates": [
568,
594
],
"size": 120
},
{
"image": "https://cdn.discordapp.com/avatars/862314649307054080/bce0b10ae900daa2da139f9e089c8e56.webp",
"class": "outline-cyan-400",
"coordinates": [
648,
709
],
"size": 128
},
{
"image": "https://cdn.discordapp.com/avatars/163678036401586177/0930f3f6f0839b4f9c59846e185aa8ad.webp",
"class": "outline-yellow-500",
"coordinates": [
24,
341
],
"size": 49
},
{
"image": "https://cdn.discordapp.com/avatars/223160360461402122/c6552a1c768613bffb83bf88e4ce2632.webp",
"class": "outline-red-500",
"coordinates": [
47,
86
],
"size": 48
},
{
"image": "https://cdn.discordapp.com/avatars/979473046404476998/3d12629a52071fabff4493c1362d59fa.webp",
"class": "outline-amber-500",
"coordinates": [
1038,
446
],
"size": 52
},
{
"image": "https://cdn.discordapp.com/avatars/486802226577276929/2a6355319a116ba3e2aff9acf4163e95.webp",
"class": "outline-cyan-500",
"coordinates": [
273,
760
],
"size": 52,
"quote": "\"meds\""
},
{
"image": "https://cdn.discordapp.com/avatars/837425748435796060/248cd938377647404e3d8d1c53b639cf.webp",
"class": "outline-orange-500",
"coordinates": [
648,
364
],
"size": 105
},
{
"image": "https://cdn.discordapp.com/avatars/480024733535174668/5453c57e69ff16f495d8dcbd597070e9.webp",
"class": "outline-purple-500",
"coordinates": [
187,
764
],
"size": 62
},
{
"image": "https://cdn.discordapp.com/avatars/544368824842190861/1fbf29ea7ca9c2109b97af2ede3806fa.webp",
"class": "outline-lime-500",
"coordinates": [
736,
277
],
"size": 105
},
{
"image": "https://cdn.discordapp.com/avatars/344854021166727180/ace83ad13015f29bc33a3f858e15b4d7.webp",
"class": "outline-yellow-500",
"coordinates": [
898,
364
],
"size": 68
},
{
"image": "https://cdn.discordapp.com/avatars/579120037034721281/c7b4341806d515f5d92bb53e322a4728.webp",
"class": "outline-rose-500",
"coordinates": [
887,
159
],
"size": 39
},
{
"image": "https://cdn.discordapp.com/avatars/246125351464337418/10c8bb5456d1ca1cebf2edff62b7001f.webp",
"class": "outline-amber-500",
"coordinates": [
1023,
552
],
"size": 48
},
{
"image": "https://cdn.discordapp.com/avatars/143031299152674816/4a143f00b0e014e9c3da37cd8599b106.webp",
"class": "outline-pink-500",
"coordinates": [
147,
553
],
"size": 87
},
{
"image": "https://cdn.discordapp.com/avatars/194157962498015232/ab11cc35bd9d057769254d1d3e29e468.webp",
"class": "outline-amber-500 ",
"coordinates": [
65,
643
],
"size": 74
},
{
"image": "https://cdn.discordapp.com/avatars/482139697796349953/0a588671aaff04b64788b86bfa8e15f9.webp",
"class": "outline-stone-500 ",
"coordinates": [
263,
653
],
"size": 65
},
{
"image": "https://cdn.discordapp.com/avatars/231040215085481984/7f378337240110b76e6e9baa31f83670.webp",
"class": "outline-amber-500",
"coordinates": [
354,
798
],
"size": 80
},
{
"image": "https://cdn.discordapp.com/avatars/784153590595321876/27df3e463a7111b75805baecaff4cdc9.webp",
"class": "outline-white",
"coordinates": [
583,
824
],
"size": 69,
"quote": "Perfect being"
},
{
"image": "https://cdn.discordapp.com/avatars/408513270916579338/bac966d3bae8ad01546ee32584917be9.webp",
"class": "outline-rose-300",
"coordinates": [
275,
844
],
"size": 40
},
{
"image": "https://cdn.discordapp.com/avatars/860670651026767942/7122b752fa6097472f8c76cc4458e33b.webp",
"class": "outline-orange-500",
"coordinates": [
939,
552
],
"size": 48
},
{
"image": "https://cdn.discordapp.com/avatars/520860407720837131/81abed4e03a9a7fc9d973b969c9d2ddd.webp",
"class": "outline-orange-500 ",
"coordinates": [
458,
913
],
"size": 35
},
{
"image": "https://cdn.discordapp.com/avatars/390226958241366016/aca819f6f27ff5b33f1d327b4d2f7049.webp",
"class": "outline-blue-500 ",
"coordinates": [
858,
707
],
"size": 45
},
{
"image": "https://cdn.discordapp.com/avatars/594256188422488069/1e532709dbd756c172c54add8536e558.webp",
"class": "outline-blue-500 bg-black ",
"coordinates": [
8,
477
],
"size": 54
},
{
"image": "https://cdn.discordapp.com/avatars/335725027377020929/2ead48f16d6119d01a01d1c17e2e97e5.webp",
"class": "outline-pink-500",
"coordinates": [
69,
561
],
"size": 54
},
{
"image": "https://cdn.discordapp.com/avatars/561863734264594452/1b3bb5e0c68b0a53d05898f8456fa5d5.webp",
"class": "outline-orange-500 bg-black ",
"coordinates": [
908,
463
],
"size": 54
},
{
"image": "https://cdn.discordapp.com/avatars/426690378830184448/09181b1b476f221b7850702158778c1a.webp",
"class": "outline-blue-500 bg-black ",
"coordinates": [
823,
184
],
"size": 44
},
{
"image": "https://cdn.discordapp.com/avatars/219573494713810945/5202607a6cf5e34ed705308459d0115c.webp",
"class": "outline-stone-900 bg-black ",
"coordinates": [
133,
493
],
"size": 49
},
{
"image": "https://cdn.discordapp.com/avatars/317785409763541002/38ba01511d57eb11c96dc74a9c4c149d.webp",
"class": "outline-blue-500 bg-black ",
"coordinates": [
119,
202
],
"size": 49
},
{
"image": "https://cdn.discordapp.com/avatars/465960044094160908/db149a0d49fd80cf7caf7cc2a637e084.webp",
"class": "outline-yellow-500 bg-black ",
"coordinates": [
179,
671
],
"size": 69
},
{
"image": "https://cdn.discordapp.com/avatars/706672850907430942/0cbb7c41b54155cee739542461755e65.webp",
"class": "outline-yellow-500 bg-black ",
"coordinates": [
771,
818
],
"size": 49
},
{
"image": "https://cdn.discordapp.com/avatars/708440359587414067/2bcdf703f69ee35c4cf39b2a89c429e8.webp",
"class": "outline-lime-500 bg-black ",
"coordinates": [
1018,
123
],
"size": 28
},
{
"image": "https://cdn.discordapp.com/avatars/142688838127583232/cd8a2857cfd1b4090e3cd2934d0bcd4e.webp",
"class": "outline-red-500 bg-black ",
"coordinates": [
57,
313
],
"size": 57
},
{
"image": "https://cdn.discordapp.com/avatars/389202953862512641/1ec9240abbae61b1f1f8f4a154859890.webp",
"class": "outline-stone-500 bg-black ",
"coordinates": [
819,
738
],
"size": 48
},
{
"image": "https://cdn.discordapp.com/avatars/1249703149057343549/4e5ddf63c78dab083a5cc3b8800b99f9.webp",
"class": "outline-stone-500 bg-black ",
"coordinates": [
474,
850
],
"size": 63
},
{
"image": "https://cdn.discordapp.com/avatars/419571665832378369/0c5806bb19fa2d06b25045a91369e950.webp",
"class": "outline-orange-500",
"coordinates": [
945,
697
],
"size": 42
},
{
"image": "https://cdn.discordapp.com/avatars/993044302957662228/6ee715b893be571ca2ff5760e7292f59.webp",
"class": "outline-purple-500",
"coordinates": [
113,
736
],
"size": 40
},
{
"image": "https://cdn.discordapp.com/avatars/284239193813680129/e81d7a3f73dd0a9d71f383ef813a50c8.webp",
"class": "outline-red-500",
"coordinates": [
143,
274
],
"size": 44
},
{
"image": "https://cdn.discordapp.com/avatars/194584980922433536/9040f8f62191553a3ab4a212ab989d41.webp",
"class": "outline-red-500",
"coordinates": [
53,
399
],
"size": 75
},
{
"image": "https://cdn.discordapp.com/avatars/1016936898670903336/c376d8fb319f8aa57c98142ad2ec101c.webp",
"class": "outline-blue-500",
"coordinates": [
50,
250
],
"size": 40
},
{
"image": "https://cdn.discordapp.com/avatars/773595387150860328/2828efc505a0cc83544a9437d089e54e.webp",
"class": "outline-cyan-500",
"coordinates": [
211,
846
],
"size": 44
},
{
"image": "https://cdn.discordapp.com/avatars/531392146767347712/0b5e5671e7e8eb0e4f6ec547c686667b.webp",
"class": "outline-orange-500 bg-stone-800",
"coordinates": [
525,
764
],
"size": 80
},
{
"image": "https://cdn.discordapp.com/avatars/623781003382751243/6a950611b395b0fbf2e8835ba9af1e84.webp",
"class": "outline-orange-500",
"coordinates": [
950,
277
],
"size": 47
},
{
"image": "https://cdn.discordapp.com/avatars/633313654283960330/edbd0bc89f1d17a90870c25958816671.webp",
"class": "outline-orange-500",
"coordinates": [
868,
606
],
"size": 85
},
{
"image": "https://cdn.discordapp.com/avatars/321614457623019520/abd926b4f0c2ba37d6ee9359ad0599d2.webp",
"class": "outline-orange-500",
"coordinates": [
962,
626
],
"size": 45
}
]
{
"image": "https://cdn.discordapp.com/avatars/372809091208445953/a_33fd25e26c0ba17c05566bc3179c6476.gif",
"class": "outline-red-500",
"coordinates": [187, 296],
"size": 172
},
{
"image": "https://cdn.discordapp.com/avatars/444952344308744203/9ee39cd422568dad9e70319df27b2560.webp",
"class": "outline-yellow-500",
"coordinates": [735, 441],
"size": 164
},
{
"image": "https://cdn.discordapp.com/avatars/419880181101232129/d5ec2616075dc0fddb3de528c9113628.webp",
"class": "outline-orange-500",
"coordinates": [391, 615],
"size": 149
},
{
"image": "https://cdn.discordapp.com/avatars/378704069726044170/415dcb2ef8d1ef635e35e1d04d523cba.webp",
"class": "outline-amber-500",
"coordinates": [568, 594],
"size": 120
},
{
"image": "https://cdn.discordapp.com/avatars/862314649307054080/bce0b10ae900daa2da139f9e089c8e56.webp",
"class": "outline-cyan-400",
"coordinates": [648, 709],
"size": 128
},
{
"image": "https://cdn.discordapp.com/avatars/163678036401586177/0930f3f6f0839b4f9c59846e185aa8ad.webp",
"class": "outline-yellow-500",
"coordinates": [24, 341],
"size": 49
},
{
"image": "https://cdn.discordapp.com/avatars/223160360461402122/c6552a1c768613bffb83bf88e4ce2632.webp",
"class": "outline-red-500",
"coordinates": [47, 86],
"size": 48
},
{
"image": "https://cdn.discordapp.com/avatars/979473046404476998/3d12629a52071fabff4493c1362d59fa.webp",
"class": "outline-amber-500",
"coordinates": [1038, 446],
"size": 52
},
{
"image": "https://cdn.discordapp.com/avatars/486802226577276929/2a6355319a116ba3e2aff9acf4163e95.webp",
"class": "outline-cyan-500",
"coordinates": [273, 760],
"size": 52,
"quote": "\"meds\""
},
{
"image": "https://cdn.discordapp.com/avatars/837425748435796060/248cd938377647404e3d8d1c53b639cf.webp",
"class": "outline-orange-500",
"coordinates": [648, 364],
"size": 105
},
{
"image": "https://cdn.discordapp.com/avatars/480024733535174668/5453c57e69ff16f495d8dcbd597070e9.webp",
"class": "outline-purple-500",
"coordinates": [187, 764],
"size": 62
},
{
"image": "https://cdn.discordapp.com/avatars/544368824842190861/1fbf29ea7ca9c2109b97af2ede3806fa.webp",
"class": "outline-lime-500",
"coordinates": [736, 277],
"size": 105
},
{
"image": "https://cdn.discordapp.com/avatars/344854021166727180/ace83ad13015f29bc33a3f858e15b4d7.webp",
"class": "outline-yellow-500",
"coordinates": [898, 364],
"size": 68
},
{
"image": "https://cdn.discordapp.com/avatars/579120037034721281/c7b4341806d515f5d92bb53e322a4728.webp",
"class": "outline-rose-500",
"coordinates": [887, 159],
"size": 39
},
{
"image": "https://cdn.discordapp.com/avatars/246125351464337418/10c8bb5456d1ca1cebf2edff62b7001f.webp",
"class": "outline-amber-500",
"coordinates": [1023, 552],
"size": 48
},
{
"image": "https://cdn.discordapp.com/avatars/143031299152674816/4a143f00b0e014e9c3da37cd8599b106.webp",
"class": "outline-pink-500",
"coordinates": [147, 553],
"size": 87
},
{
"image": "https://cdn.discordapp.com/avatars/194157962498015232/ab11cc35bd9d057769254d1d3e29e468.webp",
"class": "outline-amber-500 ",
"coordinates": [65, 643],
"size": 74
},
{
"image": "https://cdn.discordapp.com/avatars/482139697796349953/0a588671aaff04b64788b86bfa8e15f9.webp",
"class": "outline-stone-500 ",
"coordinates": [263, 653],
"size": 65
},
{
"image": "https://cdn.discordapp.com/avatars/231040215085481984/7f378337240110b76e6e9baa31f83670.webp",
"class": "outline-amber-500",
"coordinates": [354, 798],
"size": 80
},
{
"image": "https://cdn.discordapp.com/avatars/784153590595321876/27df3e463a7111b75805baecaff4cdc9.webp",
"class": "outline-white",
"coordinates": [583, 824],
"size": 69,
"quote": "Perfect being"
},
{
"image": "https://cdn.discordapp.com/avatars/408513270916579338/bac966d3bae8ad01546ee32584917be9.webp",
"class": "outline-rose-300",
"coordinates": [275, 844],
"size": 40
},
{
"image": "https://cdn.discordapp.com/avatars/860670651026767942/7122b752fa6097472f8c76cc4458e33b.webp",
"class": "outline-orange-500",
"coordinates": [939, 552],
"size": 48
},
{
"image": "https://cdn.discordapp.com/avatars/520860407720837131/81abed4e03a9a7fc9d973b969c9d2ddd.webp",
"class": "outline-orange-500 ",
"coordinates": [458, 913],
"size": 35
},
{
"image": "https://cdn.discordapp.com/avatars/390226958241366016/aca819f6f27ff5b33f1d327b4d2f7049.webp",
"class": "outline-blue-500 ",
"coordinates": [858, 707],
"size": 45
},
{
"image": "https://cdn.discordapp.com/avatars/594256188422488069/1e532709dbd756c172c54add8536e558.webp",
"class": "outline-blue-500 bg-black ",
"coordinates": [8, 477],
"size": 54
},
{
"image": "https://cdn.discordapp.com/avatars/335725027377020929/2ead48f16d6119d01a01d1c17e2e97e5.webp",
"class": "outline-pink-500",
"coordinates": [69, 561],
"size": 54
},
{
"image": "https://cdn.discordapp.com/avatars/561863734264594452/1b3bb5e0c68b0a53d05898f8456fa5d5.webp",
"class": "outline-orange-500 bg-black ",
"coordinates": [908, 463],
"size": 54
},
{
"image": "https://cdn.discordapp.com/avatars/426690378830184448/09181b1b476f221b7850702158778c1a.webp",
"class": "outline-blue-500 bg-black ",
"coordinates": [823, 184],
"size": 44
},
{
"image": "https://cdn.discordapp.com/avatars/219573494713810945/5202607a6cf5e34ed705308459d0115c.webp",
"class": "outline-stone-900 bg-black ",
"coordinates": [133, 493],
"size": 49
},
{
"image": "https://cdn.discordapp.com/avatars/317785409763541002/38ba01511d57eb11c96dc74a9c4c149d.webp",
"class": "outline-blue-500 bg-black ",
"coordinates": [119, 202],
"size": 49
},
{
"image": "https://cdn.discordapp.com/avatars/465960044094160908/db149a0d49fd80cf7caf7cc2a637e084.webp",
"class": "outline-yellow-500 bg-black ",
"coordinates": [179, 671],
"size": 69
},
{
"image": "https://cdn.discordapp.com/avatars/706672850907430942/0cbb7c41b54155cee739542461755e65.webp",
"class": "outline-yellow-500 bg-black ",
"coordinates": [771, 818],
"size": 49
},
{
"image": "https://cdn.discordapp.com/avatars/708440359587414067/2bcdf703f69ee35c4cf39b2a89c429e8.webp",
"class": "outline-lime-500 bg-black ",
"coordinates": [1018, 123],
"size": 28
},
{
"image": "https://cdn.discordapp.com/avatars/142688838127583232/cd8a2857cfd1b4090e3cd2934d0bcd4e.webp",
"class": "outline-red-500 bg-black ",
"coordinates": [57, 313],
"size": 57
},
{
"image": "https://cdn.discordapp.com/avatars/389202953862512641/1ec9240abbae61b1f1f8f4a154859890.webp",
"class": "outline-stone-500 bg-black ",
"coordinates": [819, 738],
"size": 48
},
{
"image": "https://cdn.discordapp.com/avatars/1249703149057343549/4e5ddf63c78dab083a5cc3b8800b99f9.webp",
"class": "outline-stone-500 bg-black ",
"coordinates": [474, 850],
"size": 63
},
{
"image": "https://cdn.discordapp.com/avatars/419571665832378369/0c5806bb19fa2d06b25045a91369e950.webp",
"class": "outline-orange-500",
"coordinates": [945, 697],
"size": 42
},
{
"image": "https://cdn.discordapp.com/avatars/993044302957662228/6ee715b893be571ca2ff5760e7292f59.webp",
"class": "outline-purple-500",
"coordinates": [113, 736],
"size": 40
},
{
"image": "https://cdn.discordapp.com/avatars/284239193813680129/e81d7a3f73dd0a9d71f383ef813a50c8.webp",
"class": "outline-red-500",
"coordinates": [143, 274],
"size": 44
},
{
"image": "https://cdn.discordapp.com/avatars/194584980922433536/9040f8f62191553a3ab4a212ab989d41.webp",
"class": "outline-red-500",
"coordinates": [53, 399],
"size": 75
},
{
"image": "https://cdn.discordapp.com/avatars/1016936898670903336/c376d8fb319f8aa57c98142ad2ec101c.webp",
"class": "outline-blue-500",
"coordinates": [50, 250],
"size": 40
},
{
"image": "https://cdn.discordapp.com/avatars/773595387150860328/2828efc505a0cc83544a9437d089e54e.webp",
"class": "outline-cyan-500",
"coordinates": [211, 846],
"size": 44
},
{
"image": "https://cdn.discordapp.com/avatars/531392146767347712/0b5e5671e7e8eb0e4f6ec547c686667b.webp",
"class": "outline-orange-500 bg-stone-800",
"coordinates": [525, 764],
"size": 80
},
{
"image": "https://cdn.discordapp.com/avatars/623781003382751243/6a950611b395b0fbf2e8835ba9af1e84.webp",
"class": "outline-orange-500",
"coordinates": [950, 277],
"size": 47
},
{
"image": "https://cdn.discordapp.com/avatars/633313654283960330/edbd0bc89f1d17a90870c25958816671.webp",
"class": "outline-orange-500",
"coordinates": [868, 606],
"size": 85
},
{
"image": "https://xcdn.discordapp.com/avatars/321614457623019520/abd926b4f0c2ba37d6ee9359ad0599d2.webp",
"class": "outline-orange-500",
"coordinates": [962, 626],
"size": 45
}
]

View file

@ -42,11 +42,16 @@ export function animateIn(
// Do nothing on mobile
if (getIsMobile()) return { destroy: () => undefined }
const observer = inview(node, { unobserveOnEnter: true, threshold: options.threshold ?? 0.4 })
const observer = inview(node, {
unobserveOnEnter: true,
threshold: options.threshold ?? 0.4
})
options.duration ??= 840
const effects = Object.entries(pick(options, ['fade', 'zoom', 'slide', 'duration']))
const effects = Object.entries(
pick(options, ['fade', 'zoom', 'slide', 'duration'])
)
const style = effects
.map(([effect, value]) => {
@ -73,9 +78,12 @@ export function animateIn(
timeoutId = setTimeout(
() =>
effects.forEach(([effect]) => {
if (effect === 'slide') node.style.removeProperty('translate')
else if (effect === 'fade') node.style.removeProperty('opacity')
else if (effect === 'zoom') node.style.removeProperty('scale')
if (effect === 'slide')
node.style.removeProperty('translate')
else if (effect === 'fade')
node.style.removeProperty('opacity')
else if (effect === 'zoom')
node.style.removeProperty('scale')
}),
options.delay ?? 0
)
@ -120,7 +128,10 @@ export function getIsMobile(): boolean {
}
/** Get the `generated_<filename>` for the provided path **/
export function getGeneratedPath(path: string, extension: string = 'webp') {
export function getGeneratedPath(
path: string,
extension: string = 'webp'
) {
const directory = path.substring(0, path.lastIndexOf('/'))
const filename = getFileNameWithoutExtension(path)
return `${directory}/generated_${filename}.${extension}`
@ -138,7 +149,9 @@ export function formatDate(
) {
const dateToFormat = new Date(date)
const dateFormatter = new Intl.DateTimeFormat(locales, { dateStyle })
const dateFormatter = new Intl.DateTimeFormat(locales, {
dateStyle
})
return dateFormatter.format(dateToFormat)
}
@ -164,7 +177,11 @@ export function getFileNameWithoutExtension(filePath: string) {
*
* Used here to do fancy stuff with clicks
*/
export function createThresholdStream({ clicksTarget = 69, clicksEachMs = 400, fallof = 20 }) {
export function createThresholdStream({
clicksTarget = 69,
clicksEachMs = 400,
fallof = 20
}) {
const FALLOF = -clicksTarget / fallof
return rxpipe(
@ -182,7 +199,9 @@ export function createThresholdStream({ clicksTarget = 69, clicksEachMs = 400, f
)
)
),
scan((level, value) => Math.min(clicksTarget, Math.max(level + value, 0))),
scan((level, value) =>
Math.min(clicksTarget, Math.max(level + value, 0))
),
startWith(0)
)
}
@ -232,8 +251,14 @@ export function convertStoreToObservable<T>(
* Checks if two rectangles are intersecting
*/
export function isIntersecting(
rect1: { size: number; coordinates: readonly [x: number, y: number] },
rect2: { size: number; coordinates: readonly [x: number, y: number] }
rect1: {
size: number
coordinates: readonly [x: number, y: number]
},
rect2: {
size: number
coordinates: readonly [x: number, y: number]
}
) {
return !(
rect1.coordinates[0] + rect1.size < rect2.coordinates[0] ||

View file

@ -1,53 +1,77 @@
<script>
<script lang="ts">
import {
BehaviorSubject,
Subject,
auditTime,
combineLatest,
debounceTime,
delay,
distinctUntilChanged,
filter,
map,
of,
startWith,
switchMap,
timer
timer,
type Observable
} from 'rxjs'
import { onDestroy, onMount } from 'svelte'
import { spring } from 'svelte/motion'
import { Spring } from 'svelte/motion'
/** The start position of the gradient. */
export let startPosition = [-1000, -1000]
type Props = {
/** The start position of the gradient. */
startPosition?: [number, number]
class?: string
}
let { class: className, startPosition = [-1000, -1000] }: Props =
$props()
let wrapperElement: HTMLDivElement | undefined = $state(undefined)
/** @type {HTMLDivElement}*/
let wrapperElement = undefined
/** @type {import('rxjs').BehaviorSubject<boolean>}*/
const isMouseOver$ = new BehaviorSubject(false).pipe(
// Do not harshly stop updating the gradient when the mouse leaves, but wait a bit
switchMap((isTrue) => (isTrue ? of(isTrue) : timer(1500).pipe(map(() => false)))),
switchMap((isTrue) =>
isTrue ? of(isTrue) : timer(1500).pipe(map(() => false))
),
distinctUntilChanged()
)
) as BehaviorSubject<boolean>
/** @type {import('rxjs').BehaviorSubject<number>}*/
const gradientSize$ = new BehaviorSubject().pipe(
const gradientSize$: BehaviorSubject<number> = new BehaviorSubject(
undefined
).pipe(
// Debounce resize events with some high number for performance
debounceTime(1),
map(() => wrapperElement.getBoundingClientRect().width * 3),
auditTime(16),
map(
() => (wrapperElement?.getBoundingClientRect().width ?? 0) * 3
),
startWith(800)
)
/** @type {import('rxjs').Subject< {clientX: number, clientY: number} >}*/
const mousePosition$ = new Subject()
) as BehaviorSubject<number>
const mousePosition$: Subject<{
clientX: number
clientY: number
}> = new Subject()
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(startPosition)
const gradientPosition$: Observable<[number, number]> =
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
] as [number, number]
}),
startWith(startPosition)
)
const gradientWiggle = new Spring(startPosition, {
damping: 0.95,
stiffness: 0.1
})
const subscription = gradientPosition$.subscribe((data) =>
gradientWiggle.set(data)
)
const gradientWiggle = spring(startPosition, { damping: 0.95, stiffness: 0.1 })
const subscription = gradientPosition$.subscribe((data) => gradientWiggle.set(data))
onDestroy(() => {
subscription.unsubscribe()
@ -60,16 +84,16 @@
hasJustMounted = false
})
$: {
$effect(() => {
if (!$isMouseOver$) {
globalThis.document?.removeEventListener('mousemove', track)
}
}
})
function resizeGradient() {
if (hasJustMounted || !isMouseOver$) return
gradientSize$.next()
gradientSize$.next(0)
}
function startTrackingMouse() {
@ -78,7 +102,13 @@
globalThis.document?.addEventListener('mousemove', track)
}
function track({ clientX, clientY }) {
function track({
clientX,
clientY
}: {
clientX: number
clientY: number
}) {
mousePosition$.next({ clientX, clientY })
}
@ -90,12 +120,16 @@
<svelte:window on:resize={resizeGradient} />
<div
class={$$props.class + ' wrapper'}
on:mouseenter={startTrackingMouse}
on:mouseleave={({ clientX, clientY, currentTarget }) => {
const { x, width, y, height } = currentTarget.getBoundingClientRect()
class={className + ' wrapper'}
onmouseenter={startTrackingMouse}
onmouseleave={({ clientX, clientY, currentTarget }) => {
const { x, width, y, height } =
currentTarget.getBoundingClientRect()
const isMouseStillOver =
x <= clientX && y <= clientY && x + width > clientX && y + height > clientY
x <= clientX &&
y <= clientY &&
x + width > clientX &&
y + height > clientY
isMouseOver$.next(isMouseStillOver)
}}
aria-hidden="true"
@ -103,12 +137,17 @@
>
<div
class="gradient"
style:--x={$gradientWiggle.at(0) + 'px'}
style:--y={$gradientWiggle.at(1) + 'px'}
style:--x={gradientWiggle.current.at(0) + 'px'}
style:--y={gradientWiggle.current.at(1) + 'px'}
style:--size={$gradientSize$ + 'px'}
></div>
<svg width="100%" height="100%" fill="none" xmlns="http://www.w3.org/2000/svg">
<svg
width="100%"
height="100%"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<pattern
id="background-pattern-id"
x="0"
@ -117,7 +156,14 @@
height="30"
patternUnits="userSpaceOnUse"
>
<rect x="0.5" y="0.5" width="30" height="30" rx="0" stroke="currentColor" />
<rect
x="0.5"
y="0.5"
width="30"
height="30"
rx="0"
stroke="currentColor"
/>
</pattern>
<rect

View file

@ -6,7 +6,7 @@ export interface CommunityProfile {
containerClass?: string
size: number
quote?: string
class?: string,
class?: string
}
export interface CommunityContext {
@ -30,7 +30,10 @@ interface ProfilesState {
*/
intersections: string[]
/** All tagged profiles */
profiles: Record<string, { size: number; coordinates: readonly [x: number, y: number] }>
profiles: Record<
string,
{ size: number; coordinates: readonly [x: number, y: number] }
>
/** Current active events. Currently not used, but maybe in the future */
events: string[]
}

View file

@ -1,21 +1,38 @@
<script lang="ts">
import { cn } from '$lib/Helper'
export let size: 'md' | 'lg' | 'xl' = 'md'
export let type: 'primary' | 'outline' | 'fancyOutline' = 'primary'
interface Props {
size?: 'md' | 'lg' | 'xl'
type?: 'primary' | 'outline' | 'fancyOutline'
href?: string | undefined
newTab?: boolean
children?: import('svelte').Snippet
onClick?: () => void
[key: string]: any
}
export let href: string | undefined = undefined
export let newTab = false
let {
size = 'md',
type = 'primary',
href = undefined,
newTab = false,
children,
onClick,
...rest
}: Props = $props()
$: classes = cn(
'animate rounded text-sm font-bold hover:scale-[1.01] active:scale-100',
'primary' == type && 'bg-slate-200 text-black',
'outline' == type && 'bg-transparent text-white outline outline-2 outline-slate-200',
'fancyOutline' == type && 'fancy',
'md' == size && 'min-w-[5.5rem] px-4 py-2.5',
'lg' == size && 'min-w-[5.5rem] px-6 py-3',
'xl' == size && 'min-w-[5.5rem] px-8 py-3.5',
$$restProps.class
let classes = $derived(
cn(
'animate rounded text-sm font-bold hover:scale-[1.01] active:scale-100',
'primary' == type && 'bg-slate-200 text-black',
'outline' == type &&
'bg-transparent text-white outline outline-2 outline-slate-200',
'fancyOutline' == type && 'fancy',
'md' == size && 'min-w-[5.5rem] px-4 py-2.5',
'lg' == size && 'min-w-[5.5rem] px-6 py-3',
'xl' == size && 'min-w-[5.5rem] px-8 py-3.5',
rest.class
)
)
</script>
@ -23,38 +40,39 @@
<div class="relative max-w-max">
<svelte:element
this={href ? 'a' : 'button'}
{...$$restProps}
{...rest}
{href}
role="button"
tabindex="0"
target={newTab ? '_blank' : undefined}
class={classes}
on:click
onclick={onClick}
>
<slot>NO LABEL PROVIDED</slot>
{#if children}{@render children()}{:else}NO LABEL PROVIDED{/if}
</svelte:element>
<span
class="fancy-bg absolute inset-0 -z-10 h-full w-[110%] min-w-[5rem] scale-y-75 bg-cyan-500/90 px-4 py-2 blur-xl"
style="--easing: x; --duration: 8s;"
/>
></span>
<span
class="fancy-bg absolute inset-0 -z-10 h-full w-[110%] min-w-[5rem] scale-y-75 bg-secondary/90 px-4 py-2 blur-xl"
style="--easing: y; --duration: 8s;"
/>
></span>
<span
class="fancy-bg absolute inset-0 -z-10 h-full w-[110%] min-w-[5rem] scale-y-75 bg-purple-500/90 px-4 py-2 blur-xl"
style="--easing: z;--duration: 8s;"
/>
></span>
</div>
{:else}
<svelte:element
this={href ? 'a' : 'button'}
{...$$restProps}
{...rest}
{href}
role="button"
tabindex="0"
class={classes}
on:click><slot>NO LABEL PROVIDED</slot></svelte:element
onclick={onClick}
>{#if children}{@render children()}{:else}NO LABEL PROVIDED{/if}</svelte:element
>
{/if}

View file

@ -1,40 +1,68 @@
<script>
import { run, createBubbler, handlers } from 'svelte/legacy'
const bubble = createBubbler()
import clsx from 'clsx'
import { getContext, onMount } from 'svelte'
import { cardsContext } from '$lib/components/CardsContainer.svelte'
import { spring } from 'svelte/motion'
import { getIsMobile } from '$lib/Helper.ts'
/** @type {'cyan' | 'purple'}*/
export let color = 'cyan'
/** @type {number | number}*/
export let gradientOpacity = undefined
const { mouseCoordinates$, isHoverCards, enableBorders = true } = getContext(cardsContext)
/**
* @typedef {Object} Props
* @property {'cyan' | 'purple'} [color]
* @property {number | number} [gradientOpacity]
* @property {import('svelte').Snippet} [children]
*/
/** @type {Props & { [key: string]: any }} */
let {
color = 'cyan',
gradientOpacity = undefined,
children,
...rest
} = $props()
const {
mouseCoordinates$,
isHoverCards,
enableBorders = true
} = getContext(cardsContext)
/** @type HTMLDivElement */
let container
let container = $state()
let isMobile = false
const damping = 0.2
const fillX = spring(0, { damping, stiffness: 0.021, precision: 0.3 })
const fillY = spring(0, { damping, stiffness: 0.021, precision: 0.3 })
const borderX = spring(0, { damping, stiffness: 0.03, precision: 0.3 })
const borderY = spring(0, { damping, stiffness: 0.03, precision: 0.3 })
const fillX = spring(0, {
damping,
stiffness: 0.021,
precision: 0.3
})
const fillY = spring(0, {
damping,
stiffness: 0.021,
precision: 0.3
})
const borderX = spring(0, {
damping,
stiffness: 0.03,
precision: 0.3
})
const borderY = spring(0, {
damping,
stiffness: 0.03,
precision: 0.3
})
const bounceBack = 2
const soft = 0.8
let isMouseOver = false
let isMouseOver = $state(false)
/** Has the mouse entered and not left*/
let hasMouseEntered = false
$: {
if (container && $mouseCoordinates$?.x !== undefined) {
updateGradient()
}
}
onMount(() => {
isMobile = getIsMobile()
})
@ -42,7 +70,12 @@
function updateGradient() {
if (isMobile) return
const { x: rectX, y: rectY, width, height } = container.getBoundingClientRect()
const {
x: rectX,
y: rectY,
width,
height
} = container.getBoundingClientRect()
const normX = $mouseCoordinates$.x - rectX
const normY = $mouseCoordinates$.y - rectY
@ -67,43 +100,56 @@
}
*/
if ($mouseCoordinates$.x < rectX) fillX.set(rectX + bounceBack, { soft })
else if ($mouseCoordinates$.x > rectX + width) fillX.set(rectX + width - bounceBack, { soft })
if ($mouseCoordinates$.x < rectX)
fillX.set(rectX + bounceBack, { soft })
else if ($mouseCoordinates$.x > rectX + width)
fillX.set(rectX + width - bounceBack, { soft })
else fillX.set(normX)
if ($mouseCoordinates$.y < rectY) fillY.set(rectY + bounceBack, { soft: 1 })
if ($mouseCoordinates$.y > rectY + height) fillX.set(rectY - height - bounceBack, { soft })
if ($mouseCoordinates$.y < rectY)
fillY.set(rectY + bounceBack, { soft: 1 })
if ($mouseCoordinates$.y > rectY + height)
fillX.set(rectY - height - bounceBack, { soft })
else fillY.set(normY)
}
run(() => {
if (container && $mouseCoordinates$?.x !== undefined) {
updateGradient()
}
})
</script>
<div
class={clsx('card group ', $$restProps.class)}
class={clsx('card group ', rest.class)}
style:--x={$fillX}
style:--y={$fillY}
style:--borderX={enableBorders && $borderX}
style:--borderY={enableBorders && $borderY}
class:isHoverCards={$isHoverCards}
bind:this={container}
on:mouseenter={() => (isMouseOver = true)}
on:mouseleave={() => {
onmouseenter={handlers(
() => (isMouseOver = true),
bubble('mouseenter')
)}
onmouseleave={handlers(() => {
isMouseOver = false
updateGradient()
}}
}, bubble('mouseleave'))}
class:purpleGradient={color === 'purple'}
role="contentinfo"
on:mouseenter
on:mouseleave
>
<div
class="absolute inset-0 z-10 m-0.5 size-full min-h-0 min-w-0 grow overflow-hidden rounded-3xl"
>
<slot>Nothing in the slot here</slot>
{#if children}{@render children()}{:else}Nothing in the slot here{/if}
</div>
<div class="gradient max-sm:hidden" style:opacity={gradientOpacity} />
<div class="gradient_black max-sm:hidden" />
<div
class="gradient max-sm:hidden"
style:opacity={gradientOpacity}
></div>
<div class="gradient_black max-sm:hidden"></div>
{#if enableBorders}
<div class="border-gradient max-sm:hidden" />
<div class="border-gradient max-sm:hidden"></div>
{/if}
</div>
@ -156,8 +202,13 @@
pointer-events: none;
contain: strict;
background: radial-gradient(
620px circle at calc(var(--borderX) * 1px) calc(var(--borderY) * 1px),
color-mix(in srgb, var(--color1, theme(colors.cyan.500)), transparent 50%),
620px circle at calc(var(--borderX) * 1px)
calc(var(--borderY) * 1px),
color-mix(
in srgb,
var(--color1, theme(colors.cyan.500)),
transparent 50%
),
transparent
);
}
@ -191,7 +242,8 @@
opacity: 0%;
contain: strict;
background: url('/imgs/grain.webp'),
background:
url('/imgs/grain.webp'),
radial-gradient(
ellipse at calc(var(--x) * 1px) calc(var(--y) * 1px),
var(--color1, theme(colors.cyan.500 / 100%)),

View file

@ -1,24 +1,38 @@
<script context="module">
<script module>
export const cardsContext = Symbol('mouseContext')
</script>
<script>
import { getIsMobile } from '$lib/Helper.ts'
import { BehaviorSubject, Subject, throttle, throttleTime } from 'rxjs'
import {
BehaviorSubject,
Subject,
throttle,
throttleTime
} from 'rxjs'
import { onMount, setContext } from 'svelte'
import { writable } from 'svelte/store'
export let enableBorders = true
/**
* @typedef {Object} Props
* @property {boolean} [enableBorders]
* @property {import('svelte').Snippet} [children]
*/
/** @type {Props & { [key: string]: any }} */
let { enableBorders = true, children, ...rest } = $props()
const fps = 1000 / 60
/** @type {HTMLElement}*/
let containerElement
let isMobile = false
let containerElement = $state()
let isMobile = $state(false)
const context = setContext(cardsContext, {
mouseCoordinates$: new BehaviorSubject({ x: 0, y: 0 }).pipe(throttleTime(fps)),
mouseCoordinates$: new BehaviorSubject({ x: 0, y: 0 }).pipe(
throttleTime(fps)
),
isHoverCards: writable(false),
enableBorders
})
@ -38,16 +52,17 @@
onMount(() => {
isMobile = getIsMobile()
return () => containerElement.removeEventListener('mousemove', trackMouse)
return () =>
containerElement.removeEventListener('mousemove', trackMouse)
})
</script>
<div
class={$$restProps.class}
class={rest.class}
bind:this={containerElement}
role="contentinfo"
on:mouseenter={!isMobile && onMouseEnter}
on:mouseleave={!isMobile && onMouseLeave}
onmouseenter={!isMobile && onMouseEnter}
onmouseleave={!isMobile && onMouseLeave}
>
<slot />
{@render children?.()}
</div>

View file

@ -1,16 +1,29 @@
<script lang="ts">
export let href: string | undefined = undefined
export let isExternal: boolean = true
interface Props {
href?: string | undefined
isExternal?: boolean
children?: import('svelte').Snippet
[key: string]: any
}
let isExternalProp = isExternal ? { target: '_blank', rel: 'noopener noreferrer' } : {}
let {
href = undefined,
isExternal = true,
children,
...rest
}: Props = $props()
let isExternalProp = isExternal
? { target: '_blank', rel: 'noopener noreferrer' }
: {}
</script>
{#if href}
<a {href} {...isExternalProp} class="cursor-pointer {$$restProps.class}">
<slot />
<a {href} {...isExternalProp} class="cursor-pointer {rest.class}">
{@render children?.()}
</a>
{:else}
<div class={$$restProps.class}>
<slot />
<div class={rest.class}>
{@render children?.()}
</div>
{/if}

View file

@ -1,3 +1,4 @@
<!-- @migration-task Error while migrating Svelte code: Cannot split a chunk that has already been edited (28:12 "on:click={$$slots.default ? undefined : copyCommand}") -->
<script>
import { onDestroy } from 'svelte'
import ClipboardIcon from '~icons/mingcute/copy-2-line'
@ -13,7 +14,9 @@
let timeoutId
async function copyCommand() {
await navigator.clipboard.writeText(command).then(() => (isShowingCopied = true))
await navigator.clipboard
.writeText(command)
.then(() => (isShowingCopied = true))
clearTimeout(timeoutId)
timeoutId = setTimeout(() => (isShowingCopied = false), 1400)
}
@ -23,7 +26,9 @@
})
</script>
<div class="relative flex grow flex-col font-mono {containerClass ?? ''}">
<div
class="relative flex grow flex-col font-mono {containerClass ?? ''}"
>
<button
class="group flex min-w-[18rem] items-center justify-center gap-4 rounded-lg border border-primary py-3 pl-6 pr-6 text-base font-medium transition-transform active:scale-[1.01] sm:rounded-full"
on:click={$$slots.default ? undefined : copyCommand}

View file

@ -1,56 +1,89 @@
<script lang="ts">
import clsx from 'clsx'
import { createEventDispatcher, onDestroy, onMount } from 'svelte'
import { onDestroy, onMount } from 'svelte'
import { spring, type Spring } from 'svelte/motion'
import { convertStoreToObservable, lerp } from '$lib/Helper'
import { inview } from 'svelte-inview'
import { throttleTime } from 'rxjs'
import type { Interactable } from '@interactjs/types/index'
import { lerp } from '$lib/Helper'
export let image: string
export let containerClass: string = ''
export let size: number
export let coordinates: readonly [number, number]
type Props = {
image: string
containerClass?: string
size: number
coordinates: readonly [number, number]
class?: string
export let quote: string | undefined = undefined
quote?: string | undefined
export let isAnimating = true
isAnimating?: boolean
export let element: HTMLElement | undefined = undefined
export let imageWrapper: HTMLDivElement | undefined = undefined
export let imageElement: HTMLImageElement | undefined = undefined
export let style: string | undefined = undefined
export let spawnDelay = 0
export let spawnInstanly = false
/**
* Usually just the size / biggestSize
* Goes from 0 - 1
*/
export let weight: number
export let getRestrictionElement: (() => HTMLElement) | undefined = undefined
element?: HTMLElement | undefined
imageWrapper?: HTMLDivElement | undefined
imageElement?: HTMLImageElement | undefined
style?: string | undefined
spawnDelay?: number
spawnInstanly?: boolean
/**
* Usually just the size / biggestSize
* Goes from 0 - 1
*/
weight: number
getRestrictionElement?: (() => HTMLElement) | undefined
onClick?: () => void
const dispatch = createEventDispatcher<{
enteredView: {
enteredView?: (data: {
dragCoordinates: Spring<readonly [number, number]>
imageElement: HTMLImageElement
element: HTMLElement
delay: number
}) => void
dragged?: (data: readonly [number, number]) => void
dragStart?: (data: any) => void
dragEnd?: (data: any) => void
hover?: (data: any) => void
children?: import('svelte').Snippet
}
let {
image,
class: className,
containerClass = '',
size,
coordinates,
quote = undefined,
isAnimating = true,
element = undefined,
imageWrapper = undefined,
imageElement = undefined,
style = undefined,
spawnDelay = 0,
spawnInstanly = false,
/**
* Usually just the size / biggestSize
* Goes from 0 - 1
*/
weight,
getRestrictionElement = undefined,
onClick,
dragEnd,
dragStart,
enteredView,
hover,
children
}: Props = $props()
const dragCoordinates = spring(
[0, 0] as readonly [number, number],
{
damping: lerp(0.2, 0.03, weight),
stiffness: lerp(0.2, 0.01, weight),
precision: 0.001
}
dragged: readonly [number, number]
dragStart: any
dragEnd: any
hover: any
}>()
)
const dragCoordinates = spring([0, 0] as readonly [number, number], {
damping: lerp(0.2, 0.03, weight),
stiffness: lerp(0.2, 0.01, weight),
// stiffness: lerp(0.81, 0.9, relativeSize),
precision: 0.001
})
let hasEnteredView = false
let hasImageLoaded = false
let hasEnteredView = $state(false)
let hasImageLoaded = $state(false)
let interactionjs: Interactable
@ -63,7 +96,7 @@
() => {
hasEnteredView = true
dispatch('enteredView', {
enteredView?.({
dragCoordinates,
imageElement: imageElement!,
element: element!,
@ -79,18 +112,14 @@
inertia: { resistance: lerp(5, 200, weight) },
listeners: {
move({ dx, dy }) {
dragCoordinates.update(([x, y]) => {
x += dx
y += dy
return [x, y]
})
dragCoordinates.update(([x, y]) => [x + dx, y + dy])
},
start(event) {
dispatch('dragStart', event)
dragStart?.(event)
},
end(event) {
dispatch('dragEnd', event)
dragEnd?.(event)
}
},
modifiers: getRestrictionElement
@ -105,29 +134,21 @@
})
}
const draggedSubscription = convertStoreToObservable(dragCoordinates)
.pipe(throttleTime(80))
.subscribe((drag) => {
const displayedPosition = getDisplayedPosition(coordinates, drag)
dispatch('dragged', displayedPosition)
})
onMount(() => {
// Nesecarry as the load image event might not get fired when its already loaded ( for example after a page reload )
hasImageLoaded = hasImageLoaded || !!imageElement?.complete
})
onDestroy(() => {
draggedSubscription.unsubscribe()
interactionjs?.unset()
})
function getDisplayedPosition(
origin: readonly [x: number, y: number],
dragCoordinates: readonly [x: number, y: number]
) {
return [origin[0] + dragCoordinates[0], origin[1] + dragCoordinates[1]] as const
}
// function getDisplayedPosition(
// origin: readonly [x: number, y: number],
// dragCoordinates: readonly [x: number, y: number]
// ) {
// return [origin[0] + dragCoordinates[0], origin[1] + dragCoordinates[1]] as const
// }
</script>
<div
@ -149,18 +170,18 @@
style:translate={`calc( ${$dragCoordinates[0]}px ) ${$dragCoordinates[1]}px`}
use:inview={{ unobserveOnEnter: true, threshold: 0.2 }}
class:_animate={hasImageLoaded && isAnimating && hasEnteredView}
on:inview_enter={onViewEnter}
on:click
oninview_enter={onViewEnter}
onclick={onClick}
>
<div class="" bind:this={imageWrapper}>
<img
class="group aspect-square h-full w-full touch-none select-none rounded-[50%] object-cover outline outline-4 {$$restProps.class}"
class="group aspect-square h-full w-full touch-none select-none rounded-[50%] object-cover outline outline-4 {className}"
bind:this={imageElement}
on:load={() => (hasImageLoaded = true)}
onload={() => (hasImageLoaded = true)}
src={image}
alt="community profile picture"
aria-hidden="true"
on:mouseenter={(event) => dispatch('hover', event)}
onmouseenter={(event) => hover?.(event)}
class:hover:scale-125={!!quote}
loading="lazy"
referrerpolicy="no-referrer"
@ -170,10 +191,12 @@
{...{
/* @ts-ignore */
}}
onerror="this.__error = true"
onerror={() => (this.__error = true)}
{style}
/>
<slot />
{#if children}
{@render children()}
{/if}
</div>
{#if quote}
@ -186,7 +209,8 @@
<style lang="postcss">
._animate {
animation: reveal 440ms 1 var(--delay) both cubic-bezier(0, 1, 0.765, 3.8);
animation: reveal 440ms 1 var(--delay) both
cubic-bezier(0, 1, 0.765, 3.8);
touch-action: none;
user-select: none;

View file

@ -1,16 +1,35 @@
<script>
import DiscordIcon from '~icons/prime/discord'
import GithubIcon from '~icons/ri/github-fill'
import { accountsLink, discordLink, forumLink } from '$lib/constants'
import {
accountsLink,
discordLink,
forumLink
} from '$lib/constants'
import { forgejoLink } from '$lib/constants'
import RssIcon from '~icons/mingcute/rss-fill'
import ForgejoIcon from '~icons/fe/git'
/** @type {[string, string, string, string]} */
const team = [
['Fufexan', 'Supporting Developer', 'cyan', 'https://github.com/fufexan'],
['NotAShelf', 'Community Manager', 'teal', 'https://github.com/NotAShelf'],
['VDawg', 'Webdesign and dev', 'emerald', 'https://github.com/vdawg-git']
[
'Fufexan',
'Supporting Developer',
'cyan',
'https://github.com/fufexan'
],
[
'NotAShelf',
'Community Manager',
'teal',
'https://github.com/NotAShelf'
],
[
'VDawg',
'Webdesign and dev',
'emerald',
'https://github.com/vdawg-git'
]
]
function createRole(role, color) {
return `<span class='text-${color}-500'><span class='text-${color}-600'>[ </span>${role}<span class='text-${color}-600'> ]</span></span>`
@ -34,12 +53,18 @@
</li>
{#each team as [name, role, color, href]}
<li>
<a {href} target="_blank">{name} {@html createRole(role, color)}</a>
<a {href} target="_blank"
>{name} {@html createRole(role, color)}</a
>
</li>
{/each}
<li>
<a href="https://github.com/hyprwm/Hyprland/graphs/contributors" target="_blank"
>and our <span class="text-indigo-500">fellow contributors</span></a
<a
href="https://github.com/hyprwm/Hyprland/graphs/contributors"
target="_blank"
>and our <span class="text-indigo-500"
>fellow contributors</span
></a
>
</li>
</ul>
@ -48,10 +73,13 @@
<div class="flex flex-col gap-4">
<div class="pretitle">Links</div>
<ul class="flex flex-col gap-3 font-medium">
<li><a href="https://wiki.hypr.land/" target="_blank">Wiki</a></li>
<li>
<a href="https://wiki.hypr.land/Getting-Started/Master-Tutorial/" target="_blank"
>Get started</a
<a href="https://wiki.hypr.land/" target="_blank">Wiki</a>
</li>
<li>
<a
href="https://wiki.hypr.land/Getting-Started/Master-Tutorial/"
target="_blank">Get started</a
>
</li>
<li><a href="/hall_of_fame">Hall of fame</a></li>
@ -76,7 +104,8 @@
href="https://github.com/hyprwm/Hyprland"
class="text-slate-400 hover:text-slate-200"
target="_blank"
aria-label="Go to our Github"><GithubIcon class="h-12 w-12 " /></a
aria-label="Go to our Github"
><GithubIcon class="h-12 w-12 " /></a
>
</li>
<li class="">
@ -92,20 +121,26 @@
href={forgejoLink}
class="text-slate-400 hover:text-slate-200"
target="_blank"
aria-label="Rss Feed"><ForgejoIcon class="h-12 w-12 " /></a
aria-label="Rss Feed"
><ForgejoIcon class="h-12 w-12 " /></a
>
</li>
</ul>
</div>
<div class="flex w-full flex-wrap gap-4 text-sm font-medium text-slate-400">
<p>Hyprland is licensed under the BSD 3-Clause "New" or "Revised" License.</p>
<div
class="flex w-full flex-wrap gap-4 text-sm font-medium text-slate-400"
>
<p>
Hyprland is licensed under the BSD 3-Clause "New" or "Revised"
License.
</p>
<p>© Hyprland Development {new Date().getFullYear()}.</p>
<p>Stay hydrated</p>
</div>
</div>
<div class="gradient" aria-hidden="true" />
<div class="gradient" aria-hidden="true"></div>
</footer>
<style lang="postcss">
@ -127,8 +162,17 @@
width: 100%;
height: 900px;
z-index: -10;
mask-image: radial-gradient(105vw 450px at 50% 50%, rgba(0, 0, 0, 1) 80%, transparent);
background: url('/imgs/grain.webp'),
radial-gradient(105vw 450px at 50% 50%, theme(colors.blue.600 / 80%), transparent);
mask-image: radial-gradient(
105vw 450px at 50% 50%,
rgba(0, 0, 0, 1) 80%,
transparent
);
background:
url('/imgs/grain.webp'),
radial-gradient(
105vw 450px at 50% 50%,
theme(colors.blue.600 / 80%),
transparent
);
}
</style>

View file

@ -1,25 +1,35 @@
<script>
import { lerp } from '$lib/Helper.ts'
<script lang="ts">
import { lerp } from '$lib/Helper'
import { createNoise2D } from 'simplex-noise'
import { onMount } from 'svelte'
import { expoIn } from 'svelte/easing'
/** Lifespan in milliseconds */
export let lifeSpan = 1500
export let maxSpeed = 20
export let minSpeed = 4
export let maxOpacity = 1
export let scale = 1
type Props = {
/** Lifespan in milliseconds */
lifeSpan: number
maxSpeed: number
minSpeed: number
maxOpacity: number
scale: number
}
let {
lifeSpan = 1500,
maxSpeed = 20,
minSpeed = 4,
maxOpacity = 1,
scale = 1
}: Props = $props()
const isDescending = Math.random() > 0.8
const wobbliness = lerp(0.0001, 0.004, Math.random())
const speed = Math.random() * (maxSpeed - minSpeed) + minSpeed
let x = Math.random() * 30 - 30
let y = Math.random() * 2
let lifeRemaining = lifeSpan
$: lifePercentage = lifeRemaining / lifeSpan
let x = $state(Math.random() * 30 - 30)
let y = $state(Math.random() * 2)
let lifeRemaining = $state(lifeSpan)
let lifePercentage = $derived(lifeRemaining / lifeSpan)
let timestamp = Date.now()
const noiseY = createNoise2D()
@ -29,13 +39,14 @@
const color = colors.at(Math.ceil(colors.length * Math.random()))
onMount(() => {
let animationId
let animationId: number
let i = 0
animate()
async function animate() {
const deltaTime = (timestamp - Date.now()) / 17 // One frame should last roughly 17ms for 60fps
// One frame should last roughly 17ms for 60fps
const deltaTime = (timestamp - Date.now()) / 17
x += noiseX(i, 1) * speed * deltaTime * expoIn(lifePercentage)
y += noiseY(i, 1) * speed * deltaTime * expoIn(lifePercentage)
@ -59,7 +70,9 @@
style:translate={`${x}px ${y}px`}
style:background={color}
style:opacity={(lifeRemaining / lifeSpan - (1 - maxOpacity)) ** 5}
style:scale={isDescending ? (lifeRemaining / lifeSpan - (1 - scale)) ** 2 : undefined}
style:scale={isDescending
? (lifeRemaining / lifeSpan - (1 - scale)) ** 2
: undefined}
style:top={50 + (Math.random() * 5 - 5) + '%'}
style:left={50 + (Math.random() * 5 - 5) + '%'}
></div>

View file

@ -3,19 +3,33 @@
import type { Sponsor } from '../../routes/api/sponsors/+server'
import Clickable from './Clickable.svelte'
export let sponsor: Sponsor
export let showImage = false
export let showSlogan = false
export let sloganClass = ''
interface Props {
sponsor: Sponsor
showImage?: boolean
showSlogan?: boolean
sloganClass?: string
[key: string]: any
}
let {
sponsor,
showImage = false,
showSlogan = false,
sloganClass = '',
...rest
}: Props = $props()
</script>
<Clickable href={sponsor.link} class="flex flex-col items-center gap-4">
<Clickable
href={sponsor.link}
class="flex flex-col items-center gap-4"
>
{#if showImage && sponsor.image}
<img
title={sponsor.name}
class={cn(
'size-full self-center justify-self-start rounded-md object-contain',
$$restProps.class
rest.class
)}
src={sponsor.image}
alt={sponsor.name}

View file

@ -5,9 +5,15 @@
default: 'text-5xl md:text-7xl lg:text-8xl',
small: 'text-3xl md:text-5xl lg:text-6xl'
}
export let size: keyof typeof sizes = 'default'
interface Props {
size?: keyof typeof sizes
children?: import('svelte').Snippet
[key: string]: any
}
let { size = 'default', children, ...rest }: Props = $props()
</script>
<h1 class={cn('mb-12 font-bold', sizes[size], $$restProps.class)}>
<slot>No title given!!!</slot>
<h1 class={cn('mb-12 font-bold', sizes[size], rest.class)}>
{#if children}{@render children()}{:else}No title given!!!{/if}
</h1>

View file

@ -1,3 +1,13 @@
<script>
/**
* @typedef {Object} Props
* @property {import('svelte').Snippet} [children]
*/
/** @type {Props} */
let { children } = $props()
</script>
<p class="mb-3 font-extrabold text-slate-300">
<slot />
{@render children?.()}
</p>

View file

@ -1,3 +1,15 @@
<p class="-mt-4 mb-7 font-extrabold text-slate-300 sm:text-lg {$$restProps.class}">
<slot />
<script>
/**
* @typedef {Object} Props
* @property {import('svelte').Snippet} [children]
*/
/** @type {Props & { [key: string]: any }} */
let { children, ...rest } = $props()
</script>
<p
class="-mt-4 mb-7 font-extrabold text-slate-300 sm:text-lg {rest.class}"
>
{@render children?.()}
</p>

View file

@ -1,26 +1,43 @@
<script>
import { animateIn, cn } from '$lib/Helper.ts'
/** @type { 'left' | 'right' | 'center'} */
export let align = 'center'
/**
* @typedef {Object} Props
* @property { 'left' | 'right' | 'center'} [align]
* @property {import('svelte').Snippet} [pre]
* @property {import('svelte').Snippet} [title]
* @property {import('svelte').Snippet} [subtitle]
* @property {import('svelte').Snippet} [children]
*/
$: alignClass =
/** @type {Props & { [key: string]: any }} */
let {
align = 'center',
pre,
title,
subtitle,
children,
...rest
} = $props()
let alignClass = $derived(
align === 'center'
? 'text-center items-center'
: align === 'left'
? 'text-left items-start'
: 'text-right items-end'
)
</script>
<hgroup
use:animateIn={{ slide: 24, fade: 0 }}
class={cn('z-10 flex flex-col px-3 ', alignClass, $$restProps.class)}
class={cn('z-10 flex flex-col px-3 ', alignClass, rest.class)}
>
<slot name="pre" />
{@render pre?.()}
<slot name="title">No title given!!!</slot>
{#if title}{@render title()}{:else}No title given!!!{/if}
<slot name="subtitle" />
{@render subtitle?.()}
<slot />
{@render children?.()}
</hgroup>

View file

@ -1,3 +1,4 @@
<!-- @migration-task Error while migrating Svelte code: $$props is used together with named props in a way that cannot be automatically migrated. -->
<script>
import clsx from 'clsx'
import PlayIcon from '~icons/mingcute/play-circle-line'

View file

@ -2,21 +2,28 @@
import ArrowRight from '~icons/mingcute/arrow-right-circle-line'
import { animateIn, formatDate } from '$lib/Helper.ts'
export let entry
let { entry } = $props()
$: link = `/news/${entry.slug}`
let link = $derived(`/news/${entry.slug}`)
</script>
<li class="flex" use:animateIn={{ fade: 0, slide: 24 }}>
<a href={link} class="w-full transition-transform hover:-translate-y-0.5">
<a
href={link}
class="w-full transition-transform hover:-translate-y-0.5"
>
<article
class="flex h-[100%] flex-col justify-between gap-3 rounded hover:outline-sky-500/80 md:flex-row md:rounded-3xl md:bg-gradient-to-tr md:from-cyan-500/10 md:to-transparent md:p-8 md:shadow-xl md:outline md:outline-1 md:outline-sky-500/30"
>
<div>
<div class="flex flex-col gap-4 font-medium text-slate-400">
<p class="font-bold text-slate-400">{formatDate(entry.date)}</p>
<p class="font-bold text-slate-400">
{formatDate(entry.date)}
</p>
</div>
<h2 class="title text-xl font-bold hover:text-slate-200 md:text-2xl lg:text-3xl">
<h2
class="title text-xl font-bold hover:text-slate-200 md:text-2xl lg:text-3xl"
>
{entry.title}
</h2>
</div>

View file

@ -1,64 +1,184 @@
<svg viewBox="0 330 1006.49 347.4685344827586" version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns="http://www.w3.org/2000/svg" style="width: 6rem; height: 2rem;" class={$$props.class} width="1006.49" height="347.4685344827586">
<defs>
<style>
.st0 {
fill: url(#a);
}
<script>
/** @type {{ [key: string]: any }} */
let { ...props } = $props()
</script>
.st1 {
fill: url(#b);
}
<svg
viewBox="0 330 1006.49 347.4685344827586"
version="1.1"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns="http://www.w3.org/2000/svg"
style="width: 6rem; height: 2rem;"
class={props.class}
width="1006.49"
height="347.4685344827586"
>
<defs>
<style>
.st0 {
fill: url(#a);
}
.st2 {
fill: url(#c);
}
.st1 {
fill: url(#b);
}
.st3 {
fill: url(#d);
}
.st2 {
fill: url(#c);
}
.st4 {
fill: url(#e);
}
.st3 {
fill: url(#d);
}
.st5 {
fill: url(#f);
}
.st4 {
fill: url(#e);
}
.st6 {
fill: url(#g);
}
.st5 {
fill: url(#f);
}
.st7 {
fill: url(#h);
}
.st6 {
fill: url(#g);
}
.st8 {
fill: url(#i);
}
</style><style class="darkreader darkreader--sync" media="screen"/>
<linearGradient gradientUnits="userSpaceOnUse" y2="491.29" x2="561.93" y1="593.85" x1="561.93" id="i">
<stop stop-color="#00a8f4" offset="0" style="--darkreader-inline-stopcolor: var(--darkreader-background-00a8f4, #0086c3);" data-darkreader-inline-stopcolor=""/>
<stop stop-color="#00e5d0" offset="1" style="--darkreader-inline-stopcolor: var(--darkreader-background-00e5d0, #00b7a6);" data-darkreader-inline-stopcolor=""/>
</linearGradient>
<linearGradient xlink:href="#i" y2="491.58" x2="630.69" y1="567.48" x1="630.69" id="c"/>
<linearGradient xlink:href="#i" y2="493.77" x2="480.34" y1="596.04" x1="480.34" id="a"/>
<linearGradient xlink:href="#i" y2="470.31" x2="392.86" y1="567.48" x1="392.86" id="b"/>
<linearGradient xlink:href="#i" y2="491.73" x2="804.79" y1="567.48" x1="804.79" id="f"/>
<linearGradient xlink:href="#i" y2="463.46" x2="883.97" y1="569.67" x1="883.97" id="g"/>
<linearGradient xlink:href="#i" y2="465.94" x2="671.56" y1="567.48" x1="671.56" id="d"/>
<linearGradient xlink:href="#i" y2="491.58" x2="724.23" y1="569.67" x1="724.23" id="e"/>
<linearGradient xlink:href="#i" y2="346.78" x2="202.09" y1="659.7" x1="202.09" id="h"/>
</defs>
<g>
<path d="M580.29,495c-5.59-2.48-12.07-3.71-19.45-3.71-4.86,0-9.4.36-13.62,1.09-4.22.73-7.94,1.82-11.15,3.28-3.2,1.46-5.73,3.3-7.58,5.54-1.85,2.24-2.77,4.95-2.77,8.16v84.5h21.27v-28.09c1.05.58,2.16,1.1,3.35,1.5,3.01,1.02,5.92,1.67,8.74,1.97,2.82.29,4.95.44,6.41.44,6.9,0,12.75-1.6,17.56-4.81,4.81-3.21,8.52-7.7,11.15-13.48,2.62-5.78,3.93-12.46,3.93-20.03,0-10.1-1.58-18.07-4.73-23.89-3.16-5.83-7.53-9.98-13.11-12.46ZM574.46,543.37c-1.31,3.16-3.23,5.42-5.75,6.77-2.53,1.36-5.73,2.04-9.62,2.04-1.75,0-3.74-.24-5.97-.73-2.24-.48-4.28-1.26-6.12-2.33v-34.67c0-1.46.99-2.64,2.99-3.57,1.99-.92,5.32-1.38,9.98-1.38,4.27,0,7.6.85,9.98,2.55,2.38,1.7,4.05,4.1,5.03,7.21.97,3.11,1.46,6.9,1.46,11.36,0,5.34-.66,9.59-1.97,12.75Z" class="st8"/>
<path d="M638.78,491.58c-4.57,0-9.11.61-13.62,1.82-4.52,1.22-8.52,2.57-12.02,4.08-3.5,1.51-5.93,2.84-7.28,4.01v66h21.71v-56.96c1.65-.29,3.69-.48,6.12-.58,2.43-.1,5-.12,7.72-.07,2.72.05,5.34.17,7.87.36,2.52.2,4.61.49,6.26.87v-15.88c-1.36-1.07-3.35-1.94-5.97-2.62-2.62-.68-6.22-1.02-10.78-1.02Z" class="st2"/>
<path d="M481.21,542.1l-15.66-48.33h-24.91l29.61,72.88c-.37,1.26-.77,2.39-1.21,3.39-1.17,2.67-2.65,4.56-4.44,5.68-1.8,1.12-3.91,1.68-6.34,1.68-1.46,0-2.87-.12-4.22-.36-1.36-.24-2.82-.66-4.37-1.24v17.63c1.65.78,3.64,1.41,5.97,1.89,2.33.48,4.71.73,7.14.73,3.5,0,6.9-.87,10.2-2.62,3.3-1.75,6.48-4.61,9.54-8.6,3.06-3.98,5.9-9.37,8.52-16.17l28.99-74.89h-23.31l-15.52,48.33Z" class="st0"/>
<polygon points="410.78 509.65 375.08 509.65 375.08 470.31 351.48 470.31 351.48 567.48 375.08 567.48 375.08 528.88 410.78 528.88 410.78 567.48 434.23 567.48 434.23 470.31 410.78 470.31 410.78 509.65" class="st1"/>
<path d="M825.12,494.28c-4.66-1.7-10.3-2.55-16.9-2.55-3.11,0-6.48.24-10.13.73-3.64.49-7.26,1.12-10.85,1.89-3.59.78-6.9,1.68-9.91,2.7-3.01,1.02-5.44,2.02-7.28,2.99v67.45h21.71v-55.36c1.17-.58,2.86-1.14,5.1-1.67,2.23-.53,4.47-.8,6.7-.8,2.62,0,4.86.32,6.7.95,1.84.63,3.3,1.51,4.37,2.62,1.07,1.12,1.87,2.45,2.4,4.01.53,1.56.8,3.26.8,5.1v45.16h21.71v-49.54c0-6.51-1.24-11.65-3.71-15.44-2.48-3.79-6.05-6.53-10.71-8.23Z" class="st5"/>
<path d="M899.27,463.46v31.53c-.93-.55-1.9-1.01-2.91-1.37-2.72-.97-5.34-1.58-7.87-1.82-2.53-.24-4.57-.36-6.12-.36-11.75,0-20.57,3.45-26.44,10.34-5.88,6.9-8.81,16.32-8.81,28.26,0,7.67.99,14.08,2.99,19.23,1.99,5.15,4.73,9.2,8.23,12.17,3.5,2.96,7.65,5.08,12.46,6.34,4.81,1.26,10.08,1.89,15.81,1.89,4.27,0,8.45-.46,12.53-1.38,4.08-.92,7.74-2.26,11-4.01,3.25-1.75,5.85-3.96,7.79-6.63,1.94-2.67,2.91-5.8,2.91-9.4v-84.79h-21.56ZM899.27,545.92c0,1.85-1.09,3.23-3.28,4.15-2.19.92-5.03,1.38-8.52,1.38-4.57,0-8.18-.85-10.85-2.55-2.67-1.7-4.59-4.08-5.75-7.14-1.17-3.06-1.75-6.72-1.75-11,0-5.44.7-9.76,2.11-12.97,1.41-3.2,3.52-5.49,6.34-6.85,2.82-1.36,6.26-2.04,10.34-2.04,1.84,0,3.76.27,5.75.8,1.99.54,3.86,1.29,5.61,2.26v33.95Z" class="st6"/>
<rect height="101.55" width="21.56" y="465.94" x="660.78" class="st3"/>
<path d="M750.3,498.72c-2.38-1.94-4.98-3.42-7.79-4.44-2.82-1.02-5.63-1.72-8.45-2.11-2.82-.39-5.39-.58-7.72-.58-6.61,0-12.55.68-17.85,2.04-5.3,1.36-9.2,2.82-11.73,4.37v19.23c2.72-2.14,6.24-3.96,10.56-5.46,4.32-1.5,8.62-2.26,12.89-2.26,5.24,0,9.3,1,12.17,2.99,2.86,1.99,4.3,5.22,4.3,9.69v4.68c-.7-.4-1.44-.8-2.26-1.19-2.48-1.17-5.27-2.09-8.38-2.77-3.11-.68-6.32-1.02-9.62-1.02-5.93,0-10.83.92-14.71,2.77-3.89,1.85-6.77,4.47-8.67,7.87-1.89,3.4-2.84,7.38-2.84,11.95,0,4.86.82,8.91,2.48,12.16,1.65,3.26,3.96,5.83,6.92,7.72,2.96,1.89,6.41,3.25,10.34,4.08,3.93.82,8.13,1.24,12.6,1.24,7.48,0,13.86-.58,19.16-1.75,5.29-1.17,9.35-3.03,12.16-5.61,2.82-2.57,4.23-5.95,4.23-10.13l.15-34.82c0-4.47-.73-8.21-2.19-11.22-1.46-3.01-3.38-5.49-5.75-7.43ZM733.7,553.35c-1.99.68-5.46,1.02-10.42,1.02-1.94,0-3.81-.39-5.61-1.17-1.8-.78-3.23-1.89-4.3-3.35-1.07-1.46-1.6-3.11-1.6-4.95,0-3.01,1.09-5.22,3.28-6.63,2.19-1.41,5.66-2.11,10.42-2.11,3.11,0,6.02.34,8.74,1.02.87.22,1.69.47,2.48.74v11.36c0,2.04-1,3.4-2.99,4.08Z" class="st4"/>
</g>
<path d="M311.03,491.55c-9.09-20.56-22.42-39.71-35.29-58.22-2.38-3.41-4.62-6.64-6.84-9.87-3.15-4.6-7.42-10.49-12.36-17.31-11.29-15.59-28.92-39.62-40.84-59.36v49.42c12.28,17.62,24.2,33.49,30.57,42.78,13.94,20.33,30.09,42,39.67,63.66,28.78,65.13-11.7,128.85-82.05,129.61h-1.26c-.18,0-.35,0-.53,0-.18,0-.35,0-.53,0h-1.26c-70.35-.76-110.84-64.48-82.05-129.61,9.58-21.67,25.72-43.33,39.67-63.66,6.36-9.28,18.28-25.16,30.57-42.78v-49.42c-11.92,19.75-29.55,43.78-40.84,59.36-4.94,6.81-9.21,12.7-12.36,17.31-2.22,3.23-4.46,6.45-6.84,9.87-12.88,18.52-26.21,37.67-35.29,58.22-8.83,19.97-12.7,40.38-11.49,60.65,1.16,19.65,7.28,38.57,17.68,54.73,10.28,15.97,24.74,29.21,41.81,38.29,17.64,9.37,37.44,14.25,58.87,14.48.52,0,1.03,0,1.56,0,.18,0,.35,0,.53,0,.18,0,.35,0,.53,0,.52,0,1.03,0,1.56,0,21.43-.23,41.23-5.1,58.87-14.48,17.07-9.08,31.52-22.32,41.81-38.29,10.4-16.16,16.52-35.08,17.68-54.73,1.2-20.27-2.67-40.68-11.49-60.65Z" class="st7"/>
.st7 {
fill: url(#h);
}
.st8 {
fill: url(#i);
}
</style><style
class="darkreader darkreader--sync"
media="screen"
></style>
<linearGradient
gradientUnits="userSpaceOnUse"
y2="491.29"
x2="561.93"
y1="593.85"
x1="561.93"
id="i"
>
<stop
stop-color="#00a8f4"
offset="0"
style="--darkreader-inline-stopcolor: var(--darkreader-background-00a8f4, #0086c3);"
data-darkreader-inline-stopcolor=""
/>
<stop
stop-color="#00e5d0"
offset="1"
style="--darkreader-inline-stopcolor: var(--darkreader-background-00e5d0, #00b7a6);"
data-darkreader-inline-stopcolor=""
/>
</linearGradient>
<linearGradient
xlink:href="#i"
y2="491.58"
x2="630.69"
y1="567.48"
x1="630.69"
id="c"
/>
<linearGradient
xlink:href="#i"
y2="493.77"
x2="480.34"
y1="596.04"
x1="480.34"
id="a"
/>
<linearGradient
xlink:href="#i"
y2="470.31"
x2="392.86"
y1="567.48"
x1="392.86"
id="b"
/>
<linearGradient
xlink:href="#i"
y2="491.73"
x2="804.79"
y1="567.48"
x1="804.79"
id="f"
/>
<linearGradient
xlink:href="#i"
y2="463.46"
x2="883.97"
y1="569.67"
x1="883.97"
id="g"
/>
<linearGradient
xlink:href="#i"
y2="465.94"
x2="671.56"
y1="567.48"
x1="671.56"
id="d"
/>
<linearGradient
xlink:href="#i"
y2="491.58"
x2="724.23"
y1="569.67"
x1="724.23"
id="e"
/>
<linearGradient
xlink:href="#i"
y2="346.78"
x2="202.09"
y1="659.7"
x1="202.09"
id="h"
/>
</defs>
<g>
<path
d="M580.29,495c-5.59-2.48-12.07-3.71-19.45-3.71-4.86,0-9.4.36-13.62,1.09-4.22.73-7.94,1.82-11.15,3.28-3.2,1.46-5.73,3.3-7.58,5.54-1.85,2.24-2.77,4.95-2.77,8.16v84.5h21.27v-28.09c1.05.58,2.16,1.1,3.35,1.5,3.01,1.02,5.92,1.67,8.74,1.97,2.82.29,4.95.44,6.41.44,6.9,0,12.75-1.6,17.56-4.81,4.81-3.21,8.52-7.7,11.15-13.48,2.62-5.78,3.93-12.46,3.93-20.03,0-10.1-1.58-18.07-4.73-23.89-3.16-5.83-7.53-9.98-13.11-12.46ZM574.46,543.37c-1.31,3.16-3.23,5.42-5.75,6.77-2.53,1.36-5.73,2.04-9.62,2.04-1.75,0-3.74-.24-5.97-.73-2.24-.48-4.28-1.26-6.12-2.33v-34.67c0-1.46.99-2.64,2.99-3.57,1.99-.92,5.32-1.38,9.98-1.38,4.27,0,7.6.85,9.98,2.55,2.38,1.7,4.05,4.1,5.03,7.21.97,3.11,1.46,6.9,1.46,11.36,0,5.34-.66,9.59-1.97,12.75Z"
class="st8"
/>
<path
d="M638.78,491.58c-4.57,0-9.11.61-13.62,1.82-4.52,1.22-8.52,2.57-12.02,4.08-3.5,1.51-5.93,2.84-7.28,4.01v66h21.71v-56.96c1.65-.29,3.69-.48,6.12-.58,2.43-.1,5-.12,7.72-.07,2.72.05,5.34.17,7.87.36,2.52.2,4.61.49,6.26.87v-15.88c-1.36-1.07-3.35-1.94-5.97-2.62-2.62-.68-6.22-1.02-10.78-1.02Z"
class="st2"
/>
<path
d="M481.21,542.1l-15.66-48.33h-24.91l29.61,72.88c-.37,1.26-.77,2.39-1.21,3.39-1.17,2.67-2.65,4.56-4.44,5.68-1.8,1.12-3.91,1.68-6.34,1.68-1.46,0-2.87-.12-4.22-.36-1.36-.24-2.82-.66-4.37-1.24v17.63c1.65.78,3.64,1.41,5.97,1.89,2.33.48,4.71.73,7.14.73,3.5,0,6.9-.87,10.2-2.62,3.3-1.75,6.48-4.61,9.54-8.6,3.06-3.98,5.9-9.37,8.52-16.17l28.99-74.89h-23.31l-15.52,48.33Z"
class="st0"
/>
<polygon
points="410.78 509.65 375.08 509.65 375.08 470.31 351.48 470.31 351.48 567.48 375.08 567.48 375.08 528.88 410.78 528.88 410.78 567.48 434.23 567.48 434.23 470.31 410.78 470.31 410.78 509.65"
class="st1"
/>
<path
d="M825.12,494.28c-4.66-1.7-10.3-2.55-16.9-2.55-3.11,0-6.48.24-10.13.73-3.64.49-7.26,1.12-10.85,1.89-3.59.78-6.9,1.68-9.91,2.7-3.01,1.02-5.44,2.02-7.28,2.99v67.45h21.71v-55.36c1.17-.58,2.86-1.14,5.1-1.67,2.23-.53,4.47-.8,6.7-.8,2.62,0,4.86.32,6.7.95,1.84.63,3.3,1.51,4.37,2.62,1.07,1.12,1.87,2.45,2.4,4.01.53,1.56.8,3.26.8,5.1v45.16h21.71v-49.54c0-6.51-1.24-11.65-3.71-15.44-2.48-3.79-6.05-6.53-10.71-8.23Z"
class="st5"
/>
<path
d="M899.27,463.46v31.53c-.93-.55-1.9-1.01-2.91-1.37-2.72-.97-5.34-1.58-7.87-1.82-2.53-.24-4.57-.36-6.12-.36-11.75,0-20.57,3.45-26.44,10.34-5.88,6.9-8.81,16.32-8.81,28.26,0,7.67.99,14.08,2.99,19.23,1.99,5.15,4.73,9.2,8.23,12.17,3.5,2.96,7.65,5.08,12.46,6.34,4.81,1.26,10.08,1.89,15.81,1.89,4.27,0,8.45-.46,12.53-1.38,4.08-.92,7.74-2.26,11-4.01,3.25-1.75,5.85-3.96,7.79-6.63,1.94-2.67,2.91-5.8,2.91-9.4v-84.79h-21.56ZM899.27,545.92c0,1.85-1.09,3.23-3.28,4.15-2.19.92-5.03,1.38-8.52,1.38-4.57,0-8.18-.85-10.85-2.55-2.67-1.7-4.59-4.08-5.75-7.14-1.17-3.06-1.75-6.72-1.75-11,0-5.44.7-9.76,2.11-12.97,1.41-3.2,3.52-5.49,6.34-6.85,2.82-1.36,6.26-2.04,10.34-2.04,1.84,0,3.76.27,5.75.8,1.99.54,3.86,1.29,5.61,2.26v33.95Z"
class="st6"
/>
<rect
height="101.55"
width="21.56"
y="465.94"
x="660.78"
class="st3"
/>
<path
d="M750.3,498.72c-2.38-1.94-4.98-3.42-7.79-4.44-2.82-1.02-5.63-1.72-8.45-2.11-2.82-.39-5.39-.58-7.72-.58-6.61,0-12.55.68-17.85,2.04-5.3,1.36-9.2,2.82-11.73,4.37v19.23c2.72-2.14,6.24-3.96,10.56-5.46,4.32-1.5,8.62-2.26,12.89-2.26,5.24,0,9.3,1,12.17,2.99,2.86,1.99,4.3,5.22,4.3,9.69v4.68c-.7-.4-1.44-.8-2.26-1.19-2.48-1.17-5.27-2.09-8.38-2.77-3.11-.68-6.32-1.02-9.62-1.02-5.93,0-10.83.92-14.71,2.77-3.89,1.85-6.77,4.47-8.67,7.87-1.89,3.4-2.84,7.38-2.84,11.95,0,4.86.82,8.91,2.48,12.16,1.65,3.26,3.96,5.83,6.92,7.72,2.96,1.89,6.41,3.25,10.34,4.08,3.93.82,8.13,1.24,12.6,1.24,7.48,0,13.86-.58,19.16-1.75,5.29-1.17,9.35-3.03,12.16-5.61,2.82-2.57,4.23-5.95,4.23-10.13l.15-34.82c0-4.47-.73-8.21-2.19-11.22-1.46-3.01-3.38-5.49-5.75-7.43ZM733.7,553.35c-1.99.68-5.46,1.02-10.42,1.02-1.94,0-3.81-.39-5.61-1.17-1.8-.78-3.23-1.89-4.3-3.35-1.07-1.46-1.6-3.11-1.6-4.95,0-3.01,1.09-5.22,3.28-6.63,2.19-1.41,5.66-2.11,10.42-2.11,3.11,0,6.02.34,8.74,1.02.87.22,1.69.47,2.48.74v11.36c0,2.04-1,3.4-2.99,4.08Z"
class="st4"
/>
</g>
<path
d="M311.03,491.55c-9.09-20.56-22.42-39.71-35.29-58.22-2.38-3.41-4.62-6.64-6.84-9.87-3.15-4.6-7.42-10.49-12.36-17.31-11.29-15.59-28.92-39.62-40.84-59.36v49.42c12.28,17.62,24.2,33.49,30.57,42.78,13.94,20.33,30.09,42,39.67,63.66,28.78,65.13-11.7,128.85-82.05,129.61h-1.26c-.18,0-.35,0-.53,0-.18,0-.35,0-.53,0h-1.26c-70.35-.76-110.84-64.48-82.05-129.61,9.58-21.67,25.72-43.33,39.67-63.66,6.36-9.28,18.28-25.16,30.57-42.78v-49.42c-11.92,19.75-29.55,43.78-40.84,59.36-4.94,6.81-9.21,12.7-12.36,17.31-2.22,3.23-4.46,6.45-6.84,9.87-12.88,18.52-26.21,37.67-35.29,58.22-8.83,19.97-12.7,40.38-11.49,60.65,1.16,19.65,7.28,38.57,17.68,54.73,10.28,15.97,24.74,29.21,41.81,38.29,17.64,9.37,37.44,14.25,58.87,14.48.52,0,1.03,0,1.56,0,.18,0,.35,0,.53,0,.18,0,.35,0,.53,0,.52,0,1.03,0,1.56,0,21.43-.23,41.23-5.1,58.87-14.48,17.07-9.08,31.52-22.32,41.81-38.29,10.4-16.16,16.52-35.08,17.68-54.73,1.2-20.27-2.67-40.68-11.49-60.65Z"
class="st7"
/>
</svg>

Before

Width:  |  Height:  |  Size: 6.7 KiB

After

Width:  |  Height:  |  Size: 6.9 KiB

View file

@ -1,18 +1,27 @@
export async function getNews() {
const posts = Object.entries(import.meta.glob('/src/content/news/*.md', { eager: true }))
const posts = Object.entries(
import.meta.glob('/src/content/news/*.md', { eager: true })
)
.flatMap(([path, { metadata }]) => {
const slug = path.split('/').at(-1)?.replace('.md', '')
const rawDate = metadata.date
const date = new Date(typeof rawDate === 'number' ? rawDate * 1000 : rawDate).getTime()
const date = new Date(
typeof rawDate === 'number' ? rawDate * 1000 : rawDate
).getTime()
if (!slug || !path || Number.isNaN(date)) {
console.error(`Invalid file ${path} ${JSON.stringify({ ...metadata, date, slug })}`)
console.error(
`Invalid file ${path} ${JSON.stringify({ ...metadata, date, slug })}`
)
return []
}
return { slug, ...metadata, date }
})
.sort(({ date: a }, { date: b }) => new Date(b).getTime() - new Date(a).getTime())
.sort(
({ date: a }, { date: b }) =>
new Date(b).getTime() - new Date(a).getTime()
)
return posts
}

View file

@ -4,12 +4,19 @@
import './styles.css'
import '@fontsource-variable/inter'
import '@fontsource/ibm-plex-mono/500.css'
/**
* @typedef {Object} Props
* @property {import('svelte').Snippet} [children]
*/
/** @type {Props} */
let { children } = $props()
</script>
<Navbar />
<main class="mx-auto flex min-h-screen w-full flex-col">
<slot />
{@render children?.()}
</main>
<Footer />

View file

@ -9,7 +9,9 @@ export const load = async ({ fetch }) => ({
.then((response) => response.json())
.then((news) => news.slice(0, 3)),
sponsors: (await fetch('/api/sponsors').then((resposne) => resposne.json())) as SponsorsRanked,
sponsors: (await fetch('/api/sponsors').then((resposne) =>
resposne.json()
)) as SponsorsRanked,
communityProfiles: (await fetch('/api/community').then((response) =>
response.json()
@ -20,7 +22,11 @@ function getHeroBackgroundTiles() {
const workspacesPerRow = 4
const workspaceHeight = 240
const gapLength = 32
const colors = [baseColors.blue[500], baseColors.cyan[400], baseColors.sky[500]]
const colors = [
baseColors.blue[500],
baseColors.cyan[400],
baseColors.sky[500]
]
const images = [
'/imgs/chan/joy.svg',
'/imgs/chan/surprise.svg',
@ -28,9 +34,13 @@ function getHeroBackgroundTiles() {
'/imgs/waylnad.webp'
]
const leftColumns = Array.from({ length: 3 }, () => generateRow(workspacesPerRow))
const leftColumns = Array.from({ length: 3 }, () =>
generateRow(workspacesPerRow)
)
const rightColumns = Array.from({ length: 3 }, () => generateRow(workspacesPerRow))
const rightColumns = Array.from({ length: 3 }, () =>
generateRow(workspacesPerRow)
)
/** Used to transform the rows by their own lenght*/
const height = workspacesPerRow * (workspaceHeight + gapLength)
@ -57,13 +67,19 @@ function getHeroBackgroundTiles() {
}
function generateTiles() {
const result = Math.random() > 0.5 ? [generateTile()] : [generateTile(), generateTile()]
const result =
Math.random() > 0.5
? [generateTile()]
: [generateTile(), generateTile()]
return result
}
function generateTile() {
return { color: getRandomColor(), image: Math.random() > 0.7 ? getRandomImage() : undefined }
return {
color: getRandomColor(),
image: Math.random() > 0.7 ? getRandomImage() : undefined
}
}
/** @returns {string} */

View file

@ -10,7 +10,7 @@
import Sponsors from './home-slices/SponsorsSlice.svelte'
import CommunitySlice from './home-slices/CommunitySlice.svelte'
export let data
let { data } = $props()
</script>
<div class="overflow-hidden">
@ -39,10 +39,16 @@
<svelte:head>
<title>Hyprland</title>
<meta name="description" content="Hyprland - Dynamic tiling Wayland compositor with the looks." />
<meta
name="description"
content="Hyprland - Dynamic tiling Wayland compositor with the looks."
/>
<meta
property="og:description"
content="Hyprland - Dynamic tiling Wayland compositor with the looks."
/>
<meta property="og:title" content="Hyprland: Dynamic tiling window compositor with the looks" />
<meta
property="og:title"
content="Hyprland: Dynamic tiling window compositor with the looks"
/>
</svelte:head>

View file

@ -1,4 +1,6 @@
<script lang="ts">
import { run } from 'svelte/legacy'
import { navigating, page } from '$app/stores'
import GithubIcon from '~icons/ri/github-fill'
import DiscordIcon from '~icons/prime/discord'
@ -9,9 +11,11 @@
import { discordLink } from '$lib/constants'
import { forgejoLink } from '$lib/constants'
let isExpanded = false
let isExpanded = $state(false)
$: if ($navigating) isExpanded = false
run(() => {
if ($navigating) isExpanded = false
})
function toggleExpanded() {
isExpanded = !isExpanded
@ -30,7 +34,7 @@
<button
class="z-50 rounded-full bg-black/50 p-2 md:backdrop-blur nav:hidden"
on:click={toggleExpanded}
onclick={toggleExpanded}
aria-label="Open Navigation"
>
{#if isExpanded}
@ -60,19 +64,36 @@
<li>
<a href="https://account.hypr.land">Account</a>
</li>
<li aria-current={$page.url.pathname === '/about' ? 'page' : undefined}>
<li
aria-current={$page.url.pathname === '/about'
? 'page'
: undefined}
>
<a href="/hall_of_fame">Hall of fame</a>
</li>
<li aria-current={$page.url.pathname === '/news' ? 'page' : undefined}>
<li
aria-current={$page.url.pathname === '/news'
? 'page'
: undefined}
>
<a href="/news">News</a>
</li>
<li aria-current={$page.url.pathname === '/plugins' ? 'page' : undefined}>
<li
aria-current={$page.url.pathname === '/plugins'
? 'page'
: undefined}
>
<a href="/plugins">Plugins</a>
</li>
</ul>
<ul class="flex flex-row items-center gap-3 px-4">
<li>
<a href={discordLink} class="social-icon" aria-label="Join us on Discord" target="_blank">
<a
href={discordLink}
class="social-icon"
aria-label="Join us on Discord"
target="_blank"
>
<DiscordIcon class="h-full w-full" />
</a>
</li>
@ -99,7 +120,11 @@
</ul>
<ul class="flex gap-4">
<li aria-current={$page.url.pathname === '/support' ? 'page' : undefined}>
<li
aria-current={$page.url.pathname === '/support'
? 'page'
: undefined}
>
<a
class="rounded-full px-4 py-2 outline outline-cyan-500 hover:outline-cyan-200"
href="/support">Support us</a

View file

@ -36,14 +36,22 @@ const extraProfiles: CommunityProfile[] = [
}
]
const validSizes = [16, 20, 24, 32, 40, 48, 64, 80, 96, 100, 128, 160, 240, 320, 640]
const validSizes = [
16, 20, 24, 32, 40, 48, 64, 80, 96, 100, 128, 160, 240, 320, 640
]
export async function GET() {
const allProfiles = [...profiles.filter(({ image }) => !!image), ...extraProfiles]
const allProfiles = [
...profiles.filter(({ image }) => !!image),
...extraProfiles
]
.map(({ image, size, ...profile }) => ({
...profile,
size,
image: image + '?size=' + validSizes.find((_, index) => size <= validSizes[index])
image:
image +
'?size=' +
validSizes.find((_, index) => size <= validSizes[index])
}))
.sort(({ size: a }, { size: b }) => b - a)

View file

@ -5,10 +5,10 @@
import Contest from './Contest.svelte'
import FamedRice from './FamedRice.svelte'
export let data
let { data } = $props()
</script>
<div class="fancy-top-gradient" />
<div class="fancy-top-gradient"></div>
<section>
<div class="hero-wrapper">
@ -19,10 +19,15 @@
</div>
</div>
<Title>
<TitleHeading slot="title" class="">Hall of Fame</TitleHeading>
<TitleSubtile slot="subtitle" class="class-w-[40ch]">
The chronicles of the triumphant from bygone rice contests held within our Discord
</TitleSubtile>
{#snippet title()}
<TitleHeading class="">Hall of Fame</TitleHeading>
{/snippet}
{#snippet subtitle()}
<TitleSubtile class="class-w-[40ch]">
The chronicles of the triumphant from bygone rice contests
held within our Discord
</TitleSubtile>
{/snippet}
</Title>
</div>
@ -45,7 +50,10 @@
<svelte:head>
<title>Hall of Fame | Hyprland</title>
<meta name="description" content="The winners from Hyprlands rice contests" />
<meta
name="description"
content="The winners from Hyprlands rice contests"
/>
<meta property="og:title" content="Hyprland's Hall of Fame" />
<meta
property="og:description"

View file

@ -1,15 +1,19 @@
<script>
import { inview } from 'svelte-inview'
/** @type {string} */
export let name
/** @type {number} */
export let number
/** @type {string} */
export let date
/**
* @typedef {Object} Props
* @property {string} name
* @property {number} number
* @property {string} date
* @property {import('svelte').Snippet} [children]
*/
/** @type {Props} */
let { name, number, date, children } = $props()
/** Used to show the background gradient. Show if user scrolls, but do not disable when the user scrolls past from the top. */
let enabled = false
let enabled = $state(false)
function setEnabled({ detail }) {
// Show the effect when the user scrolls in and keep it enabled until the user scrolls up from it again
@ -31,10 +35,16 @@
<header
class="flex flex-col items-center gap-2 p-6 mix-blend-color-dodge"
use:inview={{ threshold: 0.45 }}
on:inview_change={setEnabled}
oninview_change={setEnabled}
>
<div class="text-xl font-bold text-neutral-300/80 sm:text-2xl">Contest #{number}</div>
<h2 class="text-center text-6xl font-bold text-neutral-200/80 sm:text-9xl">{name}</h2>
<div class="text-xl font-bold text-neutral-300/80 sm:text-2xl">
Contest #{number}
</div>
<h2
class="text-center text-6xl font-bold text-neutral-200/80 sm:text-9xl"
>
{name}
</h2>
<div
class="mt-2 rounded-full bg-slate-100/5 px-4 py-1 text-center text-xl font-bold text-slate-200/80 shadow"
>
@ -48,7 +58,7 @@
</div>
<div class="flex flex-col gap-40">
<slot />
{@render children?.()}
</div>
</section>
@ -75,7 +85,10 @@
height: 300px;
width: 100%;
z-index: -10;
background: linear-gradient(theme(colors.black / 40%), transparent);
background: linear-gradient(
theme(colors.black / 40%),
transparent
);
mix-blend-mode: color-burn;
border-radius: inherit;
}
@ -93,25 +106,66 @@
transform-origin: top;
}
.background {
--c1: color-mix(in hsl shorter hue, var(--color), hsl(0, 100%, 0%) 10%);
--c2: color-mix(in hsl shorter hue, var(--color), hsl(0, 100%, 0%) 15%);
--c3: color-mix(in hsl shorter hue, var(--color), hsl(0, 100%, 0%) 20%);
--c4: color-mix(in hsl shorter hue, var(--color), hsl(0, 100%, 0%) 30%);
--c1: color-mix(
in hsl shorter hue,
var(--color),
hsl(0, 100%, 0%) 10%
);
--c2: color-mix(
in hsl shorter hue,
var(--color),
hsl(0, 100%, 0%) 15%
);
--c3: color-mix(
in hsl shorter hue,
var(--color),
hsl(0, 100%, 0%) 20%
);
--c4: color-mix(
in hsl shorter hue,
var(--color),
hsl(0, 100%, 0%) 30%
);
position: absolute;
translate: -50% -50%;
width: 100%;
height: 1000px;
z-index: -10;
background: url('/imgs/grain.webp'),
radial-gradient(140px 100px at 50% 45%, var(--color), transparent),
background:
url('/imgs/grain.webp'),
radial-gradient(
140px 100px at 50% 45%,
var(--color),
transparent
),
radial-gradient(145px 110px at 50% 45%, var(--c1), transparent),
radial-gradient(210px 140px, var(--c2, theme(colors.blue.600)), transparent),
radial-gradient(300px 200px, var(--c2, theme(colors.sky.600)), transparent),
radial-gradient(600px 220px, var(--c3, theme(colors.blue.700)), transparent),
radial-gradient(1100px 420px, var(--c4, theme(colors.blue.800 / 60%)), transparent);
radial-gradient(
210px 140px,
var(--c2, theme(colors.blue.600)),
transparent
),
radial-gradient(
300px 200px,
var(--c2, theme(colors.sky.600)),
transparent
),
radial-gradient(
600px 220px,
var(--c3, theme(colors.blue.700)),
transparent
),
radial-gradient(
1100px 420px,
var(--c4, theme(colors.blue.800 / 60%)),
transparent
);
mask-image: radial-gradient(100% 100% at 50% 50%, black 20%, transparent 50%);
mask-image: radial-gradient(
100% 100% at 50% 50%,
black 20%,
transparent 50%
);
opacity: 0.25;
scale: 0.95 1;

View file

@ -4,30 +4,47 @@
import PlayIconFill from '~icons/mingcute/play-fill'
import PlayIconOutline from '~icons/mingcute/play-line'
export let name: string
export let creator: string
export let dotfilesLink: string
export let creatorProfilePicture: string
export let thumbnail: string
export let video: string | undefined = undefined
/**
* Specify the blurred background image to be used.
* Defaults to `"generated_<thumbnail>"` * */
export let blurredThumbnail: string | undefined = undefined
export let pretitel: string
interface Props {
name: string
creator: string
dotfilesLink: string
creatorProfilePicture: string
thumbnail: string
video?: string | undefined
/**
* Specify the blurred background image to be used.
* Defaults to `"generated_<thumbnail>"` * */
blurredThumbnail?: string | undefined
pretitel: string
[key: string]: any
}
let toShow: 'thumbnail' | 'video' = 'thumbnail'
let {
name,
creator,
dotfilesLink,
creatorProfilePicture,
thumbnail,
video = undefined,
blurredThumbnail = undefined,
pretitel,
...rest
}: Props = $props()
let toShow: 'thumbnail' | 'video' = $state('thumbnail')
let background = blurredThumbnail ?? getGeneratedPath(thumbnail)
</script>
<div
class="flex flex-col items-center gap-10 px-4 {$$restProps.class}"
class="flex flex-col items-center gap-10 px-4 {rest.class}"
style:--bg="url('{background}')"
>
<div class="flex flex-col items-center justify-center">
<div class="flex items-center gap-2">
<div class="relative mb-2 rounded-full px-3 py-1 text-lg font-bold">
<div
class="relative mb-2 rounded-full px-3 py-1 text-lg font-bold"
>
{pretitel}
</div>
</div>
@ -47,7 +64,9 @@
alt={creator + ' profile picture'}
loading="lazy"
/>
<div class="font-medium text-slate-300 transition-colors group-hover:text-white">
<div
class="font-medium text-slate-300 transition-colors group-hover:text-white"
>
{creator}
</div>
</a>
@ -57,24 +76,37 @@
<div class="rice group relative" class:hasVideo={video}>
{#if toShow === 'thumbnail'}
{#if video}
<button on:click={() => (toShow = 'video')}>
<img src={thumbnail} alt={`${name} by ${creator} thumbnail`} class="" loading="lazy" />
<button onclick={() => (toShow = 'video')}>
<img
src={thumbnail}
alt={`${name} by ${creator} thumbnail`}
class=""
loading="lazy"
/>
</button>
<button
class="absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 rounded-full bg-gray-900/60 p-4 text-white/80 opacity-0 outline outline-gray-100/10 duration-300 hover:scale-105 hover:bg-gray-900/80 hover:text-white group-hover:opacity-100"
on:click={() => (toShow = 'video')}><PlayIconFill class="size-8 " /></button
onclick={() => (toShow = 'video')}
><PlayIconFill class="size-8 " /></button
>
<button
class="absolute bottom-6 left-6 rounded-full bg-gray-900/60 p-2 text-white/80 outline outline-gray-100/10 duration-300 hover:scale-105 hover:bg-gray-900/80 hover:text-white"
on:click={() => (toShow = 'video')}><PlayIconOutline class="size-5 " /></button
onclick={() => (toShow = 'video')}
><PlayIconOutline class="size-5 " /></button
>
{:else}
<img src={thumbnail} alt={`${name} by ${creator} thumbnail`} class="" loading="lazy" />
<img
src={thumbnail}
alt={`${name} by ${creator} thumbnail`}
class=""
loading="lazy"
/>
{/if}
{:else if toShow === 'video'}
<!-- svelte-ignore a11y-media-has-caption -->
<video autoplay controls poster={thumbnail} src={video} />
<!-- svelte-ignore a11y_media_has_caption -->
<video autoplay controls poster={thumbnail} src={video}
></video>
{/if}
</div>
<!-- blur background -->
@ -85,7 +117,7 @@
alt={`${name} by ${creator} thumbnail`}
loading="lazy"
/>
<div class="grain_" />
<div class="grain_"></div>
</div>
</div>
@ -155,14 +187,19 @@
contain: layout size style paint;
background-blend-mode: hard-light;
mask-image: radial-gradient(farthest-side, black 80%, transparent);
mask-image: radial-gradient(
farthest-side,
black 80%,
transparent
);
background-image: url('/imgs/grain.webp');
}
.title_ {
filter: saturate(1.5) brightness(1);
@apply relative -mt-1 mb-5 py-1 text-4xl font-bold text-transparent sm:text-6xl;
background-image: linear-gradient(
background-image:
linear-gradient(
-195deg,
theme(colors.white / 80%) 50%,
rgba(255, 255, 255, 0.4)

View file

@ -34,29 +34,35 @@ export const contests: Contest[] = [
name: '𝕽𝖎𝖛𝖊𝖓𝖉𝖊𝖑𝖑',
creator: 'zacoons',
pretitel: '#1',
dotfilesLink: 'https://codeberg.org/zacoons/rivendell-hyprdots',
dotfilesLink:
'https://codeberg.org/zacoons/rivendell-hyprdots',
creatorProfilePicture:
'https://codeberg.org/avatars/c4669a53a4de58a5f700fa461405b1fd151d90da48cf927639e3855570e0ca49?size=24',
thumbnail: '/ricing_competitions/4/zacoons.webp',
video: 'https://dl.hypr.land/contest-videos/hyprrice_zacoons.mp4'
video:
'https://dl.hypr.land/contest-videos/hyprrice_zacoons.mp4'
},
{
name: '𝕯𝖎𝖘𝖓𝖆𝖞 𝖙𝖞𝖕𝖊 𝖘𝖍𝖎',
creator: 'VDawg',
pretitel: '#2',
dotfilesLink: 'https://github.com/vdawg-git/fantasy-rice',
creatorProfilePicture: 'https://avatars.githubusercontent.com/u/28539403?s=24&v=4',
creatorProfilePicture:
'https://avatars.githubusercontent.com/u/28539403?s=24&v=4',
thumbnail: '/ricing_competitions/4/vdawg.webp',
video: 'https://dl.hypr.land/contest-videos/disnay_type_shit_vdawg.mp4'
video:
'https://dl.hypr.land/contest-videos/disnay_type_shit_vdawg.mp4'
},
{
name: 'Duskhide',
creator: 'Flafy',
pretitel: '#3',
dotfilesLink: 'https://github.com/flafydev/fantasy_rice',
creatorProfilePicture: 'https://avatars.githubusercontent.com/u/44374434?s=24&v=4',
creatorProfilePicture:
'https://avatars.githubusercontent.com/u/44374434?s=24&v=4',
thumbnail: '/ricing_competitions/4/flafy.webp',
video: 'https://dl.hypr.land/contest-videos/fantasy_flafy.webm'
video:
'https://dl.hypr.land/contest-videos/fantasy_flafy.webm'
}
]
},
@ -71,7 +77,8 @@ export const contests: Contest[] = [
creator: 'Flafy',
pretitel: '#1',
dotfilesLink: 'https://github.com/flafydev/nixos-config/',
creatorProfilePicture: 'https://avatars.githubusercontent.com/u/44374434?s=24&v=4',
creatorProfilePicture:
'https://avatars.githubusercontent.com/u/44374434?s=24&v=4',
thumbnail: '/ricing_competitions/3/flafy.webp',
video: 'https://dl.hypr.land/videos/flafy-3-space.mp4'
},
@ -79,8 +86,10 @@ export const contests: Contest[] = [
name: 'Globes',
creator: 'Aylur',
pretitel: '#2',
dotfilesLink: 'https://github.com/Aylur/dotfiles/tree/ags-pre-ts',
creatorProfilePicture: 'https://avatars.githubusercontent.com/u/104676705?s=24&v=4',
dotfilesLink:
'https://github.com/Aylur/dotfiles/tree/ags-pre-ts',
creatorProfilePicture:
'https://avatars.githubusercontent.com/u/104676705?s=24&v=4',
thumbnail: '/ricing_competitions/3/aylur.webp',
video: 'https://dl.hypr.land/videos/aylur-3-space.mp4'
},
@ -89,9 +98,11 @@ export const contests: Contest[] = [
creator: 'VDawg',
pretitel: '#3',
dotfilesLink: 'https://github.com/vdawg-git/space_dots',
creatorProfilePicture: 'https://avatars.githubusercontent.com/u/28539403?s=24&v=4',
creatorProfilePicture:
'https://avatars.githubusercontent.com/u/28539403?s=24&v=4',
thumbnail: '/ricing_competitions/3/vdawg.webp',
video: 'https://dl.hypr.land/contest-videos/golden_era_vdawg.mp4'
video:
'https://dl.hypr.land/contest-videos/golden_era_vdawg.mp4'
}
]
},
@ -106,8 +117,10 @@ export const contests: Contest[] = [
name: 'Hybrid Summer',
creator: 'end_4',
pretitel: '#1',
dotfilesLink: 'https://github.com/end-4/dots-hyprland/tree/archive/hybrid-summer',
creatorProfilePicture: 'https://avatars.githubusercontent.com/u/97237370?s=24&v=4',
dotfilesLink:
'https://github.com/end-4/dots-hyprland/tree/archive/hybrid-summer',
creatorProfilePicture:
'https://avatars.githubusercontent.com/u/97237370?s=24&v=4',
thumbnail: '/ricing_competitions/2/end_4.webp',
video: 'https://dl.hypr.land/videos/end4-2-summer.mp4'
},
@ -115,8 +128,10 @@ export const contests: Contest[] = [
name: 'Unnamed',
creator: 'Flafy',
pretitel: '#2',
dotfilesLink: 'https://github.com/FlafyDev/flutter_background_bar',
creatorProfilePicture: 'https://avatars.githubusercontent.com/u/44374434?s=24&v=4',
dotfilesLink:
'https://github.com/FlafyDev/flutter_background_bar',
creatorProfilePicture:
'https://avatars.githubusercontent.com/u/44374434?s=24&v=4',
thumbnail: '/ricing_competitions/2/flafy.webp',
video: 'https://dl.hypr.land/videos/flafy-2-summer.mp4'
},
@ -124,8 +139,10 @@ export const contests: Contest[] = [
name: 'Day and Night',
creator: 'Mathisbuilder',
pretitel: '#3',
dotfilesLink: 'https://github.com/MathisP75/summer-day-and-night',
creatorProfilePicture: 'https://avatars.githubusercontent.com/u/98901170?s=24&v=4',
dotfilesLink:
'https://github.com/MathisP75/summer-day-and-night',
creatorProfilePicture:
'https://avatars.githubusercontent.com/u/98901170?s=24&v=4',
thumbnail: '/ricing_competitions/2/day-night.webp'
}
]
@ -141,8 +158,10 @@ export const contests: Contest[] = [
name: 'Unnamed',
creator: 'Flafy',
pretitel: '#1',
dotfilesLink: 'https://github.com/FlafyDev/flutter_workspaces_2',
creatorProfilePicture: 'https://avatars.githubusercontent.com/u/25975326?s=24&v=4',
dotfilesLink:
'https://github.com/FlafyDev/flutter_workspaces_2',
creatorProfilePicture:
'https://avatars.githubusercontent.com/u/25975326?s=24&v=4',
thumbnail: '/ricing_competitions/1/flafy.webp',
video: 'https://dl.hypr.land/videos/flafy-1-winter.mp4'
},
@ -150,8 +169,10 @@ export const contests: Contest[] = [
name: 'Aurora',
creator: 'flick0',
pretitel: '#2 (ex aequo)',
dotfilesLink: 'https://github.com/flick0/dotfiles/tree/aurora',
creatorProfilePicture: 'https://avatars.githubusercontent.com/u/77581181?s=24&v=4',
dotfilesLink:
'https://github.com/flick0/dotfiles/tree/aurora',
creatorProfilePicture:
'https://avatars.githubusercontent.com/u/77581181?s=24&v=4',
thumbnail: '/ricing_competitions/1/flicko.webp',
video: 'https://dl.hypr.land/videos/flicko-1-winter.mp4'
},
@ -160,7 +181,8 @@ export const contests: Contest[] = [
creator: 'amadeus',
pretitel: '#2 (ex aequo)',
dotfilesLink: 'https://github.com/AmadeusWM/hyprland-winter',
creatorProfilePicture: 'https://avatars.githubusercontent.com/u/63149896?s=24&v=4',
creatorProfilePicture:
'https://avatars.githubusercontent.com/u/63149896?s=24&v=4',
thumbnail: '/ricing_competitions/1/amadeus.webp'
},
{
@ -168,7 +190,8 @@ export const contests: Contest[] = [
creator: 'Lyasm',
pretitel: '#3 (ex aequo)',
dotfilesLink: '#',
creatorProfilePicture: 'https://avatars.githubusercontent.com/u/111616244?s=24&v=4',
creatorProfilePicture:
'https://avatars.githubusercontent.com/u/111616244?s=24&v=4',
thumbnail: '/ricing_competitions/1/lyasm.webp'
},
{
@ -176,7 +199,8 @@ export const contests: Contest[] = [
creator: 'lauroro',
pretitel: '#3 (ex aequo)',
dotfilesLink: 'https://github.com/lauroro/hyprland-dotfiles',
creatorProfilePicture: 'https://avatars.githubusercontent.com/u/88981092?s=24&v=4',
creatorProfilePicture:
'https://avatars.githubusercontent.com/u/88981092?s=24&v=4',
thumbnail: '/ricing_competitions/1/lauroro.webp'
}
]

View file

@ -10,14 +10,19 @@
import Chan from './community/Chan.svelte'
import type { CommunityProfile } from '$lib/Types'
export let communityProfiles: readonly CommunityProfile[]
interface Props {
communityProfiles: readonly CommunityProfile[]
}
let { communityProfiles }: Props = $props()
const biggestSize = communityProfiles.reduce(
(previousSize, { size }) => (size > previousSize ? size : previousSize),
(previousSize, { size }) =>
size > previousSize ? size : previousSize,
0
)
let restrictionElement: HTMLElement
let restrictionElement: HTMLElement | undefined = $state()
</script>
<section
@ -25,10 +30,16 @@
bind:this={restrictionElement}
>
<Title>
<TitleHeading slot="title" class="">Join a great<br />community</TitleHeading>
<TitleSubtile slot="subtitle" class="class-w-[40ch]">
Get help from Distro Hoppers, Haiku writers,<br />Hydrohomies, and human_(probably)
</TitleSubtile>
{#snippet title()}
<TitleHeading class="">Join a great<br />community</TitleHeading
>
{/snippet}
{#snippet subtitle()}
<TitleSubtile class="class-w-[40ch]">
Get help from Distro Hoppers, Haiku writers,<br />Hydrohomies,
and human_(probably)
</TitleSubtile>
{/snippet}
</Title>
<div class="group mt-16 flex flex-col items-center">
@ -48,18 +59,24 @@
</div>
<div class="absolute w-[1024px] select-none">
<div class="flex h-full origin-bottom-right select-none flex-wrap gap-4">
<div
class="flex h-full origin-bottom-right select-none flex-wrap gap-4"
>
{#each communityProfiles as props}
{@const relativeSize = props.size / biggestSize}
<DiscordProfilePicture
{...props}
weight={relativeSize}
spawnDelay={Math.pow(1 - props.size / biggestSize, 4) * 4654}
getRestrictionElement={() => restrictionElement}
spawnDelay={Math.pow(1 - props.size / biggestSize, 4) *
4654}
getRestrictionElement={() => restrictionElement!}
/>
{/each}
<Chan {biggestSize} getRestrictionElement={() => restrictionElement} />
<Chan
{biggestSize}
getRestrictionElement={() => restrictionElement!}
/>
</div>
</div>
@ -82,13 +99,15 @@
filter 840ms;
transition-delay: 240ms, 180ms, 20ms;
transform: translateY(-25%);
filter: drop-shadow(0px 0px 0px cyan) drop-shadow(0px 0px 0px blue);
filter: drop-shadow(0px 0px 0px cyan)
drop-shadow(0px 0px 0px blue);
&:hover,
.group:hover & {
scale: 1.2 1.2;
rotate: 360deg;
filter: drop-shadow(4px 4px 14px #0fffef7a) drop-shadow(-4px -4px 12px purple);
filter: drop-shadow(4px 4px 14px #0fffef7a)
drop-shadow(-4px -4px 12px purple);
animation: bounce 0.7s infinite 180ms both;
}
&:active {

View file

@ -1,27 +1,41 @@
<script>
/** @type {string | undefined} */
export let image = undefined
/** @type {string | undefined} */
export let name = undefined
/**
* @type {string | undefined }
* Classes for the command text
* @typedef {Object} Props
* @property {string | undefined} [image]
* @property {string | undefined} [name]
* @property {import('svelte').Snippet} [imageExtra]
* @property {import('svelte').Snippet} [children]
*/
/** @type {Props} */
let {
image = undefined,
name = undefined,
imageExtra,
children
} = $props()
</script>
<div class="group relative flex flex-col items-center gap-2 md:flex-row md:gap-4">
<div
class="group relative flex flex-col items-center gap-2 md:flex-row md:gap-4"
>
{#if image && name}
<div
class="relative flex h-32 w-32 flex-col items-center justify-center gap-3 rounded-full text-lg font-medium text-primary transition-transform group-focus-within:-translate-y-1"
>
<img src={image} class="h-20 w-32 object-contain" alt="{name} Logo" loading="lazy" />{name}
<img
src={image}
class="h-20 w-32 object-contain"
alt="{name} Logo"
loading="lazy"
/>{name}
<slot name="imageExtra" />
{@render imageExtra?.()}
</div>
{/if}
<!-- Command button slot -->
<div class="mb-2 w-full">
<slot />
{@render children?.()}
</div>
</div>

View file

@ -1,22 +1,28 @@
<script>
import { animateIn, getGeneratedPath } from '$lib/Helper.ts'
/** @type {string}
* The path to the image. Usually the file within `static`, but can also be an URL
/**
* @typedef {Object} Props
* @property {string} image
* @property {string | undefined} [imageClass]
* @property {string | undefined} [containerClass]
* @property {string} [blurredBackground]
*/
export let image
/** @type {string | undefined} */
export let imageClass = undefined
/** @type {string | undefined} */
export let containerClass = undefined
/** @type {string}
* The path to the image. Usually the file within `static`, but can also be an URL. Defaults to `generated_<image>`
*/
export let blurredBackground = undefined
/** @type {Props} */
let {
image,
imageClass = undefined,
containerClass = undefined,
blurredBackground = undefined
} = $props()
</script>
<div class="rice {containerClass} group">
<div class="h-full w-full" use:animateIn={{ slide: 20, duration: 800 }}>
<div
class="h-full w-full"
use:animateIn={{ slide: 20, duration: 800 }}
>
<img
src={image}
alt="Rice desktop"
@ -52,7 +58,11 @@
opacity: 0.9;
/* filter: brightness(2.5); */
z-index: -10;
mask-image: radial-gradient(50% 50% at 50% 50%, black, transparent);
mask-image: radial-gradient(
50% 50% at 50% 50%,
black,
transparent
);
contain: content layout size style;
@apply -z-10 transition-[filter] duration-500;

View file

@ -1,4 +1,4 @@
<script>
<script lang="ts">
import Card from '$lib/components/Card.svelte'
import GameIcon from '~icons/gg/games'
import SpecialWorkspaceIcon from '~icons/gg/shutterstock'
@ -19,10 +19,16 @@
import TitleHeading from '$lib/components/Title/TitleHeading.svelte'
</script>
<section class="relative flex max-w-screen-xl flex-col items-center px-3 md:px-8">
<section
class="relative flex max-w-screen-xl flex-col items-center px-3 md:px-8"
>
<Title>
<TitlePre slot="pre">TLDR</TitlePre>
<TitleHeading slot="title" class="">Features</TitleHeading>
{#snippet pre()}
<TitlePre>TLDR</TitlePre>
{/snippet}
{#snippet title()}
<TitleHeading class="">Features</TitleHeading>
{/snippet}
</Title>
<CardsContainer
@ -30,12 +36,18 @@
>
<Card class="row-span-2 min-h-[20rem]" color="purple">
<div class="flex h-full flex-col justify-end p-8 sm:p-12">
<h2 class="mb-6 text-5xl font-bold text-white lg:text-8xl">Smooth</h2>
<h2 class="mb-6 text-5xl font-bold text-white lg:text-8xl">
Smooth
</h2>
<p class="max-w-[60ch]">
Smooth transitions. Eye-pleasing animations. Great performance. Highly responsive.
Smooth transitions. Eye-pleasing animations. Great
performance. Highly responsive.
</p>
<div class="_wrapper absolute inset-0 select-none" aria-hidden="true">
<div
class="_wrapper absolute inset-0 select-none"
aria-hidden="true"
>
<div class="feature-image">
<img
src={smoothDefaultImage}
@ -57,12 +69,18 @@
</Card>
<Card class="min-h-[20rem]" color="purple">
<div class="flex h-full flex-col justify-end p-8 sm:p-12">
<h2 class="mb-6 text-5xl font-bold text-white">Easy to configure</h2>
<h2 class="mb-6 text-5xl font-bold text-white">
Easy to configure
</h2>
<p class="max-w-[60ch]">
Live reloading config. Easy configuration format. Sensible defaults. Great documentation.
Live reloading config. Easy configuration format. Sensible
defaults. Great documentation.
</p>
<div class="_wrapper absolute inset-0 select-none" aria-hidden="true">
<div
class="_wrapper absolute inset-0 select-none"
aria-hidden="true"
>
<div class="feature-image">
<img
src={configDefaultImage}
@ -84,12 +102,17 @@
</Card>
<Card class="min-h-[20rem]" color="purple">
<div class="flex h-full flex-col justify-end p-8 sm:p-12">
<h2 class="mb-6 text-5xl font-bold text-white">Dynamic tiling</h2>
<h2 class="mb-6 text-5xl font-bold text-white">
Dynamic tiling
</h2>
<p class="max-w-[60ch]">
Automatic tiling that just works. Supports multiple fine-tuneable layouts, with even more
as plugins.
Automatic tiling that just works. Supports multiple
fine-tuneable layouts, with even more as plugins.
</p>
<div class="_wrapper absolute inset-0 select-none" aria-hidden="true">
<div
class="_wrapper absolute inset-0 select-none"
aria-hidden="true"
>
<div class="feature-image">
<img
src={tileDefaultImage}
@ -122,7 +145,11 @@
<GameIcon class="h-8 w-8" />
Tearing support
</a>
<a href="https://wiki.hyprland.org/IPC/" target="_blank" class="icon-feature hover:underline">
<a
href="https://wiki.hyprland.org/IPC/"
target="_blank"
class="icon-feature hover:underline"
>
<IpcIcon class="h-8 w-8" />
Socket-based IPC
</a>

View file

@ -7,22 +7,38 @@
import FameRicePreview from './FameRicePreview.svelte'
</script>
<section class="" use:animateIn={{ fade: 0, slide: 24, duration: 3000, threshold: 0.1 }}>
<section
class=""
use:animateIn={{
fade: 0,
slide: 24,
duration: 3000,
threshold: 0.1
}}
>
<div class="z-20 -mb-40 px-4">
<Title>
<TitlePre slot="pre">Memorials of the ricing legends</TitlePre>
<TitleHeading slot="title" class="">Hall of Fame</TitleHeading>
{#snippet pre()}
<TitlePre>Memorials of the ricing legends</TitlePre>
{/snippet}
{#snippet title()}
<TitleHeading class="">Hall of Fame</TitleHeading>
{/snippet}
</Title>
</div>
<div class="atmosphere" />
<div class="atmosphere"></div>
<div
class="relative -mt-8 flex w-full max-w-[1100px] flex-col items-center justify-end gap-16 overflow-hidden [perspective:100px] md:px-16 lg:gap-24"
use:animateIn={{ slide: 24, fade: 0.5, duration: 800 }}
>
<a class="absolute bottom-24 left-1/2 z-20 -translate-x-1/2" href="/hall_of_fame">
<Button size="lg" type="fancyOutline">Go to Hall of Fame</Button>
<a
class="absolute bottom-24 left-1/2 z-20 -translate-x-1/2"
href="/hall_of_fame"
>
<Button size="lg" type="fancyOutline">Go to Hall of Fame</Button
>
</a>
<FameRicePreview
@ -41,7 +57,7 @@
/>
</div>
<div class="glow" />
<div class="glow"></div>
</section>
<style lang="postcss">
@ -60,10 +76,24 @@
width: 200vw;
max-width: 2400px;
max-height: 1000px;
background: url('/imgs/grain.webp'),
radial-gradient(closest-side, theme(colors.blue.500 / 30%), transparent),
radial-gradient(15% 20%, theme(colors.cyan.500 / 70%), transparent);
mask-image: radial-gradient(closest-side, white, rgba(0, 0, 0, 0.8) 80%, transparent);
background:
url('/imgs/grain.webp'),
radial-gradient(
closest-side,
theme(colors.blue.500 / 30%),
transparent
),
radial-gradient(
15% 20%,
theme(colors.cyan.500 / 70%),
transparent
);
mask-image: radial-gradient(
closest-side,
white,
rgba(0, 0, 0, 0.8) 80%,
transparent
);
}
.glow {
@ -76,13 +106,19 @@
left: 0;
right: 0;
pointer-events: none;
background-image: url('/imgs/grain.webp'),
background-image:
url('/imgs/grain.webp'),
radial-gradient(
ellipse at bottom,
theme(colors.pink.400),
theme(colors.indigo.700 / 50%) 50%,
theme(colors.indigo.950 / 0%) 80%
);
mask-image: radial-gradient(ellipse at bottom, white, rgba(0, 0, 0, 1) 90%, transparent);
mask-image: radial-gradient(
ellipse at bottom,
white,
rgba(0, 0, 0, 1) 90%,
transparent
);
}
</style>

View file

@ -6,9 +6,9 @@
import HeroBackground from './HeroBackground.svelte'
import HyprlandLogo from '$lib/images/logos/HyprlandLogo.svelte'
export let backgroundData
let { backgroundData } = $props()
let isVisible = true
let isVisible = $state(true)
let isMobile = false
onMount(() => {
@ -19,7 +19,7 @@
<section
class="relative flex h-[100svh] min-h-[36rem] w-full flex-col items-center justify-center overflow-x-hidden"
use:inview
on:inview_change={({ detail: { inView } }) => {
oninview_change={({ detail: { inView } }) => {
isVisible = inView
}}
>
@ -30,13 +30,16 @@
<h1
class="ani-in title pointer-events-auto mb-4 max-w-[20ch] text-center text-4xl font-bold !leading-[1] fill-mode-backwards [animation-delay:384ms] sm:text-6xl md:text-7xl lg:text-8xl lg:tracking-tight"
>
Modern compositor <br /><span class="hyprgradient title-gradient">with the looks</span>
Modern compositor <br /><span
class="hyprgradient title-gradient">with the looks</span
>
</h1>
<p
class="ani-in mb-8 text-center text-base font-medium text-slate-400 fill-mode-backwards [animation-delay:794ms] sm:mb-12 sm:max-w-lg sm:px-0 sm:text-xl md:max-w-lg md:text-2xl lg:max-w-[50ch]"
>
Hyprland provides the latest Wayland features, dynamic tiling, all the eyecandy, powerful
plugins and much more, while still being lightweight and responsive
Hyprland provides the latest Wayland features, dynamic tiling,
all the eyecandy, powerful plugins and much more, while still
being lightweight and responsive
</p>
<div

View file

@ -1,19 +1,29 @@
<script>
export let backgroundData
let { backgroundData } = $props()
const { workspacesPerRow, gapLength, workspaceHeight, height, leftColumns, rightColumns } =
backgroundData
const {
workspacesPerRow,
gapLength,
workspaceHeight,
height,
leftColumns,
rightColumns
} = backgroundData
const REVEAL_DURATION = 800 // in ms
</script>
<div class="wrapper" aria-hidden="true" style:--reveal-duration={REVEAL_DURATION + 'ms'}>
<div
class="wrapper"
aria-hidden="true"
style:--reveal-duration={REVEAL_DURATION + 'ms'}
>
<div
class="inner-wrapper"
style={`--amount: ${workspacesPerRow}; --workspace-gap: ${gapLength}px;--workspace-height: ${workspaceHeight}px; --length: ${height}px;`}
>
<!-- Gradient background -->
<div class="top-light" />
<div class="top-light"></div>
<div class="columns left" aria-hidden="true">
{#each leftColumns as column}
@ -23,9 +33,17 @@
{#each workspace as tiles}
<div class="tiles">
{#each tiles as { color, image }}
<div class="tile" style:--color={color} class:hasImage={image}>
<div
class="tile"
style:--color={color}
class:hasImage={image}
>
{#if image}
<img src={image} class="h-full w-full object-contain" alt="" />
<img
src={image}
class="h-full w-full object-contain"
alt=""
/>
{/if}
</div>
{/each}
@ -45,9 +63,17 @@
{#each workspace as tiles}
<div class="tiles">
{#each tiles as { color, image }}
<div class="tile" style:--color={color} class:hasImage={image}>
<div
class="tile"
style:--color={color}
class:hasImage={image}
>
{#if image}
<img src={image} class="h-full w-full object-contain" alt="" />
<img
src={image}
class="h-full w-full object-contain"
alt=""
/>
{/if}
</div>
{/each}
@ -94,7 +120,11 @@
&::after {
content: ' ';
background: radial-gradient(80% 250%, theme(colors.black) 10%, transparent 50%);
background: radial-gradient(
80% 250%,
theme(colors.black) 10%,
transparent 50%
);
position: absolute;
top: 50%;
left: 50%;
@ -161,7 +191,11 @@
&:hover {
opacity: 1;
scale: 1.02;
background-color: color-mix(in hsl, var(--color), transparent 20%);
background-color: color-mix(
in hsl,
var(--color),
transparent 20%
);
box-shadow:
0px 0px 10px var(--color),
0px 0px 40px var(--color);
@ -181,7 +215,8 @@
& img {
opacity: 0;
transition: opacity var(--reveal-duration) cubic-bezier(1, -0.4, 0.165, 1);
transition: opacity var(--reveal-duration)
cubic-bezier(1, -0.4, 0.165, 1);
pointer-events: none;
}
&:hover img {
@ -191,7 +226,8 @@
}
.top-light {
background: url('/imgs/grain.webp'),
background:
url('/imgs/grain.webp'),
radial-gradient(
100% 80% at top,
theme(colors.cyan.500 / 50%) 0%,

View file

@ -17,27 +17,41 @@
<section class="relative w-full">
<div class="mb-16 md:mb-32">
<div class="h-[2px] w-full bg-gradient-to-l from-transparent via-cyan-400/50 to-transparent" />
<div
class="h-[2px] w-full bg-gradient-to-l from-transparent via-cyan-400/50 to-transparent"
></div>
</div>
<div class="top-gradient"></div>
<div class="relative mx-auto mb-12 max-w-5xl px-8 md:mb-20">
<Title class="mb-10">
<TitleHeading slot="title" class="">Hyprperks</TitleHeading>
{#snippet title()}
<TitleHeading class="">Hyprperks</TitleHeading>
{/snippet}
<TitleSubtile class="max-w-[55ch]">
<a href={hyprperksLink} class=" text-primary hover:underline" target="_blank"
<a
href={hyprperksLink}
class=" text-primary hover:underline"
target="_blank"
>Subscribe for first-party configurations, priority support</a
>
and to support the development. Fully optional, but crafted with love
and to support the development. Fully optional, but crafted with
love
</TitleSubtile>
</Title>
<CardsContainer
class="group flex w-full flex-col gap-8 text-lg font-medium text-white/70 md:grid lg:grid-cols-2 lg:grid-rows-2 lg:gap-4"
>
<Card class="col-span-2 row-span-2 aspect-video min-h-[20rem]" color="cyan">
<Card
class="col-span-2 row-span-2 aspect-video min-h-[20rem]"
color="cyan"
>
<div class="grid h-full">
<div class="col-start-1 col-end-2 row-start-1 row-end-2 m-0.5" aria-hidden="true">
<div
class="col-start-1 col-end-2 row-start-1 row-end-2 m-0.5"
aria-hidden="true"
>
<Video
muted
sources={['https://dl.hypr.land/videos/hyprde2.mp4']}
@ -51,7 +65,9 @@
<Card class="row-span-2 min-h-[20rem]" color="purple">
<div class="flex h-full flex-col justify-end p-8 sm:p-12">
<h2 class="mb-6 text-2xl font-bold text-white lg:text-5xl">First-class support</h2>
<h2 class="mb-6 text-2xl font-bold text-white lg:text-5xl">
First-class support
</h2>
<p class="max-w-[60ch] text-balance">
Always working, actively developed, one-click-update
</p>
@ -81,8 +97,13 @@
<Card class="min-h-[20rem]" color="purple">
<div class="flex h-full flex-col justify-end p-8 sm:p-12">
<div class="z-20 mix-blend-color-dodge">
<h2 class="mb-6 text-5xl font-bold text-slate-300">Easy</h2>
<p class="max-w-[60ch]">Designed to stay out of your way, and let you do your thing.</p>
<h2 class="mb-6 text-5xl font-bold text-slate-300">
Easy
</h2>
<p class="max-w-[60ch]">
Designed to stay out of your way, and let you do your
thing.
</p>
</div>
<img
@ -98,10 +119,12 @@
<Card class="min-h-[20rem]" color="purple">
<div class="flex h-full flex-col justify-end">
<div class="z-20 p-8 mix-blend-color-dodge sm:p-12">
<h2 class="mb-6 text-5xl font-bold text-slate-300">Beautiful</h2>
<h2 class="mb-6 text-5xl font-bold text-slate-300">
Beautiful
</h2>
<p class="max-w-[60ch]">
Customizable, with automatic theming for everything. Crafted with love from the
Hyprland team
Customizable, with automatic theming for everything.
Crafted with love from the Hyprland team
</p>
</div>
@ -118,7 +141,9 @@
<div
class=" col-span-full mt-8 flex size-full w-full items-center justify-center self-center"
>
<Button type="fancyOutline" href={hyprperksLink} size="xl">Subscribe now</Button>
<Button type="fancyOutline" href={hyprperksLink} size="xl"
>Subscribe now</Button
>
</div>
</CardsContainer>
</div>
@ -137,7 +162,12 @@
top: 0;
left: 0;
right: 0;
mask-image: linear-gradient(to left, transparent, black, transparent);
mask-image: linear-gradient(
to left,
transparent,
black,
transparent
);
contain: strict;
}
</style>

View file

@ -1,4 +1,4 @@
<script>
<script lang="ts">
import ActiveGitIcon from '~icons/gg/git-branch'
import VaxryImage from '$lib/images/vaxry-github.webp'
@ -18,7 +18,7 @@
take
} from 'rxjs'
import GitTile from '$lib/components/GitTile.svelte'
import { lerp } from '$lib/Helper.ts'
import { lerp } from '$lib/Helper'
import { cubicInOut, expoInOut } from 'svelte/easing'
import DiscordProfilePicture from '$lib/components/DiscordProfilePicture.svelte'
import { setContext } from 'svelte'
@ -49,11 +49,15 @@
)
)
),
scan((level, value) => Math.min(ASCENION_CLICKS, Math.max(level + value, 0))),
scan((level, value) =>
Math.min(ASCENION_CLICKS, Math.max(level + value, 0))
),
startWith(0)
)
/** How many clicks are left in percent */
const relativeLevel$ = clickLevel$.pipe(map((clicks) => clicks / ASCENION_CLICKS))
const relativeLevel$ = clickLevel$.pipe(
map((clicks) => clicks / ASCENION_CLICKS)
)
/** Tween/Ease the percents for a nicer look */
const cubicRelativeLevel$ = relativeLevel$.pipe(map(cubicInOut))
const expoRelativeLevel$ = relativeLevel$.pipe(map(expoInOut))
@ -69,25 +73,36 @@
const tiles$ = click$.pipe(
switchMap(() =>
merge(
of(Math.floor(lerp(MIN_TILES_PER_CLICK, MAX_TILES_PER_CLICK, $cubicRelativeLevel$))),
of(
Math.floor(
lerp(
MIN_TILES_PER_CLICK,
MAX_TILES_PER_CLICK,
$cubicRelativeLevel$
)
)
),
// Remove the tiles after a timeout, if no new ones came in
timer(MAX_LIFESPAN_TILE)
)
),
scan(
(acc, value) => (value === 0 ? [] : [...acc, ...Array.from({ length: value }, () => 1)]),
[]
(acc, value) =>
value === 0
? []
: [...acc, ...Array.from({ length: value }, () => 1)],
[] as number[]
),
startWith([])
startWith([] as number[])
)
$: hue = lerp(200, 130, $cubicRelativeLevel$)
$: scale = lerp(0.9, 2, $cubicRelativeLevel$)
$: translateY = lerp(0, 10, $cubicRelativeLevel$)
let hue = $derived(lerp(200, 130, $cubicRelativeLevel$))
let scale = $derived(lerp(0.9, 2, $cubicRelativeLevel$))
let translateY = $derived(lerp(0, 10, $cubicRelativeLevel$))
/** @type {HTMLDivElement} */
let containerElement
let isAnimationComplete = false
let containerElement = $state()
let isAnimationComplete = $state(false)
const vaxrySize = 220
const contextId = Symbol('hypractive context')
@ -101,17 +116,24 @@
}
function onClickUnlocked() {
window.open('https://github.com/hyprwm/Hyprland/commits/main/', '_blank')
window.open(
'https://github.com/hyprwm/Hyprland/commits/main/',
'_blank'
)
}
</script>
<div class="relative overflow-visible">
<button
class="flex items-center gap-3 font-bold text-slate-400 shadow-black drop-shadow-lg transition-colors hover:underline active:scale-95"
on:click={isAnimationComplete ? onClickUnlocked : onClick}
style:color={$relativeLevel$ > 0 ? `hsl(${hue} 64% 53%)` : undefined}
onclick={isAnimationComplete ? onClickUnlocked : onClick}
style:color={$relativeLevel$ > 0
? `hsl(${hue} 64% 53%)`
: undefined}
style:scale={$relativeLevel$ > 0 ? scale : undefined}
style:translate={$relativeLevel$ > 0 ? `0px -${translateY}px` : undefined}
style:translate={$relativeLevel$ > 0
? `0px -${translateY}px`
: undefined}
>
<ActiveGitIcon class="h-8 w-8" />
<span class="transition-colors"> Hypractive development </span>
@ -120,7 +142,11 @@
<div class="pointer-events-none absolute left-1/2 top-1/2 -z-10">
{#each $tiles$ as _}
<GitTile
lifeSpan={lerp(MIN_LIFESPAN_TILE, MAX_LIFESPAN_TILE, $cubicRelativeLevel$)}
lifeSpan={lerp(
MIN_LIFESPAN_TILE,
MAX_LIFESPAN_TILE,
$cubicRelativeLevel$
)}
maxSpeed={lerp(10, 38, $expoRelativeLevel$)}
minSpeed={lerp(1, 9, $expoRelativeLevel$)}
/>
@ -132,14 +158,14 @@
<div
class="vaxx-wrapper absolute bottom-[240px] left-1/2 z-50 aspect-square -translate-x-[100px] rounded-full animate-in fade-in-0 zoom-in-95 slide-in-from-bottom-[500px] slide-in-from-left-20 [animation-duration:2.5s]"
style:width={vaxrySize + 'px'}
on:animationend={() => (isAnimationComplete = true)}
onanimationend={() => (isAnimationComplete = true)}
>
<DiscordProfilePicture
image={VaxryImage}
size={vaxrySize}
weight={10}
coordinates={[0, 0]}
class="outline-orange-300"
{contextId}
isAnimating={false}
/>
</div>
@ -149,8 +175,10 @@
<div
class="bg-gradient"
style:opacity={$hasAscended$ ? 1 : $relativeLevel$}
style="--relativeLevel: {$hasAscended$ ? 1 : $expoRelativeLevel$ - 0.2}"
/>
style="--relativeLevel: {$hasAscended$
? 1
: $expoRelativeLevel$ - 0.2}"
></div>
</div>
</div>

View file

@ -15,33 +15,49 @@
<section class="pb-6">
<Title>
<TitleHeading slot="title" class="">Install now</TitleHeading>
<TitleSubtile slot="subtitle" class="">For your favourite distro</TitleSubtile>
{#snippet title()}
<TitleHeading class="">Install now</TitleHeading>
{/snippet}
{#snippet subtitle()}
<TitleSubtile class="">For your favourite distro</TitleSubtile>
{/snippet}
</Title>
<div class="flex flex-col items-center gap-12 md:gap-6" use:animateIn={{ slide: 24, fade: 0 }}>
<div
class="flex flex-col items-center gap-12 md:gap-6"
use:animateIn={{ slide: 24, fade: 0 }}
>
<div
class="links_ flex flex-col gap-12 px-4 md:gap-6 md:rounded-3xl md:bg-gradient-to-tr md:from-blue-500/40 md:to-transparent md:p-8 md:shadow-xl md:outline md:outline-1 md:outline-blue-500"
>
<DistroOption name="Arch" image={archLogo}>
<CommandButton command="pacman -S hyprland">
<div slot="extra" class="absolute -bottom-4 left-1/2 min-w-max -translate-x-1/2">
AUR git version: <a
class=" "
target="_blank"
href="https://aur.archlinux.org/packages/hyprland-git/">hyprland-git</a
{#snippet extra()}
<div
class="absolute -bottom-4 left-1/2 min-w-max -translate-x-1/2"
>
</div>
AUR git version: <a
class=" "
target="_blank"
href="https://aur.archlinux.org/packages/hyprland-git/"
>hyprland-git</a
>
</div>
{/snippet}
</CommandButton>
</DistroOption>
<DistroOption name="NixOS" image={nixLogo}>
<CommandButton command="programs.hyprland.enable">
<div slot="extra" class="absolute -bottom-4 left-1/2 min-w-max -translate-x-1/2">
<a href="https://wiki.hypr.land/Nix/" target="_blank"
>See more details and git version ↗</a
{#snippet extra()}
<div
class="absolute -bottom-4 left-1/2 min-w-max -translate-x-1/2"
>
</div>
<a href="https://wiki.hypr.land/Nix/" target="_blank"
>See more details and git version ↗</a
>
</div>
{/snippet}
</CommandButton>
</DistroOption>
@ -51,17 +67,22 @@
<DistroOption name="openSUSE" image={suseLogo}>
<CommandButton command="zypper in hyprland">
<div slot="extra" class="absolute -bottom-4 left-1/2 min-w-max -translate-x-1/2">
or install “hyprland” via YaST2 Software.
</div>
{#snippet extra()}
<div
class="absolute -bottom-4 left-1/2 min-w-max -translate-x-1/2"
>
or install “hyprland” via YaST2 Software.
</div>
{/snippet}
</CommandButton>
<img
class=" absolute inset-0 -z-10 translate-y-1 rotate-0 scale-90 opacity-0 transition-all duration-700 [transition-delay:2s] group-hover:-translate-x-3 group-hover:-translate-y-0 group-hover:-rotate-12 group-hover:scale-100 group-hover:opacity-90"
src={amongus}
slot="imageExtra"
alt=""
srcset=""
/>
{#snippet imageExtra()}
<img
class=" absolute inset-0 -z-10 translate-y-1 rotate-0 scale-90 opacity-0 transition-all duration-700 [transition-delay:2s] group-hover:-translate-x-3 group-hover:-translate-y-0 group-hover:-rotate-12 group-hover:scale-100 group-hover:opacity-90"
src={amongus}
alt=""
srcset=""
/>
{/snippet}
</DistroOption>
</div>

View file

@ -4,13 +4,17 @@
import TitlePre from '$lib/components/Title/TitlePre.svelte'
import Title from '$lib/components/Title/TitleWrapper.svelte'
export let news
let { news } = $props()
</script>
<section class="relative mb-12 max-w-5xl px-8 md:mb-20">
<Title>
<TitlePre slot="pre">Read while it's fresh!</TitlePre>
<TitleHeading slot="title" class="">Latest news</TitleHeading>
{#snippet pre()}
<TitlePre>Read while it's fresh!</TitlePre>
{/snippet}
{#snippet title()}
<TitleHeading class="">Latest news</TitleHeading>
{/snippet}
</Title>
<ul class="mt-8 flex flex-col gap-10">
@ -30,7 +34,8 @@
translate: -25% 0;
margin-top: -100px;
background: url('/imgs/grain.webp'),
background:
url('/imgs/grain.webp'),
radial-gradient(
50% 50% at 50% 50%,
theme(colors.cyan.500 / 50%) 0%,

View file

@ -7,15 +7,22 @@
import clsx from 'clsx'
import Video from '$lib/components/Video.svelte'
import { animateIn } from '$lib/Helper.ts'
import { Subject, debounceTime, map, tap, throttle, throttleTime } from 'rxjs'
import {
Subject,
debounceTime,
map,
tap,
throttle,
throttleTime
} from 'rxjs'
import { onMount } from 'svelte'
import { fade } from 'svelte/transition'
import Button from '$lib/components/Button.svelte'
/** @type {HTMLVideoElement[]}*/
const videos = []
let activeIndex = 0
let isHoveringVideo = false
const videos = $state([])
let activeIndex = $state(0)
let isHoveringVideo = $state(false)
const isVideoCroppedInput$ = new Subject()
/** @type {import('rxjs').Subject<boolean>}*/
const isVideoCropped$ = isVideoCroppedInput$.pipe(
@ -41,7 +48,8 @@
{
icon: IconIpc,
title: 'Bindings and IPC.',
description: 'Control your desktop with your favourite languages or simply via IPC.',
description:
'Control your desktop with your favourite languages or simply via IPC.',
poster: '/videos/aylur_thumb.png',
src: '/videos/aylur',
subtext: `Setup by <a href="https://github.com/Aylur/dotfiles" target="_blank">Aylur</a>, creator of
@ -55,13 +63,17 @@
}
function onPlay(currentIndex) {
videos.filter((_, index) => index !== currentIndex).forEach((video) => video.play())
videos
.filter((_, index) => index !== currentIndex)
.forEach((video) => video.play())
}
function onPause(activeIndex, currentIndex) {
// Prevent infinite loop when active video gets paused and other videos also get paused as a result
if (currentIndex !== activeIndex) return
videos.filter((_, index) => index !== currentIndex).forEach((video) => video.pause())
videos
.filter((_, index) => index !== currentIndex)
.forEach((video) => video.pause())
}
function toggleVideoSlide() {
isHoveringVideo ? slideVideoOut() : slideVideoIn()
@ -78,9 +90,11 @@
})
</script>
<svelte:window on:resize={() => isVideoCroppedInput$.next(0)} />
<svelte:window onresize={() => isVideoCroppedInput$.next(0)} />
<section class="relative z-0 flex min-h-max w-full flex-col items-center py-20">
<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-8 transition-all lg:grid-cols-2 lg:gap-12"
use:animateIn={{ slide: 24 }}
@ -94,22 +108,24 @@
<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 control over your workflow by customizing
and extending it how you want.
Get the latest features Linux offers. Have full control 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}
{@const SvelteComponent = icon}
<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 '
isActive &&
'bg-blue-300/5 shadow-md outline outline-1 backdrop-blur-sm '
)}
on:mouseenter={() => setActiveItem(index)}
onmouseenter={() => setActiveItem(index)}
>
<svelte:component this={icon} class="h-8 w-8 shrink-0 text-primary" />
<SvelteComponent class="h-8 w-8 shrink-0 text-primary" />
<p
class={clsx(
'txt-shadow_ text-left text-lg font-medium transition-colors ',
@ -134,12 +150,15 @@
target="_blank"
>
<div>
Also see <span class="text-cyan-500">Awesome Hyprland</span>
Also see <span class="text-cyan-500"
>Awesome Hyprland</span
>
</div>
<IconLinkOut />
</a>
<p class="font-medium text-slate-400">
A list of plugins, bindings, apps and more made by the community
A list of plugins, bindings, apps and more made by the
community
</p>
</div>
</div>
@ -153,7 +172,7 @@
>
{#if $isVideoCropped$}
<button
on:click={toggleVideoSlide}
onclick={toggleVideoSlide}
class:rotate-180={isHoveringVideo}
class="group absolute -left-6 top-1/2 z-50 rounded-full bg-blue-400/5 p-2 outline outline-white/10 backdrop-blur-sm transition-transform"
out:fade
@ -189,7 +208,9 @@
</div>
</div>
<PatternBackground class="absolute inset-0 h-[110%] w-full text-slate-800 opacity-40" />
<PatternBackground
class="absolute inset-0 h-[110%] w-full text-slate-800 opacity-40"
/>
</section>
<style lang="postcss">

View file

@ -2,13 +2,15 @@
import { inview } from 'svelte-inview'
import Video from '$lib/components/Video.svelte'
import { onMount } from 'svelte'
/** @type {{ [key: string]: any }} */
let { ...rest } = $props()
/** @type HTMLVideoElement */
let videoElement
let videoElement = $state()
let isVisible = false
let isManuallyPaused = false
let isChrome = false
let isVisible = $state(false)
let isManuallyPaused = $state(false)
let isChrome = $state(false)
onMount(() => {
isChrome = navigator.userAgent.toLowerCase().includes('chrome')
@ -21,18 +23,18 @@
})
</script>
<section class={$$restProps.class} class:isVisible>
<section class={rest.class} class:isVisible>
<div
class="wrapper"
role="banner"
use:inview={{ threshold: 0.5 }}
on:inview_enter={() => {
oninview_enter={() => {
isVisible = true
if (!isManuallyPaused && isChrome) {
videoElement.play().catch(() => {})
}
}}
on:inview_leave={() => {
oninview_leave={() => {
isVisible = false
isManuallyPaused = videoElement.paused
videoElement.pause()
@ -56,7 +58,7 @@
>
</div>
<div class="preview-rice-bg" aria="hidden" />
<div class="preview-rice-bg" aria="hidden"></div>
</section>
<style lang="postcss">
@ -66,7 +68,7 @@
contain: layout style content;
}
.wrapper {
@apply mx-3 rounded-xl;
@apply mx-3 rounded-xl;
transition: all cubic-bezier(0.9, -1, 0.065, 1.8) 1060ms;
position: relative;
box-shadow: 0px 0px 44px theme(colors.primary / 80%);
@ -109,8 +111,13 @@
width: 1100px;
height: 200%;
background-image: url('/imgs/grain.webp'),
radial-gradient(closest-side, theme(colors.sky.500), theme(colors.indigo.500 / 0%));
background-image:
url('/imgs/grain.webp'),
radial-gradient(
closest-side,
theme(colors.sky.500),
theme(colors.indigo.500 / 0%)
);
mask-image: radial-gradient(
closest-side,

View file

@ -7,7 +7,11 @@
import Button from '$lib/components/Button.svelte'
import PatternBackground from '$lib/PatternBackground.svelte'
export let sponsors: SponsorsRanked
interface Props {
sponsors: SponsorsRanked
}
let { sponsors }: Props = $props()
const hasSponsors = Object.values(sponsors)
.filter((value) => !Array.isArray(value))
.every((array) => array.length === 0)
@ -16,15 +20,23 @@
</script>
{#if hasSponsors}
<div class="relative flex w-full flex-col items-center justify-center">
<div
class="relative flex w-full flex-col items-center justify-center"
>
<PatternBackground
class="absolute -inset-12 inset-x-0 h-[100%] w-full text-slate-800 opacity-40"
class="absolute -inset-24 inset-x-0 h-[120%] w-full text-slate-800 opacity-40"
/>
<section class="relative mb-12 flex max-w-5xl flex-col gap-2 px-8 md:mb-20">
<section
class="relative mb-12 flex max-w-5xl flex-col gap-2 px-8 md:mb-20"
>
<Title class="px-0">
<TitlePre slot="pre">A huge thank you!</TitlePre>
<TitleHeading size="small" slot="title">Our sponsors</TitleHeading>
{#snippet pre()}
<TitlePre>A huge thank you!</TitlePre>
{/snippet}
{#snippet title()}
<TitleHeading size="small">Our sponsors</TitleHeading>
{/snippet}
</Title>
<div
@ -40,7 +52,12 @@
</h2>
<div class="flex justify-center gap-14 md:gap-16">
{#each sponsors.diamond as sponsor}
<Sponsor {sponsor} showImage showSlogan class="h-18 w-50 md:h-30 md:w-72" />
<Sponsor
{sponsor}
showImage
showSlogan
class="h-18 w-50 md:h-30 md:w-72"
/>
{/each}
</div>
</div>
@ -55,7 +72,12 @@
</h2>
<div class="flex justify-center gap-14 md:gap-16">
{#each sponsors.platinum as sponsor}
<Sponsor {sponsor} showImage showSlogan class="h-16 w-40 md:h-20 md:w-60" />
<Sponsor
{sponsor}
showImage
showSlogan
class="h-16 w-40 md:h-20 md:w-60"
/>
{/each}
</div>
</div>
@ -82,14 +104,24 @@
</div>
{/if}
<div class="flex flex-wrap justify-center gap-x-16 gap-y-10">
<div
class="flex flex-wrap justify-center gap-x-16 gap-y-10"
>
{#if sponsors.silver.length > 0}
<div class="flex flex-col gap-2">
<h2 class="text-center font-bold text-slate-300">Silver</h2>
<h2 class="text-center font-bold text-slate-300">
Silver
</h2>
<div class="flex flex-wrap gap-x-4 gap-y-2 font-medium">
<div
class="flex flex-wrap gap-x-4 gap-y-2 font-medium"
>
{#each sponsors.silver as sponsor}
<Sponsor {sponsor} showImage class={smallImages} />
<Sponsor
{sponsor}
showImage
class={smallImages}
/>
{/each}
</div>
</div>
@ -97,9 +129,13 @@
{#if sponsors.bronze.length > 0}
<div class="flex flex-col gap-2">
<h2 class="text-center font-bold text-slate-300">Bronze</h2>
<h2 class="text-center font-bold text-slate-300">
Bronze
</h2>
<div class="flex flex-wrap gap-x-4 gap-y-2 font-medium">
<div
class="flex flex-wrap gap-x-4 gap-y-2 font-medium"
>
{#each sponsors.bronze as sponsor}
<Sponsor {sponsor} class={smallImages} />
{/each}
@ -113,8 +149,10 @@
class="col-span-full mt-8 flex size-full w-full items-center justify-center self-center"
style="margin-top: 5rem; margin-bottom: -5rem;"
>
<Button type="outline" href="https://account.hypr.land/sponsors" size="xl"
>Sponsor us</Button
<Button
type="outline"
href="https://account.hypr.land/sponsors"
size="xl">Sponsor us</Button
>
</div>
</div>
@ -124,8 +162,12 @@
<section
class="group -my-5 mt-16 flex flex-col gap-3 self-center text-center text-slate-300 md:-my-[8rem]"
>
<p class="text-2xl font-medium text-slate-500">We currently have no sponsors :(</p>
<p class="text-slate-200">Are you interested or know one person who might be?</p>
<p class="text-2xl font-medium text-slate-500">
We currently have no sponsors :(
</p>
<p class="text-slate-200">
Are you interested or know one person who might be?
</p>
<a
href="https://account.hypr.land/sponsors"
class="text-cyan-400 decoration-primary hover:text-cyan-300 hover:underline"

View file

@ -1,18 +1,24 @@
<script lang="ts">
import DiscordProfilePicture from '$lib/components/DiscordProfilePicture.svelte'
export let biggestSize: number
export let getRestrictionElement: (() => HTMLElement) | undefined = undefined
interface Props {
biggestSize: number
getRestrictionElement?: (() => HTMLElement) | undefined
}
let { biggestSize, getRestrictionElement = undefined }: Props =
$props()
const size = 90
let isDragging = false
let isHover = false
let image: string
$: image = isDragging
? 'imgs/chan/surprise.svg'
: isHover
? 'imgs/chan/wink.svg'
: 'imgs/chan/joy.svg'
let isDragging = $state(false)
let isHover = $state(false)
let image: string = $derived(
isDragging
? 'imgs/chan/surprise.svg'
: isHover
? 'imgs/chan/wink.svg'
: 'imgs/chan/joy.svg'
)
</script>
<div class="absolute z-20">
@ -23,8 +29,8 @@
weight={size / biggestSize}
{getRestrictionElement}
class={'bg-blue-300 outline-cyan-500'}
on:dragStart={() => (isDragging = true)}
on:dragEnd={() => (isDragging = false)}
on:hover={() => (isHover = true)}
ondragStart={() => (isDragging = true)}
ondragEnd={() => (isDragging = false)}
onhover={() => (isHover = true)}
></DiscordProfilePicture>
</div>

View file

@ -1,26 +1,51 @@
<script lang="ts">
import { createThresholdStream, lerp, preloadImage } from '$lib/Helper'
import {
createThresholdStream,
lerp,
preloadImage
} from '$lib/Helper'
import DiscordProfilePicture from '$lib/components/DiscordProfilePicture.svelte'
import { Subject, filter, first, map, merge, of, startWith, switchMap, timer } from 'rxjs'
import {
Subject,
filter,
first,
map,
merge,
of,
startWith,
switchMap,
timer
} from 'rxjs'
import { onDestroy } from 'svelte'
export let biggestSize: number
export let getRestrictionElement: (() => HTMLElement) | undefined = undefined
interface Props {
biggestSize: number
getRestrictionElement?: (() => HTMLElement) | undefined
}
const thePozArmy = Object.values(import.meta.glob('$lib/images/poz/*', { eager: true })).map(
(x) => x.default as string
)
let { biggestSize, getRestrictionElement = undefined }: Props =
$props()
const thePozArmy = Object.values(
import.meta.glob('$lib/images/poz/*', { eager: true })
).map((x) => x.default as string)
const size = 90
const origin = [710, 615] as const
let newPosition: readonly [number, number]
let newPosition: readonly [number, number] = $state()
const clicksTarget = 9
const shakeMax = 24
const clicksInput$ = new Subject()
const level$ = clicksInput$.pipe(
createThresholdStream({ clicksTarget, clicksEachMs: 250, fallof: 10 })
createThresholdStream({
clicksTarget,
clicksEachMs: 250,
fallof: 10
})
)
const relativeLevel$ = level$.pipe(
map((clicks) => clicks / clicksTarget)
)
const relativeLevel$ = level$.pipe(map((clicks) => clicks / clicksTarget))
const hasFinished$ = relativeLevel$.pipe(
filter((clicks) => clicks >= 1),
first(),
@ -74,10 +99,14 @@
isAnimating={false}
tag="poz"
{getRestrictionElement}
on:enteredView={({ detail: { dragCoordinates } }) => {
onenteredView={({ detail: { dragCoordinates } }) => {
dragCoordinates.update(([x, y]) => {
x += lerp(400, 0, (size / maxSize) * (1 - Math.random())) * (Math.random() > 0.5 ? 1 : -1)
y += lerp(400, 0, (size / maxSize) * (1 - Math.random())) * (Math.random() > 0.5 ? 1 : -1)
x +=
lerp(400, 0, (size / maxSize) * (1 - Math.random())) *
(Math.random() > 0.5 ? 1 : -1)
y +=
lerp(400, 0, (size / maxSize) * (1 - Math.random())) *
(Math.random() > 0.5 ? 1 : -1)
return [x, y]
})
}}

View file

@ -6,10 +6,10 @@
import TitleSubtile from '$lib/components/Title/TitleSubtile.svelte'
import TitleHeading from '$lib/components/Title/TitleHeading.svelte'
export let data
let { data } = $props()
/** @type {HTMLElement} */
let asciiElement
let asciiElement = $state()
const { posts } = data
@ -18,7 +18,9 @@
// Taken from https://github.com/NotAShelf/hyprascii/blob/main/web/script.js
onMount(async () => {
const logoBlob = await fetch(LogoPng).then((response) => response.blob())
const logoBlob = await fetch(LogoPng).then((response) =>
response.blob()
)
objectUrl = URL.createObjectURL(logoBlob)
const img = document.createElement('img')
img.src = objectUrl
@ -48,8 +50,13 @@
const chars = ' .-=+'
for (let y = 0; y < cvs.height; y++) {
for (let x = 0; x < cvs.width; x++) {
const idx = 4 * ((sx < 0 ? cvs.width - x - 1 : x) + cvs.width * y)
const br = getLuminance(pixels[idx] / 256, pixels[idx + 1] / 256, pixels[idx + 2] / 256)
const idx =
4 * ((sx < 0 ? cvs.width - x - 1 : x) + cvs.width * y)
const br = getLuminance(
pixels[idx] / 256,
pixels[idx + 1] / 256,
pixels[idx + 2] / 256
)
text.push(chars[Math.floor(br * chars.length)])
}
text.push('\n')
@ -68,15 +75,18 @@
<title>Hyprland News</title>
</svelte:head>
<div class="fancy-top-gradient" />
<div class="fancy-top-gradient"></div>
<section>
<header class="header">
<pre class="spinner-wrapper" bind:this={asciiElement} />
<pre class="spinner-wrapper" bind:this={asciiElement}></pre>
<Title>
<TitleHeading slot="title" class="">News</TitleHeading>
<TitleSubtile>Fresh updates straight from the oven</TitleSubtile>
{#snippet title()}
<TitleHeading class="">News</TitleHeading>
{/snippet}
<TitleSubtile>Fresh updates straight from the oven</TitleSubtile
>
</Title>
</header>

View file

@ -2,10 +2,14 @@ import { error } from '@sveltejs/kit'
export async function load({ params, fetch }) {
try {
const post = await import(`../../../content/news/${params.slug}.md`)
const post = await import(
`../../../content/news/${params.slug}.md`
)
const other = await fetch('/api/news')
.then((response) => response.json())
.then((news) => news.filter((entry) => entry.slug !== params.slug).slice(0, 4))
.then((news) =>
news.filter((entry) => entry.slug !== params.slug).slice(0, 4)
)
return {
content: post.default,

View file

@ -5,7 +5,7 @@
import NewsThumb from '$lib/components/news-thumb.svelte'
import clsx from 'clsx'
export let data
let { data } = $props()
</script>
<svelte:head>
@ -14,7 +14,7 @@
<meta property="og:title" content={data.meta.title} />
</svelte:head>
<div class="fancy-top-gradient fad animate-in" />
<div class="fancy-top-gradient fad animate-in"></div>
<article
class="mx-auto mt-navbar flex max-w-4xl flex-col gap-4 px-6 pt-20 md:gap-8 md:px-8 lg:mt-32 lg:gap-14"
@ -23,7 +23,9 @@
class="flex flex-col items-start gap-8 duration-1000 animate-in fade-in-0 slide-in-from-bottom-4"
>
<h1 class="text-4xl font-bold lg:text-6xl">{data.meta.title}</h1>
<div class="flex items-center gap-8 text-base font-medium text-slate-400">
<div
class="flex items-center gap-8 text-base font-medium text-slate-400"
>
{#if data.meta.author}
<svelte:element
this={data.meta.author.link ? 'a' : 'div'}
@ -32,7 +34,8 @@
target="_blank"
class={clsx(
'flex items-center gap-3 rounded-full bg-slate-700/50 px-4 py-2 text-base font-medium text-slate-300 ',
data.meta.author.link && 'transition-colors hover:bg-slate-700/70 hover:text-slate-200'
data.meta.author.link &&
'transition-colors hover:bg-slate-700/70 hover:text-slate-200'
)}
>
{#if data.meta.author.picture}
@ -49,7 +52,9 @@
</div>
</svelte:element>
{/if}
<time class="" datetime={new Date(data.meta.date * 1000).toISOString()}
<time
class=""
datetime={new Date(data.meta.date * 1000).toISOString()}
>{formatDate(data.meta.date * 1000)}</time
>
</div>
@ -59,14 +64,16 @@
<div
class="prose prose-slate prose-invert transition-none delay-500 animate-in fade-in-0 fill-mode-backwards [animation-duration:2500ms] lg:prose-xl prose-a:text-cyan-400 prose-img:rounded-lg"
>
<svelte:component this={data.content} />
<data.content />
</div>
</article>
{#if data.other.length > 0}
<section class="mx-auto mt-72 max-w-screen-lg">
<Title class="mb-6">
<TitleHeading slot="title" class="">More news</TitleHeading>
{#snippet title()}
<TitleHeading class="">More news</TitleHeading>
{/snippet}
</Title>
<ul class="grid grid-cols-2 gap-x-7 gap-y-16">

View file

@ -10,10 +10,12 @@
import TitleHeading from '$lib/components/Title/TitleHeading.svelte'
import Button from '$lib/components/Button.svelte'
export let data
let { data } = $props()
const { plugins } = data
const featuredPlugins = plugins.filter(({ featured }) => featured).slice(0, 4)
const featuredPlugins = plugins
.filter(({ featured }) => featured)
.slice(0, 4)
const pluginsByCategory = R.pipe(
plugins,
R.groupBy(({ category }) => category),
@ -48,11 +50,19 @@
>
<div class="top-light"></div>
<header class="mt-24 flex flex-col items-center justify-center md:mt-32">
<header
class="mt-24 flex flex-col items-center justify-center md:mt-32"
>
<Title>
<TitlePre slot="pre">Plugins</TitlePre>
<TitleHeading slot="title" class="">Unlock full power</TitleHeading>
<TitleSubtile>Easily load up plugins and customize everything</TitleSubtile>
{#snippet pre()}
<TitlePre>Plugins</TitlePre>
{/snippet}
{#snippet title()}
<TitleHeading class="">Unlock full power</TitleHeading>
{/snippet}
<TitleSubtile
>Easily load up plugins and customize everything</TitleSubtile
>
</Title>
<div
@ -74,11 +84,17 @@
>
<!-- Secondary navigation -->
<div class="hidden lg:block">
<nav class="sticky top-32 z-40 max-h-max min-w-52 shrink-0 grow-0 flex-col gap-2 px-8">
<ul class="flex flex-col gap-4 text-sm font-medium text-slate-400">
<nav
class="sticky top-32 z-40 max-h-max min-w-52 shrink-0 grow-0 flex-col gap-2 px-8"
>
<ul
class="flex flex-col gap-4 text-sm font-medium text-slate-400"
>
{#each pluginsByCategory as category}
<li>
<a href={'#' + category[0]} class=" px-2 py-1 transition-colors hover:text-white"
<a
href={'#' + category[0]}
class=" px-2 py-1 transition-colors hover:text-white"
>{category[0]}</a
>
</li>
@ -101,7 +117,11 @@
plugin.banner &&
featuredPlugins
.slice(0, index)
.reduce((total, { banner }) => (banner ? total + 1 : total), 0) < 2
.reduce(
(total, { banner }) =>
banner ? total + 1 : total,
0
) < 2
? 'col-span-8 h-[18rem] md:col-span-6 lg:col-span-8 '
: 'col-span-6 h-[16rem] lg:col-span-4 ',
@ -119,7 +139,10 @@
>
{#each pluginsByCategory as [category, plugins]}
<div class="flex w-full flex-col gap-2">
<h3 id={category} class="scroll-mt-32 text-lg font-medium text-slate-300">
<h3
id={category}
class="scroll-mt-32 text-lg font-medium text-slate-300"
>
{category}
</h3>
<div class="flex w-full flex-wrap gap-4">
@ -141,7 +164,8 @@
<style lang="postcss">
.top-light {
background: url('/imgs/grain.webp'),
background:
url('/imgs/grain.webp'),
radial-gradient(
100% 80% at top,
theme(colors.cyan.500 / 50%) 0%,

View file

@ -6,22 +6,34 @@
// Dont forget to put this component inside of CardsContainer.svelte
// Also pass a hight class to the element, as otherwise the banner might not align properly,
// due to the parent container being unable to use height: full as there wont be a reference
export let plugin
export let color = undefined
export let showCategory = false
export let taglineMaxLength = 0
/**
* @typedef {Object} Props
* @property {any} plugin - due to the parent container being unable to use height: full as there wont be a reference
* @property {any} [color]
* @property {boolean} [showCategory]
* @property {number} [taglineMaxLength]
*/
/** @type {Props & { [key: string]: any }} */
let {
plugin,
color = undefined,
showCategory = false,
taglineMaxLength = 0,
...rest
} = $props()
/** @type {HTMLVideoElement}*/
let videoElement
let videoElement = $state()
</script>
<Card
on:mouseenter={() => videoElement && videoElement.play().catch(console.error)}
on:mouseenter={() =>
videoElement && videoElement.play().catch(console.error)}
on:mouseleave={() => videoElement && videoElement.pause()}
{color}
class={$$restProps.class}
class={rest.class}
gradientOpacity={0.5}
><a
href={plugin.url}
@ -47,7 +59,11 @@
)}
style:--background={`url("${getGeneratedPath(plugin.logo)}")`}
>
<img class="size-full" src={plugin.logo} alt={'Logo of ' + plugin.name} />
<img
class="size-full"
src={plugin.logo}
alt={'Logo of ' + plugin.name}
/>
</div>
{:else}
<!-- Placeholder logo -->
@ -73,7 +89,10 @@
<p
class="overflow-hiddenx text-nowrapx max-w-[60ch] text-ellipsis text-sm font-medium text-slate-400 @xl:overflow-auto @xl:text-pretty @xl:text-base"
>
{trimText(plugin.tagline, taglineMaxLength || Number.POSITIVE_INFINITY)}
{trimText(
plugin.tagline,
taglineMaxLength || Number.POSITIVE_INFINITY
)}
</p>
</div>
@ -100,7 +119,11 @@
loop
></video>
{:else}
<img src={plugin.banner} class="absolute inset-0 size-full object-cover" alt="" />
<img
src={plugin.banner}
class="absolute inset-0 size-full object-cover"
alt=""
/>
{/if}
</div>
{/if}
@ -124,7 +147,11 @@
z-index: -1;
opacity: 50%;
/* filter: brightness(2); */
mask-image: radial-gradient(closest-side, black 0%, transparent 99%);
mask-image: radial-gradient(
closest-side,
black 0%,
transparent 99%
);
}
}
.logo-container:not(.banner) {
@ -139,7 +166,11 @@
height: 100%;
z-index: -20;
/* translate: 0 -2px; */
mask-image: radial-gradient(450% 120% at 0% 100%, black 18%, white);
mask-image: radial-gradient(
450% 120% at 0% 100%,
black 18%,
white
);
mask-mode: luminance;
@apply rounded-3xl;
contain: strict;

View file

@ -1,6 +1,11 @@
<script>
/** @type {string}*/
export let tag
/**
* @typedef {Object} Props
* @property {string} tag
*/
/** @type {Props} */
let { tag } = $props()
const colors = {
Design: 'text-pink-200 bg-pink-500/15',
@ -12,7 +17,8 @@
</script>
<div
class={'min-w-max rounded-full p-1 px-2.5 text-xs font-bold ' + colors[tag] ?? 'bg-slate-100/5'}
class={'min-w-max rounded-full p-1 px-2.5 text-xs font-bold ' +
colors[tag] ?? 'bg-slate-100/5'}
>
{tag}
</div>

View file

@ -15,7 +15,12 @@ html.lock-scroll,
}
.hyprgradient {
background-image: linear-gradient(to bottom right, #00e6cf, #00c4e3, #0081c6);
background-image: linear-gradient(
to bottom right,
#00e6cf,
#00c4e3,
#0081c6
);
}
::selection {
@ -82,7 +87,16 @@ html.lock-scroll,
position: absolute;
inset: 0;
z-index: -1000;
mask-image: radial-gradient(var(--sizethis), white 70%, transparent);
background: url('/imgs/grain.webp'),
radial-gradient(var(--sizethis), theme(colors.cyan.500 / 40%), transparent);
mask-image: radial-gradient(
var(--sizethis),
white 70%,
transparent
);
background:
url('/imgs/grain.webp'),
radial-gradient(
var(--sizethis),
theme(colors.cyan.500 / 40%),
transparent
);
}

View file

@ -5,7 +5,7 @@
import Button from '$lib/components/Button.svelte'
import CloseIcon from '~icons/mingcute/close-line'
let kofiModal: HTMLDialogElement | undefined
let kofiModal: HTMLDialogElement | undefined = $state()
function lockScroll() {
document.querySelector('html')?.classList.add('lock-scroll')
@ -19,47 +19,66 @@
<title>{'Support | Hyprland'}</title>
<meta name="description" content="Support Hyprland Development" />
<meta property="og:title" content="Donate to Hyprland" />
<meta property="og:description" content="Support the continuation of Hyprland Development" />
<meta
property="og:description"
content="Support the continuation of Hyprland Development"
/>
</svelte:head>
<div class="top-light"></div>
<section class="min-h-screen w-full md:mt-32">
<Title class="mb-4 md:mb-8">
<TitlePre slot="pre">Help continue Hyprland development</TitlePre>
<TitleHeading slot="title" class="">Support Hyprland</TitleHeading>
{#snippet pre()}
<TitlePre>Help continue Hyprland development</TitlePre>
{/snippet}
{#snippet title()}
<TitleHeading class="">Support Hyprland</TitleHeading>
{/snippet}
</Title>
<div
class="prose prose-slate prose-invert m-0 mx-auto flex flex-col px-6 transition-none delay-500 animate-in fade-in-0 slide-in-from-bottom-6 fill-mode-backwards [animation-delay:800ms] [animation-duration:1500ms] lg:prose-xl prose-a:text-cyan-400 prose-img:rounded-lg"
>
<p>
Hyprland development is done by volunteers, and led by one person in their free time. If you
want to show a token of appreciation, or help the development continue, consider supporting
the project!
Hyprland development is done by volunteers, and led by one
person in their free time. If you want to show a token of
appreciation, or help the development continue, consider
supporting the project!
</p>
<h2>Subscribe to Hyprperks</h2>
<p class="!mb-0 !pb-0">We offer hyprperks, a 5€+tax/mo subscription that is like a monthly donation,
but you also get access to member-only forums (with dev Q&A, support straight from the devs)
and Hyprland DE, a fully preconfigured, easy to use, one-click updating dotfiles
<p class="!mb-0 !pb-0">
We offer hyprperks, a 5€+tax/mo subscription that is like a
monthly donation, but you also get access to member-only forums
(with dev Q&A, support straight from the devs) and Hyprland DE,
a fully preconfigured, easy to use, one-click updating dotfiles
straight from the Hyprland team.
<br/>
<br/>
<br />
<br />
Check it out <a href="https://account.hypr.land/pricing" target="_blank">here</a>
Check it out
<a href="https://account.hypr.land/pricing" target="_blank"
>here</a
>
</p>
<h2>Donate</h2>
<p class="!mb-0 !pb-0">You can donate once, or monthly, via the following channels:</p>
<p class="!mb-0 !pb-0">
You can donate once, or monthly, via the following channels:
</p>
<ul class="">
<li>
PayPal: <a href="https://ko-fi.com/vaxry" target="_blank">ko-fi.com/vaxry</a>
PayPal: <a href="https://ko-fi.com/vaxry" target="_blank"
>ko-fi.com/vaxry</a
>
</li>
<li>
Crypto: please see the pinned post <a href="https://ko-fi.com/vaxry" target="_blank">here</a
Crypto: please see the pinned post <a
href="https://ko-fi.com/vaxry"
target="_blank">here</a
>
</li>
</ul>
@ -67,11 +86,13 @@
<h2>Do I get anything?</h2>
<p>
If you decide to donate, you will be listed in the special thanks part of the next Hyprland
release notes, and, if you are a member of the Discord server, a role to signify you have
If you decide to donate, you will be listed in the special
thanks part of the next Hyprland release notes, and, if you are
a member of the Discord server, a role to signify you have
supported the project.
<br /><br />
Outside of that, you get the satisfaction that you rock and support the software you use and love!
Outside of that, you get the satisfaction that you rock and support
the software you use and love!
</p>
<div
@ -87,8 +108,9 @@
>
<p>
Hyprland is, and will always stay Free and Open Source software. Donating is purely
voluntary.<br />We will never lock out features behind a paywall.
Hyprland is, and will always stay Free and Open Source
software. Donating is purely voluntary.<br />We will never
lock out features behind a paywall.
</p>
</div>
@ -101,11 +123,12 @@
<div class="relative h-[712px] min-h-[712px] shadow-2xl">
<form class="absolute -right-2 -top-4 z-10">
<button
on:click={() => {
onclick={() => {
unlockScroll()
}}
formmethod="dialog"
class="rounded-full bg-white p-1 shadow-md"><CloseIcon class="size-5" /></button
class="rounded-full bg-white p-1 shadow-md"
><CloseIcon class="size-5" /></button
>
</form>
<div class="modal-content overflow-hidden rounded-2xl">
@ -133,7 +156,8 @@
}
.top-light {
background: url('/imgs/grain.webp'),
background:
url('/imgs/grain.webp'),
radial-gradient(
100% 80% at top,
theme(colors.cyan.500 / 50%) 0%,

View file

@ -10,8 +10,12 @@ const mdsvexOptions = {
extensions: ['.md'],
highlight: {
highlighter: async (code, lang = 'text') => {
const highlighter = await getHighlighter({ theme: 'github-dark' })
const html = escapeSvelte(highlighter.codeToHtml(code, { lang }))
const highlighter = await getHighlighter({
theme: 'github-dark'
})
const html = escapeSvelte(
highlighter.codeToHtml(code, { lang })
)
return `{@html \`${html}\` }`
}
},
@ -23,7 +27,10 @@ const mdsvexOptions = {
const config = {
extensions: ['.svelte', '.md'],
kit: { adapter: adapter() },
preprocess: [vitePreprocess(), mdsvex(mdsvexOptions)]
preprocess: [
vitePreprocess({ script: true }),
mdsvex(mdsvexOptions)
]
}
export default config

View file

@ -3,10 +3,17 @@ const colors = require('tailwindcss/colors')
/** @type {import('tailwindcss').Config} */
export default {
content: ['./src/**/**/*.{html,js,svelte,ts}', './src/content/profiles.json'],
content: [
'./src/**/**/*.{html,js,svelte,ts}',
'./src/content/profiles.json'
],
theme: {
extend: {
colors: { black: '#090b0c', primary: '#58E1FF', secondary: '#00A2F8' },
colors: {
black: '#090b0c',
primary: '#58E1FF',
secondary: '#00A2F8'
},
fontFamily: {
...fontFamily,
sans: ['Inter Variable', ...fontFamily['sans']],
@ -19,7 +26,7 @@ export default {
screens: {
'3xl': '2560px',
'nav': '1124px'
nav: '1124px'
},
typography: {