generated from ztimson/template
Better color support, glow and simple component styling
This commit is contained in:
73
SKILL.md
73
SKILL.md
@@ -1,42 +1,54 @@
|
|||||||
---
|
---
|
||||||
name: CSS Utilities
|
name: CSS Utilities
|
||||||
description: How to style all HTML websites using @ztimson/css-utils (bootstrap inspired)
|
description: How to style all HTML websites using @ztimson/css-utils (bootstrap inspired)
|
||||||
modified: 2026-08-01T06:12:28.966Z
|
modified: 2026-08-29T00:00:00.000Z
|
||||||
---
|
---
|
||||||
|
|
||||||
# SCSS Utility Framework
|
# SCSS Utility Framework
|
||||||
|
|
||||||
A single-file (`_theme.scss`) utility-class system. No build step config needed beyond Sass with the `sass:map` module. Import once globally.
|
Two files: `_theme.scss` (utility classes) and `_components.scss` (styled elements). No build step config needed beyond Sass with the `sass:map` module. Import both once globally, theme before components.
|
||||||
|
|
||||||
## Theme Setup
|
## Theme Setup
|
||||||
|
|
||||||
All colors/tokens live in `:root` as CSS custom properties — edit these, not the utility classes:
|
All colors/tokens live in `:root` as CSS custom properties — edit these, not the utility classes:
|
||||||
|
|
||||||
|
|
||||||
```scss
|
```scss
|
||||||
--theme-background
|
--theme-background
|
||||||
|
--theme-background-contrast
|
||||||
--theme-primary
|
--theme-primary
|
||||||
|
--theme-primary-contrast
|
||||||
--theme-accent
|
--theme-accent
|
||||||
--theme-success
|
--theme-accent-contrast
|
||||||
|
|
||||||
--theme-info
|
--theme-info
|
||||||
|
--theme-success
|
||||||
--theme-warn
|
--theme-warn
|
||||||
--theme-danger
|
--theme-danger
|
||||||
--theme-black
|
--theme-black
|
||||||
--theme-white
|
--theme-white
|
||||||
|
|
||||||
|
--theme-animation
|
||||||
|
--theme-content-width
|
||||||
|
--theme-font
|
||||||
|
--theme-radius
|
||||||
|
|
||||||
// Light/dark aware variables (auto via `light-dark()`):
|
// Light/dark aware variables (auto via `light-dark()`):
|
||||||
--theme-surface
|
--theme-surface
|
||||||
--theme-border
|
--theme-border
|
||||||
--theme-text
|
|
||||||
--theme-muted
|
--theme-muted
|
||||||
--theme-lighter (transparent)
|
--theme-text
|
||||||
--theme-darker (transparent)
|
--theme-darker (transparent overlay)
|
||||||
|
--theme-lighter (transparent overlay)
|
||||||
|
--theme-shade // = darker in light mode, lighter in dark mode
|
||||||
|
--theme-shade-alt // inverse of --theme-shade
|
||||||
|
|
||||||
// Breakpoints & spacing
|
// Breakpoints & spacing
|
||||||
$breakpoints: (xs: 0, sm: 576px, md: 768px, lg: 992px, xl: 1200px);
|
$breakpoints: (xs: 0, sm: 576px, md: 768px, lg: 992px, xl: 1200px);
|
||||||
$sizes: (0: 0, 1: 0.25rem, 2: 0.5rem, 3: 1rem, 4: 1.5rem, 5: 2.5rem);
|
$sizes: (0: 0, 1: 0.25rem, 2: 0.5rem, 3: 1rem, 4: 1.5rem, 5: 2.5rem);
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Every color in `$variant-colors` (`primary, accent, background, surface, info, success, warn, danger, muted, border, text, black, white`) auto-generates `--theme-{color}-lighter` / `-darker` CSS vars (`color-mix` 80% toward white/black). These back the `-lighter`/`-darker` utility classes below — don't hand-write them.
|
||||||
|
|
||||||
Force a scheme on any element: `.theme-light` / `.theme-dark`, defaults to system preference.
|
Force a scheme on any element: `.theme-light` / `.theme-dark`, defaults to system preference.
|
||||||
|
|
||||||
## "Fix" Classes (reboot/reset helpers)
|
## "Fix" Classes (reboot/reset helpers)
|
||||||
@@ -44,14 +56,14 @@ Force a scheme on any element: `.theme-light` / `.theme-dark`, defaults to syste
|
|||||||
Apply `.fix` on `body`/root wrapper for all resets, or pick individual ones:
|
Apply `.fix` on `body`/root wrapper for all resets, or pick individual ones:
|
||||||
|
|
||||||
| Class | Purpose |
|
| Class | Purpose |
|
||||||
|------------------|-------------------------------------------------------------|
|
|------------------|---------------------------------------------------------------|
|
||||||
| `.fix-anchor` | Styled `<a>` links using `--theme-primary` |
|
| `.fix-anchor` | Styled `<a>` links using `--theme-primary` |
|
||||||
| `.fix-button` | `cursor: pointer` on enabled buttons |
|
| `.fix-button` | `cursor: pointer` on enabled buttons |
|
||||||
| `.fix-dom` | 100% height/width, no margin/padding, box-sizing border-box |
|
| `.fix-dom` | 100% height/width, no margin/padding, box-sizing border-box |
|
||||||
| `.fix-focus` | Removes outline/tap highlight |
|
| `.fix-focus` | Removes outline/tap highlight |
|
||||||
| `.fix-font` | Base font sizing/weights for headings & form elements |
|
| `.fix-font` | Base font sizing/weights for headings & form elements |
|
||||||
| `.fix-scrollbar` | Styled webkit scrollbars using `--theme-muted` |
|
| `.fix-scrollbar` | Styled webkit scrollbars using `--theme-muted` |
|
||||||
| `.fix` | All of the above |
|
| `.fix` | All of the above |
|
||||||
|
|
||||||
Other misc: `.center` (absolute centering), `.clamp` (max content width), `.reset` (`all: revert`).
|
Other misc: `.center` (absolute centering), `.clamp` (max content width), `.reset` (`all: revert`).
|
||||||
|
|
||||||
@@ -59,11 +71,18 @@ Other misc: `.center` (absolute centering), `.clamp` (max content width), `.rese
|
|||||||
|
|
||||||
Pattern: `.b-{color}` (border), `.bg-{color}` (background + auto contrast text), `.fg-{color}` (text color).
|
Pattern: `.b-{color}` (border), `.bg-{color}` (background + auto contrast text), `.fg-{color}` (text color).
|
||||||
|
|
||||||
Colors: `black, white, background, background-contrast, primary, primary-contrast, accent, accent-contrast, surface, border, text, muted, lighter, darker, info, success, warn, danger`
|
Base colors: `black, white, background, background-contrast, primary, primary-contrast, accent, accent-contrast, surface, border, text, muted, lighter, darker, info, success, warn, danger`
|
||||||
|
|
||||||
Variants: `.bg-{color}-lighter` / `.bg-{color}-darker` (filtered overlay) for `primary, accent, background, surface, info, success, warn, danger, muted`.
|
- `primary` and `accent` auto-apply their `-contrast` color as text/decoration when used via `.bg-primary` / `.bg-accent`.
|
||||||
|
- `lighter` / `darker` here are the transparent shade overlays, not color variants.
|
||||||
|
|
||||||
`.bg-transparent` also available.
|
**Lighter/darker color variants** (`.bg-{color}-lighter`, `.bg-{color}-darker`, `.fg-{color}-lighter`, `.fg-{color}-darker` — no border variant) for every color in `$variant-colors`: `primary, accent, background, surface, info, success, warn, danger, muted, border, text, black, white`.
|
||||||
|
|
||||||
|
`.bg-shade` / `.bg-shade-alt` (scheme-aware shade), `.bg-transparent`.
|
||||||
|
|
||||||
|
### Glow
|
||||||
|
|
||||||
|
`.bg-glow-{primary|accent|info|success|warn|danger}{,-sm,-lg}` — radial glow from top-left corner, spread fixed at 110%, intensity varies by suffix (`-sm`=25%, none=50%, `-lg`=75%). Combine with any background: `.bg-black.bg-glow-primary-lg`.
|
||||||
|
|
||||||
## Typography (`.fs-*`)
|
## Typography (`.fs-*`)
|
||||||
|
|
||||||
@@ -117,10 +136,36 @@ Per breakpoint, uses `$sizes` keys (0–5):
|
|||||||
- `.z-{0-5}`
|
- `.z-{0-5}`
|
||||||
- `.curs-{name}` — full CSS cursor keyword list (pointer, grab, not-allowed, zoom-in, etc.)
|
- `.curs-{name}` — full CSS cursor keyword list (pointer, grab, not-allowed, zoom-in, etc.)
|
||||||
- `.hover:hover`, `.curs-hover` — brightness(125%) on hover
|
- `.hover:hover`, `.curs-hover` — brightness(125%) on hover
|
||||||
- `.align-x, .align-x-start, .align-x-end` (text-align), `.align-y*` (vertical-align)
|
|
||||||
|
|
||||||
## Responsive Breakpoints
|
## Responsive Breakpoints
|
||||||
|
|
||||||
Every layout/spacing/flex utility above is generated per breakpoint suffix: `-sm-, -md-, -lg-, -xl-` (min-width media queries) plus a `-print-` variant for `@media print`. No suffix = base/xs.
|
Every layout/spacing/flex utility above is generated per breakpoint suffix: `-sm-, -md-, -lg-, -xl-` (min-width media queries) plus a `-print-` variant for `@media print`. No suffix = base/xs.
|
||||||
|
|
||||||
Example: `w-md-50` → 50% width from `md` breakpoint up. `d-lg-flex` → flex display from `lg` up.
|
Example: `w-md-50` → 50% width from `md` breakpoint up. `d-lg-flex` → flex display from `lg` up.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Component Classes
|
||||||
|
|
||||||
|
Styled elements from `_components.scss`. Most support color variants by adding a modifier class matching a theme color: `info, success, warn, danger, primary, accent, black, white` (component-dependent, see below). No background/divider overrides needed — components are seamless with theme surfaces.
|
||||||
|
|
||||||
|
| Class | Element | Notes |
|
||||||
|
|---|---|---|
|
||||||
|
| `.banner` | `div` | Alert/notice box. Default `info`. Variants: `success, warn, danger, info, primary, accent, black, white`. |
|
||||||
|
| `.button` | `button` | Default neutral (`--theme-shade`). Variants: `info, success, danger, warn, primary, accent, white, black`. |
|
||||||
|
| `.card` | `div` | Surface panel, padding + subtle shadow. |
|
||||||
|
| `.checkbox` | `label > input[type=checkbox] + span` | Custom check, disabled fades label text. |
|
||||||
|
| `.radio` | `label > input[type=radio] + span` | Custom radio dot, disabled fades label text. |
|
||||||
|
| `.chip` | `span`/`div` | Small pill badge, muted text. |
|
||||||
|
| `.expander` | `details.expander > summary + .expander-body` | Must use native `<details>/<summary>`; marker rotates on `[open]`, body has top border. |
|
||||||
|
| `.indicator` | `div`/`span` | Round icon/status badge. Default `info`. Variants: `warn, danger, success, primary, accent, white, black`. |
|
||||||
|
| `.input` | `input`, `textarea.input` | Surface form control. `textarea.input` is vertically resizable. `input[type=color].input` styled swatch. |
|
||||||
|
| `.input-group` | `div > .input/.button/.select` children | Bootstrap-style joined controls, no inner borders/dividers; colored `.button` children keep their own bg/text. |
|
||||||
|
| `.select` | `select.select` | Styled dropdown; `[multiple]` gets scrollable option list with hover/checked highlighting. |
|
||||||
|
| `.range` | `input[type=range].range` | Custom track + thumb, themed via `--theme-accent`. |
|
||||||
|
| `.table` | `table.table` | Bordered table. `.select` modifier = clickable rows with accent hover highlight. `.striped` modifier = zebra rows. |
|
||||||
|
| `.toggle` | `label > input[type=checkbox] + span` | Switch-style checkbox. |
|
||||||
|
|
||||||
|
**Disabled state:** form controls (`.input`, `.select`, `.range`, `.checkbox`/`.radio`/`.toggle` inputs) fade to 50% opacity and mute their label `span` automatically via `:disabled`.
|
||||||
|
|
||||||
|
**Color variant convention:** where supported, just add the theme color name as a class alongside the component class, e.g. `<div class="banner danger">`, `<button class="button primary">`, `<span class="indicator success">`.
|
||||||
|
|||||||
1092
index.html
1092
index.html
File diff suppressed because it is too large
Load Diff
@@ -1,10 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "@ztimson/css-utils",
|
"name": "@ztimson/css-utils",
|
||||||
"version": "2.1.0",
|
"version": "2.2.0",
|
||||||
"description": "CSS Utility Classes",
|
"description": "CSS Utility Classes",
|
||||||
"readme": "README.md",
|
"readme": "README.md",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "npx sass src/main.scss > dist/css-utils.css && npx sass src/main.scss --style compressed > dist/css-utils.min.css"
|
"build": "npm run build:utils && npm run build:comp",
|
||||||
|
"build:comp": "npx sass src/components.scss > dist/components.css && npx sass src/components.scss --style compressed > dist/components.min.css",
|
||||||
|
"build:utils": "npx sass src/utils.scss > dist/css-utils.css && npx sass src/utils.scss --style compressed > dist/css-utils.min.css"
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
|||||||
429
src/components.scss
Normal file
429
src/components.scss
Normal file
@@ -0,0 +1,429 @@
|
|||||||
|
@use 'sass:map';
|
||||||
|
|
||||||
|
// Shared bits ================================================================
|
||||||
|
|
||||||
|
%surface {
|
||||||
|
background: var(--theme-darker);
|
||||||
|
border: 0.5px solid var(--theme-border);
|
||||||
|
border-radius: var(--theme-radius);
|
||||||
|
color: var(--theme-text);
|
||||||
|
}
|
||||||
|
|
||||||
|
%disabled-control {
|
||||||
|
&:disabled {
|
||||||
|
cursor: not-allowed;
|
||||||
|
opacity: .5;
|
||||||
|
& ~ span { color: var(--theme-muted); cursor: not-allowed; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// $text lets black/white variants flip contrast; everything else defaults to white
|
||||||
|
@mixin color-variant($color, $text: white) {
|
||||||
|
&.#{$color} {
|
||||||
|
--color: var(--theme-#{$color});
|
||||||
|
--text: #{$text};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// $mix is the color the tint blends toward; black variant needs to blend toward
|
||||||
|
// white instead of black or the background goes fully black (invisible text)
|
||||||
|
@mixin indicator-variant($color, $mix: black) {
|
||||||
|
&.#{$color} {
|
||||||
|
--background: color-mix(in srgb, var(--theme-#{$color}) 20%, #{$mix});
|
||||||
|
--color: var(--theme-#{$color});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Checkbox/radio share hover-halo + checked/disabled behavior, differ only in shape
|
||||||
|
@mixin control-input($radius) {
|
||||||
|
appearance: none;
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
position: relative;
|
||||||
|
border: 2px solid var(--theme-border);
|
||||||
|
border-radius: $radius;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: border-color 0.2s;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
inset: -10px;
|
||||||
|
border-radius: $radius;
|
||||||
|
background: transparent;
|
||||||
|
transition: background 0.2s;
|
||||||
|
}
|
||||||
|
&:hover::before { background: var(--theme-shade); }
|
||||||
|
&:checked { border-color: var(--theme-accent); }
|
||||||
|
@include disableable-input;
|
||||||
|
}
|
||||||
|
|
||||||
|
@mixin disableable-input {
|
||||||
|
&:disabled {
|
||||||
|
opacity: .5;
|
||||||
|
cursor: not-allowed;
|
||||||
|
& ~ span { color: var(--theme-muted); cursor: not-allowed; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Components ==================================================================
|
||||||
|
|
||||||
|
.banner {
|
||||||
|
--color: var(--theme-info);
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
padding: 12px 16px;
|
||||||
|
border-radius: var(--theme-radius);
|
||||||
|
border: 0.5px solid var(--color);
|
||||||
|
background: color-mix(in srgb, var(--color) 12%, var(--theme-surface));
|
||||||
|
color: var(--theme-text);
|
||||||
|
|
||||||
|
@each $c in (success, warn, danger, info, primary, accent, "black", "white") {
|
||||||
|
&.#{$c} { --color: var(--theme-#{$c}); }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.button {
|
||||||
|
--color: var(--theme-shade);
|
||||||
|
--text: var(--theme-text);
|
||||||
|
|
||||||
|
font-size: 13px;
|
||||||
|
padding: 6px 15px;
|
||||||
|
border-radius: var(--theme-radius);
|
||||||
|
border: 0.5px solid var(--theme-border);
|
||||||
|
background: var(--color);
|
||||||
|
color: var(--text);
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background .15s, transform .1s;
|
||||||
|
white-space: nowrap;
|
||||||
|
|
||||||
|
@include color-variant('info');
|
||||||
|
@include color-variant('success');
|
||||||
|
@include color-variant('danger');
|
||||||
|
@include color-variant('warn');
|
||||||
|
@include color-variant('primary');
|
||||||
|
@include color-variant('accent');
|
||||||
|
@include color-variant('white', black);
|
||||||
|
@include color-variant('black', white);
|
||||||
|
|
||||||
|
&:hover { filter: brightness(1.2); }
|
||||||
|
}
|
||||||
|
|
||||||
|
.card {
|
||||||
|
@extend %surface;
|
||||||
|
padding: 18px 22px;
|
||||||
|
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.06);
|
||||||
|
background: var(--theme-surface);
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox, .radio {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 14px;
|
||||||
|
font-family: var(--theme-font), sans-serif;
|
||||||
|
color: var(--theme-text);
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox input[type="checkbox"] {
|
||||||
|
@include control-input(4px);
|
||||||
|
&:checked {
|
||||||
|
background: var(--theme-accent);
|
||||||
|
&::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
left: 4.5px; top: 0;
|
||||||
|
width: 5px; height: 10px;
|
||||||
|
border: 2px solid white;
|
||||||
|
border-top: none; border-left: none;
|
||||||
|
transform: rotate(45deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.chip {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
color: var(--theme-muted);
|
||||||
|
background: var(--theme-darker);
|
||||||
|
border: 0.5px solid var(--theme-border);
|
||||||
|
padding: 3px 10px;
|
||||||
|
border-radius: 999px;
|
||||||
|
font-size: 0.75em;
|
||||||
|
* { margin: 0; }
|
||||||
|
}
|
||||||
|
|
||||||
|
.expander {
|
||||||
|
@extend %surface;
|
||||||
|
padding: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
summary {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 10px;
|
||||||
|
padding: 10px 14px;
|
||||||
|
cursor: pointer;
|
||||||
|
list-style: none;
|
||||||
|
|
||||||
|
&::-webkit-details-marker { display: none; }
|
||||||
|
&::after {
|
||||||
|
content: '▸';
|
||||||
|
color: var(--theme-muted);
|
||||||
|
transition: transform .2s;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&[open] summary::after { transform: rotate(90deg); }
|
||||||
|
|
||||||
|
.expander-body {
|
||||||
|
padding: 0 14px 14px;
|
||||||
|
border-top: 0.5px solid var(--theme-border);
|
||||||
|
padding-top: 14px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.indicator {
|
||||||
|
--background: color-mix(in srgb, var(--theme-info) 20%, black);
|
||||||
|
--color: var(--theme-info);
|
||||||
|
|
||||||
|
width: 26px;
|
||||||
|
height: 26px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
background: var(--background);
|
||||||
|
border: 0.5px solid var(--color);
|
||||||
|
border-radius: 50%;
|
||||||
|
color: var(--color);
|
||||||
|
font-size: 16px;
|
||||||
|
|
||||||
|
@include indicator-variant('warn');
|
||||||
|
@include indicator-variant('danger');
|
||||||
|
@include indicator-variant('success');
|
||||||
|
@include indicator-variant('primary');
|
||||||
|
@include indicator-variant('accent');
|
||||||
|
@include indicator-variant('white');
|
||||||
|
@include indicator-variant('black', white);
|
||||||
|
}
|
||||||
|
|
||||||
|
.input, textarea.input {
|
||||||
|
@extend %surface;
|
||||||
|
padding: 8px 10px;
|
||||||
|
cursor: text;
|
||||||
|
font-family: var(--theme-font), sans-serif;
|
||||||
|
@include disableable-input;
|
||||||
|
|
||||||
|
&:focus-within { border-color: var(--theme-accent); }
|
||||||
|
}
|
||||||
|
|
||||||
|
textarea.input {
|
||||||
|
resize: vertical;
|
||||||
|
min-height: 60px;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="color"].input {
|
||||||
|
padding: 0;
|
||||||
|
width: 40px;
|
||||||
|
height: 28px;
|
||||||
|
border-radius: var(--theme-radius);
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
&::-webkit-color-swatch-wrapper { padding: 0; }
|
||||||
|
&::-webkit-color-swatch { border: none; border-radius: var(--theme-radius); }
|
||||||
|
&::-moz-color-swatch { border: none; border-radius: var(--theme-radius); }
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-group {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: stretch;
|
||||||
|
@extend %surface;
|
||||||
|
overflow: hidden;
|
||||||
|
background: var(--theme-darker);
|
||||||
|
|
||||||
|
> * {
|
||||||
|
border: none;
|
||||||
|
border-radius: 0;
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
// let colored buttons keep their own bg/text, everything else blends in
|
||||||
|
> .button:is(.info, .success, .warn, .danger, .primary, .accent, .white, .black) {
|
||||||
|
background: var(--color);
|
||||||
|
color: var(--text);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:focus-within { border-color: var(--theme-accent); }
|
||||||
|
}
|
||||||
|
|
||||||
|
select.select {
|
||||||
|
@extend %surface;
|
||||||
|
padding: 8px 10px;
|
||||||
|
cursor: pointer;
|
||||||
|
@include disableable-input;
|
||||||
|
|
||||||
|
&:focus-within { border-color: var(--theme-accent); }
|
||||||
|
|
||||||
|
option {
|
||||||
|
color: var(--theme-text);
|
||||||
|
background: var(--theme-surface);
|
||||||
|
}
|
||||||
|
|
||||||
|
&[multiple] {
|
||||||
|
padding: 0;
|
||||||
|
max-height: 200px;
|
||||||
|
overflow-y: auto;
|
||||||
|
|
||||||
|
option {
|
||||||
|
padding: 5px 10px;
|
||||||
|
background: var(--theme-darker);
|
||||||
|
|
||||||
|
&:checked {
|
||||||
|
background: color-mix(in srgb, var(--theme-primary) 10%, transparent);
|
||||||
|
color: var(--theme-primary);
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
&:hover { background: color-mix(in srgb, var(--theme-primary) 10%, transparent); }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.radio {
|
||||||
|
padding: 6px 0;
|
||||||
|
|
||||||
|
input[type="radio"] {
|
||||||
|
@include control-input(50%);
|
||||||
|
&:checked::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
inset: 3px;
|
||||||
|
background: var(--theme-accent);
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.range {
|
||||||
|
appearance: none;
|
||||||
|
-webkit-appearance: none;
|
||||||
|
-moz-appearance: none;
|
||||||
|
width: 100%;
|
||||||
|
height: 16px;
|
||||||
|
background: transparent;
|
||||||
|
cursor: pointer;
|
||||||
|
@extend %disabled-control;
|
||||||
|
|
||||||
|
&::-webkit-slider-runnable-track {
|
||||||
|
width: 100%;
|
||||||
|
height: 4px;
|
||||||
|
border-radius: 999px;
|
||||||
|
background: var(--theme-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
&::-moz-range-track {
|
||||||
|
width: 100%;
|
||||||
|
height: 4px;
|
||||||
|
border-radius: 999px;
|
||||||
|
background: var(--theme-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
&::-webkit-slider-thumb {
|
||||||
|
appearance: none;
|
||||||
|
-webkit-appearance: none;
|
||||||
|
width: 16px; height: 16px;
|
||||||
|
margin-top: -6px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: var(--theme-accent);
|
||||||
|
border: 2px solid var(--theme-surface);
|
||||||
|
transition: transform .15s;
|
||||||
|
&:hover { transform: scale(1.15); }
|
||||||
|
}
|
||||||
|
|
||||||
|
&::-moz-range-thumb {
|
||||||
|
width: 16px; height: 16px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: var(--theme-accent);
|
||||||
|
border: 2px solid var(--theme-surface);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.table {
|
||||||
|
display: table;
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: separate;
|
||||||
|
border-spacing: 0;
|
||||||
|
border: 1px solid var(--theme-border);
|
||||||
|
border-radius: var(--theme-radius);
|
||||||
|
color: var(--theme-text);
|
||||||
|
font-size: .875rem;
|
||||||
|
|
||||||
|
&.select tbody {
|
||||||
|
& > * { cursor: pointer; }
|
||||||
|
tr:hover {
|
||||||
|
background-color: var(--theme-accent);
|
||||||
|
color: var(--theme-white);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
thead {
|
||||||
|
background-color: var(--theme-shade);
|
||||||
|
border-bottom: 2px solid var(--theme-border);
|
||||||
|
}
|
||||||
|
|
||||||
|
tbody tr { transition: background-color 0.2s; }
|
||||||
|
|
||||||
|
&.striped {
|
||||||
|
tbody tr:nth-child(even) { background-color: var(--theme-shade-alt); }
|
||||||
|
}
|
||||||
|
|
||||||
|
th { padding: 1rem; text-align: left; font-weight: bold; color: var(--theme-primary); }
|
||||||
|
td { padding: 1rem; vertical-align: middle; }
|
||||||
|
}
|
||||||
|
|
||||||
|
.toggle {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 14px;
|
||||||
|
font-family: var(--theme-font), sans-serif;
|
||||||
|
color: var(--theme-text);
|
||||||
|
user-select: none;
|
||||||
|
|
||||||
|
input[type="checkbox"] {
|
||||||
|
appearance: none;
|
||||||
|
width: 36px;
|
||||||
|
height: 20px;
|
||||||
|
border: none;
|
||||||
|
border-radius: 999px;
|
||||||
|
background: var(--theme-border);
|
||||||
|
position: relative;
|
||||||
|
cursor: pointer;
|
||||||
|
flex-shrink: 0;
|
||||||
|
transition: background .2s;
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: 2px; left: 2px;
|
||||||
|
width: 16px; height: 16px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: var(--theme-white);
|
||||||
|
transition: transform .2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:checked {
|
||||||
|
background: var(--theme-accent);
|
||||||
|
&::after { transform: translateX(16px); }
|
||||||
|
}
|
||||||
|
|
||||||
|
@extend %disabled-control;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -11,7 +11,7 @@
|
|||||||
--theme-accent: #2caec0;
|
--theme-accent: #2caec0;
|
||||||
--theme-accent-contrast: black;
|
--theme-accent-contrast: black;
|
||||||
|
|
||||||
--theme-info: #6157cc;
|
--theme-info: #5786cc;
|
||||||
--theme-success: #65c147;
|
--theme-success: #65c147;
|
||||||
--theme-warn: #d88750;
|
--theme-warn: #d88750;
|
||||||
--theme-danger: #bf4f4f;
|
--theme-danger: #bf4f4f;
|
||||||
@@ -22,6 +22,7 @@
|
|||||||
--theme-animation: 0.2s linear;
|
--theme-animation: 0.2s linear;
|
||||||
--theme-content-width: 1100px;
|
--theme-content-width: 1100px;
|
||||||
--theme-font: Arial, sans-serif;
|
--theme-font: Arial, sans-serif;
|
||||||
|
--theme-radius: 0.5rem;
|
||||||
|
|
||||||
// Theme dependant (light/dark)
|
// Theme dependant (light/dark)
|
||||||
color-scheme: light dark;
|
color-scheme: light dark;
|
||||||
@@ -29,8 +30,13 @@
|
|||||||
--theme-border: light-dark(#E5E7EB, #374151);
|
--theme-border: light-dark(#E5E7EB, #374151);
|
||||||
--theme-muted: light-dark(#6c757d, #cccccc);
|
--theme-muted: light-dark(#6c757d, #cccccc);
|
||||||
--theme-text: light-dark(#000000, #ffffff);
|
--theme-text: light-dark(#000000, #ffffff);
|
||||||
--theme-lighter: light-dark(rgba(255, 255, 255, 0.05), rgba(255, 255, 255, 0.1));
|
|
||||||
--theme-darker: light-dark(rgba(0, 0, 0, 0.05), rgba(0, 0, 0, 0.1));
|
--theme-darker: light-dark(rgba(0, 0, 0, 0.05), rgba(0, 0, 0, 0.1));
|
||||||
|
--theme-lighter: light-dark(rgba(255, 255, 255, 0.05), rgba(255, 255, 255, 0.1));
|
||||||
|
--theme-shade: light-dark(var(--theme-darker), var(--theme-lighter));
|
||||||
|
--theme-shade-alt: light-dark(var(--theme-lighter), var(--theme-darker));
|
||||||
|
|
||||||
|
scrollbar-width: thin;
|
||||||
|
scrollbar-color: var(--theme-border) transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
.theme-light { color-scheme: light !important; }
|
.theme-light { color-scheme: light !important; }
|
||||||
@@ -39,6 +45,17 @@
|
|||||||
$breakpoints: (xs: 0, sm: 576px, md: 768px, lg: 992px, xl: 1200px);
|
$breakpoints: (xs: 0, sm: 576px, md: 768px, lg: 992px, xl: 1200px);
|
||||||
$sizes: (0: 0, 1: 0.25rem, 2: 0.5rem, 3: 1rem, 4: 1.5rem, 5: 2.5rem);
|
$sizes: (0: 0, 1: 0.25rem, 2: 0.5rem, 3: 1rem, 4: 1.5rem, 5: 2.5rem);
|
||||||
|
|
||||||
|
// Colors that get generated lighter/darker variants (computed once as CSS vars below)
|
||||||
|
$variant-colors: ('primary', 'accent', 'background', 'surface', 'info', 'success', 'warn', 'danger', 'muted', 'border', 'text', 'black', 'white');
|
||||||
|
|
||||||
|
// Generate lighter/darker CSS custom properties once, so all utilities can reuse them
|
||||||
|
:root {
|
||||||
|
@each $c in $variant-colors {
|
||||||
|
--theme-#{$c}-lighter: color-mix(in srgb, var(--theme-#{$c}) 80%, white);
|
||||||
|
--theme-#{$c}-darker: color-mix(in srgb, var(--theme-#{$c}) 80%, black);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Don't edit past this point ===============================================================================
|
// Don't edit past this point ===============================================================================
|
||||||
|
|
||||||
$direction: ('s': 'left', 't': 'top', 'e': 'right', 'b': 'bottom');
|
$direction: ('s': 'left', 't': 'top', 'e': 'right', 'b': 'bottom');
|
||||||
@@ -171,15 +188,17 @@ $divisible: (0, 10, 20, 25, 30, 33, 34, 40, 50, 60, 66, 70, 75, 80, 90, 100);
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Border - Style
|
// Border - Style
|
||||||
.b-dash { border-style: dashed !important; }
|
.b-dash { border-style: dashed !important; border-color: var(--theme-border); }
|
||||||
.b-dot { border-style: dotted !important; }
|
.b-dot { border-style: dotted !important; border-color: var(--theme-border); }
|
||||||
.b-double { border-style: double !important; }
|
.b-double { border-style: double !important; border-color: var(--theme-border); }
|
||||||
.b-none { border-style: none !important; }
|
.b-none { border-style: none !important; border-color: var(--theme-border); }
|
||||||
.b-solid { border-style: solid !important; }
|
.b-solid { border-style: solid !important; border-color: var(--theme-border); }
|
||||||
|
|
||||||
// Colors - Shared mixin for efficiency
|
// Colors - Shared mixin, generates border/background/foreground variants for a color
|
||||||
@mixin color-variant($name, $bg-var, $fg-var: null) {
|
@mixin color-variant($name, $bg-var, $fg-var: null, $border: true) {
|
||||||
.b-#{$name} { border-color: var(--theme-#{$bg-var}) !important; }
|
@if $border {
|
||||||
|
.b-#{$name} { border-color: var(--theme-#{$bg-var}) !important; }
|
||||||
|
}
|
||||||
.bg-#{$name} {
|
.bg-#{$name} {
|
||||||
background-color: var(--theme-#{$bg-var}) !important;
|
background-color: var(--theme-#{$bg-var}) !important;
|
||||||
@if $fg-var {
|
@if $fg-var {
|
||||||
@@ -212,27 +231,24 @@ $divisible: (0, 10, 20, 25, 30, 33, 34, 40, 50, 60, 66, 70, 75, 80, 90, 100);
|
|||||||
@include color-variant('warn', 'warn');
|
@include color-variant('warn', 'warn');
|
||||||
@include color-variant('danger', 'danger');
|
@include color-variant('danger', 'danger');
|
||||||
|
|
||||||
|
// Lighter/darker variants (bg-, fg- only) for every color in $variant-colors, using the CSS vars generated above
|
||||||
|
@each $c in $variant-colors {
|
||||||
|
@include color-variant('#{$c}-lighter', '#{$c}-lighter', $border: false);
|
||||||
|
@include color-variant('#{$c}-darker', '#{$c}-darker', $border: false);
|
||||||
|
}
|
||||||
|
|
||||||
|
.bg-shade { background-color: var(--theme-shade); }
|
||||||
|
.bg-shade-alt { background-color: var(--theme-shade-alt); }
|
||||||
.bg-transparent { background-color: transparent !important; }
|
.bg-transparent { background-color: transparent !important; }
|
||||||
|
|
||||||
// Lighter/Darker pseudo variants
|
$glow-intensity: ('': 50%, '-sm': 25%, '-lg': 75%);
|
||||||
@each $color in ('primary', 'accent', 'background', 'surface', 'info', 'success', 'warn', 'danger', 'muted') {
|
@each $name in ('primary', 'accent', 'info', 'success', 'warn', 'danger') {
|
||||||
.bg-#{$color}-lighter, .bg-#{$color}-darker {
|
@each $suffix, $intensity in $glow-intensity {
|
||||||
position: relative;
|
.bg-glow-#{$name}#{$suffix} {
|
||||||
z-index: 1;
|
background-image: radial-gradient(circle at top left, color-mix(in srgb, var(--theme-#{$name}) #{$intensity}, transparent) 0%, transparent 110%);
|
||||||
color: var(--theme-#{$color}-contrast);
|
background-repeat: no-repeat;
|
||||||
text-decoration-color: var(--theme-#{$color}-contrast);
|
|
||||||
|
|
||||||
&::before {
|
|
||||||
content: '';
|
|
||||||
position: absolute;
|
|
||||||
inset: 0;
|
|
||||||
background-color: var(--theme-#{$color});
|
|
||||||
border-radius: inherit;
|
|
||||||
z-index: -1;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.bg-#{$color}-lighter::before { filter: saturate(.5) brightness(150%); }
|
|
||||||
.bg-#{$color}-darker::before { filter: saturate(.5) brightness(50%); }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cursors
|
// Cursors
|
||||||
Reference in New Issue
Block a user