huge css update (light/dark)
authorPhiTux <redacted>
Sat, 20 Sep 2025 23:00:56 +0000 (01:00 +0200)
committerPhiTux <redacted>
Sat, 20 Sep 2025 23:00:56 +0000 (01:00 +0200)
13 files changed:
frontend/src/lib/ALookBack.svelte
frontend/src/lib/Datepicker.svelte
frontend/src/lib/EmojiMart.svelte
frontend/src/lib/Sidenav.svelte
frontend/src/lib/TagModal.svelte
frontend/src/lib/settings/Admin.svelte
frontend/src/lib/settings/Statistics.svelte
frontend/src/routes/(authed)/+layout.svelte
frontend/src/routes/(authed)/read/+page.svelte
frontend/src/routes/(authed)/write/+page.svelte
frontend/src/routes/+layout.svelte
frontend/src/routes/login/+page.svelte
frontend/src/routes/reauth/+page.svelte

index c225228d75976e956a1ed9c03aed09bf702d2872..1c30cb8c30e418f57479e563003c6d7adc5f54f1 100644 (file)
@@ -2,6 +2,7 @@
        import { marked } from 'marked';
        import { selectedDate } from './calendarStore';
        import { getTranslate } from '@tolgee/svelte';
+       import { onMount } from 'svelte';
 
        const { t } = getTranslate();
 
 
        let { log } = $props();
 
-       let preview;
-       let content;
        let modal;
-       let isModalOpen = $state(false);
+       let modalInstance;
+
+       onMount(() => {
+               // Import Bootstrap Modal
+               import('bootstrap').then((bootstrap) => {
+                       modalInstance = new bootstrap.Modal(modal, {
+                               backdrop: true,
+                               keyboard: true
+                       });
+               });
+       });
 
        function openModal() {
-               if (!preview || !modal || !content) return;
-
-               const previewRect = preview.getBoundingClientRect();
-               const targetWidth = Math.min(window.innerWidth * 0.8, 600); // Target width
-
-               // Initial state for the animation
-               // Position and scale to match the button
-               content.style.left = `${previewRect.left}px`;
-               content.style.top = `${previewRect.top}px`;
-               content.style.width = `${previewRect.width}px`;
-               content.style.height = `${previewRect.height}px`;
-               content.style.transform = 'scale(1)'; // Start at button's scale
-               content.style.opacity = '0';
-
-               modal.style.display = 'flex';
-
-               void content.offsetWidth;
-
-               // Target state for the animation
-               // Calculate scale factor to reach targetWidth from previewRect.width
-               const scaleX = targetWidth / previewRect.width;
-
-               const targetLeft = (window.innerWidth - targetWidth) / 2;
-               const targetTop = window.innerHeight * 0.2;
-
-               content.style.left = `${targetLeft}px`; // Position for final state
-               content.style.top = `${targetTop}px`; // Position for final state
-               content.style.width = `${targetWidth}px`;
-               // Height will be 'auto' or controlled by max-height in CSS
-               content.style.height = 'auto'; // Let CSS max-height handle this
-               content.style.transform = 'scale(1)'; // End at normal scale, but at new position/size
-               content.style.opacity = '1';
-
-               isModalOpen = true;
-               document.body.style.overflow = 'hidden';
-       }
-
-       function closeModal() {
-               if (!preview || !modal || !content) return;
-
-               const previewRect = preview.getBoundingClientRect();
-
-               content.style.left = `${previewRect.left}px`;
-               content.style.top = `${previewRect.top}px`;
-               content.style.width = `${previewRect.width}px`;
-               content.style.height = `${previewRect.height}px`;
-               content.style.transform = 'scale(1)';
-               content.style.opacity = '0';
-
-               setTimeout(() => {
-                       if (!isModalOpen) {
-                               modal.style.display = 'none';
-                               document.body.style.overflow = '';
-                       }
-               }, 400);
-
-               isModalOpen = false;
+               if (modalInstance) {
+                       modalInstance.show();
+               }
        }
 
-       function handleKeydown(event) {
-               if (event.key === 'Escape' && isModalOpen) {
-                       closeModal();
+       function goToDate() {
+               $selectedDate = { year: log.year, month: log.month, day: log.day };
+               if (modalInstance) {
+                       modalInstance.hide();
                }
        }
 </script>
 
-<!-- svelte-ignore a11y_click_events_have_key_events -->
-<svelte:window on:keydown={handleKeydown} />
-
 <!-- svelte-ignore a11y_consider_explicit_label -->
-<button
-       bind:this={preview}
-       onclick={openModal}
-       id="zoomButton"
-       class="btn"
-       style="width: 200px; height: 100px;"
->
+<button onclick={openModal} id="zoomButton" class="btn" style="width: 200px; height: 100px;">
        <div class="d-flex flex-row h-100">
                <div class="left d-flex flex-column justify-content-evenly px-1">
                        <div><b>{log?.year}</b></div>
-                       <div><em><b>{log?.years_old}</b> J</em></div>
+                       <div><em><b>{log?.years_old}</b> {$t('aLookBack.Year_one_letter')}</em></div>
                </div>
                <div class="html-preview p-1">
                        {@html marked.parse(log?.text)}
        </div>
 </button>
 
-<!-- svelte-ignore a11y_no_static_element_interactions -->
-<!-- svelte-ignore a11y_click_events_have_key_events -->
+<!-- Standard Bootstrap Modal -->
 <div
        bind:this={modal}
-       id="zoomModal"
-       class="zoom-modal"
-       onclick={(event) => {
-               if (event.target === modal) {
-                       closeModal();
-               }
-       }}
+       class="modal fade"
+       tabindex="-1"
+       aria-labelledby="alookbackModalLabel"
+       aria-hidden="true"
 >
-       <div bind:this={content} class="zoom-content">
-               <div class="zoom-content-header">
-                       <span
-                               >{$t('aLookBack.header_X_years_ago', { years_old: log?.years_old })} | {new Date(
-                                       log?.year,
-                                       log?.month - 1,
-                                       log?.day
-                               ).toLocaleDateString('locale', {
-                                       weekday: 'long',
-                                       day: '2-digit',
-                                       month: '2-digit',
-                                       year: 'numeric'
-                               })}</span
-                       >
-                       <button
-                               type="button"
-                               class="btn-close btn-close-white"
-                               aria-label="Close"
-                               onclick={closeModal}
-                       ></button>
+       <div class="modal-dialog modal-dialog-centered modal-dialog-scrollable modal-lg">
+               <div class="modal-content">
+                       <div class="modal-header">
+                               <h5 class="modal-title" id="alookbackModalLabel">
+                                       {$t('aLookBack.header_X_years_ago', { years_old: log?.years_old })} | {new Date(
+                                               log?.year,
+                                               log?.month - 1,
+                                               log?.day
+                                       ).toLocaleDateString('locale', {
+                                               weekday: 'long',
+                                               day: '2-digit',
+                                               month: '2-digit',
+                                               year: 'numeric'
+                                       })}
+                               </h5>
+                               <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
+                       </div>
+                       <div class="modal-body">
+                               {@html marked.parse(log?.text)}
+                       </div>
+                       <div class="modal-footer">
+                               <button onclick={goToDate} class="btn btn-primary">
+                                       {$t('aLookBack.open')}
+                               </button>
+                       </div>
                </div>
-               <div class="modal-text">{@html marked.parse(log?.text)}</div>
-               <button
-                       onclick={() => {
-                               $selectedDate = { year: log.year, month: log.month, day: log.day };
-                               closeModal();
-                       }}
-                       class="btn btn-primary"
-                       id="closeZoom">{$t('aLookBack.open')}</button
-               >
        </div>
 </div>
 
 <style>
        .left {
-               background-color: rgba(180, 180, 180, 0.45);
                border-top-left-radius: 0.375rem;
                border-bottom-left-radius: 0.375rem;
        }
 
-       .modal-text {
-               margin-left: 20px;
-               margin-right: 20px;
-               margin-top: 20px;
-       }
-
-       #closeZoom {
-               margin-left: 20px;
-               margin-bottom: 20px;
-       }
-
-       .zoom-content-header {
-               display: flex;
-               justify-content: space-between;
-               align-items: center;
-               padding: 8px 15px;
-               background-color: #343a40;
-               color: white;
-               border-bottom: 1px solid #495057;
-               flex-shrink: 0;
-               border-top-left-radius: 8px;
-               border-top-right-radius: 8px;
-       }
-
        .html-preview :global(h1),
        .html-preview :global(h2),
        .html-preview :global(h3),
                left: 0;
                right: 0;
                height: 40px;
-               background: linear-gradient(to bottom, transparent, rgba(219, 219, 219, 0.45) 80%);
                pointer-events: none;
        }
 
+       :global(body[data-bs-theme='dark']) .html-preview::after {
+               background: linear-gradient(to bottom, transparent, rgba(80, 80, 80, 0.45) 80%);
+       }
+
+       :global(body[data-bs-theme='light']) .html-preview::after {
+               background: linear-gradient(to bottom, transparent, rgba(219, 219, 219, 0.45) 80%);
+       }
+
+       :global(body[data-bs-theme='dark']) #zoomButton {
+               background-color: rgba(138, 138, 138, 0.45);
+               color: #ececec;
+       }
+
+       :global(body[data-bs-theme='dark']) .left {
+               background-color: rgba(141, 141, 141, 0.45);
+       }
+
+       :global(body[data-bs-theme='light']) .left {
+               background-color: rgba(180, 180, 180, 0.45);
+       }
+
        #zoomButton {
                background-color: rgba(219, 219, 219, 0.45);
                transition: 0.3s ease;
                background-color: rgba(219, 219, 219, 0.65);
        }
 
-       .zoom-modal {
-               position: fixed;
-               top: 0;
-               left: 0;
-               width: 100vw;
-               height: 100vh;
-               display: none;
-               justify-content: center;
-               align-items: center;
-               z-index: 1050;
-               background-color: rgba(0, 0, 0, 0.5);
-               overflow: hidden;
+       .modal-header {
+               border-bottom: none;
        }
 
-       .zoom-content {
-               position: absolute;
-               background: white;
-               color: black;
-               border-radius: 8px;
-               box-shadow: 0 0 20px rgba(0, 0, 0, 0.4);
-               /* transform-origin: top left; */ /* Let's try center for smoother scaling if we use scale for size */
-               transition:
-                       left 0.4s cubic-bezier(0.25, 0.8, 0.25, 1),
-                       top 0.4s cubic-bezier(0.25, 0.8, 0.25, 1),
-                       width 0.4s cubic-bezier(0.25, 0.8, 0.25, 1),
-                       height 0.4s cubic-bezier(0.25, 0.8, 0.25, 1),
-                       /* Animating height can be jittery */ transform 0.4s cubic-bezier(0.25, 0.8, 0.25, 1),
-                       opacity 0.4s ease-out;
-               /* padding: 20px; */
-               overflow-y: auto;
-               max-width: 600px; /* Set max-width here */
-               max-height: 80vh; /* Set max-height here */
-               /* Consider adding will-change for properties you animate, but use sparingly */
-               /* will-change: transform, opacity, left, top, width, height; */
+       .modal-footer {
+               border-top: none;
        }
 </style>
index 96a5c995268be0ee25ca156ba62c48924cc6e126..173585018894006b74b91554e0ffc152576721c7 100644 (file)
        ];
 </script>
 
-<div class="datepicker glassLight">
+<div class="datepicker glass">
        <div class="datepicker-header">
                <button type="button" class="btn btnLeftRight" onclick={() => changeMonth(-1)}>&lt;</button>
                <div class="date-selectors">
                font-weight: bold;
                padding: 8px 0;
                font-size: 0.9em;
+       }
+
+       :global(body[data-bs-theme='light']) .day-header {
                color: #666;
        }
+
+       :global(body[data-bs-theme='light']) .day-header {
+               color: #5c5c5c;
+       }
+
        .day {
                height: 32px;
                width: 32px;
                user-select: none;
                --dot-color: rgb(250, 199, 58);
        }
+       :global(body[data-bs-theme='light']) .day {
+               color: #222;
+       }
        .day:hover {
                background: #f0f0f0;
+               color: black;
        }
        .day.mark-background {
                background-color: #00ad00;
                color: white;
                aspect-ratio: 1;
        }
+       .day.mark-background:hover {
+               background-color: #008a00;
+       }
 
        .day.mark-circle {
-               /* background-color: transparent;*/
                border: 3px solid #f57c00;
-               /* color: #ff9224; */
        }
 
        .day.mark-dot::after {
index 82b673b35a819c08f634e0eef703591642dfe727..f1e1ed8e65a8a378389ed3c7070fd5437bef9aea 100644 (file)
 
        // Wait for darkMode and language to be initialized before creating picker
        $effect(() => {
-               console.log('EmojiMart effect:', {
-                       darkMode: $darkMode,
-                       emojiPickerEl: !!emojiPickerEl,
-                       picker: !!picker
-               });
                if ($darkMode !== undefined && emojiPickerEl && !picker) {
                        createPicker();
                }
@@ -29,7 +24,6 @@
        });
 
        function createPicker() {
-               console.log('Creating emoji picker with theme:', $darkMode ? 'dark' : 'light');
                picker = new Picker({
                        theme: $darkMode ? 'dark' : 'light',
                        autoFocus: true,
index 7a1649d27d5ff9aaa149cecc62afd0315396a7ca..8fd780cfc53dfb10532c6928904bf382e01d58c0 100644 (file)
        <div class="search d-flex flex-column">
                <form onsubmit={searchForString} class="input-group">
                        <button
-                               class="btnSearchPopover btn btn-outline-secondary glassLight"
+                               class="btnSearchPopover btn btn-outline-secondary glass"
                                data-bs-toggle="popover"
                                data-bs-title="Suche"
                                data-bs-content={$t('search.description')}
                                                setTimeout(() => (showTagDropdown = false), 150);
                                        }}
                                />
-                               <button class="btn btn-outline-secondary glassLight" type="submit" id="search-button">
+                               <button class="btn btn-outline-secondary glass" type="submit" id="search-button">
                                        {#if $isSearching}
                                                <span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span>
                                        {:else}
                                {/if}
                        </div>
                {/if}
-               <div class="list-group flex-grow-1 mb-2 glassLight">
+               <div class="list-group flex-grow-1 mb-2 glass">
                        {#if $searchResults.length > 0}
                                {#each $searchResults as result}
                                        <button
        .noResult {
                font-size: 25pt;
                font-weight: 750;
-               color: #ccc;
                text-align: center;
                padding: 1rem;
                user-select: none;
        }
 
+       :global(body[data-bs-theme='dark']) .noResult {
+               color: #757575;
+       }
+
+       :global(body[data-bs-theme='light']) .noResult {
+               color: #cccccc;
+       }
+
        :global(.popover-body > span) {
                font-family: monospace;
                border: 1px solid #ccc;
index cca33c84e4b1e04f36aa9610b281974edf3b2b82..c93a82fca61bfde7c6c6138558382c5c6c2d1059 100644 (file)
                background-color: transparent;
                border: 1px solid #ccc;
                border-radius: 5px;
-               color: #495057;
+
                cursor: pointer;
                font-size: 11pt;
                margin-left: 0.3rem;
                transition: all 0.3s ease;
        }
 
+       :global(body[data-bs-theme='dark']) .removeBtn {
+               color: #c2c2c2;
+       }
+
+       :global(body[data-bs-theme='light']) .removeBtn {
+               color: #495057;
+       }
+
        .removeBtn:hover {
-               color: #dc3545;
+               color: #dc3545 !important;
+       }
+
+       .modal-header {
+               border-bottom: none;
+       }
+
+       .modal-footer {
+               border-top: none;
        }
 </style>
index 10e1e627f7bcd19fd74ab1c5ab8ef22c8ca0ac5b..81d4b2bca947f8da9f374e9c04a88803cecc1d02 100644 (file)
 
                        <!-- Admin status bar -->
                        <div
-                               class="d-flex align-items-center mb-4 p-3 bg-success bg-opacity-10 border border-success rounded"
+                               class="d-flex align-items-center mb-4 p-3 bg-success bg-opacity-25 border border-success rounded"
                        >
-                               <span class="text-success me-3">🔓 {$t('settings.admin.authenticated')}</span>
+                               <span class="text-success me-3">🔓 {$t('settings.admin.authorized')} </span>
                                <button class="btn btn-outline-secondary btn-sm ms-2" onclick={resetAdminState}>
                                        {$t('settings.admin.logout')}
                                </button>
 
 <style>
        .settings-admin {
-               min-height: 40vh;
+               min-height: 65vh;
        }
 
        .table th {
index 3fbc414e91dc2810cb811ac55c14bbbdc22ea5d5..e7c5f554c61796e85449503a8f4d94359b89ddf9 100644 (file)
                        if (to < from) to = from; // ensure non-inverted
                        return { level: s.level, from, to };
                });
+
+               // Fix overlapping ranges by making them exclusive/inclusive properly
+               for (let i = 1; i < legendRanges.length; i++) {
+                       // Make sure current segment doesn't start where previous ends
+                       if (legendRanges[i].from === legendRanges[i - 1].to && legendRanges[i].from > 0) {
+                               legendRanges[i].from = legendRanges[i - 1].to + 1;
+                       }
+               }
        }
 
        const colorLevel = (wc) => {
        {:else if years.length !== 0}
                <div class="year-selector d-flex align-items-center gap-2 mb-3 flex-wrap">
                        <button
-                               class="btn btn-sm btn-outline-secondary"
+                               class="btn btn-sm btn-secondary nav-button"
                                onclick={prevYear}
                                disabled={years.indexOf(selectedYear) === 0}
-                               aria-label="previous year">«</button
+                               aria-label="previous year">&lt;</button
                        >
                        <select
                                class="form-select form-select-sm year-dropdown"
                                {/each}
                        </select>
                        <button
-                               class="btn btn-sm btn-outline-secondary"
+                               class="btn btn-sm btn-secondary nav-button"
                                onclick={nextYear}
                                disabled={years.indexOf(selectedYear) === years.length - 1}
-                               aria-label="next year">»</button
+                               aria-label="next year">&gt;</button
                        >
                        <div class="legend ms-auto d-flex align-items-center gap-1">
                                <span class="legend-label small">{$t('settings.statistics.legend')}</span>
                                        return dayCount[maxIndex] > 0 ? weekdays[maxIndex] : '🤷‍♂️';
                                })()}
                        </li>
-                       <li>
-                               📖 {$t('settings.statistics.bookpages', {
-                                       pages: Math.round(dayStats.reduce((sum, d) => sum + d.wordCount, 0) / 300)
-                               })}
-                       </li>
                        <li>
                                🎯 {(() => {
                                        if (dayStats.length === 0) return '0%';
                                        return $t('settings.statistics.activityRate', { percent: activityRate });
                                })()}
                        </li>
+                       <li>
+                               📖 {$t('settings.statistics.bookpages', {
+                                       pages: Math.round(dayStats.reduce((sum, d) => sum + d.wordCount, 0) / 300)
+                               })}
+                       </li>
                </ul>
        {:else if years.length === 0}
                <p class="text-info">{$t('settings.statistics.no_data')}</p>
 </div>
 
 <style>
+       :global(body[data-bs-theme='dark']) .nav-button {
+               color: #bebebe;
+       }
+
+       .nav-button:disabled {
+               opacity: 0.5;
+       }
+
        .headerTotal {
                margin-top: 1rem;
                margin-bottom: 0.5rem;
        }
 
        .settings-stats {
-               min-height: 40vh;
+               min-height: 65vh;
        }
+
        .year-selector .year-dropdown {
                width: auto;
        }
                background: transparent;
        }
        /* Color scale (adjust to theme) */
-       .level-0 {
+
+       :global(body[data-bs-theme='light']) .level-0 {
                background: var(--heatmap-empty, #ebedf0);
        }
+
+       :global(body[data-bs-theme='dark']) .level-0 {
+               background: var(--heatmap-empty, #333333);
+       }
+
        .level-1 {
                background: #c6e48b;
        }
                background: #196127;
        }
        .bookmark {
-               font-size: 9px;
+               font-size: 10px;
                line-height: 1;
-               color: #fff;
-               text-shadow: 0 0 2px rgba(0, 0, 0, 0.6);
+               color: #000;
+               text-shadow: none;
+       }
+       :global(body[data-bs-theme='dark']) .day-cell.level-0 .bookmark {
+               color: #bbb;
        }
-       .day-cell.level-0 .bookmark {
+       :global(body[data-bs-theme='light']) .day-cell.level-0 .bookmark {
                color: #555;
-               text-shadow: none;
        }
+       :global(body[data-bs-theme='light']) .day-cell.level-4 .bookmark {
+               color: #dddddd;
+       }
+
        /* Popover styling (applies inside Bootstrap popover) */
        :global(.popover-day-content) {
                min-width: 200px;
index 5fdc406109ea715abbe234c0a206555898e7a7cc..540b6e892e0a56c11ddd7ced29bdb523fc8e96ce 100644 (file)
        // Active sub-view of settings modal: 'settings' | 'stats' | 'admin'
        let activeSettingsView = $state('settings');
 
+       // References for sliding indicator
+       let settingsTabGroup;
+       let settingsButton;
+       let statsButton;
+       let adminButton;
+
+       // Calculate slide offset and width for the indicator
+       function getSlideOffset(activeView) {
+               if (!settingsTabGroup || !settingsButton) return 0;
+
+               const container = settingsTabGroup;
+               const containerRect = container.getBoundingClientRect();
+
+               let activeButton;
+               switch (activeView) {
+                       case 'settings':
+                               activeButton = settingsButton;
+                               break;
+                       case 'stats':
+                               activeButton = statsButton;
+                               break;
+                       case 'admin':
+                               activeButton = adminButton;
+                               break;
+                       default:
+                               activeButton = settingsButton;
+               }
+
+               if (!activeButton) return 0;
+
+               const buttonRect = activeButton.getBoundingClientRect();
+               // Add the container's scrollLeft to account for horizontal scrolling
+               return buttonRect.left - containerRect.left + container.scrollLeft;
+       }
+
+       function getSlideWidth(activeView) {
+               let activeButton;
+               switch (activeView) {
+                       case 'settings':
+                               activeButton = settingsButton;
+                               break;
+                       case 'stats':
+                               activeButton = statsButton;
+                               break;
+                       case 'admin':
+                               activeButton = adminButton;
+                               break;
+                       default:
+                               activeButton = settingsButton;
+               }
+
+               return activeButton ? activeButton.offsetWidth : 0;
+       }
+
+       // Force indicator update when activeSettingsView changes or when modal is shown
+       let indicatorNeedsUpdate = $state(0);
+
+       /* $effect(() => {
+               // Trigger when activeSettingsView changes
+               activeSettingsView;
+               // Trigger a re-render to update indicator position
+               setTimeout(() => {
+                       indicatorNeedsUpdate++;
+               }, 10);
+       }); */
+
        // Function to compare version strings (semver-like)
        function compareVersions(v1, v2) {
                if (!v1 || !v2) return 0;
                                activeSettingsSection = 'appearance';
                                // Short timeout to allow layout calculation before reading offsets
                                setTimeout(initSettingsScrollSpy, 100);
+                               // Update indicator position after modal is fully shown
+                               setTimeout(() => {
+                                       indicatorNeedsUpdate++;
+                               }, 50);
                        }
                };
                modalEl.addEventListener('shown.bs.modal', onShown);
                class="modal-dialog modal-dialog-scrollable modal-dialog-centered modal-xl modal-fullscreen-sm-down"
        >
                <!--  -->
-               <div class="modal-content shadow-lg glass">
+               <div class="modal-content shadow-lg glass glass-modal">
                        <div class="modal-header flex-wrap gap-2">
                                <div class="d-flex w-100 align-items-center">
                                        <div
-                                               class="btn-group flex-grow-1 overflow-auto"
+                                               class="btn-group flex-grow-1 overflow-auto position-relative"
+                                               id="settingsTabGroup"
                                                role="group"
                                                aria-label="Settings views"
                                                style="scrollbar-width: none; -ms-overflow-style: none;"
+                                               bind:this={settingsTabGroup}
                                        >
+                                               <!-- Sliding indicator -->
+                                               <div
+                                                       class="sliding-indicator"
+                                                       style="transform: translateX({indicatorNeedsUpdate &&
+                                                               getSlideOffset(activeSettingsView)}px); width: {indicatorNeedsUpdate &&
+                                                               getSlideWidth(activeSettingsView)}px;"
+                                               ></div>
+
                                                <button
                                                        type="button"
                                                        class="btn btn-outline-primary flex-shrink-0 {activeSettingsView === 'settings'
                                                                ? 'active'
                                                                : ''}"
                                                        onclick={switchToSettingsTab}
+                                                       bind:this={settingsButton}
                                                >
                                                        {$t('settings.title')}
                                                </button>
                                                                ? 'active'
                                                                : ''}"
                                                        onclick={switchToStatsTab}
+                                                       bind:this={statsButton}
                                                >
                                                        {$t('settings.statistics.title')}
                                                </button>
                                                                ? 'active'
                                                                : ''}"
                                                        onclick={switchToAdminTab}
+                                                       bind:this={adminButton}
                                                >
                                                        {$t('settings.admin.title')}
                                                </button>
                                                                                        ? 'active'
                                                                                        : ''}"
                                                                                onclick={() => scrollToSection('appearance')}
-                                                                               >{$t('settings.appearance')}</button
+                                                                               >🎨 {$t('settings.appearance')}</button
                                                                        >
                                                                        <button
                                                                                type="button"
                                                                                class="nav-link mb-1 text-start {activeSettingsSection === 'functions'
                                                                                        ? 'active'
                                                                                        : ''}"
-                                                                               onclick={() => scrollToSection('functions')}>{$t('settings.functions')}</button
+                                                                               onclick={() => scrollToSection('functions')}
+                                                                               >🛠️ {$t('settings.functions')}</button
                                                                        >
                                                                        <button
                                                                                type="button"
                                                                                class="nav-link mb-1 text-start {activeSettingsSection === 'tags'
                                                                                        ? 'active'
                                                                                        : ''}"
-                                                                               onclick={() => scrollToSection('tags')}>{$t('settings.tags')}</button
+                                                                               onclick={() => scrollToSection('tags')}>#️⃣ {$t('settings.tags')}</button
                                                                        >
                                                                        <button
                                                                                type="button"
                                                                                class="nav-link mb-1 text-start {activeSettingsSection === 'templates'
                                                                                        ? 'active'
                                                                                        : ''}"
-                                                                               onclick={() => scrollToSection('templates')}>{$t('settings.templates')}</button
+                                                                               onclick={() => scrollToSection('templates')}
+                                                                               >📝 {$t('settings.templates')}</button
                                                                        >
                                                                        <button
                                                                                type="button"
                                                                                class="nav-link mb-1 text-start {activeSettingsSection === 'data'
                                                                                        ? 'active'
                                                                                        : ''}"
-                                                                               onclick={() => scrollToSection('data')}>{$t('settings.data')}</button
+                                                                               onclick={() => scrollToSection('data')}>📁 {$t('settings.data')}</button
                                                                        >
                                                                        <button
                                                                                type="button"
                                                                                class="nav-link mb-1 text-start {activeSettingsSection === 'security'
                                                                                        ? 'active'
                                                                                        : ''}"
-                                                                               onclick={() => scrollToSection('security')}>{$t('settings.security')}</button
+                                                                               onclick={() => scrollToSection('security')}>🔒 {$t('settings.security')}</button
+                                                                       >
+                                                                       <button
+                                                                               type="button"
+                                                                               class="nav-link mb-1 text-start {activeSettingsSection === 'account'
+                                                                                       ? 'active'
+                                                                                       : ''}"
+                                                                               onclick={() => scrollToSection('account')}>👤 {$t('settings.account')}</button
                                                                        >
                                                                        <button
                                                                                type="button"
                                                                                        : ''}"
                                                                                onclick={() => scrollToSection('about')}
                                                                        >
-                                                                               {$t('settings.about')}
+                                                                               💡 {$t('settings.about')}
                                                                                {#if updateAvailable}
                                                                                        <Fa icon={faCircleUp} size="1.2x" class="text-info" />
                                                                                {/if}
                                                                                        </div>
                                                                                </div>
                                                                        </div>
-                                                                       <div id="loginonreload">
-                                                                               {#if $tempSettings.requirePasswordOnPageLoad !== $settings.requirePasswordOnPageLoad}
-                                                                                       {@render unsavedChanges()}
-                                                                               {/if}
-
-                                                                               <h5>🔒 {$t('settings.reauth.title')}</h5>
-                                                                               {$t('settings.reauth.description')}
-
-                                                                               <div class="form-check form-switch mt-2">
-                                                                                       <input
-                                                                                               class="form-check-input"
-                                                                                               bind:checked={$tempSettings.requirePasswordOnPageLoad}
-                                                                                               type="checkbox"
-                                                                                               role="switch"
-                                                                                               id="requirePasswordOnPageLoadSwitch"
-                                                                                       />
-                                                                                       <label class="form-check-label" for="requirePasswordOnPageLoadSwitch">
-                                                                                               {$t('settings.reauth.label')}
-                                                                                       </label>
-                                                                               </div>
-                                                                       </div>
                                                                </div>
 
                                                                <div id="tags">
                                                                                        {/if}
                                                                                </button>
                                                                        </div>
-                                                                       <div><h5>Import</h5></div>
                                                                </div>
 
                                                                <div id="security">
                                                                                        </div>
                                                                                {/if}
                                                                        </div>
+                                                                       <div id="loginonreload">
+                                                                               {#if $tempSettings.requirePasswordOnPageLoad !== $settings.requirePasswordOnPageLoad}
+                                                                                       {@render unsavedChanges()}
+                                                                               {/if}
+
+                                                                               <h5>{$t('settings.reauth.title')}</h5>
+                                                                               {$t('settings.reauth.description')}
+
+                                                                               <div class="form-check form-switch mt-2">
+                                                                                       <input
+                                                                                               class="form-check-input"
+                                                                                               bind:checked={$tempSettings.requirePasswordOnPageLoad}
+                                                                                               type="checkbox"
+                                                                                               role="switch"
+                                                                                               id="requirePasswordOnPageLoadSwitch"
+                                                                                       />
+                                                                                       <label class="form-check-label" for="requirePasswordOnPageLoadSwitch">
+                                                                                               {$t('settings.reauth.label')}
+                                                                                       </label>
+                                                                               </div>
+                                                                       </div>
+                                                               </div>
+
+                                                               <div id="account">
+                                                                       <h3 class="text-primary">👤 {$t('settings.account')}</h3>
+
                                                                        <div>
                                                                                <h5>{$t('settings.change_username')}</h5>
                                                                                <div class="form-text">
                                                                                        </div>
                                                                                {/if}
                                                                        </div>
+
                                                                        <div>
                                                                                <h5>{$t('settings.delete_account')}</h5>
                                                                                <p>
 </div>
 
 <style>
+       #settingsTabGroup > button {
+               transition: text-decoration 0.3s ease;
+       }
+       :global(body[data-bs-theme='dark']) #settingsTabGroup > button {
+               color: white;
+       }
+       :global(body[data-bs-theme='light']) #settingsTabGroup > button {
+               color: black;
+       }
+
+       #settingsTabGroup > button.active {
+               text-decoration-color: #f57c00;
+               text-decoration-thickness: 3px;
+       }
+
+       :global(body[data-bs-theme='light']) #settingsTabGroup {
+               background-color: #b8b8b8;
+       }
+
        .dailytxt {
                color: #f57c00;
                font-size: 1.8rem;
                border-top-right-radius: 10px;
                padding-left: 0.5rem;
                margin-bottom: 0.5rem;
+               color: black;
        }
 
        .unsaved-changes::before {
        }
 
        #settings-content > div > div {
-               background-color: #bdbdbd5d;
                padding: 0.5rem;
                border-radius: 10px;
                margin-bottom: 1rem;
        }
+       :global(body[data-bs-theme='dark']) #settings-content > div > div {
+               background-color: #8080805d;
+       }
+       :global(body[data-bs-theme='light']) #settings-content > div > div {
+               background-color: #dfdfdf5d;
+       }
 
        h3.text-primary {
                font-weight: 700;
        }
 
        .modal-header {
-               border-bottom: 1px solid rgba(255, 255, 255, 0.2);
+               border-bottom: none;
        }
 
        .modal-footer {
-               border-top: 1px solid rgba(255, 255, 255, 0.2);
+               border-top: none;
        }
 
        /* Custom ScrollSpy styles */
                        border-color 0.25s ease;
                will-change: background-color, color, border-color;
        }
+       :global(body[data-bs-theme='dark']) .custom-scrollspy-nav .nav-link {
+               color: #9ac2ff;
+       }
+       :global(body[data-bs-theme='light']) .custom-scrollspy-nav .nav-link {
+               color: #0c6dff;
+       }
+
        .custom-scrollspy-nav .nav-link.active {
-               background-color: rgba(13, 110, 253, 0.15);
-               color: #0d6efd;
                font-weight: 600;
+       }
+       :global(body[data-bs-theme='dark']) .custom-scrollspy-nav .nav-link.active {
+               background-color: rgba(116, 116, 116, 0.521);
+               color: #62a1ff;
+               border-left-color: #0d6efd;
+       }
+       :global(body[data-bs-theme='light']) .custom-scrollspy-nav .nav-link.active {
+               background-color: rgba(13, 110, 253, 0.1);
+               color: #0066ff;
                border-left-color: #0d6efd;
        }
        .custom-scrollspy-nav .nav-link:not(.active):hover {
                scrollbar-width: none;
                -ms-overflow-style: none;
        }
+
+       /* Sliding indicator for settings tabs */
+       .sliding-indicator {
+               position: absolute;
+               top: 0;
+               height: 100%;
+               background-color: var(--bs-primary);
+               border-radius: 0.375rem;
+               transition:
+                       transform 0.3s cubic-bezier(0.4, 0, 0.2, 1),
+                       width 0.3s cubic-bezier(0.4, 0, 0.2, 1);
+               z-index: 0;
+               pointer-events: none;
+       }
+
+       /* Ensure buttons are above the indicator */
+       .btn-group .btn {
+               position: relative;
+               z-index: 1;
+               background-color: transparent !important;
+               border-color: transparent !important;
+       }
+
+       /* Active button styling - remove background since indicator handles it */
+       .btn-group .btn.active {
+               background-color: transparent !important;
+               border-color: transparent !important;
+               color: white !important;
+       }
+
+       /* Hover effect */
+       .btn-group .btn:hover {
+               background-color: rgba(13, 110, 253, 0.1) !important;
+               border-color: transparent !important;
+       }
 </style>
index b694ff6e008c0d32a5fd26d0a7020b9540e708e4..5c0af408bfe923eaae45df90e289b7cf72dfcba0 100644 (file)
                {#each logs as log (log.day)}
                        <!-- Log-Area -->
                        {#if ('text' in log && log.text !== '') || log.tags?.length > 0 || log.files?.length > 0}
-                               <div class="log mb-3 p-3 d-flex flex-row" data-log-day={log.day}>
+                               <div class="log glass mb-3 p-3 d-flex flex-row" data-log-day={log.day}>
                                        <div class="date me-3 d-flex flex-column align-items-center">
                                                <p class="dateNumber">{log.day}</p>
                                                <p class="dateDay">
        }
 
        .log {
-               backdrop-filter: blur(10px) saturate(150%);
-               background-color: rgba(199, 199, 201, 0.329);
                border-radius: 15px;
-               border: 1px solid rgba(223, 221, 221, 0.658);
+       }
+
+       :global(body[data-bs-theme='dark']) .glass {
+               background-color: rgba(68, 68, 68, 0.6) !important;
+       }
+
+       :global(body[data-bs-theme='light']) .glass {
+               background-color: rgba(122, 122, 122, 0.6) !important;
+               color: rgb(19, 19, 19);
        }
 
        .dateNumber {
index a0fc369af5cccf77948894ac88b34588fcdecdfe..b009b7d42020eecb8f68b7def1151e4087e1acb7 100644 (file)
        let filteredTags = $state([]);
        let selectedTags = $state([]);
 
+       // Action: portal dropdown to <body> and position it under the input
+       function portalDropdown(node, params) {
+               let anchorEl;
+
+               function getAnchor() {
+                       if (params?.anchor) {
+                               return typeof params.anchor === 'string'
+                                       ? document.querySelector(params.anchor)
+                                       : params.anchor;
+                       }
+                       return document.getElementById('tag-input');
+               }
+
+               function position() {
+                       if (!anchorEl) return;
+                       const rect = anchorEl.getBoundingClientRect();
+                       node.style.position = 'fixed';
+                       node.style.top = rect.bottom + 'px';
+                       node.style.left = rect.left + 'px';
+                       /* node.style.width = rect.width + 'px'; */
+                       // keep within viewport horizontally (basic guard)
+                       const maxLeft = Math.max(8, Math.min(rect.left, window.innerWidth - node.offsetWidth - 8));
+                       node.style.left = maxLeft + 'px';
+               }
+
+               function attach() {
+                       // move element into body so it's not clipped by ancestors and backdrop-filter works as expected
+                       document.body.appendChild(node);
+                       position();
+               }
+
+               function onScroll() {
+                       position();
+               }
+               function onResize() {
+                       position();
+               }
+
+               anchorEl = getAnchor();
+               attach();
+               // use capture to react to scrolls on any ancestor
+               window.addEventListener('scroll', onScroll, true);
+               window.addEventListener('resize', onResize);
+
+               return {
+                       update(newParams) {
+                               params = newParams;
+                               anchorEl = getAnchor();
+                               position();
+                       },
+                       destroy() {
+                               window.removeEventListener('scroll', onScroll, true);
+                               window.removeEventListener('resize', onResize);
+                               // Do not manually remove node; Svelte will detach it.
+                       }
+               };
+       }
+
        // show the correct tags in the dropdown
        $effect(() => {
                if ($tags.length === 0) {
                <!-- 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 glassLight">
+                       <div class="d-flex flex-row textAreaHeader glass">
                                <div class="flex-fill textAreaDate">
                                        {new Date(
                                                Date.UTC($selectedDate.year, $selectedDate.month - 1, $selectedDate.day)
                                        </button>
                                </div>
                                {#if showTagDropdown}
-                                       <div id="tagDropdown">
+                                       <div id="tagDropdown" use:portalDropdown>
                                                {#if filteredTags.length === 0}
                                                        <em style="padding: 0.2rem;">{$t('tags.no_tags_found')}</em>
                                                {:else}
                white-space: nowrap;
        }
 
-       .tag-item.selected {
-               background-color: #b2b4b6;
-       }
-
        .selectedTags {
                margin-top: 0.5rem;
                gap: 0.5rem;
 
        #tagDropdown {
                position: absolute;
-               background-color: white;
-               box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
+               box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
                z-index: 1000;
                max-height: 200px;
                overflow-y: scroll;
                overflow-x: hidden;
                display: flex;
                flex-direction: column;
+               backdrop-filter: blur(10px) saturate(150%);
+               border-radius: 10px;
+       }
+
+       :global(body[data-bs-theme='dark']) #tagDropdown {
+               background-color: rgba(87, 87, 87, 0.5);
+       }
+
+       :global(body[data-bs-theme='light']) #tagDropdown {
+               background-color: rgba(196, 196, 196, 0.5);
        }
 
        .tag-item {
                padding: 5px;
        }
 
+       :global(body[data-bs-theme='dark']) .tag-item.selected {
+               background-color: #5f5f5f;
+       }
+
+       :global(body[data-bs-theme='light']) .tag-item.selected {
+               background-color: #b9b9b9;
+       }
+
        .tags {
                z-index: 10;
                padding: 0.5rem;
                margin-bottom: 2rem;
-               /* backdrop-filter: blur(8px) saturate(150%);
-               background-color: rgba(219, 219, 219, 0.45);
-               border: 1px solid #ececec77; */
                border-radius: 10px;
-               /* color: #ececec; */
        }
 
        .loadImageBtn {
                transition: all ease 0.2s;
        }
 
-       .modal-content {
-               backdrop-filter: blur(8px) saturate(150%);
-               background-color: rgba(219, 219, 219, 0.45);
+       .modal-header {
+               border-bottom: none;
+       }
+
+       .modal-footer {
+               border-top: none;
        }
 
        .files {
-               /* margin-right: 2rem; */
                margin-bottom: 1rem;
                border-radius: 10px;
                padding: 1rem;
-               /* backdrop-filter: blur(8px) saturate(150%);
-               background-color: rgba(219, 219, 219, 0.45);
-               border: 1px solid #ececec77; */
        }
 
        :global(#uploadIcon) {
        }
 
        :global(.TMCommandBar) {
-               border-top: 1px solid #ccc;
-               border-left: 1px solid #ccc;
-               border-right: 1px solid #ccc;
+               border-top: none;
+               border-bottom: none;
                height: auto;
                flex-wrap: wrap;
+               padding-top: 2px;
+               padding-bottom: 3px;
+       }
+
+       :global(body[data-bs-theme='dark'] .TMCommandBar) {
+               border-left: 1px solid #6a6a6a;
+               border-right: 1px solid #6a6a6a;
+       }
+
+       :global(body[data-bs-theme='light'] .TMCommandBar) {
+               border-left: 1px solid #cccccc;
+               border-right: 1px solid #cccccc;
+       }
+
+       :global(body[data-bs-theme='dark'] .TMCommandBar) {
+               background-color: rgba(70, 70, 70, 0.5);
+       }
+
+       :global(body[data-bs-theme='light'] .TMCommandBar) {
+               background-color: rgba(202, 202, 202, 0.5);
+       }
+
+       :global(body[data-bs-theme='dark'] .TMCommandButton_Inactive) {
+               background-color: transparent;
+               fill: #f0f0f0;
+       }
+
+       :global(body[data-bs-theme='light'] .TMCommandButton_Inactive) {
+               background-color: transparent;
+               fill: #161616;
+       }
+
+       :global(body[data-bs-theme='dark'] .TMCommandButton_Inactive:hover) {
+               background-color: rgba(180, 180, 180, 0.438);
+       }
+
+       :global(body[data-bs-theme='light'] .TMCommandButton_Inactive:hover) {
+               background-color: rgba(180, 180, 180, 0.438);
+       }
+
+       :global(.TMCommandButton) {
+               border-radius: 3px;
+       }
+
+       :global(body[data-bs-theme='dark'] .TinyMDE) {
+               backdrop-filter: blur(8px) saturate(130%);
+               background-color: rgba(50, 50, 50, 0.8);
+               color: #f0f0f0;
+       }
+
+       :global(body[data-bs-theme='light'] .TinyMDE) {
+               backdrop-filter: blur(8px) saturate(130%);
+               background-color: rgba(255, 255, 255, 0.7);
+               color: #1f1f1f;
        }
 
        #editor {
index 63cd5b1f3aef9a850a73b75e86aa91a48af4e81e..6f44d6b9561003c50d035cc3ca21cfa0670ea841 100644 (file)
@@ -10,6 +10,7 @@
        import * as bootstrap from 'bootstrap';
        import { TolgeeProvider, Tolgee, DevTools, LanguageStorage } from '@tolgee/svelte';
        import { FormatIcu } from '@tolgee/format-icu';
+       import { darkMode } from '$lib/settingsStore.js';
 
        const tolgee = Tolgee()
                .use(DevTools())
 
                // if on login page, generate neon mesh
                if (page.url.pathname === '/login') {
-                       generateNeonMesh();
+                       generateNeonMesh($darkMode);
+               }
+       });
+
+       $effect(() => {
+               if ($darkMode !== undefined) {
+                       document.body.setAttribute('data-bs-theme', $darkMode ? 'dark' : 'light');
                }
        });
 
                background-color: rgba(0, 0, 0, 0.3) !important;
        }
 
-       :global(.modal-content) {
+       :global(body[data-bs-theme='dark'] .modal-content) {
                backdrop-filter: blur(20px) saturate(150%);
-               background-color: rgba(83, 83, 83, 0.85) !important;
+               background-color: rgba(70, 70, 70, 0.5) !important;
                border: 1px solid rgba(255, 255, 255, 0.2);
                color: #ececec;
        }
+       :global(body[data-bs-theme='light'] .modal-content) {
+               backdrop-filter: blur(20px) saturate(150%);
+               background-color: rgba(211, 211, 211, 0.5) !important;
+               border: 1px solid rgba(255, 255, 255, 0.2);
+               color: #161616;
+       }
 
-       :global(.glass) {
+       :global(body[data-bs-theme='dark'] .glass) {
                backdrop-filter: blur(14px) saturate(130%);
-               background-color: rgba(83, 83, 83, 0.8);
+               background-color: rgba(83, 83, 83, 0.4);
                border: 1px solid #62626278;
                color: #ececec;
        }
+       :global(body[data-bs-theme='light'] .glass) {
+               backdrop-filter: blur(14px) saturate(130%);
+               background-color: rgba(187, 187, 187, 0.3);
+               border: 1px solid #ccc;
+               color: #222;
+       }
 
-       :global(.glassLight) {
-               backdrop-filter: blur(8px) saturate(130%);
-               background-color: rgba(83, 83, 83, 0.445);
-               border: 1px solid #62626278;
-               color: #ececec;
+       :global(body[data-bs-theme='dark'] .popover-body > span) {
+               background-color: #444;
        }
 </style>
index d7a54906b9a6ac2cab04c8b16512c184f6fc95fd..06ba004061e2ad73283b263bb3b0c4914553b680 100644 (file)
 <div class="logo-login-flex d-flex justify-content-center align-items-center flex-row h-100">
        <div class="logo-wrapper d-flex flex-column align-items-center">
                <img id="largeLogo" src={img} alt="locked heart with keyhole" />
-               <p>DailyTxT</p>
+               <span class="dailytxt">DailyTxT</span>
        </div>
        <div class="login-wrapper">
                <div class="accordion" id="loginAccordion">
 </div>
 
 <style>
+       .dailytxt {
+               margin-top: 1.5rem;
+               color: #f57c00;
+               font-size: 2.7rem;
+               font-weight: 500;
+               line-height: 1rem;
+               position: relative;
+               text-decoration-line: underline;
+               text-decoration-color: #0d6efd;
+       }
+
        .language-select-wrapper {
                position: absolute;
                top: 10px;
        #largeLogo {
                width: 40%;
                min-height: 10%;
+               filter: drop-shadow(0 0 0.7rem #7e7e7e);
+               transition: 0.3s ease;
+       }
+
+       #largeLogo:hover {
+               filter: drop-shadow(0 0 1rem #0d6efd);
+               transform: scale(1.1);
        }
 
        .login-wrapper {
                width: 70%;
        }
 
+       @media screen and (min-width: 769px) and (max-width: 1000px) {
+               #loginAccordion {
+                       width: 80%;
+               }
+       }
+
        @media screen and (max-width: 768px) {
                .logo-login-flex {
                        flex-direction: column !important;
                        min-width: 50%;
                        max-width: 75%;
                }
+
+               #loginAccordion {
+                       width: 100%;
+               }
+
+               .logo-wrapper {
+                       width: 70%;
+                       margin-bottom: 3rem;
+               }
+       }
+
+       @media screen and (max-width: 540px) {
+               .logo-wrapper {
+                       width: 90%;
+               }
+
+               .login-wrapper {
+                       width: 80%;
+               }
        }
 </style>
index df86d0fb30392fef06efe7dca54bb2364cf55c21..59bdb6105c50e127f144a53fdce5bde6e6735667 100644 (file)
        }
 
        .card {
-               backdrop-filter: blur(10px);
-               background: rgba(255, 255, 255, 0.5);
+               /* backdrop-filter: blur(10px); */
+               /* background: rgba(255, 255, 255, 0.5); */
                border: none;
                border-radius: 10px;
        }
git clone https://git.99rst.org/PROJECT