Styling basics

This commit is contained in:
2024-05-11 00:59:50 +03:00
parent 14ce3c0662
commit dac057c052
25 changed files with 1277 additions and 52 deletions
+21
View File
@@ -0,0 +1,21 @@
<script>
/**
* @type {import("$types/base").User}
*/
// export let user;
/**
* @type {number}
*/
export let size;
</script>
<style>
.icon {
border-radius: 100%;
}
</style>
<svg class="icon" viewBox="0 0 100 100" height={size} width={size}>
<use href="/avatar.svg#avatar"></use>
</svg>
+12 -3
View File
@@ -1,5 +1,6 @@
<script>
import Rating from "./rating.svelte";
import Mention from "./mention.svelte";
import Rating from "./rating.svelte";
/**
* @type {import("$types/base").CommentTreeNode}
@@ -9,11 +10,19 @@
$: comment = commentNode.parent;
</script>
<style>
.replyTree {
padding-left: 20px;
margin-left: 10px;
border-left: 2px #fff5 solid;
}
</style>
<div>
<h5>{comment.author?.name}</h5>
<Mention user={comment.author}></Mention>
<Rating rating={comment.rating}></Rating>
<p>{comment.content}</p>
<div>
<div class="replyTree">
{#each commentNode.children as reply}
<svelte:self commentNode={reply}></svelte:self>
{/each}
+100
View File
@@ -0,0 +1,100 @@
<script>
/**
* @type {number}
*/
export let borderRadius;
/**
* @type {string}
*/
export let color = "white";
/**
* @type {number}
*/
export let opacity = 0.5;
/**
* @type {string | undefined | null}
*/
export let style = undefined;
/** @type {Element} */
let filterElement;
let mouseX = 0;
let mouseY = 0;
function getElementGradientSize(/** @type {Element} */ element) {
if (element === undefined) return 100;
const rect = element.getBoundingClientRect();
return Math.max(rect.width, rect.height);
}
function updateMousePosition() {
const rect = filterElement.getBoundingClientRect();
const y = mouseY - rect.top;
const x = mouseX - rect.left;
filterElement.setAttribute('style', `--cur-x: ${x / rect.width * 100}%; --cur-y: ${y / rect.height * 100}%;`)
}
function mouseEnterRegion(/** @type {MouseEvent} */ event) {
filterElement.setAttribute('show', '');
}
function mouseMove(/** @type {MouseEvent} */ event) {
mouseX = event.clientX;
mouseY = event.clientY;
updateMousePosition();
}
function mouseLeaveRegion(/** @type {MouseEvent} */ event) {
filterElement.removeAttribute('show');
}
$: size = getElementGradientSize(filterElement);
</script>
<style>
.hoverFx {
position: relative;
}
.filter {
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
pointer-events: none;
--cur-x: 0%;
--cur-y: 0%;
border-radius: var(--border);
background: radial-gradient(circle var(--size) at var(--cur-x) var(--cur-y), var(--color) 0%, transparent 100%);
opacity: 0.0;
mix-blend-mode: overlay;
transition: 0.2s;
&[show] {
opacity: var(--opacity);
}
}
</style>
<!-- svelte-ignore a11y-no-static-element-interactions -->
<div class="hoverFx" on:mouseenter={mouseEnterRegion} on:mousemove={mouseMove} on:mouseleave={mouseLeaveRegion} style="--border: {borderRadius}px; --color: {color}; --opacity: {opacity}; --size: {size}px; {style}" {...$$restProps}>
<slot />
<div class="filter" style="--cur-x: 100%; --cur-y: 100%;" bind:this={filterElement}></div>
</div>
<svelte:window on:resize={updateMousePosition}></svelte:window>
+79
View File
@@ -0,0 +1,79 @@
<script>
/**
* @type {number}
*/
export let maxRotation = 1.5;
/**
* @type {number}
*/
export let translateZ = 5;
/** @type {Element} */
let element;
let mouseX = 0;
let mouseY = 0;
function updateMousePosition() {
const rect = element.getBoundingClientRect();
const y = mouseY - rect.top;
const x = mouseX - rect.left;
const mulForX = Math.max(rect.height / rect.width, 1.0);
const mulForY = Math.max(rect.width / rect.height, 1.0);
const bound = Math.max(rect.height, rect.width);
const rotation = maxRotation / bound * 200;
element.setAttribute('style', `--max-rotation: ${maxRotation}; --translate-z: ${translateZ}px; --cur-x: ${(-x / rect.width + 0.5) * rotation * mulForX}deg; --cur-y: ${(y / rect.height - 0.5) * rotation * mulForY}deg;`)
}
function mouseEnterRegion(/** @type {MouseEvent} */ event) {
element.setAttribute('transform', '');
}
function mouseMove(/** @type {MouseEvent} */ event) {
mouseX = event.clientX;
mouseY = event.clientY;
updateMousePosition();
}
function mouseLeaveRegion(/** @type {MouseEvent} */ event) {
element.removeAttribute('transform');
}
</script>
<style>
.perspectiveFx {
position: relative;
transform: perspective(180px) translateZ(0px);
transition: 0.2s;
transition-property: transform;
& > .subPerspectiveFx {
transform: perspective(180px) rotateX(0deg) rotateY(0deg);
}
&[transform] {
transform: perspective(180px) translateZ(var(--translate-z));
& > .subPerspectiveFx {
transform: perspective(180px) rotateX(var(--cur-y)) rotateY(var(--cur-x));
}
}
}
</style>
<!-- svelte-ignore a11y-no-static-element-interactions -->
<div class="perspectiveFx" bind:this={element} on:mouseenter={mouseEnterRegion} on:mousemove={mouseMove} on:mouseleave={mouseLeaveRegion} {...$$restProps}>
<div class="subPerspectiveFx">
<slot />
</div>
</div>
<svelte:window on:resize={updateMousePosition}></svelte:window>
+61
View File
@@ -0,0 +1,61 @@
<script>
import { getNamedId } from "$lib/util";
/**
* @type {import("$types/base").User | undefined | null}
*/
export let user;
</script>
<style>
.mention {
display: flex;
flex-flow: row nowrap;
gap: 8px;
text-decoration: none;
}
.mentionBlock {
display: flex;
flex-flow: row nowrap;
gap: 0.2em;
align-items: baseline;
width: max-content;
text-decoration: none;
color: var(--accent);
padding: 0em 0.2em;
background: var(--accent-very-dim);
border-radius: 0.2em;
&:hover {
color: white;
background: var(--accent-dim);
}
}
.at {
color: inherit;
font-size: 0.8em;
}
</style>
{#if user}
<a class="mention" href="/users/{getNamedId(user.id, user.name)}">
<slot />
<div class="mentionBlock">
<span class="at">@</span>
{user.name}
</div>
</a>
{:else}
<span class="mention">
<slot />
<div class="mentionBlock">
<span class="at">@</span>
Deleted User
</div>
</span>
{/if}
-17
View File
@@ -1,17 +0,0 @@
<script>
import { getNamedId } from '$lib/util';
import Rating from './rating.svelte';
/**
* @type {import('$types/base').Post}
*/
export let post;
</script>
<div>
<a href="/posts/{getNamedId(post.id, post.name)}">{post.name}</a>
<p>{post.author?.name}</p>
<p>{post.category.name}</p>
<Rating rating={post.rating}></Rating>
<p>{post.content}</p>
</div>
+100
View File
@@ -0,0 +1,100 @@
<script>
import { getNamedId } from '$lib/util';
import Avatar from './avatar.svelte';
import GlowFX from './fx/glowfx.svelte';
import PerspectiveFX from './fx/perspectivefx.svelte';
import Mention from './mention.svelte';
import Rating from './rating.svelte';
import RatingVertical from './ratingvertical.svelte';
import Useritem from './useritem.svelte';
/**
* @type {import('$types/base').Post}
*/
export let post;
</script>
<style type="scss">
.postCardOutline {
border-radius: 18px;
padding: 2px;
position: relative;
background-size: 350vw;
background-position: 90% 0%;
background-image: linear-gradient(30deg, #7d65ff30 45%, white 50%, transparent 55%);
transition: ease-out 0.5s;
&:hover {
background-position: 0% 0%;
& .postCard {
background-position: 0% 0%;
}
}
}
.postCard {
display: block;
text-decoration: none;
border-radius: 16px;
padding: 16px;
background: #12121b;
background-size: 300vw;
background-position: 100% 0%;
background-image: linear-gradient(30deg, #131320 40%, #373052 50%, #12121b 60%);
position: relative;
transition: ease-out 0.2s;
}
.title {
font-size: 1.2rem;
margin: 0;
}
.postContainer {
display: flex;
flex-flow: row nowrap;
gap: 8px;
}
.sidePad {
display: flex;
flex-flow: column nowrap;
padding: 18px 0px;
gap: 16px;
}
.mention {
text-decoration: none;
color: var(--accent);
padding: 0em 0.2em;
background: var(--accent-dim);
border-radius: 0.2em;
}
</style>
<div class="postContainer">
<div class="sidePad">
<Avatar size={32}></Avatar>
<RatingVertical rating={post.rating}></RatingVertical>
</div>
<GlowFX borderRadius={18} opacity={0.7} style="flex: 1;">
<div class="postCardOutline">
<a class="postCard" href="/posts/{getNamedId(post.id, post.name)}">
<Mention user={post.author}></Mention>
<h6 class="title taviraj-regular">{post.name}</h6>
<p>{post.category.name}</p>
<p>{post.content}</p>
</a>
</div>
</GlowFX>
</div>
+11 -3
View File
@@ -1,5 +1,5 @@
<script>
import Post from './post.svelte';
import Postcard from './postcard.svelte';
/**
* @type {import("$types/base").Post[]}
@@ -7,8 +7,16 @@
export let posts = [];
</script>
<div>
<style>
.postList {
display: flex;
flex-direction: column;
gap: 16px;
}
</style>
<div class="postList">
{#each posts as post}
<Post post={post}></Post>
<Postcard post={post}></Postcard>
{/each}
</div>
+29 -3
View File
@@ -5,7 +5,33 @@
export let rating;
</script>
<div>
<p>up: {rating.likes}</p>
<p>down: {rating.dislikes}</p>
<style type="scss">
.rating {
display: flex;
flex-flow: row nowrap;
gap: 10px;
align-items: center;
}
svg.icon {
height: 16px;
width: 16px;
& > use {
color: white;
}
}
</style>
<div class="rating">
<svg class="icon" viewBox="0 0 24 24">
<use href="/icon/heart.svg#icon"></use>
</svg>
<span>{rating.likes}</span>
<svg class="icon" viewBox="0 0 24 24">
<use href="/icon/thumb-down.svg#icon"></use>
</svg>
<span>{rating.dislikes}</span>
</div>
+36
View File
@@ -0,0 +1,36 @@
<script>
/**
* @type {import("$types/base").Rating}
*/
export let rating;
</script>
<style type="scss">
.rating {
display: flex;
flex-flow: column nowrap;
gap: 10px;
align-items: center;
}
svg.icon {
height: 16px;
width: 16px;
& > use {
color: white;
}
}
</style>
<div class="rating">
<svg class="icon" viewBox="0 0 24 24">
<use href="/icon/heart.svg#icon"></use>
</svg>
<span>{rating.likes - rating.dislikes}</span>
<svg class="icon" viewBox="0 0 24 24">
<use href="/icon/thumb-down.svg#icon"></use>
</svg>
</div>
+42
View File
@@ -0,0 +1,42 @@
<script>
import GlowFX from "./fx/glowfx.svelte";
/**
* @type {import("$types/base").Category[]}
*/
export let categories;
</script>
<style>
aside {
display: flex;
flex-direction: column;
padding: 8px;
gap: 8px;
}
.button {
height: 32px;
width: 32px;
padding: 8px;
background-color: rgb(255, 88, 88, 0.2);
border-radius: 8px;
}
.button svg use {
color: rgb(255, 88, 88);
}
</style>
<aside>
{#each categories as category}
<GlowFX borderRadius={8}>
<div class="button">
<svg viewBox="0 0 24 24">
<use href="icon/bulb.svg#icon"></use>
</svg>
</div>
</GlowFX>
{/each}
</aside>
+22
View File
@@ -0,0 +1,22 @@
<script>
import Mention from "./mention.svelte";
/**
* @type {import("$types/base").User | undefined | null}
*/
export let user;
</script>
<style>
.icon {
width: 16px;
height: 16px;
border-radius: 100%;
}
</style>
<Mention user={user}>
<svg class="icon" viewBox="0 0 100 100">
<use href="/avatar.svg#avatar"></use>
</svg>
</Mention>
+7 -5
View File
@@ -31,7 +31,7 @@ const updateCategoryCache = cacheUpdater(cache);
/**
* @param {import('postgres').Sql} sql
* @param {number[]} user_ids
* @param {number[] | undefined} user_ids
* @returns {Promise<Result<Category>>}
*/
export const getCategoriesCached = cachedMethod(cache, getCategories);
@@ -40,16 +40,18 @@ export const getCategoriesCachedByRef = refExtendCachedMethod(getCategoriesCache
/**
* @param {import('postgres').Sql} sql
* @param {number[]} category_ids
* @param {number[] | undefined} category_ids
* @returns {Promise<Result<Category>>}
*/
export async function getCategories(sql, category_ids) {
if (category_ids.length == 0) return new Map();
export async function getCategories(sql, category_ids = undefined) {
if (category_ids !== undefined && category_ids.length == 0) return new Map();
const filter = category_ids ? sql`WHERE id IN ${ sql(category_ids) }` : sql``;
const query = sql`
SELECT id, name
FROM doki8902.post_category
WHERE id IN ${ sql(category_ids) };`;
${ filter };`;
let categories = await query;
+10
View File
@@ -0,0 +1,10 @@
import { getCategories } from "$lib/server/db/category";
/** @type {import("@sveltejs/kit").ServerLoad} */
export async function load({ locals }) {
const categories = await getCategories(locals.sql);
return {
categories: categories
};
}
+26 -3
View File
@@ -1,11 +1,34 @@
<script>
import Sidebar from "$comp/sidebar.svelte";
/**
* @type {{
* categories: import("$types/base").Category[]
* }}
*/
export let data;
$: categories = data.categories;
</script>
<style>
.content {
margin: 16px;
width: calc(100% - 16px);
display: flex;
flex-direction: row;
/* width: calc(100% - 16px); */
height: 100%;
}
main {
display: flex;
flex-direction: column;
flex: 1;
}
</style>
<div class="content">
<slot />
<Sidebar categories={categories}></Sidebar>
<main>
<slot />
</main>
</div>
+3 -5
View File
@@ -1,5 +1,7 @@
<script>
import Comment from "$comp/comment.svelte";
import Mention from "$comp/mention.svelte";
import UserItem from "$comp/useritem.svelte";
import { buildCommentTree } from "$lib/client/nodetree";
import { getNamedId, gotoNamedId } from "$lib/util";
import { onMount } from "svelte";
@@ -36,11 +38,7 @@
<div class="post">
<h1 class="taviraj-light">{post.name}</h1>
{#if post.author}
<a href="/users/{getNamedId(post.author.id, post.author.name)}">{post.author.name}</a>
{:else}
<p>Deleted</p>
{/if}
<UserItem user={post.author}></UserItem>
<p>{post.content}</p>
+2 -2
View File
@@ -7,8 +7,8 @@ export type User = {
};
export type Rating = {
likes: BigInt,
dislikes: BigInt
likes: bigint,
dislikes: bigint
}
export type Category = {