Compare commits

...

6 commits

Author SHA1 Message Date
c096fc1dfd
feat: add home button to navigate back to character select
When deep-linking character pages there's no way for a visitor to know there's other characters as well
2025-08-02 23:02:25 +02:00
06866831a3
fix: 💬 update sebin's description 2025-08-02 21:43:11 +02:00
b0b745b2eb
feat: add artist credit for profile picture 2025-08-02 21:42:47 +02:00
deceea3071
feat: update title generation 2025-08-02 20:14:54 +02:00
3d1a78f0a4
feat: 📈 clarify message on consent banner 2025-08-02 20:14:08 +02:00
ff23e060db
feat: 💬 update privacy policy
Clarify a few aspects of how data collection works
2025-08-02 19:53:40 +02:00
10 changed files with 99 additions and 30 deletions

View file

@ -33,8 +33,9 @@
<dialog id="ackee-banner" webc:root="override">
<form method="dialog">
<p class="message">
<strong>📊 Analytics 💡</strong> May I collect some anonymized data about the device you use
to view this site? I won't know who you are. See: <a href="/privacy/">Privacy Policy</a>
<strong>📊 Analytics 💡</strong> May I collect anonymized data about your device to help me
improve this site? No personally identifiable data is stored. See:
<a href="/privacy/">Privacy Policy</a>
</p>
<ref-button class="positive">Yeah sure</ref-button>
<ref-button class="negative">Nope</ref-button>

View file

@ -1,8 +1,7 @@
<script webc:setup>
const getTitle = (fullName) => {
if (fullName) {
return `${fullName} — Character Reference`;
}
const getTitle = (fullName, title) => {
if (fullName) return `${fullName} — Character Reference`;
if (title) return `The World of Sebin's Characters — ${title}`;
return "The World of Sebin's Characters";
};
@ -11,7 +10,7 @@
char ? `src/img/${char.toLowerCase()}/${type}.png` : `src/img/${type}.png`;
</script>
<title @text="getTitle($data.fullName)"></title>
<title @text="getTitle($data.fullName, $data.title)"></title>
<link
rel="shortcut icon"

View file

@ -1,4 +1,5 @@
<nav :aria-label="label" webc:root="override">
<a class="home link" href="/#characters"> <icon icon="fa6-solid:chevron-left"></icon> Home </a>
<label for="nav-toggle">
<icon icon="fa6-solid:bars"></icon>
</label>
@ -32,7 +33,11 @@
inset: 0.5em;
display: grid;
grid-auto-flow: column;
justify-content: space-between;
justify-items: end;
align-items: center;
gap: 1em;
background-color: var(--nav-background-color);
box-shadow: var(--box-shadow);
@ -44,7 +49,7 @@
z-index: 2;
@media (min-width: 35em) {
@media (min-width: 48em) {
--nav-spacing: 1.5em;
--nav-background-color: transparent;
--nav-border: none;
@ -70,7 +75,7 @@
margin-block: 0.75em;
margin-inline: 1em;
@media (min-width: 35em) {
@media (min-width: 48em) {
display: none;
}
}
@ -109,7 +114,7 @@
opacity: 0;
transition: opacity 0.4s ease-in-out;
@media (min-width: 35em) {
@media (min-width: 48em) {
--nav-items-position: static;
--nav-items-orientation: row nowrap;
align-items: start;
@ -123,7 +128,7 @@
height: 0;
transition: height 0.4s ease-in-out;
@media (min-width: 35em) {
@media (min-width: 48em) {
height: 100%;
}
}
@ -138,10 +143,36 @@
padding-block: calc(var(--nav-spacing) / 2);
transition: color 0.4s ease;
& svg {
width: 1em;
height: 1em;
transition: fill 0.4s ease;
}
}
:host .home {
display: flex;
align-items: center;
gap: 0.125em;
padding-inline: 0.75em 1em;
z-index: 3;
@media (min-width: 48em) {
background-color: var(--clr-nav-background);
box-shadow: var(--box-shadow);
border-radius: inherit;
}
}
:host .link:hover {
color: var(--clr-nav-link-hover);
& svg {
fill: var(--clr-nav-link-hover);
}
}
:host .link-active {

View file

@ -1,7 +1,10 @@
<aside webc:root="override">
<div class="avatar">
<figure class="avatar">
<slot name="avatar"></slot>
</div>
<figcaption class="credit">
<slot name="credit"></slot>
</figcaption>
</figure>
<p class="name">
<slot name="name"></slot>
</p>
@ -76,15 +79,35 @@
grid-template-columns: subgrid;
justify-items: center;
align-items: center;
margin: 0;
border-radius: 1em;
overflow: clip;
& > * {
grid-area: avatar;
}
& :where(picture, img) {
display: block;
width: var(--sidebar-avatar-size);
height: var(--sidebar-avatar-size);
aspect-ratio: 1/1;
border-radius: 1em;
border-radius: inherit;
object-fit: cover;
}
& .credit {
justify-self: stretch;
align-self: end;
background-color: oklch(from var(--clr-box-background) l c h/0.75);
font-size: 0.5em;
text-align: center;
margin: 0;
padding-block: 0.25em;
}
}
:host :where(.name, .species) {

View file

@ -1,4 +1,8 @@
{
"layout": "character.webc",
"language": "en_US"
"language": "en_US",
"avatar": {
"artist": "Wolfthings",
"href": "https://www.furaffinity.net/user/wolfthings"
}
}

View file

@ -21,6 +21,9 @@ layout: base.webc
:width="[100, 200, 300, 400, 500, 600]"
sizes="(min-width: 120em) 250px, (min-width: 64em) 200px, 100px"
></eleventy-image>
<template webc:nokeep slot="credit">
&copy; <a :href="$data.avatar.href" target="_blank" @text="$data.avatar.artist"></a>
</template>
<template webc:nokeep slot="name" @text="fullName"></template>
<template webc:nokeep slot="species" @text="species"></template>
<traits :@traits="$data.getTraits()"></traits>

View file

@ -11,7 +11,7 @@ Ackee is open-source analytics software. The data it collects is anonymized in a
Ackee gives me insight into the following data points:
* Number of visits per day
* Number of visitors for a given day
* Number of visitors that currently view the site
* Approximate duration of stay
* Number of times a given page was visited
@ -22,24 +22,26 @@ Ackee gives me insight into the following data points:
* Screen size of the device
* Primary language of the browser or operating system
Ackee uses the IP, user-agent and domainId to identify a user. All information will be hashed together with a [salt](https://en.wikipedia.org/wiki/Salt_(cryptography)) that changes daily. The final hash is called `clientId`.
Ackee uses the IP, user-agent (browser name, version, operating system) and domain ID of a visitor to calculate a client ID. This data is immediately discarded after calculation. Ackee calculates the client ID in a way that it cannot be used to trace back to individual visitors. It avoids that database backups can be used to stick data together to reconstruct the browsing history of a user.
The daily salt is never stored anywhere. It avoids that database backups can be used to stick data together to reconstruct the browsing history of a user.
In other words: Unless we're in the same room together and already know the above data points in advance, I do not know who clicks around on this site. I only know that *someone* was here but not *who*.
## Purpose of collecting data
I collect this data to better understand my audience, improve the site's user experience, and to inform future development and editorial decisions. The data will not be shared with anyone, ever.
I collect this data to better understand my audience, improve the site's compatibility so it looks and works right on as many devices as possible, and to inform future development and editorial decisions. The data will not be shared with anyone, I will remain the sole person with access to it. It is purely for the purpose of maintenance.
## Data retention
Ackee removes data from previous records when a new record with an existing identification gets added. This way the user identifier and other identifiable data is only stored once in the database.
Ackee removes data from previous records when a new record with an existing identification gets added. This way the user identifier and other potentially identifiable data is only stored once in the database.
In other words: Ackee forgets who you are as soon as it sees you again. It's not possible to reconstruct a browsing history, even on a daily basis.
## User consent
Data collection is opt-in. I will not collect any analytics data until you explicitly allow me to do so. If you previously opted-in but changed your mind, click the button below:
Data collection is opt-in. No data is sent to my Ackee installation before you give your explicit consent. If you decline, no data will ever be sent to my Ackee installation. The banner at the bottom of the page will keep asking you until you decide to agree or disagree. In either case, your browser will remember your choice in its local storage. If you clear your browser's cache your choice may be cleared as well, in which case the banner will appear and ask for your consent once more.
<button onclick="localStorage.setItem('ackeeDetailed', false)" class="items-center bg-sky-600 *:stroke-[2.5] 2xl:px-6 2xl:py-3 2xl:rounded-2xl button duration-300 font-bold gap-2 hover:bg-sky-700 inline-flex no-underline px-5 py-2 rounded-xl text-white transition-colors">Opt-out</button>
If you previously opted-in but changed your mind, click the button below:
<ref-button onclick="localStorage.setItem('ackeeDetailed', false)">Opt-out</ref-button>
By using this site, you acknowledge that you have read and understood this privacy policy. If you have any questions or concerns about how your data is collected or used, please feel free to [contact me](https://sebin-nyshkim.net/contact).

View file

@ -4,15 +4,13 @@ eleventyNavigation:
order: 1
---
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.
Sebin is the kind of person who wears his heart on his sleeve. He's 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, but it's how he shows that he cares. Occasionally, his good intentions might clash with his sharp tongue, but he always tries to do the right thing.
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.
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.
What you absolutely don't want, though, is 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 a conflict. He will absolutely cuss someone out of a room if he's had enough of their antics. He's not one to hold grudges, as he believes everyone deserves a second chance, but he's ready to cut people out of his life if things keep taking a wrong turn.
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.
Sebin wants to be the anchor people can rely on when things get tough because he developed a strong protective instinct over the years. He knows from experience how hard it is to have to fight your battles alone, so he's usually the first to extend a helping hand. However, he often tends to put himself last when he does, to the point of burning himself out. That's when he needs that helping hand extended to him, but he's embarrassed by it. Nevertheless, he's grateful when someone does.
## Hobbies
@ -31,7 +29,7 @@ Some of these include:
</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 fully immerses himself in the worlds and mechanics of these virtual worlds. He'll spend hours finding every secret, go achievement hunting, and discussing different strategies with friends over voice-chat and online communities.</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 fully immerses himself in the worlds and mechanics of these virtual worlds. He'll spend hours finding every secret, go achievement hunting, and discussing different strategies with friends over voice-chat and in online communities.</template>
</trait-card>
<trait-card @icon="fa6-solid:laptop-code">
<template webc:nokeep slot="title">Technology</template>
@ -43,7 +41,7 @@ Sebin is always happy to share his passions when people show a shared interest i
## Food & Drink
Sebin's go-to morning pick-me-up is a cup of coffee, with added milk and sugar. He enjoys a varied breakfast, whether that be bacon and eggs, omelettes, scrambled eggs, pancakes, simple sandwiches or a good ol' bowl of cereal (when there's no time or he's too lazy to cook).
Sebin's go-to morning pick-me-up is a cup of coffee, with added milk and sugar. He enjoys a varied breakfast, whether that be bacon and eggs, pancakes, simple sandwiches or a good ol' bowl of cereal (when there's no time or he's too lazy to cook).
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 and 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.

View file

@ -1,6 +1,10 @@
{
"layout": "character.webc",
"language": "en_US",
"avatar": {
"artist": "RainbowT",
"href": "https://bsky.app/profile/rainbowt.bsky.social"
},
"galleryMuscle": [
{
"alt": "Sebin looking aloof (but chill)",

View file

@ -1,4 +1,8 @@
{
"layout": "character.webc",
"language": "en_US"
"language": "en_US",
"avatar": {
"artist": "RyaxShark",
"href": "https://bsky.app/profile/ryaxshark.bsky.social"
}
}