1
0
Fork 0
forked from goffee/cup
cup/storage/templates/custom_templates_functions.html

471 lines
No EOL
16 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>{{.SiteTitle}} — template lab</title>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link href="https://fonts.googleapis.com/css2?family=Playfair+Display:ital,wght@0,400;0,700;1,400&family=JetBrains+Mono:wght@400;500&family=DM+Sans:wght@300;400;500&display=swap" rel="stylesheet" />
<style>
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
:root {
--bg: #0d0d0f;
--surface: #141416;
--border: #222226;
--muted: #3a3a40;
--subtle: #6b6b75;
--body: #c8c8d0;
--heading: #f0f0f4;
--accent: #e8c547;
--accent2: #5b8cff;
--danger: #ff6b6b;
--mono: 'JetBrains Mono', monospace;
--serif: 'Playfair Display', Georgia, serif;
--sans: 'DM Sans', sans-serif;
}
html { background: var(--bg); color: var(--body); font-family: var(--sans); font-size: 16px; line-height: 1.6; }
body { max-width: 1100px; margin: 0 auto; padding: 0 2rem 6rem; }
/* ── Header ── */
header {
border-bottom: 1px solid var(--border);
padding: 3rem 0 2rem;
display: flex;
justify-content: space-between;
align-items: flex-end;
gap: 1rem;
}
.site-title {
font-family: var(--mono);
font-size: .75rem;
letter-spacing: .2em;
text-transform: uppercase;
color: var(--accent);
}
.site-headline {
font-family: var(--serif);
font-size: clamp(2rem, 5vw, 3.5rem);
color: var(--heading);
line-height: 1.1;
margin-top: .4rem;
}
.site-headline em { color: var(--accent); font-style: italic; }
.helper-count {
font-family: var(--mono);
font-size: .7rem;
color: var(--subtle);
text-align: right;
white-space: nowrap;
}
/* ── Section label ── */
.section-label {
display: flex;
align-items: center;
gap: 1rem;
margin: 4rem 0 1.5rem;
}
.section-label span {
font-family: var(--mono);
font-size: .65rem;
letter-spacing: .15em;
text-transform: uppercase;
color: var(--accent);
white-space: nowrap;
}
.section-label::after {
content: '';
flex: 1;
height: 1px;
background: var(--border);
}
/* ── Helper badge ── */
.badge {
display: inline-block;
font-family: var(--mono);
font-size: .6rem;
letter-spacing: .08em;
padding: .15em .5em;
border-radius: 3px;
border: 1px solid var(--muted);
color: var(--subtle);
vertical-align: middle;
margin-left: .4rem;
}
.badge-accent { border-color: var(--accent); color: var(--accent); }
.badge-blue { border-color: var(--accent2); color: var(--accent2); }
/* ── Demo block ── */
.demo-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
gap: 1px;
background: var(--border);
border: 1px solid var(--border);
border-radius: 8px;
overflow: hidden;
}
.demo-cell {
background: var(--surface);
padding: 1.5rem;
}
.demo-cell h3 {
font-family: var(--mono);
font-size: .7rem;
letter-spacing: .1em;
text-transform: uppercase;
color: var(--accent);
margin-bottom: .75rem;
}
.demo-cell .result {
font-size: 1rem;
color: var(--heading);
}
.demo-cell .note {
margin-top: .5rem;
font-size: .75rem;
color: var(--subtle);
font-family: var(--mono);
}
/* ── Article cards ── */
.article-list { display: flex; flex-direction: column; gap: 1px; background: var(--border); border: 1px solid var(--border); border-radius: 8px; overflow: hidden; }
.article-card { background: var(--surface); padding: 2rem; display: grid; grid-template-columns: 1fr auto; gap: 1rem; align-items: start; transition: background .15s; }
.article-card:hover { background: #18181c; }
.card-meta { display: flex; align-items: center; gap: .75rem; flex-wrap: wrap; margin-bottom: .75rem; }
.tag {
font-family: var(--mono);
font-size: .6rem;
letter-spacing: .08em;
text-transform: uppercase;
padding: .2em .55em;
background: var(--muted);
border-radius: 3px;
color: var(--body);
}
.tag-featured { background: color-mix(in srgb, var(--accent) 15%, transparent); color: var(--accent); }
.card-title { font-family: var(--serif); font-size: 1.35rem; color: var(--heading); line-height: 1.25; margin-bottom: .5rem; }
.card-subtitle { font-size: .85rem; color: var(--accent2); margin-bottom: .6rem; font-style: italic; }
.card-excerpt { font-size: .875rem; color: var(--body); line-height: 1.65; }
.card-author { display: flex; align-items: center; gap: .6rem; margin-top: 1.25rem; }
.avatar {
width: 32px; height: 32px; border-radius: 50%;
background: var(--muted);
display: flex; align-items: center; justify-content: center;
font-family: var(--mono); font-size: .55rem; color: var(--accent);
flex-shrink: 0;
}
.author-name { font-size: .8rem; color: var(--heading); }
.author-date { font-size: .72rem; color: var(--subtle); font-family: var(--mono); }
.card-aside { text-align: right; }
.views { font-family: var(--mono); font-size: .75rem; color: var(--subtle); white-space: nowrap; }
.views strong { color: var(--body); display: block; font-size: 1rem; }
.price-badge {
display: inline-block; margin-top: .75rem;
font-family: var(--mono); font-size: .7rem;
padding: .3em .7em; border-radius: 4px;
background: color-mix(in srgb, var(--accent2) 15%, transparent);
color: var(--accent2); border: 1px solid var(--accent2);
}
.price-free {
background: color-mix(in srgb, #4caf50 12%, transparent);
color: #6fcf97; border-color: #4caf50;
}
/* ── Logic demo table ── */
.logic-table { width: 100%; border-collapse: collapse; font-size: .85rem; }
.logic-table th {
font-family: var(--mono); font-size: .65rem; letter-spacing: .1em; text-transform: uppercase;
color: var(--subtle); text-align: left; padding: .6rem 1rem;
border-bottom: 1px solid var(--border);
}
.logic-table td { padding: .75rem 1rem; border-bottom: 1px solid var(--border); color: var(--body); }
.logic-table tr:last-child td { border-bottom: none; }
.logic-table tr:hover td { background: var(--surface); }
.val { font-family: var(--mono); font-size: .8rem; color: var(--accent); }
/* ── Footer ── */
footer {
margin-top: 5rem; padding-top: 2rem; border-top: 1px solid var(--border);
font-family: var(--mono); font-size: .65rem; color: var(--muted);
display: flex; justify-content: space-between; flex-wrap: wrap; gap: .5rem;
}
</style>
</head>
<body>
<!-- ════════════════════════════════════════════
HEADER
════════════════════════════════════════════ -->
<header>
<div>
<p class="site-title">{{.SiteTitle}}</p>
<h1 class="site-headline">Template <em>helpers</em><br>in action</h1>
</div>
<p class="helper-count">22 helpers registered<br>across 6 groups</p>
</header>
<!-- ════════════════════════════════════════════
STRING HELPERS
════════════════════════════════════════════ -->
<div class="section-label"><span>String helpers</span></div>
{{$first := first .Articles}}
<div class="demo-grid">
<div class="demo-cell">
<h3>capitalize <span class="badge">capitalize</span></h3>
<p class="result">{{capitalize $first.Title}}</p>
<p class="note">input: "{{$first.Title}}"</p>
</div>
<div class="demo-cell">
<h3>truncate <span class="badge">truncate</span></h3>
<p class="result">{{$first.Excerpt | truncate 80}}</p>
<p class="note">capped at 80 chars</p>
</div>
<div class="demo-cell">
<h3>prepend <span class="badge">prepend</span></h3>
<p class="result">{{prepend $first.Slug "/articles/"}}</p>
<p class="note">prepended "/articles/" to slug</p>
</div>
<div class="demo-cell">
<h3>strAppend <span class="badge">strAppend</span></h3>
<p class="result">{{strAppend $first.Slug ".html"}}</p>
<p class="note">appended ".html" to slug</p>
</div>
<div class="demo-cell">
<h3>split → join <span class="badge">split</span> <span class="badge">join</span></h3>
{{$parts := split "go,templates,funcmap" ","}}
<p class="result">{{join $parts " · "}}</p>
<p class="note">split on "," then joined with " · "</p>
</div>
</div>
<!-- ════════════════════════════════════════════
NUMBER & DATE HELPERS
════════════════════════════════════════════ -->
<div class="section-label"><span>Number &amp; Date helpers</span></div>
<div class="demo-grid">
<div class="demo-cell">
<h3>fmtNumber — int <span class="badge">fmtNumber</span></h3>
<p class="result">{{fmtNumber $first.Views}}</p>
<p class="note">raw value: {{$first.Views}}</p>
</div>
{{$paid := index .Articles 2}}
<div class="demo-cell">
<h3>fmtNumber — float <span class="badge">fmtNumber</span></h3>
<p class="result">$ {{fmtNumber $paid.Price}}</p>
<p class="note">raw value: {{$paid.Price}}</p>
</div>
<div class="demo-cell">
<h3>fmtDate "short" <span class="badge">fmtDate</span></h3>
<p class="result">{{fmtDate $first.PublishedAt "short"}}</p>
<p class="note">layout: "02 Jan 2006"</p>
</div>
<div class="demo-cell">
<h3>fmtDate "long" <span class="badge">fmtDate</span></h3>
<p class="result">{{fmtDate $first.PublishedAt "long"}}</p>
<p class="note">layout: "02 January 2006"</p>
</div>
<div class="demo-cell">
<h3>fmtDate "iso" <span class="badge">fmtDate</span></h3>
<p class="result">{{fmtDate $first.PublishedAt "iso"}}</p>
<p class="note">layout: "2006-01-02"</p>
</div>
<div class="demo-cell">
<h3>timeAgo <span class="badge">timeAgo</span></h3>
{{range .Articles}}
<p class="result" style="margin-bottom:.3rem">{{timeAgo .PublishedAt}} <span class="note" style="display:inline">— {{fmtDate .PublishedAt "short"}}</span></p>
{{end}}
</div>
</div>
<!-- ════════════════════════════════════════════
COLLECTION HELPERS
════════════════════════════════════════════ -->
<div class="section-label"><span>Collection helpers</span></div>
<div class="demo-grid">
<div class="demo-cell">
<h3>first <span class="badge">first</span></h3>
{{with first .Articles}}
<p class="result">{{capitalize .Title}}</p>
<p class="note">first article in the list</p>
{{end}}
</div>
<div class="demo-cell">
<h3>last <span class="badge">last</span></h3>
{{with last .Articles}}
<p class="result">{{capitalize .Title}}</p>
<p class="note">last article in the list</p>
{{end}}
</div>
<div class="demo-cell">
<h3>sliceOf 02 <span class="badge">sliceOf</span></h3>
{{range sliceOf .Articles 0 2}}
<p class="result" style="margin-bottom:.25rem">— {{capitalize .Title}}</p>
{{end}}
<p class="note">only first 2 of {{len .Articles}} articles</p>
</div>
<div class="demo-cell">
<h3>contains — slice <span class="badge">contains</span></h3>
{{if contains $first.Tags "go"}}
<p class="result" style="color:var(--accent)">✓ has tag "go"</p>
{{else}}
<p class="result" style="color:var(--danger)">✗ missing tag "go"</p>
{{end}}
<p class="note">tags: {{join $first.Tags ", "}}</p>
</div>
<div class="demo-cell">
<h3>contains — string <span class="badge">contains</span></h3>
{{if contains $first.Excerpt "FuncMap"}}
<p class="result" style="color:var(--accent)">✓ excerpt mentions "FuncMap"</p>
{{else}}
<p class="result" style="color:var(--danger)">✗ not found</p>
{{end}}
<p class="note">substring search on .Excerpt</p>
</div>
<div class="demo-cell">
<h3>join <span class="badge">join</span></h3>
<p class="result">{{join $first.Tags " / "}}</p>
<p class="note">tags joined with " / "</p>
</div>
</div>
<!-- ════════════════════════════════════════════
LOGIC HELPERS
════════════════════════════════════════════ -->
<div class="section-label"><span>Logic helpers</span></div>
<table class="logic-table">
<thead>
<tr>
<th>Helper</th>
<th>Article</th>
<th>Input</th>
<th>Output</th>
</tr>
</thead>
<tbody>
{{range .Articles}}
<tr>
<td><span class="badge badge-accent">defaultVal</span></td>
<td>{{.Title | capitalize | truncate 30}}</td>
<td class="val">.Subtitle ({{if .Subtitle}}"{{.Subtitle}}"{{else}}empty{{end}})</td>
<td class="val">{{defaultVal "No subtitle" .Subtitle}}</td>
</tr>
<tr>
<td><span class="badge badge-blue">ternary</span></td>
<td>{{capitalize .Title | truncate 30}}</td>
<td class="val">.Featured = {{.Featured}}</td>
<td class="val">{{ternary "⭐ featured" "regular" .Featured}}</td>
</tr>
<tr>
<td><span class="badge badge-accent">coalesce</span></td>
<td>{{capitalize .Title | truncate 30}}</td>
<td class="val">.Subtitle → .Title</td>
<td class="val">{{coalesce .Subtitle .Title}}</td>
</tr>
{{end}}
</tbody>
</table>
<!-- ════════════════════════════════════════════
FULL ARTICLE CARDS (all helpers combined)
════════════════════════════════════════════ -->
<div class="section-label"><span>Full article cards — all helpers combined</span></div>
<div class="article-list">
{{range .Articles}}
<div class="article-card">
<div>
<div class="card-meta">
{{if .Featured}}<span class="tag tag-featured">featured</span>{{end}}
{{range .Tags}}<span class="tag">{{.}}</span>{{end}}
</div>
<h2 class="card-title">{{capitalize .Title}}</h2>
{{with coalesce .Subtitle ""}}
<p class="card-subtitle">{{.}}</p>
{{end}}
<p class="card-excerpt">{{.Excerpt | truncate 160}}</p>
<a style="font-family:var(--mono);font-size:.7rem;color:var(--accent2);text-decoration:none;margin-top:.75rem;display:inline-block;"
href="{{prepend .Slug "/articles/"}}">
{{prepend .Slug "/articles/"}} →
</a>
<div class="card-author">
<div class="avatar">{{.Author.Avatar}}</div>
<div>
<div class="author-name">{{capitalize .Author.Name}}</div>
<div class="author-date">
{{fmtDate .PublishedAt "short"}} · {{timeAgo .PublishedAt}}
</div>
</div>
</div>
</div>
<div class="card-aside">
<div class="views">
<strong>{{fmtNumber .Views}}</strong>
views
</div>
{{if gt .Price 0.0}}
<span class="price-badge">$ {{fmtNumber .Price}}</span>
{{else}}
<span class="price-badge price-free">free</span>
{{end}}
</div>
</div>
{{end}}
</div>
<!-- ════════════════════════════════════════════
FOOTER
════════════════════════════════════════════ -->
<footer>
<span>{{.SiteTitle}} / template sandbox</span>
<span>{{len .Articles}} articles · {{fmtDate (first .Articles).PublishedAt "iso"}} → {{fmtDate (last .Articles).PublishedAt "iso"}}</span>
</footer>
</body>
</html>