From 25873516fd623671f60da2aa5edde895ace6f161 Mon Sep 17 00:00:00 2001 From: gerteck Date: Mon, 30 Mar 2026 04:00:14 +0800 Subject: [PATCH 01/31] Clear the canvas --- docs/index.md | 344 +++-------------------------------- docs/stylesheets/custard.css | 340 +++------------------------------- 2 files changed, 47 insertions(+), 637 deletions(-) diff --git a/docs/index.md b/docs/index.md index a80082ae..62905161 100644 --- a/docs/index.md +++ b/docs/index.md @@ -2,311 +2,78 @@ title: CustardUI - Your docs, shaped for every reader -
✦ Open Source · Lightweight · Framework-Agnostic
+

**CustardUI**

-

**CustardUI**

-
+
-**Your docs, shaped for every reader.** -Memory-persistent interactivity for static sites — no backend required. - -
- -CustardUI adds tabs that sync across pages, content that hides until needed, and shareable deep links — all from a single script tag. Built for educational sites, documentation portals, and course textbooks. - - - ---- - - - - - -

See it in action

-

Static sites don't have to be static.

- -The demo below is a live CustardUI-powered page. Try interacting with the tabs, toggles, and placeholders to see how they work. - -
-
-
- Live Demo — Project Setup Guide +**Let readers customise your static web pages!**
-
-++**Hey, [[username]]!**++ Let's get your project set up. -_Your name is remembered across every page on this site — set it once, see it everywhere._ +Add CustardUI to your static website and let readers personalise their experience by hiding sections they don't need, setting their preferred view across every page, and sharing exactly the right content with others. No backend required. - -
--- - - -
- -#### Prerequisites - -Make sure these are installed before continuing. - - +

What it can do

+

Hide irrelevant sections; Collapse less relevant sections.

-```bash -brew install node git -``` -
- +something something -```bash -winget install OpenJS.NodeJS Git.Git -``` - - - -```bash -sudo apt install nodejs git -``` - - -
- -
- -↑ _peeked by default_ — visible enough to know it's there, collapsed until you actually need it --- - - -
-#### Installation +

Set the default tab in tab groups.

- - -```bash -npm install custardui -``` - - -```bash -yarn add custardui -``` +--- - - +

Tweak placeholder text to match the reader's profile.

-```bash -pnpm add custardui -``` -
-
- -
--- - - -
- -#### Configuration - -Config file location varies by OS: - - - - -``` -~/.config/custardui/config.json -``` - - - - -``` -%APPDATA%\custardui\config.json -``` - - - - -``` -~/.config/custardui/config.json -``` - - - +

Share selected parts of a page with others.

-↑ _synced with Prerequisites_ — switching OS above updates this automatically. No extra clicks. -
-
-Set `"theme": "auto"` to follow the reader's system preference. This is the most commonly missed config option. :fa-solid-link: Share this step - -
-
- -
--- - - -
- -#### Running the Project - - - - -```bash -npm run dev -``` - - - - -```bash -yarn dev -``` - - - - -```bash -pnpm dev -``` - - - - -↑ _synced with Installation_ — pick your package manager once, every code block on the site follows - -
+

Share your customisations with others.

--- - - -
- -#### Troubleshooting - -**Port already in use?** Kill the process on port 3000: - - - - -```bash -lsof -ti:3000 | xargs kill -``` - - - - -```bash -netstat -ano | findstr :3000 -taskkill /PID /F -``` +

Advanced: let adopters create ‘adaptation’ of your site.

-
- -```bash -fuser -k 3000/tcp -``` - - -
- -
- -↑ _peeked by default_ — troubleshooting is there when you need it, invisible when you don't - -
--- - - - - -

Focus & Share

-

Answer questions with a link.

-Did you notice the **Share this step** link in the demo above? Any element on the page can become a shareable, highlighted anchor — with or without an existing `id`. No more "scroll down and find the paragraph about the config file." You can even share this paragraph [right here](?cv-highlight=W3sidCI6IlAiLCJpIjoyOSwicCI6ImNvbnRlbnQtd3JhcHBlciIsInMiOiJEaWQgeW91IG5vdGljZSB0aGUgU2hhcmUgdGhpcyB0aSIsImgiOjg0Mzk0NDcxNywiaWQiOiIifV0%3D). +

How it Works

- + -**For TAs and instructors:** When a student asks a question that's already answered on the site, share a URL like: -``` -https://your-course-site.com/textbook?cv-highlight=the-element-id -``` +All settings saved in the Browser. Works with all popular browsers. +You control what can be customised. -The page loads with that section visually highlighted — drawing the student's eye straight to the answer. - --- - - - +

How to Get Started

-

Adaptations

-

One resource. Every audience.

- -Adaptations go beyond themes. They let different organizations share the same underlying content while each seeing a version tailored to them — logos, links, terminology, and all. No backend. No duplicate repositories. - -

Consider a Git mastery guide used by multiple courses:

- -
-
- -#### :fa-solid-circle: Organization A — NUS CS2103T - -Students see the CS2103T branding, TEAMMATES-specific Git workflow diagrams, and links to their course forum. The "fork and PR" model matches their exact project setup. -
-
- -#### :fa-solid-circle: Organization B — A different course - -The same guide, but with their own branding, a different recommended branching model, and links to their issue tracker. No content was duplicated — only the adaptation config differs. -
-
- - - -This is how **[git-mastery.org](https://git-mastery.org/)** uses CustardUI — a single site serving multiple audiences with zero backend infrastructure. - - - ---- - - - - - -

Getting started

-

Up and running in minutes.

- -No build step, no server to manage. Works with MarkBind, Jekyll, plain HTML — anything. +No build step, no server to manage. Works with MarkBind, Jekyll, plain HTML — anything.
@@ -322,7 +89,7 @@ Include the CustardUI CDN script in your base layout or page template.
#### Create your config -Add a `config.json` to your site root defining your toggles, tabs, and placeholders. +Add a `custardui.config.json` to your site root defining your toggles, tabs, and placeholders.
@@ -335,78 +102,13 @@ Wrap content with ``, ``, and add `[[placeholders]]` any
-```html - - -``` - -```js -// Step 2: config.json — define your toggles and tabs -{ - "config": { - "toggles": [ - { - "toggleId": "prerequisites", - "label": "Prerequisites", - "isLocal": true, - "default": "peek" - }, - ... - ], - "tabgroups": [ - { - "groupId": "os", - "tabs": [ - { "tabId": "macos", "label": "macOS" }, - { "tabId": "windows", "label": "Windows" }, - { "tabId": "linux", "label": "Linux" } - ], - ... - } - ], - "placeholders": [ - { - "name": "username", - "defaultValue": "there", - ... - } - ] - }, - ... -} -``` - -[:fa-solid-book: Full documentation →]({{baseUrl}}/authorGuide/gettingStarted.html) - - - - - - -
-
- -## Your docs, shaped for every reader. - -CustardUI is open source, free to use, and designed to slot into your existing workflow with minimal effort. +The author guide is here. - -
- + :fa-solid-book: Read the Docs + :fa-brands-github: View on GitHub
-
diff --git a/docs/stylesheets/custard.css b/docs/stylesheets/custard.css index 01bceab1..475a9c6d 100644 --- a/docs/stylesheets/custard.css +++ b/docs/stylesheets/custard.css @@ -8,247 +8,30 @@ --custard-muted: #9A7355; } -/* ===== HERO SECTION ===== */ -.cv-hero-badge { -display: inline-block; -background: var(--custard-cream); -border: 1px solid var(--custard-brown); -color: var(--custard-brown); -padding: 4px 14px; -border-radius: 20px; -font-size: 0.8rem; -font-weight: 600; -letter-spacing: 0.08em; -text-transform: uppercase; -margin-bottom: 1.2rem; +.font-with-serif { + font-family: Georgia, serif; } -.cv-hero-actions { - display: flex; - gap: 12px; - flex-wrap: wrap; - align-items: center; - margin-bottom: 1.5rem; -} - -.cv-btn-primary { - display: inline-block; - border: 1.5px solid var(--custard-brown); - color: var(--custard-brown) !important; - font-weight: 700; - padding: 12px 28px; - border-radius: 8px; - text-decoration: none !important; - font-size: 0.95rem; - transition: border-color 0.15s, background 0.15s; -} - -.cv-btn-primary:hover { - border-color: var(--custard-brown); - background: rgba(129,76,32,0.05); -} - -.cv-btn-secondary { - display: inline-block; - border: 1.5px solid rgba(129,76,32,0.3); - color: var(--custard-brown) !important; - font-weight: 600; - padding: 11px 28px; - border-radius: 8px; - text-decoration: none !important; - font-size: 0.95rem; - transition: border-color 0.15s, background 0.15s; -} - -.cv-btn-secondary:hover { - border-color: var(--custard-brown); - background: rgba(129,76,32,0.05); -} - - -/* ===== FEATURE CARDS ===== */ -.cv-feature-icon { - font-size: 1.1rem; - color: var(--custard-light-brown); - margin-bottom: 0.9rem; - opacity: 0.85; -} - -.cv-features-grid { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); - gap: 1px; - background: rgba(129,76,32,0.12); - border: 1px solid rgba(129,76,32,0.12); - border-radius: 14px; - overflow: hidden; - margin-bottom: 3rem; -} - -.cv-feature-card { - background: #fff; - padding: 2rem 1.8rem; - position: relative; - transition: background 0.2s; -} - -.cv-feature-card:hover { - background: var(--custard-cream); -} - -.cv-feature-index { - font-size: 3rem; - font-weight: 900; - color: rgba(129,76,32,0.08); - line-height: 1; - margin-bottom: 0.8rem; - font-variant-numeric: tabular-nums; - letter-spacing: -0.03em; -} - -.cv-feature-card h3 { - font-size: 0.97rem; - font-weight: 700; - color: var(--custard-dark); - margin-bottom: 0.5rem; - letter-spacing: 0.01em; -} - -.cv-feature-card p { - font-size: 0.9rem; - color: #666; - line-height: 1.65; - margin: 0; -} - -/* ===== SECTION TITLES ===== */ -.cv-section-eyebrow { -font-size: 0.78rem; -font-weight: 700; -letter-spacing: 0.12em; -text-transform: uppercase; -color: var(--custard-light-brown); -margin-bottom: 0.4rem; } .cv-section-title { -font-size: 1.8rem; -font-weight: 800; -color: var(--custard-dark); -margin-bottom: 0.6rem; -line-height: 1.2; -} - -.cv-section-lead { -font-size: 1.05rem; -color: #555; -max-width: 620px; -line-height: 1.7; -margin-bottom: 2rem; -} - -/* ===== DEMO SECTION ===== */ -.cv-demo-wrapper { - border-top: 2px solid var(--custard-yellow); - padding-top: 1.75rem; - margin-bottom: 3rem; -} - -.cv-demo-header { - display: flex; - align-items: center; - gap: 8px; - margin-bottom: 1.5rem; -} - -.cv-demo-dot { - width: 7px; - height: 7px; - border-radius: 50%; - background: var(--custard-yellow); - flex-shrink: 0; + font-size: 1.8rem; + font-weight: 800; + color: var(--custard-dark); + margin-bottom: 0.6rem; + line-height: 1.2; } -.cv-demo-label { - font-size: 0.72rem; +.cv-section-header { + font-size: 0.95rem; font-weight: 700; - letter-spacing: 0.12em; + letter-spacing: 0.05em; text-transform: uppercase; - color: var(--custard-muted); + color: var(--custard-brown); + margin-bottom: 0.4rem; } -.cv-demo-intro { - background: rgba(243, 203, 82, 0.07); - border-left: 3px solid var(--custard-yellow); - padding: 0.9rem 1.25rem; - border-radius: 0 8px 8px 0; - margin-bottom: 1.75rem; - font-size: 1.02rem; - line-height: 1.7; -} -/* ===== DEMO ANNOTATIONS ===== */ -.cv-sticky-note { - display: block; - font-size: 0.76rem; - color: var(--custard-muted); - font-style: italic; - margin: 0.4rem 0 1.2rem 0; - padding: 4px 12px; - border-left: 2px solid rgba(243, 203, 82, 0.7); - background: rgba(243, 203, 82, 0.05); - border-radius: 0 4px 4px 0; - line-height: 1.6; -} - -/* ===== HIGHLIGHT (Focus & Share) ===== */ -.highlight-target { -border-left: 3px solid var(--custard-yellow); -padding-left: 1rem; -background: rgba(242,202,85,0.08); -border-radius: 0 8px 8px 0; -} - -/* ===== ADAPTATIONS SECTION ===== */ -.cv-adaptations-split { -display: grid; -grid-template-columns: 1fr 1fr; -gap: 1.5rem; -margin-top: 1.5rem; -} - -@media (max-width: 640px) { -.cv-adaptations-split { grid-template-columns: 1fr; } -.cv-hero { padding: 2.5rem 1.5rem; } -.cv-demo-wrapper { padding: 1.5rem; } -} - -.cv-adaptation-card { -border-radius: 12px; -padding: 1.4rem; -border: 1px solid rgba(129,76,32,0.15); -} - -.cv-adaptation-card.org-a { -background: linear-gradient(135deg, #fff8ee, #fff3d6); -} - -.cv-adaptation-card.org-b { -background: linear-gradient(135deg, #f0f7ff, #e6f0fa); -} - -.cv-adaptation-card h4 { -font-size: 0.95rem; -font-weight: 700; -margin-bottom: 0.5rem; -} - -.cv-adaptation-card p { -font-size: 0.88rem; -color: #555; -line-height: 1.6; -margin: 0; -} /* ===== SETUP SECTION ===== */ .cv-setup-steps { @@ -294,103 +77,28 @@ line-height: 1.5; } -/* ===== CTA + USED BY (unified) ===== */ -.cv-cta { - background: var(--custard-cream); - border: 1px solid rgba(129,76,32,0.15); - border-radius: 16px; - overflow: hidden; - margin-top: 3rem; -} - -.cv-cta-body { - padding: 2rem 2.5rem 1.5rem; - text-align: center; -} - -.cv-cta h2 { - font-size: 1.4rem; - font-weight: 700; - color: var(--custard-dark); - margin-bottom: 0.5rem; -} - -.cv-cta p { - color: var(--custard-muted); - font-size: 0.92rem; - margin-bottom: 1.4rem; - max-width: 500px; - margin-left: auto; - margin-right: auto; - line-height: 1.7; -} - .cv-cta-actions { display: flex; gap: 12px; - justify-content: center; flex-wrap: wrap; -} - -.cv-used-by { - background: transparent; - border: none; - border-top: 1px solid rgba(129,76,32,0.12); - border-radius: 0; - padding: 1rem 2rem; - margin: 0; - display: flex; align-items: center; justify-content: center; - gap: 1.5rem; - flex-wrap: wrap; - width: 100%; - box-sizing: border-box; -} - -.cv-used-by-label { - font-size: 0.75rem; - font-weight: 700; - letter-spacing: 0.1em; - text-transform: uppercase; - color: var(--custard-muted); - white-space: nowrap; - flex-shrink: 0; -} - -.cv-used-by-divider { - width: 1px; - height: 16px; - background: rgba(129,76,32,0.2); - flex-shrink: 0; -} - -.cv-used-by-sites { - display: flex; - gap: 2rem; - flex-wrap: wrap; - align-items: center; + margin-bottom: 1.5rem; } -.cv-used-by-sites a { - display: inline-flex; - align-items: center; - gap: 0.4rem; - font-weight: 600; +.cv-btn-secondary { + display: inline-block; + border: 1.5px solid rgba(129,76,32,0.3); color: var(--custard-brown) !important; + font-weight: 600; + padding: 11px 28px; + border-radius: 8px; text-decoration: none !important; - font-size: 0.88rem; - border-bottom: 2px solid transparent; - transition: border-color 0.15s; - white-space: nowrap; - line-height: 1; -} - -.cv-used-by-sites a:hover { - border-bottom-color: var(--custard-yellow); + font-size: 0.95rem; + transition: border-color 0.15s, background 0.15s; } -.cv-used-by-sites a p { - margin: 0; - display: inline; +.cv-btn-secondary:hover { + border-color: var(--custard-brown); + background: rgba(129,76,32,0.05); } \ No newline at end of file From 90bec1f58103a703c88353c522872bc7e1ee0cc8 Mon Sep 17 00:00:00 2001 From: gerteck Date: Mon, 30 Mar 2026 12:44:33 +0800 Subject: [PATCH 02/31] Add browser boilerplates --- docs/_markbind/boilerplates/browserBox.md | 70 +++++++++ .../_markbind/boilerplates/browserBoxSplit.md | 138 ++++++++++++++++++ 2 files changed, 208 insertions(+) create mode 100644 docs/_markbind/boilerplates/browserBox.md create mode 100644 docs/_markbind/boilerplates/browserBoxSplit.md diff --git a/docs/_markbind/boilerplates/browserBox.md b/docs/_markbind/boilerplates/browserBox.md new file mode 100644 index 00000000..433ad30a --- /dev/null +++ b/docs/_markbind/boilerplates/browserBox.md @@ -0,0 +1,70 @@ + + + + + +
+
+
+
+
+
+
+
{{ url | safe}}
+
+
+ + {{ content | safe }} + +
+
+ + \ No newline at end of file diff --git a/docs/_markbind/boilerplates/browserBoxSplit.md b/docs/_markbind/boilerplates/browserBoxSplit.md new file mode 100644 index 00000000..b02988c4 --- /dev/null +++ b/docs/_markbind/boilerplates/browserBoxSplit.md @@ -0,0 +1,138 @@ + + + + + + + +
+ +
+
+
+
+
+
+
+
{{ url1 | safe }}
+
+
{{ url2 | safe }}
+
+
+
+
+
+
+
+ +
+ +
+ +{{ content1 | safe }} +
+
+ +{{ content2 | safe }} +
+ + +
+ + +
+ + \ No newline at end of file From f50482334ffdb1f91aa700947a30841963e24b11 Mon Sep 17 00:00:00 2001 From: gerteck Date: Mon, 30 Mar 2026 13:01:12 +0800 Subject: [PATCH 03/31] Add comments boilers --- docs/_markbind/boilerplates/browserBox.md | 14 +++++++++++++ .../_markbind/boilerplates/browserBoxSplit.md | 20 +++++++++++++++++++ 2 files changed, 34 insertions(+) diff --git a/docs/_markbind/boilerplates/browserBox.md b/docs/_markbind/boilerplates/browserBox.md index 433ad30a..da35bf73 100644 --- a/docs/_markbind/boilerplates/browserBox.md +++ b/docs/_markbind/boilerplates/browserBox.md @@ -3,6 +3,20 @@ + +
diff --git a/docs/_markbind/boilerplates/browserBoxSplit.md b/docs/_markbind/boilerplates/browserBoxSplit.md index b02988c4..eaa9b12e 100644 --- a/docs/_markbind/boilerplates/browserBoxSplit.md +++ b/docs/_markbind/boilerplates/browserBoxSplit.md @@ -5,6 +5,26 @@ + + + +
From 7373b298f267993d21f38fc377ff62f29f627e48 Mon Sep 17 00:00:00 2001 From: gerteck Date: Mon, 30 Mar 2026 13:01:21 +0800 Subject: [PATCH 04/31] Update css --- docs/stylesheets/custard.css | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/docs/stylesheets/custard.css b/docs/stylesheets/custard.css index 475a9c6d..24e8d8c2 100644 --- a/docs/stylesheets/custard.css +++ b/docs/stylesheets/custard.css @@ -12,8 +12,6 @@ font-family: Georgia, serif; } -} - .cv-section-title { font-size: 1.8rem; font-weight: 800; @@ -32,8 +30,6 @@ } - -/* ===== SETUP SECTION ===== */ .cv-setup-steps { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); @@ -101,4 +97,4 @@ line-height: 1.5; .cv-btn-secondary:hover { border-color: var(--custard-brown); background: rgba(129,76,32,0.05); -} \ No newline at end of file +} From 04b9310d7fd60646fbae06493e660e8ce3d4a58e Mon Sep 17 00:00:00 2001 From: gerteck Date: Mon, 30 Mar 2026 13:01:49 +0800 Subject: [PATCH 05/31] Add toggles example --- docs/custardui.config.json | 60 ++++--------------------------- docs/index.md | 72 ++++++++++++++++++++++++++++++++++++-- 2 files changed, 77 insertions(+), 55 deletions(-) diff --git a/docs/custardui.config.json b/docs/custardui.config.json index 1daf0c7f..9a3712a2 100644 --- a/docs/custardui.config.json +++ b/docs/custardui.config.json @@ -3,32 +3,16 @@ "config": { "toggles": [ { - "toggleId": "prerequisites", - "label": "Prerequisites", - "isLocal": true, - "default": "peek" - }, - { - "toggleId": "installation", - "label": "Installation", + "toggleId": "optional-exercises", + "label": "Optional Exercises", + "description": "Hide or show exercise content.", "isLocal": true, "default": "show" }, { - "toggleId": "configuration", - "label": "Configuration", - "isLocal": true, - "default": "show" - }, - { - "toggleId": "run-project", - "label": "Running the Project", - "isLocal": true, - "default": "show" - }, - { - "toggleId": "troubleshooting-setup", - "label": "Troubleshooting", + "toggleId": "additional-info", + "label": "Additional Information", + "description": "Personalize content visibility!", "isLocal": true, "default": "peek" }, @@ -39,43 +23,13 @@ "isLocal": true, "default": "peek" }, - { - "toggleId": "mac", - "label": "MacOS", - "isLocal": true, - "description": "Show content for macOS users", - "default": "peek" - }, + { "toggleId": "mac", "label": "MacOS", "isLocal": true, "description": "Show content for macOS users", "default": "peek" }, { "toggleId": "linux", "label": "Linux", "isLocal": true, "description": "Show content for Linux users" }, { "toggleId": "windows", "label": "Windows", "isLocal": true, "default": "show", "description": "Show content for Windows users" }, { "toggleId": "nus-resources", "label": "NUS Resources", "siteManaged": true, "default": "hide", "isLocal": true }, { "toggleId": "sample-resources", "label": "Sample Resources", "siteManaged": true, "default": "hide", "isLocal": true } ], "tabGroups": [ - { - "groupId": "os", - "label": "Operating System", - "description": "Select your operating system.", - "isLocal": true, - "default": "macos", - "tabs": [ - { "tabId": "macos", "label": "macOS" }, - { "tabId": "windows", "label": "Windows" }, - { "tabId": "linux", "label": "Linux" } - ] - }, - { - "groupId": "pkg", - "label": "Package Manager", - "description": "Select your preferred package manager.", - "isLocal": true, - "default": "npm", - "tabs": [ - { "tabId": "npm", "label": "npm" }, - { "tabId": "yarn", "label": "yarn" }, - { "tabId": "pnpm", "label": "pnpm" } - ] - }, { "groupId": "localTabGroup", "label": "Page specific tabgroup", diff --git a/docs/index.md b/docs/index.md index 62905161..7c7bffcf 100644 --- a/docs/index.md +++ b/docs/index.md @@ -16,15 +16,76 @@ Add CustardUI to your static website and let readers personalise their experienc --- -

What it can do

Hide irrelevant sections; Collapse less relevant sections.

+Let readers collapse what they don't need. Their choice sticks across pages and sessions. + + +https://nus-cs2103t.github.io/week6/topics +https://nus-cs2103t.github.io/week7/topics + + + +#### Week 6 — Recursion + + +
+ +Recursion is a technique where a function calls itself to solve a smaller version of the same problem. + + + +**Optional Exercises** + +Try these additional problems to reinforce your understanding of recursion. + +1. Implement `fibonacci(n)` recursively. +2. Write a recursive function to flatten a nested list. + + + + + +**Additional Information** + +Recursion can cause a stack overflow if the base case is missing or unreachable. Always verify your base case before testing. -something something + +
+ +#### Week 7 — Binary Search + + +
+ +Binary search finds an element in a sorted list by repeatedly halving the search space. + + + +**Optional Exercises** + +Practice binary search with these problems. + +1. Find the index of a target value in a sorted array. +2. Implement binary search recursively. + + + + + +**Additional Information** + +Binary search requires the list to be sorted. Applying it to an unsorted list will produce incorrect results. + + + +
+
+ --- @@ -38,6 +99,13 @@ something something

Tweak placeholder text to match the reader's profile.

+ +git-mastery.org/faq + + +Hello there! + + --- From 4773ac106cc5763715c1cc8001a4198fd2f97312 Mon Sep 17 00:00:00 2001 From: gerteck Date: Tue, 31 Mar 2026 17:56:04 +0800 Subject: [PATCH 06/31] Add tabgroup example --- docs/custardui.config.json | 11 +++ docs/index.md | 127 ++++++++++++++++++++++++++++++++++- docs/stylesheets/custard.css | 3 +- 3 files changed, 137 insertions(+), 4 deletions(-) diff --git a/docs/custardui.config.json b/docs/custardui.config.json index 9a3712a2..b53f64d1 100644 --- a/docs/custardui.config.json +++ b/docs/custardui.config.json @@ -30,6 +30,17 @@ { "toggleId": "sample-resources", "label": "Sample Resources", "siteManaged": true, "default": "hide", "isLocal": true } ], "tabGroups": [ + { + "groupId": "uml-view", + "label": "UML View", + "description": "Switch between diagram and text views.", + "isLocal": true, + "default": "text", + "tabs": [ + { "tabId": "diagram", "label": "Diagram" }, + { "tabId": "text", "label": "Text" } + ] + }, { "groupId": "localTabGroup", "label": "Page specific tabgroup", diff --git a/docs/index.md b/docs/index.md index 7c7bffcf..31da2b48 100644 --- a/docs/index.md +++ b/docs/index.md @@ -5,6 +5,8 @@

**CustardUI**

+ +
**Let readers customise your static web pages!** @@ -12,7 +14,14 @@ Add CustardUI to your static website and let readers personalise their experience by hiding sections they don't need, setting their preferred view across every page, and sharing exactly the right content with others. No backend required. + + + + + --- @@ -32,7 +41,7 @@ Let readers collapse what they don't need. Their choice sticks across pages and
-Recursion is a technique where a function calls itself to solve a smaller version of the same problem. +%%Recursion is a technique where a function calls itself to solve a smaller version of the same problem.%% @@ -53,6 +62,9 @@ Recursion can cause a stack overflow if the base case is missing or unreachable. +%%The base case is the most important part of any recursive function.%% + + @@ -62,7 +74,7 @@ Recursion can cause a stack overflow if the base case is missing or unreachable.
-Binary search finds an element in a sorted list by repeatedly halving the search space. +%%Binary search finds an element in a sorted list by repeatedly halving the search space.%% @@ -83,14 +95,100 @@ Binary search requires the list to be sorted. Applying it to an unsorted list wi +%%Time complexity: O(log n). Space complexity: O(1) for the iterative version.%% + +
+ +%%Toggle preferences sync across pages.%% + ---

Set the default tab in tab groups.

+Let readers switch to their preferred view for tabgroups once, and it follows them across pages. + + +https://cs4321.github.io/week6/software-process +https://cs4321.github.io/week7/networking + + +#### Git Branching Strategy + +%%A Git branching strategy defines how branches organise work. In a typical feature branch workflow, changes are developed in isolation and merged back to `main` only after review.%% + + + + +1. A `feature` branch is created off `main` for new work. +2. Two commits are made: the login form and its validation. +3. Meanwhile, a `hotfix` branch is cut from `main` to address a bug. +4. `hotfix` is merged back into `main` first. +5. `feature` is then merged into `main` after review. + + + + + +%%{init: { 'theme': 'neutral' } }%% +gitGraph + commit id: "Initial" + branch feature + checkout feature + commit id: "Login form" + checkout main + branch hotfix + checkout hotfix + commit id: "Fix bug" + checkout main + merge feature + + + + + +%%Branches should be short-lived. Long-running branches accumulate merge conflicts and slow down the team.%% + + + + +#### HTTP Request-Response + +%%Web interactions involve a client sending a request to a server, which may query a data source to return a structured response.%% + + + + +1. The **Client** sends a `GET /api/data` request to the **Server**. +2. The **Server** queries the **Database** using a SQL `SELECT` statement. +3. The **Database** returns the matching rows to the **Server**. +4. The **Server** formats the data as JSON and replies with `200 OK`. + + + + + + +sequenceDiagram + Client->>Server: GET /api/data + Server->>Database: SELECT * FROM records + Database-->>Server: rows[] + Server-->>Client: 200 OK, JSON payload + + + + + +%%A `404` response means the resource was not found. A `500` response indicates a server-side error. Always handle both in your client code.%% + + + + +%%Switch between "Diagram" and "Text" on the left — the right pane follows automatically.%% + @@ -111,19 +209,44 @@ Hello there!

Share selected parts of a page with others.

+ +git-mastery.org/faq + +Hello there! + + ---

Share your customisations with others.

+ +git-mastery.org/faq + + +Hello there! + + + ---

Advanced: let adopters create ‘adaptation’ of your site.

+ + https://XXX + https://YYY + + + + + + + + --- diff --git a/docs/stylesheets/custard.css b/docs/stylesheets/custard.css index 24e8d8c2..39ba5e1e 100644 --- a/docs/stylesheets/custard.css +++ b/docs/stylesheets/custard.css @@ -21,10 +21,9 @@ } .cv-section-header { - font-size: 0.95rem; + font-size: 1.2rem; font-weight: 700; letter-spacing: 0.05em; - text-transform: uppercase; color: var(--custard-brown); margin-bottom: 0.4rem; } From 0173a7b20938e9388c81a1ed8ae44e83a02c777c Mon Sep 17 00:00:00 2001 From: gerteck Date: Tue, 31 Mar 2026 19:29:28 +0800 Subject: [PATCH 07/31] Fix stateManaged Toggles bug Defaults were not being applied and being removed --- src/lib/stores/active-state-store.svelte.ts | 33 ++++++++++--- tests/lib/stores/active-state-store.test.ts | 51 +++++++++++++++++++-- 2 files changed, 74 insertions(+), 10 deletions(-) diff --git a/src/lib/stores/active-state-store.svelte.ts b/src/lib/stores/active-state-store.svelte.ts index 52ee7a1e..bf85fe56 100644 --- a/src/lib/stores/active-state-store.svelte.ts +++ b/src/lib/stores/active-state-store.svelte.ts @@ -135,8 +135,17 @@ export class ActiveStateStore { const validatedTabs = this.filterValidTabs(newState.tabs ?? {}); const validatedPlaceholders = placeholderManager.filterUserSettablePlaceholders(newState.placeholders ?? {}); - const validatedShownToggles = this.filterNonSiteManagedToggleIds(this.filterValidToggles(newState.shownToggles ?? defaults.shownToggles ?? [])); - const validatedPeekToggles = this.filterNonSiteManagedToggleIds(this.filterValidToggles(newState.peekToggles ?? defaults.peekToggles ?? [])); + + // For site-managed toggles, we use the current state (which includes adaptation defaults). + // For user-settable toggles, we use the incoming newState (if present) or fall back to defaults. + const validatedShownToggles = [ + ...this.filterNonSiteManagedToggleIds(this.filterValidToggles(newState.shownToggles ?? defaults.shownToggles ?? [])), + ...this.filterSiteManagedToggleIds(this.state.shownToggles ?? []), + ]; + const validatedPeekToggles = [ + ...this.filterNonSiteManagedToggleIds(this.filterValidToggles(newState.peekToggles ?? defaults.peekToggles ?? [])), + ...this.filterSiteManagedToggleIds(this.state.peekToggles ?? []), + ]; this.state = { shownToggles: validatedShownToggles, @@ -358,10 +367,22 @@ export class ActiveStateStore { * Used to prevent user-supplied state (URL, localStorage) from overriding site-controlled toggles. */ private filterNonSiteManagedToggleIds(ids: string[]): string[] { - return ids.filter((id) => { - const toggle = this.config.toggles?.find((t) => t.toggleId === id); - return !toggle?.siteManaged; - }); + return ids.filter((id) => !this.isSiteManaged(id)); + } + + /** + * Returns only the toggle IDs that are siteManaged. + */ + private filterSiteManagedToggleIds(ids: string[]): string[] { + return ids.filter((id) => this.isSiteManaged(id)); + } + + /** + * Checks if a toggle is siteManaged. + */ + private isSiteManaged(toggleId: string): boolean { + const toggle = this.config.toggles?.find((t) => t.toggleId === toggleId); + return !!toggle?.siteManaged; } /** diff --git a/tests/lib/stores/active-state-store.test.ts b/tests/lib/stores/active-state-store.test.ts index 276ddf77..74570685 100644 --- a/tests/lib/stores/active-state-store.test.ts +++ b/tests/lib/stores/active-state-store.test.ts @@ -447,24 +447,41 @@ describe('ActiveStateStore', () => { // --------------------------------------------------------------------------- describe('applyState — siteManaged toggles and tabs', () => { - it('ignores persisted shownToggles for siteManaged toggles', () => { + it('ignores persisted shownToggles for siteManaged toggles that default to hide', () => { store.init({ toggles: [ - { toggleId: 'managed', siteManaged: true }, + { toggleId: 'managed', siteManaged: true, default: 'hide' }, { toggleId: 'normal' }, ], }); + // Persisted state says 'managed' is shown, but siteManaged says ignore persistence. + // Since default is 'hide', it should not be in shownToggles. store.applyState({ shownToggles: ['managed', 'normal'], peekToggles: [] }); expect(store.state.shownToggles).not.toContain('managed'); expect(store.state.shownToggles).toContain('normal'); }); - it('ignores persisted peekToggles for siteManaged toggles', () => { + it('uses default state for siteManaged toggles even if persisted state is provided', () => { + store.init({ + toggles: [ + { toggleId: 'managed-show', siteManaged: true, default: 'show' }, + { toggleId: 'managed-hide', siteManaged: true, default: 'hide' }, + ], + }); + + // Persisted state tries to hide 'managed-show' and show 'managed-hide' + store.applyState({ shownToggles: ['managed-hide'], peekToggles: [], hiddenToggles: ['managed-show'] }); + + expect(store.state.shownToggles).toContain('managed-show'); + expect(store.state.shownToggles).not.toContain('managed-hide'); + }); + + it('ignores persisted peekToggles for siteManaged toggles that default to hide', () => { store.init({ toggles: [ - { toggleId: 'managed', siteManaged: true }, + { toggleId: 'managed', siteManaged: true, default: 'hide' }, { toggleId: 'normal' }, ], }); @@ -475,6 +492,32 @@ describe('ActiveStateStore', () => { expect(store.state.peekToggles).toContain('normal'); }); + it('preserves adaptation-applied siteManaged toggles when applying persistence', () => { + store.init({ + toggles: [ + { toggleId: 'managed', siteManaged: true, default: 'hide' }, + { toggleId: 'normal', default: 'hide' }, + ], + }); + + // 1. Adaptation sets 'managed' to 'show' + store.applyAdaptationDefaults({ + toggles: { managed: 'show' } + }); + expect(store.state.shownToggles).toContain('managed'); + + // 2. Persistence is applied (user tried to peek 'managed' and show 'normal') + store.applyState({ + shownToggles: ['normal'], + peekToggles: ['managed'] + }); + + // 'managed' should still be 'show' (from adaptation), not 'peek' (from persistence) + expect(store.state.shownToggles).toContain('managed'); + expect(store.state.peekToggles).not.toContain('managed'); + expect(store.state.shownToggles).toContain('normal'); + }); + }); // --------------------------------------------------------------------------- From 4812d009cfa93b93ee3e8e803c0af8924d9a1d75 Mon Sep 17 00:00:00 2001 From: gerteck Date: Tue, 31 Mar 2026 21:04:46 +0800 Subject: [PATCH 08/31] Fix bottom border showing up on TabGroup when nav headers disabled --- src/lib/features/tabs/components/TabGroup.svelte | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/lib/features/tabs/components/TabGroup.svelte b/src/lib/features/tabs/components/TabGroup.svelte index 04446e88..a161d714 100644 --- a/src/lib/features/tabs/components/TabGroup.svelte +++ b/src/lib/features/tabs/components/TabGroup.svelte @@ -291,7 +291,9 @@
-
+ {#if tabs.length > 0 && navHeadingVisible} +
+ {/if}
\ No newline at end of file diff --git a/docs/_markbind/boilerplates/browserBoxTriple.md b/docs/_markbind/boilerplates/browserBoxTriple.md new file mode 100644 index 00000000..99e91a50 --- /dev/null +++ b/docs/_markbind/boilerplates/browserBoxTriple.md @@ -0,0 +1,159 @@ + + + + + + + + + +
+ +
+
+
+
+
+
+
{{ url | safe }}
+
+
+
+
+
+
+ +
+ +
+
+ +{{ content1 | safe }} + +
+
+ +
+
+ +{{ content2 | safe }} + +
+
+ +
+
+ +{{ content3 | safe }} + +
+
+ +
+ +
+ + diff --git a/docs/custardui.config.json b/docs/custardui.config.json index b53f64d1..37cacc45 100644 --- a/docs/custardui.config.json +++ b/docs/custardui.config.json @@ -3,19 +3,26 @@ "config": { "toggles": [ { - "toggleId": "optional-exercises", - "label": "Optional Exercises", - "description": "Hide or show exercise content.", + "toggleId": "code-example-hide", + "label": "Toggle Example 1", + "description": "Hide the code example!", "isLocal": true, - "default": "show" + "default": "hide" }, { - "toggleId": "additional-info", - "label": "Additional Information", - "description": "Personalize content visibility!", + "toggleId": "code-example-peek", + "label": "Toggle Example 2", + "description": "Peek at the code example!", "isLocal": true, "default": "peek" }, + { + "toggleId": "code-example-show", + "label": "Toggle Example 3", + "description": "Show the code example!", + "isLocal": true, + "default": "show" + }, { "toggleId": "localToggle", "label": "Local Toggle Label", @@ -31,14 +38,26 @@ ], "tabGroups": [ { - "groupId": "uml-view", - "label": "UML View", - "description": "Switch between diagram and text views.", + "groupId": "tab-group-example", + "label": "Tab Group Example", + "description": "Switch between tabs across pages!", + "isLocal": true, + "default": "javascript", + "tabs": [ + { "tabId": "javascript", "label": "JavaScript" }, + { "tabId": "java", "label": "Java" }, + { "tabId": "python", "label": "Python" } + ] + }, + { + "groupId": "tab-group-test", + "label": "Tab Group Test", + "description": "Switch between tabs across pages!", "isLocal": true, "default": "text", "tabs": [ - { "tabId": "diagram", "label": "Diagram" }, - { "tabId": "text", "label": "Text" } + { "tabId": "text", "label": "Text" }, + { "tabId": "diagram", "label": "Diagram" } ] }, { diff --git a/docs/devGuide/tests/tabs.md b/docs/devGuide/tests/tabs.md index 5e5d14ee..58d83635 100644 --- a/docs/devGuide/tests/tabs.md +++ b/docs/devGuide/tests/tabs.md @@ -9,6 +9,96 @@ # {{ title }} + + +https://cs4321.github.io/week6/time-management +https://cs4321.github.io/week7/networking + + +#### Time Management + +%%The Eisenhower Matrix helps prioritise tasks by urgency and importance. Not everything urgent is important; not everything important is urgent.%% + + + + +The matrix divides tasks into four quadrants: + +1. **Do**: Urgent and important. Deadlines, critical bugs. Handle immediately. +2. **Decide**: Not urgent but important. Planning, learning, refactoring. Schedule time for these. +3. **Delegate**: Urgent but not important. Pass to someone else if possible. +4. **Delete**: Don't do it. Neither urgent nor important. Busywork, distractions. Drop entirely. + + + + +
+
+ +%%{init: { 'theme': 'neutral' } }%% +quadrantChart + x-axis High Urgency --> Low Urgency + y-axis Low Importance --> High Importance + quadrant-1 Decide + quadrant-2 Do + quadrant-3 Delegate + quadrant-4 Delete + Critical bug fix: [0.15, 0.85] + Project planning: [0.8, 0.8] + Urgent email: [0.2, 0.75] + Code refactoring: [0.75, 0.7] + Updating internal wiki: [0.2, 0.25] + Busywork: [0.8, 0.2] + +
+
+ +
+
+ +%%Putting things to-do on a list frees your mind. But always question what is worth doing first.%% + +
+ + +#### HTTP Request-Response + +%%Web interactions involve a client sending a request to a server, which may query a data source to return a structured response. Understanding this cycle is fundamental to building reliable web software.%% + + + + +1. The **Client** sends a `GET /api/data` request to the **Server**. +2. The **Server** validates the request token with the **Auth** service. +3. Once authorized, the **Server** queries the **Database** using a SQL `SELECT` statement. +4. The **Database** returns the matching rows to the **Server**. +5. The **Server** formats the data as JSON. +6. The **Server** replies to the **Client** with `200 OK` and the JSON payload. + + + + + +%%{init: { 'theme': 'neutral' } }%% +sequenceDiagram + Client->>Server: GET /api/data + Server->>Auth: Validate token + Auth-->>Server: 200 Authorized + Server->>Database: SELECT * FROM records + Database-->>Server: rows[] + Server->>Server: Format as JSON + Server-->>Client: 200 OK, JSON payload + + + + + +%%A `404` response means the resource was not found. A `500` response indicates a server-side error. Always handle both in your client code.%% + + +
+ + Non preload. diff --git a/docs/index.md b/docs/index.md index 31da2b48..8f75505a 100644 --- a/docs/index.md +++ b/docs/index.md @@ -2,8 +2,7 @@ title: CustardUI - Your docs, shaped for every reader -

**CustardUI**

- +

**Custard**UI

@@ -30,166 +29,253 @@ Example content should be realistic but also fully disjoint from page content e. Let readers collapse what they don't need. Their choice sticks across pages and sessions. - -https://nus-cs2103t.github.io/week6/topics -https://nus-cs2103t.github.io/week7/topics + +https://cs2103t.github.io/website/textbook +#### Dependency Injection -#### Week 6 — Recursion +**Toggle:** - -
+Dependency Injection (DI) is a technique where an object's dependencies are provided externally rather than created by the object itself. This reduces coupling and makes code easier to test. -%%Recursion is a technique where a function calls itself to solve a smaller version of the same problem.%% + - -**Optional Exercises** +```java +// Without DI — tightly coupled +class UserService { + private Database db = new Database(); +} -Try these additional problems to reinforce your understanding of recursion. +// With DI — dependency is injected +class UserService { + private Database db; -1. Implement `fibonacci(n)` recursively. -2. Write a recursive function to flatten a nested list. + public UserService(Database db) { + this.db = db; + } +} +``` - +%%Prefer constructor injection over field injection — it makes dependencies explicit and the class easier to test.%% -**Additional Information** - -Recursion can cause a stack overflow if the base case is missing or unreachable. Always verify your base case before testing. - - - -%%The base case is the most important part of any recursive function.%%
-#### Week 7 — Binary Search +#### Dependency Injection - -
+**Toggle:** + +Dependency Injection (DI) is a technique where an object's dependencies are provided externally rather than created by the object itself. This reduces coupling and makes code easier to test. -%%Binary search finds an element in a sorted list by repeatedly halving the search space.%% + - -**Optional Exercises** +```java +// Without DI — tightly coupled +class UserService { + private Database db = new Database(); +} -Practice binary search with these problems. +// With DI — dependency is injected +class UserService { + private Database db; -1. Find the index of a target value in a sorted array. -2. Implement binary search recursively. + public UserService(Database db) { + this.db = db; + } +} +``` - +%%Prefer constructor injection over field injection — it makes dependencies explicit and the class easier to test.%% -**Additional Information** -Binary search requires the list to be sorted. Applying it to an unsorted list will produce incorrect results. +
+ - -%%Time complexity: O(log n). Space complexity: O(1) for the iterative version.%% +#### Dependency Injection + +**Toggle:** + +Dependency Injection (DI) is a technique where an object's dependencies are provided externally rather than created by the object itself. This reduces coupling and makes code easier to test. + + + + +```java +// Without DI — tightly coupled +class UserService { + private Database db = new Database(); +} + +// With DI — dependency is injected +class UserService { + private Database db; + + public UserService(Database db) { + this.db = db; + } +} +``` + + +%%Prefer constructor injection over field injection — it makes dependencies explicit and the class easier to test.%%
- -%%Toggle preferences sync across pages.%% +%%The same page, three toggle states, where each reader can set their own peferences and see only what they chose to.%% --- + +

Set the default tab in tab groups.

-Let readers switch to their preferred view for tabgroups once, and it follows them across pages. +Let readers switch to their preferred view for tabgroups once, and this selection persists across your site. + +* Hover and click the   :fa-solid-bookmark:   icon on any tab, or simply double-click it, to set it as your default across the site. +* Prefer a cleaner view? Hide the tab bar entirely in the settings modal through the  :fa-solid-gear:   icon on the left. Click the option to \ +_"show only the selected tab"_ to hide the navigation headers and show only your preferred content! -https://cs4321.github.io/week6/software-process -https://cs4321.github.io/week7/networking +https://course-website.org/textbook/sorting +https://course-website.org/textbook/searching -#### Git Branching Strategy -%%A Git branching strategy defines how branches organise work. In a typical feature branch workflow, changes are developed in isolation and merged back to `main` only after review.%% +#### Insertion Sort - - +%%Insertion Sort builds a sorted array one element at a time by picking each element and placing it in its correct position. Simple and efficient for small or nearly sorted arrays.%% -1. A `feature` branch is created off `main` for new work. -2. Two commits are made: the login form and its validation. -3. Meanwhile, a `hotfix` branch is cut from `main` to address a bug. -4. `hotfix` is merged back into `main` first. -5. `feature` is then merged into `main` after review. + + + +```javascript +function insertionSort(arr) { + for (let i = 1; i < arr.length; i++) { + let key = arr[i]; + let j = i - 1; + while (j >= 0 && arr[j] > key) { + arr[j + 1] = arr[j]; + j--; + } + arr[j + 1] = key; + } +} +``` + + + + +```java +void insertionSort(int[] arr) { + for (int i = 1; i < arr.length; i++) { + int key = arr[i]; + int j = i - 1; + while (j >= 0 && arr[j] > key) { + arr[j + 1] = arr[j]; + j--; + } + arr[j + 1] = key; + } +} +``` - - - -%%{init: { 'theme': 'neutral' } }%% -gitGraph - commit id: "Initial" - branch feature - checkout feature - commit id: "Login form" - checkout main - branch hotfix - checkout hotfix - commit id: "Fix bug" - checkout main - merge feature - + + +```python +def insertion_sort(arr): + for i in range(1, len(arr)): + key = arr[i] + j = i - 1 + while j >= 0 and arr[j] > key: + arr[j + 1] = arr[j] + j -= 1 + arr[j + 1] = key +``` -%%Branches should be short-lived. Long-running branches accumulate merge conflicts and slow down the team.%% +%%Time complexity: O(n²) worst case, O(n) best case on nearly sorted arrays.%% -#### HTTP Request-Response - -%%Web interactions involve a client sending a request to a server, which may query a data source to return a structured response.%% - - - - -1. The **Client** sends a `GET /api/data` request to the **Server**. -2. The **Server** queries the **Database** using a SQL `SELECT` statement. -3. The **Database** returns the matching rows to the **Server**. -4. The **Server** formats the data as JSON and replies with `200 OK`. - - - - +#### Binary Search + +%%Binary Search finds a target value in a sorted array by repeatedly halving the search space. Far more efficient than linear search for large datasets.%% + + + + +```javascript +function binarySearch(arr, target) { + let left = 0, right = arr.length - 1; + while (left <= right) { + const mid = Math.floor((left + right) / 2); + if (arr[mid] === target) return mid; + else if (arr[mid] < target) left = mid + 1; + else right = mid - 1; + } + return -1; +} +``` + + + +```java +int binarySearch(int[] arr, int target) { + int left = 0, right = arr.length - 1; + while (left <= right) { + int mid = (left + right) / 2; + if (arr[mid] == target) return mid; + else if (arr[mid] < target) left = mid + 1; + else right = mid - 1; + } + return -1; +} +``` - -sequenceDiagram - Client->>Server: GET /api/data - Server->>Database: SELECT * FROM records - Database-->>Server: rows[] - Server-->>Client: 200 OK, JSON payload - + + + +```python +def binary_search(arr, target): + left, right = 0, len(arr) - 1 + while left <= right: + mid = (left + right) // 2 + if arr[mid] == target: + return mid + elif arr[mid] < target: + left = mid + 1 + else: + right = mid - 1 + return -1 +``` - + -%%A `404` response means the resource was not found. A `500` response indicates a server-side error. Always handle both in your client code.%% +%%Time complexity: O(log n). Requires the array to be sorted before searching.%% -%%Switch between "Diagram" and "Text" on the left — the right pane follows automatically.%% - - +%%Switch to Python on either page, and every code example across the site follows.%% --- diff --git a/docs/stylesheets/custard.css b/docs/stylesheets/custard.css index 39ba5e1e..db415cb3 100644 --- a/docs/stylesheets/custard.css +++ b/docs/stylesheets/custard.css @@ -9,7 +9,7 @@ } .font-with-serif { - font-family: Georgia, serif; + font-family: serif, Georgia; } .cv-section-title { From 3c1ea3e435b1fc228db5191a866845578024af46 Mon Sep 17 00:00:00 2001 From: gerteck Date: Thu, 2 Apr 2026 00:11:52 +0800 Subject: [PATCH 10/31] Update `no-label` attribute to `inline` for cv-toggle-control --- docs/authorGuide/components/toggles.md | 8 +++++--- src/lib/features/toggles/components/ToggleControl.svelte | 8 ++++---- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/docs/authorGuide/components/toggles.md b/docs/authorGuide/components/toggles.md index 8235df48..e9f8d513 100644 --- a/docs/authorGuide/components/toggles.md +++ b/docs/authorGuide/components/toggles.md @@ -14,6 +14,8 @@ Toggles let you show or hide sections of a page based on a category (for example +
+ html @@ -91,7 +93,7 @@ You can apply multiple toggles to a single element by separating categories with You can place a **toggle control** directly on the page so readers can switch a toggle's visibility state (Hide · Peek · Show) without opening the Settings modal. By default it renders as a card, matching the look of the settings panel, with the toggle's label shown on the left and the segmented control on the right. * You can place the control anywhere, above the toggle, in a sidebar, or grouped together for multiple toggles. -* To hide the label and render only the segmented control inline, add the `no-label` attribute. +* To hide the label and render only the segmented control inline, add the `inline` attribute. * Note: `cv-toggle-control` renders nothing for `siteManaged` toggles, since those states are controlled by the site rather than the reader. @@ -100,7 +102,7 @@ You can place a **toggle control** directly on the page so readers can switch a
-Control the toggles for the id `localToggle` here: +Control the toggles for the id `localToggle` here: @@ -119,7 +121,7 @@ Control the toggles for the id `localToggle` here: ` which accepts space-separated IDs). | -| no-label | `boolean` | `false` | If present, hides the label and card styling, rendering only the segmented control inline. | +| inline | `boolean` | `false` | If present, hides the label and card styling, rendering only the segmented control inline. | ## Configuration diff --git a/src/lib/features/toggles/components/ToggleControl.svelte b/src/lib/features/toggles/components/ToggleControl.svelte index eba2a086..d71ef29a 100644 --- a/src/lib/features/toggles/components/ToggleControl.svelte +++ b/src/lib/features/toggles/components/ToggleControl.svelte @@ -3,7 +3,7 @@ tag: 'cv-toggle-control', props: { toggleId: { reflect: true, type: 'String', attribute: 'toggle-id' }, - noLabel: { type: 'Boolean', attribute: 'no-label' }, + inline: { type: 'Boolean', attribute: 'inline' }, }, }} /> @@ -15,10 +15,10 @@ let { toggleId = '', - noLabel = false, + inline = false, }: { toggleId?: string; - noLabel?: boolean; + inline?: boolean; } = $props(); let configLoaded = $derived(!!activeStateStore.config.toggles); @@ -46,7 +46,7 @@ {#if configLoaded && !!toggleConfig && !isSiteManaged} - {#if !noLabel} + {#if !inline}
From 760e34c76eacbd289d673ed6b875ccf6fd571b2e Mon Sep 17 00:00:00 2001 From: gerteck Date: Thu, 2 Apr 2026 02:06:14 +0800 Subject: [PATCH 11/31] Add description field to placeholder config --- src/lib/features/placeholder/types.ts | 1 + .../features/settings/PlaceholderItem.svelte | 45 ++++++++++++++++--- 2 files changed, 41 insertions(+), 5 deletions(-) diff --git a/src/lib/features/placeholder/types.ts b/src/lib/features/placeholder/types.ts index f202dfeb..d26fa378 100644 --- a/src/lib/features/placeholder/types.ts +++ b/src/lib/features/placeholder/types.ts @@ -6,6 +6,7 @@ export interface PlaceholderDefinition { name: string; settingsLabel?: string | undefined; settingsHint?: string | undefined; + description?: string | undefined; defaultValue?: string | undefined; hiddenFromSettings?: boolean | undefined; /** If true, this placeholder is only shown in settings if detected on the page */ diff --git a/src/lib/features/settings/PlaceholderItem.svelte b/src/lib/features/settings/PlaceholderItem.svelte index c6b862d0..e9d0f3b5 100644 --- a/src/lib/features/settings/PlaceholderItem.svelte +++ b/src/lib/features/settings/PlaceholderItem.svelte @@ -19,9 +19,14 @@
- +
+ + {#if definition.description} +

{definition.description}

+ {/if} +
.placeholder-item { + display: flex; + flex-direction: row; + align-items: center; + justify-content: space-between; + flex-wrap: wrap; + gap: 0.5rem 0.75rem; + padding: 0.75rem; + background: var(--cv-bg); + border: 1px solid var(--cv-border); + border-radius: var(--cv-card-radius, 0.5rem); + transition: background 0.15s ease; + } + + .placeholder-item:hover { + background: var(--cv-bg-hover); + } + + .label-group { + flex: 1; + min-width: 8rem; display: flex; flex-direction: column; - gap: 0.25rem; + gap: 0.125rem; } .placeholder-label { - font-size: 0.85rem; + font-size: 0.875rem; font-weight: 500; color: var(--cv-text); + margin: 0; + } + + .placeholder-description { + font-size: 0.75rem; + color: var(--cv-text-secondary); + margin: 0; + line-height: 1.4; } .placeholder-input { + width: 12rem; + flex-shrink: 0; padding: 0.5rem 0.75rem; border: 1px solid var(--cv-input-border); border-radius: var(--cv-card-radius, 0.5rem); From ef7af34ff3e2e9c391070e40505ab90855063253 Mon Sep 17 00:00:00 2001 From: gerteck Date: Thu, 2 Apr 2026 02:12:19 +0800 Subject: [PATCH 12/31] Update placeholders docs --- docs/authorGuide/components/placeholders.md | 109 ++++++++++++-------- docs/custardui.config.json | 7 +- docs/stylesheets/custard.css | 2 +- 3 files changed, 72 insertions(+), 46 deletions(-) diff --git a/docs/authorGuide/components/placeholders.md b/docs/authorGuide/components/placeholders.md index 80271601..2dd1b256 100644 --- a/docs/authorGuide/components/placeholders.md +++ b/docs/authorGuide/components/placeholders.md @@ -5,49 +5,37 @@ pageNavTitle: "Topics" - - + + -## Simple Placeholders (Variable Interpolation) +## Placeholders -`\[[ variable_name ]]`, ``, `` +`\[[ variable_name ]]`, `` -Placeholders allow you to create dynamic "Mad Libs" style documentation. Authors can define variable placeholders that readers can customize via the Settings Widget. The values entered by the reader are persisted and automatically update text, code blocks, and other content across the site. +The **Placeholders** component let readers personalise your site to their own context. Define placeholders once in your [configuration](#configuration), and readers fill it in via the settings panel or an inline input, and every instance across the site updates immediately. Values persist across page reloads and navigation. -## Usage + -### Add the Placeholder Configuration - -Placeholders are defined in your `custardui.config.json` under the `placeholders` key. - -```json -{ - "config": { - "placeholders": [ - { "name": "username", "settingsLabel": "Your Username", "settingsHint": "Enter username" }, - { "name": "api_key", "settingsLabel": "API Key", "isLocal": true } - ] - } -} -``` -### Use the placeholder + +md + -To use the variable in your content, wrap the variable name in double square brackets: `[[ variable_name ]]`. +Hello, \[[username]]! -For example, we write `Hello, \[[username]]` here: + + -```markdown -Hello, \[[username]]! -↓ Hello, [[username]]! -``` + + -The system scans the page and replaces these tokens with the current value. When the user updates the value in the settings, all instances on the page update immediately. +To use the variable in your content, wrap the variable name in double square brackets: `[[ variable_name ]]`. The plugin scans the page and replaces these tokens with the current value. When the user updates the value in the settings, all instances on the page update immediately. **Manual Component Usage**: You could also use the internal custom element directly, although it is not necessary. `` is functionally equivalent to `\[[ email : support@example.com ]]` but may be useful for debugging or etc. -## Inline Fallback + +### Inline Fallback You can provide a fallback value directly in the usage syntax. This is useful if you haven't defined a formal placeholder or want a specific default for one instance. @@ -67,7 +55,7 @@ If the user has not set a value for `email`, "support@example.com" will be displ > User-set empty strings (`""`) are treated as "not set" and fall through to the inline fallback, instead of displaying nothing. Use `\[[email : ]]` to explicitly show nothing as the fallback. -## Conditional Syntax (Fall-Forward) +### Conditional Syntax (Fall-Forward) You can render content **only when** a placeholder has a value using the conditional syntax: @@ -121,7 +109,8 @@ No `user` → `src="https://cdn.example.com"`. With `user=alice` → `src="https > **Known limitation**: The `:` character cannot appear inside the truthy template (e.g., URLs with ports like `localhost:8080`). Use a regular placeholder or construct the value differently in those cases. -## Placeholder-Driven Toggle Visibility + +### Placeholder-Driven Toggle Visibility You can show or hide a block of content based on whether a placeholder has a value, using the `placeholder-id` attribute on ``: @@ -147,7 +136,7 @@ The block is **hidden** when `username` has no value, and **visible** when it do Placeholder mode has no peek, label, or border behaviour. `placeholder-id` takes precedence over `toggle-id`, and should not be used on the same element. -## HTML Attribute Interpolation +### HTML Attribute Interpolation In addition to text, you can interpolate variables into HTML attributes, such as `href` or `src`. This is useful for creating dynamic links or loading images based on user preferences. @@ -188,22 +177,29 @@ If the user sets `searchQuery` to `hello/world`, the link becomes `https://www.g Theme Preview ``` -## Escaping Syntax +### Escaping Syntax You can "escape" the placeholder syntax if you want to display the literal brackets instead of a variable. - **In Markdown Text**: Use two backslashes: `\\\[[ variable ]]`. - **In Code Blocks**: Use one backslash: `\\[[ variable ]]`. -## Inline Editing of Placeholders - + + +## Placeholder Input + +`` + +To make it better, you can use the `` component to allow **inline editing** of placeholders. + You can allow users to edit placeholders directly on the page using the `` component. By default, the component renders inline with text. For example, you can update your username placeholder here: , which updates your username: [[username]]. Several attributes control the appearance and behavior of the component: -* `layout`: The layout to use (inline, stacked, or horizontal). +* `layout`: The layout to use (inline, stacked, horizontal, or card). * `inline` (Default): Component sits in the text flow, and the label is hidden visually. * `stacked`: Label sits on top of the input. Input takes full width. * `horizontal`: Label sits to the left of the input. Input takes remaining space. + * `card`: Styled card matching the look of toggle controls — label on the left, input on the right. * `appearance`: The appearance to use (outline, underline, or ghost). * `outline` (Default): Standard input box with border. * `underline`: Only a bottom border. Great for inline text. @@ -218,14 +214,24 @@ Several attributes control the appearance and behavior of the component: -My username is and it is . +%%Use the inline layout to edit placeholders directly in the text flow:%% + +My username is 'outline': , +'underline': , +and 'ghost': . +%%Alternatively, use the stacked layout:%% +%%Or the horizontal layout:%% + +%%Or the card layout, which is great for settings pages:%% + + @@ -236,19 +242,38 @@ My username is