Styling basics
This commit is contained in:
@@ -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
@@ -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}
|
||||
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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}
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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
|
||||
};
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
@@ -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
@@ -7,8 +7,8 @@ export type User = {
|
||||
};
|
||||
|
||||
export type Rating = {
|
||||
likes: BigInt,
|
||||
dislikes: BigInt
|
||||
likes: bigint,
|
||||
dislikes: bigint
|
||||
}
|
||||
|
||||
export type Category = {
|
||||
|
||||
Reference in New Issue
Block a user