Browse Source
* [feature] User-selectable preset themes * docs, more theme stuff * lint, tests * fix css name * correct some little issues * add another theme * fix poll background * okay last theme i swear * make retrieval of apimodel themes more conventional * preallocate stylesheet slicespull/2785/head
32 changed files with 1230 additions and 28 deletions
@ -0,0 +1,30 @@
|
||||
# Themes |
||||
|
||||
Users on your instance can select a theme for their profile from any css files present in the `web/assets/themes` directory. |
||||
|
||||
GoToSocial comes with some theme files already, but you can add more yourself by doing the following: |
||||
|
||||
1. Create a file in `web/assets/themes` called (for example) `new-theme.css`. |
||||
2. (Optional) Include the following comment at the top of your theme file to title and describe your theme: |
||||
```css |
||||
/* |
||||
theme-title: My New Theme |
||||
theme-description: This is an example theme |
||||
*/ |
||||
``` |
||||
You can use any text you like for these fields, but bear in mind whatever you write here will appear in the settings panel to help users when selecting a theme, so keep it short and sweet. |
||||
3. Fill out your custom CSS in the rest of the file. You can use one of the existing CSS files to guide you. Also see [this page](../user_guide/custom_css.md) for some rough guidelines about how to write accessible CSS. |
||||
4. Restart your instance so that the new CSS file is picked up. |
||||
|
||||
!!! info |
||||
If you're using Docker for your deployment, you can mount theme files from the host machine into your GoToSocial `web/assets/themes` directory instead, by including entries for them in the `volumes` section of your Docker configuration. |
||||
|
||||
For example, say you've created a theme on your host machine at `~/gotosocial/my-themes/new-theme.css`, you could mount that theme into the GoToSocial Docker container in the following way: |
||||
|
||||
```yaml |
||||
volumes: |
||||
[.... some other volume entries ...] |
||||
- "~/gotosocial/my-themes/new-theme.css:/gotosocial/web/assets/themes/new-theme.css" |
||||
``` |
||||
|
||||
Bear in mind if you mount an entire directory to `/gotosocial/web/assets/themes` instead of mounting individual theme files, you'll override the default themes. |
||||
|
Before Width: | Height: | Size: 137 KiB After Width: | Height: | Size: 102 KiB |
@ -0,0 +1,77 @@
|
||||
// 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/>.
|
||||
|
||||
package accounts |
||||
|
||||
import ( |
||||
"net/http" |
||||
|
||||
"github.com/gin-gonic/gin" |
||||
apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util" |
||||
"github.com/superseriousbusiness/gotosocial/internal/gtserror" |
||||
"github.com/superseriousbusiness/gotosocial/internal/oauth" |
||||
) |
||||
|
||||
// AccountThemesGETHandler swagger:operation GET /api/v1/accounts/themes accountThemes
|
||||
//
|
||||
// See preset CSS themes available to accounts on this instance.
|
||||
//
|
||||
// ---
|
||||
// tags:
|
||||
// - accounts
|
||||
//
|
||||
// produces:
|
||||
// - application/json
|
||||
//
|
||||
// security:
|
||||
// - OAuth2 Bearer:
|
||||
// - read:accounts
|
||||
//
|
||||
// responses:
|
||||
// '200':
|
||||
// name: statuses
|
||||
// description: Array of themes.
|
||||
// schema:
|
||||
// type: array
|
||||
// items:
|
||||
// "$ref": "#/definitions/theme"
|
||||
// '400':
|
||||
// description: bad request
|
||||
// '401':
|
||||
// description: unauthorized
|
||||
// '404':
|
||||
// description: not found
|
||||
// '406':
|
||||
// description: not acceptable
|
||||
// '500':
|
||||
// description: internal server error
|
||||
func (m *Module) AccountThemesGETHandler(c *gin.Context) { |
||||
_, err := oauth.Authed(c, true, true, true, true) |
||||
if err != nil { |
||||
apiutil.ErrorHandler(c, gtserror.NewErrorUnauthorized(err, err.Error()), m.processor.InstanceGetV1) |
||||
return |
||||
} |
||||
|
||||
if _, err := apiutil.NegotiateAccept(c, apiutil.JSONAcceptHeaders...); err != nil { |
||||
apiutil.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGetV1) |
||||
return |
||||
} |
||||
|
||||
// Retrieve available themes.
|
||||
themes := m.processor.Account().ThemesGet() |
||||
apiutil.JSON(c, http.StatusOK, themes) |
||||
} |
||||
@ -0,0 +1,32 @@
|
||||
// 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/>.
|
||||
|
||||
package model |
||||
|
||||
// Theme represents one user-selectable preset CSS theme.
|
||||
//
|
||||
// swagger:model theme
|
||||
type Theme struct { |
||||
// User-facing title of this theme.
|
||||
Title string `json:"title"` |
||||
|
||||
// User-facing description of this theme.
|
||||
Description string `json:"description"` |
||||
|
||||
// FileName of this theme in the themes directory.
|
||||
FileName string `json:"file_name"` |
||||
} |
||||
@ -0,0 +1,55 @@
|
||||
// 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/>.
|
||||
|
||||
package migrations |
||||
|
||||
import ( |
||||
"context" |
||||
"strings" |
||||
|
||||
"github.com/uptrace/bun" |
||||
) |
||||
|
||||
func init() { |
||||
up := func(ctx context.Context, db *bun.DB) error { |
||||
// Add theme to account settings table.
|
||||
_, err := db.ExecContext(ctx, |
||||
"ALTER TABLE ? ADD COLUMN ? TEXT", |
||||
bun.Ident("account_settings"), bun.Ident("theme"), |
||||
) |
||||
if err != nil { |
||||
e := err.Error() |
||||
if !(strings.Contains(e, "already exists") || |
||||
strings.Contains(e, "duplicate column name") || |
||||
strings.Contains(e, "SQLSTATE 42701")) { |
||||
return err |
||||
} |
||||
} |
||||
|
||||
return nil |
||||
} |
||||
|
||||
down := func(ctx context.Context, db *bun.DB) error { |
||||
return db.RunInTx(ctx, nil, func(ctx context.Context, tx bun.Tx) error { |
||||
return nil |
||||
}) |
||||
} |
||||
|
||||
if err := Migrations.Register(up, down); err != nil { |
||||
panic(err) |
||||
} |
||||
} |
||||
@ -0,0 +1,151 @@
|
||||
// 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/>.
|
||||
|
||||
package account |
||||
|
||||
import ( |
||||
"cmp" |
||||
"os" |
||||
"path/filepath" |
||||
"regexp" |
||||
"slices" |
||||
"strings" |
||||
|
||||
"codeberg.org/gruf/go-bytesize" |
||||
apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" |
||||
"github.com/superseriousbusiness/gotosocial/internal/config" |
||||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" |
||||
"github.com/superseriousbusiness/gotosocial/internal/log" |
||||
) |
||||
|
||||
var ( |
||||
themeTitleRegex = regexp.MustCompile(`(?m)^\ *theme-title:(.*)$`) |
||||
themeDescriptionRegex = regexp.MustCompile(`(?m)^\ *theme-description:(.*)$`) |
||||
) |
||||
|
||||
// GetThemes returns available account css themes.
|
||||
func (p *Processor) ThemesGet() []apimodel.Theme { |
||||
return p.converter.ThemesToAPIThemes(p.themes.SortedByTitle) |
||||
} |
||||
|
||||
// Themes represents an in-memory
|
||||
// storage structure for themes.
|
||||
type Themes struct { |
||||
// Themes sorted alphabetically
|
||||
// by title (case insensitive).
|
||||
SortedByTitle []*gtsmodel.Theme |
||||
|
||||
// ByFileName contains themes retrievable
|
||||
// by their filename eg., `light-blurple.css`.
|
||||
ByFileName map[string]*gtsmodel.Theme |
||||
} |
||||
|
||||
// PopulateThemes parses available account CSS
|
||||
// themes from the web assets themes directory.
|
||||
func PopulateThemes() *Themes { |
||||
webAssetsAbsFilePath, err := filepath.Abs(config.GetWebAssetBaseDir()) |
||||
if err != nil { |
||||
log.Panicf(nil, "error getting abs path for web assets: %v", err) |
||||
} |
||||
|
||||
themesAbsFilePath := filepath.Join(webAssetsAbsFilePath, "themes") |
||||
themesFiles, err := os.ReadDir(themesAbsFilePath) |
||||
if err != nil { |
||||
log.Warnf(nil, "error reading themes at %s: %v", themesAbsFilePath, err) |
||||
return nil |
||||
} |
||||
|
||||
themes := &Themes{ |
||||
ByFileName: make(map[string]*gtsmodel.Theme), |
||||
} |
||||
|
||||
for _, f := range themesFiles { |
||||
// Ignore nested directories.
|
||||
if f.IsDir() { |
||||
continue |
||||
} |
||||
|
||||
// Ignore weird files.
|
||||
info, err := f.Info() |
||||
if err != nil { |
||||
continue |
||||
} |
||||
|
||||
// Ignore really big files.
|
||||
if info.Size() > int64(bytesize.MiB) { |
||||
continue |
||||
} |
||||
|
||||
// Get just the name of the
|
||||
// file, eg `blurple-light.css`.
|
||||
fileName := f.Name() |
||||
|
||||
// Get just the `.css` part.
|
||||
extensionWithDot := filepath.Ext(fileName) |
||||
|
||||
// Remove any leading `.`
|
||||
extension := strings.TrimPrefix(extensionWithDot, ".") |
||||
|
||||
// Ignore non-css files.
|
||||
if extension != "css" { |
||||
continue |
||||
} |
||||
|
||||
// Load the file contents.
|
||||
path := filepath.Join(themesAbsFilePath, fileName) |
||||
contents, err := os.ReadFile(path) |
||||
if err != nil { |
||||
log.Warnf(nil, "error reading css theme at %s: %v", path, err) |
||||
continue |
||||
} |
||||
|
||||
// Try to parse a title and description
|
||||
// for this theme from the file itself.
|
||||
var themeTitle string |
||||
titleMatches := themeTitleRegex.FindSubmatch(contents) |
||||
if len(titleMatches) == 2 { |
||||
themeTitle = strings.TrimSpace(string(titleMatches[1])) |
||||
} else { |
||||
// Fall back to file name
|
||||
// without `.css` suffix.
|
||||
themeTitle = strings.TrimSuffix(fileName, ".css") |
||||
} |
||||
|
||||
var themeDescription string |
||||
descMatches := themeDescriptionRegex.FindSubmatch(contents) |
||||
if len(descMatches) == 2 { |
||||
themeDescription = strings.TrimSpace(string(descMatches[1])) |
||||
} |
||||
|
||||
theme := >smodel.Theme{ |
||||
Title: themeTitle, |
||||
Description: themeDescription, |
||||
FileName: fileName, |
||||
} |
||||
|
||||
themes.SortedByTitle = append(themes.SortedByTitle, theme) |
||||
themes.ByFileName[fileName] = theme |
||||
} |
||||
|
||||
// Sort themes alphabetically
|
||||
// by title (case insensitive).
|
||||
slices.SortFunc(themes.SortedByTitle, func(a, b *gtsmodel.Theme) int { |
||||
return cmp.Compare(strings.ToLower(a.Title), strings.ToLower(b.Title)) |
||||
}) |
||||
|
||||
return themes |
||||
} |
||||
@ -0,0 +1,52 @@
|
||||
// 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/>.
|
||||
|
||||
package account_test |
||||
|
||||
import ( |
||||
"testing" |
||||
|
||||
"github.com/stretchr/testify/suite" |
||||
"github.com/superseriousbusiness/gotosocial/internal/config" |
||||
"github.com/superseriousbusiness/gotosocial/internal/processing/account" |
||||
) |
||||
|
||||
type ThemesTestSuite struct { |
||||
AccountStandardTestSuite |
||||
} |
||||
|
||||
func (suite *ThemesTestSuite) TestPopulateThemes() { |
||||
config.SetWebAssetBaseDir("../../../web/assets") |
||||
|
||||
themes := account.PopulateThemes() |
||||
if themes == nil { |
||||
suite.FailNow("themes was nil") |
||||
} |
||||
|
||||
suite.NotEmpty(themes.SortedByTitle) |
||||
theme := themes.ByFileName["blurple-light.css"] |
||||
if theme == nil { |
||||
suite.FailNow("theme was nil") |
||||
} |
||||
suite.Equal("Blurple (light)", theme.Title) |
||||
suite.Equal("Official light blurple theme", theme.Description) |
||||
suite.Equal("blurple-light.css", theme.FileName) |
||||
} |
||||
|
||||
func TestThemesTestSuite(t *testing.T) { |
||||
suite.Run(t, new(ThemesTestSuite)) |
||||
} |
||||
@ -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); |
||||
} |
||||
@ -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); |
||||
} |
||||
@ -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); |
||||
} |
||||
@ -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); |
||||
} |
||||
@ -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); |
||||
} |
||||
@ -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; |
||||
} |
||||
Loading…
Reference in new issue