├── .gitignore
├── README.md
├── assets
├── favicon
│ ├── android-chrome-192x192.png
│ ├── android-chrome-512x512.png
│ ├── apple-touch-icon.png
│ ├── browserconfig.xml
│ ├── favicon-16x16.png
│ ├── favicon-32x32.png
│ ├── favicon.ico
│ ├── mstile-150x150.png
│ └── site.webmanifest
├── fonts
│ ├── Poppins-Regular.woff
│ └── Poppins-Regular.woff2
└── images
│ └── .gitkeep
├── index.html
├── package-lock.json
├── package.json
├── scripts
├── app.js
├── events
│ ├── Emitter.js
│ ├── Raf.js
│ ├── Resize.js
│ └── index.js
├── gl
│ ├── index.js
│ └── shaders
│ │ ├── quad.frag
│ │ └── quad.vert
├── store.js
└── utils
│ ├── bindAll.js
│ ├── index.js
│ ├── lerp.js
│ ├── map.js
│ └── selector.js
├── styles
├── base
│ ├── elements.scss
│ ├── fonts.scss
│ └── reset.scss
├── components
│ └── .gitkeep
├── main.scss
└── utils
│ ├── functions.scss
│ ├── responsive.scss
│ └── variables.scss
└── webpack.config.js
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | lerna-debug.log*
8 |
9 | # Dependency directories
10 | node_modules/
11 |
12 | # Optional npm cache directory
13 | .npm
14 |
15 | # parcel-bundler cache (https://parceljs.org/)
16 | .cache
17 |
18 | # Next.js build output
19 | .next
20 |
21 | # build / generate output
22 | build
23 | dist
24 |
25 | .DS_Store
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Full screen shader ✨🎨
2 | Template for a full screen shader on the web.
3 |
4 | ## Installation
5 |
6 | Install dependencies:
7 |
8 | ```
9 | npm install
10 | ```
11 |
12 | Run local server with hot reloading
13 |
14 | ```
15 | npm run serve
16 | ```
17 |
18 | Compile the code for development:
19 |
20 | ```
21 | npm run dev
22 | ```
23 |
24 | Create the build:
25 |
26 | ```
27 | npm run build
28 | ```
29 |
--------------------------------------------------------------------------------
/assets/favicon/android-chrome-192x192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/marioecg/full-screen-shader/d50cb0535d9de925f0e738e24a5d52607a4523ef/assets/favicon/android-chrome-192x192.png
--------------------------------------------------------------------------------
/assets/favicon/android-chrome-512x512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/marioecg/full-screen-shader/d50cb0535d9de925f0e738e24a5d52607a4523ef/assets/favicon/android-chrome-512x512.png
--------------------------------------------------------------------------------
/assets/favicon/apple-touch-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/marioecg/full-screen-shader/d50cb0535d9de925f0e738e24a5d52607a4523ef/assets/favicon/apple-touch-icon.png
--------------------------------------------------------------------------------
/assets/favicon/browserconfig.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | #da532c
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/assets/favicon/favicon-16x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/marioecg/full-screen-shader/d50cb0535d9de925f0e738e24a5d52607a4523ef/assets/favicon/favicon-16x16.png
--------------------------------------------------------------------------------
/assets/favicon/favicon-32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/marioecg/full-screen-shader/d50cb0535d9de925f0e738e24a5d52607a4523ef/assets/favicon/favicon-32x32.png
--------------------------------------------------------------------------------
/assets/favicon/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/marioecg/full-screen-shader/d50cb0535d9de925f0e738e24a5d52607a4523ef/assets/favicon/favicon.ico
--------------------------------------------------------------------------------
/assets/favicon/mstile-150x150.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/marioecg/full-screen-shader/d50cb0535d9de925f0e738e24a5d52607a4523ef/assets/favicon/mstile-150x150.png
--------------------------------------------------------------------------------
/assets/favicon/site.webmanifest:
--------------------------------------------------------------------------------
1 | {
2 | "name": "",
3 | "short_name": "",
4 | "icons": [
5 | {
6 | "src": "/android-chrome-192x192.png",
7 | "sizes": "192x192",
8 | "type": "image/png"
9 | },
10 | {
11 | "src": "/android-chrome-512x512.png",
12 | "sizes": "512x512",
13 | "type": "image/png"
14 | }
15 | ],
16 | "theme_color": "#ffffff",
17 | "background_color": "#ffffff",
18 | "display": "standalone"
19 | }
20 |
--------------------------------------------------------------------------------
/assets/fonts/Poppins-Regular.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/marioecg/full-screen-shader/d50cb0535d9de925f0e738e24a5d52607a4523ef/assets/fonts/Poppins-Regular.woff
--------------------------------------------------------------------------------
/assets/fonts/Poppins-Regular.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/marioecg/full-screen-shader/d50cb0535d9de925f0e738e24a5d52607a4523ef/assets/fonts/Poppins-Regular.woff2
--------------------------------------------------------------------------------
/assets/images/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/marioecg/full-screen-shader/d50cb0535d9de925f0e738e24a5d52607a4523ef/assets/images/.gitkeep
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Shader
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "temple",
3 | "version": "1.0.0",
4 | "main": "index.js",
5 | "author": "Mario Carrillo ",
6 | "description": "Template for a full screen shader on the web",
7 | "license": "ISC",
8 | "scripts": {
9 | "clean": "rm -rf dist",
10 | "build": "npm run clean && webpack --mode=production --node-env=production",
11 | "dev": "npm run clean && webpack --mode=development",
12 | "watch": "webpack --watch",
13 | "serve": "webpack serve"
14 | },
15 | "dependencies": {
16 | "glsl-hsl2rgb": "^1.1.0",
17 | "glsl-map": "^1.0.1",
18 | "glsl-noise": "^0.0.0",
19 | "glsl-rotate": "^1.1.0",
20 | "glslify": "^7.1.1",
21 | "gsap": "^3.7.0",
22 | "include-media": "^1.4.10",
23 | "lodash.debounce": "^4.0.8",
24 | "ogl": "^0.0.73",
25 | "tiny-emitter": "^2.1.0"
26 | },
27 | "devDependencies": {
28 | "@babel/core": "^7.14.6",
29 | "@babel/plugin-proposal-class-properties": "^7.14.5",
30 | "@babel/preset-env": "^7.14.7",
31 | "babel-loader": "^8.2.2",
32 | "copy-webpack-plugin": "^9.0.0",
33 | "css-loader": "^5.2.6",
34 | "file-loader": "^6.2.0",
35 | "glslify-loader": "^2.0.0",
36 | "html-loader": "^2.1.2",
37 | "html-webpack-plugin": "^5.3.2",
38 | "mini-css-extract-plugin": "^1.6.0",
39 | "path": "^0.12.7",
40 | "postcss-loader": "^6.1.0",
41 | "postcss-preset-env": "^6.7.0",
42 | "raw-loader": "^4.0.2",
43 | "sass": "^1.35.1",
44 | "sass-loader": "^12.1.0",
45 | "webpack": "^5.40.0",
46 | "webpack-cli": "^4.7.2",
47 | "webpack-dev-server": "^3.11.2"
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/scripts/app.js:
--------------------------------------------------------------------------------
1 | import Gl from './gl';
2 |
3 | class App {
4 | constructor() {
5 | this.init();
6 | }
7 |
8 | init() {
9 | const shader = new Gl();
10 | }
11 | }
12 |
13 | new App();
--------------------------------------------------------------------------------
/scripts/events/Emitter.js:
--------------------------------------------------------------------------------
1 | import Emitter from 'tiny-emitter';
2 |
3 | export default new Emitter();
--------------------------------------------------------------------------------
/scripts/events/Raf.js:
--------------------------------------------------------------------------------
1 | import gsap from 'gsap';
2 | import Emitter from './Emitter';
3 |
4 | class Raf {
5 | constructor() {
6 | this.tick = this.tick.bind(this);
7 |
8 | this.init();
9 | }
10 |
11 | tick(time, delta) {
12 | Emitter.emit('tick', { time, delta });
13 | }
14 |
15 | on() {
16 | gsap.ticker.add(this.tick);
17 | }
18 |
19 | init() {
20 | this.on();
21 | }
22 | }
23 |
24 | export default new Raf();
--------------------------------------------------------------------------------
/scripts/events/Resize.js:
--------------------------------------------------------------------------------
1 | import Emitter from './Emitter';
2 | import store from '../store';
3 | import debounce from 'lodash.debounce';
4 |
5 | class Resize {
6 | constructor() {
7 | this.resize = this.resize.bind(this);
8 | this.init();
9 | }
10 |
11 | resize() {
12 | store.bounds.ww = window.innerWidth;
13 | store.bounds.wh = window.innerHeight;
14 |
15 | Emitter.emit('resize');
16 | }
17 |
18 | on() {
19 | window.addEventListener('resize', debounce(this.resize, 200));
20 | }
21 |
22 | init() {
23 | this.on();
24 | }
25 | }
26 |
27 | export default new Resize();
--------------------------------------------------------------------------------
/scripts/events/index.js:
--------------------------------------------------------------------------------
1 | import Events from './Emitter';
2 | import GlobalRaf from './Raf';
3 | import GlobalResize from './Resize';
4 |
5 | export { Events, GlobalRaf, GlobalResize };
--------------------------------------------------------------------------------
/scripts/gl/index.js:
--------------------------------------------------------------------------------
1 | import { Renderer, Triangle, Program, Mesh } from 'ogl';
2 |
3 | import vertexShader from './shaders/quad.vert';
4 | import fragmentShader from './shaders/quad.frag';
5 |
6 | import glsl from 'glslify';
7 |
8 | import store from '@/store';
9 | import { Events } from '@/events';
10 | import { bindAll } from '@/utils';
11 |
12 | export default class Gl {
13 | constructor() {
14 | this.renderer = new Renderer({
15 | width: store.bounds.ww,
16 | height: store.bounds.wh,
17 | });
18 |
19 | this.gl = this.renderer.gl;
20 |
21 | bindAll(this, 'resize', 'update');
22 |
23 | this.init();
24 | }
25 |
26 | init() {
27 | this.addToDom();
28 | this.createMesh();
29 | this.addEvents();
30 | }
31 |
32 | addToDom() {
33 | document.body.appendChild(this.gl.canvas);
34 | }
35 |
36 | createMesh() {
37 | // Rather than using a plane (two triangles) to cover the viewport here is a
38 | // triangle that includes -1 to 1 range for 'position', and 0 to 1 range for 'uv'.
39 | // Excess will be out of the viewport.
40 | // https://github.com/oframe/ogl/blob/3a271343c4ccfdd830c2c8cf2e0b3648145b3175/examples/triangle-screen-shader.html#L58
41 |
42 | // position uv
43 | // (-1, 3) (0, 2)
44 | // |\ |\
45 | // |__\(1, 1) |__\(1, 1)
46 | // |__|_\ |__|_\
47 | // (-1, -1) (3, -1) (0, 0) (2, 0)
48 |
49 | this.geometry = new Triangle(this.gl);
50 |
51 | this.program = new Program(this.gl, {
52 | vertex: glsl(vertexShader),
53 | fragment: glsl(fragmentShader),
54 | uniforms: {
55 | uTime: { value: 0 },
56 | },
57 | });
58 |
59 | this.mesh = new Mesh(this.gl, {
60 | geometry: this.geometry,
61 | program: this.program
62 | });
63 | }
64 |
65 | addEvents() {
66 | Events.on('tick', this.update);
67 | Events.on('resize', this.resize);
68 | }
69 |
70 | update({ time }) {
71 | this.program.uniforms.uTime.value = time * 0.05;
72 |
73 | this.renderer.render({ scene: this.mesh });
74 | }
75 |
76 | resize() {
77 | this.renderer.setSize(window.innerWidth, window.innerHeight);
78 | }
79 | }
--------------------------------------------------------------------------------
/scripts/gl/shaders/quad.frag:
--------------------------------------------------------------------------------
1 | precision highp float;
2 |
3 | #define PI 3.1415926535897932384626433832795
4 |
5 | uniform float uTime;
6 |
7 | varying vec2 vUv;
8 |
9 | #pragma glslify: noise = require(glsl-noise/simplex/3d)
10 |
11 | void main() {
12 | vec2 uv = vUv * 0.1;
13 |
14 | float t = uTime ;
15 |
16 | vec3 a = vec3(0.5, 0.5, 0.5);
17 | vec3 b = vec3(0.5, 0.5, 0.5);
18 | vec3 c = vec3(2.0, 1.0, 0.0);
19 | vec3 d = vec3(0.50, 0.20, 0.25);
20 |
21 | float n = noise(vec3(uv.x, uv.y, t));
22 |
23 | vec3 color = a + b * cos(2. * PI * (c * n + d));
24 | gl_FragColor = vec4(color, 1.);
25 | }
--------------------------------------------------------------------------------
/scripts/gl/shaders/quad.vert:
--------------------------------------------------------------------------------
1 | attribute vec2 uv;
2 | attribute vec2 position;
3 |
4 | uniform float uTime;
5 |
6 | varying vec2 vUv;
7 |
8 | void main() {
9 | vUv = uv;
10 |
11 | vec2 pos = position;
12 |
13 | gl_Position = vec4(pos, 0, 1);
14 | }
--------------------------------------------------------------------------------
/scripts/store.js:
--------------------------------------------------------------------------------
1 | const store = {
2 | bounds: {
3 | ww: window.innerWidth,
4 | wh: window.innerHeight,
5 | },
6 | };
7 |
8 | export default store;
--------------------------------------------------------------------------------
/scripts/utils/bindAll.js:
--------------------------------------------------------------------------------
1 | export function bindAll(object) {
2 | const functions = [].slice.call(arguments, 1);
3 |
4 | for (let i = 0; i < functions.length; i++) {
5 | const f = functions[i];
6 | object[f] = bind(object[f], object);
7 | }
8 | }
9 |
10 | function bind(func, context) {
11 | return function() {
12 | return func.apply(context, arguments);
13 | }
14 | }
--------------------------------------------------------------------------------
/scripts/utils/index.js:
--------------------------------------------------------------------------------
1 | export { lerp } from './lerp';
2 | export { qs, qsa } from './selector';
3 | export { map } from './map';
4 | export { bindAll } from './bindAll';
--------------------------------------------------------------------------------
/scripts/utils/lerp.js:
--------------------------------------------------------------------------------
1 | export function lerp(a, b, n) {
2 | return a * (1 - n) + b * n;
3 | }
--------------------------------------------------------------------------------
/scripts/utils/map.js:
--------------------------------------------------------------------------------
1 | export function map(value, start1, stop1, start2, stop2) {
2 | return ((value - start1) / (stop1 - start1)) * (stop2 - start2) + start2
3 | }
--------------------------------------------------------------------------------
/scripts/utils/selector.js:
--------------------------------------------------------------------------------
1 | export const qs = (s, o = document) => o.querySelector(s);
2 | export const qsa = (s, o = document) => o.querySelectorAll(s);
--------------------------------------------------------------------------------
/styles/base/elements.scss:
--------------------------------------------------------------------------------
1 | html {
2 | height: 100%;
3 | font-family: 'Poppins';
4 | font-size: calc(100vw / 1920 * 10);
5 |
6 | @include media('<=phone') {
7 | font-size: calc(100vw / 375 * 10);
8 | }
9 | }
10 |
11 | body {
12 | width: 100%;
13 | height: 100%;
14 | }
15 |
16 | canvas {
17 | position: fixed;
18 | top: 0;
19 | left: 0;
20 | }
--------------------------------------------------------------------------------
/styles/base/fonts.scss:
--------------------------------------------------------------------------------
1 | @font-face {
2 | font-family: 'Poppins';
3 | src: url('/assets/fonts/Poppins-Regular.woff2') format('woff2'),
4 | url('/assets/fonts/Poppins-Regular.woff') format('woff');
5 | font-weight: normal;
6 | font-style: normal;
7 | font-display: swap;
8 | }
--------------------------------------------------------------------------------
/styles/base/reset.scss:
--------------------------------------------------------------------------------
1 | /* http://meyerweb.com/eric/tools/css/reset/
2 | v2.0 | 20110126
3 | License: none (public domain)
4 | */
5 |
6 | html, body, div, span, applet, object, iframe,
7 | h1, h2, h3, h4, h5, h6, p, blockquote, pre,
8 | a, abbr, acronym, address, big, cite, code,
9 | del, dfn, em, img, ins, kbd, q, s, samp,
10 | small, strike, strong, sub, sup, tt, var,
11 | b, u, i, center,
12 | dl, dt, dd, ol, ul, li,
13 | fieldset, form, label, legend,
14 | table, caption, tbody, tfoot, thead, tr, th, td,
15 | article, aside, canvas, details, embed,
16 | figure, figcaption, footer, header, hgroup,
17 | menu, nav, output, ruby, section, summary,
18 | time, mark, audio, video {
19 | margin: 0;
20 | padding: 0;
21 | border: 0;
22 | font-size: 100%;
23 | font: inherit;
24 | vertical-align: baseline;
25 | }
26 |
27 | /* HTML5 display-role reset for older browsers */
28 | article, aside, details, figcaption, figure,
29 | footer, header, hgroup, menu, nav, section {
30 | display: block;
31 | }
32 |
33 | body {
34 | line-height: 1;
35 | }
36 |
37 | ol, ul {
38 | list-style: none;
39 | }
40 |
41 | blockquote, q {
42 | quotes: none;
43 | }
44 |
45 | blockquote:before, blockquote:after,
46 | q:before, q:after {
47 | content: '';
48 | content: none;
49 | }
50 |
51 | table {
52 | border-collapse: collapse;
53 | border-spacing: 0;
54 | }
--------------------------------------------------------------------------------
/styles/components/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/marioecg/full-screen-shader/d50cb0535d9de925f0e738e24a5d52607a4523ef/styles/components/.gitkeep
--------------------------------------------------------------------------------
/styles/main.scss:
--------------------------------------------------------------------------------
1 | @import './utils/variables';
2 | @import './utils/responsive';
3 |
4 | @import './base/fonts';
5 | @import './base/reset';
6 | @import './base/elements';
--------------------------------------------------------------------------------
/styles/utils/functions.scss:
--------------------------------------------------------------------------------
1 | @function z($name) {
2 | @if index($z-indexes, $name) {
3 | @return (length($z-indexes) - index($z-indexes, $name)) + 1;
4 | } @else {
5 | @warn 'There is no item "#{$name}" in this list; Choose one of: #{$z-indexes}';
6 |
7 | @return null;
8 | }
9 | }
--------------------------------------------------------------------------------
/styles/utils/responsive.scss:
--------------------------------------------------------------------------------
1 | $breakpoints: (
2 | 'phone': 768px,
3 | 'tablet': 1024px,
4 | 'desktop': 1440px
5 | ) !default;
6 |
7 | @import '../../node_modules/include-media/dist/include-media.scss';
--------------------------------------------------------------------------------
/styles/utils/variables.scss:
--------------------------------------------------------------------------------
1 | // Colors
2 | $black: #000000;
3 | $white: #ffffff;
4 |
5 | // Z-index
6 | $z-indexes:();
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const HtmlWebpackPlugin = require('html-webpack-plugin');
3 | const MiniCssExtractPlugin = require('mini-css-extract-plugin');
4 | const CopyWebpackPlugin = require('copy-webpack-plugin');
5 |
6 | const isProd = process.env.NODE_ENV == 'production';
7 |
8 | const appDir = path.join(__dirname, 'scripts');
9 | const assetsDir = path.join(__dirname, 'assets');
10 | const stylesDir = path.join(__dirname, 'styles');
11 |
12 | const config = {
13 | mode: isProd ? 'production' : 'development',
14 |
15 | entry: [
16 | path.join(appDir, 'app.js'),
17 | path.join(stylesDir, 'main.scss')
18 | ],
19 |
20 | output: {
21 | path: path.resolve(__dirname, 'dist'),
22 | filename: isProd ? '[name].[contenthash].js' : '[name].js',
23 | chunkFilename: isProd ? '[id].css' : '[id].[contenthash].css',
24 | publicPath: '/',
25 | },
26 |
27 | resolve: {
28 | alias: {
29 | '@': path.resolve(__dirname, 'scripts'),
30 | },
31 | },
32 |
33 | devServer: {
34 | open: false,
35 | host: 'localhost',
36 | },
37 |
38 | plugins: [
39 | new HtmlWebpackPlugin({
40 | template: 'index.html',
41 | }),
42 |
43 | new CopyWebpackPlugin({
44 | patterns: [
45 | {
46 | from: path.join(assetsDir, 'favicon'),
47 | to: ''
48 | },
49 |
50 | {
51 | from: path.join(assetsDir, 'images'),
52 | to: 'images/'
53 | }
54 | ],
55 | }),
56 |
57 | new MiniCssExtractPlugin({
58 | filename: isProd ? '[name].[contenthash].css' : '[name].css',
59 | chunkFilename: isProd ? '[id].css' : '[id].[contenthash].css'
60 | }),
61 |
62 | // Other plugins...
63 | ],
64 |
65 | module: {
66 | rules: [
67 | // JavaScript
68 | {
69 | test: /\.js$/,
70 | use: {
71 | loader: 'babel-loader',
72 | options: {
73 | presets: ['@babel/preset-env'],
74 | plugins: ['@babel/plugin-proposal-class-properties']
75 | }
76 | }
77 | },
78 |
79 | // Sass
80 | {
81 | test: /\.scss$/,
82 | use: [
83 | {
84 | loader: MiniCssExtractPlugin.loader,
85 | options: {
86 | publicPath: ''
87 | }
88 | },
89 |
90 | {
91 | loader: 'css-loader'
92 | },
93 |
94 | {
95 | loader: 'postcss-loader',
96 | options: {
97 | postcssOptions: {
98 | plugins: ['postcss-preset-env'],
99 | },
100 | },
101 | },
102 |
103 | {
104 | loader: 'sass-loader'
105 | },
106 | ],
107 | },
108 |
109 | // Image, svg assets
110 | {
111 | test: /\.(svg|png|jpg|gif)$/i,
112 | loader: 'file-loader',
113 | options: {
114 | name: '[name].[ext]',
115 | outputPath: 'images/',
116 |
117 | }
118 | },
119 |
120 | // Font assets
121 | {
122 | test: /\.(woff|woff2)$/i,
123 | loader: 'file-loader',
124 | options: {
125 | name: '[name].[ext]',
126 | outputPath: 'fonts/'
127 | }
128 | },
129 |
130 | // Shaders
131 | {
132 | test: /\.(glsl|frag|vert)$/,
133 | loader: 'raw-loader',
134 | exclude: /node_modules/
135 | },
136 |
137 | {
138 | test: /\.(glsl|frag|vert)$/,
139 | loader: 'glslify-loader',
140 | exclude: /node_modules/
141 | }
142 |
143 | // Other loaders...
144 | ],
145 | },
146 | };
147 |
148 | module.exports = () => {
149 | if (isProd) {
150 | config.mode = 'production';
151 | } else {
152 | config.mode = 'development';
153 | }
154 |
155 | return config;
156 | };
157 |
--------------------------------------------------------------------------------