├── .editorconfig ├── .eslintrc.cjs ├── .gitignore ├── .prettierrc ├── LICENSE ├── README.md ├── next-env.d.ts ├── next.config.js ├── package-lock.json ├── package.json ├── panda.config.ts ├── postcss.config.cjs ├── public ├── favicon.ico └── fe2024_og.png ├── src ├── app │ ├── components │ │ ├── ChildcareSection │ │ │ ├── ChildcareSection.tsx │ │ │ ├── assets │ │ │ │ └── jaranda.png │ │ │ └── index.ts │ │ ├── CoCSection │ │ │ ├── CoCSection.tsx │ │ │ └── index.ts │ │ ├── ContactSection │ │ │ ├── ContactSection.tsx │ │ │ └── index.ts │ │ ├── FooterSection │ │ │ ├── FooterSection.tsx │ │ │ └── index.ts │ │ ├── HeroIntroWrap │ │ │ ├── HeroIntroWrap.tsx │ │ │ └── index.ts │ │ ├── HeroSection │ │ │ ├── HeroSection.tsx │ │ │ ├── components │ │ │ │ ├── DataLocation.tsx │ │ │ │ ├── HeroLogo.tsx │ │ │ │ └── index.ts │ │ │ └── index.ts │ │ ├── IntroSection │ │ │ ├── IntroSection.tsx │ │ │ └── index.ts │ │ ├── OpenSourceSection │ │ │ ├── OpenSourceSection.tsx │ │ │ ├── assets │ │ │ │ ├── babel.png │ │ │ │ ├── core-js.png │ │ │ │ ├── ky.png │ │ │ │ ├── pretendard.png │ │ │ │ ├── query.png │ │ │ │ ├── react-hook-form.png │ │ │ │ ├── ts-pattern.png │ │ │ │ └── zod.png │ │ │ └── index.ts │ │ ├── ProgramSection │ │ │ ├── ProgramSection.tsx │ │ │ ├── assets │ │ │ │ └── timetable.jpg │ │ │ ├── components │ │ │ │ ├── ProgramHeader.tsx │ │ │ │ ├── ProgramTab.tsx │ │ │ │ ├── SessionList.tsx │ │ │ │ ├── components │ │ │ │ │ ├── ArrowIcon.tsx │ │ │ │ │ ├── ClockIcon.tsx │ │ │ │ │ ├── CloseIcon.tsx │ │ │ │ │ ├── SessionModal.tsx │ │ │ │ │ └── index.ts │ │ │ │ └── index.ts │ │ │ └── index.ts │ │ ├── SponsorSection │ │ │ ├── SponsorSection.tsx │ │ │ ├── assets │ │ │ │ └── it.png │ │ │ ├── comopnents │ │ │ │ ├── SponsorInfo.tsx │ │ │ │ ├── assets │ │ │ │ │ ├── F-Lab.png │ │ │ │ │ ├── carrot.png │ │ │ │ │ ├── googlecloud.png │ │ │ │ │ ├── imweb.png │ │ │ │ │ ├── oliveyoung.png │ │ │ │ │ ├── soomgo.png │ │ │ │ │ ├── stibee.png │ │ │ │ │ ├── toss.png │ │ │ │ │ ├── wooritech.png │ │ │ │ │ └── wyyyes.png │ │ │ │ └── index.ts │ │ │ └── index.ts │ │ └── index.ts │ ├── globals.css │ ├── layout.tsx │ ├── page.tsx │ └── reset.css ├── features │ ├── .gitkeep │ ├── aurora │ │ ├── assets │ │ │ ├── background.png │ │ │ ├── background2.png │ │ │ ├── background3.png │ │ │ ├── background4-half.png │ │ │ ├── background4-min.png │ │ │ ├── background4-tiny.png │ │ │ ├── background4.png │ │ │ └── bg2-tiny.png │ │ ├── components │ │ │ ├── Aurora.tsx │ │ │ └── index.ts │ │ └── contexts │ │ │ ├── AuroraContext.tsx │ │ │ └── index.ts │ ├── effects │ │ └── components │ │ │ ├── AuroraContainer.tsx │ │ │ ├── SphereContainer.tsx │ │ │ ├── SphereEffect │ │ │ ├── SphereEffect.tsx │ │ │ ├── assets │ │ │ │ └── img_noise.png │ │ │ ├── components │ │ │ │ ├── GradientSphere │ │ │ │ │ ├── ExtendedMaterial.ts │ │ │ │ │ ├── GradientSphere.tsx │ │ │ │ │ ├── MeshPhysicalMaterialWithGlow.tsx │ │ │ │ │ ├── ReactExtendedMaterial.tsx │ │ │ │ │ ├── assets │ │ │ │ │ │ ├── gradient1.jpg │ │ │ │ │ │ ├── gradient2.jpg │ │ │ │ │ │ └── gradient3.jpg │ │ │ │ │ ├── external │ │ │ │ │ │ ├── BlurShader.ts │ │ │ │ │ │ ├── BokehPass2.js │ │ │ │ │ │ ├── BokehShader.ts │ │ │ │ │ │ ├── FixedNoiseEffect.ts │ │ │ │ │ │ ├── KawaseBlurPass.js │ │ │ │ │ │ ├── KawaseBlurShader.js │ │ │ │ │ │ ├── ReactFixedNoiseEffect.ts │ │ │ │ │ │ └── _BlurPass.ts │ │ │ │ │ └── index.ts │ │ │ │ └── index.ts │ │ │ └── index.ts │ │ │ └── index.ts │ ├── hooks │ │ ├── useHeroScreen.ts │ │ └── usePrefersReducedMotion.ts │ └── programs │ │ ├── constants │ │ └── index.ts │ │ ├── contexts │ │ └── index.tsx │ │ ├── data │ │ └── sessions.ts │ │ └── types │ │ └── index.ts └── shared │ ├── components │ ├── Button │ │ ├── Button.tsx │ │ └── index.ts │ ├── Column.tsx │ ├── FadeIn.tsx │ ├── Footer │ │ ├── Footer.tsx │ │ ├── components │ │ │ ├── Logo.tsx │ │ │ └── index.ts │ │ ├── icons │ │ │ └── index.tsx │ │ └── index.ts │ ├── Header │ │ ├── Header.tsx │ │ ├── components │ │ │ ├── Logo.tsx │ │ │ └── index.ts │ │ └── index.ts │ ├── MainCTAButton │ │ ├── MainCTAButton.tsx │ │ ├── hooks │ │ │ └── index.ts │ │ └── index.ts │ ├── SectionTitle.tsx │ └── index.ts │ ├── constants │ └── index.ts │ └── icons │ ├── LocationIcon.tsx │ ├── TimeIcon.tsx │ └── index.ts ├── tsconfig.json └── yarn.lock /.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 2 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.md] 12 | max_line_length = off 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | /** 2 | * This is intended to be a basic starting point for linting in your app. 3 | * It relies on recommended configs out of the box for simplicity, but you can 4 | * and should modify this configuration to best suit your team's needs. 5 | */ 6 | 7 | /** @type {import('eslint').Linter.Config} */ 8 | module.exports = { 9 | root: true, 10 | parserOptions: { 11 | ecmaVersion: 'latest', 12 | sourceType: 'module', 13 | ecmaFeatures: { 14 | jsx: true, 15 | }, 16 | }, 17 | env: { 18 | browser: true, 19 | commonjs: true, 20 | es6: true, 21 | }, 22 | 23 | // Base config 24 | extends: ['eslint:recommended', 'eslint-config-prettier'], 25 | plugins: ['prettier'], 26 | rules: { 27 | 'prettier/prettier': 'error', 28 | }, 29 | 30 | overrides: [ 31 | // React 32 | { 33 | files: ['**/*.{js,jsx,ts,tsx}'], 34 | plugins: ['react', 'jsx-a11y'], 35 | extends: [ 36 | 'plugin:react/recommended', 37 | 'plugin:react/jsx-runtime', 38 | 'plugin:react-hooks/recommended', 39 | 'plugin:jsx-a11y/recommended', 40 | ], 41 | settings: { 42 | react: { 43 | version: 'detect', 44 | }, 45 | formComponents: ['Form'], 46 | linkComponents: [ 47 | { name: 'Link', linkAttribute: 'to' }, 48 | { name: 'NavLink', linkAttribute: 'to' }, 49 | ], 50 | 'import/resolver': { 51 | typescript: {}, 52 | }, 53 | }, 54 | rules: { 55 | 'react/display-name': 'off', 56 | 'react/no-unknown-property': 'off', 57 | }, 58 | }, 59 | 60 | // Typescript 61 | { 62 | files: ['**/*.{ts,tsx}'], 63 | plugins: ['@typescript-eslint', 'import'], 64 | parser: '@typescript-eslint/parser', 65 | settings: { 66 | 'import/internal-regex': '^~/', 67 | 'import/resolver': { 68 | node: { 69 | extensions: ['.ts', '.tsx'], 70 | }, 71 | typescript: { 72 | alwaysTryTypes: true, 73 | }, 74 | }, 75 | }, 76 | rules: { 77 | '@typescript-eslint/semi': ['error'], 78 | }, 79 | extends: [ 80 | 'plugin:@typescript-eslint/recommended', 81 | 'plugin:import/recommended', 82 | 'plugin:import/typescript', 83 | ], 84 | }, 85 | 86 | // Node 87 | { 88 | files: ['.eslintrc.cjs'], 89 | env: { 90 | node: true, 91 | }, 92 | }, 93 | ], 94 | }; 95 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /dist 4 | /out 5 | 6 | 7 | # Log files 8 | npm-debug.log* 9 | yarn-debug.log* 10 | yarn-error.log* 11 | pnpm-debug.log* 12 | 13 | # Editor directories and files 14 | .idea 15 | .vscode 16 | *.suo 17 | *.ntvs* 18 | *.njsproj 19 | *.sln 20 | *.sw? 21 | 22 | # ignore local settings 23 | .env.local 24 | .next 25 | 26 | ## Panda 27 | styled-system 28 | styled-system-studio 29 | .vercel 30 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 80, 3 | "singleQuote": true, 4 | "trailingComma": "es5", 5 | "tabWidth": 2, 6 | "arrowParens": "avoid" 7 | } 8 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 FEDG KR - Frontend Development Group Korea 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app). 2 | 3 | ## Getting Started 4 | 5 | First, run the development server: 6 | 7 | ```bash 8 | npm run dev 9 | # or 10 | yarn dev 11 | # or 12 | pnpm dev 13 | # or 14 | bun dev 15 | ``` 16 | 17 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. 18 | 19 | You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file. 20 | 21 | This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font. 22 | 23 | ## Learn More 24 | 25 | To learn more about Next.js, take a look at the following resources: 26 | 27 | - [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. 28 | - [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. 29 | 30 | You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome! 31 | 32 | ## Deploy on Vercel 33 | 34 | The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. 35 | 36 | Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details. 37 | -------------------------------------------------------------------------------- /next-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | 4 | // NOTE: This file should not be edited 5 | // see https://nextjs.org/docs/basic-features/typescript for more information. 6 | -------------------------------------------------------------------------------- /next.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | const nextConfig = { 3 | output: 'export', 4 | }; 5 | 6 | module.exports = nextConfig; 7 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "feconf2024", 3 | "version": "1.0.0", 4 | "private": true, 5 | "repository": "git@github.com:fedgkr/feconf2024.git", 6 | "contributors": [ 7 | { 8 | "name": "Chaehyun Kim", 9 | "email": "elina001106@gmail.com", 10 | "url": "https://www.linkedin.com/in/chaehyunkim1106" 11 | }, 12 | { 13 | "name": "Juyun Cheon", 14 | "email": "ewq3167@gmail.com", 15 | "url": "https://www.linkedin.com/in/juyun-cheon-713b37243" 16 | }, 17 | { 18 | "name": "Daybrush", 19 | "email": "daybrush@gmail.com", 20 | "url": "https://github.com/daybrush" 21 | }, 22 | { 23 | "name": "Jooyoung Moon", 24 | "email": "hckrmoon@gmail.com", 25 | "url": "https://github.com/codemilli" 26 | } 27 | ], 28 | "homepage": "https://2024.feconf.kr", 29 | "license": "MIT", 30 | "scripts": { 31 | "prepare": "panda codegen", 32 | "dev": "next dev", 33 | "build": "next build", 34 | "start": "next start", 35 | "lint": "next lint", 36 | "env": "touch out/.nojekyll && echo '2024.feconf.kr' >> out/CNAME", 37 | "push": "push-dir --dir=out --branch=gh-pages --cleanup --verbose", 38 | "deploy": "npm run build && npm run env && npm run push" 39 | }, 40 | "dependencies": { 41 | "@radix-ui/react-dialog": "^1.1.1", 42 | "@react-three/drei": "^9.105.4", 43 | "@react-three/fiber": "^8.16.2", 44 | "@react-three/postprocessing": "^2.16.2", 45 | "framer-motion": "^11.3.2", 46 | "lodash-es": "^4.17.21", 47 | "next": "14.2.3", 48 | "react": "^18", 49 | "react-dom": "^18", 50 | "react-intersection-observer": "^9.13.0", 51 | "three": "^0.163.0", 52 | "use-intersection": "^0.2.1" 53 | }, 54 | "devDependencies": { 55 | "@pandacss/dev": "^0.43.0", 56 | "@types/dat.gui": "^0.7.13", 57 | "@types/lodash-es": "^4.17.12", 58 | "@types/node": "^20", 59 | "@types/react": "^18", 60 | "@types/react-dom": "^18", 61 | "@types/three": "^0.163.0", 62 | "@typescript-eslint/eslint-plugin": "^6.7.4", 63 | "@typescript-eslint/parser": "^6.7.4", 64 | "dat.gui": "^0.7.9", 65 | "eslint": "^8.57.0", 66 | "eslint-config-next": "14.2.5", 67 | "eslint-config-prettier": "^9.1.0", 68 | "eslint-import-resolver-typescript": "^3.6.1", 69 | "eslint-plugin-import": "^2.28.1", 70 | "eslint-plugin-jsx-a11y": "^6.7.1", 71 | "eslint-plugin-prettier": "^5.1.3", 72 | "eslint-plugin-react": "^7.35.0", 73 | "eslint-plugin-react-hooks": "^4.6.0", 74 | "prettier": "^3.2.5", 75 | "push-dir": "^0.4.1", 76 | "typescript": "^5.1.6" 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /panda.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "@pandacss/dev"; 2 | 3 | export default defineConfig({ 4 | // Whether to use css reset 5 | preflight: true, 6 | 7 | // Where to look for your css declarations 8 | include: ["./src/**/*.{js,jsx,ts,tsx}", "./pages/**/*.{js,jsx,ts,tsx}"], 9 | 10 | // Files to exclude 11 | exclude: [], 12 | 13 | // Useful for theme customization 14 | theme: { 15 | extend: {}, 16 | }, 17 | 18 | // The output directory for your css system 19 | outdir: "styled-system", 20 | 21 | // The JSX framework to use 22 | jsxFramework: "react", 23 | }); 24 | -------------------------------------------------------------------------------- /postcss.config.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | '@pandacss/dev/postcss': {}, 4 | }, 5 | } -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fedgkr/feconf2024/e3d1fee15b66c8927cd4c61fdb747283e9cc0211/public/favicon.ico -------------------------------------------------------------------------------- /public/fe2024_og.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fedgkr/feconf2024/e3d1fee15b66c8927cd4c61fdb747283e9cc0211/public/fe2024_og.png -------------------------------------------------------------------------------- /src/app/components/ChildcareSection/ChildcareSection.tsx: -------------------------------------------------------------------------------- 1 | import { FC } from 'react'; 2 | import { styled } from '@styled-system/jsx'; 3 | import { Column, FadeIn, SectionTitle } from '~/shared/components'; 4 | 5 | import jaranda from './assets/jaranda.png'; 6 | import { motion, Variants } from 'framer-motion'; 7 | import { useInView } from 'react-intersection-observer'; 8 | 9 | const container: Variants = { 10 | visible: { 11 | transition: { 12 | staggerChildren: 0.15, 13 | delayChildren: 0.15, 14 | }, 15 | }, 16 | }; 17 | 18 | const ChildcareSection: FC = () => { 19 | const { ref, inView } = useInView({ 20 | triggerOnce: true, 21 | }); 22 | return ( 23 |
28 | 29 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | FEConf2024는 자녀 동반 참가자를 위한 현장 아이돌봄 케어 서비스를 40 | 제공합니다. 41 |
42 | 육아 케어 전문 기업 자란다와 함께 안전하고 보람찬 아이돌봄 43 | 서비스를 운영합니다. 44 |
45 |
46 |
47 |
48 |
49 | ); 50 | }; 51 | 52 | const Section = styled(motion.section, { 53 | base: { 54 | position: 'relative', 55 | padding: { 56 | base: '60px 20px', 57 | xl: '150px 0', 58 | }, 59 | }, 60 | }); 61 | 62 | const Info = styled(FadeIn, { 63 | base: { 64 | marginTop: { 65 | base: '50px', 66 | xl: '100px', 67 | }, 68 | display: 'flex', 69 | width: '100%', 70 | maxWidth: '920px', 71 | padding: { 72 | base: '20px 25px 30px 25px', 73 | xl: '40px 25px 60px 25px', 74 | }, 75 | flexDirection: 'column', 76 | justifyContent: 'center', 77 | alignItems: 'center', 78 | gap: { 79 | base: '20px', 80 | xl: '40px', 81 | }, 82 | borderRadius: { 83 | base: '10px', 84 | xl: '20px', 85 | }, 86 | background: 'rgba(78, 77, 96, 0.2)', 87 | boxShadow: '20px 20px 200px 0px rgba(1, 3, 8, 0.07)', 88 | }, 89 | }); 90 | 91 | const Image = styled('img', { 92 | base: { 93 | width: { 94 | base: '188px', 95 | xl: '357px', 96 | }, 97 | height: { 98 | base: '59px', 99 | xl: '113px', 100 | }, 101 | }, 102 | }); 103 | 104 | const Description = styled('p', { 105 | base: { 106 | color: 'rgba(255, 255, 255, 0.7)', 107 | textAlign: 'center', 108 | fontSize: { 109 | base: '14px', 110 | xl: '18px', 111 | }, 112 | fontStyle: 'normal', 113 | fontWeight: '500', 114 | lineHeight: '160%' /* 28.8px */, 115 | }, 116 | }); 117 | 118 | export default ChildcareSection; 119 | -------------------------------------------------------------------------------- /src/app/components/ChildcareSection/assets/jaranda.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fedgkr/feconf2024/e3d1fee15b66c8927cd4c61fdb747283e9cc0211/src/app/components/ChildcareSection/assets/jaranda.png -------------------------------------------------------------------------------- /src/app/components/ChildcareSection/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from './ChildcareSection'; 2 | -------------------------------------------------------------------------------- /src/app/components/CoCSection/CoCSection.tsx: -------------------------------------------------------------------------------- 1 | import { FC } from 'react'; 2 | import { styled } from '@styled-system/jsx'; 3 | import { Column, FadeIn, SectionTitle } from '~/shared/components'; 4 | import { motion, Variants } from 'framer-motion'; 5 | import { useInView } from 'react-intersection-observer'; 6 | 7 | const container: Variants = { 8 | visible: { 9 | transition: { 10 | staggerChildren: 0.15, 11 | delayChildren: 0.15, 12 | }, 13 | }, 14 | }; 15 | 16 | const CoCSection: FC = () => { 17 | const { ref, inView } = useInView({ 18 | triggerOnce: true, 19 | }); 20 | return ( 21 |
26 | 27 | 31 | 32 | 33 | 34 | 35 | 다양성 36 | 37 | FEConf는 개개인의 정체성과 개성 및 취향을 존중합니다. 하지만 38 | 성별, 성 정체성, 외모, 인종, 종교, 지역, 장애, 나이, 국가, 39 | 약자 등에 대한 혐오와 폭력은 어떤 방식이라도 허용하지 40 | 않습니다. 41 | 42 | 43 | 44 | 사회적 책임 45 | 46 | FEConf참여자는 프론트엔드 분야의 성장에 대한 사회적 책임을 47 | 가집니다. 내가 알고 있는 지식은 아무리 작은 것이라도 다른 48 | 누군가에 도움을 줄 수 있습니다. 이를 다양한 방법으로 49 | 공유하세요. 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 서로 돕고 협력하기 58 | 59 | 참여자의 다양한 배경이 협업과 커뮤니케이션을 방해하는 요소가 60 | 될 수 없습니다. 도움을 요청하기 전에 먼저 도움을 주고 자신의 61 | 생각을 자유롭게 표현할 수 있는 FEConf가 될 수 있도록 노력해 62 | 주세요. 63 | 64 | 65 | 66 | 지식 재산권 및 개인 정보 67 | 68 | FEConf는 지식 재산권과 개인 정보 등의 권리를 존중합니다. 지식 69 | 재산권을 위배하거나 개인 정보를 침해하는 어떠한 콘텐츠도 70 | FEConf에서 사용할 수 없습니다. 71 | 72 | 73 | 74 | 75 | 76 | 77 |
78 | ); 79 | }; 80 | 81 | const Section = styled(motion.section, { 82 | base: { 83 | position: 'relative', 84 | padding: { 85 | base: '60px 20px', 86 | xl: '150px 0', 87 | }, 88 | }, 89 | }); 90 | 91 | const CoCList = styled('ul', { 92 | base: { 93 | display: 'flex', 94 | flexDirection: 'column', 95 | marginTop: { 96 | base: '50px', 97 | xl: '130px', 98 | }, 99 | gap: { 100 | base: '20px', 101 | xl: '30px', 102 | }, 103 | }, 104 | }); 105 | 106 | const CoCItem = styled('li', { 107 | base: { 108 | display: 'flex', 109 | }, 110 | }); 111 | 112 | const Wrap = styled(FadeIn, { 113 | base: { 114 | display: 'flex', 115 | justifyContent: 'center', 116 | flexDirection: { 117 | base: 'column', 118 | xl: 'row', 119 | }, 120 | gap: { 121 | base: '20px', 122 | xl: '30px', 123 | }, 124 | }, 125 | }); 126 | 127 | const Item = styled(FadeIn, { 128 | base: { 129 | display: 'flex', 130 | flexDirection: 'column', 131 | width: { 132 | base: '100%', 133 | xl: '540px', 134 | }, 135 | padding: { 136 | base: '20px', 137 | xl: '40px', 138 | }, 139 | alignItems: 'flex-start', 140 | flexShrink: 0, 141 | borderRadius: { 142 | base: '10px', 143 | xl: '20px', 144 | }, 145 | background: 'rgba(78, 77, 96, 0.2)', 146 | }, 147 | }); 148 | 149 | const Title = styled('h4', { 150 | base: { 151 | color: '#fff', 152 | fontSize: '16px', 153 | fontStyle: 'normal', 154 | fontWeight: '700', 155 | lineHeight: '140%', 156 | }, 157 | }); 158 | 159 | const Description = styled('p', { 160 | base: { 161 | color: 'rgba(255, 255, 255, 0.8)', 162 | fontSize: '14px', 163 | fontStyle: 'normal', 164 | fontWeight: '400', 165 | lineHeight: '160%', 166 | marginTop: { 167 | base: '10px', 168 | xl: '20px', 169 | }, 170 | }, 171 | }); 172 | 173 | export default CoCSection; 174 | -------------------------------------------------------------------------------- /src/app/components/CoCSection/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from './CoCSection'; 2 | -------------------------------------------------------------------------------- /src/app/components/ContactSection/ContactSection.tsx: -------------------------------------------------------------------------------- 1 | import { FC } from 'react'; 2 | import { styled } from '@styled-system/jsx'; 3 | import { Column, FadeIn, SectionTitle } from '~/shared/components'; 4 | import { motion, Variants } from 'framer-motion'; 5 | import { useInView } from 'react-intersection-observer'; 6 | import { EMAIL, LINKED_IN_LINK } from '~/shared/constants'; 7 | 8 | const container: Variants = { 9 | visible: { 10 | transition: { 11 | staggerChildren: 0.15, 12 | delayChildren: 0.15, 13 | }, 14 | }, 15 | }; 16 | 17 | const ContactSection: FC = () => { 18 | const { ref, inView } = useInView({ 19 | triggerOnce: true, 20 | }); 21 | return ( 22 |
27 | 28 | 29 | 30 | 31 | Share 32 | 33 | 국내 최고의 프론트엔드 개발 인사이트를 얻을 수 있는 기회를 34 | 공유하여 함께 배우고, 함께 성장해보세요. 35 | 36 | 37 | 44 | 45 | 52 | 53 | 54 | LinkedIn 55 | 56 | 57 | 58 | Contact 59 | 60 | 접근성 관련하여 행사 참석에 도움이 필요하실 경우, 메일로 편하게 61 | 연락주세요. 프론트엔드 개발자에 의한, 프론트엔드 개발자를 위한 62 | FEConf의 발전을 위해 도움을 주실 분도 언제든 환영합니다. 63 | 64 | 65 | 72 | 73 | 81 | 82 | 83 | 메일 보내기 84 | 85 | 86 | 87 | 88 |
89 | ); 90 | }; 91 | 92 | const Section = styled(motion.section, { 93 | base: { 94 | position: 'relative', 95 | padding: { 96 | base: '60px 20px', 97 | xl: '150px 0', 98 | }, 99 | }, 100 | }); 101 | 102 | const Wrap = styled('div', { 103 | base: { 104 | marginTop: { 105 | base: '50px', 106 | xl: '130px', 107 | }, 108 | display: 'flex', 109 | flexDirection: { 110 | base: 'column', 111 | xl: 'row', 112 | }, 113 | gap: { 114 | base: '20px', 115 | xl: '50px', 116 | }, 117 | }, 118 | }); 119 | 120 | const Item = styled(FadeIn, { 121 | base: { 122 | position: 'relative', 123 | display: 'flex', 124 | width: { 125 | base: '100%', 126 | xl: '540px', 127 | }, 128 | height: { 129 | base: 'initial', 130 | xl: '336px', 131 | }, 132 | padding: { 133 | base: '20px 20px 100px 20px', 134 | xl: '50px', 135 | }, 136 | flexDirection: 'column', 137 | alignItems: 'flex-start', 138 | borderRadius: { 139 | base: '10px', 140 | xl: '20px', 141 | }, 142 | background: 'rgba(78, 77, 96, 0.2)', 143 | }, 144 | }); 145 | 146 | const Title = styled('h4', { 147 | base: { 148 | color: '#fff', 149 | textAlign: 'center', 150 | fontSize: { 151 | base: '16px', 152 | xl: '30px', 153 | }, 154 | fontStyle: 'normal', 155 | fontWeight: '500', 156 | lineHeight: '130%', 157 | letterSpacing: '1.2px', 158 | }, 159 | }); 160 | 161 | const Description = styled('div', { 162 | base: { 163 | marginTop: '20px', 164 | color: 'rgba(255, 255, 255, 0.8)', 165 | fontSize: '14px', 166 | fontStyle: 'normal', 167 | fontWeight: '500', 168 | lineHeight: '160%', 169 | }, 170 | }); 171 | 172 | const Link = styled('a', { 173 | base: { 174 | position: 'absolute', 175 | right: { 176 | base: '20px', 177 | xl: '52px', 178 | }, 179 | bottom: { 180 | base: '30px', 181 | xl: '50px', 182 | }, 183 | display: 'flex', 184 | padding: { 185 | base: '10px 14px', 186 | xl: '16px 22px', 187 | }, 188 | justifyContent: 'center', 189 | alignItems: 'center', 190 | borderRadius: { 191 | base: '5px', 192 | xl: '10px', 193 | }, 194 | gap: { 195 | base: '6px', 196 | xl: '12px', 197 | }, 198 | color: '#010308', 199 | fontSize: { 200 | base: '14px', 201 | xl: '16px', 202 | }, 203 | fontStyle: 'normal', 204 | fontWeight: '700', 205 | lineHeight: '130%', 206 | background: '#fff', 207 | cursor: 'pointer', 208 | backdropFilter: 'blur(3.1121041774749756px)', 209 | '& > svg': { 210 | width: { 211 | base: '20px', 212 | xl: 'initial', 213 | }, 214 | height: { 215 | base: '20px', 216 | xl: 'initial', 217 | }, 218 | }, 219 | }, 220 | }); 221 | 222 | export default ContactSection; 223 | -------------------------------------------------------------------------------- /src/app/components/ContactSection/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from './ContactSection'; 2 | -------------------------------------------------------------------------------- /src/app/components/FooterSection/FooterSection.tsx: -------------------------------------------------------------------------------- 1 | import { FC } from 'react'; 2 | import { styled } from '@styled-system/jsx'; 3 | import { Column, FadeIn, MainCTAButton } from '~/shared/components'; 4 | import { 5 | DataLocation, 6 | HeroLogo, 7 | } from '~/app/components/HeroSection/components'; 8 | import { motion, Variants } from 'framer-motion'; 9 | import { useInView } from 'react-intersection-observer'; 10 | 11 | const container: Variants = { 12 | visible: { 13 | transition: { 14 | staggerChildren: 0.15, 15 | delayChildren: 0.15, 16 | }, 17 | }, 18 | }; 19 | 20 | const FooterSection: FC = () => { 21 | const { ref, inView } = useInView({ 22 | triggerOnce: true, 23 | }); 24 | return ( 25 |
30 | 31 | 32 | 8월 24일 토요일, FEConf와 함께해요 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 |
49 | ); 50 | }; 51 | 52 | const Section = styled(motion.section, { 53 | base: { 54 | position: 'relative', 55 | marginTop: { 56 | base: '60px', 57 | xl: '150px', 58 | }, 59 | marginBottom: '160px', 60 | padding: { 61 | base: '0 20px', 62 | }, 63 | }, 64 | }); 65 | 66 | const Title = styled('h3', { 67 | base: { 68 | color: '#fff', 69 | textAlign: 'center', 70 | fontSize: { 71 | base: '18px', 72 | xl: '34px', 73 | }, 74 | fontStyle: 'normal', 75 | fontWeight: '600', 76 | lineHeight: '140%', 77 | }, 78 | }); 79 | 80 | const LogoWrap = styled('div', { 81 | base: { 82 | marginTop: { 83 | base: '30px', 84 | xl: '50px', 85 | }, 86 | '& > svg': { 87 | width: { 88 | base: '300px', 89 | xl: 'initial', 90 | }, 91 | height: { 92 | base: '64px', 93 | xl: 'initial', 94 | }, 95 | }, 96 | }, 97 | }); 98 | 99 | const InfoWrap = styled('div', { 100 | base: { 101 | marginTop: { 102 | base: '40px', 103 | xl: '60px', 104 | }, 105 | }, 106 | }); 107 | 108 | const ButtonWrap = styled(FadeIn, { 109 | base: { 110 | width: '100%', 111 | textAlign: 'center', 112 | marginTop: { 113 | base: '40px', 114 | xl: '90px', 115 | }, 116 | }, 117 | }); 118 | 119 | export default FooterSection; 120 | -------------------------------------------------------------------------------- /src/app/components/FooterSection/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from './FooterSection'; 2 | -------------------------------------------------------------------------------- /src/app/components/HeroIntroWrap/HeroIntroWrap.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import { FC, useEffect, useState } from 'react'; 4 | import HeroSection from '../HeroSection'; 5 | import IntroSection from '../IntroSection'; 6 | import { useInView } from 'react-intersection-observer'; 7 | import { useAurora } from '~/features/aurora/contexts'; 8 | 9 | const HeroIntroWrap: FC = () => { 10 | const { ref: heroRef, inView: heroInView } = useInView({ 11 | threshold: 0.3, 12 | triggerOnce: true, 13 | }); 14 | const { ref: introRef, inView: introInView } = useInView({ 15 | threshold: 0.3, 16 | triggerOnce: true, 17 | }); 18 | const { show, hide } = useAurora(); 19 | const [mounted, setMounted] = useState(false); 20 | useEffect(() => { 21 | setTimeout(() => setMounted(true), 200); 22 | }, []); 23 | useEffect(() => { 24 | const isInSection = heroInView || introInView; 25 | isInSection ? hide() : show(); 26 | }, [heroInView, introInView, show, hide]); 27 | return ( 28 | <> 29 | 30 | 31 | 32 | ); 33 | }; 34 | 35 | export default HeroIntroWrap; 36 | -------------------------------------------------------------------------------- /src/app/components/HeroIntroWrap/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from './HeroIntroWrap'; 2 | -------------------------------------------------------------------------------- /src/app/components/HeroSection/HeroSection.tsx: -------------------------------------------------------------------------------- 1 | import { forwardRef } from 'react'; 2 | import { styled } from '@styled-system/jsx'; 3 | import { DataLocation, HeroLogo } from './components'; 4 | import { Column, FadeIn, MainCTAButton } from '~/shared/components'; 5 | import { motion, Variants } from 'framer-motion'; 6 | 7 | interface Props { 8 | active?: boolean; 9 | } 10 | 11 | const container: Variants = { 12 | visible: { 13 | transition: { 14 | staggerChildren: 0.15, 15 | delayChildren: 0.2, 16 | }, 17 | }, 18 | }; 19 | 20 | const HeroSection = forwardRef(({ active }, ref) => { 21 | return ( 22 |
27 | 28 | 29 | 뜨거운 열정을 가진 당신이 올해의 주인공 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 |
42 | ); 43 | }); 44 | 45 | const Section = styled(motion.section, { 46 | base: { 47 | position: 'relative', 48 | height: '100vh', 49 | minHeight: '800px', 50 | }, 51 | }); 52 | 53 | const Container = styled(Column, { 54 | base: { 55 | position: 'absolute', 56 | top: '50%', 57 | width: '100%', 58 | padding: '0 20px', 59 | transform: 'translateY(-50%) translateY(-40px)', 60 | }, 61 | }); 62 | 63 | const Title = styled(FadeIn, { 64 | base: { 65 | fontSize: { 66 | base: '18px', 67 | xl: '24px', 68 | }, 69 | fontWeight: '600', 70 | lineHeight: '1.3', 71 | color: '#ffffff', 72 | }, 73 | }); 74 | 75 | const LogoWrap = styled(FadeIn, { 76 | base: { 77 | marginTop: { 78 | base: '30px', 79 | xl: '50px', 80 | }, 81 | '& svg': { 82 | width: { 83 | base: '300px', 84 | xl: 'initial', 85 | }, 86 | height: { 87 | base: '64px', 88 | xl: 'initial', 89 | }, 90 | }, 91 | }, 92 | }); 93 | 94 | const Info = styled(FadeIn, { 95 | base: { 96 | marginTop: { 97 | base: '40px', 98 | xl: '78px', 99 | }, 100 | }, 101 | }); 102 | 103 | const ButtonWrap = styled(FadeIn, { 104 | base: { 105 | width: '100%', 106 | textAlign: 'center', 107 | marginTop: { 108 | base: '50px', 109 | xl: '90px', 110 | }, 111 | }, 112 | }); 113 | 114 | export default HeroSection; 115 | -------------------------------------------------------------------------------- /src/app/components/HeroSection/components/DataLocation.tsx: -------------------------------------------------------------------------------- 1 | import { FC } from 'react'; 2 | import { styled } from '@styled-system/jsx'; 3 | import { LocationIcon, TimeIcon } from '~/shared/icons'; 4 | 5 | const DataLocation: FC = () => { 6 | return ( 7 | 8 | 9 | 10 | 11 | 12 | 2024.8.24 13 | 14 | 15 | 16 | 17 | 18 | 세종대학교 광개토회관 19 | 20 | 21 | ); 22 | }; 23 | 24 | const Container = styled('div', { 25 | base: { 26 | display: 'flex', 27 | gap: { 28 | base: '12px', 29 | xl: '30px', 30 | }, 31 | }, 32 | }); 33 | 34 | const Wrap = styled('div', { 35 | base: { 36 | display: 'flex', 37 | alignItems: 'center', 38 | gap: { 39 | base: '8px', 40 | xl: '20px', 41 | }, 42 | }, 43 | }); 44 | 45 | const IconWrap = styled('div', { 46 | base: { 47 | display: 'flex', 48 | alignItems: 'center', 49 | justifyContent: 'center', 50 | width: { 51 | base: '26px', 52 | xl: '34px', 53 | }, 54 | height: { 55 | base: '26px', 56 | xl: '34px', 57 | }, 58 | borderRadius: '10px', 59 | background: 'rgba(255, 255, 255, 0.3)', 60 | '& svg': { 61 | width: { 62 | base: '15px', 63 | xl: 'initial', 64 | }, 65 | height: { 66 | base: '15px', 67 | xl: 'initial', 68 | }, 69 | }, 70 | }, 71 | }); 72 | 73 | const Text = styled('span', { 74 | base: { 75 | color: '#fff', 76 | textAlign: 'center', 77 | fontVariantNumeric: 'lining-nums tabular-nums', 78 | fontSize: { 79 | base: '14px', 80 | xl: '20px', 81 | }, 82 | fontStyle: 'normal', 83 | fontWeight: '600', 84 | lineHeight: '140%', 85 | }, 86 | }); 87 | 88 | export default DataLocation; 89 | -------------------------------------------------------------------------------- /src/app/components/HeroSection/components/HeroLogo.tsx: -------------------------------------------------------------------------------- 1 | import { FC, SVGProps } from 'react'; 2 | 3 | interface Props extends SVGProps {} 4 | 5 | const HeroLogo: FC = ({ ...props }) => { 6 | return ( 7 | 15 | 19 | 23 | 27 | 31 | 35 | 39 | 40 | 48 | 49 | 50 | 51 | 52 | 53 | ); 54 | }; 55 | 56 | export default HeroLogo; 57 | -------------------------------------------------------------------------------- /src/app/components/HeroSection/components/index.ts: -------------------------------------------------------------------------------- 1 | export { default as DataLocation } from './DataLocation'; 2 | export { default as HeroLogo } from './HeroLogo'; 3 | -------------------------------------------------------------------------------- /src/app/components/HeroSection/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from './HeroSection'; 2 | -------------------------------------------------------------------------------- /src/app/components/IntroSection/IntroSection.tsx: -------------------------------------------------------------------------------- 1 | import { forwardRef } from 'react'; 2 | import { styled } from '@styled-system/jsx'; 3 | import { Column, FadeIn } from '~/shared/components'; 4 | import { motion, Variants } from 'framer-motion'; 5 | import { SphereContainer } from '~/features/effects/components/SphereContainer'; 6 | 7 | interface Props { 8 | active?: boolean; 9 | } 10 | 11 | const container: Variants = { 12 | visible: { 13 | transition: { 14 | staggerChildren: 0.15, 15 | delayChildren: 0.3, 16 | }, 17 | }, 18 | }; 19 | 20 | const IntroSection = forwardRef(({ active }, ref) => { 21 | return ( 22 |
27 | 28 | 29 | 30 | 31 | <Highlight>프론트엔드 엔지니어</Highlight>들에 의한, 32 | 33 | 34 | 35 | 36 | <Highlight>프론트엔드 엔지니어</Highlight>들을 위한 37 | 38 | 39 | 40 | 41 | 국내 최고 컨퍼런스 <Highlight>FEConf</Highlight> 42 | 43 | 44 | 45 | 46 | 더 나은 프론트엔드 엔지니어링을 위해 노력하는 이들이 함께 모여 47 | 현장에서 겪는 다양한 문제를 함께 공유하고 성장합니다. 48 | 49 | 50 | 51 |
52 | ); 53 | }); 54 | 55 | const Section = styled(motion.section, { 56 | base: { 57 | position: 'relative', 58 | padding: { 59 | base: '200px 0 300px 0', 60 | xl: '180px 0 300px 0', 61 | }, 62 | }, 63 | }); 64 | 65 | const Title = styled('h1', { 66 | base: { 67 | fontSize: { 68 | base: '26px', 69 | xl: '54px', 70 | }, 71 | fontWeight: '700', 72 | lineHeight: '2', 73 | color: 'rgba(1, 3, 8, 0.5)', 74 | }, 75 | }); 76 | 77 | const Description = styled('p', { 78 | base: { 79 | width: { 80 | base: '313px', 81 | xl: '492px', 82 | }, 83 | marginTop: { 84 | base: '60px', 85 | xl: '50px', 86 | }, 87 | fontSize: { 88 | base: '16px', 89 | xl: '20px', 90 | }, 91 | fontWeight: '600', 92 | lineHeight: '1.6', 93 | color: 'rgba(1, 3, 8, 0.8)', 94 | textAlign: 'center', 95 | }, 96 | }); 97 | 98 | const Highlight = styled('span', { 99 | base: { 100 | position: 'relative', 101 | display: 'inline-block', 102 | lineHeight: '1', 103 | color: 'black', 104 | }, 105 | }); 106 | 107 | export default IntroSection; 108 | -------------------------------------------------------------------------------- /src/app/components/IntroSection/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from './IntroSection'; 2 | -------------------------------------------------------------------------------- /src/app/components/OpenSourceSection/OpenSourceSection.tsx: -------------------------------------------------------------------------------- 1 | import { FC } from 'react'; 2 | import { styled } from '@styled-system/jsx'; 3 | import { Column, FadeIn, SectionTitle } from '~/shared/components'; 4 | 5 | import pretendardLogo from './assets/pretendard.png'; 6 | import zodLogo from './assets/zod.png'; 7 | import kyLogo from './assets/ky.png'; 8 | import queryLogo from './assets/query.png'; 9 | import { motion, Variants } from 'framer-motion'; 10 | import { useInView } from 'react-intersection-observer'; 11 | 12 | const container: Variants = { 13 | visible: { 14 | transition: { 15 | staggerChildren: 0.15, 16 | delayChildren: 0.15, 17 | }, 18 | }, 19 | }; 20 | 21 | const OpenSourceSection: FC = () => { 22 | const { ref, inView } = useInView({ 23 | triggerOnce: true, 24 | }); 25 | return ( 26 |
31 | 32 | 36 | 37 | 38 | 39 | 40 | Pretendard 45 | 46 | 47 | Zod 48 | 49 | 50 | KY 51 | 52 | 53 | React Query 54 | 55 | 56 | 57 | 58 | 59 | 프론트엔드 오픈소스 프로젝트에 대한 감사의 마음을 담아 FEConf에서 60 | 후원을 진행합니다.
61 | 행사 현장에서 오픈소스 이벤트에 참여하고, 후원할 프로젝트에 직접 62 | 투표하세요!
63 | 여러분의 투표 결과에 따라 오픈소스 프로젝트에 대한 후원 금액이 64 | 결정됩니다. 65 |
66 |
67 |
68 |
69 |
70 | ); 71 | }; 72 | 73 | const Section = styled(motion.section, { 74 | base: { 75 | position: 'relative', 76 | padding: { 77 | base: '60px 20px', 78 | xl: '150px 0', 79 | }, 80 | }, 81 | }); 82 | 83 | const Wrap = styled(FadeIn, { 84 | base: { 85 | display: 'flex', 86 | width: '100%', 87 | maxWidth: '920px', 88 | marginTop: { 89 | base: '50px', 90 | xl: '130px', 91 | }, 92 | padding: { 93 | base: '20px 16px 30px 16px', 94 | xl: '40px 25px 60px 25px', 95 | }, 96 | flexDirection: 'column', 97 | justifyContent: 'center', 98 | alignItems: 'center', 99 | gap: { 100 | base: '20px', 101 | xl: '40px', 102 | }, 103 | borderRadius: { 104 | base: '10px', 105 | xl: '20px', 106 | }, 107 | background: 'rgba(78, 77, 96, 0.2)', 108 | boxShadow: '20px 20px 200px 0px rgba(1, 3, 8, 0.07)', 109 | }, 110 | }); 111 | 112 | const List = styled('ul', { 113 | base: { 114 | margin: { 115 | base: '0 auto', 116 | }, 117 | display: 'flex', 118 | width: { 119 | base: '100%', 120 | }, 121 | gap: { 122 | base: '10px', 123 | xl: '20px', 124 | }, 125 | justifyContent: 'center', 126 | alignItems: 'center', 127 | flexWrap: 'wrap', 128 | }, 129 | }); 130 | 131 | const Item = styled('li', { 132 | base: { 133 | display: 'flex', 134 | alignItems: 'center', 135 | height: { 136 | base: '50px', 137 | xl: '70px', 138 | }, 139 | boxSizing: 'content-box', 140 | marginTop: '16px', 141 | }, 142 | }); 143 | 144 | const Image = styled('img', { 145 | base: { 146 | height: { 147 | base: '50px', 148 | xl: '70px', 149 | }, 150 | }, 151 | }); 152 | 153 | const Description = styled('p', { 154 | base: { 155 | color: 'rgba(255, 255, 255, 0.7)', 156 | textAlign: 'center', 157 | fontSize: { 158 | base: '14px', 159 | xl: '18px', 160 | }, 161 | fontStyle: 'normal', 162 | fontWeight: '500', 163 | lineHeight: '160%', 164 | }, 165 | }); 166 | 167 | const Br = styled('br', { 168 | base: { 169 | display: { 170 | base: 'none', 171 | xl: 'initial', 172 | }, 173 | }, 174 | }); 175 | 176 | export default OpenSourceSection; 177 | -------------------------------------------------------------------------------- /src/app/components/OpenSourceSection/assets/babel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fedgkr/feconf2024/e3d1fee15b66c8927cd4c61fdb747283e9cc0211/src/app/components/OpenSourceSection/assets/babel.png -------------------------------------------------------------------------------- /src/app/components/OpenSourceSection/assets/core-js.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fedgkr/feconf2024/e3d1fee15b66c8927cd4c61fdb747283e9cc0211/src/app/components/OpenSourceSection/assets/core-js.png -------------------------------------------------------------------------------- /src/app/components/OpenSourceSection/assets/ky.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fedgkr/feconf2024/e3d1fee15b66c8927cd4c61fdb747283e9cc0211/src/app/components/OpenSourceSection/assets/ky.png -------------------------------------------------------------------------------- /src/app/components/OpenSourceSection/assets/pretendard.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fedgkr/feconf2024/e3d1fee15b66c8927cd4c61fdb747283e9cc0211/src/app/components/OpenSourceSection/assets/pretendard.png -------------------------------------------------------------------------------- /src/app/components/OpenSourceSection/assets/query.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fedgkr/feconf2024/e3d1fee15b66c8927cd4c61fdb747283e9cc0211/src/app/components/OpenSourceSection/assets/query.png -------------------------------------------------------------------------------- /src/app/components/OpenSourceSection/assets/react-hook-form.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fedgkr/feconf2024/e3d1fee15b66c8927cd4c61fdb747283e9cc0211/src/app/components/OpenSourceSection/assets/react-hook-form.png -------------------------------------------------------------------------------- /src/app/components/OpenSourceSection/assets/ts-pattern.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fedgkr/feconf2024/e3d1fee15b66c8927cd4c61fdb747283e9cc0211/src/app/components/OpenSourceSection/assets/ts-pattern.png -------------------------------------------------------------------------------- /src/app/components/OpenSourceSection/assets/zod.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fedgkr/feconf2024/e3d1fee15b66c8927cd4c61fdb747283e9cc0211/src/app/components/OpenSourceSection/assets/zod.png -------------------------------------------------------------------------------- /src/app/components/OpenSourceSection/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from './OpenSourceSection'; 2 | -------------------------------------------------------------------------------- /src/app/components/ProgramSection/ProgramSection.tsx: -------------------------------------------------------------------------------- 1 | import { FC } from 'react'; 2 | import { Column, FadeIn, SectionTitle } from '~/shared/components'; 3 | import { styled } from '@styled-system/jsx'; 4 | import { ProgramTab, ProgramHeader, SessionList } from './components'; 5 | import { ProgramContextProvider } from '~/features/programs/contexts'; 6 | import { motion, Variants } from 'framer-motion'; 7 | 8 | import timetable from './assets/timetable.jpg'; 9 | import { useInView } from 'react-intersection-observer'; 10 | 11 | const container: Variants = { 12 | visible: { 13 | transition: { 14 | staggerChildren: 0.15, 15 | delayChildren: 0.15, 16 | }, 17 | }, 18 | }; 19 | 20 | const ProgramSection: FC = () => { 21 | const { ref, inView } = useInView({ 22 | triggerOnce: true, 23 | }); 24 | return ( 25 | 26 |
31 | 32 | 36 | 37 | 38 | 39 | 40 | 41 | 타임 테이블 이미지 다운로드 42 | 43 | 44 | 45 |
46 |
47 | ); 48 | }; 49 | 50 | const Section = styled(motion.section, { 51 | base: { 52 | position: 'relative', 53 | padding: { 54 | base: '60px 20px', 55 | xl: '150px 0', 56 | }, 57 | }, 58 | }); 59 | 60 | const DownloadTimetable = styled('a', { 61 | base: { 62 | marginTop: { 63 | base: '40px', 64 | xl: '150px', 65 | }, 66 | marginLeft: 'auto', 67 | marginRight: 'auto', 68 | display: 'flex', 69 | maxWidth: { 70 | base: 'initial', 71 | xl: '350px', 72 | }, 73 | width: '100%', 74 | height: { 75 | base: '54px', 76 | xl: '64px', 77 | }, 78 | padding: '20px 60px', 79 | justifyContent: 'center', 80 | alignItems: 'center', 81 | gap: '8px', 82 | borderRadius: '10px', 83 | color: '#010308', 84 | fontSize: { 85 | base: '16px', 86 | xl: '20px', 87 | }, 88 | fontStyle: 'normal', 89 | fontWeight: '700', 90 | lineHeight: '130%', 91 | cursor: 'pointer', 92 | background: '#fff', 93 | backdropFilter: 'blur(3.1121041774749756px)', 94 | }, 95 | }); 96 | 97 | const ButtonWrap = styled(FadeIn, { 98 | base: { 99 | width: '100%', 100 | }, 101 | }); 102 | 103 | export default ProgramSection; 104 | -------------------------------------------------------------------------------- /src/app/components/ProgramSection/assets/timetable.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fedgkr/feconf2024/e3d1fee15b66c8927cd4c61fdb747283e9cc0211/src/app/components/ProgramSection/assets/timetable.jpg -------------------------------------------------------------------------------- /src/app/components/ProgramSection/components/ProgramHeader.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import { FC } from 'react'; 4 | import { SessionType } from '~/features/programs/types'; 5 | import { styled } from '@styled-system/jsx'; 6 | import { useProgram } from '~/features/programs/contexts'; 7 | import { ArrowIcon } from './components'; 8 | import { get, indexOf, lt, size } from 'lodash-es'; 9 | import { FadeIn } from '~/shared/components'; 10 | 11 | type HeaderInfo = { 12 | title: string; 13 | description: string; 14 | icon?: string; 15 | }; 16 | 17 | const programList = [SessionType.A, SessionType.B, SessionType.Lightning]; 18 | 19 | const headerLookup: Record = { 20 | [SessionType.A]: { 21 | title: 'Speaker', 22 | description: 23 | '2024년, 국내 최정상 프론트 엔지니어들의 가장 뜨거웠던 현장 경험을 공유합니다', 24 | icon: 'A', 25 | }, 26 | [SessionType.B]: { 27 | title: 'Speaker', 28 | description: 29 | '2024년, 국내 최정상 프론트 엔지니어들의 가장 뜨거웠던 현장 경험을 공유합니다', 30 | icon: 'B', 31 | }, 32 | [SessionType.Lightning]: { 33 | title: 'Lightning Talk', 34 | description: 35 | '짧지만 강렬하게 프론트엔드 개발에 대한 진솔한 이야기를 나누며 함께 공감하고 토론하세요', 36 | }, 37 | }; 38 | 39 | const ProgramHeader: FC = () => { 40 | const { currentTab, onChangeTab } = useProgram(); 41 | const { title, description, icon } = headerLookup[currentTab]; 42 | const index = indexOf(programList, currentTab); 43 | const hasPrev = index !== 0; 44 | const hasNext = lt(index, size(programList) - 1); 45 | const handleClickPrev = () => { 46 | const target = get(programList, index - 1); 47 | onChangeTab(target); 48 | }; 49 | const handleClickNext = () => { 50 | const target = get(programList, index + 1); 51 | onChangeTab(target); 52 | }; 53 | return ( 54 | 55 | 56 | 57 | <span>{title}</span> 58 | {icon && <Icon>{icon}</Icon>} 59 | 60 | 61 | 62 | {description} 63 | 64 | {hasPrev && ( 65 | 69 | 70 | 71 | )} 72 | {hasNext && ( 73 | 74 | 75 | 76 | )} 77 | 78 | ); 79 | }; 80 | 81 | const Container = styled(FadeIn, { 82 | base: { 83 | position: 'relative', 84 | width: '100%', 85 | marginTop: { 86 | base: '20px', 87 | xl: '50px', 88 | }, 89 | padding: { 90 | base: '20px 22px', 91 | xl: '45px 0', 92 | }, 93 | borderRadius: { 94 | base: '10px', 95 | xl: '20px', 96 | }, 97 | border: '1px solid rgba(255, 255, 255, 0.1)', 98 | background: 'rgba(33, 35, 50, 0.2)', 99 | }, 100 | }); 101 | 102 | const Title = styled('h2', { 103 | base: { 104 | display: 'flex', 105 | justifyContent: 'center', 106 | alignItems: 'center', 107 | fontSize: { 108 | base: '20px', 109 | xl: '40px', 110 | }, 111 | fontWeight: '500', 112 | color: '#fff', 113 | lineHeight: '1.3', 114 | textAlign: 'center', 115 | }, 116 | }); 117 | 118 | const Icon = styled('div', { 119 | base: { 120 | width: { 121 | base: '24px', 122 | xl: '35px', 123 | }, 124 | height: { 125 | base: '24px', 126 | xl: '35px', 127 | }, 128 | marginLeft: { 129 | base: '6px', 130 | xl: '20px', 131 | }, 132 | borderRadius: { 133 | base: '5px', 134 | xl: '10px', 135 | }, 136 | display: 'inline-flex', 137 | alignItems: 'center', 138 | justifyContent: 'center', 139 | color: 'white', 140 | fontSize: { 141 | base: '12px', 142 | xl: '18px', 143 | }, 144 | fontWeight: '400', 145 | backgroundColor: 'rgba(78, 77, 96, 0.3)', 146 | }, 147 | }); 148 | 149 | const Description = styled('p', { 150 | base: { 151 | marginTop: { 152 | base: '20px', 153 | xl: '13px', 154 | }, 155 | color: 'rgba(255, 255, 255, 0.7)', 156 | textAlign: 'center', 157 | fontSize: { 158 | base: '14px', 159 | xl: '16px', 160 | }, 161 | fontStyle: 'normal', 162 | fontWeight: '500', 163 | lineHeight: '160%', 164 | }, 165 | }); 166 | 167 | const NavWrap = styled('button', { 168 | base: { 169 | position: 'absolute', 170 | top: '0', 171 | bottom: '0', 172 | width: '35px', 173 | height: '35px', 174 | display: { 175 | base: 'none', 176 | xl: 'flex', 177 | }, 178 | justifyContent: 'center', 179 | alignItems: 'center', 180 | margin: 'auto 0', 181 | border: '1px solid rgba(255, 255, 255, 0.1)', 182 | borderRadius: '10px', 183 | cursor: 'pointer', 184 | }, 185 | }); 186 | 187 | export default ProgramHeader; 188 | -------------------------------------------------------------------------------- /src/app/components/ProgramSection/components/ProgramTab.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import { FC } from 'react'; 4 | import { SessionType } from '~/features/programs/types'; 5 | import { styled } from '@styled-system/jsx'; 6 | import { eq, map } from 'lodash-es'; 7 | import { useProgram } from '~/features/programs/contexts'; 8 | import { FadeIn } from '~/shared/components'; 9 | 10 | interface Props {} 11 | 12 | const tabList = [SessionType.A, SessionType.B, SessionType.Lightning]; 13 | 14 | const labelLookup: Record = { 15 | [SessionType.A]: 'Speaker A', 16 | [SessionType.B]: 'Speaker B', 17 | [SessionType.Lightning]: 'Lightning Talk', 18 | }; 19 | 20 | const ProgramTab: FC = () => { 21 | const { currentTab, onChangeTab } = useProgram(); 22 | return ( 23 | 24 | 25 | {map(tabList, tab => ( 26 | onChangeTab(tab)} 30 | > 31 | 32 | 33 | ))} 34 | 35 | 36 | ); 37 | }; 38 | 39 | const List = styled('ul', { 40 | base: { 41 | display: 'flex', 42 | marginTop: { 43 | base: '50px', 44 | xl: '100px', 45 | }, 46 | padding: { 47 | base: '8px', 48 | xl: '10px', 49 | }, 50 | borderRadius: { 51 | base: '10px', 52 | xl: '12px', 53 | }, 54 | gap: { 55 | base: '10px', 56 | xl: '10px', 57 | }, 58 | backgroundColor: 'rgba(78, 77, 96, 0.2)', 59 | }, 60 | }); 61 | 62 | const Tab = styled('li', { 63 | base: { 64 | display: 'inline-block', 65 | padding: { 66 | base: '4px 8px', 67 | xl: '6px 14px', 68 | }, 69 | cursor: 'pointer', 70 | color: '#fff', 71 | fontSize: { 72 | base: '12px', 73 | xl: '18px', 74 | }, 75 | fontWeight: 500, 76 | lineHeight: '140%', 77 | borderRadius: { 78 | base: '5px', 79 | xl: '8px', 80 | }, 81 | textAlign: 'center', 82 | transition: 'background-color 200ms ease-out', 83 | }, 84 | variants: { 85 | active: { 86 | true: { 87 | backgroundColor: 'rgba(78, 77, 96, 0.50)', 88 | }, 89 | false: { 90 | backgroundColor: 'transparent', 91 | }, 92 | }, 93 | }, 94 | }); 95 | 96 | const Button = styled('button', { 97 | base: { 98 | cursor: 'pointer', 99 | }, 100 | }); 101 | 102 | export default ProgramTab; 103 | -------------------------------------------------------------------------------- /src/app/components/ProgramSection/components/SessionList.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import { FC, useState } from 'react'; 4 | import { styled } from '@styled-system/jsx'; 5 | import { map, size } from 'lodash-es'; 6 | import { FadeIn } from '~/shared/components'; 7 | import type { Session } from '~/features/programs/types'; 8 | import { SessionType } from '~/features/programs/types'; 9 | import { 10 | lightningSessionTimeLabelLookup, 11 | sessionTimeLabelLookup, 12 | } from '~/features/programs/constants'; 13 | 14 | import { ClockIcon, SessionModal } from './components'; 15 | import { useProgram } from '~/features/programs/contexts'; 16 | import { 17 | aSessionList, 18 | bSessionList, 19 | lightningSessionList, 20 | } from '~/features/programs/data/sessions'; 21 | import { match } from 'ts-pattern'; 22 | 23 | const sessionsLookup: Record = { 24 | [SessionType.A]: aSessionList, 25 | [SessionType.B]: bSessionList, 26 | [SessionType.Lightning]: lightningSessionList, 27 | }; 28 | 29 | const SessionList: FC = () => { 30 | const { currentTab } = useProgram(); 31 | const [currentSession, setCurrentSession] = useState(null); 32 | const [open, setOpen] = useState(false); 33 | const sessions = sessionsLookup[currentTab]; 34 | const handleClickSession = (session: Session) => { 35 | setCurrentSession(session); 36 | setOpen(true); 37 | }; 38 | const handleChangeOpen = (open: boolean) => { 39 | if (!open) setOpen(open); 40 | }; 41 | const timeLabelLookup = match(currentTab) 42 | .with(SessionType.Lightning, () => lightningSessionTimeLabelLookup) 43 | .otherwise(() => sessionTimeLabelLookup); 44 | const length = sessions.length; 45 | return ( 46 | 47 | 48 | 49 | {map(sessions, (session, index) => { 50 | const { title, speakers, order } = session; 51 | return ( 52 | handleClickSession(session)} 56 | > 57 | 66 | 72 | 73 | 74 | {title} 75 | 76 | {map(speakers, (speaker, index) => ( 77 | 78 | {speaker?.name} 79 | {index < size(speakers) - 1 ? ', ' : ''} 80 | 81 | ))} 82 | 83 | 84 | 85 | 86 | ); 87 | })} 88 | 93 | 94 | ); 95 | }; 96 | 97 | const Container = styled('div', { 98 | base: { 99 | position: 'relative', 100 | marginTop: { 101 | base: '40px', 102 | xl: '100px', 103 | }, 104 | paddingTop: { 105 | base: '70px', 106 | xl: '0px', 107 | }, 108 | width: '100%', 109 | }, 110 | }); 111 | 112 | const Session = styled(FadeIn, { 113 | base: { 114 | position: 'relative', 115 | width: '100%', 116 | display: 'flex', 117 | alignItems: 'flex-start', 118 | flexDirection: { 119 | base: 'column', 120 | xl: 'row', 121 | }, 122 | cursor: 'pointer', 123 | '&:not(:first-of-type)': { 124 | marginTop: { 125 | base: '30px', 126 | xl: '60px', 127 | }, 128 | }, 129 | }, 130 | }); 131 | 132 | const Line = styled(FadeIn, { 133 | base: { 134 | '--session-margin-top': { 135 | base: '30px', 136 | xl: '60px', 137 | }, 138 | '--session-time-top': { 139 | base: '0px', 140 | xl: '58px', 141 | }, 142 | '--session-time-height': '24px', 143 | '--session-time-center': 'calc(var(--session-time-height) / 2)', 144 | position: 'absolute', 145 | top: { 146 | base: 'calc(-1 * var(--session-margin-top))', 147 | xl: 'calc(var(--session-time-top) + var(--session-time-center))', 148 | }, 149 | height: { 150 | // 24px 원이 포함된 영역 크기 151 | // 14px 그 사이 여백 152 | base: 'calc(var(--session-margin-top) + var(--session-time-height) + 14px)', 153 | xl: 'calc(100% + var(--session-margin-top))', 154 | }, 155 | left: { 156 | base: '15px', 157 | xl: '135px', 158 | }, 159 | width: '1px', 160 | background: 'rgba(255, 255, 255, 10%)', 161 | '&.session-time-line': { 162 | top: '35px', 163 | height: 164 | 'calc(var(--session-margin-top) + var(--session-time-top) + var(--session-time-center) - 35px)', 165 | display: { 166 | base: 'none', 167 | xl: 'block', 168 | }, 169 | }, 170 | '&.session-first-line': { 171 | // padding-top 35px 172 | top: { 173 | base: 'calc(-1 * var(--session-margin-top) - 35px)', 174 | xl: 'calc(var(--session-time-top) + var(--session-time-center))', 175 | }, 176 | height: { 177 | // 24px 원이 포함된 영역 크기 178 | // 14px 그 사이 여백 179 | base: 'calc(var(--session-margin-top) + var(--session-time-height) + 14px + 35px)', 180 | xl: 'calc(100% + var(--session-margin-top))', 181 | }, 182 | }, 183 | '&.session-last-line': { 184 | display: { 185 | base: 'block', 186 | xl: 'none', 187 | }, 188 | }, 189 | }, 190 | }); 191 | const TimeIcon = styled(ClockIcon, { 192 | base: { 193 | position: 'absolute', 194 | top: '0px', 195 | left: { 196 | base: '-2px', 197 | xl: '118px', 198 | }, 199 | opacity: '0', 200 | transform: 'translateY(30px)', 201 | padding: '9px 8px 8px 9px', 202 | boxSizing: 'content-box !important', 203 | borderRadius: '10px', 204 | backgroundColor: 'rgba(78, 77, 96, 0.2)', 205 | }, 206 | }); 207 | 208 | const Time = styled('div', { 209 | base: { 210 | position: 'relative', 211 | display: 'flex', 212 | flexGrow: 0, 213 | flexDirection: { 214 | base: 'row-reverse', 215 | xl: 'row', 216 | }, 217 | color: 'rgba(255, 255, 255, 0.8)', 218 | textAlign: 'right', 219 | fontSize: '16px', 220 | fontStyle: 'normal', 221 | fontWeight: '500', 222 | marginTop: { 223 | base: 'initial', 224 | xl: '58px', 225 | }, 226 | // 100px (시간) + 86px (충분한 오른쪽 여백) 227 | width: { 228 | base: 'initial', 229 | xl: '186px', 230 | }, 231 | // marginRight: { 232 | // base: 'initial', 233 | // xl: '84px', 234 | // }, 235 | paddingLeft: { 236 | base: '31px', 237 | xl: 'initial', 238 | }, 239 | }, 240 | }); 241 | 242 | const LiveIcon = styled('div', { 243 | base: { 244 | position: 'absolute', 245 | display: 'flex', 246 | alignItems: 'center', 247 | justifyContent: 'center', 248 | top: '50%', 249 | right: { 250 | base: 'initial', 251 | // 여백을 줬기 때문에 자체적인 right 값 부여 252 | xl: '58px', 253 | }, 254 | left: { 255 | base: '8px', 256 | xl: 'initial', 257 | }, 258 | width: '15px', 259 | height: '15px', 260 | fontSize: 0, 261 | borderRadius: '50%', 262 | background: 'rgba(255, 255, 255, 0.35)', 263 | transform: { 264 | base: 'translate(0, -50%)', 265 | xl: 'translate(100%, -50%)', 266 | }, 267 | }, 268 | }); 269 | 270 | const IconChild = styled('div', { 271 | base: { 272 | width: '7px', 273 | height: '7px', 274 | borderRadius: '50%', 275 | background: 'rgba(255, 255, 255, 0.8)', 276 | }, 277 | }); 278 | 279 | const SessionInfo = styled('div', { 280 | base: { 281 | width: '100%', 282 | flex: 1, 283 | borderRadius: { 284 | base: '10px', 285 | xl: '20px', 286 | }, 287 | marginTop: { 288 | base: '14px', 289 | xl: 'initial', 290 | }, 291 | background: 'rgba(78, 77, 96, 0.2)', 292 | }, 293 | }); 294 | 295 | const Info = styled('div', { 296 | base: { 297 | padding: { 298 | base: '16px', 299 | xl: '30px 180px 45px 40px', 300 | }, 301 | }, 302 | }); 303 | 304 | const Title = styled('div', { 305 | base: { 306 | color: '#fff', 307 | fontSize: { 308 | base: '16px', 309 | xl: '28px', 310 | }, 311 | fontStyle: 'normal', 312 | fontWeight: '600', 313 | lineHeight: '140%', 314 | }, 315 | }); 316 | 317 | const SpeakerInfo = styled('div', { 318 | base: { 319 | marginTop: { 320 | base: '10px', 321 | xl: '24px', 322 | }, 323 | color: 'rgba(255, 255, 255, 0.5)', 324 | fontSize: { 325 | base: '12px', 326 | xl: '18px', 327 | }, 328 | fontStyle: 'normal', 329 | fontWeight: '500', 330 | lineHeight: '100%', 331 | }, 332 | }); 333 | 334 | export default SessionList; 335 | -------------------------------------------------------------------------------- /src/app/components/ProgramSection/components/components/ArrowIcon.tsx: -------------------------------------------------------------------------------- 1 | import { FC, SVGProps } from 'react'; 2 | 3 | const ArrowIcon: FC> = props => ( 4 | 12 | 19 | 20 | ); 21 | 22 | export default ArrowIcon; 23 | -------------------------------------------------------------------------------- /src/app/components/ProgramSection/components/components/ClockIcon.tsx: -------------------------------------------------------------------------------- 1 | import { FC, SVGProps } from 'react'; 2 | import { motion } from 'framer-motion'; 3 | 4 | interface Props extends SVGProps {} 5 | 6 | const variants = { 7 | visible: { 8 | opacity: 1, 9 | y: 0, 10 | transition: { duration: 0.4 }, 11 | }, 12 | hidden: { 13 | opacity: 0, 14 | y: `30px`, 15 | transition: { duration: 0.25 }, 16 | }, 17 | }; 18 | 19 | const ClockIcon: FC = props => { 20 | return ( 21 | 30 | 37 | 38 | ); 39 | }; 40 | 41 | export default ClockIcon; 42 | -------------------------------------------------------------------------------- /src/app/components/ProgramSection/components/components/CloseIcon.tsx: -------------------------------------------------------------------------------- 1 | import { FC, SVGProps } from 'react'; 2 | 3 | interface Props extends SVGProps {} 4 | 5 | const CloseIcon: FC = props => { 6 | return ( 7 | 15 | 22 | 23 | ); 24 | }; 25 | 26 | export default CloseIcon; 27 | -------------------------------------------------------------------------------- /src/app/components/ProgramSection/components/components/SessionModal.tsx: -------------------------------------------------------------------------------- 1 | import { FC, Fragment } from 'react'; 2 | 3 | import * as Dialog from '@radix-ui/react-dialog'; 4 | import { styled } from '@styled-system/jsx'; 5 | import { Session, SessionType } from '~/features/programs/types'; 6 | import { 7 | lightningSessionTimeLabelLookup, 8 | sessionTimeLabelLookup, 9 | } from '~/features/programs/constants'; 10 | 11 | import CloseIcon from './CloseIcon'; 12 | import { map, size } from 'lodash-es'; 13 | 14 | interface Props { 15 | session: Session | null; 16 | open: boolean; 17 | onChangeOpen: (open: boolean) => void; 18 | } 19 | 20 | const labelLookup: Record = { 21 | [SessionType.A]: 'Speaker A', 22 | [SessionType.B]: 'Speaker B', 23 | [SessionType.Lightning]: 'Lightning Talk', 24 | }; 25 | 26 | const SessionModal: FC = ({ session, open, onChangeOpen }) => { 27 | const timeLabelLookup = 28 | session?.type === SessionType.Lightning 29 | ? lightningSessionTimeLabelLookup 30 | : sessionTimeLabelLookup; 31 | return ( 32 | 33 | 34 | 35 | 36 |
37 | {session && ( 38 | 42 | )} 43 | 44 | 45 | 46 |
47 | {session && ( 48 | <> 49 | {session.title} 50 | 53 | 54 | {map(session?.speakers, (speaker, index) => ( 55 | 56 | 57 | {speaker?.name} 58 | {index < size(session?.speakers) - 1 ? ', ' : ''} 59 | 60 | {speaker?.company && | {speaker?.company}} 61 | 62 | ))} 63 | 64 | 65 | )} 66 |
67 |
68 |
69 | ); 70 | }; 71 | 72 | const Overlay = styled(Dialog.Overlay, { 73 | base: { 74 | position: 'fixed', 75 | inset: 0, 76 | zIndex: 1, 77 | backgroundColor: 'rgba(0, 0, 0, 0.5)', 78 | }, 79 | }); 80 | 81 | const Content = styled(Dialog.Content, { 82 | base: { 83 | position: 'fixed', 84 | top: '50%', 85 | left: '50%', 86 | transform: 'translate(-50%, -50%)', 87 | width: 'calc(100% - 40px)', 88 | maxWidth: 595, 89 | borderRadius: { 90 | base: '10px', 91 | xl: '20px', 92 | }, 93 | padding: { 94 | base: '20px', 95 | xl: '30px 40px 40px 40px', 96 | }, 97 | backgroundColor: '#1A1B23', 98 | zIndex: 2, 99 | }, 100 | }); 101 | 102 | const Header = styled('header', { 103 | base: { 104 | display: 'flex', 105 | alignItems: 'center', 106 | justifyContent: 'space-between', 107 | }, 108 | }); 109 | 110 | const Time = styled('div', { 111 | base: { 112 | padding: '10px 12px', 113 | display: 'inline-flex', 114 | alignItems: 'center', 115 | gap: '10px', 116 | border: '1px solid rgba(168, 172, 192, 0.10)', 117 | borderRadius: 8, 118 | color: '#A8ACC0', 119 | fontSize: 12, 120 | fontStyle: 'normal', 121 | fontWeight: 500, 122 | }, 123 | }); 124 | 125 | const Title = styled(Dialog.Title, { 126 | base: { 127 | color: '#FFF', 128 | fontSize: { 129 | base: '18px', 130 | xl: '32px', 131 | }, 132 | fontStyle: 'normal', 133 | fontWeight: 600, 134 | lineHeight: '140%', 135 | marginTop: { 136 | base: '20px', 137 | xl: '24px', 138 | }, 139 | }, 140 | }); 141 | 142 | const Description = styled('p', { 143 | base: { 144 | color: '#FFF', 145 | fontSize: '14px', 146 | fontStyle: 'normal', 147 | fontWeight: 400, 148 | lineHeight: '160%', 149 | marginTop: { 150 | base: '12px', 151 | xl: '14px', 152 | }, 153 | }, 154 | }); 155 | 156 | const Speaker = styled('div', { 157 | base: { 158 | color: '#A8ACC0', 159 | fontSize: '14px', 160 | fontStyle: 'normal', 161 | fontWeight: 500, 162 | marginTop: { 163 | base: '20px', 164 | xl: '30px', 165 | }, 166 | }, 167 | }); 168 | 169 | const CloseButton = styled(CloseIcon, { 170 | base: { 171 | cursor: 'pointer', 172 | }, 173 | }); 174 | 175 | export default SessionModal; 176 | -------------------------------------------------------------------------------- /src/app/components/ProgramSection/components/components/index.ts: -------------------------------------------------------------------------------- 1 | export { default as ArrowIcon } from './ArrowIcon'; 2 | export { default as ClockIcon } from './ClockIcon'; 3 | export { default as SessionModal } from './SessionModal'; 4 | -------------------------------------------------------------------------------- /src/app/components/ProgramSection/components/index.ts: -------------------------------------------------------------------------------- 1 | export { default as ProgramHeader } from './ProgramHeader'; 2 | export { default as ProgramTab } from './ProgramTab'; 3 | export { default as SessionList } from './SessionList'; 4 | -------------------------------------------------------------------------------- /src/app/components/ProgramSection/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from './ProgramSection'; 2 | -------------------------------------------------------------------------------- /src/app/components/SponsorSection/SponsorSection.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import { FC } from 'react'; 4 | import { styled } from '@styled-system/jsx'; 5 | import { Column, FadeIn, SectionTitle } from '~/shared/components'; 6 | 7 | import { SponsorInfo } from './comopnents'; 8 | import { motion, Variants } from 'framer-motion'; 9 | import { useInView } from 'react-intersection-observer'; 10 | 11 | import mediaSponsorLogo from './assets/it.png'; 12 | 13 | const container: Variants = { 14 | visible: { 15 | transition: { 16 | staggerChildren: 0.15, 17 | delayChildren: 0.3, 18 | }, 19 | }, 20 | }; 21 | 22 | const SponsorSection: FC = () => { 23 | const { ref, inView } = useInView({ 24 | triggerOnce: true, 25 | }); 26 | return ( 27 |
32 | 33 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | Media Partner <MediaLogo src={mediaSponsorLogo.src} /> 46 | 47 | 48 | 49 | 50 | 51 |
52 | ); 53 | }; 54 | 55 | const Section = styled(motion.section, { 56 | base: { 57 | position: 'relative', 58 | padding: { 59 | base: '120px 0 60px 0', 60 | xl: '150px 0', 61 | }, 62 | }, 63 | }); 64 | 65 | const SponsorList = styled('div', { 66 | base: { 67 | display: 'flex', 68 | flexDirection: 'column', 69 | width: { 70 | base: '100%', 71 | xl: '920px', 72 | }, 73 | padding: { 74 | base: '20px', 75 | }, 76 | gap: { 77 | base: '20px', 78 | xl: '50px', 79 | }, 80 | marginTop: { 81 | base: '50px', 82 | xl: '100px', 83 | }, 84 | }, 85 | }); 86 | 87 | const MediaSponsor = styled(FadeIn, { 88 | base: { 89 | display: 'flex', 90 | alignItems: 'center', 91 | gap: { 92 | base: '12px', 93 | xl: '24px', 94 | }, 95 | marginTop: { 96 | base: '20px', 97 | xl: '50px', 98 | }, 99 | height: { 100 | base: '25px', 101 | xl: '25px', 102 | }, 103 | }, 104 | }); 105 | 106 | const Line = styled('div', { 107 | base: { 108 | flex: '1', 109 | height: '1px', 110 | backgroundColor: 'rgba(255, 255, 255, 0.2)', 111 | }, 112 | }); 113 | 114 | const Title = styled('a', { 115 | base: { 116 | display: 'flex', 117 | alignItems: 'center', 118 | fontSize: { 119 | base: '14px', 120 | xl: '18px', 121 | }, 122 | gap: { 123 | base: '14px', 124 | xl: '24px', 125 | }, 126 | cursor: 'pointer', 127 | color: 'rgba(255, 255, 255, 0.3)', 128 | }, 129 | }); 130 | 131 | const MediaLogo = styled('img', { 132 | base: { 133 | width: { 134 | base: '68px', 135 | xl: '102px', 136 | }, 137 | height: { 138 | base: '17px', 139 | xl: '25px', 140 | }, 141 | }, 142 | }); 143 | 144 | export default SponsorSection; 145 | -------------------------------------------------------------------------------- /src/app/components/SponsorSection/assets/it.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fedgkr/feconf2024/e3d1fee15b66c8927cd4c61fdb747283e9cc0211/src/app/components/SponsorSection/assets/it.png -------------------------------------------------------------------------------- /src/app/components/SponsorSection/comopnents/SponsorInfo.tsx: -------------------------------------------------------------------------------- 1 | import { map } from 'lodash-es'; 2 | import { FC } from 'react'; 3 | import { styled } from '@styled-system/jsx'; 4 | import { FadeIn } from '~/shared/components'; 5 | 6 | import googlecloudImage from './assets/googlecloud.png'; 7 | import soomgoImage from './assets/soomgo.png'; 8 | import wooritechImage from './assets/wooritech.png'; 9 | import wyyyesImage from './assets/wyyyes.png'; 10 | import imwebImage from './assets/imweb.png'; 11 | import carrotImage from './assets/carrot.png'; 12 | import oliveyoungImage from './assets/oliveyoung.png'; 13 | import tossImage from './assets/toss.png'; 14 | import stibeeImage from './assets/stibee.png'; 15 | import fLabImage from './assets/F-Lab.png'; 16 | 17 | type Grade = 'Master' | 'Diamond' | 'Platiunum' | 'Rookie'; 18 | type SponsorData = { 19 | imageSrc: string; 20 | href: string; 21 | }; 22 | 23 | const googlecloud: SponsorData = { 24 | imageSrc: googlecloudImage.src, 25 | href: 'https://cloud.google.com/', 26 | }; 27 | const soomgo: SponsorData = { 28 | imageSrc: soomgoImage.src, 29 | href: 'https://www.soomgo.team/', 30 | }; 31 | const wooritech: SponsorData = { 32 | imageSrc: wooritechImage.src, 33 | href: 'https://realgrid.com/', 34 | }; 35 | const wyyyes: SponsorData = { 36 | imageSrc: wyyyesImage.src, 37 | href: 'https://wyyyes.career.greetinghr.com/o/111768', 38 | }; 39 | const imweb: SponsorData = { 40 | imageSrc: imwebImage.src, 41 | href: 'https://team.imweb.me/', 42 | }; 43 | const carrot: SponsorData = { 44 | imageSrc: carrotImage.src, 45 | href: 'https://about.daangn.com/', 46 | }; 47 | const oliveyoung: SponsorData = { 48 | imageSrc: oliveyoungImage.src, 49 | href: 'https://career.oliveyoung.com/', 50 | }; 51 | const toss: SponsorData = { 52 | imageSrc: tossImage.src, 53 | href: 'https://toss.im/career', 54 | }; 55 | const stibee: SponsorData = { 56 | imageSrc: stibeeImage.src, 57 | href: 'https://stibee.com/', 58 | }; 59 | const fLab: SponsorData = { 60 | imageSrc: fLabImage.src, 61 | href: 'https://f-lab.kr/', 62 | }; 63 | 64 | const masterList = [googlecloud]; 65 | const diamondList = [soomgo, wooritech]; 66 | const platinumList = [wyyyes, imweb, oliveyoung, carrot, toss]; 67 | const rookieList = [stibee, fLab]; 68 | const sponsorListLookup: Record = { 69 | Master: masterList, 70 | Diamond: diamondList, 71 | Platiunum: platinumList, 72 | Rookie: rookieList, 73 | }; 74 | 75 | interface Props { 76 | grade: Grade; 77 | } 78 | 79 | const SponsorInfo: FC = ({ grade }) => { 80 | const list = sponsorListLookup[grade]; 81 | return ( 82 | 83 | {grade} 84 | 85 | {map(list, ({ imageSrc, href }) => ( 86 |
  • 87 | 88 | 89 | 90 |
  • 91 | ))} 92 |
    93 |
    94 | ); 95 | }; 96 | 97 | const Container = styled(FadeIn, { 98 | base: { 99 | display: 'flex', 100 | flexDirection: 'column', 101 | alignItems: 'center', 102 | width: '100%', 103 | height: { 104 | base: 'initial', 105 | }, 106 | minHeight: { 107 | base: '144px', 108 | xl: '230px', 109 | }, 110 | padding: { 111 | base: '20px 25px 30px 25px', 112 | xl: '30px 25px 28px 25px', 113 | }, 114 | borderRadius: { 115 | base: '10px', 116 | xl: '20px', 117 | }, 118 | background: 'rgba(78, 77, 96, 0.2)', 119 | }, 120 | }); 121 | 122 | const Grade = styled('h2', { 123 | base: { 124 | fontSize: { 125 | base: '16px', 126 | xl: '18px', 127 | }, 128 | fontWeight: '500', 129 | color: 'rgba(255, 255, 255, 0.3)', 130 | lineHeight: '1.4', 131 | }, 132 | }); 133 | 134 | const List = styled('ul', { 135 | base: { 136 | display: 'flex', 137 | flexWrap: 'wrap', 138 | justifyContent: 'center', 139 | gap: { 140 | base: '10px', 141 | xl: '20px', 142 | }, 143 | marginTop: { 144 | base: '20px', 145 | xl: '33px', 146 | }, 147 | }, 148 | }); 149 | 150 | const Link = styled('a', { 151 | base: { 152 | display: 'block', 153 | cursor: 'pointer', 154 | borderRadius: { 155 | base: '10px', 156 | xl: '15px', 157 | }, 158 | _hover: { 159 | backgroundColor: 'rgba(59, 59, 79, 0.3)', 160 | }, 161 | }, 162 | }); 163 | 164 | const SponsorImage = styled('img', { 165 | variants: { 166 | grade: { 167 | Master: { 168 | height: { 169 | base: '52px', 170 | xl: '114px', 171 | }, 172 | }, 173 | Diamond: { 174 | height: { 175 | base: '64px', 176 | xl: '100px', 177 | }, 178 | }, 179 | Platiunum: { 180 | height: { 181 | base: '52px', 182 | xl: '70px', 183 | }, 184 | }, 185 | Rookie: { 186 | height: { 187 | base: '52px', 188 | xl: '70px', 189 | }, 190 | }, 191 | }, 192 | }, 193 | }); 194 | 195 | export default SponsorInfo; 196 | -------------------------------------------------------------------------------- /src/app/components/SponsorSection/comopnents/assets/F-Lab.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fedgkr/feconf2024/e3d1fee15b66c8927cd4c61fdb747283e9cc0211/src/app/components/SponsorSection/comopnents/assets/F-Lab.png -------------------------------------------------------------------------------- /src/app/components/SponsorSection/comopnents/assets/carrot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fedgkr/feconf2024/e3d1fee15b66c8927cd4c61fdb747283e9cc0211/src/app/components/SponsorSection/comopnents/assets/carrot.png -------------------------------------------------------------------------------- /src/app/components/SponsorSection/comopnents/assets/googlecloud.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fedgkr/feconf2024/e3d1fee15b66c8927cd4c61fdb747283e9cc0211/src/app/components/SponsorSection/comopnents/assets/googlecloud.png -------------------------------------------------------------------------------- /src/app/components/SponsorSection/comopnents/assets/imweb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fedgkr/feconf2024/e3d1fee15b66c8927cd4c61fdb747283e9cc0211/src/app/components/SponsorSection/comopnents/assets/imweb.png -------------------------------------------------------------------------------- /src/app/components/SponsorSection/comopnents/assets/oliveyoung.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fedgkr/feconf2024/e3d1fee15b66c8927cd4c61fdb747283e9cc0211/src/app/components/SponsorSection/comopnents/assets/oliveyoung.png -------------------------------------------------------------------------------- /src/app/components/SponsorSection/comopnents/assets/soomgo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fedgkr/feconf2024/e3d1fee15b66c8927cd4c61fdb747283e9cc0211/src/app/components/SponsorSection/comopnents/assets/soomgo.png -------------------------------------------------------------------------------- /src/app/components/SponsorSection/comopnents/assets/stibee.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fedgkr/feconf2024/e3d1fee15b66c8927cd4c61fdb747283e9cc0211/src/app/components/SponsorSection/comopnents/assets/stibee.png -------------------------------------------------------------------------------- /src/app/components/SponsorSection/comopnents/assets/toss.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fedgkr/feconf2024/e3d1fee15b66c8927cd4c61fdb747283e9cc0211/src/app/components/SponsorSection/comopnents/assets/toss.png -------------------------------------------------------------------------------- /src/app/components/SponsorSection/comopnents/assets/wooritech.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fedgkr/feconf2024/e3d1fee15b66c8927cd4c61fdb747283e9cc0211/src/app/components/SponsorSection/comopnents/assets/wooritech.png -------------------------------------------------------------------------------- /src/app/components/SponsorSection/comopnents/assets/wyyyes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fedgkr/feconf2024/e3d1fee15b66c8927cd4c61fdb747283e9cc0211/src/app/components/SponsorSection/comopnents/assets/wyyyes.png -------------------------------------------------------------------------------- /src/app/components/SponsorSection/comopnents/index.ts: -------------------------------------------------------------------------------- 1 | export { default as SponsorInfo } from './SponsorInfo'; 2 | -------------------------------------------------------------------------------- /src/app/components/SponsorSection/index.ts: -------------------------------------------------------------------------------- 1 | export { default } from './SponsorSection'; 2 | -------------------------------------------------------------------------------- /src/app/components/index.ts: -------------------------------------------------------------------------------- 1 | export { default as HeroIntroWrap } from './HeroIntroWrap'; 2 | export { default as SponsorSection } from './SponsorSection'; 3 | export { default as ProgramSection } from './ProgramSection'; 4 | export { default as ChildcareSection } from './ChildcareSection'; 5 | export { default as OpenSourceSection } from './OpenSourceSection'; 6 | export { default as CoCSection } from './CoCSection'; 7 | export { default as ContactSection } from './ContactSection'; 8 | export { default as FooterSection } from './FooterSection'; 9 | -------------------------------------------------------------------------------- /src/app/globals.css: -------------------------------------------------------------------------------- 1 | * { 2 | box-sizing: border-box; 3 | } 4 | 5 | html, 6 | body { 7 | max-width: 100vw; 8 | overflow-x: hidden; 9 | overflow-wrap: break-word; 10 | word-break: keep-all; 11 | } 12 | 13 | body { 14 | background-color: #010308; 15 | } 16 | 17 | a { 18 | text-decoration: none; 19 | } 20 | 21 | @media (prefers-color-scheme: dark) { 22 | html { 23 | color-scheme: dark; 24 | } 25 | } 26 | 27 | @layer reset, base, tokens, recipes, utilities; 28 | -------------------------------------------------------------------------------- /src/app/layout.tsx: -------------------------------------------------------------------------------- 1 | import type { Metadata } from 'next'; 2 | import { Inter } from 'next/font/google'; 3 | 4 | import './reset.css'; 5 | import './globals.css'; 6 | import { PropsWithChildren } from 'react'; 7 | 8 | const inter = Inter({ subsets: ['latin'] }); 9 | 10 | export const metadata: Metadata = { 11 | title: 'FEConf 2024', 12 | description: 13 | '국내 최대 프론트엔드 개발 컨퍼런스, FECONF 2024가 8월 24일 오프라인으로 찾아옵니다.', 14 | openGraph: { 15 | type: 'website', 16 | url: 'https://2024.feconf.kr', 17 | images: [ 18 | { 19 | url: 'https://2024.feconf.kr/fe2024_og.png', 20 | width: 1200, 21 | height: 630, 22 | alt: 'FEConf 2024', 23 | }, 24 | ], 25 | }, 26 | twitter: { 27 | title: 'FEConf 2024', 28 | description: 29 | '국내 최대 프론트엔드 개발 컨퍼런스, FECONF 2024가 8월 24일 오프라인으로 찾아옵니다.', 30 | card: 'summary_large_image', 31 | creator: '@FeConf', 32 | images: [{ url: 'https://2024.feconf.kr/fe2024_og.png' }], 33 | }, 34 | }; 35 | 36 | function RootLayout({ children }: Readonly) { 37 | return ( 38 | 39 | 40 | 45 |