began background settings AND prepared light/dark-mode
authorPhiTux <redacted>
Mon, 18 Aug 2025 16:34:46 +0000 (18:34 +0200)
committerPhiTux <redacted>
Mon, 18 Aug 2025 16:34:46 +0000 (18:34 +0200)
backend/handlers/users.go
frontend/src/lib/Datepicker.svelte
frontend/src/lib/Sidenav.svelte
frontend/src/lib/helpers.js
frontend/src/routes/(authed)/+layout.svelte
frontend/src/routes/(authed)/write/+page.svelte
frontend/src/routes/+layout.svelte

index cc9845724681f3caad4fefe084c4257d742a045b..4127b1365504b7e36648e314f8bd3eb68246ab4f 100644 (file)
@@ -428,6 +428,10 @@ func GetDefaultSettings() map[string]any {
                "timezone":                   "UTC",
                "useBrowserLanguage":         true,
                "language":                   "en",
+               "darkModeAutoDetect":         true,
+               "useDarkMode":                false,
+               "background":                 "gradient",
+               "monochromeBackgroundColor":  "#ececec",
        }
 }
 
index d205d5ad160928beb955ec46f9f795a9105165f3..09df6922dba201033e9f92e1a122281063d9421f 100644 (file)
        const weekDays = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'];
 </script>
 
-<div class="datepicker">
+<div class="datepicker glassLight">
        <div class="datepicker-header">
                <button type="button" class="btn btnLeftRight" onclick={() => changeMonth(-1)}>&lt;</button>
                <div class="date-selectors">
        .datepicker {
                display: inline-block;
                font-family: Arial, sans-serif;
-               border: 1px solid #ececec77;
+               /* border: 1px solid #ececec77; */
                border-radius: 8px;
                /* overflow: hidden; */
                /* width: 300px; */
                box-sizing: border-box;
-               backdrop-filter: blur(8px) saturate(150%);
-               background-color: rgba(219, 219, 219, 0.45);
+               /* backdrop-filter: blur(8px) saturate(150%);
+               background-color: rgba(219, 219, 219, 0.45); */
        }
        .datepicker-header {
                display: flex;
index 8607244de6a8764aa5599df50265dd7bb8f29ae6..133f33b0997cfa67603619d8feaf839d341affc9 100644 (file)
        <div class="search d-flex flex-column">
                <form onsubmit={searchForString} class="input-group">
                        <button
-                               class="btnSearchPopover btn btn-outline-secondary"
+                               class="btnSearchPopover btn btn-outline-secondary glassLight"
                                data-bs-toggle="popover"
                                data-bs-title="Suche"
                                data-bs-content="Du kannst nach <b>Text</b>, <b>Dateinamen</b> und <b>Tags</b> suchen.<br>
                                                setTimeout(() => (showTagDropdown = false), 150);
                                        }}
                                />
-                               <button class="btn btn-outline-secondary" type="submit" id="search-button">
+                               <button class="btn btn-outline-secondary glassLight" type="submit" id="search-button">
                                        {#if $isSearching}
                                                <span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span>
                                        {:else}
                        {/if}
                </form>
                {#if showTagDropdown}
-                       <div class="searchTagDropdown">
+                       <div class="searchTagDropdown glass">
                                {#if filteredTags.length === 0}
                                        <em style="padding: 0.2rem;">Kein Tag gefunden...</em>
                                {:else}
                                {/if}
                        </div>
                {/if}
-               <div class="list-group flex-grow-1 mb-2">
+               <div class="list-group flex-grow-1 mb-2 glassLight">
                        {#if $searchResults.length > 0}
                                {#each $searchResults as result}
                                        <button
 
        .searchTagDropdown {
                position: absolute;
-               background-color: white;
+               /* background-color: white; */
                box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
                z-index: 1000;
                left: 60px;
                margin-top: 38px;
                max-height: 200px;
-               overflow-y: scroll;
+               overflow-y: auto;
                overflow-x: hidden;
                display: flex;
                flex-direction: column;
                border-top-right-radius: 0;
                overflow-y: auto;
                min-height: 250px;
-               backdrop-filter: blur(8px) saturate(150%);
+               /* backdrop-filter: blur(8px) saturate(150%);
                background-color: rgba(219, 219, 219, 0.45);
-               border: 1px solid #ececec77;
+               border: 1px solid #ececec77; */
        }
 
        .input-group {
index a453295e8f5235a720fdf916728e04a3fec48e3f..62136089af13957d5852fd277fef8b7236cd6811 100644 (file)
@@ -21,11 +21,42 @@ function sameDate(date1, date2) {
        );
 }
 
+function generateNeonMesh(dark) {
+       /* const baseColors = ['#ff00ff', '#00ffff', '#ffea00', '#ff0080', '#00ff80', '#ff4500']; */
+       const baseColors = ["#ff00ff", "#00ffff", "#ffea00", "#ff0080", "#00ff80", "#ff4500",
+    "#ff1493", "#00ffcc", "#ff3333", "#66ff66", "#3399ff", "#ffcc00",
+    "#ff6666", "#00ccff", "#cc33ff", "#33ffcc", "#ffff99", "#ff99ff",
+    "#99ff99", "#66ccff", "#ff9900", "#ff0066", "#66ffcc", "#ff33cc",
+    "#99ccff"]
+       const numGradients = Math.floor(Math.random() * 3) + 3; // 3–5 Radial Gradients
+       let gradients = [];
+
+       for (let i = 0; i < numGradients; i++) {
+               const baseColor = baseColors[Math.floor(Math.random() * baseColors.length)];
+               const alpha = Math.random() * 0.4 + 0.1; // random alpha between 0.1 and 0.5
+
+               // convert hex color to rgba
+               const hex = baseColor.substring(1);
+               const r = parseInt(hex.substring(0, 2), 16);
+               const g = parseInt(hex.substring(2, 4), 16);
+               const b = parseInt(hex.substring(4, 6), 16);
+               const color = `rgba(${r}, ${g}, ${b}, ${alpha})`;
+
+               const x = Math.floor(Math.random() * 100);
+               const y = Math.floor(Math.random() * 100);
+               const size = Math.floor(Math.random() * 30) + 30; // 30–60%
+               gradients.push(`radial-gradient(circle at ${x}% ${y}%, ${color}, transparent ${size}%)`);
+       }
+
+       document.querySelector('.background').style.background = dark ? gradients.join(', ') + ', #111' : gradients;
+       document.querySelector('.background').style.backgroundBlendMode = 'screen';
+}
+
 function loadFlagEmoji(language) {
        return json[language] || '';
 }
 
-export { formatBytes, sameDate, loadFlagEmoji };
+export { formatBytes, sameDate, loadFlagEmoji, generateNeonMesh };
 
 export let alwaysShowSidenav = writable(true);
 
index 5896330c0bc7e0093223b02a1ea05373b632dffd..b2c21fc1ef01bc68f07cfacab582b0a1b782aa0f 100644 (file)
@@ -14,7 +14,7 @@
        import { API_URL } from '$lib/APIurl.js';
        import { tags } from '$lib/tagStore.js';
        import TagModal from '$lib/TagModal.svelte';
-       import { alwaysShowSidenav, loadFlagEmoji } from '$lib/helpers.js';
+       import { alwaysShowSidenav, generateNeonMesh, loadFlagEmoji } from '$lib/helpers.js';
        import { templates } from '$lib/templateStore';
        import {
                faRightFromBracket,
@@ -24,7 +24,9 @@
                faTriangleExclamation,
                faTrash,
                faCopy,
-               faCheck
+               faCheck,
+               faSun,
+               faMoon
        } from '@fortawesome/free-solid-svg-icons';
        import Tag from '$lib/Tag.svelte';
        import SelectTimezone from '$lib/SelectTimezone.svelte';
                        .get(API_URL + '/users/getUserSettings')
                        .then((response) => {
                                $settings = response.data;
+                               $tempSettings = JSON.parse(JSON.stringify($settings));
                                aLookBackYears = $settings.aLookBackYears.toString();
                                updateLanguage();
+
+                               // set background
+                               setBackground();
                        })
                        .catch((error) => {
                                console.error(error);
        );
 
        function updateLanguage() {
-               console.log('updateLanguage()');
                if ($settings.useBrowserLanguage) {
                        let browserLanguage = tolgeesMatchForBrowserLanguage();
                        $tolgee.changeLanguage(
                return '';
        }
 
+       function setBackground() {
+               if ($settings.background === 'monochrome') {
+                       document.querySelector('.background').style.background = '';
+                       document.body.style.backgroundColor = $settings.monochromeBackgroundColor;
+               } else if ($settings.background === 'gradient') {
+                       document.body.style.backgroundColor = '';
+                       generateNeonMesh();
+               }
+       }
+
        let isSaving = $state(false);
        function saveUserSettings() {
                if (isSaving) return;
                                        // update language
                                        updateLanguage();
 
+                                       // set background
+                                       setBackground();
+
                                        // show toast
                                        const toast = new bootstrap.Toast(document.getElementById('toastSuccessSaveSettings'));
                                        toast.show();
 </script>
 
 <div class="d-flex flex-column h-100">
-       <nav class="navbar navbar-expand-lg bg-body-tertiary">
+       <nav class="navbar navbar-expand-lg glass">
                <div class="row w-100">
                        <div class="col-lg-4 col-sm-5 col d-flex flex-row justify-content-start align-items-center">
                                {#if !$alwaysShowSidenav}
                class="modal-dialog modal-dialog-scrollable modal-dialog-centered modal-xl modal-fullscreen-sm-down"
        >
                <!--  -->
-               <div class="modal-content shadow-lg">
+               <div class="modal-content shadow-lg glass">
                        <div class="modal-header">
                                <h1>{$t('settings.title')}</h1>
                                <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
                                                        <div id="appearance">
                                                                <h3 class="text-primary">🎨 Aussehen</h3>
                                                                <div id="lightdark">
-                                                                       <h5>Light/Dark-Mode</h5>
-                                                                       Bla<br />
-                                                                       blub <br />
-                                                                       bla <br />
-                                                                       blub <br />
+                                                                       {#if $tempSettings.darkModeAutoDetect !== $settings.darkModeAutoDetect || $tempSettings.useDarkMode !== $settings.useDarkMode}
+                                                                               <div class="unsaved-changes" transition:slide></div>
+                                                                       {/if}
+                                                                       <h5>Light-/Dark-Mode</h5>
+                                                                       <div class="form-check mt-2">
+                                                                               <input
+                                                                                       class="form-check-input"
+                                                                                       type="radio"
+                                                                                       name="darkMode"
+                                                                                       id="darkModeAutoTrue"
+                                                                                       value={true}
+                                                                                       bind:group={$tempSettings.darkModeAutoDetect}
+                                                                               />
+                                                                               <label class="form-check-label" for="darkModeAutoTrue">
+                                                                                       Light-/Dark-Mode automatisch erkennen (aktuell:
+                                                                                       {#if window.matchMedia('(prefers-color-scheme: dark)').matches}
+                                                                                               <b>Dark <Fa icon={faMoon} /></b>
+                                                                                       {:else}
+                                                                                               <b>Light <Fa icon={faSun} /></b>
+                                                                                       {/if})
+                                                                               </label>
+                                                                       </div>
+                                                                       <div class="form-check mt-2">
+                                                                               <input
+                                                                                       class="form-check-input"
+                                                                                       type="radio"
+                                                                                       name="darkMode"
+                                                                                       id="darkModeAutoFalse"
+                                                                                       value={false}
+                                                                                       bind:group={$tempSettings.darkModeAutoDetect}
+                                                                               />
+                                                                               <label class="form-check-label" for="darkModeAutoFalse">
+                                                                                       Light-/Dark-Mode manuell festlegen
+                                                                               </label>
+                                                                               {#if $tempSettings.darkModeAutoDetect === false}
+                                                                                       <div class="form-check form-switch d-flex flex-row ps-0" transition:slide>
+                                                                                               <label class="form-check-label me-5" for="darkModeSwitch">
+                                                                                                       <Fa icon={faSun} />
+                                                                                               </label>
+                                                                                               <input
+                                                                                                       class="form-check-input"
+                                                                                                       bind:checked={$tempSettings.useDarkMode}
+                                                                                                       type="checkbox"
+                                                                                                       role="switch"
+                                                                                                       id="darkModeSwitch"
+                                                                                               />
+                                                                                               <label class="form-check-label ms-2" for="darkModeSwitch">
+                                                                                                       <Fa icon={faMoon} />
+                                                                                               </label>
+                                                                                       </div>
+                                                                               {/if}
+                                                                       </div>
                                                                </div>
                                                                <div id="background">
+                                                                       {#if $tempSettings.background !== $settings.background || $tempSettings.monochromeBackgroundColor !== $settings.monochromeBackgroundColor}
+                                                                               <div class="unsaved-changes" transition:slide></div>
+                                                                       {/if}
+
                                                                        <h5>Hintergrund</h5>
-                                                                       <div class="d-flex flex-row justify-content-start">
-                                                                               <label for="trianglifyOpacity" class="form-label"
-                                                                                       >Transparenz der Dreiecke</label
-                                                                               >
+                                                                       <div class="form-check mt-2">
                                                                                <input
-                                                                                       bind:value={$trianglifyOpacity}
-                                                                                       type="range"
-                                                                                       class="mx-3 form-range"
-                                                                                       id="trianglifyOpacity"
-                                                                                       min="0"
-                                                                                       max="1"
-                                                                                       step="0.01"
+                                                                                       class="form-check-input"
+                                                                                       type="radio"
+                                                                                       name="background"
+                                                                                       id="background_gradient"
+                                                                                       value={'gradient'}
+                                                                                       bind:group={$tempSettings.background}
                                                                                />
+                                                                               <label class="form-check-label" for="background_gradient">
+                                                                                       Farbverlauf (wird bei jedem Seitenaufruf neu generiert)
+                                                                               </label>
+                                                                       </div>
+                                                                       <div class="form-check mt-2">
                                                                                <input
-                                                                                       bind:value={$trianglifyOpacity}
-                                                                                       type="number"
-                                                                                       id="trianglifyOpacityNumber"
+                                                                                       class="form-check-input"
+                                                                                       type="radio"
+                                                                                       name="background"
+                                                                                       id="background_monochrome"
+                                                                                       value={'monochrome'}
+                                                                                       bind:group={$tempSettings.background}
                                                                                />
+                                                                               <label class="form-check-label" for="background_monochrome"> Einfarbig </label>
+                                                                               {#if $tempSettings.background === 'monochrome'}
+                                                                                       <input
+                                                                                               transition:slide
+                                                                                               class="form-control form-control-color"
+                                                                                               bind:value={$tempSettings.monochromeBackgroundColor}
+                                                                                               type="color"
+                                                                                       />
+                                                                               {/if}
                                                                        </div>
                                                                </div>
                                                        </div>
        }
 
        .modal-content {
-               backdrop-filter: blur(10px) saturate(150%);
+               /* backdrop-filter: blur(10px) saturate(150%);
+               background-color: rgba(208, 208, 208, 0.61); */
+
                /* background-color: rgba(43, 56, 78, 0.75); */
-               background-color: rgba(208, 208, 208, 0.61);
                /* color: rgb(22, 22, 22); */
        }
 
index 4618e59e58ab89e2eb197917447b984b1fe03fc5..2a2e58d3fc6775b2a1d6319a71f7ea09d7bcb387 100644 (file)
                <!-- Center -->
                <div class="d-flex flex-column pt-4 px-4 flex-grow-1" id="middle">
                        <!-- Input-Area -->
-                       <div class="d-flex flex-row textAreaHeader">
+                       <div class="d-flex flex-row textAreaHeader glassLight">
                                <div class="flex-fill textAreaDate">
                                        {new Date(
                                                Date.UTC($selectedDate.year, $selectedDate.month - 1, $selectedDate.day)
                </div>
 
                <div id="right" class="d-flex flex-column">
-                       <div class="tags">
+                       <div class="tags glass">
                                <div class="d-flex flex-row justify-content-between">
                                        <div class="d-flex flex-row">
                                                <h3>Tags</h3>
                                </div>
                        </div>
 
-                       <div class="files d-flex flex-column">
+                       <div class="files d-flex flex-column glass">
                                <button
                                        class="btn btn-secondary {filesOfDay?.length > 0 ? 'mb-2' : ''}"
                                        id="uploadBtn"
                z-index: 10;
                padding: 0.5rem;
                margin-bottom: 2rem;
-               backdrop-filter: blur(8px) saturate(150%);
+               /* backdrop-filter: blur(8px) saturate(150%);
                background-color: rgba(219, 219, 219, 0.45);
-               border: 1px solid #ececec77;
+               border: 1px solid #ececec77; */
                border-radius: 10px;
+               /* color: #ececec; */
        }
 
        .loadImageBtn {
                margin-bottom: 1rem;
                border-radius: 10px;
                padding: 1rem;
-               backdrop-filter: blur(8px) saturate(150%);
+               /* backdrop-filter: blur(8px) saturate(150%);
                background-color: rgba(219, 219, 219, 0.45);
-               border: 1px solid #ececec77;
+               border: 1px solid #ececec77; */
        }
 
        :global(#uploadIcon) {
        }
 
        .textAreaHeader {
-               border-left: 1px solid #ccc;
-               border-top: 1px solid #ccc;
-               border-right: 1px solid #ccc;
+               border-left: 1px solid #6a6a6a;
+               border-top: 1px solid #6a6a6a;
+               border-right: 1px solid #6a6a6a;
                border-top-left-radius: 5px;
                border-top-right-radius: 5px;
        }
        .textAreaDate,
        .textAreaWrittenAt,
        .textAreaHistory {
-               border-right: 1px solid #ccc;
+               border-right: 1px solid #6a6a6a;
                padding: 0.25em;
        }
 
index f0d2e024887f18db95bbde063704b4b3870f4504..d8a3b50376fa2d0247a362d1ab47591240535c88 100644 (file)
@@ -1,25 +1,16 @@
 <script>
        import { blur } from 'svelte/transition';
        import axios from 'axios';
-       //import { dev } from '$app/environment';
        import { goto } from '$app/navigation';
        import { onMount } from 'svelte';
        import '../scss/styles.scss';
-       import { useTrianglify, trianglifyOpacity } from '$lib/settingsStore.js';
+       import { trianglifyOpacity } from '$lib/settingsStore.js';
        import { page } from '$app/state';
        import { API_URL } from '$lib/APIurl.js';
-       import trianglify from 'trianglify';
-       import { alwaysShowSidenav } from '$lib/helpers.js';
+       import { alwaysShowSidenav, generateNeonMesh } from '$lib/helpers.js';
        import * as bootstrap from 'bootstrap';
-       import {
-               TolgeeProvider,
-               Tolgee,
-               DevTools,
-               LanguageDetector,
-               LanguageStorage
-       } from '@tolgee/svelte';
+       import { TolgeeProvider, Tolgee, DevTools, LanguageStorage } from '@tolgee/svelte';
        import { FormatIcu } from '@tolgee/format-icu';
-       import { use } from 'marked';
 
        const tolgee = Tolgee()
                .use(DevTools())
                }
        );
 
-       function createBackground() {
-               if ($useTrianglify) {
-                       //remove old canvas
-                       const oldCanvas = document.querySelector('canvas');
-                       if (oldCanvas) {
-                               oldCanvas.remove();
-                       }
-
-                       //xColors: ['#F3F3F3', '#FEFEFE', '#E5E5E5'],
-                       const canvas = trianglify({
-                               width: window.innerWidth,
-                               height: window.innerHeight,
-                               xColors: ['#FA2'],
-                               fill: false,
-                               strokeWidth: 1,
-                               cellSize: 100
-                       });
-
-                       document.body.appendChild(canvas.toCanvas());
-                       document.querySelector('canvas').style =
-                               'position: fixed; top: 0; left: 0; z-index: -1; opacity: 0.4; width: 100%; height: 100%; background-color: #eaeaea;';
-               }
-       }
-
        $effect(() => {
                if ($trianglifyOpacity) {
                        if (document.querySelector('canvas')) {
        });
 
        onMount(() => {
-               createBackground();
                calculateResize();
+
+               // if on login page, generate neon mesh
+               if (page.url.pathname === '/login') {
+                       generateNeonMesh();
+               }
        });
 
        let routeToFromLoginKey = $derived(page.url.pathname === '/login');
 </script>
 
-<TolgeeProvider {tolgee}>
-       <main class="d-flex flex-column">
-               <div class="wrapper h-100">
+<main class="d-flex flex-column background" use:focus={generateNeonMesh}>
+       <TolgeeProvider {tolgee}>
+               <div class="wrapper h-100" transition:blur={{ duration: inDuration * 2 }}>
                        {#key routeToFromLoginKey}
                                <div
                                        class="transition-wrapper h-100"
                                </div>
                        </div>
                </div>
-       </main>
-</TolgeeProvider>
+       </TolgeeProvider>
+</main>
 
 <style>
        main {
                height: 100vh;
-
-               /* background-image: linear-gradient(#ff8a00, #e52e71); */
-               /* background-image: linear-gradient(to right, violet, darkred, purple); */
-               /* background: linear-gradient(40deg, #38bdf8, #fb7185, #84cc16); */
        }
 
        .wrapper {
                width: 100%;
                height: 100%;
        }
+
+       :global(.glass) {
+               backdrop-filter: blur(12px) saturate(130%);
+               /* background-color: rgba(219, 219, 219, 0.45); */
+               background-color: rgba(83, 83, 83, 0.73);
+               border: 1px solid #62626278;
+               color: #ececec;
+       }
+
+       :global(.glassLight) {
+               backdrop-filter: blur(8px) saturate(130%);
+               /* background-color: rgba(219, 219, 219, 0.45); */
+               background-color: rgba(83, 83, 83, 0.445);
+               border: 1px solid #62626278;
+               color: #ececec;
+       }
 </style>
git clone https://git.99rst.org/PROJECT