Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
128 changes: 128 additions & 0 deletions blocks/multiplatform/hero/hero.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
@import "@jetbrains/kotlin-web-site-ui/out/components/breakpoints-v2/media.pcss";

.wrapper {
position: relative;
z-index: 0;
overflow: hidden;
}

.wrapper::before {
content: '';
display: block;

background: radial-gradient(238.09% 60.02% at 49.98% 105.17%, rgba(177, 38, 235, 0.00) 0%, #260053 100%) center no-repeat;
filter: blur(200px);

height: 696px;
width: 826px;

position: absolute;
top: 36px;
left: 50%;
transform: translate(-50%, -50%);
z-index: -1;
}

.hero {
display: flex;
flex-direction: column;
align-items: center;

padding-block: 72px 96px;

--hero-logo-image: '';
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's better when icons and other resources in the same folder with component, but i can't find a normal way for nextjs. Ask for help

Image

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfortunately, the images should be in public directory, this seems to be a kind of contract in Next.JS: https://nextjs.org/docs/app/api-reference/file-conventions/public-folder.

Regarding the inlining of the SVGs, I'd prefer to see them as paths to files instead of base64 (to reduce size of the page via caching). As I see, it's something related to webpack configuration: maybe it's downside of the optimizedImages task, maybe we just need to configure "asset/resource" configuration for SVG files. Not sure we can fix it right away, without breaking all SVG imports.

@krutilov, ,maybe you know what we can do?

}

.hero::before {
display: block;
content: '';
background: var(--hero-logo-image) no-repeat center;

height: 72px;
width: 72px;

@media (--ktl-ts-more) {
height: 96px;
width: 96px;
}
}

.title, .subtitle {
margin: 0;
padding: 0;
text-align: center;
}

.title {
padding-block: 24px;

@media (--ktl-ts-in) {
/* @mixin rs-hero-sm-*; */
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried to add mixins.p.css from rescui, but I see there is no way to get sm value if you are higher than 640px. Ask for help.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you want to get font-size and line height, right?
Have you tried @mixin rs-typography-hero-sm?

font-size: 42px;
line-height: 50px;
}
}

.subtitle {
@media (--ktl-ts-in) {
/* @mixin rs-subtitle-1-sm-* */
font-size: 23px;
line-height: 30px;
}
}

.platforms {
display: flex;
flex-wrap: wrap;
justify-content: center;

box-sizing: border-box;
gap: 18px 40px;
width: 100%;

margin: 0;
padding: 48px 0;
list-style: none;

--hero-item-src: '';
}

.platform {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 8px;

line-height: 1.5;

@media (--ktl-ts-more) {
flex-basis: 80px;
}

@media (--ktl-ms-in) {
padding-inline: 14px;
}
}

.platform::before {
display: block;
content: '';

background: var(--hero-item-src) no-repeat center;
background-size: 32px;
height: 32px;
width: 32px;

@media (--ktl-ts-more) {
background-size: 48px;
height: 48px;
width: 48px;
}
}

.button {
@media (--ktl-ms-in) {
width: 100%;
}
}
5 changes: 5 additions & 0 deletions blocks/multiplatform/hero/icons/android.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions blocks/multiplatform/hero/icons/desktop.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions blocks/multiplatform/hero/icons/ios.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions blocks/multiplatform/hero/icons/server.svg
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

desktop and server seems to be replaced with each other according to the figma

Image

Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions blocks/multiplatform/hero/icons/web.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
54 changes: 54 additions & 0 deletions blocks/multiplatform/hero/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import cn from 'classnames';

import { useTextStyles } from '@rescui/typography';
import { Button } from '@rescui/button';

import { useML } from '@jetbrains/kotlin-web-site-ui/out/components/breakpoints-v2';

import android from './icons/android.svg';
import ios from './icons/ios.svg';
import web from './icons/web.svg';
import desktop from './icons/desktop.svg';
import server from './icons/server.svg';

import logo from './logo.svg';

import styles from './hero.module.css';

const platforms = [
Copy link
Contributor

@berezinant berezinant Nov 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

JIC: We have some platform types declared in the case studies module here. Probably it's worth extracting it to some common place (or maybe not)

Anyway, there is a difference in names for platforms on different pages (frontend + backend on case studies, web + server on this kmp landing) and there is still a discussion what options are better.
Not sure if there are any certain action points here, so just to share some context

{ title: 'Android', icon: android },
{ title: 'iOS', icon: ios },
{ title: 'Web', icon: web },
{ title: 'Desktop', icon: desktop },
{ title: 'Server', icon: server }
] as const;

export function HeroBanner({ url }: { url: string }) {
const isML = useML();
const textCn = useTextStyles();

return (
<div className={styles.wrapper}>
<div className={cn(styles.hero, 'ktl-layout', 'ktl-layout--center')}
style={{ '--hero-logo-image': `url(${logo.src})` }}>
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't get why you prefer using a background image instead of the img tag. Not a major thing at all, just seems more complicated then a image tag.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

there is a ts error also, something like could fix it

declare module 'react' {
    interface CSSProperties {
        [key: `--${string}`]: string | number;
    }
}

Copy link
Collaborator Author

@zoobestik zoobestik Nov 26, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@nikpachoo

I didn't get why you prefer using a background image instead of the img tag. Not a major thing at all, just seems more complicated then a image tag.

If your question is about the custom property - it's simply a technique to keep images colocated with the component.

If this is a broader question about images in HTML vs CSS, here's my reasoning:

Content vs Decorative graphics

This practice stems from accessibility techniques. The core idea is distinguishing between two types of images: content images and decorative graphics.

  1. Content images are those essential to the narrative - screenshots, photos, charts, and similar. They're crucial to storytelling and require attributes like alt for cases when the image fails to load or when users rely on screen readers. They should be visible when you copy an article to clipboard. Ideally, search engines should index only these images and display them in image search results.

  2. Decorative graphics are purely visual - backgrounds, parallax effects, animations. They're contextually disconnected from the article's content and serve as visual "noise." They don't need alt attributes, shouldn't be copied, crawled, or indexed. Plus, they unnecessarily inflate customer's context window for LLMs 🙃

    To make an <img> inaccessible for decorative elements, you'd need to add numerous attributes. It's far simpler to use backgrounds and pseudo-elements. This approach ensures your HTML remains clean and your page cache consistency is maintained as long as you don't modify the article content. This also doesn't bloat your HTML size anyway.

My own baseline rule: if an image must remain accessible without CSS - use the <img> tag. If not - any CSS technique works (including sprites, which you can't implement with <img> tags). Possibly, my understanding may be outdated. I'd welcome an alternative perspective if mine isn't convincing.

<h1 className={cn(styles.title, textCn('rs-hero'))}>Kotlin Multiplatform</h1>
<p className={cn(styles.subtitle, textCn('rs-subtitle-1'))}>
Go&nbsp;cross&#8209;platform without compromising performance, UX, or&nbsp;code&nbsp;quality
</p>
<ul className={styles.platforms}>
{platforms.map(({ title, icon }) => (
<li
key={title.toLowerCase()}
className={cn(styles.platform, textCn(isML ? 'rs-h5' : 'rs-h4'))}
style={{ '--hero-item-src': `url(${icon.src})` }}
>
{title}
</li>
))}
</ul>
<Button className={cn(styles.button)} mode={'rock'} href={url} size={isML ? 'm' : 'l'}>Get
Started</Button>
</div>
</div>
);
}
10 changes: 10 additions & 0 deletions blocks/multiplatform/hero/logo.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions components/landing-layout/landing-layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import React, { FC, useCallback, useMemo } from 'react';

import Head from 'next/head';

import '@jetbrains/kotlin-web-site-ui/out/components/layout-v2';
import GlobalHeader from '@jetbrains/kotlin-web-site-ui/out/components/header';
import GlobalFooter from '@jetbrains/kotlin-web-site-ui/out/components/footer';
import TopMenu from '@jetbrains/kotlin-web-site-ui/out/components/top-menu';
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
"@babel/core": "^7.22.10",
"@babel/register": "7.15.3",
"@jetbrains/babel-preset-jetbrains": "^2.3.1",
"@jetbrains/kotlin-web-site-ui": "4.12.0",
"@jetbrains/kotlin-web-site-ui": "4.13.0-alpha.4",
"@react-hook/resize-observer": "1.2.5",
"@rescui/button": "0.18.0",
"@rescui/card": "^0.8.1",
Expand Down
34 changes: 24 additions & 10 deletions pages/multiplatform/index.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,26 @@
import { Button } from '@rescui/button';
import { LandingLayout, LandingLayoutProps } from '../../components/landing-layout/landing-layout';
import { ThemeProvider } from '@rescui/ui-contexts';

import {
MULTIPLATFORM_MOBILE_TITLE,
MULTIPLATFORM_MOBILE_URL
} from '@jetbrains/kotlin-web-site-ui/out/components/header';
import { LandingLayout, LandingLayoutProps } from '../../components/landing-layout/landing-layout';

import styles from './multiplatform.module.css';
import { FaqBlock } from '../../blocks/multiplatform/faq-block/faq-block';
import { HeroBanner } from '../../blocks/multiplatform/hero';

const MULTIPLATFORM_MOBILE_TITLE = 'Kotlin Multiplatform' as const;
const MULTIPLATFORM_MOBILE_URL = '/multiplatform/' as const;

const TOP_MENU_ITEMS: LandingLayoutProps['topMenuItems'] = [
{
title: 'Compose Multiplatform',
url: 'https://www.jetbrains.com/compose-multiplatform/'
},
{
title: 'Docs',
url: 'https://www.jetbrains.com/help/kotlin-multiplatform-dev/get-started.html'
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

:)
Let's use the link without redirect: https://kotlinlang.org/docs/multiplatform/get-started.html

}
];

const TOP_MENU_ITEMS: LandingLayoutProps['topMenuItems'] = [];
const GET_STARTED_URL = '/docs/multiplatform/get-started.html' as const;

export default function MultiplatformLanding() {
return (
Expand All @@ -23,11 +34,14 @@ export default function MultiplatformLanding() {
topMenuTitle={MULTIPLATFORM_MOBILE_TITLE}
topMenuHomeUrl={MULTIPLATFORM_MOBILE_URL}
topMenuItems={TOP_MENU_ITEMS}
topMenuButton={<Button href={'#get-started'}>Get started</Button>}
topMenuButton={<Button href={GET_STARTED_URL}>Get started</Button>}
canonical={'https://kotlinlang.org/multiplatform/'}
>
<div className={styles.page}>
<FaqBlock />
<div className="ktl-layout-to-2">
<ThemeProvider theme={'dark'}>
<HeroBanner url={GET_STARTED_URL} />
<FaqBlock />
</ThemeProvider>
</div>
</LandingLayout>
);
Expand Down
16 changes: 8 additions & 8 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1433,10 +1433,10 @@
"@babel/runtime" "^7.14.0"
babel-plugin-angularjs-annotate "^0.10.0"

"@jetbrains/kotlin-web-site-ui@4.12.0":
version "4.12.0"
resolved "https://registry.yarnpkg.com/@jetbrains/kotlin-web-site-ui/-/kotlin-web-site-ui-4.12.0.tgz#54ac736c11588e3caf427d1f6041081ecda72960"
integrity sha512-xjHSphIJPKGnd74jZThqbGqeKk4OH3/znCJ6jdVUP3x5REwcPzegcF0qhEzcYnzl3wZ6/LLk2wHudnwCIAfm+w==
"@jetbrains/kotlin-web-site-ui@4.13.0-alpha.4":
version "4.13.0-alpha.4"
resolved "https://registry.yarnpkg.com/@jetbrains/kotlin-web-site-ui/-/kotlin-web-site-ui-4.13.0-alpha.4.tgz#e8c8ee5a6ad203632b7f65ee37b55b9da9bafb8e"
integrity sha512-RYka7guyOoPdR4GQoTM+qIVDcwNuRRhsjjuTJflGe/XB+ipo3nR8UsPvZmnJtDcoLXGT2XgHyQiIyvQfy6uijg==

"@jridgewell/gen-mapping@^0.3.0", "@jridgewell/gen-mapping@^0.3.2":
version "0.3.3"
Expand Down Expand Up @@ -1790,9 +1790,9 @@
integrity sha512-SfFy7W8vlWuRsEpT9VYSVRXGso6PK2pP5BuPnt3d525aW8Rx99kiuKYaqNPdu9p2nh2/XIf+febOl4Q3x/fH1g==

"@rescui/colors@^0.1.9":
version "0.1.9"
resolved "https://registry.npmjs.org/@rescui/colors/-/colors-0.1.9.tgz"
integrity sha512-ZgMmIuCJERqPsfCY+7I9UvOIWEypyiZ4VdDdHPb/HUy9lxPpfNzV7Fs/RalBJP4wtgsfwR24d4IlYNhpJcunTA==
version "0.1.11"
resolved "https://registry.yarnpkg.com/@rescui/colors/-/colors-0.1.11.tgz#438b5058cc308d28d4bda407236c25b67aff0179"
integrity sha512-zfZ7czMTQ5/DyZeMXCm1nTWEclIY5+Iuc8D3CZK2Zl64La//mjHz9Rjf+NlAE70bESzYtBNlItATN/lWgkowaQ==

"@rescui/colors@^0.2.10":
version "0.2.10"
Expand Down Expand Up @@ -1941,7 +1941,7 @@

"@rescui/typography@^0.11.0":
version "0.11.0"
resolved "https://registry.npmjs.org/@rescui/typography/-/typography-0.11.0.tgz"
resolved "https://registry.yarnpkg.com/@rescui/typography/-/typography-0.11.0.tgz#8a348121ea3bb18249f6af18628426b04a3ee6d7"
integrity sha512-IT8pXLkFQs7RsJ0eWWsCVz11HuJn1dQ78Lti2mz8q6NLbvb8TS+dlh53UzN5hLVISC9kFTRtyK2IblMIDd0zpA==
dependencies:
"@babel/runtime-corejs3" "^7.14.0"
Expand Down