style: persona flow (#225)

Co-authored-by: Vojtech Simetka <vojtech@simetka.cz>
This commit is contained in:
Barbara Gomes 2023-02-28 09:30:53 -05:00 committed by GitHub
parent 5184eb6680
commit 777e1f18f9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
29 changed files with 1071 additions and 746 deletions

View File

@ -43,6 +43,7 @@
font-weight: 600;
font-size: var(--font-size-normal);
transition: border-color 0.2s, background-color 0.2s, color 0.2s;
white-space: nowrap;
&:disabled {
cursor: not-allowed;
@ -99,7 +100,7 @@
}
}
.secondary {
background-color: var(--color-body-bg);
background-color: transparent;
border-color: var(--grey-200);
color: var(--color-body-text);
@ -164,7 +165,7 @@
&:active:not(:disabled),
&:hover:not(:disabled) {
background-color: var(--color-black-rgb);
background-color: var(--color-black);
transition: background-color 0.2s;
}
}

View File

@ -0,0 +1,34 @@
<script lang="ts">
let cls: string | undefined = undefined
export { cls as class }
</script>
<div class={`container ${cls}`}>
<slot />
</div>
<style lang="scss">
.container {
padding: 0 var(--spacing-24);
transition: padding 0.2s;
max-width: 498px;
margin-inline: auto;
@media (min-width: 688px) {
padding: 0 var(--spacing-48);
max-width: 996px;
}
@media (min-width: 1242px) {
max-width: 1494px;
}
@media (min-width: 1640px) {
max-width: 1992px;
}
@media (min-width: 2038px) {
max-width: 2490px;
}
}
</style>

View File

@ -0,0 +1,81 @@
<script lang="ts">
</script>
<!-- svelte-ignore a11y-click-events-have-key-events -->
<div class="root card-wrapper" on:click>
<div class="card">
<slot />
</div>
<hr />
</div>
<style lang="scss">
.root {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
align-items: flex-end;
justify-content: flex-end;
hr {
@media (min-width: 688px) {
display: none;
}
}
&:hover {
background-color: var(--grey-150);
}
}
.card {
display: flex;
flex-direction: row;
align-items: flex-start;
justify-content: flex-start;
flex-wrap: nowrap;
gap: var(--spacing-12);
padding: var(--spacing-24);
cursor: pointer;
width: 100%;
max-width: 498px;
margin-inline: auto;
@media (min-width: 1242px) {
min-width: 350px;
}
}
@keyframes new {
from {
background-color: var(--success-highlight);
}
to {
background-color: transparent;
}
}
:global(.new) {
animation-name: new;
animation-duration: 2.5s;
animation-timing-function: ease-out;
}
@media (prefers-color-scheme: dark) {
.root {
&:not(:last-child) {
border-bottom-color: var(--grey-500);
}
&:hover {
background-color: var(--grey-500);
}
}
:global(svg) {
fill: var(--grey-100);
width: 16px;
height: 16px;
}
}
</style>

View File

@ -11,9 +11,9 @@
grid-auto-columns: auto;
grid-template-columns: 100%;
grid-auto-rows: auto;
align-items: end;
margin-inline: auto;
// decided to use max-width here to make it easier to match the page headers' width
@media (min-width: 688px) {
padding: 0 var(--spacing-24);
transition: padding 0.2s;

View File

@ -44,7 +44,7 @@
<Button
icon={Wallet}
variant={'primary'}
label={'Connect'}
label={y === 0 ? 'Connect' : ''}
on:click={() => handleConnect()}
/>
{/if}
@ -59,7 +59,7 @@
left: 0;
right: 0;
background-color: rgba(var(--color-body-bg-rgb), 0.93);
backdrop-filter: blur(3px);
backdrop-filter: blur(var(--blur));
transition: box-shadow 0.2s;
z-index: 100;
@ -69,26 +69,11 @@
align-items: center;
padding: var(--spacing-24);
transition: padding 0.2s;
max-width: 498px;
margin-inline: auto;
@media (min-width: 688px) {
padding: var(--spacing-48);
max-width: 996px;
transition: padding 0.2s;
}
@media (min-width: 1242px) {
max-width: 1494px;
}
@media (min-width: 1640px) {
max-width: 1992px;
}
@media (min-width: 2038px) {
max-width: 2490px;
}
}
.header-title {
@ -104,10 +89,8 @@
transition: box-shadow 0.2s;
.header-content {
@media (min-width: 688px) {
padding-block: var(--spacing-24);
transition: padding 0.2s;
}
padding-block: var(--spacing-12);
transition: padding 0.2s;
}
@media (prefers-color-scheme: dark) {
box-shadow: 0 1px 5px 0 rgba(var(--color-body-bg-rgb), 0.75);

View File

@ -0,0 +1,98 @@
<script lang="ts">
import Button from './button.svelte'
import Undo from '$lib/components/icons/undo.svelte'
let cls: string | undefined = undefined
export { cls as class }
let y: number
export let onBack: (() => unknown) | undefined = undefined
export let title = ''
</script>
<svelte:window bind:scrollY={y} />
<header class={`root ${y > 0 ? 'scrolled' : ''} ${cls}`}>
<div class="content">
<div>
{#if typeof onBack === 'function'}
<Button icon={Undo} on:click={onBack} />
{/if}
</div>
<h1 class={`title ${cls}`}>
{title}
</h1>
<div class="btns">
<slot />
</div>
</div>
</header>
<style lang="scss">
header.root {
position: sticky;
inset: 0 -10px auto;
background-color: rgba(var(--color-body-bg-rgb), 0.93);
backdrop-filter: blur(var(--blur));
z-index: 100;
padding-inline: var(--spacing-24);
padding-block: var(--spacing-24);
transition: box-shadow 0.2s, padding 0.2s;
@media (min-width: 688px) {
padding-block: var(--spacing-48);
padding-inline: var(--spacing-48);
transition: padding 0.2s;
}
.content {
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
gap: var(--spacing-12);
> * {
flex-basis: 65%;
&.title:not(:empty) {
flex-basis: 100%;
text-align: center;
}
&:last-child {
display: flex;
justify-content: flex-end;
}
}
.btns:not(:empty) {
display: flex;
justify-content: flex-end;
align-items: center;
flex-direction: row;
gap: var(--spacing-12);
}
}
.title {
font-family: var(--font-body);
font-weight: 600;
font-size: 18px;
font-style: normal;
text-align: center;
}
&.scrolled {
box-shadow: 0 6px 6px -6px rgba(var(--color-body-text-rgb), 0.25);
padding-block: var(--spacing-12);
padding-inline: 12px;
transition: box-shadow 0.2s, padding 0.2s;
// @media (prefers-color-scheme: dark) {
// box-shadow: 0 1px 5px 0 rgba(var(--color-body-bg-rgb), 0.75);
// }
}
}
</style>

View File

@ -0,0 +1,15 @@
<script lang="ts">
import type { IconProps } from '$lib/types'
type $$Props = IconProps
let cls: string | undefined = undefined
export { cls as class }
export let size = 20
</script>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" width={size} height={size} class={cls}>
<path
d="M23,11.67V4h3V2H6V4H9v7.67a2,2,0,0,0,.4,1.2L11.75,16,9.4,19.13a2,2,0,0,0-.4,1.2V28H6v2H26V28H23V20.33a2,2,0,0,0-.4-1.2L20.25,16l2.35-3.13A2,2,0,0,0,23,11.67ZM21,4v7H11V4Zm0,16.33V28H11V20.33L14.25,16,12,13h8l-2.25,3Z"
/>
</svg>

View File

@ -0,0 +1,16 @@
<script lang="ts">
import type { IconProps } from '$lib/types'
type $$Props = IconProps
let cls: string | undefined = undefined
export { cls as class }
export let size = 20
</script>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" width={size} height={size} class={cls}>
<path
d="M17 22L17 14 13 14 13 16 15 16 15 22 12 22 12 24 20 24 20 22 17 22zM16 8a1.5 1.5 0 101.5 1.5A1.5 1.5 0 0016 8z"
/>
<path d="M16,30A14,14,0,1,1,30,16,14,14,0,0,1,16,30ZM16,4A12,12,0,1,0,28,16,12,12,0,0,0,16,4Z" />
</svg>

View File

@ -0,0 +1,16 @@
<script lang="ts">
import type { IconProps } from '$lib/types'
type $$Props = IconProps
let cls: string | undefined = undefined
export { cls as class }
export let size = 20
</script>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" width={size} height={size} class={cls}>
<path d="M22,22v6H6V4H16V2H6A2,2,0,0,0,4,4V28a2,2,0,0,0,2,2H22a2,2,0,0,0,2-2V22Z" />
<path
d="M29.54,5.76l-3.3-3.3a1.6,1.6,0,0,0-2.24,0l-14,14V22h5.53l14-14a1.6,1.6,0,0,0,0-2.24ZM14.7,20H12V17.3l9.44-9.45,2.71,2.71ZM25.56,9.15,22.85,6.44l2.27-2.27,2.71,2.71Z"
/>
</svg>

View File

@ -0,0 +1,16 @@
<script lang="ts">
import type { IconProps } from '$lib/types'
type $$Props = IconProps
let cls: string | undefined = undefined
export { cls as class }
export let size = 20
</script>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" width={size} height={size} class={cls}>
<path d="M6.34 19H17.65V21H6.34z" transform="rotate(-45 11.995 20.002)" />
<path
d="M17,30a1,1,0,0,1-.37-.07,1,1,0,0,1-.62-.79l-1-7,2-.28.75,5.27L21,24.52V17a1,1,0,0,1,.29-.71l4.07-4.07A8.94,8.94,0,0,0,28,5.86V4H26.14a8.94,8.94,0,0,0-6.36,2.64l-4.07,4.07A1,1,0,0,1,15,11H7.48L4.87,14.26l5.27.75-.28,2-7-1a1,1,0,0,1-.79-.62,1,1,0,0,1,.15-1l4-5A1,1,0,0,1,7,9h7.59l3.77-3.78A10.92,10.92,0,0,1,26.14,2H28a2,2,0,0,1,2,2V5.86a10.92,10.92,0,0,1-3.22,7.78L23,17.41V25a1,1,0,0,1-.38.78l-5,4A1,1,0,0,1,17,30Z"
/>
</svg>

View File

@ -0,0 +1,29 @@
<script lang="ts">
let cls: string | undefined = undefined
export { cls as class }
</script>
<div class={`info ${cls}`}>
<slot />
</div>
<style lang="scss">
.info {
text-align: center;
padding-block: var(--spacing-24);
transition: padding 0.2s;
:global(p) {
margin-bottom: var(--spacing-6);
}
:global(.h2) {
margin-bottom: var(--spacing-12);
}
@media (min-width: 688px) {
padding-block: var(--spacing-48);
transition: padding 0.2s;
}
}
</style>

View File

@ -1,23 +1,12 @@
<script lang="ts">
import Undo from '$lib/components/icons/undo.svelte'
import Button from '$lib/components/button.svelte'
let y: number
import Header from '$lib/components/header.svelte'
export let title: string
export let onBack: () => unknown = () => history.back()
</script>
<svelte:window bind:scrollY={y} />
<header class={y > 0 ? 'scrolled' : ''}>
<div class="header-content">
<div class="btn-undo">
<Button icon={Undo} on:click={onBack} />
</div>
<h1>{title}</h1>
</div>
</header>
<Header {title} {onBack} />
<div class="content">
<slot />
@ -28,78 +17,6 @@
</div>
<style lang="scss">
header {
position: sticky;
top: 0;
left: 0;
right: 0;
background-color: rgba(var(--color-body-bg-rgb), 0.93);
backdrop-filter: blur(3px);
z-index: 100;
padding: var(--spacing-24);
transition: padding 0.2s, box-shadow 0.2s;
@media (prefers-color-scheme: dark) {
box-shadow: 0 1px 5px 0 rgba(var(--color-body-bg-rgb), 0.75);
}
@media (min-width: 688px) {
padding: var(--spacing-48);
transition: padding 0.2s;
}
.header-content {
position: relative;
max-width: 450px;
margin-inline: auto;
display: flex;
align-items: center;
justify-content: center;
transition: max-width 0.2s;
@media (min-width: 688px) {
max-width: 996px;
transition: max-width 0.2s;
}
@media (min-width: 1242px) {
max-width: 1494px;
}
@media (min-width: 1640px) {
max-width: 1992px;
}
@media (min-width: 2038px) {
max-width: 2490px;
}
}
.btn-undo {
position: absolute;
inset: 0 0 auto 0;
}
h1 {
font-family: var(--font-body);
font-weight: 600;
font-size: 18px;
font-style: normal;
text-align: center;
line-height: 44px;
}
&.scrolled {
box-shadow: 0 1px 5px 0 rgba(var(--color-body-text-rgb), 0.25);
transition: box-shadow 0.2s;
@media (min-width: 688px) {
padding-block: var(--spacing-24);
transition: padding 0.2s;
}
}
}
.content {
min-height: calc(100dvh - 92px);
min-height: calc(100vh - 92px);
@ -118,7 +35,6 @@
align-items: center;
justify-content: center;
gap: var(--spacing-12);
padding-top: var(--spacing-48);
}
}
</style>

View File

@ -7,7 +7,7 @@
export let disabled: boolean | undefined = undefined
export let label: string | undefined = undefined
export let icon: ComponentConstructor<IconProps> | undefined = undefined
export let variant: 'secondary' | 'primary' = 'secondary'
export let variant: 'secondary' | 'primary' | 'overlay' = 'secondary'
</script>
<label class={`root ${variant} ${cls}`}>
@ -28,9 +28,6 @@
padding-left: var(--spacing-12);
padding-right: var(--spacing-12);
height: 44px;
outline-width: 1px;
outline-style: solid;
outline-offset: -1px;
border-width: 1px;
border-style: solid;
box-sizing: border-box;
@ -40,11 +37,11 @@
flex-direction: row;
align-items: center;
justify-content: center;
gap: var(--spacing-6);
font-family: var(--font-body);
font-weight: 600;
font-size: var(--font-size-normal);
transition: outline-width 0.1s, outline-color 0.1s, outline-style 0.1s, border-color 0.1s,
outline-offset 0.1s, background-color 0.2s, color 0.2s;
transition: border-color 0.2s, background-color 0.2s, color 0.2s;
&:disabled {
cursor: not-allowed;
@ -60,11 +57,9 @@
.wrapper {
width: 20px;
height: 20px;
margin-right: var(--spacing-6);
}
.primary {
color: var(--color-body-bg);
outline-color: var(--color-body-text);
border-color: var(--color-body-text);
background-color: var(--color-body-text);
@ -75,22 +70,19 @@
&:disabled {
background-color: var(--grey-200);
border-color: var(--grey-200);
outline-color: var(--grey-200);
color: var(--color-body-bg);
}
&:active:not(:disabled),
&:hover:not(:disabled) {
outline-width: 3px;
transition: outline-width 0.1s, outline-color 0.1s, outline-style 0.1s, border-color 0.1s,
outline-offset 0.1s;
border-color: var(--color-black);
background-color: var(--color-black);
transition: border-color 0.2s, background-color 0.2s;
}
@media (prefers-color-scheme: dark) {
&:disabled {
background-color: var(--grey-500);
border-color: transparent;
outline-color: transparent;
background-color: var(--grey-200);
color: var(--color-body-bg);
& :global(svg) {
@ -100,16 +92,13 @@
&:active:not(:disabled),
&:hover:not(:disabled) {
outline-width: 3px;
transition: outline-width 0.1s, outline-color 0.1s, outline-style 0.1s, border-color 0.1s,
outline-offset 0.1s;
transition: border-color 0.2s, background-color 0.2s;
}
}
}
.secondary {
background-color: var(--color-body-bg);
border-color: transparent;
outline-color: var(--grey-200);
border-color: var(--grey-200);
color: var(--color-body-text);
& :global(svg) {
@ -126,19 +115,13 @@
&:active:not(:disabled),
&:hover:not(:disabled) {
outline-width: 1px;
outline-color: var(--color-body-text);
outline-offset: 2px;
transition: outline-width 0.1s, outline-color 0.1s, outline-style 0.1s, border-color 0.1s,
outline-offset 0.1s;
background-color: var(--grey-150);
transition: border-color 0.2s, background-color 0.2s;
}
@media (prefers-color-scheme: dark) {
background-color: var(--color-body-bg);
// border-color: var(--color-body-text);
outline-width: 1px;
outline-offset: 0px;
outline-color: var(--grey-500);
border-color: var(--color-body-text);
color: var(--color-body-text);
& :global(svg) {
@ -147,8 +130,6 @@
&:disabled {
background-color: var(--color-body-bg);
outline-color: var(--grey-500);
border-color: transparent;
color: var(--grey-500);
& :global(svg) {
@ -158,11 +139,31 @@
&:active:not(:disabled),
&:hover:not(:disabled) {
outline-offset: 2px;
border-color: transparent;
transition: outline-width 0.1s, outline-color 0.1s, outline-style 0.1s, border-color 0.1s,
outline-offset 0.1s;
transition: border-color 0.2s, background-color 0.2s;
}
}
}
.overlay {
background-color: rgba(var(--color-black-rgb), 0.5);
border-color: transparent;
backdrop-filter: blur(var(--blur));
color: var(--color-body-bg);
& :global(svg) {
fill: var(--color-body-bg);
}
&:disabled {
color: var(--grey-300);
& :global(svg) {
fill: var(--grey-300);
}
}
&:active:not(:disabled),
&:hover:not(:disabled) {
background-color: var(--color-black);
transition: background-color 0.2s;
}
}
</style>

View File

@ -0,0 +1,25 @@
<script lang="ts">
import ArrowRight from '$lib/components/icons/arrow-right.svelte'
export let href: string | undefined = undefined
export let target: string | undefined = '_blank'
export let arrow: boolean | undefined = undefined
</script>
<a class="root" {href} {target}>
Learn more
{#if arrow === true}
<ArrowRight size={12} />
{/if}
</a>
<style lang="scss">
.root {
display: flex;
justify-content: center;
align-items: center;
flex-direction: row;
flex-wrap: nowrap;
gap: 3px;
}
</style>

View File

@ -0,0 +1,25 @@
<script lang="ts">
import type { ComponentConstructor, IconProps } from '$lib/types'
export let icon: ComponentConstructor<IconProps> | undefined = undefined
</script>
<div class="info-banner">
<svelte:component this={icon} />
<slot />
</div>
<style lang="scss">
.info-banner {
position: sticky;
inset: 0 0 auto;
z-index: 100;
background-color: var(--grey-200);
padding: var(--spacing-12);
display: flex;
align-items: center;
justify-content: center;
flex-direction: row;
gap: var(--spacing-6);
font-size: 14px;
}
</style>

View File

@ -1,74 +1,33 @@
<script lang="ts">
import Card from '$lib/components/grid-card.svelte'
import UserMultiple from './icons/user-multiple.svelte'
import Forum from './icons/forum.svelte'
let cls: string | undefined = undefined
export { cls as class }
export let name: string | undefined
export let description: string | undefined
export let postsCount: number
export let picture: string | undefined
</script>
<div class="persona-card-wrapper">
<!-- svelte-ignore a11y-click-events-have-key-events -->
<div class={`root ${cls}`} on:click>
<div class="picture"><img src={picture} alt="persona" /></div>
<div class="details">
<div class="header">{name}</div>
<div class="description">{description}</div>
<div class="post-count">
<div>
<UserMultiple size={18} />
{postsCount}
</div>
<div>
<Forum size={18} />
{postsCount}
</div>
<Card on:click>
<div class="picture"><img src={picture} alt="persona" /></div>
<div class="details">
<div class="header">{name}</div>
<div class="description">{description}</div>
<div class="post-count">
<div>
<UserMultiple size={18} />
{postsCount}
</div>
<div>
<Forum size={18} />
{postsCount}
</div>
</div>
</div>
<hr />
</div>
</Card>
<style lang="scss">
.persona-card-wrapper {
width: 100%;
display: flex;
align-items: flex-end;
hr {
@media (min-width: 688px) {
display: none;
}
}
}
.root {
display: flex;
flex-direction: row;
align-items: flex-start;
justify-content: flex-start;
flex-wrap: nowrap;
gap: var(--spacing-12);
padding: var(--spacing-24);
cursor: pointer;
width: 100%;
max-width: 498px;
margin-inline: auto;
@media (min-width: 1242px) {
min-width: 350px;
}
&:hover {
background-color: var(--grey-150);
}
@media (prefers-color-scheme: dark) {
}
}
.picture {
flex: 0 0 90px;
aspect-ratio: 1;
@ -125,22 +84,4 @@
}
}
}
@media (prefers-color-scheme: dark) {
.root {
&:not(:last-child) {
border-bottom-color: var(--grey-500);
}
&:hover {
background-color: var(--grey-500);
}
}
:global(svg) {
fill: var(--grey-100);
width: 16px;
height: 16px;
}
}
</style>

View File

@ -2,12 +2,14 @@
import Image from '$lib/components/icons/image.svelte'
import Renew from '$lib/components/icons/renew.svelte'
import Undo from '$lib/components/icons/undo.svelte'
import UserMultiple from './icons/user-multiple.svelte'
import Forum from './icons/forum.svelte'
import Profile from '$lib/components/profile-default.svelte'
import Button from '$lib/components/button.svelte'
import InputFile from '$lib/components/input-file.svelte'
import Container from '$lib/components/container.svelte'
import { clipAndResize } from '$lib/utils/image'
import { MAX_DIMENSIONS } from '$lib/constants'
export let name: string
@ -17,6 +19,7 @@
export let picture: string | undefined
export let cover: string | undefined
export let canEditPictures = false
export let postsCount: number | undefined = undefined
let coverFiles: FileList | undefined = undefined
let pictureFiles: FileList | undefined = undefined
@ -52,11 +55,11 @@
{/if}
</div>
<div class="buttons">
<Button icon={Undo} variant="primary" on:click={onBack} />
<Button icon={Undo} variant="overlay" on:click={onBack} />
{#if canEditPictures}
<InputFile
icon={cover ? Renew : Image}
variant="primary"
variant="overlay"
label={cover ? 'Change cover' : 'Add cover'}
bind:files={coverFiles}
/>
@ -68,25 +71,41 @@
{#if picture}
<div class="img">
<img src={picture} alt="profile" />
</div>
{#if canEditPictures}
<div class="change">
<InputFile icon={Renew} variant="primary" bind:files={pictureFiles} />
</div>
{/if}
<div class="change">
<InputFile icon={Renew} variant="primary" bind:files={pictureFiles} />
{#if canEditPictures}
<div class="change">
<InputFile icon={Renew} variant="overlay" bind:files={pictureFiles} />
</div>
{/if}
</div>
{:else if canEditPictures}
<div class="empty">
<InputFile icon={Image} variant="primary" label="Add picture" bind:files={pictureFiles} />
<div class="no-img">
<div class="empty">
<InputFile icon={Image} variant="overlay" label="Add profile" bind:files={pictureFiles} />
</div>
<div class="profile-default">
<Profile />
</div>
</div>
{/if}
</div>
<div>{name}</div>
<div>{pitch}</div>
<div>{description}</div>
<Container>
<div class="persona-info">
<h1 class="name">{name}</h1>
<div class="pitch">{pitch}</div>
<div class="description">{description}</div>
<div class="post-count">
<div>
<UserMultiple size={18} />
{postsCount}
</div>
<div>
<Forum size={18} />
{postsCount}
</div>
</div>
</div>
</Container>
<div class="buttons-bottom">
<slot name="button_primary" />
@ -97,77 +116,153 @@
<style lang="scss">
.top {
height: 360px;
position: absolute;
aspect-ratio: 16/9;
max-height: 342px;
width: 100vw;
background-color: #666666;
position: absolute;
z-index: -1;
overflow: hidden;
@media (min-width: 608px) {
aspect-ratio: none;
height: 342px;
}
.img {
position: absolute;
width: inherit;
height: inherit;
inset: 0 0 0 0;
display: flex;
justify-content: center;
align-items: center;
img {
width: inherit;
height: inherit;
width: 100%;
object-fit: cover;
}
}
}
.buttons {
padding: 48px;
display: flex;
justify-content: space-between;
align-items: center;
padding: var(--spacing-24);
transition: padding 0.2s;
@media (min-width: 688px) {
padding: var(--spacing-48);
transition: padding 0.2s;
}
}
.avatar {
width: 100vw;
position: relative;
margin-bottom: var(--spacing-12);
.no-img,
.img {
aspect-ratio: 1;
height: calc(calc(100vw / 1.777) - 68px);
display: flex;
justify-content: center;
align-items: center;
background-color: #c9c9c9;
margin-inline: auto;
position: relative;
@media (min-width: 608px) {
height: 274px;
}
@media (min-width: 688px) {
height: 250px;
}
img {
aspect-ratio: 1;
object-fit: cover;
}
}
.empty,
.change {
position: absolute;
inset: auto var(--spacing-12) var(--spacing-12) auto;
transform: none;
width: max-content;
height: fit-content;
display: flex;
justify-content: center;
align-items: center;
z-index: 10;
}
.profile-default {
position: relative;
width: 100%;
height: 100%;
:global(svg) {
fill: var(--grey-300);
position: absolute;
inset: auto;
}
}
}
.persona-info {
text-align: center;
max-width: 738px;
margin-inline: auto;
.name,
.pitch,
.description {
margin-bottom: var(--spacing-12);
max-width: 498px;
margin-inline: auto;
}
.description {
font-family: var(--font-serif);
}
}
.post-count {
font-size: 12px;
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
justify-content: center;
flex-wrap: nowrap;
gap: var(--spacing-12);
> div {
display: flex;
flex-direction: row;
align-items: center;
justify-content: flex-start;
flex-wrap: nowrap;
gap: var(--spacing-3);
}
}
.buttons-bottom {
padding: 48px;
position: relative;
padding: var(--spacing-24);
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
}
.avatar {
width: 268px;
height: 268px;
background-color: #c9c9c9;
margin: auto;
gap: var(--spacing-12);
.img {
position: absolute;
width: inherit;
height: inherit;
display: flex;
justify-content: center;
align-items: center;
img {
width: inherit;
height: inherit;
object-fit: cover;
}
}
.empty {
width: inherit;
height: inherit;
position: absolute;
display: flex;
justify-content: center;
align-items: center;
}
.change {
width: inherit;
height: inherit;
position: absolute;
display: flex;
justify-content: flex-end;
align-items: flex-end;
padding: 12px;
@media (min-width: 688px) {
padding: var(--spacing-48);
}
}
// @media (prefers-color-scheme: dark) {
// .buttons-bottom {
// border-bottom: 1px solid var(--grey-500);
// }
// }
</style>

View File

@ -1,9 +1,9 @@
<script lang="ts">
import Undo from '$lib/components/icons/undo.svelte'
import Close from '$lib/components/icons/close.svelte'
import ArrowRight from '$lib/components/icons/arrow-right.svelte'
import Button from '$lib/components/button.svelte'
import Textarea from '$lib/components/textarea.svelte'
import Header from '$lib/components/header.svelte'
import Checkmark from './icons/checkmark.svelte'
let y: number
@ -11,20 +11,14 @@
export let pitch = ''
export let description = ''
export let title: string
export let onCancel: () => void | Promise<void>
export let onSubmit: () => void | Promise<void>
export let onCancel: () => unknown
export let onSubmit: () => unknown
export let onBack: undefined | (() => unknown) = undefined
</script>
<svelte:window bind:scrollY={y} />
<header class={y > 0 ? 'scrolled' : ''}>
<div class="header-content">
<div class="btn-undo">
<Button icon={Undo} on:click={() => history.back()} />
</div>
<h1>{title}</h1>
</div>
</header>
<Header {title} {onBack} />
<form>
<Textarea placeholder="Enter a short memorable name…" label="Persona name" bind:value={name} />
@ -40,91 +34,19 @@
/>
<div class="btns">
<Button label="Cancel" icon={Close} on:click={onCancel} />
<Button
label="Proceed"
icon={ArrowRight}
icon={Checkmark}
variant="primary"
disabled={!name || !pitch || !description}
on:click={onSubmit}
/>
<Button label="Cancel" icon={Close} on:click={onCancel} />
</div>
</form>
<style lang="scss">
header {
position: sticky;
top: 0;
left: 0;
right: 0;
background-color: rgba(var(--color-body-bg-rgb), 0.93);
backdrop-filter: blur(3px);
z-index: 100;
padding: var(--spacing-24);
transition: padding 0.2s, box-shadow 0.2s;
@media (prefers-color-scheme: dark) {
box-shadow: 0 1px 5px 0 rgba(var(--color-body-bg-rgb), 0.75);
}
@media (min-width: 688px) {
padding: var(--spacing-48);
transition: padding 0.2s;
}
.header-content {
position: relative;
max-width: 450px;
margin-inline: auto;
display: flex;
align-items: center;
justify-content: center;
transition: max-width 0.2s;
@media (min-width: 688px) {
max-width: 996px;
transition: max-width 0.2s;
}
@media (min-width: 1242px) {
max-width: 1494px;
}
@media (min-width: 1640px) {
max-width: 1992px;
}
@media (min-width: 2038px) {
max-width: 2490px;
}
}
.btn-undo {
position: absolute;
inset: 0 0 auto 0;
}
h1 {
font-family: var(--font-body);
font-weight: 600;
font-size: 18px;
font-style: normal;
text-align: center;
line-height: 44px;
}
&.scrolled {
box-shadow: 0 1px 5px 0 rgba(var(--color-body-text-rgb), 0.25);
transition: box-shadow 0.2s;
@media (min-width: 688px) {
padding-block: var(--spacing-24);
transition: padding 0.2s;
}
}
}
form {
min-height: calc(100dvh - 92px);
min-height: calc(100vh - 92px);

View File

@ -1,50 +1,73 @@
<script lang="ts">
import Card from '$lib/components/grid-card.svelte'
import { formatDateAndTime } from '$lib/utils/format'
import type { Post } from '$lib/stores/post'
let cls: string | undefined = undefined
export { cls as class }
export let post: Post
</script>
<div class={`root ${cls}`}>
<Card on:click>
<div class="content-wrapper">
<div class="imgs">
<!-- I HARD CODED SOME IMAGES TO STYLE THE SECTION AND LEFT THEM TO SHOW THE STRUCTURE IN USE -->
<!-- MORE THAN 3 IMAGES SHOULD HAVE A "PLUS" ICON OVER THE THIRD WITH COUNT OF EXTRA IMAGES -->
<div>
<img src="https://via.placeholder.com/300x500" alt="Placeholder for testing" />
</div>
<div>
<img src="https://via.placeholder.com/400x840" alt="Placeholder for testing" />
</div>
<div>
<img src="https://via.placeholder.com/100x75" alt="Placeholder for testing" />
</div>
</div>
<div class="post-content">{post.text}</div>
<div class="user-info">
<div class="faded">{formatDateAndTime(post.timestamp)}</div>
</div>
<div class="post-content">{post.text}</div>
</div>
</div>
</Card>
<style lang="scss">
.root {
border-bottom: 1px solid var(--grey-200);
padding: var(--spacing-12);
.imgs {
display: flex;
gap: var(--spacing-12);
flex-direction: row;
break-inside: avoid-column;
gap: var(--spacing-6);
justify-content: flex-start;
align-items: center;
flex-wrap: nowrap;
@media (min-width: 640px) {
border: none;
outline-style: solid;
outline-width: 1px;
outline-color: var(--grey-200);
outline-offset: -0.5px;
div {
img {
max-height: 300px;
}
&:not(:only-child) img {
aspect-ratio: 1;
object-fit: cover;
width: 100%;
height: 100%;
}
}
@media (prefers-color-scheme: dark) {
border-bottom-color: var(--grey-500);
outline-color: var(--grey-500);
/* one item */
div:first-child:nth-last-child(1) {
width: 100%;
}
/* two items */
div:first-child:nth-last-child(2),
div:first-child:nth-last-child(2) ~ div {
width: 50%;
}
/* three items */
div:first-child:nth-last-child(3),
div:first-child:nth-last-child(3) ~ div {
width: 33.3333%;
}
}
.user-img {
flex-shrink: 0;
}
.content-wrapper {
flex-grow: 1;
}
.user-info {
display: flex;
flex-direction: row;
@ -52,28 +75,15 @@
margin-bottom: var(--spacing-3);
font-size: var(--font-size-sm);
}
.content-wrapper {
display: flex;
flex-direction: column;
gap: var(--spacing-6);
}
.post-content {
font-family: var(--font-serif);
line-height: 1.38;
}
.faded {
color: var(--grey-300);
@media (prefers-color-scheme: dark) {
color: var(--grey-400);
}
}
@keyframes newpost {
from {
background-color: var(--success-highlight);
}
to {
background-color: transparent;
}
}
:global(.newpost) {
animation-name: newpost;
animation-duration: 2.5s;
animation-timing-function: ease-out;
}
</style>

View File

@ -1,14 +1,16 @@
<script lang="ts">
import Button from '$lib/components/button.svelte'
import Close from '$lib/components/icons/close.svelte'
import SendAltFilled from '$lib/components/icons/send-alt-filled.svelte'
import InputString from '$lib/components/input-string.svelte'
import Checkmark from '$lib/components/icons/checkmark.svelte'
import Image from '$lib/components/icons/image.svelte'
import Header from '$lib/components/header.svelte'
import Textarea from '$lib/components/textarea.svelte'
import { profile } from '$lib/stores/profile'
let cls: string | undefined = undefined
export { cls as class }
export let submit: (postText: string) => void | Promise<void>
export let cancel: () => void
export let submit: (postText: string) => unknown
export let onBack: () => unknown = () => history.back()
export let label: string | undefined = 'Publish'
let postText = ''
let x: number
@ -17,61 +19,24 @@
<svelte:window bind:innerWidth={x} />
<div class={`root ${cls}`}>
<div class="header">
<div>Create post</div>
<div class="btns">
<Button
variant="secondary"
icon={Close}
label={x < 1280 ? '' : 'Cancel'}
on:click={() => cancel()}
/>
<Button
variant="primary"
label="Publish"
icon={SendAltFilled}
on:click={() => submit(postText)}
disabled={!$profile.signer}
/>
</div>
</div>
<Header {onBack}>
<Button icon={Image} />
<Button
icon={Checkmark}
variant="primary"
{label}
on:click={() => submit(postText)}
disabled={!$profile.signer}
/>
</Header>
<div class="post-content">
<InputString bind:value={postText} placeholder="Write here..." autofocus />
<Textarea bind:value={postText} placeholder="Write here..." autofocus />
</div>
</div>
<style lang="scss">
.header {
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
font-weight: 600;
padding: var(--spacing-12) var(--spacing-12) var(--spacing-24);
margin: 0 auto;
transition: padding 0.2s;
max-width: var(--container-width);
@media (min-width: 1280px) {
padding-top: var(--spacing-48);
transition: padding 0.2s;
}
}
.btns {
display: flex;
flex-direction: row;
gap: var(--spacing-12);
align-items: center;
}
.post-content {
padding: var(--spacing-24) var(--spacing-12) var(--spacing-12);
height: calc(100vh - 80px);
text-align: center;
@media (min-width: 1280px) {
height: calc(100vh - 116px);
}
max-width: 450px;
margin-inline: auto;
}
</style>

View File

@ -0,0 +1,15 @@
<script lang="ts">
let cls: string | undefined = undefined
export { cls as class }
</script>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 250 250"
preserveAspectRatio="xMinYMin slice"
class={cls}
>
<path
d="M126.344 61.828c-.225 0-.449.014-.672.042-17.706.06-31.415 2.182-40.88 4.82-4.798 1.338-8.513 2.778-11.308 4.274-1.398.748-2.565 1.483-3.644 2.458-1.079.974-2.636 2.173-2.636 5.334v49.126c0 8.7 4.616 21.123 13.998 33.688 9.381 12.565 23.897 24.686 43.977 29.162.767.17 1.563.17 2.33 0 20.08-4.476 34.596-16.596 43.977-29.162 9.382-12.566 13.998-24.998 13.998-33.699V78.756c0-3.161-1.557-4.36-2.636-5.334-1.079-.975-2.246-1.71-3.644-2.458-2.794-1.496-6.51-2.936-11.309-4.274-9.46-2.637-23.163-4.758-40.858-4.82a5.551 5.551 0 0 0-.693-.042zm0 10.753c17.204 0 30.2 2.104 38.664 4.463 4.231 1.18 7.351 2.454 9.125 3.403.35.188.361.226.598.378v47.046c0 4.048-3.659 16.283-11.855 27.262-7.082 9.486-17.43 18.29-31.156 22.987v-14.324h-10.752v14.324c-13.726-4.697-24.074-13.502-31.156-22.987-8.196-10.977-11.855-23.204-11.855-27.251V80.825c.237-.152.248-.19.599-.378 1.773-.95 4.893-2.223 9.125-3.403 8.463-2.359 21.46-4.463 38.663-4.463zm-22.965 16.13c-14.667 0-20.046 10.754-20.046 10.754s7.28-5.377 16.13-5.377c7.214 0 11.292 7.733 11.33 7.813h.01a5.376 5.376 0 0 0 4.788 2.94 5.377 5.377 0 0 0 4.81-7.78l-.021-.032c-.3-.634-3.162-8.317-17-8.317zm45.93 0c-13.914 0-16.747 7.79-17.022 8.35h.011a5.377 5.377 0 0 0-.578 2.404 5.377 5.377 0 0 0 5.377 5.377 5.376 5.376 0 0 0 4.788-2.951l.01.01c.038-.08 4.116-7.813 11.33-7.813 8.85 0 16.13 5.377 16.13 5.377s-5.38-10.753-20.046-10.753zm-47.158 16.13c-5.511 0-10.355 2.13-13.441 5.377 3.086 3.248 7.93 5.377 13.44 5.377 5.511 0 10.355-2.13 13.441-5.377-3.086-3.247-7.93-5.376-13.44-5.376zm48.387 0c-5.511 0-10.355 2.13-13.441 5.377 3.086 3.248 7.93 5.377 13.44 5.377 5.511 0 10.355-2.13 13.441-5.377-3.086-3.247-7.93-5.376-13.44-5.376zm-24.162 23.408-10.953 14.23h-8.096l-9.135-10.806-8.212 6.952 12.37 14.607h18.355l5.608-7.278 5.512 7.278h18.513l12.37-14.607-8.212-6.952-9.135 10.806h-8.19l-10.795-14.23z"
/>
</svg>

View File

@ -4,6 +4,7 @@
export let value = ''
export let placeholder = ''
export let label = ''
export let autofocus = false
let placeholderHeight: number
let textarea: HTMLTextAreaElement
@ -47,7 +48,8 @@
>
{placeholder}
</div>
<textarea bind:value bind:this={textarea} class={value != '' ? 'content' : ''} />
<!-- svelte-ignore a11y-autofocus -->
<textarea bind:value bind:this={textarea} class={value != '' ? 'content' : ''} {autofocus} />
</div>
</label>
@ -59,6 +61,14 @@
gap: var(--spacing-6);
font-size: var(--font-size-sm);
color: var(--color-body-text);
padding: var(--spacing-24);
background-color: transparent;
transition: background-color 0.2s;
&:focus-within {
background-color: var(--grey-150);
transition: background-color 0.2s;
}
.area-placeholder {
position: relative;
@ -83,15 +93,11 @@
background-color: transparent;
transition: background-color 0.2s;
font-size: var(--font-size-lg);
font-family: var(--font-serif);
&:focus,
&.content {
background-color: #000;
transition: background-color 0.2s;
outline: none;
@media (prefers-color-scheme: light) {
background-color: #ffffff;
}
}
&.content {

View File

@ -1,141 +0,0 @@
<script lang="ts">
import { onMount, onDestroy, tick } from 'svelte'
interface GridItem {
_el: HTMLDivElement
gap: number
items: HTMLElement[]
ncol: number
mod: number
}
export let stretchFirst = false
export let gridGap = '0.5em'
export let colWidth = 'minmax(Min(20em, 100%), 1fr)'
export let items: unknown[] = [] // pass in data if it's dynamically updated
let grids: GridItem[] = []
let masonryElement: HTMLDivElement | undefined
const refreshLayout = async () => {
grids.forEach(async (grid) => {
/* get the post relayout number of columns */
let ncol = getComputedStyle(grid._el).gridTemplateColumns.split(' ').length
grid.items.forEach((c) => {
let new_h = c.getBoundingClientRect().height
if (new_h !== Number(c.dataset.h)) {
c.dataset.h = new_h.toString()
grid.mod++
}
})
/* if the number of columns has changed */
if (grid.ncol !== ncol || grid.mod) {
/* update number of columns */
grid.ncol = ncol
/* revert to initial positioning, no margin */
grid.items.forEach((c) => c.style.removeProperty('margin-top'))
/* if we have more than one column */
if (grid.ncol > 1) {
grid.items.slice(ncol).forEach((c, i) => {
let prev_fin =
grid.items[i].getBoundingClientRect().bottom /* bottom edge of item above */,
curr_ini = c.getBoundingClientRect().top /* top edge of current item */
c.style.marginTop = `${prev_fin + grid.gap - curr_ini}px`
})
}
grid.mod = 0
}
})
}
const calcGrid = async (_masonryArr: HTMLDivElement[]) => {
await tick()
if (_masonryArr.length && getComputedStyle(_masonryArr[0]).gridTemplateRows !== 'masonry') {
grids = _masonryArr.map((grid) => {
return {
_el: grid,
gap: parseFloat(getComputedStyle(grid).gridRowGap),
items: ([...grid.childNodes] as HTMLElement[]).filter(
(c) => c.nodeType === 1 && +getComputedStyle(c).gridColumnEnd !== -1,
),
ncol: 0,
mod: 0,
}
})
refreshLayout() /* initial load */
}
}
let _window: Window | undefined
onMount(() => {
_window = window
_window.addEventListener('resize', refreshLayout, false) /* on resize */
})
onDestroy(() => {
if (_window) {
_window.removeEventListener('resize', refreshLayout, false) /* on resize */
}
})
$: if (masonryElement) {
calcGrid([masonryElement])
}
$: if (items) {
// update if items are changed
masonryElement = masonryElement // refresh masonryElement
}
</script>
<!--
An almost direct copy and paste of: https://css-tricks.com/a-lightweight-masonry-solution
Usage:
- stretchFirst stretches the first item across the top
<Masonry stretchFirst={true} >
{#each data as o}
<div class="_card _padding">
Here's some stuff {o.name}
<header>
<h3>{o.name}</h3>
</header>
<section>
<p>{o.text}</p>
</section>
</div>
{/each}
</Masonry>
-->
<div
bind:this={masonryElement}
class={`__grid--masonry ${stretchFirst ? '__stretch-first' : ''}`}
style={`--grid-gap: ${gridGap}; --col-width: ${colWidth};`}
>
<slot />
</div>
<!--
$w: var(--col-width); // minmax(Min(20em, 100%), 1fr);
$s: var(--grid-gap); // .5em;
-->
<style>
:global(.__grid--masonry) {
display: grid;
grid-template-columns: repeat(auto-fit, var(--col-width));
grid-template-rows: masonry;
justify-content: center;
grid-gap: var(--grid-gap);
padding: var(--grid-gap);
}
:global(.__grid--masonry > *) {
align-self: start;
}
:global(.__grid--masonry.__stretch-first > *:first-child) {
grid-column: 1/ -1;
}
</style>

View File

@ -1,7 +1,13 @@
<script lang="ts">
import Search from '$lib/components/icons/search.svelte'
import SettingsView from '$lib/components/icons/settings-view.svelte'
import Add from '$lib/components/icons/add.svelte'
import HeaderTop from '$lib/components/header-top.svelte'
import Persona from '$lib/components/persona.svelte'
import Grid from '$lib/components/grid.svelte'
import Button from '$lib/components/button.svelte'
import Container from '$lib/components/container.svelte'
import { profile } from '$lib/stores/profile'
import { personas } from '$lib/stores/persona'
@ -10,11 +16,6 @@
import { goto } from '$app/navigation'
import { ROUTES } from '$lib/routes'
import Button from '$lib/components/button.svelte'
import Search from '$lib/components/icons/search.svelte'
import SettingsView from '$lib/components/icons/settings-view.svelte'
import Add from '$lib/components/icons/add.svelte'
let filterText = ''
let showChat = false
@ -49,9 +50,13 @@
{:else}
{#if $personas.draft?.length !== 0 && $profile.signer !== undefined}
<div class="section-wrapper">
<div class="subtitle">Draft personas</div>
<div class="section-top">
<Container class="flex">
<div class="subtitle">Draft personas</div>
<Button icon={Add} label="Create persona" on:click={createDraft} />
</Container>
</div>
<hr />
<Button icon={Add} label="Create persona" on:click={createDraft} />
<Grid>
{#each $personas.draft as draftPersona, index}
<Persona
@ -68,7 +73,11 @@
{#if $personas.favorite.length !== 0 && $profile.signer !== undefined}
<div class="section-wrapper">
<div class="subtitle">Favorites</div>
<div class="section-top">
<Container>
<div class="subtitle">Favorites</div>
</Container>
</div>
<hr />
<Grid>
{#each $personas.favorite as personaId}
@ -146,20 +155,26 @@
border-bottom-color: var(--grey-500);
}
}
.section-top {
padding-block: var(--spacing-24);
:global(.flex) {
display: flex;
align-items: center;
justify-content: space-between;
}
}
}
.subtitle {
padding: var(--spacing-24);
font-size: var(--font-size-lg);
font-weight: var(--font-weight-sb);
transition: padding 0.2s;
max-width: 498px;
margin-inline: auto;
@media (min-width: 688px) {
max-width: 996px;
transition: padding 0.2s;
padding-inline: var(--spacing-48);
}
@media (min-width: 1242px) {

View File

@ -3,51 +3,72 @@
import Button from '$lib/components/button.svelte'
import Edit from '$lib/components/icons/edit.svelte'
import Star from '$lib/components/icons/star.svelte'
import Wallet from '$lib/components/icons/wallet.svelte'
import StarFilled from '$lib/components/icons/star_filled.svelte'
import Hourglass from '$lib/components/icons/hourglass.svelte'
import Grid from '$lib/components/grid.svelte'
import PersonaDetail from '$lib/components/persona_detail.svelte'
import Header from '$lib/components/header.svelte'
import { posts } from '$lib/stores/post'
import { personas } from '$lib/stores/persona'
import { profile } from '$lib/stores/profile'
import { goto } from '$app/navigation'
import { browser } from '$app/environment'
import { page } from '$app/stores'
import Masonry from '$lib/masonry.svelte'
import { ROUTES } from '$lib/routes'
import PersonaDetail from '$lib/components/persona_detail.svelte'
let windowWidth: number = browser ? window.innerWidth : 0
function getMasonryColumnWidth(windowInnerWidth: number) {
if (windowInnerWidth < 739) return '100%'
if (windowInnerWidth < 1060) return 'minmax(min(100%/2, max(320px, 100%/2)), 1fr)'
if (windowInnerWidth < 1381) return 'minmax(min(100%/3, max(320px, 100%/3)), 1fr)'
if (windowInnerWidth < 1702) return 'minmax(min(100%/4, max(320px, 100%/4)), 1fr)'
if (windowInnerWidth < 2023) return 'minmax(min(100%/5, max(320px, 100%/5)), 1fr)'
if (windowInnerWidth < 2560) return 'minmax(min(100%/6, max(320px, 100%/6)), 1fr)'
if (windowInnerWidth < 3009) return 'minmax(min(100%/7, max(320px, 100%/7)), 1fr)'
return 'minmax(323px, 1fr)'
}
import { connectWallet } from '$lib/services'
const persona = $personas.all.get($page.params.id)
const handleConnect = async () => {
try {
const signer = await connectWallet()
const address = await signer.getAddress()
$profile = { signer, address }
} catch (err) {
console.error(err)
}
}
let y: number
export let onBack: () => unknown = () => history.back()
</script>
<svelte:window bind:innerWidth={windowWidth} />
<svelte:window bind:scrollY={y} />
{#if persona === undefined}
<div>There is no persona with group ID {$page.params.id}</div>
{:else}
<div class={`header ${y > 0 ? 'scrolled' : ''}`}>
<Header title={persona.name} {onBack}>
{#if $profile.signer !== undefined}
<Button
variant="primary"
icon={Edit}
on:click={() => goto(ROUTES.POST_NEW($page.params.id))}
/>
{:else}
<Button variant="primary" icon={Wallet} on:click={() => handleConnect()} />
{/if}
</Header>
</div>
<PersonaDetail
name={persona.name}
pitch={persona.pitch}
description={persona.description}
postsCount={persona.postsCount}
bind:picture={persona.picture}
bind:cover={persona.cover}
>
<svelte:fragment slot="button_top">
{#if $personas.favorite.includes($page.params.id)}
<Button icon={StarFilled} variant="primary" label="Remove favorite" />
{:else}
<Button icon={Star} variant="primary" label="Add to favorites" />
{#if $profile.signer !== undefined}
{#if $personas.favorite.includes($page.params.id)}
<Button icon={StarFilled} variant="overlay" label="Remove favorite" />
{:else}
<Button icon={Star} variant="overlay" label="Add to favorites" />
{/if}
{/if}
</svelte:fragment>
@ -59,22 +80,48 @@
icon={Edit}
on:click={() => goto(ROUTES.POST_NEW($page.params.id))}
/>
{/if}</svelte:fragment
>
{:else}
<Button
variant="primary"
label="Connect to post"
icon={Wallet}
on:click={() => handleConnect()}
/>
{/if}
</svelte:fragment>
<svelte:fragment slot="button_other">
<!-- NEED TO ADD CORRECT ACTION HERE -->
<Button label="Review pending" icon={Hourglass} />
</svelte:fragment>
<!-- PLACE FILTER COMPONENT HERE -->
{#if $posts.loading}
<p>Loading posts...</p>
{:else if $posts.posts.length == 0}
<p>There are no posts yet</p>
{:else}
<Masonry gridGap="0" colWidth={getMasonryColumnWidth(windowWidth)} items={$posts.posts}>
<Grid>
{#each $posts.posts as post}
<Post {post} />
<!-- NEEDS ONCLICK ACTION => SHOULD GO TO POST PAGE -->
<Post {post} on:click />
{/each}
</Masonry>
</Grid>
{/if}
</PersonaDetail>
{/if}
<style lang="scss">
.header {
position: fixed;
inset: -100% 0 auto;
z-index: 100;
transition: inset 0.5s;
&.scrolled {
inset: 0 0 auto;
transition: inset 0.3s;
}
}
</style>

View File

@ -54,10 +54,6 @@
console.error(error)
}
}
function cancel() {
history.back()
}
</script>
<PostNew {submit} {cancel} />
<PostNew {submit} />

View File

@ -1,21 +1,28 @@
<script lang="ts">
import ArrowRight from '$lib/components/icons/arrow-right.svelte'
import Checkmark from '$lib/components/icons/checkmark.svelte'
import Close from '$lib/components/icons/close.svelte'
import Edit from '$lib/components/icons/edit.svelte'
import EditPersona from '$lib/components/icons/request-quote.svelte'
import Undo from '$lib/components/icons/undo.svelte'
import Info from '$lib/components/icons/information.svelte'
import Launch from '$lib/components/icons/rocket.svelte'
import Button from '$lib/components/button.svelte'
import PersonaEditText from '$lib/components/persona_edit_text.svelte'
import PersonaDetail from '$lib/components/persona_detail.svelte'
import Post from '$lib/components/post.svelte'
import PostNew from '$lib/components/post_new.svelte'
import LearnMore from '$lib/components/learn-more.svelte'
import Header from '$lib/components/header.svelte'
import InfoScreen from '$lib/components/info_screen.svelte'
import Banner from '$lib/components/message-banner.svelte'
import Grid from '$lib/components/grid.svelte'
import Container from '$lib/components/container.svelte'
import InfoBox from '$lib/components/info-box.svelte'
import { personas } from '$lib/stores/persona'
import { tokens } from '$lib/stores/tokens'
import { page } from '$app/stores'
import InfoScreen from '$lib/components/info_screen.svelte'
const PERSONA_LIMIT = 5
const TOKEN_POST_COST = 10
@ -33,8 +40,13 @@
$tokens.go -= TOKEN_POST_COST
state = 'publish_success'
}
let y: number
export let onBack: () => unknown = () => history.back()
</script>
<svelte:window bind:scrollY={y} />
{#if persona === undefined}
<div>No draft persona with id {personaIndex}</div>
{:else if state === 'text'}
@ -42,7 +54,7 @@
bind:name
bind:pitch
bind:description
title="Edit persona"
title="Edit Persona details"
onSubmit={() => {
persona.name = name
persona.pitch = pitch
@ -55,6 +67,10 @@
}}
/>
{:else if state === 'posts'}
<Banner icon={Info}>This is a preview of the Persona's page</Banner>
<div class={`header ${y > 0 ? 'scrolled' : ''}`}>
<Header title={persona.name} {onBack} />
</div>
<PersonaDetail
name={persona.name}
pitch={persona.pitch}
@ -74,9 +90,9 @@
/>
{:else}
<Button
icon={Edit}
icon={Launch}
variant="primary"
label="Publish persona"
label="Publish Persona"
on:click={() => (state = 'publish_warning')}
/>
{/if}
@ -84,26 +100,35 @@
<Button
slot="button_other"
variant="secondary"
label="Edit persona"
icon={Edit}
label="Edit Persona details"
icon={EditPersona}
on:click={() => (state = 'text')}
/>
{#if persona.posts.length < PERSONA_LIMIT}
<p>{persona.posts.length} out {PERSONA_LIMIT} seed posts added</p>
<p>You need {PERSONA_LIMIT} seed posts to publish this persona.</p>
<a href="/" target="_blank">Learn more</a>
{:else}
<p>{PERSONA_LIMIT} out {PERSONA_LIMIT} seed posts added</p>
<p>You can publish this persona.</p>
<a href="/" target="_blank">Learn more</a>
{/if}
{#each persona.posts as post}
<Post {post} />
{:else}
<p>There are no posts yet</p>
{/each}
<hr />
<Container>
<InfoBox>
{#if persona.posts.length < PERSONA_LIMIT}
<div class="icon-info">
<Info size={32} />
</div>
<p class="h2">{persona.posts.length} out {PERSONA_LIMIT} seed posts added</p>
<p>You need {PERSONA_LIMIT} seed posts to publish this Persona.</p>
<LearnMore href="/" />
{:else}
<div class="icon-success">
<Checkmark />
</div>
<p>{PERSONA_LIMIT} out {PERSONA_LIMIT} seed posts added</p>
<p>You can publish this Persona.</p>
<LearnMore href="/" />
{/if}
</InfoBox>
</Container>
<Grid>
{#each persona.posts as post}
<Post {post} />
{/each}
</Grid>
</PersonaDetail>
{:else if state === 'post_new'}
<PostNew
@ -112,56 +137,77 @@
personas.updateDraft(personaIndex, persona)
state = 'posts'
}}
cancel={() => (state = 'posts')}
label="Save seed post"
onBack={() => (state = 'posts')}
/>
{:else if state === 'publish_warning'}
<InfoScreen title="Publish persona" onBack={() => history.back()}>
<InfoScreen
title={$tokens.go >= TOKEN_POST_COST ? 'Publish Persona' : 'Not enough token'}
onBack={() => history.back()}
>
{#if $tokens.go >= TOKEN_POST_COST}
<div>
<h1>This will use {TOKEN_POST_COST} GO</h1>
<p>People will be able to post with this persona.</p>
<a href="/" target="_blank">Learn more</a>
</div>
<div>
<h1>Currently available</h1>
<span>{$tokens.go} GO</span>
<p>Until cycle ends.</p>
<a href="/" target="_blank">Learn more</a>
<div class="token-info">
<div>
<div class="icon">
<Info size={32} />
</div>
<h2>This will use {TOKEN_POST_COST} GO</h2>
<p>This Persona will be live, and everyone will be able to post with it.</p>
<p><LearnMore href="/" /></p>
</div>
<div class="box">
<div class="h3">Currently available</div>
<div class="go-amt">
{$tokens.go}
</div>
<div class="go">GO</div>
<p>Until new cycle begins</p>
<LearnMore href="/" />
</div>
</div>
{:else}
<div>
<h1>Sorry, you cant publish now</h1>
<p>You need {TOKEN_POST_COST} GO to publish a persona.</p>
<a href="/" target="_blank">Learn more</a>
</div>
<div>
<h1>Currently available</h1>
<span>{$tokens.go} GO</span>
<p>Until cycle ends.</p>
<a href="/" target="_blank">Learn more</a>
<div class="token-info">
<div>
<div class="icon">
<Info size={32} />
</div>
<h2>Sorry, you can't publish now</h2>
<p>You need {TOKEN_POST_COST} GO to publish a Persona.</p>
<LearnMore href="/" />
</div>
<div class="box error">
<div class="h3">Currently available</div>
<div class="go-amt">
{$tokens.go}
</div>
<div class="go">GO</div>
<p>Until new cycle begins</p>
<LearnMore href="/" />
</div>
</div>
{/if}
<svelte:fragment slot="buttons">
{#if $tokens.go >= TOKEN_POST_COST}
<Button variant="secondary" label="Cancel" icon={Close} on:click={() => (state = 'text')} />
<Button icon={ArrowRight} variant="primary" label="Proceed" on:click={publishPersona} />
<Button icon={Checkmark} variant="primary" label="I agree" on:click={publishPersona} />
<Button variant="secondary" label="Nope" icon={Close} on:click={() => (state = 'text')} />
{:else}
<Button variant="secondary" label="Back" icon={Undo} on:click={() => (state = 'text')} />
{/if}
</svelte:fragment>
</InfoScreen>
{:else}
<InfoScreen title="Publish successful" onBack={() => history.back()}>
<div>
<h1>This persona is now public</h1>
<InfoScreen title="Persona published">
<div class="token-info">
<div class="icon-success">
<Checkmark />
</div>
<h2>This Persona is now public</h2>
<p>
Anyone can now submit posts with this persona. All posts will be subject to community review
before being published. This persona was added to your favorites on your homepage.
Anyone can now submit posts with this Persona. All posts will be subject to community review
before being published. This Persona was added to your favorites.
</p>
<a href="/" target="_blank">Learn more</a>
<LearnMore href="/" />
</div>
<svelte:fragment slot="buttons">
@ -171,4 +217,84 @@
{/if}
<style lang="scss">
.header {
position: fixed;
inset: -100% 0 auto;
z-index: 50;
transition: inset 0.5s;
&.scrolled {
inset: 44px 0 auto;
transition: inset 0.3s;
}
}
.token-info {
text-align: center;
.icon {
margin-bottom: var(--spacing-12);
}
p,
h2 {
margin-bottom: var(--spacing-6);
}
}
.box {
border: 1px solid var(--grey-200);
padding: var(--spacing-24);
margin-top: var(--spacing-48);
.go-amt {
font-size: 40px;
line-height: 1;
font-weight: var(--font-weight-sb);
margin-top: var(--spacing-12);
}
.go {
text-transform: uppercase;
font-weight: var(--font-weight-sb);
margin-bottom: var(--spacing-12);
}
&.error {
border: 1px solid var(--color-red);
background-color: rgba(var(--color-red-rgb), 0.05);
.go-amt,
.go {
color: var(--color-red);
}
}
}
.icon-success {
position: relative;
display: inline-block;
margin-bottom: var(--spacing-12);
:global(svg) {
fill: var(--color-body-bg);
}
:global(polygon) {
stroke: #fff;
stroke-width: 1px;
}
&::before {
position: absolute;
content: '';
inset: -4px auto auto -6px;
background-color: var(--color-success);
border-radius: 50%;
width: 28px;
height: 28px;
transform: translateX(2px);
z-index: -1;
}
}
</style>

View File

@ -3,10 +3,15 @@
import Checkmark from '$lib/components/icons/checkmark.svelte'
import Close from '$lib/components/icons/close.svelte'
import Edit from '$lib/components/icons/edit.svelte'
import Info from '$lib/components/icons/information.svelte'
import Button from '$lib/components/button.svelte'
import PersonaEditText from '$lib/components/persona_edit_text.svelte'
import PersonaDetail from '$lib/components/persona_detail.svelte'
import LearnMore from '$lib/components/learn-more.svelte'
import Banner from '$lib/components/message-banner.svelte'
import Container from '$lib/components/container.svelte'
import InfoBox from '$lib/components/info-box.svelte'
import { ROUTES } from '$lib/routes'
import { goto } from '$app/navigation'
@ -36,14 +41,14 @@
</script>
{#if showWarningModal}
<InfoScreen title="Leaving persona creation" onBack={() => (showWarningModal = false)}>
<div>
<h1>Are you sure you want to leave?</h1>
<p>
You are about to leave the persona creation screenWARNING: If you do so, all changes will be
lost.
</p>
</div>
<InfoScreen title="Leaving Persona creation" onBack={() => (showWarningModal = false)}>
<Container>
<InfoBox>
<h2>Are you sure you want to leave?</h2>
<p>You are about to leave the persona creation screen</p>
<p>WARNING: If you do so, all changes will be lost.</p>
</InfoBox>
</Container>
<svelte:fragment slot="buttons">
<Button
@ -64,9 +69,11 @@
onSubmit={() => {
state = 'edit_images'
}}
onBack={onCancel}
{onCancel}
/>
{:else if state === 'edit_images'}
<Banner icon={Info}>This is a preview of the Persona's page</Banner>
<PersonaDetail
name={persona.name}
pitch={persona.pitch}
@ -80,21 +87,24 @@
>
<Button
slot="button_primary"
variant="secondary"
label="Edit text"
icon={Edit}
on:click={onCancel}
/>
<Button
slot="button_other"
variant="primary"
label="Save changes"
label="Save Persona"
icon={Checkmark}
disabled={!persona.picture || !persona.cover}
on:click={savePersona}
/>
<p>Please provide at least a cover image.</p>
<a href="/" target="_blank">Learn more →</a>
<Button
slot="button_other"
variant="secondary"
label="Edit Persona details"
icon={Edit}
on:click={() => (state = 'edit_text')}
/>
<Container>
<InfoBox>
<p>Please provide a profile picture and a cover image.</p>
</InfoBox>
</Container>
</PersonaDetail>
{:else}
<InfoScreen title="All changes saved">
@ -105,7 +115,7 @@
“seed” posts. These posts should serve as inspiring examples for people willing to post with
this persona.
</p>
<a href="/" target="_blank">Learn more</a>
<LearnMore href="/" />
</div>
<svelte:fragment slot="buttons">
<Button variant="secondary" label="Continue later" on:click={() => history.back()} />

View File

@ -1,15 +1,10 @@
:root {
color-scheme: light dark;
/* color-scheme: light dark; */
--font-body: 'Source Sans Pro';
--font-mono: 'Source Code Pro';
--font-serif: 'Source Serif Pro';
--font-size-normal: 16px;
--font-size-lg: 18px;
--font-size-sm: 14px;
--font-size-xs: 12px;
--font-weight-normal: 400;
--font-weight-sb: 600;
@ -19,6 +14,10 @@
--color-body-text-rgb: 24, 24, 24;
--color-black: #000000;
--color-black-rgb: 0, 0, 0;
--color-red: #ff0000;
--color-red-rgb: 255, 0, 0;
--color-success: #12bb10;
--grey-100: #f0f0f0;
--grey-150: #f9f9f9;
@ -38,12 +37,54 @@
--spacing-24: 24px;
--spacing-48: 48px;
--blur: 3px;
--font-size-xl: 18px;
--font-size-lg: 16px;
--font-size-normal: 14px;
--font-size-sm: 12px;
--font-size-xs: 12px;
--container-width: 100%;
}
@media (min-width: 1280px) {
h1:not(:empty),
h2:not(:empty),
h3:not(:empty),
.h1:not(:empty),
.h2:not(:empty),
.h3:not(:empty) {
font-family: var(--font-body);
font-weight: var(--font-weight-sb);
font-style: normal;
line-height: 1;
}
h1:not(:empty),
.h1:not(:empty) {
font-size: var(--font-size-xl);
}
h2:not(:empty),
.h2:not(:empty) {
font-size: var(--font-size-lg);
}
h3:not(:empty),
.h3:not(:empty) {
font-size: var(--font-size-normal);
}
.text-large {
font-size: var(--font-size-lg);
}
@media (min-width: 398px) {
:root {
--container-width: 1280px;
--font-size-xl: 20px;
--font-size-lg: 18px;
--font-size-normal: 16px;
--font-size-sm: 14px;
}
}
@ -63,7 +104,8 @@ body {
color: var(--color-body-text);
background-color: var(--color-body-bg);
font-family: var(--font-body);
font-weight: 400;
font-weight: var(--font-weight-normal);
font-size: var(--font-size-normal);
}
*,
@ -80,7 +122,7 @@ body {
img,
picture,
svg,
/* svg, */
video {
display: block;
max-width: 100%;
@ -89,11 +131,31 @@ video {
hr {
border: none;
width: 100%;
height: 1px;
background-color: var(--grey-200);
}
@media (prefers-color-scheme: dark) {
a {
color: var(--color-black);
text-decoration: underline;
}
/* THIS CLASS SHOULD BE REMOVED WHEN NO LONGER NEEDED */
.note {
background-color: palevioletred;
color: white;
text-align: center;
padding: var(--spacing-24);
}
@media (min-width: 1280px) {
:root {
--container-width: 1280px;
}
}
/* @media (prefers-color-scheme: dark) {
:root {
--success-highlight: #052b05;
--color-body-bg: #000000;
@ -108,4 +170,4 @@ hr {
hr {
background-color: var(--grey-500);
}
}
} */