├── .eslintignore
├── .eslintrc.json
├── .gitignore
├── .prettierrc.json
├── README.md
├── World
├── systems
│ ├── composer.ts
│ ├── index.ts
│ ├── pass
│ │ ├── chess.ts
│ │ ├── dotsGrid.ts
│ │ ├── dotsLanding.ts
│ │ ├── gradient.ts
│ │ ├── grain.ts
│ │ ├── helpers.ts
│ │ ├── index.ts
│ │ ├── liquid.ts
│ │ ├── liquidLarge.ts
│ │ ├── masking.ts
│ │ ├── silk.ts
│ │ ├── stepped.ts
│ │ ├── steppedWobble.ts
│ │ ├── verner.ts
│ │ └── zebra.ts
│ └── renderer.ts
└── things
│ ├── camera.ts
│ ├── index.ts
│ └── scene.ts
├── app.vue
├── assets
├── css
│ └── reset.css
└── scss
│ ├── global.scss
│ └── variables.scss
├── components
├── AppHeader.vue
├── Footer.vue
├── ToolLayout.vue
├── glass
│ ├── GlassControls.vue
│ ├── GlassCss.vue
│ ├── GlassImage.vue
│ ├── GlassImageSegment.vue
│ └── GlassWrapper.vue
├── landing
│ ├── Hero.vue
│ ├── Tool.vue
│ └── Tools.vue
├── poster
│ ├── AppContainer.vue
│ ├── controls
│ │ ├── ColorPicker.vue
│ │ ├── ColorsPicker.vue
│ │ ├── Controls.vue
│ │ └── ExportOptions.vue
│ └── scene
│ │ ├── Scene.vue
│ │ ├── SceneText.vue
│ │ └── text
│ │ └── TextTitle.vue
└── progressive-blur
│ ├── BlurredImage.vue
│ ├── CSSDisplay.vue
│ ├── Controls.vue
│ ├── MainWrapper.vue
│ ├── ProgressiveBlur.vue
│ └── ProgressiveBlur2.vue
├── constants
└── index.ts
├── helpers
├── fonts.ts
├── glass
│ ├── images.ts
│ └── index.ts
└── progressive-blur
│ ├── images.ts
│ └── index.ts
├── layouts
├── default.vue
└── tool.vue
├── nuxt.config.ts
├── package.json
├── pages
├── about.vue
├── glass
│ └── index.vue
├── index.vue
├── poster
│ ├── download.vue
│ └── index.vue
└── progressive-blur
│ └── index.vue
├── plugins
├── useEmitter.ts
└── useRandom.ts
├── public
├── android-chrome-192x192.png
├── android-chrome-512x512.png
├── apple-touch-icon.png
├── backdrop.png
├── favicon-16x16.png
├── favicon-32x32.png
├── favicon.ico
└── star.png
├── store
└── scene.ts
├── tsconfig.json
└── yarn.lock
/.eslintignore:
--------------------------------------------------------------------------------
1 | .nuxt
2 | dist
3 | .output
4 | .vercel
--------------------------------------------------------------------------------
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "browser": true,
4 | "es2021": true
5 | },
6 | "extends": [
7 | "plugin:vue/vue3-recommended"
8 | ],
9 | "parser": "vue-eslint-parser",
10 | "parserOptions": {
11 | "ecmaVersion": "latest",
12 | "parser": "@typescript-eslint/parser"
13 | },
14 | "plugins": [],
15 | "rules": {
16 | "vue/multi-word-component-names": 0
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Created by .ignore support plugin (hsz.mobi)
2 | ### Node template
3 | # Logs
4 | logs
5 | *.log
6 | npm-debug.log*
7 | yarn-debug.log*
8 | yarn-error.log*
9 |
10 | # Runtime data
11 | pids
12 | *.pid
13 | *.seed
14 | *.pid.lock
15 |
16 | # Directory for instrumented libs generated by jscoverage/JSCover
17 | lib-cov
18 |
19 | # Coverage directory used by tools like istanbul
20 | coverage
21 |
22 | # nyc test coverage
23 | .nyc_output
24 |
25 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
26 | .grunt
27 |
28 | # Bower dependency directory (https://bower.io/)
29 | bower_components
30 |
31 | # node-waf configuration
32 | .lock-wscript
33 |
34 | # Compiled binary addons (https://nodejs.org/api/addons.html)
35 | build/Release
36 |
37 | # Dependency directories
38 | node_modules/
39 | jspm_packages/
40 |
41 | # TypeScript v1 declaration files
42 | typings/
43 |
44 | # Optional npm cache directory
45 | .npm
46 |
47 | # Optional eslint cache
48 | .eslintcache
49 |
50 | # Optional REPL history
51 | .node_repl_history
52 |
53 | # Output of 'npm pack'
54 | *.tgz
55 |
56 | # Yarn Integrity file
57 | .yarn-integrity
58 |
59 | # dotenv environment variables file
60 | .env
61 | .env.build
62 |
63 | # parcel-bundler cache (https://parceljs.org/)
64 | .cache
65 |
66 | # next.js build output
67 | .next
68 |
69 | # nuxt.js build output
70 | .nuxt
71 | .output
72 |
73 | # Nuxt generate
74 | dist
75 |
76 | # vuepress build output
77 | .vuepress/dist
78 |
79 | # Serverless directories
80 | .serverless
81 |
82 | # IDE / Editor
83 | .idea
84 |
85 | # Service worker
86 | sw.*
87 |
88 | # macOS
89 | .DS_Store
90 |
91 | # Vim swap files
92 | *.swp
93 |
94 | # Vercel
95 | .vercel
96 |
--------------------------------------------------------------------------------
/.prettierrc.json:
--------------------------------------------------------------------------------
1 | {}
2 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Nuxt Example
2 |
3 | Deploy your [Nuxt](https://nuxt.com) project to Vercel with zero configuration.
4 |
5 | [](https://vercel.com/new/clone?repository-url=https://github.com/vercel/vercel/tree/main/examples/nuxtjs&template=nuxtjs)
6 |
7 | _Live Example: https://nuxtjs-template.vercel.app_
8 |
9 | Look at the [Nuxt 3 documentation](https://v3.nuxtjs.org) to learn more.
10 |
11 | ## Setup
12 |
13 | Make sure to install the dependencies:
14 |
15 | ```bash
16 | # yarn
17 | yarn
18 |
19 | # npm
20 | npm install
21 |
22 | # pnpm
23 | pnpm install --shamefully-hoist
24 | ```
25 |
26 | ## Development Server
27 |
28 | Start the development server on http://localhost:3000
29 |
30 | ```bash
31 | npm run dev
32 | ```
33 |
34 | ## Production
35 |
36 | Build the application for production:
37 |
38 | ```bash
39 | npm run build
40 | ```
41 |
42 | Locally preview production build:
43 |
44 | ```bash
45 | npm run preview
46 | ```
47 |
48 | Checkout the [deployment documentation](https://nuxt.com/docs/getting-started/deployment#presets) for more information.
49 |
--------------------------------------------------------------------------------
/World/systems/composer.ts:
--------------------------------------------------------------------------------
1 | import {
2 | Camera,
3 | LinearFilter,
4 | RGBAFormat,
5 | Scene,
6 | WebGLRenderer,
7 | WebGLRenderTarget,
8 | } from "three";
9 | import { HEIGHT, WIDTH } from "~/constants";
10 | import { EffectComposer } from "three/examples/jsm/postprocessing/EffectComposer.js";
11 | import {
12 | createExportRenderer,
13 | createLandingRenderer,
14 | createRenderer,
15 | } from "~/World/systems/renderer";
16 | import { getPasses } from "~/World/systems/pass";
17 | import { Pass } from "three/examples/jsm/postprocessing/Pass";
18 |
19 | function createBaseComposer(renderer: WebGLRenderer, passes: Pass[]) {
20 | const parameters = {
21 | minFilter: LinearFilter,
22 | magFilter: LinearFilter,
23 | format: RGBAFormat,
24 | stencilBuffer: false,
25 | };
26 | const renderTarget = new WebGLRenderTarget(WIDTH, HEIGHT, parameters);
27 | const composer = new EffectComposer(renderer, renderTarget);
28 | composer.setSize(WIDTH, HEIGHT);
29 | passes.forEach((pass) => composer.addPass(pass));
30 | return composer;
31 | }
32 |
33 | function createComposer(camera: Camera, passes: Pass[] = getPasses(camera)) {
34 | return createBaseComposer(createRenderer(), passes);
35 | }
36 |
37 | function createExportComposer(
38 | camera: Camera,
39 | passes: Pass[] = getPasses(camera),
40 | ) {
41 | return createBaseComposer(createExportRenderer(), passes);
42 | }
43 |
44 | export { createComposer, createExportComposer };
45 |
--------------------------------------------------------------------------------
/World/systems/index.ts:
--------------------------------------------------------------------------------
1 | export { createRenderer } from "./renderer";
2 |
--------------------------------------------------------------------------------
/World/systems/pass/chess.ts:
--------------------------------------------------------------------------------
1 | import { ShaderPass } from "three/examples/jsm/postprocessing/ShaderPass.js";
2 | import { Camera } from "three";
3 | import { baseShaderUniforms, baseUniforms } from "~/World/systems/pass/helpers";
4 |
5 | const vertexShader = `
6 | varying vec2 vUv;
7 | void main() {
8 | vUv = uv;
9 | gl_Position = projectionMatrix
10 | * modelViewMatrix
11 | * vec4( position, 1.0 );
12 | }
13 | `;
14 |
15 | const fragmentShader = `
16 | #define PI 3.14159265358979323846
17 | uniform float offset;
18 | varying vec2 vUv;
19 | ${baseShaderUniforms}
20 |
21 | // Function to generate a simple procedural noise
22 | float simpleNoise(vec2 st)
23 | {
24 | return fract(sin(dot(st, vec2(12.9898, 78.233))) * 43758.5453);
25 | }
26 |
27 | void main()
28 | {
29 | ivec2 cell = ivec2(floor(vUv * 8.0));
30 | bool isBlack = (cell.x + cell.y) % 2 == 1;
31 | float noise = simpleNoise(vUv * position.x * 00.1);
32 | vec2 perturb = vec2(noise * 0.1 - 0.05, noise * position.y * 00.1 - 0.05);
33 | vec2 distortedTexCoord = vUv + perturb;
34 |
35 | ivec2 distortedCell = ivec2(floor(distortedTexCoord * 8.0));
36 | bool isDistortedBlack = (distortedCell.x + distortedCell.y) % 2 == 1;
37 | vec3 color = isDistortedBlack ? colors.background.rgb : colors.color.rgb;
38 |
39 | gl_FragColor = vec4(color, 1.0);
40 | }
41 |
42 | `;
43 |
44 | function createChessPass(camera: Camera) {
45 | const { $random } = useNuxtApp();
46 | const effect = {
47 | uniforms: {
48 | ...baseUniforms(),
49 | offset: { value: 10 + $random.$getRandom() * 50 },
50 | },
51 | vertexShader: vertexShader,
52 | fragmentShader: fragmentShader,
53 | name: "ChessPass",
54 | };
55 | const pass = new ShaderPass(effect);
56 | pass.renderToScreen = true;
57 | return pass;
58 | }
59 |
60 | export { createChessPass };
61 |
--------------------------------------------------------------------------------
/World/systems/pass/dotsGrid.ts:
--------------------------------------------------------------------------------
1 | import { ShaderPass } from "three/examples/jsm/postprocessing/ShaderPass.js";
2 | import { Camera } from "three";
3 | import { baseShaderUniforms, baseUniforms } from "~/World/systems/pass/helpers";
4 |
5 | const vertexShader = `
6 | varying vec2 vUv;
7 | void main() {
8 | vUv = uv;
9 | gl_Position = projectionMatrix
10 | * modelViewMatrix
11 | * vec4( position, 1.0 );
12 | }
13 | `;
14 |
15 | const fragmentShader = `
16 | #define PI 3.14159265358979323846
17 | uniform float offset;
18 | varying vec2 vUv;
19 | ${baseShaderUniforms}
20 |
21 | float random (in vec2 _st) {
22 | return fract(sin(dot(_st.xy,
23 | vec2(12.9898,78.233)))*
24 | 43758.5453123);
25 | }
26 |
27 | vec2 pattern(in vec2 _st, in float _index){
28 | _index = fract(((_index-sin(position.x+2.))*sin(position.y-1.)));
29 | if (_index > 0.804) {
30 | _st = vec2(1.560-_st.x, 1.536-_st.y);
31 | }
32 | else {
33 | _st = vec2(2.400-_st.x, 1.976-_st.y);
34 | }
35 | return _st;
36 | }
37 |
38 | void main() {
39 | vec2 st = vUv;
40 | float aspect = width / height;
41 | st.x *= aspect;
42 | st *= offset;
43 | vec2 ipos = floor(st);
44 | vec2 fpos = fract(st);
45 | vec2 tile = pattern(fpos, random( ipos ));
46 | float color = 0.0;
47 | color = step(length(tile-vec2(1.,1.)),0.4);
48 | vec3 mixed = mix(colors.color.rgb, colors.background.rgb, clamp(1. - color, 0.0, 1.0));
49 | gl_FragColor = vec4(mixed, 1.);
50 | }
51 | `;
52 |
53 | function createDotsPass(camera: Camera) {
54 | const { $random } = useNuxtApp();
55 | const effect = {
56 | uniforms: {
57 | ...baseUniforms(),
58 | offset: { value: 10 + $random.$getRandom() * 50 },
59 | },
60 | vertexShader: vertexShader,
61 | fragmentShader: fragmentShader,
62 | name: "DotsPass",
63 | };
64 | const pass = new ShaderPass(effect);
65 | pass.renderToScreen = true;
66 | return pass;
67 | }
68 |
69 | export { createDotsPass };
70 |
--------------------------------------------------------------------------------
/World/systems/pass/dotsLanding.ts:
--------------------------------------------------------------------------------
1 | import { ShaderPass } from "three/examples/jsm/postprocessing/ShaderPass.js";
2 | import { useSceneStore } from "~/store/scene";
3 |
4 | const vertexShader = `
5 | varying vec2 vUv;
6 | void main() {
7 | vUv = uv;
8 | gl_Position = projectionMatrix
9 | * modelViewMatrix
10 | * vec4( position, 1.0 );
11 | }
12 | `;
13 |
14 | const fragmentShader = `
15 | vec3 white = vec3(1.0, 1.0, 1.0);
16 | vec3 black = vec3(0.0, 0.0, 0.0);
17 | float pixelWidth = 0.368;
18 | float thickness = 0.980;
19 | vec2 st;
20 | uniform float offset;
21 |
22 | float random(in vec2 st) {
23 | return fract(sin(dot(st.xy,
24 | vec2(12.9898,78.233)))*
25 | 43758.5453123);
26 | }
27 |
28 | vec3 drawCircle(vec2 center, float radius, vec3 color) {
29 | float r = sqrt(pow(st.xy.xy.x - center.x, 2.) + pow(st.xy.xy.y - center.y, 2.));
30 | float delta = r - radius;
31 | vec3 inside = color;
32 | float blend = smoothstep(0., pixelWidth, abs(delta) - thickness);
33 | return mix(white, inside, blend);
34 | }
35 |
36 | vec2 pattern(in vec2 _st, in float _index){
37 | _index = fract(((_index-1.660)*(10. / 10.)));
38 | if (_index > 0.860) {
39 | _st = vec2(0.5-_st.x, 0.5-_st.y);
40 | }
41 | else {
42 | _st = vec2(1.216-_st.x, 1.680-_st.y);
43 | }
44 | return _st;
45 | }
46 |
47 | void main() {
48 | vec2 st = gl_FragCoord.xy;
49 | st *= offset;
50 | vec2 ipos = floor(st);
51 | vec2 fpos = fract(st);
52 | vec2 tile = pattern(fpos, random( ipos ));
53 | vec3 color = drawCircle(tile, 1.512, vec3(random(ipos), cos(ipos.y), sin(ipos.x*random( ipos ))));
54 | gl_FragColor = vec4(color, 1.0);
55 | }
56 | `;
57 |
58 | function createLandingPass() {
59 | const { $random } = useNuxtApp();
60 | const effect = {
61 | uniforms: {
62 | offset: { value: $random.$getRandom() / 20 },
63 | },
64 | vertexShader: vertexShader,
65 | fragmentShader: fragmentShader,
66 | };
67 | const pass = new ShaderPass(effect);
68 | pass.renderToScreen = true;
69 | return pass;
70 | }
71 |
72 | export { createLandingPass };
73 |
--------------------------------------------------------------------------------
/World/systems/pass/gradient.ts:
--------------------------------------------------------------------------------
1 | import { Camera } from "three";
2 | import { ShaderPass } from "three/examples/jsm/postprocessing/ShaderPass";
3 | import { HEIGHT, WIDTH } from "~/constants";
4 | import {
5 | baseShaderUniforms,
6 | baseUniforms,
7 | translateColorspace,
8 | } from "~/World/systems/pass/helpers";
9 |
10 | const vertexShader = `
11 | varying vec2 vUv;
12 | void main() {
13 | vUv = uv;
14 | gl_Position = projectionMatrix
15 | * modelViewMatrix
16 | * vec4( position, 1.0 );
17 | }
18 | `;
19 |
20 | const fragmentShader = `
21 | uniform float offset;
22 | varying vec2 vUv;
23 | ${baseShaderUniforms}
24 | ${translateColorspace}
25 | void main() {
26 | vec2 center = vec2(0.5, 0.5 - (borders.top / (borders.bottom * 100.)));
27 | float aspect = width / height;
28 | vec2 uv = vUv;
29 | uv.x *= aspect;
30 | center.x *= aspect;
31 | vec4 gradientColor = vec4(0.5 - distance(center, uv), 0.0 + 0.5 * sin(position.x), 1.0 + 0.5 * cos(position.y), 1.0);
32 | vec3 hsv = rgb2hsv(gradientColor.rgb);
33 | hsv.x *= offset;
34 | gl_FragColor = vec4(hsv2rgb(hsv), 1.0);
35 | }
36 | `;
37 |
38 | function createGradientPass(camera: Camera) {
39 | const { $random } = useNuxtApp();
40 | const effect = {
41 | uniforms: {
42 | ...baseUniforms(),
43 | offset: { value: $random.$getRandom() * 10 },
44 | },
45 | vertexShader: vertexShader,
46 | fragmentShader: fragmentShader,
47 | name: "GradientPass",
48 | };
49 | const pass = new ShaderPass(effect);
50 | pass.renderToScreen = true;
51 | return pass;
52 | }
53 |
54 | export { createGradientPass };
55 |
--------------------------------------------------------------------------------
/World/systems/pass/grain.ts:
--------------------------------------------------------------------------------
1 | import { ShaderPass } from "three/examples/jsm/postprocessing/ShaderPass.js";
2 |
3 | const vertexShader = `
4 | varying vec2 vUv;
5 | void main() {
6 | vUv = uv;
7 | gl_Position = projectionMatrix
8 | * modelViewMatrix
9 | * vec4( position, 1.0 );
10 | }
11 | `;
12 |
13 | const fragmentShader = `
14 | uniform float amount;
15 | uniform sampler2D tDiffuse;
16 | varying vec2 vUv;
17 |
18 | float random( vec2 p )
19 | {
20 | vec2 K1 = vec2(
21 | 23.14069263277926, // e^pi (Gelfond's constant)
22 | 2.665144142690225 // 2^sqrt(2) (Gelfond–Schneider constant)
23 | );
24 | return fract( cos( dot(p,K1) ) * 12345.6789 );
25 | }
26 | void main() {
27 | vec4 color = texture2D( tDiffuse, vUv );
28 | vec2 uvRandom = vUv;
29 | uvRandom.y *= random(vec2(uvRandom.y,amount));
30 | color.rgb += random(uvRandom)*0.15;
31 | gl_FragColor = vec4( color );
32 | }
33 | `;
34 |
35 | function createGrainPass() {
36 | const { $random } = useNuxtApp();
37 | const counter = $random.$getRandom();
38 | const grainEffect = {
39 | uniforms: {
40 | tDiffuse: { value: null },
41 | amount: { value: counter },
42 | },
43 | vertexShader: vertexShader,
44 | fragmentShader: fragmentShader,
45 | };
46 | const grainPass = new ShaderPass(grainEffect);
47 | grainPass.renderToScreen = true;
48 | return grainPass;
49 | }
50 |
51 | export { createGrainPass };
52 |
--------------------------------------------------------------------------------
/World/systems/pass/helpers.ts:
--------------------------------------------------------------------------------
1 | import { Vector4 } from "three";
2 | import { useSceneStore } from "~/store/scene";
3 | import { HEIGHT, WIDTH } from "~/constants";
4 |
5 | export const translateColorspace = `
6 | vec3 rgb2hsv(vec3 c) {
7 | vec4 K = vec4(0.0, -1.0 / 3.0, 2.0 / 3.0, -1.0);
8 | vec4 p = mix(vec4(c.bg, K.wz), vec4(c.gb, K.xy), step(c.b, c.g));
9 | vec4 q = mix(vec4(p.xyw, c.r), vec4(c.r, p.yzx), step(p.x, c.r));
10 |
11 | float d = q.x - min(q.w, q.y);
12 | float e = 1.0e-10;
13 | return vec3(abs(q.z + (q.w - q.y) / (6.0 * d + e)), d / (q.x + e), q.x);
14 | }
15 |
16 | vec3 hsv2rgb(vec3 c) {
17 | vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0);
18 | vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www);
19 | return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y);
20 | }`;
21 |
22 | export const baseUniforms = () => {
23 | const sceneStore = useSceneStore();
24 | const showBorders =
25 | sceneStore.scene(sceneStore.activeScene!).showBorders ?? true;
26 | return {
27 | colors: {
28 | value: {
29 | background:
30 | sceneStore.scene(sceneStore.activeScene!).background ??
31 | new Vector4(1, 1, 1, 1),
32 | color:
33 | sceneStore.scene(sceneStore.activeScene!).color ??
34 | new Vector4(0, 0, 0, 1),
35 | },
36 | },
37 | borders: {
38 | value: {
39 | top: 228 / HEIGHT,
40 | right: 32 / WIDTH,
41 | bottom: 228 / HEIGHT,
42 | left: 32 / WIDTH,
43 | show: showBorders ?? true,
44 | },
45 | },
46 | position: {
47 | x: 0,
48 | y: 0,
49 | },
50 | width: { value: WIDTH },
51 | height: { value: HEIGHT },
52 | };
53 | };
54 |
55 | export const baseShaderUniforms = `
56 | struct ColorData {
57 | vec4 background;
58 | vec4 color;
59 | };
60 | uniform ColorData colors;
61 |
62 | struct BorderData {
63 | float top;
64 | float right;
65 | float bottom;
66 | float left;
67 | bool show;
68 | };
69 | uniform BorderData borders;
70 |
71 | struct PositionData {
72 | float x;
73 | float y;
74 | };
75 | uniform PositionData position;
76 |
77 | uniform float width;
78 | uniform float height;
79 | `;
80 |
--------------------------------------------------------------------------------
/World/systems/pass/index.ts:
--------------------------------------------------------------------------------
1 | import { createLiquidPass } from "~/World/systems/pass/liquid";
2 | import { createMaskingPass } from "~/World/systems/pass/masking";
3 | import { createGrainPass } from "~/World/systems/pass/grain";
4 | import { Camera, Scene } from "three";
5 | import { createZebraPass } from "~/World/systems/pass/zebra";
6 | import { createLiquidLargePass } from "~/World/systems/pass/liquidLarge";
7 | import { createGradientPass } from "~/World/systems/pass/gradient";
8 | import { createSteppedPass } from "~/World/systems/pass/stepped";
9 | import { createSilkPass } from "~/World/systems/pass/silk";
10 | import { createDotsPass } from "~/World/systems/pass/dotsGrid";
11 | import { createChessPass } from "~/World/systems/pass/chess";
12 | import { createVernerPass } from "~/World/systems/pass/verner";
13 | import { createSteppedWobblePass } from "~/World/systems/pass/steppedWobble";
14 |
15 | const basePasses = (camera: Camera) => {
16 | return [
17 | createLiquidPass(camera),
18 | createZebraPass(camera),
19 | createLiquidLargePass(camera),
20 | createGradientPass(camera),
21 | createSteppedPass(camera),
22 | createSteppedWobblePass(camera),
23 | createSilkPass(camera),
24 | createDotsPass(camera),
25 | createChessPass(camera),
26 | createVernerPass(camera),
27 | ];
28 | };
29 |
30 | export const getPasses = (camera: Camera) => {
31 | const { $random } = useNuxtApp();
32 | const base = basePasses(camera);
33 | return [base[Math.floor($random.$getRandom() * base.length)]];
34 | };
35 |
--------------------------------------------------------------------------------
/World/systems/pass/liquid.ts:
--------------------------------------------------------------------------------
1 | import { ShaderPass } from "three/examples/jsm/postprocessing/ShaderPass.js";
2 | import { Camera } from "three";
3 | import { baseShaderUniforms, baseUniforms } from "~/World/systems/pass/helpers";
4 |
5 | const vertexShader = `
6 | varying vec2 vUv;
7 | void main() {
8 | vUv = uv;
9 | gl_Position = projectionMatrix
10 | * modelViewMatrix
11 | * vec4( position, 1.0 );
12 | }
13 | `;
14 |
15 | const fragmentShader = `
16 | uniform float offset;
17 | varying vec2 vUv;
18 | ${baseShaderUniforms}
19 |
20 | mat2 rot(float deg)
21 | {
22 | return mat2(cos(deg),-sin(deg),
23 | sin(deg), cos(deg));
24 |
25 | }
26 | float random( vec2 p )
27 | {
28 | vec2 K1 = vec2(
29 | 23.14069263277926,
30 | 2.665144142690225
31 | );
32 | return fract( cos( dot(p,K1) ) * 12345.6789 );
33 | }
34 |
35 | void main() {
36 | vec2 uv = vUv;
37 | float t = position.x + position.y + offset;
38 |
39 | uv-=.5 * offset;
40 | uv*=5. * offset;
41 |
42 | uv*=rot(uv.y/5.-t*.15);
43 | uv-=sin(sqrt(uv.x*uv.x+uv.y*uv.y)-t*2.)*3.;
44 | uv.y+=sin(uv.x-t)*1.2;
45 | uv-=sin(sqrt(uv.x*uv.x+uv.y*uv.y)+t)*.6;
46 | uv.x+=sin(uv.y*1.4+t)*.6;
47 |
48 | uv*=rot(uv.x/5.-t*.8);
49 | uv.x/=length(.75*uv);
50 | uv.y/=length(.75*uv);
51 |
52 | gl_FragColor = vec4( vec3(cos(uv.x+uv.y-t*.7), cos(uv.x+uv.y-t*.6), cos(uv.x+uv.y-t*.8)), 1. );
53 | }
54 | `;
55 |
56 | function createLiquidPass(camera: Camera) {
57 | const { $random } = useNuxtApp();
58 | const liquidEffect = {
59 | uniforms: {
60 | ...baseUniforms(),
61 | offset: { value: $random.$getRandom() * 10 },
62 | },
63 | vertexShader: vertexShader,
64 | fragmentShader: fragmentShader,
65 | name: "LiquidPass",
66 | };
67 | const liquidPass = new ShaderPass(liquidEffect);
68 | liquidPass.renderToScreen = true;
69 | return liquidPass;
70 | }
71 |
72 | export { createLiquidPass };
73 |
--------------------------------------------------------------------------------
/World/systems/pass/liquidLarge.ts:
--------------------------------------------------------------------------------
1 | import { ShaderPass } from "three/examples/jsm/postprocessing/ShaderPass.js";
2 | import { Camera } from "three";
3 | import { baseShaderUniforms, baseUniforms } from "~/World/systems/pass/helpers";
4 |
5 | const vertexShader = `
6 | varying vec2 vUv;
7 | void main() {
8 | vUv = uv;
9 | gl_Position = projectionMatrix
10 | * modelViewMatrix
11 | * vec4( position, 1.0 );
12 | }
13 | `;
14 |
15 | const fragmentShader = `
16 | uniform float offset;
17 | varying vec2 vUv;
18 | ${baseShaderUniforms}
19 |
20 | mat2 rot(float deg)
21 | {
22 | return mat2(cos(deg),-sin(deg),
23 | sin(deg), cos(deg));
24 |
25 | }
26 | float random( vec2 p )
27 | {
28 | vec2 K1 = vec2(
29 | 23.14069263277926,
30 | 2.665144142690225
31 | );
32 | return fract( cos( dot(p,K1) ) * 12345.6789 );
33 | }
34 |
35 | void main() {
36 | float t = position.x + position.y + offset;
37 | vec2 uv = vUv;
38 |
39 | uv-=position.x * 0.1;
40 | uv*=position.y * 0.5;
41 |
42 | uv*=rot(uv.y/5.-t*.15);
43 | uv-=sin(sqrt(uv.x*uv.x+uv.y*uv.y)-t*2.)*3.;
44 | uv.y+=sin(uv.x-t)*1.2;
45 | uv-=sin(sqrt(uv.x*uv.x+uv.y*uv.y)+t)*.6;
46 | uv.x+=sin(uv.y*1.4+t)*.6;
47 |
48 | uv*=rot(uv.x/5.-t*.8);
49 | uv.x/=length(.75*uv);
50 | uv.y/=length(.75*uv);
51 |
52 | float value = cos(uv.x+uv.y-t*.6);
53 | gl_FragColor = vec4( vec3(cos(uv.x+uv.y-t*.7),cos(uv.x+uv.y-t*.6),cos(uv.x+uv.y-t*.8)), 1. );
54 | }
55 | `;
56 |
57 | function createLiquidLargePass(camera: Camera) {
58 | const { $random } = useNuxtApp();
59 | const liquidEffect = {
60 | uniforms: {
61 | ...baseUniforms(),
62 | offset: { value: $random.$getRandom() * 10 },
63 | },
64 | vertexShader: vertexShader,
65 | fragmentShader: fragmentShader,
66 | name: "liquidLargePass",
67 | };
68 | const liquidPass = new ShaderPass(liquidEffect);
69 | liquidPass.renderToScreen = true;
70 | return liquidPass;
71 | }
72 |
73 | export { createLiquidLargePass };
74 |
--------------------------------------------------------------------------------
/World/systems/pass/masking.ts:
--------------------------------------------------------------------------------
1 | import { ShaderPass } from "three/examples/jsm/postprocessing/ShaderPass.js";
2 | import { baseShaderUniforms, baseUniforms } from "~/World/systems/pass/helpers";
3 |
4 | const vertexShader = `
5 | varying vec2 vUv;
6 | void main() {
7 | vUv = uv;
8 | gl_Position = projectionMatrix
9 | * modelViewMatrix
10 | * vec4( position, 1.0 );
11 | }
12 | `;
13 |
14 | const fragmentShader = `
15 | uniform sampler2D tDiffuse;
16 | varying vec2 vUv;
17 | ${baseShaderUniforms}
18 |
19 | void main() {
20 | vec4 color = texture2D( tDiffuse, vUv );
21 | vec2 uv = vUv;
22 | vec2 bl = step(vec2(borders.left, borders.bottom),uv);
23 | float padding = bl.x * bl.y;
24 | vec2 tr = step(vec2(borders.right, borders.top),1.0-uv);
25 | padding *= tr.x * tr.y;
26 | if (borders.show && step( 0.5, 1. - padding ) > 0.) {
27 | color *= colors.background;
28 | }
29 | gl_FragColor = color;
30 | }
31 | `;
32 |
33 | function createMaskingPass() {
34 | const maskingEffect = {
35 | uniforms: {
36 | ...baseUniforms(),
37 | tDiffuse: { value: null },
38 | },
39 | vertexShader: vertexShader,
40 | fragmentShader: fragmentShader,
41 | name: "MaskingPass",
42 | };
43 | const maskingPass = new ShaderPass(maskingEffect);
44 | maskingPass.renderToScreen = true;
45 | return maskingPass;
46 | }
47 |
48 | export { createMaskingPass };
49 |
--------------------------------------------------------------------------------
/World/systems/pass/silk.ts:
--------------------------------------------------------------------------------
1 | import { Camera } from "three";
2 | import { ShaderPass } from "three/examples/jsm/postprocessing/ShaderPass";
3 | import {
4 | baseShaderUniforms,
5 | baseUniforms,
6 | translateColorspace,
7 | } from "~/World/systems/pass/helpers";
8 |
9 | const vertexShader = `
10 | varying vec2 vUv;
11 | void main() {
12 | vUv = uv;
13 | gl_Position = projectionMatrix
14 | * modelViewMatrix
15 | * vec4( position, 1.0 );
16 | }
17 | `;
18 |
19 | const fragmentShader = `
20 | uniform float offset;
21 | varying vec2 vUv;
22 | ${translateColorspace}
23 | ${baseShaderUniforms}
24 | #define rot(x) mat2(cos(x), -sin(x), sin(x), cos(x))
25 |
26 | float heightS(vec2 p){
27 | return sin(p.x)+sin(p.x+p.y)+cos(p.y)/1.5+sin(offset+p.x)+5.;
28 | }
29 |
30 | float map(vec3 p){
31 | return p.y-heightS(p.xz);
32 | }
33 | void main(){
34 | vec2 uv = vUv;
35 | vec3 ray = normalize(vec3(uv,1.));
36 | ray.yz *= rot((sin(position.y)/3.+1.5));
37 | ray.xz *= rot((sin(position.x)/2.+1.)/5.);
38 |
39 | float t = 0.;
40 | for(int i = 0; i < 29 ; ++i)
41 | t += map(vec3(position.x,0.,position.x/2.)+ray*t)*.5;
42 |
43 | float fog = 1./(1.+t*t*.005);
44 | vec3 fc = vec3(fog*fog, fog/2., fog);
45 | vec3 hsl = rgb2hsv(fc);
46 | hsl.x *= sin(offset);
47 | gl_FragColor = vec4(hsv2rgb(hsl), 1.);
48 | }
49 | `;
50 |
51 | function createSilkPass(camera: Camera) {
52 | const { $random } = useNuxtApp();
53 | const effect = {
54 | uniforms: {
55 | ...baseUniforms(),
56 | offset: { value: $random.$getRandom() * 10 },
57 | },
58 | vertexShader: vertexShader,
59 | fragmentShader: fragmentShader,
60 | name: "SilkPass",
61 | };
62 | const pass = new ShaderPass(effect);
63 | pass.renderToScreen = true;
64 | return pass;
65 | }
66 |
67 | export { createSilkPass };
68 |
--------------------------------------------------------------------------------
/World/systems/pass/stepped.ts:
--------------------------------------------------------------------------------
1 | import { Camera } from "three";
2 | import { ShaderPass } from "three/examples/jsm/postprocessing/ShaderPass";
3 | import {
4 | baseShaderUniforms,
5 | baseUniforms,
6 | translateColorspace,
7 | } from "~/World/systems/pass/helpers";
8 |
9 | const vertexShader = `
10 | varying vec2 vUv;
11 | void main() {
12 | vUv = uv;
13 | gl_Position = projectionMatrix
14 | * modelViewMatrix
15 | * vec4( position, 1.0 );
16 | }
17 | `;
18 |
19 | const fragmentShader = `
20 | uniform float offset;
21 | varying vec2 vUv;
22 | ${baseShaderUniforms}
23 | ${translateColorspace}
24 |
25 | float stepped(in float s, in float scale, in int steps) {
26 | return floor( s / ((1.0*scale) / float(steps))) * 1.0 / float(steps-1);
27 | }
28 | void main() {
29 | float o = offset / 10.;
30 | float r = o > 0.8 ? sin(offset) : o;
31 | float g = o > 0.5 ? cos(offset) : sin(offset);
32 | float b = o > 0.2 ? o : cos(offset);
33 | vec4 Color1 = normalize(vec4(r, g, b, 1.0));
34 | vec3 hsv = rgb2hsv(normalize(vec3(r,g,b)));
35 | hsv.x *= sin((position.x + offset) / 10.);
36 | Color1 = vec4(hsv2rgb(hsv),1.);
37 | vec4 Color2 = vec4(colors.background.rgb, 1.0);
38 | int NumSteps = int(20. * o);
39 | float aspect = width / height;
40 |
41 | vec2 uv = vUv;
42 | vec2 center = vec2(0.5, 0.5 - (borders.top / (borders.bottom * 100.)));
43 |
44 | uv.x *= aspect;
45 | center.x *= aspect;
46 | float dist = distance( uv, center);
47 | float size = offset / 10. + offset * abs(sin(position.y / 10.));
48 | float s = stepped(dist, size, NumSteps );
49 |
50 | gl_FragColor = mix(Color1, Color2, clamp(s, 0.0, 1.0));
51 | }
52 | `;
53 |
54 | function createSteppedPass(camera: Camera) {
55 | const { $random } = useNuxtApp();
56 | const effect = {
57 | uniforms: {
58 | ...baseUniforms(),
59 | offset: { value: $random.$getRandom() * 10 },
60 | },
61 | vertexShader: vertexShader,
62 | fragmentShader: fragmentShader,
63 | name: "SteppedPass",
64 | };
65 | const pass = new ShaderPass(effect);
66 | pass.renderToScreen = true;
67 | return pass;
68 | }
69 |
70 | export { createSteppedPass };
71 |
--------------------------------------------------------------------------------
/World/systems/pass/steppedWobble.ts:
--------------------------------------------------------------------------------
1 | import { Camera } from "three";
2 | import { ShaderPass } from "three/examples/jsm/postprocessing/ShaderPass";
3 | import {
4 | baseShaderUniforms,
5 | baseUniforms,
6 | translateColorspace,
7 | } from "~/World/systems/pass/helpers";
8 |
9 | const vertexShader = `
10 | varying vec2 vUv;
11 | void main() {
12 | vUv = uv;
13 | gl_Position = projectionMatrix
14 | * modelViewMatrix
15 | * vec4( position, 1.0 );
16 | }
17 | `;
18 |
19 | const fragmentShader = `
20 | uniform float offset;
21 | varying vec2 vUv;
22 | ${baseShaderUniforms}
23 | ${translateColorspace}
24 |
25 | float stepped(in float s, in float scale, in int steps) {
26 | return floor( s / ((1.0*scale) / float(steps))) * 1.0 / float(steps-1);
27 | }
28 |
29 | vec2 random2(vec2 st){
30 | st = vec2( dot(st,vec2(127.1,311.7)),
31 | dot(st,vec2(269.5,183.3)) );
32 | return -1.0 + 2.0*fract(sin(st)*43758.5453123);
33 | }
34 |
35 | float noise(vec2 st) {
36 | vec2 i = floor(st);
37 | vec2 f = fract(st);
38 |
39 | vec2 u = f*f*(3.0-2.0*f);
40 |
41 | return mix( mix( dot( random2(i + vec2(0.0,0.0) ), f - vec2(0.0,0.0) ),
42 | dot( random2(i + vec2(1.0,0.0) ), f - vec2(1.0,0.0) ), u.x),
43 | mix( dot( random2(i + vec2(0.0,1.0) ), f - vec2(0.0,1.0) ),
44 | dot( random2(i + vec2(1.0,1.0) ), f - vec2(1.0,1.0) ), u.x), u.y);
45 | }
46 |
47 | void main() {
48 | float o = offset / 10.;
49 | float r = o > 0.8 ? sin(offset) : o;
50 | float g = o > 0.5 ? cos(offset) : sin(offset);
51 | float b = o > 0.2 ? o : cos(offset);
52 | vec4 Color1 = normalize(vec4(r, g, b, 1.0));
53 | vec3 hsv = rgb2hsv(normalize(vec3(r,g,b)));
54 | hsv.x *= sin((position.x + offset) / 10.);
55 | Color1 = vec4(hsv2rgb(hsv),1.);
56 | vec4 Color2 = vec4(colors.background.rgb, 1.0);
57 | int NumSteps = int(20. * o);
58 | float aspect = width / height;
59 |
60 | vec2 uv = vUv;
61 | vec2 center = vec2(0.5, 0.5 - (borders.top / (borders.bottom * 100.)));
62 |
63 | uv.x *= aspect;
64 | center.x *= aspect;
65 | float dist = distance( uv, center);
66 | float size = offset / 10. + offset * abs(sin(position.y / 10.));
67 | size += noise(uv * 2. * offset) * 0.1;
68 | float s = stepped(dist, size, NumSteps );
69 |
70 | gl_FragColor = mix(Color1, Color2, clamp(s, 0.0, 1.0));
71 | }
72 | `;
73 |
74 | function createSteppedWobblePass(camera: Camera) {
75 | const { $random } = useNuxtApp();
76 | const effect = {
77 | uniforms: {
78 | ...baseUniforms(),
79 | offset: { value: $random.$getRandom() * 10 },
80 | },
81 | vertexShader: vertexShader,
82 | fragmentShader: fragmentShader,
83 | name: "SteppedWobblePass",
84 | };
85 | const pass = new ShaderPass(effect);
86 | pass.renderToScreen = true;
87 | return pass;
88 | }
89 |
90 | export { createSteppedWobblePass };
91 |
--------------------------------------------------------------------------------
/World/systems/pass/verner.ts:
--------------------------------------------------------------------------------
1 | import { ShaderPass } from "three/examples/jsm/postprocessing/ShaderPass.js";
2 | import { Camera } from "three";
3 | import { baseShaderUniforms, baseUniforms } from "~/World/systems/pass/helpers";
4 |
5 | const vertexShader = `
6 | varying vec2 vUv;
7 | void main() {
8 | vUv = uv;
9 | gl_Position = projectionMatrix
10 | * modelViewMatrix
11 | * vec4( position, 1.0 );
12 | }
13 | `;
14 |
15 | const fragmentShader = `
16 | uniform float offset;
17 | varying vec2 vUv;
18 | ${baseShaderUniforms}
19 |
20 | void main() {
21 | vec2 uv = vUv;
22 | vec2 p = uv;
23 | vec4 o = gl_FragColor;
24 | p /= offset;
25 | p.x *= sign(cos(length(ceil(p))*99.));
26 | o = cos( min( length(p = fract(p)), length(--p) ) * 31.4*vec4(position.x, position.y, .6,0) );
27 | gl_FragColor = o;
28 | }
29 | `;
30 |
31 | function createVernerPass(camera: Camera) {
32 | const { $random } = useNuxtApp();
33 | const effect = {
34 | uniforms: {
35 | ...baseUniforms(),
36 | offset: { value: $random.$getRandom() * 10 },
37 | },
38 | vertexShader: vertexShader,
39 | fragmentShader: fragmentShader,
40 | name: "VernerPass",
41 | };
42 | const pass = new ShaderPass(effect);
43 | pass.renderToScreen = true;
44 | return pass;
45 | }
46 |
47 | export { createVernerPass };
48 |
--------------------------------------------------------------------------------
/World/systems/pass/zebra.ts:
--------------------------------------------------------------------------------
1 | import { ShaderPass } from "three/examples/jsm/postprocessing/ShaderPass.js";
2 | import { Camera } from "three";
3 | import { baseShaderUniforms, baseUniforms } from "~/World/systems/pass/helpers";
4 |
5 | const vertexShader = `
6 | varying vec2 vUv;
7 | void main() {
8 | vUv = uv;
9 | gl_Position = projectionMatrix
10 | * modelViewMatrix
11 | * vec4( position, 1.0 );
12 | }
13 | `;
14 |
15 | const fragmentShader = `
16 | uniform float offset;
17 | varying vec2 vUv;
18 | ${baseShaderUniforms}
19 |
20 | void main() {
21 | vec2 uv = vUv;
22 | float t = (position.x * position.y) + offset;
23 | vec2 center = uv / 4.;
24 | float g = 3.1;
25 | center.x+=sin(uv.y*g+position.x);
26 | center.y+=cos(uv.x*g+position.y);
27 | float d = distance(uv,center);
28 | float k = -sin(d*6.283*10. - t);
29 | float e = smoothstep(0., fwidth(k)*1.5, k);
30 | float finalVal = sqrt(max(e, 0.));
31 | vec3 mixed = mix(colors.color.rgb, colors.background.rgb, clamp(finalVal, 0.0, 1.0));
32 | gl_FragColor = vec4(mixed, 1.);
33 | }
34 | `;
35 |
36 | function createZebraPass(camera: Camera) {
37 | const { $random } = useNuxtApp();
38 | const effect = {
39 | uniforms: {
40 | ...baseUniforms(),
41 | offset: { value: $random.$getRandom() * 10 },
42 | },
43 | vertexShader: vertexShader,
44 | fragmentShader: fragmentShader,
45 | name: "ZebraPass",
46 | };
47 | const pass = new ShaderPass(effect);
48 | pass.renderToScreen = true;
49 | return pass;
50 | }
51 |
52 | export { createZebraPass };
53 |
--------------------------------------------------------------------------------
/World/systems/renderer.ts:
--------------------------------------------------------------------------------
1 | import { ACESFilmicToneMapping, PCFSoftShadowMap, WebGLRenderer } from "three";
2 | import { HEIGHT, WIDTH } from "~/constants";
3 |
4 | function createBaseRenderer() {
5 | const renderer = new WebGLRenderer({
6 | alpha: true,
7 | antialias: true,
8 | powerPreference: "high-performance",
9 | preserveDrawingBuffer: true,
10 | });
11 | renderer.setClearColor(0x000000, 0);
12 | renderer.setSize(WIDTH, HEIGHT);
13 | renderer.shadowMap.enabled = true; // important!
14 | renderer.shadowMap.type = PCFSoftShadowMap;
15 | renderer.toneMapping = ACESFilmicToneMapping;
16 | renderer.toneMappingExposure = 1;
17 | return renderer;
18 | }
19 |
20 | function createRenderer() {
21 | const renderer = createBaseRenderer();
22 | renderer.setPixelRatio(window.devicePixelRatio ? window.devicePixelRatio : 1);
23 | return renderer;
24 | }
25 |
26 | function createExportRenderer() {
27 | const renderer = createBaseRenderer();
28 | renderer.setPixelRatio(window.devicePixelRatio ? window.devicePixelRatio : 2);
29 | return renderer;
30 | }
31 |
32 | function createLandingRenderer(width: number, height: number) {
33 | const renderer = new WebGLRenderer({
34 | alpha: true,
35 | antialias: true,
36 | powerPreference: "high-performance",
37 | preserveDrawingBuffer: true,
38 | });
39 | renderer.setClearColor(0x000000, 0);
40 | renderer.setSize(width, height);
41 | renderer.shadowMap.enabled = true; // important!
42 | renderer.shadowMap.type = PCFSoftShadowMap;
43 | renderer.toneMapping = ACESFilmicToneMapping;
44 | renderer.toneMappingExposure = 1;
45 | renderer.setPixelRatio(window.devicePixelRatio);
46 | return renderer;
47 | }
48 |
49 | export { createRenderer, createExportRenderer, createLandingRenderer };
50 |
--------------------------------------------------------------------------------
/World/things/camera.ts:
--------------------------------------------------------------------------------
1 | import { OrthographicCamera } from "three";
2 | import { HEIGHT, WIDTH } from "~/constants";
3 |
4 | function createCamera() {
5 | return new OrthographicCamera(
6 | WIDTH / -2,
7 | WIDTH / 2,
8 | HEIGHT / 2,
9 | HEIGHT / -2,
10 | 0,
11 | Number.MAX_VALUE,
12 | );
13 | }
14 |
15 | function createCameraParams(
16 | left: number,
17 | right: number,
18 | top: number,
19 | bottom: number,
20 | ) {
21 | return new OrthographicCamera(left, right, top, bottom, 0, Number.MAX_VALUE);
22 | }
23 |
24 | export { createCamera, createCameraParams };
25 |
--------------------------------------------------------------------------------
/World/things/index.ts:
--------------------------------------------------------------------------------
1 | export { createCamera } from "./camera";
2 | export { createScene } from "./scene";
3 |
--------------------------------------------------------------------------------
/World/things/scene.ts:
--------------------------------------------------------------------------------
1 | import { Scene } from "three";
2 |
3 | function createScene() {
4 | return new Scene();
5 | }
6 |
7 | export { createScene };
8 |
--------------------------------------------------------------------------------
/app.vue:
--------------------------------------------------------------------------------
1 |
2 |
5 |
5 | Upload Your Image
4 |
11 |
12 | Or Browse Default
13 |
21 |
24 | Set Parameters
25 | Click Image to Download
113 |
16 | {{ htmlSource }}
23 | {{ cssSource }}
17 |
Design Explore Prototype
5 |
7 | Poster Ramen is a set of single-purpose tools helping you quickly
8 | prototype and explore designs & typography.
9 |
10 |
12 |
24 |
4 |
10 |
16 | {{ htmlSource }}
23 | {{ cssSource }}
Upload Your Image
4 |
11 |
12 | Or Browse Default
13 |
21 |
24 | Set Parameters
25 | Click Image to Download
91 |
5 | Made by 6 | @devslovecoffee. 9 |
10 |11 | Check out my 12 | blog to learn 13 | more about what I do. 14 |
15 |16 | Feedback is appreciated. You can reach me feedback [at] 17 | devslovecoffee.com 18 |
19 | 20 |See how to contribute here (soon).
21 |Your work is being downloaded now.
16 |17 | Click here if the download didn't start 18 | automatically. 19 |
20 |