Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 4 additions & 9 deletions .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,13 @@
// Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile
"image": "mcr.microsoft.com/devcontainers/javascript-node:24-bookworm",
"features": {
"ghcr.io/devcontainers/features/github-cli:1": {
"installDirectlyFromGitHubRelease": true,
"version": "latest"
},
"ghcr.io/devcontainers/features/github-cli:1": {},
"ghcr.io/devcontainers/features/node:1": {
"nodeGypDependencies": true,
"installYarnUsingApt": true,
"version": "24.14.0",
"pnpmVersion": "latest",
"nvmVersion": "latest"
"version": "24.14.0"
}
}
},

// Features to add to the dev container. More info: https://containers.dev/features.
// "features": {},
Expand All @@ -25,7 +20,7 @@
// "forwardPorts": [],

// Use 'postCreateCommand' to run commands after the container is created.
// "postCreateCommand": "yarn install",
"postCreateCommand": "yarn"

// Configure tool-specific properties.
// "customizations": {},
Expand Down
22 changes: 19 additions & 3 deletions CLAUDE.md → AGENTS.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,22 @@
# CLAUDE.md
# AGENTS.md

此文件為 Claude Code (claude.ai/code) 在處理此專案時的開發指引。
此文件為各種 AI Coding Agent 在處理此專案時的開發指引。

## Agent 必讀規範

- 只使用 Yarn,禁止使用 npm(安裝與執行腳本皆同)。
- 不要修改 `public/` 內容:該目錄為建置產物,應由 `yarn build` 生成。
- 變更應聚焦需求本身,避免順手重構不相干檔案。
- 修改 JSON 資料時務必維持既有 schema,避免破壞 Gatsby build。
- 提交前一定要執行 `yarn prettier` 以統一程式碼風格。
- 涉及 API 端點(`functions/api/`)時,需保留 CORS 行為與既有回傳格式相容性。

## Agent 工作流程

1. 先讀需求,再找出受影響檔案與資料來源(頁面、模板、JSON、API)。
2. 優先做最小變更,避免改動既有命名與資料路徑。
3. 完成修改後執行格式化與必要建置檢查。
4. 回報變更檔案、核心差異、驗證結果與未完成風險。

## 專案概述

Expand Down Expand Up @@ -47,7 +63,7 @@ yarn prettier
### 專案結構

```
├── CLAUDE.md # 開發指引文件
├── AGENTS.md # Agent 開發指引文件
├── README.md # 專案說明文件
├── package.json # 相依套件與腳本
├── gatsby-config.js # Gatsby 設定檔
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@

## Quick start

先安裝依賴
先安裝依賴(如使用 Dev Container 可省略)

```bash
yarn
Expand Down
25 changes: 12 additions & 13 deletions src/pages/404.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,39 +8,38 @@ const NotFoundPage = () => {

// 開始倒數
React.useEffect(() => {
let id = setInterval(() => {
setCountDown(countDown - 1);
const id = setInterval(() => {
setCountDown((prev) => prev - 1);
}, 1000);
return () => clearInterval(id);
});
}, []);

// 倒數結束回首頁
React.useEffect(() => {
if (countDown <= 0) {
window.location.href = "/";
}
});
}, [countDown]);

return (
<div
className="h-screen w-screen content-center items-center justify-center m-0"
style={{ background: "#4d689b" }}
>
<AppHeader title="iOS Club - 404 Opps" />
<AppHeader title="iOS Club - 404 Oops" />
<div className="h-full w-full flex flex-col justify-center items-center p-20">
<div className="w-full flex flex-col md:flex-row items-center justify-center space-x-4 space-y-3">
<LogoSvg className="h-48 w-48 md:w-32 md:h-32 text-white fill-current" />
<h2 className="text-white text-xl md:text-3xl">網頁努力建置中...</h2>
<h2 className="px-3 text-white text-xl md:text-3xl font-bold">
網頁努力建置中⋯⋯
</h2>
</div>
<p className="py-3 text-white">在 {countDown} 秒後幫您回首頁</p>
<a
href="/"
className="pt-3 underline underline-offset-2 text-white font-blod"
>
..或點我回首頁
<p className="py-3 text-white">在 {countDown} 秒後幫您回到首頁</p>
<a href="/" className="pt-3 underline underline-offset-2 text-white">
或者點我直接回首頁
</a>
<div className="mt-10">
<h2 className="text-lg text-white">404 Not Found</h2>
<h2 className="text-lg text-white font-bold">404:Page Not Found</h2>
</div>
</div>
</div>
Expand Down
7 changes: 1 addition & 6 deletions src/pages/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -314,7 +314,7 @@ const AnnouncementLiet = (props) => {
<a
className="text-blue-500 underline underline-offset-2 font-bold"
href={node.url}
target="__blank"
target="_blank"
>
{node.urlText}
</a>
Expand Down Expand Up @@ -422,11 +422,6 @@ const socialItems = [
icon: "akar-icons:discord-fill",
href: "https://discord.com/invite/z2VPCNFupv",
},
{
name: "Threads",
icon: "akar-icons--threads-fill",
href: "https://www.threads.net/@fcu.iosclub",
},
];

export const qldata = graphql`
Expand Down
16 changes: 14 additions & 2 deletions src/pages/qrcode_generator.js
Original file line number Diff line number Diff line change
@@ -1,18 +1,26 @@
import * as React from "react";
import { useState } from "react";
import { useEffect, useState } from "react";
import Footer from "../components/footer";
import Navbar from "../components/navbar";
import AppHeader from "../components/header";
import SliderButton from "../components/buttons/slider_button";
import { Icon } from "@iconify/react";

const logo = "https://i.meee.com.tw/0SiZRVA.jpg";

const QRCodeGeneratorPage = () => {
const [urlInput, setUrlInput] = useState("");
const [qrCodeUrl, setQrCodeUrl] = useState(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);

const logo = "https://i.meee.com.tw/0SiZRVA.jpg";
useEffect(() => {
return () => {
if (qrCodeUrl && qrCodeUrl.startsWith("blob:")) {
URL.revokeObjectURL(qrCodeUrl);
}
};
}, [qrCodeUrl]);

const generateQRCode = async () => {
if (!urlInput.trim()) {
Expand Down Expand Up @@ -95,6 +103,10 @@ const QRCodeGeneratorPage = () => {
const blob = await imgResponse.blob();
const url = URL.createObjectURL(blob);
setQrCodeUrl(url);
setTimeout(
() => previewRef.current?.scrollIntoView({ behavior: "smooth" }),
100,
);
} else {
throw new Error(`圖片下載失敗 (${imgResponse.status})`);
}
Expand Down
26 changes: 13 additions & 13 deletions src/pages/rfid_converter.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ const RfidConverterPage = () => {
// 分割成位元組
const bytes = [];
for (let i = 0; i < hexStr.length; i += 2) {
bytes.push(hexStr.substr(i, 2));
bytes.push(hexStr.slice(i, i + 2));
}

// 創建副本來避免修改原始數組
Expand Down Expand Up @@ -62,7 +62,7 @@ const RfidConverterPage = () => {
// 分割成位元組
const bytes = [];
for (let i = 0; i < hex.length; i += 2) {
bytes.push(hex.substr(i, 2));
bytes.push(hex.slice(i, i + 2));
}

// 創建副本來避免修改原始數組
Expand Down Expand Up @@ -125,19 +125,19 @@ const RfidConverterPage = () => {

<div className="mb-6">
<label className="block text-sm font-semibold text-gray-700 mb-2">
16進制卡號 (例: B4C5D677 或 0xB4C5D677)
16進制卡號例: B4C5D677 或 0xB4C5D677
</label>
<div className="flex gap-2">
<input
type="text"
value={hexInput}
onChange={(e) => setHexInput(e.target.value)}
placeholder="請輸入16進制卡號..."
className="flex-1 px-4 py-3 border-2 border-gray-300 rounded-lg font-mono text-lg focus:border-btnbg focus:outline-none transition-colors"
placeholder="請輸入16進制卡號⋯⋯"
className="flex-1 px-4 py-3 border-2 border-gray-300 rounded-full font-mono text-lg focus:border-btnbg focus:outline-none transition-colors"
/>
<button
onClick={clearHex}
className="px-4 py-3 border border-gray-400 text-gray-700 rounded-lg hover:bg-btnbg hover:text-white transition-colors flex items-center justify-center"
className="px-4 py-3 text-gray-700 rounded-full hover:bg-btnbg hover:text-white transition-colors flex items-center justify-center"
title="清除輸入"
>
<Icon icon="mdi:close" className="text-xl" />
Expand All @@ -156,7 +156,7 @@ const RfidConverterPage = () => {
: "bg-white border-gray-300 text-gray-600"
}`}
>
{!hexInput && "等待輸入..."}
{!hexInput && "等待輸入⋯⋯"}
{hexInput && !hexResult && "請輸入有效的16進制數值"}
{hexResult && (
<div className="space-y-2">
Expand Down Expand Up @@ -191,19 +191,19 @@ const RfidConverterPage = () => {

<div className="mb-6">
<label className="block text-sm font-semibold text-gray-700 mb-2">
10進制卡號 (例: 1234567890)
10進制卡號例: 1234567890
</label>
<div className="flex gap-2">
<input
type="text"
value={decInput}
onChange={(e) => setDecInput(e.target.value)}
placeholder="請輸入10進制卡號..."
className="flex-1 px-4 py-3 border-2 border-gray-300 rounded-lg font-mono text-lg focus:border-btnbg focus:outline-none transition-colors"
placeholder="請輸入10進制卡號⋯⋯"
className="flex-1 px-4 py-3 border-2 border-gray-300 rounded-full font-mono text-lg focus:border-btnbg focus:outline-none transition-colors"
/>
<button
onClick={clearDec}
className="px-4 py-3 border border-gray-400 text-gray-700 rounded-lg hover:bg-btnbg hover:text-white transition-colors flex items-center justify-center"
className="px-4 py-3 text-gray-700 rounded-full hover:bg-btnbg hover:text-white transition-colors flex items-center justify-center"
title="清除輸入"
>
<Icon icon="mdi:close" className="text-xl" />
Expand All @@ -222,7 +222,7 @@ const RfidConverterPage = () => {
: "bg-white border-gray-300 text-gray-600"
}`}
>
{!decInput && "等待輸入..."}
{!decInput && "等待輸入⋯⋯"}
{decInput &&
!decResult &&
"請輸入有效的10進制數值 (0-4294967295)"}
Expand Down Expand Up @@ -258,7 +258,7 @@ const RfidConverterPage = () => {
</h4>
<div className="space-y-2 text-gray-800">
<div>
<strong>軟體顯示:</strong>B4C5D677 (0xB4C5D677) →{" "}
<strong>軟體顯示:</strong>B4C5D6770xB4C5D677→{" "}
<strong>反轉後:</strong>77D6C5B4 → <strong>10進制:</strong>
2010883508
</div>
Expand Down
60 changes: 53 additions & 7 deletions src/styles/global.css
Original file line number Diff line number Diff line change
Expand Up @@ -71,11 +71,30 @@ layer(utilities);

/* 液態玻璃navbar效果 */
@layer components {
.navbar-glass,
.navbar-glass-scrolled,
.navbar-glass-mobile-open {
position: relative;
isolation: isolate;
}

.navbar-glass::before,
.navbar-glass-scrolled::before,
.navbar-glass-mobile-open::before {
content: "";
position: absolute;
inset: 0;
z-index: -1;
border-radius: inherit;
pointer-events: none;
transform: translateZ(0);
-webkit-transform: translateZ(0);
will-change: backdrop-filter;
}

/* 預設狀態 */
.navbar-glass {
background: rgba(255, 255, 255, 0.3);
backdrop-filter: blur(24px) saturate(180%);
-webkit-backdrop-filter: blur(24px) saturate(180%);
background: rgba(255, 255, 255, 0.35);
border: 1px solid rgba(255, 255, 255, 0.4);
box-shadow:
inset 0 1px 0 rgba(255, 255, 255, 0.7),
Expand All @@ -86,11 +105,15 @@ layer(utilities);
box-shadow 0.4s ease;
}

.navbar-glass::before {
background: rgba(255, 255, 255, 0.3);
backdrop-filter: blur(24px) saturate(180%);
-webkit-backdrop-filter: blur(24px) saturate(180%);
}

/* 捲動後:稍增不透明 */
.navbar-glass-scrolled {
background: rgba(255, 255, 255, 0.5);
backdrop-filter: blur(30px) saturate(200%);
-webkit-backdrop-filter: blur(30px) saturate(200%);
border: 1px solid rgba(255, 255, 255, 0.5);
box-shadow:
inset 0 1px 0 rgba(255, 255, 255, 0.8),
Expand All @@ -101,6 +124,12 @@ layer(utilities);
box-shadow 0.4s ease;
}

.navbar-glass-scrolled::before {
background: rgba(255, 255, 255, 0.5);
backdrop-filter: blur(30px) saturate(200%);
-webkit-backdrop-filter: blur(30px) saturate(200%);
}

/* 手機版下拉選單分隔線 */
.navbar-glass-dropdown {
border-top: 1px solid rgba(180, 200, 220, 0.35);
Expand All @@ -109,8 +138,6 @@ layer(utilities);
/* 手機版展開時不透明 */
.navbar-glass-mobile-open {
background: rgba(235, 242, 250, 0.8);
backdrop-filter: blur(32px) saturate(200%);
-webkit-backdrop-filter: blur(32px) saturate(200%);
border: 1px solid rgba(255, 255, 255, 0.6);
box-shadow:
inset 0 1px 0 rgba(255, 255, 255, 0.9),
Expand All @@ -121,6 +148,25 @@ layer(utilities);
box-shadow 0.4s ease;
}

.navbar-glass-mobile-open::before {
background: rgba(235, 242, 250, 0.8);
backdrop-filter: blur(32px) saturate(200%);
-webkit-backdrop-filter: blur(32px) saturate(200%);
}

@supports not (
(-webkit-backdrop-filter: blur(1px)) or (backdrop-filter: blur(1px))
) {
.navbar-glass {
background: rgba(231, 238, 246, 0.9);
}

.navbar-glass-scrolled,
.navbar-glass-mobile-open {
background: rgba(224, 233, 244, 0.9);
}
}

/* 液態滑入動畫 */
@keyframes liquid-slide-down {
from {
Expand Down
Loading