Created
August 21, 2024 10:31
-
-
Save michaelforrest/b6dc049507c6a9403596842ca718f769 to your computer and use it in GitHub Desktop.
Dashboard front end code reference (bring your own data)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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} </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>; | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
$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