Compare commits

...

14 commits

14 changed files with 203 additions and 53 deletions

View file

@ -62,7 +62,7 @@ export default async function (eleventyConfig) {
ignoreCustomFragments: [
/<(use|path)[^>]*>(?:(?!<\/(use|path)>)[\s\S])*?<\/(use|path)>/,
/<div[^>]*class="color-box"[^>]*>(?:(?!<\/div>)[\s\S])*?<\/div>/,
/<nav[^>]*class="gallery-nav"[^>]*>.*?<\/nav>/
/<nav[^>]*class="slider-nav"[^>]*>.*?<\/nav>/
]
});
}

View file

@ -42,6 +42,8 @@
overflow: clip;
box-shadow: 0 0 0.5em var(--clr-box-shadow);
@media (min-width: 35em) {
--areas: 'color' 'heading' 'value';
--rows: var(--color-box-size) 1fr auto;

View file

@ -52,7 +52,7 @@
font-size: 0.75em;
box-shadow: 0.125em 0.125em 0.5em var(--clr-box-shadow);
box-shadow: 0.125em 0.125em 0.75em 0.25em var(--clr-box-shadow);
margin-block: 1em;
border-radius: 1em;

View file

@ -40,7 +40,7 @@
--clr-box-background: var(--theme-c-primary-150);
--clr-box-border: var(--theme-c-primary-250);
--clr-box-shadow: var(--theme-c-primary-200);
--clr-box-gradient-start: oklch(from var(--clr-box-background) calc(l + 0.2) c h);
--clr-box-gradient-start: oklch(from var(--clr-box-background) calc(l + 0.3) c h);
--clr-box-gradient-end: oklch(from var(--clr-box-background) l c h);
--clr-color-box-background: var(--theme-c-primary-200);
@ -77,6 +77,7 @@
--clr-selection: var(--clr-accent-1);
--clr-selection-text: var(--theme-c-primary-100);
--clr-nav-link-hover: oklch(from var(--clr-accent-1) calc(l + 0.2) calc(c * 1.5) h);
--clr-nav-link-active: var(--clr-accent-1);
--clr-heading-underline: var(--clr-accent-1);
--clr-heading-data: var(--clr-accent-1);
@ -97,6 +98,7 @@
--clr-selection: var(--clr-accent-8);
--clr-selection-text: var(--theme-c-primary-100);
--clr-nav-link-hover: oklch(from var(--clr-accent-7) calc(l + 0.2) calc(c * 1.5) h);
--clr-nav-link-active: var(--clr-accent-8);
--clr-heading-underline: var(--clr-accent-8);
--clr-heading-data: var(--clr-accent-8);
@ -120,6 +122,7 @@
--clr-selection: var(--clr-accent-1);
--clr-selection-text: var(--theme-c-primary-100);
--clr-nav-link-hover: oklch(from var(--clr-accent-1) calc(l + 0.2) calc(c * 1.5) h);
--clr-nav-link-active: var(--clr-accent-1);
--clr-heading-underline: var(--clr-accent-1);
--clr-heading-data: var(--clr-accent-1);

View file

@ -1,6 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<svg webc:root="override">
<defs>
<symbol id="fa6-solid:utensils" viewBox="0 0 448 512">
<!-- !Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc. -->
<path
d="M416 0C400 0 288 32 288 176l0 112c0 35.3 28.7 64 64 64l32 0 0 128c0 17.7 14.3 32 32 32s32-14.3 32-32l0-128 0-112 0-208c0-17.7-14.3-32-32-32zM64 16C64 7.8 57.9 1 49.7 .1S34.2 4.6 32.4 12.5L2.1 148.8C.7 155.1 0 161.5 0 167.9c0 45.9 35.1 83.6 80 87.7L80 480c0 17.7 14.3 32 32 32s32-14.3 32-32l0-224.4c44.9-4.1 80-41.8 80-87.7c0-6.4-.7-12.8-2.1-19.1L191.6 12.5c-1.8-8-9.3-13.3-17.4-12.4S160 7.8 160 16l0 134.2c0 5.4-4.4 9.8-9.8 9.8c-5.1 0-9.3-3.9-9.8-9L127.9 14.6C127.2 6.3 120.3 0 112 0s-15.2 6.3-15.9 14.6L83.7 151c-.5 5.1-4.7 9-9.8 9c-5.4 0-9.8-4.4-9.8-9.8L64 16zm48.3 152l-.3 0-.3 0 .3-.7 .3 .7z"
/>
</symbol>
<symbol id="fa6-solid:glass-water" viewBox="0 0 384 512">
<!-- !Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc. -->
<path
d="M32 0C23.1 0 14.6 3.7 8.6 10.2S-.6 25.4 .1 34.3L28.9 437.7c3 41.9 37.8 74.3 79.8 74.3l166.6 0c42 0 76.8-32.4 79.8-74.3L383.9 34.3c.6-8.9-2.4-17.6-8.5-24.1S360.9 0 352 0L32 0zM73 156.5L66.4 64l251.3 0L311 156.5l-24.2 12.1c-19.4 9.7-42.2 9.7-61.6 0c-20.9-10.4-45.5-10.4-66.4 0c-19.4 9.7-42.2 9.7-61.6 0L73 156.5z"
/>
</symbol>
<symbol id="fa6-solid:whiskey-glass" viewBox="0 0 512 512">
<!-- !Font Awesome Free 6.7.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2025 Fonticons, Inc. -->
<path

Before

Width:  |  Height:  |  Size: 35 KiB

After

Width:  |  Height:  |  Size: 36 KiB

Before After
Before After

View file

@ -7,6 +7,7 @@
<style webc:scoped="page-content">
:host {
width: 100%;
max-width: 65ch;
background-color: var(--clr-box-background);
border: var(--border-thin) solid var(--clr-box-border);

View file

@ -29,6 +29,7 @@
gap: 0.75em;
width: 100%;
max-width: 65ch;
background-color: var(--clr-box-background);

View file

@ -16,7 +16,7 @@
var(--gradient-end) 50%
);
box-shadow: 0.125em 0.125em 0.5em var(--clr-box-shadow);
box-shadow: 0.125em 0.125em 0.75em 0.25em var(--clr-box-shadow);
margin-block: 1em;
border-radius: 1em;
@ -26,16 +26,18 @@
}
:host::before {
--gradient-start: var(--clr-quick-info-gradient-start);
--gradient-end: var(--clr-quick-info-gradient-end);
--gradient-dir: ellipse at bottom right;
--gradient-start: var(--clr-quick-info-gradient-end);
--gradient-end: var(--clr-quick-info-gradient-start);
content: '';
position: absolute;
inset: var(--border-thin);
background: linear-gradient(
background: radial-gradient(
var(--gradient-dir),
var(--gradient-start) 0%,
var(--gradient-end) 50%
var(--gradient-start) 70%,
var(--gradient-end) 100%
);
border-radius: inherit;
z-index: -1;

View file

@ -1,10 +1,10 @@
<script>
const gallery = document.querySelector('.gallery');
const track = gallery.querySelector('.track');
const images = Array.from(gallery.querySelectorAll('.track > *'));
const nav = gallery.querySelector('.gallery-nav');
const prevButton = gallery.querySelector('button.prev');
const nextButton = gallery.querySelector('button.next');
const slider = document.querySelector('.slider');
const track = slider.querySelector('.track');
const items = Array.from(slider.querySelectorAll('.track > *'));
const nav = slider.querySelector('.slider-nav');
const prevButton = slider.querySelector('button.prev');
const nextButton = slider.querySelector('button.next');
const TOLERANCE = 2;
@ -27,8 +27,8 @@
};
const updateActiveStates = () => {
images.forEach(updateAriaCurrent);
nav.querySelectorAll('.indicator-btn').forEach(updateAriaCurrent);
items.forEach(updateAriaCurrent);
nav?.querySelectorAll('.indicator-btn').forEach(updateAriaCurrent);
prevButton.disabled = isAtStart();
nextButton.disabled = isAtEnd();
};
@ -36,10 +36,10 @@
let currentIndex = 0;
const scrollToIndex = (index) => {
if (index < 0 || index >= images.length || index === currentIndex) return;
if (index < 0 || index >= items.length || index === currentIndex) return;
currentIndex = index;
images[index].scrollIntoView({ behavior: 'smooth', block: 'nearest', inline: 'center' });
items[index].scrollIntoView({ behavior: 'smooth', block: 'nearest', inline: 'center' });
updateActiveStates();
};
@ -50,11 +50,11 @@
const resizeObserver = new ResizeObserver(() => debounce(scrollToIndex(currentIndex)));
resizeObserver.observe(track);
const galleryObserver = new IntersectionObserver(
const sliderObserver = new IntersectionObserver(
(entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting && entry.intersectionRatio >= 0.75) {
const index = images.indexOf(entry.target);
const index = items.indexOf(entry.target);
if (index !== -1) {
currentIndex = index;
updateActiveStates();
@ -66,39 +66,39 @@
);
const initializeGallery = () => {
images.forEach((el, i) => {
items.forEach((el, i) => {
const btn = document.createElement('button');
if (i === 0) btn.setAttribute('aria-current', 'true');
btn.classList.add('indicator-btn');
btn.dataset.index = i;
btn.innerText = i + 1;
btn.setAttribute('aria-label', `Go to image ${i + 1} of ${images.length}`);
btn.setAttribute('aria-label', `Go to item ${i + 1} of ${items.length}`);
btn.addEventListener('click', () => scrollToIndex(Number(btn.dataset.index)));
if (i === 0) el.setAttribute('aria-current', 'true');
el.setAttribute('aria-label', `Image ${i + 1} of ${images.length}`);
el.setAttribute('aria-label', `Item ${i + 1} of ${items.length}`);
el.setAttribute('tabindex', '0');
el.setAttribute('role', 'group');
nav.appendChild(btn);
nav?.appendChild(btn);
});
updateActiveStates();
};
images.forEach((el) => galleryObserver.observe(el));
items.forEach((el) => sliderObserver.observe(el));
prevButton.addEventListener('click', prev);
nextButton.addEventListener('click', next);
track.addEventListener('scroll', debounce(updateActiveStates, 100));
document.addEventListener('DOMContentLoaded', initializeGallery);
</script>
<section aria-label="Image gallery" webc:root="override">
<button class="prev" aria-label="Previous Image">
<section aria-label="Slider" webc:root="override">
<button class="prev" aria-label="Previous item">
<icon icon="fa6-solid:chevron-left"></icon>
</button>
<button class="next" aria-label="Next Image">
<button class="next" aria-label="Next item">
<icon icon="fa6-solid:chevron-right"></icon>
</button>
@ -106,10 +106,10 @@
<slot></slot>
</div>
<nav class="gallery-nav" aria-label="Gallery"></nav>
<nav webc:if="nav !== false" class="slider-nav" aria-label="Slider"></nav>
</section>
<style webc:scoped="gallery">
<style webc:scoped="slider">
:host {
display: grid;
grid-auto-columns: auto 1fr auto;
@ -168,7 +168,12 @@
}
:host :where(.prev, .next) {
--btn-alignment: -0.75em;
grid-row: track;
@media (min-width: 48em) {
--btn-alignment: 0.5em;
}
}
:host :where(.prev, .next):disabled {
@ -184,12 +189,12 @@
:host .prev {
grid-column: prev;
left: 0.5em;
left: var(--btn-alignment);
}
:host .next {
grid-column: next;
right: 0.5em;
right: var(--btn-alignment);
}
:host .track {
@ -236,7 +241,7 @@
padding-inline: 1em;
}
:host .gallery-nav {
:host .slider-nav {
--indicator-size: 2.5em;
grid-row: progress;

View file

@ -0,0 +1,104 @@
<div webc:root="override">
<div class="icon-box">
<icon :icon="icon"></icon>
</div>
<div class="content">
<h3 class="title">
<slot name="title"></slot>
</h3>
<p>
<slot></slot>
</p>
</div>
</div>
<style webc:scoped="trait-card">
:host {
--card-margin-inline: 0.5em;
position: relative;
display: grid;
font-size: 0.875em;
margin-block: 2em 0.5em;
margin-inline: var(--card-margin-inline);
border-radius: 1em;
@media (min-width: 48em) {
--card-margin-inline: 4em;
}
}
:host .icon-box {
position: absolute;
top: -1.25em;
left: 1.5em;
width: 3em;
height: 3em;
background: linear-gradient(
to bottom right,
var(--clr-box-gradient-start) 0%,
var(--clr-box-gradient-end) 50%
);
box-shadow: 0.125em 0.125em 0.5em var(--clr-box-shadow);
border-radius: 0.5em;
overflow: hidden;
z-index: 2;
}
:host .icon-box > * {
margin: var(--border-thin);
background-color: var(--clr-box-background);
border-radius: inherit;
}
:host svg {
padding: 0.625em;
}
:host .content {
--gradient-dir: to bottom right;
--gradient-start: var(--clr-box-gradient-start);
--gradient-end: var(--clr-box-gradient-end);
position: relative;
background: linear-gradient(
var(--gradient-dir),
var(--gradient-start) 0%,
var(--gradient-end) 50%
);
border-radius: inherit;
padding: 1.5em;
box-shadow: 0.125em 0.125em 0.75em 0.25em var(--clr-box-shadow);
z-index: 0;
}
:host .content::before {
--gradient-dir: ellipse at bottom right;
--gradient-start: var(--clr-quick-info-gradient-end);
--gradient-end: var(--clr-quick-info-gradient-start);
content: '';
position: absolute;
inset: var(--border-thin);
background: radial-gradient(
var(--gradient-dir),
var(--gradient-start) 70%,
var(--gradient-end) 100%
);
border-radius: inherit;
z-index: -1;
}
:host .title {
font-size: 1.75em;
margin-block-start: 0.5em;
}
</style>

View file

@ -21,10 +21,10 @@
grid-template-areas:
'icon type'
'icon data';
grid-template-rows: auto 1fr;
grid-template-columns: auto 1fr;
column-gap: 0.75em;
justify-items: start;
}
:host .icon-box {
@ -44,7 +44,6 @@
border-radius: 0.5em;
overflow: hidden;
z-index: 1;
}
:host .icon-box::before {

View file

@ -90,7 +90,7 @@ Sebin is very concerned with an even distribution of muscle mass, but pays parti
An assortment of additional references how Sebin can be drawn.
<ref-gallery>
<slider>
<ref-img webc:for="ref of [...Array(9).keys()]"
:@src="`ref-muscle-${ref + 1}.png`"
:@alt="$data.galleryMuscle[ref].alt"
@ -100,7 +100,7 @@ An assortment of additional references how Sebin can be drawn.
:@char="$data.firstName.toLowerCase()"
:@dropshadow="false"
></ref-img>
</ref-gallery>
</slider>
<nsfw-barrier>
<div slot="message">

View file

@ -6,7 +6,7 @@ eleventyNavigation:
Sebin knows how to dress!
<ref-gallery>
<slider>
<ref-img webc:for="ref of Array(4).keys()"
:@src="`ref-clothes-${ref + 1}.png`"
:@alt="$data.galleryClothing[ref].alt"
@ -16,4 +16,4 @@ Sebin knows how to dress!
:@artist="$data.galleryClothing[ref].artist"
:@dropshadow="false"
></ref-img>
</ref-gallery>
</slider>

View file

@ -4,38 +4,59 @@ eleventyNavigation:
order: 1
---
Sebin is a warm-hearted guy who cares a lot about the well-being of his loved ones. Bad vibes rarely escape him and he offers his help without hesitation. He also won't avoid difficult conversations in the process. Not being able to help a friend in need is synonymous with failing them, a realization that can leave him feeling uneasy long after the fact.
Sebin is the kind of person who wears his heart on his sleeve. He deeply cares about those close to him and is the first to lend a helping hand. He doesn't shy away from difficult topics either. Not being able to help a friend in need weighs heavily on him.
At the same time, he is very open and honest about his feelings. He does not mince words and finds clear words when speaking his mind. Unfortunately, Sebin sometimes forgets his good manners in the heat of the moment, once he gets invested into a quarrel — especially when it comes to topics that are near and dear to his heart. Anyone looking to have a bad time can try their luck at pissing him off even once. This includes an equally vulgar vocabulary. It is not uncommon to hear him swear.
At the same time, Sebin is honest about his feelings and doesn't mince words. He will absolutely tell you the things you need to hear, not what you want to hear, if he believes the situation calls for it. This sometimes gets him into trouble, especially when he deeply cares about something. Occasionally, his good intentions might clash with his sharp tongue, but he always tries to do the right thing.
Nevertheless, Sebin strives to put his best foot forward at all times. He is of the sociable type and likes to laugh a lot, as he is easily amused. Sometimes to a degree where it can become very childish and immature very quickly.
At the same time, you don't want to end up on Sebin's bad side. The same way he's not afraid to be straight-forward towards friends, he's not afraid to give someone a piece of his mind in conflicts. He will absolutely cuss someone out of a room if he's had enough of someone's antics. He's not one to hold grudges, but depending on how wronged he feels, it can take a long time before he can lay the past to rest.
Physical strength is not the only thing that plays a big role for Sebin. He is of a firm believe that a healthy body must also have a healthy mind in order to find a balance. But he only came to this realization at the end of a rocky road. While a setback in the past could easily throw him off track, today he stands much more firmly in life. Not only for his own sake, but also to be a kind of anchor for others. He always keeps his cool, so he can be a tower of strength for others.
Despite all this, Sebin is actually a big softie at heart and loves to make people laugh. He's got a great sense of humor and can usually be found grinning from ear to ear—although it's not uncommon for him to be on the childish and silly side.
Sebin believes that taking care of the body and mind are equally important. The path to realizing this has been rocky, but he learned a lot from past experiences and now prioritizes self-care—both for himself and those around him. He wants to be the anchor people can rely on when things get tough. Stay calm, and trust him to help you navigate any challenges that come your way.
## Hobbies
<quick-info>
<traits :@traits="$data.getTraits('hobbies')"></traits>
</quick-info>
Sebin is a dragon of many interests. When he's not busy with work or other obligations, he usually has his hands full with something else. It's not uncommon for him to pick up new interests easily and get lost in them for hours.
Sebin is passionate about his hobbies. If he notices even the smallest spark of interest in his hobbies you should bring a lot of time, as he will chew your ear off first. Patience is known to be a virtue — one unknown to this dragon.
Some of these include:
When he indulges in his hobbies, he does so with devotion. Every move has to be right and everything has to be in perfect harmony. Once he is in his flow, he must not be disturbed, otherwise he can sometimes become quite eccentric in expressing his dismay of being disrupted, possibly losing a very important train of thought.
<slider :@nav="false">
<trait-card @icon="fa6-solid:dumbbell">
<template webc:nokeep slot="title">Working out</template>
<template webc:nokeep>Sebin has a regular workout routine. He loves pushing his limits, whether that's at home during downtime or at the gym. Once the headphones are on, nothing comes between him and his workout. A nice run to warm up followed by pumping some heavy iron. When he's working out, Sebin is all business. He sets tough goals for himself and pushes through even when it gets hard.</template>
</trait-card>
<trait-card @icon="fa6-solid:plane">
<template webc:nokeep slot="title">Travel</template>
<template webc:nokeep>Sebin loves exploring new places and experiencing different cultures. Whether it's a quick weekend getaway or a longer trip to a far-flung destination. Sebin is a big eater and loves to experience the local cuisine wherever he goes. His wanderlust also greatly coincides with his love for hikes and camping in the great outdoors. Sebin loves sitting around a campfire with friends, just talking and enjoying some fire-breath roasted marshmallows and some beers.</template>
</trait-card>
<trait-card @icon="fa6-solid:gamepad">
<template webc:nokeep slot="title">Video Games</template>
<template webc:nokeep>Sebin is a bit of a gaming addict, but in the best possible way! Some of his favorite types of games are action-packed shooters, chill building games and role-playing epics—whether it's hot new releases or retro classics. He gets fully immersed in these virtual worlds. He'll spend hours finding every secret, achievement hunting, and discussing different strategies with friends over voice-chat.</template>
</trait-card>
<trait-card @icon="fa6-solid:laptop-code">
<template webc:nokeep slot="title">Technology</template>
<template webc:nokeep>If Sebin isn't busy working out, he's tinkering with the latest gadgets and technologies. He loves getting into the weeds of all of his devices to get a deep understanding how they work and make them operate just the way he needs. He's always happy to share his expertise with others and teach them how to get the best out of their tech. He also has a knack for a little bit of coding on the side.</template>
</trait-card>
</slider>
Despite being pretty chill about his hobbies, Sebin is always happy to share his passions when people show a shared interest in them! Just bring a lot of time if you engage him about any of them, because this dragon is prone to yapping and *lots* of it.
## Food & Drink
Sebin's day starts with a strong cup of black coffee and sandwiches. He's also a massive sweet tooth which sounds like a big detriment to his fitness routine. That's because it is and it's often very hard for him to resist.
Sebin's go-to morning pick-me-up is a cup of coffee, with added milk and sugar. He enjoys a varied breakfast, ranging from bacon and eggs, omelettes, scrambled eggs, pancakes, to simple sandwiches and a bowl of cereal (when there's no time or he's too lazy to cook).
Besides snacking, Sebin also likes to eat hearty and savory things. He doesn't disdain a cheese platter with a wide selection, nor a medium-rare steak.
He has a particular love for hearty and savory food: juicy hotdogs, roasted chicken, a medium-rare steak, extra meaty hamburgers, pizza loaded with toppings, and plates piled high with spaghetti or pasta. But he's also got a major sweet tooth for chocolate, sweet and sour candy, soft-serve ice cream an various kinds of cake. If all of this sounds like it would be a huge detriment to his workout routine, that's because it is, but he tries to keep a balance—unless it's cheat day, then all bets are off.
Sebin rarely says no to a good beer with friends, just as he rarely says no to a bar tour to try new and interesting cocktails.
Sebin also has a penchant for spice, since he has a natural tolerance for heat as a fire dragon. If he has the option to spice things up, he won't think twice about it. He especially loves spicy dishes from Asian and Mexican cuisine. Sometimes he likes challenging unsuspecting victims to a spicy food contest—which he usually wins, while his opponent drowns themselves in milk.
When socializing, Sebin can usually be found enjoying a cold beer or two (or three…). Sebin enjoys going bar hopping, especially on travels, to sample local delicacies and try out unique drinks. He enjoys creamy liqueurs and fiery shots. Whether he's sipping on a classic martini or experimenting with the latest trends, Sebin always knows how to have a good time.
<nsfw-barrier>
<div slot="message">
## But wait! There's more…
Curious what he's into? 😏
Curious about what else he's into? 😏
</div>
<filter-list :@data="$data.kinks"></filter-list>
</nsfw-barrier>