6 Useful Global CSS Snippets
96LawDawg edited this page 2025-11-11 10:56:07 -06:00

This page holds small CSS snippets that you might find useful when building a game. These snippets should be added under the sidebar menu option for Game Settings --> Global Room CSS. The intent is that they should require minimal modification to be used and should include an image or video of what the css does.

Shaking dice

Button border effects

Shaking dice

https://github.com/user-attachments/assets/5d89fe57-05a3-4f46-adb5-11ff1c7eeb3f

Use this with a button that has a DELAY that adds the "mixing" class to the dice and then removes the class. Dice in VTT already have a "shaking" class that is used when they are clicked so that functionality should be retained.

CSS:

/* Dice shaking */

@property --dx {
  syntax: "<length>";
  inherits: true;
  initial-value: 0px;
}
@property --dy {
  syntax: "<length>";
  inherits: true;
  initial-value: 0px;
}

.mixing {
  --dx: 0px;
  --dy: 0px;

  margin-left: var(--dx);
  margin-top:  var(--dy);

  animation: diceMix 2s ease-out forwards;
  will-change: margin-left, margin-top;
}

.mixing:nth-child(odd)  { animation-delay: 0.06s; }
.mixing:nth-child(3n)   { animation-delay: 0.12s; }

@keyframes diceMix {
  0%   { --dx: 0px;   --dy: 0px; }
  10%  { --dx: -25px; --dy:  20px; }
  20%  { --dx:  25px; --dy: -25px; }
  35%  { --dx: -18px; --dy: -12px; }
  50%  { --dx:  15px; --dy:  15px; }
  65%  { --dx: -10px; --dy:  10px; }
  80%  { --dx:  8px;  --dy: -8px; }
  90%  { --dx: 4px;   --dy: 2px; }
  100% { --dx: 0px;   --dy: 0px; }
}

clickRoutine:

   {
      "func": "SELECT",
      "type": "dice",
      "property": "id",
      "relation": "!=",
      "value": null
    },
    {
      "func": "CLICK"
    },
    {
      "func": "SET",
      "property": "classes",
      "value": "shaking mixing"
    },
    {
      "func": "SET",
      "collection": "thisButton",
      "property": "clickable",
      "value": false
    },
    {
      "func": "DELAY",
      "milliseconds": 1000
    },
    {
      "func": "SET",
      "property": "classes",
      "value": "shaking"
    },
    {
      "func": "SET",
      "collection": "thisButton",
      "property": "clickable",
      "value": true
    }

Button border effects

https://github.com/user-attachments/assets/38ee601f-5f10-44a5-bb86-ef29120234b9

Use this with a basic widget that has "classes": "borderfx". The css of the basic widget can set the button color with --baseColor and the moving light around the edge with --lightColor like the following example. Other css is standard, such as color and font-size:

  "classes": "borderfx",
  "css": {
    "--baseColor": "#152f5f",
    "--lightColor": "white",
    "color": "white",
    "font-size": "30px"
  },

This is the global CSS:

.widget.basic.borderfx {
  /* === MAIN VARIABLES === */
  --baseColor: #0099ff;
  --lightColor: #ffffff;

  /* === AUTO DERIVED SHADES === */
  --baseTop: color-mix(in srgb, var(--baseColor) 70%, white 25%);
  --baseBottom: color-mix(in srgb, var(--baseColor) 85%, black 20%);
  --borderRaised: color-mix(in srgb, var(--baseColor) 55%, black 45%);
  --borderPressed: color-mix(in srgb, var(--baseColor) 40%, white 60%);

  /* edge tracer uses this */
  --edgeColor: var(--lightColor);

  display: flex;
  justify-content: center;
  align-items: center;
  text-align: center;
  border-radius: 12px;
  border: 2px outset var(--borderRaised);
  background: linear-gradient(160deg, var(--baseTop), var(--baseBottom));
  box-shadow:
    0 4px 0 color-mix(in srgb, var(--baseColor) 30%, black 70%),
    0 6px 10px rgba(0,0,0,.6);
  cursor: pointer;
  overflow: hidden;
  transition: transform .1s ease, box-shadow .1s ease, border-color .1s ease, color .1s ease;
}

/* === PRESSED LOOK (steady glow + inset shading) === */
.widget.basic.borderfx:active {
  transform: translateY(3px);
  border: 2px inset var(--borderPressed);
  text-shadow:
    0 0 6px currentColor,
    0 0 12px color-mix(in srgb, currentColor 70%, white 30%);
  box-shadow:
    inset 0 2px 4px rgba(0,0,0,.55),
    inset 0 -2px 3px rgba(255,255,255,.15);
}

/* === EDGE TRACER LIGHT === */
.widget.basic.borderfx::before {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  pointer-events: none;
  background:
    linear-gradient(90deg,  var(--edgeColor) 0%, transparent 100%) top left    / 0% 2px no-repeat,
    linear-gradient(180deg, var(--edgeColor) 0%, transparent 100%) top right   / 2px 0% no-repeat,
    linear-gradient(270deg, var(--edgeColor) 0%, transparent 100%) bottom right/ 0% 2px no-repeat,
    linear-gradient(0deg,   var(--edgeColor) 0%, transparent 100%) bottom left / 2px 0% no-repeat;
  opacity: 0;
  transition: opacity .15s ease-out;
}

/* === HOVER ANIMATION === */
.widget.basic.borderfx:hover:not(:active)::before {
  opacity: 1;
  animation: edge-fill 1.5s linear infinite alternate;
}

/* === CLOCKWISE EDGE LIGHT === */
@keyframes edge-fill {
  0%   { background-size: 0% 3px, 3px 0%, 0% 2px, 2px 0%; }
  25%  { background-size: 100% 3px, 3px 0%, 0% 3px, 3px 0%; }
  50%  { background-size: 100% 3px, 3px 100%, 0% 3px, 3px 0%; }
  75%  { background-size: 100% 3px, 3px 100%, 100% 3px, 3px 0%; }
  100% { background-size: 100% 3px, 3px 100%, 100% 3px, 3px 100%; }
}