├── .gitignore ├── src ├── css │ ├── _animations-finished.styl │ ├── _colours.styl │ ├── fonts │ │ ├── haymaker-webfont.eot │ │ ├── haymaker-webfont.ttf │ │ ├── haymaker-webfont.woff │ │ ├── blanch_caps_inline-webfont.eot │ │ ├── blanch_caps_inline-webfont.ttf │ │ ├── blanch_caps_inline-webfont.woff │ │ ├── haymaker-webfont.svg │ │ └── blanch_caps_inline-webfont.svg │ ├── _typography.styl │ ├── _fonts.styl │ ├── _animations.styl │ ├── _normalize.styl │ ├── images │ │ └── anchor.svg │ ├── style.styl │ └── style.css ├── components │ ├── .gitkeep │ ├── NotFound.js │ ├── Router.js │ ├── Login.js │ ├── Header.js │ ├── StorePicker.js │ ├── Fish.js │ ├── AddFishForm.js │ ├── EditFishForm.js │ ├── Order.js │ ├── Inventory.js │ └── App.js ├── index.js ├── base.js ├── helpers.js └── sample-fishes.js ├── .netlify └── state.json ├── public ├── favicon.ico ├── images │ ├── crab.jpg │ ├── hali.jpg │ ├── mahi.jpg │ ├── lobster.jpg │ ├── mussels.jpg │ ├── oysters.jpg │ ├── prawns.jpg │ ├── salmon.jpg │ └── scallops.jpg └── index.html ├── security-rules.json └── package.json /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /src/css/_animations-finished.styl: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.netlify/state.json: -------------------------------------------------------------------------------- 1 | { 2 | "siteId": "24bacee9-4806-469b-896a-3e0c2f9b1a0c" 3 | } -------------------------------------------------------------------------------- /src/css/_colours.styl: -------------------------------------------------------------------------------- 1 | orange = #F5A623 2 | red = #d12028 3 | green = #2DC22D 4 | -------------------------------------------------------------------------------- /src/components/.gitkeep: -------------------------------------------------------------------------------- 1 | // this is just an empty file so the empty folder will stay in git! 2 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nikkifurls/tutorial-react-wes-bos/master/public/favicon.ico -------------------------------------------------------------------------------- /public/images/crab.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nikkifurls/tutorial-react-wes-bos/master/public/images/crab.jpg -------------------------------------------------------------------------------- /public/images/hali.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nikkifurls/tutorial-react-wes-bos/master/public/images/hali.jpg -------------------------------------------------------------------------------- /public/images/mahi.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nikkifurls/tutorial-react-wes-bos/master/public/images/mahi.jpg -------------------------------------------------------------------------------- /public/images/lobster.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nikkifurls/tutorial-react-wes-bos/master/public/images/lobster.jpg -------------------------------------------------------------------------------- /public/images/mussels.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nikkifurls/tutorial-react-wes-bos/master/public/images/mussels.jpg -------------------------------------------------------------------------------- /public/images/oysters.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nikkifurls/tutorial-react-wes-bos/master/public/images/oysters.jpg -------------------------------------------------------------------------------- /public/images/prawns.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nikkifurls/tutorial-react-wes-bos/master/public/images/prawns.jpg -------------------------------------------------------------------------------- /public/images/salmon.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nikkifurls/tutorial-react-wes-bos/master/public/images/salmon.jpg -------------------------------------------------------------------------------- /public/images/scallops.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nikkifurls/tutorial-react-wes-bos/master/public/images/scallops.jpg -------------------------------------------------------------------------------- /src/css/fonts/haymaker-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nikkifurls/tutorial-react-wes-bos/master/src/css/fonts/haymaker-webfont.eot -------------------------------------------------------------------------------- /src/css/fonts/haymaker-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nikkifurls/tutorial-react-wes-bos/master/src/css/fonts/haymaker-webfont.ttf -------------------------------------------------------------------------------- /src/css/fonts/haymaker-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nikkifurls/tutorial-react-wes-bos/master/src/css/fonts/haymaker-webfont.woff -------------------------------------------------------------------------------- /src/css/fonts/blanch_caps_inline-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nikkifurls/tutorial-react-wes-bos/master/src/css/fonts/blanch_caps_inline-webfont.eot -------------------------------------------------------------------------------- /src/css/fonts/blanch_caps_inline-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nikkifurls/tutorial-react-wes-bos/master/src/css/fonts/blanch_caps_inline-webfont.ttf -------------------------------------------------------------------------------- /src/css/fonts/blanch_caps_inline-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nikkifurls/tutorial-react-wes-bos/master/src/css/fonts/blanch_caps_inline-webfont.woff -------------------------------------------------------------------------------- /src/components/NotFound.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const NotFound = () => ( 4 |
5 |

Not Found

6 |
7 | ); 8 | 9 | export default NotFound; -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { render } from 'react-dom'; 3 | import Router from './components/Router'; 4 | import "./css/style.css"; 5 | 6 | render(, document.querySelector('#main')); -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Catch of the Day 8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 |
17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /security-rules.json: -------------------------------------------------------------------------------- 1 | // These are your firebase security rules - put them in the "Security & Rules" tab of your database 2 | { 3 | "rules": { 4 | // won't let people delete an existing room 5 | ".write": "!data.exists()", 6 | ".read": true, 7 | "$room": { 8 | // only the store owner can edit the data 9 | ".write": 10 | "auth != null && (!data.exists() || data.child('owner').val() === auth.uid)", 11 | ".read": true 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/components/Router.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { BrowserRouter, Route, Switch } from 'react-router-dom'; 3 | import StorePicker from './StorePicker'; 4 | import App from './App'; 5 | import NotFound from './NotFound'; 6 | 7 | const Router = () => ( 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | ) 16 | 17 | export default Router; -------------------------------------------------------------------------------- /src/components/Login.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | 4 | const Login = props => ( 5 | 15 | ); 16 | 17 | Login.propTypes = { 18 | authenticate: PropTypes.func.isRequired 19 | } 20 | 21 | export default Login; -------------------------------------------------------------------------------- /src/base.js: -------------------------------------------------------------------------------- 1 | import Rebase from 're-base'; 2 | import firebase from 'firebase'; 3 | 4 | const firebaseApp = firebase.initializeApp({ 5 | apiKey: "AIzaSyCj1JLw3V4IFl0WumX9HKT1yG6iBh_mCB4", 6 | authDomain: "nicole-catch-of-the-day.firebaseapp.com", 7 | databaseURL: "https://nicole-catch-of-the-day-default-rtdb.firebaseio.com", 8 | // measurementId: "G-VYXTX1X678" 9 | }); 10 | 11 | // Rebase bindings 12 | const base = Rebase.createClass(firebaseApp.database()); 13 | 14 | // Named export 15 | export { firebaseApp }; 16 | 17 | // Default export 18 | export default base; -------------------------------------------------------------------------------- /src/css/_typography.styl: -------------------------------------------------------------------------------- 1 | html 2 | font-size 62.5% 3 | 4 | body 5 | background #D4D4D4 6 | -webkit-font-smoothing antialiased 7 | -moz-osx-font-smoothing grayscale 8 | font-family 'Open Sans', sans-serif 9 | font-size 2rem 10 | 11 | h1 12 | font-family 'blanchcaps_inline', sans-serif; 13 | text-align center 14 | font-weight normal 15 | margin 0 16 | 17 | 18 | 19 | h2,h3,h4,h5,h6 20 | font-weight normal 21 | font-family 'haymakerregular', sans-serif 22 | 23 | h2 24 | text-align center 25 | margin-top 0 26 | margin-bottom 2rem 27 | 28 | h3 29 | font-size 3rem 30 | -------------------------------------------------------------------------------- /src/components/Header.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | 4 | // Stateless functional component 5 | const Header = props => ( 6 |
7 |

8 | Catch 9 | 10 | of 11 | the 12 | 13 | Day 14 |

15 |

16 | {props.tagline} 17 |

18 |
19 | ); 20 | 21 | Header.propTypes = { 22 | tagline: PropTypes.string.isRequired 23 | }; 24 | 25 | export default Header; -------------------------------------------------------------------------------- /src/css/_fonts.styl: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: "haymakerregular"; 3 | src: url("./fonts/haymaker-webfont.eot"); 4 | src: url("./fonts/haymaker-webfont.eot?#iefix") format("embedded-opentype"), url("./fonts/haymaker-webfont.woff") format("woff"), url("./fonts/haymaker-webfont.ttf") format("truetype"), url("./fonts/haymaker-webfont.svg#haymakerregular") format("svg"); 5 | font-weight: normal; 6 | font-style: normal; 7 | } 8 | 9 | @font-face { 10 | font-family: 'blanchcaps_inline'; 11 | src: url('./fonts/blanch_caps_inline-webfont.eot'); 12 | src: url('./fonts/blanch_caps_inline-webfont.eot?#iefix') format('embedded-opentype'), 13 | url('./fonts/blanch_caps_inline-webfont.woff') format('woff'), 14 | url('./fonts/blanch_caps_inline-webfont.ttf') format('truetype'), 15 | url('./fonts/blanch_caps_inline-webfont.svg#blanchcaps_inline') format('svg'); 16 | font-weight: normal; 17 | font-style: normal; 18 | } 19 | -------------------------------------------------------------------------------- /src/css/_animations.styl: -------------------------------------------------------------------------------- 1 | /* 2 | Possible Animations: 3 | enter 4 | exit 5 | appear Must set transitionAppear={true} on animation component 6 | */ 7 | 8 | .order-enter 9 | transform: translateX(-120%) 10 | transition 0.5s 11 | max-height: 0 12 | padding 0 !important 13 | &.order-enter-active 14 | max-height 60px 15 | transform: translateX(0) 16 | padding 2rem 0 !important 17 | 18 | .order-exit 19 | transition 0.5s 20 | transform: translateX(0) 21 | &.order-exit-active 22 | transform: translateX(120%) 23 | padding: 0 24 | 25 | 26 | .count-enter 27 | background: red 28 | transition .5s 29 | transform translateY(100%) 30 | &.count-enter-active 31 | background: yellow 32 | transform translateY(0) 33 | 34 | .count-exit 35 | background: black 36 | transform translateY(0) 37 | transition .5s 38 | position absolute 39 | left 0 40 | bottom 0 41 | &.count-exit-active 42 | background green 43 | transform translateY(-100%) scale(3) -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cotd", 3 | "version": "0.0.3", 4 | "private": true, 5 | "devDependencies": { 6 | "concurrently": "4.1.0", 7 | "react-scripts": "3.4.1" 8 | }, 9 | "dependencies": { 10 | "autoprefixer-stylus": "0.14.0", 11 | "firebase": "^7.6.0", 12 | "prop-types": "^15.6.0", 13 | "re-base": "4.0.0", 14 | "react": "^16.6.3", 15 | "react-dom": "^16.6.3", 16 | "react-router-dom": "^4.2.2", 17 | "react-transition-group": "^2.2.1", 18 | "serve": "^10.1.2", 19 | "stylus": "0.54.5" 20 | }, 21 | "scripts": { 22 | "dev": "react-scripts start", 23 | "start": "serve --single ./build", 24 | "watch": "concurrently --names \"webpack, stylus\" --prefix name \"npm run start\" \"npm run styles:watch\"", 25 | "build": "react-scripts build", 26 | "eject": "react-scripts eject", 27 | "styles": "stylus -u autoprefixer-stylus ./src/css/style.styl -o ./src/css/style.css", 28 | "now-build": "npm run build && mv build dist", 29 | "styles:watch": "npm run styles -- -w" 30 | }, 31 | "browserslist": [ 32 | ">0.2%", 33 | "not ie <= 11", 34 | "not op_mini all" 35 | ] 36 | } 37 | -------------------------------------------------------------------------------- /src/components/StorePicker.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import { getFunName } from "../helpers"; 4 | 5 | class StorePicker extends React.Component { 6 | 7 | // Set component properties 8 | myInput = React.createRef(); 9 | 10 | static propTypes = { 11 | history: PropTypes.object 12 | } 13 | 14 | goToStore = event => { 15 | // 1. Stop the form from submitting 16 | event.preventDefault(); 17 | 18 | // 2. Get the text from the input 19 | const storeName = this.myInput.current.value; 20 | 21 | // 3. Change the page to /store/whatever-they-entered 22 | this.props.history.push(`/store/${storeName}`); 23 | } 24 | 25 | render() { 26 | return ( 27 |
28 |

Please Enter a Store

29 | 36 | 37 |
38 | ); 39 | } 40 | } 41 | 42 | export default StorePicker; -------------------------------------------------------------------------------- /src/components/Fish.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import { formatPrice } from '../helpers'; 4 | 5 | class Fish extends React.Component { 6 | 7 | static propTypes = { 8 | index: PropTypes.string, 9 | details: PropTypes.shape({ 10 | image: PropTypes.string, 11 | name: PropTypes.string, 12 | desc: PropTypes.string, 13 | status: PropTypes.string, 14 | price: PropTypes.number 15 | }), 16 | addToOrder: PropTypes.func 17 | } 18 | 19 | render() { 20 | const { image, name, price, desc, status } = this.props.details; 21 | const isAvailable = status === 'available'; 22 | 23 | return ( 24 |
  • 25 | {name} 26 |

    27 | {name} 28 | {formatPrice(price)} 29 |

    30 |

    {desc}

    31 | 34 |
  • 35 | ); 36 | } 37 | } 38 | 39 | export default Fish; -------------------------------------------------------------------------------- /src/components/AddFishForm.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | 4 | class AddFishForm extends React.Component { 5 | nameRef = React.createRef(); 6 | priceRef = React.createRef(); 7 | statusRef = React.createRef(); 8 | descRef = React.createRef(); 9 | imageRef = React.createRef(); 10 | 11 | static propTypes = { 12 | addFish: PropTypes.func 13 | } 14 | 15 | createFish = event => { 16 | // 1. Stop the form from submitting 17 | event.preventDefault(); 18 | 19 | // 2. Create fish 20 | const fish = { 21 | name: this.nameRef.current.value, 22 | price: parseFloat(this.priceRef.current.value), 23 | status: this.statusRef.current.value, 24 | desc: this.descRef.current.value, 25 | image: this.imageRef.current.value, 26 | } 27 | 28 | this.props.addFish(fish); 29 | 30 | // 3. Refresh form 31 | event.currentTarget.reset(); 32 | } 33 | 34 | render() { 35 | return ( 36 |
    37 | 38 | 39 | 43 | 44 | 45 | 46 |
    47 | ); 48 | } 49 | } 50 | 51 | export default AddFishForm; -------------------------------------------------------------------------------- /src/css/_normalize.styl: -------------------------------------------------------------------------------- 1 | // CSS Normalize 2 | 3 | article,aside,details,figcaption,figure,footer,header,hgroup,nav,section,summary{display:block;}audio,canvas,video{display:inline-block;}audio:not([controls]){display:none;height:0;}[hidden]{display:none;}html{font-family:sans-serif;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%;}a:focus{outline:thin dotted;}a:active,a:hover{outline:0;}h1{font-size:2em;}abbr[title]{border-bottom:1px dotted;}b,strong{font-weight:700;}dfn{font-style:italic;}mark{background:#ff0;color:#000;}code,kbd,pre,samp{font-family:monospace, serif;font-size:1em;}pre{white-space:pre-wrap;word-wrap:break-word;}q{quotes:\201C \201D \2018 \2019;}small{font-size:80%;}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline;}sup{top:-.5em;}sub{bottom:-.25em;}img{border:0;}svg:not(:root){overflow:hidden;}fieldset{border:1px solid silver;margin:0 2px;padding:.35em .625em .75em;}button,input,select,textarea{font-family:inherit;font-size:100%;margin:0;}button,input{line-height:normal;}button,html input[type=button], input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer;}button[disabled],input[disabled]{cursor:default;}input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0;}input[type=search]{-webkit-appearance:textfield;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box;}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none;}textarea{overflow:auto;vertical-align:top;}table{border-collapse:collapse;border-spacing:0;}body,figure{margin:0;}legend,button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0;} 4 | 5 | // clearfix 6 | .clearfix:after {visibility: hidden; display: block; font-size: 0; content: " "; clear: both; height: 0; } 7 | 8 | // Sane border box 9 | * { -moz-box-sizing: border-box; -webkit-box-sizing: border-box; box-sizing: border-box; } 10 | -------------------------------------------------------------------------------- /src/components/EditFishForm.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | 4 | class EditFishForm extends React.Component { 5 | 6 | static propTypes = { 7 | fish: PropTypes.shape({ 8 | image: PropTypes.string, 9 | name: PropTypes.string, 10 | desc: PropTypes.string, 11 | status: PropTypes.string, 12 | price: PropTypes.number 13 | }), 14 | index: PropTypes.string, 15 | updateFish: PropTypes.func 16 | } 17 | 18 | handleChange = event => { 19 | // Update fish 20 | // 1. Copy current fish 21 | const updatedFish = { 22 | ...this.props.fish, 23 | [event.currentTarget.name]: event.currentTarget.value 24 | } 25 | this.props.updateFish(this.props.index, updatedFish); 26 | }; 27 | 28 | render() { 29 | return ( 30 |
    31 | 37 | 43 | 50 | 54 | 59 | 60 |
    61 | ); 62 | } 63 | } 64 | 65 | export default EditFishForm; -------------------------------------------------------------------------------- /src/helpers.js: -------------------------------------------------------------------------------- 1 | export function formatPrice(cents) { 2 | return (cents / 100).toLocaleString("en-US", { 3 | style: "currency", 4 | currency: "USD" 5 | }); 6 | } 7 | 8 | export function rando(arr) { 9 | return arr[Math.floor(Math.random() * arr.length)]; 10 | } 11 | 12 | export function slugify(text) { 13 | return text 14 | .toString() 15 | .toLowerCase() 16 | .replace(/\s+/g, "-") 17 | .replace(/[^\w-]+/g, "") 18 | .replace(/--+/g, "-") 19 | .replace(/^-+/, "") 20 | .replace(/-+$/, ""); 21 | } 22 | 23 | export function getFunName() { 24 | const adjectives = [ 25 | "adorable", 26 | "beautiful", 27 | "clean", 28 | "drab", 29 | "elegant", 30 | "fancy", 31 | "glamorous", 32 | "handsome", 33 | "long", 34 | "magnificent", 35 | "old-fashioned", 36 | "plain", 37 | "quaint", 38 | "sparkling", 39 | "ugliest", 40 | "unsightly", 41 | "angry", 42 | "bewildered", 43 | "clumsy", 44 | "defeated", 45 | "embarrassed", 46 | "fierce", 47 | "grumpy", 48 | "helpless", 49 | "itchy", 50 | "jealous", 51 | "lazy", 52 | "mysterious", 53 | "nervous", 54 | "obnoxious", 55 | "panicky", 56 | "repulsive", 57 | "scary", 58 | "thoughtless", 59 | "uptight", 60 | "worried" 61 | ]; 62 | 63 | const nouns = [ 64 | "women", 65 | "men", 66 | "children", 67 | "teeth", 68 | "feet", 69 | "people", 70 | "leaves", 71 | "mice", 72 | "geese", 73 | "halves", 74 | "knives", 75 | "wives", 76 | "lives", 77 | "elves", 78 | "loaves", 79 | "potatoes", 80 | "tomatoes", 81 | "cacti", 82 | "foci", 83 | "fungi", 84 | "nuclei", 85 | "syllabuses", 86 | "analyses", 87 | "diagnoses", 88 | "oases", 89 | "theses", 90 | "crises", 91 | "phenomena", 92 | "criteria", 93 | "data" 94 | ]; 95 | 96 | return `${rando(adjectives)}-${rando(adjectives)}-${rando(nouns)}`; 97 | } 98 | -------------------------------------------------------------------------------- /src/sample-fishes.js: -------------------------------------------------------------------------------- 1 | // This is just some sample data so you don't have to think of your own! 2 | const fishes = { 3 | fish1: { 4 | name: "Pacific Halibut", 5 | image: "/images/hali.jpg", 6 | desc: 7 | "Everyone’s favorite white fish. We will cut it to the size you need and ship it.", 8 | price: 1724, 9 | status: "available" 10 | }, 11 | 12 | fish2: { 13 | name: "Lobster", 14 | image: "/images/lobster.jpg", 15 | desc: 16 | "These tender, mouth-watering beauties are a fantastic hit at any dinner party.", 17 | price: 3200, 18 | status: "available" 19 | }, 20 | 21 | fish3: { 22 | name: "Sea Scallops", 23 | image: "/images/scallops.jpg", 24 | desc: 25 | "Big, sweet and tender. True dry-pack scallops from the icey waters of Alaska. About 8-10 per pound", 26 | price: 1684, 27 | status: "unavailable" 28 | }, 29 | 30 | fish4: { 31 | name: "Mahi Mahi", 32 | image: "/images/mahi.jpg", 33 | desc: 34 | "Lean flesh with a mild, sweet flavor profile, moderately firm texture and large, moist flakes. ", 35 | price: 1129, 36 | status: "available" 37 | }, 38 | 39 | fish5: { 40 | name: "King Crab", 41 | image: "/images/crab.jpg", 42 | desc: 43 | "Crack these open and enjoy them plain or with one of our cocktail sauces", 44 | price: 4234, 45 | status: "available" 46 | }, 47 | 48 | fish6: { 49 | name: "Atlantic Salmon", 50 | image: "/images/salmon.jpg", 51 | desc: 52 | "This flaky, oily salmon is truly the king of the sea. Bake it, grill it, broil it...as good as it gets!", 53 | price: 1453, 54 | status: "available" 55 | }, 56 | 57 | fish7: { 58 | name: "Oysters", 59 | image: "/images/oysters.jpg", 60 | desc: "A soft plump oyster with a sweet salty flavor and a clean finish.", 61 | price: 2543, 62 | status: "available" 63 | }, 64 | 65 | fish8: { 66 | name: "Mussels", 67 | image: "/images/mussels.jpg", 68 | desc: 69 | "The best mussels from the Pacific Northwest with a full-flavored and complex taste.", 70 | price: 425, 71 | status: "available" 72 | }, 73 | 74 | fish9: { 75 | name: "Jumbo Prawns", 76 | image: "/images/prawns.jpg", 77 | desc: 78 | "With 21-25 two bite prawns in each pound, these sweet morsels are perfect for shish-kabobs.", 79 | price: 2250, 80 | status: "available" 81 | } 82 | }; 83 | 84 | export default fishes; 85 | -------------------------------------------------------------------------------- /src/components/Order.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import { formatPrice } from '../helpers'; 4 | import { TransitionGroup, CSSTransition } from 'react-transition-group'; 5 | 6 | class Order extends React.Component { 7 | 8 | static propTypes = { 9 | fishes: PropTypes.object, 10 | order: PropTypes.object, 11 | removeFromOrder: PropTypes.func 12 | } 13 | 14 | renderOrder = key => { 15 | const fish = this.props.fishes[key]; 16 | const count = this.props.order[key]; 17 | const isAvailable = fish && fish.status === 'available'; 18 | const transitionOptions = { 19 | classNames: "order", 20 | key, 21 | timeout: { enter: 500, exit: 500 } 22 | }; 23 | 24 | // Make sure fish is loaded 25 | if (!fish) return null; 26 | 27 | if (!isAvailable) { 28 | return ( 29 | 30 |
  • 31 | Sorry {fish ? fish.name : 'fish'} is no longer available 32 | 33 |
  • 34 |
    35 | ) 36 | } 37 | 38 | return ( 39 | 40 |
  • 41 | 42 | 43 | 44 | {count} 45 | 46 | 47 | lbs {fish.name} {formatPrice(count * fish.price)} 48 | 49 | 50 |
  • 51 |
    52 | ) 53 | } 54 | 55 | render() { 56 | const orderIds = Object.keys(this.props.order); 57 | const total = orderIds.reduce((prevTotal, key) => { 58 | const fish = this.props.fishes[key]; 59 | const count = this.props.order[key]; 60 | const isAvailable = fish && fish.status === 'available'; 61 | if (isAvailable) { 62 | return prevTotal + (count * fish.price); 63 | } 64 | return prevTotal; 65 | }, 0); 66 | 67 | return ( 68 |
    69 |

    Order

    70 | 71 | {orderIds.map(this.renderOrder)} 72 | 73 |
    {formatPrice(total)}
    74 |
    75 | ); 76 | } 77 | } 78 | 79 | export default Order; -------------------------------------------------------------------------------- /src/components/Inventory.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import AddFishForm from './AddFishForm'; 4 | import EditFishForm from './EditFishForm'; 5 | import Login from './Login'; 6 | import base, { firebaseApp } from '../base'; 7 | import firebase from 'firebase'; 8 | 9 | class Inventory extends React.Component { 10 | 11 | static propTypes = { 12 | storeId: PropTypes.string, 13 | fishes: PropTypes.object, 14 | updateFish: PropTypes.func, 15 | deleteFish: PropTypes.func, 16 | loadSampleFishes: PropTypes.func, 17 | }; 18 | 19 | state = { 20 | uid: null, 21 | onwer: null 22 | }; 23 | 24 | componentDidMount() { 25 | firebase.auth().onAuthStateChanged(user => { 26 | if (user) { 27 | this.authHandler({ user }); 28 | } 29 | }) 30 | } 31 | 32 | authHandler = async authData => { 33 | // 1. Look up current store in Firebase database 34 | const store = await base.fetch(this.props.storeId, { context: this }); 35 | 36 | // 2. Claim it if there is no owner 37 | if (!store.owner) { 38 | // Save it as our own 39 | await base.post(`${this.props.storeId}/owner`, { 40 | data: authData.user.uid 41 | }) 42 | } 43 | 44 | // 3. Set state of inventory component to reflect current user 45 | this.setState({ 46 | uid: authData.user.uid, 47 | owner: store.owner || authData.user.uid, 48 | }); 49 | }; 50 | 51 | authenticate = provider => { 52 | // Auth provider for FB 53 | const authProvider = new firebase.auth[`${provider}AuthProvider`](); 54 | firebaseApp.auth().signInWithPopup(authProvider).then(this.authHandler); 55 | } 56 | 57 | logout = async () => { 58 | await firebase.auth().signOut(); 59 | this.setState({ uid: null }); 60 | } 61 | 62 | render() { 63 | const logout = ; 64 | 65 | // 1. Check if logged in 66 | if (!this.state.uid) { 67 | return ; 68 | } 69 | 70 | // 2. Check if not owner 71 | if (this.state.uid !== this.state.owner) { 72 | return ( 73 |
    74 |

    Sorry, you are not the owner!

    75 | {logout} 76 |
    77 | ); 78 | } 79 | 80 | // 3. Render inventory 81 | return ( 82 |
    83 |

    Inventory

    84 | {logout} 85 | {Object.keys(this.props.fishes).map(key => ( 86 | 93 | ))} 94 | 97 | 98 |
    99 | ); 100 | } 101 | } 102 | 103 | export default Inventory; -------------------------------------------------------------------------------- /src/css/images/anchor.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/components/App.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import Header from './Header'; 4 | import Order from './Order'; 5 | import Inventory from './Inventory'; 6 | import sampleFishes from "../sample-fishes"; 7 | import Fish from './Fish'; 8 | import base from '../base'; 9 | 10 | // Order: state, lifecycle events, custom stuff, render 11 | class App extends React.Component { 12 | // State 13 | 14 | // State property 15 | state = { 16 | fishes: {}, 17 | order: {} 18 | }; 19 | 20 | static propTypes = { 21 | match: PropTypes.object 22 | // match: this.propTypes.object 23 | } 24 | 25 | componentDidMount() { 26 | const { params } = this.props.match; 27 | 28 | // 1. Reinstate localStorage 29 | const localStorageRef = localStorage.getItem(params.storeId); 30 | if (localStorageRef) { 31 | this.setState({ order: JSON.parse(localStorageRef) }); 32 | } 33 | 34 | // In Firebase, refs are references to data 35 | this.ref = base.syncState(`${params.storeId}/fishes`, { 36 | context: this, 37 | state: 'fishes' 38 | }); 39 | } 40 | 41 | componentDidUpdate() { 42 | localStorage.setItem(this.props.match.params.storeId, JSON.stringify(this.state.order)); 43 | } 44 | 45 | componentWillUnmount() { 46 | base.removeBinding(this.ref); 47 | } 48 | 49 | // State functions - must live inside same component that state lives in 50 | 51 | // Update state 52 | addFish = fish => { 53 | // 1. Copy existing state, never want to directly modify state (mutation) 54 | const fishes = { ...this.state.fishes }; 55 | 56 | // 2. Add new fish to fishes variable 57 | fishes[`fish${Date.now()}`] = fish; 58 | 59 | // 3. Set the new fishes object to state 60 | // this.setState({ fishes: fishes }); 61 | this.setState({ fishes }); 62 | }; 63 | 64 | updateFish = (key, updatedFish) => { 65 | // 1. Copy current state 66 | const fishes = { ...this.state.fishes }; 67 | 68 | // 2. Update state 69 | fishes[key] = updatedFish; 70 | 71 | // 3. Set that to state 72 | this.setState({ fishes }); 73 | } 74 | 75 | deleteFish = key => { 76 | // 1. Copy current state 77 | const fishes = { ...this.state.fishes }; 78 | 79 | // 2. Update state 80 | fishes[key] = null; 81 | 82 | // 3. Set that to state 83 | this.setState({ fishes }); 84 | } 85 | 86 | // Load sample data 87 | loadSampleFishes = () => { 88 | this.setState({ fishes: sampleFishes }); 89 | } 90 | 91 | addToOrder = key => { 92 | // 1. Copy existing state 93 | const order = { ...this.state.order }; 94 | 95 | // 2. Either add to order or update number in order 96 | order[key] = order[key] + 1 || 1; 97 | 98 | // 3. Update state object 99 | this.setState({ order }); 100 | } 101 | 102 | removeFromOrder = key => { 103 | // 1. Copy existing state 104 | const order = { ...this.state.order }; 105 | 106 | // 2. Remove from order 107 | delete order[key]; 108 | 109 | // 3. Update state object 110 | this.setState({ order }); 111 | } 112 | 113 | render() { 114 | return ( 115 |
    116 |
    117 |
    118 |
      119 | {Object.keys(this.state.fishes).map(key => 120 | 126 | )} 127 |
    128 |
    129 | 134 | 142 |
    143 | ); 144 | } 145 | } 146 | 147 | export default App; -------------------------------------------------------------------------------- /src/css/style.styl: -------------------------------------------------------------------------------- 1 | // Import all partials 2 | @require './_*.styl' 3 | 4 | 5 | header.top 6 | text-align center 7 | h1 8 | font-size 14.4rem 9 | line-height 0.7 // this font has a wacky baseline 10 | display flex 11 | justify-content: center; 12 | 13 | // This is a bunch of goofy CSS to make the logo look decent 14 | .ofThe 15 | display flex 16 | font-size 3rem 17 | color orange 18 | justify-content: center; 19 | align-items: center; 20 | background url('images/anchor.svg') center no-repeat; 21 | background-size cover 22 | padding 0 1rem 23 | .of 24 | padding-right 2rem 25 | position relative 26 | right -0.5rem 27 | h3 28 | margin 0 29 | font-size 2rem 30 | color orange 31 | position relative 32 | display inline-block 33 | span 34 | background white 35 | position relative 36 | z-index 2 37 | padding-left 1rem 38 | padding-right 1rem 39 | &:before, &:after 40 | display block 41 | z-index 1 42 | background black 43 | position absolute 44 | width 130% 45 | height 1px 46 | content '' 47 | top 5px 48 | margin-left -15% 49 | &:after 50 | top auto 51 | bottom 7px 52 | 53 | 54 | .catch-of-the-day 55 | display flex 56 | height 90vh 57 | max-width:1500px 58 | margin 0 auto 59 | margin-top 5vh 60 | perspective: 1000px; 61 | transform-style preserve-3d 62 | & > * 63 | flex 1 4 auto 64 | padding 2rem 65 | border 1rem double lighten(black,10%) 66 | position relative 67 | background white 68 | transition all 0.3s 69 | box-shadow 0 5px 5px rgba(0,0,0,0.1) 70 | overflow scroll 71 | &:first-child 72 | flex-shrink 1 // take 4x the extra room 73 | flex-basis 50% 74 | transform translateX(50%) rotateY(6deg) translateX(-50%) 75 | &:nth-child(2) 76 | transform translateX(-50%) rotateY(-14deg) translateX(50%) 77 | border-left 0 78 | border-right 0 79 | min-width 300px 80 | &:last-child 81 | flex-shrink 1 // take 4x the extra room 82 | flex-basis 50% 83 | transform translateX(-50%) rotateY(10deg) translateX(50%) scale(1.08) translateX(24px) 84 | 85 | // Folding Transforms 86 | // Take off folding when not checked 87 | input#fold:not(:checked) ~ #main 88 | .catch-of-the-day > * 89 | transform none 90 | 91 | label[for="fold"] 92 | position absolute 93 | top 1rem 94 | left 1rem 95 | text-transform uppercase 96 | font-size 1.3rem 97 | background black 98 | color white 99 | border 2px solid black 100 | cursor pointer 101 | padding 0.5rem 1rem 102 | 103 | input#fold 104 | display none 105 | &:checked + label 106 | background white 107 | color black 108 | 109 | ul 110 | list-style none 111 | margin 0 112 | padding 0 113 | 114 | ul.order 115 | // Default state 116 | li 117 | border-bottom 1px solid black 118 | padding 2rem 0 119 | display flex 120 | font-size 1.4rem 121 | justify-content space-between 122 | align-items center 123 | &:hover 124 | // padding 1rem 0 125 | button 126 | display inline 127 | button 128 | border 0 129 | display none 130 | line-height 1 131 | padding 0 132 | &.unavailable 133 | text-decoration line-through 134 | background lighten(red, 80%) 135 | .price 136 | font-size 1.2rem 137 | span.count 138 | position relative 139 | overflow hidden 140 | float left // only works if it's floated?! 141 | span 142 | display inline-block 143 | // transition all 0.5s 144 | .total 145 | padding 2rem 0 146 | font-size 1.4rem 147 | border-bottom 3px solid black 148 | border-top 3px double black 149 | & strong 150 | float: right 151 | 152 | .order-title 153 | text-align center 154 | 155 | .fish-edit 156 | margin-bottom 20px 157 | border 2px solid black 158 | overflow hidden 159 | display flex 160 | flex-wrap wrap 161 | input, textarea, select 162 | width 33.33% 163 | padding 10px 164 | line-height 1 165 | font-size 1.2rem 166 | border 0 167 | border-bottom 1px solid black 168 | border-right 1px solid black 169 | appearance none 170 | border-radius 0 171 | background white 172 | &:focus 173 | outline 0 174 | background lighten(orange, 85%) 175 | textarea 176 | width 100% 177 | input:last-of-type 178 | width 100% 179 | button 180 | width 100% 181 | border 0 182 | 183 | 184 | // Menu Styles 185 | .list-of-fish 186 | border-top 2px solid black 187 | border-bottom 1px solid black 188 | padding-top 5px 189 | margin-top 2rem 190 | transform translateZ(0); 191 | 192 | .menu-fish 193 | border-bottom 2px solid black 194 | border-top 1px solid black 195 | padding-bottom 2rem 196 | padding-top 2rem 197 | margin-bottom 5px 198 | clear both 199 | overflow hidden 200 | p 201 | margin 0 202 | font-size 1.8rem 203 | .fish-name 204 | margin 0 205 | display flex 206 | justify-content space-between 207 | align-items center 208 | .price 209 | font-size 1.4rem 210 | // color orange 211 | justify-content flex-end 212 | // font-family 'Open Sans Condensed' 213 | img 214 | float left 215 | width 150px 216 | margin-right 1rem 217 | 218 | button, input[type=submit] 219 | text-transform uppercase 220 | background none 221 | border 1px solid black 222 | font-weight 600 223 | font-size 1.5rem 224 | font-family 'Open Sans', sans-serif 225 | transition all 0.2s 226 | position relative 227 | z-index 2 228 | &[disabled] 229 | color red 230 | background white 231 | border-color red 232 | transform rotate(-10deg) scale(2) translateX(50%) translateY(-50%) 233 | &:hover 234 | color red 235 | cursor not-allowed 236 | &:after 237 | display none 238 | &:after 239 | content '' 240 | z-index -1 241 | display block 242 | background black 243 | position absolute 244 | width 100% 245 | height 0 246 | left 0 247 | top 0 248 | transition all 0.2s 249 | &:hover, &:focus 250 | color white 251 | outline 0 252 | &:after 253 | height 100% 254 | // variants 255 | &.warning 256 | &:after 257 | background red 258 | &.success 259 | &:after 260 | background green 261 | 262 | &.github, &.facebook, &.twitter 263 | border 0 264 | display block 265 | margin-bottom 2rem 266 | width 100% 267 | color white 268 | padding 2rem 269 | 270 | &.github 271 | background #82D465 272 | &:after 273 | background darken(#82D465, 20%) 274 | 275 | &.facebook 276 | background #3864A3 277 | &:after 278 | background darken(#3864A3, 20%) 279 | 280 | &.twitter 281 | background #5EA9DD 282 | &:after 283 | background darken(#5EA9DD, 20%) 284 | 285 | 286 | // Store Selector 287 | .store-selector 288 | background white 289 | max-width 500px 290 | margin 50px auto 291 | padding 2rem 292 | border 2px solid black 293 | input, button 294 | width 100% 295 | &[type="text"] 296 | text-align center 297 | font-size 3rem 298 | -------------------------------------------------------------------------------- /src/css/fonts/haymaker-webfont.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | -------------------------------------------------------------------------------- /src/css/style.css: -------------------------------------------------------------------------------- 1 | .order-enter { 2 | -webkit-transform: translateX(-120%); 3 | transform: translateX(-120%); 4 | -webkit-transition: 0.5s; 5 | transition: 0.5s; 6 | max-height: 0; 7 | padding: 0 !important; 8 | } 9 | .order-enter.order-enter-active { 10 | max-height: 60px; 11 | -webkit-transform: translateX(0); 12 | transform: translateX(0); 13 | padding: 2rem 0 !important; 14 | } 15 | .order-exit { 16 | -webkit-transition: 0.5s; 17 | transition: 0.5s; 18 | -webkit-transform: translateX(0); 19 | transform: translateX(0); 20 | } 21 | .order-exit.order-exit-active { 22 | -webkit-transform: translateX(120%); 23 | transform: translateX(120%); 24 | padding: 0; 25 | } 26 | .count-enter { 27 | background: #f00; 28 | -webkit-transition: 0.5s; 29 | transition: 0.5s; 30 | -webkit-transform: translateY(100%); 31 | transform: translateY(100%); 32 | } 33 | .count-enter.count-enter-active { 34 | background: #ff0; 35 | -webkit-transform: translateY(0); 36 | transform: translateY(0); 37 | } 38 | .count-exit { 39 | background: #000; 40 | -webkit-transform: translateY(0); 41 | transform: translateY(0); 42 | -webkit-transition: 0.5s; 43 | transition: 0.5s; 44 | position: absolute; 45 | left: 0; 46 | bottom: 0; 47 | } 48 | .count-exit.count-exit-active { 49 | background: #008000; 50 | -webkit-transform: translateY(-100%) scale(3); 51 | transform: translateY(-100%) scale(3); 52 | } 53 | @font-face { 54 | font-family: "haymakerregular"; 55 | src: url("./fonts/haymaker-webfont.eot"); 56 | src: url("./fonts/haymaker-webfont.eot?#iefix") format("embedded-opentype"), url("./fonts/haymaker-webfont.woff") format("woff"), url("./fonts/haymaker-webfont.ttf") format("truetype"), url("./fonts/haymaker-webfont.svg#haymakerregular") format("svg"); 57 | font-weight: normal; 58 | font-style: normal; 59 | } 60 | @font-face { 61 | font-family: 'blanchcaps_inline'; 62 | src: url("./fonts/blanch_caps_inline-webfont.eot"); 63 | src: url("./fonts/blanch_caps_inline-webfont.eot?#iefix") format('embedded-opentype'), url("./fonts/blanch_caps_inline-webfont.woff") format('woff'), url("./fonts/blanch_caps_inline-webfont.ttf") format('truetype'), url("./fonts/blanch_caps_inline-webfont.svg#blanchcaps_inline") format('svg'); 64 | font-weight: normal; 65 | font-style: normal; 66 | } 67 | article, 68 | aside, 69 | details, 70 | figcaption, 71 | figure, 72 | footer, 73 | header, 74 | hgroup, 75 | nav, 76 | section, 77 | summary { 78 | display: block; 79 | } 80 | audio, 81 | canvas, 82 | video { 83 | display: inline-block; 84 | } 85 | audio:not([controls]) { 86 | display: none; 87 | height: 0; 88 | } 89 | [hidden] { 90 | display: none; 91 | } 92 | html { 93 | font-family: sans-serif; 94 | -webkit-text-size-adjust: 100%; 95 | -ms-text-size-adjust: 100%; 96 | } 97 | a:focus { 98 | outline: thin dotted; 99 | } 100 | a:active, 101 | a:hover { 102 | outline: 0; 103 | } 104 | h1 { 105 | font-size: 2em; 106 | } 107 | abbr[title] { 108 | border-bottom: 1px dotted; 109 | } 110 | b, 111 | strong { 112 | font-weight: 700; 113 | } 114 | dfn { 115 | font-style: italic; 116 | } 117 | mark { 118 | background: #ff0; 119 | color: #000; 120 | } 121 | code, 122 | kbd, 123 | pre, 124 | samp { 125 | font-family: monospace, serif; 126 | font-size: 1em; 127 | } 128 | pre { 129 | white-space: pre-wrap; 130 | word-wrap: break-word; 131 | } 132 | q { 133 | quotes: 2 1C 2 1D 2 18 2 19; 134 | } 135 | small { 136 | font-size: 80%; 137 | } 138 | sub, 139 | sup { 140 | font-size: 75%; 141 | line-height: 0; 142 | position: relative; 143 | vertical-align: baseline; 144 | } 145 | sup { 146 | top: -0.5em; 147 | } 148 | sub { 149 | bottom: -0.25em; 150 | } 151 | img { 152 | border: 0; 153 | } 154 | svg:not(:root) { 155 | overflow: hidden; 156 | } 157 | fieldset { 158 | border: 1px solid #c0c0c0; 159 | margin: 0 2px; 160 | padding: 0.35em 0.625em 0.75em; 161 | } 162 | button, 163 | input, 164 | select, 165 | textarea { 166 | font-family: inherit; 167 | font-size: 100%; 168 | margin: 0; 169 | } 170 | button, 171 | input { 172 | line-height: normal; 173 | } 174 | button, 175 | html input[type=button], 176 | input[type=reset], 177 | input[type=submit] { 178 | -webkit-appearance: button; 179 | cursor: pointer; 180 | } 181 | button[disabled], 182 | input[disabled] { 183 | cursor: default; 184 | } 185 | input[type=checkbox], 186 | input[type=radio] { 187 | box-sizing: border-box; 188 | padding: 0; 189 | } 190 | input[type=search] { 191 | -webkit-appearance: textfield; 192 | box-sizing: content-box; 193 | } 194 | input[type=search]::-webkit-search-cancel-button, 195 | input[type=search]::-webkit-search-decoration { 196 | -webkit-appearance: none; 197 | } 198 | textarea { 199 | overflow: auto; 200 | vertical-align: top; 201 | } 202 | table { 203 | border-collapse: collapse; 204 | border-spacing: 0; 205 | } 206 | body, 207 | figure { 208 | margin: 0; 209 | } 210 | legend, 211 | button::-moz-focus-inner, 212 | input::-moz-focus-inner { 213 | border: 0; 214 | padding: 0; 215 | } 216 | .clearfix:after { 217 | visibility: hidden; 218 | display: block; 219 | font-size: 0; 220 | content: " "; 221 | clear: both; 222 | height: 0; 223 | } 224 | * { 225 | box-sizing: border-box; 226 | } 227 | html { 228 | font-size: 62.5%; 229 | } 230 | body { 231 | background: #d4d4d4; 232 | -webkit-font-smoothing: antialiased; 233 | -moz-osx-font-smoothing: grayscale; 234 | font-family: 'Open Sans', sans-serif; 235 | font-size: 2rem; 236 | } 237 | h1 { 238 | font-family: 'blanchcaps_inline', sans-serif; 239 | text-align: center; 240 | font-weight: normal; 241 | margin: 0; 242 | } 243 | h2, 244 | h3, 245 | h4, 246 | h5, 247 | h6 { 248 | font-weight: normal; 249 | font-family: 'haymakerregular', sans-serif; 250 | } 251 | h2 { 252 | text-align: center; 253 | margin-top: 0; 254 | margin-bottom: 2rem; 255 | } 256 | h3 { 257 | font-size: 3rem; 258 | } 259 | header.top { 260 | text-align: center; 261 | } 262 | header.top h1 { 263 | font-size: 14.4rem; 264 | line-height: 0.7; 265 | display: -webkit-box; 266 | display: flex; 267 | -webkit-box-pack: center; 268 | justify-content: center; 269 | } 270 | header.top h1 .ofThe { 271 | display: -webkit-box; 272 | display: flex; 273 | font-size: 3rem; 274 | color: #f5a623; 275 | -webkit-box-pack: center; 276 | justify-content: center; 277 | -webkit-box-align: center; 278 | align-items: center; 279 | background: url("images/anchor.svg") center no-repeat; 280 | background-size: cover; 281 | padding: 0 1rem; 282 | } 283 | header.top h1 .ofThe .of { 284 | padding-right: 2rem; 285 | position: relative; 286 | right: -0.5rem; 287 | } 288 | header.top h3 { 289 | margin: 0; 290 | font-size: 2rem; 291 | color: #f5a623; 292 | position: relative; 293 | display: inline-block; 294 | } 295 | header.top h3 span { 296 | background: #fff; 297 | position: relative; 298 | z-index: 2; 299 | padding-left: 1rem; 300 | padding-right: 1rem; 301 | } 302 | header.top h3:before, 303 | header.top h3:after { 304 | display: block; 305 | z-index: 1; 306 | background: #000; 307 | position: absolute; 308 | width: 130%; 309 | height: 1px; 310 | content: ''; 311 | top: 5px; 312 | margin-left: -15%; 313 | } 314 | header.top h3:after { 315 | top: auto; 316 | bottom: 7px; 317 | } 318 | .catch-of-the-day { 319 | display: -webkit-box; 320 | display: flex; 321 | height: 90vh; 322 | max-width: 1500px; 323 | margin: 0 auto; 324 | margin-top: 5vh; 325 | -webkit-perspective: 1000px; 326 | perspective: 1000px; 327 | -webkit-transform-style: preserve-3d; 328 | transform-style: preserve-3d; 329 | } 330 | .catch-of-the-day > * { 331 | -webkit-box-flex: 1; 332 | flex: 1 4 auto; 333 | padding: 2rem; 334 | border: 1rem double #1a1a1a; 335 | position: relative; 336 | background: #fff; 337 | -webkit-transition: all 0.3s; 338 | transition: all 0.3s; 339 | box-shadow: 0 5px 5px rgba(0,0,0,0.1); 340 | overflow: scroll; 341 | } 342 | .catch-of-the-day > *:first-child { 343 | flex-shrink: 1; 344 | flex-basis: 50%; 345 | -webkit-transform: translateX(50%) rotateY(6deg) translateX(-50%); 346 | transform: translateX(50%) rotateY(6deg) translateX(-50%); 347 | } 348 | .catch-of-the-day > *:nth-child(2) { 349 | -webkit-transform: translateX(-50%) rotateY(-14deg) translateX(50%); 350 | transform: translateX(-50%) rotateY(-14deg) translateX(50%); 351 | border-left: 0; 352 | border-right: 0; 353 | min-width: 300px; 354 | } 355 | .catch-of-the-day > *:last-child { 356 | flex-shrink: 1; 357 | flex-basis: 50%; 358 | -webkit-transform: translateX(-50%) rotateY(10deg) translateX(50%) scale(1.08) translateX(24px); 359 | transform: translateX(-50%) rotateY(10deg) translateX(50%) scale(1.08) translateX(24px); 360 | } 361 | input#fold:not(:checked) ~ #main .catch-of-the-day > * { 362 | -webkit-transform: none; 363 | transform: none; 364 | } 365 | label[for="fold"] { 366 | position: absolute; 367 | top: 1rem; 368 | left: 1rem; 369 | text-transform: uppercase; 370 | font-size: 1.3rem; 371 | background: #000; 372 | color: #fff; 373 | border: 2px solid #000; 374 | cursor: pointer; 375 | padding: 0.5rem 1rem; 376 | } 377 | input#fold { 378 | display: none; 379 | } 380 | input#fold:checked + label { 381 | background: #fff; 382 | color: #000; 383 | } 384 | ul { 385 | list-style: none; 386 | margin: 0; 387 | padding: 0; 388 | } 389 | ul.order li { 390 | border-bottom: 1px solid #000; 391 | padding: 2rem 0; 392 | display: -webkit-box; 393 | display: flex; 394 | font-size: 1.4rem; 395 | -webkit-box-pack: justify; 396 | justify-content: space-between; 397 | -webkit-box-align: center; 398 | align-items: center; 399 | } 400 | ul.order li:hover button { 401 | display: inline; 402 | } 403 | ul.order li button { 404 | border: 0; 405 | display: none; 406 | line-height: 1; 407 | padding: 0; 408 | } 409 | ul.order li.unavailable { 410 | text-decoration: line-through; 411 | background: #f8d0d2; 412 | } 413 | ul.order li .price { 414 | font-size: 1.2rem; 415 | } 416 | ul.order li span.count { 417 | position: relative; 418 | overflow: hidden; 419 | float: left; 420 | } 421 | ul.order li span.count span { 422 | display: inline-block; 423 | } 424 | .total { 425 | padding: 2rem 0; 426 | font-size: 1.4rem; 427 | border-bottom: 3px solid #000; 428 | border-top: 3px double #000; 429 | } 430 | .total strong { 431 | float: right; 432 | } 433 | .order-title { 434 | text-align: center; 435 | } 436 | .fish-edit { 437 | margin-bottom: 20px; 438 | border: 2px solid #000; 439 | overflow: hidden; 440 | display: -webkit-box; 441 | display: flex; 442 | flex-wrap: wrap; 443 | } 444 | .fish-edit input, 445 | .fish-edit textarea, 446 | .fish-edit select { 447 | width: 33.33%; 448 | padding: 10px; 449 | line-height: 1; 450 | font-size: 1.2rem; 451 | border: 0; 452 | border-bottom: 1px solid #000; 453 | border-right: 1px solid #000; 454 | -webkit-appearance: none; 455 | -moz-appearance: none; 456 | appearance: none; 457 | border-radius: 0; 458 | background: #fff; 459 | } 460 | .fish-edit input:focus, 461 | .fish-edit textarea:focus, 462 | .fish-edit select:focus { 463 | outline: 0; 464 | background: #fef2de; 465 | } 466 | .fish-edit textarea { 467 | width: 100%; 468 | } 469 | .fish-edit input:last-of-type { 470 | width: 100%; 471 | } 472 | .fish-edit button { 473 | width: 100%; 474 | border: 0; 475 | } 476 | .list-of-fish { 477 | border-top: 2px solid #000; 478 | border-bottom: 1px solid #000; 479 | padding-top: 5px; 480 | margin-top: 2rem; 481 | -webkit-transform: translateZ(0); 482 | transform: translateZ(0); 483 | } 484 | .menu-fish { 485 | border-bottom: 2px solid #000; 486 | border-top: 1px solid #000; 487 | padding-bottom: 2rem; 488 | padding-top: 2rem; 489 | margin-bottom: 5px; 490 | clear: both; 491 | overflow: hidden; 492 | } 493 | .menu-fish p { 494 | margin: 0; 495 | font-size: 1.8rem; 496 | } 497 | .menu-fish .fish-name { 498 | margin: 0; 499 | display: -webkit-box; 500 | display: flex; 501 | -webkit-box-pack: justify; 502 | justify-content: space-between; 503 | -webkit-box-align: center; 504 | align-items: center; 505 | } 506 | .menu-fish .price { 507 | font-size: 1.4rem; 508 | -webkit-box-pack: end; 509 | justify-content: flex-end; 510 | } 511 | .menu-fish img { 512 | float: left; 513 | width: 150px; 514 | margin-right: 1rem; 515 | } 516 | button, 517 | input[type=submit] { 518 | text-transform: uppercase; 519 | background: none; 520 | border: 1px solid #000; 521 | font-weight: 600; 522 | font-size: 1.5rem; 523 | font-family: 'Open Sans'; 524 | -webkit-transition: all 0.2s; 525 | transition: all 0.2s; 526 | position: relative; 527 | z-index: 2; 528 | } 529 | button[disabled], 530 | input[type=submit][disabled] { 531 | color: #d12028; 532 | background: #fff; 533 | border-color: #d12028; 534 | -webkit-transform: rotate(-10deg) scale(2) translateX(50%) translateY(-50%); 535 | transform: rotate(-10deg) scale(2) translateX(50%) translateY(-50%); 536 | } 537 | button[disabled]:hover, 538 | input[type=submit][disabled]:hover { 539 | color: #d12028; 540 | cursor: not-allowed; 541 | } 542 | button[disabled]:after, 543 | input[type=submit][disabled]:after { 544 | display: none; 545 | } 546 | button:after, 547 | input[type=submit]:after { 548 | content: ''; 549 | z-index: -1; 550 | display: block; 551 | background: #000; 552 | position: absolute; 553 | width: 100%; 554 | height: 0; 555 | left: 0; 556 | top: 0; 557 | -webkit-transition: all 0.2s; 558 | transition: all 0.2s; 559 | } 560 | button:hover, 561 | input[type=submit]:hover, 562 | button:focus, 563 | input[type=submit]:focus { 564 | color: #fff; 565 | outline: 0; 566 | } 567 | button:hover:after, 568 | input[type=submit]:hover:after, 569 | button:focus:after, 570 | input[type=submit]:focus:after { 571 | height: 100%; 572 | } 573 | button.warning:after, 574 | input[type=submit].warning:after { 575 | background: #d12028; 576 | } 577 | button.success:after, 578 | input[type=submit].success:after { 579 | background: #2dc22d; 580 | } 581 | button.github, 582 | input[type=submit].github, 583 | button.facebook, 584 | input[type=submit].facebook, 585 | button.twitter, 586 | input[type=submit].twitter { 587 | border: 0; 588 | display: block; 589 | margin-bottom: 2rem; 590 | width: 100%; 591 | color: #fff; 592 | padding: 2rem; 593 | } 594 | button.github, 595 | input[type=submit].github { 596 | background: #82d465; 597 | } 598 | button.github:after, 599 | input[type=submit].github:after { 600 | background: #5cc437; 601 | } 602 | button.facebook, 603 | input[type=submit].facebook { 604 | background: #3864a3; 605 | } 606 | button.facebook:after, 607 | input[type=submit].facebook:after { 608 | background: #2d5082; 609 | } 610 | button.twitter, 611 | input[type=submit].twitter { 612 | background: #5ea9dd; 613 | } 614 | button.twitter:after, 615 | input[type=submit].twitter:after { 616 | background: #2c8dd0; 617 | } 618 | .store-selector { 619 | background: #fff; 620 | max-width: 500px; 621 | margin: 50px auto; 622 | padding: 2rem; 623 | border: 2px solid #000; 624 | } 625 | .store-selector input, 626 | .store-selector button { 627 | width: 100%; 628 | } 629 | .store-selector input[type="text"], 630 | .store-selector button[type="text"] { 631 | text-align: center; 632 | font-size: 3rem; 633 | } 634 | -------------------------------------------------------------------------------- /src/css/fonts/blanch_caps_inline-webfont.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | --------------------------------------------------------------------------------