├── .gitignore
├── README.md
├── package-lock.json
├── package.json
├── public
├── _redirects
├── favicon.ico
├── index.html
├── manifest.json
└── og.png
└── src
├── App.css
├── App.js
├── ComponentGrid.js
├── Directory.js
├── DynamicLighting
├── DynamicLighting.js
├── LightCanvas.js
├── lightToStyles.js
├── useDimensions.js
├── useMousePosition.js
├── useRayCasterEngine.js
└── vector.js
├── FlagObjectBuilder
├── Controls.js
├── FlagObject.js
├── FlagObjectBuilder.js
├── components.js
└── theme.js
├── GenerativeLayout
├── Article.js
├── GenerativeGrid.js
├── GeneratorContainer.js
├── GeneratorControls.js
├── generative-layout.css
├── index.js
├── useDebugGrid.js
└── utils.js
├── GenerativePalette
├── DynamicCanvas.js
├── GenerativePalette.js
├── Sliders.js
└── useGenerativePalette.js
├── Info.js
├── Pager.js
├── RadioGroup.js
├── Responsive
├── Artboard.js
├── RedLines.js
├── Responsive.js
└── components.js
├── Toggle.js
├── components.js
├── icons
├── arrow-down.svg
├── arrow-left.svg
├── arrow-right.svg
├── arrow-up.svg
├── flag.svg
├── info.svg
├── layout.svg
├── maximize-2.svg
├── menu.svg
├── search.svg
├── sliders.svg
├── sun.svg
└── x-circle.svg
├── imgs
├── ayla.jpg
├── ernest-porzi-19106-unsplash.jpg
├── fabian-moller-401625-unsplash.jpg
├── light.png
├── light.svg
├── paridhi.png
├── sandra-ollier-663002-unsplash.jpg
└── zainab.png
├── index.css
├── index.js
├── primitives.js
├── serviceWorker.js
├── theme.js
└── useThemes.js
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | /build
13 |
14 | # misc
15 | .DS_Store
16 | .env.local
17 | .env.development.local
18 | .env.test.local
19 | .env.production.local
20 |
21 | npm-debug.log*
22 | yarn-debug.log*
23 | yarn-error.log*
24 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
2 |
3 | ## Available Scripts
4 |
5 | In the project directory, you can run:
6 |
7 | ### `npm start`
8 |
9 | Runs the app in the development mode.
10 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser.
11 |
12 | The page will reload if you make edits.
13 | You will also see any lint errors in the console.
14 |
15 | ### `npm test`
16 |
17 | Launches the test runner in the interactive watch mode.
18 | See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
19 |
20 | ### `npm run build`
21 |
22 | Builds the app for production to the `build` folder.
23 | It correctly bundles React in production mode and optimizes the build for the best performance.
24 |
25 | The build is minified and the filenames include the hashes.
26 | Your app is ready to be deployed!
27 |
28 | See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.
29 |
30 | ### `npm run eject`
31 |
32 | **Note: this is a one-way operation. Once you `eject`, you can’t go back!**
33 |
34 | If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project.
35 |
36 | Instead, it will copy all the configuration files and the transitive dependencies (Webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own.
37 |
38 | You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it.
39 |
40 | ## Learn More
41 |
42 | You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started).
43 |
44 | To learn React, check out the [React documentation](https://reactjs.org/).
45 |
46 | ### Code Splitting
47 |
48 | This section has moved here: https://facebook.github.io/create-react-app/docs/code-splitting
49 |
50 | ### Analyzing the Bundle Size
51 |
52 | This section has moved here: https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size
53 |
54 | ### Making a Progressive Web App
55 |
56 | This section has moved here: https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app
57 |
58 | ### Advanced Configuration
59 |
60 | This section has moved here: https://facebook.github.io/create-react-app/docs/advanced-configuration
61 |
62 | ### Deployment
63 |
64 | This section has moved here: https://facebook.github.io/create-react-app/docs/deployment
65 |
66 | ### `npm run build` fails to minify
67 |
68 | This section has moved here: https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify
69 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "dynamic-lighting",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "@emotion/core": "^10.0.10",
7 | "@emotion/styled": "^10.0.11",
8 | "@reach/dialog": "^0.2.8",
9 | "@reach/router": "^1.2.1",
10 | "@rehooks/component-size": "^1.0.2",
11 | "@styled-system/should-forward-prop": "^5.0.12",
12 | "@styled-system/theme-get": "^5.0.18",
13 | "canvas-sketch-util": "^1.8.0",
14 | "chroma-js": "^2.0.3",
15 | "constate": "^1.2.0",
16 | "emotion-theming": "^10.0.10",
17 | "hsluv": "0.0.3",
18 | "rc-slider": "^8.6.13",
19 | "re-resizable": "^5.0.1",
20 | "react": "^16.8.6",
21 | "react-dom": "^16.8.6",
22 | "react-pose": "^4.0.8",
23 | "react-scripts": "3.0.1",
24 | "rebass": "^3.1.1",
25 | "resize-observer-polyfill": "^1.5.1",
26 | "styled-system": "^5.0.12",
27 | "tachyons": "^4.11.1"
28 | },
29 | "scripts": {
30 | "start": "react-scripts start",
31 | "build": "react-scripts build",
32 | "test": "react-scripts test",
33 | "eject": "react-scripts eject"
34 | },
35 | "eslintConfig": {
36 | "extends": "react-app"
37 | },
38 | "browserslist": {
39 | "production": [
40 | ">0.2%",
41 | "not dead",
42 | "not op_mini all"
43 | ],
44 | "development": [
45 | "last 1 chrome version",
46 | "last 1 firefox version",
47 | "last 1 safari version"
48 | ]
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/public/_redirects:
--------------------------------------------------------------------------------
1 | /* /index.html 200
2 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/winkerVSbecks/generative-parts/a596f912c66c5f4b0cd02bddab0622a7e18df124/public/favicon.ico
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
12 |
13 |
14 | Generative.parts
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
36 |
37 |
46 | React App
47 |
48 |
49 | You need to enable JavaScript to run this app.
50 |
51 |
61 |
62 |
63 |
--------------------------------------------------------------------------------
/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | }
10 | ],
11 | "start_url": ".",
12 | "display": "standalone",
13 | "theme_color": "#000000",
14 | "background_color": "#ffffff"
15 | }
16 |
--------------------------------------------------------------------------------
/public/og.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/winkerVSbecks/generative-parts/a596f912c66c5f4b0cd02bddab0622a7e18df124/public/og.png
--------------------------------------------------------------------------------
/src/App.css:
--------------------------------------------------------------------------------
1 | @import url('https://rsms.me/inter/inter.css');
2 |
3 | body {
4 | background-color: black;
5 | cursor: move;
6 | }
7 |
8 | [data-reach-dialog-overlay] {
9 | z-index: 999;
10 | }
11 |
12 | :root {
13 | --material-white: #fff;
14 | --material-gray: #898ba3;
15 | }
16 |
--------------------------------------------------------------------------------
/src/App.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Router } from '@reach/router';
3 | import { DynamicLighting } from './DynamicLighting/DynamicLighting';
4 | import { GenerativePalette } from './GenerativePalette/GenerativePalette';
5 | import { Responsive } from './Responsive/Responsive';
6 | import { Directory } from './Directory';
7 | import { GenerativeLayout } from './GenerativeLayout';
8 | import { FlagObjectBuilder } from './FlagObjectBuilder/FlagObjectBuilder';
9 | import './App.css';
10 |
11 | export default function App() {
12 | return (
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 | );
23 | }
24 |
--------------------------------------------------------------------------------
/src/ComponentGrid.js:
--------------------------------------------------------------------------------
1 | import styled from '@emotion/styled';
2 | import { grid } from 'styled-system';
3 | import { Card, Flex } from './primitives';
4 |
5 | export const ComponentGrid = styled(Card)(grid, {
6 | position: 'relative',
7 | });
8 |
9 | ComponentGrid.defaultProps = {
10 | backgroundColor: 'tertiary',
11 | alignContent: ['flex-end', 'center'],
12 | justifyItems: 'center',
13 | justifyContent: 'center',
14 | borderRadius: [0, 0, 0, 2],
15 | flexWrap: 'wrap',
16 | height: ['100%', '100%', '100%', 720],
17 | width: ['100%', '100%', '100%', 1184],
18 | };
19 |
20 | ComponentGrid.One = styled(Flex)({ zIndex: 1 });
21 | ComponentGrid.One.defaultProps = {
22 | flexDirection: 'column',
23 | mr: [2, 3, 3],
24 | ml: [2, 0, 0],
25 | mb: [3, 0, 0],
26 | width: ['auto', 240, 240],
27 | flex: ['1 0 192px', 'none', 'none'],
28 | };
29 |
30 | ComponentGrid.Two = styled(Flex)({ zIndex: 1 });
31 | ComponentGrid.Two.defaultProps = {
32 | flexDirection: 'column',
33 | width: ['100%', 384, 384],
34 | mr: [2, 0, 3],
35 | ml: [2, 0, 0],
36 | };
37 |
38 | ComponentGrid.Three = styled(Flex)({ zIndex: 1 });
39 | ComponentGrid.Three.defaultProps = {
40 | flexDirection: ['row', 'row', 'column'],
41 | width: ['100%', 640, 176],
42 | height: [128, 176, 472],
43 | mt: [3, 3, 0],
44 | mx: [2, 0, 0],
45 | };
46 |
--------------------------------------------------------------------------------
/src/Directory.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { ThemeProvider } from 'emotion-theming';
3 | import { Link as RouterLink } from '@reach/router';
4 | import styled from '@emotion/styled';
5 | import { border, shadow, background, grid } from 'styled-system';
6 | import shouldForwardProp from '@styled-system/should-forward-prop';
7 | import theme from './theme';
8 | import { Flex, Link, Heading, Box, Icon } from './primitives';
9 | import { ReactComponent as SunIcon } from './icons/sun.svg';
10 | import { ReactComponent as SlidersIcon } from './icons/sliders.svg';
11 | import { ReactComponent as MaxIcon } from './icons/maximize-2.svg';
12 | import { ReactComponent as LayoutIcon } from './icons/layout.svg';
13 | import { ReactComponent as FlagIcon } from './icons/flag.svg';
14 |
15 | const ExperimentLink = styled(Link, { shouldForwardProp })(
16 | { display: 'flex' },
17 | props => ({
18 | ':hover, :focus': {
19 | backgroundColor: props.theme.colors.primary,
20 | color: props.theme.colors.black,
21 | },
22 | }),
23 | border,
24 | shadow,
25 | background,
26 | grid,
27 | );
28 | ExperimentLink.defaultProps = {
29 | width: [4, 5],
30 | height: [4, 5],
31 | backgroundColor: 'white',
32 | borderRadius: '3px',
33 | alignItems: 'center',
34 | justifyContent: 'center',
35 | color: 'black',
36 | mr: 3,
37 | mb: 3,
38 | };
39 |
40 | export const Directory = () => (
41 |
42 |
43 |
44 | Generative.parts
45 |
46 |
47 |
48 |
53 |
54 |
55 |
60 |
61 |
62 |
63 |
64 |
65 |
70 |
71 |
72 |
77 |
78 |
79 |
80 |
81 |
82 |
87 | GitHub
88 |
89 |
90 | varun.ca
91 |
92 |
93 |
94 |
95 | );
96 |
--------------------------------------------------------------------------------
/src/DynamicLighting/DynamicLighting.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { ThemeProvider } from 'emotion-theming';
3 | import theme from '../theme';
4 | import { Container } from '../primitives';
5 | import { useThemes } from '../useThemes';
6 | import LightCanvas from './LightCanvas';
7 |
8 | export function DynamicLighting() {
9 | const [data, activeIndex, selectTheme] = useThemes();
10 |
11 | return (
12 |
13 |
14 |
20 |
21 |
22 | );
23 | }
24 |
--------------------------------------------------------------------------------
/src/DynamicLighting/LightCanvas.js:
--------------------------------------------------------------------------------
1 | import React, { useRef, useState } from 'react';
2 | import { withTheme } from 'emotion-theming';
3 | import {
4 | TypographySwatch,
5 | ColorSwatch,
6 | NavBar,
7 | MediaCard,
8 | SearchBar,
9 | ProfileCard,
10 | } from '../components';
11 | import { ComponentGrid } from '../ComponentGrid';
12 | import { Pager } from '../Pager';
13 | import { InfoButton } from '../Info';
14 | import { Toggle } from '../Toggle';
15 | import { useDimensions } from './useDimensions';
16 | import { useRayCasterEngine, RayCasterDebug } from './useRayCasterEngine';
17 | import { lightToStyles } from './lightToStyles';
18 |
19 | function LightCanvas({ profile, media, activeIndex, selectTheme, theme }) {
20 | const typeSwRef = useRef(null);
21 | const profileRef = useRef(null);
22 | const navRef = useRef(null);
23 | const mediaRef = useRef(null);
24 | const searchRef = useRef(null);
25 | const blackRef = useRef(null);
26 | const primaryRef = useRef(null);
27 | const secondaryRef = useRef(null);
28 | const canvasRef = useRef(null);
29 |
30 | const [windowDims, surfaceDims] = useDimensions(
31 | typeSwRef,
32 | profileRef,
33 | navRef,
34 | mediaRef,
35 | searchRef,
36 | blackRef,
37 | primaryRef,
38 | secondaryRef,
39 | );
40 |
41 | const light = useRayCasterEngine(
42 | windowDims,
43 | surfaceDims,
44 | canvasRef,
45 | theme.colors.primary,
46 | );
47 |
48 | const [debug, setDebug] = useState(false);
49 |
50 | return (
51 |
52 | {debug && }
53 | setDebug(!debug)}
61 | />
62 |
63 |
64 |
65 |
70 |
76 |
77 |
78 |
79 |
84 |
91 |
96 |
97 |
98 |
99 |
107 |
115 |
121 |
122 |
123 |
124 |
125 | );
126 | }
127 |
128 | export default withTheme(LightCanvas);
129 |
--------------------------------------------------------------------------------
/src/DynamicLighting/lightToStyles.js:
--------------------------------------------------------------------------------
1 | import chroma from 'chroma-js';
2 |
3 | export function lightToStyles(light = {}, colors) {
4 | const { volume = 0 } = light;
5 |
6 | return {
7 | // '--material-white': chroma
8 | // .mix('white', colors.tertiary, mapRange(lightVolume, 0, 1, 0.2, 1), 'hsl')
9 | // .css(),
10 | // '--material-white': chroma(colors.tertiary)
11 | // .luminance(mapRange(lightVolume, 0, 1, 0.5, 1))
12 | // .css(),
13 | transitionProperty: 'color, background-color',
14 | transitionDuration: '0.15s',
15 | transitionTimingFunction: 'ease-in-out',
16 | '--material-white': chroma
17 | .scale([colors.tertiary, colors.materialWhite])
18 | .domain([0, 0.25, 1])(mapRange(volume, 0, 1, 0, 1))
19 | .css(),
20 | '--material-gray': chroma(colors.materialGray)
21 | .darken(mapRange(volume, 0, 1, 2, 0))
22 | .css(),
23 | boxShadow: shadow(light),
24 | };
25 | }
26 |
27 | function shadow({ volume = 0, direction = [0, 0], distance = 0 }) {
28 | const [x, y] = direction;
29 | const xOff = shadowOffset(x);
30 | const yOff = shadowOffset(y);
31 | const opacity = mapRange(volume, 0, 1, 0, 0.2);
32 |
33 | return `${xOff}px ${yOff}px 8px 0 rgba(0, 0, 0, ${opacity})`;
34 | }
35 |
36 | function shadowOffset(v) {
37 | return mapRange(v, -1, 1, 8, -8).toFixed(3);
38 | }
39 |
40 | function constrain(n, low, high) {
41 | return Math.max(Math.min(n, high), low);
42 | }
43 |
44 | function mapRange(n, start1, stop1, start2, stop2) {
45 | const value = ((n - start1) / (stop1 - start1)) * (stop2 - start2) + start2;
46 | if (start2 < stop2) {
47 | return constrain(value, start2, stop2);
48 | } else {
49 | return constrain(value, stop2, start2);
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/src/DynamicLighting/useDimensions.js:
--------------------------------------------------------------------------------
1 | import { useCallback, useState, useEffect } from 'react';
2 |
3 | export function useDimensions(
4 | typeSwRef,
5 | profileRef,
6 | navRef,
7 | mediaRef,
8 | searchRef,
9 | blackRef,
10 | primaryRef,
11 | secondaryRef,
12 | ) {
13 | const [dimensions, setDimensions] = useState([{}, {}]);
14 |
15 | const handleResize = useCallback(() => {
16 | const size = [
17 | {
18 | width: window.innerWidth,
19 | height: window.innerHeight,
20 | },
21 | {
22 | typeSw: typeSwRef.current
23 | ? typeSwRef.current.getBoundingClientRect()
24 | : null,
25 | profile: profileRef.current
26 | ? profileRef.current.getBoundingClientRect()
27 | : null,
28 | nav: navRef.current ? navRef.current.getBoundingClientRect() : null,
29 | media: mediaRef.current
30 | ? mediaRef.current.getBoundingClientRect()
31 | : null,
32 | search: searchRef.current
33 | ? searchRef.current.getBoundingClientRect()
34 | : null,
35 | black: blackRef.current
36 | ? blackRef.current.getBoundingClientRect()
37 | : null,
38 | primary: primaryRef.current
39 | ? primaryRef.current.getBoundingClientRect()
40 | : null,
41 | secondary: secondaryRef.current
42 | ? secondaryRef.current.getBoundingClientRect()
43 | : null,
44 | },
45 | ];
46 |
47 | setDimensions(size);
48 | }, [
49 | typeSwRef,
50 | profileRef,
51 | navRef,
52 | mediaRef,
53 | searchRef,
54 | blackRef,
55 | primaryRef,
56 | secondaryRef,
57 | ]);
58 |
59 | useEffect(() => {
60 | if (!typeSwRef.current) {
61 | return;
62 | }
63 |
64 | handleResize();
65 |
66 | window.addEventListener('resize', handleResize);
67 | return () => window.removeEventListener('resize', handleResize);
68 | // eslint-disable-next-line
69 | }, [
70 | typeSwRef.current,
71 | profileRef.current,
72 | navRef.current,
73 | mediaRef.current,
74 | searchRef.current,
75 | blackRef.current,
76 | primaryRef.current,
77 | secondaryRef.current,
78 | ]);
79 |
80 | return dimensions;
81 | }
82 |
--------------------------------------------------------------------------------
/src/DynamicLighting/useMousePosition.js:
--------------------------------------------------------------------------------
1 | import { useState, useEffect } from 'react';
2 |
3 | /**
4 | * from https://github.com/rehooks/window-mouse-position/blob/master/index.js
5 | */
6 | export function useWindowMousePosition() {
7 | let [WindowMousePosition, setWindowMousePosition] = useState({
8 | x: null,
9 | y: null,
10 | });
11 |
12 | function handleMouseMove(e) {
13 | setWindowMousePosition({
14 | x: e.pageX,
15 | y: e.pageY,
16 | });
17 | }
18 |
19 | useEffect(() => {
20 | window.addEventListener('mousemove', handleMouseMove);
21 |
22 | return () => {
23 | window.removeEventListener('mousemove', handleMouseMove);
24 | };
25 | }, []);
26 |
27 | return WindowMousePosition;
28 | }
29 |
--------------------------------------------------------------------------------
/src/DynamicLighting/useRayCasterEngine.js:
--------------------------------------------------------------------------------
1 | import { useState, useEffect } from 'react';
2 | import styled from '@emotion/styled';
3 | import Vector, { createVector, toRadians } from './vector';
4 | import { useWindowMousePosition } from './useMousePosition';
5 |
6 | export const RayCasterDebug = styled.canvas`
7 | position: fixed;
8 | top: 0;
9 | right: 0;
10 | bottom: 0;
11 | left: 0;
12 | width: 100%;
13 | height: 100%;
14 | z-index: 0;
15 | background-color: transparent;
16 | `;
17 |
18 | /**
19 | * Boundary
20 | * Surfaces that interact with light rays
21 | */
22 | function createBoundary([x1, y1], [x2, y2], type) {
23 | const a = createVector(x1, y1);
24 | const b = createVector(x2, y2);
25 |
26 | return {
27 | a,
28 | b,
29 | type,
30 | draw(context) {
31 | context.lineWidth = 2;
32 | context.beginPath();
33 | context.moveTo(a.x, a.y);
34 | context.lineTo(b.x, b.y);
35 | context.stroke();
36 | },
37 | };
38 | }
39 |
40 | /**
41 | * Light Ray
42 | */
43 | function createRay(position, angle, limit) {
44 | const dir = Vector.fromAngle(angle);
45 |
46 | return {
47 | lookAt(x, y) {
48 | dir.x = x - position.x;
49 | dir.y = y - position.y;
50 | dir.normalize();
51 | },
52 | cast(surface) {
53 | const x1 = surface.a.x;
54 | const y1 = surface.a.y;
55 | const x2 = surface.b.x;
56 | const y2 = surface.b.y;
57 |
58 | const x3 = position.x;
59 | const y3 = position.y;
60 | const x4 = position.x + dir.x;
61 | const y4 = position.y + dir.y;
62 |
63 | const den = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4);
64 | if (den === 0) {
65 | return;
66 | }
67 |
68 | const t = ((x1 - x3) * (y3 - y4) - (y1 - y3) * (x3 - x4)) / den;
69 | const u = -((x1 - x2) * (y1 - y3) - (y1 - y2) * (x1 - x3)) / den;
70 | if (t > 0 && t < 1 && u > 0) {
71 | const pt = createVector();
72 | pt.x = x1 + t * (x2 - x1);
73 | pt.y = y1 + t * (y2 - y1);
74 | return pt;
75 | } else {
76 | return undefined;
77 | }
78 | },
79 | draw(context) {
80 | context.beginPath();
81 | context.moveTo(position.x, position.y);
82 | context.lineTo(position.x + dir.x * limit, position.y + dir.y * limit);
83 | context.stroke();
84 | },
85 | };
86 | }
87 |
88 | /**
89 | * Light Source
90 | * Generates rays in 360° and then casts
91 | * them onto the surfaces
92 | */
93 | function createLight(x, y, limit) {
94 | const position = createVector(x, y);
95 |
96 | const rays = [...Array(360).keys()].map((_, a) =>
97 | createRay(position, toRadians(a), limit),
98 | );
99 |
100 | return {
101 | position,
102 | rays,
103 | draw(context) {
104 | context.lineWidth = 1;
105 | context.beginPath();
106 | context.ellipse(position.x, position.y, 5, 5, 0, 0, 2 * Math.PI);
107 | context.fill();
108 | },
109 | };
110 | }
111 |
112 | function look({ rays, position }, surfaces) {
113 | return rays.reduce((acc, ray) => {
114 | let closest = null;
115 | let closestSurface = null;
116 | let record = Infinity;
117 |
118 | surfaces.forEach(surface => {
119 | const pt = ray.cast(surface);
120 |
121 | if (pt) {
122 | const d = position.dist(pt);
123 |
124 | if (d < record) {
125 | record = d;
126 | closest = pt;
127 | closestSurface = surface;
128 | }
129 | }
130 | });
131 |
132 | return closest
133 | ? acc.concat([
134 | {
135 | a: [position.x, position.y],
136 | b: [closest.x, closest.y],
137 | type: closestSurface.type,
138 | },
139 | ])
140 | : acc;
141 | }, []);
142 | }
143 |
144 | export function useRayCasterEngine(
145 | { width = 0, height = 0 },
146 | surfaceDims,
147 | canvasRef,
148 | color = 'white',
149 | ) {
150 | const [lights, setLights] = useState({});
151 | let { x, y } = useWindowMousePosition();
152 |
153 | useEffect(() => {
154 | const light = createLight(x, y, Math.max(width, height));
155 | const p = 0; // padding
156 |
157 | const surfaces = Object.keys(surfaceDims).map(name => ({
158 | name,
159 | position: createVector(
160 | surfaceDims[name].left + surfaceDims[name].width / 2,
161 | surfaceDims[name].top + surfaceDims[name].height / 2,
162 | ),
163 | dims: surfaceDims[name],
164 | }));
165 |
166 | const boundaries = surfaces
167 | .filter(s => s.dims)
168 | .map(({ dims: { top: t, left: l, bottom: b, right: r } = {}, name }) => [
169 | createBoundary([l - p, t - p], [r + p, t - p], name),
170 | createBoundary([r + p, t - p], [r + p, b + p], name),
171 | createBoundary([r + p, b + p], [l - p, b + p], name),
172 | createBoundary([l - p, b + p], [l - p, t - p], name),
173 | ])
174 | .flat();
175 |
176 | const interactions = look(light, boundaries);
177 |
178 | const lightVolumes = interactions.reduce((acc, ray) => {
179 | if (!acc[ray.type]) acc[ray.type] = 0;
180 | acc[ray.type] = acc[ray.type] < 1 ? acc[ray.type] + 0.1 : acc[ray.type];
181 | return acc;
182 | }, {});
183 |
184 | setLights(
185 | surfaces.reduce((acc, surface) => {
186 | const distance = light.position.dist(surface.position);
187 |
188 | acc[surface.name] = {
189 | direction: light.position
190 | .copy()
191 | .sub(surface.position)
192 | .normalize()
193 | .array(),
194 | distance,
195 | volume: lightVolumes[surface.name],
196 | };
197 |
198 | return acc;
199 | }, {}),
200 | );
201 |
202 | if (canvasRef.current) {
203 | canvasRef.current.width = width * 2;
204 | canvasRef.current.height = height * 2;
205 | canvasRef.current.style.width = `${width}px`;
206 | canvasRef.current.style.height = `${height}px`;
207 |
208 | const context = canvasRef.current.getContext('2d');
209 | context.scale(2, 2);
210 | context.clearRect(0, 0, width, height);
211 |
212 | context.strokeStyle = 'rgba(255, 255, 255, 0.25)';
213 | // context.fillStyle = color;
214 |
215 | if (boundaries) {
216 | // boundaries.forEach(boundary => boundary.draw(context));
217 | // light.draw(context);
218 |
219 | interactions.forEach(({ a, b }) => {
220 | context.beginPath();
221 | context.moveTo(...a);
222 | context.lineTo(...b);
223 | context.stroke();
224 | });
225 | }
226 | }
227 | }, [width, height, surfaceDims, x, y, canvasRef, color]);
228 |
229 | return lights;
230 | }
231 |
--------------------------------------------------------------------------------
/src/DynamicLighting/vector.js:
--------------------------------------------------------------------------------
1 | /**
2 | * P5 Vector
3 | * from:
4 | * https://github.com/processing/p5.js/blob/0.8.0/src/math/p5.Vector.js#L12
5 | */
6 | export default function Vector(x, y, z = 0) {
7 | this.x = x;
8 | this.y = y;
9 | this.z = z;
10 | }
11 |
12 | Vector.prototype.toString = function p5VectorToString() {
13 | return 'Vector Object : [' + this.x + ', ' + this.y + ', ' + this.z + ']';
14 | };
15 |
16 | Vector.prototype.set = function set(x, y, z) {
17 | if (x instanceof Vector) {
18 | this.x = x.x || 0;
19 | this.y = x.y || 0;
20 | this.z = x.z || 0;
21 | return this;
22 | }
23 | if (x instanceof Array) {
24 | this.x = x[0] || 0;
25 | this.y = x[1] || 0;
26 | this.z = x[2] || 0;
27 | return this;
28 | }
29 | this.x = x || 0;
30 | this.y = y || 0;
31 | this.z = z || 0;
32 | return this;
33 | };
34 |
35 | Vector.prototype.copy = function copy() {
36 | return new Vector(this.x, this.y, this.z);
37 | };
38 |
39 | Vector.prototype.add = function add(x, y, z) {
40 | if (x instanceof Vector) {
41 | this.x += x.x || 0;
42 | this.y += x.y || 0;
43 | this.z += x.z || 0;
44 | return this;
45 | }
46 | if (x instanceof Array) {
47 | this.x += x[0] || 0;
48 | this.y += x[1] || 0;
49 | this.z += x[2] || 0;
50 | return this;
51 | }
52 | this.x += x || 0;
53 | this.y += y || 0;
54 | this.z += z || 0;
55 | return this;
56 | };
57 |
58 | Vector.prototype.sub = function sub(x, y, z) {
59 | if (x instanceof Vector) {
60 | this.x -= x.x || 0;
61 | this.y -= x.y || 0;
62 | this.z -= x.z || 0;
63 | return this;
64 | }
65 | if (x instanceof Array) {
66 | this.x -= x[0] || 0;
67 | this.y -= x[1] || 0;
68 | this.z -= x[2] || 0;
69 | return this;
70 | }
71 | this.x -= x || 0;
72 | this.y -= y || 0;
73 | this.z -= z || 0;
74 | return this;
75 | };
76 |
77 | Vector.prototype.mult = function mult(n) {
78 | if (!(typeof n === 'number' && isFinite(n))) {
79 | console.warn(
80 | 'Vector.prototype.mult:',
81 | 'n is undefined or not a finite number',
82 | );
83 | return this;
84 | }
85 | this.x *= n;
86 | this.y *= n;
87 | this.z *= n;
88 | return this;
89 | };
90 |
91 | Vector.prototype.div = function div(n) {
92 | if (!(typeof n === 'number' && isFinite(n))) {
93 | console.warn(
94 | 'Vector.prototype.div:',
95 | 'n is undefined or not a finite number',
96 | );
97 | return this;
98 | }
99 | if (n === 0) {
100 | console.warn('Vector.prototype.div:', 'divide by 0');
101 | return this;
102 | }
103 | this.x /= n;
104 | this.y /= n;
105 | this.z /= n;
106 | return this;
107 | };
108 |
109 | Vector.prototype.mag = function mag() {
110 | return Math.sqrt(this.magSq());
111 | };
112 |
113 | Vector.prototype.magSq = function magSq() {
114 | var x = this.x;
115 | var y = this.y;
116 | var z = this.z;
117 | return x * x + y * y + z * z;
118 | };
119 |
120 | Vector.prototype.dot = function dot(x, y, z) {
121 | if (x instanceof Vector) {
122 | return this.dot(x.x, x.y, x.z);
123 | }
124 | return this.x * (x || 0) + this.y * (y || 0) + this.z * (z || 0);
125 | };
126 |
127 | Vector.prototype.cross = function cross(v) {
128 | var x = this.y * v.z - this.z * v.y;
129 | var y = this.z * v.x - this.x * v.z;
130 | var z = this.x * v.y - this.y * v.x;
131 | return new Vector(x, y, z);
132 | };
133 |
134 | Vector.prototype.dist = function dist(v) {
135 | return v
136 | .copy()
137 | .sub(this)
138 | .mag();
139 | };
140 |
141 | Vector.prototype.normalize = function normalize() {
142 | var len = this.mag();
143 | // here we multiply by the reciprocal instead of calling 'div()'
144 | // since div duplicates this zero check.
145 | if (len !== 0) this.mult(1 / len);
146 | return this;
147 | };
148 |
149 | Vector.prototype.limit = function limit(max) {
150 | var mSq = this.magSq();
151 | if (mSq > max * max) {
152 | this.div(Math.sqrt(mSq)) //normalize it
153 | .mult(max);
154 | }
155 | return this;
156 | };
157 |
158 | Vector.prototype.setMag = function setMag(n) {
159 | return this.normalize().mult(n);
160 | };
161 |
162 | Vector.prototype.heading = function heading() {
163 | var h = Math.atan2(this.y, this.x);
164 | return fromRadians(h);
165 | };
166 |
167 | Vector.prototype.rotate = function rotate(a) {
168 | var newHeading = this.heading() + a;
169 | newHeading = toRadians(newHeading);
170 | var mag = this.mag();
171 | this.x = Math.cos(newHeading) * mag;
172 | this.y = Math.sin(newHeading) * mag;
173 | return this;
174 | };
175 |
176 | Vector.prototype.angleBetween = function angleBetween(v) {
177 | var dotmagmag = this.dot(v) / (this.mag() * v.mag());
178 | // Mathematically speaking: the dotmagmag variable will be between -1 and 1
179 | // inclusive. Practically though it could be slightly outside this range due
180 | // to floating-point rounding issues. This can make Math.acos return NaN.
181 | //
182 | // Solution: we'll clamp the value to the -1,1 range
183 | var angle = Math.acos(Math.min(1, Math.max(-1, dotmagmag)));
184 | return fromRadians(angle);
185 | };
186 |
187 | Vector.prototype.lerp = function lerp(x, y, z, amt) {
188 | if (x instanceof Vector) {
189 | return this.lerp(x.x, x.y, x.z, y);
190 | }
191 | this.x += (x - this.x) * amt || 0;
192 | this.y += (y - this.y) * amt || 0;
193 | this.z += (z - this.z) * amt || 0;
194 | return this;
195 | };
196 |
197 | Vector.prototype.array = function array() {
198 | return [this.x || 0, this.y || 0, this.z || 0];
199 | };
200 |
201 | Vector.prototype.equals = function equals(x, y, z) {
202 | var a, b, c;
203 | if (x instanceof Vector) {
204 | a = x.x || 0;
205 | b = x.y || 0;
206 | c = x.z || 0;
207 | } else if (x instanceof Array) {
208 | a = x[0] || 0;
209 | b = x[1] || 0;
210 | c = x[2] || 0;
211 | } else {
212 | a = x || 0;
213 | b = y || 0;
214 | c = z || 0;
215 | }
216 | return this.x === a && this.y === b && this.z === c;
217 | };
218 |
219 | Vector.fromAngle = function fromAngle(angle, length) {
220 | if (typeof length === 'undefined') {
221 | length = 1;
222 | }
223 | return new Vector(length * Math.cos(angle), length * Math.sin(angle), 0);
224 | };
225 |
226 | Vector.fromAngles = function(theta, phi, length) {
227 | if (typeof length === 'undefined') {
228 | length = 1;
229 | }
230 | var cosPhi = Math.cos(phi);
231 | var sinPhi = Math.sin(phi);
232 | var cosTheta = Math.cos(theta);
233 | var sinTheta = Math.sin(theta);
234 |
235 | return new Vector(
236 | length * sinTheta * sinPhi,
237 | -length * cosTheta,
238 | length * sinTheta * cosPhi,
239 | );
240 | };
241 |
242 | Vector.random2D = function random2D() {
243 | return this.fromAngle(Math.random() * 2 * Math.PI);
244 | };
245 |
246 | Vector.random3D = function random3D() {
247 | var angle = Math.random() * 2 * Math.PI;
248 | var vz = Math.random() * 2 - 1;
249 | var vzBase = Math.sqrt(1 - vz * vz);
250 | var vx = vzBase * Math.cos(angle);
251 | var vy = vzBase * Math.sin(angle);
252 | return new Vector(vx, vy, vz);
253 | };
254 |
255 | Vector.add = function add(v1, v2, target) {
256 | if (!target) {
257 | target = v1.copy();
258 | } else {
259 | target.set(v1);
260 | }
261 | target.add(v2);
262 | return target;
263 | };
264 |
265 | Vector.sub = function sub(v1, v2, target) {
266 | if (!target) {
267 | target = v1.copy();
268 | } else {
269 | target.set(v1);
270 | }
271 | target.sub(v2);
272 | return target;
273 | };
274 |
275 | Vector.mult = function mult(v, n, target) {
276 | if (!target) {
277 | target = v.copy();
278 | } else {
279 | target.set(v);
280 | }
281 | target.mult(n);
282 | return target;
283 | };
284 |
285 | Vector.div = function div(v, n, target) {
286 | if (!target) {
287 | target = v.copy();
288 | } else {
289 | target.set(v);
290 | }
291 | target.div(n);
292 | return target;
293 | };
294 |
295 | Vector.dot = function dot(v1, v2) {
296 | return v1.dot(v2);
297 | };
298 |
299 | Vector.cross = function cross(v1, v2) {
300 | return v1.cross(v2);
301 | };
302 |
303 | Vector.dist = function dist(v1, v2) {
304 | return v1.dist(v2);
305 | };
306 |
307 | Vector.lerp = function lerp(v1, v2, amt, target) {
308 | if (!target) {
309 | target = v1.copy();
310 | } else {
311 | target.set(v1);
312 | }
313 | target.lerp(v2, amt);
314 | return target;
315 | };
316 |
317 | Vector.mag = function mag(vecT) {
318 | var x = vecT.x,
319 | y = vecT.y,
320 | z = vecT.z;
321 | var magSq = x * x + y * y + z * z;
322 | return Math.sqrt(magSq);
323 | };
324 |
325 | export function createVector(x, y, z) {
326 | return new Vector(x, y, z);
327 | }
328 |
329 | const DEG_TO_RAD = Math.PI / 180;
330 | const RAD_TO_DEG = 180 / Math.PI;
331 |
332 | export function toRadians(angle) {
333 | return angle * DEG_TO_RAD;
334 | }
335 |
336 | export function fromRadians(angle) {
337 | return angle * RAD_TO_DEG;
338 | }
339 |
--------------------------------------------------------------------------------
/src/FlagObjectBuilder/Controls.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Box, Flex, Label, Heading, InputRange } from '../primitives';
3 | import { Toggle } from '../Toggle';
4 | import { RadioGroup } from '../RadioGroup';
5 |
6 | const Togglable = ({ name, value, onChange }) => (
7 |
8 |
9 | {name}
10 |
11 | onChange(v => !v)}
16 | />
17 |
18 | );
19 |
20 | const JustifyContent = ({ label, value, onChange }) => (
21 |
22 |
28 | {label}
29 |
30 | onChange(e.target.value)}
34 | >
35 | start
36 | center
37 | space-between
38 | space-around
39 | end
40 |
41 |
42 | );
43 |
44 | export const Controls = ({
45 | toggles,
46 | alignment,
47 | sliders,
48 | contentAlignment,
49 | contentOrder,
50 | setContentOrder,
51 | ...props
52 | }) => {
53 | return (
54 |
55 |
56 | Controls
57 |
58 |
66 | Visibility
67 |
68 | {toggles.map(toggle => (
69 |
70 | ))}
71 |
79 | Alignment
80 |
81 | {alignment.map(radioGrp => (
82 |
93 | ))}
94 | {contentAlignment.map(section => (
95 |
96 | ))}
97 |
105 | Size
106 |
107 | {sliders.map(slider => (
108 |
109 | {slider.name}
110 |
111 |
112 | ))}
113 |
114 |
115 | );
116 | };
117 |
--------------------------------------------------------------------------------
/src/FlagObjectBuilder/FlagObject.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Flex, Icon, Hidden, Box } from '../primitives';
3 | import {
4 | Card,
5 | Headline,
6 | StandFirst,
7 | Meta,
8 | MetaLink,
9 | Pillar,
10 | Media,
11 | Blocky,
12 | } from './components';
13 |
14 | const story = {
15 | headline:
16 | 'Manchester United agree £80m deal to buy Harry Maguire from Leicester',
17 | standFirst:
18 | 'Manchester United have agreed an £80m deal to buy Harry Maguire from Leicester City, making him the world’s most expensive defender',
19 | meta: '2 Aug 2019',
20 | media:
21 | 'https://i.guim.co.uk/img/media/974ef47a5a96b017510dca7371c48fd133d416c1/2_125_3522_2113/master/3522.jpg?width=470&quality=45&auto=format&fit=max&dpr=2&s=0b4979f9faaefbfbd015dd77d22d2d4b',
22 | };
23 |
24 | export const FlagObject = ({
25 | headline,
26 | pillar,
27 | standFirst,
28 | meta,
29 | media,
30 | contentHidden,
31 | border,
32 | contentDir,
33 | standfirstDir,
34 | metaDir,
35 | width,
36 | height,
37 | mediaPadding,
38 | textAlignment,
39 | metaAlignment,
40 | reverseContentOrder,
41 | mediaTextRadio,
42 | }) => (
43 | 9 ? 'auto' : height}
47 | backgroundColor="neutral.6"
48 | justifyContent="stretch"
49 | borderWidth={border ? 2 : 0}
50 | >
51 |
57 | {headline && (
58 |
59 | {pillar && Football }{' '}
60 |
61 | {story.headline}
62 |
63 |
64 | )}
65 |
66 | {standFirst && (
67 |
68 |
69 | {story.standFirst}
70 |
71 |
72 | )}
73 | {meta && (
74 |
80 | )}
81 |
82 |
83 | {media && (
84 |
85 |
86 |
87 | )}
88 |
89 | );
90 |
91 | const MetaContent = ({ contentHidden, flexDirection, ...props }) => (
92 |
98 |
105 |
113 |
114 | {' '}
115 | Published: 2 Aug 2019
116 |
117 |
123 |
132 |
133 | {' '}
134 | 32
135 |
136 |
137 | );
138 |
--------------------------------------------------------------------------------
/src/FlagObjectBuilder/FlagObjectBuilder.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { ThemeProvider } from 'emotion-theming';
3 | import { Global } from '@emotion/core';
4 | import theme, { globalStyles } from './theme';
5 | import { Flex } from '../primitives';
6 | import { FlagObject } from './FlagObject';
7 | import { Controls } from './Controls';
8 |
9 | export function FlagObjectBuilder() {
10 | const controls = useControls();
11 |
12 | return (
13 |
14 |
15 |
16 |
25 |
26 |
27 |
36 |
37 |
38 | );
39 | }
40 |
41 | function useControls() {
42 | const [contentHidden, setContentHidden] = React.useState(true);
43 | const [headline, setHeadline] = React.useState(true);
44 | const [pillar, setPillar] = React.useState(true);
45 | const [standFirst, setStandFirst] = React.useState(true);
46 | const [meta, setMeta] = React.useState(true);
47 | const [media, setMedia] = React.useState(true);
48 | const [border, setBorder] = React.useState(true);
49 | const [reverseContentOrder, setReverseContentOrder] = React.useState(true);
50 |
51 | const [contentDir, setContentDir] = React.useState('column');
52 | const [standfirstDir, setStandfirstDir] = React.useState('column');
53 | const [metaDir, setMetaDir] = React.useState('row');
54 | const [textAlignment, setTextAlignment] = React.useState('flex-start');
55 | const [metaAlignment, setMetaAlignment] = React.useState('flex-start');
56 |
57 | const [width, setWidth] = React.useState(10);
58 | const [height, setHeight] = React.useState(10);
59 | const [mediaPadding, setMediaPadding] = React.useState(0);
60 | const [mediaTextRadio, setMediaTextRadio] = React.useState(66);
61 |
62 | return {
63 | toggles: [
64 | {
65 | name: 'Blocky Content',
66 | value: contentHidden,
67 | onChange: setContentHidden,
68 | },
69 | { name: 'Headline', value: headline, onChange: setHeadline },
70 | { name: 'Pillar', value: pillar, onChange: setPillar },
71 | { name: 'Stand First', value: standFirst, onChange: setStandFirst },
72 | { name: 'Meta', value: meta, onChange: setMeta },
73 | { name: 'Media', value: media, onChange: setMedia },
74 | { name: 'Border', value: border, onChange: setBorder },
75 | {
76 | name: 'Reverse Content Order',
77 | value: reverseContentOrder,
78 | onChange: setReverseContentOrder,
79 | },
80 | ],
81 | alignment: [
82 | { title: 'Content', selected: contentDir, onChange: setContentDir },
83 | {
84 | title: 'Standfirst Group',
85 | selected: standfirstDir,
86 | onChange: setStandfirstDir,
87 | },
88 | {
89 | title: 'Meta',
90 | selected: metaDir,
91 | onChange: setMetaDir,
92 | },
93 | ],
94 | contentAlignment: [
95 | {
96 | label: 'Text Content',
97 | value: textAlignment,
98 | onChange: setTextAlignment,
99 | },
100 | {
101 | label: 'Meta Content',
102 | value: metaAlignment,
103 | onChange: setMetaAlignment,
104 | },
105 | ],
106 | sliders: [
107 | { name: 'Width', value: width, onChange: setWidth, min: 0, max: 12 },
108 | // { name: 'Height', value: height, onChange: setHeight, min: 5, max: 10 },
109 | {
110 | name: 'Media Text Ratio',
111 | value: mediaTextRadio,
112 | onChange: setMediaTextRadio,
113 | min: 0,
114 | max: 100,
115 | },
116 | {
117 | name: 'Media Padding',
118 | value: mediaPadding,
119 | onChange: setMediaPadding,
120 | min: 0,
121 | max: 8,
122 | },
123 | ],
124 | flagObjectProps: {
125 | headline,
126 | pillar,
127 | standFirst,
128 | meta,
129 | media,
130 | contentHidden,
131 | reverseContentOrder,
132 | border,
133 | contentDir,
134 | standfirstDir,
135 | metaDir,
136 | width,
137 | height,
138 | mediaPadding,
139 | textAlignment,
140 | metaAlignment,
141 | mediaTextRadio,
142 | },
143 | };
144 | }
145 |
--------------------------------------------------------------------------------
/src/FlagObjectBuilder/components.js:
--------------------------------------------------------------------------------
1 | import styled from '@emotion/styled';
2 | import { space, color, layout, flexbox, typography } from 'styled-system';
3 | import { themeGet } from '@styled-system/theme-get';
4 | import {
5 | Text,
6 | Heading,
7 | AspectRatioImage,
8 | Card as BaseCard,
9 | } from '../primitives';
10 |
11 | export const Card = styled(BaseCard)();
12 | Card.defaultProps = {
13 | borderColor: 'pillar.main',
14 | borderWidth: 2,
15 | borderStyle: 'solid',
16 | borderX: 'none',
17 | borderBottom: 'none',
18 | flexDirection: 'column',
19 | };
20 |
21 | export const Headline = styled(Heading)();
22 |
23 | Headline.defaultProps = {
24 | as: 'h2',
25 | mb: 3,
26 | mt: 0,
27 | fontFamily: 'serif',
28 | fontWeight: 'bold',
29 | lineHeight: 'title',
30 | fontSize: [2, 3, 4],
31 | color: 'neutral.0',
32 | };
33 |
34 | export const StandFirst = styled(Text)();
35 |
36 | StandFirst.defaultProps = {
37 | as: 'p',
38 | mb: 0,
39 | mt: 0,
40 | fontFamily: 'serif',
41 | lineHeight: 'copy',
42 | color: 'neutral.0',
43 | fontSize: 1,
44 | };
45 |
46 | export const Pillar = styled(Heading)`
47 | ${blocky}
48 | :after {
49 | content: '/';
50 | display: inline-block;
51 | font-weight: bold;
52 | margin-left: ${props => props.theme.space[1]}px;
53 | }
54 | `;
55 |
56 | Pillar.defaultProps = {
57 | as: 'span',
58 | fontFamily: 'serif',
59 | color: 'pillar.main',
60 | };
61 |
62 | export const Meta = styled.span(
63 | { display: 'flex' },
64 | space,
65 | color,
66 | layout,
67 | flexbox,
68 | typography,
69 | blocky,
70 | );
71 | Meta.defaultProps = {
72 | alignItems: 'center',
73 | fontFamily: 'sans',
74 | lineHeight: 'solid',
75 | fontSize: 0,
76 | color: 'neutral.3',
77 | };
78 |
79 | export const MetaLink = styled(Meta)(
80 | {
81 | textDecoration: 'none',
82 | display: 'flex',
83 | ':hover, :focus': {
84 | textDecoration: 'underline',
85 | },
86 | },
87 | blocky,
88 | );
89 |
90 | MetaLink.defaultProps = {
91 | as: 'a',
92 | alignItems: 'center',
93 | fontFamily: 'sans',
94 | lineHeight: 'solid',
95 | fontSize: 0,
96 | color: 'neutral.3',
97 | };
98 |
99 | export const Media = styled(AspectRatioImage)`
100 | position: relative;
101 |
102 | :after {
103 | content: '';
104 | position: absolute;
105 | background-color: ${props => props.theme.colors.neutral[4]};
106 | top: 0;
107 | right: 0;
108 | bottom: 0;
109 | left: 0;
110 | z-index: 1;
111 | opacity: ${props => (props.blocky ? 1 : 0)};
112 | }
113 | `;
114 |
115 | export const Blocky = styled.span(blocky);
116 |
117 | function blocky(props) {
118 | return {
119 | backgroundColor: props.blocky
120 | ? themeGet(`colors.${props.color}`, 'black')(props)
121 | : 'transparent',
122 | ...(props.blocky && { '> *': { opacity: 0 } }),
123 | ...(props.blocky && { color: 'transparent', lineHeight: 1.4 }),
124 | };
125 | }
126 |
--------------------------------------------------------------------------------
/src/FlagObjectBuilder/theme.js:
--------------------------------------------------------------------------------
1 | import { css } from '@emotion/core';
2 |
3 | export default {
4 | breakpoints: ['40em', '52em', '64em', '72em'],
5 | colors: {
6 | pillar: {
7 | dark: '#005689',
8 | main: '#0084c6',
9 | bright: '#00b2ff',
10 | pastel: '#90dcff',
11 | faded: '#f1f8fc',
12 | },
13 | neutral: [
14 | '#121212', // 0 brightness 7
15 | '#333333', // 1 brightness 20
16 | '#767676', // 2 brightness 46
17 | '#999999', // 3 brightness 60
18 | '#dcdcdc', // 4 brightness 86
19 | '#ededed', // 5 brightness 93
20 | '#f6f6f6', // 6 brightness 97
21 | '#ffffff', // 7 brightness 100
22 | ],
23 | black: '#121212',
24 | primary: '#78f0a6',
25 | white: '#ffffff',
26 | gray: '#767676',
27 | },
28 | space: [0, 4, 8, 16, 32, 64, 128, 256, 512],
29 | sizes: [16, 32, 64, 80, 128, 192, 256, 288, 384, 512, 768, 1024, 1536],
30 | radii: [0, 8, 16, 9999, '100%'],
31 | fontSizes: [12, 14, 16, 20, 24, 36, 48, 80, 96],
32 | fontWeights: [100, 200, 300, 400, 500, 600, 700, 800, 900],
33 | fonts: {
34 | serif: '"Libre Baskerville", Georgia, serif',
35 | sans:
36 | '-apple-system, BlinkMacSystemFont, "avenir next", avenir, "helvetica neue", helvetica, ubuntu, roboto, noto, "segoe ui", arial, sans-serif',
37 | },
38 | lineHeights: {
39 | solid: 1,
40 | title: 1.25,
41 | copy: 1.5,
42 | },
43 | letterSpacings: {
44 | normal: 'normal',
45 | tracked: '0.1em',
46 | tight: '-0.05em',
47 | mega: '0.25em',
48 | },
49 | };
50 |
51 | /* */
52 | export const globalStyles = css`
53 | /* latin-ext */
54 | @font-face {
55 | font-family: 'Libre Baskerville';
56 | font-style: italic;
57 | font-weight: 400;
58 | font-display: swap;
59 | src: local('Libre Baskerville Italic'), local('LibreBaskerville-Italic'),
60 | url(https://fonts.gstatic.com/s/librebaskerville/v7/kmKhZrc3Hgbbcjq75U4uslyuy4kn0qNcWx8QDO-WyrubOA.woff2)
61 | format('woff2');
62 | unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB,
63 | U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
64 | }
65 | /* latin */
66 | @font-face {
67 | font-family: 'Libre Baskerville';
68 | font-style: italic;
69 | font-weight: 400;
70 | font-display: swap;
71 | src: local('Libre Baskerville Italic'), local('LibreBaskerville-Italic'),
72 | url(https://fonts.gstatic.com/s/librebaskerville/v7/kmKhZrc3Hgbbcjq75U4uslyuy4kn0qNcWxEQDO-Wyrs.woff2)
73 | format('woff2');
74 | unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
75 | U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212,
76 | U+2215, U+FEFF, U+FFFD;
77 | }
78 | /* latin-ext */
79 | @font-face {
80 | font-family: 'Libre Baskerville';
81 | font-style: normal;
82 | font-weight: 400;
83 | font-display: swap;
84 | src: local('Libre Baskerville'), local('LibreBaskerville-Regular'),
85 | url(https://fonts.gstatic.com/s/librebaskerville/v7/kmKnZrc3Hgbbcjq75U4uslyuy4kn0qNXaxMaC82U-ro.woff2)
86 | format('woff2');
87 | unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB,
88 | U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
89 | }
90 | /* latin */
91 | @font-face {
92 | font-family: 'Libre Baskerville';
93 | font-style: normal;
94 | font-weight: 400;
95 | font-display: swap;
96 | src: local('Libre Baskerville'), local('LibreBaskerville-Regular'),
97 | url(https://fonts.gstatic.com/s/librebaskerville/v7/kmKnZrc3Hgbbcjq75U4uslyuy4kn0qNZaxMaC82U.woff2)
98 | format('woff2');
99 | unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
100 | U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212,
101 | U+2215, U+FEFF, U+FFFD;
102 | }
103 | /* latin-ext */
104 | @font-face {
105 | font-family: 'Libre Baskerville';
106 | font-style: normal;
107 | font-weight: 700;
108 | font-display: swap;
109 | src: local('Libre Baskerville Bold'), local('LibreBaskerville-Bold'),
110 | url(https://fonts.gstatic.com/s/librebaskerville/v7/kmKiZrc3Hgbbcjq75U4uslyuy4kn0qviTgY5KcC-wLOjAUw.woff2)
111 | format('woff2');
112 | unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB,
113 | U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
114 | }
115 | /* latin */
116 | @font-face {
117 | font-family: 'Libre Baskerville';
118 | font-style: normal;
119 | font-weight: 700;
120 | font-display: swap;
121 | src: local('Libre Baskerville Bold'), local('LibreBaskerville-Bold'),
122 | url(https://fonts.gstatic.com/s/librebaskerville/v7/kmKiZrc3Hgbbcjq75U4uslyuy4kn0qviTgY3KcC-wLOj.woff2)
123 | format('woff2');
124 | unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
125 | U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212,
126 | U+2215, U+FEFF, U+FFFD;
127 | }
128 |
129 | body {
130 | background-color: #fff;
131 | cursor: auto;
132 | }
133 | `;
134 |
--------------------------------------------------------------------------------
/src/GenerativeLayout/Article.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | // import { Card } from 'rebass';
3 | import { Card } from '../primitives';
4 |
5 | export default [
6 |
7 |
8 | A Night of Taking Photos at the Spooky Ruins of the Sutro Baths
9 |
10 | ,
11 |
12 | March 19th, 2015
13 | ,
14 | ,
21 |
22 | On March 14, 1896 , the Sutro Baths were opened to the public as
23 | the world's largest indoor swimming pool establishment.
24 |
,
25 | ,
32 |
33 | Before it burned to the ground, the structure filled a small beach inlet
34 | below the Cliff House, also owned by Adolph Sutro at the time. Shortly after
35 | closing, a fire in 1966 destroyed the building while it was in the process
36 | of being demolished.
37 |
,
38 | ,
45 |
46 | During high tides, water would flow directly into the pools from the nearby
47 | ocean, recycling the two million US gallons of water in about an hour.
48 |
,
49 | ,
56 |
57 | All that remains of the site are concrete walls, blocked off stairs and
58 | passageways, and a tunnel with a deep crevice in the middle. The cause of
59 | the fire was arson. Shortly afterwards, the developer left San Francisco and
60 | claimed insurance money.
61 |
,
62 | ,
69 |
70 | During low tides, a powerful turbine water pump, built inside a cave at sea
71 | level, could be switched on from a control room and could fill the tanks at
72 | a rate of 6,000 US gallons a minute, recycling all the water in five hours.
73 |
,
74 | ];
75 |
--------------------------------------------------------------------------------
/src/GenerativeLayout/GenerativeGrid.js:
--------------------------------------------------------------------------------
1 | import React, { useContext } from 'react';
2 | import { useDebugGrid } from './useDebugGrid';
3 | import article from './Article';
4 | import { GeneratorContainer } from './GeneratorContainer';
5 |
6 | const GridItem = ({ idx, x, y, Component, debug, pinned }) => {
7 | const { togglePin } = useContext(GeneratorContainer.Context);
8 |
9 | return (
10 | togglePin(idx)}
19 | >
20 | {article[idx]}
21 |
22 | );
23 | };
24 |
25 | export const GenerativeGrid = ({ row, col, size, gap, style }) => {
26 | const gridStyles = {
27 | display: 'grid',
28 | gridTemplateColumns: `repeat(${col}, ${size})`,
29 | gridTemplateRows: `repeat(${row}, ${size})`,
30 | gridGap: gap,
31 | padding: gap,
32 | gridAutoRows: size,
33 | gridAutoColumns: size,
34 | gridAutoFlow: 'dense',
35 | };
36 |
37 | const DebugGrid = useDebugGrid(4, 6);
38 | const { items, debug } = useContext(GeneratorContainer.Context);
39 |
40 | return (
41 |
42 |
46 | {items.map(item => (
47 |
48 | ))}
49 |
50 |
51 |
52 | );
53 | };
54 |
--------------------------------------------------------------------------------
/src/GenerativeLayout/GeneratorContainer.js:
--------------------------------------------------------------------------------
1 | import { useState } from 'react';
2 | import createContainer from 'constate';
3 | import { range, randomSize } from './utils';
4 |
5 | const generateGridItems = count =>
6 | range(count).map(idx => ({
7 | key: idx,
8 | ...randomSize(4, 6),
9 | }));
10 |
11 | const reGenerateGridItems = items =>
12 | items.map(item =>
13 | item.pinned
14 | ? item
15 | : {
16 | ...item,
17 | ...randomSize(4, 6),
18 | },
19 | );
20 |
21 | function useGeneratorState({ initCount = 4 }) {
22 | const [items, setItems] = useState(generateGridItems(initCount));
23 | const [debug, setDebugGrid] = useState(true);
24 |
25 | return {
26 | items: items,
27 | debug,
28 | togglePin: key => {
29 | const gridItems = items.map(item =>
30 | item.key === key ? { ...item, pinned: !item.pinned } : item,
31 | );
32 | setItems(gridItems);
33 | },
34 | toggleDebugGrid: () => {
35 | setDebugGrid(!debug);
36 | },
37 | onChange: e => {
38 | const count = Number(e.target.value);
39 | const gridItems = generateGridItems(count);
40 | setItems(gridItems);
41 | },
42 | randomGrid: () => {
43 | const gridItems = reGenerateGridItems(items);
44 | setItems(gridItems);
45 | },
46 | };
47 | }
48 |
49 | export const GeneratorContainer = createContainer(useGeneratorState);
50 |
--------------------------------------------------------------------------------
/src/GenerativeLayout/GeneratorControls.js:
--------------------------------------------------------------------------------
1 | import React, { useContext } from 'react';
2 | import { GeneratorContainer } from './GeneratorContainer';
3 |
4 | export const GeneratorControls = () => {
5 | const { items, onChange, randomGrid, toggleDebugGrid } = useContext(
6 | GeneratorContainer.Context,
7 | );
8 |
9 | return (
10 |
47 | );
48 | };
49 |
--------------------------------------------------------------------------------
/src/GenerativeLayout/generative-layout.css:
--------------------------------------------------------------------------------
1 | .input-range {
2 | vertical-align: middle;
3 | background-color: transparent;
4 | padding-top: 8px;
5 | padding-top: 0.5rem;
6 | padding-bottom: 8px;
7 | padding-bottom: 0.5rem;
8 | color: inherit;
9 | background-color: transparent;
10 | -webkit-appearance: none;
11 | }
12 |
13 | .input-range::-webkit-slider-thumb {
14 | position: relative;
15 | width: 8px;
16 | width: 0.5rem;
17 | height: 20px;
18 | height: 1.25rem;
19 | cursor: pointer;
20 | margin-top: -8px;
21 | margin-top: -0.5rem;
22 | border-radius: 3px;
23 | background-color: currentcolor;
24 | -webkit-appearance: none;
25 | }
26 |
27 | /* Touch screen friendly pseudo element */
28 | .input-range::-webkit-slider-thumb:before {
29 | content: '';
30 | display: block;
31 | position: absolute;
32 | top: -8px;
33 | top: -0.5rem;
34 | left: -14px;
35 | left: -0.875rem;
36 | width: 36px;
37 | width: 2.25rem;
38 | height: 36px;
39 | height: 2.25rem;
40 | opacity: 0;
41 | }
42 |
43 | .input-range::-moz-range-thumb {
44 | width: 8px;
45 | width: 0.5rem;
46 | height: 20px;
47 | height: 1.25rem;
48 | cursor: pointer;
49 | border-radius: 3px;
50 | border-color: transparent;
51 | border-width: 0;
52 | background-color: currentcolor;
53 | }
54 |
55 | .input-range::-webkit-slider-runnable-track {
56 | height: 4px;
57 | height: 0.25rem;
58 | cursor: pointer;
59 | border-radius: 3px;
60 | background-color: rgba(0, 0, 0, 0.25);
61 | }
62 |
63 | .input-range::-moz-range-track {
64 | height: 4px;
65 | height: 0.25rem;
66 | cursor: pointer;
67 | border-radius: 3px;
68 | background-color: rgba(0, 0, 0, 0.25);
69 | }
70 |
71 | .input-range:focus {
72 | outline: none;
73 | }
74 |
--------------------------------------------------------------------------------
/src/GenerativeLayout/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import 'tachyons';
3 | import './generative-layout.css';
4 | import { Global } from '@emotion/core';
5 |
6 | import { GenerativeGrid } from './GenerativeGrid';
7 | import { GeneratorControls } from './GeneratorControls';
8 | import { GeneratorContainer } from './GeneratorContainer';
9 |
10 | export const GenerativeLayout = () => {
11 | return (
12 |
13 |
21 |
22 |
23 |
30 |
31 |
32 | );
33 | };
34 |
--------------------------------------------------------------------------------
/src/GenerativeLayout/useDebugGrid.js:
--------------------------------------------------------------------------------
1 | import React, { useState, useRef, useEffect } from 'react';
2 | import { range } from './utils';
3 |
4 | export function useDebugGrid(initialRowCount, initialColumnCount) {
5 | const gridEl = useRef(null);
6 |
7 | const [rowCount, setRowCount] = useState(initialRowCount);
8 | const [columnCount, setColumnCount] = useState(initialColumnCount);
9 |
10 | useEffect(() => {
11 | const size = window.innerWidth * 0.15;
12 | const height = gridEl.current.getBoundingClientRect().height;
13 | const width = gridEl.current.getBoundingClientRect().width;
14 | setRowCount(Math.floor(height / size));
15 | setColumnCount(Math.floor(width / size));
16 | }, []);
17 |
18 | const itemCount = rowCount * columnCount;
19 |
20 | const DebugGrid = ({ definition, visible }) => (
21 |
26 | {range(itemCount).map(idx => (
27 |
28 | ))}
29 |
30 | );
31 |
32 | return DebugGrid;
33 | }
34 |
--------------------------------------------------------------------------------
/src/GenerativeLayout/utils.js:
--------------------------------------------------------------------------------
1 | export const range = n => Array.from(Array(n).keys());
2 |
3 | export const randomRange = (min, max) => {
4 | if (max === undefined) {
5 | max = min;
6 | min = 0;
7 | }
8 |
9 | const value = Math.random() * (max - min) + min;
10 | return Math.floor(value);
11 | };
12 |
13 | export const randomSize = (w, h) => ({
14 | x: randomRange(1, w),
15 | y: randomRange(1, h),
16 | pinned: false,
17 | align: 'center',
18 | });
19 |
20 | export const pickRandom = items =>
21 | items[Math.floor(Math.random() * items.length)];
22 |
--------------------------------------------------------------------------------
/src/GenerativePalette/DynamicCanvas.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { withTheme } from 'emotion-theming';
3 | import {
4 | TypographySwatch,
5 | ColorSwatch,
6 | NavBar,
7 | MediaCard,
8 | SearchBar,
9 | ProfileCard,
10 | } from '../components';
11 | import { ComponentGrid } from '../ComponentGrid';
12 | import { Pager } from '../Pager';
13 |
14 | function DynamicCanvas({
15 | profile,
16 | media,
17 | activeIndex,
18 | selectTheme,
19 | children,
20 | theme,
21 | ...props
22 | }) {
23 | console.log(props);
24 |
25 | return (
26 |
27 | {children}
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
47 |
53 |
54 |
55 |
56 |
57 |
58 | );
59 | }
60 |
61 | export default withTheme(DynamicCanvas);
62 |
--------------------------------------------------------------------------------
/src/GenerativePalette/GenerativePalette.js:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import { ThemeProvider } from 'emotion-theming';
3 | import theme from '../theme';
4 | import { Container } from '../primitives';
5 | import { useThemes } from '../useThemes';
6 | import DynamicCanvas from './DynamicCanvas';
7 | import { Sliders } from './Sliders';
8 | import { useGenerativePalette } from './useGenerativePalette';
9 |
10 | export function GenerativePalette() {
11 | const [data, activeIndex, selectTheme] = useThemes();
12 | const [
13 | palette,
14 | hue,
15 | saturation,
16 | adjustHue,
17 | adjustSaturation,
18 | ] = useGenerativePalette();
19 | const [roundness, radii, adjustRoundness] = useRadii();
20 | const [spaciousness, space, adjustSpaciousness] = useSpace();
21 |
22 | return (
23 |
24 |
25 |
42 |
50 |
58 |
59 |
60 |
61 | );
62 | }
63 |
64 | function useRadii() {
65 | const [roundness, setRoundness] = useState(1);
66 | const [radii, setRadii] = useState([0, 8, 16, 9999, '100%']);
67 | const adjustRoundness = roundness => {
68 | setRoundness(roundness);
69 | setRadii([...[0, 8, 16].map(x => x * roundness), 9999, '100%']);
70 | };
71 |
72 | return [roundness, radii, adjustRoundness];
73 | }
74 |
75 | function useSpace() {
76 | const [spaciousness, setSpaciousness] = useState(1);
77 | const [space, setSpace] = useState([0, 4, 8, 16, 32, 64, 128, 256, 512]);
78 | const adjustSpaciousness = spaciousness => {
79 | setSpaciousness(spaciousness);
80 | setSpace([0, 4, 8, 16, 32, 64, 128, 256, 512].map(x => x * spaciousness));
81 | };
82 |
83 | return [spaciousness, space, adjustSpaciousness];
84 | }
85 |
--------------------------------------------------------------------------------
/src/GenerativePalette/Sliders.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { InputRange, Label, Card, Box } from '../primitives';
3 |
4 | export const Sliders = ({
5 | hue,
6 | adjustHue,
7 | saturation,
8 | adjustSaturation,
9 | roundness,
10 | adjustRoundness,
11 | spaciousness,
12 | adjustSpaciousness,
13 | ...props
14 | }) => (
15 |
24 |
25 | Hue
26 |
35 |
36 |
37 |
38 | Saturation
39 |
48 |
49 |
50 |
51 | Roundness
52 |
61 |
62 |
63 |
64 | Spaciousness
65 |
74 |
75 |
76 | );
77 |
--------------------------------------------------------------------------------
/src/GenerativePalette/useGenerativePalette.js:
--------------------------------------------------------------------------------
1 | import { useState } from 'react';
2 | import hsluv from 'hsluv';
3 | import Random from 'canvas-sketch-util/random';
4 | import { lerp } from 'canvas-sketch-util/math';
5 |
6 | function colourPalette(hue, saturation) {
7 | const black = hsluv.hsluvToHex([
8 | 0,
9 | Random.rangeFloor(0, 25),
10 | Random.rangeFloor(0, 10),
11 | ]);
12 |
13 | const gray = hsluv.hsluvToHex([0, 0, Random.rangeFloor(40, 60)]);
14 |
15 | const h = i => lerp(hue - 40, hue, i);
16 | const s = i => lerp(saturation - 4, saturation, i);
17 | const l = i => lerp(96, 56, i);
18 |
19 | const tertiary = hsluv.hsluvToHex([h(1 / 2), s(1 / 2), l(1 / 2)]);
20 | const secondary = hsluv.hsluvToHex([h(0 / 2), s(0 / 2), l(0 / 2)]);
21 | const primary = hsluv.hsluvToHex([h(2 / 2), s(2 / 2), l(2 / 2)]);
22 | return {
23 | black,
24 | gray,
25 | primary,
26 | tertiary,
27 | secondary,
28 | };
29 | }
30 |
31 | export function useGenerativePalette() {
32 | const start = { h: Random.rangeFloor(0, 360), s: Random.rangeFloor(0, 100) };
33 | const [palette, setPalette] = useState(colourPalette(start.h, start.s));
34 | const [hue, setHue] = useState(start.h);
35 | const [saturation, setSaturation] = useState(start.s);
36 |
37 | const adjustHue = value => {
38 | setHue(value);
39 | setPalette(colourPalette(hue, saturation));
40 | };
41 |
42 | const adjustSaturation = value => {
43 | setSaturation(value);
44 | setPalette(colourPalette(hue, saturation));
45 | };
46 |
47 | return [palette, hue, saturation, adjustHue, adjustSaturation];
48 | }
49 |
--------------------------------------------------------------------------------
/src/Info.js:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import styled from '@emotion/styled';
3 | import { Dialog } from '@reach/dialog';
4 | import '@reach/dialog/styles.css';
5 | import {
6 | IconButton,
7 | Info,
8 | Close,
9 | Heading,
10 | Flex,
11 | Text,
12 | Link,
13 | } from './primitives';
14 |
15 | const ShowInfoButton = styled(IconButton)`
16 | position: absolute;
17 | right: 0;
18 | top: 0;
19 | padding: ${props => props.theme.space[3]}px;
20 | `;
21 |
22 | const StyledDialog = styled(Dialog)`
23 | box-sizing: border-box;
24 | padding: ${props => props.theme.space[4]}px;
25 | border-radius: ${props => props.theme.radii[2]}px;
26 | max-width: 40em;
27 | width: 100%;
28 | `;
29 |
30 | export const InfoButton = () => {
31 | const [showInfo, setVisibility] = useState(false);
32 | return (
33 |
34 | }
38 | onClick={() => setVisibility(true)}
39 | />
40 |
41 | setVisibility(false)}>
42 |
43 | Generative Lighting
44 | }
48 | onClick={() => setVisibility(false)}
49 | />
50 |
51 |
52 |
53 | This experiment uses a basic ray casting engine to dynamically
54 | determine the colours of interface. The engine's light source emits
55 | vector rays . The interface provides surfaces for the
56 | light to interact with. The number of rays hitting a particular UI
57 | component determines how much light it is receiving and therefore its
58 | colour.
59 |
60 |
61 |
62 | For more on ray casting check out Daniel Shiffman's fantastic
63 | tutorial:{' '}
64 |
65 | Coding Challenge #145: 2D Raycasting
66 |
67 | .
68 |
69 |
70 |
71 | The design for all the UI components are based on{' '}
72 | usedetails.com.
73 |
74 |
75 |
76 | );
77 | };
78 |
--------------------------------------------------------------------------------
/src/Pager.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import styled from '@emotion/styled';
3 | import { Box, Flex, IconButton } from './primitives';
4 | import { position } from 'styled-system';
5 |
6 | const Circle = ({ isActive, ...props }) => (
7 |
18 |
19 |
20 | );
21 |
22 | const PagerItem = styled.li`
23 | list-style: none;
24 | display: inline-block;
25 | line-height: 1;
26 | `;
27 |
28 | const PagerNav = styled(Box)({}, position);
29 |
30 | PagerNav.defaultProps = {
31 | position: ['relative', 'absolute'],
32 | bottom: [0, '24px', '32px'],
33 | mt: [3, 0],
34 | mb: [3, 0],
35 | };
36 |
37 | export const Pager = ({ active, onUpdate, color = '#fff', ...props }) => (
38 |
39 |
40 |
41 | }
45 | onClick={() => onUpdate(0)}
46 | />
47 |
48 |
49 | }
53 | onClick={() => onUpdate(1)}
54 | />
55 |
56 |
57 | }
61 | onClick={() => onUpdate(2)}
62 | />
63 |
64 |
65 |
66 | );
67 |
--------------------------------------------------------------------------------
/src/RadioGroup.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import styled from '@emotion/styled';
3 | import { Box, Flex, Text, Label } from './primitives';
4 |
5 | const RadioGroupFieldset = styled(Flex)({
6 | padding: 0,
7 | border: 0,
8 | });
9 | RadioGroupFieldset.defaultProps = {
10 | // as: 'fieldset', doesn't support flex
11 | role: 'group',
12 | flexDirection: 'column',
13 | mt: 0,
14 | mb: 0,
15 | mr: 0,
16 | ml: 0,
17 | };
18 |
19 | const RadioGroupContainer = styled(Flex)({
20 | marginLeft: 0,
21 | ':hover, :focus-within': {
22 | borderColor: '#e5e5e5',
23 | },
24 | });
25 | RadioGroupContainer.defaultProps = {
26 | borderWidth: 2,
27 | borderStyle: 'solid',
28 | borderColor: 'white',
29 | borderRadius: '3px',
30 | };
31 |
32 | const RadioGroupTitle = styled.legend`
33 | position: fixed !important;
34 | _position: absolute !important;
35 | clip: rect(1px 1px 1px 1px); /* IE6, IE7 */
36 | clip: rect(1px, 1px, 1px, 1px);
37 | `;
38 |
39 | const RadioInput = styled.input`
40 | opacity: 0;
41 | position: absolute;
42 |
43 | &:focus + label {
44 | border-radius: 0;
45 | }
46 |
47 | &:checked + label {
48 | background-color: #e5e5e5;
49 | }
50 | `;
51 |
52 | const RadioGroupLabel = styled(Text)({
53 | display: 'flex',
54 | alignItems: 'center',
55 | justifyContent: 'center',
56 | borderRadius: '3px',
57 | });
58 |
59 | RadioGroupLabel.defaultProps = {
60 | as: 'label',
61 | backgroundColor: '#fff',
62 | lineHeight: 'solid',
63 | color: 'black',
64 | fontWeight: 5,
65 | fontSize: 2,
66 | };
67 |
68 | export const RadioGroup = ({
69 | onChange,
70 | selected,
71 | title,
72 | hideTitle = false,
73 | items,
74 | direction = 'column',
75 | size = 2,
76 | ...props
77 | }) => (
78 |
79 | {hideTitle ? (
80 | {title}
81 | ) : (
82 |
83 | {title}
84 |
85 | )}
86 |
91 | {items.map(item => (
92 |
93 | onChange(item.value)}
98 | checked={selected === item.value}
99 | />
100 |
107 | {item.label}
108 |
109 |
110 | ))}
111 |
112 |
113 | );
114 |
--------------------------------------------------------------------------------
/src/Responsive/Artboard.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { TypographySwatch, MediaCard, ProfileCard } from './components';
3 |
4 | import { Box } from '../primitives';
5 | import { Dimensions } from './RedLines';
6 |
7 | export function Artboard({
8 | profile,
9 | media,
10 | activeIndex,
11 | selectTheme,
12 | children,
13 | theme,
14 | debug,
15 | ...props
16 | }) {
17 | return (
18 |
19 | (
22 |
23 |
24 |
25 | )}
26 | />
27 |
28 | (
31 |
34 | )}
35 | />
36 |
37 | (
40 |
41 |
42 |
43 | )}
44 | />
45 |
46 | );
47 | }
48 |
--------------------------------------------------------------------------------
/src/Responsive/RedLines.js:
--------------------------------------------------------------------------------
1 | import React, { useRef } from 'react';
2 | import useComponentSize from '@rehooks/component-size';
3 | import styled from '@emotion/styled';
4 | import { system, border, typography } from 'styled-system';
5 | import { Resizable } from 're-resizable';
6 | import { Box, Text, Flex } from '../primitives';
7 |
8 | const spacer = system({
9 | vertical: {
10 | property: 'height',
11 | scale: 'space',
12 | },
13 | horizontal: {
14 | property: 'width',
15 | scale: 'space',
16 | },
17 | });
18 |
19 | export const SpacingY = styled(Flex)(
20 | props => ({
21 | flex: 'none',
22 | backgroundColor:
23 | props.type === 'margin'
24 | ? 'rgba(245, 88, 88, .16)'
25 | : 'rgba(0, 235, 172, .24)',
26 | color: props.type === 'margin' ? '#B80000' : '#008F5F',
27 | textAlign: 'center',
28 | opacity: !props.visible || props.vertical === 0 ? 0 : 0.75,
29 | display: props.vertical === 0 ? 'none' : 'flex',
30 | }),
31 | spacer,
32 | typography,
33 | );
34 |
35 | SpacingY.defaultProps = {
36 | type: 'margin',
37 | alignItems: 'center',
38 | justifyContent: 'center',
39 | width: 3,
40 | vertical: 4,
41 | ml: 'auto',
42 | mr: 'auto',
43 | fontSize: '0',
44 | fontWeight: 4,
45 | };
46 |
47 | export const SpacingX = styled(Flex)(
48 | props => ({
49 | flex: 'none',
50 | backgroundColor:
51 | props.type === 'margin'
52 | ? 'rgba(245, 88, 88, .16)'
53 | : 'rgba(0, 235, 172, .24)',
54 | color: props.type === 'margin' ? '#B80000' : '#008F5F',
55 | textAlign: 'center',
56 | opacity: !props.visible || props.horizontal === 0 ? 0 : 0.75,
57 | display: props.horizontal === 0 ? 'none' : 'flex',
58 | }),
59 | spacer,
60 | typography,
61 | );
62 |
63 | SpacingX.defaultProps = {
64 | type: 'margin',
65 | alignItems: 'center',
66 | justifyContent: 'center',
67 | height: 3,
68 | horizontal: 4,
69 | mt: 'auto',
70 | mb: 'auto',
71 | fontSize: '0',
72 | fontWeight: 4,
73 | };
74 |
75 | const DimensionContainer = styled(Flex)(border);
76 | const DimensionValue = styled(Text)(border);
77 |
78 | export const Dimensions = ({ render, ...props }) => {
79 | const ref = useRef(null);
80 | const size = useComponentSize(ref);
81 |
82 | return (
83 |
84 |
106 |
113 |
114 |
124 | {size.width}
125 |
126 |
127 |
128 | {render(ref)}
129 |
130 |
131 | );
132 | };
133 |
--------------------------------------------------------------------------------
/src/Responsive/Responsive.js:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import { ThemeProvider } from 'emotion-theming';
3 | import { Global } from '@emotion/core';
4 | import theme from '../theme';
5 | import { useThemes } from '../useThemes';
6 | import { Artboard } from './Artboard';
7 | import { Flex } from '../primitives';
8 | import { RadioGroup } from '../RadioGroup';
9 | import { Toggle } from '../Toggle';
10 |
11 | export function Responsive() {
12 | const [data, activeIndex, selectTheme] = useThemes();
13 | const [debug, setDebug] = useState(false);
14 | console.log(data.theme, activeIndex);
15 |
16 | return (
17 |
18 |
26 |
27 |
28 |
39 | setDebug(!debug)}
43 | />
44 |
45 |
55 |
56 |
57 | );
58 | }
59 |
--------------------------------------------------------------------------------
/src/Responsive/components.js:
--------------------------------------------------------------------------------
1 | import React, { useRef } from 'react';
2 | import useComponentSize from '@rehooks/component-size';
3 | import theme from '../theme';
4 | import {
5 | Card,
6 | Text,
7 | Box,
8 | IconButton,
9 | Heading,
10 | BackgroundImage,
11 | Flex,
12 | Input,
13 | Image,
14 | PrimaryButton,
15 | } from '../primitives';
16 | import { ReactComponent as Menu } from '../icons/menu.svg';
17 | import { ReactComponent as Search } from '../icons/search.svg';
18 | import { SpacingY, SpacingX } from './RedLines';
19 |
20 | function useResponsiveSystem(bps = [640, 832, 1024]) {
21 | const ref = useRef(null);
22 | const { width } = useComponentSize(ref);
23 |
24 | bps.push(width);
25 | bps.sort((a, b) => a - b);
26 | const cq = bps.indexOf(width);
27 |
28 | const rsx = styles =>
29 | styles[cq] !== undefined ? styles[cq] : styles[styles.length - 1];
30 |
31 | return [ref, rsx];
32 | }
33 |
34 | export const TypographySwatch = props => {
35 | const [ref, rsx] = useResponsiveSystem();
36 |
37 | return (
38 |
46 |
47 | Aa
48 |
49 |
50 | );
51 | };
52 |
53 | export const MediaCard = ({ image, title, body, debug, ...props }) => {
54 | const [ref, rsx] = useResponsiveSystem([480, 640, 1024]);
55 |
56 | return (
57 |
64 |
65 |
69 |
70 | {theme.space[rsx([3, 3, 4])]}
71 |
72 |
79 |
80 | {theme.space[rsx([0, 4, 0])]}
81 |
82 |
83 | {title}
84 |
85 |
86 | {theme.space[2]}
87 |
88 |
89 | {body}
90 |
91 |
92 | {theme.space[rsx([0, 4, 0])]}
93 |
94 |
95 |
96 | {theme.space[rsx([3, 3, 4])]}
97 |
98 |
99 |
100 | );
101 | };
102 |
103 | export const ProfileCard = ({ avatar, name, title, debug, ...props }) => {
104 | const [ref, rsx] = useResponsiveSystem([480, 640, 1024]);
105 |
106 | return (
107 |
116 |
123 |
124 | {theme.space[rsx([0, 4])]}
125 |
126 |
127 | {theme.space[rsx([3, 0])]}
128 |
129 |
130 |
131 | {theme.space[rsx([3, 0])]}
132 |
133 |
138 |
144 | {name}
145 |
146 |
152 | {theme.space[rsx([1, 2])]}
153 |
154 |
160 | {title}
161 |
162 |
168 | {theme.space[rsx([4, 3])]}
169 |
170 |
176 | Follow
177 |
178 |
179 |
180 | {theme.space[rsx([3, 0])]}
181 |
182 |
183 |
184 | );
185 | };
186 |
187 | export const ColorSwatch = ({ name, ...props }) => (
188 |
189 |
190 |
198 | {name}
199 |
200 |
201 | );
202 |
203 | export const NavBar = ({ name, ...props }) => (
204 |
205 | } />
206 |
212 | Details
213 |
214 | } />
215 |
216 | );
217 |
218 | export const SearchBar = ({ name, ...props }) => (
219 |
228 |
229 |
230 |
231 | );
232 |
--------------------------------------------------------------------------------
/src/Toggle.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import styled from '@emotion/styled';
3 | import { color, space } from 'styled-system';
4 | import posed from 'react-pose';
5 |
6 | const ToggleIndicator = posed.circle({
7 | off: {
8 | cx: 8,
9 | },
10 | on: {
11 | cx: 16,
12 | },
13 | });
14 |
15 | const ToggleButton = styled.svg`
16 | ${space}
17 | ${color}
18 | `;
19 |
20 | ToggleButton.defaultProps = {
21 | color: 'white',
22 | p: 3,
23 | };
24 |
25 | export const Toggle = ({ enabled, onClick, ...props }) => (
26 | {
38 | if (e.key === ' ') {
39 | onClick();
40 | }
41 | }}
42 | role="button"
43 | tabIndex="0"
44 | aria-pressed={enabled}
45 | {...props}
46 | >
47 |
48 |
55 |
56 | );
57 |
--------------------------------------------------------------------------------
/src/components.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {
3 | Card,
4 | Text,
5 | Box,
6 | IconButton,
7 | Heading,
8 | BackgroundImage,
9 | Flex,
10 | Input,
11 | Image,
12 | PrimaryButton,
13 | } from './primitives';
14 | import { ReactComponent as Menu } from './icons/menu.svg';
15 | import { ReactComponent as Search } from './icons/search.svg';
16 |
17 | export const TypographySwatch = React.forwardRef((props, ref) => (
18 |
26 |
27 | Aa
28 |
29 |
30 | ));
31 |
32 | export const ColorSwatch = React.forwardRef(({ name, ...props }, ref) => (
33 |
34 |
35 |
43 | {name}
44 |
45 |
46 | ));
47 |
48 | export const NavBar = React.forwardRef(({ name, ...props }, ref) => (
49 |
58 | } />
59 |
65 | Details
66 |
67 | } />
68 |
69 | ));
70 |
71 | export const MediaCard = React.forwardRef(
72 | ({ image, title, body, ...props }, ref) => (
73 |
74 |
75 |
76 |
77 | {title}
78 |
79 |
80 | {body}
81 |
82 |
83 |
84 | ),
85 | );
86 |
87 | export const SearchBar = React.forwardRef(({ name, ...props }, ref) => (
88 |
98 |
99 |
100 |
101 | ));
102 |
103 | export const ProfileCard = React.forwardRef(
104 | ({ avatar, name, title, ...props }, ref) => (
105 |
115 |
122 |
129 |
135 | {name}
136 |
137 |
143 | {title}
144 |
145 |
146 | Follow
147 |
148 |
149 |
150 | ),
151 | );
152 |
--------------------------------------------------------------------------------
/src/icons/arrow-down.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/icons/arrow-left.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/icons/arrow-right.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/icons/arrow-up.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/icons/flag.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/icons/info.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/icons/layout.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/icons/maximize-2.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/icons/menu.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/icons/search.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/icons/sliders.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/icons/sun.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/icons/x-circle.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/imgs/ayla.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/winkerVSbecks/generative-parts/a596f912c66c5f4b0cd02bddab0622a7e18df124/src/imgs/ayla.jpg
--------------------------------------------------------------------------------
/src/imgs/ernest-porzi-19106-unsplash.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/winkerVSbecks/generative-parts/a596f912c66c5f4b0cd02bddab0622a7e18df124/src/imgs/ernest-porzi-19106-unsplash.jpg
--------------------------------------------------------------------------------
/src/imgs/fabian-moller-401625-unsplash.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/winkerVSbecks/generative-parts/a596f912c66c5f4b0cd02bddab0622a7e18df124/src/imgs/fabian-moller-401625-unsplash.jpg
--------------------------------------------------------------------------------
/src/imgs/light.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/winkerVSbecks/generative-parts/a596f912c66c5f4b0cd02bddab0622a7e18df124/src/imgs/light.png
--------------------------------------------------------------------------------
/src/imgs/light.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/imgs/paridhi.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/winkerVSbecks/generative-parts/a596f912c66c5f4b0cd02bddab0622a7e18df124/src/imgs/paridhi.png
--------------------------------------------------------------------------------
/src/imgs/sandra-ollier-663002-unsplash.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/winkerVSbecks/generative-parts/a596f912c66c5f4b0cd02bddab0622a7e18df124/src/imgs/sandra-ollier-663002-unsplash.jpg
--------------------------------------------------------------------------------
/src/imgs/zainab.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/winkerVSbecks/generative-parts/a596f912c66c5f4b0cd02bddab0622a7e18df124/src/imgs/zainab.png
--------------------------------------------------------------------------------
/src/index.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen",
4 | "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue",
5 | sans-serif;
6 | -webkit-font-smoothing: antialiased;
7 | -moz-osx-font-smoothing: grayscale;
8 | }
9 |
10 | code {
11 | font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New",
12 | monospace;
13 | }
14 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import './index.css';
4 | import App from './App';
5 | import * as serviceWorker from './serviceWorker';
6 |
7 | loadPolyfills().then(() => {
8 | ReactDOM.render( , document.getElementById('root'));
9 | });
10 |
11 | /**
12 | * Do feature detection, to figure out which polyfills needs to be imported.
13 | **/
14 | function loadPolyfills() {
15 | const polyfills = [];
16 |
17 | if (!supportsResizeObserver()) {
18 | polyfills.push(import('resize-observer-polyfill'));
19 | }
20 |
21 | return Promise.all(polyfills);
22 | }
23 |
24 | function supportsResizeObserver() {
25 | return (
26 | 'ResizeObserver' in global &&
27 | 'ResizeObserverEntry' in global &&
28 | 'contentRect' in ResizeObserverEntry.prototype
29 | );
30 | }
31 |
32 | // If you want your app to work offline and load faster, you can change
33 | // unregister() to register() below. Note this comes with some pitfalls.
34 | // Learn more about service workers: https://bit.ly/CRA-PWA
35 | serviceWorker.unregister();
36 |
--------------------------------------------------------------------------------
/src/primitives.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import styled from '@emotion/styled';
3 | import {
4 | space,
5 | color,
6 | layout,
7 | flexbox,
8 | typography,
9 | border,
10 | shadow,
11 | background,
12 | buttonStyle,
13 | grid,
14 | } from 'styled-system';
15 | import { withTheme } from 'emotion-theming';
16 | import 'rc-slider/assets/index.css';
17 | import Slider from 'rc-slider';
18 | import { ReactComponent as MenuIcon } from './icons/menu.svg';
19 | import { ReactComponent as SearchIcon } from './icons/search.svg';
20 | import { ReactComponent as InfoIcon } from './icons/info.svg';
21 | import { ReactComponent as CloseIcon } from './icons/x-circle.svg';
22 |
23 | const StyledSlider = styled(Slider)`
24 | .rc-slider-handle:active {
25 | border: 0;
26 | box-shadow: none;
27 | }
28 |
29 | .rc-slider-handle:focus {
30 | border: 0;
31 | box-shadow: none;
32 | }
33 | `;
34 |
35 | export const InputRange = withTheme(({ theme, ...props }) => (
36 |
47 | ));
48 |
49 | export const Box = styled.div(
50 | {
51 | boxSizing: 'border-box',
52 | minWidth: 0,
53 | },
54 | space,
55 | color,
56 | layout,
57 | flexbox,
58 | border,
59 | );
60 |
61 | export const Flex = styled(Box)({
62 | display: 'flex',
63 | });
64 |
65 | export const Text = styled(Box)(typography);
66 |
67 | Text.defaultProps = {
68 | as: 'p',
69 | mb: 0,
70 | mt: 0,
71 | fontFamily: 'inter',
72 | lineHeight: 'copy',
73 | color: 'gray',
74 | };
75 |
76 | export const Heading = styled(Text)();
77 |
78 | Heading.defaultProps = {
79 | as: 'h2',
80 | mb: 0,
81 | mt: 0,
82 | fontSize: 4,
83 | fontWeight: 'bold',
84 | fontFamily: 'inter',
85 | lineHeight: 'title',
86 | color: 'black',
87 | };
88 |
89 | export const Label = styled(Text)({
90 | display: 'block',
91 | });
92 |
93 | Label.defaultProps = {
94 | as: 'label',
95 | mb: 2,
96 | mt: 0,
97 | lineHeight: 'solid',
98 | color: 'currentColor',
99 | fontWeight: 5,
100 | fontSize: 1,
101 | };
102 |
103 | export const Image = styled(Box)(
104 | {
105 | maxWidth: '100%',
106 | },
107 | border,
108 | );
109 |
110 | Image.defaultProps = {
111 | as: 'img',
112 | m: 0,
113 | height: 'auto',
114 | };
115 |
116 | export const BackgroundImage = styled.div(
117 | {
118 | backgroundPosition: 'center',
119 | backgroundSize: 'cover',
120 | backgroundRepeat: 'no-repeat',
121 | },
122 | props => ({
123 | backgroundColor: props.theme.colors.gray,
124 | backgroundImage: props.image ? `url(${props.image})` : null,
125 | }),
126 | layout,
127 | flexbox,
128 | );
129 |
130 | export const AspectRatioImage = styled.div(
131 | {
132 | backgroundPosition: 'center',
133 | backgroundSize: 'cover',
134 | backgroundRepeat: 'no-repeat',
135 | height: 0,
136 | },
137 | props => ({
138 | backgroundColor: props.theme.colors.gray,
139 | backgroundImage: props.image ? `url(${props.image})` : null,
140 | paddingBottom: `${(1 / props.aspectRatio) * 100}%`,
141 | }),
142 | space,
143 | layout,
144 | flexbox,
145 | );
146 | AspectRatioImage.defaultProps = {
147 | aspectRatio: 1,
148 | };
149 |
150 | export const Card = styled(Box)(
151 | { display: 'flex', overflow: 'hidden' },
152 | border,
153 | shadow,
154 | background,
155 | grid,
156 | );
157 |
158 | Card.defaultProps = {
159 | borderRadius: 1,
160 | };
161 |
162 | export const Button = styled(Box)(
163 | {
164 | appearance: 'none',
165 | display: 'inline-block',
166 | textAlign: 'center',
167 | lineHeight: 'inherit',
168 | textDecoration: 'none',
169 | },
170 | typography,
171 | border,
172 | buttonStyle,
173 | );
174 |
175 | Button.defaultProps = {
176 | as: 'button',
177 | border: 0,
178 | };
179 |
180 | export const PrimaryButton = styled(Button)({
181 | display: 'block',
182 | backfaceVisibility: 'hidden',
183 | transform: 'translateZ(0)',
184 | transition: 'transform .25s ease-out',
185 | outline: 0,
186 | ':hover,:focus': {
187 | transform: 'scale( 1.05 )',
188 | },
189 | ':active': {
190 | transform: 'scale( .90 )',
191 | },
192 | });
193 |
194 | PrimaryButton.defaultProps = {
195 | as: 'button',
196 | border: 0,
197 | backgroundColor: 'secondary',
198 | borderRadius: 1,
199 | color: 'primary',
200 | fontSize: 1,
201 | fontWeight: 6,
202 | };
203 |
204 | export const Link = styled(Text)(props => ({
205 | textDecoration: 'none',
206 |
207 | ':hover, :focus': {
208 | color: props.theme.colors.primary,
209 | },
210 | }));
211 |
212 | Link.defaultProps = {
213 | as: 'a',
214 | color: 'white',
215 | };
216 |
217 | export const Container = styled(Flex)({ overflow: 'hidden' });
218 |
219 | Container.defaultProps = {
220 | alignItems: 'center',
221 | mx: 'auto',
222 | height: '100vh',
223 | maxWidth: 1184,
224 | position: 'relative',
225 | };
226 |
227 | export const TransparentButton = styled(Button)`
228 | background-color: transparent;
229 | &:hover {
230 | background-color: transparent;
231 | }
232 | & > div {
233 | display: flex;
234 | }
235 | `;
236 |
237 | export const Icon = styled.svg(space, color, layout, flexbox);
238 | Icon.defaultProps = { width: 1, height: 1 };
239 | export const Menu = styled(MenuIcon)({}, color, space);
240 | export const Search = styled(SearchIcon)({}, color, space);
241 | export const Info = styled(InfoIcon)({}, color, space);
242 | export const Close = styled(CloseIcon)({}, color, space);
243 |
244 | export const IconButton = ({ name, icon, ...props }) => (
245 |
246 | {icon}
247 |
248 | );
249 |
250 | export const Input = styled.input(
251 | {
252 | appearance: 'none',
253 | display: 'block',
254 | width: '100%',
255 | fontFamily: 'inherit',
256 | color: 'inherit',
257 | backgroundColor: 'transparent',
258 | borderWidth: 0,
259 | borderStyle: 'solid',
260 | '::-ms-clear': {
261 | display: 'none',
262 | },
263 | },
264 | border,
265 | space,
266 | props => ({
267 | '::placeholder': {
268 | color: props.theme.gray,
269 | },
270 | }),
271 | );
272 |
273 | Input.defaultProps = {
274 | fontSize: 1,
275 | fontWeight: 400,
276 | pt: 3,
277 | pb: 3,
278 | pl: 2,
279 | pr: 2,
280 | borderRadius: 0,
281 | mt: 0,
282 | mb: 0,
283 | ml: 0,
284 | mr: 0,
285 | };
286 |
287 | export const Hidden = styled.span`
288 | border: 0 !important;
289 | clip: rect(0 0 0 0) !important;
290 | height: 0.0625rem !important;
291 | margin: -0.0625rem !important;
292 | overflow: hidden !important;
293 | padding: 0 !important;
294 | position: absolute !important;
295 | width: 0.0625rem !important;
296 | `;
297 |
--------------------------------------------------------------------------------
/src/serviceWorker.js:
--------------------------------------------------------------------------------
1 | // This optional code is used to register a service worker.
2 | // register() is not called by default.
3 |
4 | // This lets the app load faster on subsequent visits in production, and gives
5 | // it offline capabilities. However, it also means that developers (and users)
6 | // will only see deployed updates on subsequent visits to a page, after all the
7 | // existing tabs open on the page have been closed, since previously cached
8 | // resources are updated in the background.
9 |
10 | // To learn more about the benefits of this model and instructions on how to
11 | // opt-in, read https://bit.ly/CRA-PWA
12 |
13 | const isLocalhost = Boolean(
14 | window.location.hostname === 'localhost' ||
15 | // [::1] is the IPv6 localhost address.
16 | window.location.hostname === '[::1]' ||
17 | // 127.0.0.1/8 is considered localhost for IPv4.
18 | window.location.hostname.match(
19 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
20 | )
21 | );
22 |
23 | export function register(config) {
24 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
25 | // The URL constructor is available in all browsers that support SW.
26 | const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href);
27 | if (publicUrl.origin !== window.location.origin) {
28 | // Our service worker won't work if PUBLIC_URL is on a different origin
29 | // from what our page is served on. This might happen if a CDN is used to
30 | // serve assets; see https://github.com/facebook/create-react-app/issues/2374
31 | return;
32 | }
33 |
34 | window.addEventListener('load', () => {
35 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
36 |
37 | if (isLocalhost) {
38 | // This is running on localhost. Let's check if a service worker still exists or not.
39 | checkValidServiceWorker(swUrl, config);
40 |
41 | // Add some additional logging to localhost, pointing developers to the
42 | // service worker/PWA documentation.
43 | navigator.serviceWorker.ready.then(() => {
44 | console.log(
45 | 'This web app is being served cache-first by a service ' +
46 | 'worker. To learn more, visit https://bit.ly/CRA-PWA'
47 | );
48 | });
49 | } else {
50 | // Is not localhost. Just register service worker
51 | registerValidSW(swUrl, config);
52 | }
53 | });
54 | }
55 | }
56 |
57 | function registerValidSW(swUrl, config) {
58 | navigator.serviceWorker
59 | .register(swUrl)
60 | .then(registration => {
61 | registration.onupdatefound = () => {
62 | const installingWorker = registration.installing;
63 | if (installingWorker == null) {
64 | return;
65 | }
66 | installingWorker.onstatechange = () => {
67 | if (installingWorker.state === 'installed') {
68 | if (navigator.serviceWorker.controller) {
69 | // At this point, the updated precached content has been fetched,
70 | // but the previous service worker will still serve the older
71 | // content until all client tabs are closed.
72 | console.log(
73 | 'New content is available and will be used when all ' +
74 | 'tabs for this page are closed. See https://bit.ly/CRA-PWA.'
75 | );
76 |
77 | // Execute callback
78 | if (config && config.onUpdate) {
79 | config.onUpdate(registration);
80 | }
81 | } else {
82 | // At this point, everything has been precached.
83 | // It's the perfect time to display a
84 | // "Content is cached for offline use." message.
85 | console.log('Content is cached for offline use.');
86 |
87 | // Execute callback
88 | if (config && config.onSuccess) {
89 | config.onSuccess(registration);
90 | }
91 | }
92 | }
93 | };
94 | };
95 | })
96 | .catch(error => {
97 | console.error('Error during service worker registration:', error);
98 | });
99 | }
100 |
101 | function checkValidServiceWorker(swUrl, config) {
102 | // Check if the service worker can be found. If it can't reload the page.
103 | fetch(swUrl)
104 | .then(response => {
105 | // Ensure service worker exists, and that we really are getting a JS file.
106 | const contentType = response.headers.get('content-type');
107 | if (
108 | response.status === 404 ||
109 | (contentType != null && contentType.indexOf('javascript') === -1)
110 | ) {
111 | // No service worker found. Probably a different app. Reload the page.
112 | navigator.serviceWorker.ready.then(registration => {
113 | registration.unregister().then(() => {
114 | window.location.reload();
115 | });
116 | });
117 | } else {
118 | // Service worker found. Proceed as normal.
119 | registerValidSW(swUrl, config);
120 | }
121 | })
122 | .catch(() => {
123 | console.log(
124 | 'No internet connection found. App is running in offline mode.'
125 | );
126 | });
127 | }
128 |
129 | export function unregister() {
130 | if ('serviceWorker' in navigator) {
131 | navigator.serviceWorker.ready.then(registration => {
132 | registration.unregister();
133 | });
134 | }
135 | }
136 |
--------------------------------------------------------------------------------
/src/theme.js:
--------------------------------------------------------------------------------
1 | export default {
2 | breakpoints: ['40em', '52em', '64em', '72em'],
3 | colors: {
4 | black: '#020502',
5 | primary: '#78f0a6',
6 | white: '#ffffff',
7 | gray: '#898ba3',
8 | },
9 | space: [0, 4, 8, 16, 32, 64, 128, 256, 512],
10 | sizes: [16, 32, 64, 80, 128, 192, 256, 288, 384, 512, 768, 1024, 1536],
11 | radii: [0, 8, 16, 9999, '100%'],
12 | fontSizes: [12, 14, 16, 20, 24, 36, 48, 80, 96],
13 | fontWeights: [100, 200, 300, 400, 500, 600, 700, 800, 900],
14 | fonts: {
15 | inter: '"Inter", sans-serif',
16 | sans:
17 | "-apple-system, BlinkMacSystemFont, 'avenir next', avenir, 'helvetica neue', helvetica, ubuntu, roboto, noto, 'segoe ui', arial, sans-serif",
18 | },
19 | lineHeights: {
20 | solid: 1,
21 | title: 1.25,
22 | copy: 1.5,
23 | },
24 | letterSpacings: {
25 | normal: 'normal',
26 | tracked: '0.1em',
27 | tight: '-0.05em',
28 | mega: '0.25em',
29 | },
30 | };
31 |
--------------------------------------------------------------------------------
/src/useThemes.js:
--------------------------------------------------------------------------------
1 | import { useState } from 'react';
2 | import breathe from './imgs/fabian-moller-401625-unsplash.jpg';
3 | import summer from './imgs/ernest-porzi-19106-unsplash.jpg';
4 | import renaissance from './imgs/sandra-ollier-663002-unsplash.jpg';
5 | import ayla from './imgs/ayla.jpg';
6 | import paridhi from './imgs/paridhi.png';
7 | import zainab from './imgs/zainab.png';
8 |
9 | const variants = [
10 | {
11 | media: {
12 | image: breathe, // Photo by Fabian Møller on Unsplash
13 | title: 'Loyal Community',
14 | body: 'How to build a loyal community for your brand.',
15 | },
16 | profile: {
17 | avatar: paridhi,
18 | name: 'Paridhi Dutta',
19 | title: 'Community Manager',
20 | },
21 | theme: {
22 | colors: {
23 | black: '#2d2733',
24 | primary: '#d675ff',
25 | secondary: '#f9e9fe',
26 | tertiary: '#e5a8ff',
27 | white: 'var(--material-white)',
28 | materialGray: '#898ba3',
29 | materialWhite: '#fff',
30 | gray: 'var(--material-gray)',
31 | },
32 | radii: [0, 8, 16, 9999, '100%'],
33 | },
34 | },
35 | {
36 | media: {
37 | image: summer, // Photo by Ernest Porzi on Unsplash
38 | title: 'Summer Days',
39 | body: 'I am awakened by these beams of light.',
40 | },
41 | profile: {
42 | avatar: zainab,
43 | name: 'Zainab Mian',
44 | title: 'UX Designer',
45 | },
46 | theme: {
47 | colors: {
48 | black: '#272833',
49 | primary: '#6a74ff',
50 | secondary: '#e7e9ff',
51 | tertiary: '#98a2fe',
52 | white: 'var(--material-white)',
53 | materialGray: '#979bae',
54 | materialWhite: '#fff',
55 | gray: 'var(--material-gray)',
56 | },
57 | radii: [0, 6, 16, 16, 9999, '100%'],
58 | },
59 | },
60 | {
61 | media: {
62 | image: renaissance, // Photo by Sandra Ollier on Unsplash
63 | title: 'Renaissance',
64 | body: 'Humanity: the center of interest.',
65 | },
66 | profile: {
67 | avatar: ayla,
68 | name: 'Ayla Gauthier',
69 | title: 'Artist',
70 | },
71 | theme: {
72 | colors: {
73 | black: '#000000',
74 | primary: '#5f7166',
75 | secondary: '#e4e8e6',
76 | tertiary: '#bbbb9f',
77 | white: 'var(--material-white)',
78 | materialGray: '#959e97',
79 | materialWhite: '#fff',
80 | gray: 'var(--material-gray)',
81 | },
82 | radii: [0, 0, 16, 9999, '100%'],
83 | },
84 | },
85 | ];
86 |
87 | export function useThemes() {
88 | const [activeIndex, setActiveIndex] = useState(1);
89 | const [theme, setTheme] = useState(variants[1]);
90 |
91 | return [
92 | theme,
93 | activeIndex,
94 | index => {
95 | setActiveIndex(index);
96 | setTheme(variants[index]);
97 | },
98 | ];
99 | }
100 |
--------------------------------------------------------------------------------