Graphical overhaul.

Remove all legacy pixeldrain pages, handlers and assets.
Remove old theming engine.
Make logo responsive to the accent colour.
Lots of other stuff.
This commit is contained in:
2026-10-01 02:06:00 +02:00
parent 7b2e4b4240
commit 58db683fc8
98 changed files with 1025 additions and 6475 deletions

View File

@@ -1,10 +1,9 @@
<script lang="ts">
import Chart from "util/Chart.svelte";
import type Chart from "util/Chart.svelte";
import { formatDataVolume, formatDate, formatThousands } from "util/Formatting";
import Modal from "util/Modal.svelte";
import { fs_path_url, fs_share_hotlink_url, fs_share_url, fs_timeseries, type FSNode } from "lib/FilesystemAPI.svelte";
import { color_by_name } from "util/Util";
import { tick } from "svelte";
import CopyButton from "layout/CopyButton.svelte";
import type { FSNavigator } from "./FSNavigator";
@@ -43,12 +42,9 @@ let total_downloads = $state(0)
let total_egress = $state(0)
let update_chart = async (base: FSNode, timespan: number, interval: number) => {
if (chart === undefined) {
// Wait for the chart element to render, if it's not rendered already
await tick()
}
if (!visible || base.type !== "file" || chart === undefined) {
// The chart is not there until it's downloaded. The $effect above reads it
// through this function, so it calls this function again when it's ready
if (!visible || base.type !== "file" || !chart) {
return
}
@@ -116,15 +112,15 @@ let update_chart = async (base: FSNode, timespan: number, interval: number) => {
label: "Downloads",
borderWidth: 2,
pointRadius: 0,
borderColor: color_by_name("highlight_color"),
backgroundColor: color_by_name("highlight_color"),
borderColor: color_by_name("accent_color"),
backgroundColor: color_by_name("accent_color"),
data: resp.downloads.amounts,
}, {
label: "Egress",
borderWidth: 2,
pointRadius: 0,
borderColor: color_by_name("danger_color"),
backgroundColor: color_by_name("danger_color"),
borderColor: color_by_name("red_color"),
backgroundColor: color_by_name("red_color"),
yAxisID: "y1",
data: resp.egress.amounts,
},
@@ -244,7 +240,10 @@ let direct_share_url = $derived(fs_share_hotlink_url($nav.path))
{/each}
</div>
<Chart bind:this={chart} />
<!-- Chart.js is big, so it's only downloaded when it's needed -->
{#await import("util/Chart.svelte") then c}
<c.default bind:this={chart} />
{/await}
{/if}
</Modal>

View File

@@ -1,5 +1,5 @@
import { loading_finish, loading_start } from "lib/Loading";
import { fs_get_node, fs_encode_path, fs_split_path } from "lib/FilesystemAPI.svelte";
import { fs_get_node, fs_encode_path, fs_split_path, fs_path_from_json } from "lib/FilesystemAPI.svelte";
import type { FSNode, FSPath, FSPermissions, FSContext } from "lib/FilesystemAPI.svelte";
export class FSNavigator {
@@ -24,16 +24,6 @@ export class FSNavigator {
this.history_enabled = history_enabled
}
// The popstate event can be used to listen for navigation events. Register
// this event listener on the <svelte:window> in the parent element. When
// the user presses the back or forward buttons in the browser we'll catch
// the event and navigate to the proper directory
popstate = (e: PopStateEvent) => {
// Get the part of the URL after the fs root and navigate to it
const path = window.location.pathname.replace(/^\/d/, "")
this.navigate(decodeURI(path), false)
}
// The FSNavigator acts as a svelte store. This allows for DOM reactivity.
// This works by implementing the store contract:
// https://svelte.dev/docs/svelte-components#script-4-prefix-stores-with-$-to-access-their-values
@@ -70,7 +60,13 @@ export class FSNavigator {
try {
loading_start()
const resp = await fs_get_node(path)
// The server embeds the node which the page was opened on. It's only
// valid for the first navigation, which is to that same page
const resp = window.initial_node !== undefined
? fs_path_from_json(window.initial_node)
: await fs_get_node(path)
window.initial_node = undefined
this.open_node(resp, push_history)
} catch (err: any) {
if (err.value !== undefined && err.value === "path_not_found") {

View File

@@ -8,6 +8,7 @@ import FilePreview from "./viewers/FilePreview.svelte";
import FSUploadWidget from "./upload_widget/FSUploadWidget.svelte";
import { global_navigator } from "./FSNavigator"
import { css_from_path } from "filesystem/edit_window/Branding";
import { update_favicon } from "util/NovaLogo.svelte";
import AffiliatePrompt from "user_home/AffiliatePrompt.svelte";
import { current_page_store } from "wrap/RouterStore";
import SearchBar from "./SearchBar.svelte";
@@ -46,11 +47,13 @@ onMount(() => {
// styles take precedence since they're lower level, and we can clean it
// up afterwards without overwriting global style
document.body.style = css_from_path(nav.path)
update_favicon()
})
return () => {
page_sub()
nav_sub()
document.body.style = ""
update_favicon()
}
})

View File

@@ -1,132 +0,0 @@
<script lang="ts">
import NovaLogo from "util/NovaLogo.svelte";
import Button from "layout/Button.svelte";
import Euro from "util/Euro.svelte";
import { formatDataVolume } from "util/Formatting";
import { user } from "lib/UserStore";
import Dialog from "layout/Dialog.svelte";
let button: HTMLButtonElement = $state()
let dialog: Dialog = $state()
let {
no_login_label = "Nova",
hide_name = true,
hide_logo = false,
style = "",
embedded = false
}: {
no_login_label?: string;
// Hide the label if the screen is smaller than 800px
hide_name?: boolean;
hide_logo?: boolean;
style?: string;
embedded?: boolean;
} = $props();
let target = $derived(embedded ? "_blank" : "_self")
const open = () => dialog.open(button.getBoundingClientRect())
</script>
<div class="wrapper">
<button bind:this={button} onclick={open} class="button round" title="Menu" style={style}>
{#if !hide_logo}
<NovaLogo style="height: 1.6em; width: 1.6em;"/>
{/if}
<span class="button_username" class:hide_name>
{#if $user !== null}
{$user.username === "" ? no_login_label : $user.username}
{/if}
</span>
</button>
</div>
<Dialog bind:this={dialog}>
<div class="menu">
{#if $user !== null}
<Button link_href="/user" link_target={target} icon="dashboard" label="Dashboard" />
<div class="separator"></div>
<div class="stats_table">
<div>Subscription</div>
<div>{$user.subscription.name}</div>
{#if $user.subscription.type === "prepaid"}
<div>Credit</div>
<div><Euro amount={$user.balance_micro_eur}/></div>
{/if}
<div>Storage used</div>
<div>{formatDataVolume($user.filesystem_storage_used, 3)}</div>
<div>Transfer used</div>
<div>{formatDataVolume($user.monthly_transfer_used, 3)}</div>
</div>
<div class="separator"></div>
<Button link_href="/d/me" link_target={target} icon="folder" label="My Filesystem"/>
<Button link_href="/filesystem" link_target={target} icon="description" label="Filesystem Guide"/>
<div class="separator"></div>
<Button link_href="/user/filemanager#files" link_target={target} icon="image" label="My Files"/>
<Button link_href="/user/filemanager#lists" link_target={target} icon="photo_library" label="My Albums"/>
<div class="separator"></div>
<Button link_href="/user/settings" link_target={target} icon="settings" label="Account Settings"/>
<Button link_href="/user/subscription" link_target={target} icon="shopping_cart" label="Subscription"/>
<Button link_href="/user/subscription/transactions" link_target={target} icon="receipt" label="Transactions"/>
{#if $user.is_admin}
<div class="separator"></div>
<Button link_href="/admin" link_target={target} icon="admin_panel_settings" label="Admin Panel"/>
{/if}
{:else}
<Button link_href="/" link_target={target} icon="home" label="Home"/>
<Button link_href="/#pro" link_target={target} icon="star" label="Get Premium"/>
<Button link_href="/login" link_target={target} icon="person" label="Log in"/>
<Button link_href="/register" link_target={target} icon="person" label="Register"/>
{/if}
</div>
</Dialog>
<style>
.wrapper {
flex-grow: 0;
flex-shrink: 0;
display: inline-flex;
align-self: center;
}
.button {
flex: 0 0 content;
}
.button_username {
margin: 0 4px;
}
.menu {
display: flex;
flex-direction: column;
max-width: 15em;
}
.separator {
height: 1px;
margin: 2px 0;
width: 100%;
background-color: var(--separator);
}
.stats_table {
display: grid;
grid-template-columns: auto auto;
gap: 0.2em 1em;
margin: 3px;
}
/* Hide username on small screen */
@media(max-width: 800px) {
.hide_name {
display: none;
}
}
</style>

View File

@@ -287,12 +287,12 @@ const window_keydown = (e: KeyboardEvent) => {
align-items: center;
gap: 4px;
text-decoration: none;
color: var(--text-color);
color: var(--body_text_color);
padding: 2px;
}
.node_selected {
background: var(--highlight_background);
color: var(--highlight_text_color);
background: var(--accent_background);
color: var(--accent_text_color);
}
.node:hover:not(.node_selected) {
background: var(--input_background);

View File

@@ -42,7 +42,8 @@ const add_password = (e: SubmitEvent) => {
}
const generate_password = () => {
// 32 hexadecimal characters, which any S3 client accepts as secret key
new_password = crypto.randomUUID().replaceAll("-", "")
const bytes = crypto.getRandomValues(new Uint8Array(16))
new_password = Array.from(bytes, b => b.toString(16).padStart(2, "0")).join("")
}
// Passwords stay hidden until their reveal button is pressed
let revealed: {[password: string]: boolean} = $state({})

View File

@@ -11,12 +11,11 @@ type Style = {
input_background?: string,
input_hover_background?: string,
input_text?: string,
highlight_color?: string,
highlight_background?: string,
highlight_text_color?: string,
accent_color?: string,
accent_shade?: string,
accent_background?: string,
accent_text_color?: string,
link_color?: string,
danger_color?: string,
danger_text_color?: string,
background_color?: string,
background?: string,
background_text_color?: string,
@@ -32,8 +31,45 @@ type Style = {
background_image_size?: string,
background_image_position?: string,
background_image_repeat?: string,
color_scheme?: string,
green_color?: string,
red_color?: string,
yellow_color?: string,
blue_color?: string,
green_tint?: string,
red_tint?: string,
yellow_tint?: string,
blue_tint?: string,
logo_color_1?: string,
logo_color_2?: string,
logo_color_3?: string,
logo_color_4?: string,
}
// The hues of the accents for success, errors, warnings and notices, and the
// accents for light themes. The dark defaults in layout.css are derived from
// the default accent colour, the light defaults are these light accents.
// Branding can also set each accent by itself
const accent_hues = { green: 140, red: 8, yellow: 45, blue: 215 }
const light_accents = { green: "#15793a", red: "#ba2c37", yellow: "#995c00", blue: "#2164ca" }
const default_accent = "#49dfa0"
// The colourways of the logo, as designed. The hue the colourway is closest to,
// the colours of the bars and the colours of the diagonal
const logo_colourways: [number, string, string, string, string][] = [
[1, "#f62e27", "#bb1d1c", "#bb1c1c", "#ee2c26"], // Red
[20, "#f78146", "#bb5420", "#ee7a40", "#c05823"], // Orange
[54, "#ffe500", "#8c8015", "#ead204", "#6d6310"], // Yellow
[71, "#dde741", "#74a511", "#8bb925", "#eaed38"], // Lime
[120, "#15c615", "#0c750c", "#0c730c", "#0c730c"], // Green
[141, "#45ed52", "#11a574", "#24b880", "#39ef3d"], // Mint
[190, "#48eef1", "#1378a8", "#258eba", "#38e8ec"], // Light blue
[230, "#4b82f5", "#1111a5", "#2354b5", "#393bee"], // Dark blue
[253, "#974af2", "#212eae", "#2929b8", "#8e39ee"], // Purple blue
[270, "#8447f0", "#7514a9", "#5924b8", "#b039ef"], // Purple
[330, "#f74bbb", "#ad174e", "#b72484", "#f13a77"], // Pink
]
// Generate a branding style from a file's properties map
export const branding_from_path = (path: Array<FSNode>) => {
const style = {}
@@ -57,7 +93,7 @@ export const css_from_path = (path: Array<FSNode>) => {
}
const gen_css = (style: Style) => {
return Object.entries(style).map(([key, value]) => `--${key}:${value}`).join(';');
return Object.entries(with_accents(style)).map(([key, value]) => `--${key}:${value}`).join(';');
}
// add_styles adds the styles configured in the properties struct to the
@@ -74,20 +110,33 @@ const add_styles = (style: Style, properties: FSNodeProperties) => {
style.input_hover_background = properties.brand_input_color
style.input_text = add_contrast(properties.brand_input_color, text_contrast)
}
if (properties.brand_highlight_color) {
style.highlight_color = properties.brand_highlight_color
style.highlight_background = properties.brand_highlight_color
style.highlight_text_color = add_contrast(properties.brand_highlight_color, text_contrast)
if (properties.brand_accent_color) {
style.accent_color = properties.brand_accent_color
style.accent_background = properties.brand_accent_color
style.accent_text_color = add_contrast(properties.brand_accent_color, text_contrast)
// If we have a body colour to compare it to we use the highlight colour
// If we have a body colour to compare it to we use the accent colour
// to generate the link colour
if (properties.brand_body_color) {
style.link_color = generate_link_color(properties.brand_highlight_color, properties.brand_body_color)
style.link_color = generate_link_color(properties.brand_accent_color, properties.brand_body_color)
}
// The logo gets the colourway closest to the accent colour. Greys
// have no meaningful hue, they keep the default
const [hue, saturation] = rgb2hsl(parse(properties.brand_accent_color))
if (saturation > 15) {
const distance = (h: number) => Math.min(Math.abs(h - hue), 360 - Math.abs(h - hue))
const way = logo_colourways.reduce((a, b) => distance(b[0]) < distance(a[0]) ? b : a)
style.logo_color_1 = way[1]
style.logo_color_2 = way[2]
style.logo_color_3 = way[3]
style.logo_color_4 = way[4]
}
}
if (properties.brand_danger_color) {
style.danger_color = properties.brand_danger_color
style.danger_text_color = add_contrast(properties.brand_danger_color, text_contrast)
for (const name of Object.keys(accent_hues)) {
if (properties["brand_" + name + "_color"]) {
style[name + "_color"] = properties["brand_" + name + "_color"]
}
}
if (properties.brand_background_color) {
style.background_color = properties.brand_background_color
@@ -102,6 +151,10 @@ const add_styles = (style: Style, properties: FSNodeProperties) => {
style.shaded_background = set_alpha(properties.brand_body_color, body_alpha)
style.separator = add_contrast(properties.brand_body_color, 15)
style.shadow_color = darken(properties.brand_body_color, 0.8)
// Makes the browser draw its own controls, like scrollbars and date
// pickers, in matching colours
style.color_scheme = rgb2hsl(parse(properties.brand_body_color))[2] < 50 ? "dark" : "light"
}
if (properties.brand_card_color) {
style.card_color = properties.brand_card_color
@@ -114,6 +167,45 @@ const add_styles = (style: Style, properties: FSNodeProperties) => {
}
}
// with_accents adds the accents for success, errors, warnings and notices, and
// their tints. Accents which the branding doesn't set get the saturation and
// lightness of the accent colour, so they match it. A light theme's
// accent colour is too light for text, those get fixed accents. A branded
// page always gets all accents, else a shared directory would inherit the
// accents of the visitor's own theme
const with_accents = (style: Style): Style => {
if (Object.keys(style).length === 0) {
return style
}
// Greys have no hue to match, those use the default accent colour
let [, saturation, lightness] = rgb2hsl(parse(style.accent_color ?? default_accent))
if (saturation <= 15) {
[, saturation, lightness] = rgb2hsl(parse(default_accent))
}
// Without its own background colours the branding is shown on the default
// theme, which follows the browser's preference
const light = (style.color_scheme ?? (matchMedia("(prefers-color-scheme: light)").matches ? "light" : "dark")) === "light"
const full = { ...style }
// The background of selected buttons. A subtle dark shade of the accent on
// dark themes and a light one on light themes, so the accent is readable on it
const [accent_hue, accent_saturation] = rgb2hsl(parse(style.accent_color ?? default_accent))
full.accent_shade = rgb2hex(hsl2rgb([accent_hue, Math.min(accent_saturation, 25), light ? 91 : 19]))
for (const [name, hue] of Object.entries(accent_hues)) {
let colour = style[name + "_color"]
if (!colour) {
colour = light ? light_accents[name] : rgb2hex(hsl2rgb([hue, saturation, lightness]))
}
full[name + "_color"] = colour
full[name + "_tint"] = set_alpha(colour, 0.12)
}
return full
}
const add_contrast = (color: string, amt: number) => {
let hsl = rgb2hsl(parse(color)) // Convert hex to hsl
// If the lightness is less than 40 it is considered a dark colour. This

View File

@@ -67,7 +67,7 @@ const handle_picker = async (nodes: FSNode[]) => {
}
}
let highlight_info = $state(false)
let accent_info = $state(false)
</script>
<fieldset>
@@ -93,14 +93,14 @@ let highlight_info = $state(false)
<fieldset class="grid" disabled={!enabled}>
<legend>Custom colours</legend>
<div>
<div style="display: inline-block">Highlight</div>
<HelpButton bind:toggle={highlight_info}/>
<div style="display: inline-block">Accent</div>
<HelpButton bind:toggle={accent_info}/>
</div>
<input type="color" value={options.brand_highlight_color} onchange={e => options.brand_highlight_color = (e.target as HTMLInputElement).value}/>
<input type="text" bind:value={options.brand_highlight_color}/>
{#if highlight_info}
<input type="color" value={options.brand_accent_color} onchange={e => options.brand_accent_color = (e.target as HTMLInputElement).value}/>
<input type="text" bind:value={options.brand_accent_color}/>
{#if accent_info}
<p class="span3">
The highlight colour is used for highlighting selected buttons and
The accent colour is used for highlighting selected buttons and
other elements. It's also used as the page's theme colour, this
affects things like the embed colour in Discord and the colour of
the address bar in some web browsers.
@@ -109,9 +109,6 @@ let highlight_info = $state(false)
<div>Button and input</div>
<input type="color" value={options.brand_input_color} onchange={e => options.brand_input_color = (e.target as HTMLInputElement).value}/>
<input type="text" bind:value={options.brand_input_color}/>
<div>Delete button</div>
<input type="color" value={options.brand_danger_color} onchange={e => options.brand_danger_color = (e.target as HTMLInputElement).value}/>
<input type="text" bind:value={options.brand_danger_color}/>
<div>Background</div>
<input type="color" value={options.brand_background_color} onchange={e => options.brand_background_color = (e.target as HTMLInputElement).value}/>
<input type="text" bind:value={options.brand_background_color}/>
@@ -123,6 +120,26 @@ let highlight_info = $state(false)
<input type="text" bind:value={options.brand_card_color}/>
</fieldset>
<fieldset class="grid" disabled={!enabled}>
<legend>Accent colours</legend>
<p class="span3">
Colours for success messages, errors, warnings and notices. Colours you
leave empty will be derived from the accent colour, so they match it.
</p>
<div>Success</div>
<input type="color" value={options.brand_green_color} onchange={e => options.brand_green_color = (e.target as HTMLInputElement).value}/>
<input type="text" bind:value={options.brand_green_color}/>
<div>Error</div>
<input type="color" value={options.brand_red_color} onchange={e => options.brand_red_color = (e.target as HTMLInputElement).value}/>
<input type="text" bind:value={options.brand_red_color}/>
<div>Warning</div>
<input type="color" value={options.brand_yellow_color} onchange={e => options.brand_yellow_color = (e.target as HTMLInputElement).value}/>
<input type="text" bind:value={options.brand_yellow_color}/>
<div>Notice</div>
<input type="color" value={options.brand_blue_color} onchange={e => options.brand_blue_color = (e.target as HTMLInputElement).value}/>
<input type="text" bind:value={options.brand_blue_color}/>
</fieldset>
<fieldset class="grid" disabled={!enabled}>
<legend>Image customization</legend>
<p class="span3">
@@ -174,6 +191,13 @@ let highlight_info = $state(false)
<input type="text" value="A text field"/>
</div>
<div class="example_button_row" style="gap: 4px;">
<div class="highlight_green">Success</div>
<div class="highlight_red">Error</div>
<div class="highlight_yellow">Warning</div>
<div class="highlight_blue">Notice</div>
</div>
<hr/>
<div class="example example_card">

View File

@@ -12,6 +12,13 @@ let current_theme = $state(-1)
const set_theme = (index: number) => {
current_theme = index
// Presets without accents derive them from the accent colour, the
// accents of an earlier preset shouldn't stick
properties.brand_green_color = ""
properties.brand_red_color = ""
properties.brand_yellow_color = ""
properties.brand_blue_color = ""
// Copy all the properties of the theme to the file, except for the theme
// name
Object.keys(themes[index]).forEach(key => {
@@ -25,99 +32,91 @@ const themes = [
{
name: "PD classic",
brand_input_color: "#2d2d2d",
brand_highlight_color: "#75b72d",
brand_danger_color: "#821b3f",
brand_accent_color: "#75b72d",
brand_background_color: "#141414",
brand_body_color: "#1e1e1e",
brand_card_color: "#282828",
}, {
name: "Nord (dark)",
brand_input_color: "#4f596d",
brand_highlight_color: "#a4be8c",
brand_danger_color: "#bd5f69",
brand_accent_color: "#a4be8c",
brand_background_color: "#2f3541",
brand_body_color: "#3b4252",
brand_card_color: "#434c5f",
}, {
name: "Nord (light)",
brand_input_color: "#cad2e1",
brand_highlight_color: "#a4be8c",
brand_danger_color: "#bd5f69",
brand_accent_color: "#a4be8c",
brand_background_color: "#d7dde8",
brand_body_color: "#ebeef3",
brand_card_color: "#e5e9f0",
}, {
name: "Black",
brand_input_color: "#202020",
brand_highlight_color: "#57e389",
brand_danger_color: "#ed333b",
brand_accent_color: "#57e389",
brand_background_color: "#000000",
brand_body_color: "#080808",
brand_card_color: "#101010",
}, {
name: "White",
brand_input_color: "#f4f4f4",
brand_highlight_color: "#2ec27e",
brand_danger_color: "#c01c28",
brand_accent_color: "#2ec27e",
brand_background_color: "#f8f8f8",
brand_body_color: "#ffffff",
brand_card_color: "#f0f0f0",
}, {
name: "Solarized (dark)",
brand_input_color: "#084453",
brand_highlight_color: "#849900",
brand_danger_color: "#db302d",
brand_accent_color: "#849900",
brand_background_color: "#002c38",
brand_body_color: "#063540",
brand_card_color: "#073c49",
}, {
name: "Solarized (light)",
brand_input_color: "#e7dfc5",
brand_highlight_color: "#849900",
brand_danger_color: "#db302d",
brand_accent_color: "#849900",
brand_background_color: "#ede7d3",
brand_body_color: "#fdf5e2",
brand_card_color: "#fcf2d8",
}, {
name: "Mocha",
brand_input_color: "#313244",
brand_highlight_color: "#a6e3a1",
brand_danger_color: "#f38ba8",
brand_accent_color: "#a6e3a1",
brand_background_color: "#1e1e2e",
brand_body_color: "#181825",
brand_card_color: "#313244",
}, {
name: "Dracula",
brand_input_color: "#424450",
brand_highlight_color: "#50FA7B",
brand_danger_color: "#FF5555",
brand_accent_color: "#50FA7B",
brand_green_color: "#50FA7B",
brand_red_color: "#FF5555",
brand_yellow_color: "#F1FA8C",
brand_blue_color: "#8BE9FD",
brand_background_color: "#191A21",
brand_body_color: "#21222C",
brand_card_color: "#343746",
}, {
name: "Nova",
brand_input_color: "#28033f",
brand_highlight_color: "#57e389",
brand_danger_color: "#ed333b",
brand_background_color: "#0d0015",
brand_body_color: "#130020",
brand_card_color: "#1a002b",
name: "Deep purple",
brand_input_color: "#342848",
brand_accent_color: "#c49efa",
brand_background_color: "#160f1f",
brand_body_color: "#1d1429",
brand_card_color: "#281e38",
}, {
name: "Green Phosphor",
brand_input_color: "#001e09",
brand_highlight_color: "#57e389",
brand_danger_color: "#ed333b",
brand_background_color: "#000502",
brand_body_color: "#000903",
brand_card_color: "#001707",
brand_input_color: "#263129",
brand_accent_color: "#69d38c",
brand_background_color: "#0d120e",
brand_body_color: "#141a16",
brand_card_color: "#1d261f",
}, {
name: "Amber",
brand_input_color: "#3c2b00",
brand_highlight_color: "#f5c211",
brand_danger_color: "#c01c28",
brand_background_color: "#171100",
brand_body_color: "#201700",
brand_card_color: "#281d00",
brand_input_color: "#363026",
brand_accent_color: "#e2b05a",
brand_background_color: "#16130e",
brand_body_color: "#1f1b14",
brand_card_color: "#2a251d",
}
]
</script>

View File

@@ -62,7 +62,7 @@ let {
display: flex;
flex-direction: row;
text-decoration: none;
color: var(--body_text-color);
color: var(--body_text_color);
padding: 2px;
align-items: center;
background: var(--body_background);
@@ -78,8 +78,8 @@ let {
text-decoration: none;
}
.node.node_selected {
background-color: var(--highlight_color) !important;
color: var(--highlight_text_color);
background-color: var(--accent_color) !important;
color: var(--accent_text_color);
}
.node>* {
flex: 0 0 content;

View File

@@ -290,7 +290,7 @@ const move_here = async () => {
class="container"
use:drop_target={{
upload: (files) => upload_widget.upload_files(files),
shadow: "var(--highlight_color) 0 0 10px 2px inset",
shadow: "var(--accent_color) 0 0 10px 2px inset",
}}
>
<div class="width_container bubble">
@@ -443,10 +443,7 @@ const move_here = async () => {
text-align: center;
}
.toolbar_edit {
background-color: rgba(0, 255, 0, 0.05);
}
.icon.button_highlight {
border-radius: 4px;
background-color: var(--green_tint);
}
/* Large icon mode only supported on wide screens. Hide the button on small screen */

View File

@@ -237,7 +237,4 @@ onMount(() => {
text-align: center;
font-size: 1.2em;
}
.icon.button_highlight {
border-radius: 4px;
}
</style>

View File

@@ -95,8 +95,8 @@ let {
background: var(--input_hover_background);
}
.file.selected {
background: var(--highlight_background);
color: var(--highlight_text_color);
background: var(--accent_background);
color: var(--accent_text_color);
}
.node_icon {
flex: 1 1 0;

View File

@@ -202,7 +202,7 @@ td {
}
.node {
text-decoration: none;
color: var(--body_text-color);
color: var(--body_text_color);
cursor: pointer;
}
.node:not(:last-child) {
@@ -214,8 +214,8 @@ td {
text-decoration: none;
}
.node.node_selected {
background-color: var(--highlight_color) !important;
color: var(--highlight_text_color);
background-color: var(--accent_color) !important;
color: var(--accent_text_color);
}
td {
vertical-align: middle;

View File

@@ -178,7 +178,7 @@ onMount(() => {
flex: 1 1 auto;
}
.error {
color: var(--danger_color, #c0392b);
color: var(--red_color);
}
.trash_table {
width: 100%;

View File

@@ -99,7 +99,7 @@ const cancel = () => {
align-items: center; /* Stop stretching the button */
}
.error {
background: var(--danger_color);
color: var(--highlight_text_color);
background: var(--red_tint);
color: var(--red_color);
}
</style>

View File

@@ -186,6 +186,6 @@ onMount(() => {
margin: 4px;
}
.playing {
color: var(--highlight_color);
color: var(--accent_color);
}
</style>

View File

@@ -206,7 +206,7 @@ const video_keydown = (e: KeyboardEvent) => {
.compatibility_warning {
background-color: var(--shaded_background);
backdrop-filter: blur(4px);
border-bottom: 2px solid #6666FF;
border-bottom: 2px solid var(--blue_color);
padding: 4px;
}
</style>