Skip to content

Instantly share code, notes, and snippets.

@michaelforrest
Created August 21, 2024 10:31
Show Gist options
  • Save michaelforrest/b6dc049507c6a9403596842ca718f769 to your computer and use it in GitHub Desktop.
Save michaelforrest/b6dc049507c6a9403596842ca718f769 to your computer and use it in GitHub Desktop.
Dashboard front end code reference (bring your own data)
import moment from 'moment';
import {Component, Fragment} from 'react'
import 'react-circular-progressbar/dist/styles.css';
import './App.scss';
import { fetchDataFromCoda, loadFromCache } from './codaFetcher';
import {Sparklines, SparklinesBars, SparklinesLine, SparklinesNormalBand, SparklinesReferenceLine} from 'react-sparklines'
import pluralize from 'pluralize';
import numeral from 'numeral'
import {AiFillFacebook, AiFillYoutube} from 'react-icons/ai'
import {ImTwitch} from 'react-icons/im'
import {sortBy, last} from 'lodash';
import { CircularProgressbar } from 'react-circular-progressbar';
import { Bar, BarChart, Cell, Label, Pie, PieChart, ReferenceLine, ResponsiveContainer, Tooltip, XAxis, YAxis } from 'recharts';
import { chunk } from 'underscore';
const BEFORE_JUNE_2021 = false;
window.moment = moment;
const direction = (numberString, inverted=false)=> {
const value = parseFloat(numberString)
if(isNaN(value) || value === 0){
return "neutral"
}
return (inverted ? value < 0 : value > 0) ? "better" : "worse"
}
const change = (value) => `${parseFloat(value) > 0 ? "+" : ""}${value}`
const satisfactionScore = (values) => {
let total = values[1] + values[2] + values[3]
let yes = values[1] / total
return {total, yes}
}
const planNames = [
"Basic",
"1 Year",
"Lifetime",
"Lifetime (Upgrade)",
"1 Year (Legacy)",
"Watermark (Legacy)",
"3 Months (Legacy)",
"6 Months (Legacy)",
"Other Proceeds",
]
const purchaseColors = {
"Lifetime": "#e49500",
"Lifetime (Upgrade)": "#f0ac50",
"1 Year": "#bc5090",
"Basic": "#003f5c",
"Watermark (Legacy)": "#46738f",
"3 Months (Legacy)": "#58508d",
"6 Months (Legacy)": "#bc5090",
"1 Year (Legacy)": "#ff9f95",
"Other Proceeds": "#5088BC",
}
const roleColors = {
"App Developer": "#e49500",
"Web Developer": "#f0ac50",
"Operations": "#bc5090",
"Video Editor": "#003f5c",
"Administrator": "#46738f",
"Cleaner": "#58508d",
"Customer Support": "#bc5090",
"1 Year (Legacy)": "#ff9f95",
"Other Proceeds": "#5088BC",
}
let notableDates = [
{date: "2020-03-20", label: "Covid"},
{date: "2021-07-01", label: "Pivot to focus on Shoot"},
{date: "2022-01-01", label: "2 Month Coding Detox"},
{date: "2022-02-22", label: "MacBreak Weekly"},
{date: "2022-09-26", label: "Office Hours"},
]
let appColors = {
shoot: "#5b69b5",
videoPencil: "#e94f84",
other: "#639691",
videoPencilCamera: "#2ca5dc",
}
const showPodcasts = false
class App extends Component {
constructor(props){
super(props)
this.state = {report: null, hideSensitive: false}
}
async componentDidMount(){
try {
let cached = loadFromCache()
console.log("CACHED", cached)
cached && this.setState({report: cached})
const report = await fetchDataFromCoda()
console.log(report);
this.setState({report})
}catch(error){
this.setState({error})
}
}
render(){
const {report,error,hideSensitive} = this.state
if(error){
return <div className="error">ERROR! {error.message} {JSON.stringify(error)}</div>
}
if(!report){
return <div>Loading...</div>
}
const {shoot,changes,habits,donegood,music,podcast,twitch,tiktok,beatSheetStudio, videoPencil,videoPencilCamera={},crashesSparkline} = report
window.report = report
const app = shoot
const allSalesBarChart = report.allSalesBarChart.slice(report.allSalesBarChart.length - 60)
const thisWeek = allSalesBarChart[allSalesBarChart.length - 1].week
const campaigns = [...shoot.campaigns , ...videoPencil.campaigns]
const delegatableTotal = report.workload ? (report.workload.reduce((m, row)=>m + row.delegatable,0)) : 0
const totalHours = report.workload.reduce((m, row)=>m + row.timeSpent,0)
console.log("SHOOT BAR CHART", app.barChart)
return <div className={`App ${hideSensitive ? "hidden-sensitive" : ""}`} onClick={()=>this.setState({hideSensitive: !hideSensitive})}>
<Row>
<BigNumber
title="Good To Hear Report"
value={moment(report.date).format("ll")}
description={`${moment(report.startDate).format("Do MMMM")} - ${moment(report.endDate).format("Do MMMM")}`}
/>
<div className="what-was-happening">
<h3>What was happening?</h3>
<p class="free-text">
{report.whatWasHappening}
</p>
</div>
</Row>
<Row>
<Column title="Headlines" color="#F5867F" className="shoot headlines">
<Row>
<div class="group">
<BigNumber
type="financial"
title="CueCam +"
color={appColors.other}
description={`${change(music.patreonChange)} vs last week`}
value={music.patreon}
direction={direction(music.patreonChange)}
/>
</div>
<div class="group">
<BigNumber
type="financial"
title="Shoot"
color={appColors.shoot}
value={app.sales}
description={`${change(app.salesChange)} vs Last Week`}
direction={direction(app.salesChange)}
/>
<pre>
{app.summary}
</pre>
</div>
<div class="group">
<BigNumber
type="financial"
title="Video Pencil"
color={appColors.videoPencil}
value={videoPencil.sales}
description={`${change(videoPencil.salesChange)} vs Last Week`}
direction={direction(videoPencil.salesChange || 100)}
/>
<pre>
{videoPencil.summary}
</pre>
</div>
<div class="group">
<BigNumber
type="financial"
title="New Emails"
value={shoot.newEmails}
description={`${shoot.newEmails - shoot.emailsChange} last week | ${numeral(shoot.totalEmails).format("0,0")} total`}
direction={direction(0)}
/>
</div>
</Row>
<Row>
<div class="subsection">
<BigNumber
title="mrr"
value={report.mrr}
description={
<Sparklines data={report.mrrSparkline} >
<SparklinesLine color="#253e56" />
</Sparklines>
}
/>
<BigNumber
title="MRR Growth"
value={`${(report.mrrGrowth * 100).toFixed(1)}%`}
direction={report.mrrGrowth > 0.07 ? "better" : "worse"}
type="growth"
description="Target 8%"
/>
<BigNumber
title="New Subscribers"
value={report.newStripeSubscribers}
direction={report.newStripeSubscribers - report.newSubsTarget >= 0 ? "better" : "worse"}
description={`Target: ${report.newSubsTarget}`}
/>
<div class="target">
<BigNumber
class="target"
title="This Week's Target"
value={report.nextWeekSubsTarget}
description="New Customers"
/>
</div>
</div>
<div className='sparky everything' style={{ flex: 1}}>
<ResponsiveContainer width="100%" height={340}>
<BarChart data={allSalesBarChart} >
<XAxis dataKey="week" />
<YAxis tickFormatter={d => "$" + d} orientation="right"/>
<Bar dataKey="shoot" fill={appColors.shoot} valueKey="week" stackId={0}>
{HighlightThisWeek({data: allSalesBarChart, thisWeek})}
</Bar>
<Bar dataKey="videoPencil" fill={appColors.videoPencil} valueKey="week" stackId={0}>
{HighlightThisWeek({data: allSalesBarChart, thisWeek})}
</Bar>
<Bar dataKey="other" fill={appColors.other} valueKey="week" stackId={0} >
{HighlightThisWeek({data: allSalesBarChart, thisWeek})}
</Bar>
{allSalesBarChart.filter(d=>d.label).map(({label,week})=>(
<ReferenceLine key={week} x={week} stroke="red" strokeWidth={1} position="start">
<Label value={label + "→"} position="insideTopRight"/>
</ReferenceLine>
))}
{/* <Bar dataKey="proceeds" fill="#bcbcbc" valueKey="week" stackId={0}/> */}
{/* {planNames.map((name,index) => (
<Bar key={name} dataKey={name} valueKey="week" fill={purchaseColors[name]} stackId={1}/>
))} */}
<ReferenceLine stroke="red"/>
<Tooltip cursor={false}/>
</BarChart>
</ResponsiveContainer>
{/* <p className="not-sensitive">{`All time: ${app.allTimeSales.split('.')[0]}`}</p> */}
</div>
</Row>
<div class="row-group">
<div class="subsection">
<h2>Campaigns</h2>
{/* <Sparkline bars values={app.salesSparkline} color="#bcbcbc" description={`All time: ${app.allTimeSales.split('.')[0]}`}/> */}
<Row>
{false && AdSpend(shoot)}
{campaigns.length > 0 && <div className="campaigns">
<Row>
<table>
<tr>
<td>Campaign</td>
<td>Emails</td>
<td>Views</td>
<td>Sales</td>
</tr>
{campaigns.map(campaign => campaign && <tr className="campaign" key={campaign.campaign}>
<th className="name">{campaign.campaign}</th>
<td className="email_signups">{campaign.email_signups}</td>
<td className="impressions">{campaign.impressions}</td>
<td className="sales">{campaign.sales}</td>
</tr>)}
</table>
</Row>
</div>}
{/* this can now be wide, so... */}
{/* <Sparkline
values={app.sessionsSparkline}
title={`Average ${Math.round(app.sessionsSparkline.reduce((a,b) => a + b,0) / app.sessionsSparkline.length)}`}
description="Since April 2021"
/> */}
</Row>
</div>
<div class="subsection problems">
<Row>
<h2>Problems</h2>
</Row>
<Row>
<BarChart data={crashesSparkline} width={300} height={140} >
<XAxis dataKey="week" />
<YAxis orientation="right"/>
<Bar dataKey="shoot" fill={appColors.shoot} valueKey="week" stackId={0} >
{HighlightThisWeek({data: crashesSparkline, thisWeek})}
</Bar>
<Bar dataKey="videoPencil" fill={appColors.videoPencil} valueKey="week" stackId={0}>
{HighlightThisWeek({data: crashesSparkline, thisWeek})}
</Bar>
<Bar dataKey="videoPencilCamera" fill={appColors.videoPencilCamera} valueKey="week" stackId={0}>
{HighlightThisWeek({data: crashesSparkline, thisWeek})}
</Bar>
{/* <Bar dataKey="proceeds" fill="#bcbcbc" valueKey="week" stackId={0}/> */}
{/* {planNames.map((name,index) => (
<Bar key={name} dataKey={name} valueKey="week" fill={purchaseColors[name]} stackId={1}/>
))} */}
<ReferenceLine/>
<Tooltip cursor={false}/>
</BarChart>
</Row>
<Row>
<BigNumber
type="arrow"
title="Shoot"
color={appColors.shoot}
value={app.crashesChange}
inverted
direction={direction(-app.crashesChange)}
description={`${app.crashes} ${pluralize("Crash", app.crashes)}`}
/>
<BigNumber
type="arrow"
title="Video Pencil"
color={appColors.videoPencil}
value={videoPencil.crashesChange}
inverted
direction={direction(-videoPencil.crashesChange)}
description={`${videoPencil.crashes} ${pluralize("Crash", videoPencil.crashes)}`}
/>
<BigNumber
type="arrow"
title="VPC"
color={appColors.videoPencilCamera}
value={videoPencilCamera.crashesChange}
inverted
direction={direction(-videoPencilCamera.crashesChange)}
description={`${videoPencilCamera.crashes} ${pluralize("Crash", videoPencilCamera.crashes)}`}
/>
</Row>
</div>
<div class="subsection">
<h2>CueCam Presenter</h2>
<Row>
<BigNumber
value={beatSheetStudio.youTubeWatchTime}
title="YouTube Watch Time"
type="arrow"
direction={direction(beatSheetStudio.youTubeWatchTimeChange)}
description={`${beatSheetStudio.youTubeSubscribersCount} subscribers`}
/>
<BigNumber
value={beatSheetStudio.bugsFixed}
title="Bugs Fixed"
description={`${beatSheetStudio.newBugsCount} new`}
/>
<BigNumber
value={beatSheetStudio.bugsCountChange}
direction={direction(-beatSheetStudio.bugsCountChange)}
type="arrow"
title="Bugs"
inverted
description={`${beatSheetStudio.openBugs} open`}
/>
</Row>
<BigNumber
title="Music Sales"
value={music.cdBabySales}
description={music.cdBabyInfo}
wrapped
/>
<Row>
<div>
<h2>My YouTube </h2>
<Row>
<BigNumber
value={music.watchTime}
title="Watch Time"
type="arrow"
direction={direction(music.watchTimeChange)}
description="Hours"
/>
<BigNumber
title="Subscribers"
value={numeral(music.subscribers).format()}
direction={direction(music.newSubscribers)}
description={`${music.newSubscribers} new`}
/>
</Row>
</div>
<div>
<h2>TikTok</h2>
<Row>
<BigNumber
value={numeral(tiktok.views).format()}
title="Views"
type="arrow"
direction={direction(tiktok.viewsChange)}
description={`${numeral(tiktok.viewsChange * 100).format()}%`}
/>
<BigNumber
title="Followers"
value={numeral(tiktok.followers).format()}
direction={direction(tiktok.newFollowers)}
description={`${tiktok.newFollowers} new`}
/>
</Row>
</div>
</Row>
<BigNumber
title="Music Sales"
value={music.cdBabySales}
description={music.cdBabyInfo}
wrapped
/>
</div>
{/* <div class="subsection workload">
<h2>Workload</h2>
<Row>
{Delegation(delegatableTotal, totalHours, report)}
</Row>
</div> */}
</div>
<div className='row-group'>
{/* <Row title="Twitch" color="#9147FF">
<BigNumber
type="financial"
title="Twitch"
value={twitch.revenue}
description={`${twitch.subs} sub(s)`}
// direction={direction(donegood.patreonChange)}
/>
<BigNumber
type="arrow"
title="Avg Viewers"
value={twitch.averageViewersChange}
direction={direction(twitch.averageViewersChange)}
description={`${twitch.averageViewers} viewers`}
/>
<BigNumber
type="arrow"
title="Follows"
value={twitch.follows}
direction={direction(twitch.follows)}
/>
<BigNumber
type="arrow"
title="Streamed"
value={twitch.hoursStreamed}
// direction={direaction(twitch.hoursStreamedChange)}
/>
</Row> */}
</div>
</Column>
<Column title="Financials" color="#464646" className="financials">
<BigNumber
type="financial"
title={`Proceeds (£1 == $${report.usdConversionRate})`}
value={report.totalProceeds}
descriptionClass=""
direction={direction(report.totalProceedsChange)}
description={`${change(report.totalProceedsDifference)} (${change(report.totalProceedsChange)})`}
/>
<div class="sensitive">
<BigNumber
type="financial"
title="Balance"
direction={direction(parseInt(report.bankBalance.replace("£","").replace(",","")) - parseInt(report.balanceOneYearAgo.replace("£","").replace(",","")))}
value={report.bankBalance}
description={`${moment(report.date).subtract(1,"year").format("MMM YYYY")}: ${report.balanceOneYearAgo} `}
// progress={parseFloat(report.bankBalance.replace("£", "").replace(",","")) / 5000}
/>
<Row>
<Sparkline values={report.balanceSparkline} color="#464646" description="Balance history"/>
</Row>
{sortBy(report.payments, p=>p.date).filter(p=>p.amount != 0).slice(0,3).map(payment =>
<BigNumber
type="financial"
title={payment.description}
value={"£" + numeral(Math.abs(payment.amount)).format("0,0")}
description={moment(payment.date).format("DD MMM")}
direction={direction(payment.amount)}
/>
)}
</div>
</Column>
</Row>
</div>
}
}
export default App;
function ShootInAppPurchasesBreakdown(app) {
return <div>
<PieChart
width={64} height={64}
>
<Pie data={app.purchases.map(d => ({ n: d[0], v: d[2] }))} nameKey="n" valueKey="v">
{app.purchases.map((entry, index) => <Cell key={`cell-${index}`} fill={purchaseColors[entry[0]]} />)}
</Pie>
<Tooltip />
</PieChart>
<div style={{ marginRight: 20 }}>
<h3>IN-APP PURCHASES</h3>
<table>
<tbody>
{app.purchases.map(purchase => <tr className='campaigns' style={{ color: purchaseColors[purchase[0]] }}>
<th>{purchase[0]}</th>
<td>{purchase[1]}</td>
<td>${purchase[2]}</td>
</tr>
)}
</tbody>
</table>
</div>
</div>;
}
function Delegation(delegatableTotal, totalHours, report) {
return <div className="delegation">
{/* <Row>
<p>
<PieChart width={64} height={64}>
<Pie data={report.workload} nameKey="role" valueKey="delegatable">
{report.workload.map((entry, index) => (
<Cell key={`cell-${index}`} fill={roleColors[entry.role]} />
))}
</Pie>
<Tooltip />
</PieChart>
</p>
{chunk(report.workload.filter(row => row.role != "Me"), 5).map(items => <table>
{items.map(row => <tr style={{ color: roleColors[row.role] }}>
<th>{row.role}</th>
<th>{row.delegatable} Hour{row.delegatable == 1 ? "" : "s"}</th>
</tr>
)}
</table>
)}
</Row> */}
<BigNumber
description={"Could have been delegated"}
type="absolute"
value={Math.round(100 * (delegatableTotal / totalHours)) + "% of " + totalHours.toFixed(1) + " " + pluralize("hour", Math.round(100 * (delegatableTotal / totalHours)))}
direction={direction(0)}
title={`${delegatableTotal.toFixed(1)} ${pluralize("hour",delegatableTotal)}`}
/>
{
chunk(report.workload.filter(row => row.role != "Me"), 2).map(items =>
<Row>
{items.map(row =><BigNumber
title={row.role}
value={row.delegatable}
description={pluralize("hours", row.delegatable)}
/>
)}
</Row>
)}
</div>;
}
function iconFor(icon){
switch(icon){
case 'Facebook': return <AiFillFacebook/>
case 'YouTube': return <AiFillYoutube/>
case 'Twitch': return <ImTwitch/>
default: return icon
}
}
function BigNumber({type,title,value,description,direction,inverted,wrapped,descriptionClass="",color=null,progress}){
return <div className={`big-number ${type} ${direction} ${inverted?"inverted":""} ${wrapped?"wrapped":""}`} >
<h3>{color && <Swatch color={color}/>}{title}&nbsp;</h3>
<strong>{type === "arrow" ? `${value}`.replace("-","") : value}{progress && <ProgressMeter progress={progress}/>}</strong>
{Array.isArray(description) ? description.map(description => <p key={description} className={descriptionClass}>{description}</p> ):
<p className={descriptionClass}>{description}</p> }
</div>
}
function Column({title,className,color,children}){
return <div className={`column ${className ? className : ""}`} style={{borderColor: color }}>
<h2>{title}</h2>
{children}
</div>
}
const Row = ({children})=> <div className="row">{children}</div>
const Swatch = ({color}) => <div className="swatch" style={{backgroundColor: color}}/>
const HighlightThisWeek = ({data, thisWeek}) => <>{data.map((item,index) => (
<Cell opacity={item.week == thisWeek ? 1 : 0.9} key={`cell-${index}`} />
))}</>
const Sparkline = ({values,color,description,title,bars,height=40})=>(
<div className="sparky">
{title && <h3>{title}</h3>}
<Sparklines data={values} height={height} min={0}>
{bars ?
<SparklinesBars className="bars" barWidth={ 3} style={{fill: color}}/>
: <SparklinesLine color={color}/>
}
<SparklinesReferenceLine type="median" style={{stroke: "#333"}}/>
<SparklinesNormalBand/>
</Sparklines>
<p className="not-sensitive">{description}</p>
</div>
)
const AppDetails = ({app})=> <Fragment>
</Fragment>
const ProgressMeter = ({progress}) => {
let percentage = Math.round(progress * 100)
return <div style={{width: 34,display: "inline-block",marginLeft: 5,verticalAlign: "top"}}>
<CircularProgressbar value={percentage} text={`${percentage}%`} />
</div>
}
function AdSpend(shoot) {
return <Row>
<BigNumber
title="Ad Spend"
value={shoot.adSpend}
description={`${shoot.adImpressions} impressions`} />
<BigNumber
title="Average CPA"
value={shoot.averageCPA}
direction={direction(shoot.averageCPAChange, true)}
description={`${shoot.averageCPAChange}`} />
<BigNumber
title="Conversion Rate"
value={shoot.searchAdCR}
description={`${shoot.searchAdTaps} Taps, ${shoot.searchAdInstalls} Installs`} />
</Row>;
}
$oldgreen: #4CC560;
$green: #1ad339;
$orange: #eec750;
$red: #D0021B;
$small-text: 12px;
$line-height: 14px;
// https://modernfontstacks.com/
$transitional: Charter, 'Bitstream Charter', 'Sitka Text', Cambria, serif;
@mixin sensitive(){
font-family: "Redacted";
opacity: 0.5;
}
body, pre{
font-family: "Helvetica Neue";
// line-height: 16px;
}
.App {
text-align: center;
background-color: white;
padding: 16px;
&.hidden-sensitive .sensitive{
@include sensitive();
}
}
.row{
display: flex;
}
.column{
text-align: left;
border-left: 2px solid;
padding-left: 16px;
padding-top: 16px;
margin-right: 16px;
&.shoot{
flex: 5;
}
}
.target{
background-color: #333;
color: white;
padding: 8px;
}
.headlines .row, .row-group{
display: flex;
> * {
}
.group{
margin-right: 40px;
display: flex;
}
}
.swatch{
width: 10px;
height: 10px;
display: inline-block;
margin-right: 6px;
}
.delegation{
margin-left: 10px;
table {
display: block;
margin-left: 10px;
}
td, th{
// height: 10px;
// line-height: 10px;
}
}
tspan{ // for charts
font-size: 50%;
}
h2{
margin: 0;
margin-bottom: 16px;
white-space: nowrap;
}
h3,pre,p,li{
font-size: $small-text;
margin: 0;
line-height: $line-height;
}
h3{
white-space: nowrap;
font-weight: bold;
}
table{
border-spacing: 0;
}
h3,pre,p,tr{
text-transform: uppercase;
font-weight: bold;
}
.recharts-label,.recharts-text{
// font-size: 20px;
text-transform: uppercase;
font-weight: bold;
white-space: pre;
}
td,th{
font-size: $small-text;
line-height: $line-height;
text-align: right;
}
td{
padding-left: 8px;
}
.row pre{
margin-top: 16px;
margin-right: 16px;
}
p,pre{
color: #aaa;
line-height: $small-text;
}
li{
font-weight: bold;
}
.money-sparkline{
width: 200px;
padding-top: 12px;
margin-right: 5px;
.sparky{
margin-bottom: -16px;
}
}
.what-was-happening{
// max-width: 200px;
text-align: left;
}
.row-group{
margin-top: 40px;
}
.sparky{
margin-bottom: 16px;
text-align: right;
flex: 1;
circle{
display: none;
}
// line{
// stroke-width: 0.2;
// }
.not-this-week{
opacity: 0.3;
}
}
.spacer{
flex: 2;
}
.big-number{
margin-right: 16px;
margin-bottom: 24px;
text-align: left;
strong{
font-size: 28px;
margin: 2px 0;
display: block;
margin-top: -4px;
white-space: nowrap;
}
p{
white-space: nowrap;
}
&.wrapped p{
white-space: inherit;
}
&.financial{
strong{
// @include sensitive();
}
}
&.financial, &.undefined{
&.better p{
color: $green;
}
&.worse p{
color: $red;
}
}
&.absolute{
strong{
font-weight: normal;
}
&.better strong{
color: $green;
}
&.worse strong{
color: $red;
}
}
&.growth{
&.worse strong{
color: $red;
}
&.better strong {
color: $green;
}
}
&.arrow{
strong{
font-weight: normal;
}
&.better strong{
color: $green;
&:before{
content: "􀄤";
font-family: "SF Pro";
font-size: 10px;
vertical-align: middle;
margin-right: 4px;
}
}
&.worse strong{
color: $red;
&:before{
content: "􀄥";
font-family: "SF Pro";
font-size: 10px;
vertical-align: middle;
margin-right: 4px;
}
}
&.inverted.better strong:before{
content: "􀄥";
}
&.inverted.worse strong:before{
content: "􀄤";
}
}
}
.account{
list-style: none;
}
.campaigns{
.row{
text-transform: uppercase;
font-weight: bold;
font-size: 9px;
.name{
color: #999;
flex: 2;
}
.impressions{
color: #999;
margin-right: 5px;
}
}
margin-bottom: 20px;
}
.CircularProgressbar{
vertical-align: inherit;
}
th{
text-align: right;
}
.free-text{
font-family: $transitional;
text-transform: none;
color: #333;
font-style: italic;
font-size: 18px;
line-height: $line-height*1.5;
}
pre.summary, .subsection{
margin-right: 80px;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment