diff --git a/src/components/app/AppCheckbox.tsx b/src/components/app/AppCheckbox.tsx index dfe0404..3a43992 100644 --- a/src/components/app/AppCheckbox.tsx +++ b/src/components/app/AppCheckbox.tsx @@ -8,22 +8,18 @@ export default class AppCheckbox extends Vue { @Prop({ default: false }) value!: boolean @Prop({ default: '' }) label!: string - get isActive() { - return this.value - } - - get classes() { + get classes(): Record { return { checkbox: true, 'checkbox--active': this.value } } - get idCheckbox() { + get idCheckbox(): string { return `input-${(this as any)._uid}` } - changeState() { + changeState(): void { this.$emit('input', !this.value) } @@ -41,9 +37,9 @@ export default class AppCheckbox extends Vue { onInput={ this.changeState } /> -

+

+ } } diff --git a/src/components/themes/agida/index.tsx b/src/components/themes/agida/index.tsx new file mode 100644 index 0000000..af52dc3 --- /dev/null +++ b/src/components/themes/agida/index.tsx @@ -0,0 +1,248 @@ +import { Component, Vue } from 'vue-property-decorator' +import { AppModule } from '@/store/app' +import { waveInit } from './lib' +import { Debounce } from '@/utils/helper' +import { Wavery } from './lib/wave' +import { changeHsl, hexToRgb, rgbToHsl } from '@/utils/color' + +export const OPACITY_ARR = [0.265, 0.4, 0.53, 1] +export const topColors = ['#03C79C', '#00A5B2', '#0080A5', '#005A8D'] +export const bottomColors = ['#9C1EFF', '#8518E9', '#6F12D3', '#590ABD'] +export const MAX_WAVES = 4 + +@Component +export default class DestructionTheme extends Vue { + iconWidth = 667 + iconHeight = 684 + test = 1 + + wave = { + height: 300, + width: 1200, + segmentCount: 5, + layerCount: 4, + variance: 1, + animation: { + steps: 2, + time: 40000 + } + } + + screen = { + width: window.innerWidth, + height: window.innerHeight + } + + get topColor() { + return '#03C79C' + } + + get bottomColor() { + return '#9C1EFF' + } + + get topColors() { + return this.generateArrayColors(this.topColor) + } + + get bottomColors() { + return this.generateArrayColors(this.bottomColor) + } + + generateArrayColors(color: string) { + const initHSL = rgbToHsl(hexToRgb(color)) + const second = changeHsl(initHSL, 17, 3, -5) + const third = changeHsl(initHSL, 26, 3, -8) + const thourd = changeHsl(initHSL, 35, 3, -12) + + return [initHSL, second, third, thourd] + } + + get animationSpeed() { + return AppModule.getThemeInput('animation-speed')?.value as number || 40 + } + + get width() { + return this.screen.width + } + + get height() { + return this.screen.height + } + + get viewBox() { + return `0 0 ${this.width} ${this.height}` + } + + get topWaves() { + return this.generateWave('top') + } + + get bottomWaves() { + return this.generateWave('bottom') + } + + mounted() { + this.updateScreen() + window.addEventListener('resize', this.updateScreen) + } + + beforeDestroy() { + window.removeEventListener('resize', this.updateScreen) + } + + @Debounce(50) + updateScreen() { + this.screen = { + width: window.innerWidth, + height: window.innerHeight + } + } + + generateWave(type: 'bottom' | 'top') { + const wavery = new Wavery(this.wave) + const waveSvg = wavery.generateSvg() + + const { height, width, xmlns, paths } = waveSvg.svg + const isTop = type === 'top' + const colorsArray = isTop ? topColors : bottomColors + const angle = Math.atan(window.innerHeight / window.innerWidth) * (180 / Math.PI) * 1.1 + + return + {paths.map((path, index) => { + const pathProps = [] + + if (path.animatedPath) { + pathProps.push() + } + + pathProps.push( + + ) + + return pathProps + })} + + } + + render() { + return
+ { this.topWaves } + { this.bottomWaves } + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ } +} diff --git a/src/components/themes/agida/lib/bezier-spline.ts b/src/components/themes/agida/lib/bezier-spline.ts new file mode 100644 index 0000000..89d94f0 --- /dev/null +++ b/src/components/themes/agida/lib/bezier-spline.ts @@ -0,0 +1,62 @@ +/* bezier-spline.js + * + * computes cubic bezier coefficients to generate a smooth + * line through specified points. couples with SVG graphics + * for interactive processing. + * + * For more info see: + * http://www.particleincell.com/2012/bezier-splines/ + * + * Lubos Brieda, Particle In Cell Consulting LLC, 2012 + * you may freely use this algorithm in your codes however where feasible + * please include a link/reference to the source article + */ +/* computes control points given knots K, this is the brain of the operation */ +export function computeControlPoints(K: number[]) { + const p1 = [] + const p2 = [] + const n = K.length - 1 + + /* rhs vector */ + const a = [] + const b = [] + const c = [] + const r = [] + + /* left most segment */ + a[0] = 0 + b[0] = 2 + c[0] = 1 + r[0] = K[0] + 2 * K[1] + + /* internal segments */ + for (let i = 1; i < n - 1; i++) { + a[i] = 1 + b[i] = 4 + c[i] = 1 + r[i] = 4 * K[i] + 2 * K[i + 1] + } + + /* right segment */ + a[n - 1] = 2 + b[n - 1] = 7 + c[n - 1] = 0 + r[n - 1] = 8 * K[n - 1] + K[n] + + /* solves Ax=b with the Thomas algorithm (from Wikipedia) */ + for (let i = 1; i < n; i++) { + const m: number = a[i] / b[i - 1] + b[i] = b[i] - m * c[i - 1] + r[i] = r[i] - m * r[i - 1] + } + + p1[n - 1] = r[n - 1] / b[n - 1] + for (let i = n - 2; i >= 0; --i) p1[i] = (r[i] - c[i] * p1[i + 1]) / b[i] + + /* we have p1, now compute p2 */ + for (let i = 0; i < n - 1; i++) p2[i] = 2 * K[i + 1] - p1[i + 1] + + p2[n - 1] = 0.5 * (K[n] + p1[n - 1]) + + return { p1: p1, p2: p2 } +} diff --git a/src/components/themes/agida/lib/index.ts b/src/components/themes/agida/lib/index.ts new file mode 100644 index 0000000..040a064 --- /dev/null +++ b/src/components/themes/agida/lib/index.ts @@ -0,0 +1,7 @@ +import { Wavery, WaveConfigInterface } from './wave' + +export function waveInit(data: WaveConfigInterface) { + const wavery = new Wavery(data) + + return wavery.generateSvg() +} diff --git a/src/components/themes/agida/lib/wave.ts b/src/components/themes/agida/lib/wave.ts new file mode 100644 index 0000000..956dfee --- /dev/null +++ b/src/components/themes/agida/lib/wave.ts @@ -0,0 +1,196 @@ +import { computeControlPoints } from './bezier-spline' +const svgns = 'http://www.w3.org/2000/svg' + +export interface WaveConfigInterface { + height: number; + width: number; + segmentCount: number; + layerCount: number; + variance: number; + strokeWidth?: number; + fillColor?: string; + strokeColor?: string; + transform?: string; + animation?: boolean | WaveConfigAnimationInterface; +} + +export interface WaveConfigAnimationInterface { + time?: number; + steps?: number; +} + +interface Point { + x: number; + y: number; +} + +const defaultAnimationValues = { + steps: 3, + time: 40000, + timingFunction: 'ease', + iteration: 'infinite' +} + +export class Wavery { + properties!: WaveConfigInterface + points: Point[][] = [] + animationPoints: Point[][][] = []; + + constructor(properties: WaveConfigInterface) { + this.properties = properties + this.points = this.generatePoints() + + if (this.needAnimation) { + const { steps } = this.animation + + Array.from(Array(steps)).forEach(() => { + this.animationPoints.push(this.generatePoints()) + }) + } + } + + get animation() { + const animation = this.properties.animation + const isBoolean = typeof animation === 'boolean' + + return isBoolean ? defaultAnimationValues : { ...defaultAnimationValues, ...animation } + } + + get cellWidth() { + return this.properties.width / this.properties.segmentCount + } + + get cellHeight() { + return this.properties.height / this.properties.layerCount + } + + get needAnimation() { + return this.properties.animation + } + + get pathList() { + const pathList = [] + + for (let i = 0; i < this.points.length; i++) { + pathList.push(this.generateClosedPath(i)) + } + + return pathList + } + + generatePoints(): Point[][] { + const { cellWidth, cellHeight } = this + const { width, height, variance } = this.properties + const moveLimitX = cellWidth * variance * 0.2 + const moveLimitY = cellHeight * variance + const points = [] + + for (let y = cellHeight; y <= height; y += cellHeight) { + const pointsPerLayer = [] + pointsPerLayer.push({ x: 0, y: Math.floor(y) }) + + for (let x = cellWidth; x < width; x += cellWidth) { + const varietalY = y - moveLimitY / 2 + Math.random() * moveLimitY + const varietalX = x - moveLimitX / 2 + Math.random() * moveLimitX + pointsPerLayer.push({ + x: Math.floor(varietalX), + y: Math.floor(varietalY) + }) + } + pointsPerLayer.push({ x: width, y: Math.floor(y) }) + points.push(pointsPerLayer) + } + + return points + } + + generateClosedPath(index: number) { + const animatedPathList: string[] = [] + + const { fillColor, strokeColor, strokeWidth, transform } = this.properties + const style = { fillColor, strokeColor, strokeWidth, transform } + + const path = this.generatePath(this.points[index]) + + if (this.needAnimation) { + this.animationPoints.forEach((waves) => { + animatedPathList.push(this.generatePath(waves[index])) + }) + } + + return { + ...style, + d: path, + animatedPath: animatedPathList + } + } + + generatePath(points: Point[]) { + const xPoints = points.map((p) => p.x) + const yPoints = points.map((p) => p.y) + + const leftCornerPoint = { x: 0, y: this.properties.height + this.cellHeight } + const rightCornerPoint = { x: this.properties.width, y: this.properties.height + this.cellHeight } + const xControlPoints = computeControlPoints(xPoints) + const yControlPoints = computeControlPoints(yPoints) + + let path = + `M ${leftCornerPoint.x},${leftCornerPoint.y} ` + + `C ${leftCornerPoint.x},${leftCornerPoint.y} ` + + `${xPoints[0]},${yPoints[0]} ` + + `${xPoints[0]},${yPoints[0]} ` + + for (let i = 0; i < xPoints.length - 1; i++) { + path += + `C ${xControlPoints.p1[i]},${yControlPoints.p1[i]} ` + + `${xControlPoints.p2[i]},${yControlPoints.p2[i]} ` + + `${xPoints[i + 1]},${yPoints[i + 1]} ` + } + + path += + `C ${xPoints[xPoints.length - 1]},${yPoints[xPoints.length - 1]} ` + + `${rightCornerPoint.x},${rightCornerPoint.y} ` + + `${rightCornerPoint.x},${rightCornerPoint.y} Z` + + return path + } + + private generateKeyframe(percent: number, d: string) { + return `${percent}% {d: path("${d}")}` + } + + generateAnimationStyle(index: number) { + const path = this.pathList[index] + const { steps } = this.animation + if (!path) { return } + + const animationList = Array.from(Array(steps + 2)).map((_, index, { length }) => { + const isBound = index === 0 || index + 1 === length + const percent = index * (100 / (steps + 1)) + return this.generateKeyframe(percent, isBound ? path.d : path.animatedPath[index - 1]) + }) + + console.log({ animationList }) + + return `.path-${index}{ + animation:pathAnim-${index} ${this.animation.time}ms; + animation-timing-function: ${this.animation.timingFunction}; + animation-iteration-count: ${this.animation.iteration}; + } + + @keyframes pathAnim-${index}{ + ${animationList.join('')} + }` + } + + generateSvg() { + return { + svg: { + width: this.properties.width, + height: this.properties.height + this.cellHeight, + xmlns: svgns, + paths: this.pathList + } + } + } +} diff --git a/src/components/themes/destruction/index.tsx b/src/components/themes/destruction/index.tsx index 8e44c3a..0250cd0 100644 --- a/src/components/themes/destruction/index.tsx +++ b/src/components/themes/destruction/index.tsx @@ -34,7 +34,7 @@ export default class DestructionTheme extends Vue { renderOptions: { externalTimeUse: true }, - renderHook: function() { + renderHook() { const gl = this as unknown as GL if (!gl.programInfo.uniforms.position) { diff --git a/src/style/general.styl b/src/style/general.styl index bac9172..ee3d508 100644 --- a/src/style/general.styl +++ b/src/style/general.styl @@ -14,5 +14,5 @@ .transition-group position: relative -.background-image - mask url('../assets/images/masks/main.svg') 50% 50% / 100% 80% no-repeat border-box \ No newline at end of file +// .background-image +// mask url('../assets/images/masks/main.svg') 50% 50% / 100% 80% no-repeat border-box \ No newline at end of file diff --git a/src/style/themes/agida.styl b/src/style/themes/agida.styl new file mode 100644 index 0000000..b1c9fd0 --- /dev/null +++ b/src/style/themes/agida.styl @@ -0,0 +1,14 @@ +.waves--top + position absolute + top 0 + left 0 + transform-origin 50% 100% + transform: translate(-50%, -100%) rotate(calc(180deg - var(--angle))) + +.waves--bottom + position absolute + bottom 0 + right 0 + transform-origin 50% 100% + transform translateX(50%) rotate(calc(-90deg + (90deg - var(--angle)))) + diff --git a/src/style/themes/index.styl b/src/style/themes/index.styl index 7e4efe2..0975292 100644 --- a/src/style/themes/index.styl +++ b/src/style/themes/index.styl @@ -1,3 +1,4 @@ +@import './agida.styl' @import './osmos.styl' @import './space.styl' @import './suprematism.styl' diff --git a/src/utils/color.ts b/src/utils/color.ts new file mode 100644 index 0000000..24172ac --- /dev/null +++ b/src/utils/color.ts @@ -0,0 +1,80 @@ +export type ColorArray = [number, number, number] + +export interface IConvertOptions { + view?: 'array' | 'string' +} + +export const hexToRgb = (color: string): ColorArray => { + const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(color) + return result ? [ + parseInt(result[1], 16), + parseInt(result[2], 16), + parseInt(result[3], 16) + ] : [0, 0, 0] +} + +export const fromBracketsToNumber = (color: string): ColorArray => { + const colors = color.slice(color.indexOf('(') + 1).replace(')', '').split(',') + + return colors.slice(0, 3).map(parseInt) as ColorArray +} + +export const changeHsl = (hsl: string, hAdd: number, sAdd: number, lAdd: number): string => { + let hslMass = fromBracketsToNumber(hsl) + return `hsl(${hslMass[0] + hAdd}, ${hslMass[1] + sAdd}%, ${hslMass[2] + lAdd}%)` +} + +export function rgbToHsl (colorRGB: ColorArray): string +export function rgbToHsl (colorRGB: ColorArray, convertOptions?: IConvertOptions): string | ColorArray { + let [r, g, b] = colorRGB + const { view } = convertOptions || {} + const isArray = view === 'array' + r /= 255 + g /= 255 + b /= 255 + let [max, min] = [Math.max(r, g, b), Math.min(r, g, b)] + + let h = 0 + let s = 0 + let l = (max + min) / 2 + + const finalArray = (): ColorArray => [Math.round(h), Math.round(s * 100), Math.round(l * 100)] + const formatString = (hsl: ColorArray) => `hsl(${hsl[0]}, ${hsl[1]}%, ${hsl[2]}%)` + if (max === min) { + h = s = 0 + return isArray ? finalArray() : formatString(finalArray()) + } + + let d = (max - min) + s = l >= 0.5 ? d / (2 - (max + min)) : d / (max + min) + switch (max) { + case r: h = ((g - b) / d + 0) * 60; break + case g: h = ((b - r) / d + 2) * 60; break + case b: h = ((r - g) / d + 4) * 60; break + } + + return isArray ? finalArray() : formatString(finalArray()) +} + +function getPrettyDate(timestamp: number): string; +function getPrettyDate(day: string, month: string, year: string): string; +function getPrettyDate(date: Date): string; + +// Может принимать от 1 до 3 параметров разных типов, в зависимости от перегрузок +function getPrettyDate(arg1: unknown, arg2?: unknown, arg3?: unknown) { + let prettyDate: string = ''; + + if(typeof arg1 === 'number') { + const date = new Date(arg1); + const day = date.getDate(); + const month = date.getMonth() + 1; + const year = date.getFullYear(); + + prettyDate = `${day}/${month}/${year}` + } + + return prettyDate; +} + +const timeStamp = new Date().getTime(); +getPrettyDate(timeStamp); \ No newline at end of file diff --git a/src/utils/constant.ts b/src/utils/constant.ts index 968ef5e..22b3065 100644 --- a/src/utils/constant.ts +++ b/src/utils/constant.ts @@ -31,6 +31,14 @@ export const defaultTheme: AppTheme = { } export const AppThemes: AppTheme[] = [ + { + name: 'Agida', + component: 'agida', + color: { + active: '#04ded4', + background: '#19102e' + } + }, defaultTheme, { name: 'Sphere',