Compare commits

...

5 commits

Author SHA1 Message Date
2acc1aae3c Merge pull request 'feat: dark mode support and container improvements' (#11) from develop into main
Reviewed-on: #11
2026-02-11 04:22:02 +00:00
b2ca03d2c7 Merge branch 'main' into develop 2026-02-11 04:21:12 +00:00
62aa324e16 feat: implement dark mode support
- Introduce CSS variables for theming in `global.css`
- Add `ThemeIcon` component for toggling light/dark modes
- Integrate theme initialization script in `BaseLayout` to prevent FOUC
- Update navigation, buttons, and tag styles for dark mode compatibility
- Configure Shiki for dual-theme syntax highlighting in `astro.config.mjs`
- Adjust link colors to accessible blue shades for both themes
2026-02-11 13:15:03 +09:00
e357f2ed68 feat(container): add SSH server and rassumfrassum for LSP multiplexing
- Install openssh-server and create SSH infrastructure in container
- Install rassumfrassum (LSP multiplexer) via Python venv
- Expose SSH port 2222 and mount user public key for TRAMP access
- Update compose.yml to map port 2222 and mount SSH_PUBKEY_PATH
- Document SSH setup and TRAMP /ssh: access method in README
2026-02-11 12:25:57 +09:00
9c0ae88705 Remove flake.nix and flake.lock 2026-02-03 20:56:26 +09:00
16 changed files with 235 additions and 148 deletions

View file

@ -3,6 +3,15 @@ FROM node:lts-bookworm-slim
WORKDIR /app
# SSH server for TRAMP /ssh: access
RUN apt-get update && apt-get install -y --no-install-recommends openssh-server python3-venv python3-pip \
&& rm -rf /var/lib/apt/lists/* \
&& mkdir -p /var/run/sshd /root/.ssh \
&& chmod 700 /root/.ssh \
&& ssh-keygen -A \
&& python3 -m venv /opt/rass && /opt/rass/bin/pip install --no-cache-dir rassumfrassum \
&& ln -s /opt/rass/bin/rass /usr/local/bin/rass
# Install dependencies first (better layer caching)
COPY package.json package-lock.json ./
RUN npm ci
@ -10,6 +19,6 @@ RUN npm ci
# Copy the rest of the project
COPY . .
EXPOSE 4321
EXPOSE 4321 22
CMD ["npm","run","dev","--","--host","0.0.0.0","--port","4321"]
CMD ["/bin/sh","-lc","/usr/sbin/sshd && npm run dev -- --host 0.0.0.0 --port 4321"]

View file

@ -44,11 +44,14 @@ This repo can be developed inside a Podman container to keep your host clean and
### Using podman compose
```sh
export SSH_PUBKEY_PATH="$HOME/.ssh/id_ed25519.pub"
podman compose up --build
```
On WSL2/rootless Podman, if you hit netavark/nftables errors, this repo defaults to `slirp4netns` via `network_mode` in `compose.yml`.
If you want SSH access (for Emacs TRAMP `/ssh:`), `SSH_PUBKEY_PATH` must point to your public key and port 2222 will be exposed for SSH.
Then open: http://localhost:4321
Run other commands in the running container:

View file

@ -6,5 +6,14 @@ import preact from "@astrojs/preact";
// https://astro.build/config
export default defineConfig({
site: "https://blog.n-daisuke897.com/",
integrations: [preact()]
integrations: [preact()],
markdown: {
shikiConfig: {
themes: {
light: 'github-light',
dark: 'github-dark',
},
wrap: true,
},
},
});

View file

@ -7,10 +7,12 @@ services:
network_mode: "slirp4netns:allow_host_loopback=true"
ports:
- "4321:4321"
- "2222:22"
volumes:
- .:/app
- node_modules:/app/node_modules
command: npm run dev -- --host 0.0.0.0 --port 4321
- ${SSH_PUBKEY_PATH}:/root/.ssh/authorized_keys:ro
command: /bin/sh -lc "/usr/sbin/sshd && npm run dev -- --host 0.0.0.0 --port 4321"
volumes:
node_modules:

61
flake.lock generated
View file

@ -1,61 +0,0 @@
{
"nodes": {
"flake-utils": {
"inputs": {
"systems": "systems"
},
"locked": {
"lastModified": 1731533236,
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1769598131,
"narHash": "sha256-e7VO/kGLgRMbWtpBqdWl0uFg8Y2XWFMdz0uUJvlML8o=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "fa83fd837f3098e3e678e6cf017b2b36102c7211",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixos-25.11",
"repo": "nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"flake-utils": "flake-utils",
"nixpkgs": "nixpkgs"
}
},
"systems": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
}
},
"root": "root",
"version": 7
}

View file

@ -1,23 +0,0 @@
{
description = "Naputo blog devshell";
inputs = {
# Use stable nixpkgs for a more predictable toolchain.
nixpkgs.url = "github:NixOS/nixpkgs/nixos-25.11";
flake-utils.url = "github:numtide/flake-utils";
};
outputs = { self, nixpkgs, flake-utils }:
flake-utils.lib.eachDefaultSystem (system:
let
pkgs = import nixpkgs { inherit system; };
in
{
devShells.default = pkgs.mkShell {
packages = with pkgs; [
forgejo-cli
nodejs
];
};
});
}

View file

@ -12,11 +12,19 @@ const { title, url, datetime } = Astro.props;
font-size: 14px;
color: #666;
}
:global(.dark) .new-article-date {
color: #94a3b8;
}
.new-article-title {
font-size: 18px;
font-weight: bold;
color: #333f;
color: var(--text-color);
text-decoration: none;
}
.new-article-title:hover {
text-decoration: underline;
}
/* Removed specific dark mode color to inherit var(--text-color) */
</style>
<div class="new-article">
<div class="new-article-date">{datetime}</div>

View file

@ -9,3 +9,11 @@ import Navigation from "./Navigation.astro";
<Navigation />
</nav>
</header>
<style>
nav {
display: flex;
align-items: center;
flex-wrap: wrap;
}
</style>

View file

@ -1,8 +1,9 @@
---
import ThemeIcon from "./ThemeIcon.astro";
---
<div class="nav-links">
<ThemeIcon />
<a href="/">Home</a>
<a href="/about/">About</a>
<a href="/blog/">Articles</a>

View file

@ -0,0 +1,36 @@
---
---
<button id="themeToggle">
<svg width="24px" height="24px" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path class="sun" d="M6.76 4.84l-1.8-1.79-1.41 1.41 1.79 1.79 1.42-1.41zM4 10.5H1v2h3v-2zm9-9.95h-2V3.5h2V.55zm7.45 3.91l-1.41-1.41-1.79 1.79 1.41 1.41 1.79-1.79zm-3.21 13.7l1.79 1.8 1.41-1.41-1.8-1.79-1.4 1.4zM20 10.5v2h3v-2h-3zm-8-5c-3.31 0-6 2.69-6 6s2.69 6 6 6 6-2.69 6-6-2.69-6-6-6zm-1 16.95h2V19.5h-2v2.95zm-7.45-3.91l1.41 1.41 1.79-1.8-1.41-1.41-1.79 1.8z" />
<path class="moon" d="M12 3c-4.97 0-9 4.03-9 9s4.03 9 9 9 9-4.03 9-9c0-.46-.04-.92-.1-1.36-.98 1.37-2.58 2.27-4.4 2.27-3.03 0-5.5-2.47-5.5-5.5 0-1.82.89-3.42 2.26-4.4-.44-.06-.9-.1-1.36-.1z" />
</svg>
</button>
<style>
#themeToggle {
border: 0;
background: none;
cursor: pointer;
display: inline-flex;
align-items: center;
padding: 0 10px;
}
.sun { fill: transparent; }
.moon { fill: transparent; }
:global(.dark) .moon { fill: white; }
:global(html:not(.dark)) .sun { fill: black; }
</style>
<script is:inline>
const handleToggleClick = () => {
const element = document.documentElement;
element.classList.toggle("dark");
const isDark = element.classList.contains("dark");
localStorage.setItem("theme", isDark ? "dark" : "light");
}
document.getElementById("themeToggle").addEventListener("click", handleToggleClick);
</script>

View file

@ -12,6 +12,22 @@ const { pageTitle } = Astro.props;
<meta name="viewport" content="width=device-width" />
<meta name="generator" content={Astro.generator} />
<title>{pageTitle + " | Naputo"}</title>
<script is:inline>
const theme = (() => {
if (typeof localStorage !== 'undefined' && localStorage.getItem('theme')) {
return localStorage.getItem('theme');
}
if (window.matchMedia('(prefers-color-scheme: dark)').matches) {
return 'dark';
}
return 'light';
})();
if (theme === 'dark') {
document.documentElement.classList.add('dark');
} else {
document.documentElement.classList.remove('dark');
}
</script>
</head>
<body>
<Header />

View file

@ -30,7 +30,11 @@ const optionsForDate = {
<style>
a {
color: #00539f;
color: #3b82f6;
}
:global(.dark) a {
color: #60a5fa;
}
.tags {
@ -47,6 +51,11 @@ const optionsForDate = {
background-color: #f8fcfd;
}
:global(.dark) .tag {
background-color: #1e293b;
border-color: #475569;
}
.markdown-content {
font-family: sans-serif;
font-size: 1.1rem;

View file

@ -77,15 +77,28 @@ const pageTitle = "Articles";
text-decoration: none;
transition: background-color 0.3s ease, transform 0.3s ease;
}
:global(.dark) nav.pagination a,
:global(.dark) nav.pagination span {
background-color: #1e293b;
color: #f8fafc;
border: 1px solid #334155;
}
nav.pagination a:hover {
background-color: #cacaca;
transform: translateY(-2px);
}
:global(.dark) nav.pagination a:hover {
background-color: #334155;
}
nav.pagination a.active {
background-color: #cacaca;
font-weight: bold;
pointer-events: none;
}
:global(.dark) nav.pagination a.active {
background-color: #334155;
color: #f8fafc;
}
@media (max-width: 600px) {
nav.pagination {
flex-direction: column;

View file

@ -8,55 +8,63 @@ const sortedPosts = allPosts.sort((a, b) => b.data.pubDate.valueOf() - a.data.pu
---
<style>
.new-articles {
text-align: start;
margin-bottom: 2rem;
}
.header-new-posts {
text-align: center;
}
.button-container {
display: flex;
justify-content: center;
align-items: center;
margin-top: 2rem;
}
.list-of-articles-button {
display: inline-block;
background-color: #e0e0e0;
color: #333333;
font-size: 1rem;
font-weight: 600;
padding: 12px 24px;
border: none;
border-radius: 6px;
text-decoration: none;
transition: background-color 0.3s ease, transform 0.3s ease;
cursor: pointer;
}
.list-of-articles-button:hover {
background-color: #cacaca;
}
.new-articles {
text-align: start;
margin-bottom: 2rem;
}
.header-new-posts {
text-align: center;
}
.button-container {
display: flex;
justify-content: center;
align-items: center;
margin-top: 2rem;
}
.list-of-articles-button {
display: inline-block;
background-color: #e0e0e0;
color: #333333;
font-size: 1rem;
font-weight: 600;
padding: 12px 24px;
border: none;
border-radius: 6px;
text-decoration: none;
transition: background-color 0.3s ease, transform 0.3s ease;
cursor: pointer;
}
:global(.dark) .list-of-articles-button {
background-color: #1e293b;
color: #f8fafc;
border: 1px solid #334155;
}
.list-of-articles-button:hover {
background-color: #cacaca;
}
:global(.dark) .list-of-articles-button:hover {
background-color: #334155;
}
</style>
<BaseLayout pageTitle={pageTitle}>
<h3 class="header-new-posts">New Posts</h3>
<div class="new-articles">
{
sortedPosts.slice(0, 5).map((post: any) => (
<BlogPost
url={`/posts/${post.id}/`}
title={post.data.title}
datetime={post.data.pubDate.toLocaleDateString("en-US", {
year: "numeric",
month: "long",
day: "numeric",
})}
/>
))
}
</div>
<h3 class="header-new-posts">New Posts</h3>
<div class="new-articles">
{
sortedPosts.slice(0, 5).map((post: any) => (
<BlogPost
url={`/posts/${post.id}/`}
title={post.data.title}
datetime={post.data.pubDate.toLocaleDateString("en-US", {
year: "numeric",
month: "long",
day: "numeric",
})}
/>
))
}
</div>
<div class="button-container">
<a href="/blog/" class="list-of-articles-button">List of Article</a>
</div>
<div class="button-container">
<a href="/blog/" class="list-of-articles-button">List of Article</a>
</div>
</BaseLayout>

View file

@ -20,7 +20,7 @@ const pageTitle = "Tag List";
<style>
a {
color: #00539f;
color: #3b82f6;
}
.tags {
@ -36,4 +36,13 @@ const pageTitle = "Tag List";
font-size: 1.15em;
background-color: #f8fcfd;
}
:global(.dark) .tag {
background-color: #1e293b;
border-color: #475569;
}
:global(.dark) .tag a {
color: #f8fafc;
}
</style>

View file

@ -1,5 +1,19 @@
/* Dark Mode support */
:root {
--bg-color: #f1f5f9;
--text-color: #333333;
--nav-hover: #e0e0e0;
}
html.dark {
--bg-color: #0f172a;
--text-color: #f8fafc;
--nav-hover: #1e293b;
}
html {
background-color: #f1f5f9;
background-color: var(--bg-color);
color: var(--text-color);
font-family: sans-serif;
}
@ -21,6 +35,14 @@ h1 {
}
/* nav styles */
a {
color: #3b82f6; /* Blue 500 - vivid but readable on light */
}
html.dark a {
color: #60a5fa; /* Blue 400 - lighter and softer for dark mode */
}
.hamburger {
padding-right: 20px;
cursor: pointer;
@ -31,17 +53,22 @@ h1 {
width: 32px;
height: 4px;
margin-bottom: 8px;
background-color: #333333;
background-color: var(--text-color);
}
.nav-links {
width: 100%;
display: flex;
display: none;
justify-content: center;
align-items: center;
margin: 0;
}
.nav-links.expanded {
display: flex;
flex-direction: column;
}
.nav-links a {
display: block;
text-align: center;
@ -51,12 +78,12 @@ h1 {
font-weight: bold;
text-transform: uppercase;
border-radius: 4px;
color: #333333;
color: var(--text-color);
}
.nav-links a:hover,
.nav-links a:focus {
background-color: #e0e0e0;
background-color: var(--nav-hover);
}
.nav-links img {
@ -66,14 +93,16 @@ h1 {
display: inline-block;
}
.expanded {
display: unset;
html.dark .nav-links img {
filter: invert(1) hue-rotate(180deg);
}
/* .expanded removed as it is handled by .nav-links.expanded */
@media screen and (min-width: 636px) {
.nav-links {
margin-left: 5em;
display: block;
display: flex;
position: static;
width: auto;
background: none;
@ -94,3 +123,14 @@ pre,
code {
font-family: "JetBrains Mono", "Fira Code", "Menlo", "Consolas", monospace;
}
pre {
padding: 1rem;
border-radius: 0.5rem;
overflow-x: auto;
}
html.dark pre {
background-color: #1e293b !important;
}