First Commit
This commit is contained in:
commit
8a8067287c
|
@ -0,0 +1,63 @@
|
|||
###############################################################################
|
||||
# Set default behavior to automatically normalize line endings.
|
||||
###############################################################################
|
||||
* text=auto
|
||||
|
||||
###############################################################################
|
||||
# Set default behavior for command prompt diff.
|
||||
#
|
||||
# This is need for earlier builds of msysgit that does not have it on by
|
||||
# default for csharp files.
|
||||
# Note: This is only used by command line
|
||||
###############################################################################
|
||||
#*.cs diff=csharp
|
||||
|
||||
###############################################################################
|
||||
# Set the merge driver for project and solution files
|
||||
#
|
||||
# Merging from the command prompt will add diff markers to the files if there
|
||||
# are conflicts (Merging from VS is not affected by the settings below, in VS
|
||||
# the diff markers are never inserted). Diff markers may cause the following
|
||||
# file extensions to fail to load in VS. An alternative would be to treat
|
||||
# these files as binary and thus will always conflict and require user
|
||||
# intervention with every merge. To do so, just uncomment the entries below
|
||||
###############################################################################
|
||||
#*.sln merge=binary
|
||||
#*.csproj merge=binary
|
||||
#*.vbproj merge=binary
|
||||
#*.vcxproj merge=binary
|
||||
#*.vcproj merge=binary
|
||||
#*.dbproj merge=binary
|
||||
#*.fsproj merge=binary
|
||||
#*.lsproj merge=binary
|
||||
#*.wixproj merge=binary
|
||||
#*.modelproj merge=binary
|
||||
#*.sqlproj merge=binary
|
||||
#*.wwaproj merge=binary
|
||||
|
||||
###############################################################################
|
||||
# behavior for image files
|
||||
#
|
||||
# image files are treated as binary by default.
|
||||
###############################################################################
|
||||
#*.jpg binary
|
||||
#*.png binary
|
||||
#*.gif binary
|
||||
|
||||
###############################################################################
|
||||
# diff behavior for common document formats
|
||||
#
|
||||
# Convert binary document formats to text before diffing them. This feature
|
||||
# is only available from the command line. Turn it on by uncommenting the
|
||||
# entries below.
|
||||
###############################################################################
|
||||
#*.doc diff=astextplain
|
||||
#*.DOC diff=astextplain
|
||||
#*.docx diff=astextplain
|
||||
#*.DOCX diff=astextplain
|
||||
#*.dot diff=astextplain
|
||||
#*.DOT diff=astextplain
|
||||
#*.pdf diff=astextplain
|
||||
#*.PDF diff=astextplain
|
||||
#*.rtf diff=astextplain
|
||||
#*.RTF diff=astextplain
|
|
@ -0,0 +1,41 @@
|
|||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
/.pnp
|
||||
.pnp.*
|
||||
.yarn/*
|
||||
!.yarn/patches
|
||||
!.yarn/plugins
|
||||
!.yarn/releases
|
||||
!.yarn/versions
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
|
||||
# next.js
|
||||
/.next/
|
||||
/out/
|
||||
|
||||
# production
|
||||
/build
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
*.pem
|
||||
|
||||
# debug
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
.pnpm-debug.log*
|
||||
|
||||
# env files (can opt-in for committing if needed)
|
||||
.env*
|
||||
|
||||
# vercel
|
||||
.vercel
|
||||
|
||||
# typescript
|
||||
*.tsbuildinfo
|
||||
next-env.d.ts
|
|
@ -0,0 +1,3 @@
|
|||
# Last2014
|
||||
https://last2014.f5.si
|
||||
Last2014 Home Website
|
|
@ -0,0 +1,16 @@
|
|||
import { dirname } from "path";
|
||||
import { fileURLToPath } from "url";
|
||||
import { FlatCompat } from "@eslint/eslintrc";
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = dirname(__filename);
|
||||
|
||||
const compat = new FlatCompat({
|
||||
baseDirectory: __dirname,
|
||||
});
|
||||
|
||||
const eslintConfig = [
|
||||
...compat.extends("next/core-web-vitals", "next/typescript"),
|
||||
];
|
||||
|
||||
export default eslintConfig;
|
|
@ -0,0 +1,11 @@
|
|||
import type { NextConfig } from "next";
|
||||
|
||||
const nextConfig: NextConfig = {
|
||||
output: "export",
|
||||
distDir: process.env.DIST || "./out",
|
||||
images: {
|
||||
unoptimized: true,
|
||||
},
|
||||
};
|
||||
|
||||
export default nextConfig;
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,32 @@
|
|||
{
|
||||
"name": "last2014",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "next dev --turbopack",
|
||||
"build": "next build",
|
||||
"start": "next start",
|
||||
"lint": "next lint"
|
||||
},
|
||||
"dependencies": {
|
||||
"@icons-pack/react-simple-icons": "^13.0.0",
|
||||
"@types/eslint": "^9.6.1",
|
||||
"@types/next": "^9.0.0",
|
||||
"bulma": "^1.0.4",
|
||||
"date-fns": "^4.1.0",
|
||||
"next": "^15.3.3",
|
||||
"react": "^19.1.0",
|
||||
"react-dom": "^19.1.0",
|
||||
"react-fast-marquee": "^1.6.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/eslintrc": "^3",
|
||||
"@iconify/react": "^6.0.0",
|
||||
"@types/node": "^20",
|
||||
"@types/react": "^18.3.12",
|
||||
"@types/react-dom": "^18.3.1",
|
||||
"eslint": "^9",
|
||||
"eslint-config-next": "15.3.3",
|
||||
"typescript": "^5"
|
||||
}
|
||||
}
|
Binary file not shown.
After Width: | Height: | Size: 15 KiB |
Binary file not shown.
After Width: | Height: | Size: 10 KiB |
|
@ -0,0 +1,65 @@
|
|||
import Link from "next/link";
|
||||
import { Icon } from "@iconify/react";
|
||||
|
||||
import type { Metadata } from "next";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "About Site | Last2014",
|
||||
description: "About Last2014 Website",
|
||||
};
|
||||
|
||||
import { Noto_Sans_JP } from "next/font/google";
|
||||
|
||||
const Font = Noto_Sans_JP({ subsets: ["latin"] });
|
||||
|
||||
export default function PrivacyPolicy() {
|
||||
return (
|
||||
<div
|
||||
className={Font.className + " section container"}
|
||||
style={{ padding: "0" }}
|
||||
>
|
||||
<div className="is-flex is-align-items-center">
|
||||
<Link href="/" className="button" style={{ marginBottom: "1rem" }}>
|
||||
<Icon icon="octicon:home-fill-16" style={{ marginRight: "5px" }} />
|
||||
Back to Home
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<div className="title is-2 is-flex is-align-items-center">
|
||||
<Icon icon="ix:about-filled" />
|
||||
このサイトについて
|
||||
</div>
|
||||
|
||||
<div className="content">
|
||||
<h2 className="title is-4">免責事項</h2>
|
||||
<p>
|
||||
当サイトによるリンクなどから参照した情報の正確性やそれに関連する損害などについては当サイトの運営者は一切の責任を負いません。
|
||||
当サイトの情報、リンク先はなるべく正確なものを利用していますが完全に保証するわけではありません。
|
||||
</p>
|
||||
|
||||
<h2 className="title is-4">著作権</h2>
|
||||
<p>
|
||||
当サイト内の文章や画像、動画などのデータは無断転載を禁じます。
|
||||
<br />
|
||||
メディアなどの記載の場合は
|
||||
<Link href="/contact">お問い合わせフォーム</Link>
|
||||
までお問い合わせください。
|
||||
<p style={{ fontSize: "0.6rem" }}>絶対ないだろうけど</p>
|
||||
</p>
|
||||
|
||||
<h2>サイトのリンク</h2>
|
||||
<p>
|
||||
当サイトへのリンクは申請などはなしで可能です。
|
||||
ただし、サイトに使用されている画像を
|
||||
<code>{"<img>"}</code>
|
||||
タグで参照したりなどの行為はおやめください。
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="is-flex is-align-items-center">
|
||||
<Icon icon="material-symbols:alarm-on-rounded" />
|
||||
2025/06/16 記述
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
"use client";
|
||||
|
||||
import Link from "next/link";
|
||||
|
||||
import type { Metadata } from "next";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Process Error | Last2014",
|
||||
description: "Error Page",
|
||||
};
|
||||
|
||||
export default function Error() {
|
||||
return (
|
||||
<div className="has-text-centered">
|
||||
<h1 className="title">Process Error - HTTP500</h1>
|
||||
|
||||
<div
|
||||
className="notification is-warning"
|
||||
style={{
|
||||
width: "15em",
|
||||
margin: "0 auto",
|
||||
padding: "0.5rem",
|
||||
}}
|
||||
>
|
||||
<h3>
|
||||
{"An error has occurred"}
|
||||
</h3>
|
||||
</div>
|
||||
|
||||
<Link className="button" style={{ marginTop: "10px" }} href="/">
|
||||
Go to Home
|
||||
</Link>
|
||||
</div>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
@import "bulma/css/bulma.min.css";
|
||||
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
body {
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 100vh;
|
||||
|
||||
font-weight: 100;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
.aboutme,
|
||||
.skills,
|
||||
.profile {
|
||||
--bulma-card-media-margin: 0.5rem;
|
||||
}
|
||||
|
||||
.footer {
|
||||
padding: 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.nomalLink {
|
||||
color: var(--bulma-body-color);
|
||||
}
|
||||
|
||||
.nomalLink:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
main {
|
||||
flex: 1;
|
||||
padding: 1rem;
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
import type { Metadata } from "next";
|
||||
import "./globals.css";
|
||||
|
||||
import Footer from "@/components/footer";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Last2014",
|
||||
description: "Last2014 Website",
|
||||
};
|
||||
|
||||
import { Roboto } from 'next/font/google';
|
||||
|
||||
const Font = Roboto({ subsets: ['latin'] });
|
||||
|
||||
export default function RootLayout({
|
||||
children,
|
||||
}: Readonly<{
|
||||
children: React.ReactNode;
|
||||
}>) {
|
||||
return (
|
||||
<html data-theme="dark">
|
||||
<body className={Font.className}>
|
||||
<main>
|
||||
{children}
|
||||
</main>
|
||||
<Footer />
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
import { notFound, redirect } from "next/navigation";
|
||||
|
||||
const serviceUrlMap = {
|
||||
blog: "https://blog.last2014.com",
|
||||
chiebukuro: "https://chiebukuro.yahoo.co.jp/user/1154047737",
|
||||
gitea: "https://gitea.last2014.f5.si/last2014",
|
||||
nicovideo: "https://www.nicovideo.jp/user/140339612",
|
||||
qiita: "https://qiita.com/last2014",
|
||||
zenn: "https://zenn.dev/last2014",
|
||||
wakatime: "https://wakatime.com/@last2014",
|
||||
mail: "mailto:last2014yh@yahoo.co.jp",
|
||||
} as const;
|
||||
|
||||
type ServiceKey = keyof typeof serviceUrlMap;
|
||||
|
||||
export function generateStaticParams() {
|
||||
return Object.keys(serviceUrlMap).map((service) => ({
|
||||
service: service as ServiceKey,
|
||||
}));
|
||||
}
|
||||
|
||||
export default async function Links({
|
||||
params,
|
||||
}: {
|
||||
params: Promise<{ service: ServiceKey }>;
|
||||
}) {
|
||||
const { service } = await params;
|
||||
const url = serviceUrlMap[service];
|
||||
|
||||
if (!url) {
|
||||
return notFound();
|
||||
}
|
||||
|
||||
redirect(url);
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
import Link from "next/link";
|
||||
|
||||
import type { Metadata } from "next";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Not Found | Last2014",
|
||||
description: "Not Found Page",
|
||||
};
|
||||
|
||||
export default function NotFound() {
|
||||
return (
|
||||
<div className="has-text-centered">
|
||||
<h1 className="title">Not Found - HTTP404</h1>
|
||||
|
||||
<div
|
||||
className="notification is-warning"
|
||||
style={{
|
||||
width: "15em",
|
||||
margin: "0 auto",
|
||||
padding: "0.5rem",
|
||||
}}
|
||||
>
|
||||
<h3>
|
||||
{"The page you requested"}
|
||||
<br />
|
||||
{"could not be found"}
|
||||
</h3>
|
||||
</div>
|
||||
|
||||
<Link className="button" style={{ marginTop: "10px" }} href="/">
|
||||
Go to Home
|
||||
</Link>
|
||||
</div>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
import "./style.css";
|
||||
|
||||
// Cards
|
||||
import TopProfile from "@/cards/profile";
|
||||
import AboutMe from "@/cards/aboutme";
|
||||
import Skill from "@/cards/skill";
|
||||
import Links from "@/cards/links";
|
||||
import Details from "@/cards/details";
|
||||
|
||||
export default function Home() {
|
||||
return (
|
||||
<>
|
||||
<TopProfile />
|
||||
|
||||
<AboutMe />
|
||||
|
||||
<Skill />
|
||||
|
||||
<Links />
|
||||
|
||||
<Details />
|
||||
</>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,99 @@
|
|||
import Link from "next/link";
|
||||
import { Icon } from "@iconify/react";
|
||||
|
||||
import type { Metadata } from "next";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Privacy Policy | Last2014",
|
||||
description: "Last2014 Website Privacy Policy",
|
||||
};
|
||||
|
||||
import { Noto_Sans_JP } from "next/font/google";
|
||||
|
||||
const Font = Noto_Sans_JP({ subsets: ["latin"] });
|
||||
|
||||
export default function PrivacyPolicy() {
|
||||
return (
|
||||
<div
|
||||
className={Font.className + " section container"}
|
||||
style={{ padding: "0" }}
|
||||
>
|
||||
<div className="is-flex is-align-items-center">
|
||||
<Link href="/" className="button" style={{ marginBottom: "1rem" }}>
|
||||
<Icon icon="octicon:home-fill-16" style={{ marginRight: "5px" }} />
|
||||
Back to Home
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<div className="title is-2 is-flex is-align-items-center">
|
||||
<Icon icon="eos-icons:network-policy" />
|
||||
プライバシーポリシー
|
||||
</div>
|
||||
|
||||
<div className="content">
|
||||
<h2 className="title is-4">個人情報の取り扱いについて</h2>
|
||||
<p>
|
||||
当サイトは、ユーザーのプライバシー保護を重視しています。本サイトの利用に際して収集される情報は、以下の通りです。
|
||||
</p>
|
||||
|
||||
<h2 className="title is-4">アクセスログについて</h2>
|
||||
<p>
|
||||
当サイトは、Cloudflare
|
||||
Tunnelを経由してサービスを提供しています。また、Cloudflare Web
|
||||
Analytics・Cloudflare Browser
|
||||
Insightsを利用してアクセス解析を行っています。
|
||||
アクセスなどの状況はCloudflareに送信される可能性があります。
|
||||
詳細なプライバシーポリシーや利用規約については、
|
||||
<a
|
||||
href="https://www.cloudflare.com/privacypolicy/"
|
||||
className="nomalLink is-underlined"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
Cloudflareのプライバシーポリシー
|
||||
</a>
|
||||
をご確認ください。
|
||||
</p>
|
||||
<p>
|
||||
アクセスログには訪問者のIPアドレスや国、場所によっては州などの地域が含まれ、時刻と保存されます。
|
||||
</p>
|
||||
<p>
|
||||
サーバーの停止、あるいはサーバーが不安定な状況などになった場合、直近のアクセスログを確認し、復旧作業や原因調査に利用することがあります。
|
||||
</p>
|
||||
|
||||
<h2 className="title is-4">アクセス解析</h2>
|
||||
<p>
|
||||
当サイトでは、Cloudflare Web Analytics、Cloudflare Browser
|
||||
Insightsを利用してアクセス解析を行っています。
|
||||
アクセス解析のよるデータはすべて当サイトの改善などに使用され、第三者には一切提供しません。
|
||||
</p>
|
||||
|
||||
<h2 className="title is-4">Cookieについて</h2>
|
||||
<p>
|
||||
Cloudflareによるアクセス解析ではCookieを使用する場合があります。詳細は
|
||||
<a
|
||||
href="https://www.cloudflare.com/cookie-policy/"
|
||||
className="nomalLink is-underlined"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
CloudflareのCookieポリシー
|
||||
</a>
|
||||
をご確認ください。
|
||||
</p>
|
||||
|
||||
<h2 className="title is-4">お問い合わせ</h2>
|
||||
<p>
|
||||
本ポリシーに関するご質問やご意見は、
|
||||
<Link href="/contact">お問い合わせフォーム</Link>
|
||||
よりご連絡ください。
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="is-flex is-align-items-center">
|
||||
<Icon icon="material-symbols:alarm-on-rounded" />
|
||||
2025/06/16 制定
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
.card {
|
||||
width: 40em;
|
||||
display: block;
|
||||
margin: 0 auto;
|
||||
margin-top: 1em;
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
.topCard {
|
||||
margin-top: 3em;
|
||||
}
|
||||
|
||||
.media-content {
|
||||
overflow: hidden;
|
||||
word-wrap: break-word;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
#icon {
|
||||
border-radius: 100%;
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
import { Icon } from "@iconify/react";
|
||||
|
||||
export default function AboutMe() {
|
||||
return (
|
||||
<div className="card card-content aboutme">
|
||||
<div className="media media-content">
|
||||
<div
|
||||
className="is-flex is-align-items-center"
|
||||
style={{
|
||||
gap: "0.5em",
|
||||
}}
|
||||
>
|
||||
<Icon
|
||||
className="title is-4"
|
||||
style={{ margin: "0" }}
|
||||
icon="iconoir:people-tag"
|
||||
/>
|
||||
<span className="title is-4">About Me</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="content">
|
||||
{
|
||||
"A programmer in elementary school. I'm playing around with Next.js and home servers."
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,144 @@
|
|||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import { Icon } from "@iconify/react";
|
||||
import { format, differenceInYears, formatDistanceToNow } from "date-fns";
|
||||
|
||||
export default function Details() {
|
||||
const [open, setOpen] = useState(false);
|
||||
const [limit, setLimit] = useState(2);
|
||||
|
||||
const showMore = () => {
|
||||
setLimit((prev) => prev + 2);
|
||||
};
|
||||
|
||||
const birthday = new Date("2014-12-8");
|
||||
const birthdayStr = format(birthday, "MMMM d, yyyy");
|
||||
const age = differenceInYears(new Date(), birthday);
|
||||
|
||||
const startday = new Date("2025-5-31");
|
||||
const startdayStr = format(startday, "MMMM d, yyyy");
|
||||
const activitytimes = formatDistanceToNow(startday, { addSuffix: true });
|
||||
|
||||
type DetailItem = {
|
||||
icon: string;
|
||||
name: string;
|
||||
value?: string;
|
||||
};
|
||||
|
||||
const details: DetailItem[] = [
|
||||
{
|
||||
icon: "iconoir:calendar",
|
||||
name: "Start of activities",
|
||||
value: `${startdayStr}(${activitytimes})`,
|
||||
},
|
||||
{
|
||||
icon: "iconoir:birthday-cake",
|
||||
name: "Birthday",
|
||||
value: `${birthdayStr}(${age}years old)`,
|
||||
},
|
||||
{
|
||||
icon: "guidance:children-must-be-supervised",
|
||||
name: "Age",
|
||||
value: `${age}years old`,
|
||||
},
|
||||
{
|
||||
icon: "iconoir:map-pin",
|
||||
name: "Location",
|
||||
value: "Kanagawa, Japan",
|
||||
},
|
||||
{
|
||||
icon: "guidance:unisex-restroom",
|
||||
name: "Gender",
|
||||
value: "Male",
|
||||
},
|
||||
{
|
||||
icon: "iconoir:globe",
|
||||
name: "From",
|
||||
value: "Japan",
|
||||
},
|
||||
{
|
||||
icon: "iconoir:translate",
|
||||
name: "Language",
|
||||
value: "Japanese / English",
|
||||
},
|
||||
];
|
||||
|
||||
const visibleDetails = details.slice(0, limit);
|
||||
const hasMore = limit < details.length;
|
||||
|
||||
return (
|
||||
<div className="card card-content links">
|
||||
<div className="is-flex is-align-items-center" style={{ gap: "0.5em" }}>
|
||||
<Icon
|
||||
className="title is-4"
|
||||
style={{ margin: "0" }}
|
||||
icon="iconoir:info-circle"
|
||||
/>
|
||||
<span className="title is-4" style={{ margin: 0 }}>
|
||||
Details
|
||||
</span>
|
||||
<span
|
||||
style={{ marginLeft: "auto", cursor: "pointer" }}
|
||||
onClick={() => setOpen((prev) => !prev)}
|
||||
>
|
||||
<Icon
|
||||
className="title is-4"
|
||||
icon={open ? "iconoir:nav-arrow-down" : "iconoir:nav-arrow-right"}
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{open && (
|
||||
<div className="content links-content" style={{ marginTop: "1em" }}>
|
||||
{visibleDetails.map((item, idx) => (
|
||||
<div
|
||||
key={idx}
|
||||
className="card card-content links"
|
||||
style={{ marginBottom: "0.5em" }}
|
||||
>
|
||||
<div
|
||||
className="is-flex is-align-items-center"
|
||||
style={{ gap: "0.5em" }}
|
||||
>
|
||||
<Icon
|
||||
className="title is-4"
|
||||
style={{ margin: "0" }}
|
||||
icon={item.icon}
|
||||
/>
|
||||
<span className="title is-4" style={{ margin: 0 }}>
|
||||
{item.name}
|
||||
</span>
|
||||
{item.value && (
|
||||
<span
|
||||
style={{
|
||||
marginLeft: "auto",
|
||||
fontSize: "0.9em",
|
||||
color: "#888",
|
||||
}}
|
||||
>
|
||||
{item.value}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
{hasMore && (
|
||||
<div
|
||||
className="is-flex is-justify-content-center"
|
||||
style={{ marginTop: "0.5em" }}
|
||||
>
|
||||
<button
|
||||
onClick={showMore}
|
||||
className="button card is-4"
|
||||
style={{ width: "auto" }}
|
||||
>
|
||||
Load More
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,173 @@
|
|||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import { Icon } from "@iconify/react";
|
||||
|
||||
type LinkItem = {
|
||||
icon: string;
|
||||
name: string;
|
||||
about?: string;
|
||||
url?: string;
|
||||
};
|
||||
|
||||
const links: LinkItem[] = [
|
||||
{
|
||||
icon: "octicon:home-fill-16",
|
||||
name: "Home Page",
|
||||
about: "This Page",
|
||||
url: "/",
|
||||
},
|
||||
{
|
||||
icon: "material-symbols:text-ad",
|
||||
name: "Blog",
|
||||
about: "Last2014's Blog",
|
||||
url: "/links/blog",
|
||||
},
|
||||
{
|
||||
icon: "simple-icons:qiita",
|
||||
name: "Qiita",
|
||||
about: "Qiita Profile",
|
||||
url: "/links/qiita",
|
||||
},
|
||||
{
|
||||
icon: "simple-icons:zenn",
|
||||
name: "Zenn",
|
||||
about: "Zenn Profile",
|
||||
url: "/links/zenn",
|
||||
},
|
||||
{
|
||||
icon: "simple-icons:gitea",
|
||||
name: "Gitea",
|
||||
about: "Public Sourcecode",
|
||||
url: "/links/gitea",
|
||||
},
|
||||
{
|
||||
icon: "jam:yahoo",
|
||||
name: "Yahoo! Chiebukuro",
|
||||
about: "Question & Answer",
|
||||
url: "/links/chiebukuro",
|
||||
},
|
||||
{
|
||||
icon: "simple-icons:niconico",
|
||||
name: "Nicovideo",
|
||||
about: "Upload & Watch",
|
||||
url: "/links/nicovideo",
|
||||
},
|
||||
{
|
||||
icon: "simple-icons:wakatime",
|
||||
name: "WakaTime",
|
||||
about: "Working Time",
|
||||
url: "/links/wakatime",
|
||||
},
|
||||
{
|
||||
icon: "iconoir:mail-solid",
|
||||
name: "Mail",
|
||||
about: "Public Mail",
|
||||
url: "/links/mail",
|
||||
},
|
||||
];
|
||||
|
||||
export default function Link() {
|
||||
const [open, setOpen] = useState(false);
|
||||
const [limit, setLimit] = useState(2);
|
||||
|
||||
const showMore = () => {
|
||||
setLimit((prev) => prev + 2);
|
||||
};
|
||||
|
||||
const visibleLinks = links.slice(0, limit);
|
||||
const hasMore = limit < links.length;
|
||||
|
||||
return (
|
||||
<div className="card card-content links">
|
||||
<div className="is-flex is-align-items-center" style={{ gap: "0.5em" }}>
|
||||
<Icon
|
||||
className="title is-4"
|
||||
style={{ margin: "0" }}
|
||||
icon="iconoir:link"
|
||||
/>
|
||||
<span className="title is-4" style={{ margin: 0 }}>
|
||||
Links
|
||||
</span>
|
||||
<span
|
||||
style={{ marginLeft: "auto", cursor: "pointer" }}
|
||||
onClick={() => setOpen((prev) => !prev)}
|
||||
>
|
||||
<Icon
|
||||
className="title is-4"
|
||||
icon={open ? "iconoir:nav-arrow-down" : "iconoir:nav-arrow-right"}
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{open && (
|
||||
<div className="content links-content" style={{ marginTop: "1em" }}>
|
||||
{visibleLinks.map((item, idx) => {
|
||||
const iconElement = (
|
||||
<Icon
|
||||
className="title is-4"
|
||||
style={{ margin: "0" }}
|
||||
icon={item.icon}
|
||||
/>
|
||||
);
|
||||
|
||||
return (
|
||||
<a
|
||||
key={idx}
|
||||
href={item.url}
|
||||
target={item.url?.startsWith("http") ? "_blank" : undefined}
|
||||
rel={
|
||||
item.url?.startsWith("http")
|
||||
? "noopener noreferrer"
|
||||
: undefined
|
||||
}
|
||||
className="card card-content links"
|
||||
style={{
|
||||
marginBottom: "0.5em",
|
||||
display: "block",
|
||||
textDecoration: "none",
|
||||
color: "inherit",
|
||||
}}
|
||||
>
|
||||
<div
|
||||
className="is-flex is-align-items-center"
|
||||
style={{ gap: "0.5em" }}
|
||||
>
|
||||
{iconElement}
|
||||
<span className="title is-4" style={{ margin: 0 }}>
|
||||
{item.name}
|
||||
</span>
|
||||
{item.about && (
|
||||
<span
|
||||
style={{
|
||||
marginLeft: "auto",
|
||||
fontSize: "0.9em",
|
||||
color: "#888",
|
||||
}}
|
||||
>
|
||||
{item.about}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</a>
|
||||
);
|
||||
})}
|
||||
{hasMore && (
|
||||
<div
|
||||
className="is-flex is-justify-content-center"
|
||||
style={{ marginTop: "0.5em" }}
|
||||
>
|
||||
<button
|
||||
onClick={showMore}
|
||||
className="button card is-4"
|
||||
style={{ width: "auto" }}
|
||||
>
|
||||
Load More
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
import Image from "next/image";
|
||||
import { Icon } from "@iconify/react";
|
||||
|
||||
export default function TopProfile() {
|
||||
return (
|
||||
<div className="card card-content topCard profile">
|
||||
<div className="media">
|
||||
<div className="media-left">
|
||||
<figure className="image is-48x48">
|
||||
<Image
|
||||
id="icon"
|
||||
src="/last2014.png"
|
||||
alt="Last2014 Icon"
|
||||
width="100"
|
||||
height="100"
|
||||
/>
|
||||
</figure>
|
||||
</div>
|
||||
|
||||
<div className="media-content">
|
||||
<p className="title is-4">Last2014</p>
|
||||
<p className="subtitle is-6">@last2014</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="content is-flex is-align-items-center">
|
||||
<span style={{ marginRight: "5px" }}>
|
||||
<Icon icon="hugeicons:computer-programming-01" />
|
||||
Web Developer
|
||||
</span>
|
||||
|
||||
{"/"}
|
||||
|
||||
<span style={{ marginLeft: "5px" }}>
|
||||
<Icon icon="ph:student-fill" />
|
||||
Elementary School Student
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
.SkillIcon {
|
||||
--SkillIconMargin: 5px;
|
||||
margin-right: var(--SkillIconMargin);
|
||||
margin-left: var(--SkillIconMargin);
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.skillsContainer {
|
||||
display: flex;
|
||||
gap: 0;
|
||||
}
|
|
@ -0,0 +1,62 @@
|
|||
import * as SimpleIcons from "@icons-pack/react-simple-icons";
|
||||
import { Icon } from "@iconify/react";
|
||||
|
||||
const Si = SimpleIcons;
|
||||
|
||||
const icons = [
|
||||
Si.SiHtml5,
|
||||
Si.SiCss,
|
||||
Si.SiJavascript,
|
||||
Si.SiTypescript,
|
||||
Si.SiPhp,
|
||||
Si.SiReact,
|
||||
Si.SiNextdotjs,
|
||||
Si.SiVuedotjs,
|
||||
Si.SiNodedotjs,
|
||||
Si.SiBulma,
|
||||
Si.SiGit,
|
||||
Si.SiGitea,
|
||||
Si.SiCloudflare,
|
||||
];
|
||||
|
||||
const repeat = 3;
|
||||
const iconList = Array.from({ length: repeat }).flatMap(() => icons);
|
||||
|
||||
import Marquee from "react-fast-marquee";
|
||||
|
||||
import "./skill.css";
|
||||
|
||||
export default function Skill() {
|
||||
return (
|
||||
<div className="card card-content skills">
|
||||
<div
|
||||
className="is-flex is-align-items-center"
|
||||
style={{
|
||||
gap: "0.5em",
|
||||
marginBottom: "var(--bulma-card-media-margin)",
|
||||
}}
|
||||
>
|
||||
<Icon
|
||||
className="title is-4"
|
||||
style={{ margin: "0" }}
|
||||
icon="iconoir:terminal-tag"
|
||||
/>
|
||||
<span className="title is-4">Skills</span>
|
||||
</div>
|
||||
<div className="content skillsContainer">
|
||||
<div className="content">Use these to create websites etc.</div>
|
||||
|
||||
<Marquee
|
||||
gradientWidth={70}
|
||||
gradientColor="#14161a"
|
||||
gradient
|
||||
className="has-text-centered"
|
||||
>
|
||||
{iconList.map((Icon, i) => (
|
||||
<Icon className="SkillIcon" size={30} key={i} />
|
||||
))}
|
||||
</Marquee>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
import { getYear } from "date-fns";
|
||||
import Link from "next/link";
|
||||
import { Icon } from "@iconify/react";
|
||||
|
||||
function getYears(start: number) {
|
||||
const currentYear = getYear(new Date());
|
||||
if (currentYear > start) {
|
||||
return `${start}-${currentYear}`;
|
||||
} else {
|
||||
return String(start);
|
||||
}
|
||||
}
|
||||
|
||||
export default function Footer() {
|
||||
return (
|
||||
<footer className="footer has-text-centered">
|
||||
<p>© {getYears(2025)} Last2014</p>
|
||||
|
||||
<p>
|
||||
<Link className="nomalLink" href="/about">
|
||||
<Icon icon="ix:about-filled" />
|
||||
About
|
||||
</Link>
|
||||
|
||||
{" / "}
|
||||
|
||||
<Link className="nomalLink" href="/privacypolicy">
|
||||
<Icon icon="eos-icons:network-policy" />
|
||||
Privacy Policy
|
||||
</Link>
|
||||
</p>
|
||||
</footer>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2017",
|
||||
"lib": [
|
||||
"dom",
|
||||
"dom.iterable",
|
||||
"esnext"
|
||||
],
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"strict": true,
|
||||
"noEmit": true,
|
||||
"esModuleInterop": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "bundler",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"jsx": "preserve",
|
||||
"incremental": true,
|
||||
"plugins": [
|
||||
{
|
||||
"name": "next"
|
||||
}
|
||||
],
|
||||
"paths": {
|
||||
"@/*": [
|
||||
"./src/*"
|
||||
]
|
||||
}
|
||||
},
|
||||
"include": [
|
||||
"**/*.ts",
|
||||
"**/*.tsx",
|
||||
"./out/types/**/*.ts",
|
||||
".next/types/**/*.ts",
|
||||
"next-env.d.ts",
|
||||
"./aaa/types/**/*.ts"
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules"
|
||||
]
|
||||
}
|
Loading…
Reference in New Issue