Output.tsx 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146
  1. import { getGradientFirstParam } from '.';
  2. import React from 'react';
  3. import styled from 'styled-components';
  4. import { breakpoints } from '~/components/layout';
  5. import { ColorType, useInputStore } from '~/components/store';
  6. export const symbols = /[\r\n%#()<>?[\\\]^`{|}]/g;
  7. export const rgbToString = ({ r, g, b, a }: ColorType) => `rgba(${r},${g},${b},${a})`;
  8. const Output: React.FC = () => {
  9. const [svgProps, cssProps, filterProps] = useInputStore((state) => [
  10. state.svgProps,
  11. state.cssProps,
  12. state.filterProps,
  13. ]);
  14. const { size, baseFrequency, numOctaves } = svgProps;
  15. const { gradients, showTransparency } = cssProps;
  16. const { brightness, contrast, invert } = filterProps;
  17. const svgString = `<!-- svg: first layer -->
  18. <svg viewBox='0 0 ${size} ${size}' xmlns='http://www.w3.org/2000/svg'>
  19. <filter id='noiseFilter'>
  20. <feTurbulence
  21. type='fractalNoise'
  22. baseFrequency='${baseFrequency}'
  23. numOctaves='${numOctaves}'
  24. stitchTiles='stitch'/>
  25. </filter>
  26. <rect width='100%' height='100%' filter='url(#noiseFilter)'/>
  27. </svg>`;
  28. const gradientsString = gradients
  29. .filter((grad) => grad.isVisible)
  30. .map(
  31. (grad) =>
  32. `${grad.type}-gradient(${getGradientFirstParam(grad)}, ${rgbToString(
  33. grad.stops[0].color
  34. )}, ${rgbToString(grad.stops[1].color)})`
  35. );
  36. const gradientCss = `/* css gradient: second layer */
  37. {
  38. width: 250px;
  39. height: 250px;
  40. background: ${gradientsString.join(', ')} ${showTransparency ? ', url(/checkers.png)' : ''};
  41. /* filter: contrast(${contrast}%) brightness(${brightness}%)${invert ? ' invert(100%)' : ''}; */
  42. }`;
  43. const liveCss = `
  44. width: 250px;
  45. height: 250px;
  46. background: ${gradientsString.join(', ')}, url("data:image/svg+xml,${svgString.replace(
  47. symbols,
  48. encodeURIComponent
  49. )}");
  50. filter: contrast(${contrast}%) brightness(${brightness}%)${invert ? ' invert(100%)' : ''};
  51. `;
  52. return (
  53. <Container>
  54. <OutputSection>
  55. <Noise size={size} code={svgString} />
  56. </OutputSection>
  57. <OutputSection>
  58. <Gradient css={gradientCss} />
  59. </OutputSection>
  60. <OutputSection>
  61. <FilterShadow />
  62. <Filter css={liveCss} />
  63. </OutputSection>
  64. </Container>
  65. );
  66. };
  67. export default Output;
  68. // hacking it for essentially flex-direction: column; which does not work
  69. const Container = styled.div`
  70. height: 100vh;
  71. display: flex;
  72. overflow-y: scroll;
  73. flex-wrap: wrap;
  74. flex-direction: row;
  75. justify-content: center;
  76. align-items: center;
  77. @media screen and (max-width: ${breakpoints.md - 1}px) {
  78. background-color: #fff;
  79. position: fixed;
  80. bottom: 0;
  81. height: 280px;
  82. overflow-x: scroll;
  83. flex-wrap: nowrap;
  84. justify-content: flex-start;
  85. width: 100vw;
  86. border-top: 2px solid #333;
  87. }
  88. `;
  89. type NoiseProps = {
  90. size: number;
  91. code: string;
  92. };
  93. // prettier-ignore
  94. const Noise = styled.div<NoiseProps>`
  95. width: ${(p) => p.size}px;
  96. height: ${(p) => p.size}px;
  97. background: url("data:image/svg+xml,${(p) => p.code.replace(symbols, encodeURIComponent)}");
  98. box-shadow: rgb(50 50 93 / 23%) 0px 30px 60px -15px, rgb(0 0 0 / 32%) 0px 18px 36px -18px;
  99. `;
  100. type GradientProps = {
  101. css: string;
  102. };
  103. const Gradient = styled.div<GradientProps>`
  104. ${(p) => p.css}
  105. box-shadow: rgb(50 50 93 / 23%) 0px 30px 60px -15px, rgb(0 0 0 / 32%) 0px 18px 36px -18px;
  106. `;
  107. const OutputSection = styled.div`
  108. position: relative;
  109. display: flex;
  110. justify-content: center;
  111. width: calc(100vw * 2 / 3);
  112. margin: 10px;
  113. @media screen and (max-width: ${breakpoints.md - 1}px) {
  114. width: auto;
  115. }
  116. `;
  117. // have to create a new layer, or the filter affects box-shadow
  118. const FilterShadow = styled.div`
  119. position: absolute;
  120. top: 0;
  121. width: 250px;
  122. height: 250px;
  123. box-shadow: rgb(50 50 93 / 23%) 0px 30px 60px -15px, rgb(0 0 0 / 32%) 0px 18px 36px -18px;
  124. `;
  125. type FilterProps = {
  126. css: string;
  127. };
  128. const Filter = styled.div<FilterProps>`
  129. ${(p) => p.css}
  130. `;