├── package.json
├── src
├── App.module.css
├── index.js
└── styles.css
├── README.md
├── LICENSE
├── .gitignore
└── public
└── index.html
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "tensorflowjs-object-detection",
3 | "version": "1.0.0",
4 | "description": "",
5 | "keywords": [],
6 | "main": "src/index.js",
7 | "dependencies": {
8 | "@cloud-annotations/models": "^0.1.0",
9 | "react": "^16.12.0",
10 | "react-dom": "^16.12.0",
11 | "react-magic-dropzone": "^1.0.1",
12 | "react-scripts": "^3.3.1"
13 | },
14 | "devDependencies": {},
15 | "scripts": {
16 | "start": "react-scripts start",
17 | "build": "react-scripts build",
18 | "test": "react-scripts test --env=jsdom",
19 | "eject": "react-scripts eject"
20 | },
21 | "browserslist": [
22 | ">0.2%",
23 | "not dead",
24 | "not ie <= 11",
25 | "not op_mini all"
26 | ]
27 | }
28 |
--------------------------------------------------------------------------------
/src/App.module.css:
--------------------------------------------------------------------------------
1 | .wrapper {
2 | display: flex;
3 | align-items: center;
4 | justify-content: center;
5 | flex-direction: column;
6 | margin: 48px 16px 32px 16px;
7 | }
8 |
9 | .dropzoneBase {
10 | position: relative;
11 | padding: 12px;
12 | color: var(--detailText);
13 | border: 2px var(--textInputUnderline) dashed;
14 | border-radius: 5px;
15 | width: 100%;
16 | min-height: 144px;
17 | display: flex;
18 | align-items: center;
19 | justify-content: center;
20 | max-width: 500px;
21 | }
22 |
23 | .dropzone {
24 | composes: dropzoneBase;
25 | cursor: pointer;
26 | }
27 |
28 | .dropzone:hover {
29 | background: var(--highlight);
30 | }
31 |
32 | .image {
33 | width: 100%;
34 | height: 100%;
35 | border: thin solid rgba(64, 64, 64, 0.15);
36 | border-radius: 4px;
37 | object-fit: cover;
38 | }
39 |
40 | .canvas {
41 | position: absolute;
42 | }
43 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Classification React App
2 |
3 | You can find an in depth walkthrough for training a TensorFlow.js model [here](https://github.com/cloud-annotations/training/).
4 |
5 | ## Setup
6 | `git clone` the repo and `cd` into it by running the following command:
7 |
8 | ```bash
9 | git clone https://github.com/cloud-annotations/classification-react.git
10 | cd classification-react
11 | ```
12 |
13 | ### `npm install`
14 |
15 | > **Note: You’ll need to have Node 8.10.0 or later on your local development machine.** You can use [nvm](https://github.com/creationix/nvm#installation) (macOS/Linux) or [nvm-windows](https://github.com/coreybutler/nvm-windows#node-version-manager-nvm-for-windows) to easily switch Node versions between different projects.
16 |
17 | ## Add TensorFlow.js Model to the App
18 | Copy the `model_web` directory generated from the classification walkthrough and paste it into the `public` folder of this repo.
19 |
20 | ## Run the App
21 | ### `npm start`
22 |
23 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser.
24 |
25 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 Cloud Annotations
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | model_web
2 |
3 | # Logs
4 | logs
5 | *.log
6 | npm-debug.log*
7 | yarn-debug.log*
8 | yarn-error.log*
9 |
10 | # Runtime data
11 | pids
12 | *.pid
13 | *.seed
14 | *.pid.lock
15 |
16 | # Directory for instrumented libs generated by jscoverage/JSCover
17 | lib-cov
18 |
19 | # Coverage directory used by tools like istanbul
20 | coverage
21 |
22 | # nyc test coverage
23 | .nyc_output
24 |
25 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
26 | .grunt
27 |
28 | # Bower dependency directory (https://bower.io/)
29 | bower_components
30 |
31 | # node-waf configuration
32 | .lock-wscript
33 |
34 | # Compiled binary addons (https://nodejs.org/api/addons.html)
35 | build/Release
36 |
37 | # Dependency directories
38 | node_modules/
39 | jspm_packages/
40 |
41 | # TypeScript v1 declaration files
42 | typings/
43 |
44 | # Optional npm cache directory
45 | .npm
46 |
47 | # Optional eslint cache
48 | .eslintcache
49 |
50 | # Optional REPL history
51 | .node_repl_history
52 |
53 | # Output of 'npm pack'
54 | *.tgz
55 |
56 | # Yarn Integrity file
57 | .yarn-integrity
58 |
59 | # dotenv environment variables file
60 | .env
61 |
62 | # next.js build output
63 | .next
64 |
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
12 |
13 |
14 |
23 | React App
24 |
25 |
26 |
27 |
30 |
31 |
41 |
42 |
43 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import React, { useState, useCallback, useEffect } from "react";
2 | import ReactDOM from "react-dom";
3 |
4 | import MagicDropzone from "react-magic-dropzone";
5 | import models from "@cloud-annotations/models";
6 |
7 | import styles from "./App.module.css";
8 | import "./styles.css";
9 |
10 | const getRetinaContext = canvas => {
11 | const ctx = canvas.getContext("2d");
12 | const scale = window.devicePixelRatio;
13 | let width = canvas.width / scale;
14 | let height = canvas.height / scale;
15 | return {
16 | setWidth: w => {
17 | width = w;
18 | canvas.style.width = w + "px";
19 | canvas.width = w * scale;
20 | },
21 | setHeight: h => {
22 | height = h;
23 | canvas.style.height = h + "px";
24 | canvas.height = h * scale;
25 | },
26 | width: width,
27 | height: height,
28 | clearAll: () => {
29 | return ctx.clearRect(0, 0, width * scale, height * scale);
30 | },
31 | clearRect: (x, y, width, height) => {
32 | return ctx.clearRect(x * scale, y * scale, width * scale, height * scale);
33 | },
34 | setFont: font => {
35 | const size = parseInt(font, 10) * scale;
36 | const retinaFont = font.replace(/^\d+px/, size + "px");
37 | ctx.font = retinaFont;
38 | },
39 | setTextBaseLine: textBaseline => {
40 | ctx.textBaseline = textBaseline;
41 | },
42 | setStrokeStyle: strokeStyle => {
43 | ctx.strokeStyle = strokeStyle;
44 | },
45 | setLineWidth: lineWidth => {
46 | ctx.lineWidth = lineWidth * scale;
47 | },
48 | strokeRect: (x, y, width, height) => {
49 | return ctx.strokeRect(
50 | x * scale,
51 | y * scale,
52 | width * scale,
53 | height * scale
54 | );
55 | },
56 | setFillStyle: fillStyle => {
57 | ctx.fillStyle = fillStyle;
58 | },
59 | measureText: text => {
60 | const metrics = ctx.measureText(text);
61 | return {
62 | width: metrics.width / scale,
63 | actualBoundingBoxLeft: metrics.actualBoundingBoxLeft / scale,
64 | actualBoundingBoxRight: metrics.actualBoundingBoxRight / scale,
65 | actualBoundingBoxAscent: metrics.actualBoundingBoxAscent / scale,
66 | actualBoundingBoxDescent: metrics.actualBoundingBoxDescent / scale
67 | };
68 | },
69 | fillRect: (x, y, width, height) => {
70 | return ctx.fillRect(x * scale, y * scale, width * scale, height * scale);
71 | },
72 | fillText: (text, x, y) => {
73 | return ctx.fillText(text, x * scale, y * scale);
74 | }
75 | };
76 | };
77 |
78 | const renderObjectDetection = (ctx, predictions) => {
79 | ctx.clearAll();
80 | // Font options.
81 | const font = `${14}px 'ibm-plex-sans', Helvetica Neue, Arial, sans-serif`;
82 | ctx.setFont(font);
83 | ctx.setTextBaseLine("top");
84 | const border = 2;
85 | const xPadding = 8;
86 | const yPadding = 4;
87 | const offset = 2;
88 | const textHeight = parseInt(font, 10); // base 10
89 |
90 | predictions.forEach(prediction => {
91 | const x = prediction.bbox[0];
92 | const y = prediction.bbox[1];
93 | const width = prediction.bbox[2];
94 | const height = prediction.bbox[3];
95 | // Draw the bounding box.
96 | ctx.setStrokeStyle("#0062ff");
97 | ctx.setLineWidth(border);
98 |
99 | ctx.strokeRect(
100 | Math.round(x),
101 | Math.round(y),
102 | Math.round(width),
103 | Math.round(height)
104 | );
105 | // Draw the label background.
106 | ctx.setFillStyle("#0062ff");
107 | const textWidth = ctx.measureText(prediction.label).width;
108 | ctx.fillRect(
109 | Math.round(x - border / 2),
110 | Math.round(y - (textHeight + yPadding) - offset),
111 | Math.round(textWidth + xPadding),
112 | Math.round(textHeight + yPadding)
113 | );
114 | });
115 |
116 | predictions.forEach(prediction => {
117 | const x = prediction.bbox[0];
118 | const y = prediction.bbox[1];
119 | // Draw the text last to ensure it's on top.
120 | ctx.setFillStyle("#ffffff");
121 | ctx.fillText(
122 | prediction.label,
123 | Math.round(x - border / 2 + xPadding / 2),
124 | Math.round(y - (textHeight + yPadding) - offset + yPadding / 2)
125 | );
126 | });
127 | };
128 |
129 | const renderClassification = (ctx, predictions) => {
130 | ctx.clearAll();
131 |
132 | const font = `${14}px 'ibm-plex-sans', Helvetica Neue, Arial, sans-serif`;
133 | const textHeight = parseInt(font, 10); // base 10
134 | const xPadding = 8;
135 | const yPadding = 4;
136 | const offset = 2;
137 | ctx.setFont(font);
138 | ctx.setTextBaseLine("top");
139 |
140 | predictions
141 | .filter(prediction => prediction.score > 0.5)
142 | .forEach((prediction, i) => {
143 | const label = `${prediction.label} ${(prediction.score * 100).toFixed(
144 | 1
145 | )}%`;
146 | // Draw the label background.
147 | ctx.setFillStyle("#0062ff");
148 | const textWidth = ctx.measureText(label).width;
149 | ctx.fillRect(
150 | Math.round(xPadding),
151 | Math.round(xPadding + i * (textHeight + yPadding + offset)),
152 | Math.round(textWidth + xPadding),
153 | Math.round(textHeight + yPadding)
154 | );
155 | // Draw the text last to ensure it's on top.
156 | ctx.setFillStyle("#ffffff");
157 | ctx.fillText(
158 | label,
159 | Math.round(xPadding + 0 + xPadding / 2),
160 | Math.round(
161 | xPadding + i * (textHeight + yPadding + offset) + yPadding / 2
162 | )
163 | );
164 | });
165 | };
166 |
167 | const App = () => {
168 | const [model, setModel] = useState(undefined);
169 | const [preview, setPreview] = useState(undefined);
170 | const [resultsCanvas, setResultsCanvas] = useState(undefined);
171 |
172 | useEffect(() => {
173 | models.load("/model_web").then(async model => {
174 | // warm up the model
175 | const image = new ImageData(1, 1);
176 | if (model.type === "detection") {
177 | await model.detect(image);
178 | } else {
179 | await model.classify(image);
180 | }
181 | setModel(model);
182 | });
183 | }, []);
184 |
185 | useEffect(() => {
186 | setPreview(undefined);
187 | if (resultsCanvas) {
188 | const ctx = getRetinaContext(resultsCanvas);
189 | ctx.clearAll();
190 | ctx.setWidth(0);
191 | ctx.setHeight(0);
192 | }
193 | }, [model, resultsCanvas]); // if model changes kill preview.
194 |
195 | const onDrop = useCallback((accepted, _, links) => {
196 | setPreview(accepted[0].preview || links[0]);
197 | }, []);
198 |
199 | const onImageChange = useCallback(
200 | async e => {
201 | const imgWidth = e.target.clientWidth;
202 | const imgHeight = e.target.clientHeight;
203 |
204 | const ctx = getRetinaContext(resultsCanvas);
205 | ctx.setWidth(imgWidth);
206 | ctx.setHeight(imgHeight);
207 |
208 | if (model.type === "detection") {
209 | const predictions = await model.detect(e.target);
210 | renderObjectDetection(ctx, predictions);
211 | } else {
212 | const predictions = await model.classify(e.target);
213 | renderClassification(ctx, predictions);
214 | }
215 | },
216 | [model, resultsCanvas]
217 | );
218 |
219 | return (
220 |
221 |
227 | {preview ? (
228 |
229 |

235 |
236 | ) : model !== undefined ? (
237 | "Drag & Drop an Image to Test"
238 | ) : (
239 | "Loading model..."
240 | )}
241 |
242 |
243 |
244 | );
245 | };
246 |
247 | const rootElement = document.getElementById("root");
248 | ReactDOM.render(, rootElement);
249 |
--------------------------------------------------------------------------------
/src/styles.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | padding: 0;
4 | -webkit-font-smoothing: antialiased;
5 | overflow: hidden;
6 | font-family: "ibm-plex-sans", Helvetica Neue, Arial, sans-serif;
7 | /* text-rendering: optimizelegibility; */
8 | background-color: hsl(210, 17%, 98%);
9 | color: rgba(0, 0, 0, 1);
10 | --detailText: rgba(0, 0, 0, 0.53);
11 | --textInput: #f3f3f3;
12 | --textInputUnderline: #a7a7a7;
13 | --highlight: #eef4fc;
14 | }
15 |
16 | * {
17 | box-sizing: border-box;
18 | }
19 |
20 | @font-face {
21 | font-family: "ibm-plex-mono";
22 | font-style: normal;
23 | font-weight: 300;
24 | src: url("https://unpkg.com/carbon-components@latest/src/globals/fonts/IBMPlexMono-Light.woff")
25 | format("woff");
26 | }
27 |
28 | @font-face {
29 | font-family: "ibm-plex-mono";
30 | font-style: normal;
31 | font-weight: 300;
32 | src: url("https://unpkg.com/carbon-components@latest/src/globals/fonts/IBMPlexMono-Light-Pi.woff2")
33 | format("woff2");
34 | unicode-range: "U+03C0, U+0E3F, U+2070, U+2074-2079, U+2080-2089, U+2113, U+2116, U+2126, U+212E, U+2150-2151, U+2153-215E, U+2190-2199, U+21A9-21AA, U+21B0-21B3, U+21B6-21B7, U+21BA-21BB, U+21C4, U+21C6, U+2202, U+2206, U+220F, U+2211, U+221A, U+221E, U+222B, U+2248, U+2260, U+2264-2265, U+25CA, U+2713, U+274C, U+2B0E-2B11, U+EBE1, U+EBE3-EBE4, U+EBE6-EBE7, U+ECE0, U+EFCC";
35 | }
36 |
37 | @font-face {
38 | font-family: "ibm-plex-mono";
39 | font-style: normal;
40 | font-weight: 300;
41 | src: url("https://unpkg.com/carbon-components@latest/src/globals/fonts/IBMPlexMono-Light-Latin3.woff2")
42 | format("woff2");
43 | unicode-range: "U+0102-0103, U+1EA0-1EF9, U+20AB";
44 | }
45 |
46 | @font-face {
47 | font-family: "ibm-plex-mono";
48 | font-style: normal;
49 | font-weight: 300;
50 | src: url("https://unpkg.com/carbon-components@latest/src/globals/fonts/IBMPlexMono-Light-Latin2.woff2")
51 | format("woff2");
52 | unicode-range: "U+0100-024F, U+0259, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF, U+FB01-FB02";
53 | }
54 |
55 | @font-face {
56 | font-family: "ibm-plex-mono";
57 | font-style: normal;
58 | font-weight: 300;
59 | src: url("https://unpkg.com/carbon-components@latest/src/globals/fonts/IBMPlexMono-Light-Latin1.woff2")
60 | format("woff2");
61 | unicode-range: "U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+20AC, U+2122, U+2212, U+FB01-FB02";
62 | }
63 |
64 | @font-face {
65 | font-family: "ibm-plex-mono";
66 | font-style: normal;
67 | font-weight: 400;
68 | src: url("https://unpkg.com/carbon-components@latest/src/globals/fonts/IBMPlexMono-Regular.woff")
69 | format("woff");
70 | }
71 |
72 | @font-face {
73 | font-family: "ibm-plex-mono";
74 | font-style: normal;
75 | font-weight: 400;
76 | src: url("https://unpkg.com/carbon-components@latest/src/globals/fonts/IBMPlexMono-Regular-Pi.woff2")
77 | format("woff2");
78 | unicode-range: "U+03C0, U+0E3F, U+2070, U+2074-2079, U+2080-2089, U+2113, U+2116, U+2126, U+212E, U+2150-2151, U+2153-215E, U+2190-2199, U+21A9-21AA, U+21B0-21B3, U+21B6-21B7, U+21BA-21BB, U+21C4, U+21C6, U+2202, U+2206, U+220F, U+2211, U+221A, U+221E, U+222B, U+2248, U+2260, U+2264-2265, U+25CA, U+2713, U+274C, U+2B0E-2B11, U+EBE1, U+EBE3-EBE4, U+EBE6-EBE7, U+ECE0, U+EFCC";
79 | }
80 |
81 | @font-face {
82 | font-family: "ibm-plex-mono";
83 | font-style: normal;
84 | font-weight: 400;
85 | src: url("https://unpkg.com/carbon-components@latest/src/globals/fonts/IBMPlexMono-Regular-Latin3.woff2")
86 | format("woff2");
87 | unicode-range: "U+0102-0103, U+1EA0-1EF9, U+20AB";
88 | }
89 |
90 | @font-face {
91 | font-family: "ibm-plex-mono";
92 | font-style: normal;
93 | font-weight: 400;
94 | src: url("https://unpkg.com/carbon-components@latest/src/globals/fonts/IBMPlexMono-Regular-Latin2.woff2")
95 | format("woff2");
96 | unicode-range: "U+0100-024F, U+0259, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF, U+FB01-FB02";
97 | }
98 |
99 | @font-face {
100 | font-family: "ibm-plex-mono";
101 | font-style: normal;
102 | font-weight: 400;
103 | src: url("https://unpkg.com/carbon-components@latest/src/globals/fonts/IBMPlexMono-Regular-Latin1.woff2")
104 | format("woff2");
105 | unicode-range: "U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+20AC, U+2122, U+2212, U+FB01-FB02";
106 | }
107 |
108 | @font-face {
109 | font-family: "ibm-plex-mono";
110 | font-style: normal;
111 | font-weight: 600;
112 | src: url("https://unpkg.com/carbon-components@latest/src/globals/fonts/IBMPlexMono-SemiBold.woff")
113 | format("woff");
114 | }
115 |
116 | @font-face {
117 | font-family: "ibm-plex-mono";
118 | font-style: normal;
119 | font-weight: 600;
120 | src: url("https://unpkg.com/carbon-components@latest/src/globals/fonts/IBMPlexMono-SemiBold-Pi.woff2")
121 | format("woff2");
122 | unicode-range: "U+03C0, U+0E3F, U+2070, U+2074-2079, U+2080-2089, U+2113, U+2116, U+2126, U+212E, U+2150-2151, U+2153-215E, U+2190-2199, U+21A9-21AA, U+21B0-21B3, U+21B6-21B7, U+21BA-21BB, U+21C4, U+21C6, U+2202, U+2206, U+220F, U+2211, U+221A, U+221E, U+222B, U+2248, U+2260, U+2264-2265, U+25CA, U+2713, U+274C, U+2B0E-2B11, U+EBE1, U+EBE3-EBE4, U+EBE6-EBE7, U+ECE0, U+EFCC";
123 | }
124 |
125 | @font-face {
126 | font-family: "ibm-plex-mono";
127 | font-style: normal;
128 | font-weight: 600;
129 | src: url("https://unpkg.com/carbon-components@latest/src/globals/fonts/IBMPlexMono-SemiBold-Latin3.woff2")
130 | format("woff2");
131 | unicode-range: "U+0102-0103, U+1EA0-1EF9, U+20AB";
132 | }
133 |
134 | @font-face {
135 | font-family: "ibm-plex-mono";
136 | font-style: normal;
137 | font-weight: 600;
138 | src: url("https://unpkg.com/carbon-components@latest/src/globals/fonts/IBMPlexMono-SemiBold-Latin2.woff2")
139 | format("woff2");
140 | unicode-range: "U+0100-024F, U+0259, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF, U+FB01-FB02";
141 | }
142 |
143 | @font-face {
144 | font-family: "ibm-plex-mono";
145 | font-style: normal;
146 | font-weight: 600;
147 | src: url("https://unpkg.com/carbon-components@latest/src/globals/fonts/IBMPlexMono-SemiBold-Latin1.woff2")
148 | format("woff2");
149 | unicode-range: "U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+20AC, U+2122, U+2212, U+FB01-FB02";
150 | }
151 |
152 | @font-face {
153 | font-family: "ibm-plex-sans";
154 | font-style: normal;
155 | font-weight: 300;
156 | src: url("https://unpkg.com/carbon-components@latest/src/globals/fonts/IBMPlexSans-Light.woff")
157 | format("woff");
158 | }
159 |
160 | @font-face {
161 | font-family: "ibm-plex-sans";
162 | font-style: normal;
163 | font-weight: 300;
164 | src: url("https://unpkg.com/carbon-components@latest/src/globals/fonts/IBMPlexSans-Light-Pi.woff2")
165 | format("woff2");
166 | unicode-range: "U+03C0, U+0E3F, U+2070, U+2074-2079, U+2080-2089, U+2113, U+2116, U+2126, U+212E, U+2150-2151, U+2153-215E, U+2190-2199, U+21A9-21AA, U+21B0-21B3, U+21B6-21B7, U+21BA-21BB, U+21C4, U+21C6, U+2202, U+2206, U+220F, U+2211, U+221A, U+221E, U+222B, U+2248, U+2260, U+2264-2265, U+25CA, U+2713, U+274C, U+2B0E-2B11, U+EBE1, U+EBE3-EBE4, U+EBE6-EBE7, U+ECE0, U+EFCC";
167 | }
168 |
169 | @font-face {
170 | font-family: "ibm-plex-sans";
171 | font-style: normal;
172 | font-weight: 300;
173 | src: url("https://unpkg.com/carbon-components@latest/src/globals/fonts/IBMPlexSans-Light-Latin3.woff2")
174 | format("woff2");
175 | unicode-range: "U+0102-0103, U+1EA0-1EF9, U+20AB";
176 | }
177 |
178 | @font-face {
179 | font-family: "ibm-plex-sans";
180 | font-style: normal;
181 | font-weight: 300;
182 | src: url("https://unpkg.com/carbon-components@latest/src/globals/fonts/IBMPlexSans-Light-Latin2.woff2")
183 | format("woff2");
184 | unicode-range: "U+0100-024F, U+0259, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF, U+FB01-FB02";
185 | }
186 |
187 | @font-face {
188 | font-family: "ibm-plex-sans";
189 | font-style: normal;
190 | font-weight: 300;
191 | src: url("https://unpkg.com/carbon-components@latest/src/globals/fonts/IBMPlexSans-Light-Latin1.woff2")
192 | format("woff2");
193 | unicode-range: "U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+20AC, U+2122, U+2212, U+FB01-FB02";
194 | }
195 |
196 | @font-face {
197 | font-family: "ibm-plex-sans";
198 | font-style: normal;
199 | font-weight: 400;
200 | src: url("https://unpkg.com/carbon-components@latest/src/globals/fonts/IBMPlexSans-Regular.woff")
201 | format("woff");
202 | }
203 |
204 | @font-face {
205 | font-family: "ibm-plex-sans";
206 | font-style: normal;
207 | font-weight: 400;
208 | src: url("https://unpkg.com/carbon-components@latest/src/globals/fonts/IBMPlexSans-Regular-Pi.woff2")
209 | format("woff2");
210 | unicode-range: "U+03C0, U+0E3F, U+2070, U+2074-2079, U+2080-2089, U+2113, U+2116, U+2126, U+212E, U+2150-2151, U+2153-215E, U+2190-2199, U+21A9-21AA, U+21B0-21B3, U+21B6-21B7, U+21BA-21BB, U+21C4, U+21C6, U+2202, U+2206, U+220F, U+2211, U+221A, U+221E, U+222B, U+2248, U+2260, U+2264-2265, U+25CA, U+2713, U+274C, U+2B0E-2B11, U+EBE1, U+EBE3-EBE4, U+EBE6-EBE7, U+ECE0, U+EFCC";
211 | }
212 |
213 | @font-face {
214 | font-family: "ibm-plex-sans";
215 | font-style: normal;
216 | font-weight: 400;
217 | src: url("https://unpkg.com/carbon-components@latest/src/globals/fonts/IBMPlexSans-Regular-Latin3.woff2")
218 | format("woff2");
219 | unicode-range: "U+0102-0103, U+1EA0-1EF9, U+20AB";
220 | }
221 |
222 | @font-face {
223 | font-family: "ibm-plex-sans";
224 | font-style: normal;
225 | font-weight: 400;
226 | src: url("https://unpkg.com/carbon-components@latest/src/globals/fonts/IBMPlexSans-Regular-Latin2.woff2")
227 | format("woff2");
228 | unicode-range: "U+0100-024F, U+0259, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF, U+FB01-FB02";
229 | }
230 |
231 | @font-face {
232 | font-family: "ibm-plex-sans";
233 | font-style: normal;
234 | font-weight: 400;
235 | src: url("https://unpkg.com/carbon-components@latest/src/globals/fonts/IBMPlexSans-Regular-Latin1.woff2")
236 | format("woff2");
237 | unicode-range: "U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+20AC, U+2122, U+2212, U+FB01-FB02";
238 | }
239 |
240 | @font-face {
241 | font-family: "ibm-plex-sans";
242 | font-style: normal;
243 | font-weight: 600;
244 | src: url("https://unpkg.com/carbon-components@latest/src/globals/fonts/IBMPlexSans-SemiBold.woff")
245 | format("woff");
246 | }
247 |
248 | @font-face {
249 | font-family: "ibm-plex-sans";
250 | font-style: normal;
251 | font-weight: 600;
252 | src: url("https://unpkg.com/carbon-components@latest/src/globals/fonts/IBMPlexSans-SemiBold-Pi.woff2")
253 | format("woff2");
254 | unicode-range: "U+03C0, U+0E3F, U+2070, U+2074-2079, U+2080-2089, U+2113, U+2116, U+2126, U+212E, U+2150-2151, U+2153-215E, U+2190-2199, U+21A9-21AA, U+21B0-21B3, U+21B6-21B7, U+21BA-21BB, U+21C4, U+21C6, U+2202, U+2206, U+220F, U+2211, U+221A, U+221E, U+222B, U+2248, U+2260, U+2264-2265, U+25CA, U+2713, U+274C, U+2B0E-2B11, U+EBE1, U+EBE3-EBE4, U+EBE6-EBE7, U+ECE0, U+EFCC";
255 | }
256 |
257 | @font-face {
258 | font-family: "ibm-plex-sans";
259 | font-style: normal;
260 | font-weight: 600;
261 | src: url("https://unpkg.com/carbon-components@latest/src/globals/fonts/IBMPlexSans-SemiBold-Latin3.woff2")
262 | format("woff2");
263 | unicode-range: "U+0102-0103, U+1EA0-1EF9, U+20AB";
264 | }
265 |
266 | @font-face {
267 | font-family: "ibm-plex-sans";
268 | font-style: normal;
269 | font-weight: 600;
270 | src: url("https://unpkg.com/carbon-components@latest/src/globals/fonts/IBMPlexSans-SemiBold-Latin2.woff2")
271 | format("woff2");
272 | unicode-range: "U+0100-024F, U+0259, U+1E00-1EFF, U+20A0-20AB, U+20AD-20CF, U+2C60-2C7F, U+A720-A7FF, U+FB01-FB02";
273 | }
274 |
275 | @font-face {
276 | font-family: "ibm-plex-sans";
277 | font-style: normal;
278 | font-weight: 600;
279 | src: url("https://unpkg.com/carbon-components@latest/src/globals/fonts/IBMPlexSans-SemiBold-Latin1.woff2")
280 | format("woff2");
281 | unicode-range: "U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+20AC, U+2122, U+2212, U+FB01-FB02";
282 | }
283 |
284 | @font-face {
285 | font-family: "ibm-plex-sans";
286 | font-style: normal;
287 | font-weight: 500;
288 | src: url("https://raw.githubusercontent.com/IBM/plex/master/IBM-Plex-Sans/fonts/complete/woff/IBMPlexSans-Medium.woff")
289 | format("woff");
290 | }
291 |
292 | @font-face {
293 | font-family: "ibm-plex-sans";
294 | font-style: italic;
295 | font-weight: 500;
296 | src: url("https://raw.githubusercontent.com/IBM/plex/master/IBM-Plex-Sans/fonts/complete/woff/IBMPlexSans-MediumItalic.woff")
297 | format("woff");
298 | }
299 |
--------------------------------------------------------------------------------