Pen Settings

HTML

CSS

CSS Base

Vendor Prefixing

Add External Stylesheets/Pens

Any URL's added here will be added as <link>s in order, and before the CSS in the editor. If you link to another Pen, it will include the CSS from that Pen. If the preprocessor matches, it will attempt to combine them before processing.

+ add another resource

JavaScript

Babel includes JSX processing.

Add External Scripts/Pens

Any URL's added here will be added as <script>s in order, and run before the JavaScript in the editor. You can use the URL of any other Pen and it will include the JavaScript from that Pen.

+ add another resource

Packages

Add Packages

Search for and use JavaScript packages from npm here. By selecting a package, an import statement will be added to the top of the JavaScript editor for this package.

Behavior

Save Automatically?

If active, Pens will autosave every 30 seconds after being saved once.

Auto-Updating Preview

If enabled, the preview panel updates automatically as you code. If disabled, use the "Run" button to update.

Format on Save

If enabled, your code will be formatted when you actively save your Pen. Note: your code becomes un-folded during formatting.

Editor Settings

Code Indentation

Want to change your Syntax Highlighting theme, Fonts and more?

Visit your global Editor Settings.

HTML

              
                <div id="root"></div>
              
            
!

CSS

              
                
              
            
!

JS

              
                /******* Styled-components *******/
const theme = {
 colors: {
  primary: 'hsla(317, 45%, 52%, 1)',
  secondary: 'hsla(340, 95%, 50%, 1)',
  primary_transparent: 'hsla(317, 45%, 52%, 0.05)',
  secondary_transparent: 'hsla(340, 95%, 50%, 0.05)',
  white: 'hsla(0, 100%, 100%, 1)',
  black: 'hsla(0, 0%, 0%, 1)',
  shadow1: 'hsla(0, 0%, 0%, 0.15)',
  shadow2: 'hsla(0, 0%, 0%, 0.3)',
  shadow3: 'hsla(0, 0%, 0%, 0.75)',
  grey: 'hsla(9, 9%, 73%, 1)',
  light_grey: 'hsla(220, 10%, 90%, 1)',
  dark_grey: 'hsla(0, 0%, 20%, 1)',
  turquoise: 'hsla(179, 81%, 65%, 1)',
  red: 'hsla(2, 48%, 57%, 1)',
  purple: 'hsla(254, 15%, 60%, 1)',
  yellow: 'hsla(39, 98%, 84%, 1)',
  light_beige: 'hsla(354, 11%, 92%, 1)',
  dark_beige: 'hsla(338, 6%, 65%, 1)',
  light_beige_shadow: 'hsla(0, 10%, 71%, 0.9)',
  grey_shadow: 'hsla(9, 9%, 53%, 1)',
  red_shadow: 'hsla(2, 48%, 37%, 1)',
  purple_shadow: 'hsla(254, 15%, 40%, 1)',
  yellow_shadow: 'hsla(34, 59%, 68%, 1)',
 },
}

const Theme = ({ children }) => (
 <styled.ThemeProvider theme={theme}>{children}</styled.ThemeProvider>
)

const GlobalStyle = styled.createGlobalStyle`
* {
  padding: 0;
  margin: 0;
}

html {
  font-size: 62.5%;
  scrollbar-color: ${({ theme: { colors } }) =>
   `${colors.primary} ${colors.secondary}`};
  box-sizing: border-box;
}

*, *:before, *:after {
	box-sizing: inherit;
}

body {
  font-family: 'Roboto', sans-serif;
  color: ${({ theme: { colors } }) => colors.dark_grey};
  background: linear-gradient(35deg, ${({ theme: { colors } }) =>
   colors.primary}, ${({ theme: { colors } }) => colors.secondary} 100%);
  user-select: none;
}
`

// App styles
const AppContainer = styled.div`
 position: relative;
 display: flex;
 justify-content: center;
 align-items: center;
 min-height: 100vh;
`
const Calculator = styled.div`
 background: 
    /* paper roll */ linear-gradient(
    ${({ theme: { colors } }) => colors.white} 0% 100%
   )
   15.1em 2em / 8.4em 13em,
  radial-gradient(
    100% 100% at 50% 50%,
    ${({ theme: { colors } }) => colors.grey} 5%,
    ${({ theme: { colors } }) => colors.white} 8%,
    ${({ theme: { colors } }) => colors.grey} 11%,
    ${({ theme: { colors } }) => colors.white} 14%,
    ${({ theme: { colors } }) => colors.grey} 17%,
    ${({ theme: { colors } }) => colors.white} 20%,
    ${({ theme: { colors } }) => colors.grey} 23%,
    ${({ theme: { colors } }) => colors.white} 26%,
    ${({ theme: { colors } }) => colors.grey} 29%,
    ${({ theme: { colors } }) => colors.white} 32%,
    ${({ theme: { colors } }) => colors.grey} 35%,
    ${({ theme: { colors } }) => colors.white} 38%,
    ${({ theme: { colors } }) => colors.grey} 41%,
    ${({ theme: { colors } }) => colors.white} 44%,
    ${({ theme: { colors } }) => colors.grey} 47%,
    transparent 50%
   )
   23.5em 0.8em / 3em 10.4em,
  radial-gradient(
    200% 200% at 100% 100%,
    ${({ theme: { colors } }) => colors.white} 0%,
    ${({ theme: { colors } }) => colors.light_grey} 49%,
    transparent 52%
   )
   15.1em 1em / 1.2em 1.2em,
  linear-gradient(
    hsla(0, 0%, 93%, 1) 0%,
    ${({ theme: { colors } }) => colors.white} 15%,
    ${({ theme: { colors } }) => colors.white} 80%,
    ${({ theme: { colors } }) => colors.light_grey} 100%
   )
   16.1em 1em / 9em 10em,
  /* paper slit */
   linear-gradient(
    ${({ theme: { colors } }) => colors.grey},
    ${({ theme: { colors } }) => colors.black}
   )
   13.3em 14.8em / 12em 0.4em,
  /* screen corners */
   radial-gradient(
    200% 200% at 100% 100%,
    ${({ theme: { colors } }) => colors.black} 50%,
    transparent 52%
   )
   4em 16.1em / 1em 1em,
  radial-gradient(
    200% 200% at 0% 100%,
    ${({ theme: { colors } }) => colors.black} 50%,
    transparent 52%
   )
   55em 16.1em / 1em 1em,
  radial-gradient(
    200% 200% at 100% 0%,
    ${({ theme: { colors } }) => colors.black} 50%,
    transparent 52%
   )
   4em 22em / 6em 6em,
  radial-gradient(
    200% 200% at 0% 0%,
    ${({ theme: { colors } }) => colors.black} 50%,
    transparent 52%
   )
   50em 22em / 6em 6em,
  /* screen body */
   linear-gradient(${({ theme: { colors } }) => colors.black} 0% 100%) 4.9em
   16.1em / 50.3em 1em,
  linear-gradient(${({ theme: { colors } }) => colors.black} 0% 100%) 4em 17em /
   52em 6em,
  linear-gradient(${({ theme: { colors } }) => colors.black} 0% 100%) 9em 16.1em /
   42em 11.9em,
  /* upper body rounded corners */
   radial-gradient(
    200% 200% at 100% 0%,
    ${({ theme: { colors } }) => colors.light_beige} 0em,
    ${({ theme: { colors } }) => colors.light_beige} 2.9em,
    ${({ theme: { colors } }) => colors.dark_beige} 49%,
    ${({ theme: { colors } }) => colors.light_beige} 51%
   )
   2.6em 27.1em / 3.1em 3.1em,
  radial-gradient(
    200% 200% at 0% 0%,
    ${({ theme: { colors } }) => colors.light_beige} 0em,
    ${({ theme: { colors } }) => colors.light_beige} 2.9em,
    ${({ theme: { colors } }) => colors.dark_beige} 49%,
    ${({ theme: { colors } }) => colors.light_beige} 51%
   )
   54.2em 27.1em / 3.1em 3.1em,
  /* calculator upper body */
   linear-gradient(
    ${({ theme: { colors } }) => colors.dark_beige} 0em,
    ${({ theme: { colors } }) => colors.light_beige} 0.2em,
    ${({ theme: { colors } }) => colors.light_beige} 18em,
    ${({ theme: { colors } }) => colors.dark_beige} 18.1em
   )
   2.8em 12em / 54.3em 18.2em,
  linear-gradient(
    90deg,
    ${({ theme: { colors } }) => colors.dark_beige} 0em,
    ${({ theme: { colors } }) => colors.light_beige} 0.2em,
    ${({ theme: { colors } }) => colors.light_beige} 54.5em,
    ${({ theme: { colors } }) => colors.dark_beige} 54.6em
   )
   2.6em 12em / 54.7em 18em,
  /* roll stand */
   radial-gradient(
    200% 200% at 100% 100%,
    ${({ theme: { colors } }) => colors.dark_beige} 0%,
    ${({ theme: { colors } }) => colors.white} 25%,
    ${({ theme: { colors } }) => colors.dark_beige} 50%,
    transparent 51%
   )
   11em 5.5em / 1.1em 1.1em,
  linear-gradient(
    ${({ theme: { colors } }) => colors.dark_beige} 0%,
    ${({ theme: { colors } }) => colors.white} 45%,
    ${({ theme: { colors } }) => colors.dark_beige} 100%
   )
   12em 5.5em / 6em 1em,
  linear-gradient(
    90deg,
    ${({ theme: { colors } }) => colors.dark_beige} 0%,
    ${({ theme: { colors } }) => colors.white} 45%,
    ${({ theme: { colors } }) => colors.dark_beige} 100%
   )
   11em 6.5em / 1em 8em,
  /* calculator lower body */
   linear-gradient(
    ${({ theme: { colors } }) => colors.light_beige} 0em,
    ${({ theme: { colors } }) => colors.light_beige} 21em
   )
   2.6em 30em / 54.7em 25em,
  /* very slanted sides */
   linear-gradient(
    -82deg,
    ${({ theme: { colors } }) => colors.light_beige} 59%,
    ${({ theme: { colors } }) => colors.dark_beige} 62%,
    transparent 66%
   )
   1.1em 18.1em / 3em 12em,
  linear-gradient(
    82deg,
    ${({ theme: { colors } }) => colors.light_beige} 59%,
    ${({ theme: { colors } }) => colors.dark_beige} 62%,
    transparent 66%
   )
   55.9em 18.1em / 3em 12em,
  /* slightly slanted sides */
   linear-gradient(
    88deg,
    ${({ theme: { colors } }) => colors.light_beige} 58%,
    ${({ theme: { colors } }) => colors.dark_beige} 65%,
    transparent 67%
   )
   57.2em 30em / 3em 25em,
  linear-gradient(
    -88deg,
    ${({ theme: { colors } }) => colors.light_beige} 58%,
    ${({ theme: { colors } }) => colors.dark_beige} 65%,
    transparent 67%
   ) -0.2em 30em / 3em 25em,
  /* right corner bottom */
   radial-gradient(
    200% 100% at 0% 0%,
    ${({ theme: { colors } }) => colors.light_beige} 4em,
    ${({ theme: { colors } }) => colors.dark_beige} 4.2em,
    ${({ theme: { colors } }) => colors.dark_beige} 4.4em,
    ${({ theme: { colors } }) => colors.light_beige} 4.6em,
    ${({ theme: { colors } }) => colors.light_beige} 4.8em,
    ${({ theme: { colors } }) => colors.dark_beige} 5em,
    ${({ theme: { colors } }) => colors.dark_beige} 50%,
    transparent 51%
   )
   54.7em 54.8em / 5.2em 8.7em,
  /* left corner bottom */
   radial-gradient(
    200% 100% at 100% 0%,
    ${({ theme: { colors } }) => colors.light_beige} 4em,
    ${({ theme: { colors } }) => colors.dark_beige} 4.2em,
    ${({ theme: { colors } }) => colors.dark_beige} 4.4em,
    ${({ theme: { colors } }) => colors.light_beige} 4.6em,
    ${({ theme: { colors } }) => colors.light_beige} 4.8em,
    ${({ theme: { colors } }) => colors.dark_beige} 5em,
    ${({ theme: { colors } }) => colors.dark_beige} 50%,
    transparent 51%
   )
   0.1em 54.8em / 5.2em 8.7em,
  /* base bottom */
   radial-gradient(
    200% 100% at 50% 0%,
    ${({ theme: { colors } }) => colors.light_beige} 41em,
    ${({ theme: { colors } }) => colors.dark_beige} 43em,
    ${({ theme: { colors } }) => colors.dark_beige} 44em,
    ${({ theme: { colors } }) => colors.light_beige} 46em,
    ${({ theme: { colors } }) => colors.light_beige} 47em,
    ${({ theme: { colors } }) => colors.dark_beige} 49em,
    ${({ theme: { colors } }) => colors.dark_beige} 50em,
    ${({ theme: { colors } }) => colors.dark_beige} 49%,
    transparent 51%
   )
   5em 54.9em / 50em 10em;
  background-repeat: no-repeat;
  font-size: 1.2rem;
  width: 60em;
  height: 60em;
  position: relative;

  @media screen and (max-width: 57em) {
      font-size: 0.55rem;
    }
`
const Screen = styled.span`
 font-size: 1em;
 display: flex;
 justify-content: flex-end;
 overflow: hidden;
 white-space: nowrap;
 color: ${({ theme: { colors } }) => colors.turquoise};
 position: absolute;
 left: 5em;
 top: 19em;
 width: 49em;
 line-height: 1.1;

 & > span {
  font-size: 4em;
 }
`
const ScreenExpression = styled.span`
 display: flex;
 justify-content: flex-end;
 overflow: hidden;
 white-space: nowrap;
 color: ${({ theme: { colors } }) => colors.turquoise};
 position: absolute;
 left: 7em;
 top: 24em;
 width: 45em;

 & > span {
  font-size: 2em;
 }
`
const Equal = styled.span`
 margin-left: 0.5em;
`
const Brand = styled.div`
 position: absolute;
 font-size: 1em;
 font-weight: 600;
 top: 28.4em;
 left: 6em;

 & > span {
  font-size: 1.5em;
 }
`
const ProductNumber = styled.div`
 position: absolute;
 font-size: 1em;
 top: 28.7em;
 left: 11.5em;
`

// MiniHistoryList styles
const MiniHistoryListContainer = styled.div`
 position: absolute;
 display: flex;
 flex-direction: column-reverse;
 align-content: flex-end;
 padding: 0.5em;
 width: 8.5em;
 height: 13em;
 top: 2em;
 left: 15em;
 overflow: hidden;
`
const List = styled.ul`
 list-style-type: none;
 padding: 0.5em;
`

// MiniHistoryItem styles
const ItemMiniHistory = styled.li`
 position: relative;
 display: flex;
 align-items: center;
 width: 100%;
 white-space: nowrap;
 text-overflow: ellipsis;
 overflow: hidden;
 margin-bottom: 0.5rem;

 & > span {
  font-size: 0.8em;
 }
`

// HistoryToggle styles
const Toggle = styled.div`
 position: absolute;
 display: flex;
 justify-content: center;
 align-items: center;
 height: 12em;
 width: 8.5em;
 top: 3em;
 left: 15em;

 &:hover {
  box-shadow: 0.3em 0.3em 0.3em 0 ${({ theme: { colors } }) => colors.shadow2};
  cursor: pointer;

  .icon {
   display: inline;
  }
 }
`
const HistoryToggleHistoryIcon = styled.div`
 display: none;
 font-size: 3em;
 color: ${({ theme: { colors } }) => colors.secondary};
`

// HistoryList styles
const HistoryListContainer = styled.div`
 background-color: ${({ theme: { colors } }) => colors.white};
 width: 50em;
 min-height: 16em;
 max-height: 50em;
 box-sizing: border-box;
 position: absolute;
 top: 0;
 left: 4em;
 box-shadow: 0 -0.1em 1em -0.6em ${({ theme: { colors } }) => colors.black};
 padding-bottom: 1em;
 display: flex;
 flex-direction: column;
 z-index: 1;

 &::before,
 &::after {
  content: '';
  position: absolute;
  width: 50em;
  background-size: 1.5em 2em;
  background-repeat: repeat-x;
 }

 &::before {
  height: 1.1em;
  top: -1.1em;
  background-image: linear-gradient(
    225deg,
    transparent 50%,
    ${({ theme: { colors } }) => colors.light_grey} 52%,
    ${({ theme: { colors } }) => colors.white} 60%
   ),
   linear-gradient(
    135deg,
    transparent 50%,
    ${({ theme: { colors } }) => colors.light_grey} 52%,
    ${({ theme: { colors } }) => colors.white} 60%
   );
  background-position: top left, top left;
 }

 &::after {
  height: 0.8em;
  bottom: -0.8em;
  background-image: linear-gradient(
    225deg,
    ${({ theme: { colors } }) => colors.white} 50%,
    ${({ theme: { colors } }) => colors.light_grey} 52%,
    transparent 60%
   ),
   linear-gradient(
    135deg,
    ${({ theme: { colors } }) => colors.white} 50%,
    ${({ theme: { colors } }) => colors.light_grey} 52%,
    transparent 60%
   );
  background-position: bottom left, bottom left;
 }
`
const NoContent = styled.div`
 font-size: 3em;
 align-self: center;
`
const IconContainer = styled.div`
 display: flex;
 justify-content: space-between;
 border-bottom: 0.1em solid ${({ theme: { colors } }) => colors.light_grey};
 padding: 1em;
`
const HistoryIcon = styled.div`
 font-size: 2em;
 color: ${({ theme: { colors } }) => colors.primary};

 &:hover {
  cursor: pointer;
 }
`
const TrashIcon = styled.div`
 font-size: 2em;
 color: ${({ theme: { colors } }) => colors.secondary};

 &:hover {
  cursor: pointer;
 }
`
const HistoryListList = styled.ul`
 position: relative;
 display: flex;
 flex-direction: column;
 list-style-type: none;
 overflow: auto;
 padding: 1em;
 max-height: 22.5em;
 z-index: 1;
 scrollbar-color: ${({ theme: { colors } }) => colors.primary} transparent;
`

// HistoryItem styles
const ItemHistoryItem = styled.li`
 display: flex;
 align-items: center;
 position: relative;
 width: 100%;
 white-space: nowrap;
 margin-bottom: 1em;
`
const HistoryItemEqual = styled.span`
 font-size: 2em;
`
const Content = styled.div`
 display: inline-block;
 margin: 0 1em;
 position: relative;
 max-width: 50%;
 border: 0.1em solid
  ${({ total, theme: { colors } }) =>
   total ? colors.secondary : colors.primary};
 border-radius: 0.5em;
 color: ${({ error, theme: { colors } }) => (error ? colors.red : 'inherit')};
 box-sizing: border-box;
 padding: 0.6em 1.2em;
 white-space: nowrap;
 text-overflow: ellipsis;
 overflow: hidden;

 &:hover {
  background-color: ${({ total, theme: { colors } }) =>
   total ? colors.secondary_transparent : colors.primary_transparent};
  cursor: pointer;
 }

 & > span {
  font-size: 2em;
 }
`

// DisplayItem styles
const Pow = styled.sup`
 font-size: 0.7em;
`

// DisplayValue styles
const Paren = styled.span`
 color: ${({ theme: { colors } }) => colors.light_grey};
`

// KeyList styles
const Grid = styled.div`
 position: absolute;
 z-index: 1;
 top: 31em;
 left: 5em;
 display: grid;
 grid-template-columns: 5em 1fr repeat(3, 5em) 1fr 5em 1fr 5em 5em;
 grid-template-rows: 3.5em repeat(4, 1fr);
 grid-gap: 0.8em 1.2em;
 width: 51em;
 height: 25em;
`
const Key = styled.button`
  ${(props) =>
   (props.plus && `grid-row: span 3;`) ||
   (props.equal && `grid-row: span 2;`) ||
   (props.zero && `grid-column: span 2;`) ||
   (props.dot && `grid-column: span 2;`)}
  box-shadow: inset 0em -0.6em 0.1em 0.3em ${(props) =>
   (props.digit && props.theme.colors.light_beige_shadow) ||
   (props.red && props.theme.colors.red_shadow) ||
   (props.purple && props.theme.colors.purple_shadow) ||
   (props.yellow && props.theme.colors.yellow_shadow) ||
   props.theme.colors.grey_shadow},
    0em 0.1em 0.05em 0.1em ${({ theme: { colors } }) => colors.shadow3},
    0.4em -0.2em 0.2em 0.2em ${({ theme: { colors } }) => colors.shadow1};
  background-color: ${(props) =>
   (props.digit && props.theme.colors.light_beige) ||
   (props.red && props.theme.colors.red) ||
   (props.purple && props.theme.colors.purple) ||
   (props.yellow && props.theme.colors.yellow) ||
   props.theme.colors.grey};
  border: 1px solid hsla(0, 0%, 0%, 1);
  border-top-color: ${(props) =>
   (props.digit && props.theme.colors.light_beige_shadow) ||
   (props.red && props.theme.colors.red_shadow) ||
   (props.purple && props.theme.colors.purple_shadow) ||
   (props.yellow && props.theme.colors.yellow_shadow) ||
   props.theme.colors.grey_shadow};
  white-space: nowrap;
  cursor: pointer;
  outline: none;
  font-family: 'Roboto', sans-serif;
  font-size: 1em;
  border-radius: 1em;
  user-select: none;
  text-decoration: none;
  display: flex;
  justify-content: center;
  align-items: center;
 
  ::-moz-focus-inner {
    border: 0;
  }

  &:active {
    box-shadow: inset -0.3em 0.1em 0.2em 0.1em ${(props) =>
     (props.digit && props.theme.colors.light_beige_shadow) ||
     (props.red && props.theme.colors.red_shadow) ||
     (props.purple && props.theme.colors.purple_shadow) ||
     props.theme.colors.grey_shadow},0em 0.1em 0.05em 0.1em ${({
 theme: { colors },
}) => colors.shadow3};
    border-top-color: ${({ theme: { colors } }) => colors.dark_grey};
    padding-top: 0.3em;
  }
`
const KeyFont = styled.span`
 font-size: ${(props) =>
  (props.digit && `2.5em`) ||
  (props.red && `3em`) ||
  (props.purple && `2em`) ||
  (props.yellow && `1.5em`) ||
  `2.5em`};
`
const KeyListPow = styled.sup`
 font-size: 0.7em;
`
const EmptyColumn1 = styled.div`
 grid-column: 2;
 grid-row: 2 / span 3;
`
const EmptyColumn2 = styled.div`
 grid-column: 6;
 grid-row: 2 / span 3;
`
const EmptyColumn3 = styled.div`
 grid-column: 8;
 grid-row: 2 / span 4;
`
const EmptyColumn4 = styled.div`
 grid-column: 3 / span 6;
`

// AngleSwitch styles
const AngleSwitchContainer = styled.div`
 font-size: 1em;
 display: flex;
 align-items: center;
 justify-content: space-around;
 background: ${({ theme: { colors } }) => colors.white};
 border: 0.1em solid ${({ theme: { colors } }) => colors.dark_beige};
 box-shadow: inset 0.1em 0.1em 0.2em
  ${({ theme: { colors } }) => colors.shadow2};
 grid-column: 1 / span 2;
 position: relative;
 width: 7em;
 height: 3em;
 border-radius: 0.5em;

 &:hover {
  cursor: pointer;
 }
`
const ToggleButton = styled.div`
 position: absolute;
 top: -0.1em;
 right: -0.1em;
 bottom: -0.1em;
 left: 50%;
 border: 0.1em solid ${({ theme: { colors } }) => colors.dark_beige};
 background-image: linear-gradient(
  180deg,
  ${({ theme: { colors } }) => colors.light_beige} 0% 25%,
  ${({ theme: { colors } }) => colors.dark_beige} 25% 50%,
  ${({ theme: { colors } }) => colors.light_beige} 50% 75%,
  ${({ theme: { colors } }) => colors.dark_beige} 75%
 );
 border-radius: 0.5em;
 transition: transform 200ms ease-in-out;
 ${({ angle }) => angle === 'Deg' && 'transform: translate(-100%, 0);'}
`
const Angle = styled.span`
 font-size: 1.6em;
`

/******* Javascript utilities *******/
// Action types
const SET_ANGLE = 'SET_ANGLE'
const ADD = 'ADD'
const CLEAR = 'CLEAR'
const EQUAL = 'EQUAL'
const UNDO = 'UNDO'
const HISTORY = 'HISTORY'
const DELETE_HISTORY = 'DELETE_HISTORY'

const isNumeric = (n) => !Number.isNaN(Number(n)) // Returns true if it's a number
const last = (a) => a[a.length - 1] // Peeks the last item in the array
const last2 = (a) => a[a.length - 2] // Peeks the second to last item in the array
const toRadians = (angle) => angle * (Math.PI / 180) // Converts from radians to degrees for trigonometric functions

// Returns the number of open parentheses in the array
const openParens = (arr) => {
 return arr.reduce((acc, cur) => {
  if (acc < 0) return acc
  else if (cur === '(') return ++acc
  else if (cur === ')') return --acc
  return acc
 }, 0)
}

// Fills the array with closing parentheses
const fillClosingParens = (arr) => new Array(openParens(arr)).fill(')')

// Trims the total
const trimTotal = (total) => {
 const totalString = total.toString()
 const totalCut = total.toPrecision(12)

 // Number is equal or smaller than 12 after removing unary minus and dot
 if (totalString.replace(/^-/, '').replace(/\./, '').length <= 12)
  return totalString
 // Exponent symbol e found
 else if (totalCut.indexOf('e') !== -1) {
  // Includes the exponent and trailing characters
  const exponentChars = totalCut.match(/e.*$/)[0].length

  // Truncate the number and remove zeros before the exponent
  return total
   .toPrecision(12 - exponentChars - (totalCut[0] === '0' ? 1 : 0))
   .replace(/\.?0*e/, 'e')
 }
 // No exponent symbol e found
 else {
  // Null or array of length 1 or 2 if the unary minus or dot is found
  const arrayChars = totalCut.match(/(^-|\.)/g)

  // Reduce the string to 12 digits plus unary minus and dot
  const reducedTotal = totalCut.substr(
   0,
   12 + (arrayChars ? arrayChars.length : 0)
  )

  // If decimal number remove trailing zeros
  return reducedTotal.indexOf('.') !== -1
   ? reducedTotal.replace(/\.?0*$/, '')
   : reducedTotal
 }
}

// Stacks the superscript exponents
const arraySuperscript = (expression) => {
 let parent = 0
 let stackParens = []
 const operatorsSuperscript = ['+', '-', '×', '÷', '√', 'sin', 'cos', 'tan']
 const functionsSuperscript = ['√', 'sin', 'cos', 'tan']

 return expression
  .reduce((acc, value, idx) => {
   if (value === '▢') {
    parent = idx
    return !expression[idx + 1]
     ? [...acc, { id: ++idx, parent, value, show: true }]
     : [...acc, { id: ++idx, parent, value }]
   }

   if (value === '(' || functionsSuperscript.includes(value)) {
    if (acc.length > 0 && acc[idx - 1].value !== '▢')
     parent = last(stackParens) ? last(stackParens).parent : 0

    if (
     (acc.length > 0 && !functionsSuperscript.includes(acc[idx - 1].value)) ||
     acc.length === 0
    ) {
     stackParens = [
      ...stackParens,
      {
       id: `filler${stackParens.length + 1}`,
       parent,
       value: ')',
       stackParens: true,
      },
     ]
    }
   }

   if (value === ')') {
    const closingParens = [
     ...acc,
     { id: ++idx, parent: last(stackParens).parent, value },
    ]
    stackParens.pop()
    return closingParens
   }

   if (operatorsSuperscript.includes(value))
    parent = last(stackParens) ? last(stackParens).parent : 0

   return [...acc, { id: ++idx, parent, value }]
  }, [])
  .concat(stackParens)
}

// Nests recursively the children elements with the right parent when stacking exponents
const nestChildren = (expression, parent = 0) => {
 return expression.reduce((acc, cur) => {
  if (cur.parent === parent) {
   const children = nestChildren(expression, cur.id)

   if (children.length) cur.children = children

   return [...acc, cur]
  }
  return acc
 }, [])
}

// Converts the infix expression to postfix or Reversed Polish Notation using the Shunting Yard Algorithm
const postfix = (expression) => {
 const ops = {
  '+': 2,
  '-': 2,
  '×': 3,
  '÷': 3,
  unary: 4,
  '▢': 5,
  '√': 6,
  '%': 6,
  sin: 6,
  cos: 6,
  tan: 6,
 }
 const functions = ['%', '√', 'sin', 'cos', 'tan']
 const operators = ['+', '-', '×', '÷', '▢']
 const stack = []
 const postfix = []

 const infix = expression.reduce((acc, cur, idx) => {
  // Handles implicit multiplications for example 6(6) or 5 sin(30) 8
  if (
   (cur === '(' ||
    cur === 'sin' ||
    cur === 'cos' ||
    cur === 'tan' ||
    cur === '√') &&
   (isNumeric(expression[idx - 1]) ||
    expression[idx - 1] === ')' ||
    expression[idx - 1] === '%')
  )
   acc = [...acc, '×', cur]
  else acc = [...acc, cur]

  return acc
 }, [])

 // Conversion to postfix starts here
 for (let [idx, token] of infix.entries()) {
  if (Number(token) || Number(token) === 0) {
   postfix.push(token)
   continue
  }

  if (functions.includes(token)) {
   stack.push(token)
   continue
  }

  if (token === '-') {
   if (
    idx === 0 ||
    infix[idx - 1] === '(' ||
    operators.includes(infix[idx - 1])
   )
    token = 'unary'
  }

  if (token in ops) {
   while (
    ops[last(stack)] > ops[token] ||
    (ops[last(stack)] >= ops[token] && token !== '▢')
   )
    postfix.push(stack.pop())
   stack.push(token)
   continue
  }

  if (token === '(') {
   stack.push(token)
   continue
  }

  if (token === ')') {
   while (last(stack) !== '(') {
    if (stack.length === 0) break
    postfix.push(stack.pop())
   }
   stack.pop()
   continue
  }
 }

 while (stack.length) {
  let op = stack.pop()
  if (op === ')') break
  postfix.push(op)
 }

 return postfix
}

// Parses and evaluates the postfix expression and returns the total
const evaluate = (postfix, angle) => {
 return postfix
  .reduce((stack, token, i) => {
   if (isNumeric(token)) stack.push(token)
   else if (
    token === '√' ||
    token === 'sin' ||
    token === 'cos' ||
    token === 'tan' ||
    token === 'unary'
   ) {
    const operand = Number(stack.pop())

    switch (token) {
     case '√':
      stack.push(Math.sqrt(operand))
      break
     case 'sin':
      stack.push(Math.sin(angle === 'Rad' ? operand : toRadians(operand)))
      break
     case 'cos':
      stack.push(Math.cos(angle === 'Rad' ? operand : toRadians(operand)))
      break
     case 'tan':
      stack.push(Math.tan(angle === 'Rad' ? operand : toRadians(operand)))
      break
     case 'unary':
      stack.push(-operand)
      break
     default:
      throw new Error('Error found in unary functions')
    }
   } else {
    const right = Number(stack.pop())
    const left = Number(stack.pop())

    switch (token) {
     case '-':
      stack.push(left - right)
      break
     case '+':
      stack.push(left + right)
      break
     case '×':
      stack.push(left * right)
      break
     case '÷':
      stack.push(left / right)
      break
     case '▢':
      stack.push(Math.pow(left, right))
      break
     case '%':
      if (!left) {
       stack.push(right / 100)
      } else if (postfix[i + 1] === '-' || postfix[i + 1] === '+') {
       stack.push(left)
       stack.push((left * right) / 100)
      } else {
       stack.push(left)
       stack.push(right / 100)
      }
      break
     default:
      throw new Error('Error found in binary expressions')
    }
   }
   return stack
  }, [])
  .pop()
}

const calculatorReducer = (state, action) => {
 const se = state.expression
 const ap = action.payload

 const keyPress = (arr) => {
  switch (arr) {
   case ADD:
    if (se[0] === 'Error') {
     if (/^[÷×+%▢]$/.test(ap)) {
      return ['0', ap]
     } else {
      return [ap]
     }
    } else if (!isNumeric(ap)) {
     switch (ap) {
      case '+':
      case '×':
      case '÷':
      case '▢':
      case '%':
       if (last(se) === '-' && (last2(se) === '×' || last2(se) === '÷')) {
        return se
       } else if (se.length === 1 && last(se) === '-') {
        return se
       } else if (/^[+\-×÷▢]$/.test(last(se)) && last(se).length === 1) {
        return [...se.slice(0, -1), ap]
       } else if (last(se) === '(') {
        return se
       } else {
        return [...se, ap]
       }

      case '.':
       return /^(\+|-)?\d+$/.test(last(se))
        ? [...se.slice(0, -1), last(se).concat(ap)]
        : se

      case '-':
       if (last(se) === '+' || (se.length === 1 && last(se) === '0')) {
        return [...se.slice(0, -1), ap]
       } else if (last(se) === '-') {
        return se
       } else {
        return [...se, ap]
       }

      case '√':
      case 'sin':
      case 'cos':
      case 'tan':
       return se[0] === '0' && se.length === 1 ? [ap, '('] : [...se, ap, '(']

      case '(':
       return se[0] === '0' && se.length === 1 ? [ap] : [...se, ap]

      case ')':
       return openParens(se) > 0 && last(se) !== '(' ? [...se, ap] : se

      default:
       return se
     }
    } else {
     if (ap === '00') {
      if (state.totalShowing) {
       return ['0']
      } else if (
       !isNumeric(last(se)) ||
       (se[0] === '0' && se.length === 1) ||
       (!isNumeric(last2(se)) && last(se) === '0')
      ) {
       return se
      } else {
       return [...se.slice(0, -1), last(se).concat(ap)]
      }
     } else if (state.totalShowing) {
      return [ap]
     } else if (isNumeric(last(se)) && last(se) !== '0') {
      return [...se.slice(0, -1), last(se).concat(ap)]
     } else if (last(se) === ')' || last(se) === '%') {
      return [...se, '×', ap]
     } else if (last(se) === '0' && !isNumeric(last2(se))) {
      return [...se.slice(0, -1), ap]
     } else {
      return [...se, ap]
     }
    }

   case UNDO:
    if (
     (last(se) === '(' && last2(se) === 'sin') ||
     last2(se) === 'cos' ||
     last2(se) === 'tan' ||
     last2(se) === '√'
    ) {
     return se.length === 2 ? ['0'] : se.slice(0, -2)
    } else {
     return se.length === 1 ? ['0'] : se.slice(0, -1)
    }

   case CLEAR:
    return ['0']

   default:
    return state
  }
 }

 if (action.type === EQUAL) {
  if (state.totalShowing) return state
  if (/^[+\-×÷▢(]$/.test(last(se))) return state
  const expressionWithParentheses = se.concat(fillClosingParens(se))
  const total = Number(
   evaluate(postfix(expressionWithParentheses), state.angle)
  )
  const trimmedTotal = Number.isNaN(total) ? 'Error' : trimTotal(total)

  return {
   ...state,
   expression: [trimmedTotal],
   history: [
    ...state.history,
    {
     id: uuid(),
     expression: expressionWithParentheses,
     total: trimmedTotal,
    },
   ],
   totalShowing: true,
  }
 } else if (action.type === HISTORY) {
  return {
   ...state,
   expression: ap,
   totalShowing: false,
  }
 } else if (action.type === DELETE_HISTORY) {
  return {
   ...state,
   history: [],
  }
 } else if (action.type === SET_ANGLE) {
  return {
   ...state,
   angle: state.angle === 'Rad' ? 'Deg' : 'Rad',
  }
 } else {
  return {
   ...state,
   expression: keyPress(action.type),
   totalShowing: false,
  }
 }
}

const useOnClickOutside = (ref, handler) => {
 React.useEffect(() => {
  const listener = (event) => {
   if (!ref.current || ref.current.contains(event.target)) {
    return
   }

   handler(event)
  }
  document.addEventListener('mousedown', listener)
  document.addEventListener('touchstart', listener)

  return () => {
   document.removeEventListener('mousedown', listener)
   document.removeEventListener('touchstart', listener)
  }
 }, [ref, handler])
}

/******* React components *******/
const AngleSwitch = ({ angle, dispatch }) => (
 <AngleSwitchContainer onClick={() => dispatch({ type: SET_ANGLE })}>
  <ToggleButton angle={angle} />
  <Angle>Rad</Angle>
  <Angle>Deg</Angle>
 </AngleSwitchContainer>
)

const KeysList = ({ dispatch, angle }) => {
 const keyPress = (payload) => () => {
  dispatch({ type: ADD, payload })
 }

 return (
  <Grid>
   <AngleSwitch angle={angle} dispatch={dispatch} />
   <EmptyColumn4 />
   <Key yellow onClick={keyPress('(')}>
    <KeyFont yellow>(</KeyFont>
   </Key>
   <Key yellow onClick={keyPress(')')}>
    <KeyFont yellow>)</KeyFont>
   </Key>
   <Key onClick={keyPress('÷')}>
    <KeyFont>÷</KeyFont>
   </Key>
   <EmptyColumn1 />
   <Key digit onClick={keyPress('7')}>
    <KeyFont digit>7</KeyFont>
   </Key>
   <Key digit onClick={keyPress('8')}>
    <KeyFont digit>8</KeyFont>
   </Key>
   <Key digit onClick={keyPress('9')}>
    <KeyFont digit>9</KeyFont>
   </Key>
   <EmptyColumn2 />
   <Key red onClick={keyPress('-')}>
    <KeyFont red>−</KeyFont>
   </Key>
   <EmptyColumn3 />
   <Key onClick={keyPress('%')}>
    <KeyFont>%</KeyFont>
   </Key>
   <Key purple onClick={keyPress('▢')}>
    <KeyFont purple>
     x<KeyListPow>y</KeyListPow>
    </KeyFont>
   </Key>
   <Key onClick={keyPress('×')}>
    <KeyFont>×</KeyFont>
   </Key>
   <Key digit onClick={keyPress('4')}>
    <KeyFont digit>4</KeyFont>
   </Key>
   <Key digit onClick={keyPress('5')}>
    <KeyFont digit>5</KeyFont>
   </Key>
   <Key digit onClick={keyPress('6')}>
    <KeyFont digit>6</KeyFont>
   </Key>
   <Key plus onClick={keyPress('+')}>
    <KeyFont plus>+</KeyFont>
   </Key>
   <Key onClick={keyPress('√')}>
    <KeyFont>√</KeyFont>
   </Key>
   <Key purple onClick={keyPress('sin')}>
    <KeyFont purple>sin</KeyFont>
   </Key>
   <Key onClick={() => dispatch({ type: CLEAR })}>
    <KeyFont>AC</KeyFont>
   </Key>
   <Key digit onClick={keyPress('1')}>
    <KeyFont digit>1</KeyFont>
   </Key>
   <Key digit onClick={keyPress('2')}>
    <KeyFont digit>2</KeyFont>
   </Key>
   <Key digit onClick={keyPress('3')}>
    <KeyFont digit>3</KeyFont>
   </Key>
   <Key equal onClick={() => dispatch({ type: EQUAL })}>
    <KeyFont equal>=</KeyFont>
   </Key>
   <Key purple onClick={keyPress('cos')}>
    <KeyFont purple>cos</KeyFont>
   </Key>
   <Key onClick={() => dispatch({ type: UNDO })}>
    <KeyFont>CE</KeyFont>
   </Key>
   <Key zero digit onClick={keyPress('0')}>
    <KeyFont digit>0</KeyFont>
   </Key>
   <Key digit onClick={keyPress('00')}>
    <KeyFont digit>00</KeyFont>
   </Key>
   <Key dot digit onClick={keyPress('.')}>
    <KeyFont dot digit>
     •
    </KeyFont>
   </Key>
   <Key purple onClick={keyPress('tan')}>
    <KeyFont purple>tan</KeyFont>
   </Key>
  </Grid>
 )
}

const DisplayValue = ({ expression, item, idx }) => {
 const { value, stackParens, show } = item

 if (/^[+\-×÷]$/.test(value)) {
  return ` ${value} `
 } else if (
  expression[idx - 1] !== '(' &&
  (value === 'sin' || value === 'cos' || value === 'tan' || value === '√')
 ) {
  return ` ${value}`
 } else if (stackParens) {
  return <Paren>{value}</Paren>
 } else if (value === '▢') {
  return show ? value : null
 } else {
  return value
 }
}

const DisplayItem = ({ expression, item, idx }) => {
 const nestedItems = (item.children || []).map((item) => {
  return (
   <DisplayItem
    key={item.id}
    expression={expression}
    item={item}
    idx={idx}
    type="child"
   />
  )
 })
 return (
  <>
   <DisplayValue expression={expression} item={item} idx={idx} />
   {nestedItems.length > 0 && <Pow>{nestedItems}</Pow>}
  </>
 )
}

const Display = ({ expression }) => (
 <>
  {nestChildren(arraySuperscript(expression)).map((item, idx) => (
   <DisplayItem key={item.id} expression={expression} item={item} idx={idx} />
  ))}
 </>
)

const HistoryItem = ({ expression, total, setHistoryOpen, dispatch }) => {
 const handleHistoryClick = (payload) => () => {
  dispatch({ type: HISTORY, payload })
  setHistoryOpen(false)
 }

 return (
  <ItemHistoryItem>
   <Content onClick={handleHistoryClick(expression)}>
    <span>
     <Display expression={expression} />
    </span>
   </Content>
   <HistoryItemEqual>{` = `}</HistoryItemEqual>
   {total === 'Error' ? (
    <Content error>
     <span>{total}</span>
    </Content>
   ) : (
    <Content total onClick={handleHistoryClick([`${total}`])}>
     <span>{total}</span>
    </Content>
   )}
  </ItemHistoryItem>
 )
}

const HistoryList = ({ calcHistory, setHistoryOpen, dispatch }) => {
 const ref = React.useRef()
 const ULref = React.useRef()
 useOnClickOutside(ref, () => setHistoryOpen((prevOpen) => !prevOpen))

 React.useEffect(() => {
  ULref.current.scrollTop = ULref.current.scrollTopMax
 }, [])

 return (
  <HistoryListContainer ref={ref}>
   <IconContainer>
    <HistoryIcon onClick={() => setHistoryOpen((prevOpen) => !prevOpen)}>
     <i className="fas fa-history" />
    </HistoryIcon>
    <TrashIcon onClick={() => dispatch({ type: DELETE_HISTORY })}>
     <i className="fas fa-trash" />
    </TrashIcon>
   </IconContainer>
   {calcHistory.length ? null : <NoContent>Calculations history</NoContent>}
   <HistoryListList ref={ULref}>
    {calcHistory.length > 0 &&
     calcHistory.map((item) => {
      const { id, expression, total } = item
      return (
       <HistoryItem
        key={id}
        expression={expression}
        total={total}
        setHistoryOpen={setHistoryOpen}
        dispatch={dispatch}
       />
      )
     })}
   </HistoryListList>
  </HistoryListContainer>
 )
}

const HistoryToggle = ({ historyOpen, setHistoryOpen, children }) => (
 <>
  <Toggle onClick={() => setHistoryOpen((prevOpen) => !prevOpen)}>
   <HistoryToggleHistoryIcon className="icon">
    <i className="fas fa-history" />
   </HistoryToggleHistoryIcon>
  </Toggle>
  {historyOpen && children}
 </>
)

const MiniHistoryItem = ({ expression, total }) => (
 <ItemMiniHistory>
  <span>
   <Display expression={expression} />
   {` = ${total}`}
  </span>
 </ItemMiniHistory>
)

const MiniHistoryList = ({ calcHistory }) => (
 <MiniHistoryListContainer>
  <List>
   {calcHistory.length > 0 &&
    calcHistory.map((item) => {
     const { id, expression, total } = item
     return <MiniHistoryItem key={id} expression={expression} total={total} />
    })}
  </List>
 </MiniHistoryListContainer>
)

const App = () => {
 const initialState = {
  expression: ['0'],
  history: [],
  totalShowing: false,
  angle: 'Rad',
 }

 const [state, dispatch] = React.useReducer(calculatorReducer, initialState)
 const [historyOpen, setHistoryOpen] = React.useState(false)
 const sh = state.history

 return (
  <Theme>
   <>
    <GlobalStyle />
    <AppContainer>
     <Calculator>
      <MiniHistoryList calcHistory={sh} />
      <HistoryToggle historyOpen={historyOpen} setHistoryOpen={setHistoryOpen}>
       <HistoryList
        calcHistory={sh}
        setHistoryOpen={setHistoryOpen}
        dispatch={dispatch}
       />
      </HistoryToggle>
      <Screen>
       <span>
        <Display expression={state.expression} />
       </span>
      </Screen>

      <ScreenExpression>
       {sh.length && state.totalShowing ? (
        <span>
         <Display expression={last(sh).expression} />
        </span>
       ) : null}

       {sh.length > 0 && state.totalShowing && <Equal>=</Equal>}
      </ScreenExpression>
      <Brand>
       <span>SHARP</span>
      </Brand>
      <ProductNumber>EL-1801V</ProductNumber>

      <KeysList dispatch={dispatch} angle={state.angle} />
     </Calculator>
    </AppContainer>
   </>
  </Theme>
 )
}

ReactDOM.render(<App />, document.getElementById('root'))
              
            
!
999px

Console