diff --git a/README.md b/README.md index bae5740..5fe228b 100644 --- a/README.md +++ b/README.md @@ -485,3 +485,28 @@ Om utvecklingsservern får en annan port än 8080 används adressen den skriver ## Hemligt rymdspel Kontroller, multiplayer, arkitektur och tester finns i [spelets README](docs/space-egg/README.md). + +### Designpilot: Verkstadsmagasinet + +Startsidan och `/systemutveckling/` provar en redaktionell form med serifrubriker, +panoramabilder och återkommande bildutsnitt. Det är en avgränsad designpilot; +övriga sidfamiljer behåller sin befintliga form. Den gemensamma mobilfootern +är kompaktare på samtliga sidor, med två kolumner och minst 44 px höga länkar. CSS ligger i +`_includes/styles/magazine.scss` med brytpunkter sist i `responsive.scss`. +Georgia används från besökarens system; inga externa fonter eller nya paket laddas. + +Startsidan får formen via `magazine-page` i `home.html`. På en Markdown-sida med +`layout: sections` väljs den med `presentation: magazine` i front matter. +Innehållet ligger fortsatt i Markdown och befintlig startsidesdata. + +En ensam Markdown-bild inne i en hero, `![](/assets/img/...)`, blir en dekorativ +panoramabild mellan ingress och sammanfattning. Kontaktlänkarna ligger efter +sammanfattningen. Utan bild behåller hero-sektionen sin tidigare struktur. +Startsidan anger bilden via `intro.image` i `_data/home/content.json` och +respektive tjänsteutsnitt via `services.items[].image`. Bilderna återanvänds +från bildförslaget i PR #59; inga av dess font- eller komponentändringar ingår. +Mobilen beskär panoramat åt höger och visar tjänsterna som en lista med små +bildutsnitt. Bildrörelse vid hovring stängs av vid minskad rörelse. + +Startsidan lyfter den befintliga ingressen till huvudrubrik och använder den +befintliga huvudrubriken som ingress. Det här är ett medvetet designförslag. diff --git a/_data/home/content.json b/_data/home/content.json index 78f9ab5..bb02615 100644 --- a/_data/home/content.json +++ b/_data/home/content.json @@ -2,7 +2,8 @@ "intro": { "heading": "Vi är experter på den senaste mjukvarutekniken", "emphasis": "och erbjuder den till våra kunder.", - "lead": "Nyfikna på att utforska nytt, med erfarenhet av vad som håller." + "lead": "Nyfikna på att utforska nytt, med erfarenhet av vad som håller.", + "image": "/assets/img/home-hero.webp" }, "about": { "eyebrow": "Athega i korthet", @@ -14,22 +15,26 @@ { "title": "Systemutveckling", "description": "Seniora utvecklingskonsulter som hjälper er att utveckla, modernisera och förvalta system oavsett teknik, plattform eller var i livscykeln ni befinner er.", - "href": "/systemutveckling/" + "href": "/systemutveckling/", + "image": "/assets/img/systemutveckling/hero.webp" }, { "title": "AI & maskininlärning", "description": "Vi tar AI från idé till fungerande lösning, med erfarenhet av allt från analys och automation till generativa modeller.", - "href": "/ai-labbet/" + "href": "/ai-labbet/", + "image": "/assets/img/ai-labbet/hero.webp" }, { "title": "AI för industri", "description": "Hårdvara, mjukvara och stöd i en helhet för produktionsnära AI, optimering, kvalitet och prediktivt underhåll.", - "href": "/ai-labbet/industri/" + "href": "/ai-labbet/industri/", + "image": "/assets/img/home-hero.webp" }, { "title": "Teknikgranskning", "description": "En djup teknisk genomlysning som ger köpare, investerare och bolag ett tydligt beslutsunderlag.", - "href": "/teknikgranskning/" + "href": "/teknikgranskning/", + "image": "/assets/img/teknikgranskning/hero.webp" } ] }, diff --git a/_includes/components/magazine-picture.html b/_includes/components/magazine-picture.html new file mode 100644 index 0000000..605e83b --- /dev/null +++ b/_includes/components/magazine-picture.html @@ -0,0 +1,5 @@ +{% if image %} + +{% endif %} diff --git a/_includes/components/service-card.html b/_includes/components/service-card.html index 7884650..9666527 100644 --- a/_includes/components/service-card.html +++ b/_includes/components/service-card.html @@ -1,4 +1,8 @@ + {% if service.image %} +
+ + {% endif %}

{{ service.title | escape }}

diff --git a/_includes/home.html b/_includes/home.html index 9ce8595..730030c 100644 --- a/_includes/home.html +++ b/_includes/home.html @@ -1,4 +1,4 @@ -
+
{% include "home/intro.html" %}
diff --git a/_includes/home/intro.html b/_includes/home/intro.html index 343f6a9..ed0600a 100644 --- a/_includes/home/intro.html +++ b/_includes/home/intro.html @@ -1,6 +1,7 @@
-

{{ home.content.intro.heading | escape }} {{ home.content.intro.emphasis | escape }}

-

{{ home.content.intro.lead | escape }}

+

{{ home.content.intro.lead | escape }}

+

{{ home.content.intro.heading | escape }} {{ home.content.intro.emphasis | escape }}

+ {% include "components/magazine-picture.html", image: home.content.intro.image %}
diff --git a/_includes/home/services.html b/_includes/home/services.html index c2932f2..7bbd093 100644 --- a/_includes/home/services.html +++ b/_includes/home/services.html @@ -1,6 +1,6 @@

{{ home.content.services.heading | escape }}

{% for service in home.content.services.items %} - {% include "components/service-card.html", service: service %} + {% include "components/service-card.html", service: service, number: forloop.index %} {% endfor %}
diff --git a/_includes/sections/hero.html b/_includes/sections/hero.html index 0f23f79..46f53ed 100644 --- a/_includes/sections/hero.html +++ b/_includes/sections/hero.html @@ -2,7 +2,8 @@
{{ eyebrow }} {{ heading }} - {{ lead }} + {{ lead }}{% if image %} + {% include "components/magazine-picture.html", image: image %}{% endif %} {% if summary != empty %} {% if variant == 'ai' %}
{{ summary }}
{% else %}{{ summary }}{% endif %} {% endif %} diff --git a/_includes/styles/magazine.scss b/_includes/styles/magazine.scss new file mode 100644 index 0000000..2c35653 --- /dev/null +++ b/_includes/styles/magazine.scss @@ -0,0 +1,178 @@ +/* Verkstadsmagasinet: scoped to the homepage and opted-in section pages. + Existing content, navigation and other page families keep their own styles. */ +.magazine-page { + --magazine-serif: Georgia, "Times New Roman", serif; + --magazine-gap: 28px; + font-weight: 400; + + :is(h1, h2, h3):not(.eyebrow) { + font-family: var(--magazine-serif); + font-weight: 700; + letter-spacing: -.035em; + text-wrap: pretty; + } + + > .page-hero { + padding-top: 56px; + padding-bottom: 0; + } + + > .page-hero > .shell { + display: block; + } + + .page-hero h1 { + max-width: 1150px; + font-size: clamp(44px, 5.5vw, 76px); + line-height: 1.08; + letter-spacing: -.045em; + } + + .page-hero h1 span { color: inherit; } + + .page-hero .page-lead { + max-width: 930px; + padding-left: 0; + border: 0; + color: var(--ink); + font-size: clamp(20px, 2.1vw, 28px); + line-height: 1.4; + } + + > .page-hero > .shell > .magazine-picture { + margin-top: 32px; + } + + > .page-hero > .shell > .hero-summary { + max-width: 820px; + margin-top: 28px; + } + + > .page-hero > .shell > .hero-actions { + margin-top: 24px; + margin-bottom: 24px; + } + + .home-service-grid { + grid-template-columns: repeat(4, minmax(0, 1fr)); + gap: var(--magazine-gap); + border: 0; + } + + .home-service-grid .service-card { + display: flex; + flex-direction: column; + align-items: stretch; + gap: 0; + padding: 0 0 24px; + border: 0; + border-bottom: 1px solid var(--line); + } + + .service-card-picture { + overflow: hidden; + margin-bottom: 22px; + background: var(--neutral); + } + + .service-card-picture img { + width: 100%; + aspect-ratio: 1 / 1; + object-fit: cover; + object-position: 55% center; + transition: transform .35s ease; + } + + .service-card:is(:hover, :focus-visible) img { transform: scale(1.035); } + + .service-card-number { + display: flex; + align-items: center; + gap: 14px; + margin-bottom: 12px; + color: var(--muted); + font-size: var(--text-sm); + font-variant-numeric: tabular-nums; + } + + .service-card-number::after { + content: ""; + width: 28px; + height: 2px; + background: var(--orange); + } + + .service-card h2 { font-size: clamp(23px, 2vw, 28px); line-height: 1.15; } + .service-card p { margin-top: 16px; font-size: var(--text-base); } + + .focus-section > .shell > .section-heading { + display: grid; + grid-template-columns: 220px minmax(0, 1fr); + gap: 40px; + margin: 0 0 44px; + padding-top: 4px; + } + + .focus-section .focus-intro { font-size: var(--text-base); } + .focus-label { margin-bottom: 24px; } + + .home-latest { margin-top: 56px; } + .home-latest-grid { gap: var(--magazine-gap); } + .home-latest-card { + grid-template-columns: 110px minmax(0, 1fr); + gap: 18px; + min-height: 0; + border: 0; + border-radius: 0; + overflow: visible; + transition: none; + } + .home-latest-card:hover { transform: none; } + .home-latest-card img { height: 150px; min-height: 0; } + .home-latest-card > div { padding: 0; } + .home-latest-card h3 { font-size: 21px; line-height: 1.2; hyphens: none; overflow-wrap: anywhere; } + .home-latest-card:is(:hover, :focus-visible) h3 { + text-decoration: underline; + text-decoration-color: var(--orange); + text-underline-offset: .2em; + } + .home-latest-card p { font-size: var(--text-sm); line-height: 1.5; } + + .section-heading h2 { font-size: clamp(30px, 3.4vw, 44px); } + .join-grid h2 { font-size: clamp(28px, 3vw, 38px); } + + .focus-grid { + grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: 32px var(--magazine-gap); + border: 0; + } + .focus-grid article { + min-height: 0; + padding: 24px 0 0; + border: 0; + border-top: 1px solid var(--line); + } + .focus-grid h3, .step-grid h3 { font-size: 26px; } + .focus-grid p { font-size: var(--text-base); } + .step-grid { gap: var(--magazine-gap); } + .step-grid article { + border-radius: 0; + padding: 24px 0 0; + border-top: 2px solid var(--orange); + background: transparent; + } + .soft-section, .join-section { background: #f5f4f1; } +} + +.magazine-picture { + width: 100%; + padding-bottom: 16px; + border-bottom: 1px solid var(--orange); +} +.magazine-picture img { + width: 100%; + height: auto; + aspect-ratio: 1916 / 700; + object-fit: cover; + object-position: center 60%; +} diff --git a/_includes/styles/responsive.scss b/_includes/styles/responsive.scss index 17faf27..0db766f 100644 --- a/_includes/styles/responsive.scss +++ b/_includes/styles/responsive.scss @@ -60,6 +60,62 @@ .article-shell { padding-top: 45px; } .back-link { margin-bottom: 45px; } .article-body iframe { min-height: 260px; } - .footer-top { grid-template-columns: 1fr; } - .footer-bottom { gap: 20px; } + .site-footer { padding: 32px 0 20px; } + .footer-top { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 24px 20px; } + .footer-top > div:first-child { grid-column: 1 / -1; } + .footer-top img { width: 120px; margin-bottom: 0; } + .footer-links { min-width: 0; gap: 0; } + .footer-links > span { margin-bottom: 8px; } + .footer-links a { display: flex; align-items: center; min-height: 44px; overflow-wrap: anywhere; } + .footer-links p { margin: 8px 0 0; } + .footer-social { grid-column: 1 / -1; width: auto; flex-direction: row; flex-wrap: wrap; gap: 0 24px; } + .footer-social > span { flex-basis: 100%; } + .footer-social a { justify-content: flex-start; } + .footer-bottom { flex-wrap: wrap; gap: 8px 20px; margin-top: 24px; padding-top: 16px; } +} + +/* Editorial pilot: keep image crops and text readable before four columns fit. */ +@media (max-width: 1100px) { + .magazine-page .home-service-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } + .magazine-page .service-card-picture img { aspect-ratio: 3 / 2; } + .magazine-page .home-latest-grid { grid-template-columns: 1fr; } + .magazine-page .home-latest-card { grid-template-columns: 150px minmax(0, 1fr); } + .magazine-page .focus-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } +} + +@media (max-width: 640px) { + .magazine-page { --magazine-gap: 24px; } + .magazine-page > .page-hero { padding-top: 36px; } + .magazine-page .page-hero h1 { font-size: clamp(36px, 9.7vw, 52px); } + .magazine-page .page-hero .page-lead { font-size: 20px; } + .magazine-page > .page-hero > .shell > .magazine-picture { margin-top: 24px; } + .magazine-picture img { aspect-ratio: 3 / 2; object-position: 70% center; } + .magazine-page .focus-section > .shell > .section-heading { grid-template-columns: 1fr; gap: 0; margin-bottom: 32px; } + .magazine-page .home-service-grid { grid-template-columns: 1fr; } + .magazine-page .home-service-grid .service-card { + display: grid; + grid-template-columns: 112px minmax(0, 1fr); + column-gap: 18px; + align-items: start; + } + .magazine-page .service-card-picture { grid-column: 1; grid-row: 1 / 4; margin-bottom: 0; } + .magazine-page .service-card-picture img { aspect-ratio: 1 / 1; } + .magazine-page .service-card-number, .magazine-page .service-card-heading, .magazine-page .service-card p { grid-column: 2; } + .magazine-page .service-card h2 { font-size: 23px; overflow-wrap: anywhere; } + .magazine-page .service-card p { font-size: var(--text-sm); margin-top: 12px; } + .magazine-page .home-latest { margin-top: 40px; } + .magazine-page .home-latest-card { grid-template-columns: 100px minmax(0, 1fr); gap: 16px; } + .magazine-page .home-latest-card img { height: 132px; } + .magazine-page .home-latest-card h3 { font-size: 21px; } + .magazine-page .focus-grid { grid-template-columns: 1fr; } + .magazine-page .focus-grid h3, .magazine-page .step-grid h3 { font-size: 25px; } +} + +@media (prefers-reduced-motion: reduce) { + .magazine-page .service-card img { transition: none; } + .magazine-page .service-card:is(:hover, :focus-visible) img { transform: none; } +} + +@media print { + .magazine-picture, .magazine-page .service-card-picture { display: none; } } diff --git a/_layouts/sections.html b/_layouts/sections.html index fd38800..38fbb3c 100644 --- a/_layouts/sections.html +++ b/_layouts/sections.html @@ -1,6 +1,6 @@ --- layout: default --- -
+
{{ content }}
diff --git a/assets/img/ai-labbet/hero.webp b/assets/img/ai-labbet/hero.webp new file mode 100644 index 0000000..19eb305 Binary files /dev/null and b/assets/img/ai-labbet/hero.webp differ diff --git a/assets/img/home-hero.webp b/assets/img/home-hero.webp new file mode 100644 index 0000000..5baa19d Binary files /dev/null and b/assets/img/home-hero.webp differ diff --git a/assets/img/systemutveckling/hero.webp b/assets/img/systemutveckling/hero.webp new file mode 100644 index 0000000..33450ca Binary files /dev/null and b/assets/img/systemutveckling/hero.webp differ diff --git a/assets/img/teknikgranskning/hero.webp b/assets/img/teknikgranskning/hero.webp new file mode 100644 index 0000000..389002e Binary files /dev/null and b/assets/img/teknikgranskning/hero.webp differ diff --git a/assets/site.scss b/assets/site.scss index c907f65..bb9abc9 100644 --- a/assets/site.scss +++ b/assets/site.scss @@ -13,4 +13,5 @@ @use "styles/prism"; @use "styles/footer"; @use "styles/section-spacing"; +@use "styles/magazine"; @use "styles/responsive"; diff --git a/scripts/markdown-sections.mjs b/scripts/markdown-sections.mjs index 5e7efa1..e35adb8 100644 --- a/scripts/markdown-sections.mjs +++ b/scripts/markdown-sections.mjs @@ -108,6 +108,13 @@ export function createSectionRenderer(md, renderTemplate) { template = 'hero'; Object.assign(data, header(blocks, 'h1')); if (!data.heading) throw new Error('A hero needs a # heading.'); + const imageIndex = blocks.findIndex(block => { + const tokens = inlineTokens(block); + return block[0].type === 'paragraph_open' && tokens.length === 1 && tokens[0].type === 'image'; + }); + if (imageIndex !== -1) { + data.image = inlineTokens(blocks.splice(imageIndex, 1)[0])[0].attrGet('src'); + } data.links = links(blocks); data.lead = paragraph(blocks.shift(), 'page-lead'); data.summary = variant === 'ai' ? render(blocks) : blocks.map(block => paragraph(block, 'hero-summary')).join(''); diff --git a/scripts/markdown-sections.test.mjs b/scripts/markdown-sections.test.mjs index bd7f932..ca6df7a 100644 --- a/scripts/markdown-sections.test.mjs +++ b/scripts/markdown-sections.test.mjs @@ -58,3 +58,25 @@ test('incorrect block names and variants give clear errors', async () => { await assert.rejects(render('Text', 'focus', 'stakced'), /Unknown focus variant: stakced/); await assert.rejects(render('Ingen rubrik', 'hero'), /hero needs a # heading/); }); + +test('a standalone hero image keeps the summary and trailing contact links intact', async () => { + for (const imageFirst of [true, false]) { + const image = '![](/assets/img/systemutveckling/hero.webp)'; + const link = '[reception@athega.se](mailto:reception@athega.se)'; + const tail = imageFirst ? `${image}\n\n${link}` : `${link}\n\n${image}`; + const html = await render(`Systemutveckling\n\n# En *rubrik*\n\nIngress.\n\nSammanfattning med **betoning**.\n\n${tail}`, 'hero'); + assert.equal((html.match(/Sammanfattning med betoning<\/strong>/); + assert.match(html, /class="hero-actions"/); + assert.match(html, /href="mailto:reception@athega.se" class="text-link"/); + assert.ok(html.indexOf('class="magazine-picture"') < html.indexOf('class="hero-summary"')); + } +}); + +test('an inline hero image remains part of its paragraph', async () => { + const html = await render('# Rubrik\n\nIngress.\n\nSe ![diagram](/diagram.png) i sammanhang.', 'hero'); + assert.doesNotMatch(html, /magazine-picture/); + assert.match(html, /class="hero-summary">Se diagram i sammanhang/); +}); diff --git a/systemutveckling/index.md b/systemutveckling/index.md index 1ad53ae..0fb2f82 100644 --- a/systemutveckling/index.md +++ b/systemutveckling/index.md @@ -1,5 +1,6 @@ --- layout: sections +presentation: magazine title: Systemutveckling description: >- Seniora systemutvecklare som hjälper er att utveckla, modernisera och förvalta verksamhetskritiska @@ -16,6 +17,8 @@ Vi hjälper er att utveckla, modernisera och förvalta system som är viktiga f Athegas konsulter går in som en del av laget. Vi kombinerar lång erfarenhet med nyfikenhet på bättre verktyg och arbetssätt, men väljer alltid teknik utifrån problemet, människorna och vad som ska fungera över tid. +![](/assets/img/systemutveckling/hero.webp) + [Prata systemutveckling med oss – reception@athega.se](mailto:reception@athega.se?subject=Systemutveckling) {% endsection %}