Add a dark mode
This commit is contained in:
parent
07c7e63ee7
commit
0325139461
12 changed files with 199 additions and 35 deletions
|
|
@ -25,6 +25,16 @@
|
||||||
%sveltekit.head%
|
%sveltekit.head%
|
||||||
</head>
|
</head>
|
||||||
<body data-sveltekit-preload-data="hover">
|
<body data-sveltekit-preload-data="hover">
|
||||||
|
<script>
|
||||||
|
// Apply theme before render to prevent flash
|
||||||
|
(function () {
|
||||||
|
const stored = localStorage.getItem("theme");
|
||||||
|
const theme =
|
||||||
|
stored ||
|
||||||
|
(window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light");
|
||||||
|
document.documentElement.setAttribute("data-bs-theme", theme);
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
<div style="display: contents">%sveltekit.body%</div>
|
<div style="display: contents">%sveltekit.body%</div>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { AuthenticationResults, DnsResults } from "$lib/api/types.gen";
|
import type { AuthenticationResults, DnsResults } from "$lib/api/types.gen";
|
||||||
import { getScoreColorClass } from "$lib/score";
|
import { getScoreColorClass } from "$lib/score";
|
||||||
|
import { theme } from "$lib/stores/theme";
|
||||||
import GradeDisplay from "./GradeDisplay.svelte";
|
import GradeDisplay from "./GradeDisplay.svelte";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
|
|
@ -62,7 +63,7 @@
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="card shadow-sm" id="authentication-details">
|
<div class="card shadow-sm" id="authentication-details">
|
||||||
<div class="card-header bg-white">
|
<div class="card-header {$theme === 'light' ? 'bg-white' : 'bg-dark'}">
|
||||||
<h4 class="mb-0 d-flex justify-content-between align-items-center">
|
<h4 class="mb-0 d-flex justify-content-between align-items-center">
|
||||||
<span>
|
<span>
|
||||||
<i class="bi bi-shield-check me-2"></i>
|
<i class="bi bi-shield-check me-2"></i>
|
||||||
|
|
@ -104,7 +105,7 @@
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
{#if authentication.iprev.details}
|
{#if authentication.iprev.details}
|
||||||
<pre class="p-2 mb-0 bg-light text-muted small" style="white-space: pre-wrap">{authentication.iprev.details}</pre>
|
<pre class="p-2 mb-0 {$theme === 'light' ? 'bg-light' : 'bg-secondary'} text-muted small" style="white-space: pre-wrap">{authentication.iprev.details}</pre>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -128,7 +129,7 @@
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
{#if authentication.spf.details}
|
{#if authentication.spf.details}
|
||||||
<pre class="p-2 mb-0 bg-light text-muted small" style="white-space: pre-wrap">{authentication.spf.details}</pre>
|
<pre class="p-2 mb-0 {$theme === 'light' ? 'bg-light' : 'bg-secondary'} text-muted small" style="white-space: pre-wrap">{authentication.spf.details}</pre>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
|
|
@ -167,7 +168,7 @@
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
{#if authentication.dkim[0].details}
|
{#if authentication.dkim[0].details}
|
||||||
<pre class="p-2 mb-0 bg-light text-muted small" style="white-space: pre-wrap">{authentication.dkim[0].details}</pre>
|
<pre class="p-2 mb-0 {$theme === 'light' ? 'bg-light' : 'bg-secondary'} text-muted small" style="white-space: pre-wrap">{authentication.dkim[0].details}</pre>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
|
|
@ -206,7 +207,7 @@
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
{#if authentication.x_google_dkim.details}
|
{#if authentication.x_google_dkim.details}
|
||||||
<pre class="p-2 mb-0 bg-light text-muted small" style="white-space: pre-wrap">{authentication.x_google_dkim.details}</pre>
|
<pre class="p-2 mb-0 {$theme === 'light' ? 'bg-light' : 'bg-secondary'} text-muted small" style="white-space: pre-wrap">{authentication.x_google_dkim.details}</pre>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -230,7 +231,7 @@
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
{#if authentication.x_aligned_from.details}
|
{#if authentication.x_aligned_from.details}
|
||||||
<pre class="p-2 mb-0 bg-light text-muted small" style="white-space: pre-wrap">{authentication.x_aligned_from.details}</pre>
|
<pre class="p-2 mb-0 {$theme === 'light' ? 'bg-light' : 'bg-secondary'} text-muted small" style="white-space: pre-wrap">{authentication.x_aligned_from.details}</pre>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -276,7 +277,7 @@
|
||||||
{/if}
|
{/if}
|
||||||
{/if}
|
{/if}
|
||||||
{#if authentication.dmarc.details}
|
{#if authentication.dmarc.details}
|
||||||
<pre class="p-2 mb-0 bg-light text-muted small" style="white-space: pre-wrap">{authentication.dmarc.details}</pre>
|
<pre class="p-2 mb-0 {$theme === 'light' ? 'bg-light' : 'bg-secondary'} text-muted small" style="white-space: pre-wrap">{authentication.dmarc.details}</pre>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
|
|
@ -303,7 +304,7 @@
|
||||||
{authentication.bimi.result}
|
{authentication.bimi.result}
|
||||||
</span>
|
</span>
|
||||||
{#if authentication.bimi.details}
|
{#if authentication.bimi.details}
|
||||||
<pre class="p-2 mb-0 bg-light text-muted small" style="white-space: pre-wrap">{authentication.bimi.details}</pre>
|
<pre class="p-2 mb-0 {$theme === 'light' ? 'bg-light' : 'bg-secondary'} text-muted small" style="white-space: pre-wrap">{authentication.bimi.details}</pre>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
{:else if authentication.bimi && authentication.bimi.result == "none"}
|
{:else if authentication.bimi && authentication.bimi.result == "none"}
|
||||||
|
|
@ -315,7 +316,7 @@
|
||||||
</span>
|
</span>
|
||||||
<div class="text-muted small">Brand Indicators for Message Identification</div>
|
<div class="text-muted small">Brand Indicators for Message Identification</div>
|
||||||
{#if authentication.bimi.details}
|
{#if authentication.bimi.details}
|
||||||
<pre class="p-2 mb-0 bg-light text-muted small" style="white-space: pre-wrap">{authentication.bimi.details}</pre>
|
<pre class="p-2 mb-0 {$theme === 'light' ? 'bg-light' : 'bg-secondary'} text-muted small" style="white-space: pre-wrap">{authentication.bimi.details}</pre>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
|
|
@ -345,7 +346,7 @@
|
||||||
<div class="text-muted small">Chain length: {authentication.arc.chain_length}</div>
|
<div class="text-muted small">Chain length: {authentication.arc.chain_length}</div>
|
||||||
{/if}
|
{/if}
|
||||||
{#if authentication.arc.details}
|
{#if authentication.arc.details}
|
||||||
<pre class="p-2 mb-0 bg-light text-muted small" style="white-space: pre-wrap">{authentication.arc.details}</pre>
|
<pre class="p-2 mb-0 {$theme === 'light' ? 'bg-light' : 'bg-secondary'} text-muted small" style="white-space: pre-wrap">{authentication.arc.details}</pre>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { BlacklistCheck, ReceivedHop } from "$lib/api/types.gen";
|
import type { BlacklistCheck, ReceivedHop } from "$lib/api/types.gen";
|
||||||
import { getScoreColorClass } from "$lib/score";
|
import { getScoreColorClass } from "$lib/score";
|
||||||
|
import { theme } from "$lib/stores/theme";
|
||||||
import GradeDisplay from "./GradeDisplay.svelte";
|
import GradeDisplay from "./GradeDisplay.svelte";
|
||||||
import EmailPathCard from "./EmailPathCard.svelte";
|
import EmailPathCard from "./EmailPathCard.svelte";
|
||||||
|
|
||||||
|
|
@ -15,7 +16,11 @@
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="card shadow-sm" id="rbl-details">
|
<div class="card shadow-sm" id="rbl-details">
|
||||||
<div class="card-header bg-white">
|
<div
|
||||||
|
class="card-header"
|
||||||
|
class:bg-white={$theme === 'light'}
|
||||||
|
class:bg-dark={$theme !== 'light'}
|
||||||
|
>
|
||||||
<h4 class="mb-0 d-flex justify-content-between align-items-center">
|
<h4 class="mb-0 d-flex justify-content-between align-items-center">
|
||||||
<span>
|
<span>
|
||||||
<i class="bi bi-shield-exclamation me-2"></i>
|
<i class="bi bi-shield-exclamation me-2"></i>
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { ContentAnalysis } from "$lib/api/types.gen";
|
import type { ContentAnalysis } from "$lib/api/types.gen";
|
||||||
import { getScoreColorClass } from "$lib/score";
|
import { getScoreColorClass } from "$lib/score";
|
||||||
|
import { theme } from "$lib/stores/theme";
|
||||||
import GradeDisplay from "./GradeDisplay.svelte";
|
import GradeDisplay from "./GradeDisplay.svelte";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
|
|
@ -13,7 +14,7 @@
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="card shadow-sm" id="content-details">
|
<div class="card shadow-sm" id="content-details">
|
||||||
<div class="card-header bg-white">
|
<div class="card-header {$theme === 'light' ? 'bg-white' : 'bg-dark'}">
|
||||||
<h4 class="mb-0 d-flex justify-content-between align-items-center">
|
<h4 class="mb-0 d-flex justify-content-between align-items-center">
|
||||||
<span>
|
<span>
|
||||||
<i class="bi bi-file-text me-2"></i>
|
<i class="bi bi-file-text me-2"></i>
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { DomainAlignment, DnsResults, ReceivedHop } from "$lib/api/types.gen";
|
import type { DomainAlignment, DnsResults, ReceivedHop } from "$lib/api/types.gen";
|
||||||
import { getScoreColorClass } from "$lib/score";
|
import { getScoreColorClass } from "$lib/score";
|
||||||
|
import { theme } from "$lib/stores/theme";
|
||||||
import GradeDisplay from "./GradeDisplay.svelte";
|
import GradeDisplay from "./GradeDisplay.svelte";
|
||||||
import MxRecordsDisplay from "./MxRecordsDisplay.svelte";
|
import MxRecordsDisplay from "./MxRecordsDisplay.svelte";
|
||||||
import SpfRecordsDisplay from "./SpfRecordsDisplay.svelte";
|
import SpfRecordsDisplay from "./SpfRecordsDisplay.svelte";
|
||||||
|
|
@ -27,7 +28,7 @@
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="card shadow-sm" id="dns-details">
|
<div class="card shadow-sm" id="dns-details">
|
||||||
<div class="card-header bg-white">
|
<div class="card-header {$theme === 'light' ? 'bg-white' : 'bg-dark'}">
|
||||||
<h4 class="mb-0 d-flex justify-content-between align-items-center">
|
<h4 class="mb-0 d-flex justify-content-between align-items-center">
|
||||||
<span>
|
<span>
|
||||||
<i class="bi bi-diagram-3 me-2"></i>
|
<i class="bi bi-diagram-3 me-2"></i>
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,6 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import { theme } from "$lib/stores/theme";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
email: string;
|
email: string;
|
||||||
}
|
}
|
||||||
|
|
@ -24,7 +26,11 @@
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="bg-light rounded rounded-4 p-4">
|
<div
|
||||||
|
class="rounded rounded-4 p-4"
|
||||||
|
class:bg-light={$theme === "light"}
|
||||||
|
class:bg-secondary={$theme !== "light"}
|
||||||
|
>
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
<input
|
<input
|
||||||
bind:this={inputElement}
|
bind:this={inputElement}
|
||||||
|
|
@ -36,6 +42,8 @@
|
||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
class="btn btn-outline-primary clipboard-btn"
|
class="btn btn-outline-primary clipboard-btn"
|
||||||
|
class:btn-outline-primary={$theme === "light"}
|
||||||
|
class:btn-primary={$theme !== "light"}
|
||||||
onclick={copyToClipboard}
|
onclick={copyToClipboard}
|
||||||
title="Copy to clipboard"
|
title="Copy to clipboard"
|
||||||
>
|
>
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { AuthResult, DmarcRecord, HeaderAnalysis } from "$lib/api/types.gen";
|
import type { AuthResult, DmarcRecord, HeaderAnalysis } from "$lib/api/types.gen";
|
||||||
import { getScoreColorClass } from "$lib/score";
|
import { getScoreColorClass } from "$lib/score";
|
||||||
|
import { theme } from "$lib/stores/theme";
|
||||||
import GradeDisplay from "./GradeDisplay.svelte";
|
import GradeDisplay from "./GradeDisplay.svelte";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
|
|
@ -15,7 +16,7 @@
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="card shadow-sm" id="header-details">
|
<div class="card shadow-sm" id="header-details">
|
||||||
<div class="card-header bg-white">
|
<div class="card-header {$theme === 'light' ? 'bg-white' : 'bg-dark'}">
|
||||||
<h4 class="mb-0 d-flex justify-content-between align-items-center">
|
<h4 class="mb-0 d-flex justify-content-between align-items-center">
|
||||||
<span>
|
<span>
|
||||||
<i class="bi bi-list-ul me-2"></i>
|
<i class="bi bi-list-ul me-2"></i>
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { ScoreSummary } from "$lib/api/types.gen";
|
import type { ScoreSummary } from "$lib/api/types.gen";
|
||||||
import GradeDisplay from "./GradeDisplay.svelte";
|
import GradeDisplay from "./GradeDisplay.svelte";
|
||||||
|
import { theme } from "$lib/stores/theme";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
grade: string;
|
grade: string;
|
||||||
|
|
@ -33,7 +34,7 @@
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="card shadow-lg bg-white">
|
<div class="card shadow-lg {$theme === 'light' ? 'bg-white' : 'bg-dark'}">
|
||||||
<div class="card-body p-5 text-center">
|
<div class="card-body p-5 text-center">
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
{#if reanalyzing}
|
{#if reanalyzing}
|
||||||
|
|
@ -55,48 +56,90 @@
|
||||||
<div class="row g-3 text-start">
|
<div class="row g-3 text-start">
|
||||||
<div class="col-sm-6 col-md-4 col-lg">
|
<div class="col-sm-6 col-md-4 col-lg">
|
||||||
<a href="#dns-details" class="text-decoration-none">
|
<a href="#dns-details" class="text-decoration-none">
|
||||||
<div class="p-2 bg-light rounded text-center summary-card">
|
<div
|
||||||
<GradeDisplay grade={summary.dns_grade} score={summary.dns_score} />
|
class="p-2 rounded text-center summary-card"
|
||||||
|
class:bg-light={$theme === 'light'}
|
||||||
|
class:bg-secondary={$theme !== 'light'}
|
||||||
|
>
|
||||||
|
<GradeDisplay
|
||||||
|
grade={summary.dns_grade}
|
||||||
|
score={summary.dns_score}
|
||||||
|
/>
|
||||||
<small class="text-muted d-block">DNS</small>
|
<small class="text-muted d-block">DNS</small>
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-sm-6 col-md-4 col-lg">
|
<div class="col-sm-6 col-md-4 col-lg">
|
||||||
<a href="#authentication-details" class="text-decoration-none">
|
<a href="#authentication-details" class="text-decoration-none">
|
||||||
<div class="p-2 bg-light rounded text-center summary-card">
|
<div
|
||||||
<GradeDisplay grade={summary.authentication_grade} score={summary.authentication_score} />
|
class="p-2 rounded text-center summary-card"
|
||||||
|
class:bg-light={$theme === 'light'}
|
||||||
|
class:bg-secondary={$theme !== 'light'}
|
||||||
|
>
|
||||||
|
<GradeDisplay
|
||||||
|
grade={summary.authentication_grade}
|
||||||
|
score={summary.authentication_score}
|
||||||
|
/>
|
||||||
<small class="text-muted d-block">Authentication</small>
|
<small class="text-muted d-block">Authentication</small>
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-sm-6 col-md-4 col-lg">
|
<div class="col-sm-6 col-md-4 col-lg">
|
||||||
<a href="#rbl-details" class="text-decoration-none">
|
<a href="#rbl-details" class="text-decoration-none">
|
||||||
<div class="p-2 bg-light rounded text-center summary-card">
|
<div
|
||||||
<GradeDisplay grade={summary.blacklist_grade} score={summary.blacklist_score} />
|
class="p-2 rounded text-center summary-card"
|
||||||
|
class:bg-light={$theme === 'light'}
|
||||||
|
class:bg-secondary={$theme !== 'light'}
|
||||||
|
>
|
||||||
|
<GradeDisplay
|
||||||
|
grade={summary.blacklist_grade}
|
||||||
|
score={summary.blacklist_score}
|
||||||
|
/>
|
||||||
<small class="text-muted d-block">Blacklists</small>
|
<small class="text-muted d-block">Blacklists</small>
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-sm-6 col-md-4 col-lg">
|
<div class="col-sm-6 col-md-4 col-lg">
|
||||||
<a href="#header-details" class="text-decoration-none">
|
<a href="#header-details" class="text-decoration-none">
|
||||||
<div class="p-2 bg-light rounded text-center summary-card">
|
<div
|
||||||
<GradeDisplay grade={summary.header_grade} score={summary.header_score} />
|
class="p-2 rounded text-center summary-card"
|
||||||
|
class:bg-light={$theme === 'light'}
|
||||||
|
class:bg-secondary={$theme !== 'light'}
|
||||||
|
>
|
||||||
|
<GradeDisplay
|
||||||
|
grade={summary.header_grade}
|
||||||
|
score={summary.header_score}
|
||||||
|
/>
|
||||||
<small class="text-muted d-block">Headers</small>
|
<small class="text-muted d-block">Headers</small>
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-sm-6 col-md-4 col-lg">
|
<div class="col-sm-6 col-md-4 col-lg">
|
||||||
<a href="#spam-details" class="text-decoration-none">
|
<a href="#spam-details" class="text-decoration-none">
|
||||||
<div class="p-2 bg-light rounded text-center summary-card">
|
<div
|
||||||
<GradeDisplay grade={summary.spam_grade} score={summary.spam_score} />
|
class="p-2 rounded text-center summary-card"
|
||||||
|
class:bg-light={$theme === 'light'}
|
||||||
|
class:bg-secondary={$theme !== 'light'}
|
||||||
|
>
|
||||||
|
<GradeDisplay
|
||||||
|
grade={summary.spam_grade}
|
||||||
|
score={summary.spam_score}
|
||||||
|
/>
|
||||||
<small class="text-muted d-block">Spam Score</small>
|
<small class="text-muted d-block">Spam Score</small>
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-sm-6 col-md-4 col-lg">
|
<div class="col-sm-6 col-md-4 col-lg">
|
||||||
<a href="#content-details" class="text-decoration-none">
|
<a href="#content-details" class="text-decoration-none">
|
||||||
<div class="p-2 bg-light rounded text-center summary-card">
|
<div
|
||||||
<GradeDisplay grade={summary.content_grade} score={summary.content_score} />
|
class="p-2 rounded text-center summary-card"
|
||||||
|
class:bg-light={$theme === 'light'}
|
||||||
|
class:bg-secondary={$theme !== 'light'}
|
||||||
|
>
|
||||||
|
<GradeDisplay
|
||||||
|
grade={summary.content_grade}
|
||||||
|
score={summary.content_score}
|
||||||
|
/>
|
||||||
<small class="text-muted d-block">Content</small>
|
<small class="text-muted d-block">Content</small>
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
|
|
@ -117,4 +160,9 @@
|
||||||
transform: translateY(-2px);
|
transform: translateY(-2px);
|
||||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
:global([data-bs-theme="dark"]) .summary-card:hover {
|
||||||
|
background-color: #495057 !important;
|
||||||
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { SpamAssassinResult } from "$lib/api/types.gen";
|
import type { SpamAssassinResult } from "$lib/api/types.gen";
|
||||||
import { getScoreColorClass } from "$lib/score";
|
import { getScoreColorClass } from "$lib/score";
|
||||||
|
import { theme } from "$lib/stores/theme";
|
||||||
import GradeDisplay from "./GradeDisplay.svelte";
|
import GradeDisplay from "./GradeDisplay.svelte";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
|
|
@ -13,7 +14,7 @@
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="card shadow-sm" id="spam-details">
|
<div class="card shadow-sm" id="spam-details">
|
||||||
<div class="card-header bg-white">
|
<div class="card-header {$theme === 'light' ? 'bg-white' : 'bg-dark'}">
|
||||||
<h4 class="mb-0 d-flex justify-content-between align-items-center">
|
<h4 class="mb-0 d-flex justify-content-between align-items-center">
|
||||||
<span>
|
<span>
|
||||||
<i class="bi bi-bug me-2"></i>
|
<i class="bi bi-bug me-2"></i>
|
||||||
|
|
@ -79,7 +80,7 @@
|
||||||
<strong>Tests Triggered:</strong>
|
<strong>Tests Triggered:</strong>
|
||||||
<div class="mt-2">
|
<div class="mt-2">
|
||||||
{#each spamassassin.tests as test}
|
{#each spamassassin.tests as test}
|
||||||
<span class="badge bg-light text-dark me-1 mb-1">{test}</span>
|
<span class="badge {$theme === 'light' ? 'bg-light text-dark' : 'bg-secondary'} me-1 mb-1">{test}</span>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -88,7 +89,7 @@
|
||||||
{#if spamassassin.report}
|
{#if spamassassin.report}
|
||||||
<details class="mt-3">
|
<details class="mt-3">
|
||||||
<summary class="cursor-pointer fw-bold">Raw Report</summary>
|
<summary class="cursor-pointer fw-bold">Raw Report</summary>
|
||||||
<pre class="mt-2 small bg-light p-3 rounded">{spamassassin.report}</pre>
|
<pre class="mt-2 small {$theme === 'light' ? 'bg-light' : 'bg-secondary'} p-3 rounded">{spamassassin.report}</pre>
|
||||||
</details>
|
</details>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -106,4 +107,15 @@
|
||||||
details summary:hover {
|
details summary:hover {
|
||||||
color: var(--bs-primary);
|
color: var(--bs-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Darker table colors in dark mode */
|
||||||
|
:global([data-bs-theme="dark"]) .table-warning {
|
||||||
|
--bs-table-bg: rgba(255, 193, 7, 0.2);
|
||||||
|
--bs-table-border-color: rgba(255, 193, 7, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
:global([data-bs-theme="dark"]) .table-success {
|
||||||
|
--bs-table-bg: rgba(25, 135, 84, 0.2);
|
||||||
|
--bs-table-border-color: rgba(25, 135, 84, 0.3);
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
20
web/src/lib/stores/theme.ts
Normal file
20
web/src/lib/stores/theme.ts
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
import { writable } from "svelte/store";
|
||||||
|
import { browser } from "$app/environment";
|
||||||
|
|
||||||
|
const getInitialTheme = () => {
|
||||||
|
if (!browser) return "light";
|
||||||
|
|
||||||
|
const stored = localStorage.getItem("theme");
|
||||||
|
if (stored) return stored;
|
||||||
|
|
||||||
|
return window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
|
||||||
|
};
|
||||||
|
|
||||||
|
export const theme = writable<"light" | "dark">(getInitialTheme());
|
||||||
|
|
||||||
|
theme.subscribe((value) => {
|
||||||
|
if (browser) {
|
||||||
|
localStorage.setItem("theme", value);
|
||||||
|
document.documentElement.setAttribute("data-bs-theme", value);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
@ -4,12 +4,22 @@
|
||||||
import "../app.css";
|
import "../app.css";
|
||||||
|
|
||||||
import Logo from "$lib/components/Logo.svelte";
|
import Logo from "$lib/components/Logo.svelte";
|
||||||
|
import { theme } from "$lib/stores/theme";
|
||||||
|
import { onMount } from "svelte";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
children?: import("svelte").Snippet;
|
children?: import("svelte").Snippet;
|
||||||
}
|
}
|
||||||
|
|
||||||
let { children }: Props = $props();
|
let { children }: Props = $props();
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
document.documentElement.setAttribute("data-bs-theme", $theme);
|
||||||
|
});
|
||||||
|
|
||||||
|
function toggleTheme() {
|
||||||
|
$theme = $theme === "light" ? "dark" : "light";
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="min-vh-100 d-flex flex-column">
|
<div class="min-vh-100 d-flex flex-column">
|
||||||
|
|
@ -17,11 +27,21 @@
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<a class="navbar-brand fw-bold" href="/">
|
<a class="navbar-brand fw-bold" href="/">
|
||||||
<i class="bi bi-envelope-check me-2"></i>
|
<i class="bi bi-envelope-check me-2"></i>
|
||||||
<Logo />
|
<Logo color={$theme === "light" ? "black" : "white"} />
|
||||||
</a>
|
</a>
|
||||||
<span class="d-none d-md-inline navbar-text text-primary small">
|
<div>
|
||||||
Open-Source Email Deliverability Tester
|
<span class="d-none d-md-inline navbar-text text-primary small">
|
||||||
</span>
|
Open-Source Email Deliverability Tester
|
||||||
|
</span>
|
||||||
|
<button
|
||||||
|
class="btn btn-link ms-auto {$theme == 'light' ? 'text-dark' : 'text-light'}"
|
||||||
|
onclick={toggleTheme}
|
||||||
|
aria-label="Toggle theme"
|
||||||
|
title="Toggle theme"
|
||||||
|
>
|
||||||
|
<i class="bi bi-{$theme === 'light' ? 'moon-stars-fill' : 'sun-fill'}"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -252,6 +252,26 @@
|
||||||
text-shadow: black 0 0 1px;
|
text-shadow: black 0 0 1px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Dark mode hero adjustments */
|
||||||
|
:global([data-bs-theme="dark"]) .hero {
|
||||||
|
background:
|
||||||
|
linear-gradient(135deg, rgba(50, 65, 140, 0.85) 0%, rgba(65, 40, 95, 0.9) 100%),
|
||||||
|
url("/img/report.webp");
|
||||||
|
background-size: cover;
|
||||||
|
background-position: center 25%;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
}
|
||||||
|
|
||||||
|
:global([data-bs-theme="dark"]) .hero h1,
|
||||||
|
:global([data-bs-theme="dark"]) .hero p {
|
||||||
|
text-shadow: black 0 0 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Dark mode section background */
|
||||||
|
:global([data-bs-theme="dark"]) #steps {
|
||||||
|
background-color: var(--bs-secondary-bg) !important;
|
||||||
|
}
|
||||||
|
|
||||||
.fade-in {
|
.fade-in {
|
||||||
animation: fadeIn 0.6s ease-out;
|
animation: fadeIn 0.6s ease-out;
|
||||||
}
|
}
|
||||||
|
|
@ -293,4 +313,21 @@
|
||||||
box-shadow: 0 1rem 2rem rgba(25, 135, 84, 0.5);
|
box-shadow: 0 1rem 2rem rgba(25, 135, 84, 0.5);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@keyframes pulse-dark {
|
||||||
|
0%,
|
||||||
|
100% {
|
||||||
|
transform: scale(1);
|
||||||
|
box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.4);
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
transform: scale(1.08);
|
||||||
|
box-shadow: 0 1rem 2rem rgba(25, 135, 84, 0.7);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Dark mode pulse animation */
|
||||||
|
:global([data-bs-theme="dark"]) .cta-button {
|
||||||
|
animation: pulse-dark 2s infinite;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue