progress-bar
FC-0018progressprimitivev1.0.0Voortgang in twee gedaanten: gemeten (percentage bekend) en onbepaald (value=null) met een pendel. De kop wisselt mee zonder te verspringen.
jaarplan.pdf
Eerst onbepaald (pendel, geen percentage), dan gemeten — dat is dezelfde component met value=null.
primitives/ui/progress-bar.tsx
/**
* ProgressBar — voortgang met twee gedaanten: een gemeten balk (percentage
* bekend) en een onbepaalde (er gebeurt iets, maar niemand weet hoe lang).
* `value={null}` zet hem in die tweede stand; het label wisselt mee.
*
* De balk vult via scaleX — een transform, geen breedte-animatie, dus geen
* layout-werk per frame. De maat blijft staan bij het wisselen tussen
* percentage en tekst: beide liggen in hetzelfde grid-vak.
*/
"use client";
import { useId } from "react";
import type * as React from "react";
import { motion, useReducedMotion } from "framer-motion";
import { useSpringTransition } from "../motion";
export interface ProgressBarProps {
/** 0…max, of null voor onbepaald. */
value: number | null;
max?: number;
label?: React.ReactNode;
/** Wat er staat zolang er geen percentage is. */
bezigLabel?: string;
/** Wat er wordt uitgesproken zodra hij vol is. */
klaarLabel?: string;
className?: string;
}
export function ProgressBar({
value,
max = 100,
label = "Voortgang",
bezigLabel = "Bezig",
klaarLabel = "Klaar",
className,
}: ProgressBarProps) {
const reduced = useReducedMotion();
const labelId = useId();
const vullen = useSpringTransition("smooth", { mass: "system" });
const wissel = useSpringTransition("smooth", { mass: "content" });
const onbepaald = value === null;
const deel = value === null || max <= 0 ? 0 : Math.min(1, Math.max(0, value / max));
const procent = Math.round(deel * 100);
const klaar = !onbepaald && deel >= 1;
return (
<div className={["fp-progress", className].filter(Boolean).join(" ")}>
<div className="fp-progress__head">
<span id={labelId} className="fp-progress__label">
{label}
</span>
<span aria-hidden="true" className="fp-progress__value">
<motion.span
className="fp-progress__value-face"
initial={false}
animate={{ opacity: onbepaald ? 1 : 0 }}
transition={wissel}
>
{bezigLabel}
</motion.span>
<motion.span
className="fp-progress__value-face fp-progress__value-face--num"
initial={false}
animate={{ opacity: onbepaald ? 0 : 1 }}
transition={wissel}
>
{procent}%
</motion.span>
</span>
</div>
<div
role="progressbar"
aria-labelledby={labelId}
aria-valuemin={0}
aria-valuemax={max}
{...(onbepaald
? {}
: { "aria-valuenow": Math.round(deel * max * 100) / 100, "aria-valuetext": `${procent}%` })}
className="fp-progress__track"
>
<div className="fp-progress__rail">
<motion.span
aria-hidden="true"
className="fp-progress__fill"
initial={false}
animate={{ scaleX: onbepaald ? 0 : deel }}
transition={vullen}
/>
{/* Onbepaald: een pendel die heen en weer trekt — doorlopend, dus lineair en buiten het duurbudget. */}
{onbepaald && !reduced ? (
<motion.span
aria-hidden="true"
className="fp-progress__shuttle"
initial={{ x: "-100%", opacity: 0 }}
animate={{ x: "250%", opacity: 1 }}
transition={{
x: { duration: 1.25, ease: "easeInOut", repeat: Infinity },
opacity: { duration: 0.18 },
}}
/>
) : null}
</div>
</div>
<span aria-live="polite" className="fp-sr-only">
{klaar ? klaarLabel : onbepaald ? bezigLabel : ""}
</span>
</div>
);
}
primitives/ui/primitives.css
/**
* Laag-1 primitives — stylesheet.
* Uitsluitend tokens uit tokens/CONTRACT.md (contract v1): geen letterlijke
* kleuren, fonts of radii. Structurele geometrie (padding, greep, marges)
* staat in rem en is gedocumenteerd als aanname in de manifests.
* Vereist tokens/motion.css naast dit bestand.
*/
/* ── Button ─────────────────────────────────────────────────────────────── */
.fp-btn {
appearance: none;
cursor: pointer;
border: 1px solid transparent;
border-radius: var(--radius);
font: inherit;
font-size: 0.875rem;
font-weight: 500;
line-height: 1;
padding: 0.625rem 1rem;
-webkit-tap-highlight-color: transparent;
user-select: none;
transition:
transform var(--motion-instant-duration) var(--motion-instant),
opacity var(--motion-instant-duration) var(--motion-instant),
background-color var(--motion-instant-duration) var(--motion-instant);
}
.fp-btn:disabled { cursor: default; opacity: 0.5; }
.fp-btn:focus-visible { outline: 2px solid var(--ring, var(--info)); outline-offset: 2px; }
/* Touch-down zonder vertraging; usePress zet data-pressed, ook bij terugslepen. */
.fp-btn[data-pressed] { transform: scale(0.96); }
.fp-btn--primary { background: var(--primary); color: var(--primary-foreground); }
.fp-btn--primary:hover:not(:disabled) { opacity: 0.9; }
.fp-btn--ghost { background: transparent; color: var(--foreground); border-color: var(--border); }
.fp-btn--ghost:hover:not(:disabled) { background: var(--muted); }
/* ── Field / Input ──────────────────────────────────────────────────────── */
.fp-field { display: flex; flex-direction: column; gap: 0.375rem; }
.fp-field__label { font-size: 0.8125rem; font-weight: 500; color: var(--foreground); }
.fp-input {
appearance: none;
font: inherit;
font-size: 0.875rem;
color: var(--foreground);
background: var(--muted);
border: 1px solid var(--border);
border-radius: var(--radius);
padding: 0.5625rem 0.75rem;
transition:
border-color var(--motion-instant-duration) var(--motion-instant),
background-color var(--motion-instant-duration) var(--motion-instant);
}
.fp-input::placeholder { color: var(--muted-foreground); }
/* Rij met trailing-slot: het veld vult de rij, het slot hangt rechts erin. */
.fp-field__row { position: relative; display: flex; }
.fp-field__row .fp-input { width: 100%; }
.fp-input--met-trailing { padding-right: 2.75rem; }
.fp-field__trailing { position: absolute; inset: 0 0.5rem 0 auto; display: flex; align-items: center; color: var(--muted-foreground); }
.fp-input--met-leading { padding-left: 2.25rem; }
.fp-field__leading { position: absolute; inset: 0 auto 0 0.75rem; display: flex; align-items: center; color: var(--muted-foreground); pointer-events: none; }
/* type=search zonder browser-kruisje; type=file met de bestandsknop als deel van het veld. */
.fp-input[type="search"]::-webkit-search-cancel-button,
.fp-input[type="search"]::-webkit-search-decoration { -webkit-appearance: none; appearance: none; }
.fp-input[type="file"] { padding: 0 0.75rem 0 0; color: var(--muted-foreground); font-style: italic; }
.fp-input[type="file"]::file-selector-button {
margin-right: 0.75rem; padding: 0.5625rem 0.75rem;
border: 0; border-right: 1px solid var(--border);
background: transparent; color: var(--foreground);
font: inherit; font-size: 0.875rem; font-weight: 500; font-style: normal; cursor: pointer;
}
.fp-input:focus-visible { outline: 2px solid var(--ring, var(--info)); outline-offset: 1px; }
.fp-input[aria-invalid] { border-color: var(--danger); }
.fp-field__description { margin: 0; font-size: 0.75rem; color: var(--muted-foreground); }
.fp-field__error { margin: 0; font-size: 0.75rem; color: var(--danger); }
/* ── Popover ────────────────────────────────────────────────────────────── */
.fp-popover-anchor { position: relative; display: inline-block; }
.fp-popover {
position: absolute;
z-index: 30;
min-width: 12rem;
background: var(--card);
color: var(--card-foreground);
border: 1px solid var(--border);
border-radius: var(--radius);
box-shadow: var(--shadow-overlay);
padding: 0.3125rem;
opacity: 0;
visibility: hidden;
transition:
transform var(--motion-pop-duration) var(--motion-pop),
opacity var(--motion-pop-duration) var(--motion-pop),
visibility 0s linear var(--motion-pop-duration);
}
.fp-popover[data-open] {
opacity: 1;
visibility: visible;
transition:
transform var(--motion-pop-duration) var(--motion-pop),
opacity var(--motion-pop-duration) var(--motion-pop);
}
/* Plaatsing + groeirichting: altijd vanuit het anker (transform-origin). */
.fp-popover--bottom-start { top: calc(100% + 0.5rem); left: 0; transform-origin: top left; transform: scale(0.96) translateY(-0.25rem); }
.fp-popover--bottom-end { top: calc(100% + 0.5rem); right: 0; transform-origin: top right; transform: scale(0.96) translateY(-0.25rem); }
.fp-popover--top-start { bottom: calc(100% + 0.5rem); left: 0; transform-origin: bottom left; transform: scale(0.96) translateY(0.25rem); }
.fp-popover--top-end { bottom: calc(100% + 0.5rem); right: 0; transform-origin: bottom right; transform: scale(0.96) translateY(0.25rem); }
.fp-popover[data-open] { transform: scale(1) translateY(0); }
/* ── DateRangeCalendar ──────────────────────────────────────────────────── */
.fp-cal {
background: var(--card);
color: var(--card-foreground);
border: 1px solid var(--border);
border-radius: var(--radius);
padding: 1.25rem;
}
.fp-cal__head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 1rem; }
.fp-cal__title { margin: 0; font-size: 0.9375rem; font-weight: 600; }
.fp-cal__nav { display: flex; gap: 0.25rem; }
.fp-cal__navbtn {
appearance: none; cursor: pointer; font: inherit; line-height: 1;
width: 1.75rem; height: 1.75rem;
background: transparent; color: var(--foreground);
border: 1px solid var(--border); border-radius: calc(var(--radius) - 2px);
transition: background-color var(--motion-instant-duration) var(--motion-instant),
transform var(--motion-instant-duration) var(--motion-instant);
}
.fp-cal__navbtn:hover:not(:disabled) { background: var(--muted); }
.fp-cal__navbtn:active:not(:disabled) { transform: scale(0.94); }
.fp-cal__navbtn:disabled { opacity: 0.3; cursor: default; }
.fp-cal__navbtn:focus-visible { outline: 2px solid var(--ring, var(--info)); outline-offset: 1px; }
.fp-cal__hint {
margin: 0 0 0.875rem; padding: 0.5rem 0.75rem;
font-size: 0.75rem; color: var(--foreground);
background: var(--muted); border-radius: calc(var(--radius) - 2px);
}
.fp-cal__months { display: grid; grid-template-columns: 1fr; gap: 2rem; }
@media (min-width: 640px) {
.fp-cal__months[data-months="2"] { grid-template-columns: 1fr 1fr; }
}
.fp-cal__monthlabel {
margin: 0 0 0.75rem; text-align: center; text-transform: capitalize;
font-size: 0.8125rem; font-weight: 500;
}
.fp-cal__weekdays {
display: grid; grid-template-columns: repeat(7, 1fr); margin-bottom: 0.25rem;
font-size: 0.6875rem; color: var(--muted-foreground); text-align: center;
}
.fp-cal__grid { display: grid; grid-template-columns: repeat(7, 1fr); row-gap: 0.25rem; }
.fp-cal__day {
appearance: none; font: inherit; font-size: 0.75rem; line-height: 1;
width: 2.25rem; height: 2.25rem; margin: 0 auto;
display: flex; align-items: center; justify-content: center;
background: transparent; color: var(--foreground);
border: 0; border-radius: 999px; cursor: pointer;
transition: background-color var(--motion-instant-duration) var(--motion-instant),
color var(--motion-instant-duration) var(--motion-instant),
transform var(--motion-instant-duration) var(--motion-instant);
}
.fp-cal__day:active:not(:disabled) { transform: scale(0.92); }
.fp-cal__day:focus-visible { outline: 2px solid var(--ring, var(--info)); outline-offset: 1px; }
.fp-cal__day:hover:not(:disabled) { background: var(--muted); }
.fp-cal__day[data-status="past"] { color: var(--muted-foreground); opacity: 0.4; cursor: default; }
.fp-cal__day[data-status="unavailable"] { color: var(--danger); text-decoration: line-through; cursor: not-allowed; }
.fp-cal__day[data-status="pending"] {
background: color-mix(in srgb, var(--warning) 18%, transparent);
color: var(--warning); cursor: not-allowed;
}
.fp-cal__day[data-status="selected-start"],
.fp-cal__day[data-status="selected-end"] {
background: var(--primary); color: var(--primary-foreground); font-weight: 600;
}
.fp-cal__day[data-status="selected-middle"],
.fp-cal__day[data-status="selecting"] { background: var(--muted); }
.fp-cal__legend {
display: flex; flex-wrap: wrap; gap: 0.5rem 1.25rem;
margin-top: 1.125rem; padding-top: 0.875rem; border-top: 1px solid var(--border);
font-size: 0.75rem; color: var(--muted-foreground);
}
.fp-cal__legend span { display: inline-flex; align-items: center; gap: 0.375rem; }
.fp-cal__dot { width: 0.75rem; height: 0.75rem; border-radius: 999px; }
.fp-cal__dot--selected { background: var(--primary); }
.fp-cal__dot--pending { background: color-mix(in srgb, var(--warning) 40%, transparent); }
.fp-cal__strike { color: var(--danger); font-size: 0.7rem; }
.fp-cal__footnote { margin-top: 0.75rem; font-size: 0.75rem; color: var(--muted-foreground); }
/* ── Sheet ──────────────────────────────────────────────────────────────── */
.fp-sheet-scrim {
position: fixed;
inset: 0;
z-index: 50;
background: var(--overlay);
opacity: 0;
pointer-events: none;
transition: opacity var(--motion-smooth-duration) var(--motion-smooth);
}
.fp-sheet-scrim[data-open] { opacity: 1; pointer-events: auto; }
.fp-sheet {
position: fixed;
z-index: 51;
left: 50%;
bottom: 0;
width: min(35rem, calc(100% - 1.5rem));
translate: -50% 0; /* centrering los van de y-animatie (transform is van de spring) */
background: var(--card);
color: var(--card-foreground);
border: 1px solid var(--border);
border-bottom: 0;
border-radius: var(--radius) var(--radius) 0 0;
box-shadow: var(--shadow-overlay);
padding: 0.625rem 1.375rem 1.75rem;
touch-action: none;
}
.fp-sheet:focus-visible { outline: 2px solid var(--ring, var(--info)); outline-offset: 2px; }
.fp-sheet__handle {
width: 2.5rem;
height: 0.25rem;
border-radius: calc(var(--radius) / 4);
background: var(--border);
margin: 0.25rem auto 1rem;
}
/* ── Gedeeld: alleen voor schermlezers ──────────────────────────────────── */
.fp-sr-only {
position: absolute;
width: 1px; height: 1px;
padding: 0; margin: -1px;
overflow: hidden;
clip-path: inset(50%);
white-space: nowrap;
border: 0;
}
/* ── FolderStack ────────────────────────────────────────────────────────── */
.fp-folder {
position: relative;
display: block;
width: 20rem;
height: 13rem;
cursor: pointer;
-webkit-tap-highlight-color: transparent;
user-select: none;
touch-action: manipulation;
}
.fp-folder:focus-visible {
outline: 2px solid var(--ring, var(--info));
outline-offset: 6px;
border-radius: var(--radius);
}
.fp-folder__back {
position: relative;
width: 87.5%;
height: 100%;
margin: 0 auto;
display: flex;
justify-content: center;
background: var(--muted);
border: 1px solid var(--border);
border-radius: var(--radius);
}
.fp-folder__sheet {
position: absolute;
top: 0.5rem;
left: 50%;
margin-left: -4rem; /* halve breedte: centreren los van de waaier-transform */
width: 8rem;
height: 9.5rem;
padding: 0.75rem;
background: var(--card);
border: 1px solid var(--border);
border-radius: var(--radius);
box-shadow: var(--shadow-overlay);
}
.fp-folder__lines { display: flex; flex-direction: column; gap: 0.5rem; }
.fp-folder__row { display: flex; gap: 0.5rem; }
.fp-folder__line {
flex: 1;
height: 0.375rem;
border-radius: 999px;
background: color-mix(in srgb, var(--muted-foreground) 22%, transparent);
}
.fp-folder__line--head { width: 100%; }
.fp-folder__flap {
position: absolute;
inset-inline: 0;
bottom: -1px;
z-index: 3;
height: 11rem;
transform-origin: bottom;
display: flex;
align-items: center;
justify-content: center;
}
.fp-folder__shape { width: 100%; height: 100%; overflow: visible; }
/* Het labelvlak op de klep: twee ogen en een mond, meer heeft het niet nodig. */
.fp-folder__label {
position: absolute;
inset: 0;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding-top: 2rem;
pointer-events: none;
}
.fp-folder__dots { display: flex; gap: 2.75rem; margin-bottom: 0.625rem; }
.fp-folder__dot {
width: 0.625rem; height: 0.625rem; border-radius: 999px;
background: color-mix(in srgb, var(--muted-foreground) 45%, transparent);
}
.fp-folder__bar {
width: 2.25rem; height: 0.25rem; border-radius: 999px;
background: color-mix(in srgb, var(--muted-foreground) 45%, transparent);
}
/* ── TextReveal ─────────────────────────────────────────────────────────── */
.fp-reveal { color: inherit; }
.fp-reveal__group { display: inline-block; white-space: nowrap; vertical-align: baseline; }
/* Bewust geen CSS-transition: de scrub is hier de enige motor (§2.6). */
.fp-reveal__unit { display: inline-block; vertical-align: baseline; }
/* ── TaskSteps ──────────────────────────────────────────────────────────── */
.fp-steps { width: 100%; }
.fp-steps ol {
list-style: none; margin: 0; padding: 0;
display: flex; flex-direction: column; gap: 0.125rem;
}
.fp-steps li {
display: flex;
align-items: center;
gap: 0.625rem;
height: 1.75rem;
padding: 0 0.25rem;
}
.fp-steps__mark {
position: relative;
display: grid;
place-items: center;
width: 1rem; height: 1rem;
flex-shrink: 0;
}
.fp-steps__mark > * { grid-area: 1 / 1; }
.fp-steps__badge {
display: grid;
place-items: center;
width: 1rem; height: 1rem;
border-radius: calc(var(--radius) / 2);
}
.fp-steps__badge--klaar {
color: var(--success);
background: color-mix(in srgb, var(--success) 14%, transparent);
}
.fp-steps__badge--fout {
color: var(--danger);
background: color-mix(in srgb, var(--danger) 14%, transparent);
}
.fp-steps__spin { color: var(--muted-foreground); display: grid; place-items: center; }
.fp-steps__pip {
width: 0.3125rem; height: 0.3125rem;
border-radius: calc(var(--radius) / 4);
background: var(--border);
}
.fp-steps__label {
flex: 1;
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
font-size: 0.8125rem;
color: var(--muted-foreground);
transition: color var(--motion-instant-duration) var(--motion-instant);
}
.fp-steps li[data-status="klaar"] .fp-steps__label { color: color-mix(in srgb, var(--foreground) 70%, var(--muted-foreground)); }
.fp-steps li[data-status="bezig"] .fp-steps__label { color: var(--foreground); font-weight: 500; }
.fp-steps li[data-status="fout"] .fp-steps__label { color: var(--danger); font-weight: 500; }
/* De loopband op de actieve regel: licht dat over de tekst trekt. */
.fp-steps__label--loopt {
font-weight: 500;
color: transparent;
background-image: linear-gradient(
90deg,
var(--muted-foreground) 38%,
var(--foreground) 50%,
var(--muted-foreground) 62%
);
background-size: 220% 100%;
-webkit-background-clip: text;
background-clip: text;
}
.fp-steps__meta {
flex-shrink: 0;
font-size: 0.6875rem;
font-variant-numeric: tabular-nums;
color: var(--muted-foreground);
opacity: 0;
transition: opacity var(--motion-instant-duration) var(--motion-instant);
}
.fp-steps li[data-status="klaar"] .fp-steps__meta { opacity: 1; }
/* ── PressDepth ─────────────────────────────────────────────────────────── */
.fp-depth {
position: relative;
display: inline-flex;
vertical-align: middle;
appearance: none;
border: 0;
background: none;
padding: 0;
cursor: pointer;
border-radius: var(--radius);
outline: none;
-webkit-tap-highlight-color: transparent;
user-select: none;
touch-action: manipulation;
}
.fp-depth:disabled { cursor: default; opacity: 0.5; }
.fp-depth__base {
position: absolute;
inset-inline: 0;
bottom: 0;
height: 100%;
border-radius: var(--radius);
background: color-mix(in srgb, var(--foreground) 18%, var(--muted));
}
.fp-depth__cap {
position: relative;
display: inline-flex;
align-items: center;
justify-content: center;
gap: 0.5rem;
height: 2.25rem;
padding: 0 0.875rem;
border: 1px solid var(--border);
border-radius: var(--radius);
background: var(--card);
color: var(--card-foreground);
font: inherit;
font-size: 0.8125rem;
font-weight: 500;
line-height: 1;
}
.fp-depth:focus-visible .fp-depth__cap {
outline: 2px solid var(--ring, var(--info));
outline-offset: 2px;
}
/* Bolling: licht van boven, schaduw van onder — weg zodra hij inzakt. */
.fp-depth__sheen {
position: absolute;
inset: 0;
border-radius: var(--radius);
pointer-events: none;
box-shadow:
inset 0 1.5px 0 color-mix(in srgb, var(--card) 70%, var(--background)),
inset 0 -1px 0 color-mix(in srgb, var(--foreground) 8%, transparent);
}
/* ── ReorderList ────────────────────────────────────────────────────────── */
.fp-reorder { width: 100%; }
.fp-reorder ul {
list-style: none; margin: 0; padding: 0;
display: flex; flex-direction: column; gap: 0.375rem;
}
.fp-reorder li {
position: relative;
display: flex;
align-items: center;
gap: 0.625rem;
padding: 0.625rem 0.75rem;
background: var(--card);
color: var(--card-foreground);
border: 1px solid var(--border);
border-radius: var(--radius);
cursor: grab;
outline: none;
touch-action: pan-x;
transition:
border-color var(--motion-instant-duration) var(--motion-instant),
background-color var(--motion-instant-duration) var(--motion-instant),
box-shadow var(--motion-instant-duration) var(--motion-instant);
}
.fp-reorder li[data-lifted] { z-index: 1; cursor: grabbing; box-shadow: var(--shadow-overlay); }
.fp-reorder li[data-held] {
border-color: var(--primary);
background: color-mix(in srgb, var(--primary) 6%, var(--card));
}
.fp-reorder li:focus-visible { outline: 2px solid var(--ring, var(--info)); outline-offset: 2px; }
.fp-reorder__grip {
flex-shrink: 0;
display: inline-flex;
color: var(--border);
transition: color var(--motion-instant-duration) var(--motion-instant);
}
.fp-reorder li[data-lifted] .fp-reorder__grip,
.fp-reorder li:hover .fp-reorder__grip { color: var(--muted-foreground); }
.fp-reorder__body { flex: 1; min-width: 0; }
/* ── ProgressBar ────────────────────────────────────────────────────────── */
.fp-progress { width: 100%; }
.fp-progress__head { display: flex; align-items: baseline; justify-content: space-between; gap: 0.75rem; }
.fp-progress__label {
overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
font-size: 0.8125rem; font-weight: 500; color: var(--foreground);
}
/* Beide waarden liggen in hetzelfde vak: de kop verspringt niet bij het wisselen. */
.fp-progress__value { display: grid; justify-items: end; flex-shrink: 0; }
.fp-progress__value-face {
grid-area: 1 / 1;
white-space: nowrap;
font-size: 0.75rem; font-weight: 500; line-height: 1.25rem;
color: var(--muted-foreground);
}
.fp-progress__value-face--num { font-variant-numeric: tabular-nums; }
.fp-progress__track {
margin-top: 0.5rem;
padding: 2px;
border-radius: calc(var(--radius) / 2);
background: var(--muted);
box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--foreground) 8%, transparent);
}
.fp-progress__rail {
position: relative;
height: 0.5rem;
overflow: hidden;
border-radius: calc(var(--radius) / 3);
}
.fp-progress__fill {
position: absolute; inset: 0;
display: block;
transform-origin: left;
border-radius: calc(var(--radius) / 3);
background: var(--primary);
}
.fp-progress__shuttle {
position: absolute; inset-block: 0; left: 0;
display: block;
width: 40%;
border-radius: calc(var(--radius) / 3);
background: var(--primary);
}
/* ── OtpInput ───────────────────────────────────────────────────────────── */
.fp-otp { display: inline-flex; flex-direction: column; }
.fp-otp__row { position: relative; display: flex; gap: 0.5rem; }
.fp-otp__cell { position: relative; width: 2.5rem; height: 3rem; }
.fp-otp__cell[data-gap] { margin-left: 0.75rem; }
/* De input is doorzichtig; het zichtbare teken is een eigen laag erboven. */
.fp-otp__input {
width: 100%; height: 100%;
border: 2px solid var(--border);
border-radius: var(--radius);
background: var(--muted);
box-shadow: inset 0 1px 2px color-mix(in srgb, var(--foreground) 7%, transparent);
color: transparent;
caret-color: transparent;
font: inherit;
font-size: 0.9375rem;
text-align: center;
outline: none;
transition:
background-color var(--motion-instant-duration) var(--motion-instant),
border-color var(--motion-instant-duration) var(--motion-instant),
box-shadow var(--motion-instant-duration) var(--motion-instant);
}
.fp-otp__input::selection { background: transparent; }
.fp-otp__input:disabled { opacity: 0.5; }
.fp-otp__input[data-gevuld] { background: var(--card); border-color: color-mix(in srgb, var(--border) 60%, var(--foreground)); box-shadow: none; }
.fp-otp__input[data-actief] { background: var(--card); border-color: var(--ring, var(--info)); box-shadow: none; }
.fp-otp__input[data-status="fout"] { border-color: var(--danger); background: var(--card); box-shadow: none; }
.fp-otp__input[data-status="goed"] { border-color: var(--success); background: var(--card); box-shadow: none; }
.fp-otp__glass {
position: absolute; inset: 0;
display: grid; place-items: center;
pointer-events: none;
}
.fp-otp__char {
grid-area: 1 / 1;
font-size: 0.9375rem;
font-variant-numeric: tabular-nums;
color: var(--foreground);
}
.fp-otp__caret {
grid-area: 1 / 1;
display: block;
width: 1.5px; height: 1.0625rem;
border-radius: 999px;
background: var(--foreground);
}
.fp-otp__status { display: grid; height: 1rem; margin-top: 0.5rem; }
.fp-otp__message {
grid-area: 1 / 1;
font-size: 0.71875rem; line-height: 1rem;
color: var(--muted-foreground);
}
.fp-otp__message[data-status="fout"] { color: var(--danger); }
.fp-otp__message[data-status="goed"] { color: var(--success); }
/* ── PasswordStrength ───────────────────────────────────────────────────── */
.fp-pw { width: 100%; }
.fp-pw__meter { display: grid; gap: 0.375rem; }
.fp-pw__slot {
position: relative;
height: 0.375rem;
overflow: hidden;
border-radius: calc(var(--radius) / 4);
background: var(--muted);
}
.fp-pw__bar {
position: absolute; inset: 0;
transform-origin: left;
border-radius: calc(var(--radius) / 4);
background: var(--border);
transition: background-color var(--motion-instant-duration) var(--motion-instant);
}
.fp-pw[data-tone="gevaar"] .fp-pw__bar { background: var(--danger); }
.fp-pw[data-tone="let-op"] .fp-pw__bar { background: var(--warning); }
.fp-pw[data-tone="veilig"] .fp-pw__bar { background: var(--success); }
.fp-pw__head {
display: flex; align-items: center; justify-content: space-between; gap: 0.75rem;
height: 1.25rem; margin-top: 0.5rem;
}
/* Alle woorden in één vak: "Zwak" → "Sterk" verschuift niets. */
.fp-pw__labels { display: inline-grid; }
.fp-pw__label {
grid-area: 1 / 1;
white-space: nowrap;
font-size: 0.78125rem; font-weight: 500; line-height: 1.25rem;
color: var(--muted-foreground);
transition: color var(--motion-instant-duration) var(--motion-instant);
}
.fp-pw[data-tone="gevaar"] .fp-pw__label { color: var(--danger); }
.fp-pw[data-tone="let-op"] .fp-pw__label { color: var(--warning); }
.fp-pw[data-tone="veilig"] .fp-pw__label { color: var(--success); }
.fp-pw__warn {
white-space: nowrap;
font-size: 0.71875rem; line-height: 1.25rem;
color: var(--warning);
}
.fp-pw__rules { list-style: none; margin: 0.75rem 0 0; padding: 0; display: grid; gap: 0.375rem; }
.fp-pw__rules li { display: flex; align-items: center; gap: 0.5rem; }
.fp-pw__box {
position: relative;
display: grid; place-items: center;
width: 0.875rem; height: 0.875rem;
flex-shrink: 0;
border: 1px solid var(--border);
border-radius: calc(var(--radius) / 3);
color: var(--background);
}
.fp-pw__box-fill {
position: absolute; inset: 0;
border-radius: calc(var(--radius) / 4);
background: var(--success);
}
.fp-pw__tick { position: relative; width: 0.5625rem; height: 0.5625rem; }
.fp-pw__rule-label {
font-size: 0.78125rem; line-height: 1.25rem;
color: var(--muted-foreground);
transition: color var(--motion-instant-duration) var(--motion-instant);
}
.fp-pw__rules li[data-voldaan] .fp-pw__rule-label { color: var(--foreground); }
/* ── ExpandingNav ───────────────────────────────────────────────────────── */
.fp-nav ul { list-style: none; margin: 0; padding: 0; display: flex; gap: 1rem; }
.fp-nav__item {
--fp-nav-from: var(--primary);
--fp-nav-to: var(--primary);
width: 3.75rem;
height: 3.75rem;
}
/* De maat springt; Framer interpoleert hem met transforms (FLIP), niet per frame layout. */
.fp-nav__item[data-open] { width: 11.25rem; }
.fp-nav__button {
position: relative;
display: flex; align-items: center; justify-content: center;
width: 100%; height: 100%;
padding: 0;
border: 0;
border-radius: 999px;
background: var(--card);
color: var(--muted-foreground);
font: inherit;
cursor: pointer;
outline: none;
overflow: visible;
-webkit-tap-highlight-color: transparent;
touch-action: manipulation;
box-shadow: var(--shadow-overlay);
transition: box-shadow var(--motion-smooth-duration) var(--motion-smooth);
}
.fp-nav__button:focus-visible { outline: 2px solid var(--ring, var(--info)); outline-offset: 3px; }
.fp-nav__item[data-open] .fp-nav__button { box-shadow: none; }
.fp-nav__wash,
.fp-nav__glow {
position: absolute;
border-radius: 999px;
background: linear-gradient(45deg, var(--fp-nav-from), var(--fp-nav-to));
opacity: 0;
transition: opacity var(--motion-smooth-duration) var(--motion-smooth);
}
.fp-nav__wash { inset: 0; }
.fp-nav__glow { inset-inline: 0; top: 0.625rem; height: 100%; filter: blur(15px); z-index: -1; }
.fp-nav__item[data-open] .fp-nav__wash { opacity: 1; }
.fp-nav__item[data-open] .fp-nav__glow { opacity: 0.5; }
.fp-nav__icon { position: relative; display: inline-flex; font-size: 1.5rem; line-height: 1; }
.fp-nav__label {
position: absolute;
white-space: nowrap;
color: var(--primary-foreground);
font-size: 0.8125rem;
font-weight: 500;
letter-spacing: 0.04em;
text-transform: uppercase;
}
/* ── NeonButton ─────────────────────────────────────────────────────────── */
.fp-neon {
--fp-neon: var(--primary);
position: relative;
display: inline-flex; align-items: center; justify-content: center;
appearance: none; cursor: pointer; user-select: none; touch-action: manipulation;
font: inherit; font-weight: 500; line-height: 1.2;
color: var(--foreground);
border: 1px solid transparent; border-radius: 999px;
transition:
background-color var(--motion-instant-duration) var(--motion-instant),
border-color var(--motion-instant-duration) var(--motion-instant),
color var(--motion-instant-duration) var(--motion-instant),
transform var(--motion-instant-duration) var(--motion-instant);
}
.fp-neon--sm { padding: 0.25rem 1rem; font-size: 0.8125rem; }
.fp-neon--md { padding: 0.5rem 1.75rem; font-size: 0.875rem; }
.fp-neon--lg { padding: 0.75rem 2.5rem; font-size: 1rem; }
.fp-neon--outline {
background: color-mix(in srgb, var(--fp-neon) 6%, transparent);
border-color: color-mix(in srgb, var(--fp-neon) 22%, transparent);
}
.fp-neon--outline:hover { background: transparent; }
.fp-neon--solid { background: var(--fp-neon); color: var(--primary-foreground); }
.fp-neon--solid:hover { border-color: color-mix(in srgb, var(--foreground) 50%, transparent); }
.fp-neon--ghost { background: transparent; }
.fp-neon--ghost:hover { border-color: var(--border); background: color-mix(in srgb, var(--foreground) 8%, transparent); }
.fp-neon[data-pressed] { transform: scale(0.97); }
.fp-neon:disabled { opacity: 0.5; cursor: default; }
.fp-neon:focus-visible { outline: 2px solid var(--ring, var(--info)); outline-offset: 2px; }
/* De lichtlijnen: 1px, driekwart breed, gecentreerd; alleen opacity beweegt. */
.fp-neon__line {
position: absolute; left: 12.5%; right: 12.5%; height: 1px;
pointer-events: none;
background: linear-gradient(90deg, transparent, var(--fp-neon), transparent);
opacity: 0;
transition: opacity var(--motion-smooth-duration) var(--motion-smooth);
}
.fp-neon__line--top { top: -1px; }
.fp-neon__line--bottom { bottom: -1px; }
.fp-neon:hover .fp-neon__line--top,
.fp-neon:focus-visible .fp-neon__line--top,
.fp-neon[data-pressed] .fp-neon__line--top { opacity: 1; }
.fp-neon:hover .fp-neon__line--bottom,
.fp-neon:focus-visible .fp-neon__line--bottom,
.fp-neon[data-pressed] .fp-neon__line--bottom { opacity: 0.3; }
/* ── GradientButton ─────────────────────────────────────────────────────── */
/* Geregistreerde properties: alleen zo kan de browser een verloop-positie
interpoleren. Dit is verf, geen layout. */
@property --fp-grad-x { syntax: "<percentage>"; inherits: false; initial-value: 11.14%; }
@property --fp-grad-y { syntax: "<percentage>"; inherits: false; initial-value: 140%; }
@property --fp-grad-sx { syntax: "<percentage>"; inherits: false; initial-value: 150%; }
@property --fp-grad-sy { syntax: "<percentage>"; inherits: false; initial-value: 180.06%; }
@property --fp-grad-s1 { syntax: "<percentage>"; inherits: false; initial-value: 37.35%; }
@property --fp-grad-s2 { syntax: "<percentage>"; inherits: false; initial-value: 61.36%; }
@property --fp-grad-s3 { syntax: "<percentage>"; inherits: false; initial-value: 78.42%; }
@property --fp-grad-s4 { syntax: "<percentage>"; inherits: false; initial-value: 89.52%; }
@property --fp-grad-s5 { syntax: "<percentage>"; inherits: false; initial-value: 100%; }
@property --fp-grad-hoek { syntax: "<angle>"; inherits: false; initial-value: 20deg; }
.fp-grad {
--fp-grad-1: var(--primary);
--fp-grad-2: color-mix(in srgb, var(--primary) 70%, var(--info));
--fp-grad-3: color-mix(in srgb, var(--primary) 40%, var(--info));
--fp-grad-4: color-mix(in srgb, var(--info) 80%, var(--background));
--fp-grad-5: color-mix(in srgb, var(--info) 55%, var(--background));
--fp-grad-rand-1: color-mix(in srgb, var(--primary) 70%, transparent);
--fp-grad-rand-2: color-mix(in srgb, var(--info) 40%, transparent);
--fp-grad-x: 11.14%; --fp-grad-y: 140%;
--fp-grad-sx: 150%; --fp-grad-sy: 180.06%;
--fp-grad-s1: 37.35%; --fp-grad-s2: 61.36%; --fp-grad-s3: 78.42%; --fp-grad-s4: 89.52%; --fp-grad-s5: 100%;
--fp-grad-hoek: 20deg;
position: relative;
display: inline-flex; align-items: center; justify-content: center;
min-width: 8.25rem; padding: 1rem 2.25rem;
appearance: none; cursor: pointer; user-select: none; touch-action: manipulation;
font: inherit; font-weight: 600; line-height: 1.2;
color: var(--primary-foreground);
border: 2px solid transparent; border-radius: var(--radius);
background:
radial-gradient(
var(--fp-grad-sx) var(--fp-grad-sy) at var(--fp-grad-x) var(--fp-grad-y),
var(--fp-grad-1) var(--fp-grad-s1),
var(--fp-grad-2) var(--fp-grad-s2),
var(--fp-grad-3) var(--fp-grad-s3),
var(--fp-grad-4) var(--fp-grad-s4),
var(--fp-grad-5) var(--fp-grad-s5)
) padding-box,
linear-gradient(var(--fp-grad-hoek), var(--fp-grad-rand-1), var(--fp-grad-rand-2)) border-box;
transition:
--fp-grad-x var(--motion-smooth-duration) var(--motion-smooth),
--fp-grad-y var(--motion-smooth-duration) var(--motion-smooth),
--fp-grad-sx var(--motion-smooth-duration) var(--motion-smooth),
--fp-grad-sy var(--motion-smooth-duration) var(--motion-smooth),
--fp-grad-s1 var(--motion-smooth-duration) var(--motion-smooth),
--fp-grad-s2 var(--motion-smooth-duration) var(--motion-smooth),
--fp-grad-s3 var(--motion-smooth-duration) var(--motion-smooth),
--fp-grad-s4 var(--motion-smooth-duration) var(--motion-smooth),
--fp-grad-s5 var(--motion-smooth-duration) var(--motion-smooth),
--fp-grad-hoek var(--motion-smooth-duration) var(--motion-smooth),
transform var(--motion-instant-duration) var(--motion-instant);
}
.fp-grad:hover, .fp-grad:focus-visible, .fp-grad[data-pressed] {
--fp-grad-x: 0%; --fp-grad-y: 91.51%;
--fp-grad-sx: 120.24%; --fp-grad-sy: 103.18%;
--fp-grad-s1: 0%; --fp-grad-s2: 8.8%; --fp-grad-s3: 21.44%; --fp-grad-s4: 71.34%; --fp-grad-s5: 85.76%;
--fp-grad-hoek: 190deg;
}
.fp-grad[data-pressed] { transform: scale(0.97); }
.fp-grad:disabled { opacity: 0.5; cursor: default; }
.fp-grad:focus-visible { outline: 2px solid var(--ring, var(--info)); outline-offset: 2px; }
/* Verloop op de tekstkleur — voor lichte vlakken waar de merkkleur te luid is. */
.fp-grad--contrast {
--fp-grad-1: var(--foreground);
--fp-grad-2: color-mix(in srgb, var(--foreground) 80%, var(--muted-foreground));
--fp-grad-3: color-mix(in srgb, var(--foreground) 55%, var(--muted-foreground));
--fp-grad-4: var(--muted-foreground);
--fp-grad-5: color-mix(in srgb, var(--muted-foreground) 70%, var(--background));
--fp-grad-rand-1: color-mix(in srgb, var(--foreground) 60%, transparent);
--fp-grad-rand-2: color-mix(in srgb, var(--muted-foreground) 40%, transparent);
color: var(--background);
}
/* ── HoverRevealButton ──────────────────────────────────────────────────── */
.fp-hreveal {
position: relative;
display: inline-grid; place-items: center; overflow: hidden;
min-width: 8rem; padding: 0.5rem 1.25rem 0.5rem 2.125rem;
appearance: none; cursor: pointer; user-select: none; touch-action: manipulation;
font: inherit; font-weight: 600; line-height: 1.4;
color: var(--foreground);
background: var(--card);
border: 1px solid var(--border); border-radius: 999px;
transition: transform var(--motion-instant-duration) var(--motion-instant);
}
.fp-hreveal:disabled { opacity: 0.5; cursor: default; }
.fp-hreveal:focus-visible { outline: 2px solid var(--ring, var(--info)); outline-offset: 2px; }
/* De stip is een cirkel zo breed als de knop, links van het woord; groeien = schalen, niet meten. */
.fp-hreveal__dot {
position: absolute; left: 1.25rem; top: 50%;
width: 100%; aspect-ratio: 1;
border-radius: 50%;
background: var(--primary);
transform: translate(-50%, -50%) scale(0.06);
pointer-events: none;
transition: transform var(--motion-smooth-duration) var(--motion-smooth);
}
.fp-hreveal:hover .fp-hreveal__dot,
.fp-hreveal:focus-visible .fp-hreveal__dot,
.fp-hreveal[data-pressed] .fp-hreveal__dot { transform: translate(-50%, -50%) scale(2.2); }
.fp-hreveal__rust {
position: relative; display: inline-block;
transform: translateX(0.25rem);
transition:
transform var(--motion-smooth-duration) var(--motion-smooth),
opacity var(--motion-smooth-duration) var(--motion-smooth);
}
.fp-hreveal:hover .fp-hreveal__rust,
.fp-hreveal:focus-visible .fp-hreveal__rust,
.fp-hreveal[data-pressed] .fp-hreveal__rust { transform: translateX(3rem); opacity: 0; }
.fp-hreveal__onthuld {
position: absolute; inset: 0; padding-left: 0.875rem;
display: flex; align-items: center; justify-content: center; gap: 0.5rem;
color: var(--primary-foreground);
transform: translateX(3rem); opacity: 0;
transition:
transform var(--motion-smooth-duration) var(--motion-smooth),
opacity var(--motion-smooth-duration) var(--motion-smooth);
}
.fp-hreveal:hover .fp-hreveal__onthuld,
.fp-hreveal:focus-visible .fp-hreveal__onthuld,
.fp-hreveal[data-pressed] .fp-hreveal__onthuld { transform: translateX(-0.25rem); opacity: 1; }
/* ── LiquidMetalButton ──────────────────────────────────────────────────── */
.fp-metal {
position: relative; isolation: isolate;
display: inline-grid; place-items: center;
height: 2.875rem; min-width: 2.875rem; padding: 0 1.5rem;
appearance: none; border: 0; background: transparent;
cursor: pointer; user-select: none; touch-action: manipulation;
font: inherit; font-size: 0.875rem; line-height: 1;
color: var(--muted-foreground);
border-radius: 999px; overflow: hidden;
}
.fp-metal--icon { width: 2.875rem; padding: 0; }
.fp-metal:disabled { opacity: 0.5; cursor: default; }
.fp-metal:focus-visible { outline: 2px solid var(--ring, var(--info)); outline-offset: 2px; }
/* De rand: een afgeknipt vlak met daarin een te groot draaiend vierkant. */
.fp-metal__ring {
position: absolute; inset: 0; z-index: -2;
border-radius: 999px; overflow: hidden;
box-shadow:
0 0 0 1px color-mix(in srgb, var(--foreground) 14%, transparent),
var(--shadow-overlay);
transition: box-shadow var(--motion-instant-duration) var(--motion-instant);
}
.fp-metal[data-pressed] .fp-metal__ring {
box-shadow: 0 0 0 1px color-mix(in srgb, var(--foreground) 24%, transparent);
}
.fp-metal__sheen {
position: absolute; left: 50%; top: 50%;
width: 200%; aspect-ratio: 1;
translate: -50% -50%;
background: conic-gradient(
from 0deg,
var(--muted-foreground),
var(--foreground),
var(--muted),
color-mix(in srgb, var(--foreground) 70%, var(--muted-foreground)),
var(--muted-foreground),
var(--card),
var(--foreground),
var(--muted-foreground)
);
}
/* Het gezicht: van paneel naar bodem, drukt 1px in. */
.fp-metal__face {
position: absolute; inset: 2px; z-index: -1;
border-radius: 999px;
background: linear-gradient(180deg, var(--card), var(--background));
transition:
transform var(--motion-instant-duration) var(--motion-instant),
box-shadow var(--motion-instant-duration) var(--motion-instant);
}
.fp-metal[data-pressed] .fp-metal__face {
transform: translateY(1px) scale(0.98);
box-shadow: inset 0 2px 4px color-mix(in srgb, var(--foreground) 10%, transparent);
}
.fp-metal__label {
position: relative; display: inline-flex; align-items: center; gap: 0.375rem;
white-space: nowrap;
text-shadow: 0 1px 2px color-mix(in srgb, var(--background) 60%, transparent);
}
.fp-metal__ripple-plek { position: absolute; width: 0; height: 0; pointer-events: none; }
.fp-metal__ripple {
position: absolute; left: -0.625rem; top: -0.625rem;
display: block; width: 1.25rem; height: 1.25rem;
border-radius: 50%;
background: radial-gradient(circle, color-mix(in srgb, var(--foreground) 40%, transparent) 0%, transparent 70%);
}
/* ── LiquidGlassButton ──────────────────────────────────────────────────── */
.fp-glass {
position: relative; isolation: isolate;
display: inline-flex; align-items: center; justify-content: center; gap: 0.5rem;
appearance: none; border: 0; background: transparent;
cursor: pointer; user-select: none; touch-action: manipulation;
font: inherit; font-weight: 500; line-height: 1;
color: var(--foreground);
border-radius: 999px;
transition: transform var(--motion-smooth-duration) var(--motion-smooth);
}
.fp-glass--md { height: 2.25rem; padding: 0 1rem; font-size: 0.875rem; }
.fp-glass--lg { height: 2.75rem; padding: 0 1.5rem; font-size: 0.9375rem; }
.fp-glass--xl { height: 3.5rem; padding: 0 2.5rem; font-size: 1rem; }
.fp-glass:hover { transform: scale(1.05); }
.fp-glass[data-pressed] { transform: scale(0.98); }
.fp-glass:disabled { opacity: 0.5; cursor: default; }
.fp-glass:focus-visible { outline: 2px solid var(--ring, var(--info)); outline-offset: 2px; }
/* Lichtval: binnenschaduwen op de tekstkleur — licht op donker, donker op licht. */
.fp-glass__rand {
position: absolute; inset: 0; z-index: -1;
border-radius: 999px;
box-shadow:
0 0 6px color-mix(in srgb, var(--foreground) 3%, transparent),
0 2px 6px color-mix(in srgb, var(--foreground) 8%, transparent),
inset 3px 3px 0.5px -3px color-mix(in srgb, var(--foreground) 90%, transparent),
inset -3px -3px 0.5px -3px color-mix(in srgb, var(--foreground) 85%, transparent),
inset 1px 1px 1px -0.5px color-mix(in srgb, var(--foreground) 60%, transparent),
inset -1px -1px 1px -0.5px color-mix(in srgb, var(--foreground) 60%, transparent),
inset 0 0 6px 6px color-mix(in srgb, var(--foreground) 12%, transparent),
inset 0 0 2px 2px color-mix(in srgb, var(--foreground) 6%, transparent),
0 0 12px color-mix(in srgb, var(--background) 15%, transparent);
}
/* De breking; zonder SVG-backdrop (Safari/Firefox) blijft deze blur staan. */
.fp-glass__laag {
position: absolute; inset: 0; z-index: -2;
border-radius: 999px; overflow: hidden;
-webkit-backdrop-filter: blur(6px);
backdrop-filter: blur(6px);
}
.fp-glass__inhoud { position: relative; pointer-events: none; }
.fp-glass__filter { position: absolute; width: 0; height: 0; overflow: hidden; }
/* ── DancingLetters ─────────────────────────────────────────────────────── */
.fp-dance {
display: inline-flex; flex-wrap: wrap; justify-content: center;
perspective: 1000px;
user-select: none;
color: inherit; font: inherit; font-weight: 700;
}
.fp-dance__letter {
position: relative; display: inline-block;
transform-style: preserve-3d;
cursor: pointer; white-space: pre;
}
.fp-dance__letter[data-bezig] { z-index: 1; }
/* ── ShiningText ────────────────────────────────────────────────────────── */
.fp-shine {
display: inline-block;
color: transparent;
background-image: linear-gradient(110deg, var(--muted-foreground) 35%, var(--foreground) 50%, var(--muted-foreground) 75%);
background-size: 200% 100%;
-webkit-background-clip: text;
background-clip: text;
}
/* ── Marquee ────────────────────────────────────────────────────────────── */
.fp-marquee {
--fp-marquee-gap: 2rem;
display: flex; width: 100%; overflow: hidden;
}
.fp-marquee--verticaal { flex-direction: column; }
.fp-marquee__band {
display: flex; flex-shrink: 0;
gap: var(--fp-marquee-gap);
padding-right: var(--fp-marquee-gap); /* zodat de naad tussen kopie A en B dezelfde ruimte heeft */
will-change: transform;
}
.fp-marquee--verticaal .fp-marquee__band { flex-direction: column; padding-right: 0; padding-bottom: var(--fp-marquee-gap); }
.fp-marquee__item { display: flex; flex-shrink: 0; align-items: center; }
.fp-marquee--verticaal .fp-marquee__item { width: 100%; }
/* ── ImagesBadge ────────────────────────────────────────────────────────── */
.fp-badge {
display: inline-flex; align-items: center;
border-radius: 999px;
border: 1px solid color-mix(in srgb, var(--border) 60%, transparent);
background: color-mix(in srgb, var(--card) 90%, transparent);
-webkit-backdrop-filter: blur(4px);
backdrop-filter: blur(4px);
box-shadow: var(--shadow-overlay);
color: var(--foreground);
user-select: none; cursor: default;
}
.fp-badge--tapbaar { cursor: pointer; }
.fp-badge--tapbaar:focus-visible { outline: 2px solid var(--ring, var(--info)); outline-offset: 2px; }
.fp-badge--sm { height: 2.25rem; padding: 0 0.875rem 0 0.5rem; gap: 0.5rem; font-size: 0.6875rem; }
.fp-badge--md { height: 2.75rem; padding: 0 1rem 0 0.625rem; gap: 0.625rem; font-size: 0.75rem; }
.fp-badge--lg { height: 3.5rem; padding: 0 1.25rem 0 0.75rem; gap: 0.75rem; font-size: 0.875rem; }
.fp-badge__strook { position: relative; flex-shrink: 0; }
.fp-badge__kaart {
position: absolute; top: 0.375rem; left: 0;
width: var(--fp-badge-px); height: var(--fp-badge-px);
display: flex; align-items: center; justify-content: center;
flex-shrink: 0; overflow: hidden;
border: 2px solid var(--card);
box-shadow: 0 2px 8px color-mix(in srgb, var(--foreground) 18%, transparent);
background: var(--muted);
}
.fp-badge__kaart img { width: 100%; height: 100%; object-fit: cover; display: block; }
.fp-badge__kaart--circle, .fp-badge__kaart--teller { border-radius: 50%; }
.fp-badge__kaart--rounded { border-radius: var(--radius); }
.fp-badge__kaart--square { border-radius: calc(var(--radius) / 3); }
.fp-badge__rest { font-weight: 600; color: var(--muted-foreground); font-variant-numeric: tabular-nums; }
.fp-badge__label { white-space: nowrap; font-weight: 500; line-height: 1; color: color-mix(in srgb, var(--foreground) 80%, transparent); }
/* ── Timeline ───────────────────────────────────────────────────────────── */
.fp-timeline {
list-style: none; margin: 0; padding: 0;
display: flex; flex-direction: column;
color: var(--foreground);
}
.fp-timeline--compact { gap: 0.5rem; }
.fp-timeline--default { gap: 1rem; }
.fp-timeline--spacious { gap: 2rem; }
.fp-timeline--horizontal { flex-direction: row; overflow-x: auto; padding-bottom: 1rem; scrollbar-width: thin; }
.fp-timeline__item { position: relative; display: flex; gap: 0.75rem; padding-bottom: 0.5rem; }
.fp-timeline--horizontal .fp-timeline__item { flex-direction: column; min-width: 16rem; flex-shrink: 0; }
/* De lijn naar de volgende stap draagt de status van deze stap. */
.fp-timeline__lijn { position: absolute; background: var(--border); }
.fp-timeline--vertical .fp-timeline__lijn { left: 0.75rem; top: 2.25rem; bottom: -1rem; width: 1px; }
.fp-timeline--horizontal .fp-timeline__lijn { top: 0.75rem; left: 2rem; right: -1rem; height: 1px; }
.fp-timeline__item[data-status="klaar"] .fp-timeline__lijn,
.fp-timeline__item[data-status="bezig"] .fp-timeline__lijn { background: var(--primary); }
.fp-timeline__item[data-status="wacht"] .fp-timeline__lijn { background: color-mix(in srgb, var(--muted-foreground) 30%, transparent); }
.fp-timeline__item[data-status="fout"] .fp-timeline__lijn { background: var(--danger); }
.fp-timeline__stip-vak { position: relative; z-index: 1; display: flex; flex-shrink: 0; }
.fp-timeline__stip {
display: flex; align-items: center; justify-content: center;
width: 1.5rem; height: 1.5rem; flex-shrink: 0;
border-radius: 50%; border: 2px solid var(--border);
background: var(--background); color: var(--muted-foreground);
font-size: 0.75rem; font-weight: 500;
}
.fp-timeline__stipje { display: block; width: 0.5rem; height: 0.5rem; border-radius: 50%; background: currentColor; }
.fp-timeline__item[data-status="klaar"] .fp-timeline__stip { border-color: var(--primary); background: var(--primary); color: var(--primary-foreground); }
.fp-timeline__item[data-status="bezig"] .fp-timeline__stip { border-color: var(--primary); color: var(--primary); }
.fp-timeline__item[data-status="wacht"] .fp-timeline__stip { border-color: color-mix(in srgb, var(--muted-foreground) 30%, transparent); }
.fp-timeline__item[data-status="fout"] .fp-timeline__stip { border-color: var(--danger); background: var(--danger); color: var(--primary-foreground); }
.fp-timeline__inhoud { display: flex; flex-direction: column; gap: 0.375rem; min-width: 0; flex: 1; }
.fp-timeline__kop { display: flex; align-items: flex-start; justify-content: space-between; gap: 0.5rem; }
.fp-timeline__titel { margin: 0; font: inherit; font-weight: 500; line-height: 1.25; }
.fp-timeline__tekst { margin: 0; font-size: 0.875rem; line-height: 1.55; color: var(--muted-foreground); }
.fp-timeline__tijd { flex-shrink: 0; font-size: 0.75rem; color: var(--muted-foreground); font-variant-numeric: tabular-nums; }
.fp-timeline__extra { margin-top: 0.375rem; }
/* ── Accordion ──────────────────────────────────────────────────────────── */
.fp-acc { width: 100%; color: var(--foreground); }
.fp-acc__item { border-bottom: 1px solid var(--border); }
.fp-acc__kop { margin: 0; font: inherit; }
.fp-acc__knop {
display: flex; align-items: flex-start; gap: 1rem; width: 100%;
padding: 1rem 0;
appearance: none; background: transparent; border: 0;
cursor: pointer; text-align: left;
font: inherit; color: inherit;
transition: color var(--motion-default-duration) var(--motion-default);
}
.fp-acc__knop:focus-visible { outline: 2px solid var(--ring, var(--info)); outline-offset: 2px; }
.fp-acc__titel { flex: 1; }
.fp-acc__vak { overflow: hidden; }
.fp-acc__inhoud { padding: 0 0 1.5rem; color: var(--muted-foreground); line-height: 1.6; }
/* display: de bron — genummerd, kapitalen, gedempt tot je hem opent. */
.fp-acc--display .fp-acc__knop { padding: 1.25rem 0 1.25rem 1.5rem; color: color-mix(in srgb, var(--foreground) 22%, transparent); }
.fp-acc--display .fp-acc__knop:hover { color: color-mix(in srgb, var(--foreground) 55%, transparent); }
.fp-acc--display .fp-acc__item[data-open] .fp-acc__knop { color: var(--primary); }
.fp-acc--display .fp-acc__nummer { font-size: 0.75rem; line-height: 1.6; font-variant-numeric: tabular-nums; }
.fp-acc--display .fp-acc__titel { text-transform: uppercase; font-size: clamp(1.75rem, 4.5vw, 3rem); line-height: 1.05; letter-spacing: -0.01em; }
.fp-acc--display .fp-acc__inhoud { padding: 0 0 1.5rem 3.5rem; }
@media (min-width: 768px) {
.fp-acc--display .fp-acc__knop { padding-left: 3.5rem; }
.fp-acc--display .fp-acc__inhoud { padding: 0 5rem 1.5rem; }
}
/* plain: lijstje met chevron. */
.fp-acc--plain .fp-acc__knop { align-items: center; font-weight: 500; }
.fp-acc--plain .fp-acc__knop:hover { color: var(--primary); }
.fp-acc__chevron { flex-shrink: 0; transition: transform var(--motion-default-duration) var(--motion-default); }
.fp-acc__item[data-open] .fp-acc__chevron { transform: rotate(180deg); }
/* ── LinkPreview ────────────────────────────────────────────────────────── */
.fp-lpreview { position: relative; display: inline-block; }
.fp-lpreview__link { color: inherit; font-weight: 600; text-decoration: underline; text-decoration-color: color-mix(in srgb, currentColor 35%, transparent); text-underline-offset: 0.15em; }
.fp-lpreview__link:hover { text-decoration-color: currentColor; }
.fp-lpreview__link:focus-visible { outline: 2px solid var(--ring, var(--info)); outline-offset: 2px; border-radius: calc(var(--radius) / 3); }
/* Het kaartje hangt boven de link, gecentreerd; transform-origin onderaan zodat het uit de link groeit. */
.fp-lpreview__kaart {
position: absolute; bottom: calc(100% + 0.625rem); left: 50%;
margin-left: calc(var(--fp-lpreview-w, 200px) / -2 - 0.25rem);
z-index: 20;
display: block; padding: 0.25rem;
background: var(--card); border: 1px solid var(--border);
border-radius: var(--radius);
box-shadow: var(--shadow-overlay);
transform-origin: bottom center;
pointer-events: none;
font-size: 0;
}
.fp-lpreview__kaart img { display: block; border-radius: calc(var(--radius) - 0.25rem); object-fit: cover; }
/* ── RadioGlider ────────────────────────────────────────────────────────── */
.fp-glider { position: relative; display: flex; flex-direction: column; padding-left: 0.75rem; color: var(--foreground); }
.fp-glider__optie { position: relative; z-index: 2; padding: 0.25rem 0; }
.fp-glider__input { position: absolute; inset: 0; width: 100%; height: 100%; margin: 0; opacity: 0; cursor: pointer; z-index: 3; appearance: none; }
.fp-glider__input:disabled { cursor: default; }
.fp-glider__label {
display: block; padding: 0.5rem 0.25rem; cursor: pointer;
font-size: 1.25rem; color: var(--muted-foreground);
transition: color var(--motion-default-duration) var(--motion-default);
}
.fp-glider__input:checked + .fp-glider__label { color: var(--primary); }
.fp-glider__input:disabled + .fp-glider__label { opacity: 0.5; cursor: default; }
.fp-glider__input:focus-visible + .fp-glider__label { outline: 2px solid var(--ring, var(--info)); outline-offset: 2px; border-radius: calc(var(--radius) / 2); }
/* De rail links: 1px, vervagend naar boven en onder; de streep is 1/n hoog. */
.fp-glider__rail {
position: absolute; left: 0; top: 0; bottom: 0; width: 1px;
background: linear-gradient(to bottom, transparent, var(--border), transparent);
}
.fp-glider__streep {
position: relative; display: block; width: 100%;
height: calc(100% / var(--fp-glider-n, 3));
background: linear-gradient(to bottom, transparent, var(--primary), transparent);
transition: transform var(--motion-playful-duration) var(--motion-playful);
}
.fp-glider__gloed {
position: absolute; top: 50%; left: 0; width: 300%; height: 60%;
transform: translateY(-50%);
background: var(--primary); filter: blur(10px);
}
.fp-glider__waas {
position: absolute; left: 0; top: 0; height: 100%; width: 9rem;
background: linear-gradient(to right, color-mix(in srgb, var(--primary) 10%, transparent), transparent);
}
/* ── Toggle ─────────────────────────────────────────────────────────────── */
.fp-toggle {
display: inline-flex; align-items: center; justify-content: center; gap: 0.5rem;
appearance: none; cursor: pointer; user-select: none; touch-action: manipulation;
font: inherit; font-size: 0.875rem; font-weight: 500;
color: var(--foreground); background: transparent;
border: 1px solid transparent; border-radius: var(--radius);
transition:
background-color var(--motion-instant-duration) var(--motion-instant),
color var(--motion-instant-duration) var(--motion-instant),
border-color var(--motion-instant-duration) var(--motion-instant),
transform var(--motion-instant-duration) var(--motion-instant);
}
.fp-toggle--sm { height: 2.25rem; padding: 0 0.625rem; }
.fp-toggle--md { height: 2.5rem; padding: 0 0.75rem; }
.fp-toggle--lg { height: 2.75rem; padding: 0 1.25rem; }
.fp-toggle--outline { border-color: var(--border); }
.fp-toggle:hover { background: var(--muted); color: var(--muted-foreground); }
.fp-toggle[data-on] { background: color-mix(in srgb, var(--primary) 14%, transparent); color: var(--foreground); }
.fp-toggle[data-pressed] { transform: scale(0.96); }
.fp-toggle:disabled { opacity: 0.5; cursor: default; pointer-events: none; }
.fp-toggle:focus-visible { outline: 2px solid var(--ring, var(--info)); outline-offset: 2px; }
.fp-toggle-groep { display: inline-flex; align-items: center; gap: 0.25rem; }
.fp-toggle-groep--vertical { flex-direction: column; align-items: stretch; }
.fp-toggle-groep--vertical .fp-toggle { width: 100%; }
/* ── FileDropzone ───────────────────────────────────────────────────────── */
.fp-drop { width: 100%; display: flex; flex-direction: column; gap: 0.75rem; color: var(--foreground); }
.fp-drop__vak {
position: relative; display: flex; flex-direction: column; align-items: center; justify-content: center;
padding: 2.5rem 1.5rem; cursor: pointer; text-align: center;
border: 2px dashed color-mix(in srgb, var(--muted-foreground) 25%, transparent);
border-radius: var(--radius);
transition:
border-color var(--motion-default-duration) var(--motion-default),
background-color var(--motion-default-duration) var(--motion-default);
}
.fp-drop__vak:hover { border-color: color-mix(in srgb, var(--muted-foreground) 40%, transparent); background: color-mix(in srgb, var(--muted) 30%, transparent); }
.fp-drop__vak[data-boven] { border-color: var(--primary); background: color-mix(in srgb, var(--primary) 5%, transparent); }
.fp-drop__vak[aria-disabled] { opacity: 0.5; pointer-events: none; }
.fp-drop__vak:focus-visible { outline: 2px solid var(--ring, var(--info)); outline-offset: 2px; }
.fp-drop__icoon { display: block; margin-bottom: 0.75rem; color: var(--muted-foreground); transition: color var(--motion-default-duration) var(--motion-default); }
.fp-drop__vak[data-boven] .fp-drop__icoon { color: var(--primary); }
/* De tekst wisselt in één vak: geen sprong bij "sleep" → "laat los". */
.fp-drop__tekst-vak { display: grid; }
.fp-drop__tekst { grid-area: 1 / 1; font-size: 0.875rem; font-weight: 500; }
.fp-drop__sub { margin-top: 0.25rem; font-size: 0.75rem; color: var(--muted-foreground); }
.fp-drop__fout { margin: 0; font-size: 0.875rem; color: var(--danger); }
.fp-drop__lijst { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 0.5rem; }
.fp-drop__rij {
display: flex; align-items: center; gap: 0.75rem;
padding: 0.5rem 0.75rem;
border: 1px solid var(--border); border-radius: calc(var(--radius) * 0.75);
background: color-mix(in srgb, var(--muted) 50%, transparent);
}
.fp-drop__rij-icoon { display: flex; color: var(--muted-foreground); }
.fp-drop__rij-tekst { display: flex; flex-direction: column; min-width: 0; flex: 1; }
.fp-drop__naam { font-size: 0.875rem; font-weight: 500; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.fp-drop__maat { font-size: 0.75rem; color: var(--muted-foreground); font-variant-numeric: tabular-nums; }
.fp-drop__verwijder {
display: flex; flex-shrink: 0; padding: 0.25rem;
appearance: none; border: 0; background: transparent; cursor: pointer;
color: var(--muted-foreground); border-radius: calc(var(--radius) / 2);
transition:
background-color var(--motion-instant-duration) var(--motion-instant),
color var(--motion-instant-duration) var(--motion-instant),
transform var(--motion-instant-duration) var(--motion-instant);
}
.fp-drop__verwijder:hover { background: var(--muted); color: var(--foreground); transform: scale(1.1); }
.fp-drop__verwijder[data-pressed] { transform: scale(0.9); }
.fp-drop__verwijder:focus-visible { outline: 2px solid var(--ring, var(--info)); }
/* ── ColorPicker ────────────────────────────────────────────────────────── */
.fp-picker { position: relative; display: grid; place-items: center; width: 16rem; height: 16rem; }
.fp-picker__ring { position: absolute; inset: 0; border-radius: 50%; }
.fp-picker__schijf { position: absolute; inset: 0; border-radius: 50%; background: var(--card); }
.fp-picker__vlak { position: relative; z-index: 1; display: grid; place-items: center; width: 100%; height: 100%; border-radius: 50%; }
.fp-picker__stip {
position: absolute; left: 50%; top: 50%;
width: 0.875rem; height: 0.875rem; margin: -0.4375rem 0 0 -0.4375rem;
appearance: none; border: 0; padding: 0; cursor: pointer;
border-radius: 50%;
box-shadow: 0 1px 2px color-mix(in srgb, var(--foreground) 20%, transparent);
}
.fp-picker__stip:focus-visible { outline: 2px solid var(--ring, var(--info)); outline-offset: 2px; }
.fp-picker__stip[aria-pressed="true"] { box-shadow: 0 0 0 2px var(--card), 0 0 0 3.5px var(--foreground); }
.fp-picker__stip-ring {
position: absolute; inset: -0.25rem; border-radius: 50%;
border: 2px solid currentColor; opacity: 0;
transition: opacity var(--motion-instant-duration) var(--motion-instant);
}
.fp-picker__stip:hover .fp-picker__stip-ring { opacity: 0.4; }
/* ── Combobox ───────────────────────────────────────────────────────────── */
.fp-combo { display: flex; flex-direction: column; gap: 0.375rem; width: 100%; max-width: 20rem; }
.fp-combo .fp-popover-anchor { display: block; }
.fp-combo__knop { width: 100%; justify-content: space-between; gap: 0.5rem; font-weight: 400; }
.fp-combo__knop svg { flex-shrink: 0; color: var(--muted-foreground); }
.fp-combo__waarde { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.fp-combo__waarde--leeg { color: var(--muted-foreground); }
.fp-combo .fp-popover { width: 100%; padding: 0; }
.fp-combo__paneel { display: flex; flex-direction: column; }
.fp-combo__zoekrij { display: flex; align-items: center; gap: 0.5rem; padding: 0 0.75rem; border-bottom: 1px solid var(--border); color: var(--muted-foreground); }
.fp-combo__zoek {
flex: 1; min-width: 0; height: 2.5rem;
appearance: none; border: 0; background: transparent; outline: none;
font: inherit; font-size: 0.875rem; color: var(--foreground);
}
.fp-combo__zoek::placeholder { color: var(--muted-foreground); }
.fp-combo__lijst { list-style: none; margin: 0; padding: 0.25rem; max-height: 18rem; overflow-y: auto; }
.fp-combo__optie {
display: flex; align-items: center; justify-content: space-between; gap: 0.5rem;
padding: 0.375rem 0.5rem; cursor: pointer;
font-size: 0.875rem; color: var(--foreground);
border-radius: calc(var(--radius) / 2);
transition: background-color var(--motion-instant-duration) var(--motion-instant);
}
.fp-combo__optie[data-actief], .fp-combo__optie:hover { background: var(--muted); }
.fp-combo__optie[aria-disabled] { opacity: 0.5; pointer-events: none; }
.fp-combo__optie-tekst { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.fp-combo__optie svg { flex-shrink: 0; color: var(--primary); }
.fp-combo__leeg { padding: 1.5rem 0; text-align: center; font-size: 0.875rem; color: var(--muted-foreground); }
/* ── Calendar (één datum) ───────────────────────────────────────────────── */
.fp-cal1 { display: inline-flex; flex-direction: column; gap: 0.5rem; padding: 0.75rem; color: var(--foreground); }
.fp-cal1__kop { display: flex; align-items: center; gap: 0.25rem; }
.fp-cal1__titel { flex: 1; display: flex; justify-content: center; align-items: center; gap: 0.375rem; font-size: 0.875rem; font-weight: 500; }
.fp-cal1__label { padding: 0 0.25rem; }
.fp-cal1__select {
appearance: none; -webkit-appearance: none;
font: inherit; font-size: 0.875rem; font-weight: 500; color: var(--foreground);
background: var(--muted); border: 1px solid var(--border); border-radius: calc(var(--radius) / 2);
padding: 0.25rem 1.5rem 0.25rem 0.5rem; cursor: pointer;
background-image: linear-gradient(45deg, transparent 50%, currentColor 50%), linear-gradient(135deg, currentColor 50%, transparent 50%);
background-position: calc(100% - 0.8rem) 55%, calc(100% - 0.55rem) 55%;
background-size: 0.25rem 0.25rem; background-repeat: no-repeat;
}
.fp-cal1__select:focus-visible { outline: 2px solid var(--ring, var(--info)); outline-offset: 1px; }
.fp-cal1__pijl {
display: flex; align-items: center; justify-content: center; width: 2.25rem; height: 2.25rem;
appearance: none; border: 0; background: transparent; cursor: pointer;
color: var(--muted-foreground); border-radius: calc(var(--radius) / 2);
transition: background-color var(--motion-instant-duration) var(--motion-instant), color var(--motion-instant-duration) var(--motion-instant), transform var(--motion-instant-duration) var(--motion-instant);
}
.fp-cal1__pijl:hover { background: var(--muted); color: var(--foreground); }
.fp-cal1__pijl[data-pressed] { transform: scale(0.92); }
.fp-cal1__pijl:disabled { opacity: 0.3; cursor: default; }
.fp-cal1__pijl:focus-visible { outline: 2px solid var(--ring, var(--info)); outline-offset: 2px; }
.fp-cal1__raster { display: grid; grid-template-columns: repeat(7, 2.25rem); gap: 0.125rem; }
.fp-cal1__dagnaam { display: flex; align-items: center; justify-content: center; height: 2rem; font-size: 0.75rem; font-weight: 500; color: var(--muted-foreground); }
.fp-cal1__dag {
position: relative; display: flex; align-items: center; justify-content: center;
width: 2.25rem; height: 2.25rem;
appearance: none; border: 1px solid transparent; background: transparent; cursor: pointer;
font: inherit; font-size: 0.875rem; color: var(--foreground);
border-radius: calc(var(--radius) / 2);
font-variant-numeric: tabular-nums;
transition: background-color var(--motion-instant-duration) var(--motion-instant), color var(--motion-instant-duration) var(--motion-instant), transform var(--motion-instant-duration) var(--motion-instant);
}
.fp-cal1__dag:hover { background: var(--muted); }
.fp-cal1__dag[data-pressed] { transform: scale(0.94); }
.fp-cal1__dag[data-buiten] { color: var(--muted-foreground); opacity: 0.55; }
.fp-cal1__dag:disabled { opacity: 0.3; cursor: default; text-decoration: line-through; }
.fp-cal1__dag[data-gekozen] { background: var(--primary); color: var(--primary-foreground); }
/* Vandaag: een stip onder het cijfer; op de gekozen dag in de tegenkleur. */
.fp-cal1__dag[data-vandaag]::after { content: ""; position: absolute; bottom: 0.25rem; left: 50%; width: 3px; height: 3px; border-radius: 50%; background: var(--primary); transform: translateX(-50%); }
.fp-cal1__dag[data-vandaag][data-gekozen]::after { background: var(--primary-foreground); }
.fp-cal1__dag:focus-visible { outline: 2px solid var(--ring, var(--info)); outline-offset: 1px; z-index: 1; }
/* ── Kbd ────────────────────────────────────────────────────────────────── */
.fp-kbd {
display: inline-flex; align-items: center; justify-content: center;
height: 1.25rem; min-width: 1.25rem; padding: 0 0.25rem;
font: inherit; font-size: 0.6875rem; font-weight: 500; line-height: 1;
color: var(--muted-foreground); background: var(--muted);
border: 1px solid var(--border); border-radius: calc(var(--radius) / 2);
pointer-events: none; user-select: none;
}
.fp-kbd-groep { display: inline-flex; align-items: center; gap: 0.25rem; }
/* ── SearchField ────────────────────────────────────────────────────────── */
.fp-search { width: 100%; }
.fp-search--label-verborgen .fp-field__label { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0; }
.fp-search .fp-input--met-trailing { padding-right: 4.25rem; }
/* ── TextRoll ───────────────────────────────────────────────────────────── */
.fp-roll { position: relative; display: inline-block; overflow: hidden; line-height: 0.85; padding-bottom: 0.08em; cursor: pointer; }
.fp-roll__laag { display: block; white-space: pre; }
.fp-roll__laag--onder { position: absolute; inset: 0; }
.fp-roll__letter { display: inline-block; will-change: transform; }
/* ── Menubar ────────────────────────────────────────────────────────────── */
.fp-mbar {
display: inline-flex; align-items: center; gap: 0.25rem;
height: 2.5rem; padding: 0.25rem;
background: var(--background); color: var(--foreground);
border: 1px solid var(--border); border-radius: var(--radius);
}
.fp-mbar__popover .fp-popover { min-width: 12rem; padding: 0.25rem; }
.fp-mbar__trigger {
display: inline-flex; align-items: center; gap: 0.5rem;
padding: 0.375rem 0.75rem;
appearance: none; border: 0; background: transparent; cursor: default;
font: inherit; font-size: 0.875rem; font-weight: 500; color: var(--foreground);
border-radius: calc(var(--radius) / 2); user-select: none;
transition: background-color var(--motion-instant-duration) var(--motion-instant);
}
.fp-mbar__trigger:hover, .fp-mbar__trigger[data-open], .fp-mbar__trigger:focus-visible { background: var(--muted); outline: none; }
.fp-mbar__menu { list-style: none; margin: 0; padding: 0; }
.fp-mbar__item {
position: relative; display: flex; align-items: center; gap: 0.5rem;
padding: 0.375rem 0.5rem; cursor: default; user-select: none;
font-size: 0.875rem; border-radius: calc(var(--radius) / 2);
transition: background-color var(--motion-instant-duration) var(--motion-instant);
}
.fp-mbar__item--vink { padding-left: 2rem; }
.fp-mbar__item:hover, .fp-mbar__item[data-actief] { background: var(--muted); }
.fp-mbar__item[aria-disabled] { opacity: 0.5; pointer-events: none; }
.fp-mbar__vink { position: absolute; left: 0.5rem; display: flex; width: 0.875rem; height: 0.875rem; align-items: center; justify-content: center; }
.fp-mbar__stip { width: 0.5rem; height: 0.5rem; border-radius: 50%; background: currentColor; }
.fp-mbar__icoon { display: flex; width: 1rem; height: 1rem; flex-shrink: 0; color: var(--muted-foreground); }
.fp-mbar__label { flex: 1; }
.fp-mbar__sneltoets { margin-left: auto; font-size: 0.75rem; letter-spacing: 0.1em; color: var(--muted-foreground); }
.fp-mbar__scheiding { height: 1px; margin: 0.25rem -0.25rem; background: var(--border); }
.fp-mbar__kopje { padding: 0.375rem 0.5rem; font-size: 0.75rem; font-weight: 600; color: var(--muted-foreground); }
/* ── ReadingProgress ────────────────────────────────────────────────────── */
.fp-lees { display: flex; align-items: center; gap: 0.75rem; width: 100%; color: var(--foreground); }
.fp-lees__rail {
flex: 1; min-width: 0; padding: 2px;
background: var(--muted); border-radius: calc(var(--radius) / 2);
box-shadow: inset 0 1px 2px color-mix(in srgb, var(--foreground) 7%, transparent);
}
.fp-lees__vul { display: block; height: 3px; width: 100%; transform-origin: left; background: var(--primary); border-radius: calc(var(--radius) / 4); }
/* Drie teksten in één vak: "nog 4 min" → "Einde · 6 min" verspringt niets. */
.fp-lees__tekst { display: grid; justify-items: end; flex-shrink: 0; font-size: 0.6875rem; font-variant-numeric: tabular-nums; }
.fp-lees__tekst > * { grid-area: 1 / 1; white-space: nowrap; }
.fp-lees__meet { visibility: hidden; display: flex; align-items: center; gap: 0.25rem; }
.fp-lees__over { color: var(--muted-foreground); }
.fp-lees__klaar { display: flex; align-items: center; gap: 0.25rem; color: var(--foreground); }
.fp-lees__vinkvak { display: inline-block; width: 0.75rem; height: 0.75rem; flex-shrink: 0; }
/* ── PhoneMockup ────────────────────────────────────────────────────────── */
.fp-phone {
--fp-phone-frame: var(--foreground);
--fp-phone-screen: var(--background);
display: inline-block; box-sizing: border-box;
/* De schaal is een transform; de layout-maat schaalt mee zodat er geen leeg vlak overblijft. */
width: calc(var(--fp-phone-w) * var(--fp-phone-scale, 1)); height: calc(var(--fp-phone-h) * var(--fp-phone-scale, 1));
}
.fp-phone__lijst { transform: scale(var(--fp-phone-scale, 1)); transform-origin: top left; }
.fp-phone__lijst {
position: relative; box-sizing: border-box; overflow: hidden;
width: var(--fp-phone-w); height: var(--fp-phone-h); padding: var(--fp-phone-bezel);
border-radius: calc(var(--fp-phone-r) + var(--fp-phone-bezel));
background: linear-gradient(135deg, color-mix(in srgb, var(--fp-phone-frame) 92%, var(--background)) 0%, var(--fp-phone-frame) 40%, color-mix(in srgb, var(--fp-phone-frame) 86%, var(--background)) 100%);
}
.fp-phone--schaduw .fp-phone__lijst { box-shadow: var(--shadow-overlay); }
.fp-phone__scherm {
position: relative; overflow: hidden; width: 100%; height: 100%;
border-radius: var(--fp-phone-r);
background: var(--fp-phone-screen);
box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--fp-phone-frame) 6%, transparent), inset 0 10px 20px color-mix(in srgb, var(--fp-phone-frame) 25%, transparent);
}
.fp-phone__wallpaper { position: absolute; inset: 0; background-position: center; background-repeat: no-repeat; }
.fp-phone__uitsnede {
position: absolute; left: 50%; transform: translateX(-50%); z-index: 2;
width: var(--fp-phone-cut-w); height: var(--fp-phone-cut-h); border-radius: var(--fp-phone-cut-r);
background: var(--fp-phone-frame);
}
.fp-phone__uitsnede--island { top: 12px; }
.fp-phone__uitsnede--notch { top: 8px; border-top-left-radius: 0; border-top-right-radius: 0; }
.fp-phone__inhoud { position: absolute; inset: var(--fp-phone-top) 0 var(--fp-phone-bottom) 0; overflow: hidden; z-index: 1; display: flex; flex-direction: column; }
.fp-phone__thuis {
position: absolute; bottom: 8px; left: 50%; transform: translateX(-50%); z-index: 3;
width: 34%; max-width: 140px; height: 5px; border-radius: 999px;
background: color-mix(in srgb, var(--foreground) 60%, transparent); pointer-events: none;
}
/* ── HoverExpandGallery ─────────────────────────────────────────────────── */
.fp-hgal { display: flex; align-items: stretch; justify-content: center; gap: 0.25rem; width: 100%; }
.fp-hgal__strook {
position: relative; flex: 1 1 0; min-width: 2.5rem; overflow: hidden;
appearance: none; border: 0; padding: 0; cursor: pointer;
border-radius: calc(var(--radius) * 2); background: var(--muted);
}
.fp-hgal__strook[data-open] { flex: 6 1 0; }
.fp-hgal__strook:focus-visible { outline: 2px solid var(--ring, var(--info)); outline-offset: 2px; }
.fp-hgal__beeld { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
.fp-hgal__waas {
position: absolute; inset: 0; display: flex; align-items: flex-end; justify-content: flex-end; padding: 1rem;
background: linear-gradient(to top, color-mix(in srgb, var(--background) 60%, transparent), transparent 50%);
}
.fp-hgal__onderschrift { font-size: 0.75rem; color: color-mix(in srgb, var(--foreground) 70%, transparent); }
/* ── HoverPreviewList ───────────────────────────────────────────────────── */
.fp-hpl { position: relative; width: 100%; overflow: hidden; color: var(--foreground); background: var(--card); }
.fp-hpl__lijst { position: relative; z-index: 3; list-style: none; margin: 0; padding: 0; }
.fp-hpl__rij {
display: grid; grid-template-columns: 1fr 1fr 2fr; gap: 1rem;
padding: 0.75rem 1.5rem; cursor: default;
transition: color var(--motion-default-duration) var(--motion-default);
}
.fp-hpl__rij[data-actief] { color: var(--background); }
.fp-hpl__rij[role="button"] { cursor: pointer; }
.fp-hpl__rij:focus-visible { outline: 2px solid var(--ring, var(--info)); outline-offset: -2px; }
.fp-hpl__cel { font-size: 0.75rem; text-transform: uppercase; letter-spacing: 0.1em; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
/* De balk onder de tekst: tekstkleur, schuift met y; hoogte is de gemeten rijhoogte. */
.fp-hpl__balk { position: absolute; left: 0; right: 0; top: 0; z-index: 1; height: var(--fp-hpl-rij, 0px); background: var(--foreground); pointer-events: none; }
.fp-hpl__beelden { position: absolute; inset: 0; z-index: 2; pointer-events: none; mix-blend-mode: difference; }
.fp-hpl__beeld {
position: absolute; left: 35%; top: 50%; transform: translateY(-50%);
width: calc(19.5rem * var(--fp-hpl-schaal, 1)); height: calc(22.5rem * var(--fp-hpl-schaal, 1));
will-change: clip-path, opacity;
}
.fp-hpl__beeld img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
/* Touch: gewone lijst met beeld ernaast. */
.fp-hpl--touch { list-style: none; margin: 0; padding: 0; }
.fp-hpl__kaart { display: flex; border-bottom: 1px solid var(--border); }
.fp-hpl__kaart-tekst { display: flex; flex-direction: column; gap: 0.25rem; width: 50%; padding: 1rem; }
.fp-hpl__kaart .fp-hpl__titel { margin: 0; font-weight: 700; text-transform: uppercase; letter-spacing: 0.1em; font-size: 0.875rem; }
.fp-hpl__kaart .fp-hpl__meta { margin: 0; text-transform: uppercase; letter-spacing: 0.1em; font-size: 0.75rem; color: var(--muted-foreground); }
.fp-hpl__kaart .fp-hpl__tekst { margin: 0; font-size: 0.75rem; line-height: 1.6; color: var(--muted-foreground); }
.fp-hpl__kaart-beeld { width: 50%; aspect-ratio: 3 / 4; object-fit: cover; }
/* ── InfiniteSlider ─────────────────────────────────────────────────────── */
.fp-slider { position: relative; width: 100%; overflow: hidden; background: var(--background); color: var(--foreground); touch-action: none; }
.fp-slider__reeks { position: absolute; inset: 0; }
.fp-slider__kaart { position: absolute; inset: 0 auto auto 0; width: 100%; height: 100%; overflow: hidden; will-change: transform; }
.fp-slider__kaart img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; transform: scale(1.5); will-change: transform; }
.fp-slider__minimap { position: absolute; right: 1.5rem; top: 50%; transform: translateY(-50%); display: none; gap: 1rem; height: 250px; }
@media (min-width: 900px) { .fp-slider__minimap { display: flex; } }
.fp-slider__minimap-beelden { position: relative; width: 160px; height: 250px; overflow: hidden; border-radius: var(--radius); }
.fp-slider__minimap-beeld { position: absolute; inset: 0 auto auto 0; width: 100%; height: 250px; overflow: hidden; will-change: transform; }
.fp-slider__minimap-beeld img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; transform: scale(1.5); will-change: transform; }
.fp-slider__minimap-info { position: relative; width: 200px; height: 250px; overflow: hidden; }
.fp-slider__info { position: absolute; inset: 0 auto auto 0; width: 100%; height: 250px; display: flex; flex-direction: column; justify-content: center; gap: 0.5rem; will-change: transform; }
.fp-slider__info-rij { display: flex; justify-content: space-between; gap: 1rem; font-size: 0.75rem; text-transform: uppercase; letter-spacing: 0.08em; color: color-mix(in srgb, var(--foreground) 80%, transparent); }
.fp-slider__info-rij:first-child { font-weight: 600; color: var(--foreground); }