Qubasa-main (#17)
Added The Vision paragraph and added a new logo, improved theme switching. Minor changes in Overview paragraph
|
@ -127,6 +127,6 @@ url = "/privacy-policy/"
|
|||
weight = 10
|
||||
|
||||
[[extra.footer.nav]]
|
||||
name = "Overview"
|
||||
url = "/docs/overview"
|
||||
name = "The Vision"
|
||||
url = "/docs/thevision"
|
||||
weight = 20
|
||||
|
|
|
@ -3,8 +3,8 @@ title = "Build your own"
|
|||
|
||||
# The homepage contents
|
||||
[extra]
|
||||
lead = '<img src="/logo.png" width="100px" height="100px" alt="cLAN logo"></img> <br><b>cLAN</b> envisions a new model for a decentralized network, designed to provide families, smaller groups, and small businesses a platform that’s private, secure, and user-friendly. '
|
||||
url = "/docs/getting-started/introduction/"
|
||||
lead = '<img src="/logo/clan-dark.png" class="clogo" width="100px" height="100px" alt="cLAN logo"></img> <br><b>cLAN</b> envisions a new model for a decentralized network, designed to provide families, smaller groups, and small businesses a platform that’s private, secure, and user-friendly. '
|
||||
url = "/docs/thevision/"
|
||||
url_button = "Learn more"
|
||||
repo_version = "cLAN v0.0.0-alpha"
|
||||
repo_license = "Open-source MIT License."
|
||||
|
@ -14,7 +14,7 @@ repo_url = "https://git.clan.lol/clan/"
|
|||
[[extra.menu.main]]
|
||||
name = "Docs"
|
||||
section = "docs"
|
||||
url = "/docs/overview"
|
||||
url = "/docs/thevision"
|
||||
weight = 10
|
||||
|
||||
[[extra.menu.main]]
|
||||
|
@ -23,6 +23,8 @@ section = "blog"
|
|||
url = "/blog/"
|
||||
weight = 20
|
||||
|
||||
|
||||
|
||||
[[extra.list]]
|
||||
title = "Easy to use"
|
||||
content = 'cLAN provides a user-friendly interface that allows you to establish your own private network, complete with services.'
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
+++
|
||||
title = "Hello World"
|
||||
title = "The Beginning"
|
||||
description = "Introducing cLAN, a new model for a decentralized network, designed to provide families, smaller groups, and small businesses a platform that’s private, secure, and user-friendly."
|
||||
date = 2021-05-01T09:19:42+00:00
|
||||
updated = 2021-05-01T09:19:42+00:00
|
||||
date = 2023-05-01T09:19:42+00:00
|
||||
updated = 2023-05-01T09:19:42+00:00
|
||||
draft = false
|
||||
template = "blog/page.html"
|
||||
|
||||
|
@ -10,6 +10,6 @@ template = "blog/page.html"
|
|||
authors = ["Mic92"]
|
||||
|
||||
[extra]
|
||||
lead = "A blog post introducing the project."
|
||||
lead = "Introducing cLAN,"
|
||||
+++
|
||||
Some more text
|
||||
a new model for a decentralized network, designed to provide families, smaller groups, and small businesses a platform that's self hosted while pertaining the commodities of a cloud infrastructure.
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
+++
|
||||
title = "Overview"
|
||||
title = "Technial Overview"
|
||||
description = "Overview of cLAN's architecture, components and security"
|
||||
date = 2021-05-01T18:10:00+00:00
|
||||
updated = 2021-07-13T18:10:00+00:00
|
||||
|
@ -15,11 +15,10 @@ top = false
|
|||
+++
|
||||
|
||||
|
||||
# cLAN
|
||||
### cLAN
|
||||
|
||||
cLAN envisions a new model for a decentralized network, designed to provide families, smaller groups, and small businesses a platform that's private, secure, and user-friendly. The system transcends the conventional reliance on centralized services, allowing for direct, end-to-end encrypted communication among users. Rooted in open-source software, cLAN ensures no vendor lock-in, and introduces robust features including remote management, backup functionality, user-friendly app store, and fleet management for small businesses.
|
||||
|
||||
## Architecture Overview
|
||||
|
||||
### Decentralized Network Model
|
||||
|
||||
|
@ -151,8 +150,8 @@ This modules would be build by the VM controller into a system with the followin
|
|||
inputs.myNetworkFlake.nixosModules.default
|
||||
./local-overrides.nix
|
||||
];
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -192,9 +191,9 @@ configs.json
|
|||
}
|
||||
```
|
||||
|
||||
A crude example of how this interface would look like
|
||||
<img width="100%" src="flake_controller_new.png" alt="VM Manager New Network">
|
||||
|
||||
<!-- A crude example of how this interface would look like
|
||||
<img width="100%" src="flake_controller_new.png" alt="VM Manager New Network">
|
||||
-->
|
||||
|
||||
### VM Manager
|
||||
|
||||
|
@ -259,7 +258,6 @@ Our safeguards will include virtual machine isolation to mitigate the damage an
|
|||
To address this, we only offer a limited set of options in our high-level modules, which either restrict or issue warnings for configurations deemed to be unsafe.
|
||||
For instance, services are exposed exclusively to the internal network and not to the wider internet, thereby offering an extra layer of security.
|
||||
|
||||
TODO: Threats that are out-of-scope
|
||||
|
||||
### Trusted compute base
|
||||
|
||||
|
|
42
content/docs/thevision/index.md
Normal file
|
@ -0,0 +1,42 @@
|
|||
+++
|
||||
title = "The Vision"
|
||||
description = "The General Vision of this Project"
|
||||
date = 2021-05-01T19:30:00+00:00
|
||||
updated = 2021-05-01T19:30:00+00:00
|
||||
draft = false
|
||||
weight = 30
|
||||
sort_by = "weight"
|
||||
paginator.next = "docs/overview/index.md"
|
||||
template = "docs/page.html"
|
||||
+++
|
||||
|
||||
|
||||
In this context, our vision defines how we envision the behavior of the final product and how users will interact with it. cLAN aims to offer a compelling alternative to the ever-increasing centralization of the internet.
|
||||
|
||||
### Decentralized Networking
|
||||
|
||||
Instead of relying on a centralized cloud accessible via the clearnet, our objective is to establish an encrypted network of interconnected computers, essentially creating a decentralized darknet, which we affectionately call a cLAN.
|
||||
|
||||
### Seamless Onboarding
|
||||
|
||||
Our vision includes providing users with a seamless experience when they join one or multiple cLANs through invitation links. Clicking the link will trigger a popup that seeks confirmation to connect to the network.
|
||||
|
||||
### Effortless VM Integration
|
||||
|
||||
During the joining process, the user's computer (referred to as the client hereafter) will download a file containing information about a virtual machine (VM). The client will then automatically set up the VM, and once complete, this VM will become part of the cLAN.
|
||||
|
||||
Users can also choose to seamlessly integrate the cLAN configuration into their base system, effectively allowing the cLAN admin(s) to remotely manage their system.
|
||||
|
||||
### Dashboard for Services
|
||||
|
||||
Once connected, users will have access to a dashboard displaying all the services offered within this specific cLAN. Within this dashboard, users can effortlessly add applications or decide to host services for fellow cLAN members, such as a Nextcloud instance or backup endpoints.
|
||||
|
||||
### Decentralization and Redundancy
|
||||
|
||||
The core idea is to ensure service decentralization without a single point of failure. If one service fails, another machine within the cLAN network can spin up a replacement service. VMs are designed to be lightweight, minimizing resource consumption.
|
||||
|
||||
### Administrative Control
|
||||
|
||||
Within a cLAN, administrators define the configuration file and have control over who becomes part of the network.
|
||||
|
||||
Go to [Technical Overview](@/docs/overview/index.md) to see more indepth details of our work.
|
BIN
static/dark-favicon/128x128.png
Normal file
After Width: | Height: | Size: 4.1 KiB |
BIN
static/dark-favicon/16x16.png
Normal file
After Width: | Height: | Size: 360 B |
BIN
static/dark-favicon/32x32.png
Normal file
After Width: | Height: | Size: 815 B |
BIN
static/dark-favicon/64x64.png
Normal file
After Width: | Height: | Size: 1.9 KiB |
BIN
static/logo/clan-dark.png
Normal file
After Width: | Height: | Size: 108 KiB |
BIN
static/logo/clan-white.png
Normal file
After Width: | Height: | Size: 106 KiB |
Before Width: | Height: | Size: 276 KiB After Width: | Height: | Size: 276 KiB |
BIN
static/white-favicon/128x128.png
Normal file
After Width: | Height: | Size: 3.1 KiB |
BIN
static/white-favicon/16x16.png
Normal file
After Width: | Height: | Size: 375 B |
BIN
static/white-favicon/32x32.png
Normal file
After Width: | Height: | Size: 717 B |
BIN
static/white-favicon/64x64.png
Normal file
After Width: | Height: | Size: 1.5 KiB |
264
templates/macros/head.html
Normal file
|
@ -0,0 +1,264 @@
|
|||
{% macro resource() %}
|
||||
<link rel="preload" as="font" href="{{ get_url(path="fonts/vendor/jost/jost-v4-latin-regular.woff2") | safe }}" type="font/woff2" crossorigin>
|
||||
<link rel="preload" as="font" href="{{ get_url(path="fonts/vendor/jost/jost-v4-latin-700.woff2") | safe }}" type="font/woff2" crossorigin>
|
||||
{% endmacro %}
|
||||
|
||||
|
||||
{% macro stylesheet() %}
|
||||
<link rel="stylesheet" href="{{ get_url(path="main.css") | safe }}">
|
||||
{% endmacro %}
|
||||
|
||||
|
||||
{% macro favicons() %}
|
||||
<meta name="theme-color" content="{{ config.extra.theme_color | default(value="#fff") }}">
|
||||
<link class="favicon" rel="apple-touch-icon" sizes="180x180" href="{{ get_url(path="dark-favicon/128x128.png") | safe }}">
|
||||
<link class="favicon" rel="icon" type="image/png" sizes="32x32" href="{{ get_url(path="dark-favicon/32x32.png") | safe }}">
|
||||
<link class="favicon" rel="icon" type="image/png" sizes="16x16" href="{{ get_url(path="dark-favicon/16x16.png") | safe }}">
|
||||
{% if not config.extra.is_netlify %}
|
||||
<link rel="manifest" href="{{ get_url(path="site.webmanifest") | safe }}" crossorigin>
|
||||
{% endif %}
|
||||
{% endmacro %}
|
||||
|
||||
|
||||
{# type: website or article, generally setting article for blog articles #}
|
||||
{# page_images: using for the blog single template page #}
|
||||
{# page_section: the blog single template page have to pass the parameter #}
|
||||
{# is_404: using for the 404.html template #}
|
||||
{% macro seo(
|
||||
title="",
|
||||
title_addition="",
|
||||
description="",
|
||||
type="website",
|
||||
is_home=false,
|
||||
is_404=false,
|
||||
is_page=false,
|
||||
page_images="",
|
||||
page_section="",
|
||||
created_time="2021-05-01T08:08:00+08:00",
|
||||
updated_time="2021-05-01T08:08:08+08:00"
|
||||
)
|
||||
%}
|
||||
|
||||
{% if is_404 %}
|
||||
<meta name="robots" content="noindex, follow">
|
||||
{% else %}
|
||||
<meta name="robots" content="index, follow">
|
||||
<meta name="googlebot" content="index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1">
|
||||
<meta name="bingbot" content="index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1">
|
||||
{% endif %}
|
||||
{% if current_url %}
|
||||
{% set page_url = current_url %}
|
||||
{% else %}
|
||||
{% set page_url = get_url(path="404.html") %}
|
||||
{% endif %}
|
||||
{% if current_path %}
|
||||
{% set page_path = current_path %}
|
||||
{% else %}
|
||||
{% set page_path = "/404.html" %}
|
||||
{% endif %}
|
||||
<title>{{ title ~ title_addition }}</title>
|
||||
<meta name="description" content="{{ description }}">
|
||||
<link rel="canonical" href="{{ page_url | safe }}">
|
||||
|
||||
{% if config.extra.open.enable %}
|
||||
<meta name="twitter:card" content="summary_large_image">
|
||||
{% if page.extra.images %}
|
||||
{% for image in page.extra.images %}
|
||||
<meta property="twitter:image" content="{{ get_url(path=image) | safe }}">
|
||||
{% endfor %}
|
||||
{% elif section.extra.images %}
|
||||
{% for image in section.extra.images %}
|
||||
<meta property="twitter:image" content="{{ get_url(path=image) | safe }}">
|
||||
{% endfor %}
|
||||
{% elif config.extra.open.image %}
|
||||
<meta name="twitter:image" content="{{ config.base_url | safe }}/{{ config.extra.open.image }}">
|
||||
{% endif %}
|
||||
<meta name="twitter:title" content="{{ title }}">
|
||||
<meta name="twitter:description" content="{{ description }}">
|
||||
<meta name="twitter:site" content="@{{ config.extra.open.twitter_site }}">
|
||||
<meta name="twitter:creator" content="@{{ config.extra.open.twitter_creator }}">
|
||||
|
||||
<meta property="og:title" content="{{ title }}">
|
||||
<meta property="og:description" content="{{ description }}">
|
||||
<meta property="og:type" content="{{ type }}">
|
||||
<meta property="og:url" content="{{ page_url | safe }}">
|
||||
|
||||
{% if page.extra.images %}
|
||||
{% for image in page.extra.images %}
|
||||
<meta property="og:image" content="{{ get_url(path=image) | safe }}">
|
||||
{% endfor %}
|
||||
{% elif section.extra.images %}
|
||||
{% for image in section.extra.images %}
|
||||
<meta property="og:image" content="{{ get_url(path=image) | safe }}">
|
||||
{% endfor %}
|
||||
{% elif config.extra.open.image %}
|
||||
<meta property="og:image" content="{{ config.base_url | safe }}/{{ config.extra.open.image }}">
|
||||
{% endif %}
|
||||
|
||||
<meta property="og:updated_time" content="{{ updated_time }}">
|
||||
<meta property="og:site_name" content="{{ title }}">
|
||||
|
||||
{% if config.extra.open.audio %}
|
||||
<meta property="og:audio" content="{{ config.extra.open.audio }}">
|
||||
{% endif %}
|
||||
|
||||
{% if config.extra.open.locale %}
|
||||
<meta property="og:locale" content="{{ config.extra.open.locale }}">
|
||||
{% endif %}
|
||||
|
||||
{% if config.extra.open.videos %}
|
||||
{% for video in config.extra.open.videos %}
|
||||
<meta property="og:video" content="{{ get_url(path=video) }}">
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
<meta property="article:publisher" content="https://www.facebook.com/{{ config.extra.open.facebook_publisher }}">
|
||||
<meta property="article:author" content="https://www.facebook.com/{{ config.extra.open.facebook_author }}">
|
||||
<meta property="og:locale" content="{{ config.extra.open.og_locale }}">
|
||||
{% endif %}
|
||||
|
||||
{% if is_home and config.extra.schema %}
|
||||
{% if config.extra.schema.type == "Organization" %}
|
||||
<script type="application/ld+json">
|
||||
{
|
||||
"@context": "https://schema.org",
|
||||
"@type": "Organization",
|
||||
"url": "{{ page_path | safe }}",
|
||||
"name": "{{ title }}",
|
||||
"logo": "{{ page_path | safe }}{{ config.extra.schema.logo }}",
|
||||
"sameAs": [
|
||||
{% if config.extra.schema.twitter %}"{{ config.extra.schema.twitter | safe }}",{% endif %}
|
||||
{% if config.extra.schema.linked_in %}"{{ config.extra.schema.linked_in | safe }}",{% endif %}
|
||||
{% if config.extra.schema.github %}"{{ config.extra.schema.github | safe }}"{% endif %}
|
||||
]
|
||||
}
|
||||
</script>
|
||||
{% endif %}
|
||||
{% if config.extra.schema.type == "Person" %}
|
||||
<script type="application/ld+json">
|
||||
{
|
||||
"@context": "https://schema.org",
|
||||
"@type": "Person",
|
||||
"url": "{{ page_path | safe }}",
|
||||
"name": "{{ title }}",
|
||||
"sameAs": [
|
||||
{% if config.extra.schema.twitter %}"{{ config.extra.schema.twitter | safe }}",{% endif %}
|
||||
{% if config.extra.schema.linked_in %}"{{ config.extra.schema.linked_in | safe }}",{% endif %}
|
||||
{% if config.extra.schema.github %}"{{ config.extra.schema.github | safe }}"{% endif %}
|
||||
]
|
||||
}
|
||||
</script>
|
||||
{% endif %}
|
||||
{% if config.extra.schema.site_links_search_box %}
|
||||
<script type="application/ld+json">
|
||||
{
|
||||
"@context": "https://schema.org",
|
||||
"@type": "WebSite",
|
||||
"url": "{{ page_path | safe }}",
|
||||
"potentialAction": {
|
||||
"@type": "SearchAction",
|
||||
"target": "{{ page_path | safe }}?q={search_term_string}",
|
||||
"query-input": "required name=search_term_string"
|
||||
}
|
||||
}
|
||||
</script>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
{% if is_page and config.extra.schema.section %}
|
||||
{% if config.extra.schema.section == page_section %}
|
||||
<script type="application/ld+json">
|
||||
{
|
||||
"@context": "https://schema.org",
|
||||
"@type": "Article",
|
||||
"mainEntityOfPage": {
|
||||
"@type": "WebPage",
|
||||
"@id": "{{ page_path | safe }}"
|
||||
},
|
||||
"headline": "{{ title }}",
|
||||
"image": {{ page_images }},
|
||||
"datePublished": "{{ created_time }}",
|
||||
"dateModified": "{{ updated_time }}",
|
||||
"author": {
|
||||
"@type": "{{ config.extra.schema.type }}",
|
||||
"name": "{{ title }}"
|
||||
},
|
||||
"publisher": {
|
||||
"@type": "{{ config.extra.schema.type }}",
|
||||
"name": "{{ title }}",
|
||||
{% if config.extra.schema.type == "Organization" %}
|
||||
"logo": {
|
||||
"@type": "ImageObject",
|
||||
"url": "{{ get_url(path=config.extra.schema.logo) | replace(from=config.base_url, to="") | safe }}"
|
||||
}
|
||||
{% endif %}
|
||||
},
|
||||
"description": "{{ description }}"
|
||||
}
|
||||
</script>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
{% set url_prefix = get_url(path="/") | split(pat="://") | first %}
|
||||
{% set url_main = get_url(path="/") | split(pat="://") | last %}
|
||||
{% set url_item = url_prefix ~ "://" ~ url_main ~ "/" %}
|
||||
<script type="application/ld+json">
|
||||
{
|
||||
"@context": "http://schema.org",
|
||||
"@type": "BreadcrumbList",
|
||||
{% if page_path == "/" %}
|
||||
{
|
||||
"@type": "ListItem",
|
||||
"position": 1 ,
|
||||
"name": "Home",
|
||||
"item": "{{ url_item | safe }}"
|
||||
},
|
||||
{% else %}
|
||||
{% set paths = page_path | trim_start_matches(pat="/") | trim_end_matches(pat="/") | split(pat="/") %}
|
||||
{% for val in paths %}
|
||||
{% set name_array = val | split(pat="-") %}
|
||||
{% set_global str = "" %}
|
||||
{% for val in name_array %}
|
||||
{% set cap_val = val | capitalize %}
|
||||
{% set_global str = str ~ cap_val ~ " " %}
|
||||
{% endfor %}
|
||||
{% set name = str | trim_end_matches(pat=" ") | title %}
|
||||
{% if not index %}
|
||||
{
|
||||
"@type": "ListItem",
|
||||
"position": 1 ,
|
||||
"name": "Home",
|
||||
"item": "{{ url_item | safe }}"
|
||||
},
|
||||
{% set_global index = 2 %}
|
||||
{% set_global url_item = url_item ~ val ~ "/" %}
|
||||
{
|
||||
"@type": "ListItem",
|
||||
"position": {{ index }} ,
|
||||
"name": "{{ name }}",
|
||||
"item": "{{ url_item | safe }}"
|
||||
},
|
||||
{% else %}
|
||||
{% set_global index = index + 1 %}
|
||||
{% set_global url_item = url_item ~ val ~ "/" %}
|
||||
{
|
||||
"@type": "ListItem",
|
||||
"position": {{ index }} ,
|
||||
"name": "{{ name }}",
|
||||
"item": "{{ url_item | safe }}"
|
||||
},
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
}
|
||||
</script>
|
||||
|
||||
{% if config.extra.ganalytics %}
|
||||
<script async src="https://www.googletagmanager.com/gtag/js?id={{config.extra.ganalytics}}"></script>
|
||||
<script>
|
||||
window.dataLayer = window.dataLayer || [];
|
||||
function gtag(){dataLayer.push(arguments);}
|
||||
gtag('js', new Date());
|
||||
gtag('config', '{{config.extra.ganalytics}}');
|
||||
</script>
|
||||
{% endif %}
|
||||
{% endmacro %}
|
|
@ -1,14 +1,80 @@
|
|||
// Set darkmode
|
||||
document.getElementById('mode').addEventListener('click', () => {
|
||||
|
||||
document.body.classList.toggle('dark');
|
||||
localStorage.setItem('theme', document.body.classList.contains('dark') ? 'dark' : 'light');
|
||||
|
||||
let isDarkTheme = document.body.classList.contains('dark');
|
||||
setColorTheme(!isDarkTheme ? "dark" : "light");
|
||||
});
|
||||
|
||||
// enforce local storage setting but also fallback to user-agent preferences
|
||||
if (localStorage.getItem('theme') === 'dark' || (!localStorage.getItem('theme') && window.matchMedia("(prefers-color-scheme: dark)").matches)) {
|
||||
|
||||
document.body.classList.add('dark');
|
||||
|
||||
|
||||
|
||||
let preferDarkTheme = prefersDarkMode();
|
||||
let theme = localStorage.getItem('theme');
|
||||
if (theme !== null) {
|
||||
setColorTheme(theme);
|
||||
} else {
|
||||
setColorTheme(preferDarkTheme ? "dark" : "light");
|
||||
}
|
||||
|
||||
// Get the media query list object for the prefers-color-scheme media feature
|
||||
const colorSchemeQueryList = window.matchMedia("(prefers-color-scheme: dark)");
|
||||
// Add an event listener for the change event
|
||||
colorSchemeQueryList.addEventListener("change", handleColorSchemeChange);
|
||||
|
||||
|
||||
function setColorTheme(theme) {
|
||||
if (theme === "dark") {
|
||||
document.body.classList.add('dark');
|
||||
switchClanLogo("white");
|
||||
localStorage.setItem('theme', 'dark');
|
||||
} else {
|
||||
document.body.classList.remove('dark');
|
||||
switchClanLogo("dark");
|
||||
localStorage.setItem('theme', 'light');
|
||||
}
|
||||
}
|
||||
|
||||
// A function that returns true if the user prefers dark mode, false otherwise
|
||||
function prefersDarkMode() {
|
||||
// Check if the browser supports the prefers-color-scheme media query
|
||||
if (window.matchMedia) {
|
||||
// Get the current value of the media query
|
||||
let colorScheme = window.matchMedia("(prefers-color-scheme: dark)");
|
||||
// Return true if the media query matches, false otherwise
|
||||
return colorScheme.matches;
|
||||
} else {
|
||||
// If the browser does not support the media query, return false by default
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function switchClanLogo(theme) {
|
||||
let favs = document.getElementsByClassName("favicon");
|
||||
for (item of favs) {
|
||||
if (theme === "white")
|
||||
{
|
||||
item.href = item.href.replace("dark-favicon", "white-favicon")
|
||||
} else {
|
||||
item.href = item.href.replace("white-favicon", "dark-favicon")
|
||||
}
|
||||
}
|
||||
let clogos = document.getElementsByClassName("clogo");
|
||||
for (item of clogos) {
|
||||
if (theme === "white")
|
||||
{
|
||||
item.src = item.src.replace("dark", "white")
|
||||
} else {
|
||||
item.src = item.src.replace("white", "dark")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// A function that executes some logic based on the color scheme preference
|
||||
function handleColorSchemeChange(e) {
|
||||
if (e.matches) {
|
||||
// The user prefers dark mode
|
||||
setColorTheme("dark");
|
||||
} else {
|
||||
// The user prefers light mode
|
||||
setColorTheme("light");
|
||||
}
|
||||
}
|
||||
|
|