diff --git a/custom/conf/app.example.ini b/custom/conf/app.example.ini index 6e89c42c647..fc0c5021947 100644 --- a/custom/conf/app.example.ini +++ b/custom/conf/app.example.ini @@ -1773,16 +1773,19 @@ ROUTER = console ;; Max Width and Height of uploaded avatars. ;; This is to limit the amount of RAM used when resizing the image. ;AVATAR_MAX_WIDTH = 4096 -;AVATAR_MAX_HEIGHT = 3072 +;AVATAR_MAX_HEIGHT = 4096 ;; ;; The multiplication factor for rendered avatar images. ;; Larger values result in finer rendering on HiDPI devices. -;AVATAR_RENDERED_SIZE_FACTOR = 3 +;AVATAR_RENDERED_SIZE_FACTOR = 2 ;; ;; Maximum allowed file size for uploaded avatars. ;; This is to limit the amount of RAM used when resizing the image. ;AVATAR_MAX_FILE_SIZE = 1048576 ;; +;; If the uploaded file is not larger than this byte size, the image will be used as is, without resizing/converting. +;AVATAR_MAX_ORIGIN_SIZE = 262144 +;; ;; Chinese users can choose "duoshuo" ;; or a custom avatar source, like: http://cn.gravatar.com/avatar/ ;GRAVATAR_SOURCE = gravatar diff --git a/docs/content/doc/administration/config-cheat-sheet.en-us.md b/docs/content/doc/administration/config-cheat-sheet.en-us.md index af6b3a2edd5..82665d7d2c6 100644 --- a/docs/content/doc/administration/config-cheat-sheet.en-us.md +++ b/docs/content/doc/administration/config-cheat-sheet.en-us.md @@ -792,9 +792,10 @@ and - `AVATAR_STORAGE_TYPE`: **default**: Storage type defined in `[storage.xxx]`. Default is `default` which will read `[storage]` if no section `[storage]` will be a type `local`. - `AVATAR_UPLOAD_PATH`: **data/avatars**: Path to store user avatar image files. - `AVATAR_MAX_WIDTH`: **4096**: Maximum avatar image width in pixels. -- `AVATAR_MAX_HEIGHT`: **3072**: Maximum avatar image height in pixels. -- `AVATAR_MAX_FILE_SIZE`: **1048576** (1Mb): Maximum avatar image file size in bytes. -- `AVATAR_RENDERED_SIZE_FACTOR`: **3**: The multiplication factor for rendered avatar images. Larger values result in finer rendering on HiDPI devices. +- `AVATAR_MAX_HEIGHT`: **4096**: Maximum avatar image height in pixels. +- `AVATAR_MAX_FILE_SIZE`: **1048576** (1MiB): Maximum avatar image file size in bytes. +- `AVATAR_MAX_ORIGIN_SIZE`: **262144** (256KiB): If the uploaded file is not larger than this byte size, the image will be used as is, without resizing/converting. +- `AVATAR_RENDERED_SIZE_FACTOR`: **2**: The multiplication factor for rendered avatar images. Larger values result in finer rendering on HiDPI devices. - `REPOSITORY_AVATAR_STORAGE_TYPE`: **default**: Storage type defined in `[storage.xxx]`. Default is `default` which will read `[storage]` if no section `[storage]` will be a type `local`. - `REPOSITORY_AVATAR_UPLOAD_PATH`: **data/repo-avatars**: Path to store repository avatar image files. diff --git a/docs/content/doc/administration/config-cheat-sheet.zh-cn.md b/docs/content/doc/administration/config-cheat-sheet.zh-cn.md index 41eed612acc..c672b61598f 100644 --- a/docs/content/doc/administration/config-cheat-sheet.zh-cn.md +++ b/docs/content/doc/administration/config-cheat-sheet.zh-cn.md @@ -214,8 +214,8 @@ menu: - `AVATAR_STORAGE_TYPE`: **local**: 头像存储类型,可以为 `local` 或 `minio`,分别支持本地文件系统和 minio 兼容的API。 - `AVATAR_UPLOAD_PATH`: **data/avatars**: 存储头像的文件系统路径。 - `AVATAR_MAX_WIDTH`: **4096**: 头像最大宽度,单位像素。 -- `AVATAR_MAX_HEIGHT`: **3072**: 头像最大高度,单位像素。 -- `AVATAR_MAX_FILE_SIZE`: **1048576** (1Mb): 头像最大大小。 +- `AVATAR_MAX_HEIGHT`: **4096**: 头像最大高度,单位像素。 +- `AVATAR_MAX_FILE_SIZE`: **1048576** (1MiB): 头像最大大小。 - `REPOSITORY_AVATAR_STORAGE_TYPE`: **local**: 仓库头像存储类型,可以为 `local` 或 `minio`,分别支持本地文件系统和 minio 兼容的API。 - `REPOSITORY_AVATAR_UPLOAD_PATH`: **data/repo-avatars**: 存储仓库头像的路径。 diff --git a/modules/avatar/avatar.go b/modules/avatar/avatar.go index c166f144042..10de85b74eb 100644 --- a/modules/avatar/avatar.go +++ b/modules/avatar/avatar.go @@ -5,13 +5,14 @@ package avatar import ( "bytes" + "errors" "fmt" "image" "image/color" + "image/png" _ "image/gif" // for processing gif images _ "image/jpeg" // for processing jpeg images - _ "image/png" // for processing png images "code.gitea.io/gitea/modules/avatar/identicon" "code.gitea.io/gitea/modules/setting" @@ -22,8 +23,11 @@ import ( _ "golang.org/x/image/webp" // for processing webp images ) -// AvatarSize returns avatar's size -const AvatarSize = 290 +// DefaultAvatarSize is the target CSS pixel size for avatar generation. It is +// multiplied by setting.Avatar.RenderedSizeFactor and the resulting size is the +// usual size of avatar image saved on server, unless the original file is smaller +// than the size after resizing. +const DefaultAvatarSize = 256 // RandomImageSize generates and returns a random avatar image unique to input data // in custom size (height and width). @@ -39,28 +43,44 @@ func RandomImageSize(size int, data []byte) (image.Image, error) { // RandomImage generates and returns a random avatar image unique to input data // in default size (height and width). func RandomImage(data []byte) (image.Image, error) { - return RandomImageSize(AvatarSize, data) + return RandomImageSize(DefaultAvatarSize*setting.Avatar.RenderedSizeFactor, data) } -// Prepare accepts a byte slice as input, validates it contains an image of an -// acceptable format, and crops and resizes it appropriately. -func Prepare(data []byte) (*image.Image, error) { - imgCfg, _, err := image.DecodeConfig(bytes.NewReader(data)) +// processAvatarImage process the avatar image data, crop and resize it if necessary. +// the returned data could be the original image if no processing is needed. +func processAvatarImage(data []byte, maxOriginSize int64) ([]byte, error) { + imgCfg, imgType, err := image.DecodeConfig(bytes.NewReader(data)) if err != nil { - return nil, fmt.Errorf("DecodeConfig: %w", err) + return nil, fmt.Errorf("image.DecodeConfig: %w", err) } + + // for safety, only accept known types explicitly + if imgType != "png" && imgType != "jpeg" && imgType != "gif" && imgType != "webp" { + return nil, errors.New("unsupported avatar image type") + } + + // do not process image which is too large, it would consume too much memory if imgCfg.Width > setting.Avatar.MaxWidth { - return nil, fmt.Errorf("Image width is too large: %d > %d", imgCfg.Width, setting.Avatar.MaxWidth) + return nil, fmt.Errorf("image width is too large: %d > %d", imgCfg.Width, setting.Avatar.MaxWidth) } if imgCfg.Height > setting.Avatar.MaxHeight { - return nil, fmt.Errorf("Image height is too large: %d > %d", imgCfg.Height, setting.Avatar.MaxHeight) + return nil, fmt.Errorf("image height is too large: %d > %d", imgCfg.Height, setting.Avatar.MaxHeight) + } + + // If the origin is small enough, just use it, then APNG could be supported, + // otherwise, if the image is processed later, APNG loses animation. + // And one more thing, webp is not fully supported, for animated webp, image.DecodeConfig works but Decode fails. + // So for animated webp, if the uploaded file is smaller than maxOriginSize, it will be used, if it's larger, there will be an error. + if len(data) < int(maxOriginSize) { + return data, nil } img, _, err := image.Decode(bytes.NewReader(data)) if err != nil { - return nil, fmt.Errorf("Decode: %w", err) + return nil, fmt.Errorf("image.Decode: %w", err) } + // try to crop and resize the origin image if necessary if imgCfg.Width != imgCfg.Height { var newSize, ax, ay int if imgCfg.Width > imgCfg.Height { @@ -74,13 +94,33 @@ func Prepare(data []byte) (*image.Image, error) { img, err = cutter.Crop(img, cutter.Config{ Width: newSize, Height: newSize, - Anchor: image.Point{ax, ay}, + Anchor: image.Point{X: ax, Y: ay}, }) if err != nil { return nil, err } } - img = resize.Resize(AvatarSize, AvatarSize, img, resize.Bilinear) - return &img, nil + targetSize := uint(DefaultAvatarSize * setting.Avatar.RenderedSizeFactor) + img = resize.Resize(targetSize, targetSize, img, resize.Bilinear) + + // try to encode the cropped/resized image to png + bs := bytes.Buffer{} + if err = png.Encode(&bs, img); err != nil { + return nil, err + } + resized := bs.Bytes() + + // usually the png compression is not good enough, use the original image (no cropping/resizing) if the origin is smaller + if len(data) <= len(resized) { + return data, nil + } + + return resized, nil +} + +// ProcessAvatarImage process the avatar image data, crop and resize it if necessary. +// the returned data could be the original image if no processing is needed. +func ProcessAvatarImage(data []byte) ([]byte, error) { + return processAvatarImage(data, setting.Avatar.MaxOriginSize) } diff --git a/modules/avatar/avatar_test.go b/modules/avatar/avatar_test.go index 5ef4ed379be..a721c778680 100644 --- a/modules/avatar/avatar_test.go +++ b/modules/avatar/avatar_test.go @@ -4,6 +4,9 @@ package avatar import ( + "bytes" + "image" + "image/png" "os" "testing" @@ -25,49 +28,109 @@ func Test_RandomImage(t *testing.T) { assert.NoError(t, err) } -func Test_PrepareWithPNG(t *testing.T) { +func Test_ProcessAvatarPNG(t *testing.T) { setting.Avatar.MaxWidth = 4096 setting.Avatar.MaxHeight = 4096 data, err := os.ReadFile("testdata/avatar.png") assert.NoError(t, err) - imgPtr, err := Prepare(data) + _, err = processAvatarImage(data, 262144) assert.NoError(t, err) - - assert.Equal(t, 290, (*imgPtr).Bounds().Max.X) - assert.Equal(t, 290, (*imgPtr).Bounds().Max.Y) } -func Test_PrepareWithJPEG(t *testing.T) { +func Test_ProcessAvatarJPEG(t *testing.T) { setting.Avatar.MaxWidth = 4096 setting.Avatar.MaxHeight = 4096 data, err := os.ReadFile("testdata/avatar.jpeg") assert.NoError(t, err) - imgPtr, err := Prepare(data) + _, err = processAvatarImage(data, 262144) assert.NoError(t, err) - - assert.Equal(t, 290, (*imgPtr).Bounds().Max.X) - assert.Equal(t, 290, (*imgPtr).Bounds().Max.Y) } -func Test_PrepareWithInvalidImage(t *testing.T) { +func Test_ProcessAvatarInvalidData(t *testing.T) { setting.Avatar.MaxWidth = 5 setting.Avatar.MaxHeight = 5 - _, err := Prepare([]byte{}) - assert.EqualError(t, err, "DecodeConfig: image: unknown format") + _, err := processAvatarImage([]byte{}, 12800) + assert.EqualError(t, err, "image.DecodeConfig: image: unknown format") } -func Test_PrepareWithInvalidImageSize(t *testing.T) { +func Test_ProcessAvatarInvalidImageSize(t *testing.T) { setting.Avatar.MaxWidth = 5 setting.Avatar.MaxHeight = 5 data, err := os.ReadFile("testdata/avatar.png") assert.NoError(t, err) - _, err = Prepare(data) - assert.EqualError(t, err, "Image width is too large: 10 > 5") + _, err = processAvatarImage(data, 12800) + assert.EqualError(t, err, "image width is too large: 10 > 5") +} + +func Test_ProcessAvatarImage(t *testing.T) { + setting.Avatar.MaxWidth = 4096 + setting.Avatar.MaxHeight = 4096 + scaledSize := DefaultAvatarSize * setting.Avatar.RenderedSizeFactor + + newImgData := func(size int, optHeight ...int) []byte { + width := size + height := size + if len(optHeight) == 1 { + height = optHeight[0] + } + img := image.NewRGBA(image.Rect(0, 0, width, height)) + bs := bytes.Buffer{} + err := png.Encode(&bs, img) + assert.NoError(t, err) + return bs.Bytes() + } + + // if origin image canvas is too large, crop and resize it + origin := newImgData(500, 600) + result, err := processAvatarImage(origin, 0) + assert.NoError(t, err) + assert.NotEqual(t, origin, result) + decoded, err := png.Decode(bytes.NewReader(result)) + assert.NoError(t, err) + assert.EqualValues(t, scaledSize, decoded.Bounds().Max.X) + assert.EqualValues(t, scaledSize, decoded.Bounds().Max.Y) + + // if origin image is smaller than the default size, use the origin image + origin = newImgData(1) + result, err = processAvatarImage(origin, 0) + assert.NoError(t, err) + assert.Equal(t, origin, result) + + // use the origin image if the origin is smaller + origin = newImgData(scaledSize + 100) + result, err = processAvatarImage(origin, 0) + assert.NoError(t, err) + assert.Less(t, len(result), len(origin)) + + // still use the origin image if the origin doesn't exceed the max-origin-size + origin = newImgData(scaledSize + 100) + result, err = processAvatarImage(origin, 262144) + assert.NoError(t, err) + assert.Equal(t, origin, result) + + // allow to use known image format (eg: webp) if it is small enough + origin, err = os.ReadFile("testdata/animated.webp") + assert.NoError(t, err) + result, err = processAvatarImage(origin, 262144) + assert.NoError(t, err) + assert.Equal(t, origin, result) + + // do not support unknown image formats, eg: SVG may contain embedded JS + origin = []byte("") + _, err = processAvatarImage(origin, 262144) + assert.ErrorContains(t, err, "image: unknown format") + + // make sure the canvas size limit works + setting.Avatar.MaxWidth = 5 + setting.Avatar.MaxHeight = 5 + origin = newImgData(10) + _, err = processAvatarImage(origin, 262144) + assert.ErrorContains(t, err, "image width is too large: 10 > 5") } diff --git a/modules/avatar/testdata/animated.webp b/modules/avatar/testdata/animated.webp new file mode 100644 index 00000000000..4c05f4695cb Binary files /dev/null and b/modules/avatar/testdata/animated.webp differ diff --git a/modules/repository/commits_test.go b/modules/repository/commits_test.go index a407083f3a8..b6ad967d4ce 100644 --- a/modules/repository/commits_test.go +++ b/modules/repository/commits_test.go @@ -6,6 +6,7 @@ package repository import ( "crypto/md5" "fmt" + "strconv" "testing" "time" @@ -136,13 +137,11 @@ func TestPushCommits_AvatarLink(t *testing.T) { enableGravatar(t) assert.Equal(t, - "https://secure.gravatar.com/avatar/ab53a2911ddf9b4817ac01ddcd3d975f?d=identicon&s=84", + "https://secure.gravatar.com/avatar/ab53a2911ddf9b4817ac01ddcd3d975f?d=identicon&s="+strconv.Itoa(28*setting.Avatar.RenderedSizeFactor), pushCommits.AvatarLink(db.DefaultContext, "user2@example.com")) assert.Equal(t, - "https://secure.gravatar.com/avatar/"+ - fmt.Sprintf("%x", md5.Sum([]byte("nonexistent@example.com")))+ - "?d=identicon&s=84", + fmt.Sprintf("https://secure.gravatar.com/avatar/%x?d=identicon&s=%d", md5.Sum([]byte("nonexistent@example.com")), 28*setting.Avatar.RenderedSizeFactor), pushCommits.AvatarLink(db.DefaultContext, "nonexistent@example.com")) } diff --git a/modules/setting/picture.go b/modules/setting/picture.go index 6d7c8b33ce4..64d9a608e65 100644 --- a/modules/setting/picture.go +++ b/modules/setting/picture.go @@ -3,21 +3,23 @@ package setting -// settings +// Avatar settings + var ( - // Picture settings Avatar = struct { Storage MaxWidth int MaxHeight int MaxFileSize int64 + MaxOriginSize int64 RenderedSizeFactor int }{ MaxWidth: 4096, - MaxHeight: 3072, + MaxHeight: 4096, MaxFileSize: 1048576, - RenderedSizeFactor: 3, + MaxOriginSize: 262144, + RenderedSizeFactor: 2, } GravatarSource string @@ -44,9 +46,10 @@ func loadPictureFrom(rootCfg ConfigProvider) { Avatar.Storage = getStorage(rootCfg, "avatars", storageType, avatarSec) Avatar.MaxWidth = sec.Key("AVATAR_MAX_WIDTH").MustInt(4096) - Avatar.MaxHeight = sec.Key("AVATAR_MAX_HEIGHT").MustInt(3072) + Avatar.MaxHeight = sec.Key("AVATAR_MAX_HEIGHT").MustInt(4096) Avatar.MaxFileSize = sec.Key("AVATAR_MAX_FILE_SIZE").MustInt64(1048576) - Avatar.RenderedSizeFactor = sec.Key("AVATAR_RENDERED_SIZE_FACTOR").MustInt(3) + Avatar.MaxOriginSize = sec.Key("AVATAR_MAX_ORIGIN_SIZE").MustInt64(262144) + Avatar.RenderedSizeFactor = sec.Key("AVATAR_RENDERED_SIZE_FACTOR").MustInt(2) switch source := sec.Key("GRAVATAR_SOURCE").MustString("gravatar"); source { case "duoshuo": @@ -94,5 +97,5 @@ func loadRepoAvatarFrom(rootCfg ConfigProvider) { RepoAvatar.Storage = getStorage(rootCfg, "repo-avatars", storageType, repoAvatarSec) RepoAvatar.Fallback = sec.Key("REPOSITORY_AVATAR_FALLBACK").MustString("none") - RepoAvatar.FallbackImage = sec.Key("REPOSITORY_AVATAR_FALLBACK_IMAGE").MustString("/assets/img/repo_default.png") + RepoAvatar.FallbackImage = sec.Key("REPOSITORY_AVATAR_FALLBACK_IMAGE").MustString(AppSubURL + "/assets/img/repo_default.png") } diff --git a/services/repository/avatar.go b/services/repository/avatar.go index 74e5de877e0..38c2621bc4d 100644 --- a/services/repository/avatar.go +++ b/services/repository/avatar.go @@ -6,7 +6,6 @@ package repository import ( "context" "fmt" - "image/png" "io" "strconv" "strings" @@ -21,7 +20,7 @@ import ( // UploadAvatar saves custom avatar for repository. // FIXME: split uploads to different subdirs in case we have massive number of repos. func UploadAvatar(ctx context.Context, repo *repo_model.Repository, data []byte) error { - m, err := avatar.Prepare(data) + avatarData, err := avatar.ProcessAvatarImage(data) if err != nil { return err } @@ -47,9 +46,7 @@ func UploadAvatar(ctx context.Context, repo *repo_model.Repository, data []byte) } if err := storage.SaveFrom(storage.RepoAvatars, repo.CustomAvatarRelativePath(), func(w io.Writer) error { - if err := png.Encode(w, *m); err != nil { - log.Error("Encode: %v", err) - } + _, err := w.Write(avatarData) return err }); err != nil { return fmt.Errorf("UploadAvatar %s failed: Failed to remove old repo avatar %s: %w", repo.RepoPath(), newAvatar, err) diff --git a/services/user/user.go b/services/user/user.go index d52a2f404bc..5148f2168d5 100644 --- a/services/user/user.go +++ b/services/user/user.go @@ -6,7 +6,6 @@ package user import ( "context" "fmt" - "image/png" "io" "time" @@ -244,7 +243,7 @@ func DeleteInactiveUsers(ctx context.Context, olderThan time.Duration) error { // UploadAvatar saves custom avatar for user. func UploadAvatar(u *user_model.User, data []byte) error { - m, err := avatar.Prepare(data) + avatarData, err := avatar.ProcessAvatarImage(data) if err != nil { return err } @@ -262,9 +261,7 @@ func UploadAvatar(u *user_model.User, data []byte) error { } if err := storage.SaveFrom(storage.Avatars, u.CustomAvatarRelativePath(), func(w io.Writer) error { - if err := png.Encode(w, *m); err != nil { - log.Error("Encode: %v", err) - } + _, err := w.Write(avatarData) return err }); err != nil { return fmt.Errorf("Failed to create dir %s: %w", u.CustomAvatarRelativePath(), err) diff --git a/templates/user/profile.tmpl b/templates/user/profile.tmpl index 08e50fd0cee..8d172a7d02a 100644 --- a/templates/user/profile.tmpl +++ b/templates/user/profile.tmpl @@ -7,11 +7,12 @@
{{if eq .SignedUserID .ContextUser.ID}} - {{avatar $.Context .ContextUser 290}} + {{/* the size doesn't take affect (and no need to take affect), image size(width) should be controlled by the parent container since this is not a flex layout*/}} + {{avatar $.Context .ContextUser 256}} {{else}} - {{avatar $.Context .ContextUser 290}} + {{avatar $.Context .ContextUser 256}} {{end}}
diff --git a/web_src/css/base.css b/web_src/css/base.css index 507d92b0114..6c1bbb00c46 100644 --- a/web_src/css/base.css +++ b/web_src/css/base.css @@ -1046,62 +1046,6 @@ a.label, box-shadow: -1px -1px 0 0 var(--color-secondary); } -.ui.cards > .card, -.ui.card { - background: var(--color-card); - border: 1px solid var(--color-secondary); - box-shadow: none; -} - -.ui.cards > .card > .content, -.ui.card > .content { - border-color: var(--color-secondary); -} - -.ui.cards > .card > .extra, -.ui.card > .extra, -.ui.cards > .card > .extra a:not(.ui), -.ui.card > .extra a:not(.ui) { - color: var(--color-text); -} - -.ui.cards > .card > .extra a:not(.ui):hover, -.ui.card > .extra a:not(.ui):hover { - color: var(--color-primary); -} - -.ui.cards > .card > .content > .header, -.ui.card > .content > .header { - color: var(--color-text); -} - -.ui.cards > .card > .content > .description, -.ui.card > .content > .description { - color: var(--color-text); -} - -.ui.cards > .card .meta > a:not(.ui), -.ui.card .meta > a:not(.ui) { - color: var(--color-text-light-2); -} - -.ui.cards > .card .meta > a:not(.ui):hover, -.ui.card .meta > a:not(.ui):hover { - color: var(--color-text); -} - -.ui.cards a.card:hover, -a.ui.card:hover { - border: 1px solid var(--color-secondary); - background: var(--color-card); -} - -.ui.cards > .card > .extra, -.ui.card > .extra { - color: var(--color-text); - border-top-color: var(--color-secondary-light-1) !important; -} - .ui.comments .comment .text { margin: 0; } @@ -1183,12 +1127,10 @@ a.ui.card:hover { img.ui.avatar, .ui.avatar img, -.ui.avatar svg, -.ui.cards > .card img.avatar, -.ui.cards > .card .avatar img, -.ui.card img.avatar, -.ui.card .avatar img { +.ui.avatar svg { border-radius: var(--border-radius); + object-fit: contain; + aspect-ratio: 1; } .ui.divided.list > .item { diff --git a/web_src/css/index.css b/web_src/css/index.css index 6fb92f2ecb2..1723de3a2d4 100644 --- a/web_src/css/index.css +++ b/web_src/css/index.css @@ -10,6 +10,7 @@ @import "./modules/tippy.css"; @import "./modules/modal.css"; @import "./modules/breadcrumb.css"; +@import "./modules/card.css"; @import "./code/linebutton.css"; @import "./markup/content.css"; @import "./markup/codecopy.css"; diff --git a/web_src/css/modules/card.css b/web_src/css/modules/card.css new file mode 100644 index 00000000000..c0f7e83037c --- /dev/null +++ b/web_src/css/modules/card.css @@ -0,0 +1,134 @@ +/* Below styles are a subset of the full fomantic card styles which are */ +/* needed to get all current uses of fomantic cards working. */ +/* TODO: remove all these styles and use custom styling instead */ + +.ui.card:last-child { + margin-bottom: 0; +} +.ui.card:first-child { + margin-top: 0; +} + +.ui.cards > .card, +.ui.card { + display: flex; + flex-direction: column; + max-width: 100%; + width: 290px; + min-height: 0; + padding: 0; + background: var(--color-card); + border: 1px solid var(--color-secondary); + box-shadow: none; + word-wrap: break-word; +} + +.ui.card { + margin: 1em 0; +} + +.ui.cards { + display: flex; + margin: -0.875em -0.5em; + flex-wrap: wrap; +} + +.ui.cards > .card { + display: flex; + margin: 0.875em 0.5em; + float: none; +} + +.ui.cards > .card > .content, +.ui.card > .content { + border-top: 1px solid var(--color-secondary); + max-width: 100%; + padding: 1em; + font-size: 1em; +} + +.ui.cards > .card > .content > .meta + .description, +.ui.cards > .card > .content > .header + .description, +.ui.card > .content > .meta + .description, +.ui.card > .content > .header + .description { + margin-top: .5em; +} + +.ui.cards > .card > .content > .header:not(.ui), +.ui.card > .content > .header:not(.ui) { + font-weight: 500; + font-size: 1.28571429em; + margin-top: -.21425em; + line-height: 1.28571429em; +} + +.ui.cards > .card > .content:first-child, +.ui.card > .content:first-child { + border-top: none; + border-radius: var(--border-radius) var(--border-radius) 0 0; +} + +.ui.cards > .card > :last-child, +.ui.card > :last-child { + border-radius: 0 0 var(--border-radius) var(--border-radius); +} + +.ui.cards > .card > :only-child, +.ui.card > :only-child { + border-radius: var(--border-radius) !important; +} + +.ui.cards > .card > .extra, +.ui.card > .extra, +.ui.cards > .card > .extra a:not(.ui), +.ui.card > .extra a:not(.ui) { + color: var(--color-text); +} + +.ui.cards > .card > .extra a:not(.ui):hover, +.ui.card > .extra a:not(.ui):hover { + color: var(--color-primary); +} + +.ui.cards > .card > .content > .header, +.ui.card > .content > .header { + color: var(--color-text); +} + +.ui.cards > .card > .content > .description, +.ui.card > .content > .description { + color: var(--color-text); +} + +.ui.cards > .card .meta > a:not(.ui), +.ui.card .meta > a:not(.ui) { + color: var(--color-text-light-2); +} + +.ui.cards > .card .meta > a:not(.ui):hover, +.ui.card .meta > a:not(.ui):hover { + color: var(--color-text); +} + +.ui.cards a.card:hover, +a.ui.card:hover { + border: 1px solid var(--color-secondary); + background: var(--color-card); +} + +.ui.cards > .card > .extra, +.ui.card > .extra { + color: var(--color-text); + border-top-color: var(--color-secondary-light-1) !important; +} + +.ui.three.cards { + margin-left: -1em; + margin-right: -1em; +} + +.ui.three.cards > .card { + width: calc(33.33333333333333% - 2em); + margin-left: 1em; + margin-right: 1em; +} diff --git a/web_src/css/user.css b/web_src/css/user.css index 0a8b49b0387..648480d71d0 100644 --- a/web_src/css/user.css +++ b/web_src/css/user.css @@ -39,16 +39,13 @@ } .user.profile .ui.card #profile-avatar { - background: none; padding: 1rem 1rem 0.25rem; justify-content: center; } .user.profile .ui.card #profile-avatar img { - width: 100%; + max-width: 100%; height: auto; - object-fit: contain; - margin: 0; } @media (max-width: 767px) { diff --git a/web_src/fomantic/build/semantic.css b/web_src/fomantic/build/semantic.css index f48201b46a8..33d53110149 100644 --- a/web_src/fomantic/build/semantic.css +++ b/web_src/fomantic/build/semantic.css @@ -4623,1384 +4623,6 @@ /******************************* Site Overrides *******************************/ -/*! - * # Fomantic-UI - Card - * http://github.com/fomantic/Fomantic-UI/ - * - * - * Released under the MIT license - * http://opensource.org/licenses/MIT - * - */ - -/******************************* - Standard -*******************************/ - -/*-------------- - Card ----------------*/ - -.ui.cards > .card, -.ui.card { - max-width: 100%; - position: relative; - display: flex; - flex-direction: column; - width: 290px; - min-height: 0; - background: #FFFFFF; - padding: 0; - border: none; - border-radius: 0.28571429rem; - box-shadow: 0 1px 3px 0 #D4D4D5, 0 0 0 1px #D4D4D5; - transition: box-shadow 0.1s ease, transform 0.1s ease; - z-index: ''; - word-wrap: break-word; -} - -.ui.card { - margin: 1em 0; -} - -.ui.cards > .card a, -.ui.card a { - cursor: pointer; -} - -.ui.card:first-child { - margin-top: 0; -} - -.ui.card:last-child { - margin-bottom: 0; -} - -/*-------------- - Cards ----------------*/ - -.ui.cards { - display: flex; - margin: -0.875em -0.5em; - flex-wrap: wrap; -} - -.ui.cards > .card { - display: flex; - margin: 0.875em 0.5em; - float: none; -} - -/* Clearing */ - -.ui.cards:after, -.ui.card:after { - display: block; - content: ' '; - height: 0; - clear: both; - overflow: hidden; - visibility: hidden; -} - -/* Consecutive Card Groups Preserve Row Spacing */ - -.ui.cards ~ .ui.cards { - margin-top: 0.875em; -} - -/*-------------- - Rounded Edges ----------------*/ - -.ui.cards > .card > :first-child, -.ui.card > :first-child { - border-radius: 0.28571429rem 0.28571429rem 0 0 !important; - border-top: none !important; -} - -.ui.cards > .card > :last-child, -.ui.card > :last-child { - border-radius: 0 0 0.28571429rem 0.28571429rem !important; -} - -.ui.cards > .card > :only-child, -.ui.card > :only-child { - border-radius: 0.28571429rem !important; -} - -/*-------------- - Images ----------------*/ - -.ui.cards > .card > .image, -.ui.card > .image { - position: relative; - display: block; - flex: 0 0 auto; - padding: 0; - background: rgba(0, 0, 0, 0.05); -} - -.ui.cards > .card > .image > img, -.ui.card > .image > img { - display: block; - width: 100%; - height: auto; - border-radius: inherit; -} - -.ui.cards > .card > .image:not(.ui) > img, -.ui.card > .image:not(.ui) > img { - border: none; -} - -/*-------------- - Content ----------------*/ - -.ui.cards > .card > .content, -.ui.card > .content { - flex-grow: 1; - border: none; - border-top: 1px solid rgba(34, 36, 38, 0.1); - background: none; - margin: 0; - padding: 1em 1em; - box-shadow: none; - font-size: 1em; - border-radius: 0; -} - -.ui.cards > .card > .content:after, -.ui.card > .content:after { - display: block; - content: ' '; - height: 0; - clear: both; - overflow: hidden; - visibility: hidden; -} - -.ui.cards > .card > .content > .header, -.ui.card > .content > .header { - display: block; - margin: ''; - font-family: var(--fonts-regular); - color: rgba(0, 0, 0, 0.85); -} - -/* Default Header Size */ - -.ui.cards > .card > .content > .header:not(.ui), -.ui.card > .content > .header:not(.ui) { - font-weight: 500; - font-size: 1.28571429em; - margin-top: -0.21425em; - line-height: 1.28571429em; -} - -.ui.cards > .card > .content > .meta + .description, -.ui.cards > .card > .content > .header + .description, -.ui.card > .content > .meta + .description, -.ui.card > .content > .header + .description { - margin-top: 0.5em; -} - -/*---------------- - Floated Content ------------------*/ - -.ui.cards > .card [class*="left floated"], -.ui.card [class*="left floated"] { - float: left; -} - -.ui.cards > .card [class*="right floated"], -.ui.card [class*="right floated"] { - float: right; -} - -/*-------------- - Aligned ----------------*/ - -.ui.cards > .card [class*="left aligned"], -.ui.card [class*="left aligned"] { - text-align: left; -} - -.ui.cards > .card [class*="center aligned"], -.ui.card [class*="center aligned"] { - text-align: center; -} - -.ui.cards > .card [class*="right aligned"], -.ui.card [class*="right aligned"] { - text-align: right; -} - -/*-------------- - Content Image ----------------*/ - -.ui.cards > .card .content img, -.ui.card .content img { - display: inline-block; - vertical-align: middle; - width: ''; -} - -.ui.cards > .card img.avatar, -.ui.cards > .card .avatar img, -.ui.card img.avatar, -.ui.card .avatar img { - width: 2em; - height: 2em; - border-radius: 500rem; -} - -/*-------------- - Description ----------------*/ - -.ui.cards > .card > .content > .description, -.ui.card > .content > .description { - clear: both; - color: rgba(0, 0, 0, 0.68); -} - -/*-------------- - Paragraph ----------------*/ - -.ui.cards > .card > .content p, -.ui.card > .content p { - margin: 0 0 0.5em; -} - -.ui.cards > .card > .content p:last-child, -.ui.card > .content p:last-child { - margin-bottom: 0; -} - -/*-------------- - Meta ----------------*/ - -.ui.cards > .card .meta, -.ui.card .meta { - font-size: 1em; - color: rgba(0, 0, 0, 0.4); -} - -.ui.cards > .card .meta *, -.ui.card .meta * { - margin-right: 0.3em; -} - -.ui.cards > .card .meta :last-child, -.ui.card .meta :last-child { - margin-right: 0; -} - -.ui.cards > .card .meta [class*="right floated"], -.ui.card .meta [class*="right floated"] { - margin-right: 0; - margin-left: 0.3em; -} - -/*-------------- - Links ----------------*/ - -/* Generic */ - -.ui.cards > .card > .content a:not(.ui), -.ui.card > .content a:not(.ui) { - color: ''; - transition: color 0.1s ease; -} - -.ui.cards > .card > .content a:not(.ui):hover, -.ui.card > .content a:not(.ui):hover { - color: ''; -} - -/* Header */ - -.ui.cards > .card > .content > a.header, -.ui.card > .content > a.header { - color: rgba(0, 0, 0, 0.85); -} - -.ui.cards > .card > .content > a.header:hover, -.ui.card > .content > a.header:hover { - color: #1e70bf; -} - -/* Meta */ - -.ui.cards > .card .meta > a:not(.ui), -.ui.card .meta > a:not(.ui) { - color: rgba(0, 0, 0, 0.4); -} - -.ui.cards > .card .meta > a:not(.ui):hover, -.ui.card .meta > a:not(.ui):hover { - color: rgba(0, 0, 0, 0.87); -} - -/*-------------- - Buttons ----------------*/ - -.ui.cards > .card > .buttons, -.ui.card > .buttons, -.ui.cards > .card > .button, -.ui.card > .button { - margin: 0 -1px; - width: calc(100% + 2px); -} - -.ui.cards > .card > .buttons:last-child, -.ui.card > .buttons:last-child, -.ui.cards > .card > .button:last-child, -.ui.card > .button:last-child { - margin-bottom: -1px; -} - -/*-------------- - Dimmer ----------------*/ - -.ui.cards > .card .dimmer, -.ui.card .dimmer { - background: ''; - z-index: 10; -} - -/*-------------- - Labels ----------------*/ - -/*-----Star----- */ - -/* Icon */ - -.ui.cards > .card > .content .star.icon, -.ui.card > .content .star.icon { - cursor: pointer; - opacity: 0.75; - transition: color 0.1s ease; -} - -.ui.cards > .card > .content .star.icon:hover, -.ui.card > .content .star.icon:hover { - opacity: 1; - color: #FFB70A; -} - -.ui.cards > .card > .content .active.star.icon, -.ui.card > .content .active.star.icon { - color: #FFE623; -} - -/*-----Like----- */ - -/* Icon */ - -.ui.cards > .card > .content .like.icon, -.ui.card > .content .like.icon { - cursor: pointer; - opacity: 0.75; - transition: color 0.1s ease; -} - -.ui.cards > .card > .content .like.icon:hover, -.ui.card > .content .like.icon:hover { - opacity: 1; - color: #FF2733; -} - -.ui.cards > .card > .content .active.like.icon, -.ui.card > .content .active.like.icon { - color: #FF2733; -} - -/*---------------- - Extra Content ------------------*/ - -.ui.cards > .card > .extra, -.ui.card > .extra { - max-width: 100%; - min-height: 0 !important; - flex-grow: 0; - border-top: 1px solid rgba(0, 0, 0, 0.05) !important; - position: static; - background: none; - width: auto; - margin: 0 0; - padding: 0.75em 1em; - top: 0; - left: 0; - color: rgba(0, 0, 0, 0.4); - box-shadow: none; - transition: color 0.1s ease; -} - -.ui.cards > .card > .extra a:not(.ui), -.ui.card > .extra a:not(.ui) { - color: rgba(0, 0, 0, 0.4); -} - -.ui.cards > .card > .extra a:not(.ui):hover, -.ui.card > .extra a:not(.ui):hover { - color: #1e70bf; -} - -/******************************* - Variations -*******************************/ - -/*------------------- - Horizontal - --------------------*/ - -.ui.horizontal.cards > .card, -.ui.card.horizontal { - flex-direction: row; - flex-wrap: wrap; - min-width: 270px; - width: 400px; - max-width: 100%; -} - -.ui.horizontal.cards > .card > .image, -.ui.card.horizontal > .image { - border-radius: 0.28571429rem 0 0 0.28571429rem; - width: 150px; -} - -.ui.horizontal.cards > .card > .image > img, -.ui.card.horizontal > .image > img { - background-size: cover; - background-repeat: no-repeat; - background-position: center; - justify-content: center; - align-items: center; - display: flex; - width: 100%; - height: 100%; - border-radius: 0.28571429rem 0 0 0.28571429rem; -} - -.ui.horizontal.cards > .card > .image:last-child > img, -.ui.card.horizontal > .image:last-child > img { - border-radius: 0 0.28571429rem 0.28571429rem 0; -} - -.ui.horizontal.cards > .card > .content, -.ui.horizontal.card > .content { - flex-basis: 1px; -} - -.ui.horizontal.cards > .card > .extra, -.ui.horizontal.card > .extra { - flex-basis: 100%; -} - -/*------------------- - Raised - --------------------*/ - -.ui.raised.cards > .card, -.ui.raised.card { - box-shadow: 0 0 0 1px #D4D4D5, 0 2px 4px 0 rgba(34, 36, 38, 0.12), 0 2px 10px 0 rgba(34, 36, 38, 0.15); -} - -.ui.raised.cards a.card:hover, -.ui.link.cards .raised.card:hover, -a.ui.raised.card:hover, -.ui.link.raised.card:hover { - box-shadow: 0 0 0 1px #D4D4D5, 0 2px 4px 0 rgba(34, 36, 38, 0.15), 0 2px 10px 0 rgba(34, 36, 38, 0.25); -} - -/*------------------- - Centered - --------------------*/ - -.ui.centered.cards { - justify-content: center; -} - -.ui.centered.card { - margin-left: auto; - margin-right: auto; -} - -/*------------------- - Fluid - --------------------*/ - -.ui.fluid.card { - width: 100%; - max-width: 9999px; -} - -/*------------------- - Link - --------------------*/ - -.ui.cards a.card, -.ui.link.cards .card, -a.ui.card, -.ui.link.card { - transform: none; -} - -.ui.cards a.card:hover, -.ui.link.cards .card:not(.icon):hover, -a.ui.card:hover, -.ui.link.card:hover { - cursor: pointer; - z-index: 5; - background: #FFFFFF; - border: none; - box-shadow: 0 1px 3px 0 #BCBDBD, 0 0 0 1px #D4D4D5; - transform: translateY(-3px); -} - -/*------------------- - Colors ---------------------*/ - -.ui.primary.cards > .card, -.ui.cards > .primary.card, -.ui.primary.card { - box-shadow: 0 0 0 1px #D4D4D5, 0 2px 0 0 #2185D0, 0 1px 3px 0 #D4D4D5; -} - -.ui.primary.cards > .card:hover, -.ui.cards > .primary.card:hover, -.ui.primary.card:hover { - box-shadow: 0 0 0 1px #D4D4D5, 0 2px 0 0 #1678c2, 0 1px 3px 0 #BCBDBD; -} - -.ui.inverted.primary.cards > .card, -.ui.inverted.cards > .primary.card, -.ui.inverted.primary.card { - box-shadow: 0 1px 3px 0 #555555, 0 2px 0 0 #54C8FF, 0 0 0 1px #555555; -} - -.ui.inverted.primary.cards > .card:hover, -.ui.inverted.cards > .primary.card:hover, -.ui.inverted.primary.card:hover { - box-shadow: 0 1px 3px 0 #555555, 0 2px 0 0 #21b8ff, 0 0 0 1px #555555; -} - -.ui.secondary.cards > .card, -.ui.cards > .secondary.card, -.ui.secondary.card { - box-shadow: 0 0 0 1px #D4D4D5, 0 2px 0 0 #1B1C1D, 0 1px 3px 0 #D4D4D5; -} - -.ui.secondary.cards > .card:hover, -.ui.cards > .secondary.card:hover, -.ui.secondary.card:hover { - box-shadow: 0 0 0 1px #D4D4D5, 0 2px 0 0 #27292a, 0 1px 3px 0 #BCBDBD; -} - -.ui.inverted.secondary.cards > .card, -.ui.inverted.cards > .secondary.card, -.ui.inverted.secondary.card { - box-shadow: 0 1px 3px 0 #555555, 0 2px 0 0 #545454, 0 0 0 1px #555555; -} - -.ui.inverted.secondary.cards > .card:hover, -.ui.inverted.cards > .secondary.card:hover, -.ui.inverted.secondary.card:hover { - box-shadow: 0 1px 3px 0 #555555, 0 2px 0 0 #6e6e6e, 0 0 0 1px #555555; -} - -.ui.red.cards > .card, -.ui.cards > .red.card, -.ui.red.card { - box-shadow: 0 0 0 1px #D4D4D5, 0 2px 0 0 #DB2828, 0 1px 3px 0 #D4D4D5; -} - -.ui.red.cards > .card:hover, -.ui.cards > .red.card:hover, -.ui.red.card:hover { - box-shadow: 0 0 0 1px #D4D4D5, 0 2px 0 0 #d01919, 0 1px 3px 0 #BCBDBD; -} - -.ui.inverted.red.cards > .card, -.ui.inverted.cards > .red.card, -.ui.inverted.red.card { - box-shadow: 0 1px 3px 0 #555555, 0 2px 0 0 #FF695E, 0 0 0 1px #555555; -} - -.ui.inverted.red.cards > .card:hover, -.ui.inverted.cards > .red.card:hover, -.ui.inverted.red.card:hover { - box-shadow: 0 1px 3px 0 #555555, 0 2px 0 0 #ff392b, 0 0 0 1px #555555; -} - -.ui.orange.cards > .card, -.ui.cards > .orange.card, -.ui.orange.card { - box-shadow: 0 0 0 1px #D4D4D5, 0 2px 0 0 #F2711C, 0 1px 3px 0 #D4D4D5; -} - -.ui.orange.cards > .card:hover, -.ui.cards > .orange.card:hover, -.ui.orange.card:hover { - box-shadow: 0 0 0 1px #D4D4D5, 0 2px 0 0 #f26202, 0 1px 3px 0 #BCBDBD; -} - -.ui.inverted.orange.cards > .card, -.ui.inverted.cards > .orange.card, -.ui.inverted.orange.card { - box-shadow: 0 1px 3px 0 #555555, 0 2px 0 0 #FF851B, 0 0 0 1px #555555; -} - -.ui.inverted.orange.cards > .card:hover, -.ui.inverted.cards > .orange.card:hover, -.ui.inverted.orange.card:hover { - box-shadow: 0 1px 3px 0 #555555, 0 2px 0 0 #e76b00, 0 0 0 1px #555555; -} - -.ui.yellow.cards > .card, -.ui.cards > .yellow.card, -.ui.yellow.card { - box-shadow: 0 0 0 1px #D4D4D5, 0 2px 0 0 #FBBD08, 0 1px 3px 0 #D4D4D5; -} - -.ui.yellow.cards > .card:hover, -.ui.cards > .yellow.card:hover, -.ui.yellow.card:hover { - box-shadow: 0 0 0 1px #D4D4D5, 0 2px 0 0 #eaae00, 0 1px 3px 0 #BCBDBD; -} - -.ui.inverted.yellow.cards > .card, -.ui.inverted.cards > .yellow.card, -.ui.inverted.yellow.card { - box-shadow: 0 1px 3px 0 #555555, 0 2px 0 0 #FFE21F, 0 0 0 1px #555555; -} - -.ui.inverted.yellow.cards > .card:hover, -.ui.inverted.cards > .yellow.card:hover, -.ui.inverted.yellow.card:hover { - box-shadow: 0 1px 3px 0 #555555, 0 2px 0 0 #ebcd00, 0 0 0 1px #555555; -} - -.ui.olive.cards > .card, -.ui.cards > .olive.card, -.ui.olive.card { - box-shadow: 0 0 0 1px #D4D4D5, 0 2px 0 0 #B5CC18, 0 1px 3px 0 #D4D4D5; -} - -.ui.olive.cards > .card:hover, -.ui.cards > .olive.card:hover, -.ui.olive.card:hover { - box-shadow: 0 0 0 1px #D4D4D5, 0 2px 0 0 #a7bd0d, 0 1px 3px 0 #BCBDBD; -} - -.ui.inverted.olive.cards > .card, -.ui.inverted.cards > .olive.card, -.ui.inverted.olive.card { - box-shadow: 0 1px 3px 0 #555555, 0 2px 0 0 #D9E778, 0 0 0 1px #555555; -} - -.ui.inverted.olive.cards > .card:hover, -.ui.inverted.cards > .olive.card:hover, -.ui.inverted.olive.card:hover { - box-shadow: 0 1px 3px 0 #555555, 0 2px 0 0 #d2e745, 0 0 0 1px #555555; -} - -.ui.green.cards > .card, -.ui.cards > .green.card, -.ui.green.card { - box-shadow: 0 0 0 1px #D4D4D5, 0 2px 0 0 #21BA45, 0 1px 3px 0 #D4D4D5; -} - -.ui.green.cards > .card:hover, -.ui.cards > .green.card:hover, -.ui.green.card:hover { - box-shadow: 0 0 0 1px #D4D4D5, 0 2px 0 0 #16ab39, 0 1px 3px 0 #BCBDBD; -} - -.ui.inverted.green.cards > .card, -.ui.inverted.cards > .green.card, -.ui.inverted.green.card { - box-shadow: 0 1px 3px 0 #555555, 0 2px 0 0 #2ECC40, 0 0 0 1px #555555; -} - -.ui.inverted.green.cards > .card:hover, -.ui.inverted.cards > .green.card:hover, -.ui.inverted.green.card:hover { - box-shadow: 0 1px 3px 0 #555555, 0 2px 0 0 #1ea92e, 0 0 0 1px #555555; -} - -.ui.teal.cards > .card, -.ui.cards > .teal.card, -.ui.teal.card { - box-shadow: 0 0 0 1px #D4D4D5, 0 2px 0 0 #00B5AD, 0 1px 3px 0 #D4D4D5; -} - -.ui.teal.cards > .card:hover, -.ui.cards > .teal.card:hover, -.ui.teal.card:hover { - box-shadow: 0 0 0 1px #D4D4D5, 0 2px 0 0 #009c95, 0 1px 3px 0 #BCBDBD; -} - -.ui.inverted.teal.cards > .card, -.ui.inverted.cards > .teal.card, -.ui.inverted.teal.card { - box-shadow: 0 1px 3px 0 #555555, 0 2px 0 0 #6DFFFF, 0 0 0 1px #555555; -} - -.ui.inverted.teal.cards > .card:hover, -.ui.inverted.cards > .teal.card:hover, -.ui.inverted.teal.card:hover { - box-shadow: 0 1px 3px 0 #555555, 0 2px 0 0 #3affff, 0 0 0 1px #555555; -} - -.ui.blue.cards > .card, -.ui.cards > .blue.card, -.ui.blue.card { - box-shadow: 0 0 0 1px #D4D4D5, 0 2px 0 0 #2185D0, 0 1px 3px 0 #D4D4D5; -} - -.ui.blue.cards > .card:hover, -.ui.cards > .blue.card:hover, -.ui.blue.card:hover { - box-shadow: 0 0 0 1px #D4D4D5, 0 2px 0 0 #1678c2, 0 1px 3px 0 #BCBDBD; -} - -.ui.inverted.blue.cards > .card, -.ui.inverted.cards > .blue.card, -.ui.inverted.blue.card { - box-shadow: 0 1px 3px 0 #555555, 0 2px 0 0 #54C8FF, 0 0 0 1px #555555; -} - -.ui.inverted.blue.cards > .card:hover, -.ui.inverted.cards > .blue.card:hover, -.ui.inverted.blue.card:hover { - box-shadow: 0 1px 3px 0 #555555, 0 2px 0 0 #21b8ff, 0 0 0 1px #555555; -} - -.ui.violet.cards > .card, -.ui.cards > .violet.card, -.ui.violet.card { - box-shadow: 0 0 0 1px #D4D4D5, 0 2px 0 0 #6435C9, 0 1px 3px 0 #D4D4D5; -} - -.ui.violet.cards > .card:hover, -.ui.cards > .violet.card:hover, -.ui.violet.card:hover { - box-shadow: 0 0 0 1px #D4D4D5, 0 2px 0 0 #5829bb, 0 1px 3px 0 #BCBDBD; -} - -.ui.inverted.violet.cards > .card, -.ui.inverted.cards > .violet.card, -.ui.inverted.violet.card { - box-shadow: 0 1px 3px 0 #555555, 0 2px 0 0 #A291FB, 0 0 0 1px #555555; -} - -.ui.inverted.violet.cards > .card:hover, -.ui.inverted.cards > .violet.card:hover, -.ui.inverted.violet.card:hover { - box-shadow: 0 1px 3px 0 #555555, 0 2px 0 0 #745aff, 0 0 0 1px #555555; -} - -.ui.purple.cards > .card, -.ui.cards > .purple.card, -.ui.purple.card { - box-shadow: 0 0 0 1px #D4D4D5, 0 2px 0 0 #A333C8, 0 1px 3px 0 #D4D4D5; -} - -.ui.purple.cards > .card:hover, -.ui.cards > .purple.card:hover, -.ui.purple.card:hover { - box-shadow: 0 0 0 1px #D4D4D5, 0 2px 0 0 #9627ba, 0 1px 3px 0 #BCBDBD; -} - -.ui.inverted.purple.cards > .card, -.ui.inverted.cards > .purple.card, -.ui.inverted.purple.card { - box-shadow: 0 1px 3px 0 #555555, 0 2px 0 0 #DC73FF, 0 0 0 1px #555555; -} - -.ui.inverted.purple.cards > .card:hover, -.ui.inverted.cards > .purple.card:hover, -.ui.inverted.purple.card:hover { - box-shadow: 0 1px 3px 0 #555555, 0 2px 0 0 #cf40ff, 0 0 0 1px #555555; -} - -.ui.pink.cards > .card, -.ui.cards > .pink.card, -.ui.pink.card { - box-shadow: 0 0 0 1px #D4D4D5, 0 2px 0 0 #E03997, 0 1px 3px 0 #D4D4D5; -} - -.ui.pink.cards > .card:hover, -.ui.cards > .pink.card:hover, -.ui.pink.card:hover { - box-shadow: 0 0 0 1px #D4D4D5, 0 2px 0 0 #e61a8d, 0 1px 3px 0 #BCBDBD; -} - -.ui.inverted.pink.cards > .card, -.ui.inverted.cards > .pink.card, -.ui.inverted.pink.card { - box-shadow: 0 1px 3px 0 #555555, 0 2px 0 0 #FF8EDF, 0 0 0 1px #555555; -} - -.ui.inverted.pink.cards > .card:hover, -.ui.inverted.cards > .pink.card:hover, -.ui.inverted.pink.card:hover { - box-shadow: 0 1px 3px 0 #555555, 0 2px 0 0 #ff5bd1, 0 0 0 1px #555555; -} - -.ui.brown.cards > .card, -.ui.cards > .brown.card, -.ui.brown.card { - box-shadow: 0 0 0 1px #D4D4D5, 0 2px 0 0 #A5673F, 0 1px 3px 0 #D4D4D5; -} - -.ui.brown.cards > .card:hover, -.ui.cards > .brown.card:hover, -.ui.brown.card:hover { - box-shadow: 0 0 0 1px #D4D4D5, 0 2px 0 0 #975b33, 0 1px 3px 0 #BCBDBD; -} - -.ui.inverted.brown.cards > .card, -.ui.inverted.cards > .brown.card, -.ui.inverted.brown.card { - box-shadow: 0 1px 3px 0 #555555, 0 2px 0 0 #D67C1C, 0 0 0 1px #555555; -} - -.ui.inverted.brown.cards > .card:hover, -.ui.inverted.cards > .brown.card:hover, -.ui.inverted.brown.card:hover { - box-shadow: 0 1px 3px 0 #555555, 0 2px 0 0 #b0620f, 0 0 0 1px #555555; -} - -.ui.grey.cards > .card, -.ui.cards > .grey.card, -.ui.grey.card { - box-shadow: 0 0 0 1px #D4D4D5, 0 2px 0 0 #767676, 0 1px 3px 0 #D4D4D5; -} - -.ui.grey.cards > .card:hover, -.ui.cards > .grey.card:hover, -.ui.grey.card:hover { - box-shadow: 0 0 0 1px #D4D4D5, 0 2px 0 0 #838383, 0 1px 3px 0 #BCBDBD; -} - -.ui.inverted.grey.cards > .card, -.ui.inverted.cards > .grey.card, -.ui.inverted.grey.card { - box-shadow: 0 1px 3px 0 #555555, 0 2px 0 0 #DCDDDE, 0 0 0 1px #555555; -} - -.ui.inverted.grey.cards > .card:hover, -.ui.inverted.cards > .grey.card:hover, -.ui.inverted.grey.card:hover { - box-shadow: 0 1px 3px 0 #555555, 0 2px 0 0 #c2c4c5, 0 0 0 1px #555555; -} - -.ui.black.cards > .card, -.ui.cards > .black.card, -.ui.black.card { - box-shadow: 0 0 0 1px #D4D4D5, 0 2px 0 0 #1B1C1D, 0 1px 3px 0 #D4D4D5; -} - -.ui.black.cards > .card:hover, -.ui.cards > .black.card:hover, -.ui.black.card:hover { - box-shadow: 0 0 0 1px #D4D4D5, 0 2px 0 0 #27292a, 0 1px 3px 0 #BCBDBD; -} - -.ui.inverted.black.cards > .card, -.ui.inverted.cards > .black.card, -.ui.inverted.black.card { - box-shadow: 0 1px 3px 0 #555555, 0 2px 0 0 #545454, 0 0 0 1px #555555; -} - -.ui.inverted.black.cards > .card:hover, -.ui.inverted.cards > .black.card:hover, -.ui.inverted.black.card:hover { - box-shadow: 0 1px 3px 0 #555555, 0 2px 0 0 #000000, 0 0 0 1px #555555; -} - -/*-------------- - Card Count ----------------*/ - -.ui.one.cards { - margin-left: 0; - margin-right: 0; -} - -.ui.one.cards > .card { - width: 100%; -} - -.ui.two.cards { - margin-left: -1em; - margin-right: -1em; -} - -.ui.two.cards > .card { - width: calc(50% - 2em); - margin-left: 1em; - margin-right: 1em; -} - -.ui.three.cards { - margin-left: -1em; - margin-right: -1em; -} - -.ui.three.cards > .card { - width: calc(33.33333333333333% - 2em); - margin-left: 1em; - margin-right: 1em; -} - -.ui.four.cards { - margin-left: -0.75em; - margin-right: -0.75em; -} - -.ui.four.cards > .card { - width: calc(25% - 1.5em); - margin-left: 0.75em; - margin-right: 0.75em; -} - -.ui.five.cards { - margin-left: -0.75em; - margin-right: -0.75em; -} - -.ui.five.cards > .card { - width: calc(20% - 1.5em); - margin-left: 0.75em; - margin-right: 0.75em; -} - -.ui.six.cards { - margin-left: -0.75em; - margin-right: -0.75em; -} - -.ui.six.cards > .card { - width: calc(16.666666666666664% - 1.5em); - margin-left: 0.75em; - margin-right: 0.75em; -} - -.ui.seven.cards { - margin-left: -0.5em; - margin-right: -0.5em; -} - -.ui.seven.cards > .card { - width: calc(14.285714285714285% - 1em); - margin-left: 0.5em; - margin-right: 0.5em; -} - -.ui.eight.cards { - margin-left: -0.5em; - margin-right: -0.5em; -} - -.ui.eight.cards > .card { - width: calc(12.5% - 1em); - margin-left: 0.5em; - margin-right: 0.5em; - font-size: 11px; -} - -.ui.nine.cards { - margin-left: -0.5em; - margin-right: -0.5em; -} - -.ui.nine.cards > .card { - width: calc(11.11111111111111% - 1em); - margin-left: 0.5em; - margin-right: 0.5em; - font-size: 10px; -} - -.ui.ten.cards { - margin-left: -0.5em; - margin-right: -0.5em; -} - -.ui.ten.cards > .card { - width: calc(10% - 1em); - margin-left: 0.5em; - margin-right: 0.5em; -} - -/*------------------- - Doubling - --------------------*/ - -/* Mobile Only */ - -@media only screen and (max-width: 767.98px) { - .ui.two.doubling.cards { - margin-left: 0; - margin-right: 0; - } - - .ui.two.doubling.cards > .card { - width: 100%; - margin-left: 0; - margin-right: 0; - } - - .ui.three.doubling.cards { - margin-left: -1em; - margin-right: -1em; - } - - .ui.three.doubling.cards > .card { - width: calc(50% - 2em); - margin-left: 1em; - margin-right: 1em; - } - - .ui.four.doubling.cards { - margin-left: -1em; - margin-right: -1em; - } - - .ui.four.doubling.cards > .card { - width: calc(50% - 2em); - margin-left: 1em; - margin-right: 1em; - } - - .ui.five.doubling.cards { - margin-left: -1em; - margin-right: -1em; - } - - .ui.five.doubling.cards > .card { - width: calc(50% - 2em); - margin-left: 1em; - margin-right: 1em; - } - - .ui.six.doubling.cards { - margin-left: -1em; - margin-right: -1em; - } - - .ui.six.doubling.cards > .card { - width: calc(50% - 2em); - margin-left: 1em; - margin-right: 1em; - } - - .ui.seven.doubling.cards { - margin-left: -1em; - margin-right: -1em; - } - - .ui.seven.doubling.cards > .card { - width: calc(33.33333333333333% - 2em); - margin-left: 1em; - margin-right: 1em; - } - - .ui.eight.doubling.cards { - margin-left: -1em; - margin-right: -1em; - } - - .ui.eight.doubling.cards > .card { - width: calc(33.33333333333333% - 2em); - margin-left: 1em; - margin-right: 1em; - } - - .ui.nine.doubling.cards { - margin-left: -1em; - margin-right: -1em; - } - - .ui.nine.doubling.cards > .card { - width: calc(33.33333333333333% - 2em); - margin-left: 1em; - margin-right: 1em; - } - - .ui.ten.doubling.cards { - margin-left: -1em; - margin-right: -1em; - } - - .ui.ten.doubling.cards > .card { - width: calc(33.33333333333333% - 2em); - margin-left: 1em; - margin-right: 1em; - } -} - -/* Tablet Only */ - -@media only screen and (min-width: 768px) and (max-width: 991.98px) { - .ui.two.doubling.cards { - margin-left: 0; - margin-right: 0; - } - - .ui.two.doubling.cards > .card { - width: 100%; - margin-left: 0; - margin-right: 0; - } - - .ui.three.doubling.cards { - margin-left: -1em; - margin-right: -1em; - } - - .ui.three.doubling.cards > .card { - width: calc(50% - 2em); - margin-left: 1em; - margin-right: 1em; - } - - .ui.four.doubling.cards { - margin-left: -1em; - margin-right: -1em; - } - - .ui.four.doubling.cards > .card { - width: calc(50% - 2em); - margin-left: 1em; - margin-right: 1em; - } - - .ui.five.doubling.cards { - margin-left: -1em; - margin-right: -1em; - } - - .ui.five.doubling.cards > .card { - width: calc(33.33333333333333% - 2em); - margin-left: 1em; - margin-right: 1em; - } - - .ui.six.doubling.cards { - margin-left: -1em; - margin-right: -1em; - } - - .ui.six.doubling.cards > .card { - width: calc(33.33333333333333% - 2em); - margin-left: 1em; - margin-right: 1em; - } - - .ui.eight.doubling.cards { - margin-left: -1em; - margin-right: -1em; - } - - .ui.eight.doubling.cards > .card { - width: calc(33.33333333333333% - 2em); - margin-left: 1em; - margin-right: 1em; - } - - .ui.eight.doubling.cards { - margin-left: -0.75em; - margin-right: -0.75em; - } - - .ui.eight.doubling.cards > .card { - width: calc(25% - 1.5em); - margin-left: 0.75em; - margin-right: 0.75em; - } - - .ui.nine.doubling.cards { - margin-left: -0.75em; - margin-right: -0.75em; - } - - .ui.nine.doubling.cards > .card { - width: calc(25% - 1.5em); - margin-left: 0.75em; - margin-right: 0.75em; - } - - .ui.ten.doubling.cards { - margin-left: -0.75em; - margin-right: -0.75em; - } - - .ui.ten.doubling.cards > .card { - width: calc(20% - 1.5em); - margin-left: 0.75em; - margin-right: 0.75em; - } -} - -/*------------------- - Stackable - --------------------*/ - -@media only screen and (max-width: 767.98px) { - .ui.stackable.cards { - display: block !important; - } - - .ui.stackable.cards .card:first-child { - margin-top: 0 !important; - } - - .ui.stackable.cards > .card { - display: block !important; - height: auto !important; - margin: 1em 1em; - padding: 0 !important; - width: calc(100% - 2em) !important; - } -} - -/*-------------- - Size ----------------*/ - -.ui.cards > .card { - font-size: 1em; -} - -.ui.mini.cards .card { - font-size: 0.78571429rem; -} - -.ui.tiny.cards .card { - font-size: 0.85714286rem; -} - -.ui.small.cards .card { - font-size: 0.92857143rem; -} - -.ui.large.cards .card { - font-size: 1.14285714rem; -} - -.ui.big.cards .card { - font-size: 1.28571429rem; -} - -.ui.huge.cards .card { - font-size: 1.42857143rem; -} - -.ui.massive.cards .card { - font-size: 1.71428571rem; -} - -/*----------------- - Inverted - ------------------*/ - -.ui.inverted.cards > .card, -.ui.inverted.card { - background: #1B1C1D; - box-shadow: 0 1px 3px 0 #555555, 0 0 0 1px #555555; -} - -/* Content */ - -.ui.inverted.cards > .card > .content, -.ui.inverted.card > .content { - border-top: 1px solid rgba(255, 255, 255, 0.15); -} - -/* Header */ - -.ui.inverted.cards > .card > .content > .header, -.ui.inverted.card > .content > .header { - color: rgba(255, 255, 255, 0.9); -} - -/* Description */ - -.ui.inverted.cards > .card > .content > .description, -.ui.inverted.card > .content > .description { - color: rgba(255, 255, 255, 0.8); -} - -/* Meta */ - -.ui.inverted.cards > .card .meta, -.ui.inverted.card .meta { - color: rgba(255, 255, 255, 0.7); -} - -.ui.inverted.cards > .card .meta > a:not(.ui), -.ui.inverted.card .meta > a:not(.ui) { - color: rgba(255, 255, 255, 0.7); -} - -.ui.inverted.cards > .card .meta > a:not(.ui):hover, -.ui.inverted.card .meta > a:not(.ui):hover { - color: #ffffff; -} - -/* Extra */ - -.ui.inverted.cards > .card > .extra, -.ui.inverted.card > .extra { - border-top: 1px solid rgba(255, 255, 255, 0.15) !important; - color: rgba(255, 255, 255, 0.7); -} - -.ui.inverted.cards > .card > .extra a:not(.ui), -.ui.inverted.card > .extra a:not(.ui) { - color: rgba(255, 255, 255, 0.5); -} - -.ui.inverted.cards > .card > .extra a:not(.ui):hover, -.ui.inverted.card > .extra a:not(.ui):hover { - color: #1e70bf; -} - -/* Link card(s) */ - -.ui.inverted.cards a.card:hover, -.ui.inverted.link.cards .card:not(.icon):hover, -a.inverted.ui.card:hover, -.ui.inverted.link.card:hover { - background: #1B1C1D; -} - -/******************************* - Theme Overrides -*******************************/ - -/******************************* - User Variable Overrides -*******************************/ /*! * # Fomantic-UI - Checkbox * http://github.com/fomantic/Fomantic-UI/ diff --git a/web_src/fomantic/semantic.json b/web_src/fomantic/semantic.json index 738f53d2976..4a516b2c3ae 100644 --- a/web_src/fomantic/semantic.json +++ b/web_src/fomantic/semantic.json @@ -23,7 +23,6 @@ "components": [ "api", "button", - "card", "checkbox", "comment", "container",