Added CraftingTable* components and UI

This commit is contained in:
Andrew Miner
2014-12-23 20:26:36 -08:00
parent b05fc4c5c9
commit 95c15abe10
30 changed files with 866 additions and 252 deletions
+59 -18
View File
@@ -8,17 +8,6 @@ All rights reserved.
@import 'mixins';
@import 'reset';
// Constants ///////////////////////////////////////////////////////////////////////////////////////////////////////////
$color-background: #AAC9FF;
$color-bedrock: #0A0A0A;
$color-content: rgba(230, 230, 230, 0.90);
$color-brick-dark: #7A7A7A;
$color-brick-light: #D9D9D9;
$color-footer: #3D1D09;
$color-footer-text: white;
$color-dirt: #906647;
// Fonts ///////////////////////////////////////////////////////////////////////////////////////////////////////////////
@font-face {
@@ -28,6 +17,29 @@ $color-dirt: #906647;
src: url('/fonts/press_start.ttf') format('truetype');
}
// Constants ///////////////////////////////////////////////////////////////////////////////////////////////////////////
$color-background: #AAC9FF;
$color-bedrock: #0A0A0A;
$color-brick-dark: #7A7A7A;
$color-brick-light: #D9D9D9;
$color-content: rgba(230, 230, 230, 0.90);
$color-dirt: #906647;
$color-footer: #3D1D09;
$color-footer-text: white;
$color-gray-faint: #F2F2F2;
$color-gray-light: #CCCCCC;
$color-table-background: white;
$font-family-header: 'PressStart';
$font-family-normal: sans-serif;
$font-family-input: monospace;
$font-size-small: 1.1em;
$font-size-normal: 1.3em;
$font-size-large: 2.0em;
$font-size-x-large: 3.0em;
// Common //////////////////////////////////////////////////////////////////////////////////////////////////////////////
body {
@@ -47,8 +59,8 @@ h1 {
}
p {
font-family: PressStart;
font-size: 3em;
font-family: $font-family-header;
font-size: $font-size-x-large;
position: relative; top: -0.5em;
}
}
@@ -59,18 +71,40 @@ h2 {
* { display: inline; }
img {
position: relative; height: 6em; width: 6em;
position: relative; height: 4em; width: 4em;
margin-right: 1em;
vertical-align: bottom;
}
p {
font-family: PressStart;
font-size: 2em;
font-family: $font-family-header;
font-size: $font-size-large;
position: relative; top: -0.25em;
}
}
h3 {
font-family: $font-family-normal;
font-weight: bold;
p { font-size: $font-size-normal; }
}
p {
font-family: $font-family-normal;
font-size: $font-size-normal;
}
input {
font-family: $font-family-input;
font-size: $font-size-normal;
}
label {
font-family: $font-family-normal;
font-size: $font-size-normal;
}
.centered {
text-align: center;
}
@@ -84,6 +118,13 @@ h2 {
width: 100%;
}
.page > div {
position: relative; width: 100%;
padding: 2em 2em 0 2em;
&:first-child { padding-top: 0; }
}
// Views ///////////////////////////////////////////////////////////////////////////////////////////////////////////////
.view__footer {
@@ -111,7 +152,7 @@ h2 {
p {
font-family: sans-serif;
font-size: 1.25em;
font-size: $font-size-small;
padding-bottom: 1.0em;
&:last-child {
@@ -123,7 +164,7 @@ h2 {
padding-bottom: 1.0em;
p {
font-family: PressStart;
font-family: $font-family-header;
font-size: 1.5em;
}
}
+1 -1
View File
@@ -1,4 +1,4 @@
a,abbr,acronym,address,applet,article,aside,audio,b,big,blockquote,body,canvas,caption,center,cite,code,dd,del,details,dfn,dialog,div,dl,dt,em,embed,fieldset,figcaption,figure,font,footer,form,h1,h2,h3,h4,h5,h6,header,hgroup,hr,html,i,iframe,img,ins,kbd,label,legend,li,mark,menu,meter,nav,object,ol,output,p,pre,progress,q,rp,rt,ruby,s,samp,section,small,span,strike,strong,sub,summary,sup,table,tbody,td,tfoot,th,thead,time,tr,tt,u,ul,var,video,xmp {
a,abbr,acronym,address,applet,article,aside,audio,b,big,blockquote,body,canvas,caption,center,cite,code,dd,del,details,dfn,dialog,div,dl,dt,em,embed,fieldset,figcaption,figure,font,footer,form,h1,h2,h3,h4,h5,h6,header,hgroup,hr,html,i,iframe,img,ins,kbd,label,legend,li,mark,menu,meter,nav,object,ol,output,p,pre,progress,q,rp,rt,ruby,s,samp,section,small,span,strike,strong,sub,summary,sup,table,tbody,td,textarea,tfoot,th,thead,time,tr,tt,u,ul,var,video,xmp {
border: 0;
margin: 0;
padding: 0;
+45
View File
@@ -0,0 +1,45 @@
/*
Crafting Table - crafting_table.scss
Copyright (c) 2014 by Redwood Labs
All rights reserved.
*/
.view__crafting_table {
.recipe_selector {
background: $color-table-background;
padding: 1em;
position: relative; width: 100%;
& > * {
display: inline;
margin-left: 0.5em;
&:first-child { margin-left: 0; }
}
input[type="checkbox"] + label { margin-left: 0; }
input[name="name"] { width: 60%; }
}
h3 {
margin-bottom: 0.5em;
}
table {
background: white;
width: 100%;
td { padding: 0 1em 1em 1em; width: 33%; }
}
textarea, ul {
border: 0.1em solid $color-gray-light;
width: 100%;
height: 100%;
min-height: 6em;
padding: 0.2em;
}
}
+1
View File
@@ -5,4 +5,5 @@ Copyright (c) 2014 by Redwood Labs
All rights reserved.
*/
@import 'crafting_table';
@import 'recipe_catalog';
+4 -7
View File
@@ -6,9 +6,6 @@ All rights reserved.
*/
.view__recipe_catalog {
position: relative; width: 100%;
padding: 0 2em;
button {
@include center-vertical;
position: absolute; right: 1em;
@@ -19,12 +16,12 @@ All rights reserved.
table {
background: white;
margin: 0 auto;
width: 98%;
width: 100%;
td {
height: 3em; padding: 0 1em; position: relative; vertical-align: middle;
p { font-size: 1.5em; }
position: relative; height: 2 * $font-size-normal;
padding: 0 1em;
vertical-align: middle;
}
td:nth-child(1) { width: 30%; }
+2 -1
View File
@@ -30,4 +30,5 @@
.centered
iframe(src="http://ghbtns.com/github-btn.html?user=andrewminer&repo=crafting-guide&type=fork&size=large",
allowtransparency="true", frameborder="0", scrolling="0", width="100", height="32")
.divider.bottom
a(href="/test.html")
.divider.bottom
+2 -1
View File
@@ -7,6 +7,7 @@
.view__header
h1
img(src="images/workbench_front.png")
a(href="/")
img(src="images/workbench_front.png")
p Crafting Guide
.divider.bottom
+7 -5
View File
@@ -16,12 +16,14 @@ Opacity.hidden = 1e-6
Opacity.shown = 1
exports.Event = Event = {}
Event.change = 'change'
Event.add = 'add' # collection, item...
Event.change = 'change' # model
Event.load = {}
Event.load.started = 'book:load:started' # controller, url
Event.load.succeeded = 'book:load:succeeded' # controller, book
Event.load.failed = 'book:load:failed' # controller, error message
Event.load.finished = 'book:load:finished' # controller
Event.load.started = 'load:started' # controller, url
Event.load.succeeded = 'load:succeeded' # controller, book
Event.load.failed = 'load:failed' # controller, error message
Event.load.finished = 'load:finished' # controller
Event.remove = 'remove' # collection, item...
exports.DefaultBookUrls = DefaultBookUrls = [
'/data/vanilla.json'
@@ -19,6 +19,7 @@ module.exports = class BaseController extends Backbone.View
@_parent = options.parent
@_children = []
Object.defineProperty this, 'rendered', get:-> return @_rendered
@_loadTemplate options.templateName
super options
@@ -0,0 +1,76 @@
###
# Crafting Guide - crafting_table_controller.coffee
#
# Copyright (c) 2014 by Redwood Labs
# All rights reserved.
###
BaseController = require './base_controller'
InventoryParser = require '../models/inventory_parser'
########################################################################################################################
module.exports = class CraftingTableController extends BaseController
constructor: (options={})->
if not options.model? then throw new Error 'options.model is required'
options.templateName = 'crafting_table'
super options
@_parser = new InventoryParser
@_name = null
@_quantity = null
@_includingTools = null
# Event Methods ################################################################################
onHaveFieldChanged: ->
return unless @rendered
@model.have.clear()
@_parser.parse @$haveField.val(), @model.have
@_recalculate()
onIncludingToolsFieldChanged: ->
return unless @rendered
@_includingTools = @$('input[name="including_tools"]:checked').length > 0
@_recalculate()
onNameFieldChanged: ->
return unless @rendered
@_name = @$nameField.val()
@_recalculate()
onQuantityFieldChanged: ->
return unless @rendered
@_quantity = parseInt @$quantityField.find(":selected").val()
@_recalculate()
# BaseController Overrides #####################################################################
onDidRender: ->
@$haveField = @$('textarea[name="have"]')
@$includingToolsField = @$('input[name="including_tools"]')
@$nameField = @$('input[name="name"]')
@$quantityField = @$('select[name="quantity"]')
super
@onHaveFieldChanged()
@onIncludingToolsFieldChanged()
@onNameFieldChanged()
@onQuantityFieldChanged()
# Backbone.View Overrides ######################################################################
events:
'input textarea[name="have"]': 'onHaveFieldChanged'
'input input[name="name"]': 'onNameFieldChanged'
'input select[name="quantity"]': 'onQuantityFieldChanged'
'change input[name="including_tools"]': 'onIncludingToolsFieldChanged'
# Private Methods ##############################################################################
_recalculate: ->
return if not @_name? or @_name.length is 0
@model.craft @_name, @_quantity
@@ -6,6 +6,7 @@
###
BaseController = require './base_controller'
CraftingTableController = require './crafting_table_controller'
LandingPage = require '../models/landing_page'
RecipeCatalogController = require './recipe_catalog_controller'
@@ -21,5 +22,6 @@ module.exports = class LandingPageController extends BaseController
# BaseController Overrides #####################################################################
onDidRender: ->
@recipeBooksController = @addChild RecipeCatalogController, '.view__recipe_catalog', model:@model.catalog
@catalogController = @addChild RecipeCatalogController, '.view__recipe_catalog', model:@model.catalog
@tableController = @addChild CraftingTableController, '.view__crafting_table', model:@model.table
super
+2 -2
View File
@@ -45,8 +45,8 @@ module.exports = class CraftingGuideRouter extends Backbone.Router
controller.render()
$pageContent = $('.page')
$pageContent.empty()
$pageContent.append controller.$el
controller.$el.addClass 'page'
$pageContent.replaceWith controller.$el
controller.$el.fadeIn showDuration, ->
controller.onDidShow()
+27
View File
@@ -0,0 +1,27 @@
###
# Crafting Guide - crafting_table.coffee
#
# Copyright (c) 2014 by Redwood Labs
# All rights reserved.
###
BaseModel = require './base_model'
Inventory = require './inventory'
########################################################################################################################
module.exports = class CraftingTable extends BaseModel
constructor: (attributes={}, options={})->
if not attributes.catalog? then throw new Error "attributes.catalog is required"
super attributes, options
@have = new Inventory
@make = new Inventory
@get = new Inventory
# Public Methods ###############################################################################
craft: (name, quantity=1, includingTools=false)->
toolPhrase = if includingTools then ' includingTools' else ''
logger.verbose "calculating build plan for #{quantity} #{name}#{toolPhrase} with inventory: #{@have}"
+79
View File
@@ -0,0 +1,79 @@
###
# Crafting Guide - inventory.coffee
#
# Copyright (c) 2014 by Redwood Labs
# All rights reserved.
###
BaseModel = require './base_model'
{Event} = require '../constants'
Item = require './item'
########################################################################################################################
module.exports = class Inventory extends BaseModel
constructor: (attributes={}, options={})->
super attributes, options
@clear()
# Public Methods ###############################################################################
add: (name, quantity=1)->
item = @_items[name]
if not item?
item = new Item name:name, quantity:quantity
@_items[name] = item
@_names.push name
else
item.quantity += quantity
@trigger Event.add, this, name, quantity
@trigger Event.change, this
return this
clear: ->
@_items = {}
@_names = []
each: (onItem)->
for name in @_names
item = @_items[name]
onItem item
hasAtLeast: (name, quantity=1)->
if quantity is 0 then return true
item = @_items[name]
return false unless item?
return item.quantity >= quantity
remove: (name, quantity=1)->
item = @_items[name]
if not item? then throw new Error "cannot remove #{name} since it is not in this inventory"
if item.quantity < quantity
throw new Error "cannot remove #{quantity} #{name} because there is only #{item.quantity} in this inventory"
item.quantity -= quantity
if item.quantity is 0
delete @_items[name]
@_names = _(@_names).without name
@trigger Event.remove, this, name, quantity
@trigger Event.change, this
return this
# Object Overrides #############################################################################
toString: ->
result = [@constructor.name, " (", @cid, ") { items: ["]
needsDelimiter = false
for name, item of @_items
if needsDelimiter then result.push ', '
result.push item.toString()
needsDelimiter = true
result.push ']'
result.push '}'
return result.join ''
@@ -0,0 +1,29 @@
###
# Crafting Guide - inventory_parser.coffee
#
# Copyright (c) 2014 by Redwood Labs
# All rights reserved.
###
Inventory = require './inventory'
########################################################################################################################
module.exports = class InventoryParser
@ITEM_REGEX = /^([0-9]+)(.*)$/
parse: (data, inventory=null)->
inventory ?= new Inventory
return inventory if not data? or data.length is 0
lines = data.split '\n'
for line in lines
match = InventoryParser.ITEM_REGEX.exec line
name = if match? then match[2].trim() else line
quantity = if match? then parseInt(match[1]) else 1
if name.length > 0
inventory.add name, quantity
return inventory
+41 -3
View File
@@ -11,9 +11,47 @@ BaseModel = require './base_model'
module.exports = class Item extends BaseModel
@DEFAULT_STACK_SIZE = 64
constructor: (attributes={}, options={})->
attributes.name ?= ''
attributes.quantity ?= 1
attributes.name ?= ''
attributes.quantity ?= 1
attributes.stackSize ?= Item.DEFAULT_STACK_SIZE
super attributes, options
# Public Methods ###############################################################################
Object.defineProperty this, 'stackQuantity', get:@getStackQuantity
# Public Methods ###############################################################################
canMerge: (item)->
return item.name is @name
merge: (item)->
if not @canMerge(item) then throw new Error "cannot merge #{item} into #{this}"
@quantity += item.quantity
toFormatHash: ->
return result =
name: @name
quantity: @quantity
stackQuantity: @stackQuantity
# Property Methods #############################################################################
getStackQuantity: ->
count = 0
extra = @quantity
while extra > @stackSize
extra -= @stackSize
count += 1
return count:count extra:extra
# Object Overrides #############################################################################
toString: ->
result = "#{@constructor.name} (#{@cid}) { name:\"#{@name}\", quantity:#{@quantity}"
if @stackSize isnt Item.DEFAULT_STACK_SIZE
result += ", stackSize:#{@stackSize}"
result += ' }'
return result
+3 -1
View File
@@ -5,7 +5,8 @@
# All rights reserved.
###
BaseModel = require './base_model'
BaseModel = require './base_model'
CraftingTable = require './crafting_table'
RecipeCatalog = require './recipe_catalog'
########################################################################################################################
@@ -14,4 +15,5 @@ module.exports = class LandingPage extends BaseModel
constructor: (attributes={}, options={})->
attributes.catalog ?= new RecipeCatalog
attributes.table ?= new CraftingTable catalog:attributes.catalog
super attributes, options
@@ -1,81 +0,0 @@
###
# Crafting Guide - v1.coffee
#
# Copyright (c) 2014 by Redwood Labs
# All rights reserved.
###
Item = require '../item'
Recipe = require '../recipe'
RecipeBook = require '../recipe_book'
########################################################################################################################
module.exports = class V1
constructor: ->
@_errorLocation = 'the header information'
parse: (data)->
return @_parseRecipeBook data
# Private Methods ##############################################################################
_parseRecipeBook: (data)->
if not data? then throw new Error 'recipe book data is missing'
if not data.version? then throw new Error 'version is required'
if not data.mod_name? then throw new Error 'mod_name is required'
if not data.mod_version? then throw new Error 'mod_version is required'
if not _.isArray(data.recipes) then throw new Error 'recipes must be an array'
book = new RecipeBook version:data.version, modName:data.mod_name, modVersion:data.mod_version
book.description = data.description or ''
for index in [0...data.recipes.length]
@_errorLocation = "recipe #{index + 1}"
recipeData = data.recipes[index]
book.recipes.push @_parseRecipe recipeData
return book
_parseRecipe: (data, options={})->
if not data? then throw new Error "recipe data is missing for #{@_errorLocation}"
if not data.output? then throw new Error "#{@_errorLocation} is missing output"
if not data.input? then throw new Error "#{@_errorLocation} is missing input"
output = @_parseItemList data.output, field:'output', canBeEmpty:false
@_errorLocation = "recipe for output[0].name"
data.tools ?= []
input = @_parseItemList data.input, field:'input', canBeEmpty:false
tools = @_parseItemList data.tools, field:'tools', canBeEmpty:true
return new Recipe input:input, output:output, tools:tools
_parseItemList: (data, options={})->
if not data? then throw new Error "#{@_errorLocation} must have an #{options.field} field"
if not _.isArray(data) then data = [data]
if data.length is 0 and not options.canBeEmpty
throw new Error "#{options.field} for #{@_errorLocation} cannot be empty"
result = []
for index in [0...data.length]
itemData = data[index]
result.push @_parseItem itemData, field:options.field, index:index
return result
_parseItem: (data, options={})->
errorBase = "#{options.field} element #{options.index} for #{@_errorLocation}"
if not data? then throw new Error "#{errorBase} is missing"
if _.isString(data) then data = [1, data]
if not _.isArray(data) then throw new Error "#{errorBase} must be an array"
if data.length is 1 then data.unshift 1
if data.length isnt 2 then throw new Error "#{errorBase} must have at least one element"
if not _.isNumber(data[0]) then throw new Error "#{errorBase} must start with a number"
return new Item quantity:data[0], name:data[1]
+36
View File
@@ -12,6 +12,42 @@ BaseModel = require './base_model'
module.exports = class Recipe extends BaseModel
constructor: (attributes={}, options={})->
if not attributes.output? then throw new Error "attributes.output is required"
if not attributes.input? then throw new Error "attributes.input is required"
attributes.tools ?= []
super attributes, options
Object.defineProperty this, 'name', get:-> @output[0].name
# Object Overrides #############################################################################
toString: ->
result = [@constructor.name, " (", @cid, ") { name:", @name]
result.push ", input:["
needsDelimiter = false
for inputItem in @input
if needsDelimiter then result.push ', '
result.push inputItem.toString()
needsDelimiter = false
result.push ']'
result.push ", output:["
needsDelimiter = false
for outputItem in @output
if needsDelimiter then result.push ', '
result.push outputItem.toString()
needsDelimiter = false
result.push ']'
if @tools.length > 0
result.push ", tools:["
needsDelimiter = false
for tool in @tools
if needsDelimiter then result.push ', '
result.push tool.toString()
needsDelimiter = false
result.push ']'
result.push '}'
return result.join ''
+73 -1
View File
@@ -5,7 +5,9 @@
# All rights reserved.
###
V1 = require './parser_versions/v1'
Item = require './item'
Recipe = require './recipe'
RecipeBook = require './recipe_book'
########################################################################################################################
@@ -24,3 +26,73 @@ module.exports = class RecipeBookParser
throw new Error "cannot parse version #{data.version} recipe books"
return parser.parse data
########################################################################################################################
module.exports.V1 = class V1
constructor: ->
@_errorLocation = 'the header information'
parse: (data)->
return @_parseRecipeBook data
# Private Methods ##############################################################################
_parseRecipeBook: (data)->
if not data? then throw new Error 'recipe book data is missing'
if not data.version? then throw new Error 'version is required'
if not data.mod_name? then throw new Error 'mod_name is required'
if not data.mod_version? then throw new Error 'mod_version is required'
if not _.isArray(data.recipes) then throw new Error 'recipes must be an array'
book = new RecipeBook version:data.version, modName:data.mod_name, modVersion:data.mod_version
book.description = data.description or ''
for index in [0...data.recipes.length]
@_errorLocation = "recipe #{index + 1}"
recipeData = data.recipes[index]
book.recipes.push @_parseRecipe recipeData
return book
_parseRecipe: (data, options={})->
if not data? then throw new Error "recipe data is missing for #{@_errorLocation}"
if not data.output? then throw new Error "#{@_errorLocation} is missing output"
if not data.input? then throw new Error "#{@_errorLocation} is missing input"
output = @_parseItemList data.output, field:'output', canBeEmpty:false
@_errorLocation = "recipe for output[0].name"
data.tools ?= []
input = @_parseItemList data.input, field:'input', canBeEmpty:false
tools = @_parseItemList data.tools, field:'tools', canBeEmpty:true
return new Recipe input:input, output:output, tools:tools
_parseItemList: (data, options={})->
if not data? then throw new Error "#{@_errorLocation} must have an #{options.field} field"
if not _.isArray(data) then data = [data]
if data.length is 0 and not options.canBeEmpty
throw new Error "#{options.field} for #{@_errorLocation} cannot be empty"
result = []
for index in [0...data.length]
itemData = data[index]
result.push @_parseItem itemData, field:options.field, index:index
return result
_parseItem: (data, options={})->
errorBase = "#{options.field} element #{options.index} for #{@_errorLocation}"
if not data? then throw new Error "#{errorBase} is missing"
if _.isString(data) then data = [1, data]
if not _.isArray(data) then throw new Error "#{errorBase} must be an array"
if data.length is 1 then data.unshift 1
if data.length isnt 2 then throw new Error "#{errorBase} must have at least one element"
if not _.isNumber(data[0]) then throw new Error "#{errorBase} must start with a number"
return new Item quantity:data[0], name:data[1]
+10 -1
View File
@@ -2,13 +2,22 @@ var jade = jade || require('jade').runtime;
this["JST"] = this["JST"] || {};
this["JST"]["crafting_table"] = function template(locals) {
var buf = [];
var jade_mixins = {};
var jade_interp;
var jade_indent = [];
buf.push("\n<div class=\"view__crafting_table\">\n <h2><img src=\"/images/workbench_top.png\"/>\n <p>Crafting Table</p>\n </h2>\n <div class=\"recipe_selector\">\n <p>I want to make</p>\n <select name=\"quantity\">\n <option value=\"1\">1</option>\n <option value=\"2\">2</option>\n <option value=\"4\">4</option>\n <option value=\"8\">8</option>\n <option value=\"16\">16</option>\n <option value=\"64\">64</option>\n </select>\n <input name=\"name\" placeholder=\"enter an item name\"/>\n <label>\n <input name=\"including_tools\" type=\"checkbox\"/>including tools\n </label>\n </div>\n <table>\n <tr>\n <td class=\"have\">\n <h3>\n <p>You have...</p>\n </h3>\n <textarea name=\"have\" placeholder=\"enter what you have, for example:\n\n3 wool\n4 iron ingot\"></textarea>\n </td>\n <td class=\"make\">\n <h3>\n <p>You'll make...</p>\n </h3>\n <ul></ul>\n </td>\n <td class=\"get\">\n <h3>\n <p>You'll get...</p>\n </h3>\n <ul></ul>\n </td>\n </tr>\n </table>\n</div>");;return buf.join("");
};
this["JST"]["landing_page"] = function template(locals) {
var buf = [];
var jade_mixins = {};
var jade_interp;
var jade_indent = [];
buf.push("\n<div class=\"view__landing_page\">\n <div class=\"view__recipe_catalog\"></div>\n <div class=\"view__crafter\"></div>\n</div>");;return buf.join("");
buf.push("\n<div class=\"view__landing_page\">\n <div class=\"view__recipe_catalog\"></div>\n <div class=\"view__crafting_table\"></div>\n</div>");;return buf.join("");
};
this["JST"]["recipe_book"] = function template(locals) {
+37
View File
@@ -0,0 +1,37 @@
//-
//- Crafting Guide - crafting_table.jade
//-
//- Copyright (c) 2014 by Redwood Labs
//- All rights reserved.
//-
.view__crafting_table
h2
img(src="/images/workbench_top.png")
p Crafting Table
.recipe_selector
p I want to make
select(name="quantity")
option(value="1") 1
option(value="2") 2
option(value="4") 4
option(value="8") 8
option(value="16") 16
option(value="64") 64
input(name="name", placeholder="enter an item name")
label
input(name="including_tools", type="checkbox")
| including tools
table
tr
td.have
h3: p You have...
textarea(name="have", placeholder="enter what you have, for example:\n\n3 wool\n4 iron ingot")
td.make
h3: p You'll make...
ul
td.get
h3: p You'll get...
ul
+1 -1
View File
@@ -7,4 +7,4 @@
.view__landing_page
.view__recipe_catalog
.view__crafter
.view__crafting_table