implement theme switching service
This commit is contained in:
		
							parent
							
								
									ee92f6639a
								
							
						
					
					
						commit
						4c205eb09d
					
				| @ -1,4 +1,4 @@ | ||||
| export const mempoolFeeColors = [ | ||||
| export const defaultMempoolFeeColors = [ | ||||
|   '557d00', | ||||
|   '5d7d01', | ||||
|   '637d02', | ||||
| @ -39,6 +39,47 @@ export const mempoolFeeColors = [ | ||||
|   'ae005b', | ||||
| ]; | ||||
| 
 | ||||
| export const contrastMempoolFeeColors = [ | ||||
|   '83fd00', | ||||
|   '83f609', | ||||
|   '83ef12', | ||||
|   '83e71a', | ||||
|   '83e023', | ||||
|   '83d92c', | ||||
|   '83d235', | ||||
|   '83cb3e', | ||||
|   '83c446', | ||||
|   '83bc4f', | ||||
|   '83b558', | ||||
|   '83ae61', | ||||
|   '83a76a', | ||||
|   '83a072', | ||||
|   '83997b', | ||||
|   '839184', | ||||
|   '838a8d', | ||||
|   '838395', | ||||
|   '837c9e', | ||||
|   '8375a7', | ||||
|   '836eb0', | ||||
|   '8366b9', | ||||
|   '835fc1', | ||||
|   '8358ca', | ||||
|   '8351d3', | ||||
|   '834adc', | ||||
|   '8343e5', | ||||
|   '833bed', | ||||
|   '8334f6', | ||||
|   '832dff', | ||||
|   '832dff', | ||||
|   '832dff', | ||||
|   '832dff', | ||||
|   '832dff', | ||||
|   '832dff', | ||||
|   '832dff', | ||||
|   '832dff', | ||||
|   '832dff', | ||||
| ]; | ||||
| 
 | ||||
| export const chartColors = [ | ||||
|   "#D81B60", | ||||
|   "#8E24AA", | ||||
|  | ||||
| @ -7,6 +7,7 @@ import TxView from './tx-view'; | ||||
| import { Color, Position } from './sprite-types'; | ||||
| import { Price } from '../../services/price.service'; | ||||
| import { StateService } from '../../services/state.service'; | ||||
| import { ThemeService } from 'src/app/services/theme.service'; | ||||
| import { Subscription } from 'rxjs'; | ||||
| import { defaultColorFunction, setOpacity, defaultFeeColors, defaultAuditFeeColors, defaultMarginalFeeColors, defaultAuditColors } from './utils'; | ||||
| import { ActiveFilter, FilterMode, toFlags } from '../../shared/filters.utils'; | ||||
| @ -55,6 +56,7 @@ export class BlockOverviewGraphComponent implements AfterViewInit, OnDestroy, On | ||||
| 
 | ||||
|   @ViewChild('blockCanvas') | ||||
|   canvas: ElementRef<HTMLCanvasElement>; | ||||
|   themeChangedSubscription: Subscription; | ||||
| 
 | ||||
|   gl: WebGLRenderingContext; | ||||
|   animationFrameRequest: number; | ||||
| @ -86,6 +88,7 @@ export class BlockOverviewGraphComponent implements AfterViewInit, OnDestroy, On | ||||
|     readonly ngZone: NgZone, | ||||
|     readonly elRef: ElementRef, | ||||
|     public stateService: StateService, | ||||
|     private themeService: ThemeService, | ||||
|   ) { | ||||
|     this.webGlEnabled = this.stateService.isBrowser && detectWebGL(); | ||||
|     this.vertexArray = new FastVertexArray(512, TxSprite.dataSize); | ||||
| @ -104,6 +107,10 @@ export class BlockOverviewGraphComponent implements AfterViewInit, OnDestroy, On | ||||
|       if (this.gl) { | ||||
|         this.initCanvas(); | ||||
|         this.resizeCanvas(); | ||||
|         this.themeChangedSubscription = this.themeService.themeChanged$.subscribe(() => { | ||||
|           // force full re-render
 | ||||
|           this.resizeCanvas(); | ||||
|         }); | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| @ -149,6 +156,7 @@ export class BlockOverviewGraphComponent implements AfterViewInit, OnDestroy, On | ||||
|     if (this.canvas) { | ||||
|       this.canvas.nativeElement.removeEventListener('webglcontextlost', this.handleContextLost); | ||||
|       this.canvas.nativeElement.removeEventListener('webglcontextrestored', this.handleContextRestored); | ||||
|       this.themeChangedSubscription?.unsubscribe(); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
| @ -291,7 +299,7 @@ export class BlockOverviewGraphComponent implements AfterViewInit, OnDestroy, On | ||||
|         this.start(); | ||||
|       } else { | ||||
|         this.scene = new BlockScene({ width: this.displayWidth, height: this.displayHeight, resolution: this.resolution, | ||||
|           blockLimit: this.blockLimit, orientation: this.orientation, flip: this.flip, vertexArray: this.vertexArray, | ||||
|           blockLimit: this.blockLimit, orientation: this.orientation, flip: this.flip, vertexArray: this.vertexArray, theme: this.themeService, | ||||
|           highlighting: this.auditHighlighting, animationDuration: this.animationDuration, animationOffset: this.animationOffset, | ||||
|         colorFunction: this.getColorFunction() }); | ||||
|         this.start(); | ||||
|  | ||||
| @ -3,12 +3,14 @@ import TxView from './tx-view'; | ||||
| import { TransactionStripped } from '../../interfaces/node-api.interface'; | ||||
| import { Color, Position, Square, ViewUpdateParams } from './sprite-types'; | ||||
| import { defaultColorFunction } from './utils'; | ||||
| import { ThemeService } from 'src/app/services/theme.service'; | ||||
| 
 | ||||
| export default class BlockScene { | ||||
|   scene: { count: number, offset: { x: number, y: number}}; | ||||
|   vertexArray: FastVertexArray; | ||||
|   txs: { [key: string]: TxView }; | ||||
|   getColor: ((tx: TxView) => Color) = defaultColorFunction; | ||||
|   theme: ThemeService; | ||||
|   orientation: string; | ||||
|   flip: boolean; | ||||
|   animationDuration: number = 900; | ||||
| @ -29,11 +31,11 @@ export default class BlockScene { | ||||
|   animateUntil = 0; | ||||
|   dirty: boolean; | ||||
| 
 | ||||
|   constructor({ width, height, resolution, blockLimit, animationDuration, animationOffset, orientation, flip, vertexArray, highlighting, colorFunction }: | ||||
|   constructor({ width, height, resolution, blockLimit, animationDuration, animationOffset, orientation, flip, vertexArray, theme, highlighting, colorFunction }: | ||||
|       { width: number, height: number, resolution: number, blockLimit: number, animationDuration: number, animationOffset: number, | ||||
|         orientation: string, flip: boolean, vertexArray: FastVertexArray, highlighting: boolean, colorFunction: ((tx: TxView) => Color) | null } | ||||
|         orientation: string, flip: boolean, vertexArray: FastVertexArray, theme: ThemeService, highlighting: boolean, colorFunction: ((tx: TxView) => Color) | null } | ||||
|   ) { | ||||
|     this.init({ width, height, resolution, blockLimit, animationDuration, animationOffset, orientation, flip, vertexArray, highlighting, colorFunction }); | ||||
|     this.init({ width, height, resolution, blockLimit, animationDuration, animationOffset, orientation, flip, vertexArray, theme, highlighting, colorFunction }); | ||||
|   } | ||||
| 
 | ||||
|   resize({ width = this.width, height = this.height, animate = true }: { width?: number, height?: number, animate: boolean }): void { | ||||
| @ -90,7 +92,7 @@ export default class BlockScene { | ||||
|     }); | ||||
|     this.layout = new BlockLayout({ width: this.gridWidth, height: this.gridHeight }); | ||||
|     txs.forEach(tx => { | ||||
|       const txView = new TxView(tx, this); | ||||
|       const txView = new TxView(tx, this, this.theme); | ||||
|       this.txs[tx.txid] = txView; | ||||
|       this.place(txView); | ||||
|       this.saveGridToScreenPosition(txView); | ||||
| @ -136,7 +138,7 @@ export default class BlockScene { | ||||
|     }); | ||||
|     txs.forEach(tx => { | ||||
|       if (!this.txs[tx.txid]) { | ||||
|         this.txs[tx.txid] = new TxView(tx, this); | ||||
|         this.txs[tx.txid] = new TxView(tx, this, this.theme); | ||||
|       } | ||||
|     }); | ||||
| 
 | ||||
| @ -178,7 +180,7 @@ export default class BlockScene { | ||||
|     if (resetLayout) { | ||||
|       add.forEach(tx => { | ||||
|         if (!this.txs[tx.txid]) { | ||||
|           this.txs[tx.txid] = new TxView(tx, this); | ||||
|           this.txs[tx.txid] = new TxView(tx, this, this.theme); | ||||
|         } | ||||
|       }); | ||||
|       this.layout = new BlockLayout({ width: this.gridWidth, height: this.gridHeight }); | ||||
| @ -198,7 +200,7 @@ export default class BlockScene { | ||||
| 
 | ||||
|       // try to insert new txs directly
 | ||||
|       const remaining = []; | ||||
|       add.map(tx => new TxView(tx, this)).sort(feeRateDescending).forEach(tx => { | ||||
|       add.map(tx => new TxView(tx, this, this.theme)).sort(feeRateDescending).forEach(tx => { | ||||
|         if (!this.tryInsertByFee(tx)) { | ||||
|           remaining.push(tx); | ||||
|         } | ||||
| @ -228,9 +230,9 @@ export default class BlockScene { | ||||
|     this.animateUntil = Math.max(this.animateUntil, tx.setHighlight(value)); | ||||
|   } | ||||
| 
 | ||||
|   private init({ width, height, resolution, blockLimit, animationDuration, animationOffset, orientation, flip, vertexArray, highlighting, colorFunction }: | ||||
|   private init({ width, height, resolution, blockLimit, animationDuration, animationOffset, orientation, flip, vertexArray, theme, highlighting, colorFunction }: | ||||
|       { width: number, height: number, resolution: number, blockLimit: number, animationDuration: number, animationOffset: number, | ||||
|         orientation: string, flip: boolean, vertexArray: FastVertexArray, highlighting: boolean, colorFunction: ((tx: TxView) => Color) | null } | ||||
|         orientation: string, flip: boolean, vertexArray: FastVertexArray, theme: ThemeService, highlighting: boolean, colorFunction: ((tx: TxView) => Color) | null } | ||||
|   ): void { | ||||
|     this.animationDuration = animationDuration || 1000; | ||||
|     this.configAnimationOffset = animationOffset; | ||||
| @ -240,6 +242,7 @@ export default class BlockScene { | ||||
|     this.vertexArray = vertexArray; | ||||
|     this.highlightingEnabled = highlighting; | ||||
|     this.getColor = colorFunction || defaultColorFunction; | ||||
|     this.theme = theme; | ||||
| 
 | ||||
|     this.scene = { | ||||
|       count: 0, | ||||
|  | ||||
| @ -5,6 +5,8 @@ import { hexToColor } from './utils'; | ||||
| import BlockScene from './block-scene'; | ||||
| import { TransactionStripped } from '../../interfaces/node-api.interface'; | ||||
| import { TransactionFlags } from '../../shared/filters.utils'; | ||||
| import { feeLevels } from '../../app.constants'; | ||||
| import { ThemeService } from 'src/app/services/theme.service'; | ||||
| 
 | ||||
| const hoverTransitionTime = 300; | ||||
| const defaultHoverColor = hexToColor('1bd8f4'); | ||||
| @ -36,6 +38,7 @@ export default class TxView implements TransactionStripped { | ||||
|   status?: 'found' | 'missing' | 'sigop' | 'fresh' | 'freshcpfp' | 'added' | 'prioritized' | 'censored' | 'selected' | 'rbf' | 'accelerated'; | ||||
|   context?: 'projected' | 'actual'; | ||||
|   scene?: BlockScene; | ||||
|   theme: ThemeService; | ||||
| 
 | ||||
|   initialised: boolean; | ||||
|   vertexArray: FastVertexArray; | ||||
| @ -50,7 +53,7 @@ export default class TxView implements TransactionStripped { | ||||
| 
 | ||||
|   dirty: boolean; | ||||
| 
 | ||||
|   constructor(tx: TransactionStripped, scene: BlockScene) { | ||||
|   constructor(tx: TransactionStripped, scene: BlockScene, theme: ThemeService) { | ||||
|     this.scene = scene; | ||||
|     this.context = tx.context; | ||||
|     this.txid = tx.txid; | ||||
| @ -66,6 +69,7 @@ export default class TxView implements TransactionStripped { | ||||
|     this.bigintFlags = tx.flags ? (BigInt(tx.flags) | (this.acc ? TransactionFlags.acceleration : 0n)): 0n; | ||||
|     this.initialised = false; | ||||
|     this.vertexArray = scene.vertexArray; | ||||
|     this.theme = theme; | ||||
| 
 | ||||
|     this.hover = false; | ||||
| 
 | ||||
| @ -138,10 +142,10 @@ export default class TxView implements TransactionStripped { | ||||
| 
 | ||||
|   // Temporarily override the tx color
 | ||||
|   // returns minimum transition end time
 | ||||
|   setHover(hoverOn: boolean, color: Color | void = defaultHoverColor): number { | ||||
|   setHover(hoverOn: boolean, color: Color | void): number { | ||||
|     if (hoverOn) { | ||||
|       this.hover = true; | ||||
|       this.hoverColor = color; | ||||
|       this.hoverColor = color || this.theme.defaultHoverColor; | ||||
| 
 | ||||
|       this.sprite.update({ | ||||
|         ...this.hoverColor, | ||||
| @ -191,4 +195,30 @@ export default class TxView implements TransactionStripped { | ||||
|     this.dirty = false; | ||||
|     return performance.now() + hoverTransitionTime; | ||||
|   } | ||||
| 
 | ||||
|   getColor(): Color { | ||||
|     const feeLevelIndex = feeLevels.findIndex((feeLvl) => Math.max(1, this.feerate) < feeLvl) - 1; | ||||
|     const feeLevelColor = this.theme.feeColors[feeLevelIndex] || this.theme.feeColors[this.theme.mempoolFeeColors.length - 1]; | ||||
|     // Block audit
 | ||||
|     switch(this.status) { | ||||
|       case 'censored': | ||||
|         return this.theme.auditColors.censored; | ||||
|       case 'missing': | ||||
|         return this.theme.auditColors.missing; | ||||
|       case 'fresh': | ||||
|         return this.theme.auditColors.missing; | ||||
|       case 'added': | ||||
|         return this.theme.auditColors.added; | ||||
|       case 'selected': | ||||
|         return this.theme.auditColors.selected; | ||||
|       case 'found': | ||||
|         if (this.context === 'projected') { | ||||
|           return this.theme.auditFeeColors[feeLevelIndex] || this.theme.auditFeeColors[this.theme.mempoolFeeColors.length - 1]; | ||||
|         } else { | ||||
|           return feeLevelColor; | ||||
|         } | ||||
|       default: | ||||
|         return feeLevelColor; | ||||
|     } | ||||
|   } | ||||
| } | ||||
|  | ||||
| @ -2,8 +2,9 @@ import { Component, OnInit, ChangeDetectionStrategy } from '@angular/core'; | ||||
| import { StateService } from '../../services/state.service'; | ||||
| import { Observable, combineLatest } from 'rxjs'; | ||||
| import { Recommendedfees } from '../../interfaces/websocket.interface'; | ||||
| import { feeLevels, mempoolFeeColors } from '../../app.constants'; | ||||
| import { feeLevels } from '../../app.constants'; | ||||
| import { map, startWith, tap } from 'rxjs/operators'; | ||||
| import { ThemeService } from 'src/app/services/theme.service'; | ||||
| 
 | ||||
| @Component({ | ||||
|   selector: 'app-fees-box', | ||||
| @ -18,7 +19,8 @@ export class FeesBoxComponent implements OnInit { | ||||
|   noPriority = '#2e324e'; | ||||
| 
 | ||||
|   constructor( | ||||
|     private stateService: StateService | ||||
|     private stateService: StateService, | ||||
|     private themeService: ThemeService, | ||||
|   ) { } | ||||
| 
 | ||||
|   ngOnInit(): void { | ||||
| @ -33,11 +35,11 @@ export class FeesBoxComponent implements OnInit { | ||||
|         tap((fees) => { | ||||
|           let feeLevelIndex = feeLevels.slice().reverse().findIndex((feeLvl) => fees.minimumFee >= feeLvl); | ||||
|           feeLevelIndex = feeLevelIndex >= 0 ? feeLevels.length - feeLevelIndex : feeLevelIndex; | ||||
|           const startColor = '#' + (mempoolFeeColors[feeLevelIndex - 1] || mempoolFeeColors[mempoolFeeColors.length - 1]); | ||||
|           const startColor = '#' + (this.themeService.mempoolFeeColors[feeLevelIndex - 1] || this.themeService.mempoolFeeColors[this.themeService.mempoolFeeColors.length - 1]); | ||||
| 
 | ||||
|           feeLevelIndex = feeLevels.slice().reverse().findIndex((feeLvl) => fees.fastestFee >= feeLvl); | ||||
|           feeLevelIndex = feeLevelIndex >= 0 ? feeLevels.length - feeLevelIndex : feeLevelIndex; | ||||
|           const endColor = '#' + (mempoolFeeColors[feeLevelIndex - 1] || mempoolFeeColors[mempoolFeeColors.length - 1]); | ||||
|           const endColor = '#' + (this.themeService.mempoolFeeColors[feeLevelIndex - 1] || this.themeService.mempoolFeeColors[this.themeService.mempoolFeeColors.length - 1]); | ||||
| 
 | ||||
|           this.gradient = `linear-gradient(to right, ${startColor}, ${endColor})`; | ||||
|           this.noPriority = startColor; | ||||
|  | ||||
| @ -4,12 +4,13 @@ import { MempoolBlock } from '../../interfaces/websocket.interface'; | ||||
| import { StateService } from '../../services/state.service'; | ||||
| import { Router } from '@angular/router'; | ||||
| import { map, switchMap, tap } from 'rxjs/operators'; | ||||
| import { feeLevels, mempoolFeeColors } from '../../app.constants'; | ||||
| import { feeLevels } from '../../app.constants'; | ||||
| import { specialBlocks } from '../../app.constants'; | ||||
| import { RelativeUrlPipe } from '../../shared/pipes/relative-url/relative-url.pipe'; | ||||
| import { Location } from '@angular/common'; | ||||
| import { DifficultyAdjustment, MempoolPosition } from '../../interfaces/node-api.interface'; | ||||
| import { animate, style, transition, trigger } from '@angular/animations'; | ||||
| import { ThemeService } from 'src/app/services/theme.service'; | ||||
| 
 | ||||
| @Component({ | ||||
|   selector: 'app-mempool-blocks', | ||||
| @ -84,6 +85,7 @@ export class MempoolBlocksComponent implements OnInit, OnChanges, OnDestroy { | ||||
|   constructor( | ||||
|     private router: Router, | ||||
|     public stateService: StateService, | ||||
|     private themeService: ThemeService, | ||||
|     private cd: ChangeDetectorRef, | ||||
|     private relativeUrlPipe: RelativeUrlPipe, | ||||
|     private location: Location, | ||||
| @ -354,7 +356,7 @@ export class MempoolBlocksComponent implements OnInit, OnChanges, OnDestroy { | ||||
|     trimmedFeeRange.forEach((fee: number) => { | ||||
|       let feeLevelIndex = feeLevels.slice().reverse().findIndex((feeLvl) => fee >= feeLvl); | ||||
|       feeLevelIndex = feeLevelIndex >= 0 ? feeLevels.length - feeLevelIndex : feeLevelIndex; | ||||
|       gradientColors.push(mempoolFeeColors[feeLevelIndex - 1] || mempoolFeeColors[mempoolFeeColors.length - 1]); | ||||
|       gradientColors.push(this.themeService.mempoolFeeColors[feeLevelIndex - 1] || this.themeService.mempoolFeeColors[this.themeService.mempoolFeeColors.length - 1]); | ||||
|     }); | ||||
| 
 | ||||
|     gradientColors.forEach((color, i, gc) => { | ||||
|  | ||||
							
								
								
									
										103
									
								
								frontend/src/app/services/theme.service.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										103
									
								
								frontend/src/app/services/theme.service.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,103 @@ | ||||
| import { Injectable } from '@angular/core'; | ||||
| import { audit, Subject } from 'rxjs'; | ||||
| import { Color } from '../components/block-overview-graph/sprite-types'; | ||||
| import { defaultMempoolFeeColors, contrastMempoolFeeColors } from '../app.constants'; | ||||
| import { StorageService } from './storage.service'; | ||||
| 
 | ||||
| const defaultAuditColors = { | ||||
|   censored: hexToColor('f344df'), | ||||
|   missing: darken(desaturate(hexToColor('f344df'), 0.3), 0.7), | ||||
|   added: hexToColor('0099ff'), | ||||
|   selected: darken(desaturate(hexToColor('0099ff'), 0.3), 0.7), | ||||
| }; | ||||
| const contrastAuditColors = { | ||||
|   censored: hexToColor('ffa8ff'), | ||||
|   missing: darken(desaturate(hexToColor('ffa8ff'), 0.3), 0.7), | ||||
|   added: hexToColor('00bb98'), | ||||
|   selected: darken(desaturate(hexToColor('00bb98'), 0.3), 0.7), | ||||
| }; | ||||
| 
 | ||||
| @Injectable({ | ||||
|   providedIn: 'root' | ||||
| }) | ||||
| export class ThemeService { | ||||
|   style: HTMLLinkElement; | ||||
|   theme: string = 'default'; | ||||
|   themeChanged$: Subject<string> = new Subject(); | ||||
|   mempoolFeeColors: string[] = defaultMempoolFeeColors; | ||||
| 
 | ||||
|   /* block visualization colors */ | ||||
|   defaultHoverColor: Color; | ||||
|   feeColors: Color[]; | ||||
|   auditFeeColors: Color[]; | ||||
|   auditColors: { [category: string]: Color } = defaultAuditColors; | ||||
| 
 | ||||
|   constructor( | ||||
|     private storageService: StorageService, | ||||
|   ) { | ||||
|     const theme = this.storageService.getValue('theme-preference') || 'default'; | ||||
|     this.apply(theme); | ||||
|   } | ||||
| 
 | ||||
|   apply(theme) { | ||||
|     this.theme = theme; | ||||
|     if (theme !== 'default') { | ||||
|       if (theme === 'contrast') { | ||||
|         this.mempoolFeeColors = contrastMempoolFeeColors; | ||||
|         this.auditColors = contrastAuditColors; | ||||
|       } | ||||
|       if (!this.style) { | ||||
|         this.style = document.createElement('link'); | ||||
|         this.style.rel = 'stylesheet'; | ||||
|         this.style.href = `theme-${theme}.css`; | ||||
|         document.head.appendChild(this.style); | ||||
|       } else { | ||||
|         this.style.href = `theme-${theme}.css`; | ||||
|       } | ||||
|     } else { | ||||
|       this.mempoolFeeColors = defaultMempoolFeeColors; | ||||
|       this.auditColors = defaultAuditColors; | ||||
|       if (this.style) { | ||||
|         this.style.remove(); | ||||
|         this.style = null; | ||||
|       } | ||||
|     } | ||||
|     this.updateFeeColors(); | ||||
|     this.storageService.setValue('theme-preference', theme); | ||||
|     this.themeChanged$.next(this.theme); | ||||
|   } | ||||
| 
 | ||||
|   updateFeeColors() { | ||||
|     this.defaultHoverColor = hexToColor('1bd8f4'); | ||||
|     this.feeColors = this.mempoolFeeColors.map(hexToColor); | ||||
|     this.auditFeeColors = this.feeColors.map((color) => darken(desaturate(color, 0.3), 0.9)); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| export function hexToColor(hex: string): Color { | ||||
|   return { | ||||
|     r: parseInt(hex.slice(0, 2), 16) / 255, | ||||
|     g: parseInt(hex.slice(2, 4), 16) / 255, | ||||
|     b: parseInt(hex.slice(4, 6), 16) / 255, | ||||
|     a: 1 | ||||
|   }; | ||||
| } | ||||
| 
 | ||||
| export function desaturate(color: Color, amount: number): Color { | ||||
|   const gray = (color.r + color.g + color.b) / 6; | ||||
|   return { | ||||
|     r: color.r + ((gray - color.r) * amount), | ||||
|     g: color.g + ((gray - color.g) * amount), | ||||
|     b: color.b + ((gray - color.b) * amount), | ||||
|     a: color.a, | ||||
|   }; | ||||
| } | ||||
| 
 | ||||
| export function darken(color: Color, amount: number): Color { | ||||
|   return { | ||||
|     r: color.r * amount, | ||||
|     g: color.g * amount, | ||||
|     b: color.b * amount, | ||||
|     a: color.a, | ||||
|   } | ||||
| } | ||||
							
								
								
									
										70
									
								
								frontend/src/theme-contrast.scss
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										70
									
								
								frontend/src/theme-contrast.scss
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,70 @@ | ||||
| /* Theme */ | ||||
| $bg: #ff1f31; | ||||
| $active-bg: #ff131f; | ||||
| $hover-bg: #ff131e; | ||||
| $fg: #ff0; | ||||
| 
 | ||||
| /* Bootstrap */ | ||||
| 
 | ||||
| $body-bg: $bg; | ||||
| $body-color: $fg; | ||||
| $gray-800: $bg; | ||||
| $gray-700: $fg; | ||||
| 
 | ||||
| $nav-tabs-link-active-bg: $active-bg; | ||||
| 
 | ||||
| $primary: #105fb0; | ||||
| $secondary: #2d3348; | ||||
| $tertiary: #653b9c; | ||||
| $success: #1a9436; | ||||
| $info: #1bd8f4; | ||||
| 
 | ||||
| $h5-font-size: 1.15rem !default; | ||||
| 
 | ||||
| $pagination-bg: $body-bg; | ||||
| $pagination-border-color: $gray-800; | ||||
| $pagination-disabled-bg:           $fg; | ||||
| $pagination-disabled-border-color: $bg; | ||||
| $pagination-active-color:          $fg; | ||||
| $pagination-active-bg:             $tertiary; | ||||
| $pagination-hover-bg:              $hover-bg; | ||||
| $pagination-hover-border-color:     $bg; | ||||
| $pagination-disabled-bg:          $bg; | ||||
| 
 | ||||
| $custom-select-indicator-color: $fg; | ||||
| 
 | ||||
| .input-group-text { | ||||
|   background-color: #1c2031 !important; | ||||
|   border: 1px solid #20263e !important; | ||||
| } | ||||
| 
 | ||||
| $link-color:                $info; | ||||
| $link-decoration:           none !default; | ||||
| $link-hover-color:          darken($link-color, 15%) !default; | ||||
| $link-hover-decoration:     underline !default; | ||||
| 
 | ||||
| $dropdown-bg: $bg; | ||||
| $dropdown-link-color: $fg; | ||||
| 
 | ||||
| $dropdown-link-hover-color: $fg; | ||||
| $dropdown-link-hover-bg: $active-bg; | ||||
| 
 | ||||
| $dropdown-link-active-color: $fg; | ||||
| $dropdown-link-active-bg: $active-bg; | ||||
| 
 | ||||
| @import "~bootstrap/scss/bootstrap"; | ||||
| 
 | ||||
| :root { | ||||
|   --bg: #{$bg}; | ||||
|   --active-bg: #{$active-bg}; | ||||
|   --hover-bg: #{$hover-bg}; | ||||
|   --fg: #{$fg}; | ||||
| 
 | ||||
|   --primary: #{$primary}; | ||||
|   --secondary: #{$secondary}; | ||||
|   --tertiary: #{$tertiary}; | ||||
|   --success: #{$success}; | ||||
|   --info: #{$info}; | ||||
| 
 | ||||
|   --box-bg: var(--box-bg); | ||||
| } | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user