mirror of
https://github.com/jgthms/bulma.git
synced 2024-12-12 13:28:30 +00:00
503 lines
13 KiB
SCSS
503 lines
13 KiB
SCSS
@use "sass:color";
|
|
@use "sass:list";
|
|
@use "sass:map";
|
|
@use "sass:math";
|
|
|
|
@use "initial-variables" as iv;
|
|
@use "functions" as fn;
|
|
|
|
@function buildVarName($name, $prefix: "", $suffix: "") {
|
|
@return "--#{iv.$cssvars-prefix}#{$prefix}#{$name}#{$suffix}";
|
|
}
|
|
|
|
@function buildHslaString($name, $l, $a: 1) {
|
|
$lightness: getVar($name, "", "-l");
|
|
@if ($l) {
|
|
$lightness: $l;
|
|
}
|
|
@return "hsla(#{getVar($name, '', '-h')}, #{getVar($name, '', '-s')}, #{$lightness}, #{$a})";
|
|
}
|
|
|
|
@function getVar($name, $prefix: "", $suffix: "") {
|
|
$varName: buildVarName($name, $prefix, $suffix);
|
|
@return var(#{$varName});
|
|
}
|
|
|
|
@function getVarWithBackup($name, $backup, $prefix: "", $suffix: "") {
|
|
$varName: buildVarName($name, $prefix, $suffix);
|
|
$backupName: buildVarName($backup, $prefix, $suffix);
|
|
@return var(#{$varName}, var(#{$backupName}));
|
|
}
|
|
|
|
@function getRgbaVar($name, $alpha, $prefix: "", $suffix: "") {
|
|
$varName: buildVarName($name, $prefix, $suffix);
|
|
@return unquote("rgba(var(#{$varName}), #{$alpha})");
|
|
}
|
|
|
|
@mixin register-var($name, $value, $prefix: "", $suffix: "") {
|
|
$varName: buildVarName($name, $prefix, $suffix);
|
|
#{$varName}: #{$value};
|
|
}
|
|
|
|
@mixin register-vars($vars, $prefix: "", $suffix: "") {
|
|
@each $name, $value in $vars {
|
|
@include register-var($name, $value, $prefix, $suffix);
|
|
}
|
|
}
|
|
|
|
@mixin register-rgb($name, $value) {
|
|
@include register-var(
|
|
$name,
|
|
(
|
|
color.channel($value, "red", $space: rgb),
|
|
color.channel($value, "green", $space: rgb),
|
|
color.channel($value, "blue", $space: rgb)
|
|
),
|
|
"",
|
|
"-rgb"
|
|
);
|
|
}
|
|
|
|
@mixin register-hsl($name, $value) {
|
|
@include register-var(
|
|
$name,
|
|
round(color.channel($value, "hue", $space: hsl)),
|
|
"",
|
|
"-h"
|
|
);
|
|
@include register-var(
|
|
$name,
|
|
round(color.channel($value, "saturation", $space: hsl)),
|
|
"",
|
|
"-s"
|
|
);
|
|
@include register-var(
|
|
$name,
|
|
round(color.channel($value, "lightness", $space: hsl)),
|
|
"",
|
|
"-l"
|
|
);
|
|
}
|
|
|
|
@mixin generate-on-scheme-colors($name, $base, $scheme-main) {
|
|
// Accessibility Contrast System
|
|
$scheme-main-brightness: fn.bulmaColorBrightness($scheme-main);
|
|
$on-scheme-color: $base;
|
|
$fg-lum: fn.bulmaColorLuminance($on-scheme-color);
|
|
$bg-lum: fn.bulmaColorLuminance($scheme-main);
|
|
$ratio: 0;
|
|
$found-decent-color: false;
|
|
|
|
@if ($fg-lum > $bg-lum) {
|
|
@for $i from 0 through 20 {
|
|
$ratio: math.div(($fg-lum + 0.05), ($bg-lum + 0.05));
|
|
|
|
@if $ratio > 5 {
|
|
$found-decent-color: true;
|
|
} @else {
|
|
$on-scheme-color: color.adjust(
|
|
$on-scheme-color,
|
|
$lightness: 5%,
|
|
$space: hsl
|
|
);
|
|
$fg-lum: fn.bulmaColorLuminance($on-scheme-color);
|
|
}
|
|
}
|
|
} @else {
|
|
@for $i from 0 through 20 {
|
|
$ratio: math.div(($bg-lum + 0.05), ($fg-lum + 0.05));
|
|
|
|
@if $ratio > 5 {
|
|
$found-decent-color: true;
|
|
} @else {
|
|
$on-scheme-color: color.adjust(
|
|
$on-scheme-color,
|
|
$lightness: -5%,
|
|
$space: hsl
|
|
);
|
|
$fg-lum: fn.bulmaColorLuminance($on-scheme-color);
|
|
}
|
|
}
|
|
}
|
|
|
|
$on-scheme-lightness: color.channel(
|
|
$on-scheme-color,
|
|
"lightness",
|
|
$space: hsl
|
|
);
|
|
@include register-var($name, $on-scheme-lightness, "", "-on-scheme-l");
|
|
$on-scheme-l: getVar($name, "", "-on-scheme-l");
|
|
@include register-var(
|
|
"#{$name}-on-scheme",
|
|
buildHslaString($name, $on-scheme-l)
|
|
);
|
|
}
|
|
|
|
@mixin v1-generate-on-scheme-colors($name, $base, $scheme-main) {
|
|
// Accessibility Contrast System
|
|
$scheme-main-brightness: fn.bulmaColorBrightness($scheme-main);
|
|
$on-scheme-color: $base;
|
|
|
|
@if ($scheme-main-brightness == "bright") {
|
|
@while (fn.bulmaEnoughContrast($on-scheme-color, #fff) == false) {
|
|
// We're on a light background, so we'll darken the test color step by step.
|
|
$on-scheme-color: color.adjust(
|
|
$on-scheme-color,
|
|
$lightness: -5%,
|
|
$space: hsl
|
|
);
|
|
}
|
|
} @else {
|
|
@while (fn.bulmaEnoughContrast($on-scheme-color, #000) == false) {
|
|
// We're on a dark background, so we'll lighten the test color step by step.
|
|
$on-scheme-color: color.adjust(
|
|
$on-scheme-color,
|
|
$lightness: 5%,
|
|
$space: hsl
|
|
);
|
|
}
|
|
}
|
|
|
|
$on-scheme-lightness: color.channel(
|
|
$on-scheme-color,
|
|
"lightness",
|
|
$space: hsl
|
|
);
|
|
@include register-var($name, $on-scheme-lightness, "", "-on-scheme-l");
|
|
}
|
|
|
|
@mixin register-base-color($name, $base) {
|
|
$hsla: buildHslaString($name, getVar($name, "", "-l"));
|
|
@include register-var($name, $hsla);
|
|
@include register-var($name, $hsla, "", "-base"); // Just for reference
|
|
@include register-rgb($name, $base);
|
|
@include register-hsl($name, $base);
|
|
}
|
|
|
|
@mixin generate-basic-palette($name, $base, $invert: null) {
|
|
@include register-base-color($name, $base);
|
|
|
|
@if $invert {
|
|
@include register-var(
|
|
$name,
|
|
color.channel($invert, "lightness", $space: hsl),
|
|
"",
|
|
"-invert-l"
|
|
);
|
|
@include register-var("#{$name}-invert", $invert);
|
|
}
|
|
}
|
|
|
|
@mixin generate-color-palette(
|
|
$name,
|
|
$base,
|
|
$scheme-main-l: 100%,
|
|
$invert: null,
|
|
$light: null,
|
|
$dark: null
|
|
) {
|
|
$h: round(color.channel($base, "hue", $space: hsl)); // Hue
|
|
$s: round(color.channel($base, "saturation", $space: hsl)); // Saturation
|
|
$l: round(color.channel($base, "lightness", $space: hsl)); // Lightness
|
|
$base-lum: fn.bulmaColorLuminance($base);
|
|
$l-base: math.round($l % 10); // Get lightness second digit: 53% -> 3%
|
|
$l-0: 0%; // 5% or less
|
|
$l-5: 5%; // More than 5%
|
|
$a: 1; // Alpha
|
|
$base-digits: "00";
|
|
|
|
// Calculate digits like "40" for the scheme-main
|
|
$scheme-l-0: 0%;
|
|
$scheme-l-base: math.round($scheme-main-l % 10);
|
|
$closest-5: math.round(math.div($scheme-main-l, 5)) * 5;
|
|
$pct-to-int: math.div($closest-5, 100%) * 100;
|
|
$scheme-main-digits: #{$pct-to-int};
|
|
|
|
// === STEP 1 ===
|
|
// Register the base colors
|
|
@include register-base-color($name, $base);
|
|
|
|
// === STEP 2 ===
|
|
// Generating 20 shades of the color
|
|
|
|
// 00: 0%, 1%, 2%
|
|
// 05: 3%, 4%, 5%, 6%, 7%
|
|
// 10: 8%, 9%
|
|
|
|
@if ($l-base < 3%) {
|
|
$l-0: $l-base;
|
|
$l-5: $l-base + 5%;
|
|
} @else if ($l-base < 8%) {
|
|
// $l-0: math.max($l-base - 5%, 0%);
|
|
$l-0: $l-base - 5%;
|
|
$l-5: $l-base;
|
|
} @else {
|
|
// $l-0: math.max($l-base - 10%, 0%);
|
|
$l-0: $l-base - 10%;
|
|
$l-5: $l-base - 5%;
|
|
}
|
|
|
|
$shades: ();
|
|
|
|
@for $i from 0 through 9 {
|
|
// if $l-base = 3%, then we get 3%, 13%, 23%, 33% etc.
|
|
$color-l-0: math.max($l-0 + $i * 10, 0%);
|
|
|
|
// if $l-base = 3%, then we get 8%, 18%, 28%, 38% etc.
|
|
$color-l-5: $l-5 + $i * 10;
|
|
|
|
$shades: map.set($shades, "#{$i}0", $color-l-0);
|
|
$shades: map.set($shades, "#{$i}5", $color-l-5);
|
|
|
|
@include register-var($name, $color-l-0, "", "-#{$i}0-l");
|
|
@include register-var($name, $color-l-5, "", "-#{$i}5-l");
|
|
|
|
@if $color-l-0 == $l {
|
|
$base-digits: "#{$i}0";
|
|
} @else if $color-l-5 == $l {
|
|
$base-digits: "#{$i}5";
|
|
}
|
|
}
|
|
|
|
$l-100: math.min($l-0 + 100%, 100%);
|
|
$shades: map.set($shades, "100", $l-100);
|
|
@include register-var($name, $l-100, "", "-100-l");
|
|
|
|
// === STEP 3 ===
|
|
// Find accessible color combinations
|
|
|
|
$combos: ();
|
|
|
|
@each $digits-bg, $bg-l in $shades {
|
|
$background: hsl($h, $s, $bg-l);
|
|
$bg-lum: fn.bulmaColorLuminance($background);
|
|
$bg-is-light: $bg-lum > 0.55;
|
|
$candidates: ();
|
|
$found: false;
|
|
|
|
// If the background color is the base color
|
|
@if $bg-l == $l {
|
|
$base-digits: $digits-bg;
|
|
|
|
// Even if the base color as a background
|
|
// doesn't have an appropriate foreground,
|
|
// we still add to the list of "valid" contrast combos for now.
|
|
@if $bg-is-light {
|
|
$combos: map.set($combos, $base-digits, "10");
|
|
} @else {
|
|
$combos: map.set($combos, $base-digits, "100");
|
|
}
|
|
}
|
|
|
|
// We capture all contrast ratios for any given background
|
|
// using all foreground options
|
|
$current-best-digits: "00";
|
|
$current-best-ratio: 0;
|
|
|
|
@each $digits-fg, $fg-l in $shades {
|
|
$foreground: hsl($h, $s, $fg-l);
|
|
$ratio: 0;
|
|
$is-light-fg: false;
|
|
|
|
// Source: https://www.w3.org/TR/WCAG20-TECHS/G17.html
|
|
$fg-lum: fn.bulmaColorLuminance($foreground);
|
|
|
|
@if (
|
|
color.channel($foreground, "lightness", $space: hsl) >
|
|
color.channel($background, "lightness", $space: hsl)
|
|
) {
|
|
$is-light-fg: true;
|
|
$ratio: math.div(($fg-lum + 0.05), ($bg-lum + 0.05));
|
|
} @else {
|
|
$ratio: math.div(($bg-lum + 0.05), ($fg-lum + 0.05));
|
|
}
|
|
|
|
@if $ratio > 7 {
|
|
$candidates: list.append(
|
|
$candidates,
|
|
fn.bulmaStringToNumber($digits-fg)
|
|
);
|
|
|
|
@if ($is-light-fg) {
|
|
@if (not $found) {
|
|
// Store the background/foreground combination
|
|
$combos: map.set($combos, $digits-bg, $digits-fg);
|
|
$current-best-digits: $digits-fg;
|
|
$current-best-ratio: $ratio;
|
|
$found: true;
|
|
}
|
|
} @else {
|
|
$combos: map.set($combos, $digits-bg, $digits-fg);
|
|
$current-best-digits: $digits-fg;
|
|
$current-best-ratio: $ratio;
|
|
}
|
|
}
|
|
}
|
|
|
|
// We haven't found a decent ratio
|
|
@each $digits-fg, $fg-l in $shades {
|
|
@if (map.has-key($combos, $digits-bg) == false) {
|
|
@if ($bg-is-light) {
|
|
// Light background so we set a dark foreground
|
|
$combos: map.set($combos, $digits-bg, "00");
|
|
} @else {
|
|
// Dark background so we set a light foreground
|
|
$combos: map.set($combos, $digits-bg, "100");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// The output needs to be:
|
|
// --bulma-primary-invert-l: var(--bulma-primary-100-l);
|
|
|
|
@each $bg, $fg in $combos {
|
|
// Just using this loop to register all 20 digits
|
|
$bg-l: getVar($name, "", "-#{$bg}-l");
|
|
@include register-var("#{$name}-#{$bg}", buildHslaString($name, $bg-l));
|
|
|
|
// Register the lightness
|
|
@include register-var(
|
|
$name,
|
|
getVar($name, "", "-#{$fg}-l"),
|
|
"",
|
|
"-#{$bg}-invert-l"
|
|
);
|
|
|
|
// Resiter the color using that lightness
|
|
$bg-invert-l: getVar($name, "", "-#{$bg}-invert-l");
|
|
@include register-var(
|
|
"#{$name}-#{$bg}-invert",
|
|
buildHslaString($name, $bg-invert-l)
|
|
);
|
|
}
|
|
|
|
// If an invert color is provided by the user
|
|
@if $invert {
|
|
@include register-var(
|
|
$name,
|
|
color.channel($invert, "lightness", $space: hsl),
|
|
"",
|
|
"-invert-l"
|
|
);
|
|
@include register-var("#{$name}-invert", $invert);
|
|
} @else {
|
|
$base-invert-l-digits: map.get($combos, $base-digits);
|
|
@include register-var(
|
|
$name,
|
|
getVar($name, "", "-#{$base-invert-l-digits}-l"),
|
|
"",
|
|
"-invert-l"
|
|
);
|
|
|
|
$base-invert-l: getVar($name, "", "-invert-l");
|
|
@include register-var(
|
|
"#{$name}-invert",
|
|
buildHslaString($name, $base-invert-l)
|
|
);
|
|
}
|
|
|
|
// Good color on light background (90% lightness)
|
|
@if $light and $dark {
|
|
@include register-var(
|
|
$name,
|
|
color.channel($light, "lightness", $space: hsl),
|
|
"",
|
|
"-light-l"
|
|
);
|
|
@include register-var(
|
|
$name,
|
|
color.channel($light, "lightness", $space: hsl),
|
|
"",
|
|
"-dark-invert-l"
|
|
);
|
|
@include register-var("#{$name}-light", $light);
|
|
@include register-var("#{$name}-dark-invert", $light);
|
|
|
|
@include register-var(
|
|
$name,
|
|
color.channel($dark, "lightness", $space: hsl),
|
|
"",
|
|
"-dark-l"
|
|
);
|
|
@include register-var(
|
|
$name,
|
|
color.channel($dark, "lightness", $space: hsl),
|
|
"",
|
|
"-light-invert-l"
|
|
);
|
|
@include register-var("#{$name}-dark", $dark);
|
|
@include register-var("#{$name}-light-invert", $dark);
|
|
} @else {
|
|
@include register-var($name, getVar($name, "", "-90-l"), "", "-light-l");
|
|
|
|
$light-l: getVar($name, "", "-light-l");
|
|
@include register-var("#{$name}-light", buildHslaString($name, $light-l));
|
|
|
|
$light-invert-l-digits: map.get($combos, "90");
|
|
@include register-var(
|
|
$name,
|
|
getVar($name, "", "-#{$light-invert-l-digits}-l"),
|
|
"",
|
|
"-light-invert-l"
|
|
);
|
|
|
|
$light-invert-l: getVar($name, "", "-light-invert-l");
|
|
@include register-var(
|
|
"#{$name}-light-invert",
|
|
buildHslaString($name, $light-invert-l)
|
|
);
|
|
|
|
// Good color on dark background (10% lightness)
|
|
@include register-var($name, getVar($name, "", "-10-l"), "", "-dark-l");
|
|
|
|
$dark-l: getVar($name, "", "-dark-l");
|
|
@include register-var("#{$name}-dark", buildHslaString($name, $dark-l));
|
|
|
|
$dark-invert-l-digits: map.get($combos, "10");
|
|
@include register-var(
|
|
$name,
|
|
getVar($name, "", "-#{$dark-invert-l-digits}-l"),
|
|
"",
|
|
"-dark-invert-l"
|
|
);
|
|
|
|
$dark-invert-l: getVar($name, "", "-dark-invert-l");
|
|
@include register-var(
|
|
"#{$name}-dark-invert",
|
|
buildHslaString($name, $dark-invert-l)
|
|
);
|
|
|
|
// Soft and Bold colors
|
|
$soft-l: getVar("soft-l");
|
|
$soft-invert-l: getVar("soft-invert-l");
|
|
$bold-l: getVar("bold-l");
|
|
$bold-invert-l: getVar("bold-invert-l");
|
|
@include register-var("#{$name}-soft", buildHslaString($name, $soft-l));
|
|
@include register-var("#{$name}-bold", buildHslaString($name, $bold-l));
|
|
@include register-var(
|
|
"#{$name}-soft-invert",
|
|
buildHslaString($name, $soft-invert-l)
|
|
);
|
|
@include register-var(
|
|
"#{$name}-bold-invert",
|
|
buildHslaString($name, $bold-invert-l)
|
|
);
|
|
}
|
|
}
|
|
|
|
@mixin bulma-theme($name) {
|
|
[data-#{iv.$class-prefix}theme="#{$name}"],
|
|
.#{iv.$class-prefix}theme-#{$name} {
|
|
@content;
|
|
}
|
|
}
|
|
|
|
@mixin system-theme($name) {
|
|
@media (prefers-color-scheme: #{$name}) {
|
|
:root {
|
|
@content;
|
|
}
|
|
}
|
|
}
|