Stackとは
現在このサイトで使っているhugoのテーマです。
おしゃれです。シンプルでいいですよね。
とりあえずまとめる程度のものなので、不備があるかも。
インストール
git cloneする
git clone https://github.com/CaiJimmy/hugo-theme-stack/ themes/hugo-theme-stack
シンプルでいいですね。
サブモジュール
git submodule add https://github.com/CaiJimmy/hugo-theme-stack/ themes/hugo-theme-stack
私はこっちを使っています。
なおCloudflarePagesでデプロイしてるのでhttpsではなくsshで。
テーマを使う
設定ファイル(例:hugo.yml)に以下を追記、もしくは編集する。
theme: hugo-theme-stack
これでOK。
設定ファイルの例
あくまで例ですので、項目などについては公式ドキュメントをご覧ください。
# 基本設定
baseURL: https://example.org/
languageCode: ja
title: サイトの名前
theme: hugo-theme-stack
# コンテンツの言語
DefaultContentLanguage: ja
# パーマリンクの設定
permalinks:
post: /p/:year/:slug
page: /:slug
# テーマの設定
params:
featuredImageField: image # フロントマターのフィーチャー画像のフィールド
rssFullContent: true # rssでページの全ての内容を出力するかどうか
# 日時のフォーマットを設定
dateFormat:
published: 2006/01/02
lastUpdated: 2006/01/02 15:04 JST
# サイドバーの設定
sidebar:
emoji: 😎 # 絵文字
subtitle: 猫大好き〜 # サブタイトル
# アイコン
avatar:
enabled: true
local: true
src: img/icon.webp
# 右側に置かれる要素
widgets:
homepage:
- type: search
- type: archives
params:
limit: 10
- type: categories
params:
limit: 5
- type: tag-cloud
page:
- type: search
- type: toc
- type: archives
params:
limit: 10
- type: categories
params:
limit: 5
- type: tag-cloud
# 画像処理の設定
imageProcessing:
cover:
enabled: false
content:
enabled: false
# メニューの初期化
menu:
main: []
# マークアップの設定
markup:
goldmark:
renderer:
unsafe: true
highlight:
noClasses: true
codeFences: true
guessSyntax: true
lineNos: false
lineNumbersInTable: false
tabWidth: 4
フォントを変える
layouts/partials/head/custom.html を作成します。
今回は私の愛しているフォントInterとNoto Sans JPを使います。
以下を追記
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:ital,opsz,wght@0,14..32,100..900;1,14..32,100..900&family=Noto+Sans+JP:wght@100..900&display=swap" rel="stylesheet">
<style>
:root {
--base-font-family: 'Inter', 'Noto Sans JP', sans-serif;
}
</style>
違和感なく美しいフォントになります。
リンクカードを実装する
Info
このセクションはかなり雑な実装があるので、すごい人は良いものに書き換えてください。
頑張って作りました;;
注意
最新のhugoじゃ動きませんでした。
当サイトはv0.129.0を使用しています。
当サイトはv0.129.0を使用しています。
ショートコードのhtmlファイル
layouts/shortcodes/link.htmlを作成し以下を追記する。
Info
コードには一部AIによる加筆があります。
{{- $url := (.Get "url") -}}
{{- $target_url := urls.Parse $url -}}
{{- $type := (.Get "type") -}}
{{- $title := "" -}}
{{- $favicon_url := "" -}}
{{- $ogp_image := "" -}}
{{- with $result := resources.GetRemote $url -}}
{{- with $result.Err -}}
{{- $title = (print $url "にアクセスできませんでした") -}}
{{- else -}}
<!-- headを取得 -->
{{- $head_matches := findRE "<head[^>]*?>(.|\n)*?</head>" $result.Content -}}
{{- if gt (len $head_matches) 0 -}}
{{- $head := index $head_matches 0 -}}
<!-- headからタイトルを取得 -->
{{- $title_matches := findRE "<title.*?>(.|\n)*?</title>" $head -}}
{{- if gt (len $title_matches) 0 -}}
{{- $title = index $title_matches 0 | replaceRE "</?title>" "" -}}
{{- end -}}
<!-- Googleのfaviconサービスを使用してfaviconを取得 -->
{{- $favicon_url = print "https://www.google.com/s2/favicons?domain=" $target_url.Hostname "&sz=64" -}}
<!-- OGP画像を取得 (正規表現を改善) -->
{{- $ogp_meta_tags := findRE "<meta[^<>]*property=[\"']og:image[\"'][^<>]*?>" $head -}}
{{- if gt (len $ogp_meta_tags) 0 -}}
{{- $ogp_meta := index $ogp_meta_tags 0 -}}
<!-- contentの値だけを抽出するためのパターン -->
{{- $content_match := findRE "content=[\"']([^\"']+)[\"']" $ogp_meta -}}
{{- if gt (len $content_match) 0 -}}
<!-- キャプチャグループを使用して引用符の中身だけを取得 -->
{{- $content_parts := split (index $content_match 0) "'" -}}
{{- if gt (len $content_parts) 1 -}}
{{- $ogp_image = index $content_parts 1 -}}
{{- else -}}
{{- $content_parts := split (index $content_match 0) "\"" -}}
{{- if gt (len $content_parts) 1 -}}
{{- $ogp_image = index $content_parts 1 -}}
{{- end -}}
{{- end -}}
<!-- 正しいURLであることを確認 -->
{{- if and $ogp_image (ne $ogp_image "") -}}
{{- if eq (strings.Substr $ogp_image 0 1) "/" -}}
{{- $ogp_image = print $target_url.Scheme "://" $target_url.Hostname $ogp_image -}}
{{- else if not (gt (len (findRE "^https?://" $ogp_image)) 0) -}}
{{- $ogp_image = print $target_url.Scheme "://" $target_url.Hostname "/" $ogp_image -}}
{{- end -}}
{{- end -}}
{{- end -}}
{{- end -}}
{{- end -}}
{{- end -}}
{{- end -}}
<!-- アイコンとラベルのマッピング -->
{{- $icon := "" -}}
{{- $label := "" -}}
{{- if $type -}}
{{- $icon_map := dict "sankou" "ti ti-book" "download" "ti ti-download" "official" "ti ti-star" "awasete" "ti ti-books" "osusume" "ti ti-heart" "sns" "ti ti-message-chatbot" -}}
{{- $label_map := dict "sankou" "参考" "download" "ダウンロード" "official" "公式サイト" "awasete" "合わせて読みたい" "osusume" "おすすめ" "sns" "SNS" -}}
{{- with index $icon_map $type -}}
{{- $icon = . -}}
{{- end -}}
{{- with index $label_map $type -}}
{{- $label = . -}}
{{- end -}}
{{- end -}}
<div class="link-card-wrapper">
<a href="{{- htmlUnescape $target_url -}}" target="_blank" class="link-card-anchor">
<div class="link-card">
{{- if $ogp_image -}}
<div class="link-card-image">
<img class="link-card-ogp" src="{{- $ogp_image -}}" alt="">
</div>
{{- end -}}
<div class="link-card-overlay">
<div class="link-card-title">
{{- $title | htmlUnescape | truncate 100 -}}
</div>
<div class="link-card-hostname">
{{- if $favicon_url -}}
<div class="link-card-hostname-img">
<img src="{{- $favicon_url -}}" alt="">
</div>
{{- end -}}
<span>
{{- $target_url.Hostname -}}
</span>
</div>
{{- if and $type $label $icon -}}
<span class="card-type">{{- $label -}} <i class="{{- $icon -}}"></i></span>
{{- end -}}
</div>
</div>
</a>
</div>
スタイルを設定
assets/scss/custom.scssに以下を追記。
/* リンクカードのラッパー */
.link-card-wrapper {
margin-top: 1em;
margin-bottom: 1em;
}
/* アンカータグのスタイルリセット */
.link-card-anchor {
text-decoration: none;
color: inherit;
display: block;
width: 100%;
}
/* リンクカードの基本スタイル */
.link-card {
display: flex;
flex-direction: row;
border: solid 1px rgba(0, 0, 0, 0.15);
border-radius: 8px;
transition: .3s;
overflow: hidden;
position: relative;
color: var(--card-text-color-main);
}
/* マウスホバー時の挙動 */
.link-card:hover {
opacity: 0.9;
box-shadow: 0 0px 8px rgba(0, 0, 0, 0.1);
transition: 0.3s;
border-radius: 4px;
}
/* OGP画像用のスタイル */
.link-card-image {
width: 25%;
aspect-ratio: 16/9;
border-right: 1px solid rgba(0, 0, 0, 0.15);
overflow: hidden;
position: relative;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.link-card-image::after {
position: absolute;
font-family: "tabler-icons";
content: '\ea99';
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, .5);
opacity: 0;
transition: all .3s;
display: flex;
justify-content: center;
align-items: center;
color: white;
font-size: 1.5em;
backdrop-filter: blur(4px);
}
.link-card-ogp {
flex: 1;
height: 100%;
width: 100%;
object-fit: cover;
object-position: center;
transition: all .3s;
position: relative;
}
.link-card:hover .link-card-ogp {
transform: scale(1.1);
}
.link-card:hover .link-card-image::after {
opacity: 1;
}
.link-card-overlay {
padding: 1em;
display: flex;
flex-direction: column;
justify-content: center;
/* gap: 0.5em; */
width: 75%;
}
/* タイトルのスタイル */
.link-card-title {
font-weight: bold;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
/* ホスト名部分のスタイル */
.link-card-hostname {
display: flex;
align-items: center;
height: 24px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
margin-top: 1rem;
}
.link-card-hostname-img {
height: 100%;
display: flex;
align-items: center;
}
.link-card-hostname-img img {
width: 24px;
height: 24px;
margin-right: 8px;
}
.link-card-hostname span {
font-size: 0.9em;
}
/* カードタイプラベルのスタイル */
.card-type {
position: absolute;
bottom: 0;
right: 0;
background-color: #333;
color: #fff;
padding: .4em 1.3em;
border-radius: 8px 0 0 0;
font-size: 12px;
}
.card-type i {
margin-left: 2px;
font-weight: normal;
}
/* スマホ表示のためのメディアクエリ */
@media screen and (max-width: 767px) {
.link-card {
flex-direction: column;
}
.link-card-image {
width: 100%;
aspect-ratio: 16/9;
border-right: none;
border-bottom: 1px solid rgba(0, 0, 0, 0.15);
}
.link-card-overlay {
width: 100%;
}
/* モバイル表示ではタイトルを2行までに制限 */
.link-card-title {
white-space: normal;
display: -webkit-box;
-webkit-line-clamp: 2;
line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
}
}
/* タブレット表示のための微調整 */
@media screen and (min-width: 768px) and (max-width: 1024px) {
.link-card-image {
width: 30%;
}
.link-card-overlay {
width: 70%;
}
}
アイコンを読み込む
当サイトではTabler Iconsを使用しています。
layouts/partials/head/custom.htmlに以下を追記。
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@tabler/icons-webfont@3.31.0/dist/tabler-icons.min.css" />
使い方
Info
[]は{}に置き換えるように。[[< link url="https://google.com" type="official" >]]
urlには完全なURLを入れ、typeは
<!-- アイコンとラベルのマッピング -->
{{- $icon := "" -}}
{{- $label := "" -}}
{{- if $type -}}
{{- $icon_map := dict "sankou" "ti ti-book" "download" "ti ti-download" "official" "ti ti-star" "awasete" "ti ti-books" "osusume" "ti ti-heart" "sns" "ti ti-message-chatbot" -}}
{{- $label_map := dict "sankou" "参考" "download" "ダウンロード" "official" "公式サイト" "awasete" "合わせて読みたい" "osusume" "おすすめ" "sns" "SNS" -}}
{{- with index $icon_map $type -}}
{{- $icon = . -}}
{{- end -}}
{{- with index $label_map $type -}}
{{- $label = . -}}
{{- end -}}
{{- end -}}
ここに対応します。
mermaidを使えるようにする
これもショートコードで。
layouts/shortcodes/mermaid.htmlを作成し以下を追記。
<!-- layouts/shortcodes/mermaid.html -->
{{ if not (.Page.Scratch.Get "mermaidLoaded") }}
<script src="https://cdn.jsdelivr.net/npm/mermaid/dist/mermaid.min.js"></script>
<script>
document.addEventListener('DOMContentLoaded', function() {
mermaid.initialize({
startOnLoad: true,
theme: 'default',
securityLevel: 'loose'
});
});
</script>
{{ .Page.Scratch.Set "mermaidLoaded" true }}
{{ end }}
<div class="mermaid">
{{.Inner}}
</div>
使い方
Info
[]は{}に置き換えるように。[[< mermaid >]]
graph TD
A[Enter Chart Definition] --> B(Preview)
B --> C{decide}
C --> D[Keep]
C --> E[Edit Definition]
E --> B
D --> F[Save Image and Code]
F --> B
[[< /mermaid >]]
これは下のようになる。
graph TD
A[Enter Chart Definition] --> B(Preview)
B --> C{decide}
C --> D[Keep]
C --> E[Edit Definition]
E --> B
D --> F[Save Image and Code]
F --> B
グラフなどについては公式を参照。
終わり
終わりです。実はもっとやってるんだけどねw
まぁいいでしょう!!終わり〜〜
