Verify About image upload #3
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Finalize About PR | |
| on: | |
| push: | |
| branches: [agent/about-trust-page-v1] | |
| permissions: | |
| contents: write | |
| jobs: | |
| finalize: | |
| if: github.actor != 'github-actions[bot]' | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| ref: agent/about-trust-page-v1 | |
| fetch-depth: 0 | |
| - name: Merge main and finalize | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| git fetch origin main | |
| git merge --no-edit origin/main | |
| python -m pip install --quiet Pillow | |
| python - <<'PY' | |
| from pathlib import Path | |
| from PIL import Image,ImageEnhance,ImageFilter | |
| import base64,re | |
| R=Path('.') | |
| d=''.join((R/f'assets/img/about/owner-photo.part-{i}.txt').read_text().strip() for i in range(1,5)) | |
| src=R/'assets/img/about/.src.jpg'; src.write_bytes(base64.b64decode(d)) | |
| out=R/'assets/img/about/tetiana-financial-stream-ceo.webp' | |
| with Image.open(src) as im: | |
| im=im.convert('RGB'); w,h=im.size; r=w/h | |
| if r>.8: | |
| nw=round(h*.8); x=(w-nw)//2; im=im.crop((x,0,x+nw,h)) | |
| elif r<.8: | |
| nh=round(w/.8); y=(h-nh)//2; im=im.crop((0,y,w,y+nh)) | |
| im=im.resize((1200,1500),Image.Resampling.LANCZOS) | |
| im=ImageEnhance.Contrast(im).enhance(1.025) | |
| im=ImageEnhance.Color(im).enhance(1.015) | |
| im=im.filter(ImageFilter.UnsharpMask(1.25,82,3)) | |
| im.save(out,'WEBP',quality=84,method=6) | |
| src.unlink() | |
| logoH='<picture class="brand-logo-picture"><source srcset="/assets/brand/financial-stream/logo-primary-web.webp" type="image/webp"><img class="logo-image" src="/assets/brand/financial-stream/logo-primary-web.png" alt="Financial Stream LLC logo" width="640" height="203" decoding="async"></picture>' | |
| logoF='<picture class="brand-logo-picture"><source srcset="/assets/brand/financial-stream/logo-primary-web.webp" type="image/webp"><img class="footer-logo" src="/assets/brand/financial-stream/logo-primary-web.png" alt="Financial Stream LLC" width="640" height="203" decoding="async"></picture>' | |
| def page(path,ru=False): | |
| p=Path(path); s=p.read_text() | |
| s=re.sub(r'<img class="logo-image" src="/assets/brand/financial-stream/logo-primary-web\.png" alt="Financial Stream LLC" width="640" height="203">',logoH,s,count=1) | |
| s=re.sub(r'<img class="footer-logo" src="/assets/brand/financial-stream/logo-primary-web\.png" alt="Financial Stream LLC" width="640" height="203">',logoF,s,count=1) | |
| s=re.sub(r'<script src="(?:\.\./)+assets/js/about-photo\.js"></script>','',s) | |
| if not ru: | |
| old='<img src="../assets/img/about/owner-photo-fallback.svg" width="480" height="600" alt="Tetiana Horb, CEO of Financial Stream LLC, seated in a professional office" data-owner-photo>' | |
| new='<picture><source srcset="../assets/img/about/tetiana-financial-stream-ceo.webp" type="image/webp"><img src="../assets/img/about/tetiana-financial-stream-ceo.webp" width="1200" height="1500" fetchpriority="high" decoding="async" alt="Tetiana Horb, CEO of Financial Stream LLC, seated in a professional office setting"></picture>' | |
| s=s.replace(old,new).replace('<div class="header-actions"><a href="/ru/about/">RU</a><a class="btn btn-primary"','<div class="header-actions"><a class="btn btn-primary"') | |
| s=s.replace('<h3>Tetiana Horb</h3>','<h2 class="about-owner-note__name">Tetiana Horb</h2>',1) | |
| mark='</section><section class="about-section"><div class="container"><div class="about-section__heading fade-in"><div class="eyebrow">Why Financial Stream exists</div>' | |
| note='</section><section class="about-section about-section--ceo-note"><div class="container"><div class="about-note fade-in"><div class="about-note__head"><div><div class="eyebrow">Leadership principle</div><h2>A note from the CEO</h2></div><div class="about-note__identity"><strong>Tetiana Horb</strong><span>CEO, Financial Stream LLC</span></div></div><p>Financial Stream is built around organized records, clear communication, and practical next steps. Every engagement begins with understanding the client’s actual situation before deciding what needs to happen next.</p><small>Company principle. This is not presented as a personal quotation.</small></div></div>'+mark | |
| if 'A note from the CEO' not in s:s=s.replace(mark,note,1) | |
| who='Tetiana Horb' | |
| else: | |
| old='<img src="../../assets/img/about/owner-photo-fallback.svg" width="480" height="600" alt="Татьяна Горб, CEO Financial Stream LLC, в профессиональной офисной обстановке" data-owner-photo>' | |
| new='<picture><source srcset="../../assets/img/about/tetiana-financial-stream-ceo.webp" type="image/webp"><img src="../../assets/img/about/tetiana-financial-stream-ceo.webp" width="1200" height="1500" fetchpriority="high" decoding="async" alt="Татьяна Горб, CEO Financial Stream LLC, в профессиональной офисной обстановке"></picture>' | |
| s=s.replace(old,new).replace('<div class="header-actions"><a href="/about/">EN</a><a class="btn btn-primary"','<div class="header-actions"><a class="btn btn-primary"') | |
| s=s.replace('<h3>Татьяна Горб</h3>','<h2 class="about-owner-note__name">Татьяна Горб</h2>',1) | |
| mark='</section><section class="about-section"><div class="container"><div class="about-section__heading fade-in"><div class="eyebrow">Зачем существует Financial Stream</div>' | |
| note='</section><section class="about-section about-section--ceo-note"><div class="container"><div class="about-note fade-in"><div class="about-note__head"><div><div class="eyebrow">Принцип руководства</div><h2>Слово руководителя</h2></div><div class="about-note__identity"><strong>Татьяна Горб</strong><span>CEO, Financial Stream LLC</span></div></div><p>Работа Financial Stream строится вокруг порядка в документах, понятной коммуникации и практических следующих шагов. Сначала важно разобраться в реальной ситуации клиента, а затем определить, что именно необходимо сделать дальше.</p><small>Принцип работы компании. Текст не оформлен как личная цитата.</small></div></div>'+mark | |
| if 'Слово руководителя' not in s:s=s.replace(mark,note,1) | |
| who='Татьяна Горб' | |
| needle='"areaServed":"US","availableLanguage":["English","Russian"]' | |
| value='"areaServed":"US","address":{"@type":"PostalAddress","addressLocality":"Federal Way","addressRegion":"WA","addressCountry":"US"},"availableLanguage":["English","Russian"],"employee":{"@type":"Person","name":"'+who+'","jobTitle":"CEO"}' | |
| s=s.replace(needle,value,1); p.write_text(s) | |
| page('about/index.html'); page('ru/about/index.html',True) | |
| def home(path,href,label,services,navtitle): | |
| p=Path(path); s=p.read_text(); name='Services' if label=='About' else 'Услуги' | |
| a=f'<a href="{services}">{name}</a>'; b=a+f'\n <a href="{href}">{label}</a>' | |
| if f'<a href="{href}">{label}</a>' not in s:s=s.replace(a,b,2) | |
| h=f'<h3 class="footer-title">{navtitle}</h3>\n <ul class="footer-links">' | |
| if s.count(f'<a href="{href}">{label}</a>')<3:s=s.replace(h,h+f'\n <li><a href="{href}">{label}</a></li>',1) | |
| p.write_text(s) | |
| home('index.html','/about/','About','/services/','Navigation') | |
| home('ru/index.html','/ru/about/','О нас','/ru/services/','Навигация') | |
| PY | |
| cat >> assets/css/about.css <<'CSS' | |
| .about-page .about-hero__copy,.about-page .about-intro-grid>*,.about-page .about-section__heading,.about-page .about-service-card>div,.about-page .about-note__head>*{min-width:0}.about-page .about-portrait__frame picture{display:block;width:100%;height:100%}.about-page .about-portrait__frame picture img{width:100%;height:100%;object-fit:cover;object-position:50% 34%}.about-page .about-owner-note__name{margin:18px 0 8px;font-size:clamp(1.75rem,3vw,2.6rem)}.about-section--ceo-note{padding:clamp(68px,8vw,112px) 0}.about-note{padding:clamp(30px,5vw,62px);border:1px solid rgba(183,154,102,.28);border-radius:30px;background:linear-gradient(135deg,rgba(255,255,255,.98),rgba(242,238,231,.72));box-shadow:0 24px 64px rgba(7,29,47,.09)}.about-note__head{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:28px;align-items:end}.about-note h2{font-size:clamp(2.2rem,4.5vw,4.5rem)}.about-note__identity{display:grid;gap:3px;padding:14px 18px;border-left:2px solid #b79a66}.about-note__identity strong{color:#0a2033}.about-note__identity span{color:#64727e;font-size:.86rem}.about-note>p{max-width:920px;margin-top:30px;color:#253b4c;font-size:clamp(1.18rem,2vw,1.65rem);line-height:1.62}.about-note>small{display:block;margin-top:18px;color:#6c7781}.about-service-card:focus-visible{outline:3px solid rgba(29,111,157,.42);outline-offset:4px}.about-page .site-header{transition:transform .24s ease}.about-page .site-header.is-hidden-mobile{transform:translateY(-110%)} | |
| @media(max-width:900px){.about-page .site-header__inner>.header-actions{display:flex;gap:8px}.about-page .site-header__inner>.header-actions .lang-switch{display:flex!important}.about-page .site-header__inner>.header-actions>.btn{display:none!important}.about-page .site-header__panel .header-actions>a:not(.btn),.about-page .site-header__panel .lang-switch{display:none!important}.about-page .site-header__panel .header-actions{justify-content:stretch}.about-page .site-header__panel .header-actions .btn{width:100%}.about-note__head{grid-template-columns:1fr;align-items:start}} | |
| @media(max-width:430px){.about-page .container{width:calc(100% - 28px)}.about-page .logo-image{width:min(154px,44vw);height:auto}.about-page .lang-switch a{min-width:40px;min-height:40px;padding:8px 10px}.about-page .menu-toggle{min-width:44px;min-height:42px;padding-inline:10px}.about-page .about-hero h1{overflow-wrap:anywhere}.about-page .about-hero__actions .btn,.about-page .about-final__actions .btn{width:100%;white-space:normal;text-align:center}.about-page .about-service-card{grid-template-columns:auto minmax(0,1fr)}.about-page .about-service-card__arrow{display:none}} | |
| CSS | |
| rm -f assets/js/about-photo.js assets/img/about/owner-photo.part-{1,2,3,4}.txt assets/img/about/owner-photo-fallback.svg .github/workflows/about-finalize.yml | |
| python - <<'PY' | |
| from pathlib import Path | |
| from PIL import Image | |
| for f in ['about/index.html','ru/about/index.html']: | |
| s=Path(f).read_text(); assert s.count('<h1')==1 and s.count('about-service-card')>=6 and s.count('class="lang-switch"')==1 | |
| assert 'CEO, Financial Stream LLC' in s and 'about-photo.js' not in s and 'owner-photo.part-' not in s and 'owner-photo-fallback.svg' not in s | |
| assert 'Practicing Accountant' not in s and 'practicing accountant' not in s | |
| with Image.open('assets/img/about/tetiana-financial-stream-ceo.webp') as im: assert im.size==(1200,1500) and im.format=='WEBP' | |
| assert Path('index.html').read_text().count('href="/about/">About</a>')>=3 | |
| assert Path('ru/index.html').read_text().count('href="/ru/about/">О нас</a>')>=3 | |
| assert '/about/' in Path('sitemap.xml').read_text() and '/ru/about/' in Path('sitemap.xml').read_text() | |
| PY | |
| if git diff --name-only origin/main...HEAD | grep -E '(^|/)blog/|article'; then exit 1; fi | |
| git config user.name "Financial Stream Automation" | |
| git config user.email "actions@users.noreply.github.com" | |
| git add -A | |
| git commit -m "Finalize bilingual About trust pages" | |
| git push origin HEAD:agent/about-trust-page-v1 |