From a315282b70afdc07799e1b5e13df73207f9686f9 Mon Sep 17 00:00:00 2001 From: Emi Matchu Date: Mon, 9 Sep 2024 18:48:08 -0700 Subject: [PATCH] Extract all the item pages' stylesheets into their own CSS files No more of this loading everything into `application.css`! I'm arbitrarily starting here because that's where I've been playing lately, but this is part of a larger effort to move toward a more straightforward CSS architecture (and away from Sass even?) --- app/assets/stylesheets/_items.sass | 30 -- app/assets/stylesheets/application.css.sass | 4 - .../stylesheets/item_trades/_index.sass | 29 -- app/assets/stylesheets/item_trades/index.sass | 28 ++ app/assets/stylesheets/items/_index.sass | 25 -- app/assets/stylesheets/items/_show.sass | 401 ------------------ app/assets/stylesheets/items/index.sass | 23 + app/assets/stylesheets/items/show.sass | 400 +++++++++++++++++ app/assets/stylesheets/layouts/items.sass | 28 ++ app/views/item_trades/index.html.haml | 4 + app/views/items/index.html.haml | 3 + app/views/items/needed.html.haml | 3 + app/views/items/show.html.haml | 2 + 13 files changed, 491 insertions(+), 489 deletions(-) delete mode 100644 app/assets/stylesheets/_items.sass delete mode 100644 app/assets/stylesheets/item_trades/_index.sass create mode 100644 app/assets/stylesheets/item_trades/index.sass delete mode 100644 app/assets/stylesheets/items/_index.sass delete mode 100644 app/assets/stylesheets/items/_show.sass create mode 100644 app/assets/stylesheets/items/index.sass create mode 100644 app/assets/stylesheets/items/show.sass create mode 100644 app/assets/stylesheets/layouts/items.sass diff --git a/app/assets/stylesheets/_items.sass b/app/assets/stylesheets/_items.sass deleted file mode 100644 index 56a5987b..00000000 --- a/app/assets/stylesheets/_items.sass +++ /dev/null @@ -1,30 +0,0 @@ -@import "partials/campaign-progress" - -body.items-index, body.items-show, body.items-needed, body.item_trades - +campaign-progress - - text-align: center - - .item-search-form - display: flex - gap: .5em - justify-content: center - - input[type=text] - font-size: 125% - width: 15em - flex: 0 1 auto - - h1 - margin-bottom: 1em - img - height: 80px - margin-bottom: -0.5em - width: 80px - a - text-decoration: none - span - text-decoration: underline - &:hover span - text-decoration: none - diff --git a/app/assets/stylesheets/application.css.sass b/app/assets/stylesheets/application.css.sass index f2bf8540..250b14f2 100644 --- a/app/assets/stylesheets/application.css.sass +++ b/app/assets/stylesheets/application.css.sass @@ -14,10 +14,6 @@ @import closet_lists/form @import neopets_page_import_tasks/new @import contributions/index -@import items -@import items/index -@import items/show -@import item_trades/index @import outfits/index @import outfits/new @import pets/bulk diff --git a/app/assets/stylesheets/item_trades/_index.sass b/app/assets/stylesheets/item_trades/_index.sass deleted file mode 100644 index 010d2b8f..00000000 --- a/app/assets/stylesheets/item_trades/_index.sass +++ /dev/null @@ -1,29 +0,0 @@ -@import "../partials/item_header" - -body.item_trades-index - .item-header - +item-header - - .item-subpage-title - text-align: left - margin-bottom: .5em - - .trades-table - text-align: left - width: 100% - table-layout: fixed - - th, td - &:nth-child(1), &:nth-child(2) - width: 15ch - overflow: hidden - text-overflow: ellipsis - - .trade-list-names - list-style: none - - li - display: inline - - &:not(:last-child)::after - content: ", " diff --git a/app/assets/stylesheets/item_trades/index.sass b/app/assets/stylesheets/item_trades/index.sass new file mode 100644 index 00000000..fc5d4a1e --- /dev/null +++ b/app/assets/stylesheets/item_trades/index.sass @@ -0,0 +1,28 @@ +@import "../partials/item_header" + +.item-header + +item-header + +.item-subpage-title + text-align: left + margin-bottom: .5em + +.trades-table + text-align: left + width: 100% + table-layout: fixed + + th, td + &:nth-child(1), &:nth-child(2) + width: 15ch + overflow: hidden + text-overflow: ellipsis + + .trade-list-names + list-style: none + + li + display: inline + + &:not(:last-child)::after + content: ", " diff --git a/app/assets/stylesheets/items/_index.sass b/app/assets/stylesheets/items/_index.sass deleted file mode 100644 index cbad0d86..00000000 --- a/app/assets/stylesheets/items/_index.sass +++ /dev/null @@ -1,25 +0,0 @@ -=main_unit - float: left - width: 49% - h2 - font-size: 125% - -body.items-index - form - margin-bottom: 2em - - #search-info - +main_unit - padding-right: 1% - dl - text-align: left - dd - margin-bottom: 1em - - #species-search-links - +main_unit - padding-left: 1% - img - height: 80px - width: 80px - diff --git a/app/assets/stylesheets/items/_show.sass b/app/assets/stylesheets/items/_show.sass deleted file mode 100644 index 174602e9..00000000 --- a/app/assets/stylesheets/items/_show.sass +++ /dev/null @@ -1,401 +0,0 @@ -@import "../partials/clean/constants" -@import "../partials/clean/mixins" -@import "../partials/item_header" - -body.items-show - #container - width: 900px // A bit more generous to the preview area! - - .item-header - +item-header - - #item-contributors - +subtle-banner - clear: both - margin: - bottom: 0 - top: 2em - - header - display: inline - font-weight: bold - margin-right: .25em - - footer - display: inline - - ul - display: inline - list-style: none - - li - display: inline - - &::after - content: ", " - - &:last-child::after - content: "." - - .nc-icon - height: 16px - width: 16px - - .preview-area - margin: 0 auto - position: relative - - .customize-more - position: absolute - top: 1em - right: 1em - - display: flex - align-items: center - text-decoration: none - - background: #EDF2F7 - padding-inline: .75em - border-radius: .375em - min-height: 2rem - min-width: 2rem - box-sizing: border-box - - .customize-more-label - width: 0 - overflow: hidden - transition: width .25s - white-space: nowrap - --natural-width: auto - - measured-content - padding-right: .5em - - &:hover, &:focus - // Expand the label to its natural width. If the JS ran to tell us - // what it is in px, we can use that for a smooth transition. If not, - // okay, we just pop out to `auto`, which CSS can't make smooth. - .customize-more-label - width: var(--natural-width) - - outfit-viewer - display: block - position: relative - width: 300px - height: 300px - border: 1px solid $module-border-color - border-radius: 1em - overflow: hidden - - // There's no useful text in here, but double-clicking the play/pause - // button can cause a weird selection state. Disable text selection. - user-select: none - -webkit-user-select: none - - outfit-layer - display: block - position: absolute - inset: 0 - - // We disable pointer-events most importantly for the iframes, which - // will ignore our `cursor: wait` and show a plain cursor for the - // inside of its own document. But also, the context menus for these - // elements are kinda actively misleading, too! - pointer-events: none - - img, iframe - width: 100% - height: 100% - - .loading-indicator - position: absolute - z-index: 1000 - bottom: 0px - right: 4px - padding: 8px - background: radial-gradient(circle closest-side, white 45%, #ffffff00) - - opacity: 0 - transition: opacity .5s - - .play-pause-button - position: absolute - z-index: 1001 - left: 8px - bottom: 8px - display: none - align-items: center - justify-content: center - color: white - background: rgba(0, 0, 0, 0.64) - width: 2.5em - height: 2.5em - border-radius: 100% - border: 2px solid transparent - transition: all .25s - - .playing-label, .paused-label - display: none - width: 1em - height: 1em - - .play-pause-toggle - // Visually hidden - clip: rect(0 0 0 0) - clip-path: inset(50%) - height: 1px - overflow: hidden - position: absolute - white-space: nowrap - width: 1px - - &:checked ~ .playing-label - display: block - - &:not(:checked) ~ .paused-label - display: block - - &:hover, &:has(.play-pause-toggle:focus) - border: 2px solid $module-border-color - background: $module-bg-color - color: $text-color - - &:has(.play-pause-toggle:active) - transform: translateY(2px) - - &:has(outfit-layer:state(has-animations)) - .play-pause-button - display: flex - - .error-indicator - font-size: 85% - color: $error-color - margin-top: .25em - margin-bottom: .5em - display: none - - // When loading, fade in the loading spinner after a brief delay. We are - // loading when the is busy, or when at least one layer - // is loading. - // - // We only apply the delay here, not on the base styles, because fading - // *out* on load should be instant. We also wait for the outfit-viewer to - // execute a `setTimeout(0)`, to make sure we always *start* in the - // non-loading state. This is because it's sometimes possible for the page to - // start with the web component already in `state(loading)`, and we need to - // make sure we *start* in *non-loading* state for the transition delay to - // happen. (This can happen when you Turbo-navigate between multiple items.) - #item-preview[busy] outfit-viewer, outfit-viewer:has(outfit-layer:state(loading)) - cursor: wait - - &:state(after-first-frame) - .loading-indicator - opacity: 1 - transition-delay: 2s - - #item-preview:has(outfit-layer:state(error)) - outfit-viewer - border: 2px solid red - .error-indicator - display: block - - species-color-picker - .error-icon - cursor: help - margin-right: .25em - - form[data-is-valid="false"] - select - border-color: $error-border-color - color: $error-color - - // If JS is enabled, but auto-loading isn't ready yet (script loading or - // failed?), hide the submit button for .75sec, to give it time to load. - @media (scripting: enabled) - input[type=submit] - position: absolute - margin-left: .5em - opacity: 0 - animation: fade-in .25s forwards - animation-delay: .75s - - // Once the auto-loading behavior is ready, remove the submit button. - &:state(auto-loading) - input[type=submit] - display: none - - species-face-picker - display: block - position: relative - margin-top: -10px - - species-face-picker-options - display: flex - justify-content: center - flex-wrap: wrap - isolation: isolate // avoid z-index conflicts between pets and noscript - overflow: auto - max-height: 200px // 4 rows of 50px images, and padding will offer a hint of below - padding: 10px // leave enough room for the zoomed-in selected face - - img - width: 54px - height: 54px - transition: all 0.2s - - // Calm down the default color, just a smidge! There's a lot of color - // on this page already, y'know? - opacity: .9 - filter: saturate(90%) - - label - display: flex - overflow: hidden - transition: all 0.2s - position: relative - line-height: 1 - - // NOTE: The box-shadows here were copy-pasted from Impress 2020, which uses - // Chakra UI's styling system to generate them! (The colors are from their - // color palette, too.) - &:has(input:checked) - border-radius: 6px - z-index: 1 - background: #9AE6B4 - box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1),0 10px 10px -5px rgba(0, 0, 0, 0.04), #2F855A 0 0 2px 2px - transform: scale(1.1) - - &:has(input:focus) - background: #BEE3F8 - box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1),0 10px 10px -5px rgba(0, 0, 0, 0.04), #4299e1 0 0 0 3px - transform: scale(1.2) - - input[type=radio] - position: absolute - left: -10000px - top: auto - width: 1px - height: 1px - overflow: hidden - - &:checked + img - opacity: 1 - filter: saturate(110%) - - &:disabled + img - opacity: .6 - filter: saturate(0%) - - label:has(input[type=radio]:disabled) - cursor: not-allowed - - noscript - position: absolute - inset: 0 - padding: 1em - background: rgba(white, .8) - z-index: 1 - cursor: auto - - display: flex - align-items: center - justify-content: center - text-align: center - - &:has(species-face-picker-options[inert]) - cursor: wait - - .item-preview-meta-info - display: grid - grid-template-columns: 1fr auto - gap: .5em - align-items: center - - .item-zones-info - h3 - display: inline - font: inherit - font-weight: bold - &:after - content: ": " - - ul - list-style-type: none - display: inline - - li - display: inline - &:not(:last-of-type):after - content: ", " - - .no-zones - font-style: italic - opacity: .85 - - .zone-species-info - font-style: italic - text-decoration: underline dotted - - // Many of these styles copied from Impress 2020 and its Chakra UI styles! - .item-html5-info - display: flex - align-items: center - border: 1px solid - border-radius: .375em - padding: 4px 8px - min-height: 30px - box-sizing: border-box - box-shadow: rgba(0, 0, 0, 0.1) 0px 4px 6px -1px, rgba(0, 0, 0, 0.06) 0px 2px 4px -1px - - &[data-status=converted] - background: $module-bg-color - color: $text-color - - svg:nth-of-type(2) - margin-right: -4px // spacing hacks! - - &[data-status=unconverted] - background: $warning-bg-color - color: #975A16 - gap: .25em // spacing hacks! - - svg:first-of-type - width: 12px - height: 12px - - svg:nth-of-type(2) - width: 20px - height: 20px - - #item-preview - display: flex - flex-direction: column - gap: .75em - - @media (min-width: 700px) - display: grid - grid-template-areas: "viewer faces" "picker meta" - gap: .5em - - .preview-area - grid-area: viewer - outfit-viewer - width: 380px - height: 380px - - species-color-picker - grid-area: picker - - species-face-picker - grid-area: faces - species-face-picker-options - max-height: 380px - - .item-preview-meta-info - grid-area: meta - -@keyframes fade-in - from - opacity: 0 - to - opacity: 1 diff --git a/app/assets/stylesheets/items/index.sass b/app/assets/stylesheets/items/index.sass new file mode 100644 index 00000000..97301bcc --- /dev/null +++ b/app/assets/stylesheets/items/index.sass @@ -0,0 +1,23 @@ +=main_unit + float: left + width: 49% + h2 + font-size: 125% + +form + margin-bottom: 2em + +#search-info + +main_unit + padding-right: 1% + dl + text-align: left + dd + margin-bottom: 1em + +#species-search-links + +main_unit + padding-left: 1% + img + height: 80px + width: 80px diff --git a/app/assets/stylesheets/items/show.sass b/app/assets/stylesheets/items/show.sass new file mode 100644 index 00000000..7d5a4105 --- /dev/null +++ b/app/assets/stylesheets/items/show.sass @@ -0,0 +1,400 @@ +@import "../partials/clean/constants" +@import "../partials/clean/mixins" +@import "../partials/item_header" + +#container + width: 900px // A bit more generous to the preview area! + +.item-header + +item-header + +#item-contributors + +subtle-banner + clear: both + margin: + bottom: 0 + top: 2em + + header + display: inline + font-weight: bold + margin-right: .25em + + footer + display: inline + + ul + display: inline + list-style: none + + li + display: inline + + &::after + content: ", " + + &:last-child::after + content: "." + +.nc-icon + height: 16px + width: 16px + +.preview-area + margin: 0 auto + position: relative + + .customize-more + position: absolute + top: 1em + right: 1em + + display: flex + align-items: center + text-decoration: none + + background: #EDF2F7 + padding-inline: .75em + border-radius: .375em + min-height: 2rem + min-width: 2rem + box-sizing: border-box + + .customize-more-label + width: 0 + overflow: hidden + transition: width .25s + white-space: nowrap + --natural-width: auto + + measured-content + padding-right: .5em + + &:hover, &:focus + // Expand the label to its natural width. If the JS ran to tell us + // what it is in px, we can use that for a smooth transition. If not, + // okay, we just pop out to `auto`, which CSS can't make smooth. + .customize-more-label + width: var(--natural-width) + +outfit-viewer + display: block + position: relative + width: 300px + height: 300px + border: 1px solid $module-border-color + border-radius: 1em + overflow: hidden + + // There's no useful text in here, but double-clicking the play/pause + // button can cause a weird selection state. Disable text selection. + user-select: none + -webkit-user-select: none + + outfit-layer + display: block + position: absolute + inset: 0 + + // We disable pointer-events most importantly for the iframes, which + // will ignore our `cursor: wait` and show a plain cursor for the + // inside of its own document. But also, the context menus for these + // elements are kinda actively misleading, too! + pointer-events: none + + img, iframe + width: 100% + height: 100% + + .loading-indicator + position: absolute + z-index: 1000 + bottom: 0px + right: 4px + padding: 8px + background: radial-gradient(circle closest-side, white 45%, #ffffff00) + + opacity: 0 + transition: opacity .5s + + .play-pause-button + position: absolute + z-index: 1001 + left: 8px + bottom: 8px + display: none + align-items: center + justify-content: center + color: white + background: rgba(0, 0, 0, 0.64) + width: 2.5em + height: 2.5em + border-radius: 100% + border: 2px solid transparent + transition: all .25s + + .playing-label, .paused-label + display: none + width: 1em + height: 1em + + .play-pause-toggle + // Visually hidden + clip: rect(0 0 0 0) + clip-path: inset(50%) + height: 1px + overflow: hidden + position: absolute + white-space: nowrap + width: 1px + + &:checked ~ .playing-label + display: block + + &:not(:checked) ~ .paused-label + display: block + + &:hover, &:has(.play-pause-toggle:focus) + border: 2px solid $module-border-color + background: $module-bg-color + color: $text-color + + &:has(.play-pause-toggle:active) + transform: translateY(2px) + + &:has(outfit-layer:state(has-animations)) + .play-pause-button + display: flex + +.error-indicator + font-size: 85% + color: $error-color + margin-top: .25em + margin-bottom: .5em + display: none + +// When loading, fade in the loading spinner after a brief delay. We are +// loading when the is busy, or when at least one layer +// is loading. +// +// We only apply the delay here, not on the base styles, because fading +// *out* on load should be instant. We also wait for the outfit-viewer to +// execute a `setTimeout(0)`, to make sure we always *start* in the +// non-loading state. This is because it's sometimes possible for the page to +// start with the web component already in `state(loading)`, and we need to +// make sure we *start* in *non-loading* state for the transition delay to +// happen. (This can happen when you Turbo-navigate between multiple items.) +#item-preview[busy] outfit-viewer, outfit-viewer:has(outfit-layer:state(loading)) + cursor: wait + + &:state(after-first-frame) + .loading-indicator + opacity: 1 + transition-delay: 2s + +#item-preview:has(outfit-layer:state(error)) + outfit-viewer + border: 2px solid red + .error-indicator + display: block + +species-color-picker + .error-icon + cursor: help + margin-right: .25em + + form[data-is-valid="false"] + select + border-color: $error-border-color + color: $error-color + + // If JS is enabled, but auto-loading isn't ready yet (script loading or + // failed?), hide the submit button for .75sec, to give it time to load. + @media (scripting: enabled) + input[type=submit] + position: absolute + margin-left: .5em + opacity: 0 + animation: fade-in .25s forwards + animation-delay: .75s + + // Once the auto-loading behavior is ready, remove the submit button. + &:state(auto-loading) + input[type=submit] + display: none + +species-face-picker + display: block + position: relative + margin-top: -10px + + species-face-picker-options + display: flex + justify-content: center + flex-wrap: wrap + isolation: isolate // avoid z-index conflicts between pets and noscript + overflow: auto + max-height: 200px // 4 rows of 50px images, and padding will offer a hint of below + padding: 10px // leave enough room for the zoomed-in selected face + + img + width: 54px + height: 54px + transition: all 0.2s + + // Calm down the default color, just a smidge! There's a lot of color + // on this page already, y'know? + opacity: .9 + filter: saturate(90%) + + label + display: flex + overflow: hidden + transition: all 0.2s + position: relative + line-height: 1 + + // NOTE: The box-shadows here were copy-pasted from Impress 2020, which uses + // Chakra UI's styling system to generate them! (The colors are from their + // color palette, too.) + &:has(input:checked) + border-radius: 6px + z-index: 1 + background: #9AE6B4 + box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1),0 10px 10px -5px rgba(0, 0, 0, 0.04), #2F855A 0 0 2px 2px + transform: scale(1.1) + + &:has(input:focus) + background: #BEE3F8 + box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1),0 10px 10px -5px rgba(0, 0, 0, 0.04), #4299e1 0 0 0 3px + transform: scale(1.2) + + input[type=radio] + position: absolute + left: -10000px + top: auto + width: 1px + height: 1px + overflow: hidden + + &:checked + img + opacity: 1 + filter: saturate(110%) + + &:disabled + img + opacity: .6 + filter: saturate(0%) + + label:has(input[type=radio]:disabled) + cursor: not-allowed + + noscript + position: absolute + inset: 0 + padding: 1em + background: rgba(white, .8) + z-index: 1 + cursor: auto + + display: flex + align-items: center + justify-content: center + text-align: center + + &:has(species-face-picker-options[inert]) + cursor: wait + +.item-preview-meta-info + display: grid + grid-template-columns: 1fr auto + gap: .5em + align-items: center + +.item-zones-info + h3 + display: inline + font: inherit + font-weight: bold + &:after + content: ": " + + ul + list-style-type: none + display: inline + + li + display: inline + &:not(:last-of-type):after + content: ", " + + .no-zones + font-style: italic + opacity: .85 + + .zone-species-info + font-style: italic + text-decoration: underline dotted + +// Many of these styles copied from Impress 2020 and its Chakra UI styles! +.item-html5-info + display: flex + align-items: center + border: 1px solid + border-radius: .375em + padding: 4px 8px + min-height: 30px + box-sizing: border-box + box-shadow: rgba(0, 0, 0, 0.1) 0px 4px 6px -1px, rgba(0, 0, 0, 0.06) 0px 2px 4px -1px + + &[data-status=converted] + background: $module-bg-color + color: $text-color + + svg:nth-of-type(2) + margin-right: -4px // spacing hacks! + + &[data-status=unconverted] + background: $warning-bg-color + color: #975A16 + gap: .25em // spacing hacks! + + svg:first-of-type + width: 12px + height: 12px + + svg:nth-of-type(2) + width: 20px + height: 20px + +#item-preview + display: flex + flex-direction: column + gap: .75em + + @media (min-width: 700px) + display: grid + grid-template-areas: "viewer faces" "picker meta" + gap: .5em + + .preview-area + grid-area: viewer + outfit-viewer + width: 380px + height: 380px + + species-color-picker + grid-area: picker + + species-face-picker + grid-area: faces + species-face-picker-options + max-height: 380px + + .item-preview-meta-info + grid-area: meta + +@keyframes fade-in + from + opacity: 0 + to + opacity: 1 diff --git a/app/assets/stylesheets/layouts/items.sass b/app/assets/stylesheets/layouts/items.sass new file mode 100644 index 00000000..153c77e3 --- /dev/null +++ b/app/assets/stylesheets/layouts/items.sass @@ -0,0 +1,28 @@ +@import "partials/campaign-progress" + +body + +campaign-progress + text-align: center + +.item-search-form + display: flex + gap: .5em + justify-content: center + + input[type=text] + font-size: 125% + width: 15em + flex: 0 1 auto + +h1 + margin-bottom: 1em + img + height: 80px + margin-bottom: -0.5em + width: 80px + a + text-decoration: none + span + text-decoration: underline + &:hover span + text-decoration: none diff --git a/app/views/item_trades/index.html.haml b/app/views/item_trades/index.html.haml index 29d474af..9c7ed59c 100644 --- a/app/views/item_trades/index.html.haml +++ b/app/views/item_trades/index.html.haml @@ -34,3 +34,7 @@ class: "not-in-a-list" - else %p= t(".no_trades_yet") + +- content_for :stylesheets do + = page_stylesheet_link_tag "layouts/items" + = page_stylesheet_link_tag "item_trades/index" diff --git a/app/views/items/index.html.haml b/app/views/items/index.html.haml index 818c4f66..6021169c 100644 --- a/app/views/items/index.html.haml +++ b/app/views/items/index.html.haml @@ -37,3 +37,6 @@ %h2= t '.species_search.header' = standard_species_search_links +- content_for :stylesheets do + = page_stylesheet_link_tag "layouts/items" + = page_stylesheet_link_tag "items/index" diff --git a/app/views/items/needed.html.haml b/app/views/items/needed.html.haml index ae5516af..c01fba78 100644 --- a/app/views/items/needed.html.haml +++ b/app/views/items/needed.html.haml @@ -14,3 +14,6 @@ = link_to 'What do I own?', 'https://www.neopets.com/closet.phtml', :class => 'button', :target => '_blank' = render @items + +- content_for :stylesheets do + = page_stylesheet_link_tag "layouts/items" diff --git a/app/views/items/show.html.haml b/app/views/items/show.html.haml index a629f9e7..ea34fa51 100644 --- a/app/views/items/show.html.haml +++ b/app/views/items/show.html.haml @@ -122,6 +122,8 @@ - content_for :stylesheets do = stylesheet_link_tag "application/hanger-spinner" + = page_stylesheet_link_tag "layouts/items" + = page_stylesheet_link_tag "items/show" - content_for :javascripts do = javascript_include_tag "lib/idiomorph", async: true