class SectionIntro extends Ophose.Component {constructor(props) {super(props);this.currentText = new Live("");this.iText = 0;this.iWord = 0;this.leftTime = 0;let timeSpan = 10;this.w = setInterval(() => {let texts = Language.translate('home.descriptions', undefined, true);if(!texts) return;let words = texts[this.iText].split(" ");if (this.iWord < words.length) {this.currentText.set(this.currentText.get() + words[this.iWord] + " ");this.iWord++;} else {if(this.leftTime < 20000) {this.leftTime += timeSpan;return;} this.iText = (this.iText + 1) % texts.length;this.iWord = 0;this.leftTime = 0;this.currentText.set("");} } , timeSpan);} onRemove() {clearInterval(this.w);} style() {return ` %self { } ` } render() {return {_: 'div', style: 'background-image: url("/assets/img/background.jpg"); background-size: cover; background-position: center;', className: 'h-4/5 lg:h-screen flex justify-center items-center w-full relative select-none overflow-hidden', children: [_('div', {className: 'w-full h-full'} ,_('div', {className: 'container mx-auto justify-between items-center h-full'} ,_('div', {className: 'flex flex-col gap-2 h-full justify-center items-center lg:items-start'} ,_('p', {className: 'text-2xl font-bold opacity-50'} , _lang('home.hello')),_('h1', {className: 'text-9xl text-black font-bold'} , "AH4", _('span', {className: ' text-white font-semibold'} , ".")),_('nav', {className: 'flex gap-4 my-2'} , [_('a', {className: 'bi bi-github text-lg text-black', href: 'https://github.com/ah-4/'}),_('a', {className: 'bi bi-linkedin text-lg text-black', href: 'https://www.linkedin.com/in/ah4ite/'}),_('a', {className: 'bi bi-instagram text-lg text-black', href: 'https://instagram.com/o4vwa/'}),_('a', {className: 'bi bi-envelope text-lg text-black', href: 'mailto:contact@ah4.fr'}),]),_('p', {className: 'text-xs text-center lg:text-left lg:text-lg opacity-50 hidden lg:block lg:w-1/2 font-semibold'} , this.currentText),) ),_('img', {className: 'hidden lg:block absolute bottom-0 right-0 w-1/2 h-full opacity-5 object-cover', draggable: false, src: '/assets/img/grid.png'}),_('a', {href: '/#projects', className: 'absolute bottom-0 left-0 w-full text-center opacity-25 font-extrabold py-4 hover:opacity-75 cursor-pointer transition-all duration-300 ease-in-out bg-transparent hover:bg-gradient-to-b from-transparent to-white/30'} , _lang('home.seeMyWork'), _('i', {className: 'bi bi-chevron-down'})),),]} } } class SectionWhoAmI extends Ophose.Component {constructor(props) {super(props);} style() {return ` %self { } ` } render() {return {_: 'div', className: 'bg-white py-16', children: [_('div', {className: 'container mx-auto flex flex-col gap-4'} , [_('h2', {className: 'text-sm font-extrabold text-neutral-400'} , _lang('about.who')),_('h1', {className: 'text-3xl lg:text-5xl font-bold w-4/5'} , _lang('about.title')),_('div', {className: 'grid grid-cols-1 lg:grid-cols-2 gap-4 my-4'} ,_('p', {className: 'text-lg text-neutral-500'} , _lang('about.p1')),_('p', {className: 'text-lg text-neutral-500'} , _lang('about.p2'))),_('hr', {className: 'border-neutral-200 w-1/3 lg:w-1/2 mx-auto'}),_('div', {className: 'flex gap-4 my-4'} ,_('div', {className: 'flex gap-8'} ,_('img', {src: '/assets/img/ah4.png', className: 'w-24 h-24 p-4'}),_('i', {className: 'text-neutral-500'} , _lang('about.pAboutMe'))),) ])]} } } class Blocker extends Ophose.Component {constructor(props) {super(props);} style() {return ` %self { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background-color: rgba(0, 0, 0, 0.8); z-index: 100; } %self .blocker { position: fixed; top: 0; left: 0; width: 100%; height: 100%; z-index: 100; inset: 0; } %self .child { position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); z-index: 110; } %self .blocker_close_button { position: absolute; top: 0; right: 0; padding: 1rem; cursor: pointer; color: white; font-size: 1rem; } ` } render() {let processClose = () => {this.props.onClose && this.props.onClose();this.remove();} return {_: 'div', children: [_('div', {className: 'blocker', onclick: () => {processClose()} }),_('div', {className: 'child'} ,_('div', {className: 'blocker_close_button', onclick: () => {processClose()} } , '✖'),this.props.children )]} } } const pfOphose = {title: 'Ophose',images: ['/assets/img/projects/ophose/1.jpg','/assets/img/projects/ophose/2.jpg','/assets/img/projects/ophose/3.jpg' ],tags: ['JavaScript', 'PHP', 'Web Framework', 'Open Source'],year: '2023 / 2024',thumbnail: '/assets/img/projects/ophose.jpg',link: 'https://ophose.ah4.fr/',translation: 'projects.ophose',} const pfYzuvAI = {title: 'YzuvAI',images: ['/assets/img/projects/yzuvai/1.jpg','/assets/img/projects/yzuvai/2.jpg','/assets/img/projects/yzuvai/3.jpg','/assets/img/projects/yzuvai/4.jpg',],tags: ['Ophose', 'PHP', 'AI', 'Python', 'Music theory'],year: '2024',thumbnail: '/assets/img/projects/yzuvai.jpg',link: 'https://yzuvai.ah4.fr/',translation: 'projects.yzuvai',} const pfUnvapor = {title: 'Unvapor',images: ['/assets/img/projects/unvapor/1.jpg','/assets/img/projects/unvapor/2.jpg','/assets/img/projects/unvapor/3.jpg' ],tags: ['Music', 'Marketing', 'Community', 'Art'],year: '2021/2022',thumbnail: '/assets/img/projects/unvapor.jpg',link: 'https://soundcloud.com/unvapor',translation: 'projects.unvapor' } const pfYzuva = {title: 'Yzuva',images: ['/assets/img/projects/yzuva/1.jpg','/assets/img/projects/yzuva/2.jpg','/assets/img/projects/yzuva/3.jpg' ],tags: ['Music', 'Music theory', 'Community', 'Art'],year: '2017/now',thumbnail: '/assets/img/projects/yzuva.jpg',link: 'https://soundcloud.com/yzuva',translation: 'projects.yzuva' } const pfAnimotion = {title: 'Animotion',images: ['/assets/img/projects/animotion/1.jpg','/assets/img/projects/animotion/2.jpg','/assets/img/projects/animotion/3.jpg' ],tags: ['Java', 'API', 'Minecraft', 'Game development'],year: '2022',thumbnail: '/assets/img/projects/animotion.jpg',link: 'https://yzuvai.ah4.fr/',shortDescription: 'Easily create animations in Minecraft with this plugin.',type: 'Minecraft Plugin',translation: 'projects.animotion' } oimpc('@/AH4/Starter/Blocker');oimpc('portfolio/list/PortfolioOphose');oimpc('portfolio/list/PortfolioYzuvAI');oimpc('portfolio/list/PortfolioUnvapor');oimpc('portfolio/list/PortfolioYzuva');oimpc('portfolio/list/PortfolioAnimotion');class PortfolioSlider extends Ophose.Component {constructor(props) {super(props);this.current = new Live(0);} render() {return {_: 'div', children: [new PlacedLive(this.current, (current) => {return _('img', {src: this.props.images[current], className: 'w-full aspect-video object-contain rounded-2xl h-96'})}),new PlacedLive(this.current, (current) => {return _('div', {className: 'grid grid-cols-4 gap-4 my-4'} ,this.props.images.map((image, index) => {let selected = index === current;let selectedClass = selected ? 'border-black' : '';return _('img', {src: image, className: 'w-full aspect-video object-cover rounded-2xl cursor-pointer border-2 ' + selectedClass, onclick: () => this.current.set(index)})})) })]} } } class Portfolio extends Ophose.Component {static show(data) {let p = new Portfolio(data);document.body.appendChild(___render___.toNode(p, true))} constructor(props) {super(props);this.translationKey = this.props.translation;} render() {return new Blocker({children: [_('div', {className: 'container mx-auto p-8 flex flex-col gap-4 rounded-2xl bg-white shadow-lg', style: 'width: 100vw; height: 80vh; overflow-y: auto;'} ,_('h1', {className: 'text-xl lg:text-3xl font-extrabold flex items-center gap-4'} ,_('img', {src: this.props.thumbnail, className: 'w-8 h-8 lg:w-12 lg:h-12 object-cover rounded-xl'}),this.props.title,_('a', {className: 'font-bold bg-indigo-500 ml-auto py-2 px-4 text-white rounded-full text-xs', href: this.props.link} , _lang('projects.visit'))),_('p', {className: 'text-xs text-neutral-500'} , _lang(this.translationKey + '.shortDescription')),_('div', {className: 'flex gap-4 flex-wrap'} ,this.props.tags.map(tag => _('span', {className: 'text-xs font-bold bg-neutral-100 text-neutral-400 py-2 px-4 rounded-full'} , tag))),_('p', {className: 'text-neutral-400'} , this.props.year + ' - ', _lang(this.translationKey + '.type')),new PortfolioSlider({images: this.props.images}),_('h2', {className: 'text-xl font-bold'} , 'Description'),_lang(this.translationKey + '.description', undefined, (p) => {return _('div', {className: 'text-neutral-500 text-lg flex flex-col gap-2'} ,p.map(paragraph => _('p', paragraph)));}),_('h2', {className: 'text-xl font-bold'} , _lang('projects.features')),_lang(this.translationKey + '.features', undefined, (p) => {return _('ul', {className: 'text-indigo-500 flex flex-col gap-2 font-bold'} ,p.map(feature => _('li', {className: 'bg-indigo-100 p-4 rounded-xl'} , feature)));}),_('h2', {className: 'text-xl font-bold'} , 'Challenges'),_lang(this.translationKey + '.challenges', undefined, (p) => {return _('div', {className: 'grid grid-cols-1 lg:grid-cols-2 gap-4'} ,p.map(challenge => _('div', {className: 'bg-neutral-100 p-4 rounded-xl'} , [_('h3', {className: 'font-bold'} , challenge.title),_('p', {className: 'text-xs text-neutral-500'} , challenge.description)]))) }),_('h2', {className: 'text-xl font-bold'} , 'Some stats'),_lang(this.translationKey + '.stats', undefined, (p) => {return _('div', {className: 'grid grid-cols-2 lg:grid-cols-4 gap-4'} ,p.map(stat => _('div', {className: 'bg-neutral-100 p-4 rounded-xl'} , [_('h3', {className: 'font-bold'} , stat.title),_('p', {className: 'text-xs text-neutral-500'} , stat.value)]))) })) ]});} static list = {ophose: pfOphose,yzuvai: pfYzuvAI,unvapor: pfUnvapor,yzuva: pfYzuva,animotion: pfAnimotion } ;} oimpc('portfolio/Portfolio') class SectionProjects extends Ophose.Component {constructor(props) {super(props);} style() {return ` %self { } ` } render() {let _project = (data, cols=1) => {let display = () => {Portfolio.show(data)} return _('div', {className: 'rounded-2xl flex flex-col gap-2 text-black cursor-pointer transition-all duration-300 ease-in-out h-fit hover:bg-white p-4 col-span-1 lg:col-span-' + cols, onclick: display} , [_('img', {src: data.thumbnail, className: 'w-full rounded-2xl shadow-md aspect-video object-cover mb-2'}),_('div', {className: 'flex gap-4 flex-wrap'} , data.tags.map(tag => _('span', {className: 'text-xs font-bold bg-neutral-100 text-neutral-400 py-1 px-2 rounded-full'} , tag))),_('h3', {className: 'text-lg font-bold'} , data.title),_('p', _lang(data.translation + '.shortDescription')),_('a', {className: 'text-xs font-bold text-neutral-400'} , _lang('projects.seeMore'))])} return {_: 'div', className: 'w-full', children: [_('div', {className: 'w-full bg-neutral-50'} ,_('div', {className: 'container mx-auto py-16 flex flex-col gap-4'} , [_('h2', {className: 'text-sm font-extrabold text-neutral-400'} , _lang('projects.title')),_('p', {className: 'text-neutral-400 lg:text-md text-lg'} , _lang('projects.description')),_('div', {className: 'grid grid-cols-1 lg:grid-cols-3 gap-4'} , [_project(Portfolio.list.ophose, 2),_project(Portfolio.list.yzuvai),_project(Portfolio.list.unvapor),_project(Portfolio.list.yzuva),_project(Portfolio.list.animotion)])])) ]} } } class SectionExperience extends Ophose.Component {constructor(props) {super(props);} render() {let _experience = (image, date, translation) => {return _('div', {className: 'flex gap-4'} , [_('img', {className: 'w-16 h-16 rounded-xl object-cover border border-neutral-200 select-none z-10', src: image}),_('div', [_('h3', {className: 'text-lg font-bold'} , _lang('experience.' + translation + '.title')),_('p', {className: 'text-sm text-neutral-400'} , _lang('experience.' + translation + '.place')),_('p', {className: 'text-sm text-neutral-400'} , date),_lang('experience.' + translation + '.description', undefined, (desc) => {return _('div', {className: 'flex flex-col font-bold text-neutral-500 text-sm'} , desc.map((p) => _('p', p)))})])])} return {_: 'div', children: [_('div', {className: 'container mx-auto py-16 flex flex-col gap-4'} ,_('h2', {className: 'text-sm font-extrabold text-neutral-400'} , _lang('experience.title')),_('p', {className: 'text-neutral-400'} , _lang('experience.description')),_('div', {className: 'grid grid-cols-1 lg:grid-cols-2 gap-16 xs:gap-4 lg:gap-0 mt-4'} , [_('div', {className: 'flex flex-col gap-12 relative'} ,_('h3', {className: 'font-bold text-2xl font-bold'} , _lang('experience.work')),_('div', {className: 'flex flex-col gap-12 relative'} ,_experience('/assets/img/resume/iut-bordeaux.jpg', '2021-2024', 'iut'),_experience('/assets/img/resume/jean-monnet.png', '2018-2021', 'jeanMonnet'),_('div', {className: 'absolute top-4 left-4 w-1 h-3/4 bg-neutral-100'}),) ),_('div', {className: 'flex flex-col gap-12 relative'} ,_('h3', {className: 'font-bold text-2xl font-bold'} , _lang('experience.education')),_('div', {className: 'flex flex-col gap-12 relative'} ,_experience('/assets/img/resume/mvmt.jpg', '2024', 'mvmt'),_experience('/assets/img/resume/icmcb.jpg', '2023-2024', 'icmcb'),_experience('/assets/img/resume/tsukuba-university.webp', '2023', 'tsukuba'),_('div', {className: 'absolute top-4 left-4 w-1 h-3/4 bg-neutral-100'}),) )])) ]} } } class SectionSkills extends Ophose.Component {constructor(props) {super(props);} style() {return ` %self { } ` } render() {let _skill = (translation, tags) => {let colors = ['bg-blue-100 text-blue-500','bg-green-100 text-green-500','bg-yellow-100 text-yellow-500','bg-red-100 text-red-500','bg-purple-100 text-purple-500','bg-pink-100 text-pink-500','bg-indigo-100 text-indigo-500','bg-cyan-100 text-cyan-500','bg-teal-100 text-teal-500','bg-gray-100 text-gray-500',];return _('div', {className: 'flex flex-col gap-2 p-4 bg-white rounded-2xl shadow-sm select-none'} , [_('h3', {className: 'text-lg font-bold'} , _lang('skills.' + translation + '.title')),_('p', {className: 'text-neutral-400'} , _lang('skills.' + translation + '.description')),_('div', {className: 'flex gap-2 mt-2 flex-wrap'} , tags.map((tag, index) => {let color = colors[index % colors.length];return _('a', {className: 'text-sm font-bold px-2 py-1 rounded-full ' + color} , tag)}))])} return {_: 'div', className: 'bg-neutral-50 w-full', children: [_('div', {className: 'container mx-auto py-16 flex flex-col gap-4'} , [_('h2', {className: 'text-sm font-extrabold text-neutral-400'} , _lang('skills.title')),_('p', {className: 'text-neutral-400'} , _lang('skills.description')),_('div', {className: 'grid grid-cols-1 lg:grid-cols-3 gap-4'} , [_skill('devAndTech', ['PHP', 'JavaScript', 'TypeScript', 'Python', 'Java', 'C#', 'C++', 'C', 'SQL', 'Laravel', 'Symfony', 'Vue.js', 'React', 'Next.js', 'Git', 'GitHub', 'Gitlab']),_skill('design', ['HTML', 'CSS', 'TailwindCSS', 'Bootstrap', 'UX']),_skill('cybersecurityAiAndDevOps', ['Cybersecurity', 'AI', 'Machine Learning', 'DevOps', 'CI/CD', 'Kubernetes', 'Docker', 'Linux']),_skill('languages', ['French [Natif]', 'English [Fluent]', '日本語 [Beginner]']),_skill('softSkills', ['Teamwork', 'Communication', 'Problem-solving', 'Adaptability', 'Time management', 'Leadership']),_skill('dbAndNetworking', ['MySQL', 'PostgreSQL', 'SQLite', 'MongoDB', 'Redis', 'Firebase', 'Networks', 'TCP/IP', 'HTTP', 'DNS', 'VPN', 'Firewall'])])])]} } } class SectionServices extends Ophose.Component {constructor(props) {super(props);} style() {return ` %self { } ` } render() {let _step = (icon, translation, color="indigo") => {return _('div', {className: 'flex flex-col gap-2 select-none text-center items-center hover:scale-105 transition-transform duration-300 ease-in-out'} , [_('div', {className: `w-20 h-20 bg-${color}-500 rounded-3xl flex items-center justify-center`} ,_('a', {className: 'text-white text-3xl bi bi-' + icon})),_('h3', {className: 'text-2xl font-bold'} , _lang('services.steps.' + translation + '.title')),_('p', {className: 'text-neutral-400 text-xs'} , _lang('services.steps.' + translation + '.description'))])} let _separator = () => {return _('a', {className: 'hidden lg:block bi bi-chevron-double-right text-2xl text-neutral-400 lg:mt-8'})} let _button = (text, bgColor, textColor, icon, link) => {return _('a', {href: link, className: `bg-${bgColor} text-${textColor} text-xl font-bold py-3 rounded-full text-center duration-300 ease-in-out`} , [_('span', {className: 'mr-2 bi bi-' + icon}),text ])} return {_: 'div', className: 'w-full mx-auto container py-16 flex flex-col gap-8', children: [_lang('services.title', undefined, (text) => _('h1', {className: 'text-3xl lg:text-6xl font-bold text-neutral-800 lg:w-3/4 w-full'} , text)),_('p', {className: 'text-neutral-400 text-xl lg:text-3xl lg:w-3/4'} , _lang('services.description')),_('div', {className: 'lg:flex lg:items-start lg:flex-row lg:justify-between gap-8 lg:w-full mx-auto lg:mx-0 grid grid-cols-2'} , [_step('tools', 'consultation'),_separator(),_step('pencil', 'prototype'),_separator(),_step('code', 'development'),_separator(),_step('check2', 'testing'),_separator(),_step('check-circle', 'launch', 'green')]),_('p', {className: 'text-neutral-400 text-xl w-full lg:w-3/4 mt-16'} , _lang('services.unique')),_('div', {className: 'grid grid-cols-1 lg:grid-cols-2 gap-8'} ,_button(_lang('services.buttons.letsWork'), 'black', 'white', 'envelope-fill', 'mailto:contact@ah4.fr'),_button(_lang('services.buttons.learnMore'), 'gray-100', 'black', 'info-circle-fill', '/#about')),_('p', {className: 'text-neutral-300 text-sm w-3/4'} , _lang('services.note'))]} } } class Language {static currentLanguageCode = new Live(null);static lang = {} ;/** * Initializes the language. * This method should be called before using the translate methods. */ static init() {oenv('ah4/lang/lang') .then((lang) => {Language.lang = lang;Language.currentLanguageCode.set(lang.code);});} /** * Returns the translation of the given key. * @param {string} key the key of the translation. * @param {object} extra an object containing the values to replace in the translation. * @param {boolean} nullIfNotFound if true, returns null if the translation is not found. * @returns the translation (or :key: or null if not found). */ static translate(key, extra, nullIfNotFound = false) {let keys = key.split('.');let value = Language.lang;for (let k of keys) {if(value[k] === undefined) {return nullIfNotFound ? null : (':' + key + ':');} value = value[k];} if(extra && typeof value === 'string') {for (let k in extra) {value = value.replace('{' + k + '}', extra[k]);} } return value;} /** * Returns the current language. * @returns the current language. */ static current() {return Language.lang;} /** * Changes the language. * @param {string} code the code of the language to change to. */ static setLanguage(code) {oenv('ah4/lang/change/' + code) .then(() => {Language.init();});} /** * Returns a live translation. * @param {string} key the key of the translation. * @param {object} extra an object containing the values to replace in the translation. * @param {boolean} nullIfNotFound if true, returns null if the translation is not found. * @returns a live translation. */ static placeTranslation(key, extra, callback, nullIfNotFound = false) {return new PlacedLive(Language.currentLanguageCode, (code) => {if(code === null) return '';let value = Language.translate(key, extra, nullIfNotFound);if(callback) value = callback(value);return value;});} } const _lang = Language.placeTranslation;;class Header extends Ophose.Component {constructor(props) {super(props);this.yGreater = false;} onPlace(element) {let c = () => {if(window.scrollY > 10 && !this.yGreater) {element.classList.add('bg-white', 'border-b');this.yGreater = true;} else if (window.scrollY <= 10 && this.yGreater){element.classList.remove('bg-white', 'border-b');this.yGreater = false;} } window.addEventListener('scroll', c);c();} render() {let _language = (name, code) => _('a', {className: 'text-sm font-bold p-2 w-full rounded-lg hover:bg-neutral-100 cursor-pointer text-neutral-600', onclick: () => {Language.setLanguage(code);} } , name);let _changeLang = () => {let showed = new Live(false);return _('a', {className: 'cursor-pointer text-lg bi bi-globe relative', onclick: () => showed.set(!showed.get())} ,new PlacedLive(showed, (showed) => {return _('div', {className: 'absolute top-8 right-0 bg-neutral-50 shadow-md p-2 rounded-xl transition-all duration-300 ease-in-out cursor-default', style: 'display: ' + (showed ? 'block' : 'none')} ,_('div', {className: 'flex gap-2 flex-col text-sm'} ,_('h2', {className: 'w-max text-xs font-normal text-neutral-300 p-2'} , 'Here are the available languages:'),_language('English', 'en-US'),_language('Français', 'fr')) )})) } let _responsiveMenu = () => {let showed = new Live(false);return _('div', {className: 'lg:hidden'} , [_('a', {className: 'bi bi-list text-2xl relative', onclick: () => showed.set(!showed.get())}),new PlacedLive(showed, (s) => {return _('div', {className: 'fixed top-0 left-0 w-full h-full p-4 transition-all duration-300 ease-in-out z-30', style: 'display: ' + (s ? 'block' : 'none')} ,_('div', {className: 'bg-black bg-opacity-50 w-full h-full absolute top-0 left-0', onclick: () => showed.set(false)}),_('div', {className: 'bg-white w-80 h-full absolute top-0 left-0 p-8 shadow-lg z-40 flex flex-col gap-8'} ,_('h2', {className: 'text-sm text-neutral-400'} , 'AH4 - Menu'),_('a', {href: '/#home', className: 'text-lg font-bold text-black'} , _lang('header.home')),_('a', {href: '/#about', className: 'text-lg font-bold text-black'} , _lang('header.about')),_('a', {href: '/#projects', className: 'text-lg font-bold text-black'} , _lang('header.projects')),_('a', {href: '/#experiences', className: 'text-lg font-bold text-black'} , _lang('header.experiences')),_('a', {href: '/#skills', className: 'text-lg font-bold text-black'} , _lang('header.skills')),_('a', {href: '/#services', className: 'text-lg font-bold text-black'} , _lang('header.services')),_('div', {className: 'flex flex-col gap-2'} , [_('h3', {className: 'text-sm text-neutral-400'} , 'Change language:'),_language('English', 'en-US'),_language('Français', 'fr')]),_('div', {className: 'flex gap-4'} , [_('a', {className: 'bi bi-github text-lg text-black', href: 'https://github.com/ah-4/'}),_('a', {className: 'bi bi-linkedin text-lg text-black', href: 'https://www.linkedin.com/in/ah4ite/'}),_('a', {className: 'bi bi-instagram text-lg text-black', href: 'https://instagram.com/o4vwa/'}),_('a', {className: 'bi bi-envelope text-lg text-black', href: 'mailto:contact@ah4.fr'}),]),_('a', {className: 'text-xs mt-auto text-neutral-300'} , _lang('footer.legal'))),) })])} return {_: 'header', className: 'fixed py-4 z-20 w-full bg-transparent transition-all duration-300 ease-in-out text-black select-none border-neutral-200', children: [_('div', {className: 'container mx-auto flex justify-between items-center flex-row-reverse lg:flex-row'} , [_('a', {href: '/#home'} , _('img', {src: '/assets/img/ah4.png', className: 'w-12 h-12'})),_('nav', {className: 'gap-4 ml-auto font-bold items-center hidden lg:flex'} , [_('a', {href: '/#about'} , _lang('header.about')),_('a', {href: '/#projects'} , _lang('header.projects')),_('a', {href: '/#experiences'} , _lang('header.experiences')),_('a', {href: '/#skills'} , _lang('header.skills')),_('a', {href: '/#services'} , _lang('header.services')),_changeLang()]),_('div', {className: 'lg:hidden'} , [_responsiveMenu()])])]} } } class Footer extends Ophose.Component {constructor(props) {super(props);} style() {return ` %self { } ` } render() {return {_: 'div', className: 'bg-neutral-900 py-16 w-full', children: [_('div', {className: 'container mx-auto flex gap-8 flex-col lg:flex-row lg:justify-between text-neutral-100'} , [_('div', {className: 'flex flex-col gap-8 flex-1 relative'} , [_('h3', {className: 'text-5xl font-bold'} , _lang('footer.question')),_('p', {className: 'w-1/2'} , _lang('footer.description')),_('a', {href: 'mailto:contact@ah4.fr', className: 'text-2xl font-bold w-fit'} , 'contact@ah4.fr'),_('a', {className: 'text-sm'} , _lang('footer.legal')),]),_('div', {className: 'flex flex-col gap-8 flex-1 relative text-right items-end'} , [_('h3', {className: 'text-5xl font-bold'} , _lang('footer.aboutMe.title')),_('p', {className: 'w-1/2'} , _lang('footer.aboutMe.description')),_('div', {className: 'flex gap-4'} , [_('a', {className: 'bi bi-github text-2xl', href: 'https://github.com/ah-4/'}),_('a', {className: 'bi bi-linkedin text-2xl', href: 'https://www.linkedin.com/in/ah4ite/'}),_('a', {className: 'bi bi-instagram text-2xl', href: 'https://instagram.com/o4vwa/'}),_('a', {className: 'bi bi-envelope text-2xl', href: 'mailto:contact@ah4.fr'}),])])])]} } } class SplashScreen extends Ophose.Component {constructor(props) {super(props);setTimeout(() => {this.remove()} , 2000) this.text = new Live('.');this.i = 0;setInterval(() => {this.text.set(this.text.get() + '.')} , 500)} style() {return ` %self { display: flex; justify-content: center; align-items: center; flex-direction: column; height: 100vh; width: 100vw; position: fixed; top: 0; left: 0; background-color: #f9f9f9; z-index: 9999; gap: 2em; } %self .logo { width: 200px; height: 200px; } %self .text { font-size: 2.5em; font-weight: bold; color: #333; } ` } render() {return {_: 'div', children: [_('img', {src: '/assets/img/logo.png', className: 'logo'}),_('div', {className: 'text'} , this.text)]} } } ;class Base extends Ophose.Base {constructor(props) {super(props);app.setTitle("AH4 - Web architect & developer");Language.init();importScript("https://cdn.tailwindcss.com");} style() {return ` @import url("https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css"); .container { padding: 0 1em; } * { scroll-margin-top: 4em; scroll-behavior: smooth; } ` } render() {return {_: 'main', children: [new SplashScreen(),new Header(),this.props.children,new Footer()]} } }