Add site-wide ArtFight2025 tag browsing page

- Created new /browse/tag/ArtFight2025 endpoint
- Added browse controller for site-wide content display
- Fixed tag slug case-sensitivity for reliable content retrieval
- Implemented a design that highlights the ArtFight2025 tag
- Show all content tags on each card for context
- Prioritize character display at the top of search results

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Andrew Brown 2025-06-23 23:28:50 -07:00
parent 78b86cfaae
commit 574cfcd6ff
5 changed files with 406 additions and 25 deletions

View File

@ -0,0 +1,150 @@
class BrowseController < ApplicationController
# This controller handles browsing global content across the site
def tag
@tag_slug = params[:tag_slug]
# Debug logging
Rails.logger.info "BrowseController#tag - Looking for content with tag slug: #{@tag_slug}"
# For now, only allow ArtFight2025 tag to be browsed (case insensitive)
unless @tag_slug.downcase == 'artfight2025'
redirect_to root_path, notice: 'This tag is not available for browsing.'
return
end
@tag = 'ArtFight2025'
@tag_slug = 'artfight2025' # Use the correctly slugified version
@tagged_content = []
# Directly check database for any pages with this tag
tag_exists = PageTag.exists?(slug: @tag_slug)
Rails.logger.info "Tag exists in PageTag table? #{tag_exists}"
if tag_exists
sample_tag = PageTag.find_by(slug: @tag_slug)
Rails.logger.info "Sample tag: #{sample_tag.inspect}"
end
# Go through each content type and find public items with this tag
Rails.application.config.content_types[:all].each do |content_type|
# Try a different query approach - first find page IDs with this tag
tag_page_ids = PageTag.where(page_type: content_type.name, slug: @tag_slug).pluck(:page_id)
if tag_page_ids.any?
Rails.logger.info "Found #{tag_page_ids.count} #{content_type.name} page IDs with tag: #{tag_page_ids}"
content_pages = content_type.where(id: tag_page_ids).where(privacy: 'public').order(:name)
else
Rails.logger.info "No #{content_type.name} pages found with tag"
content_pages = content_type.none
end
@tagged_content << {
type: content_type.name,
icon: content_type.icon,
color: content_type.color,
content: content_pages
} if content_pages.any?
end
# Add documents separately since they don't use the common content type structure
document_tag_page_ids = PageTag.where(page_type: 'Document', slug: @tag_slug).pluck(:page_id)
if document_tag_page_ids.any?
Rails.logger.info "Found #{document_tag_page_ids.count} Document page IDs with tag: #{document_tag_page_ids}"
documents = Document.where(id: document_tag_page_ids).where(privacy: 'public').order(:title)
else
Rails.logger.info "No Document pages found with tag"
documents = Document.none
end # Documents use 'title' instead of 'name'
@tagged_content << {
type: 'Document',
icon: 'description',
color: 'blue',
content: documents
} if documents.any?
# Add timelines separately since they don't use the common content type structure
timeline_tag_page_ids = PageTag.where(page_type: 'Timeline', slug: @tag_slug).pluck(:page_id)
if timeline_tag_page_ids.any?
Rails.logger.info "Found #{timeline_tag_page_ids.count} Timeline page IDs with tag: #{timeline_tag_page_ids}"
timelines = Timeline.where(id: timeline_tag_page_ids).where(privacy: 'public').order(:name)
else
Rails.logger.info "No Timeline pages found with tag"
timelines = Timeline.none
end
@tagged_content << {
type: 'Timeline',
icon: 'timeline',
color: 'blue',
content: timelines
} if timelines.any?
# Get images for content cards from all users
content_ids_by_type = {}
user_ids = []
@tagged_content.each do |content_group|
content_group[:content].each do |content|
content_type = content.class.name
content_ids_by_type[content_type] ||= []
content_ids_by_type[content_type] << content.id
user_ids << content.user_id
end
end
# Get unique user IDs
user_ids.uniq!
# Get all relevant images
@random_image_pool_cache = {}
if content_ids_by_type.any?
content_ids_by_type.each do |content_type, ids|
images = ImageUpload.where(content_type: content_type, content_id: ids)
images.each do |image|
key = [image.content_type, image.content_id]
@random_image_pool_cache[key] ||= []
@random_image_pool_cache[key] << image
end
end
end
# Initialize basil commissions if there are any content items
if content_ids_by_type.any?
entity_types = []
entity_ids = []
content_ids_by_type.each do |content_type, ids|
entity_type = content_type.downcase.pluralize
entity_types.concat([entity_type] * ids.length)
entity_ids.concat(ids)
end
@saved_basil_commissions = BasilCommission.where(
entity_type: entity_types,
entity_id: entity_ids
).where.not(saved_at: nil)
.group_by { |commission| [commission.entity_type, commission.entity_id] }
end
# Set a default accent color for the page
@accent_color = 'purple'
# Get usernames for display with content
@users_cache = User.where(id: user_ids).index_by(&:id)
# Sort content types so Characters always appear first
@tagged_content = @tagged_content.sort_by do |content_group|
if content_group[:type] == 'Character'
# Characters first
"0_#{content_group[:type]}"
else
# Everything else alphabetically
"1_#{content_group[:type]}"
end
end
@sidenav_expansion = 'community'
end
end

View File

@ -6,55 +6,80 @@ class PageTagService < Service
def self.suggested_tags_for(class_name)
case class_name
when Building.name
["School", "Business"]
["School", "Business", "Residential", "Religious", "Government", "Military", "Historical", "Abandoned", "Underground", "Magical", "Headquarters", "Landmark", "WIP", "Complete", "Stub",
"Ancient", "Modern", "Futuristic", "Secret", "Well-known", "Mysterious", "Imposing", "Central to plot", "Background detail", "Needs expansion"]
when Character.name
["Main character", "Side character", "Background character"]
["Main character", "Side character", "Background character", "Villain", "Hero", "Mentor", "Love interest", "Ally", "Rival", "Family member", "Historical figure", "Child", "Elder", "WIP", "Complete", "Stub",
"Protagonist", "Antagonist", "Supporting", "Main plot", "Subplot", "Origin story", "High priority", "Needs development", "Tragic", "Comedic"]
when Condition.name
["Disease", "Blessing", "Curse"]
["Disease", "Blessing", "Curse", "Magical affliction", "Mental condition", "Physical disability", "Temporary", "Permanent", "Contagious", "Hereditary", "Supernatural", "Rare", "Common", "WIP", "Complete", "Stub",
"Unknown", "Legendary", "Tragic", "Beneficial", "Plot device", "Background detail", "Mysterious", "Needs research", "Science based", "Mythology inspired"]
when Country.name
["Kingdom", "Country", "Region"]
["Kingdom", "Country", "Region", "Empire", "Republic", "City-state", "Colony", "Island nation", "Confederation", "Disputed territory", "Fallen kingdom", "Hidden realm", "Tribal lands", "WIP", "Complete", "Stub",
"Ancient", "Modern", "Developing", "Major power", "Minor nation", "Well-known", "Isolated", "Political entity", "Cultural focus", "Based on history"]
when Creature.name
["Domesticated", "Wild", "Humanoid"]
["Domesticated", "Wild", "Humanoid", "Mythical", "Magical", "Predator", "Aquatic", "Flying", "Subterranean", "Extinct", "Intelligent", "Hybrid", "Shapeshifter", "WIP", "Complete", "Stub",
"Common", "Rare", "Legendary", "Keystone species", "Background fauna", "Terrifying", "Majestic", "Cute", "Mythology inspired", "Needs illustration"]
when Deity.name
["Good", "Evil", "Neutral"]
["Good", "Evil", "Neutral", "Creator", "Destroyer", "Nature deity", "War deity", "Love deity", "Death deity", "Trickster", "Ancient", "Forgotten", "Patron", "WIP", "Complete", "Stub",
"Worshipped", "Obscure", "Legendary", "Major deity", "Minor deity", "Metaphysical", "Mythology inspired", "Central to plot", "Cultural element", "Needs expansion"]
when Flora.name
["Floral", "Weed", "Tree", "Bush"]
["Floral", "Weed", "Tree", "Bush", "Magical", "Medicinal", "Poisonous", "Edible", "Rare", "Cultivated", "Aquatic", "Carnivorous", "Luminescent", "Extinct", "WIP", "Complete", "Stub",
"Common", "Legendary", "Plot device", "Background detail", "Physical world", "Needs illustration", "Science based", "Mysterious", "Prehistoric", "Modern"]
when Government.name
["Republic", "Democracy", "Monarchy", "Dictatorship"]
["Republic", "Democracy", "Monarchy", "Dictatorship", "Oligarchy", "Theocracy", "Feudal", "Tribal", "Anarchy", "Colonial", "Magocracy", "Puppet state", "Technocracy", "Confederation", "WIP", "Complete", "Stub",
"Stable", "Unstable", "Corrupt", "Benevolent", "Social structure", "Political entity", "Based on history", "Central to plot", "Background detail", "Needs expansion"]
when Group.name
["Good", "Evil", "Secret"]
["Good", "Evil", "Secret", "Military", "Religious", "Political", "Criminal", "Mercenary", "Merchant", "Nomadic", "Resistance", "Magical", "Ancient", "WIP", "Complete", "Stub",
"Well-known", "Obscure", "Powerful", "Minor", "Main plot", "Subplot", "Social structure", "Cultural element", "Needs development", "Based on history"]
when Item.name
["Weapon", "Armor", "Artifact", "Relic"]
["Weapon", "Armor", "Artifact", "Relic", "Magical", "Cursed", "Legendary", "Tool", "Jewelry", "Clothing", "Consumable", "Book", "Technology", "Ritual object", "WIP", "Complete", "Stub",
"Common", "Rare", "Unique", "Plot device", "MacGuffin", "Ancient", "Modern", "Futuristic", "Needs illustration", "Mythology inspired"]
when Job.name
["Military", "Government", "Private sector"]
["Military", "Government", "Private sector", "Artisan", "Merchant", "Criminal", "Religious", "Magical", "Academic", "Entertainment", "Nomadic", "Seasonal", "Hereditary", "WIP", "Complete", "Stub",
"Common", "Rare", "Prestigious", "Lowly", "Social structure", "Economic system", "Cultural element", "Based on history", "Background detail", "Needs expansion"]
when Landmark.name
["Cave", "Mountain", "Temple", "Ruins"]
["Cave", "Mountain", "Temple", "Ruins", "Waterfall", "Forest", "Monument", "Battlefield", "Sacred site", "Natural wonder", "Magical location", "Bridge", "Graveyard", "Portal", "WIP", "Complete", "Stub",
"Famous", "Hidden", "Ancient", "Modern", "Physical world", "Plot location", "Mysterious", "Majestic", "Needs illustration", "Based on geography"]
when Language.name
["Modern", "Ancient"]
["Modern", "Ancient", "Dead", "Sacred", "Trade", "Secret", "Regional", "Magical", "Written only", "Spoken only", "Pidgin", "Artificial", "WIP", "Complete", "Stub",
"Common", "Rare", "Scholarly", "Cultural element", "Based on linguistics", "Needs development", "Conlang", "Background detail", "Low priority", "Needs examples"]
when Location.name
["Town", "City", "River", "Mountains", "Desert"]
["Town", "City", "River", "Mountains", "Desert", "Forest", "Ocean", "Lake", "Island", "Swamp", "Tundra", "Valley", "Plateau", "Jungle", "Wasteland", "WIP", "Complete", "Stub",
"Inhabited", "Uninhabited", "Explored", "Unexplored", "Dangerous", "Safe", "Physical world", "Plot location", "Needs map", "Based on geography"]
when Magic.name
["Spell", "Ability", "Superpower"]
["Spell", "Ability", "Superpower", "Ritual", "Enchantment", "Elemental", "Necromancy", "Divination", "Illusion", "Healing", "Summoning", "Forbidden", "Innate", "WIP", "Complete", "Stub",
"Common", "Rare", "Legendary", "Powerful", "Weak", "Metaphysical", "System-defining", "Plot device", "High priority", "Needs rules"]
when Planet.name
["Habitable", "Inhabitable"]
["Habitable", "Inhabitable", "Earth-like", "Gas giant", "Desert world", "Ocean world", "Ice world", "Jungle world", "Artificial", "Moon", "Dying", "Ancient", "WIP", "Complete", "Stub",
"Explored", "Unexplored", "Homeworld", "Colony", "Physical world", "Science based", "Needs map", "Central setting", "Background detail", "Needs development"]
when Race.name
["Race", "Species", "Humanoid"]
["Race", "Species", "Humanoid", "Ancient", "Magical", "Hybrid", "Extinct", "Subterranean", "Aquatic", "Aerial", "Nomadic", "Tribal", "Technological", "WIP", "Complete", "Stub",
"Common", "Rare", "Dominant", "Minority", "Cultural focus", "Social structure", "Needs illustration", "Main characters", "Background people", "High priority"]
when Religion.name
["Modern", "Ancient", "Monotheistic", "Polytheistic"]
["Modern", "Ancient", "Monotheistic", "Polytheistic", "State religion", "Cult", "Animistic", "Shamanic", "Ancestor worship", "Nature worship", "Mystical", "Heretical", "Secretive", "Widespread", "WIP", "Complete", "Stub",
"Dominant", "Minority", "Cultural element", "Metaphysical", "Based on history", "Mythology inspired", "Plot relevant", "Background detail", "Needs expansion"]
when Scene.name
["Atmospheric", "Exposition", "Transition"]
["Atmospheric", "Exposition", "Transition", "Action", "Dialogue", "Flashback", "Dream sequence", "Climactic", "Introduction", "Resolution", "Worldbuilding", "Character development", "Plot twist", "WIP", "Complete", "Stub",
"Main plot", "Subplot", "High tension", "Low tension", "Mysterious", "Romantic", "Tragic", "Comedic", "High priority", "Needs revision"]
when Technology.name
["Military", "Consumer", "Theoretical"]
["Military", "Consumer", "Theoretical", "Ancient", "Futuristic", "Magical", "Medical", "Transportation", "Communication", "Energy", "Forbidden", "Experimental", "Everyday", "WIP", "Complete", "Stub",
"Common", "Rare", "Revolutionary", "Obsolete", "Science based", "Plot device", "Physical world", "Needs illustration", "Background detail", "High priority"]
when Town.name
["Village", "Town", "City", "Metropolis"]
["Village", "Town", "City", "Metropolis", "Hamlet", "Outpost", "Port", "Mining settlement", "Trading hub", "Fortress", "Religious center", "Abandoned", "Hidden", "Capital", "WIP", "Complete", "Stub",
"Prosperous", "Poor", "Ancient", "Modern", "Plot location", "Character hometown", "Cultural focus", "Needs map", "Based on history", "Background detail"]
when Tradition.name
["Holiday", "Practice"]
["Holiday", "Practice", "Festival", "Ceremony", "Ritual", "Coming of age", "Seasonal", "Religious", "Cultural", "Family", "Military", "Ancient", "WIP", "Complete", "Stub",
"Common", "Rare", "Widespread", "Regional", "Cultural element", "Social structure", "Based on history", "Mythology inspired", "Background detail", "Needs expansion"]
when Universe.name
["Favorite"]
["Favorite", "Science fiction", "Fantasy", "Historical", "Post-apocalyptic", "Dystopian", "Utopian", "Alternate history", "Urban fantasy", "Steampunk", "Magical realism", "WIP", "Complete", "Stub",
"Main project", "Side project", "Collaborative", "Personal", "High priority", "Low priority", "Needs worldbuilding", "Core concept", "Experimental", "Based on literature"]
when Vehicle.name
["Automobile", "Train", "Plane", "Spaceship"]
["Automobile", "Train", "Plane", "Spaceship", "Ship", "Submarine", "Magical", "Animal-drawn", "Military", "Civilian", "Ancient", "Futuristic", "Unique", "Experimental", "WIP", "Complete", "Stub",
"Common", "Rare", "Legendary", "Personal", "Public", "Plot device", "Needs illustration", "Science based", "Background detail", "Technology focus"]
when Document.name
['Idea', 'Draft', 'In Progress', 'Done']
['Idea', 'Draft', 'In Progress', 'Done', 'Historical', 'Religious', 'Legal', 'Personal', 'Scientific', 'Map', 'Journal', 'Letter', 'Prophecy', 'Secret', 'WIP', 'Complete', 'Stub',
'Plot relevant', 'Background lore', 'Ancient', 'Modern', 'Cultural element', 'Needs revision', 'High priority', 'Open for collaboration', 'Based on literature', 'Worldbuilding']
else
[]
end

View File

@ -0,0 +1,200 @@
<div class="row" style="margin-bottom: 0;">
<div class="col s12">
<%
# Generate a consistent pattern for the background based on tag name
pattern_seed = 5 # Fixed for ArtFight tag
pattern_url = asset_path("card-headers/patterns/pattern#{pattern_seed}.png")
# Calculate total items for the stats display
total_items = @tagged_content.sum { |group| group[:content].count }
content_types_count = @tagged_content.size
creators_count = @tagged_content.flat_map { |group| group[:content].map(&:user_id) }.uniq.count
%>
<div class="card z-depth-1" style="border-radius: 4px; overflow: hidden;">
<!-- Header with hero image background -->
<div class="<%= @accent_color %>" style="position: relative; background: url('<%= pattern_url %>') center center; background-size: cover; height: 180px;">
<!-- Dark overlay for better text contrast -->
<div style="position: absolute; top: 0; left: 0; right: 0; bottom: 0; background-color: rgba(0,0,0,0.3);"></div>
<!-- Tag floating badge -->
<div style="position: absolute; bottom: -24px; left: 24px; z-index: 2;">
<div style="display: inline-block; background-color: white; padding: 14px 22px; border-radius: 4px; box-shadow: 0 2px 10px 0 rgba(0,0,0,0.2);">
<div style="display: flex; align-items: center;">
<i class="material-icons <%= @accent_color %>-text" style="font-size: 28px; margin-right: 12px;"><%= PageTag.icon %></i>
<span style="font-size: 28px; font-weight: 500;" class="<%= @accent_color %>-text"><%= @tag %></span>
</div>
</div>
</div>
<!-- Art Fight 2025 header info -->
<div style="position: absolute; top: 20px; left: 24px; z-index: 1;">
<div>
<span style="font-size: 24px; font-weight: 500; text-shadow: 0 1px 3px rgba(0,0,0,0.3); line-height: 1.2; display: block; color: white;">
Art Fight 2025
</span>
<span style="font-size: 16px; text-shadow: 0 1px 2px rgba(0,0,0,0.3); display: block; color: white; margin-top: 4px;">
Discover all public content tagged with ArtFight2025 across Notebook.ai
</span>
</div>
</div>
</div>
<!-- Stats bar - clean and elegant -->
<div class="<%= @accent_color %> lighten-5" style="padding: 30px 24px 16px 24px;">
<div style="display: flex; flex-wrap: wrap; justify-content: space-between; align-items: center;">
<!-- Left section (for title) -->
<div style="margin-bottom: 10px;">
&nbsp;
</div>
<!-- Right section (for stats) -->
<div style="display: flex; flex-wrap: wrap; gap: 10px;">
<div class="chip z-depth-0 <%= @accent_color %> lighten-3" style="border: none; margin: 0;">
<i class="material-icons left"><%= PageTag.icon %></i>
<%= pluralize(total_items, 'item') %>
</div>
<div class="chip z-depth-0 <%= @accent_color %> lighten-3" style="border: none; margin: 0;">
<i class="material-icons left">category</i>
<%= pluralize(content_types_count, 'category') %>
</div>
<div class="chip z-depth-0 <%= @accent_color %> lighten-3" style="border: none; margin: 0;">
<i class="material-icons left">people</i>
<%= pluralize(creators_count, 'creator') %>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="col s12">
<% if @tagged_content.empty? %>
<div class="center-align" style="margin-top: 80px">
<h5 class="grey-text">No public content with this tag</h5>
<p class="grey-text">
No one has created any public content with the "<%= @tag %>" tag yet.
</p>
</div>
<% else %>
<% @tagged_content.each do |content_group| %>
<div class="section">
<h5 class="<%= content_group[:color] %>-text">
<i class="material-icons left"><%= content_group[:icon] %></i>
<%= content_group[:type].pluralize %> (<%= content_group[:content].count %>)
</h5>
<div class="row js-content-cards-list">
<% content_group[:content].each do |content| %>
<div class="col s12 m6 l4 js-content-card-container">
<div class="hoverable card sticky-action" style="margin-bottom: 2px">
<div class="card-image waves-effect waves-block waves-light">
<%
# Find image for this content
content_image = @random_image_pool_cache.fetch([content.class.name, content.id], [])
.sample
.try(:src, :medium)
if @saved_basil_commissions
content_image ||= @saved_basil_commissions.fetch([content.class.name, content.id], [])
.sample
.try(:image)
.try(:url)
end
content_image ||= asset_path("card-headers/#{content.class.name.downcase.pluralize}.jpg")
%>
<div class="activator" style="height: 265px; background: url('<%= content_image %>'); background-size: cover;"></div>
<span class="card-title js-content-name activator">
<div class="bordered-text">
<% content_name = content.respond_to?(:name) ? content.name : content.title %>
<%= ContentFormatterService.show(text: content_name.presence || 'Untitled', viewing_user: current_user) %>
</div>
<% if content.respond_to?(:page_tags) && content.page_tags.any? %>
<p class="tags-container">
<% content.page_tags.each do |tag| %>
<% if tag.tag == @tag %>
<span class="new badge <%= content_group[:color] %> left" data-badge-caption="<%= tag.tag %>"></span>
<% else %>
<span class="new badge grey left" data-badge-caption="<%= tag.tag %>"></span>
<% end %>
<% end %>
</p>
<% end %>
</span>
</div>
<div class="card-action">
<% if content.is_a?(Document) %>
<%= link_to 'View', content, class: 'blue-text text-lighten-1', target: '_blank' %>
<% else %>
<%= link_to 'View', content, class: 'blue-text text-lighten-1' %>
<% end %>
<% user = @users_cache[content.user_id] %>
<% if user %>
<%= link_to profile_by_username_path(username: user.username), class: 'right grey-text' do %>
<% if user.image_url %>
<img src="<%= user.image_url.html_safe %>" alt="<%= user.display_name %>" style="width: 20px; height: 20px; border-radius: 50%; vertical-align: middle; margin-right: 5px;">
<% else %>
<i class="material-icons tiny" style="vertical-align: middle;">person</i>
<% end %>
<span style="vertical-align: middle; font-size: 0.9rem;"><%= user.display_name %></span>
<% end %>
<% end %>
</div>
<div class="card-reveal">
<span class="card-title">
<%= content_name.presence || 'Untitled' %>
<i class="material-icons right">close</i>
</span>
<% content_description = content.try(:description) || content.try(:synopsis) %>
<% if content_description.present? %>
<p>
<%= sanitize ContentFormatterService.show(text: truncate(content_description, length: 420, escape: false), viewing_user: current_user) %>
</p>
<% end %>
<% if content.respond_to?(:page_tags) && content.page_tags.any? %>
<p class="tags-container">
<% content.page_tags.each do |tag| %>
<% if tag.tag == @tag %>
<span class="new badge <%= content_group[:color] %> left" data-badge-caption="<%= tag.tag %>"></span>
<% else %>
<span class="new badge grey left" data-badge-caption="<%= tag.tag %>"></span>
<% end %>
<% end %>
</p>
<div class="clearfix"></div>
<% end %>
<% user = @users_cache[content.user_id] %>
<% if user %>
<p>
<strong>Creator:</strong>
<%= link_to profile_by_username_path(username: user.username) do %>
<%= user.display_name %>
<% end %>
</p>
<% end %>
<p class="grey-text clearfix">
<%= content.created_at == content.updated_at ? 'created' : 'last updated' %>
<%= time_ago_in_words content.updated_at %>
ago
</p>
</div>
</div>
</div>
<% end %>
</div>
</div>
<% end %>
<% end %>
</div>
</div>

View File

@ -4,6 +4,7 @@
%>
<% if show_notice?(id: 24) && show_artfight_promo %>
<div class="clearfix"></div>
<div class="card-panel purple lighten-4 black-text show-when-focused" style="margin-top: 30px; border-left: 4px solid #9c27b0; position: relative; box-shadow: 0 2px 5px rgba(0,0,0,0.1); border-radius: 4px;">
<div style="display: flex; align-items: flex-start;">
<i class="material-icons purple-text" style="margin-right: 10px;">brush</i>

View File

@ -327,6 +327,11 @@ Rails.application.routes.draw do
mount RailsAdmin::Engine => '/admin', as: 'rails_admin'
end
# Browse global content routes
scope '/browse' do
get '/tag/:tag_slug', to: 'browse#tag', as: :browse_tag
end
# Fancy shmancy informative pages
scope '/worldbuilding' do
Rails.application.config.content_types[:all].each do |content_type|