<html lang="en"><head>
<meta charset="UTF-8">
<link rel="apple-touch-icon" type="image/png" href="https://cpwebassets.codepen.io/assets/favicon/apple-touch-icon-5ae1a0698dcc2402e9712f7d01ed509a57814f994c660df9f7a952f3060705ee.png">
<meta name="apple-mobile-web-app-title" content="CodePen">
<link rel="shortcut icon" type="image/x-icon" href="https://cpwebassets.codepen.io/assets/favicon/favicon-aec34940fbc1a6e787974dcd360f2c6b63348d4b1f4e06c77743096d55480f33.ico">
<link rel="mask-icon" type="image/x-icon" href="https://cpwebassets.codepen.io/assets/favicon/logo-pin-8f3771b1072e3c38bd662872f6b673a722f4b3ca2421637d5596661b4e2132cc.svg" color="#111">
<title>CodePen - DND Conditions List</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/normalize/5.0.0/normalize.min.css">
<style>
@-webkit-keyframes fadeIn {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
@keyframes fadeIn {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
* {
box-sizing: border-box;
}
body {
background: #778 radial-gradient(transparent, rgba(0, 0, 0, 0.4)) fixed center center;
color: #331;
font-family: "Times", serif;
margin: 0;
padding: 0;
}
.dnd-conditions-list-app {
padding: 1em;
max-width: 800px;
min-width: 320px;
min-height: 100vh;
margin: 0 auto;
display: flex;
flex-direction: column;
justify-content: space-between;
}
.dnd-conditions-list-app h2 {
text-align: center;
color: white;
text-shadow: 0 0 6px white, 2px 2px rgba(0, 0, 0, 0.4);
}
.dnd-conditions-list-app a {
color: #991100;
}
.dnd-conditions-list-app footer {
margin-top: 3em;
text-align: right;
font-size: 0.7em;
color: silver;
}
.dnd-conditions-list-app footer a {
color: silver;
}
.wait-indicator {
display: block;
margin: 0 auto;
text-align: center;
color: transparent;
position: relative;
}
.wait-indicator::after {
content: "";
display: block;
width: 30px;
height: 30px;
border-top: 3px solid white;
border-left: 3px solid white;
border-right: 3px solid transparent;
border-bottom: 3px solid transparent;
border-radius: 50%;
-webkit-animation: spin 0.5s linear;
animation: spin 0.5s linear;
-webkit-animation-iteration-count: infinite;
animation-iteration-count: infinite;
position: absolute;
top: 0;
left: 50%;
}
@-webkit-keyframes spin {
0% {
transform: translate(-50%, 200%) rotate(0deg);
}
100% {
transform: translate(-50%, 200%) rotate(359deg);
}
}
@keyframes spin {
0% {
transform: translate(-50%, 200%) rotate(0deg);
}
100% {
transform: translate(-50%, 200%) rotate(359deg);
}
}
.conditions-list__list {
display: flex;
flex-direction: row;
align-items: start;
justify-content: center;
flex-wrap: wrap;
-webkit-animation: fadeIn 0.3s ease-out;
animation: fadeIn 0.3s ease-out;
-webkit-animation-fill-mode: forward;
animation-fill-mode: forward;
list-style-type: none;
margin: 0;
padding: 0;
}
.conditions-list__list li {
width: 20%;
height: 80px;
min-width: 140px;
max-width: 200px;
position: relative;
}
.conditions-list__list li a {
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
flex-wrap: wrap;
background: oldlace linear-gradient(120deg, rgba(255, 255, 255, 0.3), rgba(0, 0, 0, 0.2));
border: 5px outset beige;
border-radius: 5px;
box-shadow: 1px 1px 6px rgba(0, 0, 0, 0.3), inset 0 0 1px silver;
background-position: center center;
background-repeat: no-repeat;
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
text-align: center;
text-decoration: none;
font-weight: 600;
margin: 2px;
text-shadow: 2px 2px 8px oldlace, -2px -2px 8px oldlace;
transition: color 0.2s ease, text-shadow 0.3s ease-out, transform 0.2s ease;
}
.conditions-list__list li a:hover {
color: #a20;
text-shadow: 0 0 6px red;
transform: scale(1.02);
}
.condition-detail {
-webkit-animation: fadeIn 0.3s ease-out;
animation: fadeIn 0.3s ease-out;
-webkit-animation-fill-mode: forward;
animation-fill-mode: forward;
position: relative;
}
.condition-detail__content {
-webkit-animation: fadeIn 0.3s ease-out;
animation: fadeIn 0.3s ease-out;
-webkit-animation-fill-mode: forward;
animation-fill-mode: forward;
}
.condition-detail__content__header {
position: relative;
}
.condition-detail__content__header a {
position: absolute;
top: 5px;
left: 0;
color: silver;
text-decoration: none;
}
.condition-detail__content__description {
background: oldlace linear-gradient(120deg, rgba(255, 255, 255, 0.3), rgba(0, 0, 0, 0.2));
border: 5px outset beige;
border-radius: 5px;
box-shadow: 1px 1px 6px rgba(0, 0, 0, 0.3), inset 0 0 1px silver;
padding: 1em;
}
.condition-detail__content__description p {
line-height: 1.3;
}
.condition-detail__content__description p:first-child {
margin-top: 0;
}
.condition-detail__content__description p:last-child {
margin-bottom: 0;
}
</style>
<script>
window.console = window.console || function(t) {};
</script>
<script>
if (document.location.search.match(/type=embed/gi)) {
window.parent.postMessage("resize", "*");
}
</script>
</head>
<body translate="no">
<div id="app"><div class="dnd-conditions-list-app"><div class="conditions-list"><h2>5e Conditions</h2><ul class="conditions-list__list"><li data-condition="blinded"><a href="#/condition/blinded"><span>Blinded</span></a></li><li data-condition="charmed"><a href="#/condition/charmed"><span>Charmed</span></a></li><li data-condition="deafened"><a href="#/condition/deafened"><span>Deafened</span></a></li><li data-condition="exhaustion"><a href="#/condition/exhaustion"><span>Exhaustion</span></a></li><li data-condition="frightened"><a href="#/condition/frightened"><span>Frightened</span></a></li><li data-condition="grappled"><a href="#/condition/grappled"><span>Grappled</span></a></li><li data-condition="incapacitated"><a href="#/condition/incapacitated"><span>Incapacitated</span></a></li><li data-condition="invisible"><a href="#/condition/invisible"><span>Invisible</span></a></li><li data-condition="paralyzed"><a href="#/condition/paralyzed"><span>Paralyzed</span></a></li><li data-condition="petrified"><a href="#/condition/petrified"><span>Petrified</span></a></li><li data-condition="poisoned"><a href="#/condition/poisoned"><span>Poisoned</span></a></li><li data-condition="prone"><a href="#/condition/prone"><span>Prone</span></a></li><li data-condition="restrained"><a href="#/condition/restrained"><span>Restrained</span></a></li><li data-condition="stunned"><a href="#/condition/stunned"><span>Stunned</span></a></li><li data-condition="unconscious"><a href="#/condition/unconscious"><span>Unconscious</span></a></li></ul></div><footer>Built with the <a href="https://www.dnd5eapi.co/" target="_blank" rel="nofollow">D&D 5e API</a>.</footer></div></div>
<script src="https://cpwebassets.codepen.io/assets/common/stopExecutionOnTimeout-1b93190375e9ccc259df3a57c1abc0e64599724ae30d7ea4c6877eb615f89387.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.1/umd/react.development.js"></script>
<script src="https://unpkg.com/prop-types@15.6.2/prop-types.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.1/umd/react-dom.production.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/whatwg-fetch@3.0.0/dist/fetch.umd.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-router-dom/4.2.2/react-router-dom.min.js"></script>
<script id="rendered-js">
console.clear();
// TODO: convert Promise use to async/await
/**
* We're loading React, prop-types and ReactDOM in via the JS panel settings.
* We also have a fetch() polyill, and an older version of react-router-dom
* (needed to properly support hash routes inside CodePen).
**/
const { useState, useEffect } = React;
const { HashRouter, Switch, Route, Link } = ReactRouterDOM;
/**
* Config
**/
const HTTPS_PROXY = 'https://api.allorigins.win/get?url=';
const API_ENDPOINT = 'http://dnd5eapi.co/api';
/**
* Utils
**/
// We can't make direct requests to the D&D API due to CORS issues.
// To get around this, we use a free HTTPS proxy with an open CORS policy.
// If the result comes back OK, we return a JS object of its contents.
// To reduce the hits on the API, we cache the responses and return
// those in the future (only for the current page load).
const responseCache = {};
const makeRequest = path => {
// If we have already hit this endpoint, return the cached result
if (responseCache[path]) {
return Promise.resolve(responseCache[path]);
}
return fetch(
`${HTTPS_PROXY}${API_ENDPOINT}${path}`).
then(res => {
if (res.ok) return res.json();
throw 'Error making request';
}).then(data => {
// Cache the response object before returning it
const contentObj = JSON.parse(data.contents);
responseCache[path] = contentObj;
return contentObj;
});
};
/**
* Components
**/
// Calls a render function if a condition is true.
// Shows an activity indicator otherwise.
// Useful for waiting on API responses.
const WaitToRender = (props) =>
props.until ?
props.onRender() : /*#__PURE__*/
React.createElement("span", { className: "wait-indicator" }, "...");
WaitToRender.propTypes = {
onRender: PropTypes.func.isRequired,
until: PropTypes.bool };
// Fetches the list of 5e conditions and renders
// router links to each one.
const ConditionsList = () => {
const [conditions, setConditions] = useState([]);
// Will be called when the component first mounts.
// The empty array in the 2nd arg ensures this will not
// be called again during state changes.
useEffect(() => {
makeRequest('/conditions').then(content => {
setConditions(content.results);
});
}, []);
const namespace = 'conditions-list';
const renderContent = () => /*#__PURE__*/
React.createElement("ul", { className: `${namespace}__list` },
conditions.map((condition) => /*#__PURE__*/
React.createElement("li", { key: condition.index, "data-condition": condition.index }, /*#__PURE__*/
React.createElement(Link, { to: `/condition/${condition.index}` }, /*#__PURE__*/
React.createElement("span", null, condition.name)))));
return /*#__PURE__*/(
React.createElement("div", { className: namespace }, /*#__PURE__*/
React.createElement("h2", null, "5e Conditions"), /*#__PURE__*/
React.createElement(WaitToRender, {
until: conditions.length > 0,
onRender: renderContent })));
};
const ConditionDetail = props => {
const [conditionDetail, setConditionDetail] = useState(null);
// Called when the component first mounts.
// Gets the `conditionId` from the URL (see the App router)
// and makes an API request using it.
useEffect(() => {
// props.match is automatically passed to the component by the Router
const { conditionId } = props.match.params;
makeRequest(`/conditions/${conditionId}`).then(content => {
setConditionDetail(content);
});
}, []);
// D&D 5e API descriptions sometimes have dashes (like bullets)
// at the start of a paragraph; remove these if present
const stripLeadingDash = str => str.indexOf('- ') === 0 ? str.substr(2) : str;
const namespace = 'condition-detail';
const renderContent = () => /*#__PURE__*/
React.createElement("div", { className: `${namespace}__content` }, /*#__PURE__*/
React.createElement("div", { className: `${namespace}__content__header` }, /*#__PURE__*/
React.createElement("h2", null, conditionDetail.name), /*#__PURE__*/
React.createElement(Link, { to: "/" }, "< Back")), /*#__PURE__*/
React.createElement("div", { className: `${namespace}__content__description` },
conditionDetail.desc.map((item, idx) => /*#__PURE__*/
React.createElement("p", { key: idx },
stripLeadingDash(item))), /*#__PURE__*/
React.createElement("p", null, /*#__PURE__*/
React.createElement(Link, { to: "/" }, "All conditions"))));
return /*#__PURE__*/(
React.createElement("div", { className: namespace }, /*#__PURE__*/
React.createElement(WaitToRender, {
until: conditionDetail !== null,
onRender: renderContent })));
};
/**
* App
**/
const App = () => /*#__PURE__*/
React.createElement("div", { className: "dnd-conditions-list-app" }, /*#__PURE__*/
React.createElement(HashRouter, null, /*#__PURE__*/
React.createElement(Switch, null, /*#__PURE__*/
React.createElement(Route, { exact: true, path: "/", component: ConditionsList }), /*#__PURE__*/
React.createElement(Route, { path: "/condition/:conditionId", component: ConditionDetail }))), /*#__PURE__*/
React.createElement("footer", null, "Built with the ", /*#__PURE__*/
React.createElement("a", { href: "https://www.dnd5eapi.co/", target: "_blank", rel: "nofollow" }, "D&D 5e API"), "."));
ReactDOM.render( /*#__PURE__*/React.createElement(App, null), document.querySelector('#app'));
//# sourceURL=pen.js
</script>
</body></html>