Article hero

Three variants of src/components/sections/Hero.astro — real component renders, fed by live post entries. The same component handles all three via variant + category.

Featured (homepage) — review featured post

Prop: variant="featured". Used by src/pages/index.astro for the lead post. Text-left + image-right grid, pills overlap the image top edge, the headline is a stretched link covering the whole block. Because this featured post is a review, it also picks up the .is-review treatment (radial mint glow + heavier grain).

Article — news style

BALDURS GATE 3 NYHETER 2026

Baldur's Gate 3 Act Two Expansion Mod: ny trailer og masse mer innhold

Baldur's Gate 3 Act Two Expansion Mod: ny trailer og masse mer innhold
Den nye områdene ser bra ut! / credit: SquallyDaBeanz, YouTube

Et år etter teaser-traileren deler SquallyDaBeanz masse nyheter: nye NPC-er, quests, 70+ items og en ny trailer.

Kay Tomas Bertheussen
8. april 2026

Prop: variant="article" with category in news, video, portfolio, first-impressions. Internal flag isNewsArticle triggers the flipped grid (image left, meta right). Full-width title + eyebrow render above the 2-col layout.

Article — review style

HYPER GAMES INTERVJU 2026

Mummi, mørke temaer og spillutvikling

Hyper Games-intervju: Mummitrollet, mørke temaer og norsk spillutvikling
Are på kontoret med mummihus | Kreditt: Hyper Games

Are Sundnes i Hyper Games snakker om Mummitrollet: Winter's Warmth, Tove Jansson, mørke temaer, kreativ frihet og livet som et norsk indiestudio med 13 folk og én hund.

Kay Tomas Bertheussen
24. april 2026
6 min. lesetid

Prop: variant="article" with category in review or interview. Adds .is-review which paints a radial-gradient glow and a grain overlay at var(--grain-opacity-hero) with mix-blend-mode: overlay. Rating badge anchors top-left of the image.

Rating dice (terningkast)

Reviews score on a 1–6 dice scale (Norwegian terningkast convention). The hero badge keeps its mint→white gradient square; the inner content is an SVG die from src/components/ui/Dice.astro. Pip colour comes from the article's pre-computed accent via getArticleAccent(slug) so each review's die echoes its hero image.

Die SVG fills a 1:1 container. Pips fixed at cx,cy on a 3×3 grid in a 0 0 24 24 viewBox. The face value determines pip layout per the standard die convention; color sets fill on each <circle>. A 6/6 (terningkast 6) is reserved for exceptional verdicts.

Badge shadow is intentionally tighter, darker and higher-opacity than a typical elevation shadow so it reads on the dark hero background — no glow, editorial not app-like. Applied via filter: drop-shadow() using these overridable tokens: --die-shadow-x (0), --die-shadow-y (4), --die-shadow-blur (12), --die-shadow-opacity (0.45), --die-shadow-color (0 0 0, RGB triplet).

On review cards, the same dice appears bottom-right of the image at 32px, translated 50% downward so half overlaps the image and half drops into the content area. Hidden on the compact variants (card--latest, card--mobile-compact) where badges are already suppressed.

Cards

Real <Card> renders (src/components/ui/Card.astro) fed by live post entries. Standard, top-pick, and first-impressions variants.

Pills are absolutely positioned and sit -10px above the image edge so they overlap. Title gradient uses linear-gradient(19deg, --color-text 0%, --color-accent 60%) text-clip. Hover lifts and tilts the whole card in 3D (translateY(-6px) rotateX(4deg) rotateY(-3deg)), bumps image saturation/brightness, and floats tag pills forward (translateZ(14px)) ahead of the title (translateZ(10px)). Disabled under prefers-reduced-motion: reduce.

Size variants

Cards default to a vertical layout. The homepage grid (HomepageGrid.astro) passes a size prop that scales the title/description and, for latest, switches the whole card to a horizontal thumb + headline row.

  • default — the standard vertical card above. Used on hubs and the /news, /review indexes.
  • feature — vertical, slightly smaller title. The homepage trio/stack default.
  • hero / spotlight / strip — vertical, progressively larger title; for the big homepage slots (8-col hero, 8-col banner, 12-col panoramic).
  • latest — horizontal: square thumb left with an accent-colored right border, headline right. Description, date, overlay pills and the 3D tilt are all dropped. This is the homepage LATEST / SISTE ticker. Live render below.

Mobile-compact rule (homepage news)

A mobile-only behavior, not a separate component — it reuses the horizontal anatomy of latest above.

The rule: on the homepage at max-width: 719px, after the first two big cards in document order, every news-category card collapses to the compact horizontal row (square thumb, accent line, headline). It keeps the News / Nyheter kicker rather than the LATEST label, and hides the description, date and overlay pills. HomepageGrid.astro walks the resolved layout, counts big cards (anything that isn’t the latest strip), and sets mobileCompact on qualifying news cards; Card.astro turns that into .card--mobile-compact, which only does anything below 720px. Desktop (≥720px) is unaffected — the same cards stay full vertical cards. Purpose: shorten the mobile scroll and break the wall-of-big-cards rhythm without changing what’s shown. Item count is identical; only the height of those news cards changes.

Homepage grid — what’s shown on the front page

The front page is data-driven from one file: src/content/homepage.ts. Edit that file to change what appears — no component changes needed. It is a list of rows; each row holds cells whose span values sum to 12 (a 12-column grid). homepageResolver.ts turns the config into resolved posts, then HomepageGrid.astro renders them.

Cell types

  • { type: 'card', span, content } — a single post card. Optional size (feature default), aspect (16:9 default), showDescription.
  • { type: 'stack', span, items: [...] } — a vertical column of cards in one cell; each item takes its own content + optional size/aspect/showDescription.
  • { type: 'news_list', span, content: { rule } } — the text-only news list (no images). The cheapest way to surface many items.

Content sources — how a slot is filled

  • Pin a specific post: content: { pin: 'the-slug', fallback?: 'showcase' | 'news' | 'any' }. If the slug isn’t found, the fallback chain fills the slot so the grid never renders a hole. Cross-language pins resolve via translationSlug.
  • Auto-fill by rule: content: { rule, count?, minDays?, maxDays? } where rule is newest_news, older_news (windowed by minDays/maxDays), or newest_any. Rules pull live from the collection, newest first.

A post is reserved the moment it’s placed, so the same article can never appear twice across the page — pins, rules and the news list all dedupe against each other automatically. To add items: append a row, add a card to a stack, or raise a news_list count. To resize: change span values (keep each row summing to 12). To swap a feature: change a cell’s pin slug.

Current layout (mobile order = top to bottom)

  • Row 1 — latest ticker (newest post, any category)
  • Row 2 — hero (8) + stack (4): pinned showcase + newest news
  • Row 3 — stack (4): two newest news + spotlight (8): pinned review
  • Row 4 — trio (4·4·4): news · pinned review · news
  • Row 5 — spotlight (8): pinned + news_list (4): older news
  • Row 6 — panoramic strip (12): pinned

On mobile every cell stacks full-width in this order, and the mobile-compact rule (see Cards above) collapses news cards past the first two big ones. On desktop the spans take over at ≥720px. Source: src/content/homepage.ts, src/lib/homepageResolver.ts, src/components/sections/HomepageGrid.astro.

Content components

Real renders of every MDX-usable component that appears in posts.

UrlLink (preferred) / ReadMyReview (legacy)

Link-card primitive used across 46 posts. Auto-detects internal vs external URLs — internal pulls the linked post's title + heroImage, external fetches OG image + title at build time. <UrlLink> is a thin alias around <ReadMyReview> with a neutral default label="Read more". Use <UrlLink> in new posts; existing posts keep using <ReadMyReview> unchanged.

Read more

Source: src/components/UrlLink.astro (wrapper) → src/components/ReadMyReview.astro (implementation). MDX usage in new posts: <UrlLink url="/review/the-slug" /> or <UrlLink url="https://example.com" label="Read the source" />.

ProsAndCons

Two-column block fed by an exported reviewProsCons object in the post’s MDX.

Pros
  • Lightweight 56 g chassis with a familiar ergonomic shape
  • 8000 Hz polling rate over wireless, no dongle compromises
  • Solid 100-hour battery life at 1000 Hz
Cons
  • Side buttons remain on the small side
  • No DPI indicator on the body

Source: src/components/ProsAndCons.astro. MDX pattern: export const reviewProsCons = { pros, cons } + <ProsAndCons /> picks it up via the export.

Gallery

Multi-image grid used across 30+ reviews and news posts. Each image opens in the lazy-loaded GLightbox modal.

Source: src/components/Gallery.astro. MDX: <Gallery images={[image1, image2]} /> where each image is imported at the top of the file.

YouTube embed

Play

From astro-embed. MDX: import { YouTube } from 'astro-embed'; then <YouTube id="VIDEO_ID" />.

Inline image + caption

Standard markdown image followed by an italic line — Markdown renders the italic as <em>, which the global stylesheet styles as a caption.

Sample inline image

Caption sits directly under the image. IBM Plex Mono, --caption-font-size, #cccccc.

MDX pattern: ![alt](./file.jpg) on its own line followed by *caption*. Article images are automatically wrapped by <Lightbox /> for click-to-zoom.

Lightbox

Source: src/components/Lightbox.astro. No props — drop the component into a layout and it script-wraps every article img (excluding gallery images and .no-lightbox). GLightbox CSS + JS are lazy-loaded on first click. Already mounted on every article page via PageComponent.astro.

ToC (Table of Contents)

Source: src/components/ToC.astro. The sidebar to the right of this page is a live render — it scanned every h2/h3 inside [data-article-content] on this page and built itself. Opt-in per post via toc: true frontmatter; mobile gets a FAB-triggered drawer.

AudioPlayer

Accent-ring play/pause control over a native <audio> element, with a label and a live MM:SS readout. Two uses: (1) inline in review MDX for sound tests, and (2) the :focus-visible + compact hero-meta variant used by the text-to-speech “Hear summary” trial — when a post sets audioSummary frontmatter, Hero.astro renders a 30px-button version beside the date / reading time.

Default (inline / sound-test):

Listen to the sound test · --:--

Compact hero-meta variant (“Hear summary”):

Hear summary · --:--

Source: src/components/AudioPlayer.astro (styles in global.css). MDX inline: import AudioPlayer from '@components/AudioPlayer.astro' + <AudioPlayer src={mp3} label="…" />. TTS variant: set audioSummary: "/audio/<date>-<slug>-<lang>.mp3" in frontmatter; audio is generated offline via npm run tts (scripts/generate-tts.mjs).

Pills, chips & tags

Review News First impressions

.category — solid mint chip, uppercase, var(--text-xs). Used in article headers and post metadata.

#keyboards #mice #razer #first-impressions

.tag — mint text, no background, var(--text-sm). Used as #tag chips in the post-tags row at the bottom of every article. Cards use a separate .card-tag class.

Featured Keyboards

.hero-badge — overlay pills anchored to the hero image (Hero.astro, featured variant). .featured uses --color-variation-4 (pink); the second pill uses --color-accent-tertiary for the first post tag.

Review Top pick

.hero-badge-pill — mint accent pill on review/interview hero images. Used for the REVIEW tag and the TOP PICK marker.

Keyboards Razer

.card-tag — overlay pill anchored top-left of card images. Smaller (0.625rem) than hero badges, same purple --color-accent-tertiary. One pill per post tag, capped via maxTags.

Top pick First impressions

.top-pick-badge and .first-impressions-badge — anchored top-right of card images, opposite the .card-tag stack. Mutually exclusive: top pick wins when both are set.

Editorial headlines (Syne)

Default <h1>–<h6> use system-ui bold + accent color. Syne is opted into selectively via .hero-title / .card-title. Bold-word emphasis inside heroHeadline uses Syne 700 with the heading gradient.

Hero title — Syne 500, with **word** emphasis → Syne 700 gradient

Why the Keychron M4 is the mouse I actually keep using

.hero-title var(--font-display), 500, var(--text-h1)

Used on review and news hero blocks. Markdown-style **word** in heroHeadline frontmatter becomes <strong> rendered at 700 with the heading gradient applied to that word.

Card title — Syne 500, h3 scale

Razer Pro Type Ergo first impressions

.card-title var(--font-display), 500, var(--text-h3)

Used on every <Card> across grids: ContentGrid, TopicSection, UpcomingSection.

Hero summary — Syne 400, 1.26rem, letter-spacing 0.04em

A short editorial summary sits below the hero title. Looser tracking and lighter weight keep it from competing with the headline.

.hero-summary p var(--font-display), 400, 1.26rem

Hero summary paragraph (src/components/sections/Hero.astro).

Type scale — headings (defaults)

These are the global defaults: system-ui, bold, accent color. Component-level overrides (above) opt into Syne.

Heading 1 — page titles

--text-h1 clamp(2.5rem, 5vw, 4rem)

Page titles (about, hero post titles)

Heading 2 — section headings

--text-h2 clamp(1.5rem, 2.5vw, 2rem)

Section headings in articles, hubs

Heading 3 — subsections

--text-h3 clamp(1.25rem, 1.75vw, 1.5rem)

Subsections, card-group titles

Heading 4

--text-h4 1.25rem

Inner subheads in articles

Heading 5
--text-h5 1.125rem

Rare — inline minor subheads

Type scale — body

Large lead text used for intros.

--text-lg 1.5rem

Lead paragraphs, hero descriptions

Medium body text.

--text-m 1.125rem

Slightly emphasized body, callouts

Default body copy size.

--text-base 1rem

Default for everything not overridden

Small text for meta and labels.

--text-sm 0.875rem

Meta info, breadcrumbs, footer

Extra small text.

--text-xs 0.75rem

Tags, micro-labels

Caption text under images.

--caption-font-size 0.65rem

Image captions (mono, #cccccc)

Font families

--font-display

The quick brown fox jumps over the lazy dog 0123456789

Syne Variable — h1–h6, brand display

body (system-ui)

The quick brown fox jumps over the lazy dog 0123456789

system-ui stack — paragraphs, default body

--font-mono

The quick brown fox jumps over the lazy dog 0123456789

IBM Plex Mono — captions, code, tags, token labels

Font weights

The quick brown fox

--font-weight-normal 400

The quick brown fox

--font-weight-medium 500

The quick brown fox

--font-weight-semibold 600

The quick brown fox

--font-weight-bold 700

Line heights

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam.

--line-height-tight 1.2

Headings

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam.

--line-height-normal 1.5

Default text

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam.

--line-height-relaxed 1.6

Long-form prose, paragraphs

Colors

Backgrounds

--color-bg #111111

Body background everywhere

--color-bg-elevated #1a1a1a

Elevated surfaces: dropdowns, ToC drawer, modal sheets

--color-bg-subtle #151515

Subtle alt sections (rarely), card background

Text

--color-text #f5f5f5

Primary body text, headlines on light contexts

--color-text-muted #c8c8c8

Paragraph body across about, posts, descriptions

--color-text-subtle #707070

Meta info, timestamps, tertiary labels

Accents

--color-accent #99FFCC

Brand mint — headings, links, tags, category chips, hover underlines

--color-accent-alt #ff006e

Hot pink — secondary accent (rare highlights)

--color-accent-tertiary #4800fb

Purple — card tag pills, tertiary accent

--color-accent-hover #7fddbb

Muted mint — hover state on accent elements

--color-variation-4 #cc33cc

Magenta variation (decorative)

Borders

--color-border rgba(255,255,255,0.1)

Card edges, hr, section dividers

--color-border-subtle rgba(255,255,255,0.05)

Faint internal separators

Gradient

Heading gradient

--gradient-heading · linear-gradient(90deg, var(--color-text) 0%, var(--color-accent) 100%) · Used on: large hero/page titles (about page h1, etc.)

Other components

Sample inline link

Default link with mint underline

inline code

Inline <code>

"A blockquote of decent length to show the styling treatment."

<blockquote> in articles

Card accent symbols

A single coloured glyph in the bottom-right of each card, tinted with a dominant colour extracted from the post’s heroImage. Quiet per-post flourish that picks up the image palette.

🜁︎ 🜃︎ 🜍︎ 🜚︎ -

Source: src/lib/cardAccent.ts + scripts/extract-card-colors.mjs. Colours are pre-extracted at build time, keyed by slug in src/data/card-accents.json. Symbol is chosen deterministically per slug via FNV-1a hash mod pool length. Roughly 20% of cards skip the symbol entirely (also slug-deterministic) so the grid breathes.

Spacing scale

4px base unit. Bars are sized to each token.

--space-1 4px
--space-2 8px
--space-3 12px
--space-4 16px
--space-5 24px
--space-6 32px
--space-8 48px
--space-10 64px
--space-12 96px

Radii

--radius-sm 4px

Default subtle rounding — tag chips, code pills, swatches

--radius-md 8px

Card-shaped surfaces — family blocks, lh-block, shadow-card

--radius-full 9999px

Circular buttons — ToC FAB, search button

Shadows

--shadow-lg

Cards, prominent surfaces

--shadow-tag

Tag chips

Transitions

Hover the buttons to feel the difference.

Film grain

Grain at --grain-opacity: 0.08

Layered over backgrounds site-wide for analogue texture.

Hero treatment

Grain at --grain-opacity-hero: 0.18

Heavier grain scoped to review and interview hero sections via the .hero.is-review modifier — layered over a radial mint glow.

Radial glow --hero-glow-color: rgba(102, 255, 184, 0.18), 500px radius

Radial gradient fading from this color in the bottom-right corner of .hero.is-review to transparent at 60%.

Interview Q&A

Four components for the interview template. Authors write <Q>, <A>, <PullQuote attribution="…"> and <Note>. Questions are marked by a large Syne ?; answers render as plain aligned text. The Note label is i18n: Editor's note (EN) / Kommentar (NO).

How does the speaker turn render on mobile vs desktop?
Two-column grid with a 56px marker column on desktop — a Syne "?" beside the question, answers aligned underneath with no badge. On viewports below 600px the "?" sits above the question. Pure CSS, no JS.
Pull quotes sit between turns, not inside answers. Same mint top-bar treatment as standard article blockquotes.
Demo attribution

Layout widths

--layout-max-width 1200px

Outer page container

--hero-max-width 1150px

Hero sections

--content-max-width 680px

Article body for readability