Compare commits

..

No commits in common. "2e99b04261f3d64edf2558b8d8e450a99a2fd68b" and "dfd6d09c97fac013bd80bbb9a8639d9a9dceff08" have entirely different histories.

9 changed files with 108 additions and 270 deletions

View file

@ -1,143 +0,0 @@
<script lang="ts" module>
export enum BlogCardSize {
Short,
Long,
Both
}
</script>
<script lang="ts">
import { BLOG_POST_FRESHNESS_MILLIS } from '$lib/util/Blogs';
import DateWidget from '$lib/components/DateWidget.svelte';
export let post: App.BlogPost;
export let size: BlogCardSize = BlogCardSize.Both;
const dateNow = new Date().valueOf();
const isPostNew = dateNow - new Date(post.date!).valueOf() <= BLOG_POST_FRESHNESS_MILLIS;
const isPostUpdated =
post.dateChanged != null &&
dateNow - new Date(post.dateChanged).valueOf() <= BLOG_POST_FRESHNESS_MILLIS;
const isPostFresh = isPostNew || isPostUpdated;
/**
* rndtrash: пришлось дублировать классы с модификатором и без, потому что Tailwind просто не понимает,
* когда его классы склеивают из нескольких частей
*/
/**
* Возвращает список классов для полноразмерной плашки.
*
* Возвращает пустую строку, если плашка может быть только короткой
* @param classes
* @param modClasses
*/
function fullClass(classes: string, modClasses: string): string {
if (size === BlogCardSize.Short) return '';
return size === BlogCardSize.Both ? modClasses : classes;
}
/**
* Возвращает список классов для короткой карточки.
*
* Возвращает пустую строку, если плашка может быть только полноразмерной
* @param classes
* @param modClasses
*/
function shortClass(classes: string, modClasses?: string): string {
if (size === BlogCardSize.Long) return '';
return size === BlogCardSize.Both && modClasses ? modClasses : classes;
}
</script>
<a
href="/blog/{post.slug}"
class="blog-card
{isPostUpdated ? 'updated' : isPostNew ? 'new' : ''}
{shortClass('flex-col justify-baseline')}
{fullClass('flex-row justify-stretch', 'sm:flex-row sm:justify-stretch')}"
>
<div
class="relative w-full basis-auto overflow-hidden
{shortClass('h-32')}
{fullClass('h-auto basis-1/3', 'sm:h-auto sm:basis-1/3')}"
>
{#if post.thumbnail}
<img
class="thumbnail"
src={`/blog/${post.slug}/${post.thumbnail}`}
alt={post.thumbnailAlt ?? 'Миниатюра поста'}
/>
{/if}
{#if isPostFresh}
<div class="toast {fullClass('hidden', 'sm:hidden')}">
{#if isPostUpdated}
ОБНОВЛЕНО
{:else}
НОВОЕ
{/if}
</div>
{/if}
</div>
<div class="flex w-full flex-col justify-center p-4 break-words md:p-8">
<div class="flex flex-row flex-wrap justify-start gap-4 pb-2">
<DateWidget
class={post.dateChanged ? shortClass('hidden', 'not-sm:hidden') : ''}
dateString={post.date}
type="published"
highlight={isPostNew && !isPostUpdated}
/>
{#if post.dateChanged}
<DateWidget
dateString={post.dateChanged}
type="updated"
highlight={isPostUpdated}
/>
{/if}
</div>
<h2 class="text-3xl font-bold">{post.title}</h2>
{#if post.description}
<p>{post.description}</p>
{/if}
</div>
</a>
<style>
@import '$src/app.css';
.blog-card {
@apply flex w-full max-w-5xl overflow-hidden rounded-lg bg-slate-100 text-slate-950 drop-shadow-xl transition-all hover:drop-shadow-2xl;
.toast {
@apply absolute top-0 right-0 rounded-bl-lg p-2 font-bold text-slate-50;
}
&.updated {
box-shadow: 0 0 0 4px var(--color-purple-600);
.toast {
@apply bg-purple-600;
}
}
&.new {
box-shadow: 0 0 0 4px var(--color-amber-600);
.toast {
@apply bg-amber-600;
}
}
img.thumbnail {
@apply absolute h-full w-full object-cover transition-transform;
transform: scale(1);
}
&:hover img.thumbnail {
transform: scale(1.1);
}
}
</style>

View file

@ -1,32 +0,0 @@
<script lang="ts">
import Icon from '@iconify/svelte';
let className: string = '';
export { className as class };
export let dateString: string | undefined;
export let type: 'published' | 'updated' = 'published';
export let highlight = false;
const icon =
type == 'published' ? 'material-symbols:calendar-today' : 'material-symbols:update';
const highlightClasses = (classes: string) => (highlight ? classes : '');
</script>
<div
class="flex items-center gap-2 p-1 text-lg font-bold {className} rounded-lg
{highlightClasses(type == 'published' ? 'bg-amber-600' : 'bg-purple-600')}
{highlightClasses('text-slate-50')}"
>
<Icon {icon} width={28} height={28} />
<span>
{dateString
? new Date(dateString).toLocaleString(undefined, {
month: 'short',
day: 'numeric',
year: 'numeric'
})
: 'Не опубликован!'}
</span>
</div>

View file

@ -1,29 +1,16 @@
import path from 'path'; import path from 'path';
export const THUMBNAIL_DEFAULT = "https://teasanctuary.ru/common/background-day.webp"; export const THUMBNAIL_DEFAULT = "https://teasanctuary.ru/common/background-day.webp";
export const BLOG_POST_FRESHNESS_MILLIS = 3 * 24 * 60 * 60 * 1000; // 3 дня
export type PostComparer = (a: App.BlogPost, b: App.BlogPost) => number; export async function fetchPostsSorted() {
export const sortPostsByPostDate: PostComparer = (a, b) => new Date(b.date!).valueOf() - new Date(a.date!).valueOf();
function laterDate(a: string, b?: string): number {
const dateA = new Date(a).valueOf();
if (!b) return dateA;
const dateB = new Date(b).valueOf();
return Math.max(dateA, dateB);
}
export const sortPostsByPostAndUpdateDate: PostComparer = (a, b) => laterDate(b.date!, b.dateChanged) - laterDate(a.date!, a.dateChanged);
export async function fetchPostsSorted(postComparer?: PostComparer) {
const allPosts = await fetchPosts(); const allPosts = await fetchPosts();
const sortedPosts = allPosts const sortedPosts = allPosts
// Для списка постов оставляем только те, у которых объявлена дата публикации // Для списка постов оставляем только те, у которых объявлена дата публикации
.filter((a) => !!a.date) .filter((a) => !!a.date)
.sort(postComparer ?? sortPostsByPostDate); .sort((a, b) => {
return new Date(b.date!).valueOf() - new Date(a.date!).valueOf();
});
return sortedPosts; return sortedPosts;
}; };

View file

@ -1,7 +0,0 @@
import { fetchPostsSorted, sortPostsByPostAndUpdateDate } from "$src/lib/util/Blogs";
const LATEST_POSTS_COUNT = 3;
export async function load() {
return { posts: (await fetchPostsSorted(sortPostsByPostAndUpdateDate)).slice(0, LATEST_POSTS_COUNT) };
}

View file

@ -2,15 +2,15 @@
import SocialButton from '$lib/components/SocialButton.svelte'; import SocialButton from '$lib/components/SocialButton.svelte';
import SocialHyperlink from '$lib/components/SocialHyperlink.svelte'; import SocialHyperlink from '$lib/components/SocialHyperlink.svelte';
import { PUBLIC_TS_DISCORD } from '$env/static/public'; import { PUBLIC_TS_DISCORD } from '$env/static/public';
import BlogCard, { BlogCardSize } from '$src/lib/components/BlogCard.svelte';
import { page } from '$app/state';
</script> </script>
<svelte:head> <svelte:head>
<title>Tea Sanctuary</title> <title>Tea Sanctuary</title>
</svelte:head> </svelte:head>
<section class="hero flex shrink-0 flex-col items-center justify-center gap-5 overflow-hidden p-4"> <section
class="flex shrink-0 flex-col items-center justify-center gap-5 overflow-hidden p-4 hero"
>
<div <div
class="flex flex-col flex-nowrap items-center justify-center gap-3 lg:flex-row lg:flex-nowrap" class="flex flex-col flex-nowrap items-center justify-center gap-3 lg:flex-row lg:flex-nowrap"
> >
@ -57,12 +57,19 @@
> >
</div> </div>
<div class="text-center font-bold lg:text-left"> <div
class="text-center font-bold lg:text-left"
>
<h1>TEA</h1> <h1>TEA</h1>
<h1>SANCTUARY</h1> <h1>SANCTUARY</h1>
</div> </div>
</div> </div>
<div class="flex flex-col items-center justify-start gap-4"> <div class="flex flex-col items-center justify-start gap-4">
<!--
<div class="flex flex-row gap-2 rounded-2xl bg-amber-950 p-2 text-center text-amber-50 ring-2 shadow">
Сайт находится в разработке. Если эта плашка висит после 1.06.2025 - пинайте Ивана!
</div>
-->
<div class="flex flex-row flex-wrap items-start justify-center gap-4"> <div class="flex flex-row flex-wrap items-start justify-center gap-4">
<SocialButton class="w-60 shrink-0" href={PUBLIC_TS_DISCORD}>Сообщество</SocialButton> <SocialButton class="w-60 shrink-0" href={PUBLIC_TS_DISCORD}>Сообщество</SocialButton>
<SocialButton class="w-60 shrink-0" href="https://github.com/TeaSanctuary/"> <SocialButton class="w-60 shrink-0" href="https://github.com/TeaSanctuary/">
@ -80,19 +87,6 @@
</SocialButton> </SocialButton>
</div> </div>
</section> </section>
<section class="flex flex-col items-stretch bg-blue-900 pt-4 text-slate-50">
<h1 class="text-center">ПОСЛЕДНИЕ ПОСТЫ</h1>
<div class="flex flex-row items-stretch justify-evenly gap-4 overflow-x-auto p-4">
{#each page.data.posts as post, i}
<div class="aspect-3/2 w-80 shrink-0">
<BlogCard {post} size={BlogCardSize.Short} />
</div>
{/each}
</div>
</section>
<section class="flex justify-center bg-slate-50 text-slate-950"> <section class="flex justify-center bg-slate-50 text-slate-950">
<div <div
class="flex w-5xl max-w-screen flex-col flex-nowrap gap-12 p-2 px-2 pt-12 pb-12 text-base sm:text-xl" class="flex w-5xl max-w-screen flex-col flex-nowrap gap-12 p-2 px-2 pt-12 pb-12 text-base sm:text-xl"
@ -100,87 +94,67 @@
<section id="who-are-we"> <section id="who-are-we">
<h1>Кто мы?</h1> <h1>Кто мы?</h1>
<div class="text-justify"> <div class="text-justify">
<b>Tea Sanctuary</b> &mdash; это в первую очередь коллектив друзей, разрабатывающих <b>Tea Sanctuary</b> &mdash; это в первую очередь коллектив друзей, разрабатывающих проекты
проекты для души, для всеобщего пользования и даже на заказ. С для души, для всеобщего пользования и даже на заказ. С <b>8 июля 2017 года</b> мы ведём публичную
<b>8 июля 2017 года</b> мы ведём публичную деятельность в сфере разработки ПО и развлечений. деятельность в сфере разработки ПО и развлечений.
</div> </div>
<br /> <br />
<div class="text-justify"> <div class="text-justify">
<b>Tea Sanctuary</b> &mdash; это также и сообщество единомышленников. Любовь к добротным <b>Tea Sanctuary</b> &mdash; это также и сообщество единомышленников. Любовь к добротным видеоиграм
видеоиграм и пассивная агрессия к вычислительной технике у нас в крови. Когда-то сообщество и пассивная агрессия к вычислительной технике у нас в крови. Когда-то сообщество было закрытым
было закрытым и насчитывало около 50 участников, но впоследствии мы решили его расширить. и насчитывало около 50 участников, но впоследствии мы решили его расширить. Станьте частью коллектива!
Станьте частью коллектива!
</div> </div>
</section> </section>
<section id="what-are-we-doing"> <section id="what-are-we-doing">
<h1>Что делаем?</h1> <h1>Что делаем?</h1>
<div class="text-justify"> <div class="text-justify">
Наша главная страсть &mdash; это, конечно, видеоигры. Мы часто участвуем в так Наша главная страсть &mdash; это, конечно, видеоигры. Мы часто участвуем в так называемых
называемых "гейм джемах" &mdash; конкурсах на разработку игр. Наши игры вы можете "гейм джемах" &mdash; конкурсах на разработку игр. Наши игры вы можете оценить здесь:
оценить здесь:
<SocialHyperlink href="https://randomtrash.itch.io">RandomTrash</SocialHyperlink> <SocialHyperlink href="https://randomtrash.itch.io">RandomTrash</SocialHyperlink>
<SocialHyperlink href="https://friendlywithmeat.itch.io/"> <SocialHyperlink href="https://friendlywithmeat.itch.io/">FriendlyWithMeat</SocialHyperlink>.
FriendlyWithMeat Также мы ведём работу над нашим первым полноценным игровым проектом.
</SocialHyperlink>. Также мы ведём работу над нашим первым полноценным игровым Следите за новостями в нашем
проектом. Следите за новостями в нашем
<SocialHyperlink href={PUBLIC_TS_DISCORD}>сообществе</SocialHyperlink>! <SocialHyperlink href={PUBLIC_TS_DISCORD}>сообществе</SocialHyperlink>!
</div> </div>
<br /> <br />
<div class="text-justify"> <div class="text-justify">
Отдельные участники нашего коллектива занимаются модификацией существующих игр, Отдельные участники нашего коллектива занимаются модификацией существующих игр, добавляя в
добавляя в них новый контент. Например, <b>MegaZerg</b> создаёт оригинальные карты них новый контент. Например, <b>MegaZerg</b> создаёт оригинальные карты для такой
для такой бессмертной классики, как Counter-Strike 1.6, и выкладывает их на ресурс бессмертной классики, как Counter-Strike 1.6, и выкладывает их на ресурс GameBanana:
GameBanana: <SocialHyperlink href="https://gamebanana.com/members/2971042">kemist</SocialHyperlink>
<SocialHyperlink href="https://gamebanana.com/members/2971042">
kemist
</SocialHyperlink>
</div> </div>
<br /> <br />
<div class="text-justify"> <div class="text-justify">
Мы размещаем игровые сервера, как постоянные, так и временные для различных событий. Мы размещаем игровые сервера, как постоянные, так и временные для различных событий.
Например, у нас есть сервер Например, у нас есть сервер
<SocialHyperlink href="https://hl.teasanctuary.ru"> <SocialHyperlink href="https://hl.teasanctuary.ru">Tea Sanctuary HLDM</SocialHyperlink>, где
Tea Sanctuary HLDM вы можете ознакомиться с новыми картами от всего сообщества Half-Life.
</SocialHyperlink>, где вы можете ознакомиться с новыми картами от всего сообщества
Half-Life.
</div> </div>
<br /> <br />
<div class="text-justify"> <div class="text-justify">
Не одними играми едины, за нашими плечами есть несколько прикладных программ, Не одними играми едины, за нашими плечами есть несколько прикладных программ, созданных под
созданных под заказ. Про них ничего особо рассказать не можем, но если вам надо заказ. Про них ничего особо рассказать не можем, но если вам надо что-нибудь сделать &mdash;
что-нибудь сделать &mdash; пишите нам! пишите нам!
</div> </div>
</section> </section>
<section id="how-can-you-contact-us"> <section id="how-can-you-contact-us">
<h1>Как с вами связаться?</h1> <h1>Как с вами связаться?</h1>
<div class="text-justify"> <div class="text-justify">
Общие вопросы можно задавать в Общие вопросы можно задавать в <SocialHyperlink href={PUBLIC_TS_DISCORD}>сообществе Tea Sanctuary</SocialHyperlink>.
<SocialHyperlink href={PUBLIC_TS_DISCORD}> Там же можете написать личное сообщение администраторам.
сообществе Tea Sanctuary
</SocialHyperlink>. Там же можете написать личное сообщение администраторам.
</div> </div>
<br /> <br />
<div class="text-justify"> <div class="text-justify">
Наши соцсети и почту для более важных обращений можно найти на странице Наши соцсети и почту для более важных обращений можно найти на странице <SocialHyperlink href="/contact">Контакты</SocialHyperlink>.
<SocialHyperlink href="/contact">Контакты</SocialHyperlink>.
</div> </div>
</section> </section>
</div> </div>
</section> </section>
<style> <style>
@import '$src/app.css'; @import "$src/app.css";
section > h1,
section > h2 {
@apply font-disket mb-4 font-bold;
}
section > h1 { section > h1 {
@apply text-2xl sm:text-4xl; @apply font-disket mb-4 text-2xl font-bold sm:text-4xl;
} }
</style>
section > h2 {
@apply text-xl sm:text-3xl;
}
</style>

3
src/routes/+page.ts Normal file
View file

@ -0,0 +1,3 @@
export async function load() {
return { title: undefined };
}

View file

@ -1,8 +1,8 @@
<script lang="ts"> <script lang="ts">
import { page } from '$app/state'; import { page } from '$app/state';
import Icon from '@iconify/svelte';
import SocialHyperlink from '$src/lib/components/SocialHyperlink.svelte'; import SocialHyperlink from '$src/lib/components/SocialHyperlink.svelte';
import InfoBlock from '$src/lib/components/InfoBlock.svelte'; import InfoBlock from '$src/lib/components/InfoBlock.svelte';
import BlogCard from '$src/lib/components/BlogCard.svelte';
function groupPostsByMonthYear(posts: App.BlogPost[]) { function groupPostsByMonthYear(posts: App.BlogPost[]) {
const groupedPosts = new Map<string, App.BlogPost[]>(); const groupedPosts = new Map<string, App.BlogPost[]>();
@ -48,7 +48,44 @@
</h1> </h1>
<div class="flex flex-col flex-wrap items-center gap-4"> <div class="flex flex-col flex-wrap items-center gap-4">
{#each postsInMonthYear as post, i} {#each postsInMonthYear as post, i}
<BlogCard {post} /> <a
href="/blog/{post.slug}"
class="flex w-full max-w-5xl flex-col justify-baseline overflow-hidden rounded-lg bg-slate-100 text-slate-950 drop-shadow-xl transition-all hover:drop-shadow-2xl sm:flex-row sm:justify-stretch"
>
<div class="relative h-32 w-full basis-auto sm:h-auto sm:basis-1/3">
{#if post.thumbnail}
<img
class="absolute h-full w-full object-cover"
src={`/blog/${post.slug}/${post.thumbnail}`}
alt={post.thumbnailAlt ?? 'Миниатюра поста'}
/>
{/if}
</div>
<div class="flex w-full flex-col justify-center p-4 break-words md:p-8">
<div class="flex flex-row flex-wrap justify-between gap-4 pb-4">
<div class="flex items-center text-lg font-bold">
<Icon
icon="material-symbols:calendar-today"
class="mr-3"
style="transform: scale( 1.3 )"
/>
<p>
{new Date(post.date!).toLocaleString('default', {
month: 'short',
day: 'numeric',
year: 'numeric'
})}
</p>
</div>
</div>
<h2 class="text-3xl font-bold">{post.title}</h2>
{#if post.description}
<p>{post.description}</p>
{/if}
</div>
</a>
{/each} {/each}
</div> </div>
{/each} {/each}

View file

@ -1,6 +1,5 @@
<script lang="ts"> <script lang="ts">
import { page } from '$app/state'; import { page } from '$app/state';
import DateWidget from '$src/lib/components/DateWidget.svelte';
import InfoBlock from '$src/lib/components/InfoBlock.svelte'; import InfoBlock from '$src/lib/components/InfoBlock.svelte';
import type { PageData } from './$types'; import type { PageData } from './$types';
import Icon from '@iconify/svelte'; import Icon from '@iconify/svelte';
@ -34,9 +33,29 @@
? 'bg-amber-50 text-slate-950' ? 'bg-amber-50 text-slate-950'
: 'bg-red-500 text-slate-50'} sm:flex-row sm:flex-nowrap sm:gap-5" : 'bg-red-500 text-slate-50'} sm:flex-row sm:flex-nowrap sm:gap-5"
> >
<DateWidget dateString={data.blogPost.date} type="published" /> <div class="flex items-center">
<Icon icon="material-symbols:calendar-today" class="mr-3" width={24} height={24} />
<p>
{data.blogPost.date
? new Date(data.blogPost.date).toLocaleString(undefined, {
month: 'short',
day: 'numeric',
year: 'numeric'
})
: 'Не опубликован!'}
</p>
</div>
{#if data.blogPost.dateChanged} {#if data.blogPost.dateChanged}
<DateWidget dateString={data.blogPost.dateChanged} type="updated" /> <div class="flex items-center font-bold">
<Icon icon="material-symbols:update" class="mr-3" width={24} height={24} />
<p>
{new Date(data.blogPost.dateChanged).toLocaleString(undefined, {
month: 'short',
day: 'numeric',
year: 'numeric'
})}
</p>
</div>
{/if} {/if}
</section> </section>
@ -61,8 +80,8 @@
prose-headings:mb-4 prose-headings:mb-4
prose-headings:font-bold prose-headings:text-slate-950 prose-h1:text-2xl prose-headings:font-bold prose-headings:text-slate-950 prose-h1:text-2xl
prose-h1:sm:text-4xl prose-h2:text-xl prose-h1:sm:text-4xl prose-h2:text-xl
prose-h2:sm:text-3xl prose-p:text-justify prose-h2:sm:text-3xl mx-auto
mx-auto prose-p:text-justify
w-5xl w-5xl
p-4 p-4
text-base text-base

View file

@ -38,7 +38,7 @@ const config = {
}, },
extensions: ['.svelte', '.md'], extensions: ['.svelte', '.md'],
preprocess: [ preprocess: [
vitePreprocess({ script: true }), vitePreprocess(),
mdsvex({ mdsvex({
extensions: ['.md'], extensions: ['.md'],
layout: join(__dirname, "./src/lib/components/MdsvexLayout.svelte") layout: join(__dirname, "./src/lib/components/MdsvexLayout.svelte")