1
1
Fork 1
mirror of https://github.com/go-gitea/gitea.git synced 2024-05-10 05:46:08 +02:00

Improve avatar uploading / resizing / compressing, remove Fomantic card module (#24653)

Fixes: #8972
Fixes: #24263

And I think it also (partially) fix #24263 (no need to convert) ,
because users could upload any supported image format if it isn't larger
than AVATAR_MAX_ORIGIN_SIZE


The main idea: 

* if the uploaded file size is not larger than AVATAR_MAX_ORIGIN_SIZE,
use the origin
* if the resized size is larger than the origin, use the origin

Screenshots:

JPG:

<details>


![image](https://github.com/go-gitea/gitea/assets/2114189/70e98bb0-ecb9-4c4e-a89f-4a37d4e37f8e)

</details>

APNG:

<details>


![image](https://github.com/go-gitea/gitea/assets/2114189/9055135b-5e2d-4152-bd72-596fcb7c6671)


![image](https://github.com/go-gitea/gitea/assets/2114189/50364caf-f7f6-4241-a289-e485fe4cd582)

</details>

WebP (animated)

<details>


![image](https://github.com/go-gitea/gitea/assets/2114189/f642eb85-498a-49a5-86bf-0a7b04089ae0)

</details>

The only exception: if a WebP image is larger than MaxOriginSize and it
is animated, then current `webp` package can't decode it, so only in
this case it isn't supported. IMO no need to support such case: why a
user would upload a 1MB animated webp as avatar? crazy .....

---------

Co-authored-by: silverwind <me@silverwind.io>
This commit is contained in:
wxiaoguang 2023-05-14 02:59:11 +08:00 committed by GitHub
parent 9f1d377b87
commit 82224c54e0
Signed by: GitHub
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 304 additions and 1505 deletions

View File

@ -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

View File

@ -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.

View File

@ -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**: 存储仓库头像的路径。

View File

@ -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)
}

View File

@ -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("<svg></svg>")
_, 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")
}

BIN
modules/avatar/testdata/animated.webp vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

View File

@ -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"))
}

View File

@ -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")
}

View File

@ -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)

View File

@ -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)

View File

@ -7,11 +7,12 @@
<div id="profile-avatar" class="content gt-df">
{{if eq .SignedUserID .ContextUser.ID}}
<a class="image" href="{{AppSubUrl}}/user/settings" data-tooltip-content="{{.locale.Tr "user.change_avatar"}}">
{{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}}
</a>
{{else}}
<span class="image">
{{avatar $.Context .ContextUser 290}}
{{avatar $.Context .ContextUser 256}}
</span>
{{end}}
</div>

View File

@ -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 {

View File

@ -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";

View File

@ -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;
}

View File

@ -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) {

View File

@ -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/

View File

@ -23,7 +23,6 @@
"components": [
"api",
"button",
"card",
"checkbox",
"comment",
"container",