]> source.dussan.org Git - gitea.git/commitdiff
Improve avatar uploading / resizing / compressing, remove Fomantic card module (...
authorwxiaoguang <wxiaoguang@gmail.com>
Sat, 13 May 2023 18:59:11 +0000 (02:59 +0800)
committerGitHub <noreply@github.com>
Sat, 13 May 2023 18:59:11 +0000 (20:59 +0200)
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>
17 files changed:
custom/conf/app.example.ini
docs/content/doc/administration/config-cheat-sheet.en-us.md
docs/content/doc/administration/config-cheat-sheet.zh-cn.md
modules/avatar/avatar.go
modules/avatar/avatar_test.go
modules/avatar/testdata/animated.webp [new file with mode: 0644]
modules/repository/commits_test.go
modules/setting/picture.go
services/repository/avatar.go
services/user/user.go
templates/user/profile.tmpl
web_src/css/base.css
web_src/css/index.css
web_src/css/modules/card.css [new file with mode: 0644]
web_src/css/user.css
web_src/fomantic/build/semantic.css
web_src/fomantic/semantic.json

index 6e89c42c647c1b7c6eee4389a828ee5f8bd2ac9d..fc0c5021947607bf407b0d64f6ba2700dc265553 100644 (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
index af6b3a2edd5d7cccfe30df6426e085a4cc70f167..82665d7d2c6491fc4687ac811cc7d605e50a9fb3 100644 (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.
index 41eed612acc5fff811942474434ca88c2289f7a3..c672b61598fde98609b81f5af68eb50c24e27275 100644 (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**: 存储仓库头像的路径。
index c166f144042a945b1927a57398d706f252b12e87..10de85b74eb36cd7b07d521c3e24b368e7287f35 100644 (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)
 }
index 5ef4ed379bec8127ef37c9480bc9869a55f605cb..a721c77868076520dd17b985ff5aea1a46da757e 100644 (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")
 }
diff --git a/modules/avatar/testdata/animated.webp b/modules/avatar/testdata/animated.webp
new file mode 100644 (file)
index 0000000..4c05f46
Binary files /dev/null and b/modules/avatar/testdata/animated.webp differ
index a407083f3a891f79a71358f3f6a8cd502ba931c6..b6ad967d4ce558ee8c40380d39a7079380bc4017 100644 (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"))
 }
 
index 6d7c8b33ce46ffaf752079a5e30cc16c8820c10d..64d9a608e65104c09ec63cd1d91d8b91a39da241 100644 (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")
 }
index 74e5de877e0cacca8437c9331aa2fc4ab9b8be35..38c2621bc4d1ad21107b3a45f8b2433ac3cbb233 100644 (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)
index d52a2f404bcf0af2eae399d424cfbda60a7913de..5148f2168d5855d0e4b8fa3e9308088af7a2592c 100644 (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)
index 08e50fd0cee8db715d208d5517e4469534f2aa1e..8d172a7d02add3f43a5ee62449c4d5b83cf18bb0 100644 (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>
index 507d92b011460b6dab0c64294964572804b8d2b2..6c1bbb00c467ed46e2e527228bfbedec3cfd52a4 100644 (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 {
index 6fb92f2ecb21cc440deb8ed4624ed5f5caa5a261..1723de3a2d40340959cd6c9672ca0b3d29c49db8 100644 (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";
diff --git a/web_src/css/modules/card.css b/web_src/css/modules/card.css
new file mode 100644 (file)
index 0000000..c0f7e83
--- /dev/null
@@ -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;
+}
index 0a8b49b0387de4850406d6b1ac8b97d804c8ae04..648480d71d04c7ddda5de3f7942e1d0da8b6b549 100644 (file)
 }
 
 .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) {
index f48201b46a8145337f938df3d9ff7e14a614f887..33d53110149468afde8273c6507138ebf7d574d1 100644 (file)
 /*******************************
          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/
index 738f53d2976e080dd0e9e8e85ba3d9ab6be8489f..4a516b2c3ae3cf7de8684365c61b192c81f60054 100644 (file)
@@ -23,7 +23,6 @@
   "components": [
     "api",
     "button",
-    "card",
     "checkbox",
     "comment",
     "container",