mirror of
https://github.com/calofmijuck/blog.git
synced 2025-12-06 14:53:50 +00:00
Compare commits
11 Commits
82691d2cca
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
c75bcf061f
|
|||
|
e2ede1cdb6
|
|||
|
56e14d045f
|
|||
|
3b632697b0
|
|||
|
378f162534
|
|||
|
122ad489e2
|
|||
|
027159b0e3
|
|||
|
ec7e1e656e
|
|||
|
7e16136864
|
|||
|
54be44d4f3
|
|||
| cf770b3a38 |
5
.gitignore
vendored
5
.gitignore
vendored
@@ -17,10 +17,11 @@ package-lock.json
|
|||||||
|
|
||||||
# IDE configurations
|
# IDE configurations
|
||||||
.idea
|
.idea
|
||||||
.vscode
|
.vscode/*
|
||||||
!.vscode/settings.json
|
!.vscode/settings.json
|
||||||
!.vscode/extensions.json
|
!.vscode/extensions.json
|
||||||
|
!.vscode/tasks.json
|
||||||
|
|
||||||
# Misc
|
# Misc
|
||||||
_sass/dist
|
_sass/vendors
|
||||||
assets/js/dist
|
assets/js/dist
|
||||||
|
|||||||
2
Gemfile
2
Gemfile
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
source "https://rubygems.org"
|
source "https://rubygems.org"
|
||||||
|
|
||||||
gem "jekyll-theme-chirpy", "~> 7.2", ">= 7.2.4"
|
gem "jekyll-theme-chirpy", "~> 7.3", ">= 7.3.1"
|
||||||
|
|
||||||
gem "html-proofer", "~> 5.0", group: :test
|
gem "html-proofer", "~> 5.0", group: :test
|
||||||
|
|
||||||
|
|||||||
@@ -62,7 +62,7 @@
|
|||||||
|
|
||||||
{% capture script %}/assets/js/dist/{{ js }}.min.js{% endcapture %}
|
{% capture script %}/assets/js/dist/{{ js }}.min.js{% endcapture %}
|
||||||
|
|
||||||
<script src="{{ script | relative_url }}"></script>
|
<script defer src="{{ script | relative_url }}"></script>
|
||||||
|
|
||||||
{% if page.math %}
|
{% if page.math %}
|
||||||
<!-- MathJax -->
|
<!-- MathJax -->
|
||||||
@@ -143,7 +143,7 @@
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
<script src="https://cdnjs.cloudflare.com/polyfill/v3/polyfill.min.js?features=es6"></script>
|
<script async src="https://cdnjs.cloudflare.com/polyfill/v3/polyfill.min.js?features=es6"></script>
|
||||||
<script id="MathJax-script" async src="{{ site.data.origin[type].mathjax.js | relative_url }}"></script>
|
<script id="MathJax-script" async src="{{ site.data.origin[type].mathjax.js | relative_url }}"></script>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
|||||||
39
_includes/post-summary.html
Normal file
39
_includes/post-summary.html
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
{%- comment -%}
|
||||||
|
Get the post's description or body content.
|
||||||
|
|
||||||
|
Arguments:
|
||||||
|
full_text: If true, return the full content. Default is false.
|
||||||
|
max_length: The maximum length of the returned content. Default is 200.
|
||||||
|
{%- endcomment -%}
|
||||||
|
|
||||||
|
{%- if post.description and include.full_text != true -%}
|
||||||
|
{{- post.description -}}
|
||||||
|
{%- else -%}
|
||||||
|
{%- comment -%} Remove the line numbers from the code snippet. {%- endcomment -%}
|
||||||
|
|
||||||
|
{%- assign content = post.content -%}
|
||||||
|
|
||||||
|
{%- if content contains '<td class="rouge-gutter gl"><pre class="lineno">' -%}
|
||||||
|
{%- assign content = content
|
||||||
|
| replace: '<td class="rouge-gutter gl"><pre class="lineno">',
|
||||||
|
'<!-- <td class="rouge-gutter gl"><pre class="lineno">'
|
||||||
|
-%}
|
||||||
|
{%- assign content = content | replace: '</td><td class="rouge-code">', '</td> --><td class="rouge-code">' -%}
|
||||||
|
{%- endif -%}
|
||||||
|
|
||||||
|
{%- assign content = content
|
||||||
|
| markdownify
|
||||||
|
| strip_html
|
||||||
|
| newline_to_br
|
||||||
|
| replace: '<br />', ' '
|
||||||
|
| strip_newlines
|
||||||
|
| strip
|
||||||
|
-%}
|
||||||
|
|
||||||
|
{%- unless include.full_text -%}
|
||||||
|
{%- assign max_length = include.max_length | default: 200 -%}
|
||||||
|
{%- assign content = content | truncate: max_length -%}
|
||||||
|
{%- endunless -%}
|
||||||
|
|
||||||
|
{{- content -}}
|
||||||
|
{%- endif -%}
|
||||||
97
_includes/sidebar.html
Normal file
97
_includes/sidebar.html
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
<!-- The Side Bar -->
|
||||||
|
|
||||||
|
<aside aria-label="Sidebar" id="sidebar" class="d-flex flex-column align-items-end">
|
||||||
|
<header class="profile-wrapper">
|
||||||
|
<a href="{{ '/' | relative_url }}" id="avatar" class="rounded-circle">
|
||||||
|
{%- if site.avatar != empty and site.avatar -%}
|
||||||
|
{%- capture avatar_url -%}
|
||||||
|
{% include media-url.html src=site.avatar %}
|
||||||
|
{%- endcapture -%}
|
||||||
|
<img src="{{- avatar_url -}}" width="112" height="112" alt="avatar" onerror="this.style.display='none'">
|
||||||
|
{%- endif -%}
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<a class="site-title d-block" href="{{ '/' | relative_url }}">{{ site.title }}</a>
|
||||||
|
<p class="site-subtitle fst-italic mb-0">{{ site.tagline }}</p>
|
||||||
|
</header>
|
||||||
|
<!-- .profile-wrapper -->
|
||||||
|
|
||||||
|
<nav class="flex-column flex-grow-1 w-100 ps-0">
|
||||||
|
<ul class="nav">
|
||||||
|
{% for tab in site.tabs %}
|
||||||
|
<li class="nav-item{% if tab.url == page.url %}{{ " active" }}{% endif %}">
|
||||||
|
<a href="{{ tab.url | relative_url }}" class="nav-link">
|
||||||
|
<i class="fa-fw {{ tab.icon }}"></i>
|
||||||
|
{% capture tab_name %}{{ tab.url | split: '/' }}{% endcapture %}
|
||||||
|
|
||||||
|
<span>{{ site.data.locales[include.lang].tabs.[tab_name] | default: tab.title | upcase }}</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<!-- .nav-item -->
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<div class="sidebar-bottom d-flex flex-wrap align-items-center w-100">
|
||||||
|
{% unless site.theme_mode %}
|
||||||
|
<button type="button" class="btn btn-link nav-link" aria-label="Switch Mode" id="mode-toggle">
|
||||||
|
<i class="fas fa-adjust"></i>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{% if site.data.contact.size > 0 %}
|
||||||
|
<span class="icon-border"></span>
|
||||||
|
{% endif %}
|
||||||
|
{% endunless %}
|
||||||
|
|
||||||
|
{% for entry in site.data.contact %}
|
||||||
|
{%- assign url = null -%}
|
||||||
|
|
||||||
|
{% case entry.type %}
|
||||||
|
{% when 'github', 'twitter' %}
|
||||||
|
{%- unless site[entry.type].username -%}
|
||||||
|
{%- continue -%}
|
||||||
|
{%- endunless -%}
|
||||||
|
{%- capture url -%}
|
||||||
|
https://{{ entry.type }}.com/{{ site[entry.type].username }}
|
||||||
|
{%- endcapture -%}
|
||||||
|
{% when 'email' %}
|
||||||
|
{%- unless site.social.email -%}
|
||||||
|
{%- continue -%}
|
||||||
|
{%- endunless -%}
|
||||||
|
{%- assign email = site.social.email | split: '@' -%}
|
||||||
|
{%- capture url -%}
|
||||||
|
javascript:location.href = 'mailto:' + ['{{ email[0] }}','{{ email[1] }}'].join('@')
|
||||||
|
{%- endcapture -%}
|
||||||
|
{% when 'rss' %}
|
||||||
|
{% assign url = '/feed.xml' | relative_url %}
|
||||||
|
{% else %}
|
||||||
|
{% assign url = entry.url %}
|
||||||
|
{% endcase %}
|
||||||
|
|
||||||
|
{% if url %}
|
||||||
|
<a
|
||||||
|
href="{{ url }}"
|
||||||
|
aria-label="{{ entry.type }}"
|
||||||
|
{% assign link_types = '' %}
|
||||||
|
|
||||||
|
{% unless entry.noblank %}
|
||||||
|
target="_blank"
|
||||||
|
{% assign link_types = 'noopener noreferrer' %}
|
||||||
|
{% endunless %}
|
||||||
|
|
||||||
|
{% if entry.type == 'mastodon' %}
|
||||||
|
{% assign link_types = link_types | append: ' me' | strip %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% unless link_types == empty %}
|
||||||
|
rel="{{ link_types }}"
|
||||||
|
{% endunless %}
|
||||||
|
>
|
||||||
|
<i class="{{ entry.icon }}"></i>
|
||||||
|
</a>
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
<!-- .sidebar-bottom -->
|
||||||
|
</aside>
|
||||||
|
<!-- #sidebar -->
|
||||||
150
_layouts/home.html
Normal file
150
_layouts/home.html
Normal file
@@ -0,0 +1,150 @@
|
|||||||
|
---
|
||||||
|
layout: default
|
||||||
|
---
|
||||||
|
|
||||||
|
{% include lang.html %}
|
||||||
|
|
||||||
|
{% assign all_pinned = site.posts | where: 'pin', 'true' %}
|
||||||
|
{% assign all_normal = site.posts | where_exp: 'item', 'item.pin != true and item.hidden != true' %}
|
||||||
|
|
||||||
|
{% assign posts = '' | split: '' %}
|
||||||
|
|
||||||
|
<!-- Pagination fallbacks -->
|
||||||
|
{% assign per_page = paginator.per_page | default: site.paginate | default: 10 %}
|
||||||
|
{% assign page_num = paginator.page | default: 1 %}
|
||||||
|
|
||||||
|
<!-- Get pinned posts on current page -->
|
||||||
|
|
||||||
|
{% assign visible_start = page_num | minus: 1 | times: per_page %}
|
||||||
|
{% assign visible_end = visible_start | plus: per_page %}
|
||||||
|
|
||||||
|
{% if all_pinned.size > visible_start %}
|
||||||
|
{% if all_pinned.size > visible_end %}
|
||||||
|
{% assign pinned_size = paginator.per_page %}
|
||||||
|
{% else %}
|
||||||
|
{% assign pinned_size = all_pinned.size | minus: visible_start %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% for i in (visible_start..all_pinned.size) limit: pinned_size %}
|
||||||
|
{% assign posts = posts | push: all_pinned[i] %}
|
||||||
|
{% endfor %}
|
||||||
|
{% else %}
|
||||||
|
{% assign pinned_size = 0 %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<!-- Get normal posts on current page -->
|
||||||
|
|
||||||
|
{% assign paginator_posts = paginator.posts | default: site.posts %}
|
||||||
|
{% assign normal_size = paginator_posts | size | minus: pinned_size %}
|
||||||
|
|
||||||
|
{% if normal_size > 0 %}
|
||||||
|
{% if pinned_size > 0 %}
|
||||||
|
{% assign normal_start = 0 %}
|
||||||
|
{% else %}
|
||||||
|
{% assign normal_start = visible_start | minus: all_pinned.size %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% assign normal_end = normal_start | plus: normal_size | minus: 1 %}
|
||||||
|
|
||||||
|
{% assign normal_end = 10 %}
|
||||||
|
|
||||||
|
{% for i in (normal_start..normal_end) %}
|
||||||
|
{% assign posts = posts | push: all_normal[i] %}
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<div id="post-list" class="flex-grow-1 px-xl-1">
|
||||||
|
{% for post in posts %}
|
||||||
|
<article class="card-wrapper card">
|
||||||
|
<a href="{{ post.url | relative_url }}" class="post-preview row g-0 flex-md-row-reverse">
|
||||||
|
{% assign card_body_col = '12' %}
|
||||||
|
|
||||||
|
{% if post.image %}
|
||||||
|
{% assign src = post.image.path | default: post.image %}
|
||||||
|
|
||||||
|
{% if post.media_subpath %}
|
||||||
|
{% unless src contains '://' %}
|
||||||
|
{% assign src = post.media_subpath
|
||||||
|
| append: '/'
|
||||||
|
| append: src
|
||||||
|
| replace: '///', '/'
|
||||||
|
| replace: '//', '/'
|
||||||
|
%}
|
||||||
|
{% endunless %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if post.image.lqip %}
|
||||||
|
{% assign lqip = post.image.lqip %}
|
||||||
|
|
||||||
|
{% if post.media_subpath %}
|
||||||
|
{% unless lqip contains 'data:' %}
|
||||||
|
{% assign lqip = post.media_subpath
|
||||||
|
| append: '/'
|
||||||
|
| append: lqip
|
||||||
|
| replace: '///', '/'
|
||||||
|
| replace: '//', '/'
|
||||||
|
%}
|
||||||
|
{% endunless %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% assign lqip_attr = 'lqip="' | append: lqip | append: '"' %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% assign alt = post.image.alt | xml_escape | default: 'Preview Image' %}
|
||||||
|
|
||||||
|
<div class="col-md-5">
|
||||||
|
<img src="{{ src }}" alt="{{ alt }}" {{ lqip_attr }}>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% assign card_body_col = '7' %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<div class="col-md-{{ card_body_col }}">
|
||||||
|
<div class="card-body d-flex flex-column">
|
||||||
|
<h1 class="card-title my-2 mt-md-0">{{ post.title }}</h1>
|
||||||
|
|
||||||
|
<div class="card-text content mt-0 mb-3">
|
||||||
|
<p>{% include post-summary.html %}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="post-meta flex-grow-1 d-flex align-items-end">
|
||||||
|
<div class="me-auto">
|
||||||
|
<!-- posted date -->
|
||||||
|
<i class="far fa-calendar fa-fw me-1"></i>
|
||||||
|
{% include datetime.html date=post.date lang=lang %}
|
||||||
|
|
||||||
|
<!-- categories -->
|
||||||
|
{% if post.categories.size > 0 %}
|
||||||
|
<i class="far fa-folder-open fa-fw me-1"></i>
|
||||||
|
<span class="categories">
|
||||||
|
{% for category in post.categories %}
|
||||||
|
{{ category }}
|
||||||
|
{%- unless forloop.last -%},{%- endunless -%}
|
||||||
|
{% endfor %}
|
||||||
|
</span>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% if post.pin %}
|
||||||
|
<div class="pin ms-1">
|
||||||
|
<i class="fas fa-thumbtack fa-fw"></i>
|
||||||
|
<span>{{ site.data.locales[lang].post.pin_prompt }}</span>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
<!-- .post-meta -->
|
||||||
|
</div>
|
||||||
|
<!-- .card-body -->
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
</article>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
<!-- #post-list -->
|
||||||
|
|
||||||
|
{% assign total_pages = paginator.total_pages | default: 2 %}
|
||||||
|
{% if total_pages > 1 and paginator %}
|
||||||
|
{% include post-paginator.html %}
|
||||||
|
{% endif %}
|
||||||
@@ -10,7 +10,9 @@ script_includes:
|
|||||||
- comment
|
- comment
|
||||||
---
|
---
|
||||||
|
|
||||||
{% include lang.html %} {% include toc-status.html %}
|
{% include lang.html %}
|
||||||
|
|
||||||
|
{% include toc-status.html %}
|
||||||
|
|
||||||
<article class="px-1" data-toc="{{ enable_toc }}">
|
<article class="px-1" data-toc="{{ enable_toc }}">
|
||||||
<header>
|
<header>
|
||||||
@@ -22,36 +24,40 @@ script_includes:
|
|||||||
<div class="post-meta text-muted">
|
<div class="post-meta text-muted">
|
||||||
<!-- published date -->
|
<!-- published date -->
|
||||||
<span>
|
<span>
|
||||||
{{ site.data.locales[lang].post.posted }} {% include datetime.html
|
{{ site.data.locales[lang].post.posted }}
|
||||||
date=page.date tooltip=true lang=lang %}
|
{% include datetime.html date=page.date tooltip=true lang=lang %}
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<!-- lastmod date -->
|
<!-- lastmod date -->
|
||||||
{% if page.last_modified_at and page.last_modified_at != page.date %}
|
{% if page.last_modified_at and page.last_modified_at != page.date %}
|
||||||
<span>
|
<span>
|
||||||
{{ site.data.locales[lang].post.updated }} {% include datetime.html
|
{{ site.data.locales[lang].post.updated }}
|
||||||
date=page.last_modified_at tooltip=true lang=lang %}
|
{% include datetime.html date=page.last_modified_at tooltip=true lang=lang %}
|
||||||
</span>
|
</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<div class="d-flex justify-content-between">
|
<div class="d-flex justify-content-between">
|
||||||
<!-- author(s) -->
|
<!-- author(s) -->
|
||||||
<span>
|
<span>
|
||||||
{% if page.author %} {% assign authors = page.author %} {% elsif
|
{% if page.author %}
|
||||||
page.authors %} {% assign authors = page.authors %} {% endif %} {{
|
{% assign authors = page.author %}
|
||||||
site.data.locales[lang].post.written_by }}
|
{% elsif page.authors %}
|
||||||
|
{% assign authors = page.authors %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{{ site.data.locales[lang].post.written_by }}
|
||||||
|
|
||||||
<em>
|
<em>
|
||||||
{% if authors %} {% for author in authors %} {% if
|
{% if authors %}
|
||||||
site.data.authors[author].url -%}
|
{% for author in authors %}
|
||||||
<a href="{{ site.data.authors[author].url }}"
|
{% if site.data.authors[author].url -%}
|
||||||
>{{ site.data.authors[author].name }}</a
|
<a href="{{ site.data.authors[author].url }}">{{ site.data.authors[author].name }}</a>
|
||||||
>
|
{%- else -%}
|
||||||
{%- else -%} {{ site.data.authors[author].name }} {%- endif %} {%
|
{{ site.data.authors[author].name }}
|
||||||
unless forloop.last %}{{ '</em
|
{%- endif %}
|
||||||
>,
|
{% unless forloop.last %}{{ '</em>, <em>' }}{% endunless %}
|
||||||
<em
|
{% endfor %}
|
||||||
>' }}{% endunless %} {% endfor %} {% else %}
|
{% else %}
|
||||||
<a href="{{ site.social.links[0] }}">{{ site.social.name }}</a>
|
<a href="{{ site.social.links[0] }}">{{ site.social.name }}</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</em>
|
</em>
|
||||||
@@ -59,8 +65,7 @@ script_includes:
|
|||||||
|
|
||||||
<div>
|
<div>
|
||||||
<!-- pageviews -->
|
<!-- pageviews -->
|
||||||
{% if site.pageviews.provider and
|
{% if site.pageviews.provider and site.analytics[site.pageviews.provider].id %}
|
||||||
site.analytics[site.pageviews.provider].id %}
|
|
||||||
<span>
|
<span>
|
||||||
<em id="pageviews">
|
<em id="pageviews">
|
||||||
<i class="fas fa-spinner fa-spin small"></i>
|
<i class="fas fa-spinner fa-spin small"></i>
|
||||||
@@ -77,37 +82,22 @@ script_includes:
|
|||||||
</header>
|
</header>
|
||||||
|
|
||||||
{% if enable_toc %}
|
{% if enable_toc %}
|
||||||
<div
|
<div id="toc-bar" class="d-flex align-items-center justify-content-between invisible">
|
||||||
id="toc-bar"
|
|
||||||
class="d-flex align-items-center justify-content-between invisible"
|
|
||||||
>
|
|
||||||
<span class="label text-truncate">{{ page.title }}</span>
|
<span class="label text-truncate">{{ page.title }}</span>
|
||||||
<button type="button" class="toc-trigger btn me-1">
|
<button type="button" class="toc-trigger btn me-1">
|
||||||
<i class="fa-solid fa-list-ul fa-fw"></i>
|
<i class="fa-solid fa-list-ul fa-fw"></i>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button
|
<button id="toc-solo-trigger" type="button" class="toc-trigger btn btn-outline-secondary btn-sm">
|
||||||
id="toc-solo-trigger"
|
<span class="label ps-2 pe-1">{{- site.data.locales[lang].panel.toc -}}</span>
|
||||||
type="button"
|
|
||||||
class="toc-trigger btn btn-outline-secondary btn-sm"
|
|
||||||
>
|
|
||||||
<span class="label ps-2 pe-1"
|
|
||||||
>{{- site.data.locales[lang].panel.toc -}}</span
|
|
||||||
>
|
|
||||||
<i class="fa-solid fa-angle-right fa-fw"></i>
|
<i class="fa-solid fa-angle-right fa-fw"></i>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<dialog id="toc-popup" class="p-0">
|
<dialog id="toc-popup" class="p-0">
|
||||||
<div
|
<div class="header d-flex flex-row align-items-center justify-content-between">
|
||||||
class="header d-flex flex-row align-items-center justify-content-between"
|
|
||||||
>
|
|
||||||
<div class="label text-truncate py-2 ms-4">{{- page.title -}}</div>
|
<div class="label text-truncate py-2 ms-4">{{- page.title -}}</div>
|
||||||
<button
|
<button id="toc-popup-close" type="button" class="btn mx-1 my-1 opacity-75">
|
||||||
id="toc-popup-close"
|
|
||||||
type="button"
|
|
||||||
class="btn mx-1 my-1 opacity-75"
|
|
||||||
>
|
|
||||||
<i class="fas fa-close"></i>
|
<i class="fas fa-close"></i>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -115,7 +105,9 @@ script_includes:
|
|||||||
</dialog>
|
</dialog>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<div class="content">{{ content }}</div>
|
<div class="content">
|
||||||
|
{{ content }}
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="post-tail-wrapper text-muted">
|
<div class="post-tail-wrapper text-muted">
|
||||||
<!-- categories -->
|
<!-- categories -->
|
||||||
@@ -123,11 +115,9 @@ script_includes:
|
|||||||
<div class="post-meta mb-3">
|
<div class="post-meta mb-3">
|
||||||
<i class="far fa-folder-open fa-fw me-1"></i>
|
<i class="far fa-folder-open fa-fw me-1"></i>
|
||||||
{% for category in page.categories %}
|
{% for category in page.categories %}
|
||||||
<a
|
<a href="{{ site.baseurl }}/categories/{{ category | slugify | url_encode }}/">{{ category }}</a>
|
||||||
href="{{ site.baseurl }}/categories/{{ category | slugify | url_encode }}/"
|
{%- unless forloop.last -%},{%- endunless -%}
|
||||||
>{{ category }}</a
|
{% endfor %}
|
||||||
>
|
|
||||||
{%- unless forloop.last -%},{%- endunless -%} {% endfor %}
|
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
@@ -147,16 +137,21 @@ script_includes:
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<div
|
<div
|
||||||
class="post-tail-bottom d-flex justify-content-between align-items-center mt-5 pb-2"
|
class="
|
||||||
|
post-tail-bottom
|
||||||
|
d-flex justify-content-between align-items-center mt-5 pb-2
|
||||||
|
"
|
||||||
>
|
>
|
||||||
<div class="license-wrapper">
|
<div class="license-wrapper">
|
||||||
{% if site.data.locales[lang].copyright.license.template %} {% capture
|
{% if site.data.locales[lang].copyright.license.template %}
|
||||||
_replacement %}
|
{% capture _replacement %}
|
||||||
<a href="{{ site.data.locales[lang].copyright.license.link }}">
|
<a href="{{ site.data.locales[lang].copyright.license.link }}">
|
||||||
{{ site.data.locales[lang].copyright.license.name }}
|
{{ site.data.locales[lang].copyright.license.name }}
|
||||||
</a>
|
</a>
|
||||||
{% endcapture %} {{ site.data.locales[lang].copyright.license.template |
|
{% endcapture %}
|
||||||
replace: ':LICENSE_NAME', _replacement }} {% endif %}
|
|
||||||
|
{{ site.data.locales[lang].copyright.license.template | replace: ':LICENSE_NAME', _replacement }}
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% include post-sharing.html lang=lang %}
|
{% include post-sharing.html lang=lang %}
|
||||||
|
|||||||
146
_posts/mathematics/2025-08-22-group-structure-of-z-2n-z-star.md
Normal file
146
_posts/mathematics/2025-08-22-group-structure-of-z-2n-z-star.md
Normal file
@@ -0,0 +1,146 @@
|
|||||||
|
---
|
||||||
|
share: true
|
||||||
|
toc: true
|
||||||
|
math: true
|
||||||
|
categories:
|
||||||
|
- Mathematics
|
||||||
|
path: _posts/mathematics
|
||||||
|
tags:
|
||||||
|
- math
|
||||||
|
- cryptography
|
||||||
|
title: Group Structure of $(\mathbb{Z}/2^n \mathbb{Z})^*$
|
||||||
|
date: 2025-08-22
|
||||||
|
github_title: 2025-08-22-group-structure-of-z-2n-z-star
|
||||||
|
---
|
||||||
|
|
||||||
|
To compute the rotation automorphism homomorphically, we use the fact that $(\Z/2^n\Z)^* \simeq \span{-1, 5}$. I couldn't find a clear proof of this result online, so I just accepted the fact although it wasn't very satisfying.
|
||||||
|
|
||||||
|
After more than a year, I got a chance to revisit the rotation automorphism and I figured that I should clear things up once and for all. So I decided to compile a proof, drawn from many sources.
|
||||||
|
|
||||||
|
## Theorem
|
||||||
|
|
||||||
|
> **Theorem 1.** $(\Z/2^n \Z)^*$ is the direct product of a cyclic group of order $2$ and cyclic group of order $2^{n-2}$, for all $n \geq 2$.
|
||||||
|
|
||||||
|
The above theorem is from Corollary 20 (2) of Section 9.5 in Abstract Algebra, 3rd Edition, Dummit and Foote.
|
||||||
|
|
||||||
|
> **Theorem 2.** $(\Z/2^n\Z)^* \simeq \span{-1, 5}$ for $n \geq 3$.
|
||||||
|
|
||||||
|
## Observations
|
||||||
|
|
||||||
|
### Order of $5$ Modulo $2^n$
|
||||||
|
|
||||||
|
> **Proposition.** $5^{2^{n-3}} \equiv 1 + 2^{n-1} \pmod {2^n}$ for $n \geq 3$.
|
||||||
|
|
||||||
|
*Proof*. This is an easy proof with induction. Omitted.
|
||||||
|
|
||||||
|
> **Lemma.** $5$ has order $2^{n-2}$ in $(\Z/2^n \Z)^*$, for $n \geq 2$.
|
||||||
|
|
||||||
|
*Proof*. We will use strong induction. For $n = 2, 3$, the lemma can be checked by direct computation. Now assume that the order of $5$ is $2^{k-2}$ in $(\Z/2^k\Z)^*$, for all $3 \leq k \leq n$.
|
||||||
|
|
||||||
|
Let $r$ be the order of $5$ modulo $2^{n+1}$. Then $2^{n-2} \mid r$. This is from the fact that
|
||||||
|
|
||||||
|
$$
|
||||||
|
5^r \equiv 1 \pmod {2^{n+1}} \implies 5^r \equiv 1 \pmod {2^n}.
|
||||||
|
$$
|
||||||
|
|
||||||
|
Therefore $r$ must be a multiple of $2^{n-2}$. (order of $5$ modulo $2^n$) But from the above proposition, $5^{2^{n-2}} \equiv 1 + 2^n \pmod {2^{n+1}}$, so $r \neq 2^{n-2}$. The next candidate of $r$ is $2^{n-1}$ since it should be a multiple of $2^{n-2}$. Observe that
|
||||||
|
|
||||||
|
$$
|
||||||
|
5^{2^{n-1}} = \paren{5^{2^{n-2}}}^2 = (1 + 2^{n})^2 \equiv 1 \pmod {2^{n+1}},
|
||||||
|
$$
|
||||||
|
|
||||||
|
completing the proof.
|
||||||
|
|
||||||
|
### Group is Not Cyclic
|
||||||
|
|
||||||
|
> **Proposition.** Let $G = \span{x}$ be a cyclic group of finite order $n < \infty$. For each divisor $a$ of $n$, there exists a unique subgroup of $G$ with order $a$.
|
||||||
|
|
||||||
|
*Proof*. Since $a \mid n$, set $d = n /a$. Then $\span{x^d}$ is a subgroup of order $a$, showing existence.
|
||||||
|
|
||||||
|
For uniqueness, suppose $H \neq \span{x^d}$ is another subgroup of $G$ with order $a$. Since subgroups of cyclic groups are also cyclic, $H = \span{x^k}$ where $k$ is the smallest positive integer with $x^k \in H$. Then from
|
||||||
|
|
||||||
|
$$
|
||||||
|
\frac{n}{d} = a = \abs{H} = \frac{n}{\gcd(n, k)},
|
||||||
|
$$
|
||||||
|
|
||||||
|
$d = \gcd(n, k)$. So $k$ is a multiple of $d$, resulting in $x^k \in \span{x^d}$. Therefore, $H \leq \span{x^d}$, but the two groups have the same order, so $H = \span{x^d}$.
|
||||||
|
|
||||||
|
> **Lemma.** $(\Z/2^n \Z)^*$ is not cyclic for any $n \geq 3$.
|
||||||
|
|
||||||
|
*Proof*. $(\Z/2^n\Z)^*$ has two distinct subgroups of order $2$. For $n \geq 3$,
|
||||||
|
|
||||||
|
$$
|
||||||
|
(2^n - 1)^2 \equiv (-1)^2 \equiv 1 \pmod {2^n}
|
||||||
|
$$
|
||||||
|
|
||||||
|
and
|
||||||
|
|
||||||
|
$$
|
||||||
|
(2^{n-1}-1)^2 = 2^{2n-2} - 2^n + 1 \equiv 1 \pmod {2^n}.
|
||||||
|
$$
|
||||||
|
|
||||||
|
Both $2^n-1$ and $2^{n-1} - 1$ have order $2$ modulo $2^n$ and they are distinct since $n \geq 3$. By the above proposition, $(\Z/2^n\Z)^*$ cannot be cyclic.
|
||||||
|
|
||||||
|
## Proof of Theorem 1
|
||||||
|
|
||||||
|
*Proof*. $(\Z/2^n\Z)^*$ is a finitely generated abelian group, so the fundamental theorem of finitely generated abelian groups applies here. We know that group has order $2^{n-1}$, and from the above results,
|
||||||
|
|
||||||
|
- $(\Z/2^n \Z)^*$ has an element of order $2^{n-2}$,
|
||||||
|
- $(\Z/2^n \Z)^*$ is not cyclic for $n \geq 3$.
|
||||||
|
|
||||||
|
Thus, for $n \geq 3$, the only possible case is $(\Z/2^n\Z)^* \simeq \Z _ 2 \times \Z_{2^{n-2}}$. As for $n = 2$, $(\Z/4\Z)^* \simeq \Z_2 \times \Z_1$ is pretty obvious.
|
||||||
|
|
||||||
|
*Note*. I'm still looking for an elementary proof that doesn't use the fundamental theorem. This sort of feels like nuking a mosquito.
|
||||||
|
|
||||||
|
## More Observations
|
||||||
|
|
||||||
|
> **Lemma.** Suppose that $H$ and $K$ are normal subgroups of $G$ and $H \cap K = \braces{1}$. Then $HK \simeq H \times K$.
|
||||||
|
|
||||||
|
*Proof*. Construct an isomorphism $\varphi : H \times K \ra HK$ such that $(h, k) \mapsto hk$.
|
||||||
|
|
||||||
|
Since $H, K \unlhd G$, observe that $hkh\inv k \inv \in K \cap H = \braces{1}$ and $hk = kh$. Therefore,
|
||||||
|
|
||||||
|
$$
|
||||||
|
\varphi(h, k) \cdot \varphi(h',k') = hkh'k' = hh' kk' = \varphi\paren{(h, k)\cdot (h', k')}
|
||||||
|
$$
|
||||||
|
|
||||||
|
and $\varphi$ is a homomorphism.
|
||||||
|
|
||||||
|
Next, if $\varphi(h, k) = hk = 1$, we have $h = k\inv \in H\cap K = \braces{1}$. Then $h = k = 1$, showing that $\ker \varphi$ is trivial and $\varphi$ is injective.
|
||||||
|
|
||||||
|
Surjectivity of $\varphi$ is trivial. $\varphi$ is an isomorphism and $HK \simeq H \times K$.
|
||||||
|
|
||||||
|
> **Proposition.** As subgroups of $(\Z/2^n\Z)^*$, $\span{-1} \cap \span{5} = \braces{1}$ for $n \geq 3$.
|
||||||
|
|
||||||
|
*Proof*. It suffices to show that $-1 \notin \span{{5}}$. Suppose that $-1 \in \span{5}$. Since $\span{5}$ is cyclic, it has a unique element of order $2$. Since $5$ has order $2^{n-2}$, it must be the case that $-1 \equiv 5^{2^{n-3}} \pmod {2^n}$.
|
||||||
|
|
||||||
|
Then we have
|
||||||
|
|
||||||
|
$$
|
||||||
|
-1 \equiv 5^{2^{n-3}} \equiv 1 + 2^{n-1} \pmod {2^n},
|
||||||
|
$$
|
||||||
|
|
||||||
|
which gives $2^{n-1} + 2 \equiv 0 \pmod {2^n}$. But for $n \geq 3$, this is impossible since $0 < 2^{n-1} + 2 < 2^n$. Contradiction.
|
||||||
|
|
||||||
|
*Note*. If $-1 \in \span{5}$, then maybe $5$ would have generated the whole group. But the group isn't cyclic, so we have a contradiction?
|
||||||
|
|
||||||
|
## Proof of Theorem 2
|
||||||
|
|
||||||
|
*Proof*. Since we are dealing with commutative groups, all subgroups are normal. We have $\span{-1}, \span{5} \unlhd (\Z/2^n\Z)^*$ and $\span{-1} \cap \span{5} = \braces{1}$. Therefore,
|
||||||
|
|
||||||
|
$$
|
||||||
|
(\Z/2^n\Z)^* \simeq \Z_2 \times \Z_{2^{n-2}} = \span{-1} \times \span{5} \simeq \span{-1}\span{5}.
|
||||||
|
$$
|
||||||
|
|
||||||
|
This means that we can uniquely write all elements of $(\Z/2^n\Z)^*$ as $(-1)^a 5^b$ for ${} 0 \leq a < 2 {}$, $0 \leq b < 2^{n-2}$. From commutativity, this exactly equals the subgroup generated by $-1$ and $5$, which is $\span{-1, 5}$. This concludes the proof.
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
The theorem wasn't so trivial after all, but I'm still happy to have resolved a long overdue task.
|
||||||
|
|
||||||
|
## References
|
||||||
|
|
||||||
|
- My notes taken from abstract algebra class
|
||||||
|
- <https://math.stackexchange.com/q/459815>
|
||||||
|
- <https://math.stackexchange.com/q/3881641>
|
||||||
|
- <https://math.stackexchange.com/a/4910312/329909>
|
||||||
@@ -1,16 +1,15 @@
|
|||||||
---
|
---
|
||||||
# the default layout is 'page'
|
# the default layout is 'page'
|
||||||
icon: fas fa-info-circle
|
icon: fas fa-info-circle
|
||||||
order: 4
|
order: 0
|
||||||
---
|
---
|
||||||
|
|
||||||
# Sungchan Yi
|
# Sungchan Yi
|
||||||
|
|
||||||
- Last updated: 2025-07-18
|
- Last updated: 2025-11-20
|
||||||
- Email: [calofmijuck at snu dot ac dot kr](mailto:calofmijuck@snu.ac.kr)
|
- Email: [calofmijuck at snu dot ac dot kr](mailto:calofmijuck@snu.ac.kr)
|
||||||
- [CV (pdf)](https://zxcvber.com/files/cv-eng.pdf)
|
|
||||||
|
|
||||||
**Research Interests**: computer architecture, hardware-software co-design, security, formal verification
|
**Research Interests**: computer architecture, hardware-software co-design, cryptography, formal verification
|
||||||
|
|
||||||
## Education
|
## Education
|
||||||
|
|
||||||
@@ -33,7 +32,7 @@ order: 4
|
|||||||
|
|
||||||
[**Cryptography & Privacy Lab**](https://crypto.snu.ac.kr), *Undergraduate Research Assistant* (Jan. 2024 ~ Feb. 2024)
|
[**Cryptography & Privacy Lab**](https://crypto.snu.ac.kr), *Undergraduate Research Assistant* (Jan. 2024 ~ Feb. 2024)
|
||||||
|
|
||||||
- Implemented BFV homomorphic encryption scheme and its bootstrapping over a large message space
|
- Implemented generalized BFV scheme with bootstrapping based on CKKS bootstrapping techniques
|
||||||
- Analyzed automorphism group of the plaintext space and determined their effects on ciphertext
|
- Analyzed automorphism group of the plaintext space and determined their effects on ciphertext
|
||||||
|
|
||||||
[**Software Foundations Lab**](https://sf.snu.ac.kr), *Undergraduate Research Assistant* (Jul. 2023 ~ Oct. 2023)
|
[**Software Foundations Lab**](https://sf.snu.ac.kr), *Undergraduate Research Assistant* (Jul. 2023 ~ Oct. 2023)
|
||||||
@@ -60,12 +59,13 @@ order: 4
|
|||||||
|
|
||||||
## Publications
|
## Publications
|
||||||
|
|
||||||
|
- **Sungchan Yi**, Keun Soo Lim, Hoyeon Jo, Sungjun Jung, Minwoo Kwak, Dongoh Kim, Luigi Cussigh, Seong Hoon Seo, Jinkyu Jeong, Jae W. Lee, "Software-Defined Manycores via Hardware-Managed Preemptive Coroutine Scheduling", _International Symposium on Computer Architecture (ISCA)_, 2026. (Submitted)
|
||||||
- **[ACCV'24]** Soosung Kim, Yeonhong Park, Hyunseung Lee, **Sungchan Yi**, and Jae W. Lee, "ReLUifying Smooth Functions: Low-Cost Knowledge Distillation to Obtain High-Performance ReLU Networks", _Asian Conference on Computer Vision (ACCV)_, Hanoi, Vietnam, December 2024. ([PDF](https://arc.snu.ac.kr/pubs/ACCV24_ReLU.pdf))
|
- **[ACCV'24]** Soosung Kim, Yeonhong Park, Hyunseung Lee, **Sungchan Yi**, and Jae W. Lee, "ReLUifying Smooth Functions: Low-Cost Knowledge Distillation to Obtain High-Performance ReLU Networks", _Asian Conference on Computer Vision (ACCV)_, Hanoi, Vietnam, December 2024. ([PDF](https://arc.snu.ac.kr/pubs/ACCV24_ReLU.pdf))
|
||||||
- **Sungchan Yi**, "Secure IAM on AWS with Multi-Account Strategy", _Undergraduate Thesis_, December 2023. **Received best undergraduate paper award.** ([PDF](https://arxiv.org/pdf/2501.02203))
|
- **Sungchan Yi**, "Secure IAM on AWS with Multi-Account Strategy", _Undergraduate Thesis_, December 2023. **Outstanding Undergraduate Thesis Award.** ([PDF](https://arxiv.org/pdf/2501.02203))
|
||||||
|
|
||||||
## Honors & Awards
|
## Honors & Awards
|
||||||
|
|
||||||
**Best Undergraduate Paper Award**
|
**Outstanding Undergraduate Thesis Award**
|
||||||
|
|
||||||
- Title: Secure IAM on AWS using Multi-Account Strategy
|
- Title: Secure IAM on AWS using Multi-Account Strategy
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
---
|
---
|
||||||
layout: archives
|
layout: archives
|
||||||
icon: fas fa-archive
|
icon: fas fa-archive
|
||||||
order: 3
|
order: 4
|
||||||
---
|
---
|
||||||
|
|||||||
13
_tabs/blog.md
Normal file
13
_tabs/blog.md
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
---
|
||||||
|
title: Blog
|
||||||
|
layout: home
|
||||||
|
icon: fas fa-pencil
|
||||||
|
order: 1
|
||||||
|
# pagination:
|
||||||
|
# enabled: true
|
||||||
|
# per_page: 10
|
||||||
|
# collection: posts
|
||||||
|
# permalink: '/page/:num/'
|
||||||
|
---
|
||||||
|
|
||||||
|
This page serves as the site "Home" (posts listing). It is now a tab in the sidebar.
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
---
|
---
|
||||||
layout: categories
|
layout: categories
|
||||||
icon: fas fa-stream
|
icon: fas fa-stream
|
||||||
order: 1
|
order: 2
|
||||||
---
|
---
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
---
|
---
|
||||||
layout: tags
|
layout: tags
|
||||||
icon: fas fa-tags
|
icon: fas fa-tags
|
||||||
order: 2
|
order: 3
|
||||||
---
|
---
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
---
|
---
|
||||||
---
|
---
|
||||||
|
|
||||||
@import 'main
|
@use 'main
|
||||||
{%- if jekyll.environment == 'production' -%}
|
{%- if jekyll.environment == 'production' -%}
|
||||||
.bundle
|
.bundle
|
||||||
{%- endif -%}
|
{%- endif -%}
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 187 KiB After Width: | Height: | Size: 848 KiB |
13
index.html
13
index.html
@@ -1,4 +1,13 @@
|
|||||||
---
|
---
|
||||||
layout: home
|
layout: page
|
||||||
# Index page
|
title: About
|
||||||
---
|
---
|
||||||
|
|
||||||
|
{%- assign about_tab = site.tabs | where: "url", "/about/" | first -%}
|
||||||
|
|
||||||
|
{%- if about_tab -%}
|
||||||
|
{{ about_tab.content }}
|
||||||
|
{%- else -%}
|
||||||
|
<!-- Fallback: no about tab found -->
|
||||||
|
<h1>About</h1>
|
||||||
|
{%- endif -%}
|
||||||
|
|||||||
Reference in New Issue
Block a user