diff options
Diffstat (limited to 'web')
-rw-r--r-- | web/assets/themes/blurple-dark.css | 92 | ||||
-rw-r--r-- | web/assets/themes/blurple-light.css | 94 | ||||
-rw-r--r-- | web/assets/themes/midnight-trip.css | 159 | ||||
-rw-r--r-- | web/assets/themes/soft.css | 124 | ||||
-rw-r--r-- | web/assets/themes/sunset-light.css | 95 | ||||
-rw-r--r-- | web/source/settings/lib/query/user/index.ts | 7 | ||||
-rw-r--r-- | web/source/settings/lib/types/theme.ts | 24 | ||||
-rw-r--r-- | web/source/settings/style.css | 8 | ||||
-rw-r--r-- | web/source/settings/user/profile.tsx | 39 |
9 files changed, 637 insertions, 5 deletions
diff --git a/web/assets/themes/blurple-dark.css b/web/assets/themes/blurple-dark.css new file mode 100644 index 000000000..7e4a9cc9a --- /dev/null +++ b/web/assets/themes/blurple-dark.css @@ -0,0 +1,92 @@ +/* + theme-title: Blurple (dark) + theme-description: Official dark blurple theme +*/ + +:root { + /* Define our nice blurple palette */ + --blurple1: #ffffff; + --blurple2: #ebe6f8; + --blurple3: #d6cceb; + --blurple4: #c2b3e1; + --blurple5: #ad99d7; + --blurple6: #9980cd; + --blurple7: #8566c2; + --blurple8: #704db8; + --blurple9: #5c33ae; + --blurple10: #471aa4; + --blurple11: #33009a; + --blurple12: #170044; + + /* Restyle basic colors to use blurple */ + --blue1: var(--blurple1); + --blue2: var(--blurple2); + --blue3: var(--blurple3); + + /* Basic page styling (background + foreground) */ + --bg: var(--blurple12); + --bg-accent: var(--blurple11); + --fg: var(--blurple1); + --fg-reduced: var(--blurple3); + + /* Profile page styling (light) */ + --profile-bg: var(--blurple11); + + /* Blurpleize buttons */ + --button-bg: var(--blurple2); + --button-fg: var(--blurple11); + + /* Blurpleize statuses */ + --status-bg: var(--blurple11); + --status-focus-bg: var(--blurple11); + --status-info-bg: var(--blurple9); + --status-focus-info-bg: var(--blurple9); + + /* Used around statuses + other items */ + --boxshadow-border: 0.08rem solid black; +} + +/* Scroll bar */ +html, body { + scrollbar-color: var(--blurple8) var(--blurple12); +} + +/* Profile fields */ +.profile .about-user .fields .field { + border-bottom: 0.1rem solid var(--blurple8); +} +.profile .about-user .fields .field:first-child { + border-top: 0.1rem solid var(--blurple8); +} + +/* Status media */ +.status .media .media-wrapper { + border: 0.08rem solid var(--blurple9); +} +.status .media .media-wrapper details .unknown-attachment .placeholder { + color: var(--blue2); +} +.status .media .media-wrapper details video.plyr-video { + background: var(--blurple11); +} + +/* Status polls */ +.status .text .poll { + background-color: var(--bg); +} +.status .text .poll .poll-info { + background-color: var(--blurple11); +} + +/* Code snippets */ +pre, pre[class*="language-"], +code, code[class*="language-"] { + background-color: var(--blurple12); + color: var(--fg-reduced); +} + +/* Block quotes */ +blockquote { + background-color: var(--blurple12); + color: var(--fg-reduced); +} diff --git a/web/assets/themes/blurple-light.css b/web/assets/themes/blurple-light.css new file mode 100644 index 000000000..7f5811401 --- /dev/null +++ b/web/assets/themes/blurple-light.css @@ -0,0 +1,94 @@ +/* + theme-title: Blurple (light) + theme-description: Official light blurple theme +*/ + +:root { + /* Define our nice blurple palette */ + --blurple1: #ffffff; + --blurple2: #ebe6f8; + --blurple3: #d6cceb; + --blurple4: #c2b3e1; + --blurple5: #ad99d7; + --blurple6: #9980cd; + --blurple7: #8566c2; + --blurple8: #704db8; + --blurple9: #5c33ae; + --blurple10: #471aa4; + --blurple11: #33009a; + --blurple12: #170044; + + /* Restyle basic colors to use blurple */ + --white1: var(--blurple2); + --white2: var(--blurple3); + --blue1: var(--blurple6); + --blue2: var(--blurple8); + --blue3: var(--blurple10); + + /* Basic page styling (background + foreground) */ + --bg: linear-gradient(var(--blurple2), var(--blurple1)); + --bg-accent: var(--white2); + --fg: var(--gray1); + --fg-reduced: var(--gray2); + + /* Profile page styling (light) */ + --profile-bg: var(--white2); + + /* Blurpleize buttons */ + --button-bg: var(--blue2); + --button-fg: var(--white1); + + /* Blurpleize statuses */ + --status-bg: var(--white1); + --status-focus-bg: var(--white1); + --status-info-bg: var(--white2); + --status-focus-info-bg: var(--white2); + + /* Used around statuses + other items */ + --boxshadow-border: 0.08rem solid var(--blurple10); +} + +/* Scroll bar */ +html, body { + scrollbar-color: var(--blurple8) var(--blurple2); +} + +/* Profile fields */ +.profile .about-user .fields .field { + border-bottom: 0.1rem solid var(--blurple10); +} +.profile .about-user .fields .field:first-child { + border-top: 0.1rem solid var(--blurple10); +} + +/* Status media */ +.status .media .media-wrapper { + border: 0.08rem solid var(--blurple10); +} +.status .media .media-wrapper details .unknown-attachment .placeholder { + color: var(--blue2); +} +.status .media .media-wrapper details video.plyr-video { + background: var(--blurple2); +} + +/* Status polls */ +.status .text .poll { + background-color: var(--white2); +} +.status .text .poll .poll-info { + background-color: var(--white1); +} + +/* Code snippets */ +pre, pre[class*="language-"], +code, code[class*="language-"] { + background-color: var(--blurple12); + color: var(--blurple2); +} + +/* Block quotes */ +blockquote { + background-color: var(--blurple1); + color: var(--blurple12); +} diff --git a/web/assets/themes/midnight-trip.css b/web/assets/themes/midnight-trip.css new file mode 100644 index 000000000..c6b1623ee --- /dev/null +++ b/web/assets/themes/midnight-trip.css @@ -0,0 +1,159 @@ +/* + theme-title: Midnight Trip + theme-description: Woah +*/ + +/* Theme colors */ +:root { + --acid-green: rgb(63, 255, 0); + --acid-green-light: #79FF4D; + --acid-green-dark: #269900; + --magenta: rgb(153, 50, 204); + --darkred: rgb(58, 0, 15); + --darkblue: rgb(0, 0, 58); + --darkmagenta: rgb(47, 1, 65); + + /* Override */ + --orange2: var(--acid-green); + --gray1: rgb(20, 21, 23); + --blue1: var(--acid-green-dark); + --blue2: var(--acid-green-light); + --blue3: var(--acid-green); +} + +body { + background: linear-gradient(-45deg, black, var(--darkmagenta), var(--darkblue), var(--darkred)); + background-size: 400% 400%; + height: 100%; +} + +@media not (prefers-reduced-motion) { + body { + animation: gradient 30s ease infinite; + } + + @keyframes gradient { + 0% { + background-position: 0% 50%; + } + 50% { + background-position: 100% 50%; + } + 100% { + background-position: 0% 50%; + } + } +} + +html, body { + /* Funky scroll bar */ + scrollbar-color: var(--acid-green) var(--gray1); +} + +/* Instance display name */ +.page-header { + grid-column: 2; + align-self: start; + margin: 1rem 0 1rem 0; + background-color: var(--gray1); + border: 0.25rem solid var(--magenta); + border-radius: var(--br); +} + +/* Header card */ +.profile .profile-header { + background-color: var(--gray1); + border: 0.25rem solid var(--magenta); +} + +/* About + Pinned posts headers */ +.profile .col-header { + background: var(--gray1); + border: 0.25rem solid var(--magenta); +} + +.profile .about-user .col-header { + border-bottom: none; + margin-bottom: 0; +} + +/* Make about sections transparent */ +.profile .about-user .fields, .profile .about-user .bio, .profile .about-user .accountstats { + background: var(--gray1); + border-left: 0.25rem solid var(--magenta); + border-right: 0.25rem solid var(--magenta); +} + +/* Fiddle around with borders on about sections */ +.profile .about-user .fields .field:first-child { + border-top: 0.25rem dashed var(--magenta); +} +.profile .about-user .fields .field { + border-bottom: 0.25rem dashed var(--magenta); +} +.profile .about-user .accountstats { + border-top: 0.25rem dashed var(--magenta); + border-bottom: 0.25rem solid var(--magenta); +} + +/* Statuses + threads */ + +/* Thread column header */ +.thread .col-header { + background: var(--gray1); + border: 0.25rem solid var(--magenta); +} + +/* Main status body */ +.status, .status.expanded { + background: var(--gray1); + border: 0.25rem solid var(--magenta); +} + +/* Code snippets */ +.status .text .content pre, .status .text .content code { + background: black; + color: var(--white2); +} + +/* Block quotes */ +.status .text .content blockquote { + background-color: black; +} + +/* Media wrapper for attachments */ +.status .media .media-wrapper { + background: var(--bg-nearly-opaque); +} +.status .media .media-wrapper details .unknown-attachment .placeholder { + border: 0.2rem dashed var(--magenta); +} + +/* Polls */ +.status .text .poll { + background-color: black; + border: 0.25rem solid var(--magenta); +} + +.status .text .poll .poll-info { + background-color: black; +} + +/* Status info bars */ +.status .status-info, .status.expanded .status-info { + background: black; +} + +/* Back + next links */ +.backnextlinks { + background: var(--gray1); + padding: 0.5rem; + border: 0.25rem solid var(--magenta); + border-radius: var(--br); +} + +.page-footer { + margin-top: 2rem; + background-color: var(--gray1); + border-top: 0.25rem solid var(--magenta); +} diff --git a/web/assets/themes/soft.css b/web/assets/themes/soft.css new file mode 100644 index 000000000..6507fa701 --- /dev/null +++ b/web/assets/themes/soft.css @@ -0,0 +1,124 @@ +/* + theme-title: Soft + theme-description: Pastel pink and blue with dark magenta trim +*/ + +:root { + /* Define our palette */ + --soft-pink: rgb(255, 199, 234); + --soft-pink-translucent: rgb(255, 199, 234, 30%); + --soft-lilac: #D8B4F8; + --soft-lilac-translucent: rgb(216, 180, 248, 30%); + --soft-blue: #d6f1ff; + + /* Override */ + --blue1: #7f16de; + --blue2: #7514cc; + --blue3: #6b12ba; + --orange2: var(--blue1); + --br: 0.8rem; + --br-inner: 0.4rem; + + /* Basic page styling (background + foreground) */ + --bg: linear-gradient(-90deg, var(--soft-blue), var(--soft-pink), white, var(--soft-pink), var(--soft-blue)); + --bg-accent: var(--soft-pink-translucent); + --fg: var(--gray1); + --fg-reduced: var(--gray3); + + /* Profile page styling (light) */ + --profile-bg: var(--soft-pink-translucent); + + /* Statuses */ + --status-bg: var(--soft-pink-translucent); + --status-focus-bg: var(--soft-pink-translucent); + --status-info-bg: var(--soft-lilac-translucent); + --status-focus-info-bg: var(--soft-lilac-translucent); + + /* Boot-on */ + --button-fg: var(--white1); + + /* Used around statuses + other items */ + --boxshadow-border: 0.08rem solid var(--gray8); +} + +/* Scroll bar */ +html, body { + scrollbar-color: var(--orange2) var(--soft-pink); +} + +/* Header card */ +.profile .profile-header { + border: var(--boxshadow-border); +} + +.profile .profile-header .basic-info .namerole .role { + border: var(--boxshadow-border); +} + +/* About + Pinned posts headers */ +.profile .col-header { + border: var(--boxshadow-border); +} + +.profile .about-user .col-header { + margin-bottom: initial; + border-bottom: none; + border-top: var(--boxshadow-border); + border-left: var(--boxshadow-border); + border-right: var(--boxshadow-border); +} + +/* Profile fields + bio */ +.profile .about-user .fields { + border-left: var(--boxshadow-border); + border-right: var(--boxshadow-border); +} +.profile .about-user .fields .field { + border-bottom: 0.1rem dashed var(--blue3); +} +.profile .about-user .fields .field:first-child { + border-top: 0.1rem dashed var(--blue3); +} +.profile .about-user .bio { + border-left: var(--boxshadow-border); + border-right: var(--boxshadow-border); +} +.profile .about-user .accountstats { + background: var(--soft-lilac-translucent); + border-bottom: var(--boxshadow-border); + border-left: var(--boxshadow-border); + border-right: var(--boxshadow-border); + border-bottom-left-radius: var(--br); + border-bottom-right-radius: var(--br); +} + +/* Status media */ +.status .media .media-wrapper { + border: 0.08rem solid var(--blue3); +} +.status .media .media-wrapper details .unknown-attachment .placeholder { + color: var(--blue2); +} +.status .media .media-wrapper details video.plyr-video { + background: var(--soft-pink-translucent); +} + +/* Status polls */ +.status .text .poll { + background-color: var(--soft-lilac-translucent); +} +.status .text .poll .poll-info { + background: var(--bg); +} + +/* Code snippets */ +pre, pre[class*="language-"], +code, code[class*="language-"] { + background-color: var(--gray1); + color: white; +} + +/* Block quotes */ +blockquote { + background-color: var(--soft-lilac-translucent); +} diff --git a/web/assets/themes/sunset-light.css b/web/assets/themes/sunset-light.css new file mode 100644 index 000000000..c9612e36b --- /dev/null +++ b/web/assets/themes/sunset-light.css @@ -0,0 +1,95 @@ +/* + theme-title: Sunset (light) + theme-description: Official light orange/yellow theme. +*/ + +:root { + /* Define our palette */ + --eggshell: #fff6eb; + --yellow: #FFAF45; + --orange: #FB6D48; + --pink: #D74B76; + --eggplant1: #5c385e; + --eggplant2: #523254; + --eggplant3: #482c49; + --eggplant4: #29192a; + + /* Restyle basic colors */ + --white1: var(--eggshell); + --white2: var(--yellow); + --blue1: var(--eggplant1); + --blue2: var(--eggplant2); + --blue3: var(--eggplant3); + --orange2: var(--pink); + + /* Basic page styling (background + foreground) */ + --bg: linear-gradient(var(--eggplant1), var(--pink), var(--orange), var(--yellow), var(--eggshell)); + --bg-accent: var(--white2); + --fg: var(--eggplant4); + --fg-reduced: var(--eggplant3); + + /* Profile page styling (light) */ + --profile-bg: var(--white2); + + /* Buttons */ + --button-bg: var(--blue2); + --button-fg: var(--white1); + + /* Statuses */ + --status-bg: var(--white1); + --status-focus-bg: var(--white1); + --status-info-bg: var(--white2); + --status-focus-info-bg: var(--white2); + + /* Used around statuses + other items */ + --boxshadow-border: 0.08rem solid var(--orange); +} + +/* Scroll bar */ +html, body { + scrollbar-color: var(--pink) var(--eggshell); +} + +.page-header a h1 { + color: var(--eggshell); +} + +/* Profile fields */ +.profile .about-user .fields .field { + border-bottom: 0.1rem solid var(--orange); +} +.profile .about-user .fields .field:first-child { + border-top: 0.1rem solid var(--orange); +} + +/* Status media */ +.status .media .media-wrapper { + border: 0.08rem solid var(--orange); +} +.status .media .media-wrapper details .unknown-attachment .placeholder { + color: var(--blue2); +} +.status .media .media-wrapper details video.plyr-video { + background: var(--eggshell); +} + +/* Status polls */ +.status .text .poll { + background-color: var(--white2); +} +.status .text .poll .poll-info { + background-color: var(--white1); +} + +/* Code snippets */ +pre, pre[class*="language-"], +code, code[class*="language-"] { + background-color: var(--eggplant4); + color: var(--white1); +} + +/* Block quotes */ +blockquote { + background-color: var(--yellow); + color: var(--eggplant4); +} diff --git a/web/source/settings/lib/query/user/index.ts b/web/source/settings/lib/query/user/index.ts index 8cf64197b..8c4e5215b 100644 --- a/web/source/settings/lib/query/user/index.ts +++ b/web/source/settings/lib/query/user/index.ts @@ -23,6 +23,7 @@ import type { MoveAccountFormData, UpdateAliasesFormData } from "../../types/migration"; +import type { Theme } from "../../types/theme"; const extended = gtsApi.injectEndpoints({ endpoints: (build) => ({ @@ -66,6 +67,11 @@ const extended = gtsApi.injectEndpoints({ url: `/api/v1/accounts/move`, body: data }) + }), + accountThemes: build.query<Theme[], void>({ + query: () => ({ + url: `/api/v1/accounts/themes` + }) }) }) }); @@ -75,4 +81,5 @@ export const { usePasswordChangeMutation, useAliasAccountMutation, useMoveAccountMutation, + useAccountThemesQuery, } = extended; diff --git a/web/source/settings/lib/types/theme.ts b/web/source/settings/lib/types/theme.ts new file mode 100644 index 000000000..e605192f8 --- /dev/null +++ b/web/source/settings/lib/types/theme.ts @@ -0,0 +1,24 @@ +/* + GoToSocial + Copyright (C) GoToSocial Authors admin@gotosocial.org + SPDX-License-Identifier: AGPL-3.0-or-later + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +export interface Theme { + title: string; + description: string; + file_name: string; +} diff --git a/web/source/settings/style.css b/web/source/settings/style.css index 501e9dbef..372031203 100644 --- a/web/source/settings/style.css +++ b/web/source/settings/style.css @@ -439,7 +439,7 @@ section.with-sidebar > div, section.with-sidebar > form { display: grid; max-width: 60rem; grid-template-columns: 70% 30%; - grid-template-rows: 100%; + grid-template-rows: auto; gap: 1rem; .files { @@ -465,6 +465,12 @@ section.with-sidebar > div, section.with-sidebar > form { gap: 0.5rem; } } + + .theme, .form-field.radio { + display: flex; + flex-direction: column; + gap: 0.5rem; + } } .migration-details { diff --git a/web/source/settings/user/profile.tsx b/web/source/settings/user/profile.tsx index a03d4d247..cd4b17227 100644 --- a/web/source/settings/user/profile.tsx +++ b/web/source/settings/user/profile.tsx @@ -23,7 +23,8 @@ import { useTextInput, useFileInput, useBoolInput, - useFieldArrayInput + useFieldArrayInput, + useRadioInput } from "../lib/form"; import useFormSubmit from "../lib/form/submit"; @@ -33,14 +34,15 @@ import { TextInput, TextArea, FileInput, - Checkbox + Checkbox, + RadioGroup } from "../components/form/inputs"; import FormWithData from "../lib/form/form-with-data"; import FakeProfile from "../components/fake-profile"; import MutationButton from "../components/form/mutation-button"; -import { useInstanceV1Query } from "../lib/query"; +import { useAccountThemesQuery, useInstanceV1Query } from "../lib/query"; import { useUpdateCredentialsMutation } from "../lib/query/user"; import { useVerifyCredentialsQuery } from "../lib/query/oauth"; @@ -64,6 +66,7 @@ function UserProfileForm({ data: profile }) { - file header - bool enable_rss - string custom_css (if enabled) + - string theme */ const { data: instance } = useInstanceV1Query(); @@ -73,13 +76,24 @@ function UserProfileForm({ data: profile }) { maxPinnedFields: instance?.configuration?.accounts?.max_profile_fields ?? 6 }; }, [instance]); + + // Parse out available theme options into nice format. + const { data: themes } = useAccountThemesQuery(); + let themeOptions = { "": "Default" }; + themes?.forEach((theme) => { + let key = theme.file_name; + let value = theme.title; + if (theme.description) { + value += " - " + theme.description; + } + themeOptions[key] = value; + }); const form = { avatar: useFileInput("avatar", { withPreview: true }), header: useFileInput("header", { withPreview: true }), displayName: useTextInput("display_name", { source: profile }), note: useTextInput("note", { source: profile, valueSelector: (p) => p.source?.note }), - customCSS: useTextInput("custom_css", { source: profile, nosubmit: !instanceConfig.allowCustomCSS }), bot: useBoolInput("bot", { source: profile }), locked: useBoolInput("locked", { source: profile }), discoverable: useBoolInput("discoverable", { source: profile}), @@ -88,6 +102,11 @@ function UserProfileForm({ data: profile }) { defaultValue: profile?.source?.fields, length: instanceConfig.maxPinnedFields }), + customCSS: useTextInput("custom_css", { source: profile, nosubmit: !instanceConfig.allowCustomCSS }), + theme: useRadioInput("theme", { + source: profile, + options: themeOptions, + }), }; const [submitForm, result] = useFormSubmit(form, useUpdateCredentialsMutation(), { @@ -125,6 +144,18 @@ function UserProfileForm({ data: profile }) { /> </div> </div> + + <div className="theme"> + <div> + <b id="theme-label">Theme</b> + <br/> + <span>After choosing theme and saving, <a href={profile.url} target="_blank">open your profile</a> and refresh to see changes.</span> + </div> + <RadioGroup + aria-labelledby="theme-label" + field={form.theme} + /> + </div> </div> <div className="form-section-docs"> |