Kalme Design System (Astro)
Reusable tokens and components for every page of the Kalme template. The published Framer template is the reference
for look and behavior; component names follow the Framer components, and every component has the same name, props and
classes as in the Next.js version (../nextjs), so both render the same DOM. Import components by path
(../../design-system/components/cards/ProjectCard.astro); pages map content to props in src/lib/to-ui.ts. Every
component is shown on /style-guide.
Tokens
Section titled “Tokens”| File | Holds |
|---|---|
theme.css |
Colours (ink, grey, line, background, red, danger), breakpoints md 810 / lg 1200, the ticker animation |
tokens/text.ts |
Text styles: display, h1–h6, number, bigParagraph, paragraph, smallParagraph, menu, button, subtitle |
tokens/motion.ts |
springSoft (bounce 0, 0.4 s), springButton (bounce 0.2, 0.4 s), reveal (height), transitions (names for data-transition) |
Framer’s translucent colours are opacity modifiers: Black 20 → ink/20, Black 05 → ink/5. Font: Inter (Astro Fonts
API, --font-inter), letter spacing −0.03em on every style (−0.02em on button labels).
Components
Section titled “Components”| Group | Component | Framer |
|---|---|---|
| actions | Button (primary, secondary) |
Button/Primary |
| actions | ArrowLink (with or without icon), ArrowFlip |
Button/Secondary |
| actions | TextLink |
Button/Text Link |
| brand | Logo (black, white) |
Misc/Logo |
| cards | FactCard |
Fact Number |
| cards | ProjectCard |
Card/Project/Primary |
| cards | PostCard |
Card/Post |
| cards | TestimonialCard (dark, light × simple, full) |
Card/Testimonial |
| cards | ValueCard, TeamCard |
Card/Value, Card/Team |
| cards | ServiceRow, ServiceCard |
Card/Service/Secondary, Card/Service/Simple |
| data | CheckList, HighlightList, PricingCard (weekly / monthly switch) |
lists in Services, CMS rich text, Card/Pricing + Misc/Switch |
| forms | Form, FormField / FieldControl, RadioGroup, SubmitButton |
Form |
| icons | Icon (Phosphor paths in paths.ts) |
Phosphor |
| layout | Section, SectionLabel, SectionTitle (split, center), PageTitle, Divider |
Title Section, page Title, Misc/Subtitle |
| media | LoopVideo, ScrollVideo, Showreel, FillImage |
Video, Page Video, Showreel |
| navigation | Navigation, Hamburger |
Button/Menu, Button/Hamburger |
| patterns | ServiceAccordion |
Services, Card/Service/List |
| patterns | Ticker, RichText |
Ticker, CMS rich text |
| text | InlineText (**bold**, [links](/path), line breaks from content) |
— |
| utils | CurrentYear |
— |
Conventions
Section titled “Conventions”- Hover = data attributes. Mark the hoverable element
data-hover; every element that changes on hover getsdata-motion(motionStates(rest, hover)fromutils/motion.ts) and, unless it uses the default spring (springButton),data-transition="soft". Write the rest state inline (style="transform: scale(1.05)") so the page looks right before the script runs.scripts/hover.tsanimates them; touch devices and reduced motion are respected. - State = a script per component. Interactive components (
ServiceAccordion,PricingCard,Showreel,LoopVideo,ScrollVideo, forms, the header menu) hold a<script>that calls aninit…()function fromscripts/. Scripts find their elements bydata-*attributes and skip ones already set up. - Slots render once. A component that shows its slot twice (the button label flip, the ticker’s second copy) renders
it to a string with
Astro.slots.render("default")first.Tickertakes its items as<li>elements. - Hidden panels use the
hiddenattribute on elements without adisplayclass, and inlinedisplay: noneon the rest (a Tailwindflexwould overridehidden). - Text that Framer puts in its own element (a stat, a button label) gets its own
<span>here too, so the layout check (tools/qa/layout-diff) measures the same boxes; borders Framer draws inside an element use anafter:border, neverborder. - Images: content refers to
/images/<file>;FillImagefinds the file insrc/assets/imagesand serves resized WebP.