Files
site/layouts/projects/single.html
T
protokeyandClaude Opus 5.5 f7d0f27faa refactor: every project landing has its own theme
- landing.css is layout only and uses tokens (colors, fonts, hero, nav)
- palettes live in css/landing-<theme>.css; Robotex gets its own dark
  theme, MyPointVPN sets the same tokens
- theme defaults to the project folder name; the build fails if the
  theme file is missing, so a landing can't ship without styles

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 21:17:17 +04:00

206 lines
7.3 KiB
HTML

{{- /*
Project landing. Served on its own subdomain (params.domain) by the ingress Caddy,
which rewrites "/" to this page and "/en/" to its English translation.
So every link here is host-relative ("/", "/en/", "#id") and assets come from
global /css/ and /img/, never from the page bundle.
*/ -}}
{{- $p := .Params -}}
{{- $home := cond (eq .Language.Lang hugo.Sites.Default.Language.Lang) "/" (printf "/%s/" .Language.Lang) -}}
{{- /* Every landing has its own theme: css/landing-<theme>.css on top of the layout in
css/landing.css. The theme is params.theme or, by default, the project folder name. */ -}}
{{- $theme := $p.theme | default .File.ContentBaseName -}}
{{- $themeCSS := resources.Get (printf "css/landing-%s.css" $theme) -}}
{{- if not $themeCSS }}{{ errorf "landing %q: theme file assets/css/landing-%s.css not found" .File.Path $theme }}{{ end -}}
{{- $css := slice (resources.Get "css/landing.css") $themeCSS | resources.Concat (printf "css/landing-%s.bundle.css" $theme) -}}
{{- $css = $css | minify | fingerprint -}}
{{- $fonts := $p.fonts | default "family=Inter:wght@400;600;800&family=JetBrains+Mono:wght@400;600" -}}
<!doctype html>
<html lang="{{ .Language.Lang }}">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>{{ .Title }} · {{ $p.tagline }}</title>
<meta name="description" content="{{ .Description }}">
<link rel="canonical" href="{{ strings.TrimSuffix "/" $p.domain }}{{ $home }}">
{{- range .AllTranslations }}
{{- $h := cond (eq .Language.Lang hugo.Sites.Default.Language.Lang) "/" (printf "/%s/" .Language.Lang) }}
<link rel="alternate" hreflang="{{ .Language.Lang }}" href="{{ strings.TrimSuffix "/" $p.domain }}{{ $h }}">
{{- end }}
<meta property="og:title" content="{{ .Title }} · {{ $p.tagline }}">
<meta property="og:description" content="{{ .Description }}">
<meta property="og:url" content="{{ strings.TrimSuffix "/" $p.domain }}{{ $home }}">
<meta property="og:type" content="website">
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png">
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link href="https://fonts.googleapis.com/css2?{{ $fonts | safeURL }}&display=swap" rel="stylesheet">
<link rel="stylesheet" href="{{ $css.RelPermalink }}" integrity="{{ $css.Data.Integrity }}">
</head>
<body class="theme-{{ $theme }}">
<header class="nav">
<div class="wrap nav__row">
<a class="nav__logo" href="{{ $home }}">{{ .Title }}</a>
<nav class="nav__links">
{{- range $p.nav }}<a href="{{ .href }}">{{ .label }}</a>{{ end }}
{{- range .Translations }}
{{- $h := cond (eq .Language.Lang hugo.Sites.Default.Language.Lang) "/" (printf "/%s/" .Language.Lang) }}
<a class="nav__lang" href="{{ $h }}" hreflang="{{ .Language.Lang }}">{{ .Language.Params.displayName }}</a>
{{- end }}
</nav>
</div>
</header>
<main>
<section class="hero{{ if $p.chat }} hero--split{{ end }}">
<div class="wrap">
<div class="hero__text">
{{ with $p.badge }}<p class="badge">{{ . }}</p>{{ end }}
<h1 class="hero__title">{{ $p.tagline }}</h1>
<p class="hero__lead">{{ .Description }}</p>
<div class="hero__cta">
{{ range $p.cta }}<a class="btn{{ if .primary }} btn--primary{{ end }}" href="{{ .href }}"{{ if hasPrefix .href "http" }} target="_blank" rel="noopener"{{ end }}>{{ .label }}</a>{{ end }}
</div>
</div>
{{ with $p.chat }}
<div class="chat" aria-label="{{ .title }}">
<p class="chat__title">{{ .title }}</p>
{{ range .messages }}
<p class="chat__msg chat__msg--{{ .from }}">{{ .text | markdownify }}</p>
{{ end }}
</div>
{{ end }}
</div>
</section>
{{ with $p.problem }}
<section class="section" id="problem">
<div class="wrap">
<h2>{{ .title }}</h2>
<p class="section__lead">{{ .lead }}</p>
<ol class="problems">
{{ range .items }}<li>{{ . }}</li>{{ end }}
</ol>
<p>{{ .text }}</p>
</div>
</section>
{{ end }}
{{ with $p.features }}
<section class="section section--alt" id="features">
<div class="wrap">
<h2>{{ .title }}</h2>
<div class="grid">
{{ range .items }}
<article class="card">
<h3>{{ .title }}</h3>
<p>{{ .text | markdownify }}</p>
</article>
{{ end }}
</div>
</div>
</section>
{{ end }}
{{ with $p.how }}
<section class="section" id="how">
<div class="wrap">
<h2>{{ .title }}</h2>
<ol class="steps">
{{ range .steps }}<li>{{ . | markdownify }}</li>{{ end }}
</ol>
<div class="code">
{{ range .code }}
<figure>
<figcaption>{{ .caption }}</figcaption>
<pre><code>{{ .body }}</code></pre>
</figure>
{{ end }}
</div>
</div>
</section>
{{ end }}
{{ with $p.start }}
<section class="section" id="start">
<div class="wrap">
<h2>{{ .title }}</h2>
<ol class="start">
{{ range .steps }}
<li>
<h3>{{ .title | markdownify }}</h3>
<p>{{ .text | markdownify }}</p>
</li>
{{ end }}
</ol>
</div>
</section>
{{ end }}
{{ with $p.note }}
<section class="section" id="note">
<div class="wrap">
<aside class="note">
<h2>{{ .title }}</h2>
<p>{{ .text | markdownify }}</p>
</aside>
</div>
</section>
{{ end }}
{{ with $p.engine }}
<section class="section engine" id="engine">
<div class="wrap">
{{ with .kicker }}<p class="engine__kicker">{{ . }}</p>{{ end }}
<h2>{{ .title }}</h2>
<p class="section__lead">{{ .lead | markdownify }}</p>
<ul class="engine__list">
{{ range .items }}<li>{{ . | markdownify }}</li>{{ end }}
</ul>
{{ with .callout }}
<div class="engine__callout">
<h3>{{ .title }}</h3>
<p>{{ .text | markdownify }}</p>
</div>
{{ end }}
</div>
</section>
{{ end }}
{{ with $p.limits }}
<section class="section section--alt" id="limits">
<div class="wrap">
<h2>{{ .title }}</h2>
<ul class="limits">
{{ range .items }}<li>{{ . | markdownify }}</li>{{ end }}
</ul>
</div>
</section>
{{ end }}
{{ with $p.community }}
<section class="section" id="community">
<div class="wrap">
<h2>{{ .title }}</h2>
<p class="section__lead">{{ .lead | markdownify }}</p>
<div class="grid grid--4">
{{ range .links }}
<a class="card card--link" href="{{ .href }}" target="_blank" rel="noopener">
<h3>{{ .title }} →</h3>
<p>{{ .text }}</p>
</a>
{{ end }}
</div>
</div>
</section>
{{ end }}
</main>
<footer class="footer">
<div class="wrap footer__row">
<p>{{ $p.footer | markdownify }}</p>
<p><a href="{{ site.Home.Permalink }}">{{ site.Params.author.name }}</a> · <a href="{{ with $p.post }}{{ (site.GetPage .).Permalink }}{{ end }}">{{ $p.postLabel }}</a></p>
</div>
</footer>
</body>
</html>