feat: add responsive professional information layer

This commit is contained in:
2026-09-02 01:35:32 +03:00
parent f3ca9d4ade
commit 857292d2b0
11 changed files with 637 additions and 6 deletions

View File

@@ -1,10 +1,17 @@
<script setup lang="ts">
import AppShell from './components/AppShell.vue';
import type { ProductionInputs } from './content/types';
import './styles/tokens.css';
import './styles/base.css';
import './styles/layout.css';
import './styles/components.css';
import './styles/print.css';
defineProps<{ inputs: Readonly<ProductionInputs> }>();
</script>
<template>
<AppShell :content="inputs.content" />
<div class="portfolio-shell">
<AppShell :content="inputs.content" />
</div>
</template>

View File

@@ -31,13 +31,13 @@ const canonicalNavigation = computed(() => canonicalSectionIds.map((id) => ({ id
<template>
<a class="skip-link" href="#portfolio-content">Skip to portfolio content</a>
<header>
<header class="site-header">
<h1>{{ content.identity.name }}</h1>
<p>{{ content.identity.role }}</p>
<SiteNavigation :items="canonicalNavigation" />
</header>
<main id="portfolio-content">
<main id="portfolio-content" class="portfolio-main">
<PortfolioSection id="start" :index="1" :eyebrow="sectionLabel('start').toUpperCase()" :heading="sectionLabel('start').toUpperCase()">
<p>{{ content.identity.valueProposition }}</p>
</PortfolioSection>

View File

@@ -8,8 +8,8 @@ defineProps<{
</script>
<template>
<section :id="id" :aria-labelledby="`${id}-heading`" :data-section-index="index">
<p>{{ String(index).padStart(2, '0') }} / {{ eyebrow }}</p>
<section :id="id" class="portfolio-section" :aria-labelledby="`${id}-heading`" :data-section-index="index">
<p class="route-label">{{ String(index).padStart(2, '0') }} / {{ eyebrow }}</p>
<h2 :id="`${id}-heading`">{{ heading }}</h2>
<slot />
</section>

View File

@@ -5,7 +5,7 @@ defineProps<{ items: ReadonlyArray<{ id: SectionId; label: string }> }>();
</script>
<template>
<nav aria-label="Portfolio sections">
<nav class="site-navigation" aria-label="Portfolio sections">
<ul>
<li v-for="item in items" :key="item.id">
<a :href="`#${item.id}`">{{ item.label }}</a>

121
src/styles/base.css Normal file
View File

@@ -0,0 +1,121 @@
* {
box-sizing: border-box;
}
html {
min-width: 0;
background: var(--canvas);
color: var(--text);
font-family: var(--sans);
font-size: 100%;
line-height: 1.55;
scroll-behavior: auto;
text-size-adjust: 100%;
}
body {
min-width: 0;
min-height: 100vh;
margin: 0;
overflow-wrap: anywhere;
background: var(--canvas);
}
button,
input,
textarea,
select {
font: inherit;
}
a {
color: var(--cyan);
overflow-wrap: anywhere;
text-decoration-color: color-mix(in srgb, currentcolor 55%, transparent);
text-underline-offset: 0.18em;
}
a:hover {
color: var(--text);
text-decoration-color: currentcolor;
}
a:focus-visible,
button:focus-visible,
input:focus-visible,
textarea:focus-visible,
select:focus-visible,
[tabindex]:focus-visible {
outline: 3px solid var(--focus);
outline-offset: 3px;
}
::selection {
background: var(--amber);
color: #14100a;
}
h1,
h2,
h3,
p,
dl,
ul,
ol,
figure {
margin-block: 0;
}
h1,
h2,
h3 {
color: var(--text);
font-weight: 680;
letter-spacing: -0.025em;
line-height: 1.08;
text-wrap: balance;
}
p,
li,
dd,
dt,
a {
max-inline-size: 72ch;
}
svg,
img,
video,
canvas {
display: block;
max-inline-size: 100%;
}
.skip-link {
position: fixed;
z-index: 10;
inset: var(--space-2) auto auto var(--space-2);
min-block-size: 44px;
padding: var(--space-1) var(--space-2);
transform: translateY(-160%);
background: var(--amber);
color: #171006;
font-weight: 700;
transition: transform var(--transition);
}
.skip-link:focus {
transform: translateY(0);
}
@media (prefers-reduced-motion: reduce) {
*,
*::before,
*::after {
scroll-behavior: auto !important;
transition-duration: 0.001s !important;
animation-duration: 0.001s !important;
animation-iteration-count: 1 !important;
}
}

131
src/styles/components.css Normal file
View File

@@ -0,0 +1,131 @@
.route-label {
color: var(--cyan);
font-family: var(--mono);
font-size: var(--step--1);
font-weight: 650;
letter-spacing: 0.08em;
line-height: 1.3;
}
.portfolio-section > h2 {
margin-block: var(--space-1) var(--space-2);
font-size: var(--step-2);
}
.portfolio-section > p:not(.route-label),
.portfolio-section article > p,
.portfolio-section dd {
color: var(--text-muted);
}
.portfolio-section > * + *,
.portfolio-section article > * + *,
.portfolio-section li + li {
margin-block-start: var(--space-2);
}
.portfolio-section article {
min-width: 0;
margin-block-start: var(--space-3);
padding-block-start: var(--space-3);
border-block-start: 1px solid var(--line);
}
.portfolio-section article h3,
.portfolio-section section h3 {
font-size: var(--step-1);
}
.portfolio-section ul,
.portfolio-section ol {
padding-inline-start: 1.2rem;
}
.portfolio-section dl {
display: grid;
gap: var(--space-1) var(--space-2);
margin-block-start: var(--space-3);
}
.portfolio-section dt {
color: var(--cyan);
font-family: var(--mono);
font-size: var(--step--1);
font-weight: 650;
letter-spacing: 0.04em;
}
.portfolio-section dd {
min-width: 0;
margin-inline-start: 0;
}
.portfolio-section figure {
min-width: 0;
margin-block-start: var(--space-3);
padding: var(--space-2);
border: 1px solid var(--line);
background: var(--surface-raised);
}
.portfolio-section figcaption {
margin-block-start: var(--space-1);
color: var(--text-muted);
font-size: var(--step--1);
}
.portfolio-section svg {
inline-size: 100%;
block-size: auto;
}
.portfolio-section svg rect {
fill: #172432;
stroke: var(--cyan);
stroke-width: 2;
}
.portfolio-section svg text {
fill: var(--text);
font-family: var(--sans);
font-size: 19px;
}
.portfolio-section > div {
display: grid;
gap: var(--space-3);
margin-block-start: var(--space-3);
}
.portfolio-section > div > section {
min-width: 0;
padding-block-start: var(--space-2);
border-block-start: 1px solid var(--line);
}
.portfolio-section > div > section ul {
display: flex;
flex-wrap: wrap;
gap: var(--space-1);
margin-block-start: var(--space-2);
padding: 0;
list-style: none;
}
.portfolio-section > div > section li {
display: inline-flex;
gap: 0.45rem;
align-items: center;
min-block-size: 2rem;
padding: 0.25rem 0.55rem;
border: 1px solid var(--line);
color: var(--text-muted);
font-size: var(--step--1);
}
.portfolio-section > div > section li span[aria-hidden='true'] {
inline-size: 0.5rem;
block-size: 0.5rem;
border: 1px solid var(--cyan);
border-radius: 50%;
}

127
src/styles/layout.css Normal file
View File

@@ -0,0 +1,127 @@
.portfolio-shell {
min-width: 0;
}
.site-header {
display: grid;
gap: var(--space-2);
min-width: 0;
padding: var(--space-4) 4vw var(--space-3);
border-block-end: var(--panel-border);
background: #0b111b;
}
.site-header h1 {
font-size: var(--step-3);
}
.site-header > p:not(.route-label) {
color: var(--text-muted);
font-size: var(--step-1);
}
.site-navigation {
margin-block-start: var(--space-1);
}
.site-navigation ul {
display: flex;
flex-wrap: wrap;
gap: 0.25rem;
max-inline-size: 78rem;
margin: 0;
padding: 0;
list-style: none;
}
.site-navigation a {
display: inline-flex;
align-items: center;
min-block-size: 44px;
padding-inline: 0.7rem;
color: var(--text-muted);
font-family: var(--mono);
font-size: var(--step--1);
letter-spacing: 0.04em;
text-decoration: none;
transition: color var(--transition), background-color var(--transition);
}
.site-navigation a:hover,
.site-navigation a:focus-visible {
background: var(--cyan-deep);
color: var(--text);
}
.portfolio-main {
min-width: 0;
padding-block: var(--space-4) var(--space-5);
}
.portfolio-section {
width: calc(100% - 2rem);
min-width: 0;
margin: 0 1rem var(--space-4);
padding: var(--space-3);
scroll-margin-block: var(--space-3);
border: var(--panel-border);
border-radius: 2px;
background: var(--surface);
box-shadow: var(--panel-shadow);
transition: border-color var(--transition), background-color var(--transition);
}
.portfolio-section:hover {
border-color: var(--line-strong);
}
.site-footer,
.portfolio-section footer {
display: grid;
gap: var(--space-2);
min-width: 0;
}
.site-footer {
padding: var(--space-4) 4vw;
border-block-start: var(--panel-border);
background: #0b111b;
}
@media (min-width: 768px) {
.site-header {
padding-inline: 5vw;
}
.portfolio-section {
width: calc(50% - 3rem);
margin-inline: 2rem 0;
}
.portfolio-section:nth-child(even) {
margin-inline-start: calc(50% + 1rem);
}
.site-footer {
padding-inline: 5vw;
}
}
@media (min-width: 1280px) {
.site-header {
padding-inline: 4vw;
}
.portfolio-section {
width: 42%;
margin-inline: 4% 0;
}
.portfolio-section:nth-child(even) {
margin-inline-start: 54%;
}
.site-footer {
padding-inline: 4vw;
}
}

61
src/styles/print.css Normal file
View File

@@ -0,0 +1,61 @@
@media print {
:root {
color-scheme: light;
--text: #121820;
--text-muted: #374151;
--cyan: #0e5c72;
--surface: #fff;
--line: #8b98a5;
}
html,
body {
background: #fff;
color: var(--text);
}
.skip-link,
.site-navigation,
[data-decorative-world],
[data-scene-control] {
display: none !important;
}
.portfolio-shell,
.site-header,
.portfolio-main,
.site-footer {
display: block;
padding: 0;
border: 0;
background: transparent;
}
.site-header {
margin-block-end: 1.5rem;
}
.portfolio-section {
width: 100%;
margin: 0 0 1.25rem;
padding: 0 0 1.25rem;
break-inside: avoid;
border: 0;
border-block-end: 1px solid var(--line);
border-radius: 0;
background: transparent;
box-shadow: none;
}
a {
color: inherit;
text-decoration: underline;
}
a[href^='http']::after,
a[href^='mailto:']::after {
content: ' (' attr(href) ')';
font-size: 0.85em;
overflow-wrap: anywhere;
}
}

29
src/styles/tokens.css Normal file
View File

@@ -0,0 +1,29 @@
:root {
color-scheme: dark;
--canvas: #080d16;
--surface: #101722;
--surface-raised: #141e2b;
--line: #314154;
--line-strong: #4a6275;
--text: #f2f4f5;
--text-muted: #b5c0c9;
--cyan: #80d9e6;
--cyan-deep: #163b48;
--amber: #f5b752;
--focus: #ffd17a;
--sans: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
--mono: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, monospace;
--step--1: clamp(0.82rem, 0.78rem + 0.14vw, 0.92rem);
--step-0: clamp(1rem, 0.94rem + 0.24vw, 1.15rem);
--step-1: clamp(1.3rem, 1.14rem + 0.68vw, 1.75rem);
--step-2: clamp(1.9rem, 1.5rem + 1.35vw, 2.85rem);
--step-3: clamp(2.45rem, 1.82rem + 2.25vw, 4rem);
--space-1: clamp(0.5rem, 0.45rem + 0.2vw, 0.68rem);
--space-2: clamp(0.85rem, 0.75rem + 0.38vw, 1.15rem);
--space-3: clamp(1.3rem, 1.1rem + 0.7vw, 1.9rem);
--space-4: clamp(2rem, 1.65rem + 1.1vw, 3rem);
--space-5: clamp(3.25rem, 2.5rem + 2.6vw, 6rem);
--panel-border: 1px solid var(--line);
--panel-shadow: 0 14px 34px rgb(0 0 0 / 18%);
--transition: 160ms ease;
}