diff --git a/.githooks/commit-msg.go b/.githooks/commit-msg.go index 07b48f5..7c354e9 100755 --- a/.githooks/commit-msg.go +++ b/.githooks/commit-msg.go @@ -17,7 +17,7 @@ func main() { } commitMessage := string(input) - pattern := fmt.Sprintf("^(%s):\\s", strings.Join(allowedPrefixes, "|")) + pattern := fmt.Sprintf("^((%s):\\s|Merge)", strings.Join(allowedPrefixes, "|")) re := regexp.MustCompile(pattern) if !re.MatchString(commitMessage) { diff --git a/.storybook/font.css b/.storybook/font.css new file mode 100644 index 0000000..305c181 --- /dev/null +++ b/.storybook/font.css @@ -0,0 +1,5 @@ +@import url("https://fonts.googleapis.com/css2?family=Noto+Sans+JP:wght@100..900&display=swap"); + +html, body { + font-family: "Noto Sans JP"; +} diff --git a/.storybook/preview.tsx b/.storybook/preview.tsx index a1bd4bc..5537db8 100644 --- a/.storybook/preview.tsx +++ b/.storybook/preview.tsx @@ -5,6 +5,7 @@ import { I18nextProvider } from 'react-i18next'; import '../app/app.css'; import "../app/i18n/config"; import i18n from '../app/i18n/config'; +import './font.css'; const preview: Preview = { parameters: { diff --git a/app/app.css b/app/app.css index 4642493..31d1495 100644 --- a/app/app.css +++ b/app/app.css @@ -1,11 +1,3 @@ -@import "tailwindcss"; -@import url('https://fonts.googleapis.com/css2?family=Noto+Sans+JP:wght@100..900&display=swap'); - -@theme { - --font-sans: "Inter", ui-sans-serif, system-ui, sans-serif, - "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; -} - :root { --main-text-color: #242525; --grey-text-color: #9D9F9F; @@ -14,13 +6,17 @@ --primery-color: #57B3E1; } -html, -body { - @apply bg-white dark:bg-gray-950; +* { + margin: 0; + padding: 0; +} - @media (prefers-color-scheme: dark) { - color-scheme: dark; - } +html { + width: 100vw; + overflow-x: hidden; +} +html, +body { font-family: "Noto Sans JP"; } diff --git a/app/components/ActivityCard/ActivityCard.tsx b/app/components/ActivityCard/ActivityCard.tsx index c4233f4..1018e1b 100644 --- a/app/components/ActivityCard/ActivityCard.tsx +++ b/app/components/ActivityCard/ActivityCard.tsx @@ -1,6 +1,5 @@ -import './activity-card.css'; -import { useTranslation } from "react-i18next" import { LinkedButton } from '../LinkedButton/LinkedButton'; +import './activity-card.css'; export interface ActivityCardProps { headerImage: string; @@ -28,7 +27,6 @@ export const ActivityCard = ({ url, ...props }: ActivityCardProps) => { - const { t } = useTranslation(); const strFromData = formatDate(from); const strToData = to ? formatDate(to) : null; @@ -59,7 +57,7 @@ export const ActivityCard = ({
- +
diff --git a/app/components/Footer/Footer.module.css b/app/components/Footer/Footer.module.css new file mode 100644 index 0000000..b7e336a --- /dev/null +++ b/app/components/Footer/Footer.module.css @@ -0,0 +1,59 @@ +footer { + width: 100vw; + background-color: #2972AD; +} + +.container { + padding: 64px 10% 0 10%; + display: flex; + flex-direction: row; + justify-content: start; + align-items: start; +} + +.container.title { + flex-direction: column; + align-items: start; + justify-content: center; +} + +.container .block { + display: flex; + align-items: start; + justify-content: center; + flex-direction: column; + width: 30%; +} + +.container p, .copy { + color: var(--accent-color); + display: inline-block; + font-weight: lighter; + font-size: 24px; +} + +.container a { + text-decoration: none; +} + +.container ul { + width: 100%; + list-style: none; + padding-left: 0; +} + +.container li::before { + content: "•"; + display: inline-block; + width: 1em; + padding-top: 16px; + color: var(--accent-color); +} + +.copy { + display: block; + padding-top: 40px; + text-align: center; + padding-bottom: 12px; + letter-spacing: 1px; +} \ No newline at end of file diff --git a/app/components/Footer/Footer.stories.tsx b/app/components/Footer/Footer.stories.tsx new file mode 100644 index 0000000..017088c --- /dev/null +++ b/app/components/Footer/Footer.stories.tsx @@ -0,0 +1,15 @@ +import type { Meta, StoryObj } from '@storybook/react'; + +import { Footer } from './Footer'; + + +const meta = { + title: 'Common/Footer', + component: Footer, + tags: ['autodocs'], +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +export const Default: Story = {}; diff --git a/app/components/Footer/Footer.tsx b/app/components/Footer/Footer.tsx new file mode 100644 index 0000000..93a21a5 --- /dev/null +++ b/app/components/Footer/Footer.tsx @@ -0,0 +1,63 @@ +import styles from './Footer.module.css'; + +const sitemap: Record = { + "ホームページ": "/", + "お知らせ": "/", + "活動報告": "/", + "技術ブログ": "/", + "": "", + "お問い合わせ": "/", +} + +const aboutSite: Record = { + "Webデザイン(Figma)": "https://www.figma.com/design/YWBtX9qhd0QKOTY4a2SEWx/Object%3CT%3E", + "ソースコード(Github)": "https://github.com/object-t/object-t-website", +} + +const school: Record = { + "学校ホームページ": "https://www.fca.ac.jp/", +} + +const getList = (list: Record) => { + return ( +
    + { + Object.entries(list).map(([label, url]) => label ? ( +
  • + +

    + {label} +

    +
    +
  • + ):
    ) + } +
+ ) +} + +export const Footer = ({...props}) => { + return ( +
+
+

学生団体:Object<T>

+

所属: 福岡デザイン&テクノロジー専門学校

+
+
+
+

サイトマップ

+ {getList(sitemap)} +
+
+

本サイトについて

+ {getList(aboutSite)} +
+
+

学校関連

+ {getList(school)} +
+
+

© 学生団体 Object<T>. All rights reserved.

+
+ ); +}; diff --git a/app/components/Header/Header.stories.ts b/app/components/Header/Header.stories.ts index 80c71d0..595f042 100644 --- a/app/components/Header/Header.stories.ts +++ b/app/components/Header/Header.stories.ts @@ -1,5 +1,4 @@ import type { Meta, StoryObj } from '@storybook/react'; -import { fn } from '@storybook/test'; import { Header } from './Header'; @@ -12,22 +11,30 @@ const meta = { // More on how to position stories at: https://storybook.js.org/docs/configure/story-layout layout: 'fullscreen', }, - args: { - onLogin: fn(), - onLogout: fn(), - onCreateAccount: fn(), - }, } satisfies Meta; export default meta; type Story = StoryObj; -export const LoggedIn: Story = { +export const Default: Story = { args: { - user: { - name: 'Jane Doe', - }, + headers: [ + { + 'label': "HOME", + 'to': "#home" + }, + { + 'label': "MEMBER", + 'to': "#member" + }, + { + 'label': "PRODUCT", + 'to': "#product" + }, + { + 'label': "BLOG", + 'to': "#blog" + } + ] }, }; - -export const LoggedOut: Story = {}; diff --git a/app/components/Header/Header.tsx b/app/components/Header/Header.tsx index f99dbf2..2f0c0f8 100644 --- a/app/components/Header/Header.tsx +++ b/app/components/Header/Header.tsx @@ -1,56 +1,120 @@ -import React from 'react'; -import { Button } from '../Button/Button'; +import { useCallback, useEffect, useRef, useState } from 'react'; +import { useNavigate } from 'react-router'; +import { HeaderLink } from '../HeaderLink/HeaderLink'; +import { LanguageButton } from '../LanguageButton/LanguageButton'; import './header.css'; -type User = { - name: string; -}; +const headers: Headers = [ + { + 'label': "HOME", + 'to': "#home" + }, + { + 'label': "MEMBER", + 'to': "#member" + }, + { + 'label': "PRODUCT", + 'to': "#product" + }, + { + 'label': "ACTIVITY", + 'to': "#activity" + }, + { + 'label': "BLOG", + 'to': "/blog" + } +] -export interface HeaderProps { - user?: User; - onLogin?: () => void; - onLogout?: () => void; - onCreateAccount?: () => void; -} +type Headers = Record<'label' | 'to', string>[]; + +export const Header = () => { + const [activeSection, setActiveSection] = useState(''); + const [isHidden, setIsHidden] = useState(false); + const lastScrollY = useRef(0); + const ignoreScroll = useRef(false); + const ignoreTimer = useRef | null>(null); + + const navigate = useNavigate(); + + const handleScroll = useCallback(() => { + if (ignoreScroll.current) return; + + const currentScrollY = window.scrollY; + + if (currentScrollY > lastScrollY.current && currentScrollY > 100) { + setIsHidden(true); + } else { + setIsHidden(false); + } + + lastScrollY.current = currentScrollY; + + for (const header of headers) { + const sectionId = header.to.replace('#', ''); + const section = document.getElementById(sectionId); + if (section) { + const rect = section.getBoundingClientRect(); + if (rect.top <= 100 && rect.bottom > 100) { + setActiveSection(header.to); + break; + } + } + } + }, [headers]); + + useEffect(() => { + window.addEventListener('scroll', handleScroll); + handleScroll(); -export const Header = ({ user, onLogin, onLogout, onCreateAccount }: HeaderProps) => ( -
-
-
- - - - - - - -

Acme

-
-
- {user ? ( - <> - - Welcome, {user.name}! - -
-
-
-); + return () => window.removeEventListener('scroll', handleScroll); + }, [handleScroll]); + + const handleClick = (to: string) => { + if (!to.startsWith("#")) { + navigate(to); + return; + } + + if (location.pathname !== '/') { + navigate(`/${to}`); + return; + } + + if (ignoreTimer.current) { + clearTimeout(ignoreTimer.current); + } + + ignoreScroll.current = true; + setActiveSection(to); + + ignoreTimer.current = setTimeout(() => { + ignoreScroll.current = false; + ignoreTimer.current = null; + }, 800); + + const targetId = to.replace('#', ''); + const target = document.getElementById(targetId); + if (target) { + target.scrollIntoView({ behavior: 'smooth' }); + } + }; + + + return ( +
+ { + headers.map(header => ( + handleClick(header.to)} to={header.to}/> + )) + } + +
+ ); +} diff --git a/app/components/Header/header.css b/app/components/Header/header.css index 5efd46c..a429377 100644 --- a/app/components/Header/header.css +++ b/app/components/Header/header.css @@ -1,32 +1,23 @@ -.storybook-header { +header { display: flex; - justify-content: space-between; align-items: center; - border-bottom: 1px solid rgba(0, 0, 0, 0.1); - padding: 15px 20px; - font-family: 'Nunito Sans', 'Helvetica Neue', Helvetica, Arial, sans-serif; + transition: opacity 0.5s ease, transform 0.5s ease; + transform: translateY(0); + opacity: 1; + width: max-content; + position: fixed; + z-index: 999999; + top: 24px; + right: 48px; } -.storybook-header svg { - display: inline-block; - vertical-align: top; +.language-button { + cursor: pointer; + margin-left: 24px; } -.storybook-header h1 { - display: inline-block; - vertical-align: top; - margin: 6px 0 6px 10px; - font-weight: 700; - font-size: 20px; - line-height: 1; -} - -.storybook-header button + button { - margin-left: 10px; -} - -.storybook-header .welcome { - margin-right: 10px; - color: #333; - font-size: 14px; -} +.hidden { + opacity: 0; + transform: translateY(-100%); + pointer-events: none; +} \ No newline at end of file diff --git a/app/components/HeaderLink/HeaderLink.css b/app/components/HeaderLink/HeaderLink.css index 90c118b..6911427 100644 --- a/app/components/HeaderLink/HeaderLink.css +++ b/app/components/HeaderLink/HeaderLink.css @@ -4,25 +4,18 @@ font-size: 30px; line-height: 100%; position: relative; - font-weight: 400; + font-weight: 900; display: inline-block; letter-spacing: 0.05em; height: 38px; text-align: center; vertical-align: middle; - transition: color 0.3s ease; + padding: 0 8px; + transition: color 0.3s ease, border 0.3s ease; } -.label::after { - content: ""; - position: absolute; - bottom: -5px; - left: 0; - width: 100%; - height: 1px; - background-color: #F5F5F5; - opacity: 0; - transition: opacity 0.3s ease; +.active .label { + border-bottom: solid 1px var(--accent-color); } .active .label { diff --git a/app/components/HeaderLink/HeaderLink.stories.tsx b/app/components/HeaderLink/HeaderLink.stories.tsx index ea4d35f..8043cbf 100644 --- a/app/components/HeaderLink/HeaderLink.stories.tsx +++ b/app/components/HeaderLink/HeaderLink.stories.tsx @@ -1,13 +1,5 @@ import type { Meta, StoryObj } from '@storybook/react'; import { HeaderLink } from './HeaderLink'; -import { BrowserRouter } from 'react-router-dom'; - - -const withRouter = (Story) => ( - - - -); const meta = { title: 'Common/HeaderLink', @@ -15,8 +7,7 @@ const meta = { tags: ['autodocs'], parameters: { layout: 'centered', - }, - decorators: [withRouter], + } } satisfies Meta; export default meta; @@ -24,17 +15,17 @@ type Story = StoryObj; export const Default: Story = { args: { - label: 'Blog', + label: 'HOME', isActive: false, - to: '/' + to: '#home' }, }; export const HeaderLinkActive: Story = { args: { - label: 'Blog', + label: 'BLOG', isActive: true, - to: '/' + to: '/blog' }, }; diff --git a/app/components/HeaderLink/HeaderLink.tsx b/app/components/HeaderLink/HeaderLink.tsx index 0c5a994..2bd2042 100644 --- a/app/components/HeaderLink/HeaderLink.tsx +++ b/app/components/HeaderLink/HeaderLink.tsx @@ -1,21 +1,33 @@ -import { Link } from 'react-router-dom'; import './HeaderLink.css'; export interface HeaderLinkProps { - isActive: boolean; - label: string; - to: string; + isActive: boolean; + label: string; + to: string; + onClick?: () => void; } export const HeaderLink = ({ - isActive, - label, - to, - ...props + isActive, + label, + to, + onClick, }: HeaderLinkProps) => { - return ( - - {label} - - ); -} \ No newline at end of file + return ( + { + e.preventDefault(); + const id = to.replace('#', ''); + const element = document.getElementById(id); + if (element) { + element.scrollIntoView({ behavior: 'smooth' }); + } + onClick?.(); + }} + > + {label} + + ); +}; diff --git a/app/components/LanguageButton/LanguageButton.css b/app/components/LanguageButton/LanguageButton.css index 505a71f..17ebc16 100644 --- a/app/components/LanguageButton/LanguageButton.css +++ b/app/components/LanguageButton/LanguageButton.css @@ -8,6 +8,7 @@ .language-button { border: 1px solid #F5F5F5; + background-color: transparent; border-radius: 5px; width: 144px; height: 55px; diff --git a/app/components/LanguageButton/LanguageButton.tsx b/app/components/LanguageButton/LanguageButton.tsx index 7404db2..72fc4cf 100644 --- a/app/components/LanguageButton/LanguageButton.tsx +++ b/app/components/LanguageButton/LanguageButton.tsx @@ -1,21 +1,35 @@ -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { faGlobe } from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { useEffect, useState } from "react"; +import i18n from '../../i18n/config'; import './LanguageButton.css'; -export interface LanguageButtonProps { - label: string; -} +export const LanguageButton = ({ ...props }) => { + const [label, setLabel] = useState<'JP' | 'EN'>(i18n.language === 'ja' ? 'JP' : 'EN'); + + const handleClick = () => { + const nextLang = label === 'JP' ? 'en' : 'ja'; + i18n.changeLanguage(nextLang); + setLabel(nextLang === 'ja' ? 'JP' : 'EN'); + }; + + useEffect(() => { + const handleLangChanged = (lng: string) => { + setLabel(lng === 'ja' ? 'JP' : 'EN'); + }; + i18n.on('languageChanged', handleLangChanged); + + return () => { + i18n.off('languageChanged', handleLangChanged); + }; + }, []); -export const LanguageButton = ({ - label, - ...props -}: LanguageButtonProps) => { - return ( - - ); -} \ No newline at end of file + return ( + + ); +}; \ No newline at end of file diff --git a/app/components/LinkedButton/LinkedButton.css b/app/components/LinkedButton/LinkedButton.css index 5d951ea..03a29dc 100644 --- a/app/components/LinkedButton/LinkedButton.css +++ b/app/components/LinkedButton/LinkedButton.css @@ -3,6 +3,7 @@ height: 45px; display: flex; justify-content: center; + text-decoration: none; align-items: center; padding: 0 40px; border-radius: 5px; diff --git a/app/components/LinkedButton/LinkedButton.tsx b/app/components/LinkedButton/LinkedButton.tsx index 6df8c30..a0378b7 100644 --- a/app/components/LinkedButton/LinkedButton.tsx +++ b/app/components/LinkedButton/LinkedButton.tsx @@ -1,15 +1,23 @@ -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { faArrowUpRightFromSquare } from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { useTranslation } from "react-i18next"; import './LinkedButton.css'; export interface LinkedButtonProps { url: string; - label?: string; + label: TemplateLabel | string; backgroundColor?: string; color?: string; style?: "default" | "outlined"; } +export type TemplateLabel = 'detail' | 'list'; + +const templateLabel: Record = { + 'detail': "common.detail", + 'list': "common.list" +} + export const LinkedButton = ({ url, label, @@ -18,6 +26,8 @@ export const LinkedButton = ({ style = "default", ...props }: LinkedButtonProps) => { + const { t } = useTranslation(); + const displayLabel = label in templateLabel ? t(templateLabel[label as TemplateLabel]) : label const linkStyle = { backgroundColor: style === "default" ? backgroundColor : "transparent", @@ -37,7 +47,7 @@ export const LinkedButton = ({ return ( - {label} + {displayLabel} ); diff --git a/app/components/MemberCard/MemberCard.stories.ts b/app/components/MemberCard/MemberCard.stories.ts index fbc4950..2732290 100644 --- a/app/components/MemberCard/MemberCard.stories.ts +++ b/app/components/MemberCard/MemberCard.stories.ts @@ -17,7 +17,7 @@ type Story = StoryObj; export const Default: Story = { args: { name: "Naoto Kido", - role: "代表", + role: "owner", description: "よわよわプログラマ", stacks: ["Kubernetes", "AWS", "Java", "Go-wordmark", "Flutter"], headerImage: "https://pbs.twimg.com/profile_banners/1846395762277826560/1737992837/1500x500", diff --git a/app/components/MemberCard/MemberCard.tsx b/app/components/MemberCard/MemberCard.tsx index 2aaf00b..1d341d6 100644 --- a/app/components/MemberCard/MemberCard.tsx +++ b/app/components/MemberCard/MemberCard.tsx @@ -4,7 +4,7 @@ import './member-card.css'; export interface MemberCardProps { name: string; - role: string; + role: TemplateRole; description: string, stacks: string[]; headerImage: string; @@ -12,6 +12,15 @@ export interface MemberCardProps { githubName: string; } +type TemplateRole = 'owner' | 'coowner' | 'backend' | 'frontend'; + +const templateRole: Record = { + 'owner': 'common.roles.owner', + 'coowner': 'common.roles.coowner', + 'backend': 'common.roles.backend', + 'frontend': 'common.roles.frontend' +} + export const MemberCard = ({ name, role, @@ -40,7 +49,7 @@ export const MemberCard = ({

{ name }

-

{ role }

+

{ t(templateRole[role]) }

{ description }

{ t("card.memberCard.stack") }

diff --git a/app/components/NoticeCard/NoticeCard.tsx b/app/components/NoticeCard/NoticeCard.tsx index d2df91d..289acd1 100644 --- a/app/components/NoticeCard/NoticeCard.tsx +++ b/app/components/NoticeCard/NoticeCard.tsx @@ -1,5 +1,5 @@ +import { Tag, type TagKind } from '../Tag/Tag'; import './notice-card.css'; -import {Tag, type TagKind} from '../Tag/Tag'; export interface NoticeCardProps { imageUrl: string; @@ -25,11 +25,11 @@ export const NoticeCard = ({

{title}

-
-
- {tags.map((tag, i) => ( - - ))} +
+ {tags.map((tag, i) => ( + + ))} +
diff --git a/app/components/NoticeCard/notice-card.css b/app/components/NoticeCard/notice-card.css index e2b8d82..ed25967 100644 --- a/app/components/NoticeCard/notice-card.css +++ b/app/components/NoticeCard/notice-card.css @@ -30,35 +30,38 @@ .notice-card-title-box { height: 68px; + width: 100%; padding-right: 20px; - width: max-content; display: flex; flex-direction: row; + align-items: flex-end; border-bottom: 1px solid var(--grey-text-color); - } .notice-card-title-title-box { display: flex; align-items: center; + width: 100%; height: 40px; margin-top: 15px; } .notice-card-title { font-size: 40px; - height: 40px; + width: 100%; color: var(--main-text-color); font-weight: 700; } .notice-card-title-date { font-size: 20px; + margin-top: 8px; color: var(--grey-text-color); font-weight: 700; } .notice-card-description { + margin-top: 10px; font-size: 24px; max-width: 925px; color: var(--grey-text-color); @@ -67,11 +70,9 @@ .notice-card-tags { display: flex; + margin: 0 40px; gap: 16px; - margin-top: 24px; - margin-left: 40px; align-items: center; - } diff --git a/app/components/Title/SubTitle.css b/app/components/Title/SubTitle.css deleted file mode 100644 index 0cc792a..0000000 --- a/app/components/Title/SubTitle.css +++ /dev/null @@ -1,18 +0,0 @@ -.sub-title { - color: #9D9F9F; - font-weight: 900; -} - -.home-sub-title { - width: 440px; - height: 36px; - font-size: 30px; - font-weight: 400; -} - -.section-sub-title { - width: 449px; - height: 43px; - font-size: 36px; - font-weight: 900; -} \ No newline at end of file diff --git a/app/components/Title/SubTitle.tsx b/app/components/Title/SubTitle.tsx deleted file mode 100644 index 723f804..0000000 --- a/app/components/Title/SubTitle.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import './SubTitle.css'; - -export interface SubTitleProps { - label: string; - variant?: 'home' | 'section'; -} - -export const SubTitle = ({ - label, - variant = 'section', - ...props -}: SubTitleProps) => { - return ( -

- {label} -

- ); -} \ No newline at end of file diff --git a/app/components/Title/Title.css b/app/components/Title/Title.css index 2077419..251afca 100644 --- a/app/components/Title/Title.css +++ b/app/components/Title/Title.css @@ -4,26 +4,10 @@ font-size: 96px; } -.home-title { - width: 537px; - height: 115px; -} - .section-title { - width: 419px; height: 115px; } -.t-highlight { - color: #1E89E0; -} - -.title-container { - display: flex; - flex-direction: column; - gap: 10px; -} - .title-container-center { align-items: center; text-align: center; @@ -37,4 +21,15 @@ .title-container-right { align-items: flex-end; text-align: right; +} + +.sub-title { + color: #9D9F9F; + font-weight: 900; +} + +.section-sub-title { + height: 43px; + font-size: 36px; + font-weight: 900; } \ No newline at end of file diff --git a/app/components/Title/Title.stories.ts b/app/components/Title/Title.stories.ts index 4cbd502..1443d81 100644 --- a/app/components/Title/Title.stories.ts +++ b/app/components/Title/Title.stories.ts @@ -9,10 +9,6 @@ const meta = { layout: 'centered', }, argTypes: { - variant: { - control: 'select', - options: ['home', 'section'], - }, align: { control: 'select', options: ['left', 'right'], @@ -26,8 +22,7 @@ type Story = StoryObj; export const HomeTitle: Story = { args: { label: 'Object', - subLabel: '学生の未来をもっと「明るく」', - variant: 'home', + subLabel: '学生の未来をもっと「明るく」' }, }; @@ -35,7 +30,6 @@ export const SectionTitle: Story = { args: { label: 'ABOUT', subLabel: '私たちについて', - variant: 'section', align: 'left', }, }; \ No newline at end of file diff --git a/app/components/Title/Title.tsx b/app/components/Title/Title.tsx index a2f09bb..687361f 100644 --- a/app/components/Title/Title.tsx +++ b/app/components/Title/Title.tsx @@ -1,50 +1,43 @@ +import { useEffect, useState } from 'react'; +import i18n from '~/i18n/config'; import './Title.css'; -import { SubTitle } from './SubTitle'; export interface TitleProps { label: string; subLabel?: string; - variant?: 'home' | 'section'; align?: 'left' | 'right'; } export const Title = ({ label, subLabel, - variant = 'section', align = 'left', ...props }: TitleProps) => { - const objectTPattern = /^Object<(T)>$/; - const match = label.match(objectTPattern); + const [isEnglish, setIsEnglish] = useState(i18n.language === 'en'); - const renderTitle = () => { - if (match && match[1] === 'T') { - const beforeT = "Object<"; - const t = "T"; - const afterT = ">"; - - return ( -

- {beforeT}{t}{afterT} -

- ); - } else { - return ( -

- {label} -

- ); - } - }; - - const containerClassName = `title-container ${variant === 'home' ? 'title-container-center' : `title-container-${align}` - }`; + useEffect(() => { + const handleLanguageChange = (lng: string) => { + setIsEnglish(lng === 'en'); + }; + + i18n.on('languageChanged', handleLanguageChange); + + return () => { + i18n.off('languageChanged', handleLanguageChange); + }; + }, []); return ( -
- {renderTitle()} - {subLabel && } +
+

+ {label} +

+ {subLabel && !isEnglish && +

+ {subLabel} +

+ }
); } \ No newline at end of file diff --git a/app/i18n/config.ts b/app/i18n/config.ts index 63002a5..330acf7 100644 --- a/app/i18n/config.ts +++ b/app/i18n/config.ts @@ -11,7 +11,7 @@ const resources = { en: { translation: translation_en } - }; + } as const; i18n .use(initReactI18next) diff --git a/app/i18n/en.json b/app/i18n/en.json index 403b68d..42ae8ff 100644 --- a/app/i18n/en.json +++ b/app/i18n/en.json @@ -1,10 +1,19 @@ { "common": { "button": "Button", + "subtitle": "Brighter Futures for Students", + "detail": "Detail", + "list": "List", "tags": { "hackathon": "Hackathon", "recruitment": "Recruitment", "urgent": "Urgent" + }, + "roles": { + "owner": "Owner", + "coowner": "Co-Owner", + "backend": "Backend", + "frontend": "Frontend" } }, "card": { diff --git a/app/i18n/ja.json b/app/i18n/ja.json index 796d608..635f028 100644 --- a/app/i18n/ja.json +++ b/app/i18n/ja.json @@ -1,13 +1,19 @@ { - "language": "JP", "common": { "button": "ボタン", "detail": "詳細", "list": "一覧", + "subtitle": "学生の未来をもっと「明るく」", "tags": { "hackathon": "ハッカソン", "recruitment": "募集", "urgent": "緊急" + }, + "roles": { + "owner": "代表", + "coowner": "副代表", + "backend": "バックエンド", + "frontend": "フロントエンド" } }, "card": { diff --git a/app/root.tsx b/app/root.tsx index a0df061..7971c08 100644 --- a/app/root.tsx +++ b/app/root.tsx @@ -19,6 +19,10 @@ export const links: Route.LinksFunction = () => [ href: "https://fonts.gstatic.com", crossOrigin: "anonymous", }, + { + rel: "stylesheet", + href: "https://fonts.googleapis.com/css2?family=Noto+Sans+JP:wght@100..900&display=swap" + }, { rel: "stylesheet", href: "https://fonts.googleapis.com/css2?family=Inter:ital,opsz,wght@0,14..32,100..900;1,14..32,100..900&display=swap", @@ -27,7 +31,7 @@ export const links: Route.LinksFunction = () => [ export function Layout({ children }: { children: React.ReactNode }) { return ( - + diff --git a/app/routes.ts b/app/routes.ts index 102b402..dd551c7 100644 --- a/app/routes.ts +++ b/app/routes.ts @@ -1,3 +1,8 @@ import { type RouteConfig, index } from "@react-router/dev/routes"; -export default [index("routes/home.tsx")] satisfies RouteConfig; +export default [ + index("routes/home.tsx"), + { + path: "blog", + file: "routes/blog/index.tsx" } +] satisfies RouteConfig; diff --git a/app/routes/blog/index.module.css b/app/routes/blog/index.module.css new file mode 100644 index 0000000..d059682 --- /dev/null +++ b/app/routes/blog/index.module.css @@ -0,0 +1,7 @@ +.container { + width: 100vw; + height: 100vh; + display: flex; + align-items: center; + justify-content: center; +} \ No newline at end of file diff --git a/app/routes/blog/index.tsx b/app/routes/blog/index.tsx new file mode 100644 index 0000000..40ba22d --- /dev/null +++ b/app/routes/blog/index.tsx @@ -0,0 +1,14 @@ +import type { JSX } from "react"; +import { Header } from "~/components/Header/Header"; +import styles from "./index.module.css"; + +export default function Blog(): JSX.Element { + return ( +
+
+
+

準備中....

+
+
+ ) +} \ No newline at end of file diff --git a/app/routes/home.css b/app/routes/home.css new file mode 100644 index 0000000..8b3bc33 --- /dev/null +++ b/app/routes/home.css @@ -0,0 +1,117 @@ +.home-main-container { + background-color: var(--based-color); + width: 100vw; +} + +section { + padding: 120px 0; +} + +.sub-title { + color: #9D9F9F; + font-weight: 900; +} + +.home-sub-title { + height: 36px; + font-size: 30px; + font-weight: 400; +} + +.t-highlight { + color: #1E89E0; +} + +.title-container { + display: flex; + flex-direction: column; + gap: 10px; +} + + +.home-title-container { + height: 100vh; + display: flex; + justify-content: center; + align-items: center; +} + +.home-notice { + justify-content: center; + flex-direction: column; + align-items: center; + display: flex; + gap: 30px; +} + +.home-notice-divider { + border: none; + border-top: 1px solid #555; + margin: 50px auto; + width: 80%; + max-width: 1300px; + border-width: 3px; +} + +.home-member { + display: grid; + grid-template-columns: repeat(2, 1fr); + gap: 30px; + max-width: 800px; +} + +.home-member-card-layout { + display: flex; + justify-content: center; + margin-top: 8rem; +} + +.home-product-card-layout { + display: flex; + justify-content: center; + align-items: center; + margin-top: 8rem; +} + +.home-product { + display: flex; + gap: 8rem; +} + +.home-section-left { + margin-left: 13rem; +} + +.home-section-right { + margin-right: 13rem; +} + +.home-about-title h1 { + margin: 3rem 13rem 3rem; + color: var(--accent-color); + font-size: 64px; + font-weight: bold; + border-left: 15px solid dodgerblue; + padding-left: 2rem; +} + +.home-about-content { + margin: 2rem 13rem; + font-size: 40px; + color: var(--accent-color); +} + +.home-activity-card-layout { + display: flex; + justify-content: center; + margin-top: 8rem; +} + +.home-activity { + justify-content: center; + flex-direction: column; + align-items: center; + display: flex; + gap: 30px; +} + diff --git a/app/routes/home.tsx b/app/routes/home.tsx index 398e47c..f45f36e 100644 --- a/app/routes/home.tsx +++ b/app/routes/home.tsx @@ -1,13 +1,133 @@ +import { useTranslation } from "react-i18next"; +import { ActivityCard, type ActivityCardProps } from "~/components/ActivityCard/ActivityCard"; +import { Footer } from "~/components/Footer/Footer"; +import { Header } from "~/components/Header/Header"; +import { MemberCard, type MemberCardProps } from "~/components/MemberCard/MemberCard"; +import { NoticeCard, type NoticeCardProps } from "~/components/NoticeCard/NoticeCard"; +import { ProductCard, type ProductCardProps } from "~/components/ProductCard/ProductCard"; +import { Title } from "../components/Title/Title"; import type { Route } from "./+types/home"; -import { Welcome } from "../welcome/welcome"; +import "./home.css"; -export function meta({}: Route.MetaArgs) { +export function meta({ }: Route.MetaArgs) { return [ - { title: "New React Router App" }, - { name: "description", content: "Welcome to React Router!" }, + { title: "団体公式ホームページ | Object" }, + { name: "description", content: "学生団体「Object」の公式ホームページです。" }, + { property: "og:site_name", content: "Object"}, + { property: "og:title", content: "団体公式ホームページ | Object" }, + { property: "og:type", content: "website" }, + { property: "og:url", content: "https://object-t.com" }, + { property: "og:image", content: "https://object-t.com/assets/images/ogp_image.jpg" }, + { property: "og:description", content: "学生団体「Object」の公式ホームページです。" }, ]; } +const members: MemberCardProps[] = [ + { name: "Naoto Kido", role: "owner", description: "よわよわプログラマー", stacks: ['Kubernetes', 'AWS', 'Java', 'Go-wordmark', 'Flutter'], headerImage: "https://pbs.twimg.com/profile_banners/1846395762277826560/1737992837/1500x500", iconImage: "https://avatars.githubusercontent.com/u/54303857", githubName: "naoido" }, + { name: "Miura Naoki", role: "coowner", description: "ねこねこプログラマー", stacks: ['Go-wordmark', 'Javascript', 'Rust', 'Python', 'Neovim'], headerImage: "https://cdn.discordapp.com/attachments/1325818102960623729/1350589227477504051/IMG_0649.jpg?ex=67edb33c&is=67ec61bc&hm=52b978d0b55914eef3fb9015748e4b496f4bb16d207d648bdc97a28a05a82bc8&", iconImage: "https://avatars.githubusercontent.com/u/114989748", githubName: "thirdlf03" }, + { name: "Takumi Matsubara", role: "frontend", description: "うまうまプログラマー", stacks: ['Typescript', 'Swift', 'React'], headerImage: "https://cdn.discordapp.com/attachments/1325818102960623729/1350615961505366016/IMG_7620.jpg?ex=67edcc22&is=67ec7aa2&hm=7aaf0e07ab67d2484cb9c54fa09ebb329082606cad3218443a3985c4f2aac415&", iconImage: "https://avatars.githubusercontent.com/u/152017354", githubName: "AnnkoATAMA" }, + { name: "Kentaro Doi", role: "backend", description: "メンズコーチ", stacks: ['Javascript', 'Python', 'Php', 'Laravel', 'Typescript'], headerImage: "https://cdn.discordapp.com/attachments/1325818102960623729/1350718393178656799/jpg.jpg?ex=67ee2b88&is=67ecda08&hm=54e2abad70d172d4ec551a3a47fd89b2deefc2ee738f0debbe9f4a0543453bee&", iconImage: "https://avatars.githubusercontent.com/u/148222450", githubName: "kenta-afk" }, +] + +const notices: NoticeCardProps[] = [ + { date: "開催日: 2025年5月12日(月)", title: "学内ハッカソン開催", description: "今回は学内のスキルアップを目指して学内ハッカソンを開催することになりました!詳細ページはこちらをクリック!", tags: ["hackathon", "recruitment"], imageUrl: "/app/components/assets/logo.webp" }, + { date: "開催日: 2025年8月32日(月)", title: "LT会", description: "勉強したことをアウトプットするために、LT会を開催することにしました", tags: ["hackathon", "recruitment"], imageUrl: "https://avatars.githubusercontent.com/u/54303857" } +] + +const products: ProductCardProps[] = [ + { headerImage: "https://pbs.twimg.com/profile_banners/1846395762277826560/1737992837/1500x500", title: "ねこbot", stacks: ["Go-wordmark", "ArgoCD"], description: "ねこbotは団体discordで運用しているbotです。団体内での情報共有や、AIによる質問返答、グループ管理の役割を担っています。", stars: 1, commits: 142, pullRequests: 75, linkedButtons: [{ label: "Github", url: "https://github.com/naoido/neko-bot" }] }, + { headerImage: "https://pbs.twimg.com/profile_banners/1846395762277826560/1737992837/1500x500", title: "団体公式サイト", stacks: ["Cloudflare", "React", "Cloudflareworkers", "Storybook"], description: "団体公式のウェブサイトです。Reactで構成されており、Cloudfalre Workers使用し記事の更新などを行い、Cloudflare Pagesにデプロイしています。", stars: 2, commits: 86, pullRequests: 54, linkedButtons: [{ label: "Github", url: "https://github.com/object-t/object-t-website" }, { label: "Figma", url: "https://www.figma.com/design/YWBtX9qhd0QKOTY4a2SEWx" }] } +] + +const activities: ActivityCardProps[] = [ + { headerImage: "https://pbs.twimg.com/media/GmZOyaFacAA4vCx?format=jpg&name=large", title: "複数名でのハッカソン参加", from: new Date("2025-03-17T12:00:00.000Z"), to: new Date("2025-03-19T12:00:00.000Z"), description: "今回は、メンバー5人でのハッカソンに参加をしました!1人のチームと、スキルアップを目指している4人の計2グループでの参加です!", url: "https://x.com/Hackz_team/status/1902294529446965727" }, + { headerImage: "https://pbs.twimg.com/media/GkyBWsiaoAA0nwH?format=jpg&name=medium", title: "2度目のハッカソン優勝", from: new Date("2025-02-26T12:00:00.000Z"), to: new Date("2024-02-27T12:00:00.000Z"), description: "今回も株式会社ハックツ様のハッカソンに参加してきました!株式会社ヌーラボ様のオフィスをお借りしてのハッカソン、そして年1回の野良ハッカソンだったのでとても楽しかったです!そして副代表のthirld03が優勝しました!!", url: "https://x.com/Hackz_team/status/1895031776290185529" }, + { headerImage: "https://pbs.twimg.com/media/Ge1P7h9bkAARNb9?format=jpg&name=medium", title: "ハックツハッカソン初優勝", from: new Date("2024-03-01T12:00:00.000Z"), to: new Date("2024-03-02T12:00:00.000Z"), description: "株式会社ハックツ様主催のハッカソン「アンキロカップ」にて最優秀賞を獲得しました!2日間のハッカソン期間中徹夜で仕上げ、気持ちを込めた作品だったので優勝できてとても嬉しかったです!!", url: "https://x.com/Hackz_team/status/1868237310086770791" }, + { headerImage: "/app/components/assets/logo.webp", title: "学生団体Object結成", from: new Date("2024-03-01T12:00:00.000Z"), description: "念願の学生団体Objectを結成しました!これからたくさんの学生のスキルアップなどを目指した学生団体を目指し、この学生団体に入ってよかったと思っていただけるような団体を目指しがんばります!", url: "https://github.com/object-t/object-t-website" } +] export default function Home() { - return ; + const { t } = useTranslation(); + + return ( +
+
+
+
+

+ Object<T> +

+

+ {t("common.subtitle")} +

+
+
+
+
+ + +
+
+
+
+ {notices.map((notice, index) => ( + + ))} +
+
+
+
+
+
+ + +
+
+

「Object」のような
全ての出発地点になることを目指す

+
+
+

昨今、小学校でのプログラミング教育の義務化や、共通テストで「情報」という科目が追加されるなど、プログラミングを触れる機会が増えてきています。しかし、プログラミングの”楽しさ”や”魅力”というのは深く知る機会がないと思います。
そこで私たちは、活動を通してプログラミングでしか得ることができない楽しみや感動を知ってもらい、プログラマーの第一歩を踏み出すきっかけになってもらうことを目指します。

+
+
+
+
+ + +
+
+
+ {members.map((member, index) => ( + + ))} +
+
+
+
+
+ + +
+
+
+ {products.map((product, index) => ( + + ))} +
+
+
+
+
+ + +
+
+
+ {activities.map((activity, index) => ( + + ))} +
+
+
+
+ ); } diff --git a/public/assets/images/ogp_image.jpg b/public/assets/images/ogp_image.jpg new file mode 100644 index 0000000..ad31ef6 Binary files /dev/null and b/public/assets/images/ogp_image.jpg differ diff --git a/public/favicon.ico b/public/favicon.ico index 5dbdfcd..b0223d0 100644 Binary files a/public/favicon.ico and b/public/favicon.ico differ diff --git a/react-router.config.ts b/react-router.config.ts index 6ff16f9..b8b143a 100644 --- a/react-router.config.ts +++ b/react-router.config.ts @@ -3,5 +3,5 @@ import type { Config } from "@react-router/dev/config"; export default { // Config options... // Server-side render by default, to enable SPA mode set this to `false` - ssr: true, + ssr: false, } satisfies Config; diff --git a/tsconfig.json b/tsconfig.json index dc391a4..e838dc9 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -3,7 +3,7 @@ "**/*", "**/.server/**/*", "**/.client/**/*", - ".react-router/types/**/*" + ".react-router/types/**/*", ], "compilerOptions": { "lib": ["DOM", "DOM.Iterable", "ES2022"], diff --git a/vite.config.ts b/vite.config.ts index b706ce8..584b67a 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -6,7 +6,7 @@ import tsconfigPaths from "vite-tsconfig-paths"; export default defineConfig({ plugins: [ tailwindcss(), - !process.env.VITEST && reactRouter(), + !process.env.VITEST && [reactRouter()], tsconfigPaths() ], }); \ No newline at end of file