The Mediovsky design system.
Every brand, type, color, spacing and component decision in Mediovsky. It is the oracle: if a component is not documented here, it either belongs to one route (see Site chrome) or it should not exist. When you add or change one, update this page in the same pass.
The page reads in two halves. Unbadged sections are what the site is built from: they load the real stylesheet, so what you see here is what a reader gets, and a change to the stylesheet reaches all 52 articles, every other page on the site and this page at once.
Sections badged Proposal are the library of directions: components drawn for where this could go if it ever became a CMS, some of them older than Mediovsky. They are real design work and they stay here to be drawn from. What they are not is available: the site ships no CSS for any of them, so adopting one starts with moving its CSS into site.css. The build enforces that, and will fail on a page that uses one before it has been promoted.
Brand & logo.
The Mediovsky wordmark is non-negotiable. Indigo dot accent (#3C3FDE) is the only brand color. Never recolor, distort, or rebuild the mark.
Logo variants
Sizes & clear space
Minimum height 24px. Always leave clear space equal to the height of the “M” on all sides.
Color.
Indigo accent #3C3FDE is the only brand color. Everything else is neutral. Dark mode uses lifted indigo #7A7DFF for contrast.
Brand accent
Light theme tokens
| Token | Value | Use |
|---|---|---|
--bg | #FBFAF7 | Page background, warm white |
--fg | #15141A | Primary text, headings |
--fg-2 | #3A3944 | Secondary text, body copy |
--fg-3 | #76757F | Tertiary, meta, captions |
--rule | #E6E4DC | Borders, dividers |
--accent | #3C3FDE | Brand accent, links, kickers |
--accent-soft | #EEEEFF | Subtle accent backgrounds, badges |
Dark theme tokens
| Token | Value | Use |
|---|---|---|
--bg | #0E0D12 | Page background |
--fg | #EBEAF0 | Primary text |
--fg-2 | #B8B7C2 | Secondary text |
--rule | #23222B | Borders |
--accent | #7A7DFF | Accent (lifted for contrast) |
Semantic colors
Use sparingly. Only for status feedback. Avoid in editorial content (indigo accent is enough).
Typography.
One typeface (Inter) used at varied weights and sizes. Editorial mark: light weight (300) for display, regular (400) for body, indigo italics for emphasis (set via <em> with font-style:normal).
Type scale
Leading scale
a single large glyph or figure where the ratio crops the line box to the letter rather than setting a rhythm: a drop cap, a stat numeral
Control sizes
| Token | Value | When to reach for it |
|---|---|---|
--control-sm | 28px | inside a table row, a card footer, a filter bar, a tag list |
--control-md | 36px | the default: forms, navigation, pagination, inline calls to action |
--control-lg | 48px | one primary call to action per page, and the only step that clears 44x44 |
Reading measures, in characters
| Token | Value | When to reach for it |
|---|---|---|
--measure-narrow | 44ch | prose that shares its space with something else: a caption, a sidenote, a stat label, a form’s helper text, one column of a two-up grid |
--measure-body | 62ch | the default, and what any ordinary run of prose gets |
--measure-wide | 76ch | prose set under or inside a block wider than the prose column: a caption beneath a full-bleed figure, a note under a wide table |
Container widths, in pixels
| Token | Value | When to reach for it |
|---|---|---|
--width-form | 560px | anything a reader types into: a contact form, the newsletter row, the search box |
--width-column | 740px | one column of prose: the article body, and the static pages that used to disagree with it |
--width-bleed | 1120px | a block that escapes the prose column but stays inside the page gutter: a full-bleed figure, an interstitial |
--width-page | 1240px | the page shell, every section.wrap on the site |
Breakpoints
| Token | Value | When to reach for it |
|---|---|---|
--bp-sm | 640px | the phone. One column, and the page gutter narrows from 32px to 20px |
--bp-md | 900px | the layout. Two columns become one: the article and its rail, every static page, the nav |
--bp-lg | 1100px | wide chrome. The page head, the listing filter bar and the pagination give up their horizontal arrangement |
--bp-page | 1303px | the page shell gains its gutter. DERIVED, not chosen: --width-page + twice the 32px gutter, minus one |
Border widths
| Token | Value | When to reach for it |
|---|---|---|
--border-1 | 1px | the hairline. Every rule that separates one thing from another: a card edge, a table row, a form field, a divider, an underline |
--border-2 | 2px | the heavier line, where the border itself carries meaning: a bar marking a quote or a summary, a chapter rule, a focus ring, a stroke drawn from two borders |
Spacing & grid.
8-point spacing system. 8-column grid with 32px gutter. 1240px max container width.
8-point spacing scale
Grid
Press G on any page to toggle live grid overlay.
Radius & shadow.
Four steps, plus a pill and a circle, read from the token block in public/site.css rather than written here. A corner tracks the size of the box it is on. Shadow only on sticky and floating elements.
--radius-sm
4px
a small mark: inline code, a kbd, a badge, a chart bar, a thumbnail, an image inside a card
--radius-md
8px
the default box, and every cornered control: a card, a panel, an alert, a form field, a nav item, a callout
--radius-lg
12px
a large block: a figure, a fact box, a stat panel, an accordion, a code block
--radius-xl
16px
a full-width feature: a calculator, a scorer, a quiz, a lead magnet, a hero, an interstitial
--radius-pill
999px
fully rounded on the short axis. Not a size: it draws half the box height, so the control scale decides what a reader sees
--radius-circle
50%
a box that is square and meant to read as round: an avatar, a step number, a bullet, an icon button
Shadows
none · sticky-nav (1) · floating (2) · modal/menu (3)
Motion.
Three steps, and the step is chosen by what moves, not by how long it ought to feel. Reach for the one that describes the movement and the tempo takes care of itself.
| Token | Value | Use | Example |
|---|---|---|---|
--motion-fast | 120ms | Feedback under the pointer, where nothing travels | A hover fill, a press, a border lighting up |
--motion-base | 150ms | The default: any state change on something already on screen | Link hover, the sticky nav’s shadow, the theme repaint |
--motion-slow | 350ms | Something entering or leaving the page | Back to top, a toast, the theme icon’s turn |
none | none | Focus rings | Accessibility, instant |
Two kinds of movement are deliberately not on this scale, and both say so where they are written with motion-ok:. A value drawing itself (a meter filling, a ring closing, a bar growing) runs from 250ms to 1.1s, because its length is tuned to the distance drawn: that is a fact about the data, not about the tempo of the interface. A loop (a spinner, a pulse) has a rhythm rather than a duration. Everything else is one of the three steps above, and F263 fails the build if it is not. Under prefers-reduced-motion the whole scale is switched off.
Buttons.
Two shapes only: .pill is the outlined default, .pill--solid the filled call to action. Both hover to accent blue. Do not hand-roll a third: the filled one existed as four slightly different inline copies until August 2026.
Primary actions
Icon buttons
Forms.
Chips & tags.
Cards.
List items.
Text-forward article list used on homepage, category, author, latest. Date on left, content middle, thumbnail right (8-col).
Standard list item from the article list.
Deck paragraph in 15px sans, max 62ch. Sits below title with comfortable line height.
Pagination.
Tabs.
Active tab content area.
Alerts & toasts.
Your enhanced conversions upload rate is below 90%. Go fix that first.
Article scheduled for publication on May 28.
Edge cache hasn’t refreshed in 48 hours. Consider manual purge.
Connection to media server lost. Article saved as draft.
Toast
Skeletons & empty states.
Empty state
No results found
Try a different keyword or browse the latest articles.
Iconography.
Stroke icons, 1.6 stroke width, 14-20px. Source: Lucide-style custom. Always currentColor.
Badges & labels.
Status pills and disclosure labels. Use to flag content type (Featured, Sponsored), status (Live, Beta, Draft), or recency (New, Updated).
Editorial labels
Status pills
In context: article card
Card with sponsored disclosure label.
Marquee ticker.
Continuous horizontal scrolling band. Pure CSS animation, pausable on hover. Use sparingly. For breaking headlines or issue announcements.
Default: dark band
Accent variant
Back to top.
Floating button bottom-right of viewport. Appears after scrolling past 600px. Site-wide. Already active on every page.
Theme toggle morph.
Sun and moon icons cross-fade with a 180° rotation when switching themes. Both icons in DOM, opacity flipped by html[data-theme] attribute.
Note: this demo button shares the global toggle logic (any element with #theme).
Avatars.
Five sizes, with optional status dot. Use initials as default; image when available. Stack overlapping for multi-author or reader lists.
Sizes
With status
Stack (multi-author)
Tooltips.
Hover bubble with arrow. Four positions. Use sparingly. Most labels should be visible without hover.
Dropdowns.
Custom select with option descriptions. Use when native <select> can’t carry enough information.
Search input.
Standalone input with leading icon and trailing clear button. For pages without the ⌘K overlay.
Toast queue.
Stacked notifications, auto-dismiss after 5s. Use for transient feedback only. Not for errors that need action.
Notification bell.
Bell icon with unread count badge. Dropdown shows latest notifications.
Count-up & sparkline.
Animated numerics for stats. Use on first view, not in re-renders.
Counter
Sparkline in stat card
Loading button.
Replaces text with spinner during async operation. Pointer events disabled.
Progress stepper.
Multi-step form indicator. Done state is solid, active is outlined, upcoming is muted.
Confetti.
Celebration animation after positive actions (subscribed, completed survey). CSS only. No canvas needed for this scale.
Joined ✓
Welcome to Mediovsky. Issue №218 ships Thursday.
Newsletter sticky bar.
Bottom-of-viewport CTA that appears after the user scrolls 60% of an article. Dismissible.
⌘K hint pill.
Subtle floating pill on first visit, hints at the keyboard shortcut. Dismiss after one use.
Reading rail.
Floating vertical column beside an article: share, save, TOC, font size. Hidden < 1300px.
circle →
Section dividers.
Five variants for article breaks. Use line for default, ornament for editorial pauses, stars for chapter-like breaks.
Line (default)
Ornament with label
Stars
Numbered
Accent block
Quote variants.
Five quote styles for different editorial weight. Big for pull-out, small for inline, portrait for testimonials, cols for long.
Big (pull quote)
AI doesn’t fail loudly. It optimizes toward your bad data.
Small (inline)
The better the algorithm gets, the worse bad data hurts.
With portrait
“Most marketing frameworks read better than they perform.”
Mikołaj Salecki, editorTwo columns
Performance Max and Meta Advantage+ optimize confidently toward whatever signal you send them. Messy CRM data and delayed conversions silently corrupt targeting models at scale, and the platforms have no incentive to tell you. The better the algorithm gets, the worse bad data hurts.
Footnote & sidenote.
Footnotes for citation, sidenotes for tangent. Both keep article body clean.
Footnote (bottom-of-article)
Sidenote (margin)
Reading progress circle.
Circular SVG progress, replaces linear progress bar in compact contexts (reading rail, mini player).
Highlight on hover.
Subtle hover effect on key phrases. The underline grows into a full highlight on hover. Use for callouts inline.
Performance Max optimizes confidently toward whatever signal you send. Messy CRM data and delayed conversions silently corrupt targeting models, and the platforms won’t tell you it’s happening.
Bookmark moment.
Reader selects a passage; a floating action lets them save the quote with the article reference.
“AI doesn’t fail loudly. It optimizes toward your bad data, quietly, on schedule.”
Caption styles.
Four caption variants for images: default, italic editorial, source attribution, rich (with label).
Page transitions.
Four built-in transitions between pages. Use sparingly. Default is fade only.
Glitch on hover.
RGB-shift glitch effect. Use with extreme caution. Only for one-off emphasis, never on body text.
Sharp insights, smarter growth.
Accordion.
Native <details> elements styled as a card. Use for FAQ, methodology notes, advanced settings, anything secondary that shouldn’t take vertical space by default.
Card variant
01 What is Mediovsky?
An independent journal of media, tech, and AI business, started in 2026. Each week brings a cover essay anchored by shorter pieces: how-to guides, data analyses, opinion, and the occasional review.
Written for professionals who run digital. Read more about us.
02 How often do you publish?
A cover essay each week, anchored by shorter pieces, plus extra coverage during major industry events.
03 Do you accept guest pitches?
Yes. From working operators only. See our editorial line for what we look for. Two-week turnaround.
04 Can I republish your articles?
Quote freely with attribution (up to ~150 words). Full republication requires permission. Email [email address].
Plain variant (no card)
A Methodology for our data pieces
First-party data from accounts we audit, vendor-published primary sources, peer-reviewed research, and named industry surveys. See full methodology.
B How we handle corrections
Public corrections posted on the original article, with the previous wording struck through. View all corrections.
Built on native <details> for accessibility: works without JS, keyboard-navigable, screen-reader friendly.
Dialog: basic.
Centered modal with title, body, action footer. Use in admin for short single-purpose flows: rename, change status, edit one field. ~520px wide.
Dialog: form.
For “Add user”, “+ New token”, “+ Manual backup”, “+ New redirect”: multi-field form with submit / cancel.
Dialog: danger.
Destructive irreversible actions: delete article, restore backup. Red icon header, typed confirmation for the worst.
Confirm.
Smallest possible decision. One line, two buttons. Use for unsubscribe, archive, revoke session.
Drawer.
Right slide-in, 480px. Subscriber details, audit log entry, revision view.
Sheet (mobile).
Bottom slide-up. Mobile fallback for drawers and dialogs, or for filter / sort.
Popover menu.
Anchored dropdown for row ⋯ menus, “Export ▾” buttons.
Inline edit.
Edit in place. Click a value in a table row → input. Use for slug, tag name, single-field changes.
Page banner.
Top-of-page status. Persistent until dismissed.
Decision matrix.
Which UI for which action.
| Use | UI | Width |
|---|---|---|
| Single binary decision | Confirm | 380px |
| 1-3 fields form | Dialog · form | 520px |
| 4+ fields | Dialog · form | 520-640px |
| Detailed view-only | Drawer | 480px |
| Destructive irreversible | Dialog · danger | 520px |
| Row ⋯ menu | Popover | 240px |
| Quick field change | Inline edit | cell |
| Filter / sort on mobile | Sheet | full |
| Status / system message | Banner | full |
| Transient confirmation | Toast | auto |
Code blocks.
A plain pre inside the body. data-label prints the language and the file above it; article-widgets.js adds the copy button. Highlighting is four hand-applied spans, not a library: .c comment, .k keyword, .s string, .n number. Code samples are the one place that keeps straight quotes, because a reader who copies a snippet with curly ones gets something that does not parse.
-- everything that spent and never converted SELECT search_term_view.search_term, metrics.cost_micros FROM search_term_view WHERE metrics.conversions = 0 AND segments.date DURING LAST_30_DAYS
Article page anatomy.
Everything below this point is styled by public/article.css, which this page loads so the demos are the real components and not a redrawing of them. The chrome in this first table is rendered by the page template from frontmatter and content collections. An author never writes it.
| Class | What it is | Rendered by |
|---|---|---|
.article-head | the whole masthead block above the body | template |
.breadcrumb | Home / category / title trail, with .sep and .cat | template |
.kicker | the accent line above the headline: the category, or the cover-story flag | template |
.deck | the standfirst under the headline | frontmatter excerpt |
.meta-row | byline row: .author, .av, .nm, .rl, .details, .share | template |
.cover | the lead image, with .cover__img and .cover__caption (.credit) | frontmatter image |
.body-wrap · .body-grid · .body | the 8-column grid and the 740px reading column inside it | template |
.article-faq | the FAQ block, with .faq and .faq__item inside | frontmatter faq |
.sources | the numbered source list: .src-num, .src-body, .src-org, .src-title, .src-meta, .src-link | frontmatter sources |
.tags | the tag row under the body | frontmatter tags |
.end-share | the share row that closes the article | template |
.author-bio | the author card | author collection |
.prevnext | previous / next article links | template |
.related-sec · .related · .rcard | the read-next grid and its cards | template |
A body element that carries a citation marks it with a.ref pointing at #src-N. Every marker must resolve to a source in the list, and every source must be cited at least once. tests/frontend.mjs fails the build on either.
Data displays.
Static figures. Every number in one of these must be traced to a primary source in the figure ledger before the piece ships. docs/AUTHORING.md section 6a says when to reach for each.
Big-number apla · .statfeature
One hero figure on a brand panel. The .alt modifier swaps the flat blue for a gradient. data-count on .n animates the number up on scroll.
Stat grid · .statgrid > .stat
Two to four quiet figure cards. Each .stat is a .n and an .l.
Keystat · .keystat
One large figure inline, lighter than an apla and safe to use more than once in a piece.
Trajectory · .trajectory
A value across periods. Bars animate to data-w percent on scroll; .is-peak accents the row that matters. .trajectory__cap carries the source.
US CTV ad spend, eMarketer forecast. 2029 is a projection.
Ranked · ol.ranked
A numbered leaderboard. The number is a CSS counter, so the markup carries no digits: each li is a .rk-name (with an optional small) and a .rk-val.
- Amazon AdsRetail media, US net spend$56.1bn
- Walmart ConnectRetail media, US net spend$5.6bn
- InstacartRetail media, US net spend$1.1bn
Share bar · .sharebar
One stacked bar for a split. Widths are inline percentages on .s1 to .s4 and must add to 100; the key repeats the same classes as swatches.
Pros and cons · .proscons
Two columns under one rule: .proscons__col--pro and --con, each a p.w-title and a list.
Keep human
- Hero images where a face carries the endorsement.
- Founder, team and customer testimonials.
Use AI freely
- Backgrounds and textures with no synthetic person in them.
- Variant generation: sizes, crops, seasonal restyles.
Structural & narrative.
Devices that shape how a piece reads rather than what it counts. Section 6b of the handbook has the editorial rule for each; this is what they look like.
TL;DR · .tldr
Required at the top of every article: 5 to 10 bullets, scaled to length. Carries its own .tldr__label.
- Agents are production-ready for closed-loop, reversible tasks.
- 2026 is assisted autonomy: agents act within hard rules, humans approve the big moves.
- The risk is not the agent. It is an agent optimizing fast toward the wrong goal.
Spotlight · .spotlight
An accent panel: a .k eyebrow, a p.w-title claim, then the argument.
Your traffic got smaller and more valuable at the same time.
If you report organic sessions, AI summaries read as a slow catastrophe. If you report revenue per visit, the same period reads as a purge of traffic that was never going to buy.
Factbox · .factbox
The quieter, bordered cousin of the spotlight: an aside or a definition.
Give to the agent: reversible, measurable execution: bids, budgets, rotation, QA.
Keep for the human: the objectives, the guardrails, and any move that cannot be cleanly undone.
Compare · .compare
Two readings under one rule, not two cards. .win marks the side that carries the point. The .shift variant uses .then, .arrow and .now instead.
~3.7x ROAS. You control audiences, placements, budgets. And you do the work.
~4.5x ROAS, ~32% lower CPA. The AI controls almost everything. You control the inputs.
Flow · .flow
The light vertical step list: a numbered circle, a title, one paragraph. Three to five steps.
Reactive
Capability exists because someone bought it once. No owner, no review cadence, no roadmap.
Aware
An owner exists on the org chart. Procurement happened. Adoption did not.
Capable
Workflows are documented, outputs are measured, and no single person carries the pillar.
Process · .process
The richer step-by-step: a connecting spine and a .tag per step. Use it when the method itself is the point.
AI, the capability layer
Where intelligence enters the work, through prompts, models and agents. Most teams operate at assistive and call it agentic.
CapabilityDigital, the platform
The floor the rest sits on: strategy, channels and data infrastructure as one engine, or a folder of subscriptions that share a logo.
PlatformChecklist · ul.checklist
Scannable takeaways with ticks. Body links and a.ref markers work inside it.
- Qualify first: reach 25+ weekly conversions with clean signal before the campaign gets majority budget.
- Cap existing customers at 25-30% so the algorithm hunts new demand instead of re-buying your own.
- Judge it on incremental ROAS after the learning phase, not the blended headline.
Timeline · .timeline
Dated milestones. .is-now marks the current one.
Accordion · .accordion
Native details, no JavaScript: a summary and an .accordion__body. For optional depth the reader can skip.
What counts as a primary source?
The document that contains the figure, not an article reporting it. A vendor blog citing a study is not the study.
Does a paywalled source count?
Yes, if you opened it and quoted the figure verbatim into the ledger.
Pullquote · .pullquote, and a sourced blockquote
The pullquote is a sharp line from the piece itself; the blockquote is somebody else’s words, with a cite.
AI does not fail loudly. It optimizes toward your bad data, quietly, on schedule.Mikolaj Salecki
Callout · .callout
A short aside in the flow of the argument: a strong label, then the point. Lighter than a factbox, and it does not break the column.
Glossary term · span.term
An inline jargon term that reveals its definition on hover or focus. Needs data-tip and tabindex="0", so it works on a keyboard. .term--flip anchors the bubble to the right edge when the term sits near the end of a line.
Most teams still report on ROAS alone, which hides whether the revenue was incremental.
Breaking the column.
The body sits in a 740px column. These devices deliberately escape it. On an article they run to 92vw, capped at 1120px; in the boxes below they are clipped to the demo, so judge the treatment, not the width. One or two per long piece, never back to back.
Interstitial · .interstitial
A full-bleed statement band that stops the reader between sections: .k eyebrow, .big line, .src attribution. .is-dark swaps the blue for near-black.
Only 51 cents of every programmatic dollar reaches the page.
ISBA and PwC Programmatic Supply Chain StudyChapter divider · h2.chapter
A numbered break announcing a new major section. It is a real h2, so it lands in the outline.
04Then test the label, do not fear it
Bleed wrapper · .bleed
Makes any block go wide without the interstitial treatment: wrap a table or a chart in it. .bleed--wide caps at 960px instead of 1120px.
A table, a chart, a factbox. The wrapper only changes the width.
Lead generation.
A piece carries either a lead magnet or a content upgrade, never both, and only when the offer is real. Whatever the CTA points at must exist before the article ships.
Lead magnet · .leadmagnet
The title is p.w-title, not a heading: article.css styles .leadmagnet .w-title and nothing else, and a promo block has no business in the document outline. .leadmagnet--cover adds a .leadmagnet__cover image on the left.
The 2026 supply-path audit checklist
Every fee layer, gate and reconciliation step, on one page you can take into a planning meeting.
- The four fee layers and what each one hides
- The ads.txt and take-rate clauses to demand
Content upgrade · a.upgrade
The lighter inline cousin: one line mid-article, a .upgrade__tag, a .upgrade__txt and a .upgrade__go.
Interactive tools.
Driven by public/article-widgets.js, which this page loads. Roughly one article in three gets one, rotated across the palette. Two rules the build enforces: the production CSP has no unsafe-eval, so a calculator formula is parsed, never evaluated; and every widget must work with a keyboard.
Donut · .donut
One share as a ring. JS draws and animates the SVG from data-pct; the caption lives in .donut__t.
Bar chart · .barchart
Categorical, not time. Bars animate to data-w percent on scroll; .is-peak accents one row.
Line chart · .linechart
A trend over time. The whole chart is one empty div: JS reads data-points, data-labels and data-cap and draws the SVG. Points and labels must be real and sourced.
Toggle · .toggle
Tabbed panels for a which-of-these-are-you branch. The tab strip is a real radio-style group, arrow keys included.
Use the official MCP server. Reporting is read-only work, so the worst case is a wrong answer you can check, not a wrong charge you cannot undo.
Use a script. If the logic is fixed and runs on a clock, you do not want an agent reasoning about it every time.
Calculator · .calc
A named formula in data-formula over input[data-field] values, with data-format and data-dec shaping the output. Every field needs a real label and id.
Blended ROAS to a rough incremental floor
Strip out the existing-customer conversions that would likely have happened anyway. What is left is a rough floor for incremental ROAS on new customers.
A directional estimate only. Validate with a geo or audience holdout before you act on it.
Scorer · .scorer
Weighted checkboxes that must sum to 100. data-gate and data-critical exist so a half-configured stack cannot read as Ready on total alone.
Can your warehouse do the CDP’s job?
Quiz · .quiz
Each option carries a data-score; the total reveals the highest .quiz__band[data-min] it clears. The bands must cover 0, or a reader who scores nothing sees nothing.
How often does this task run?
What happens if it runs wrong?
Leave it manual
Rare, judgment-heavy or expensive to get wrong. Automating it buys you little and risks a lot.
Automate it
Frequent, rule-based and cheap to undo. This is exactly the work a scheduled job should own.
Before / after and hotspots
Both need a real image pair or an image worth exploring, so there is no honest demo here. The markup, for when there is one:
<!-- draggable slider between two images --> <div class="beforeafter"> <img src="/img/after.webp" alt="..." width="1200" height="800"> <div class="beforeafter__before"><img src="/img/before.webp" alt="..." width="1200" height="800"></div> <span class="beforeafter__tag is-before">Before</span> <span class="beforeafter__tag is-after">After</span> <input class="beforeafter__range" type="range" min="0" max="100" value="50" aria-label="Reveal the after image"> <span class="beforeafter__handle"></span> </div> <!-- pins that reveal a note on click --> <div class="hotspots"> <img src="/img/screenshot.webp" alt="..." width="1200" height="800"> <button class="hotspots__pin" style="left:22%; top:38%" data-note="<b>Budget</b> is the field most teams set once and never revisit.">1</button> <div class="hotspots__note" hidden></div> </div>
Images & gallery.
Standard aspect ratios: 16:9 (cover, video), 16:10 (article thumbs), 1:1 (Editor’s picks), 4:5 (portrait), 21:9 (cinematic hero), 3:4 (issue cover).
Aspect ratios
Gallery (mosaic)
Audio & podcast.
Reserved for when we ship audio editions and the podcast. Compact player for inline use, full card for episode pages.
Inline audio player
Issue №001: audio edition (read by author)
Podcast episode card
What broke in performance marketing this quarter, and why nobody noticed.
Video.
Embeds.
Frames for YouTube, Vimeo, X/Twitter, Mastodon, GitHub gist, CodePen, Figma. Always 16:9 or auto.
Stats & KPIs.
Tables.
| Channel | Articles | Avg. read | Reads (30d) | Status |
|---|---|---|---|---|
| PPC | 38 | 10:24 | 8,412 | healthy |
| SEO | 31 | 9:18 | 6,205 | healthy |
| AI & Data | 42 | 11:02 | 11,304 | healthy |
| MarTech | 29 | 9:45 | 4,118 | slow growth |
| E-Commerce | 24 | 8:09 | 3,002 | needs attention |
Site chrome.
Page furniture that belongs to one route or to the shell around every route. Nothing here is a demo, because none of it is reusable: each one exists in exactly one place and this is the map of where.
| Class | What it is | Lives in |
|---|---|---|
.skip-link | the first thing in the tab order on every page: jump straight to the content | BaseLayout.astro |
.page-head | the title block a listing or a standalone page opens with | PageHeader.astro |
.pagenav | previous / next between pages of a listing | Pager.astro |
.pagination--bare | the pagination row without its box, for the homepage | index.astro |
.filterbar | the category filter above the latest listing | latest/[...page].astro |
.ar-card | the tall cover card in the homepage archive strip (__cover, __num, __date, __title, __meta) | index.astro |
.search-overlay | the full-screen search layer the shortcut opens | site.js |
.bell-wrap | the notification bell and its dropdown in the header | Bell.astro |
.saved-badge | the count on the reading-list icon; .is-saved is the saved state of a Save button | Nav.astro, site.js |
.err-hero · .err-content | the 404 page | 404.astro |
Anything genuinely reusable belongs above this section with a live demo. If you find yourself adding a second use of something listed here, that is the signal to promote it, not to copy it.
Voice & tone.
Editorial voice principles. Keep these in mind when writing UI copy, microcopy, and article body.
| Principle | Do | Don’t |
|---|---|---|
| Direct | “AI optimizes toward your bad data.” | “AI may potentially optimize...” |
| Specific | “55-80% of SERPs shifted in 12 days.” | “Lots of SERPs shifted.” |
| Practical | “Block 90 minutes on Monday...” | “Consider exploring options...” |
| Skeptical | “Most frameworks read better than they perform.” | “This framework will transform your business.” |
| American English | “optimization, behavior” | “optimisation, behaviour” |
Naming conventions.
| Type | Convention | Example |
|---|---|---|
| CSS classes | BEM-lite | .card · .card__title · .card.is-active |
| State classes | is-X / has-X | .is-open · .is-active · .has-error |
| JS hooks | data-* attrs | data-pane="A" · data-r="insight" |
| localStorage keys | mediovsky-X | mediovsky-saved · mediovsky-theme |
| Custom events | mediovsky:X-Y | mediovsky:saved-changed |
| IDs (unique) | camelCase | #savedBadge · #navDrawer |
| Pages | kebab-case.astro | style-guide.astro · [slug].astro |
| Components | PascalCase.astro | PageHeader.astro · ArticleListItem.astro |
| Article slugs | angle-specific, no year | ctv-budget-gap.md, not ctv-2026.md |
This page is the oracle: if a component is not here, it is either site chrome (the section above) or it should not exist. When you add or change one, update this page in the same pass. It loads article.css and article-widgets.js directly, so the article demos cannot drift from the real thing; the site-chrome demos use the global stylesheet for the same reason. Anything drawn with inline styles in a demo box is a mock and will go stale, which is how the editorial sections rotted before August 2026.