├── .gitignore
├── .nova
├── Artwork
├── Configuration.json
└── Tasks
│ ├── Develop.json
│ └── Package.json
├── README.md
├── craco.config.js
├── package.json
├── public
├── favicon-128.png
├── favicon-152.png
├── favicon-167.png
├── favicon-180.png
├── favicon-196.png
├── favicon-32.png
├── index.html
├── manifest.json
├── ogimage.png
└── robots.txt
├── src
├── App.tsx
├── Banner.tsx
├── BikePicker.tsx
├── BlockLink.tsx
├── Char.tsx
├── CodeExample.tsx
├── Dialog.tsx
├── DialogElement.tsx
├── Example.tsx
├── Heading.tsx
├── Paragraph.tsx
├── Performer.tsx
├── RewindEffect.tsx
├── RewindListener.tsx
├── Section.tsx
├── SectionFocusContext.tsx
├── SkipEffect.tsx
├── SmashEffect.tsx
├── StaticCodeExample.tsx
├── Subheading.tsx
├── TextPanel.tsx
├── colours.ts
├── content
│ ├── APIDocs.tsx
│ ├── Chat.tsx
│ ├── Guides.tsx
│ └── QuickStart.tsx
├── guides
│ ├── Accessibility.tsx
│ ├── ChangingValues.tsx
│ ├── Effects.tsx
│ ├── GuidesHelp.tsx
│ ├── HookIntro.tsx
│ ├── Install.tsx
│ ├── Linebreaking.tsx
│ ├── LinebreakingWithStyle.tsx
│ ├── SkipRewind.tsx
│ ├── StyledText.tsx
│ ├── StylingCharacters.tsx
│ ├── Timing.tsx
│ └── WindupsWithAnything.tsx
├── images
│ ├── auntie.svg
│ ├── compass-menu.svg
│ ├── forks.svg
│ ├── frog-menu.svg
│ ├── frog
│ │ ├── Group 12-1.svg
│ │ ├── Group 12-2.svg
│ │ ├── Group 12.svg
│ │ ├── Group 13-1.svg
│ │ ├── Group 13-2.svg
│ │ ├── Group 13.svg
│ │ ├── f-cool-open-1.svg
│ │ ├── f-cool-open-2.svg
│ │ ├── f-cool-resting.svg
│ │ ├── f-laff-open-1.svg
│ │ ├── f-laff-open-2.svg
│ │ ├── f-laff-resting.svg
│ │ ├── f-mad-open-1.svg
│ │ ├── f-mad-open-2.svg
│ │ ├── f-mad-resting.svg
│ │ ├── f-norm-open-1.svg
│ │ ├── f-norm-open-2.svg
│ │ ├── f-norm-resting.svg
│ │ ├── f-shame-open-1.svg
│ │ ├── f-shame-open-2.svg
│ │ ├── f-shame-resting.svg
│ │ ├── f-shock-open-1.svg
│ │ ├── f-shock-open-2.svg
│ │ ├── f-shock-resting.svg
│ │ ├── f-smug-open-1.svg
│ │ ├── f-smug-open-2.svg
│ │ └── f-smug-resting.svg
│ ├── gwilco.svg
│ ├── key-a.svg
│ ├── key-b.svg
│ ├── key-c.svg
│ ├── key-d.svg
│ ├── key-menu.svg
│ ├── keyboard-menu.svg
│ ├── little-hand.svg
│ ├── mega-yaki.svg
│ ├── nexters.svg
│ ├── point.svg
│ ├── repo-menu.svg
│ ├── ruffle-pizza.svg
│ └── snowman-kebab.svg
├── index.tsx
├── performers
│ └── Frog.tsx
├── react-app-env.d.ts
└── serviceWorker.ts
├── tsconfig.json
└── yarn.lock
/.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 |
25 | .linaria_cache
26 |
--------------------------------------------------------------------------------
/.nova/Artwork:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sgwilym/windups-docs/809404d6215ba515cebe6bc4137fc3d18ea90dcf/.nova/Artwork
--------------------------------------------------------------------------------
/.nova/Configuration.json:
--------------------------------------------------------------------------------
1 | {
2 | "index.use_scm_ignored_files" : true,
3 | "workspace.art_style" : 1,
4 | "workspace.color" : 9,
5 | "workspace.name" : "Windups Docs",
6 | "workspace.preview_append_paths" : false,
7 | "workspace.preview_type" : "custom",
8 | "workspace.preview_url" : "http:\/\/localhost:3000"
9 | }
10 |
--------------------------------------------------------------------------------
/.nova/Tasks/Develop.json:
--------------------------------------------------------------------------------
1 | {
2 | "actions" : {
3 | "run" : {
4 | "enabled" : true,
5 | "script" : "yarn start"
6 | }
7 | },
8 | "identifier" : "00C30011-A45F-4675-841A-F1EFB3E34F3B",
9 | "openLogOnRun" : "start",
10 | "persistent" : true
11 | }
--------------------------------------------------------------------------------
/.nova/Tasks/Package.json:
--------------------------------------------------------------------------------
1 | {
2 | "actions" : {
3 | "build" : {
4 | "enabled" : true,
5 | "script" : "yarn build"
6 | }
7 | },
8 | "identifier" : "5223FE37-D050-4208-8C24-09849B29074C",
9 | "openLogOnRun" : "finish"
10 | }
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # windups docs
2 |
3 | This is a React app that acts as documentation for the [windups](https://github.com/sgwilym/windups) library.
4 |
5 | You can see the docs site in action at https://windups.gwil.co
6 |
7 | Building that library and its docs was a solo project, and at a certain point my focus was getting it out the door — so this repo might seem a bit like a teenager's bedroom! Which is to say, some things in this code base are the way they are because I thought I'd be the only person to look at them.
8 |
9 | That said it's an extensive example of a real world application of the windups library, with lots of little tricks to make the effect more user-friendly: stuff like auto-scrolling the page to keep up with text that's being typed out; animating character portraits next to text; special effects.
10 |
11 | There is also a pretty full-fledged dialogue system in here that could probably be pulled out into its own family of components. Some day.
--------------------------------------------------------------------------------
/craco.config.js:
--------------------------------------------------------------------------------
1 | const CracoLinariaPlugin = require("craco-linaria");
2 |
3 | module.exports = {
4 | plugins: [
5 | {
6 | plugin: CracoLinariaPlugin,
7 | options: {
8 | // Linaria options
9 | }
10 | }
11 | ]
12 | };
13 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "windup-docs",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "@craco/craco": "5.5.0",
7 | "@reach/visually-hidden": "0.10.1",
8 | "@rehooks/component-size": "1.0.3",
9 | "@rooks/use-key": "3.4.3",
10 | "@types/jest": "24.0.18",
11 | "@types/node": "12.7.12",
12 | "@types/react": "16.9.5",
13 | "@types/react-dom": "16.9.1",
14 | "linaria": "1.3.1",
15 | "prism-react-renderer": "1.1.0",
16 | "react": "16.10.2",
17 | "react-dom": "16.10.2",
18 | "react-intersection-observer": "8.25.2",
19 | "react-router": "5.1.2",
20 | "react-router-dom": "5.1.2",
21 | "react-router-hash-link": "1.2.2",
22 | "react-scripts": "3.3.0",
23 | "resize-observer-polyfill": "1.5.1",
24 | "scroll-behavior-polyfill": "2.0.13",
25 | "typescript": "3.7.2",
26 | "use-debounce": "3.4.1",
27 | "use-internet-time": "1.0.1",
28 | "windups": "^1.1.7"
29 | },
30 | "scripts": {
31 | "start": "craco start",
32 | "build": "craco build",
33 | "test": "craco test",
34 | "eject": "react-scripts eject"
35 | },
36 | "eslintConfig": {
37 | "extends": "react-app"
38 | },
39 | "browserslist": {
40 | "production": [
41 | ">0.2%",
42 | "not dead",
43 | "not op_mini all"
44 | ],
45 | "development": [
46 | "last 1 chrome version",
47 | "last 1 firefox version",
48 | "last 1 safari version"
49 | ]
50 | },
51 | "devDependencies": {
52 | "@types/react-router-dom": "^5.1.3",
53 | "@types/react-router-hash-link": "^1.2.1",
54 | "core-js": "2",
55 | "craco-linaria": "^1.1.2"
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/public/favicon-128.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sgwilym/windups-docs/809404d6215ba515cebe6bc4137fc3d18ea90dcf/public/favicon-128.png
--------------------------------------------------------------------------------
/public/favicon-152.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sgwilym/windups-docs/809404d6215ba515cebe6bc4137fc3d18ea90dcf/public/favicon-152.png
--------------------------------------------------------------------------------
/public/favicon-167.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sgwilym/windups-docs/809404d6215ba515cebe6bc4137fc3d18ea90dcf/public/favicon-167.png
--------------------------------------------------------------------------------
/public/favicon-180.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sgwilym/windups-docs/809404d6215ba515cebe6bc4137fc3d18ea90dcf/public/favicon-180.png
--------------------------------------------------------------------------------
/public/favicon-196.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sgwilym/windups-docs/809404d6215ba515cebe6bc4137fc3d18ea90dcf/public/favicon-196.png
--------------------------------------------------------------------------------
/public/favicon-32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sgwilym/windups-docs/809404d6215ba515cebe6bc4137fc3d18ea90dcf/public/favicon-32.png
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
21 |
22 |
23 |
24 |
25 |
26 | windups
27 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/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 | "src": "logo192.png",
12 | "type": "image/png",
13 | "sizes": "192x192"
14 | },
15 | {
16 | "src": "logo512.png",
17 | "type": "image/png",
18 | "sizes": "512x512"
19 | }
20 | ],
21 | "start_url": ".",
22 | "display": "standalone",
23 | "theme_color": "#000000",
24 | "background_color": "#ffffff"
25 | }
26 |
--------------------------------------------------------------------------------
/public/ogimage.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sgwilym/windups-docs/809404d6215ba515cebe6bc4137fc3d18ea90dcf/public/ogimage.png
--------------------------------------------------------------------------------
/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 |
--------------------------------------------------------------------------------
/src/Banner.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { useWindupString } from "windups";
3 | import { cx, css } from "linaria";
4 | import KeyA from "./images/key-a.svg";
5 | import KeyB from "./images/key-b.svg";
6 | import KeyC from "./images/key-c.svg";
7 | import KeyD from "./images/key-d.svg";
8 |
9 | const keyRootStyle = css`
10 | width: 76px;
11 | display: flex;
12 | justify-content: right;
13 | margin: 0 12px;
14 | `;
15 |
16 | type KeyProps = {
17 | changeValue: any;
18 | };
19 |
20 | const Key: React.FC = ({ changeValue }) => {
21 | const [[src], next] = React.useReducer(
22 | (state) => {
23 | const [first, ...rest] = state;
24 | return [...rest, first];
25 | },
26 | [KeyD, KeyA, KeyB, KeyC]
27 | );
28 |
29 | React.useEffect(() => {
30 | next("whatever");
31 | }, [changeValue]);
32 |
33 | return (
34 |
35 |

36 |
37 | );
38 | };
39 |
40 | const bannerRootStyle = css`
41 | flex: 1 0 auto;
42 | display: flex;
43 | align-items: center;
44 | justify-content: center;
45 | margin-bottom: 48px;
46 | `;
47 |
48 | const bannerStyle = css`
49 | color: black;
50 | border-radius: 6px;
51 | border: 4px black solid;
52 | transform: skew(-5deg);
53 | padding: 12px;
54 | font-size: 64px;
55 | font-family: "Menlo", monospace;
56 | font-style: italic;
57 | display: inline-block;
58 | transition: width 50ms;
59 | margin-right: 24px;
60 | `;
61 |
62 | const bannerTextStyle = css`
63 | transform: skew(5deg);
64 | display: inline-block;
65 | `;
66 |
67 | const finishedBannerStyle = css`
68 | color: white;
69 | background-color: black;
70 | `;
71 |
72 | type BannerProps = {
73 | onFinished: () => void;
74 | };
75 |
76 | const Banner: React.FC = ({ onFinished }) => {
77 | const [isFinished, setIsFinished] = React.useState(false);
78 | const [count, setCount] = React.useState(0);
79 |
80 | const [text] = useWindupString("windups", {
81 | pace: () => 150,
82 | onChar: () => {
83 | setCount((prev) => prev + 1);
84 | },
85 | onFinished: () => {
86 | setTimeout(() => {
87 | setCount((prev) => prev + 1);
88 | onFinished();
89 | setIsFinished(true);
90 | }, 300);
91 | },
92 | });
93 |
94 | return (
95 |
96 |
97 |
98 | {text}
99 |
100 |
101 | );
102 | };
103 |
104 | export default Banner;
105 |
--------------------------------------------------------------------------------
/src/BikePicker.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from "react";
2 | import { WindupChildren, Pace } from "windups";
3 | import { GREEN, PINK } from "./colours";
4 | import { css } from "linaria";
5 |
6 | const Bicycle = ({ colour }: { colour: string }) => {
7 | return (
8 |
39 | );
40 | };
41 |
42 | const buttonStyle = css`
43 | font-family: "Menlo", monospace;
44 | font-size: 1.5em;
45 | color: white;
46 | padding: 16px;
47 | border-radius: 24px;
48 | border: none;
49 | box-shadow: 2px 2px 7px rgba(0, 0, 0, 0.05);
50 | transform: skew(0deg, -3deg);
51 | transition: 300ms all;
52 | &:hover {
53 | transform: scale(1.03) skew(0deg, -3deg);
54 | }
55 | `;
56 |
57 | const Button = ({
58 | colour,
59 | label,
60 | onClick,
61 | }: {
62 | colour: string;
63 | label: string;
64 | onClick: () => void;
65 | }) => {
66 | return (
67 |
74 | );
75 | };
76 |
77 | const rootStyle = css`
78 | display: flex;
79 | flex-direction: column;
80 | align-items: stretch;
81 | `;
82 |
83 | const BikePicker = () => {
84 | const [colour, setColour] = useState("black");
85 |
86 | return (
87 |
88 |
89 |
90 |
91 |
107 |
108 |
109 | );
110 | };
111 |
112 | export default BikePicker;
113 |
--------------------------------------------------------------------------------
/src/BlockLink.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { LinkProps } from "react-router-dom";
3 | import { css, cx } from "linaria";
4 | import { TEXT_PINK, GREEN } from "./colours";
5 | import { HashLink as Link } from "react-router-hash-link";
6 | import LittleHand from "./images/little-hand.svg";
7 |
8 | const rootStyle = css`
9 | font-family: Menlo, monospace;
10 | display: inline-block;
11 |
12 | border-radius: 5px;
13 | box-shadow: 2px 2px 7px rgba(0, 0, 0, 0.05);
14 | padding: 8px;
15 | text-decoration-thickness: 2px;
16 |
17 | transition: all 200ms;
18 | color: white;
19 | position: relative;
20 |
21 | &:hover {
22 | background: white;
23 | }
24 |
25 | &:hover:before {
26 | content: url(${LittleHand});
27 | position: absolute;
28 |
29 | top: 50%;
30 | transform: translate(-110%, -45%);
31 | }
32 |
33 | img {
34 | flex: 0 0 auto;
35 | margin-right: 0.5em;
36 | filter: invert(100%);
37 | transition: all 200ms;
38 | }
39 |
40 | &:hover img {
41 | filter: none;
42 | }
43 | `;
44 |
45 | const blackStyle = css`
46 | background: black;
47 |
48 | &:hover {
49 | color: black;
50 | }
51 | `;
52 |
53 | const greenStyle = css`
54 | background: ${GREEN};
55 |
56 | &:hover {
57 | color: ${GREEN};
58 | }
59 | `;
60 |
61 | const pinkStyle = css`
62 | background: ${TEXT_PINK};
63 |
64 | &: hover {
65 | color: ${TEXT_PINK};
66 | }
67 | `;
68 |
69 | const innerStyle = css`
70 | display: flex;
71 | align-items: center;
72 | `;
73 |
74 | const themeStyles = {
75 | PINK: pinkStyle,
76 | GREEN: greenStyle,
77 | BLACK: blackStyle
78 | };
79 |
80 | type BlockLinkProps = {
81 | theme?: "PINK" | "GREEN" | "BLACK";
82 | } & LinkProps;
83 |
84 | const BlockLink: React.FC = ({
85 | children,
86 | theme = "PINK",
87 | className,
88 | ...rest
89 | }) => {
90 | return (
91 |
92 | {children}
93 |
94 | );
95 | };
96 |
97 | export default BlockLink;
98 |
--------------------------------------------------------------------------------
/src/Char.tsx:
--------------------------------------------------------------------------------
1 | import React, { useContext } from "react";
2 | import { css, cx } from "linaria";
3 | import { CharWrapper } from "windups";
4 | import { TEXT_PINK, GREY } from "./colours";
5 |
6 | export const CharContext = React.createContext({ animated: false });
7 |
8 | export const CHAR_FONT_STYLE = "14pt 'Menlo'";
9 |
10 | const charStyle = css`
11 | display: inline-block;
12 | font: ${CHAR_FONT_STYLE};
13 | transform: translateZ(0);
14 | `;
15 |
16 | const animatingStyle = css`
17 | @keyframes enter {
18 | from {
19 | opacity: 0;
20 | transform: scale(0.1) rotate(-80deg);
21 | }
22 | to {
23 | opacity: 1;
24 | }
25 | }
26 | animation-name: enter;
27 | animation-duration: 100ms;
28 | `;
29 |
30 | export const StandardChar: React.FC = ({ children }) => {
31 | const { animated } = useContext(CharContext);
32 |
33 | return (
34 |
35 | {children}
36 |
37 | );
38 | };
39 |
40 | const dropStyle = css`
41 | @keyframes enter {
42 | 0% {
43 | opacity: 0;
44 | }
45 | 50% {
46 | opacity: 1;
47 | }
48 | 25% {
49 | animation-timing-function: cubic-bezier(0.4, 0, 1, 0.6);
50 | transform: translate3d(0, -100%, 0);
51 | transform-style: preserve-3d;
52 | }
53 | 0%,
54 | 50%,
55 | 88%,
56 | 96%,
57 | 100% {
58 | animation-timing-function: cubic-bezier(0.12, 0.52, 0.57, 1);
59 | transform: translate3d(0, 0, 0);
60 | transform-style: preserve-3d;
61 | }
62 | 75% {
63 | animation-timing-function: cubic-bezier(0.4, 0, 1, 0.6);
64 | transform: translate3d(0, -33%, 0);
65 | transform-style: preserve-3d;
66 | }
67 | 94% {
68 | animation-timing-function: cubic-bezier(0.4, 0, 1, 0.6);
69 | transform: translate3d(0, -11%, 0);
70 | transform-style: preserve-3d;
71 | }
72 | 97% {
73 | animation-timing-function: cubic-bezier(0.4, 0, 1, 0.6);
74 | transform: translate3d(0, -3%, 0);
75 | transform-style: preserve-3d;
76 | }
77 | }
78 | animation-name: enter;
79 | animation-duration: 500ms;
80 | `;
81 |
82 | const emphasisStyle = css`
83 | color: ${TEXT_PINK};
84 | `;
85 |
86 | const thinkingStyle = css`
87 | @keyframes fade {
88 | 0% {
89 | opacity: 0;
90 | }
91 | 100% {
92 | opacity: 0.7;
93 | }
94 | }
95 | animation-name: fade;
96 | animation-duration: 500ms;
97 | opacity: 0.7;
98 | `;
99 |
100 | const EmphasisChar: React.FC = ({ children }) => {
101 | const { animated } = useContext(CharContext);
102 |
103 | return (
104 |
105 | {children}
106 |
107 | );
108 | };
109 |
110 | export const Emphasis: React.FC = ({ children }) => {
111 | return {children};
112 | };
113 |
114 | const ThinkingChar: React.FC = ({ children }) => {
115 | return {children};
116 | };
117 |
118 | export const Thinking: React.FC = ({ children }) => {
119 | return {children};
120 | };
121 |
122 | const DropChar: React.FC = ({ children }) => {
123 | const { animated } = useContext(CharContext);
124 |
125 | return (
126 | {children}
127 | );
128 | };
129 |
130 | export const Dropped: React.FC = ({ children }) => {
131 | return {children};
132 | };
133 |
--------------------------------------------------------------------------------
/src/CodeExample.tsx:
--------------------------------------------------------------------------------
1 | import React, { useContext } from "react";
2 | import { DialogChildContext, DialogContext } from "./Dialog";
3 | import { textFromChildren, useWindupString } from "windups";
4 | import { css, cx } from "linaria";
5 | import { GREEN } from "./colours";
6 | import Highlight, { defaultProps } from "prism-react-renderer";
7 | import theme from "prism-react-renderer/themes/nightOwl";
8 | import useKey from "@rooks/use-key";
9 | import SectionFocusContext from "./SectionFocusContext";
10 | import { SectionContext } from "./Section";
11 |
12 | const rootStyle = css`
13 | font: 16px "Menlo", monospace;
14 | padding: 16px;
15 | border: 2px solid #e6e6e6;
16 | border-radius: 5px;
17 | box-shadow: 2px 2px 7px rgba(0, 0, 0, 0.05);
18 | margin-bottom: 16px;
19 | white-space: pre;
20 | color: ${GREEN};
21 | background-color: black;
22 | line-height: 1.5;
23 | overflow: auto;
24 | `;
25 |
26 | function randomInt(max: number) {
27 | return Math.floor(Math.random() * Math.floor(max));
28 | }
29 |
30 | type CodeExampleProps = {
31 | children: string;
32 | };
33 |
34 | const CodeExample: React.FC = ({ children }) => {
35 | const { proceed, isActive } = useContext(DialogChildContext);
36 | const { isFinished: dialogIsFinished } = useContext(DialogContext);
37 | const { activeSectionID } = useContext(SectionFocusContext);
38 | const { id } = useContext(SectionContext);
39 | const isTotallyActive = isActive && activeSectionID === id;
40 | const [windup, { skip, isFinished }] = useWindupString(children, {
41 | onFinished: () => {
42 | setTimeout(() => {
43 | proceed();
44 | }, 500);
45 | },
46 | pace: (char) => {
47 | if (char === "\n") {
48 | return 200;
49 | }
50 | return randomInt(80);
51 | },
52 | skipped: dialogIsFinished,
53 | });
54 |
55 | useKey([13, 39], () => {
56 | if (isTotallyActive && !isFinished) {
57 | skip();
58 | } else {
59 | if (isTotallyActive && !dialogIsFinished) {
60 | proceed();
61 | }
62 | }
63 | });
64 |
65 | return (
66 |
67 | {({ className, style, tokens, getLineProps, getTokenProps }) => (
68 |
69 | {tokens.map((line, i) => (
70 |
71 | {line.map((token, key) => (
72 |
73 | ))}
74 |
75 | ))}
76 |
77 | )}
78 |
79 | );
80 | };
81 |
82 | export default CodeExample;
83 |
--------------------------------------------------------------------------------
/src/Dialog.tsx:
--------------------------------------------------------------------------------
1 | import React, {
2 | useState,
3 | useCallback,
4 | useRef,
5 | useEffect,
6 | useContext,
7 | } from "react";
8 | import { useInView } from "react-intersection-observer";
9 | import useSize from "@rehooks/component-size";
10 | import SectionFocusContext from "./SectionFocusContext";
11 | import { SectionContext } from "./Section";
12 |
13 | if (!("scrollBehavior" in document.documentElement.style)) {
14 | import("scroll-behavior-polyfill");
15 | }
16 |
17 | if (!("ResizeObserver" in window)) {
18 | (global as any).ResizeObserver = import("resize-observer-polyfill");
19 | }
20 |
21 | export const DialogContext = React.createContext({
22 | isFinished: false,
23 | });
24 |
25 | export const DialogChildContext = React.createContext({
26 | isActive: true,
27 | proceed: () => {},
28 | });
29 |
30 | function useKeepInViewer(height: number) {
31 | const measurementRef = useRef();
32 | const prevHeightRef = useRef(height);
33 | const { isActive } = useContext(SectionContext);
34 |
35 | const [inViewRef, isInView] = useInView({
36 | rootMargin: "-100px 0px",
37 | });
38 |
39 | const setRef = useCallback(
40 | (node) => {
41 | measurementRef.current = node;
42 | inViewRef(node);
43 | },
44 | [inViewRef]
45 | );
46 |
47 | useEffect(() => {
48 | if (!measurementRef.current) {
49 | return;
50 | }
51 |
52 | // only scroll there within a certain threshold....
53 |
54 | const scrollBottom = window.scrollY + window.innerHeight;
55 | const bottomPos =
56 | measurementRef.current.offsetTop + measurementRef.current.offsetHeight;
57 |
58 | const isJustOutOfView = Math.abs(scrollBottom - bottomPos) < 200;
59 |
60 | if (
61 | !isInView &&
62 | isActive &&
63 | height !== prevHeightRef.current &&
64 | isJustOutOfView
65 | ) {
66 | window.scroll({
67 | top: measurementRef.current.offsetTop - (window.innerHeight / 3) * 2,
68 | behavior: "smooth",
69 | });
70 | }
71 |
72 | prevHeightRef.current = height;
73 | }, [isInView, isActive, height]);
74 |
75 | return ;
76 | }
77 |
78 | const Dialog: React.FC<{ skipped?: boolean }> = ({
79 | children,
80 | skipped = false,
81 | }) => {
82 | const [numberOfChildrenToShow, setNumberOfChildrenToShow] = useState(1);
83 | const activeChildIndex = numberOfChildrenToShow - 1;
84 | const rootRef = useRef(null);
85 | const { height } = useSize(rootRef);
86 | const { activeSectionID } = useContext(SectionFocusContext);
87 | const { id, hasSkipped } = useContext(SectionContext);
88 | const isDialogActive = activeSectionID === id;
89 | const keepy = useKeepInViewer(height);
90 | const finish = useCallback(() => {
91 | setNumberOfChildrenToShow(React.Children.count(children));
92 | }, [children]);
93 |
94 | useEffect(() => {
95 | if (skipped) {
96 | finish();
97 | }
98 | }, [skipped, finish]);
99 |
100 | useEffect(() => {
101 | if (hasSkipped) {
102 | finish();
103 | }
104 | }, [hasSkipped, finish]);
105 |
106 | const shownChildren = React.Children.toArray(children)
107 | .slice(0, numberOfChildrenToShow)
108 | .map((child, i) => (
109 | {
114 | if (i + 2 > numberOfChildrenToShow && isDialogActive) {
115 | setNumberOfChildrenToShow(i + 2);
116 | }
117 | },
118 | }}
119 | >
120 | {child}
121 |
122 | ));
123 |
124 | return (
125 | = React.Children.count(children),
128 | }}
129 | >
130 |
131 | {shownChildren}
132 | {keepy}
133 |
134 |
135 | );
136 | };
137 |
138 | export default Dialog;
139 |
--------------------------------------------------------------------------------
/src/DialogElement.tsx:
--------------------------------------------------------------------------------
1 | import React, { useContext } from "react";
2 | import TextPanel from "./TextPanel";
3 | import { DialogContext, DialogChildContext } from "./Dialog";
4 | import useKey from "@rooks/use-key";
5 | import { useIsFinished, useSkip, Effect } from "windups";
6 | import { SectionContext } from "./Section";
7 | import SectionFocusContext from "./SectionFocusContext";
8 | import { css } from "linaria";
9 | import Nexters from "./images/nexters.svg";
10 | import { GREEN } from "./colours";
11 |
12 | export const NextListener: React.FC = () => {
13 | const isFinished = useIsFinished();
14 | const skip = useSkip();
15 | const { isFinished: dialogIsFinished } = useContext(DialogContext);
16 | const { proceed, isActive } = useContext(DialogChildContext);
17 | const { activeSectionID } = useContext(SectionFocusContext);
18 | const { id } = useContext(SectionContext);
19 | const isTotallyActive = isActive && activeSectionID === id;
20 |
21 | useKey([13, 39], () => {
22 | if (isTotallyActive && !isFinished) {
23 | skip();
24 | } else {
25 | if (isTotallyActive && !dialogIsFinished) {
26 | proceed();
27 | }
28 | }
29 | });
30 |
31 | return null;
32 | };
33 |
34 | const nextRootStyles = css`
35 | @keyframes drift {
36 | 100% {
37 | background-position: 111px 73px;
38 | }
39 | }
40 | @keyframes fade-in {
41 | from {
42 | opacity: 0;
43 | }
44 | to {
45 | opacity: 1;
46 | }
47 | }
48 | animation-name: drift, fade-in;
49 | animation-duration: 5s, 200ms;
50 | animation-iteration-count: infinite, 1;
51 | animation-timing-function: linear;
52 | display: block;
53 | height: 48px;
54 | border-radius: 5px;
55 | border: 2px solid #e5e5e5;
56 | background-image: url(${Nexters});
57 | background-color: white;
58 | width: 100%;
59 | background-size: 111px 73px;
60 | font-size: 1em;
61 | font-family: "Menlo", monospace;
62 | margin-top: 16px;
63 | box-shadow: 2px 2px 7px rgba(0, 0, 0, 0.05);
64 | transition: transform 200ms;
65 | appearance: none;
66 |
67 | &:hover {
68 | transform: scale(1.02);
69 | }
70 |
71 | &:active {
72 | transform: scale(0.98);
73 | }
74 |
75 | &:focus {
76 | color: ${GREEN};
77 | border-color: ${GREEN};
78 | }
79 | `;
80 |
81 | export const NextButton: React.FC = () => {
82 | const { setActiveSectionID } = useContext(SectionFocusContext);
83 | const { id } = useContext(SectionContext);
84 | const { isFinished: dialogIsFinished } = useContext(DialogContext);
85 | const { proceed, isActive } = useContext(DialogChildContext);
86 |
87 | return !dialogIsFinished && isActive ? (
88 | {
91 | setActiveSectionID(id);
92 | proceed();
93 | }}
94 | tabIndex={0}
95 | >
96 | {"Continue"}
97 |
98 | ) : null;
99 | };
100 |
101 | export type DialogElementProps = {
102 | autoProceed?: boolean;
103 | };
104 |
105 | const DialogElement: React.FC = ({
106 | children,
107 | autoProceed
108 | }) => {
109 | const { proceed } = useContext(DialogChildContext);
110 |
111 | return (
112 | <>
113 |
114 |
115 | {children}
116 | {autoProceed ? : }
117 |
118 | >
119 | );
120 | };
121 |
122 | export default DialogElement;
123 |
--------------------------------------------------------------------------------
/src/Example.tsx:
--------------------------------------------------------------------------------
1 | import React, { useContext } from "react";
2 | import { css } from "linaria";
3 | import { DialogChildContext } from "./Dialog";
4 |
5 | const rootStyle = css`
6 | font: 16px "Menlo", monospace;
7 | padding: 16px;
8 | border: 2px solid #e6e6e6;
9 | border-radius: 5px;
10 | box-shadow: 2px 2px 7px rgba(0, 0, 0, 0.05);
11 | margin-bottom: 16px;
12 | background-color: white;
13 | `;
14 |
15 | const Example: React.FC = ({ children }) => {
16 | const { proceed, ...rest } = useContext(DialogChildContext);
17 |
18 | return (
19 | {
22 | setTimeout(() => {
23 | proceed();
24 | }, 500);
25 | },
26 | ...rest,
27 | }}
28 | >
29 | {children}
30 |
31 | );
32 | };
33 |
34 | export default Example;
35 |
--------------------------------------------------------------------------------
/src/Heading.tsx:
--------------------------------------------------------------------------------
1 | import { css, cx } from "linaria";
2 | import React from "react";
3 | import { WindupChildren, CharWrapper, textFromChildren } from "windups";
4 |
5 | const rootStyle = css`
6 | grid-column: 1/8;
7 | display: flex;
8 | flex-direction: column;
9 | `;
10 |
11 | const headingStyle = css`
12 | font-family: "Menlo", monospace;
13 | font-size: 24px;
14 | font-weight: normal;
15 | font-style: italic;
16 | border-bottom: 2px solid black;
17 | transform: skew(0, -3deg);
18 | white-space: pre;
19 | display: inline-block;
20 | align-self: flex-start;
21 | `;
22 |
23 | const headingStyleRight = css`
24 | transform: skew(0, 3deg);
25 | font-style: oblique;
26 | align-self: flex-end;
27 | `;
28 |
29 | const headingLetterStyle = css`
30 | @keyframes enter {
31 | 0% {
32 | opacity: 0;
33 | }
34 | 20% {
35 | opacity: 1;
36 | }
37 | 25% {
38 | animation-timing-function: cubic-bezier(0.4, 0, 1, 0.6);
39 | transform: translate3d(0, -100%, 0);
40 | transform-style: preserve-3d;
41 | }
42 | 0%,
43 | 50%,
44 | 88%,
45 | 96%,
46 | 100% {
47 | animation-timing-function: cubic-bezier(0.12, 0.52, 0.57, 1);
48 | transform: translate3d(0, 0, 0);
49 | transform-style: preserve-3d;
50 | }
51 | 75% {
52 | animation-timing-function: cubic-bezier(0.4, 0, 1, 0.6);
53 | transform: translate3d(0, -33%, 0);
54 | transform-style: preserve-3d;
55 | }
56 | 94% {
57 | animation-timing-function: cubic-bezier(0.4, 0, 1, 0.6);
58 | transform: translate3d(0, -11%, 0);
59 | transform-style: preserve-3d;
60 | }
61 | 97% {
62 | animation-timing-function: cubic-bezier(0.4, 0, 1, 0.6);
63 | transform: translate3d(0, -3%, 0);
64 | transform-style: preserve-3d;
65 | }
66 | }
67 | animation-name: enter;
68 | animation-duration: 500ms;
69 | display: inline-block;
70 | `;
71 |
72 | const staticHeadingLetterStyle = css`
73 | display: inline-block;
74 | `;
75 |
76 | export const HeadingChar: React.FC = ({ children }) => {
77 | return (
78 |
79 | {children}
80 |
81 | );
82 | };
83 |
84 | const StaticHeadingChar: React.FC = ({ children }) => {
85 | return (
86 |
87 | {children}
88 |
89 | );
90 | };
91 |
92 | type HeadingProps = {
93 | onFinished?: () => void;
94 | right?: boolean;
95 | noWindup?: boolean;
96 | };
97 |
98 | const Heading: React.FC = ({
99 | children,
100 | onFinished,
101 | right,
102 | noWindup,
103 | }) => {
104 | const text = textFromChildren(children);
105 |
106 | return (
107 |
108 |
109 |
110 |
114 | {children}
115 |
116 |
117 |
118 |
119 | );
120 | };
121 |
122 | export default Heading;
123 |
--------------------------------------------------------------------------------
/src/Paragraph.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { css } from "linaria";
3 |
4 | const rootStyle = css`
5 | font-size: 14px;
6 | font-family: "Menlo", monospace;
7 | `;
8 |
9 | const Paragraph: React.FC = ({ children }) => {
10 | return {children}
;
11 | };
12 |
13 | export default Paragraph;
14 |
--------------------------------------------------------------------------------
/src/Performer.tsx:
--------------------------------------------------------------------------------
1 | import React, { useContext, useReducer, useState } from "react";
2 | import DialogElement, { DialogElementProps } from "./DialogElement";
3 | import { css, cx } from "linaria";
4 | import { DialogChildContext } from "./Dialog";
5 | import { SectionContext } from "./Section";
6 | import { useDebounce } from "use-debounce";
7 | import { OnChar, textFromChildren, Effect } from "windups";
8 | import VisuallyHidden from "@reach/visually-hidden";
9 |
10 | const rootStyle = css`
11 | display: flex;
12 | align-items: flex-start;
13 | margin-bottom: 1em;
14 | `;
15 |
16 | const textStyle = css`
17 | flex: 1 0 auto;
18 | margin: 1em 0 0 1em;
19 | `;
20 |
21 | const inactiveStyle = css`
22 | opacity: 0.5;
23 | filter: grayscale(70%);
24 | `;
25 |
26 | export const PerformerContext = React.createContext({
27 | setAvatarFrames: (_frames: FrameSet) => {},
28 | });
29 |
30 | type AvatorProps = {
31 | currentAvatar: string;
32 | };
33 |
34 | const Avatar: React.FC = ({ currentAvatar }) => {
35 | const [debouncedAvatar] = useDebounce(currentAvatar, 30, { maxWait: 40 });
36 |
37 | return
;
38 | };
39 |
40 | type Action =
41 | | {
42 | type: "next";
43 | fromChar?: (char: string) => void;
44 | }
45 | | {
46 | type: "newFrameSet";
47 | frameSet: FrameSet;
48 | };
49 |
50 | function cycleFrames(frames: string[]) {
51 | const [head, ...tail] = frames;
52 | return [...tail, head];
53 | }
54 |
55 | function avatarReducer(state: FrameSet, action: Action) {
56 | switch (action.type) {
57 | case "newFrameSet":
58 | return action.frameSet;
59 | default:
60 | return {
61 | normal: cycleFrames(state.normal),
62 | resting: cycleFrames(state.resting),
63 | };
64 | }
65 | }
66 |
67 | function getIsResting(char: string) {
68 | switch (char) {
69 | case ".":
70 | case " ":
71 | return true;
72 | default:
73 | return false;
74 | }
75 | }
76 |
77 | export type FrameSet = {
78 | normal: string[];
79 | resting: string[];
80 | };
81 |
82 | interface PerformerProps extends DialogElementProps {
83 | initialFrameSet: FrameSet;
84 | silent?: boolean;
85 | }
86 |
87 | const Performer: React.FC = ({
88 | children,
89 | autoProceed,
90 | initialFrameSet,
91 | silent,
92 | }) => {
93 | const { isActive: sectionIsActive } = useContext(SectionContext);
94 | const { isActive } = useContext(DialogChildContext);
95 | const [frameSet, dispatch] = useReducer(avatarReducer, initialFrameSet);
96 | const text = textFromChildren(children);
97 | const [isResting, setIsResting] = useState(false);
98 | const frames = silent
99 | ? frameSet.resting
100 | : isResting
101 | ? frameSet.resting
102 | : frameSet.normal;
103 |
104 | return (
105 | {
108 | dispatch({ type: "newFrameSet", frameSet: newFrameSet });
109 | },
110 | }}
111 | >
112 |
118 |
119 |
120 | {text}
121 |
122 | {
124 | setIsResting(getIsResting(char));
125 | dispatch({ type: "next" });
126 | }}
127 | >
128 | {children}
129 |
130 | {
132 | setIsResting(true);
133 | }}
134 | />
135 |
136 |
137 |
138 |
139 | );
140 | };
141 |
142 | export default Performer;
143 |
--------------------------------------------------------------------------------
/src/RewindEffect.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { useRewind, Effect } from "windups";
3 | import { useContext } from "react";
4 | import { DialogChildContext } from "./Dialog";
5 |
6 | const RewindEffect = () => {
7 | const rewind = useRewind();
8 | const { proceed } = useContext(DialogChildContext);
9 |
10 | return (
11 | {
13 | proceed();
14 | rewind();
15 | }}
16 | />
17 | );
18 | };
19 |
20 | export default RewindEffect;
21 |
--------------------------------------------------------------------------------
/src/RewindListener.tsx:
--------------------------------------------------------------------------------
1 | import { useRewind } from "windups";
2 | import useKey from "@rooks/use-key";
3 | import { useContext } from "react";
4 | import { DialogChildContext } from "./Dialog";
5 |
6 | type RewindListenerProps = {
7 | onRewind: () => void;
8 | };
9 |
10 | const RewindListener: React.FC = ({ onRewind }) => {
11 | const { isActive } = useContext(DialogChildContext);
12 | const rewind = useRewind();
13 | useKey(
14 | [37],
15 | isActive
16 | ? () => {
17 | onRewind();
18 | rewind();
19 | }
20 | : () => {}
21 | );
22 |
23 | return null;
24 | };
25 |
26 | export default RewindListener;
27 |
--------------------------------------------------------------------------------
/src/Section.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useContext } from "react";
2 | import { css, cx } from "linaria";
3 | import SectionFocusContext from "./SectionFocusContext";
4 | import { SubGrid, Indent1 } from "./App";
5 | import { GREEN, PINK } from "./colours";
6 | import { useWindupString, CharWrapper } from "windups";
7 | import { useLocation } from "react-router";
8 | import { HeadingChar } from "./Heading";
9 |
10 | const dialogRoot = css`
11 | margin: 1em 0 10em 0;
12 | `;
13 |
14 | const headingRootStyle = css`
15 | grid-column: 2/8;
16 | display: flex;
17 | align-items: center;
18 | padding: 0;
19 | border: none;
20 | font-size: 1em;
21 | height: 48px;
22 | border-bottom: 2px solid black;
23 | padding: 0 0 8px 0;
24 | margin: 0 0 1em 0;
25 | background: none;
26 | justify-content: space-between;
27 | &:focus {
28 | color: ${GREEN};
29 | }
30 | `;
31 |
32 | const activeHeadingStyle = css`
33 | border-bottom-color: ${GREEN};
34 | `;
35 |
36 | const headingStyle = css`
37 | font-family: "Menlo", monospace;
38 | font-size: 1.2em;
39 | font-weight: normal;
40 | font-style: italic;
41 | margin: 0 0 0 0.3em;
42 | text-align: left;
43 | white-space: pre;
44 | `;
45 |
46 | const playButtonStyle = css`
47 | font-size: 2em;
48 | width: 1.3em;
49 | height: 1.3em;
50 | color: white;
51 | background-color: ${GREEN};
52 | border-radius: 50%;
53 | padding: 2px 0 0 2px
54 | text-align: center;
55 | box-shadow: 2px 2px 7px rgba(0, 0, 0, 0.05);
56 | `;
57 |
58 | const skipButtonStyle = css`
59 | font-size: 2em;
60 | width: 2em;
61 | height: 1.3em;
62 | color: white;
63 | background-color: ${PINK};
64 | letter-spacing: -5px;
65 | border-radius: 1.3em;
66 | padding: 2px 0 0 0
67 | text-align: center;
68 | box-shadow: 2px 2px 7px rgba(0, 0, 0, 0.05);
69 | `;
70 |
71 | export const SectionContext = React.createContext({
72 | id: "",
73 | isActive: true,
74 | hasSkipped: false,
75 | });
76 |
77 | type SectionProps = {
78 | title: string;
79 | id: string;
80 | alt?: boolean;
81 | };
82 |
83 | const SectionHeading: React.FC<{ id: string; title: string }> = ({
84 | title,
85 | id,
86 | }) => {
87 | const { hash } = useLocation();
88 | const isHashLinked = hash === `#${id}`;
89 | const [titleWindup] = useWindupString(title, { skipped: !isHashLinked });
90 |
91 | return (
92 |
93 |
94 | {titleWindup}
95 |
96 |
97 | );
98 | };
99 |
100 | const Section: React.FC = ({ id, title, children, alt }) => {
101 | const [pressedPlay, setPressedPlay] = useState(false);
102 | const [hasSkipped, setHasSkipped] = useState(false);
103 | const { setActiveSectionID, activeSectionID } = useContext(
104 | SectionFocusContext
105 | );
106 | const isActive = activeSectionID === id;
107 |
108 | return (
109 |
110 |
111 | {
114 | if (!pressedPlay) {
115 | setPressedPlay(true);
116 | }
117 |
118 | if (pressedPlay && !hasSkipped) {
119 | setHasSkipped(true);
120 | }
121 |
122 | setActiveSectionID(id);
123 | }}
124 | >
125 |
126 | {!pressedPlay ? (
127 |
128 | {"▶"}
129 |
130 | ) : !hasSkipped ? (
131 |
132 | {"▶▶"}
133 |
134 | ) : null}
135 |
136 |
137 | {pressedPlay && (
138 |
139 | {children}
140 |
141 | )}
142 |
143 |
144 | );
145 | };
146 |
147 | export default Section;
148 |
--------------------------------------------------------------------------------
/src/SectionFocusContext.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | const SectionFocusContext = React.createContext<{
4 | activeSectionID: string | null;
5 | setActiveSectionID: Function;
6 | }>({
7 | activeSectionID: null,
8 | setActiveSectionID: () => {}
9 | });
10 |
11 | export default SectionFocusContext
--------------------------------------------------------------------------------
/src/SkipEffect.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { useSkip, Effect } from "windups";
3 |
4 | const SkipEffect = () => {
5 | const skip = useSkip();
6 |
7 | return ;
8 | };
9 |
10 | export default SkipEffect;
11 |
--------------------------------------------------------------------------------
/src/SmashEffect.tsx:
--------------------------------------------------------------------------------
1 | import React, { useContext } from "react";
2 | import { SFXContext } from "./App";
3 | import { Effect } from "windups";
4 |
5 | const SmashEffect: React.FC = () => {
6 | const { smash } = useContext(SFXContext);
7 | return ;
8 | };
9 |
10 | export default SmashEffect;
11 |
--------------------------------------------------------------------------------
/src/StaticCodeExample.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { css, cx } from "linaria";
3 | import { LIGHT_GREY } from "./colours";
4 | import Highlight, { defaultProps } from "prism-react-renderer";
5 | import theme from "prism-react-renderer/themes/nightOwlLight";
6 | import { textFromChildren } from "windups";
7 |
8 | const rootStyle = css`
9 | font: 14px "Menlo", monospace;
10 | padding: 16px;
11 | border-radius: 5px;
12 | box-shadow: 2px 2px 7px rgba(0, 0, 0, 0.05);
13 | background: ${LIGHT_GREY};
14 | margin-bottom: 8px;
15 | white-space: pre;
16 | line-height: 1.5;
17 | overflow: auto;
18 | `;
19 |
20 | const StaticCodeExample: React.FC = ({ children }) => {
21 | const text = textFromChildren(children);
22 |
23 | return (
24 |
25 | {({ className, style, tokens, getLineProps, getTokenProps }) => (
26 |
27 | {tokens.map((line, i) => (
28 |
29 | {line.map((token, key) => (
30 |
31 | ))}
32 |
33 | ))}
34 |
35 | )}
36 |
37 | );
38 | };
39 |
40 | export default StaticCodeExample;
41 |
--------------------------------------------------------------------------------
/src/Subheading.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { css } from "linaria";
3 |
4 | const rootStyle = css`
5 | font-family: Menlo, monospace;
6 | margin: 2em 0 1em 0;
7 | font-weight: normal;
8 | font-style: italic;
9 | font-size: 18px;
10 | `;
11 |
12 | const Subheading: React.FC = ({ children }) => {
13 | return {children}
;
14 | };
15 |
16 | export default Subheading;
17 |
--------------------------------------------------------------------------------
/src/TextPanel.tsx:
--------------------------------------------------------------------------------
1 | import React, { useRef, useState, useContext, useEffect } from "react";
2 | import { Linebreaker, WindupChildren, CharWrapper } from "windups";
3 | import { css } from "linaria";
4 | import useSize from "@rehooks/component-size";
5 | import { CHAR_FONT_STYLE, StandardChar, CharContext } from "./Char";
6 | import { DialogContext } from "./Dialog";
7 | import RewindListener from "./RewindListener";
8 |
9 | const rootStyle = css`
10 | line-height: 1.5em;
11 | `;
12 |
13 | const TextPanel: React.FC = ({ children }) => {
14 | const panelRef = useRef(null);
15 | const { width: panelWidth } = useSize(panelRef);
16 | const [isFinished, setIsFinished] = useState(false);
17 | const { isFinished: dialogIsFinished } = useContext(DialogContext);
18 |
19 | useEffect(() => {
20 | if (dialogIsFinished) {
21 | setIsFinished(true);
22 | }
23 | }, [dialogIsFinished, setIsFinished]);
24 |
25 | // TODO: code examples and normal examples are not skipped!
26 |
27 | return (
28 |
29 |
30 |
31 | {
33 | setIsFinished(true);
34 | }}
35 | skipped={isFinished}
36 | >
37 | setIsFinished(false)} />
38 | {children}
39 |
40 |
41 |
42 |
43 | );
44 | };
45 |
46 | export default TextPanel;
47 |
--------------------------------------------------------------------------------
/src/colours.ts:
--------------------------------------------------------------------------------
1 | export const GREEN = "#47bc76";
2 | export const PINK = "#EFBBD1";
3 | export const TEXT_PINK = "#ff92c0";
4 | export const YELLOW = "#FFFCBE";
5 | export const GREY = "#eeeeee";
6 | export const LIGHT_GREY = "#f9f9f9";
7 |
--------------------------------------------------------------------------------
/src/content/Chat.tsx:
--------------------------------------------------------------------------------
1 | import React, { useRef } from "react";
2 | import { CharWrapper, WindupChildren, Linebreaker } from "windups";
3 | import { css } from "linaria";
4 | import { GREEN, PINK } from "../colours";
5 | import useComponentSize from "@rehooks/component-size";
6 |
7 | const chatChar = css`
8 | @keyframes enter {
9 | from {
10 | opacity: 0;
11 | transform: scale(0.1) rotate(-180deg) translateY(-100%);
12 | }
13 | to {
14 | opacity: 1;
15 | transform: scale(1) rotate(0deg) translateY(0);
16 | }
17 | }
18 | animation-name: enter;
19 | animation-duration: 150ms;
20 | display: inline-block;
21 | `;
22 |
23 | const SpeechBubbleChar: React.FC = ({ children }) => {
24 | return {children};
25 | };
26 |
27 | type SpeechBubbleProps = {
28 | text: string;
29 | onFinished?: () => void;
30 | };
31 |
32 | const greenBubble = css`
33 | font-family: "Menlo", monospace;
34 | padding: 12px;
35 | color: white;
36 | border-radius: 3px;
37 | background-color: ${GREEN};
38 | transform: skew(-5deg, -3deg);
39 | display: inline-block;
40 | white-space: pre-wrap;
41 | font-size: 24px;
42 | align-self: flex-start;
43 | box-shadow: 2px 2px 7px rgba(0, 0, 0, 0.05);
44 | `;
45 |
46 | export const SpeechBubbleA: React.FC = ({
47 | text,
48 | onFinished,
49 | }) => {
50 | const ref = useRef(null);
51 | const { width } = useComponentSize(ref);
52 |
53 | return (
54 |
55 |
56 |
57 |
58 | {text}
59 |
60 |
61 |
62 |
63 | );
64 | };
65 |
66 | const pinkBubble = css`
67 | font-family: "Menlo", monospace;
68 | padding: 12px;
69 | color: black;
70 | border-radius: 3px;
71 | background-color: ${PINK};
72 | transform: skew(5deg, 3deg);
73 | display: inline-block;
74 | white-space: pre;
75 | font-size: 24px;
76 | align-self: flex-end;
77 | box-shadow: -2px 2px 7px rgba(0, 0, 0, 0.05);
78 | `;
79 |
80 | const rootStyle = css`
81 | display: flex;
82 | flex-direction: column;
83 | `;
84 |
85 | export const SpeechBubbleB: React.FC = ({
86 | text,
87 | onFinished,
88 | }) => {
89 | const ref = useRef(null);
90 | const { width } = useComponentSize(ref);
91 |
92 | return (
93 |
94 |
95 |
96 |
97 | {text}
98 |
99 |
100 |
101 |
102 | );
103 | };
104 |
105 | const chatRoot = css`
106 | display: flex;
107 | flex-direction: column;
108 | max-width: 50em;
109 | `;
110 |
111 | type ChatProps = {
112 | onFinished: () => void;
113 | };
114 |
115 | const Chat: React.FC = ({ onFinished }) => {
116 | const [linesToShow, setLinesToShow] = React.useState(1);
117 |
118 | const setLines = (num: number) => {
119 | setTimeout(() => {
120 | setLinesToShow(num);
121 | }, 300);
122 | };
123 |
124 | return (
125 |
126 | setLines(2)}
129 | />
130 | {linesToShow >= 2 && (
131 | setLines(3)}
134 | />
135 | )}
136 | {linesToShow >= 3 && (
137 | setLines(4)}
140 | />
141 | )}
142 | {linesToShow >= 4 && (
143 |
147 | )}
148 |
149 | );
150 | };
151 |
152 | export default Chat;
153 |
--------------------------------------------------------------------------------
/src/content/Guides.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import GuidesHelp from "../guides/GuidesHelp";
3 | import Install from "../guides/Install";
4 | import HookIntro from "../guides/HookIntro";
5 | import StyledText from "../guides/StyledText";
6 | import WindupsWithAnything from "../guides/WindupsWithAnything";
7 | import StylingCharacters from "../guides/StylingCharacters";
8 | import Timing from "../guides/Timing";
9 | import SkipRewind from "../guides/SkipRewind";
10 | import Effects from "../guides/Effects";
11 | import Linebreaking from "../guides/Linebreaking";
12 | import SectionFocusContext from "../SectionFocusContext";
13 | import Heading from "../Heading";
14 | import ChangingValues from "../guides/ChangingValues";
15 | import Accessibility from "../guides/Accessibility";
16 | import LineBreakingWithStyle from "../guides/LinebreakingWithStyle";
17 |
18 | const Guides: React.FC = () => {
19 | const [activeSectionID, setActiveSectionID] = React.useState(
20 | null
21 | );
22 |
23 | return (
24 |
30 | {"Guides"}
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 | );
46 | };
47 |
48 | export default Guides;
49 |
--------------------------------------------------------------------------------
/src/guides/Accessibility.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import Section from "../Section";
3 | import Dialog from "../Dialog";
4 | import Frog from "../performers/Frog";
5 | import { Emphasis } from "../Char";
6 | import CodeExample from "../CodeExample";
7 |
8 | const REDUCED_MOTION_EXAMPLE = `import { useWindupString } from "windups";
9 | import { usePrefersReducedMotion } from "../a11y-hooks";
10 | // Good example at https://joshwcomeau.com/snippets/react-hooks/use-prefers-reduced-motion
11 |
12 | const SkipForReducedMotion = () => {
13 | const prefersReducedMotion = usePrefersReducedMotion();
14 | const [text] = useWindupString("Respect user preferences!", {
15 | skipped: prefersReducedMotion,
16 | });
17 |
18 | return {text}
;
19 | };`;
20 |
21 | const SCREENREADER_EXAMPLE = `import { useWindupString } from "windups";
22 | import { VisuallyHidden } from "@reach/visually-hidden";
23 |
24 | const AccessibleWindupString = ({ text }) => {
25 | const [windupText] = useWindupString(text);
26 |
27 | return (
28 | <>
29 | {text}
30 | {windupText}
31 | >
32 | )
33 | }
34 | `;
35 |
36 | const SCREENREADER_CHILDREN_EXAMPLE = `import { WindupChildren, textFromChildren } from "windups";
37 | import { VisuallyHidden } from "@reach/visually-hidden";
38 |
39 | const AccessibleWindupChildren = ({ children }) => {
40 | const text = textFromChildren(children);
41 |
42 | return (
43 | <>
44 | {text}
45 |
46 |
47 | {text}
48 |
49 |
50 | >
51 | )
52 | }
53 | `;
54 |
55 | const Accessibility: React.FC = () => {
56 | return (
57 |
58 |
121 |
122 | );
123 | };
124 |
125 | export default Accessibility;
126 |
--------------------------------------------------------------------------------
/src/guides/ChangingValues.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useContext } from "react";
2 | import Section from "../Section";
3 | import Frog, { MadExpression, HappyExpression } from "../performers/Frog";
4 | import CodeExample from "../CodeExample";
5 | import { Emphasis } from "../Char";
6 | import Dialog, { DialogChildContext } from "../Dialog";
7 | import { WindupChildren, Pause } from "windups";
8 | import Example from "../Example";
9 | import { TEXT_PINK } from "../colours";
10 | import SmashEffect from "../SmashEffect";
11 |
12 | const TRICKY_EXAMPLE = `const TrickyWindup = () => {
13 | const [isPink, setIsPink] = useState(false);
14 |
15 | return (
16 | <>
17 | setIsPink(true)}>{"Make it pink!"}
18 |
19 |
20 | {"Click the button to turn this pink! Or not?!"}
21 |
22 |
23 | >
24 | );
25 | };
26 | `;
27 |
28 | const TrickyWindup = () => {
29 | const { proceed } = useContext(DialogChildContext);
30 | const [isPink, setIsPink] = useState(false);
31 |
32 | return (
33 | <>
34 | setIsPink(true)}>{"Make it pink!"}
35 |
36 |
37 | {"Click the button to turn this pink! Or not?!"}
38 |
39 |
40 | >
41 | );
42 | };
43 |
44 | const FIXED_EXAMPLE = `const FixedWindup = () => {
45 | const [isPink, setIsPink] = useState(false);
46 |
47 | return (
48 | <>
49 | setIsPink(true)}>{"Make it pink!"}
50 |
51 |
55 | {"Click the button to turn this pink! Or not?!"}
56 |
57 |
58 | >
59 | );
60 | };
61 | `;
62 |
63 | const FixedWindup = () => {
64 | const { proceed } = useContext(DialogChildContext);
65 | const [isPink, setIsPink] = useState(false);
66 |
67 | return (
68 | <>
69 | setIsPink(true)}>{"Make it pink!"}
70 |
71 |
75 | {"Click the button to turn this pink! Or not?!"}
76 |
77 |
78 | >
79 | );
80 | };
81 |
82 | const ChangingValues: React.FC = () => {
83 | return (
84 |
85 |
172 |
173 | );
174 | };
175 |
176 | export default ChangingValues;
177 |
--------------------------------------------------------------------------------
/src/guides/Effects.tsx:
--------------------------------------------------------------------------------
1 | import React, { useContext } from "react";
2 | import Section from "../Section";
3 | import Dialog, { DialogChildContext } from "../Dialog";
4 | import Frog, { HappyExpression } from "../performers/Frog";
5 | import SmashEffect from "../SmashEffect";
6 | import { useWindupString, WindupChildren, OnChar } from "windups";
7 | import Example from "../Example";
8 | import CodeExample from "../CodeExample";
9 | import { Emphasis } from "../Char";
10 | import { SFXContext } from "../App";
11 |
12 | const ON_FINISH_HOOK_EXAMPLE = `import { useWindupString } from "windups";
13 |
14 | const OnFinishHookExample = () => {
15 | const [text] = useWindupString(
16 | "When this text finishes, I'll alert you. I'm sorry.",
17 | {
18 | onFinished: () => {
19 | alert("Finished!");
20 | }
21 | }
22 | );
23 |
24 | return {text}
;
25 | }
26 | `;
27 |
28 | const OnFinishHookExample = () => {
29 | const { proceed } = useContext(DialogChildContext);
30 |
31 | const [text] = useWindupString(
32 | "When this text finishes, I'll alert you. I'm sorry.",
33 | {
34 | onFinished: () => {
35 | alert("Finished!");
36 | proceed();
37 | },
38 | }
39 | );
40 |
41 | return {text}
;
42 | };
43 |
44 | const EFFECT_EXAMPLE = `import { useWindupString } from "windups";
45 | import { smash } from 'gratuitous-fx';
46 |
47 | const EffectExample = () => {
48 | return (
49 |
50 | {"I carefully steadied my aim... and "}
51 |
52 | {"struck! The carrot was cut clean in half."}
53 |
54 | );
55 | };
56 | `;
57 |
58 | const EffectExample = () => {
59 | const { proceed } = useContext(DialogChildContext);
60 |
61 | return (
62 |
63 | {"I carefully steadied my aim... and "}
64 |
65 | {"struck! The carrot was cut clean in half."}
66 |
67 | );
68 | };
69 |
70 | const ON_CHAR_EXAMPLE = `import { WindupChildren } from "windups";
71 |
72 | const ContrivedOnCharExample = () => {
73 | const [count, setCount] = React.useState(0);
74 | const [text] = useWindupString(
75 | "You'll probably want to do something more interesting than this!",
76 | {
77 | onChar: () => setCount(prev => prev + 1),
78 | }
79 | );
80 |
81 | return (
82 |
83 |
{text}
84 |
{"Characters printed: "}{count}
85 |
86 | );
87 | };
88 | `;
89 |
90 | const ContrivedOnCharExample = () => {
91 | const { proceed } = useContext(DialogChildContext);
92 | const [count, setCount] = React.useState(0);
93 | const [text] = useWindupString(
94 | "You'll probably want to do something more interesting than this!",
95 | {
96 | onChar: () => setCount((prev) => prev + 1),
97 | onFinished: proceed,
98 | }
99 | );
100 |
101 | return (
102 |
103 |
{text}
104 |
{`Characters printed: ${count}`}
105 |
106 | );
107 | };
108 |
109 | const ON_CHAR_CHILDREN_EXAMPLE = `import { WindupChildren, OnChar } from "windups";
110 | import { smash } from "gratuitous-fx";
111 |
112 | const OnCharChildrenExample = () => {
113 | const { smash } = useContext(SFXContext);
114 |
115 | return (
116 |
117 | {"Hey! Use the brakes! "}
118 | {"Noooo!"}
119 |
120 | );
121 | };
122 | `;
123 |
124 | const OnCharChildrenExample = () => {
125 | const { smash } = useContext(SFXContext);
126 | const { proceed } = useContext(DialogChildContext);
127 |
128 | return (
129 |
130 | {"Hey! Use the brakes! "}
131 | {"Noooo!"}
132 |
133 | );
134 | };
135 |
136 | const Effects = () => {
137 | return (
138 |
139 |
207 |
208 | );
209 | };
210 |
211 | export default Effects;
212 |
--------------------------------------------------------------------------------
/src/guides/GuidesHelp.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import Section from "../Section";
3 | import Dialog from "../Dialog";
4 | import Frog from "../performers/Frog";
5 | import { Pace, Pause } from "windups";
6 | import SmashEffect from "../SmashEffect";
7 | import { Emphasis, Dropped } from "../Char";
8 |
9 | function getRandomArbitrary(min: number, max: number) {
10 | return Math.random() * (max - min) + min;
11 | }
12 |
13 | const GuidesHelp: React.FC = () => {
14 | return (
15 |
16 |
62 |
63 | );
64 | };
65 |
66 | export default GuidesHelp;
67 |
--------------------------------------------------------------------------------
/src/guides/HookIntro.tsx:
--------------------------------------------------------------------------------
1 | import React, { useContext } from "react";
2 | import Section from "../Section";
3 | import Dialog, { DialogChildContext } from "../Dialog";
4 | import Frog, { SmugExpression } from "../performers/Frog";
5 | import SmashEffect from "../SmashEffect";
6 | import CodeExample from "../CodeExample";
7 | import Example from "../Example";
8 | import { useWindupString } from "windups";
9 | import { Emphasis } from "../Char";
10 |
11 | const HOOK_EXAMPLE = `import React from "react";
12 | import { useWindupString } from "windups";
13 |
14 | // Make a new component
15 | const StringyWindup = () => {
16 | const [text] = useWindupString("Hello world!");
17 |
18 | return {text}
;
19 | };`;
20 |
21 | const StringyWindup = () => {
22 | const { proceed } = useContext(DialogChildContext);
23 |
24 | const [text] = useWindupString("Hello world!", {
25 | onFinished: proceed,
26 | });
27 |
28 | return {text}
;
29 | };
30 |
31 | const HookIntro: React.FC = () => {
32 | return (
33 |
34 |
74 |
75 | );
76 | };
77 |
78 | export default HookIntro;
79 |
--------------------------------------------------------------------------------
/src/guides/Install.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import Section from "../Section";
3 | import Dialog from "../Dialog";
4 | import Frog, { ShameExpression, HappyExpression } from "../performers/Frog";
5 | import CodeExample from "../CodeExample";
6 | import { Emphasis } from "../Char";
7 |
8 | const Install: React.FC = () => {
9 | return (
10 |
11 |
50 |
51 | );
52 | };
53 |
54 | export default Install;
55 |
--------------------------------------------------------------------------------
/src/guides/Linebreaking.tsx:
--------------------------------------------------------------------------------
1 | import React, { useContext } from "react";
2 | import Section from "../Section";
3 | import Dialog, { DialogChildContext } from "../Dialog";
4 | import Frog, { ShameExpression } from "../performers/Frog";
5 | import { useWindupString, WindupChildren, Linebreaker, Pace } from "windups";
6 | import Example from "../Example";
7 | import CodeExample from "../CodeExample";
8 | import SmashEffect from "../SmashEffect";
9 | import { Emphasis } from "../Char";
10 | import useSize from "@rehooks/component-size";
11 |
12 | const BadlyBreakingLine = () => {
13 | const { proceed } = useContext(DialogChildContext);
14 | const [text] = useWindupString("Is line-breaking necessary?", {
15 | pace: () => 200,
16 | onFinished: proceed,
17 | });
18 |
19 | return {text}
;
20 | };
21 |
22 | const FULL_EXAMPLE = `import { Linebreaker, WindupChildren } from "windups";
23 |
24 | const GoodBreakingLine = () => {
25 | return (
26 |
27 |
28 |
29 | {"Is line-breaking necessary?"}
30 |
31 |
32 |
33 | );
34 | }
35 | `;
36 |
37 | const GoodBreakingLine = () => {
38 | const divRef = React.useRef(null);
39 | const { width } = useSize(divRef);
40 | const { proceed } = useContext(DialogChildContext);
41 |
42 | return (
43 |
44 |
45 |
46 | {"Is line-breaking necessary?"}
47 |
48 |
49 |
50 | );
51 | };
52 |
53 | const STEP_ONE = `
54 |
55 | {"Is line-breaking necessary?"}
56 |
57 |
58 | `;
59 |
60 | const STEP_TWO = `
61 |
62 |
63 | {"Is line-breaking necessary?"}
64 |
65 |
66 |
67 | `;
68 |
69 | const Linebreaking = () => {
70 | return (
71 |
72 |
163 |
164 | );
165 | };
166 |
167 | export default Linebreaking;
168 |
--------------------------------------------------------------------------------
/src/guides/LinebreakingWithStyle.tsx:
--------------------------------------------------------------------------------
1 | import React, { useContext } from "react";
2 | import Section from "../Section";
3 | import Dialog, { DialogChildContext } from "../Dialog";
4 | import Frog, {
5 | ShameExpression,
6 | MadExpression,
7 | HappyExpression,
8 | } from "../performers/Frog";
9 | import { WindupChildren, Linebreaker, Pace, Pause, StyledText } from "windups";
10 | import Example from "../Example";
11 | import CodeExample from "../CodeExample";
12 | import SmashEffect from "../SmashEffect";
13 | import { Emphasis, Thinking } from "../Char";
14 | import useSize from "@rehooks/component-size";
15 |
16 | const BROKEN_EXAMPLE = `
17 |
18 | {"Let's talk about the "}
19 | {"elephant"}
20 | {" in the room..."}
21 |
22 | `;
23 |
24 | const FIXED_EXAMPLE = `
25 |
26 | {"Let's talk about the "}
27 |
28 | {"elephant"}
29 |
30 | {" in the room..."}
31 |
32 | `;
33 |
34 | const VanillaBreakingLine = () => {
35 | const divRef = React.useRef(null);
36 | const { width } = useSize(divRef);
37 | const { proceed } = useContext(DialogChildContext);
38 |
39 | return (
40 |
41 |
42 |
43 | {"Let's talk about the elephant in the room..."}
44 |
45 |
46 |
47 | );
48 | };
49 |
50 | const BadBreakingLine = () => {
51 | const divRef = React.useRef(null);
52 | const { width } = useSize(divRef);
53 | const { proceed } = useContext(DialogChildContext);
54 |
55 | return (
56 |
57 |
58 |
59 |
60 | {"Let's talk about the "}
61 | {"elephant"}
62 | {" in the room..."}
63 |
64 |
65 |
66 |
67 | );
68 | };
69 |
70 | const StyledBreakingLine = () => {
71 | const divRef = React.useRef(null);
72 | const { width } = useSize(divRef);
73 | const { proceed } = useContext(DialogChildContext);
74 |
75 | return (
76 |
77 |
78 |
79 |
80 | {"Let's talk about the "}
81 |
82 | {"elephant"}
83 |
84 | {" in the room..."}
85 |
86 |
87 |
88 |
89 | );
90 | };
91 |
92 | const SeveralHoursLater = () => {
93 | const { proceed } = useContext(DialogChildContext);
94 | const divRef = React.useRef(null);
95 | const { width } = useSize(divRef);
96 |
97 | return (
98 |
99 |
108 |
109 | {"SEVERAL HOURS LATER..."}
110 |
111 |
112 |
113 |
114 | );
115 | };
116 |
117 | const LinebreakingWithStyle = () => {
118 | return (
119 |
120 |
206 |
207 | );
208 | };
209 |
210 | export default LinebreakingWithStyle;
211 |
--------------------------------------------------------------------------------
/src/guides/SkipRewind.tsx:
--------------------------------------------------------------------------------
1 | import React, { useContext } from "react";
2 | import Section from "../Section";
3 | import Dialog, { DialogChildContext } from "../Dialog";
4 | import Frog, { ShockExpression } from "../performers/Frog";
5 | import SmashEffect from "../SmashEffect";
6 | import { useWindupString, WindupChildren, useSkip, Pace } from "windups";
7 | import Example from "../Example";
8 | import CodeExample from "../CodeExample";
9 | import { Emphasis } from "../Char";
10 |
11 | const SKIP_HOOK_EXAMPLE = `import { useWindupString } from "windups";
12 |
13 | const SkipHookExample = () => {
14 | const [text, { skip }] = useWindupString(
15 | "A fly? A fly! Why, oh why, would one cry for a fly?",
16 | {
17 | pace: () => 400,
18 | onFinished: proceed,
19 | }
20 | );
21 |
22 | return (
23 |
24 |
{text}
25 |
{"Skip ahead"}
26 |
27 | );
28 | }`;
29 |
30 | const SkipHookExample = () => {
31 | const { proceed } = useContext(DialogChildContext);
32 |
33 | const [text, { skip }] = useWindupString(
34 | "A fly? A fly! Why, oh why, would one cry for a fly?",
35 | {
36 | pace: () => 400,
37 | onFinished: proceed
38 | }
39 | );
40 |
41 | return (
42 |
43 |
{text}
44 |
{"Skip ahead"}
45 |
46 | );
47 | };
48 |
49 | const SKIP_CHILDREN_EXAMPLE = `import { WindupChildren, useSkip } from "windups";
50 |
51 | const SkipButton = () => {
52 | const skip = useSkip();
53 |
54 | return {"Skip"};
55 | };
56 |
57 | const SkippableWindupChildren = () => {
58 | const { proceed } = useContext(DialogChildContext);
59 |
60 | return (
61 |
62 |
63 |
64 |
65 | {"Why, if one would lie on a fly it would assuredly die."}
66 |
67 |
68 |
69 | );
70 | };`;
71 |
72 | const SkipButton = () => {
73 | const skip = useSkip();
74 |
75 | return {"Skip"};
76 | };
77 |
78 | const SkippableWindupChildren = () => {
79 | return (
80 |
81 |
82 |
83 |
84 | {"Why, if one would lie on a fly it would assuredly die."}
85 |
86 |
87 |
88 | );
89 | };
90 |
91 | const SkipAndRewind = () => {
92 | return (
93 |
94 |
187 |
188 | );
189 | };
190 |
191 | export default SkipAndRewind;
192 |
--------------------------------------------------------------------------------
/src/guides/StyledText.tsx:
--------------------------------------------------------------------------------
1 | import React, { useContext } from "react";
2 | import Section from "../Section";
3 | import Dialog, { DialogChildContext } from "../Dialog";
4 | import Frog, { HappyExpression } from "../performers/Frog";
5 | import SmashEffect from "../SmashEffect";
6 | import CodeExample from "../CodeExample";
7 | import { TEXT_PINK } from "../colours";
8 | import { useWindupString, WindupChildren } from "windups";
9 | import Example from "../Example";
10 | import { Emphasis } from "../Char";
11 |
12 | const UNSTYLED_EXAMPLE = `import React from "react";
13 | import { useWindupString } from "windups";
14 |
15 | const DressCodeWarning = () => {
16 | const { proceed } = useContext(DialogChildContext);
17 | const [text] = useWindupString(
18 | "This club admits only those wearing bright pink hats."
19 | );
20 |
21 | return {text}
;
22 | };`;
23 |
24 | const UnstyledExample: React.FC = () => {
25 | const { proceed } = useContext(DialogChildContext);
26 | const [text] = useWindupString(
27 | "This club admits only those wearing bright pink hats.",
28 | {
29 | onFinished: proceed
30 | }
31 | );
32 |
33 | return {text}
;
34 | };
35 |
36 | const STYLED_EXAMPLE = `import React from "react";
37 | import { WindupChildren } from "windups";
38 |
39 | const StyledExample = () => {
40 | return (
41 |
42 | {"This club admits only those wearing "}
43 | {"bright pink"}
44 | {" hats."}
45 |
46 | );
47 | };`;
48 |
49 | const StyledExample = () => {
50 | const { proceed } = useContext(DialogChildContext);
51 |
52 | return (
53 |
54 | {"This club admits only those wearing "}
55 | {"bright pink"}
56 | {" hats."}
57 |
58 | );
59 | };
60 |
61 | const GuideName = () => {
62 | return (
63 |
64 |
122 |
123 | );
124 | };
125 |
126 | export default GuideName;
127 |
--------------------------------------------------------------------------------
/src/guides/StylingCharacters.tsx:
--------------------------------------------------------------------------------
1 | import React, { useContext } from "react";
2 | import Section from "../Section";
3 | import Dialog, { DialogChildContext } from "../Dialog";
4 | import Frog, { ShockExpression, ShameExpression } from "../performers/Frog";
5 | import CodeExample from "../CodeExample";
6 | import { useWindupString, CharWrapper, WindupChildren, Pace } from "windups";
7 | import SmashEffect from "../SmashEffect";
8 | import { Emphasis } from "../Char";
9 | import { css } from "linaria";
10 | import Example from "../Example";
11 |
12 | const BORING_EXAMPLE = `import React from "react";
13 | import { useWindupString } from "windups";
14 |
15 | const VanillaWindup = () => {
16 | const [text] = useWindupString("Baked Beans On Toast");
17 |
18 | return {text}
;
19 | };`;
20 |
21 | const STRING_EXAMPLE = `import React from "react";
22 | import { useWindupString, CharWrapper } from "windups";
23 | import { css } from "unnamed-styling-library";
24 |
25 | // Our animation CSS. A slow fade in.
26 | const fadeInAnimationStyle = css\`
27 | @keyframes fadeIn {
28 | from {
29 | opacity: 0;
30 | }
31 | to {
32 | opacity: 1;
33 | }
34 | }
35 | animation-name: fadeIn;
36 | animation-duration: 3s;
37 | animation-iteration-count: 1;
38 | \`;
39 |
40 | // A component to wrap around each character.
41 | const SpookyChar = ({children}) => {
42 | return {children}
43 | }
44 |
45 | // Our windup
46 | const GhostlyWindup = () => {
47 | const [text] = useWindupString("Baked Beans On Toast");
48 |
49 | return {text}
;
50 | };`;
51 |
52 | const WITHOUT_CHARWRAPPER_EXAMPLE = `return (
53 |
54 | {text.split("").map(char => (
55 | {char}
56 | ))}
57 |
58 | );`;
59 |
60 | const WINDUP_CHILDREN_EXAMPLE = `import React from "react";
61 | import { WindupChildren, CharWrapper } from "windups";
62 | import { SpookyChar } from "./that-last-example";
63 |
64 | const SpookyEmphasisedWindup = () => {
65 | return (
66 |
67 |
68 | {"Baked "}
69 | {"beans"}
70 | {" on toast"}
71 |
72 |
73 | )
74 | }
75 | `;
76 |
77 | // Our animation CSS. A simple fade.
78 | const fadeInAnimationStyle = css`
79 | @keyframes fadeIn {
80 | from {
81 | opacity: 0;
82 | }
83 | to {
84 | opacity: 1;
85 | }
86 | }
87 | animation-name: fadeIn;
88 | animation-duration: 3s;
89 | animation-iteration-count: 1;
90 | `;
91 |
92 | const VanillaWindup = () => {
93 | const { proceed } = useContext(DialogChildContext);
94 | const [text] = useWindupString("Baked Beans On Toast", {
95 | onFinished: proceed
96 | });
97 |
98 | return {text}
;
99 | };
100 |
101 | const SpookyChar: React.FC = ({ children }) => {
102 | return {children};
103 | };
104 |
105 | const GhostlyWindup = () => {
106 | const { proceed } = useContext(DialogChildContext);
107 |
108 | const [text] = useWindupString("Baked Beans On Toast", {
109 | onFinished: proceed,
110 | pace: () => 200
111 | });
112 |
113 | return (
114 |
115 | {text}
116 |
117 | );
118 | };
119 |
120 | const SpookyEmphasisedWindup = () => {
121 | const { proceed } = useContext(DialogChildContext);
122 |
123 | return (
124 |
125 |
126 |
127 | {"Baked "}
128 | {"beans"}
129 | {" on toast"}
130 |
131 |
132 |
133 | );
134 | };
135 |
136 | const StylingCharacters: React.FC = () => {
137 | return (
138 |
142 |
234 |
235 | );
236 | };
237 |
238 | export default StylingCharacters;
239 |
--------------------------------------------------------------------------------
/src/guides/Timing.tsx:
--------------------------------------------------------------------------------
1 | import React, { useContext } from "react";
2 | import Section from "../Section";
3 | import Dialog, { DialogChildContext } from "../Dialog";
4 | import Frog, { ShameExpression, HappyExpression } from "../performers/Frog";
5 | import CodeExample from "../CodeExample";
6 | import { Pause, useWindupString, WindupChildren, Pace } from "windups";
7 | import Example from "../Example";
8 | import { Emphasis } from "../Char";
9 |
10 | const PACE_HOOK_EXAMPLE = `import React from "react";
11 | import { useWindupString } from "windups";
12 |
13 | const ThinkingHard = () => {
14 | const [text] = useWindupString("I'm thinking really hard.", {
15 | pace: (char) => (char === " " ? 600 : 40),
16 | });
17 |
18 | return {text}
;
19 | };`;
20 |
21 | const PACE_COMPONENT_EXAMPLE = `import React from "react";
22 | import { Pace, WindupChildren } from "windups";
23 |
24 | const SassyThinkingHard = () => {
25 | return (
26 |
27 | {"Didn't you hear me the first time? "}
28 | (char === " " ? 600 : 40)}>
29 | {"I'm thinking really hard."}
30 |
31 |
32 | );
33 | };`;
34 |
35 | const RobotExample = () => {
36 | const { proceed } = useContext(DialogChildContext);
37 | const [text] = useWindupString(
38 | "Hello friend. I am definitely a living thing and not a computer. Ha ha ha",
39 | {
40 | pace: () => 70,
41 | onFinished: proceed
42 | }
43 | );
44 |
45 | return {text}
;
46 | };
47 |
48 | const BetterExample = () => {
49 | const { proceed } = useContext(DialogChildContext);
50 | const [text] = useWindupString(
51 | "Hello friend. I am definitely a living thing and not a computer. Ha ha ha",
52 | {
53 | onFinished: proceed
54 | }
55 | );
56 |
57 | return {text}
;
58 | };
59 |
60 | const PaceStringExample = () => {
61 | const { proceed } = useContext(DialogChildContext);
62 |
63 | const [text] = useWindupString("I'm thinking really hard.", {
64 | onFinished: proceed,
65 | pace: char => (char === " " ? 600 : 40)
66 | });
67 |
68 | return {text}
;
69 | };
70 |
71 | const PaceComponentExample = () => {
72 | const { proceed } = useContext(DialogChildContext);
73 |
74 | return (
75 |
76 | {"Didn't you hear me the first time? "}
77 | (char === " " ? 600 : 40)}>
78 | {"I'm thinking really hard."}
79 |
80 |
81 | );
82 | };
83 |
84 | const PAUSED_CODE_EXAMPLE = `import { WindupChildren, Pause } from "windups";
85 |
86 | const PausedExample = () => {
87 | return (
88 |
89 |
90 | {
91 | "I asked her: why did you do it?
92 | Why did you tear apart the only lily pad I'd ever know as home?"
93 | }
94 |
95 |
96 |
97 | {
98 | "She looked back at me with those
99 | froggy little eyes of hers and croaked one word:"
100 | }
101 |
102 |
103 |
104 | {"Pleasure."}
105 |
106 |
107 | );
108 | };`;
109 |
110 | const UntimedExample = () => {
111 | const { proceed } = useContext(DialogChildContext);
112 |
113 | return (
114 |
115 |
116 | {
117 | "I asked her: why did you do it? Why did you tear apart the only lily pad I'd ever know as home?"
118 | }
119 |
120 |
121 | {
122 | "She looked back at me with those froggy little eyes of hers and croaked one word:"
123 | }
124 |
125 |
126 | {"Pleasure."}
127 |
128 |
129 | );
130 | };
131 |
132 | const PausedExample = () => {
133 | const { proceed } = useContext(DialogChildContext);
134 |
135 | return (
136 |
137 |
138 | {
139 | "I asked her: why did you do it? Why did you tear apart the only lily pad I'd ever know as home?"
140 | }
141 |
142 |
143 |
144 | {
145 | "She looked back at me with those froggy little eyes of hers and croaked one word:"
146 | }
147 |
148 |
149 |
150 | {"Pleasure."}
151 |
152 |
153 | );
154 | };
155 |
156 | const PacingPausing = () => {
157 | return (
158 |
159 |
258 |
259 | );
260 | };
261 |
262 | export default PacingPausing;
263 |
--------------------------------------------------------------------------------
/src/guides/WindupsWithAnything.tsx:
--------------------------------------------------------------------------------
1 | import React, { useContext } from "react";
2 | import Section from "../Section";
3 | import Dialog, { DialogChildContext } from "../Dialog";
4 | import Frog, { HappyExpression } from "../performers/Frog";
5 | import CodeExample from "../CodeExample";
6 | import Example from "../Example";
7 | import { WindupChildren } from "windups";
8 | import Auntie from "../images/auntie.svg";
9 |
10 | const PIC_EXAMPLE = `import React from "react";
11 | import { WindupChildren } from "windups";
12 |
13 | const AuntieWindup = () => {
14 | return (
15 |
16 |
17 | {"My dear auntie"}
18 |
19 |
20 |
21 | {"The world's kindest frog"}
22 |
23 |
24 | );
25 | };`;
26 |
27 | const COMPOSED_EXAMPLE = `import React from "react";
28 | import { WindupChildren } from "windups";
29 |
30 | const ComponentWithInlineText = ({children}) => {
31 | return (
32 | <>
33 | {"This text will render immediately!"}
34 | {children}
35 | >
36 | )
37 | }
38 |
39 | const ComposedWindup = () => {
40 | return (
41 |
42 |
43 | {"This text will become part of the windup."}
44 |
45 |
46 | );
47 | };`;
48 |
49 | // Real stuff starts here
50 |
51 | const AuntieWindup = () => {
52 | const { proceed } = useContext(DialogChildContext);
53 | return (
54 |
55 | {"My dear auntie"}
56 |
57 | {"The world's kindest frog"}
58 |
59 | );
60 | };
61 |
62 | const ComponentWithInlineText: React.FC = ({ children }) => {
63 | return (
64 | <>
65 | {"This text will render immediately!"}
66 | {children}
67 | >
68 | );
69 | };
70 |
71 | const ComposedWindup = () => {
72 | const { proceed } = useContext(DialogChildContext);
73 |
74 | return (
75 |
76 |
77 | {"This text will become part of the windup."}
78 |
79 |
80 | );
81 | };
82 |
83 | const WindupsWithAnything: React.FC = () => {
84 | return (
85 |
86 |
148 |
149 | );
150 | };
151 |
152 | export default WindupsWithAnything;
153 |
--------------------------------------------------------------------------------
/src/images/auntie.svg:
--------------------------------------------------------------------------------
1 |
13 |
--------------------------------------------------------------------------------
/src/images/compass-menu.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/src/images/forks.svg:
--------------------------------------------------------------------------------
1 |
7 |
--------------------------------------------------------------------------------
/src/images/frog-menu.svg:
--------------------------------------------------------------------------------
1 |
7 |
--------------------------------------------------------------------------------
/src/images/frog/Group 12-1.svg:
--------------------------------------------------------------------------------
1 |
6 |
--------------------------------------------------------------------------------
/src/images/frog/Group 12-2.svg:
--------------------------------------------------------------------------------
1 |
6 |
--------------------------------------------------------------------------------
/src/images/frog/Group 12.svg:
--------------------------------------------------------------------------------
1 |
7 |
--------------------------------------------------------------------------------
/src/images/frog/Group 13-1.svg:
--------------------------------------------------------------------------------
1 |
8 |
--------------------------------------------------------------------------------
/src/images/frog/Group 13-2.svg:
--------------------------------------------------------------------------------
1 |
8 |
--------------------------------------------------------------------------------
/src/images/frog/Group 13.svg:
--------------------------------------------------------------------------------
1 |
9 |
--------------------------------------------------------------------------------
/src/images/frog/f-cool-open-1.svg:
--------------------------------------------------------------------------------
1 |
11 |
--------------------------------------------------------------------------------
/src/images/frog/f-cool-open-2.svg:
--------------------------------------------------------------------------------
1 |
9 |
--------------------------------------------------------------------------------
/src/images/frog/f-cool-resting.svg:
--------------------------------------------------------------------------------
1 |
12 |
--------------------------------------------------------------------------------
/src/images/frog/f-laff-open-1.svg:
--------------------------------------------------------------------------------
1 |
8 |
--------------------------------------------------------------------------------
/src/images/frog/f-laff-open-2.svg:
--------------------------------------------------------------------------------
1 |
8 |
--------------------------------------------------------------------------------
/src/images/frog/f-laff-resting.svg:
--------------------------------------------------------------------------------
1 |
9 |
--------------------------------------------------------------------------------
/src/images/frog/f-mad-open-1.svg:
--------------------------------------------------------------------------------
1 |
15 |
--------------------------------------------------------------------------------
/src/images/frog/f-mad-open-2.svg:
--------------------------------------------------------------------------------
1 |
15 |
--------------------------------------------------------------------------------
/src/images/frog/f-mad-resting.svg:
--------------------------------------------------------------------------------
1 |
9 |
--------------------------------------------------------------------------------
/src/images/frog/f-norm-open-1.svg:
--------------------------------------------------------------------------------
1 |
8 |
--------------------------------------------------------------------------------
/src/images/frog/f-norm-open-2.svg:
--------------------------------------------------------------------------------
1 |
8 |
--------------------------------------------------------------------------------
/src/images/frog/f-norm-resting.svg:
--------------------------------------------------------------------------------
1 |
9 |
--------------------------------------------------------------------------------
/src/images/frog/f-shame-open-1.svg:
--------------------------------------------------------------------------------
1 |
9 |
--------------------------------------------------------------------------------
/src/images/frog/f-shame-open-2.svg:
--------------------------------------------------------------------------------
1 |
9 |
--------------------------------------------------------------------------------
/src/images/frog/f-shame-resting.svg:
--------------------------------------------------------------------------------
1 |
10 |
--------------------------------------------------------------------------------
/src/images/frog/f-shock-open-1.svg:
--------------------------------------------------------------------------------
1 |
10 |
--------------------------------------------------------------------------------
/src/images/frog/f-shock-open-2.svg:
--------------------------------------------------------------------------------
1 |
10 |
--------------------------------------------------------------------------------
/src/images/frog/f-shock-resting.svg:
--------------------------------------------------------------------------------
1 |
11 |
--------------------------------------------------------------------------------
/src/images/frog/f-smug-open-1.svg:
--------------------------------------------------------------------------------
1 |
8 |
--------------------------------------------------------------------------------
/src/images/frog/f-smug-open-2.svg:
--------------------------------------------------------------------------------
1 |
8 |
--------------------------------------------------------------------------------
/src/images/frog/f-smug-resting.svg:
--------------------------------------------------------------------------------
1 |
9 |
--------------------------------------------------------------------------------
/src/images/gwilco.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/images/key-a.svg:
--------------------------------------------------------------------------------
1 |
7 |
--------------------------------------------------------------------------------
/src/images/key-b.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/images/key-c.svg:
--------------------------------------------------------------------------------
1 |
9 |
--------------------------------------------------------------------------------
/src/images/key-d.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/images/key-menu.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/images/keyboard-menu.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/images/little-hand.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/images/nexters.svg:
--------------------------------------------------------------------------------
1 |
9 |
--------------------------------------------------------------------------------
/src/images/point.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/images/repo-menu.svg:
--------------------------------------------------------------------------------
1 |
8 |
--------------------------------------------------------------------------------
/src/images/ruffle-pizza.svg:
--------------------------------------------------------------------------------
1 |
13 |
--------------------------------------------------------------------------------
/src/images/snowman-kebab.svg:
--------------------------------------------------------------------------------
1 |
19 |
--------------------------------------------------------------------------------
/src/index.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import ReactDOM from "react-dom";
3 | import App from "./App";
4 | import * as serviceWorker from "./serviceWorker";
5 |
6 | ReactDOM.render(, document.getElementById("root"));
7 |
8 | // If you want your app to work offline and load faster, you can change
9 | // unregister() to register() below. Note this comes with some pitfalls.
10 | // Learn more about service workers: https://bit.ly/CRA-PWA
11 | serviceWorker.unregister();
12 |
--------------------------------------------------------------------------------
/src/performers/Frog.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useContext, createContext } from "react";
2 | import Performer, { PerformerContext } from "../Performer";
3 | import { DialogElementProps } from "../DialogElement";
4 | import AvNormResting from "../images/frog/f-norm-resting.svg";
5 | import AvNorm1 from "../images/frog/f-norm-open-1.svg";
6 | import AvNorm2 from "../images/frog/f-norm-open-2.svg";
7 | import AvLaffResting from "../images/frog/f-laff-resting.svg";
8 | import AvLaff1 from "../images/frog/f-laff-open-1.svg";
9 | import AvLaff2 from "../images/frog/f-laff-open-2.svg";
10 | import AvMadResting from "../images/frog/f-mad-resting.svg";
11 | import AvMad1 from "../images/frog/f-mad-open-1.svg";
12 | import AvMad2 from "../images/frog/f-mad-open-2.svg";
13 | import AvSmugResting from "../images/frog/f-smug-resting.svg";
14 | import AvSmug1 from "../images/frog/f-smug-open-1.svg";
15 | import AvSmug2 from "../images/frog/f-smug-open-2.svg";
16 | import AvShameResting from "../images/frog/f-shame-resting.svg";
17 | import AvShame1 from "../images/frog/f-shame-open-1.svg";
18 | import AvShame2 from "../images/frog/f-shame-open-2.svg";
19 | import AvShockResting from "../images/frog/f-shock-resting.svg";
20 | import AvShock1 from "../images/frog/f-shock-open-1.svg";
21 | import AvShock2 from "../images/frog/f-shock-open-2.svg";
22 | import AvCoolResting from "../images/frog/f-cool-resting.svg";
23 | import AvCool1 from "../images/frog/f-cool-open-1.svg";
24 | import AvCool2 from "../images/frog/f-cool-open-2.svg";
25 | import { FrameSet } from "../Performer";
26 |
27 | import { Effect } from "windups";
28 |
29 | const normFrames = [AvNorm1, AvNorm2];
30 | const normRestingFrames = [AvNormResting];
31 | const laffFrames = [AvLaff1, AvLaff2];
32 | const laffRestingFrames = [AvLaffResting];
33 | const madFrames = [AvMad1, AvMad2];
34 | const madRestingFrames = [AvMadResting];
35 | const smugFrames = [AvSmug1, AvSmug2];
36 | const smugRestingFrames = [AvSmugResting];
37 | const shameFrames = [AvShame1, AvShame2];
38 | const shameRestingFrames = [AvShameResting];
39 | const shockFrames = [AvShock1, AvShock2];
40 | const shockRestingFrames = [AvShockResting];
41 | const coolFrames = [AvCool1, AvCool2];
42 | const coolRestingFrames = [AvCoolResting];
43 |
44 | type SetExpressionProps = {
45 | expression: FrogEmotion;
46 | };
47 |
48 | const SetExpression: React.FC = ({ expression }) => {
49 | const { setExpression } = useContext(FrogContext);
50 | const { setAvatarFrames } = useContext(PerformerContext);
51 | return (
52 | {
54 | setExpression(expression);
55 | setAvatarFrames(frogFrameSets[expression]);
56 | }}
57 | />
58 | );
59 | };
60 |
61 | function makeExpression(expr: FrogEmotion) {
62 | return () => ;
63 | }
64 |
65 | export const HappyExpression = makeExpression("HAPPY");
66 | export const SmugExpression = makeExpression("SMUG");
67 | export const ShameExpression = makeExpression("SHAME");
68 | export const ShockExpression = makeExpression("SHOCK");
69 | export const MadExpression = makeExpression("MAD");
70 |
71 | type FrogEmotion =
72 | | "NORMAL"
73 | | "HAPPY"
74 | | "MAD"
75 | | "SMUG"
76 | | "SHAME"
77 | | "SHOCK"
78 | | "COOL";
79 |
80 | type FrogFrameMap = Record;
81 |
82 | const frogFrameSets: FrogFrameMap = {
83 | NORMAL: { normal: normFrames, resting: normRestingFrames },
84 | HAPPY: { normal: laffFrames, resting: laffRestingFrames },
85 | MAD: { normal: madFrames, resting: madRestingFrames },
86 | SMUG: { normal: smugFrames, resting: smugRestingFrames },
87 | SHAME: { normal: shameFrames, resting: shameRestingFrames },
88 | SHOCK: { normal: shockFrames, resting: shockRestingFrames },
89 | COOL: { normal: coolFrames, resting: coolRestingFrames },
90 | };
91 |
92 | interface FrogProps extends DialogElementProps {
93 | expression?: FrogEmotion;
94 | silent?: boolean;
95 | }
96 |
97 | const FrogContext = createContext<{
98 | setExpression: React.Dispatch>;
99 | currentExpression: FrogEmotion;
100 | }>({
101 | setExpression: () => {},
102 | currentExpression: "NORMAL",
103 | });
104 |
105 | const Frog: React.FC = ({
106 | children,
107 | autoProceed,
108 | expression: initialExpression = "NORMAL",
109 | silent,
110 | }) => {
111 | const [currentExpression, setCurrentExpression] = useState(initialExpression);
112 |
113 | return (
114 |
117 |
122 |
123 | {children}
124 |
125 |
126 | );
127 | };
128 |
129 | export const usePreloadFrogFrames = () => {
130 | const frames = [
131 | ...normFrames,
132 | ...normRestingFrames,
133 | ...laffFrames,
134 | ...laffRestingFrames,
135 | ...madFrames,
136 | ...madRestingFrames,
137 | ...smugFrames,
138 | ...smugRestingFrames,
139 | ...shameFrames,
140 | ...shameRestingFrames,
141 | ...shockFrames,
142 | ...shockRestingFrames,
143 | ...coolFrames,
144 | ...coolRestingFrames,
145 | ];
146 |
147 | frames.forEach((source) => {
148 | const image = new Image();
149 | image.src = source;
150 | });
151 | };
152 |
153 | export default Frog;
154 |
--------------------------------------------------------------------------------
/src/react-app-env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
--------------------------------------------------------------------------------
/src/serviceWorker.ts:
--------------------------------------------------------------------------------
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 | type Config = {
24 | onSuccess?: (registration: ServiceWorkerRegistration) => void;
25 | onUpdate?: (registration: ServiceWorkerRegistration) => void;
26 | };
27 |
28 | export function register(config?: Config) {
29 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
30 | // The URL constructor is available in all browsers that support SW.
31 | const publicUrl = new URL(
32 | (process as { env: { [key: string]: string } }).env.PUBLIC_URL,
33 | window.location.href
34 | );
35 | if (publicUrl.origin !== window.location.origin) {
36 | // Our service worker won't work if PUBLIC_URL is on a different origin
37 | // from what our page is served on. This might happen if a CDN is used to
38 | // serve assets; see https://github.com/facebook/create-react-app/issues/2374
39 | return;
40 | }
41 |
42 | window.addEventListener('load', () => {
43 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
44 |
45 | if (isLocalhost) {
46 | // This is running on localhost. Let's check if a service worker still exists or not.
47 | checkValidServiceWorker(swUrl, config);
48 |
49 | // Add some additional logging to localhost, pointing developers to the
50 | // service worker/PWA documentation.
51 | navigator.serviceWorker.ready.then(() => {
52 | console.log(
53 | 'This web app is being served cache-first by a service ' +
54 | 'worker. To learn more, visit https://bit.ly/CRA-PWA'
55 | );
56 | });
57 | } else {
58 | // Is not localhost. Just register service worker
59 | registerValidSW(swUrl, config);
60 | }
61 | });
62 | }
63 | }
64 |
65 | function registerValidSW(swUrl: string, config?: Config) {
66 | navigator.serviceWorker
67 | .register(swUrl)
68 | .then(registration => {
69 | registration.onupdatefound = () => {
70 | const installingWorker = registration.installing;
71 | if (installingWorker == null) {
72 | return;
73 | }
74 | installingWorker.onstatechange = () => {
75 | if (installingWorker.state === 'installed') {
76 | if (navigator.serviceWorker.controller) {
77 | // At this point, the updated precached content has been fetched,
78 | // but the previous service worker will still serve the older
79 | // content until all client tabs are closed.
80 | console.log(
81 | 'New content is available and will be used when all ' +
82 | 'tabs for this page are closed. See https://bit.ly/CRA-PWA.'
83 | );
84 |
85 | // Execute callback
86 | if (config && config.onUpdate) {
87 | config.onUpdate(registration);
88 | }
89 | } else {
90 | // At this point, everything has been precached.
91 | // It's the perfect time to display a
92 | // "Content is cached for offline use." message.
93 | console.log('Content is cached for offline use.');
94 |
95 | // Execute callback
96 | if (config && config.onSuccess) {
97 | config.onSuccess(registration);
98 | }
99 | }
100 | }
101 | };
102 | };
103 | })
104 | .catch(error => {
105 | console.error('Error during service worker registration:', error);
106 | });
107 | }
108 |
109 | function checkValidServiceWorker(swUrl: string, config?: Config) {
110 | // Check if the service worker can be found. If it can't reload the page.
111 | fetch(swUrl)
112 | .then(response => {
113 | // Ensure service worker exists, and that we really are getting a JS file.
114 | const contentType = response.headers.get('content-type');
115 | if (
116 | response.status === 404 ||
117 | (contentType != null && contentType.indexOf('javascript') === -1)
118 | ) {
119 | // No service worker found. Probably a different app. Reload the page.
120 | navigator.serviceWorker.ready.then(registration => {
121 | registration.unregister().then(() => {
122 | window.location.reload();
123 | });
124 | });
125 | } else {
126 | // Service worker found. Proceed as normal.
127 | registerValidSW(swUrl, config);
128 | }
129 | })
130 | .catch(() => {
131 | console.log(
132 | 'No internet connection found. App is running in offline mode.'
133 | );
134 | });
135 | }
136 |
137 | export function unregister() {
138 | if ('serviceWorker' in navigator) {
139 | navigator.serviceWorker.ready.then(registration => {
140 | registration.unregister();
141 | });
142 | }
143 | }
144 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "lib": [
5 | "dom",
6 | "dom.iterable",
7 | "esnext"
8 | ],
9 | "allowJs": true,
10 | "skipLibCheck": true,
11 | "esModuleInterop": true,
12 | "allowSyntheticDefaultImports": true,
13 | "strict": true,
14 | "forceConsistentCasingInFileNames": true,
15 | "module": "esnext",
16 | "moduleResolution": "node",
17 | "resolveJsonModule": true,
18 | "isolatedModules": true,
19 | "noEmit": true,
20 | "jsx": "react"
21 | },
22 | "include": [
23 | "src"
24 | ]
25 | }
26 |
--------------------------------------------------------------------------------