Compare commits

..

1 commit

6 changed files with 411 additions and 159 deletions

1
.prettierignore Normal file
View file

@ -0,0 +1 @@
public/utility.css

View file

@ -9,6 +9,8 @@
<link rel="icon" type="image/png" sizes="32x32" href="/icons/favicon-32x32.png" ></link> <link rel="icon" type="image/png" sizes="32x32" href="/icons/favicon-32x32.png" ></link>
<link rel="icon" type="image/png" sizes="16x16" href="/icons/favicon-16x16.png" ></link> <link rel="icon" type="image/png" sizes="16x16" href="/icons/favicon-16x16.png" ></link>
<link rel="stylesheet" href="/reset.css"></link>
<link rel="stylesheet" href="/utility.css"></link>
<link rel="stylesheet" href="/base.css"></link> <link rel="stylesheet" href="/base.css"></link>
<link rel="stylesheet" href="/icons.css"></link> <link rel="stylesheet" href="/icons.css"></link>
<link rel="stylesheet" href="/files/custom.css"></link> <link rel="stylesheet" href="/files/custom.css"></link>

0
public/reset.css Normal file
View file

View file

@ -92,22 +92,6 @@
</script> </script>
<style type="text/css"> <style type="text/css">
#sidebar {
z-index: 100;
background: var(--bg);
position: relative;
width: auto;
left: 0;
max-width: 32rem;
padding-left: 6rem;
transition: all ease-in-out 0.33s;
border-right: 1px solid var(--border-subtle);
}
#sidebar .profile-container {
width: 100%;
max-width: 100%;
}
#sidebar #sidebar-context-menu { #sidebar #sidebar-context-menu {
display: none; display: none;
@ -298,7 +282,7 @@
} }
</style> </style>
<div id="sidebar"> <div id="sidebar" class="z-100 rel left-0 pad-left-8 transition border-right" style="max-width: 32rem;">
<div id="sidebar-context-menu"> <div id="sidebar-context-menu">
<form <form
data-smart="true" data-smart="true"
@ -358,56 +342,12 @@
APP.on( 'load', update_servers_list ); APP.on( 'load', update_servers_list );
APP.on( 'user_logged_in', update_servers_list ); APP.on( 'user_logged_in', update_servers_list );
</script> </script>
<style> <div id="server-list-container" class="abs top-0 left-0 bottom-0 pad-4 border-right scroll-y width-6">
#server-list-container {
position: absolute;
top: 0;
left: 0;
bottom: 0;
padding: 1rem;
width: 6rem;
border-right: 1px solid var(--border-subtle);
overflow-y: auto;
}
.server-list-entry {
width: 100%;
padding: 0.5rem;
margin: 0.5rem auto;
overflow: hidden;
text-align: center;
margin-bottom: 1rem;
}
.server-list-entry a {
text-decoration: none;
}
.server-list-entry img {
width: 3rem;
height: 3rem;
margin: 0 auto 0.75rem;
padding: 0.2rem;
border-radius: var(--border-radius);
object-fit: scale-down;
align-content: center;
border: 1px solid var(--border-subtle);
background: var(--icon-background);
}
.server-list-entry .server-name {
font-size: x-small;
word-wrap: break-word;
font-weight: bold;
max-height: 3rem;
}
</style>
<div id="server-list-container">
<template id="server-list-entry-template"> <template id="server-list-entry-template">
<div class="server-list-entry"> <div class="server-list-entry width-100 pad-2 margin-top-3 margin-x-auto hideoverflow text-center margin-bottom-4">
<a href="${ server.url }"> <a href="${ server.url }" class="undecorated">
<img class="server-icon" src="${ server.icon ?? ( server.url + '/favicon.ico' ) }" alt="${ server.name ?? server.url } icon" style="${ server.icon_background ? `--icon-background: ${ server.icon_background };` : '' }" loading="lazy" /> <img class="server-icon box-sm margin-top-0 margin-x-auto margin-bottom-3 pad-1 border border-rounded align-center" style="object-fit: scale-down; background: var(--icon-background);" src="${ server.icon ?? ( server.url + '/favicon.ico' ) }" alt="${ server.name ?? server.url } icon" style="${ server.icon_background ? `--icon-background: ${ server.icon_background };` : '' }" loading="lazy" />
<div class="server-name">${ server.name ?? server.url }</div> <div class="server-name text-sm text-bold" style="word-wrap: break-work; max-height: 3rem;">${ server.name ?? server.url }</div>
</a> </a>
</div> </div>
</template> </template>
@ -417,14 +357,7 @@
<div id="user-servers-container"></div> <div id="user-servers-container"></div>
</div> </div>
<style> <div id="server-info" class="rel pad-y-4 pad-x-6 height-100">
#server-info {
position: relative;
height: 100%;
padding: 1rem 2rem;
}
</style>
<div id="server-info">
<script> <script>
const DEFAULT_AVATAR_URL = `//${window.location.host}/images/default_avatar.gif`; const DEFAULT_AVATAR_URL = `//${window.location.host}/images/default_avatar.gif`;
@ -478,30 +411,7 @@
attributeFilter: ["data-user"], attributeFilter: ["data-user"],
}); });
</script> </script>
<style type="text/css"> <form class="pad-4 pad-y-0" style="max-width: 1024px;">
.profile-container {
max-width: 1024px;
padding: 1rem;
padding-bottom: 0;
}
.profile-container .avatar-container {
position: relative;
width: 100%;
max-width: 200px;
}
.profile-container .avatar-container input[type="file"] {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
opacity: 0;
cursor: pointer;
}
</style>
<form class="profile-container">
<script> <script>
const profile_form = document.currentScript.closest("form"); const profile_form = document.currentScript.closest("form");
@ -600,7 +510,7 @@
} }
}); });
</script> </script>
<div class="avatar-container xx-large"> <div class="box-sm rel">
<img <img
id="user-avatar" id="user-avatar"
src="/images/default_avatar.gif" src="/images/default_avatar.gif"
@ -609,49 +519,38 @@
data-user-field-target="src" data-user-field-target="src"
data-user-field-default="/images/default_avatar.gif" data-user-field-default="/images/default_avatar.gif"
/> />
<input type="file" accept="image/*" name="meta.avatar" /> <input class="abs top-0 right-0 bottom-0 left-0 transparent pointer" type="file" accept="image/*" name="meta.avatar" />
</div> </div>
<details class="additional-profile"> <details>
<summary> <summary>
<div class="username-container"> <div class="text-lg text-bold">
<span class="username" data-bind-to-user-field="username"></span> <span class="username" data-bind-to-user-field="username"></span>
</div> </div>
</summary> </summary>
<div class="notifications-settings-container"> <button onclick="NOTIFICATIONS.request_permission()">
<button onclick="NOTIFICATIONS.request_permission()"> Enable Notifications
Enable Notifications </button>
</button>
</div>
<div class="color-settings-container"> <input
<input type="text"
type="text" id="user-color-setting-primary"
id="user-color-setting-primary" name="meta.primary_color"
name="meta.primary_color" value=""
value="" data-bind-to-user-field="meta.primary_color"
data-bind-to-user-field="meta.primary_color" data-user-field-target="value"
data-user-field-target="value" data-user-field-default=""
data-user-field-default="" />
/> <label class="placeholder" for="user-color-setting-primary">Primary Color</label>
<label class="placeholder" for="user-color-setting-primary">Primary Color</label>
</div>
</details> </details>
</form> </form>
<button
style="text-transform: uppercase; width: 100%; padding: 1.1rem 0"
onclick="generate_invite(event)"
>
Invite Another Human
</button>
<form <form
data-smart="true" data-smart="true"
data-method="DELETE" data-method="DELETE"
action="/api/auth" action="/api/auth"
style="position: absolute; left: 1rem; right: 1rem; bottom: 1rem" style="margin: 1em 0;"
> >
<script> <script>
{ {
@ -669,6 +568,13 @@
<button class="primary">Log Out</button> <button class="primary">Log Out</button>
</form> </form>
<button
style="text-transform: uppercase; width: 100%; padding: 1.1rem 0"
onclick="generate_invite(event)"
>
Invite Another Human
</button>
<div id="sidebar-dynamic-container"> <div id="sidebar-dynamic-container">
</div> </div>
</div> </div>

View file

@ -1,19 +1,19 @@
<script> <script>
function on_channels_updated({ channels }) { function on_channels_updated({ channels }) {
const channel_list = document.getElementById("channel-list"); const channel_list = document.getElementById("channel-list");
if ( !channel_list ) { if (!channel_list) {
setTimeout( () => { setTimeout(() => {
on_channels_updated( { channels } ); on_channels_updated({ channels });
}, 100 ); }, 100);
return; return;
} }
channel_list.innerHTML = ""; channel_list.innerHTML = "";
for (const channel of channels.sort((lhs, rhs) => lhs.name.localeCompare(rhs.name))) { for (const channel of channels.sort((lhs, rhs) => lhs.name.localeCompare(rhs.name))) {
if ( !document.body.dataset.channel ) { if (!document.body.dataset.channel) {
document.body.dataset.channel = APP.user?.meta?.chat?.last_channel ?? channel.id; document.body.dataset.channel = APP.user?.meta?.chat?.last_channel ?? channel.id;
if ( APP.view === 'chat' ) { if (APP.view === "chat") {
window.location.hash = '/chat/channel/' + document.body.dataset.channel; window.location.hash = "/chat/channel/" + document.body.dataset.channel;
} }
} }
@ -24,28 +24,30 @@
} }
} }
APP.on("channels_updated", on_channels_updated ); APP.on("channels_updated", on_channels_updated);
APP.on( 'view_changed', async ( { previous, view, channel_id } ) => { APP.on("view_changed", async ({ previous, view, channel_id }) => {
if ( view !== 'chat' ) { if (view !== "chat") {
return; return;
} }
const previous_channel = typeof document.body.dataset.channel === 'string' ? document.body.dataset.channel : undefined; const previous_channel =
typeof document.body.dataset.channel === "string"
? document.body.dataset.channel
: undefined;
if ( channel_id ) { if (channel_id) {
document.body.dataset.channel = channel_id; document.body.dataset.channel = channel_id;
} } else {
else {
delete document.body.dataset.channel; delete document.body.dataset.channel;
} }
await APP.CHANNELS.update(); // don't force, but ensure we have channels await APP.CHANNELS.update(); // don't force, but ensure we have channels
const target_channel_id = channel_id ?? APP.CHANNELS.CHANNEL_LIST[0]?.id; const target_channel_id = channel_id ?? APP.CHANNELS.CHANNEL_LIST[0]?.id;
const hash_target = `/chat` + ( target_channel_id ? `/channel/${ target_channel_id }` : '' ); const hash_target = `/chat` + (target_channel_id ? `/channel/${target_channel_id}` : "");
if ( window.location.hash?.slice( 1 ) !== hash_target ) { if (window.location.hash?.slice(1) !== hash_target) {
window.location.hash = hash_target; window.location.hash = hash_target;
} }
}); });
@ -62,7 +64,7 @@
.forEach((element) => element.classList.add("active")); .forEach((element) => element.classList.add("active"));
} }
for ( const watch of APP.user_watches ) { for (const watch of APP.user_watches) {
// find the channel indicator for this watch // find the channel indicator for this watch
// if there is new stuff - TODO implement a HEAD for getting latest event id? // if there is new stuff - TODO implement a HEAD for getting latest event id?
// add a class of 'new-content' // add a class of 'new-content'
@ -117,23 +119,22 @@
} }
}); });
APP.on( 'view_changed', ( {previous, view} ) => { APP.on("view_changed", ({ previous, view }) => {
const sidebar_dynamic_container = document.getElementById( 'sidebar-dynamic-container'); const sidebar_dynamic_container = document.getElementById("sidebar-dynamic-container");
if ( !sidebar_dynamic_container ) { if (!sidebar_dynamic_container) {
console.error( 'could not get #sidebar-dynamic-container' ); console.error("could not get #sidebar-dynamic-container");
return; return;
} }
if ( view !== 'chat' && previous === 'chat' ) { if (view !== "chat" && previous === "chat") {
sidebar_dynamic_container.innerHTML = ''; sidebar_dynamic_container.innerHTML = "";
delete document.body.dataset.channel; delete document.body.dataset.channel;
return; return;
} } else if (view !== "chat") {
else if ( view !== 'chat' ) {
return; return;
} }
const template = document.getElementById( 'channel-list-template'); const template = document.getElementById("channel-list-template");
sidebar_dynamic_container.innerHTML = template.innerHTML.trim(); sidebar_dynamic_container.innerHTML = template.innerHTML.trim();
APP.CHANNELS.update(true); APP.CHANNELS.update(true);
}); });
@ -186,14 +187,43 @@
</div> </div>
<ul id="channel-list" class="channel-list"></ul> <ul id="channel-list" class="channel-list"></ul>
<div data-requires-permission="channels.create">
<form
id="channel-create"
data-smart="true"
action="/api/channels"
method="POST"
on_reply="(new_channel) => {
APP.CHANNELS.update(true);
document.getElementById('new-channel-name-input').value = '';
document.getElementById('channel-create').style['height'] = '0';
window.location.hash = `/chat/channel/${new_channel.id}`;
}"
>
<input
id="new-channel-name-input"
type="text"
name="name"
value=""
placeholder="new channel"
/>
<button type="submit">Create Channel</button>
</form>
</div>
<!--
<div id="channel-creation-container" data-requires-permission="channels.create"> <div id="channel-creation-container" data-requires-permission="channels.create">
<button <button
id="toggle-channel-creation-form-button" id="toggle-channel-creation-form-button"
onclick="((event) => { onclick="
event.preventDefault(); ((event) => {
const channel_create_form = document.getElementById( 'channel-create' ); event.preventDefault();
channel_create_form.style[ 'height' ] = channel_create_form.style[ 'height' ] === '5rem' ? '0' : '5rem'; const channel_create_form = document.getElementById('channel-create');
})(event)" channel_create_form.style['height'] =
channel_create_form.style['height'] === '5rem' ? '0' : '5rem';
})(event)
"
> >
<div class="icon plus"></div> <div class="icon plus"></div>
</button> </button>
@ -225,8 +255,9 @@
placeholder="new channel" placeholder="new channel"
/> />
<input type="submit" hidden /> <button type="submit">Create</button>
</form> </form>
</div> </div>
-->
</div> </div>
</template> </template>

312
public/utility.css Normal file
View file

@ -0,0 +1,312 @@
:root {
/* ===== COLORS (Base) ===== */
--color-primary: hsl(220, 80%, 50%);
--color-error: hsl(0, 80%, 50%);
--color-warning: hsl(45, 100%, 50%);
--color-caution: hsl(30, 80%, 50%);
--color-success: hsl(120, 80%, 50%);
--color-accent: hsl(200, 80%, 50%);
--color-muted: hsl(210, 10%, 50%);
--color-highlighted: hsl(220, 80%, 70%);
--color-text: hsl(0, 0%, 0%);
--color-bg: hsl(0, 0%, 100%);
--color-border: hsl(0, 0%, 90%);
/* ===== LIGHT/DARK MODE ===== */
--color-text-dark: hsl(0, 0%, 0%);
--color-text-light: hsl(0, 0%, 100%);
--color-bg-dark: hsl(0, 0%, 10%);
--color-bg-light: hsl(0, 0%, 95%);
--color-border-dark: hsl(0, 0%, 20%);
--color-border-light: hsl(0, 0%, 90%);
/* ===== SHADES (Lighten/Darken) ===== */
--shade-50: oklch(from var(--color-primary) l c h / 0.05);
--shade-100: oklch(from var(--color-primary) l c h / 0.1);
--shade-200: oklch(from var(--color-primary) l c h / 0.2);
--shade-300: oklch(from var(--color-primary) l c h / 0.3);
--shade-400: oklch(from var(--color-primary) l c h / 0.4);
--shade-500: oklch(from var(--color-primary) l c h / 0.5);
--shade-600: oklch(from var(--color-primary) l c h / 0.6);
--shade-700: oklch(from var(--color-primary) l c h / 0.7);
--shade-800: oklch(from var(--color-primary) l c h / 0.8);
--shade-900: oklch(from var(--color-primary) l c h / 0.9);
/* ===== DARK MODE OVERRIDES ===== */
--color-text: var(--color-text-light);
--color-bg: var(--color-bg-dark);
--color-border: var(--color-border-dark);
}
/* ===== DARK MODE TOGGLE (Optional) ===== */
@media (prefers-color-scheme: dark) {
:root {
--color-text: var(--color-text-dark);
--color-bg: var(--color-bg-dark);
--color-border: var(--color-border-dark);
}
}
/* ===== UTILITY CLASSES ===== */
/* ===== SPACING ===== */
.margin-0 { margin: 0 !important; }
.margin-1 { margin: 0.25rem !important; }
.margin-2 { margin: 0.5rem !important; }
.margin-3 { margin: 0.75rem !important; }
.margin-4 { margin: 1rem !important; }
.margin-5 { margin: 1.5rem !important; }
.margin-6 { margin: 2rem !important; }
.margin-7 { margin: 3rem !important; }
.margin-8 { margin: 4rem !important; }
.margin-0 { margin: 0 !important; }
.margin-top-1 { margin-top: 0.25rem !important; }
.margin-top-2 { margin-top: 0.5rem !important; }
.margin-top-3 { margin-top: 0.75rem !important; }
.margin-top-4 { margin-top: 1rem !important; }
.margin-top-5 { margin-top: 1.5rem !important; }
.margin-top-6 { margin-top: 2rem !important; }
.margin-top-7 { margin-top: 3rem !important; }
.margin-top-8 { margin-top: 4rem !important; }
.margin-bottom-1 { margin-bottom: 0.25rem !important; }
.margin-bottom-2 { margin-bottom: 0.5rem !important; }
.margin-bottom-3 { margin-bottom: 0.75rem !important; }
.margin-bottom-4 { margin-bottom: 1rem !important; }
.margin-bottom-5 { margin-bottom: 1.5rem !important; }
.margin-bottom-6 { margin-bottom: 2rem !important; }
.margin-bottom-7 { margin-bottom: 3rem !important; }
.margin-bottom-8 { margin-bottom: 4rem !important; }
.margin-left-1 { margin-left: 0.25rem !important; }
.margin-left-2 { margin-left: 0.5rem !important; }
.margin-left-3 { margin-left: 0.75rem !important; }
.margin-left-4 { margin-left: 1rem !important; }
.margin-left-5 { margin-left: 1.5rem !important; }
.margin-left-6 { margin-left: 2rem !important; }
.margin-left-7 { margin-left: 3rem !important; }
.margin-left-8 { margin-left: 4rem !important; }
.margin-right-1 { margin-right: 0.25rem !important; }
.margin-right-2 { margin-right: 0.5rem !important; }
.margin-right-3 { margin-right: 0.75rem !important; }
.margin-right-4 { margin-right: 1rem !important; }
.margin-right-5 { margin-right: 1.5rem !important; }
.margin-right-6 { margin-right: 2rem !important; }
.margin-right-7 { margin-right: 3rem !important; }
.margin-right-8 { margin-right: 4rem !important; }
.margin-x-auto { margin-left: auto !important; margin-right: auto !important; }
.margin-x-0 { margin-left: 0 !important; margin-right: 0 !important; }
.margin-x-1 { margin-left: 0.25rem !important; margin-right: 0.25rem !important; }
.margin-x-2 { margin-left: 0.5rem !important; margin-right: 0.5rem !important; }
.margin-x-3 { margin-left: 0.75rem !important; margin-right: 0.75rem !important; }
.margin-x-4 { margin-left: 1rem !important; margin-right: 1rem !important; }
.margin-x-5 { margin-left: 1.5rem !important; margin-right: 1.5rem !important; }
.margin-x-6 { margin-left: 2rem !important; margin-right: 2rem !important; }
.margin-x-7 { margin-left: 3rem !important; margin-right: 3rem !important; }
.margin-x-8 { margin-left: 4rem !important; margin-right: 4rem !important; }
.margin-y-0 { margin-top: 0 !important; margin-bottom: 0 !important; }
.margin-y-1 { margin-top: 0.25rem !important; margin-bottom: 0.25rem !important; }
.margin-y-2 { margin-top: 0.5rem !important; margin-bottom: 0.5rem !important; }
.margin-y-3 { margin-top: 0.75rem !important; margin-bottom: 0.75rem !important; }
.margin-y-4 { margin-top: 1rem !important; margin-bottom: 1rem !important; }
.margin-y-5 { margin-top: 1.5rem !important; margin-bottom: 1.5rem !important; }
.margin-y-6 { margin-top: 2rem !important; margin-bottom: 2rem !important; }
.margin-y-7 { margin-top: 3rem !important; margin-bottom: 3rem !important; }
.margin-y-8 { margin-top: 4rem !important; margin-bottom: 4rem !important; }
.pad-0 { padding: 0 !important; }
.pad-1 { padding: 0.25rem !important; }
.pad-2 { padding: 0.5rem !important; }
.pad-3 { padding: 0.75rem !important; }
.pad-4 { padding: 1rem !important; }
.pad-5 { padding: 1.5rem !important; }
.pad-6 { padding: 2rem !important; }
.pad-7 { padding: 3rem !important; }
.pad-8 { padding: 4rem !important; }
.pad-0 { padding: 0 !important; }
.pad-top-1 { padding-top: 0.25rem !important; }
.pad-top-2 { padding-top: 0.5rem !important; }
.pad-top-3 { padding-top: 0.75rem !important; }
.pad-top-4 { padding-top: 1rem !important; }
.pad-top-5 { padding-top: 1.5rem !important; }
.pad-top-6 { padding-top: 2rem !important; }
.pad-top-7 { padding-top: 3rem !important; }
.pad-top-8 { padding-top: 4rem !important; }
.pad-bottom-1 { padding-bottom: 0.25rem !important; }
.pad-bottom-2 { padding-bottom: 0.5rem !important; }
.pad-bottom-3 { padding-bottom: 0.75rem !important; }
.pad-bottom-4 { padding-bottom: 1rem !important; }
.pad-bottom-5 { padding-bottom: 1.5rem !important; }
.pad-bottom-6 { padding-bottom: 2rem !important; }
.pad-bottom-7 { padding-bottom: 3rem !important; }
.pad-bottom-8 { padding-bottom: 4rem !important; }
.pad-left-1 { padding-left: 0.25rem !important; }
.pad-left-2 { padding-left: 0.5rem !important; }
.pad-left-3 { padding-left: 0.75rem !important; }
.pad-left-4 { padding-left: 1rem !important; }
.pad-left-5 { padding-left: 1.5rem !important; }
.pad-left-6 { padding-left: 2rem !important; }
.pad-left-7 { padding-left: 3rem !important; }
.pad-left-8 { padding-left: 4rem !important; }
.pad-right-1 { padding-right: 0.25rem !important; }
.pad-right-2 { padding-right: 0.5rem !important; }
.pad-right-3 { padding-right: 0.75rem !important; }
.pad-right-4 { padding-right: 1rem !important; }
.pad-right-5 { padding-right: 1.5rem !important; }
.pad-right-6 { padding-right: 2rem !important; }
.pad-right-7 { padding-right: 3rem !important; }
.pad-right-8 { padding-right: 4rem !important; }
.pad-x-0 { padding-left: 0 !important; padding-right: 0 !important; }
.pad-x-1 { padding-left: 0.25rem !important; padding-right: 0.25rem !important; }
.pad-x-2 { padding-left: 0.5rem !important; padding-right: 0.5rem !important; }
.pad-x-3 { padding-left: 0.75rem !important; padding-right: 0.75rem !important; }
.pad-x-4 { padding-left: 1rem !important; padding-right: 1rem !important; }
.pad-x-5 { padding-left: 1.5rem !important; padding-right: 1.5rem !important; }
.pad-x-6 { padding-left: 2rem !important; padding-right: 2rem !important; }
.pad-x-7 { padding-left: 3rem !important; padding-right: 3rem !important; }
.pad-x-8 { padding-left: 4rem !important; padding-right: 4rem !important; }
.pad-y-0 { padding-top: 0 !important; padding-bottom: 0 !important; }
.pad-y-1 { padding-top: 0.25rem !important; padding-bottom: 0.25rem !important; }
.pad-y-2 { padding-top: 0.5rem !important; padding-bottom: 0.5rem !important; }
.pad-y-3 { padding-top: 0.75rem !important; padding-bottom: 0.75rem !important; }
.pad-y-4 { padding-top: 1rem !important; padding-bottom: 1rem !important; }
.pad-y-5 { padding-top: 1.5rem !important; padding-bottom: 1.5rem !important; }
.pad-y-6 { padding-top: 2rem !important; padding-bottom: 2rem !important; }
.pad-y-7 { padding-top: 3rem !important; padding-bottom: 3rem !important; }
.pad-y-8 { padding-top: 4rem !important; padding-bottom: 4rem !important; }
/* ===== BORDERS ===== */
.border { border: 1px solid var(--color-border) !important; }
.border-right { border-right: 1px solid var(--color-border) !important; }
.border-left { border-left: 1px solid var(--color-border) !important; }
.border-top { border-top: 1px solid var(--color-border) !important; }
.border-bottom { border-bottom: 1px solid var(--color-border) !important; }
.border-0 { border: none !important; }
.border-color-primary { border-color: var(--color-primary) !important; }
.border-color-error { border-color: var(--color-error) !important; }
.border-color-warning { border-color: var(--color-warning) !important; }
.border-color-success { border-color: var(--color-success) !important; }
.border-color-accent { border-color: var(--color-accent) !important; }
.border-rounded { border-radius: 0.25rem !important; }
.border-rounded-lg { border-radius: 0.5rem !important; }
.border-rounded-full { border-radius: 9999px !important; }
/* ===== FLEX & GRID ===== */
.align-center { align-content: center !important; }
.flex { display: flex !important; }
.flex-col { flex-direction: column !important; }
.flex-row { flex-direction: row !important; }
.flex-wrap { flex-wrap: wrap !important; }
.flex-center { justify-content: center !important; align-items: center !important; }
.flex-start { justify-content: flex-start !important; align-items: flex-start !important; }
.flex-end { justify-content: flex-end !important; align-items: flex-end !important; }
.grid { display: grid !important; }
.grid-cols-1 { grid-template-columns: repeat(1, 1fr) !important; }
.grid-cols-2 { grid-template-columns: repeat(2, 1fr) !important; }
.grid-cols-3 { grid-template-columns: repeat(3, 1fr) !important; }
.grid-cols-4 { grid-template-columns: repeat(4, 1fr) !important; }
/* ===== TYPOGRAPHY ===== */
.text-sm { font-size: 0.875rem !important; }
.text-md { font-size: 1rem !important; }
.text-lg { font-size: 1.125rem !important; }
.text-xl { font-size: 1.25rem !important; }
.text-bold { font-weight: bold !important; }
.text-italic { font-style: italic !important; }
.text-center { text-align: center !important; }
.text-left { text-align: left !important; }
.text-right { text-align: right !important; }
.undecorated { text-decoration: none !important; }
/* ===== COLORS (Background, Text, etc.) ===== */
.bg-primary { background-color: var(--color-primary) !important; }
.bg-primary-50 { background-color: var(--shade-50) !important; }
.bg-primary-100 { background-color: var(--shade-100) !important; }
.bg-error { background-color: var(--color-error) !important; }
.bg-warning { background-color: var(--color-warning) !important; }
.bg-success { background-color: var(--color-success) !important; }
.bg-accent { background-color: var(--color-accent) !important; }
.bg-muted { background-color: var(--color-muted) !important; }
.bg-highlighted { background-color: var(--color-highlighted) !important; }
.bg-transparent { background-color: transparent !important; }
.text-primary { color: var(--color-primary) !important; }
.text-error { color: var(--color-error) !important; }
.text-warning { color: var(--color-warning) !important; }
.text-success { color: var(--color-success) !important; }
.text-accent { color: var(--color-accent) !important; }
.text-muted { color: var(--color-muted) !important; }
.text-highlighted { color: var(--color-highlighted) !important; }
/* ===== EFFECTS ===== */
.shadow { box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1) !important; }
.shadow-sm { box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05) !important; }
.shadow-md { box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1) !important; }
.shadow-lg { box-shadow: 0 10px 15px rgba(0, 0, 0, 0.1) !important; }
.opacity-0 { opacity: 0 !important; }
.opacity-25 { opacity: 0.25 !important; }
.opacity-50 { opacity: 0.5 !important; }
.opacity-75 { opacity: 0.75 !important; }
.opacity-100 { opacity: 1 !important; }
.transition { transition: all 0.2s ease-in-out !important; }
.transition-fast { transition: all 0.1s ease-in-out !important; }
.transition-slow { transition: all 0.5s ease-in-out !important; }
/* ===== POSITIONING ===== */
.abs { position: absolute !important; }
.rel { position: relative !important; }
.sticky { position: sticky !important; }
.top-0 { top: 0 !important; }
.bottom-0 { bottom: 0 !important; }
.left-0 { left: 0 !important; }
.right-0 { right: 0 !important; }
.z-0 { z-index: 0 !important; }
.z-10 { z-index: 10 !important; }
.z-20 { z-index: 20 !important; }
.z-30 { z-index: 30 !important; }
.z-40 { z-index: 40 !important; }
.z-50 { z-index: 50 !important; }
/* === GENERICS === */
.transparent { opacity: 0 !important; }
.pointer { cursor: pointer !important; }
.hideoverflow { overflow: hidden !important; }
.scroll-x { overflow-x: scroll !important; }
.scroll-y { overflow-y: scroll !important; }
.box-xxs { width: 1rem; height: 1rem; }
.box-xs { width: 2rem; height: 2rem; }
.box-sm { width: 3rem; height: 3rem; }
.box-md { width: 4.5rem; height: 4.5rem; }
.box-lg { width: 6rem; height: 6rem; }
.box-xl { width: 9rem; height: 9rem; }
.box-xxl { width: 12rem; height: 12rem; }
/* === REM WIDTHS === */
.width-0 { width: 0 !important; }
.width-1 { width: 1rem !important; }
.width-2 { width: 2rem !important; }
.width-3 { width: 3rem !important; }
.width-4 { width: 4rem !important; }
.width-5 { width: 5rem !important; }
.width-6 { width: 6rem !important; }
.width-7 { width: 7rem !important; }
.width-8 { width: 8rem !important; }
.width-9 { width: 9rem !important; }
.width-10 { width: 10rem !important; }
.width-11 { width: 11rem !important; }
.width-12 { width: 12rem !important; }
.width-13 { width: 13rem !important; }
.width-14 { width: 14rem !important; }
.width-15 { width: 15rem !important; }
.width-16 { width: 16rem !important; }
.width-20 { width: 20rem !important; }
.width-24 { width: 24rem !important; }
.width-28 { width: 28rem !important; }
.width-32 { width: 32rem !important; }
.width-100 { width: 100% !important; }
.height-100 { height: 100% !important; }