New project home pages (incomplete)

This commit is contained in:
Ben Visness 2024-05-20 22:39:58 -05:00 committed by Ben Visness
parent 1a0be1912e
commit be7440acce
13 changed files with 152 additions and 306 deletions

View File

@ -7183,10 +7183,12 @@ code {
--notice-warn-color: #aa7d30; --notice-warn-color: #aa7d30;
--notice-failure-color: #b42222; --notice-failure-color: #b42222;
--spoiler-border: #aaa; --spoiler-border: #aaa;
--site-width: 80rem;
--site-width-narrow: 60rem;
} }
@media (prefers-color-scheme: dark) { @media (prefers-color-scheme: dark) {
:root { :root {
--background-color: #202020; --background-color: #2f2f2f;
--color: #eee; --color: #eee;
--link-color: #cc3b95; --link-color: #cc3b95;
--dim-color: #bbb; --dim-color: #bbb;
@ -7202,6 +7204,7 @@ code {
--main-background-color-transparent: rgba(#202020, 0); --main-background-color-transparent: rgba(#202020, 0);
--card-background: #494949; --card-background: #494949;
--card-background-hover: #333; --card-background-hover: #333;
--card-background-transparent: #242424D8;
--dim-background: #252525; --dim-background: #252525;
--dim-background-transparent: rgba(#252525, 0); --dim-background-transparent: rgba(#252525, 0);
--forum-thread-read-color: #777; --forum-thread-read-color: #777;
@ -7226,7 +7229,7 @@ br {
body { body {
background-color: var(--background-color); background-color: var(--background-color);
color: var(--color); color: var(--color);
font-family: "Fira Sans", sans-serif; font-family: "Inter", sans-serif;
min-height: 100vh; min-height: 100vh;
box-sizing: border-box; box-sizing: border-box;
} }
@ -7242,6 +7245,9 @@ a.external::after,
content: " 1"; content: " 1";
vertical-align: middle; vertical-align: middle;
} }
.link--normal {
--link-color: var(--color);
}
b, b,
strong { strong {
font-weight: 500; font-weight: 500;
@ -7374,7 +7380,10 @@ pre,
font-size: 0.65rem; font-size: 0.65rem;
} }
.mw-site { .mw-site {
max-width: 80rem; max-width: var(--site-width);
}
.mw-site-narrow {
max-width: var(--site-width-narrow);
} }
.mh-3 { .mh-3 {
max-height: var(--height-3); max-height: var(--height-3);
@ -7496,6 +7505,12 @@ pre,
.hide-if-empty:empty { .hide-if-empty:empty {
display: none !important; display: none !important;
} }
.fill-current {
fill: currentColor;
}
.rot-180 {
transform: rotate(180deg);
}
@media screen and (min-width: 35em) { @media screen and (min-width: 35em) {
.bi-avoid-ns { .bi-avoid-ns {
break-inside: avoid; break-inside: avoid;
@ -8475,18 +8490,13 @@ span.icon-rss::before {
.project-card { .project-card {
color: var(--fg-font-color); color: var(--fg-font-color);
background-color: var(--card-background); background-color: var(--card-background);
border-color: var(--project-card-border-color); }
transition: box-shadow 0.2s, background-color 0.2s; .project-card.project-card-black {
background-color: var(--card-background-transparent);
} }
.slideshow .project-card { .slideshow .project-card {
margin-top: 8px; margin-top: 8px;
} }
.project-card:hover {
background-color: var(--card-background-hover);
}
.project-card:hover > .title {
text-decoration: underline;
}
.project-card .image-container { .project-card .image-container {
--image-size: 8rem; --image-size: 8rem;
width: var(--image-size); width: var(--image-size);
@ -8498,9 +8508,6 @@ span.icon-rss::before {
top: 0; top: 0;
bottom: 0; bottom: 0;
} }
.project-card .details {
transition: background-color 0.2s;
}
.project-card .badges:empty { .project-card .badges:empty {
display: none; display: none;
} }
@ -8537,6 +8544,9 @@ span.icon-rss::before {
max-width: 100%; max-width: 100%;
max-height: 80vh; max-height: 80vh;
} }
.timeline-item .avatar {
width: 2.5rem;
}
.timeline-modal .container { .timeline-modal .container {
max-height: 100vh; max-height: 100vh;
max-width: 100%; max-width: 100%;

View File

@ -13,7 +13,7 @@ br {
body { body {
background-color: var(--background-color); background-color: var(--background-color);
color: var(--color); color: var(--color);
font-family: "Fira Sans", sans-serif; font-family: "Inter", sans-serif;
min-height: 100vh; min-height: 100vh;
box-sizing: border-box; box-sizing: border-box;
} }
@ -31,6 +31,10 @@ a,
} }
} }
.link--normal {
--link-color: var(--color);
}
b, b,
strong { strong {
font-weight: 500; font-weight: 500;
@ -209,7 +213,11 @@ pre,
} }
.mw-site { .mw-site {
max-width: 80rem; max-width: var(--site-width);
}
.mw-site-narrow {
max-width: var(--site-width-narrow);
} }
.mh-3 { .mh-3 {
@ -372,6 +380,14 @@ pre,
display: none !important; display: none !important;
} }
.fill-current {
fill: currentColor;
}
.rot-180 {
transform: rotate(180deg);
}
@media screen and (min-width: 35em) { @media screen and (min-width: 35em) {
.bi-avoid-ns { .bi-avoid-ns {
break-inside: avoid; break-inside: avoid;

View File

@ -53,22 +53,15 @@
.project-card { .project-card {
color: var(--fg-font-color); color: var(--fg-font-color);
background-color: var(--card-background); background-color: var(--card-background);
border-color: var(--project-card-border-color);
transition: box-shadow 0.2s, background-color 0.2s; &.project-card-black {
background-color: var(--card-background-transparent);
}
.slideshow & { .slideshow & {
margin-top: 8px; margin-top: 8px;
} }
&:hover {
background-color: var(--card-background-hover);
&>.title {
text-decoration: underline;
}
}
.image-container { .image-container {
--image-size: 8rem; --image-size: 8rem;
@ -83,11 +76,6 @@
} }
} }
.details {
/* Background color given by theme */
transition: background-color 0.2s;
}
.badges:empty { .badges:empty {
display: none; display: none;
} }

View File

@ -22,6 +22,11 @@
max-height: 80vh; max-height: 80vh;
} }
} }
.avatar {
/* 40px */
width: 2.5rem;
}
} }
.timeline-modal { .timeline-modal {

View File

@ -47,11 +47,14 @@ $breakpoint-large: screen and (min-width: 60em)
--notice-failure-color: #b42222; --notice-failure-color: #b42222;
--spoiler-border: #aaa; --spoiler-border: #aaa;
--site-width: 80rem;
--site-width-narrow: 60rem;
} }
@media (prefers-color-scheme: dark) { @media (prefers-color-scheme: dark) {
:root { :root {
--background-color: #202020; --background-color: #2f2f2f;
--color: #eee; --color: #eee;
--link-color: #cc3b95; --link-color: #cc3b95;
@ -73,6 +76,7 @@ $breakpoint-large: screen and (min-width: 60em)
/* --card-background: #282828; */ /* --card-background: #282828; */
--card-background: #494949; --card-background: #494949;
--card-background-hover: #333; --card-background-hover: #333;
--card-background-transparent: #242424D8;
--dim-background: #252525; --dim-background: #252525;
--dim-background-transparent: rgba(#252525, 0); --dim-background-transparent: rgba(#252525, 0);

View File

@ -1,5 +1,5 @@
<footer> <footer>
<div class="mv5 h3 fill-current link--white"> <div class="mv5 h3 fill-current link--normal">
<a href="{{ .Header.HMNHomepageUrl }}">{{ svg "hmn_circuit" }}</a> <a href="{{ .Header.HMNHomepageUrl }}">{{ svg "hmn_circuit" }}</a>
</div> </div>
</footer> </footer>

View File

@ -1,4 +1,4 @@
<header id="site-header" class="flex flex-row items-center link--white"> <header id="site-header" class="flex flex-row items-center link--normal">
<a href="{{ .Header.HMNHomepageUrl }}" class="hmn-logo flex-shrink-0"> <a href="{{ .Header.HMNHomepageUrl }}" class="hmn-logo flex-shrink-0">
Handmade Handmade
</a> </a>

View File

@ -6,13 +6,15 @@
</div> </div>
{{ end }} {{ end }}
<div class="details pa3 flex-grow-1"> <div class="details pa3 flex-grow-1">
<h3 class="mb2 f4">{{ .Project.Name }}</h3> <h3 class="b mb2 f4">{{ .Project.Name }}</h3>
<div class="blurb">{{ .Project.Blurb }}</div> <div class="blurb">{{ .Project.Blurb }}</div>
<div class="badges mt2"> <hr>
{{ if .Project.LifecycleString }} <div>TODO: Authors</div>
{{ if .Project.LifecycleString }}
<div class="badges mt2">
<span class="badge {{ .Project.LifecycleBadgeClass }}">{{ .Project.LifecycleString }}</span> <span class="badge {{ .Project.LifecycleBadgeClass }}">{{ .Project.LifecycleString }}</span>
{{ end }} </div>
</div> {{ end }}
</div> </div>
</a> </a>

View File

@ -9,7 +9,7 @@
</style> </style>
<template id="snippet-edit"> <template id="snippet-edit">
<form data-tmpl="root" class="snippet-edit-root timeline-item pa3 mb2 br3" method="POST" action="{{ .SnippetEdit.SubmitUrl }}" enctype="multipart/form-data"> <form data-tmpl="root" class="snippet-edit-root timeline-item pa3 mb2" method="POST" action="{{ .SnippetEdit.SubmitUrl }}" enctype="multipart/form-data">
{{ csrftoken .Session }} {{ csrftoken .Session }}
<input data-tmpl="redirect" type="hidden" name="redirect" /> <input data-tmpl="redirect" type="hidden" name="redirect" />
<input data-tmpl="snippetId" type="hidden" name="snippet_id" /> <input data-tmpl="snippetId" type="hidden" name="snippet_id" />

View File

@ -1,9 +1,9 @@
<div class="timeline-item flex flex-column pa3 mb2 br3" data-id="{{ .ID }}" {{ with .FilterTitle }}data-filter-title="{{ . }}"{{ end }}> <div class="timeline-item flex flex-column pa3" data-id="{{ .ID }}" {{ with .FilterTitle }}data-filter-title="{{ . }}"{{ end }}>
{{/* top bar - avatar, info, date */}} {{/* top bar - avatar, info, date */}}
<div class="flex items-center"> <div class="flex items-center">
{{ if .OwnerAvatarUrl }} {{ if .OwnerAvatarUrl }}
<a class="flex flex-shrink-0" href="{{ .OwnerUrl }}"> <a class="flex flex-shrink-0" href="{{ .OwnerUrl }}">
<img class="avatar lite {{ if not .SmallInfo }}big{{ end }} {{ if .SmallInfo }}mr2{{ else }}mr3{{ end }}" src="{{ .OwnerAvatarUrl }}" /> <img class="avatar {{ if not .SmallInfo }}big{{ end }} {{ if .SmallInfo }}mr2{{ else }}mr3{{ end }}" src="{{ .OwnerAvatarUrl }}" />
</a> </a>
{{ end }} {{ end }}
@ -36,20 +36,6 @@
{{/* content */}} {{/* content */}}
{{ if .Description }}
<div class="mt3 overflow-hidden relative {{ if .TruncateDescription }}mh-5{{ end }}">
<div class="post-content">{{ trim .Description }}</div>
{{ if .TruncateDescription }}
<div class="excerpt-fade absolute w-100 h4 bottom-0 z-999"></div>
{{ end }}
</div>
{{ if .TruncateDescription }}
<div class="mt2">
<a href="{{ .Url }}">Read more »</a>
</div>
{{ end }}
{{ end }}
{{ range .EmbedMedia }} {{ range .EmbedMedia }}
<div class="timeline-content-box mt3 {{ if eq .Type mediaembed }}embed{{ end }} br2 overflow-hidden flex {{ if not (eq .Type mediaunknown) }}timeline-item-bg justify-center{{ end }}"> <div class="timeline-content-box mt3 {{ if eq .Type mediaembed }}embed{{ end }} br2 overflow-hidden flex {{ if not (eq .Type mediaunknown) }}timeline-item-bg justify-center{{ end }}">
{{ if eq .Type mediaimage }} {{ if eq .Type mediaimage }}
@ -72,6 +58,20 @@
</div> </div>
{{ end }} {{ end }}
{{ if .Description }}
<div class="mt3 overflow-hidden relative {{ if .TruncateDescription }}mh-5{{ end }}">
<div class="post-content">{{ trim .Description }}</div>
{{ if .TruncateDescription }}
<div class="excerpt-fade absolute w-100 h4 bottom-0 z-999"></div>
{{ end }}
</div>
{{ if .TruncateDescription }}
<div class="mt2">
<a href="{{ .Url }}">Read more »</a>
</div>
{{ end }}
{{ end }}
{{ with .Projects }} {{ with .Projects }}
<div class="mt3 flex g2 projects"> <div class="mt3 flex g2 projects">
{{ range $i, $proj := . }} {{ range $i, $proj := . }}

View File

@ -26,210 +26,13 @@
{{ end }} {{ end }}
<meta name="theme-color" content="#003C83"> <meta name="theme-color" content="#003C83">
<script src="{{ static "js/templates.js" }}"></script>
<link rel="stylesheet" href="{{ static "fonts/mohave/stylesheet.css" }}"> <link rel="stylesheet" href="{{ static "fonts/mohave/stylesheet.css" }}">
<link href='https://fonts.googleapis.com/css?family=Fira+Sans:300,400,500,600' rel='stylesheet' type='text/css'> <link href='https://fonts.googleapis.com/css?family=Fira+Sans:300,400,500,600' rel='stylesheet' type='text/css'>
<link href='https://fonts.googleapis.com/css?family=Fira+Mono:300,400,500,700' rel='stylesheet' type='text/css'> <link href='https://fonts.googleapis.com/css?family=Fira+Mono:300,400,500,700' rel='stylesheet' type='text/css'>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@100..900&display=swap" rel="stylesheet"> <link href="https://fonts.googleapis.com/css2?family=Inter:wght@100..900&display=swap" rel="stylesheet">
<link rel="stylesheet" type="text/css" href="{{ static "style.css" }}"> <link rel="stylesheet" type="text/css" href="{{ static "style.css" }}">
<style> <script src="{{ static "js/script.js" }}"></script>
:root {
--theme-gradient-dark: linear-gradient(to bottom right, #003c83, #019AD2);
--theme-gradient-light: linear-gradient(to bottom right, #8BD5FF, #5899FF);
--white: #fff;
--bg-button: rgba(255, 255, 255, 0);
--bg-button-hover: rgba(255, 255, 255, 0.1);
--charcoal: #2F2F2F;
--gray: #CBCBCB;
--rich-gray: #494949;
--spacing-0: 0;
--spacing-1: .25rem;
--spacing-2: .5rem;
--spacing-3: 1rem;
--spacing-4: 2rem;
--spacing-5: 4rem;
--spacing-6: 8rem;
--spacing-7: 16rem;
--border-radius-2: 0.25rem;
--link-color: transparent;
}
body {
font-family: "Inter", "Fira Sans", sans-serif;
font-size: 1rem; /* remove this override when base stylesheet is less dumb */
}
h1, h2, h3, h4, h5, h6 {
font-weight: 700;
}
.w2-5 {
width: 3rem;
}
.link--white {
--link-color: var(--white);
}
.post-content p {
/* stupid override, should be done by .post-content instead of .content */
margin: 0.6rem 0;
}
.bg--theme-gradient-dark {
background: var(--theme-gradient-dark);
}
.bg--theme-gradient-light {
background: var(--theme-gradient-light);
}
.bg--charcoal {
background: var(--charcoal);
}
.bg--rich-gray {
background: var(--rich-gray);
}
.bg--gray {
background: var(--gray);
}
.b--charcoal {
border-color: var(--charcoal);
}
.b--rich-gray {
border-color: var(--rich-gray);
}
.c--theme-gradient-dark {
background: var(--theme-gradient-dark);
background-clip: text;
-webkit-background-clip: text;
color: transparent;
}
a, .c--theme-gradient-light {
background: var(--theme-gradient-light);
background-clip: text;
-webkit-background-clip: text;
}
.c--theme-gradient-light {
color: transparent;
}
.c--gray {
color: var(--gray);
}
.btn--jam {
border: 1px solid var(--white);
border-radius: var(--border-radius-2);
padding: var(--spacing-2) var(--spacing-3);
background-color: var(--bg-button);
}
.btn--jam.small {
padding: var(--spacing-1) var(--spacing-2);
}
.btn--jam:hover {
background-color: var(--bg-button-hover);
}
.button-simple {
background: var(--bg-button);
border: 1px solid var(--white);
}
.button-simple:hover {
background: var(--bg-button-hover);
}
.c-white {
color: var(--white);
}
.invisible {
visibility: hidden;
}
.svg-mask {
mask: var(--mask-url);
-webkit-mask: var(--mask-url);
}
.fill-current {
fill: currentColor;
}
.square {
aspect-ratio: 1 / 1;
}
.wide-screen {
aspect-ratio: 16 / 9;
}
.iframe-fill iframe {
flex-grow: 1;
}
.jam-logo {
max-width: 18rem;
}
.jam-logo-small {
max-width: 8rem;
}
.jam-title {
/* align with the width of the logo */
font-size: 2.76rem;
font-weight: 700;
/* align with the text's actual bounding box */
line-height: 1.18;
margin-top: -0.18em;
margin-left: -0.03em;
margin-right: -0.03em;
}
.jam-title.small {
font-size: 2.25rem;
/* align with the text's actual bounding box */
line-height: 1.18;
margin-top: -0.18em;
margin-left: -0.03em;
margin-right: -0.03em;
}
/* not small */
@media screen and (min-width: 35em) {
.jam-logo {
max-width: 24rem;
}
.jam-title {
font-size: 3.7rem;
}
}
/* not small */
@media screen and (min-width: 35em) {
.flex-basis-40-ns {
flex-basis: 40%;
}
}
</style>
{{ template "extrahead" . }} {{ template "extrahead" . }}
</head> </head>

View File

@ -8,40 +8,51 @@
{{ end }} {{ end }}
{{ define "content" }} {{ define "content" }}
<div class="flex flex-column flex-row-l"> <div class="flex flex-row justify-center">
<div class=" <div class="flex-grow-1 flex flex-column items-center mw-site">
sidebar flex-shrink-0 self-start-l <div class="w-100 h5 bg-white-50"></div>
flex flex-column flex-row-ns items-start-ns flex-column-l items-stretch-l <div class="w-100 mw-site-narrow flex justify-center" style="margin-top: -3rem">
mw5-l mh3 ml0-ns mb3 overflow-hidden {{ template "project_card.html" projectcarddata .Project "flex-grow-1 project-card-black" }}
"> </div>
<div class="w-100 w5-ns flex-shrink-0 flex justify-center br3 overflow-hidden"> {{ if .Project.ParsedDescription }}
{{ if .Project.Logo }} <div id="longdesc" class="description w-100 mw-site-narrow pt4">
<img alt="{{ .Project.Name }}" src="{{ .Project.Logo }}"> <h3 class="f4">About {{ .Project.Name }}</h3>
{{ else }} <hr class="mv3">
<div class="bg--dim w-100 aspect-ratio--1x1 relative"> <div class="longdesc-content post-content mh-5 overflow-hidden">
<div class="aspect-ratio--object flex justify-center items-center f3 b c--dimmest tc">{{ .Project.Name }}</div> {{ .Project.ParsedDescription }}
</div> </div>
{{ end }} <a class="longdesc-link pt3 db" href="#">
</div> <span class="longdesc-text">Read more</span> <span class="svgicon f7 dib">{{ svg "chevron-down-thick" }}</span>
<div class="mt3 mt0-ns mt3-l ml3-ns ml0-l overflow-hidden"> </a>
<div class="mb3">
{{ range $i, $owner := .Owners }}
<div class="flex mb3 items-center">
<img class="avatar mr2" src="{{ $owner.AvatarUrl }}" />
<a class="rel" href="{{ $owner.ProfileUrl }}">{{ $owner.Name }}</a>
</div>
{{ end }}
</div> </div>
<div class="w-100 w-auto-ns w-100-l"> {{ end }}
{{ range .ProjectLinks }} {{ if or .Header.Project.CanEdit (gt (len .RecentActivity) 0) }}
<div class="pair flex"> <hr class="w-100 mv4">
<div class="key flex-auto flex-shrink-0 mr2">{{ .Name }}</div> <div class="w-100 flex g3">
<div class="value projectlink truncate"><a class="external" href="{{ .Url }}" ><span class="icon-{{ .Icon }}"></span> {{ .LinkText }}</a></div> <div class="flex flex-column g3">
<div class="bg--card pa3 w5">
Filters
</div> </div>
{{ end }} </div>
<div>
<div class="flex flex-row items-center mb2">
<h2 id="recent">Recent Activity</h2>
<div class="flex-grow-1"></div>
{{ if .Header.Project.CanEdit }}
<a href="javascript:;" class="create_snippet_link button">Add Snippet</a>
{{ end }}
</div>
<div class="timeline flex flex-column g3">
{{ range .RecentActivity }}
{{ template "timeline_item.html" . }}
{{ end }}
</div>
</div>
</div> </div>
</div> {{ end }}
</div> </div>
</div>
<div class="flex flex-column flex-row-l">
<div class="flex-grow-1 overflow-hidden"> <div class="flex-grow-1 overflow-hidden">
{{ with .Screenshots }} {{ with .Screenshots }}
<div class="carousel-container mw-100 mb3"> <div class="carousel-container mw-100 mb3">
@ -68,29 +79,7 @@
</div> </div>
</div> </div>
{{ end }} {{ end }}
<div class="description ph3 ph0-ns post-content">
{{ if .Project.ParsedDescription }}
{{ .Project.ParsedDescription }}
{{ else }}
{{ .Project.Blurb }}
{{ end }}
</div>
{{ if or .Header.Project.CanEdit (gt (len .RecentActivity) 0) }}
<div class="content-block timeline-container ph3 ph0-ns mv4">
<div class="flex flex-row items-center mb2">
<h2 id="recent">Recent Activity</h2>
<div class="flex-grow-1"></div>
{{ if .Header.Project.CanEdit }}
<a href="javascript:;" class="create_snippet_link button">Add Snippet</a>
{{ end }}
</div>
<div class="timeline">
{{ range .RecentActivity }}
{{ template "timeline_item.html" . }}
{{ end }}
</div>
</div>
{{ end }}
</div> </div>
</div> </div>
{{ if .User }} {{ if .User }}
@ -154,5 +143,30 @@
activateCarouselItem(i); activateCarouselItem(i);
clearInterval(carouselTimer); clearInterval(carouselTimer);
} }
const longdesc = document.querySelector("#longdesc");
const longdescLink = longdesc.querySelector(".longdesc-link");
const longdescContent = longdesc.querySelector(".longdesc-content");
longdescLink.addEventListener("click", e => {
e.preventDefault();
const expandText = longdesc.querySelector(".longdesc-text");
const chevron = longdesc.querySelector(".svgicon");
const expanding = longdescContent.classList.contains("mh-5");
longdescContent.classList.toggle("mh-5", !expanding);
expandText.innerText = expanding ? "Read less" : "Read more";
chevron.classList.toggle("rot-180", expanding);
});
function showOrHideLongdescLink() {
// The content has mh-5, which is max-height: 16rem
const hide = longdescContent.clientHeight < rem2px(16);
longdescLink.classList.toggle("dn", hide);
longdescLink.classList.toggle("db", !hide);
}
showOrHideLongdescLink();
window.addEventListener("resize", showOrHideLongdescLink);
</script> </script>
{{ end }} {{ end }}

View File

@ -504,7 +504,11 @@ func ProjectHomepage(c *RequestContext) ResponseData {
return c.ErrorResponse(http.StatusInternalServerError, oops.New(err, "failed to fetch user projects")) return c.ErrorResponse(http.StatusInternalServerError, oops.New(err, "failed to fetch user projects"))
} }
templateProjects := make([]templates.Project, 0, len(userProjects)) templateProjects := make([]templates.Project, 0, len(userProjects))
templateProjects = append(templateProjects, templates.ProjectAndStuffToTemplate(&p, hmndata.UrlContextForProject(&p.Project).BuildHomepage(), c.Theme))
for _, p := range userProjects { for _, p := range userProjects {
if p.Project.ID == c.CurrentProject.ID {
continue
}
templateProject := templates.ProjectAndStuffToTemplate(&p, hmndata.UrlContextForProject(&p.Project).BuildHomepage(), c.Theme) templateProject := templates.ProjectAndStuffToTemplate(&p, hmndata.UrlContextForProject(&p.Project).BuildHomepage(), c.Theme)
templateProjects = append(templateProjects, templateProject) templateProjects = append(templateProjects, templateProject)
} }