feat: redesign homepage with hero, experience, education, languages, and articles sections

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
riceset 2026-03-18 17:56:58 +09:00
parent d479b7fc71
commit d13066c983
10 changed files with 791 additions and 59 deletions

View File

@ -1,38 +1,3 @@
--- ---
title: "Komeno" title: "Komeno"
--- ---
<style>
.container {
display: flex;
flex-direction: column;
align-items: center;
margin: 0;
}
@media (min-width: 801px) {
.container {
justify-content: center;
min-height: 70vh;
}
}
@media (max-width: 800px) {
.container {
justify-content: space-evenly;
min-height: 40vh;
padding-top: 4vh;
padding-bottom: 4vh;
}
}
</style>
<div class="container" style="display: flex; flex-direction: column; align-items: center; text-align: center;">
<img src="media/index/icon.png" alt="icon" width="150" />
<div style="font-size: 24px; font-weight: bold; margin-top: 6px;">
Komeno
</div>
<p style="margin-top: 6px; max-width: 500px; padding: 0 15px;">
Software Engineer
</p>
</div>

View File

@ -15,36 +15,62 @@ export const sharedPageComponents: SharedLayout = {
// components for pages that display a single page (e.g. a single note) // components for pages that display a single page (e.g. a single note)
export const defaultContentPageLayout: PageLayout = { export const defaultContentPageLayout: PageLayout = {
beforeBody: [ beforeBody: [
Component.ConditionalRender({
component: Component.HomeHero(),
condition: (props) => props.fileData.slug === "index",
}),
Component.ConditionalRender({
component: Component.HomeProfile(),
condition: (props) => props.fileData.slug === "index",
}),
Component.ConditionalRender({
component: Component.HomeArticles(),
condition: (props) => props.fileData.slug === "index",
}),
], ],
left: [ left: [
Component.PageTitle(), Component.ConditionalRender({
Component.MobileOnly(Component.Spacer()), component: Component.PageTitle(),
//Component.Search(), condition: (props) => props.fileData.slug !== "index",
}),
Component.ConditionalRender({
component: Component.MobileOnly(Component.Spacer()),
condition: (props) => props.fileData.slug !== "index",
}),
Component.Darkmode(), Component.Darkmode(),
Component.DesktopOnly(Component.LinksList({ Component.ConditionalRender({
component: Component.DesktopOnly(Component.LinksList({
links: { links: {
"E-Mail": "mailto:riceset@icloud.com", "E-Mail": "mailto:riceset@icloud.com",
GitHub: "https://github.com/riceset", GitHub: "https://github.com/riceset",
LinkedIn: "https://www.linkedin.com/in/riceset/", LinkedIn: "https://www.linkedin.com/in/riceset/",
} },
})), })),
Component.Explorer(), condition: (props) => props.fileData.slug !== "index",
}),
Component.ConditionalRender({
component: Component.Explorer(),
condition: (props) => props.fileData.slug !== "index",
}),
], ],
right: [ right: [
Component.DesktopOnly(Component.RecentNotes({ Component.ConditionalRender({
title: "Latest", component: Component.DesktopOnly(Component.RecentNotes({ title: "Latest", limit: 8 })),
limit: 8 condition: (props) => props.fileData.slug !== "index",
})), }),
Component.MobileOnly(Component.RecentNotes({ Component.ConditionalRender({
title: "Latest", component: Component.MobileOnly(Component.RecentNotes({ title: "Latest", limit: 1 })),
limit: 1 condition: (props) => props.fileData.slug !== "index",
})), }),
Component.MobileOnly(Component.LinksList({ Component.ConditionalRender({
component: Component.MobileOnly(Component.LinksList({
links: { links: {
GitHub: "https://github.com/riceset", GitHub: "https://github.com/riceset",
LinkedIn: "https://www.linkedin.com/in/riceset/", LinkedIn: "https://www.linkedin.com/in/riceset/",
} },
})) })),
condition: (props) => props.fileData.slug !== "index",
}),
], ],
} }

View File

@ -0,0 +1,54 @@
import { QuartzComponent, QuartzComponentConstructor, QuartzComponentProps } from "./types"
import { resolveRelative } from "../util/path"
import { byDateAndAlphabetical } from "./PageList"
import { Date, getDate } from "./Date"
import style from "./styles/homeArticles.scss"
const BookIcon = () => (
<svg class="section-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M4 19.5A2.5 2.5 0 0 1 6.5 17H20" />
<path d="M6.5 2H20v20H6.5A2.5 2.5 0 0 1 4 19.5v-15A2.5 2.5 0 0 1 6.5 2z" />
</svg>
)
const HomeArticles: QuartzComponent = ({ allFiles, fileData, cfg }: QuartzComponentProps) => {
const pages = allFiles
.filter((page) => page.slug !== "index")
.sort(byDateAndAlphabetical(cfg))
if (pages.length === 0) return null
return (
<section class="home-articles">
<h2 class="home-articles-heading">
<BookIcon />
Articles
</h2>
<ul class="home-articles-list">
{pages.map((page) => {
const title = page.frontmatter?.title ?? "Untitled"
const date = page.dates ? getDate(cfg, page) : null
return (
<li class="home-article-item">
<a
href={resolveRelative(fileData.slug!, page.slug!)}
class="home-article-title internal"
>
{title}
</a>
{date && (
<span class="home-article-date">
<Date date={date} locale={cfg.locale} />
</span>
)}
</li>
)
})}
</ul>
</section>
)
}
HomeArticles.css = style
export default (() => HomeArticles) satisfies QuartzComponentConstructor

View File

@ -0,0 +1,57 @@
import { QuartzComponent, QuartzComponentConstructor } from "./types"
import style from "./styles/homeHero.scss"
const MailIcon = () => (
<svg class="home-link-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<rect x="2" y="4" width="20" height="16" rx="2" />
<path d="m22 7-8.97 5.7a1.94 1.94 0 0 1-2.06 0L2 7" />
</svg>
)
const GitHubIcon = () => (
<svg class="home-link-icon" viewBox="0 0 24 24" fill="currentColor">
<path d="M12 0C5.37 0 0 5.37 0 12c0 5.3 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61-.546-1.385-1.335-1.755-1.335-1.755-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 21.795 24 17.295 24 12c0-6.63-5.37-12-12-12" />
</svg>
)
const LinkedInIcon = () => (
<svg class="home-link-icon" viewBox="0 0 24 24" fill="currentColor">
<path d="M20.447 20.452h-3.554v-5.569c0-1.328-.027-3.037-1.852-3.037-1.853 0-2.136 1.445-2.136 2.939v5.667H9.351V9h3.414v1.561h.046c.477-.9 1.637-1.85 3.37-1.85 3.601 0 4.267 2.37 4.267 5.455v6.286zM5.337 7.433a2.062 2.062 0 0 1-2.063-2.065 2.064 2.064 0 1 1 2.063 2.065zm1.782 13.019H3.555V9h3.564v11.452zM22.225 0H1.771C.792 0 0 .774 0 1.729v20.542C0 23.227.792 24 1.771 24h20.451C23.2 24 24 23.227 24 22.271V1.729C24 .774 23.2 0 22.222 0h.003z" />
</svg>
)
const HomeHero: QuartzComponent = () => {
return (
<section class="home-hero">
<div class="home-hero-inner">
<img class="home-avatar" src="/media/index/icon.png" alt="Komeno" />
<div class="home-hero-text">
<h1 class="home-name">Komeno</h1>
<p class="home-title">Software Engineer</p>
<p class="home-bio">
Software engineer and linguist. Interning at MIXI, Inc building iOS features for
FamilyAlbum. MEXT Scholar at Tokyo University of Foreign Studies. 42 Network alumnus.
Native in English, Japanese, and Portuguese also speak Spanish and Mandarin.
</p>
<div class="home-links">
<a href="mailto:riceset@icloud.com" class="home-link">
<MailIcon />
E-Mail
</a>
<a href="https://github.com/riceset" class="home-link" target="_blank" rel="noopener noreferrer">
<GitHubIcon />
GitHub
</a>
<a href="https://www.linkedin.com/in/riceset/" class="home-link" target="_blank" rel="noopener noreferrer">
<LinkedInIcon />
LinkedIn
</a>
</div>
</div>
</div>
</section>
)
}
HomeHero.css = style
export default (() => HomeHero) satisfies QuartzComponentConstructor

View File

@ -0,0 +1,189 @@
import { QuartzComponent, QuartzComponentConstructor } from "./types"
import style from "./styles/homeProfile.scss"
// ── Icons ──────────────────────────────────────────────────────────────────
const BriefcaseIcon = () => (
<svg class="section-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<rect x="2" y="7" width="20" height="14" rx="2" />
<path d="M16 7V5a2 2 0 0 0-2-2h-4a2 2 0 0 0-2 2v2" />
<line x1="12" y1="12" x2="12" y2="12.01" />
<path d="M2 12h20" />
</svg>
)
const GraduationCapIcon = () => (
<svg class="section-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M22 10v6M2 10l10-5 10 5-10 5z" />
<path d="M6 12v5c3 3 9 3 12 0v-5" />
</svg>
)
const GlobeIcon = () => (
<svg class="section-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="12" r="10" />
<line x1="2" y1="12" x2="22" y2="12" />
<path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z" />
</svg>
)
// ── Data ───────────────────────────────────────────────────────────────────
interface ExperienceItem {
role: string
company: string
companyUrl: string
logo: string
period: string
location: string
description: string
tags?: string[]
}
interface EducationItem {
degree: string
institution: string
institutionUrl: string
logo: string
period: string
}
interface Language {
flag: string
name: string
level: string
}
const experience: ExperienceItem[] = [
{
role: "Product Development Engineer",
company: "MIXI, Inc",
companyUrl: "https://mixi.co.jp",
logo: "/static/logos/mixi.svg",
period: "Jan 2026 Present",
location: "Tokyo, Japan",
description:
"Building and optimizing iOS features for FamilyAlbum, a photo-sharing platform with 27M+ users across 175 countries, used by 60% of parents in Japan.",
tags: ["Swift", "iOS", "Agile"],
},
{
role: "Google Student Ambassador",
company: "Google Japan",
companyUrl: "https://about.google/intl/ALL_jp/",
logo: "/static/logos/google.svg",
period: "Aug 2025 Feb 2026",
location: "Tokyo, Japan",
description:
"Collaborated with Google Japan to bridge AI and university students — exploring practical Gemini use cases and promoting responsible AI integration on campus.",
tags: ["AI", "Gemini"],
},
]
const education: EducationItem[] = [
{
degree: "B.A. Language and Area Studies",
institution: "Tokyo University of Foreign Studies",
institutionUrl: "https://www.tufs.ac.jp/english/",
logo: "/static/logos/tufs.svg",
period: "2024 2028",
},
{
degree: "Computer Software Engineering",
institution: "42 Network (Paris / São Paulo / Tokyo)",
institutionUrl: "https://42.fr",
logo: "/static/logos/42.svg",
period: "2022 2025",
},
]
const languages: Language[] = [
{ flag: "🇧🇷", name: "Portuguese", level: "Native" },
{ flag: "🇺🇸", name: "English", level: "Native" },
{ flag: "🇯🇵", name: "Japanese", level: "Native · JLPT N1" },
{ flag: "🇪🇸", name: "Spanish", level: "Professional · TOEIC 945" },
{ flag: "🇨🇳", name: "Mandarin", level: "Working · HSK 3 · TOCFL 4" },
]
// ── Component ──────────────────────────────────────────────────────────────
const HomeProfile: QuartzComponent = () => {
return (
<div class="home-profile">
{/* Experience */}
<section class="home-section">
<h2 class="home-section-heading">
<BriefcaseIcon />
Experience
</h2>
<div class="home-exp-list">
{experience.map((item) => (
<div class="home-exp-item">
<div class="home-exp-row">
<img class="home-org-logo" src={item.logo} alt={item.company} />
<div class="home-exp-header">
<div class="home-exp-text">
<span class="home-exp-role">{item.role}</span>
<span class="home-exp-company">{item.company}</span>
</div>
<div class="home-exp-right">
<span class="home-exp-period">{item.period}</span>
<span class="home-exp-location">{item.location}</span>
</div>
</div>
</div>
{item.description && (
<div class="home-exp-body">
<p class="home-exp-desc">{item.description}</p>
</div>
)}
</div>
))}
</div>
</section>
{/* Education */}
<section class="home-section">
<h2 class="home-section-heading">
<GraduationCapIcon />
Education
</h2>
<div class="home-edu-list">
{education.map((item) => (
<div class="home-edu-item">
<img class="home-org-logo" src={item.logo} alt={item.institution} />
<div class="home-edu-text">
<span class="home-edu-institution">{item.institution}</span>
<span class="home-edu-degree">{item.degree}</span>
</div>
<span class="home-edu-period">{item.period}</span>
</div>
))}
</div>
</section>
{/* Languages */}
<section class="home-section">
<h2 class="home-section-heading">
<GlobeIcon />
Languages
</h2>
<div class="home-lang-list">
{languages.map((lang) => (
<div class="home-lang-item">
<span class="home-lang-name">
<span class="home-lang-flag">{lang.flag}</span>
{lang.name}
</span>
<span class="home-lang-level">{lang.level}</span>
</div>
))}
</div>
</section>
</div>
)
}
HomeProfile.css = style
export default (() => HomeProfile) satisfies QuartzComponentConstructor

View File

@ -3,6 +3,9 @@ import TagContent from "./pages/TagContent"
import FolderContent from "./pages/FolderContent" import FolderContent from "./pages/FolderContent"
import NotFound from "./pages/404" import NotFound from "./pages/404"
import ArticleTitle from "./ArticleTitle" import ArticleTitle from "./ArticleTitle"
import HomeHero from "./HomeHero"
import HomeProfile from "./HomeProfile"
import HomeArticles from "./HomeArticles"
import Darkmode from "./Darkmode" import Darkmode from "./Darkmode"
import ReaderMode from "./ReaderMode" import ReaderMode from "./ReaderMode"
import Head from "./Head" import Head from "./Head"
@ -27,6 +30,9 @@ import LinksList from "./LinksList"
export { export {
ArticleTitle, ArticleTitle,
HomeHero,
HomeProfile,
HomeArticles,
Content, Content,
TagContent, TagContent,
FolderContent, FolderContent,

View File

@ -288,7 +288,7 @@ export function renderPage(
</div> </div>
</div> </div>
{RightComponent} {RightComponent}
{slug !== "index" && <Footer {...componentData} />} <Footer {...componentData} />
</Body> </Body>
</div> </div>
</body> </body>

View File

@ -0,0 +1,75 @@
@use "../../styles/variables.scss" as *;
.home-articles {
margin-top: 2rem;
padding-top: 2rem;
border-top: 1px solid var(--lightgray);
}
.home-articles-heading {
display: flex;
align-items: center;
gap: 0.4rem;
font-family: var(--headerFont);
font-size: 0.8rem;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.08em;
color: var(--gray);
margin: 0 0 1.25rem;
}
.section-icon {
width: 14px;
height: 14px;
flex-shrink: 0;
stroke: var(--gray);
}
.home-articles-list {
list-style: none;
padding: 0;
margin: 0;
display: flex;
flex-direction: column;
}
.home-article-item {
display: flex;
justify-content: space-between;
align-items: baseline;
gap: 1rem;
padding: 0.4rem 0;
@media all and ($mobile) {
flex-direction: column;
gap: 0.2rem;
}
}
.home-article-title {
font-size: 0.95rem;
font-weight: 500;
color: var(--dark);
text-decoration: none;
background-color: transparent !important;
transition: color 0.15s ease;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
&:hover {
color: var(--secondary);
}
@media all and ($mobile) {
white-space: normal;
}
}
.home-article-date {
font-size: 0.8rem;
color: var(--gray);
flex-shrink: 0;
white-space: nowrap;
}

View File

@ -0,0 +1,97 @@
@use "../../styles/variables.scss" as *;
.home-hero {
padding: 3rem 0 2rem;
@media all and ($mobile) {
padding: 2rem 0 1.5rem;
}
}
.home-hero-inner {
display: flex;
align-items: center;
gap: 2.5rem;
@media all and ($mobile) {
flex-direction: column;
align-items: flex-start;
gap: 1.25rem;
}
}
.home-avatar {
width: 100px;
height: 100px;
border-radius: 50%;
object-fit: cover;
flex-shrink: 0;
border: 2px solid var(--lightgray);
@media all and ($mobile) {
width: 80px;
height: 80px;
}
}
.home-hero-text {
display: flex;
flex-direction: column;
gap: 0.35rem;
}
.home-name {
font-family: var(--headerFont);
font-size: 1.75rem;
font-weight: 700;
color: var(--dark);
margin: 0;
line-height: 1.2;
}
.home-title {
font-family: var(--headerFont);
font-size: 1rem;
color: var(--gray);
margin: 0;
}
.home-bio {
font-size: 0.9rem;
color: var(--darkgray);
margin: 0.5rem 0 0;
max-width: 480px;
line-height: 1.6;
}
.home-links {
display: flex;
flex-wrap: wrap;
gap: 0.75rem;
margin-top: 0.75rem;
}
.home-link {
display: inline-flex;
align-items: center;
gap: 0.35rem;
font-size: 0.85rem;
color: var(--secondary);
text-decoration: none;
border: 1px solid var(--lightgray);
padding: 0.3rem 0.75rem;
border-radius: 999px;
transition: border-color 0.15s ease, color 0.15s ease;
background-color: transparent;
&:hover {
border-color: var(--secondary);
color: var(--secondary);
}
}
.home-link-icon {
width: 14px;
height: 14px;
flex-shrink: 0;
}

View File

@ -0,0 +1,263 @@
@use "../../styles/variables.scss" as *;
.home-profile {
display: flex;
flex-direction: column;
gap: 0;
}
// Shared section chrome
.home-section {
margin-top: 2rem;
padding-top: 2rem;
border-top: 1px solid var(--lightgray);
}
.home-section-heading {
display: flex;
align-items: center;
gap: 0.4rem;
font-family: var(--headerFont);
font-size: 0.8rem;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.08em;
color: var(--gray);
margin: 0 0 1.25rem;
}
.section-icon {
width: 14px;
height: 14px;
flex-shrink: 0;
stroke: var(--gray);
}
// External links
.home-ext-link {
font-size: inherit;
color: var(--secondary);
text-decoration: none;
background-color: transparent !important;
&:hover {
text-decoration: underline;
}
}
// Org logo
.home-org-logo {
width: 2.5rem;
height: 2.5rem;
border-radius: 50%;
object-fit: cover;
background-color: var(--lightgray);
border: 1px solid var(--lightgray);
flex-shrink: 0;
display: block;
}
// Experience
.home-exp-list {
display: flex;
flex-direction: column;
gap: 1.5rem;
}
.home-exp-item {
display: flex;
flex-direction: column;
gap: 0.4rem;
}
.home-exp-row {
display: flex;
flex-direction: row;
align-items: center;
gap: 0.75rem;
}
.home-exp-header {
flex: 1;
display: flex;
justify-content: space-between;
align-items: flex-start;
gap: 1rem;
min-width: 0;
@media all and ($mobile) {
flex-direction: column;
gap: 0.15rem;
}
}
.home-exp-text {
display: flex;
flex-direction: column;
gap: 0.1rem;
}
.home-exp-right {
display: flex;
flex-direction: column;
align-items: flex-end;
gap: 0.1rem;
flex-shrink: 0;
@media all and ($mobile) {
align-items: flex-start;
}
}
.home-exp-role {
font-family: var(--headerFont);
font-size: 1.05rem;
font-weight: 600;
line-height: 1.4;
color: var(--dark);
}
.home-exp-company {
font-size: 0.95rem;
line-height: 1.4;
color: var(--secondary);
}
.home-exp-period {
font-size: 0.8rem;
color: var(--gray);
white-space: nowrap;
}
.home-exp-location {
font-size: 0.78rem;
color: var(--gray);
white-space: nowrap;
}
.home-exp-body {
display: flex;
flex-direction: column;
gap: 0.5rem;
padding-left: calc(2.5rem + 0.75rem);
@media all and ($mobile) {
padding-left: 0;
}
}
.home-exp-desc {
font-size: 0.85rem;
color: var(--darkgray);
line-height: 1.6;
margin: 0;
max-width: 580px;
}
.home-exp-tags {
display: flex;
flex-wrap: wrap;
gap: 0.35rem;
}
.home-exp-tag {
font-size: 0.75rem;
color: var(--secondary);
background-color: var(--highlight);
padding: 0.1rem 0.5rem;
border-radius: 999px;
}
// Education
.home-edu-list {
display: flex;
flex-direction: column;
gap: 0.9rem;
}
.home-edu-item {
display: flex;
flex-direction: row;
align-items: center;
gap: 0.75rem;
@media all and ($mobile) {
flex-wrap: wrap;
}
}
.home-edu-text {
flex: 1;
display: flex;
flex-direction: column;
gap: 0.1rem;
min-width: 0;
}
.home-edu-institution {
font-family: var(--headerFont);
font-size: 1.05rem;
font-weight: 600;
line-height: 1.4;
color: var(--dark);
}
.home-edu-degree {
font-family: var(--headerFont);
font-size: 0.95rem;
font-weight: 400;
line-height: 1.4;
color: var(--darkgray);
}
.home-edu-period {
font-size: 0.8rem;
color: var(--gray);
flex-shrink: 0;
white-space: nowrap;
@media all and ($mobile) {
margin-top: 0;
}
}
// Languages
.home-lang-list {
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.home-lang-item {
display: flex;
justify-content: space-between;
align-items: baseline;
gap: 1rem;
padding: 0.25rem 0;
}
.home-lang-name {
display: flex;
align-items: center;
gap: 0.5rem;
font-size: 0.9rem;
font-weight: 500;
color: var(--dark);
}
.home-lang-flag {
font-size: 1rem;
line-height: 1;
}
.home-lang-level {
font-size: 0.8rem;
color: var(--gray);
text-align: right;
}