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%; }
}
Quick Links
Home
1. Basics
2. Developer Documentation
- Widgets
- Functions, automation, and routines
- Dynamic Expressions and using variables
- Math, string, array, color, JSON functions
- Cards and Decks
- Editing JSON
- Using CSS
- Fonts and Symbols
3. Available Resources
- Publicly available games
- Tutorials
- Demonstration Features
- Useful Code Snippets
- Useful Global CSS Snippets
4. Usage Guidelines and Copyrights
5. Other Technical Information
- Download Repository
- Using GitHub and creating Pull Requests
- Internals Overview
- Testing with TestCafé
- URL-addressing rooms
- Using Docker containers
- Config.json file