D-MED Design System
Overview
D-MED is a medical education and training platform designed for Thai-speaking healthcare professionals. The visual system is built around a fresh, clinical-yet-warm aesthetic anchored by a deep forest-green primary palette ({colors.primary} — #187534) that evokes medical trust without sterility. The design language balances institutional credibility (heavy-weight display typography, structured information architecture) with approachable humanity (friendly character illustrations, generous rounded corners, soft mint-tinted surfaces).
Brand voltage concentrates in three key locations: the {component.hero-card} headline treatment where {colors.heading-green} renders at {typography.display-xl} weight 900; the {component.button-primary} call-to-action surfaces that use solid {colors.primary} fills; and the sidebar navigation where active states glow with {colors.signature-forest}. Secondary voltage lives in accent moments—the {colors.accent-green} badge dots, notification indicators, and gradient-enhanced consent buttons.
The defining typographic move is extreme weight confidence: headings run from 700–900 weight in the Prompt typeface, a deliberate choice that gives Thai-script legibility maximum presence. This is not a shy system—it uses boldness as its primary hierarchical lever rather than size alone. Body copy sits at a comfortable 18px/500 weight, creating clear separation from display text without resorting to delicate thin weights.
Shape language is consistently soft and approachable: the smallest meaningful radius is 10px ({rounded.sm}), with most containers landing at 16–24px ({rounded.md} through {rounded.xl}). Pill shapes ({rounded.pill} at 9999px) are reserved specifically for buttons, badges, tags, and circular icon triggers—a consistent signal of "interactive element." This creates a visual grammar where roundness = actionability.
Two sub-systems operate within the broader platform: an event/course dialect (the right-rail event cards with date/location metadata, registration CTAs, and carousel pagination) and a gamification dialect (the miles/stats card with large numeric displays and progress tracking). Both share the parent system's tokens but apply them in compositionally distinct ways.
Key Characteristics
- Canvas is pure white (
{colors.canvas}) with optional mint tinting ({surface-mint}) for sectional differentiation. - Primary brand color is deep forest green (
{colors.primary}— #187534), used for CTAs, active states, and heading accents. - Display typography hits weight 900 at 80px (
{typography.display-xl}) for the hero lockup—an intentionally maximal statement. - Minimum corner radius across all components is 10px (
{rounded.sm}); most surfaces sit at 20–24px. - Sidebar navigation uses
{colors.surface-mint}background to create a persistent left-column zoning. - Buttons follow a pill-shaped convention (
{rounded.pill}or{rounded.sm}depending on context). - Shadows are extremely subtle—max opacity never exceeds 0.1 in any layer ({
extensions.shadows.lg}). - The system is Tailwind-native, with utility-class patterns evident throughout the DOM structure.
Colors
Brand & Accent
- Primary (
{colors.primary}— #187534): Deep forest green used for all primary call-to-action buttons, active navigation states, link defaults, and the hero headline color treatment. This is the single brand anchor. - Primary Dark (
{colors.primary-dark}— #145a28): Darker shade for pressed/active button states and high-emphasis interactive feedback. - Primary Hover (
{colors.primary-hover}— #1a80339): Lightened primary for hover state indication on buttons and links. - Primary Light (
{colors.primary-light}— #228b47): Mid-tone green used for decorative text accents in the hero sub-headlines (MOTIVATION, IMPROVEMENT, etc.). - Accent Green (
{colors.accent-green}— #5dba45): Bright lime-green reserved exclusively for status dots, notification badges, and "new item" indicators. - Signature Forest (
{colors.signature-forest}— #336633): A slightly muted olive-green used in body text contexts where brand color needs to be softer than primary—active sidebar labels and certain metadata.
Surface
- Canvas (
{colors.canvas}— #ffffff): Pure white baseline for the main content area, header, cards, and all elevated surfaces. - Surface Mint (
{colors.surface-mint}— #f4fbf4): Very pale green wash applied to the sidebar navigation panel and optional section backgrounds to create warm zoning. - Surface Mint Soft (
{colors.surface-mint-soft}— #eaf4ea): Slightly more saturated mint used for alternating row backgrounds or highlighted zones within content areas. - Surface Sage (
{colors.surface-sage}— #d2e8d0): Noticeable green tint for stats cards and gamification surfaces—signals "progress/tracking" zone. - Surface Pale (
{colors.surface-pale}— #e0eed0): Yellowish-green tint observed in certain card backgrounds, likely for course-category differentiation. - Surface Gray (
{colors.surface-gray}— #e2e8f0): Neutral cool gray used for disabled-looking surfaces, input borders when unfocused, and structural dividers. - Surface Elevated (
{colors.surface-elevated}— #f3f4f6): Warm neutral gray for secondary button backgrounds, tag pills, and subtle container lifts.
Text
- Ink (
{colors.ink}— #1e293b): Near-black slate for primary headings, body copy in high-contrast contexts, and the darkest text on the page. - Body (
{colors.body}— #64748b): Mid-slate gray for standard body text, descriptions, and supporting content that doesn't need maximum contrast. - Body Strong (
{colors.body-strong}— #3d4a5c): Darker-than-body tone for emphasized paragraphs, secondary button text, and metadata requiring more weight than muted but less than ink. - Muted (
{colors.muted}— #94a3b8): Medium-light slate for placeholder text, timestamps, tertiary navigation, and icon-only buttons. - Muted Soft (
{colors.muted-soft}— #cbd5e1): Very light slate for dividers, disabled text hints, and decorative line work. - On Primary (
{colors.on-primary}— #ffffff): Pure white text rendered on all{colors.primary}backgrounds—buttons, filled badges, and solid-color blocks. - On Dark (
{colors.on-dark}— #ffffff): White text reserved for any future dark-mode surfaces or overlay contexts. - Heading Green (
{colors.heading-green}— #1e5a22): Slightly lighter forest green than primary, specifically tuned for the massive 80px hero headline where it reads as authoritative yet organic.
Hairlines & Borders
- Hairline (
{colors.hairline}— #f1f5f9): Near-invisible boundary for card outlines, input fields in rest state, and subtle container edges. - Hairline Soft (
{colors.hairline-soft}— #e2e8f0): Slightly more visible than hairline, used for icon button borders and separator lines between list items. - Border Strong (
{colors.border-strong}— #dadbdd): Medium-strength gray for focused input rings, modal borders, and elements needing definite enclosure. - Border Muted (
{colors.border-muted}— #e5e7eb): Standard border color matching Tailwind's defaultborder-gray-200, used for secondary button outlines and structural frames.
Semantic
- Error (
{colors.error}— #dc2626): Red-600 equivalent for form validation errors, destructive actions, and system warnings. - Success (
{colors.success}— #16a34a): Green-600 for confirmation messages, completed states, and positive feedback indicators. - Warning (
{colors.warning}— #d97706): Amber for caution states, pending reviews, and attention-needed notices. - Info (
{colors.info}— #2563eb): Blue for informational tooltips, help text, and external-link indicators. - Link (
{colors.link}— #187534): Matches primary color—inline links inherit brand green rather than default blue. - Link Active (
{colors.link-active}— #145a28): Darker green for visited or actively-focused link states.
Typography
Font Family
The system is built entirely on Prompt, a Google Fonts typeface purpose-designed for Thai script with Latin-alphabet support. The declared fallback stack is "Prompt, Inter, Kanit, Sarabun, system-ui, sans-serif"—this four-font safety net covers Thai (Prompt, Kanit, Sarabun) and Latin (Inter, system-ui) with graceful degradation. Prompt carries every role in the system; there is no serif or monospace face in use. This is a single-family typographic universe, which creates strong cohesion but requires careful weight management to create hierarchy.
Kanit and Sarabun appear in the stack primarily as regional alternatives if Prompt fails to load—they share similar geometric-humanist proportions suitable for Thai UI text. Inter provides Latin-script refinement for any English words mixed into the interface (the "D-MED" wordmark itself, English category labels, technical terms like "CPR & First Aid").
Hierarchy
| Token | Size | Weight | Line Height | Letter Spacing | Use |
|---|---|---|---|---|---|
| {typography.display-xl} | 80px | 900 | 80px | -2px | Hero "D-MED" lockup, stats card mega-numerals |
| {typography.display-lg} | 64px | 800 | ~70px | -1.5px | Reserved for future campaign-level headlines |
| {typography.title-lg} | 24px | 900 | 32px | 0 | Section headings, event titles, card headers |
| {typography.title-md} | 20px | 700 | 28px | 0 | Sub-section titles, navigation labels, feature names |
| {typography.title-sm} | 18px | 700 | 26px | 0 | Card titles, list item heads, compact headings |
| {typography.body-md} | 18px | 500 | 28px | 0 | Primary paragraph text, descriptions, readable prose |
| {typography.body-sm} | 16px | 400 | 24px | 0 | Secondary body, metadata, form labels |
| {typography.body-xs} | 14px | 400 | 20px | 0 | Captions, fine print, timestamp data |
| {typography.caption} | 13px | 400 | 18px | 0 | Legal text, disclaimers, micro-copy |
| {typography.button} | 16px | 600 | 1.5 | 0 | Primary button labels, major CTA text |
| {typography.button-sm} | 14px | 600 | 1.4 | 0 | Small button labels, secondary CTA text |
| {typography.label} | 13px | 600 | 18px | 0.05em | Tag pills, category badges, field labels |
| {typography.label-uppercase} | 13px | 500 | 18px | 2px | Uppercase label treatments ("MOTIVATION", "IMPROVEMENT") |
Principles
The D-MED typography system's central thesis is weight-as-hierarchy. Where many systems rely on size deltas between heading levels (48px → 32px → 24px → 20px), D-MED keeps body and small-heading sizes relatively close (24px down to 18px is only a 33% reduction) while swinging weight dramatically: 900 → 700 → 500 → 400. This works exceptionally well for Thai script, where character complexity benefits from heavier strokes at smaller sizes. A 24px/weight-900 heading reads as clearly dominant over an 18px/weight-500 paragraph, even though the size difference is modest.
The {typography.display-xl} role at 80px/900 is a signature moment—this is the only place in the entire system where type exceeds 64px, and it only appears in the hero card's brand lockup. It is deliberately oversized to create a "wow" first impression, then the system immediately settles into a more restrained scale. The negative letter-spacing (-2px) tightens what would otherwise be very loose Thai-character spacing at this size.
Uppercase Latin text appears in the hero sub-headline block ("MOTIVATION", "IMPROVEMENT", "LEARNING", "EXCELLENCE", "SKILL DEVELOPMENT") using {typography.label-uppercase} with +2px tracking. This is a rare stylistic flourish—elsewhere the system avoids all-caps in favor of natural sentence case, which is more comfortable for Thai readers who don't have a case distinction.
Line heights are consistently generous: 28px for 18px body (1.55 ratio) ensures Thai vowel marks and tone markers have vertical room. Headings use tighter ratios (80px/80px = 1.0 for display-xl) because short, bold line fragments don't need much leading.
Note on Font Substitutes
Prompt is freely available via Google Fonts and should be loaded from their CDN (the site already imports it at https://fonts.googleapis.com/css2?family=Prompt:wght@300;400;500;600;700). If Google Fonts is inaccessible in your deployment region, Noto Sans Thai is the closest open-source substitute with nearly identical metrics. For Latin-script-only fallbacks, Inter (already in the stack) or Plus Jakarta Sans provide geometric humanist compatibility. Define these as CSS variables:
--font-sans: 'Prompt', 'Inter', 'Kanit', 'Sarabun', system-ui, -apple-system, sans-serif;
No proprietary fonts are in use—all faces in this system can be legally self-hosted or loaded from CDNs without licensing concerns.
Layout
Spacing System
- Base unit: 4px (with 2px and 6px exceptions for micro-adjustments). The majority of values cluster around 8px multiples.
- Tokens:
{spacing.xxs}(2px — micro-gaps, focus ring offsets),{spacing.xs}(4px — icon internal padding, inline gaps),{spacing.xsm}(6px — non-standard but frequent for tight layouts),{spacing.sm}(8px — standard gap between related elements),{spacing.md}(12px — list item padding, compact card internals),{spacing.base}(16px — form label-to-input gaps, grid gutters),{spacing.lg}(20px — common padding inside medium containers),{spacing.xl}(24px — card internal padding, section margins),{spacing.2xl}(32px — large card padding, between sections),{spacing.3xl}(40px — hero internal spacing),{spacing.4xl}(48px — section vertical rhythm),{spacing.section}(80px — major section breakpoints). - Section padding (vertical):
{spacing.section}(80px) — applied between major content bands (hero-to-content, content-to-footer transitions). - Card internal padding: Varies by role:
{component.event-card}uses{spacing.xl}(24px),{component.notification-toast}uses{spacing.xl},{component.stats-card}uses{spacing.lg}(20px),{component.hero-card}uses{spacing.section}(80px) for dramatic breathing room. - Gutters: Desktop grid gutters sit at
{spacing.base}(16px) to{spacing.xl}(24px) depending on column density. The three-column main layout (sidebar | content | rail) uses approximately 24px inter-column gaps.
Grid & Container
- Max content width: The main content area renders at 1096px wide based on extracted container data, sitting within a viewport that supports up to 1440px header spans. This is a comfortable reading width optimized for mixed Thai/Latin text.
- Editorial density choice: The platform uses a three-zone asymmetric layout: fixed-width left sidebar (~240-280px estimated), fluid center content column (~600-700px), and fixed-width right rail (~320-360px). This is an application-style dashboard density rather than a marketing-page airy layout—information density is moderate-high.
- Specific desktop grid behavior: The hero card occupies the full center column with internal padding. The right rail contains the event card which has its own internal carousel (prev/next buttons overlaid). The left sidebar is sticky-positioned for persistent navigation access during scroll.
- Hero column splits: Within
{component.hero-card}, text content (brand name + keyword list + CTA) takes roughly the left 55% of the card, illustration takes the right 45%. This is a split-column hero rather than centered-stack.
Whitespace Philosophy
D-MED practices generous-but-purposeful whitespace. The hero card's 80px internal padding creates a "stage" feeling around the 80px headline. However, once past the hero fold, the layout tightens considerably—the three-column dashboard layout packs navigation, content feed, and auxiliary widgets into efficient proximity. Whitespace here serves zoning (separating sidebar from content, content from rail) rather than editorial luxury. Card interiors maintain 20-24px breathing room so content never feels cramped, but cards sit relatively close to each other vertically.
Header Architecture
┌─────────────────────────────────────────────────────────────────┐
│ [Logo] หน้าแรก หลักสูตร กิจกรรม สาระน่ารู้ เกียรติบัตร [เข้าสู่ระบบ] │
│ ──── │
└─────────────────────────────────────────────────────────────────┘
↑fixed, z-50, h=80px (100px xl), max-w=1440px, px=32px
The top navigation bar is fixed-positioned at the top of the viewport with z-index 50, ensuring it stays accessible during scroll. Logo anchors flush-left, navigation links spread across the center-left with an underline indicator on the active item ("หน้าแรก"), and the primary login CTA sits flush-right. At XL breakpoint (≥1280px), the bar expands from 80px to 100px height for increased touch target comfort.
Hero Section
┌──────────────────────────────────────────────────────┐
│ │
│ D-MED ┌─────────────────────┐ │
│ │ [Illustration: │ │
│ MOTIVATION │ Medical Team] │ │
│ IMPROVEMENT │ │ │
│ LEARNING └─────────────────────┘ │
│ EXCELLENCE │
│ SKILL DEVELOPMENT │
│ │
│ [ อันดับสมาชิก › ] │
│ │
└──────────────────────────────────────────────────────┘
↑white card, r=24px, p=80px, shadow=lg
The {component.hero-card} is a center-stage white card with 24px rounding and the largest shadow in the system ({extensions.shadows.lg}). The headline "D-MED" renders in {colors.heading-green} at {typography.display-xl} (80px/900). Below it, five uppercase keywords stack in {colors.primary-light} at {typography.label-uppercase} (13px/500, +2px tracking). A pill-shaped CTA button ({component.button-primary}) sits below the keywords, left-aligned. To the right, a custom illustration depicts diverse healthcare workers in scrubs and lab coats—this is flat-vector character art with no gradients, using a limited palette that complements the brand greens.
Right-Rail Event Card
┌──────────────────────────────────┐
│ 📢 ประกาศข่าวสาร │
│ ───────────────────────── │
│ [tag: หลักสูตรอบรมพื้นฐาน] │
│ │
│ **การอบรมซีพีอาร์** │
│ **ฟื้นคืนชีพ** │
│ CPR & First Aid วันที่ 15 │
│ │
│ 📅 20 ธันวาคม 2568 │
│ 🕐 08.30 - 16.30 น. │
│ 📍 ห้องประชุม 3 │
│ คณะแพทยศาสตร์ ม.อ. │
│ │
│ [< >] 👤 │
│ │
│ [ จองสำรองที่นั่ง ] │
│ │
│ ● ○ ○ ○ │
└──────────────────────────────────┘
↑white card, r=24px, p=24px, shadow=md
The {component.event-card} occupies the right rail and follows a metadata-rich card pattern: notification icon + title at top, category tag pill, event name in bold, date/time/location rows with icon prefixes, image carousel controls (circular prev/next buttons overlaid at horizontal center), and a full-width registration CTA at bottom. Pagination dots below indicate multi-slide capability.
Elevation & Depth
| Level | Treatment | Use |
|---|---|---|
| 0 — Flat | No shadow, {colors.canvas} background | Base page surface, sidebar background, flat sections |
| 1 — Hairline outline | 1px {colors.hairline} border, {extensions.shadows.xs} | Default input fields, icon button rest state, subtle container edges |
| 2 — Soft lift | {extensions.shadows.sm} (0.05 opacity, 1px blur) | Cards in rest state, notification toast, cookie banner base |
| 3 — Medium lift | {extensions.shadows.md} (0.04 opacity, 4px blur, 24px spread) | Event cards, elevated panels, dropdown menus |
| 4 — Strong lift | {extensions.shadows.lg} (dual layer, 0.1 opacity, 10+4px blur) | Hero card, modal dialogs, primary promotional surfaces |
| 5 — Overlay | {extensions.shadows["2xl"]} (0.25 opacity, 25px blur, 50px spread) | Full-screen overlays, deep modals, consent banners |
The depth philosophy is layered-flat with shadow-only elevation. There are no color-block depth techniques (no darker surface tints to imply stacking)—depth comes entirely from box-shadow. Shadows are cool-toned and neutral (all use rgba(0,0,0,x) without blue/orange tints), keeping the focus on the green brand story. Even the strongest shadow ({extensions.shadows.lg}) caps at 10% opacity, ensuring nothing feels heavy or muddy.
The system avoids inset shadows, inner glow, or neon effects entirely. If a surface needs emphasis beyond shadow, it receives a {colors.hairline} border instead. Focus rings on interactive elements use a 1px solid outline (typically {colors.border-strong} or a primary-color ring at 15-20% opacity).
Decorative Depth
Beyond functional elevation, the system employs illustration-as-depth: the hero card's character illustration creates visual layering through overlapping figures with subtle drop shadows behind each person. The illustration style is flat 2D vector art with minimal shading—depth comes from occlusion (figures standing in front/behind each other) rather than painterly technique. No gradient meshes, noise textures, or photographic depth exist in the current implementation.
Shapes
Border Radius Scale
| Token | Value | Use |
|---|---|---|
| {rounded.sm} | 10px | Primary buttons, input fields, small containers |
| {rounded.DEFAULT} / {rounded.md} | 12px | Default component radius, secondary buttons, tag pills |
| {rounded.lg} | 20px | Sidebar nav items, medium cards, panel containers |
| {rounded.xl} | 24px | Major cards (hero, event, toast, cookie banner) |
| {rounded["2xl"]} | 32px | Large featured cards, showcase panels |
| {rounded.pill} / {rounded.full} | 9999px | Icon circular buttons, badge dots, CTA pills, avatar placeholders |
The radius progression tells a semantic story: 10–12px = "standard UI element" (buttons, inputs); 20–24px = "content container" (cards, panels); 9999px = "actionable indicator" (pills, circles, badges). This tripartite vocabulary means designers can read shape language at a glance without checking token names. The system never uses sharp corners (0px)—even the smallest radius is 10px, which gives everything a softened, approachable quality befitting a healthcare education platform.
Photography Geometry
Custom vector illustrations dominate the current design; photographic imagery is absent from the above-fold view. Based on the illustration treatment observed:
- Character illustrations render as full-bleed elements within their card zones, with transparent backgrounds allowing the card's
{colors.canvas}to show through. - Aspect ratio for the hero illustration is approximately 3:2 or 4:5 portrait (taller than wide) to accommodate the team-of-five figure arrangement.
- Corner rounding does not apply to illustrations themselves—they're composited as sharp-edged assets inside rounded containers.
- Avatar-sized circular crops (implied by the user profile icon in the event card carousel) would use
{rounded.pill}(effectively circle) at approximately 40-48px diameter.
If photography were introduced (e.g., instructor headshots, event photos), the established pattern suggests rounded-corner rectangles at 16-24px for rectangular photos and perfect circles for avatars, both with potential {extensions.shadows.sm} lift.
Components
No hover states documented. Hover behavior is unreliable to extract from a single screenshot. State variants live as separate entries in the
components:frontmatter (e.g.{component.button-primary-active},{component.text-input-focus}); add hover styling at implementation time per your stack's conventions.
Buttons
{component.button-primary} — The principal call-to-action button used for login ("เข้าสู่ระบบ"), registration ("จองสำรองที่นั่ง"), and primary conversions throughout the platform. Background {colors.primary}, white text {colors.on-primary}, set in {typography.button} (16px/600), rounded to {rounded.sm} (10px), padded 10px 24px. Carries {extensions.shadows.lg} for prominent lift. State variants: {component.button-primary-hover} (lightens to {colors.primary-hover}), {component.button-primary-active} (darkens to {colors.primary-dark}). Transition uses the system's 300ms cubic-bezier easing per {extensions.motion}.
{component.button-secondary} — Neutral-toned alternative button seen in cookie consent ("เฉพาะที่จำเป็น"). Background {colors.surface-elevated}, text {colors.body-strong}, {typography.button-sm} (14px/600), {rounded.DEFAULT} (12px), padded 10px 20px. Includes a 1px {colors.border-muted} border for definition. Hover likely shifts background toward {colors.surface-gray}.
{component.button-ghost} — Minimal button for dismissive or tertiary actions. Transparent background, {colors.muted} text, {typography.body-xs}, fully rounded pill shape ({rounded.pill}), compact 6px 16px padding. Used where action exists but shouldn't compete visually (close buttons, "learn more" links styled as buttons).
{component.button-icon-circle} — Circular 32×32px trigger button for carousel navigation (prev/next arrows on the event card). White background, {colors.muted} icon color, {rounded.pill} (circle), {extensions.shadows.sm}. Includes a 1px {colors.hairline} border. On hover (inferred), text shifts to {colors.signature-forest} per CSS class hover:text-[#336633]. State variant: {component.button-icon-circle-hover} captures this color shift.
{component.event-card-button} — Registration-specific CTA inside the event card. Uses {colors.primary} background, {colors.on-primary} text, {typography.button-sm}, and notably uses {rounded.pill} (full pill) rather than {rounded.sm}—signaling it as a distinct conversion moment from the global primary button. Padded 10px 20px.
{component.cookie-banner-primary-btn} — Consent-banner accept button ("ยอมรับทั้งหมด"). Features a gradient background from {colors.primary} to a lighter primary shade (observed class bg-gradient-to-r from-primary-500 to-primary-400), white text, {typography.button-sm}, {rounded.md} (16px). This is one of few gradient instances in the system.
{component.cookie-banner-secondary-btn} — Decline/necessary-only option ("เฉพาะที่จำเป็น"). Background {colors.surface-elevated}, text {colors.body-strong}, includes 1px border, {rounded.md}, same padding as primary counterpart.
{component.stats-card-button} — Gamification CTA ("สูบอากาศ"). Solid {colors.primary} background, white text, {typography.button-sm}, pill-shaped ({rounded.pill}), wider padding 10px 24px.
Cards & Containers
{component.hero-card} — The flagship above-fold container holding the D-MED brand expression. White background ({colors.canvas}), headline text in {colors.heading-green}, {typography.display-xl} (80px/900) for "D-MED", {rounded.xl} (24px), generously padded at {spacing.section} (80px). Shadow is {extensions.shadows.lg}—the deepest in the system—to make it float above the page. Internally split ~55/45 between text column (left) and illustration (right). Contains a {component.button-primary} CTA at the bottom of the text block.
{component.event-card} — Information-dense card occupying the right rail. White background, {typography.body-sm} for metadata, {rounded.xl}, padded {spacing.xl} (24px). Internal structure: header row with megaphone icon + "ประกาศข่าวสาร"; horizontal rule divider; category tag pill ({component.event-card-label}); event title in {typography.title-lg}; date/time/location rows with icon prefixes; carousel control strip (two {component.button-icon-circle} buttons + thumbnail avatar); full-width CTA ({component.event-card-button}); pagination dots (3 dots, first active). Shadow: {extensions.shadows.md}.
{component.notification-toast} — Dismissible notification appearing above the cookie consent banner. Shows a cloud/snowflake icon, title ("การใช้งานคุกกี้..."), descriptive paragraph with an inline link ({component.notification-toast-link}), and two action buttons. White background, {rounded.xl}, padded {spacing.xl}, {extensions.shadows.lg} for prominence. Close X button in top-right corner. This is a floating overlay component, likely positioned fixed or absolute near viewport bottom.
{component.cookie-banner} — GDPR/consent management surface fixed at bottom of viewport. White background, contains legal text, primary and secondary action buttons. {rounded.xl}, {spacing.xl} padding, substantial shadow ({extensions.shadows.lg}). Wraps {component.cookie-banner-primary-btn} and {component.cookie-banner-secondary-btn} horizontally.
{component.stats-card} — Gamification widget in the lower-left showing accumulated "miles." Background {colors.surface-sage} (noticeable green tint signaling the game-mechanic zone), rounded {rounded.xl}, padded {spacing.lg}. Contains: airplane icon in a green circle, label text ("สะสมไมล์"), massive value "0" in {typography.display-xl}, unit label "MILES" in {typography.label-uppercase}, and a {component.stats-card-button} CTA at bottom. This card uses color-background differentiation (green wash) to distinguish it functionally from white content cards.
{component.tag-pill} — Small categorical label used for course/event classification (e.g., "หลักสูตรอบรมพื้นฐาน"). Background {colors.surface-elevated}, text {colors.body-strong}, {typography.caption}, fully pill-rounded, tight padding 4px 12px. Inline with surrounding text flow.
{component.badge-notification} — Status indicator dot/badge for unread counts or live indicators. Bright {colors.accent-green} (lime) background, white text, tiny {typography.caption}, pill-shaped, minimal 2px 8px padding. High visibility against any background.
{component.event-card-label} — Specialized tag variant inside the event card for categorizing the announcement type. Sits on {colors.surface-elevated}, uses {colors.signature-forest} text, {typography.label} weight 600, pill-shaped. Slightly more prominent than generic {component.tag-pill} due to semantic importance.
Inputs & Forms
{component.text-input} — Standard text entry field. White background, {colors.ink} text, {typography.body-md}, {rounded.sm} (10px), padded 10px 14px, fixed height 44px. Resting state has subtle {colors.hairline} border or none with shadow-{extensions.shadows.xs}. State variant: {component.text-input-focus} likely gains a {colors.primary} or {colors.border-strong} focus ring per Tailwind conventions (class focus-visible:ring-1 detected in button classes suggests ring-based focus pattern).
Note: No explicit <input> elements appeared in the extracted DOM for this page (which is a dashboard home, not a form), but the token is provided for forms that exist elsewhere in the application.
Navigation
{component.top-nav} — Fixed header bar spanning full viewport width. White background, 80px height (expands to 100px at ≥1280px), z-index 50 for overlay behavior. Contains logo (left), navigation menu (center-left, with active-item underline), and login CTA (right). Padding is 32px horizontal. Uses {typography.body-sm} for menu items. The active nav item ("หน้าแรก") shows an underline indicator in {colors.primary}.
{component.sidebar-nav} — Left-column persistent navigation panel. Background {colors.surface-mint} (very pale green), providing visual separation from the white content area. Contains vertically stacked navigation items with icons and Thai labels. Padded {spacing.lg} (24px) internally. Estimated width ~260-280px.
{component.sidebar-nav-item} — Individual sidebar navigation row. Transparent background (allows {colors.surface-mint} to show through), {colors.body-strong} text, {typography.body-md}, {rounded.lg} (20px) for soft tap targets, padded {spacing.md} {spacing.xl}. Each item pairs an icon (Lucide) with Thai text label. Items observed: หน้าแรก (home), หลักสูตรออนไลน์ (online courses), บัณฑิตศึกษา (graduate studies), สื่อการเรียนรู้ (learning media), LeaderBoard, สาระน่ารู้ (knowledge), ประกาศข่าวสาร (announcements), ถาม-ตอบ (Q&A).
{component.sidebar-nav-item-active} — Active/hover state for sidebar items. Text color shifts to {colors.signature-forest} (olive-green), maintaining the same geometry as default state. This color change is the sole indicator of current section—no background fill change, no left-border accent. The restraint signals confidence in the type-only interaction model.
Do's and Don'ts
Do
- Do use
{colors.primary}(#187534) for all primary CTAs and active navigation states—this is the single source of brand color truth. - Do reserve
{colors.heading-green}(#1e5a22) specifically for the hero headline and other 80px+ display text where a slightly lighter green improves readability at massive sizes. - Do apply
{rounded.sm}(10px) as the minimum radius for any rectangular element—never use 0px sharp corners anywhere in the system. - Do use
{typography.display-xl}(80px/900) exclusively for the "D-MED" brand lockup or stats-card numerals; it is too heavy for general headings. - Do leverage
{colors.surface-mint}(#f4fbf4) for sidebar or sectional backgrounds when you need warmth without adding a new color to the palette. - Do pair
{colors.signature-forest}(#336633) with{typography.body-md}for active sidebar labels—it provides clear affordance without shouting. - Do use
{rounded.pill}(9999px) for buttons that represent "conversion" or "completion" actions (registration, acceptance, gamification CTAs) to differentiate them from utility buttons. - Do keep card shadows at or below
{extensions.shadows.lg}intensity—even the hero card doesn't exceed 10% shadow opacity. - Do maintain 28px line-height for 18px body text ({
typography.body-md}) to ensure Thai vowel mark clearance. - Do use
{typography.label-uppercase}(+2px letter-spacing) only for the five hero keywords or category labels—avoid all-casual elsewhere in Thai content.
Don't
- Don't substitute
{colors.link}(#187534) with blue—links in this system always carry the brand green, not browser-default blue. - Don't reduce heading weight below 700 for any title role—
{typography.title-md}starts at 700 and goes up to 900; lighter weights break the system's hierarchy strategy. - Don't add gradient backgrounds to cards or buttons unless implementing the specific cookie-banner accept button pattern—gradients are exceptional, not routine.
- Don't use
{colors.accent-green}(#5dba45) for anything except notification dots and status badges—its brightness will overwhelm if applied to larger surfaces. - Don't expand the radius vocabulary beyond the seven defined tokens (
{rounded.sm}through{rounded.pill})—arbitrary radii like 8px or 14px break the shape grammar. - Don't place text directly on
{colors.primary}in any weight lighter than 600—{colors.on-primary}(white) must always be the foreground, and it requires sufficient weight for readability. - Don't mix serif or monospace typefaces into the interface—this is a single-family (Prompt) system; introducing a second family breaks the cohesive Thai-script treatment.
- Don't apply
{extensions.shadows["2xl"]}to anything except full-screen overlays or consent banners—it's too heavy for normal card elevation. - Don't use
{colors.surface-sage}(#d2e8d0) outside of gamification/metrics contexts—that green tint is semantically bound to "progress tracking." - Don't omit the
-2pxletter-spacing from{typography.display-xl}at 80px—without it, the Thai characters will feel loosely spaced and less impactful.
Motion & Animation
Transition Tokens
/* Core transition preset — used for color/background/border changes */
--transition-base: color 0.3s cubic-bezier(0.4, 0, 0.2, 1),
background-color 0.3s cubic-bezier(0.4, 0, 0.2, 1),
border-color 0.3s cubic-bezier(0.4, 0, 0.2, 1),
text-decoration-color 0.3s cubic-bezier(0.4, 0, 0.2, 1),
fill 0.3s cubic-bezier(0.4, 0, 0.2, 1),
stroke 0.3s cubic-bezier(0.4, 0, 0.2, 1);
/* Fast transition — for micro-interactions (focus rings, hover hints) */
--transition-fast: color 0.15s cubic-bezier(0.4, 0, 0.2, 1),
background-color 0.15s cubic-bezier(0.4, 0, 0.2, 1),
border-color 0.15s cubic-bezier(0.4, 0, 0.2, 1);
/* Transform-only transition — for movement/scale */
--transition-transform: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1);
/* Quick transform — for icon spins, small bounces */
--transform-fast: transform 0.15s cubic-bezier(0.4, 0, 0.2, 1);
Keyframe Animations
The following keyframes are defined in the codebase (likely inherited from Tailwind's plugin ecosystem or animation libraries):
- bounce — Standard elastic bounce effect for attention-grabbing elements (notification dots, new-badge pulses).
- ping — Radial ripple outward from a center point, typically used for "live" or "unread" indicators on avatar-like elements.
- pulse — Slow opacity oscillation (usually 0.5→1.0→0.5) for drawing eye toward important static elements.
- spin — Continuous 360° rotation, reserved for loading spinners and refresh icons.
- enter — Entrance choreography (likely fade + slide-up combination) for modal/card appearance.
- exit — Exit choreography (fade-out + slide-down) for dismissal animations.
- slideIn — Horizontal slide from off-screen edge, used for drawer/sidebar reveals or carousel slides.
- drawLine — SVG stroke-dashoffset animation for progressive line drawing (potentially unused currently).
- fadeIn — Simple 0→1 opacity transition for gentle appearances.
- float — Vertical bobbing motion (translateY oscillation), potentially for illustration characters or decorative elements.
- pulse-glow — Combined scale + opacity pulse, possibly for the accent-green badge glow effect.
- fade-in-up — Opacity fade combined with upward translate (0→1 opacity, 20px→0 Y offset), classic scroll-triggered entrance.
- fade-in-fast — Shortened version of fade-in-up for rapid sequential reveals (150ms vs 300ms).
- nprogress-spinner — Page-load progress bar spinner rotation, tied to route-change loading states.
Interaction Patterns
- Button hover-lift: Primary and secondary buttons likely gain a subtle Y-axis translate (-1px or -2px) on hover combined with
{extensions.shadows.lg}intensification, per Tailwind'shover:utility pattern observed in classes. - Sidebar item highlight: Active sidebar items show
{colors.signature-forest}text color; hover probably adds a{colors.surface-mint-soft}background wash within the{rounded.lg}bounds. - Icon button color-shift: The event card's prev/next circular buttons transition from
{colors.muted}to{colors.signature-forest}on hover (explicitly declared in extracted classes:hover:text-[#336633]). - Carousel slide transition: The event card's image carousel uses
transform 0.3s cubic-bezier(0.4, 0, 0.2, 1)for smooth horizontal slide-between transitions. - Toast/banner slide-up: The notification toast and cookie consent banner likely animate from
translateY(100%)(off-bottom) totranslateY(0)using theenter/slideInkeyframes with{extensions.motion.duration}timing. - Focus ring expansion: Interactive elements gain a 1px ring (detected
focus-visible:ring-1) on keyboard focus, likely in{colors.primary}at 15-20% opacity, with 150ms transition per--transition-fast. - Nav bar hide-on-scroll: The top nav includes
transition-transform duration-300suggesting it may translateY(-100%) to hide on downward scroll and reappear on upward scroll—a common pattern in the extracted class naming convention. - Cookie banner opacity: The consent banner's wrapper shows
opacity-10in one state (possibly initial load before JS hydration), transitioning to full opacity viatransition-all duration-300 ease-out.
Imagery Style
- Illustration-first approach: The hero section features a custom vector illustration depicting five diverse healthcare professionals (doctors, nurses, technicians) in scrubs, coats, and medical accessories, posed together in a friendly group arrangement. This establishes a character-driven, inclusive visual identity.
- Flat 2D vector technique: Illustrations use solid color fills with no gradients, minimal shading (perhaps a single shadow layer behind figures for grounding), and clean stroke outlines. The palette mirrors the brand's green spectrum plus skin-tone diversity.
- Friendly, non-clinical aesthetic: Despite being a medical platform, the illustration style leans warm and approachable—not sterile or anatomically precise. Figures smile, stand casually, and wear colorful scrubs (teal, pink, sage green, lavender).
- No photography in evidence: The above-fold view contains zero photographic imagery. All visuals are illustrated or iconographic (Lucide icons, custom SVG illustrations).
- Icon consistency: All UI icons use the Lucide library (stroke-based, 1.5-2px stroke width, 16-24px sizing), maintaining a unified line-art vocabulary alongside the illustration style.
- Color harmony: Illustrations avoid competing with the brand green by using complementary or analogous colors—teals, soft pinks, warm skin tones, and neutral whites/grays that sit comfortably beside
{colors.primary}. - Avatar placeholder pattern: User-related images (like the small figure thumbnail in the event card carousel) appear as simplified character bust shots or initials-in-circle, consistent with the illustration style.
- Full-bleed composition: The hero illustration fills its allocated card zone edge-to-edge without additional framing or masking, letting the card's
{rounded.xl}provide the shape boundary. - Scale relationship: Characters in the hero illustration are sized to feel "life-sized relative to the headline"—roughly 120-160px tall in the 80px-headline context, creating visual parity between text and image.
Icon System
- Library: Lucide (confirmed by
iconLibs: ["lucide"]in extracted framework data and class patterns matching Lucide's Tailwind plugin output). - Specific icons observed:
- Home/House (sidebar "หน้าแรก")
- Graduation Cap (sidebar "บัณฑิตศึกษา")
- Calendar (sidebar "กิจกรรม" or date display)
- Award/Ribbon (sidebar "LeaderBoard")
- Book Open (sidebar "สาระน่ารู้")
- Megaphone/Bullhorn (event card "ประกาศข่าวสาร" header)
- Clock/Time (event time display)
- Map Pin/Location (event venue)
- Chevron Right (CTA arrow "อันดับสมาชิก ›")
- Chevron Left/Right (carousel navigation arrows)
- Airplane/Airplane Takeoff (stats card "สะสมไมล์")
- Cloud/Snowflake (notification toast icon)
- X/Close (toast dismissal)
- Help Circle (sidebar "ถาม-ตอบ")
- Check/Circle Check (checkboxes in implied forms)
- Treatment: Stroke-based icons (not filled), using
currentColorfor color inheritance (typically{colors.muted}at rest, shifting to{colors.signature-forest}or{colors.primary}on interaction). Stroke width appears consistent at 1.5-2px. - Sizing: Sidebar icons are approximately 20-24px; inline metadata icons (clock, pin) are 16px; icon-button contents are 16-18px; the stats card's airplane icon is 24-28px (slightly larger for emphasis).
- Alignment: Icons sit vertically centered with their text labels, with 8-10px gap between icon and text (inferred from
gap-2Tailwind classes).
Recommended Frontend Stack
- Framework: Next.js 14+ (App Router) or Laravel Blade + Livewire
(URL indicates Laravel backend; frontend appears SPA-capable)
- Styling: Tailwind CSS v3.4+ with custom theme extension
(utility-class patterns confirmed throughout extracted DOM)
- Fonts: Google Fonts CDN: Prompt (wght 300-700),
Inter (wght 400-700), Kanit, Sarabun
(loaded via single <link> tag as observed)
- Animation: Tailwind's built-in animation utilities +
Framer Motion (for complex enter/exit sequences)
(keyframe definitions suggest animation library usage)
- Icons: Lucide React (or lucide-vue-next for Vue/Laravel stacks)
(explicitly detected in framework data)
- Component lib: shadcn/ui primitives + custom components
(button classes match shadcn's btnVariants pattern;
card/structure follows shadcn conventions)
- State mgmt: React Context / Zustand or Laravel Livewire
(depends on chosen meta-framework)
Responsive Behavior
Breakpoints
| Name | Width | Key Changes | |---|---|---| | Mobile | < 768px | Single-column stack; sidebar collapses to bottom tab-bar; hero card becomes full-width with illustration stacked below text; event card moves below content or becomes horizontally scrollable; top nav reduces to hamburger menu. | | Tablet | 768px – 1279px | Two-column layout (sidebar hidden or toggleable, content + rail); hero maintains split layout at reduced scale (60-70px headline); nav bar height locks at 80px. | | Desktop (XL) | ≥ 1280px | Full three-column layout (sidebar | content | rail); nav bar expands to 100px height; hero headline reaches full 80px; all spacing tokens apply at specified values. |
Touch Targets
{component.button-primary}: 44px effective height (10px top + 24px font approx + 10px bottom ≈ 44px), exceeding WCAG AAA minimum. Width varies by text but minimum ~140px with padding.{component.text-input}: Fixed 44px height—exactly at WCAG AAA threshold, acceptable for thumb tap.{component.button-icon-circle}: 32×32px—below WCAG AAA 44×44 recommendation. On mobile, this should expand to 44px or gain invisible padding. Currently acceptable only on desktop cursor interfaces.{component.sidebar-nav-item}: Estimated 44-48px height with 24px padding—meets accessibility standards for touch.- Top nav items: Within the 80-100px nav bar height, ample touch area available even for text-only links.
Collapsing Strategy
- Navigation: The top horizontal menu (หน้าแรก, หลักสูตร, etc.) collapses into a hamburger drawer on mobile (< 768px), likely sliding in from the right or bottom. The left sidebar either hides entirely or converts into the same drawer.
- Bottom tab bar: Extracted HTML includes
<nav class="fixed right-4 bottom-4 left-4 z-50 lg:hidden">indicating a mobile-only bottom navigation bar (safe-area-aware for notch devices) that appears only on small screens, providing quick access to core sections. - Hero column split: The 55/45 text-illustration split becomes vertical stack (text top, illustration bottom) on mobile, with the headline scaling down (probably to 48-56px) and padding reducing from 80px to
{spacing.3xl}(40px). - Event card carousel: The right-rail event card either moves below the main content feed (becoming full-width) or transforms into a horizontal swipe card at the top of the feed, maintaining its internal carousel functionality.
- Stats card: Likely repositions from left-sidebar-adjacent to within the main content flow or into a collapsible "quick-stats" section, since the sidebar disappears.
- Cookie/notification banners: Remain full-width fixed at bottom but adjust padding (reduce from
{spacing.xl}to{spacing.lg}) and may stack buttons vertically instead of horizontally on narrow screens.
Image Behavior
- Hero illustration: Scales down proportionally on mobile, maintaining aspect ratio. May switch from side-by-side to stacked (above or below headline). Should remain visible at reduced dimensions (minimum 200px width recommended).
- Event card thumbnails/carousel: Carousel slides become swipeable touch gestures; thumbnail preview may hide to save space; prev/next buttons increase to 44px touch targets.
- Avatar images: Maintain circular crop (
{rounded.pill}) at all sizes; reduce from ~40px to ~32px on mobile to fit denser layouts.
Iteration Guide
-
Initialize with the correct framework stack. Use Next.js (React) or Laravel Livewire (PHP) as the base, with Tailwind CSS v3.4+ configured with a custom theme extension that maps all tokens from this file's YAML frontmatter into
tailwind.config.jsundertheme.colors,theme.borderRadius,theme.spacing, andtheme.fontSize. -
Map every design token to a Tailwind variable before writing any component. Define
colors.primary,colors.ink,typography.display-xl,rounded.sm,spacing.xl, etc., as explicit theme keys. Never hard-code hex values, pixel measurements, or font weights in component JSX/Blade templates—always reference the token via Tailwind's utility syntax (bg-primary,text-ink,text-display-xl,rounded-sm,px-xl). -
Build components in dependency order. Start with atomic elements:
{component.button-primary}→{component.button-secondary}→{component.button-icon-circle}→{component.text-input}. Then assemble molecules:{component.event-card}(which contains buttons, tags, typography). Then organisms:{component.hero-card}(contains button + illustration + display text). Finally templates: the full three-column dashboard layout. -
Implement state variants as separate component configurations, not nested objects. In your component library, export
ButtonPrimary,ButtonPrimaryHover,ButtonPrimaryDisabled,ButtonSecondaryas distinct variants (or use avariantprop with strict enum values). The design spec documents each state as a unique frontmatter entry—mirror that structure in code. -
Apply the font-loading strategy correctly. Add the single Google Fonts
<link>tag (observed in extraction) loading Prompt (weights 300-700), Inter (300-700), Kanit (300-800), and Sarabun (300-700). Set CSS variable--font-sans: 'Prompt', 'Inter', ...and apply it tobodyand all component root elements. Do not add @font-face self-hosting unless Google Fonts is blocked in your deployment region (see Font Substitutes note). -
Respect the shape grammar unconditionally. Before shipping any PR, audit all
borderRadiusvalues: if you find8px,14px,18px, or any value not in the{rounded.*}scale, replace it with the nearest defined token. The system's softness depends on this discipline. -
Test Thai-script rendering at every heading level. Prompt handles Thai well, but verify that
{typography.display-xl}(80px/900) doesn't cause glyph overlap at your deployed font size. Adjust-2pxletter-spacing to-3pxif needed for particularly wide Thai character combinations. Test with real Thai content strings, not Latin filler text. -
Validate shadow layering against the elevation table. When placing a new card or overlay, cross-reference the
Elevation & Depthtable to pick the correct shadow token. If a surface feels "too floating," try stepping down one level (e.g.,lg→md). Never invent a new shadow value—use only the six defined in{extensions.shadows}. -
Run accessibility checks on the primary-on-white contrast ratio.
{colors.primary}(#187534) on{colors.canvas}(#ffffff) yields approximately 4.87:1 contrast, passing AA for normal text but failing AAA. For body text (18px+), AA is sufficient; for smaller text (14-16px), consider using{colors.primary-dark}(#145a28) which achieves ~6.3:1 and passes AAA. -
Animate responsibly following the motion tokens. Use
--transition-base(300ms ease) for all color/property changes,--transition-fast(150ms) for micro-interactions, and reserve--transition-transformfor spatial movements. Never exceed 300ms duration for UI transitions—this system's voice is "responsive but not playful."
Font Setup
For immediate implementation, add this to your <head>:
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=Kanit:wght@300;400;500;600;700;800&family=Prompt:wght@300;400;500;600;700&family=Sarabun:wght@300;400;500;600;700&display=swap" rel="stylesheet">
And define in your global CSS/Tailwind config:
:root {
--font-sans: 'Prompt', 'Inter', 'Kanit', 'Sarabun', system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
}
If Google Fonts is unavailable, substitute Noto Sans Thai (same weights) as the primary face and Plus Jakarta Sans for Latin support.
Known Gaps
- Hover state styling: Per the no-hover policy, all
:hoverpseudo-state styles (background color shifts, shadow intensification, Y-translate lifts) are inferred from class-name patterns but not directly observable from a static screenshot. Implementation teams must synthesize these from the documented Active/Pressed states and conventional UX patterns. - Loading/skeleton states: No loading spinners, skeleton screens, or pending-state treatments were visible in the captured page. The
nprogress-spinnerkeyframe suggests a top-bar progress indicator exists during route changes, but its visual design (color, thickness, position) wasn't captured. - Form validation styling: Error states (
{colors.error}), success checkmarks, warning banners, and input-level validation messaging weren't present on this particular page (dashboard home). Tokens are defined but component applications are extrapolated. - Dark mode: The entire system is documented in light mode (
{extensions.mode}: "light"). No dark-mode token mappings (dark surfaces, inverted text, adjusted primary saturation) were extractable from this screenshot. A dark theme would require systematic token generation. - Authenticated-only flows: Screens beyond the public/dashboard home (course player, quiz interface, certificate generator, admin panel) aren't represented. Their sub-systems (video player chrome, timer components, PDF viewer) would extend this design language significantly.
- Mobile navigation details: While the existence of a mobile bottom tab bar was confirmed via extracted HTML (
<nav class="...lg:hidden">), its exact icon arrangement, labeling, and active-state treatment weren't visible in the desktop screenshot. - Exact mint-surface hex precision: Several surface tokens (
{colors.surface-mint},{colors.surface-mint-soft},{colors.surface-sage},{colors.surface-pale}) were derived from RGB values observed in the DOM that appeared only 1-3 times each. Their perceptual accuracy is high but statistical confidence is lower than frequently-used tokens like{colors.ink}or{colors.primary}. - Gradient stop endpoints: The cookie-banner accept button uses a
bg-gradient-to-r from-primary-500 to-primary-400gradient, but the exact hex stops forprimary-500andprimary-400(Tailwind's shade notation) weren't explicitly resolved. These map approximately to{colors.primary-hover}(#1a80339) and{colors.accent-green}(#5dba45) but may differ by a few shades. - Carousel mechanics: The event card's image carousel (pagination dots, slide count, autoplay behavior, touch-swipe support) is partially inferred from control-button placement. The actual JavaScript/logic governing slide advancement isn't extractable from static CSS.