From 5d884ffc13d0035ebcde82173b181112966d3bea Mon Sep 17 00:00:00 2001 From: Vatsal Sanjay Date: Sat, 1 Mar 2025 22:52:30 +0100 Subject: [PATCH 01/19] feat(search): Implement Cmd+K shortcut and improve search data The changes in this commit include: 1. Implement a Cmd+K (or Ctrl+K on non-Mac) shortcut to open the search modal in the team page. 2. Convert the existing `search_db.json` file to a more structured `searchData` format that is compatible with the NinjaKeys library. 3. Automatically detect the user's operating system and display the appropriate shortcut text (Cmd or Ctrl) in the search button. 4. Enhance the search data by adding more metadata, such as section, keywords, and icons, to improve the search experience. --- README.md | 18 ++- _layouts/default.html | 32 +++++ _layouts/research.html | 8 ++ _layouts/teaching.html | 8 ++ _layouts/team.html | 8 ++ assets/css/search-modal.css | 46 ++++++++ assets/js/search-data.js | 111 ++++++++++++++++++ assets/js/search-setup.js | 33 ++++++ .../js/search/hotkeys-js/hotkeys.esm.min.js | 16 +++ assets/js/search/ninja-keys.min.js | 35 ++++++ assets/js/shortcut-key.js | 16 +++ 11 files changed, 325 insertions(+), 6 deletions(-) create mode 100644 assets/css/search-modal.css create mode 100644 assets/js/search-data.js create mode 100644 assets/js/search-setup.js create mode 100644 assets/js/search/hotkeys-js/hotkeys.esm.min.js create mode 100644 assets/js/search/ninja-keys.min.js create mode 100644 assets/js/shortcut-key.js diff --git a/README.md b/README.md index 2a98fa5..12cd0d2 100644 --- a/README.md +++ b/README.md @@ -32,11 +32,18 @@ A static website for the Computational Multiphase Physics Laboratory, built with │ │ ├── research.css # Research page styles │ │ ├── teaching.css # Teaching page styles │ │ ├── team.css # Team page styles -│ │ └── search.css # Search functionality styles +│ │ ├── search.css # Search functionality styles +│ │ └── search-modal.css # Cmd+K search modal styles │ ├── js # JavaScript files │ │ ├── main.js # Main JavaScript │ │ ├── search.js # Search functionality -│ │ └── search_db.json # Generated search database +│ │ ├── search-data.js # Cmd+K search data converter +│ │ ├── search-setup.js # Cmd+K search initialization +│ │ ├── shortcut-key.js # Platform detection for shortcuts +│ │ ├── search_db.json # Generated search database +│ │ └── search # Search libraries +│ │ ├── ninja-keys.min.js # NinjaKeys command palette +│ │ └── hotkeys-js # Keyboard shortcut library │ ├── favicon # Favicon files │ └── img # Image assets │ └── teaching # Teaching images @@ -223,6 +230,7 @@ The website includes a powerful search feature that allows users to: - Get instant search results with highlighted matching text - See match percentage for each result - Navigate directly to specific sections using anchor links +- Access search via keyboard shortcut (cmd+k on Mac, ctrl+k on Windows) Search results are prioritized and filtered as follows: 1. Team Members (highest priority) @@ -237,10 +245,8 @@ Search results are prioritized and filtered as follows: Search behavior and restrictions: - Minimum query length: 2 characters -- Shows only top 5 most relevant results -- Requires at least 50% of query words to match -- Prioritizes matches near the start of content -- Properly renders markdown and HTML in results +- Keyboard shortcut (cmd+k / ctrl+k) opens a command palette style search interface +- Search button in navigation also provides visual access to the command palette The search database is automatically generated during the build process by `scripts/generate_search_db.rb`. This script: - Indexes all HTML and markdown content diff --git a/_layouts/default.html b/_layouts/default.html index 57b0e10..3a165a0 100644 --- a/_layouts/default.html +++ b/_layouts/default.html @@ -55,6 +55,7 @@ + @@ -134,6 +135,14 @@
+ +
  • + +
  • @@ -186,6 +195,29 @@ + + + + + + + + + + + + + diff --git a/_layouts/research.html b/_layouts/research.html index 1b96d17..73daded 100644 --- a/_layouts/research.html +++ b/_layouts/research.html @@ -191,6 +191,14 @@
    + +
  • + +
  • diff --git a/_layouts/teaching.html b/_layouts/teaching.html index e7695ab..215e7ac 100644 --- a/_layouts/teaching.html +++ b/_layouts/teaching.html @@ -135,6 +135,14 @@
    + +
  • + +
  • diff --git a/_layouts/team.html b/_layouts/team.html index b68e22d..71db708 100644 --- a/_layouts/team.html +++ b/_layouts/team.html @@ -109,6 +109,14 @@
    + +
  • + +
  • diff --git a/assets/css/search-modal.css b/assets/css/search-modal.css new file mode 100644 index 0000000..e15619b --- /dev/null +++ b/assets/css/search-modal.css @@ -0,0 +1,46 @@ +/* Ninja Keys styling */ +ninja-keys { + --ninja-width: 640px; + --ninja-backdrop-filter: blur(16px); + --ninja-overflow: hidden; + --ninja-z-index: 1000; + --ninja-modal-shadow: 0 0 10px rgba(0, 0, 0, 0.2); + --ninja-modal-background: rgba(255, 255, 255, 0.8); + --ninja-accent-color: #5b79a8; /* Match your site theme color */ + --ninja-selected-background: rgba(91, 121, 168, 0.1); + --ninja-font-family: inherit; +} + +/* Dark mode styling if needed */ +@media (prefers-color-scheme: dark) { + ninja-keys { + --ninja-modal-background: rgba(30, 30, 30, 0.8); + --ninja-accent-color: #7a9bc9; + --ninja-selected-background: rgba(122, 155, 201, 0.1); + --ninja-text-color: #e1e1e1; + --ninja-secondary-text-color: #a0a0a0; + } +} + +/* Search button styling */ +.search-button .btn { + padding: 0.25rem 0.5rem; + font-size: 0.875rem; + border-radius: 0.2rem; + cursor: pointer; + display: inline-flex; + align-items: center; + gap: 0.5rem; + background-color: transparent; + border: 1px solid #5b79a8; + color: #5b79a8; +} + +.search-button .btn:hover { + background-color: rgba(91, 121, 168, 0.1); +} + +/* Hide one of the shortcut texts depending on platform */ +.mac-theme-text, .default-theme-text { + display: none; +} \ No newline at end of file diff --git a/assets/js/search-data.js b/assets/js/search-data.js new file mode 100644 index 0000000..b6e4563 --- /dev/null +++ b/assets/js/search-data.js @@ -0,0 +1,111 @@ +// search-data.js - Convert existing search_db.json to NinjaKeys format +document.addEventListener('DOMContentLoaded', function() { + // Initialize empty search data array + window.searchData = []; + + // Add navigation items + window.searchData.push( + { + id: "home", + title: "Home", + handler: () => { window.location.href = '/'; }, + section: "Navigation", + shortcuts: ["h"], + icon: '' + }, + { + id: "team", + title: "Team", + handler: () => { window.location.href = '/team/'; }, + section: "Navigation", + shortcuts: ["t"], + icon: '' + }, + { + id: "research", + title: "Research", + handler: () => { window.location.href = '/research/'; }, + section: "Navigation", + shortcuts: ["r"], + icon: '' + }, + { + id: "teaching", + title: "Teaching", + handler: () => { window.location.href = '/teaching/'; }, + section: "Navigation", + shortcuts: ["e"], + icon: '' + }, + { + id: "join", + title: "Join Us", + handler: () => { window.location.href = '/join/'; }, + section: "Navigation", + icon: '' + }, + { + id: "contact", + title: "Contact", + handler: () => { window.location.href = '/contact/'; }, + section: "Navigation", + icon: '' + } + ); + + // Get the base URL from meta tag if it exists + const baseUrl = document.querySelector('meta[name="base-url"]')?.content || ''; + + // Load existing search database to add content items + fetch(`${baseUrl}/assets/js/search_db.json`) + .then(response => response.json()) + .then(data => { + // Process each search item and convert to NinjaKeys format + data.forEach(item => { + // Create appropriate section based on item type + let section = "Content"; + let icon = ''; + + if (item.type === 'team_member') { + section = "Team Members"; + icon = ''; + } else if (item.type === 'paper') { + section = "Research Papers"; + icon = ''; + } else if (item.type === 'markdown_section' || item.type === 'markdown_text') { + section = "Pages"; + icon = ''; + } + + // Create a subtitle from content (truncate if needed) + let subtitle = ''; + if (item.content) { + // Strip markdown and HTML + const tempDiv = document.createElement('div'); + tempDiv.innerHTML = item.content.replace(/\*\*|__|\*|_|`|\[.*?\]\(.*?\)/g, ''); + subtitle = tempDiv.textContent.substring(0, 60).trim(); + if (item.content.length > 60) subtitle += '...'; + } + + // Add to search data + window.searchData.push({ + id: item.url.replace(/[^\w-]/g, '-'), + title: item.title, + subtitle: subtitle, + handler: () => { window.location.href = item.url; }, + section: section, + keywords: item.tags ? item.tags.join(', ') : '', + icon: icon + }); + }); + + // Update the ninja-keys component with the data + const ninjaKeys = document.querySelector('ninja-keys'); + if (ninjaKeys) { + ninjaKeys.data = window.searchData; + } + }) + .catch(error => { + console.error('Error loading search database:', error); + }); +}); \ No newline at end of file diff --git a/assets/js/search-setup.js b/assets/js/search-setup.js new file mode 100644 index 0000000..2f0c475 --- /dev/null +++ b/assets/js/search-setup.js @@ -0,0 +1,33 @@ +// Initialize the search modal +function initSearch() { + const ninjaKeys = document.querySelector('ninja-keys'); + if (!ninjaKeys) return; + + // Data is loaded in search-data.js + + // Add theme change listener if you support dark/light modes + const htmlEl = document.querySelector('html'); + if (htmlEl) { + const observer = new MutationObserver(() => { + const darkMode = htmlEl.dataset.theme === 'dark'; + ninjaKeys.setAttribute('theme', darkMode ? 'dark' : 'light'); + }); + observer.observe(htmlEl, { attributes: true }); + + // Set initial theme based on current site theme or time of day + const prefersDark = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches; + ninjaKeys.setAttribute('theme', prefersDark ? 'dark' : 'light'); + } +} + +// Function to open the search modal +function openSearchModal() { + // Open the ninja-keys modal + const ninjaKeys = document.querySelector('ninja-keys'); + if (ninjaKeys) { + ninjaKeys.open(); + } +} + +// Initialize on page load +document.addEventListener('DOMContentLoaded', initSearch); \ No newline at end of file diff --git a/assets/js/search/hotkeys-js/hotkeys.esm.min.js b/assets/js/search/hotkeys-js/hotkeys.esm.min.js new file mode 100644 index 0000000..86a9ffe --- /dev/null +++ b/assets/js/search/hotkeys-js/hotkeys.esm.min.js @@ -0,0 +1,16 @@ +/** + * Minified by jsDelivr using Terser v5.15.1. + * Original file: /npm/hotkeys-js@3.10.1/dist/hotkeys.esm.js + * + * Do NOT use SRI with dynamically generated files! More information: https://www.jsdelivr.com/using-sri-with-dynamic-files + */ +/**! + * hotkeys-js v3.10.1 + * A simple micro-library for defining and dispatching keyboard shortcuts. It has no dependencies. + * + * Copyright (c) 2022 kenny wong + * http://jaywcjlove.github.io/hotkeys + * Licensed under the MIT license + */ +var isff="undefined"!=typeof navigator&&navigator.userAgent.toLowerCase().indexOf("firefox")>0;function addEvent(e,n,t,o){e.addEventListener?e.addEventListener(n,t,o):e.attachEvent&&e.attachEvent("on".concat(n),(function(){t(window.event)}))}function getMods(e,n){for(var t=n.slice(0,n.length-1),o=0;o=0;)n[t-1]+=",",n.splice(t,1),t=n.lastIndexOf("");return n}function compareArray(e,n){for(var t=e.length>=n.length?e:n,o=e.length>=n.length?n:e,r=!0,i=0;i=0&&_downKeys.splice(t,1),e.key&&"meta"===e.key.toLowerCase()&&_downKeys.splice(0,_downKeys.length),93!==n&&224!==n||(n=91),n in _mods)for(var o in _mods[n]=!1,_modifier)_modifier[o]===n&&(hotkeys[o]=!1)}function unbind(e){if(void 0===e)Object.keys(_handlers).forEach((function(e){return delete _handlers[e]}));else if(Array.isArray(e))e.forEach((function(e){e.key&&eachUnbind(e)}));else if("object"==typeof e)e.key&&eachUnbind(e);else if("string"==typeof e){for(var n=arguments.length,t=new Array(n>1?n-1:0),o=1;o1?getMods(_modifier,n):[];_handlers[d]=_handlers[d].filter((function(e){return!((!o||e.method===o)&&e.scope===t&&compareArray(e.mods,a))}))}}))};function eventHandler(e,n,t,o){var r;if(n.element===o&&(n.scope===t||"all"===n.scope)){for(var i in r=n.mods.length>0,_mods)Object.prototype.hasOwnProperty.call(_mods,i)&&(!_mods[i]&&n.mods.indexOf(+i)>-1||_mods[i]&&-1===n.mods.indexOf(+i))&&(r=!1);(0!==n.mods.length||_mods[16]||_mods[18]||_mods[17]||_mods[91])&&!r&&"*"!==n.shortcut||!1===n.method(e,n)&&(e.preventDefault?e.preventDefault():e.returnValue=!1,e.stopPropagation&&e.stopPropagation(),e.cancelBubble&&(e.cancelBubble=!0))}}function dispatch(e,n){var t=_handlers["*"],o=e.keyCode||e.which||e.charCode;if(hotkeys.filter.call(this,e)){if(93!==o&&224!==o||(o=91),-1===_downKeys.indexOf(o)&&229!==o&&_downKeys.push(o),["ctrlKey","altKey","shiftKey","metaKey"].forEach((function(n){var t=modifierMap[n];e[n]&&-1===_downKeys.indexOf(t)?_downKeys.push(t):!e[n]&&_downKeys.indexOf(t)>-1?_downKeys.splice(_downKeys.indexOf(t),1):"metaKey"===n&&e[n]&&3===_downKeys.length&&(e.ctrlKey||e.shiftKey||e.altKey||(_downKeys=_downKeys.slice(_downKeys.indexOf(t))))})),o in _mods){for(var r in _mods[o]=!0,_modifier)_modifier[r]===o&&(hotkeys[r]=!0);if(!t)return}for(var i in _mods)Object.prototype.hasOwnProperty.call(_mods,i)&&(_mods[i]=e[modifierMap[i]]);e.getModifierState&&(!e.altKey||e.ctrlKey)&&e.getModifierState("AltGraph")&&(-1===_downKeys.indexOf(17)&&_downKeys.push(17),-1===_downKeys.indexOf(18)&&_downKeys.push(18),_mods[17]=!0,_mods[18]=!0);var s=getScope();if(t)for(var d=0;d-1}function hotkeys(e,n,t){_downKeys=[];var o=getKeys(e),r=[],i="all",s=document,d=0,a=!1,c=!0,f="+",l=!1;for(void 0===t&&"function"==typeof n&&(t=n),"[object Object]"===Object.prototype.toString.call(n)&&(n.scope&&(i=n.scope),n.element&&(s=n.element),n.keyup&&(a=n.keyup),void 0!==n.keydown&&(c=n.keydown),void 0!==n.capture&&(l=n.capture),"string"==typeof n.splitKey&&(f=n.splitKey)),"string"==typeof n&&(i=n);d1&&(r=getMods(_modifier,e)),(e="*"===(e=e[e.length-1])?"*":code(e))in _handlers||(_handlers[e]=[]),_handlers[e].push({keyup:a,keydown:c,scope:i,mods:r,shortcut:o[d],method:t,key:o[d],splitKey:f,element:s});void 0!==s&&!isElementBind(s)&&window&&(elementHasBindEvent.push(s),addEvent(s,"keydown",(function(e){dispatch(e,s)}),l),winListendFocus||(winListendFocus=!0,addEvent(window,"focus",(function(){_downKeys=[]}),l)),addEvent(s,"keyup",(function(e){dispatch(e,s),clearModifier(e)}),l))}function trigger(e){var n=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"all";Object.keys(_handlers).forEach((function(t){_handlers[t].filter((function(t){return t.scope===n&&t.shortcut===e})).forEach((function(e){e&&e.method&&e.method()}))}))}var _api={getPressedKeyString:getPressedKeyString,setScope:setScope,getScope:getScope,deleteScope:deleteScope,getPressedKeyCodes:getPressedKeyCodes,isPressed:isPressed,filter:filter,trigger:trigger,unbind:unbind,keyMap:_keyMap,modifier:_modifier,modifierMap:modifierMap};for(var a in _api)Object.prototype.hasOwnProperty.call(_api,a)&&(hotkeys[a]=_api[a]);if("undefined"!=typeof window){var _hotkeys=window.hotkeys;hotkeys.noConflict=function(e){return e&&window.hotkeys===hotkeys&&(window.hotkeys=_hotkeys),hotkeys},window.hotkeys=hotkeys}export{hotkeys as default}; +//# sourceMappingURL=/sm/57fed5bcab1877840f34e6f82efa3e116c9761284e6a7ae04fbf43251971954e.map \ No newline at end of file diff --git a/assets/js/search/ninja-keys.min.js b/assets/js/search/ninja-keys.min.js new file mode 100644 index 0000000..b33ce3a --- /dev/null +++ b/assets/js/search/ninja-keys.min.js @@ -0,0 +1,35 @@ +/** + * Minified by jsDelivr using Terser v5.15.1. + * Original file: /npm/ninja-keys@1.2.2/dist/ninja-keys.js + * + * Do NOT use SRI with dynamically generated files! More information: https://www.jsdelivr.com/using-sri-with-dynamic-files + */ +var __decorate=this&&this.__decorate||function(e,t,s,i){var o,a=arguments.length,n=a<3?t:null===i?i=Object.getOwnPropertyDescriptor(t,s):i;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)n=Reflect.decorate(e,t,s,i);else for(var r=e.length-1;r>=0;r--)(o=e[r])&&(n=(a<3?o(n):a>3?o(t,s,n):o(t,s))||n);return a>3&&n&&Object.defineProperty(t,s,n),n};import{LitElement,html}from"lit";import{customElement,property,state}from"lit/decorators.js";import{repeat}from"lit/directives/repeat.js";import{live}from"lit/directives/live.js";import{createRef,ref}from"lit-html/directives/ref.js";import{classMap}from"lit/directives/class-map.js";import hotkeys from"hotkeys-js";import"./ninja-header.js";import"./ninja-action.js";import{footerHtml}from"./ninja-footer.js";import{baseStyles}from"./base-styles.js";let NinjaKeys=class extends LitElement{constructor(){super(...arguments),this.placeholder="Type a command or search...",this.disableHotkeys=!1,this.hideBreadcrumbs=!1,this.openHotkey="cmd+k,ctrl+k",this.navigationUpHotkey="up,shift+tab",this.navigationDownHotkey="down,tab",this.closeHotkey="esc",this.goBackHotkey="backspace",this.selectHotkey="enter",this.hotKeysJoinedView=!1,this.noAutoLoadMdIcons=!1,this.data=[],this.visible=!1,this._bump=!0,this._actionMatches=[],this._search="",this._flatData=[],this._headerRef=createRef()}open(e={}){this._bump=!0,this.visible=!0,this._headerRef.value.focusSearch(),this._actionMatches.length>0&&(this._selected=this._actionMatches[0]),this.setParent(e.parent)}close(){this._bump=!1,this.visible=!1}setParent(e){this._currentRoot=e||void 0,this._selected=void 0,this._search="",this._headerRef.value.setSearch("")}get breadcrumbs(){var e;const t=[];let s=null===(e=this._selected)||void 0===e?void 0:e.parent;if(s)for(t.push(s);s;){const e=this._flatData.find((e=>e.id===s));(null==e?void 0:e.parent)&&t.push(e.parent),s=e?e.parent:void 0}return t.reverse()}connectedCallback(){super.connectedCallback(),this.noAutoLoadMdIcons||document.fonts.load("24px Material Icons","apps").then((()=>{})),this._registerInternalHotkeys()}disconnectedCallback(){super.disconnectedCallback(),this._unregisterInternalHotkeys()}_flattern(e,t){let s=[];return e||(e=[]),e.map((e=>{const i=e.children&&e.children.some((e=>"string"==typeof e)),o={...e,parent:e.parent||t};return i||(o.children&&o.children.length&&(t=e.id,s=[...s,...o.children]),o.children=o.children?o.children.map((e=>e.id)):[]),o})).concat(s.length?this._flattern(s,t):s)}update(e){e.has("data")&&!this.disableHotkeys&&(this._flatData=this._flattern(this.data),this._flatData.filter((e=>!!e.hotkey)).forEach((e=>{hotkeys(e.hotkey,(t=>{t.preventDefault(),e.handler&&e.handler(e)}))}))),super.update(e)}_registerInternalHotkeys(){this.openHotkey&&hotkeys(this.openHotkey,(e=>{e.preventDefault(),this.visible?this.close():this.open()})),this.selectHotkey&&hotkeys(this.selectHotkey,(e=>{this.visible&&(e.preventDefault(),this._actionSelected(this._actionMatches[this._selectedIndex]))})),this.goBackHotkey&&hotkeys(this.goBackHotkey,(e=>{this.visible&&(this._search||(e.preventDefault(),this._goBack()))})),this.navigationDownHotkey&&hotkeys(this.navigationDownHotkey,(e=>{this.visible&&(e.preventDefault(),this._selectedIndex>=this._actionMatches.length-1?this._selected=this._actionMatches[0]:this._selected=this._actionMatches[this._selectedIndex+1])})),this.navigationUpHotkey&&hotkeys(this.navigationUpHotkey,(e=>{this.visible&&(e.preventDefault(),0===this._selectedIndex?this._selected=this._actionMatches[this._actionMatches.length-1]:this._selected=this._actionMatches[this._selectedIndex-1])})),this.closeHotkey&&hotkeys(this.closeHotkey,(()=>{this.visible&&this.close()}))}_unregisterInternalHotkeys(){this.openHotkey&&hotkeys.unbind(this.openHotkey),this.selectHotkey&&hotkeys.unbind(this.selectHotkey),this.goBackHotkey&&hotkeys.unbind(this.goBackHotkey),this.navigationDownHotkey&&hotkeys.unbind(this.navigationDownHotkey),this.navigationUpHotkey&&hotkeys.unbind(this.navigationUpHotkey),this.closeHotkey&&hotkeys.unbind(this.closeHotkey)}_actionFocused(e,t){this._selected=e,t.target.ensureInView()}_onTransitionEnd(){this._bump=!1}_goBack(){const e=this.breadcrumbs.length>1?this.breadcrumbs[this.breadcrumbs.length-2]:void 0;this.setParent(e)}render(){const e={bump:this._bump,"modal-content":!0},t={visible:this.visible,modal:!0},s=this._flatData.filter((e=>{var t;const s=new RegExp(this._search,"gi"),i=e.title.match(s)||(null===(t=e.keywords)||void 0===t?void 0:t.match(s));return(!this._currentRoot&&this._search||e.parent===this._currentRoot)&&i})).reduce(((e,t)=>e.set(t.section,[...e.get(t.section)||[],t])),new Map);this._actionMatches=[...s.values()].flat(),this._actionMatches.length>0&&-1===this._selectedIndex&&(this._selected=this._actionMatches[0]),0===this._actionMatches.length&&(this._selected=void 0);const i=e=>html` ${repeat(e,(e=>e.id),(e=>{var t;return html`this._actionFocused(e,t)} + @actionsSelected=${e=>this._actionSelected(e.detail)} + .action=${e} + >`}))}`,o=[];return s.forEach(((e,t)=>{const s=t?html`
    ${t}
    `:void 0;o.push(html`${s}${i(e)}`)})),html` +
    +
    + this.setParent(e.detail.parent)} + @close=${this.close} + > + + + ${footerHtml} +
    +
    + `}get _selectedIndex(){return this._selected?this._actionMatches.indexOf(this._selected):-1}_actionSelected(e){var t;if(this.dispatchEvent(new CustomEvent("selected",{detail:{search:this._search,action:e},bubbles:!0,composed:!0})),e){if(e.children&&(null===(t=e.children)||void 0===t?void 0:t.length)>0&&(this._currentRoot=e.id,this._search=""),this._headerRef.value.setSearch(""),this._headerRef.value.focusSearch(),e.handler){const t=e.handler(e);(null==t?void 0:t.keepOpen)||this.close()}this._bump=!0}}async _handleInput(e){this._search=e.detail.search,await this.updateComplete,this.dispatchEvent(new CustomEvent("change",{detail:{search:this._search,actions:this._actionMatches},bubbles:!0,composed:!0}))}_overlayClick(e){var t;(null===(t=e.target)||void 0===t?void 0:t.classList.contains("modal"))&&this.close()}};NinjaKeys.styles=[baseStyles],__decorate([property({type:String})],NinjaKeys.prototype,"placeholder",void 0),__decorate([property({type:Boolean})],NinjaKeys.prototype,"disableHotkeys",void 0),__decorate([property({type:Boolean})],NinjaKeys.prototype,"hideBreadcrumbs",void 0),__decorate([property()],NinjaKeys.prototype,"openHotkey",void 0),__decorate([property()],NinjaKeys.prototype,"navigationUpHotkey",void 0),__decorate([property()],NinjaKeys.prototype,"navigationDownHotkey",void 0),__decorate([property()],NinjaKeys.prototype,"closeHotkey",void 0),__decorate([property()],NinjaKeys.prototype,"goBackHotkey",void 0),__decorate([property()],NinjaKeys.prototype,"selectHotkey",void 0),__decorate([property({type:Boolean})],NinjaKeys.prototype,"hotKeysJoinedView",void 0),__decorate([property({type:Boolean})],NinjaKeys.prototype,"noAutoLoadMdIcons",void 0),__decorate([property({type:Array,hasChanged:()=>!0})],NinjaKeys.prototype,"data",void 0),__decorate([state()],NinjaKeys.prototype,"visible",void 0),__decorate([state()],NinjaKeys.prototype,"_bump",void 0),__decorate([state()],NinjaKeys.prototype,"_actionMatches",void 0),__decorate([state()],NinjaKeys.prototype,"_search",void 0),__decorate([state()],NinjaKeys.prototype,"_currentRoot",void 0),__decorate([state()],NinjaKeys.prototype,"_flatData",void 0),__decorate([state()],NinjaKeys.prototype,"breadcrumbs",null),__decorate([state()],NinjaKeys.prototype,"_selected",void 0),NinjaKeys=__decorate([customElement("ninja-keys")],NinjaKeys);export{NinjaKeys}; +//# sourceMappingURL=/sm/acda11afe47968a6322b61500a2789f4191d6f4dc73daf53212b55752de5b10c.map \ No newline at end of file diff --git a/assets/js/shortcut-key.js b/assets/js/shortcut-key.js new file mode 100644 index 0000000..2438fd7 --- /dev/null +++ b/assets/js/shortcut-key.js @@ -0,0 +1,16 @@ +document.addEventListener('DOMContentLoaded', function() { + // Detect if the user is on a Mac + const isMac = navigator.platform.toUpperCase().indexOf('MAC') >= 0; + + // Update the displayed shortcut text + const defaultThemeElements = document.querySelectorAll('.default-theme-text'); + const macThemeElements = document.querySelectorAll('.mac-theme-text'); + + defaultThemeElements.forEach(function(element) { + element.style.display = isMac ? 'none' : 'inline'; + }); + + macThemeElements.forEach(function(element) { + element.style.display = isMac ? 'inline' : 'none'; + }); +}); \ No newline at end of file From 42435dc4bff98dfc4a96ef1bc3bf5e428ea58247 Mon Sep 17 00:00:00 2001 From: Vatsal Sanjay Date: Sat, 1 Mar 2025 23:15:37 +0100 Subject: [PATCH 02/19] feat: Enhance search functionality and user experience MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This commit introduces several improvements to the search functionality and user experience: - Adds JavaScript dependencies for Marked.js and Fuse.js to enable advanced search capabilities - Integrates NinjaKeys library for seamless keyboard-driven search experience - Replaces the search button with a magnifying glass icon for a more intuitive visual cue - Simplifies the search input placeholder to "⌘K" to clearly indicate the keyboard shortcut - Removes the separate "Cmd+K" search button, as the keyboard shortcut is now the primary way to access search - Updates the README to reflect the new search functionality and keyboard shortcut --- README.md | 12 +++-- _layouts/default.html | 45 ++++------------ _layouts/research.html | 21 ++++---- _layouts/teaching.html | 20 +++---- _layouts/team.html | 60 +++++++++++++++++---- assets/css/search.css | 2 +- assets/js/search/search-ninja.js | 89 ++++++++++++++++++++++++++++++++ 7 files changed, 180 insertions(+), 69 deletions(-) create mode 100644 assets/js/search/search-ninja.js diff --git a/README.md b/README.md index 12cd0d2..df5def0 100644 --- a/README.md +++ b/README.md @@ -230,7 +230,7 @@ The website includes a powerful search feature that allows users to: - Get instant search results with highlighted matching text - See match percentage for each result - Navigate directly to specific sections using anchor links -- Access search via keyboard shortcut (cmd+k on Mac, ctrl+k on Windows) +- Access search via keyboard shortcut (⌘K on Mac, ctrl+K on Windows) or by clicking the magnifying glass icon in the navigation Search results are prioritized and filtered as follows: 1. Team Members (highest priority) @@ -243,10 +243,14 @@ Search results are prioritized and filtered as follows: 3. Blog Posts from blogs.comphy-lab.org 4. Regular content (headings and paragraphs) -Search behavior and restrictions: +Search behavior and features: - Minimum query length: 2 characters -- Keyboard shortcut (cmd+k / ctrl+k) opens a command palette style search interface -- Search button in navigation also provides visual access to the command palette +- Keyboard shortcut (⌘K / ctrl+K) opens a command palette style search interface on all pages +- Magnifying glass icon in navigation opens the search interface when clicked +- Search input in navigation shows the full "⌘K (search)" text by default +- NinjaKeys integration provides a modern command palette experience +- Search results appear instantly as you type +- Results are ranked by relevance and match percentage The search database is automatically generated during the build process by `scripts/generate_search_db.rb`. This script: - Indexes all HTML and markdown content diff --git a/_layouts/default.html b/_layouts/default.html index 3a165a0..78d47ab 100644 --- a/_layouts/default.html +++ b/_layouts/default.html @@ -57,7 +57,15 @@ + + + + + + + + - - - - - - - - - - diff --git a/_layouts/research.html b/_layouts/research.html index 73daded..bbf5035 100644 --- a/_layouts/research.html +++ b/_layouts/research.html @@ -184,21 +184,13 @@
  • - +
  • - -
  • - -
  • @@ -480,5 +472,14 @@

    Contents

    } }); + + + + + + + + + \ No newline at end of file diff --git a/_layouts/teaching.html b/_layouts/teaching.html index 215e7ac..4d4adb8 100644 --- a/_layouts/teaching.html +++ b/_layouts/teaching.html @@ -128,21 +128,13 @@
  • - +
  • - -
  • - -
  • @@ -250,5 +242,13 @@ }); + + + + + + + + \ No newline at end of file diff --git a/_layouts/team.html b/_layouts/team.html index 71db708..dea0b63 100644 --- a/_layouts/team.html +++ b/_layouts/team.html @@ -102,21 +102,13 @@
  • - +
  • - -
  • - -
  • @@ -325,5 +317,53 @@

    Team, collaborators, and Conference visits

    // Rest of your existing DOMContentLoaded code... }); + + + + + + + + + + + + \ No newline at end of file diff --git a/assets/css/search.css b/assets/css/search.css index 9c6c03f..2edfe15 100644 --- a/assets/css/search.css +++ b/assets/css/search.css @@ -11,7 +11,7 @@ } #searchInput { - width: 50px; + width: 75px; padding: 0.8rem 3rem 0.8rem 1rem; border: none; border-radius: 5px; diff --git a/assets/js/search/search-ninja.js b/assets/js/search/search-ninja.js new file mode 100644 index 0000000..1c88f7c --- /dev/null +++ b/assets/js/search/search-ninja.js @@ -0,0 +1,89 @@ +// search-ninja.js - Integration of NinjaKeys with search functionality +document.addEventListener('DOMContentLoaded', () => { + // Create and append the ninja-keys element to the body + const ninjaKeys = document.getElementById('search-modal') || document.createElement('ninja-keys'); + ninjaKeys.id = 'search-modal'; + ninjaKeys.setAttribute('placeholder', '⌘K (search)'); + ninjaKeys.setAttribute('data-info', 'Press ctrl+k to search (⌘+k on Mac)'); + + if (!document.getElementById('search-modal')) { + document.body.appendChild(ninjaKeys); + } + + // Get the base URL from meta tag if it exists + const baseUrl = document.querySelector('meta[name="base-url"]')?.content || ''; + let searchDatabase = null; + + // Load the search database + fetch(`${baseUrl}/assets/js/search_db.json`) + .then(response => response.json()) + .then(data => { + searchDatabase = data; + console.log(`Loaded search database with ${data.length} entries for ninja search`); + + // Once we have the data, update the ninja-keys data + updateNinjaKeysData(searchDatabase); + }) + .catch(error => { + console.error('Error loading search database for ninja search:', error); + }); + + // Function to update ninja-keys data based on search database + function updateNinjaKeysData(database) { + if (!database) return; + + // Create actions for ninja-keys + const actions = database.map(item => { + return { + id: item.url, + title: item.title, + section: item.section || 'Pages', + keywords: item.content, + handler: () => { + window.location.href = item.url; + return { keepOpen: false }; + } + }; + }); + + ninjaKeys.data = actions; + } + + // Add keyboard shortcut listener for cmd+k/ctrl+k + document.addEventListener('keydown', (e) => { + // Check if cmd+k or ctrl+k is pressed + if ((e.metaKey || e.ctrlKey) && e.key === 'k') { + e.preventDefault(); // Prevent default browser behavior + + // Focus the search input if it exists + const searchInput = document.getElementById('searchInput'); + if (searchInput) { + searchInput.focus(); + + // If ninja-keys is available, open it + if (ninjaKeys) { + ninjaKeys.open(); + } + } + } + }); + + // Add click event listener to the search button + const searchButton = document.getElementById('searchButton'); + if (searchButton) { + searchButton.addEventListener('click', (e) => { + e.preventDefault(); + + // Focus the search input if it exists + const searchInput = document.getElementById('searchInput'); + if (searchInput) { + searchInput.focus(); + + // If ninja-keys is available, open it + if (ninjaKeys) { + ninjaKeys.open(); + } + } + }); + } +}); \ No newline at end of file From bb3d82734d67f3740b3f372883728c91e4b8ce61 Mon Sep 17 00:00:00 2001 From: Vatsal Sanjay Date: Sat, 1 Mar 2025 23:22:41 +0100 Subject: [PATCH 03/19] feat(search): Improve search input and button styles Modify the styles for the search input and button to enhance the user experience. The key changes are: - Increase the width of the search input to 75px - Reduce the right padding of the search input to 1.6rem - Reduce the left padding of the search input to 0.8rem - Reduce the font size of the search icon to 1rem - Remove the left margin of the search icon - Add a 24px width and height, 50% border-radius, and centered positioning to the search button - Add a light gray background color to the search button These changes make the search input and button more visually appealing and better aligned with the overall design of the application. --- assets/css/search.css | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/assets/css/search.css b/assets/css/search.css index 2edfe15..1d2bbd7 100644 --- a/assets/css/search.css +++ b/assets/css/search.css @@ -12,7 +12,7 @@ #searchInput { width: 75px; - padding: 0.8rem 3rem 0.8rem 1rem; + padding: 0.8rem 1.6rem 0.8rem 0.8rem; border: none; border-radius: 5px; background: rgba(255, 255, 255, 0.9); @@ -30,10 +30,9 @@ .search-icon { position: absolute; - right: 1rem; color: #666; - font-size: 1.4rem; - margin-left: 8px; + font-size: 1rem; + margin-left: 0; } .search-button { @@ -45,6 +44,14 @@ align-items: center; justify-content: center; transition: color 0.2s ease; + width: 24px; + height: 24px; + border-radius: 50%; + position: absolute; + right: 0.4rem; + top: 50%; + transform: translateY(-50%); + background-color: #f2f2f2; } .search-button:hover .search-icon { From 29951fb57e6c07eafd62a9dbfd3238483d0180be Mon Sep 17 00:00:00 2001 From: Vatsal Sanjay Date: Sat, 1 Mar 2025 23:30:02 +0100 Subject: [PATCH 04/19] feat: Enhance search database with teaching content and research papers This commit makes the following improvements to the search database: - Identifies and prioritizes teaching content, in addition to team members and research papers - Processes teaching pages and course details to include in the search index - Extracts tags from research papers to improve search relevance - Fetches and indexes blog posts from the comphy-lab.org blog These changes will provide users with a more comprehensive and relevant search experience, allowing them to easily find teaching materials, research papers, and other important content on the website. --- README.md | 15 +- assets/js/search_db.json | 1982 +++++++++++++++++++++++++++++---- scripts/generate_search_db.rb | 170 +++ 3 files changed, 1919 insertions(+), 248 deletions(-) diff --git a/README.md b/README.md index df5def0..f4fb2e9 100644 --- a/README.md +++ b/README.md @@ -237,11 +237,15 @@ Search results are prioritized and filtered as follows: - Direct matches in names - Research interests and affiliations - Social media links and profile information -2. Research Papers +2. Teaching Content + - Course titles and descriptions + - Course details (dates, locations, prerequisites) + - Course schedules and topics +3. Research Papers - Titles and authors - Tags and categories -3. Blog Posts from blogs.comphy-lab.org -4. Regular content (headings and paragraphs) +4. Blog Posts from blogs.comphy-lab.org +5. Regular content (headings and paragraphs) Search behavior and features: - Minimum query length: 2 characters @@ -251,11 +255,12 @@ Search behavior and features: - NinjaKeys integration provides a modern command palette experience - Search results appear instantly as you type - Results are ranked by relevance and match percentage - +in The search database is automatically generated during the build process by `scripts/generate_search_db.rb`. This script: - Indexes all HTML and markdown content -- Identifies and prioritizes team members and research papers +- Identifies and prioritizes team members, teaching content, and research papers - Extracts tags from research papers +- Processes teaching pages and course details - Fetches and indexes blog posts from blogs.comphy-lab.org - Generates a JSON database used by the search functionality diff --git a/assets/js/search_db.json b/assets/js/search_db.json index 81a2cc1..2dd2400 100644 --- a/assets/js/search_db.json +++ b/assets/js/search_db.json @@ -427,436 +427,1932 @@ "priority": 3 }, { - "title": "0_ToDo-Blog-public - tags:", - "content": "tags:\n - \"#TODO-master\"Here is a list of things on our to-publish list:\nStokes waves and related topics 📅 2025-03-01 ⏳ 2025-02-05 \nShowcase bubbles, waves: 📅 2025-01-12 \nTo-Do: Blog Posts on Research Papers and Thesis Below is a list of all the papers and the thesis requiring a dedicated blog post.", - "url": "https://blogs.comphy-lab.org/0_ToDo-Blog-public", - "type": "blog_excerpt", + "title": "\"High-Fidelity Simulations Using Basilisk C\" - High-Fidelity Simulations Using Basilisk C", + "content": "
    \n
    \n

    Dates

    \n

    March 10-13, 2025

    \n
    \n
    \n

    Location

    \n

    Universidad Carlos III de Madrid, Spain

    \n
    \n
    \n

    Duration

    \n

    4 days, full-time

    \n
    \n
    ", + "url": "/teaching/2025-Basilisk101-Madrid#High-Fidelity%2BSimulations%2BUsing%2BBasilisk%2BC", + "type": "teaching_content", + "priority": 2 + }, + { + "title": "\"High-Fidelity Simulations Using Basilisk C\" - What will you learn?", + "content": "- **Think before you compute!** Understanding the physics before implementation\n- **Writing the first code in Basilisk C** Getting comfortable with the framework\n- **Solving conservation equations** Numerical approaches to fluid dynamics\n- **Interface tracking methods** Capturing multiphase phenomena accurately\n- **Non-Newtonian flows** Modeling complex rheological behaviors", + "url": "/teaching/2025-Basilisk101-Madrid#What%2Bwill%2Byou%2Blearn", + "type": "teaching_content", + "priority": 2 + }, + { + "title": "\"High-Fidelity Simulations Using Basilisk C\" - What will you learn?", + "content": "- **Think before you compute!** Understanding the physics before implementation\n- **Writing the first code in Basilisk C** Getting comfortable with the framework\n- **Solving conservation equations** Numerical approaches to fluid dynamics\n- **Interface tracking methods** Capturing multiphase phenomena accurately\n- **Non-Newtonian flows** Modeling complex rheological behaviors", + "url": "/teaching/2025-Basilisk101-Madrid#What%2Bwill%2Byou%2Blearn", + "type": "teaching_paragraph", + "priority": 2 + }, + { + "title": "\"High-Fidelity Simulations Using Basilisk C\" - Course Description", + "content": "This intensive 4-day course provides a comprehensive introduction to high-fidelity simulations using Basilisk C, a powerful computational framework for fluid dynamics. Participants will learn to implement and solve complex fluid mechanics problems with an emphasis on multiphase flows, interface dynamics, and non-Newtonian rheology.\n\nThe course combines theoretical lectures with extensive hands-on sessions, allowing participants to immediately apply concepts through guided coding exercises. By the end of the course, you'll be able to develop your own simulations for a variety of fluid dynamics applications.", + "url": "/teaching/2025-Basilisk101-Madrid#Course%2BDescription", + "type": "teaching_content", + "priority": 2 + }, + { + "title": "\"High-Fidelity Simulations Using Basilisk C\" - Course Description", + "content": "This intensive 4-day course provides a comprehensive introduction to high-fidelity simulations using Basilisk C, a powerful computational framework for fluid dynamics. Participants will learn to implement and solve complex fluid mechanics problems with an emphasis on multiphase flows, interface dynamics, and non-Newtonian rheology.", + "url": "/teaching/2025-Basilisk101-Madrid#Course%2BDescription", + "type": "teaching_paragraph", + "priority": 2 + }, + { + "title": "\"High-Fidelity Simulations Using Basilisk C\" - Course Description", + "content": "The course combines theoretical lectures with extensive hands-on sessions, allowing participants to immediately apply concepts through guided coding exercises. By the end of the course, you'll be able to develop your own simulations for a variety of fluid dynamics applications.", + "url": "/teaching/2025-Basilisk101-Madrid#Course%2BDescription", + "type": "teaching_paragraph", + "priority": 2 + }, + { + "title": "\"High-Fidelity Simulations Using Basilisk C\" - Think before you compute", + "content": "- **10:00-11:30** | **Lecture (1a)**\n - Conservation laws and the numerical solution of the Navier–Stokes equations\n- **11:45-13:00** | **Lecture (1b)**\n - Advection-diffusion, diffusion-reaction, and other transport equations\n - *Brief intro to Basilisk coding framework*", + "url": "/teaching/2025-Basilisk101-Madrid#Think%2Bbefore%2Byou%2Bcompute", + "type": "teaching_content", + "priority": 2 + }, + { + "title": "\"High-Fidelity Simulations Using Basilisk C\" - Think before you compute", + "content": "- **10:00-11:30** | **Lecture (1a)**\n - Conservation laws and the numerical solution of the Navier–Stokes equations\n- **11:45-13:00** | **Lecture (1b)**\n - Advection-diffusion, diffusion-reaction, and other transport equations\n - *Brief intro to Basilisk coding framework*", + "url": "/teaching/2025-Basilisk101-Madrid#Think%2Bbefore%2Byou%2Bcompute", + "type": "teaching_paragraph", + "priority": 2 + }, + { + "title": "\"High-Fidelity Simulations Using Basilisk C\" - First coding steps", + "content": "- **15:00-18:00** | **Hybrid Session**\n - Implementing basic transport equations in Basilisk C\n - *Whiteboard + coding*", + "url": "/teaching/2025-Basilisk101-Madrid#First%2Bcoding%2Bsteps", + "type": "teaching_content", + "priority": 2 + }, + { + "title": "\"High-Fidelity Simulations Using Basilisk C\" - First coding steps", + "content": "- **15:00-18:00** | **Hybrid Session**\n - Implementing basic transport equations in Basilisk C\n - *Whiteboard + coding*", + "url": "/teaching/2025-Basilisk101-Madrid#First%2Bcoding%2Bsteps", + "type": "teaching_paragraph", + "priority": 2 + }, + { + "title": "\"High-Fidelity Simulations Using Basilisk C\" - Coding like a pro", + "content": "- **10:00-11:15** | **Hackathon (1c)**\n - Using headers in Basilisk, modular code structure, problem setup, and compilation\n- **11:30-13:00** | **Hackathon Continued**\n - Expanding on the morning tasks and code debugging", + "url": "/teaching/2025-Basilisk101-Madrid#Coding%2Blike%2Ba%2Bpro", + "type": "teaching_content", + "priority": 2 + }, + { + "title": "\"High-Fidelity Simulations Using Basilisk C\" - Coding like a pro", + "content": "- **10:00-11:15** | **Hackathon (1c)**\n - Using headers in Basilisk, modular code structure, problem setup, and compilation\n- **11:30-13:00** | **Hackathon Continued**\n - Expanding on the morning tasks and code debugging", + "url": "/teaching/2025-Basilisk101-Madrid#Coding%2Blike%2Ba%2Bpro", + "type": "teaching_paragraph", + "priority": 2 + }, + { + "title": "\"High-Fidelity Simulations Using Basilisk C\" - Interface tracking methods", + "content": "- **10:00-11:30** | **Lecture (2a)**\n - Interface tracking methods (VoF, level set, phase-field approaches) and numerical strategies\n- **11:45-13:00** | **Hackathon (2b)**\n - Hands-on tutorial applying interface-tracking to a simple two-phase problem", + "url": "/teaching/2025-Basilisk101-Madrid#Interface%2Btracking%2Bmethods", + "type": "teaching_content", + "priority": 2 + }, + { + "title": "\"High-Fidelity Simulations Using Basilisk C\" - Interface tracking methods", + "content": "- **10:00-11:30** | **Lecture (2a)**\n - Interface tracking methods (VoF, level set, phase-field approaches) and numerical strategies\n- **11:45-13:00** | **Hackathon (2b)**\n - Hands-on tutorial applying interface-tracking to a simple two-phase problem", + "url": "/teaching/2025-Basilisk101-Madrid#Interface%2Btracking%2Bmethods", + "type": "teaching_paragraph", + "priority": 2 + }, + { + "title": "\"High-Fidelity Simulations Using Basilisk C\" - Seminar", + "content": "- **13:30-14:00** | **Department seminar (2c)**\n - A note on the thrust of airfoils by [José Mnauel Gordillo](https://scholar.google.com/citations?user=14wOsewAAAAJ&hl=en&inst=5726176096060060532&oi=ao)", + "url": "/teaching/2025-Basilisk101-Madrid#Seminar", + "type": "teaching_content", + "priority": 2 + }, + { + "title": "\"High-Fidelity Simulations Using Basilisk C\" - Seminar", + "content": "- **13:30-14:00** | **Department seminar (2c)**\n - A note on the thrust of airfoils by [José Mnauel Gordillo](https://scholar.google.com/citations?user=14wOsewAAAAJ&hl=en&inst=5726176096060060532&oi=ao)", + "url": "/teaching/2025-Basilisk101-Madrid#Seminar", + "type": "teaching_paragraph", + "priority": 2 + }, + { + "title": "\"High-Fidelity Simulations Using Basilisk C\" - Non-Newtonian flows", + "content": "- **15:00-16:00** | **Lecture (3a)**\n - Non-Newtonian flows: viscoplasticity and viscoelasticity\n- **16:15-18:00** | **Hackathon (3b)**\n - Coding exercises for shear-thinning, viscoplastic, or viscoelastic fluids", + "url": "/teaching/2025-Basilisk101-Madrid#Non-Newtonian%2Bflows", + "type": "teaching_content", + "priority": 2 + }, + { + "title": "\"High-Fidelity Simulations Using Basilisk C\" - Non-Newtonian flows", + "content": "- **15:00-16:00** | **Lecture (3a)**\n - Non-Newtonian flows: viscoplasticity and viscoelasticity\n- **16:15-18:00** | **Hackathon (3b)**\n - Coding exercises for shear-thinning, viscoplastic, or viscoelastic fluids", + "url": "/teaching/2025-Basilisk101-Madrid#Non-Newtonian%2Bflows", + "type": "teaching_paragraph", + "priority": 2 + }, + { + "title": "\"High-Fidelity Simulations Using Basilisk C\" - Special topics", + "content": "- **10:00-11:30** | **Lecture (4a)**\n - Special Topics: multilayer solver, lubrication equation, Marangoni flows, manifold death, and research-oriented examples\n- **11:45-13:00** | **Hackathon (4b)**\n - Focused tutorials on the special topics introduced in the lecture\n- **15:00-16:30** | **Lecture (4c)**\n - Open discussion, deeper dives into advanced features, final code reviews, and next steps\n\n---", + "url": "/teaching/2025-Basilisk101-Madrid#Special%2Btopics", + "type": "teaching_content", + "priority": 2 + }, + { + "title": "\"High-Fidelity Simulations Using Basilisk C\" - Special topics", + "content": "- **10:00-11:30** | **Lecture (4a)**\n - Special Topics: multilayer solver, lubrication equation, Marangoni flows, manifold death, and research-oriented examples\n- **11:45-13:00** | **Hackathon (4b)**\n - Focused tutorials on the special topics introduced in the lecture\n- **15:00-16:30** | **Lecture (4c)**\n - Open discussion, deeper dives into advanced features, final code reviews, and next steps", + "url": "/teaching/2025-Basilisk101-Madrid#Special%2Btopics", + "type": "teaching_paragraph", + "priority": 2 + }, + { + "title": "\"High-Fidelity Simulations Using Basilisk C\" - Prerequisites", + "content": "- Basic knowledge of fluid mechanics\n- Experience with programming (any language, C preferred)\n- Understanding of partial differential equations\n- Laptop with ability to compile C code", + "url": "/teaching/2025-Basilisk101-Madrid#Prerequisites", + "type": "teaching_content", + "priority": 2 + }, + { + "title": "\"High-Fidelity Simulations Using Basilisk C\" - Prerequisites", + "content": "- Basic knowledge of fluid mechanics\n- Experience with programming (any language, C preferred)\n- Understanding of partial differential equations\n- Laptop with ability to compile C code", + "url": "/teaching/2025-Basilisk101-Madrid#Prerequisites", + "type": "teaching_paragraph", + "priority": 2 + }, + { + "title": "\"High-Fidelity Simulations Using Basilisk C\" - Registration", + "content": "For registration details, please contact \n
    \n bubbles@ing.uc3m.es\n \n
    \n
    \n vatsalsy@comphy-lab.org\n \n
    \n\n\n\n", + "url": "/teaching/2025-Basilisk101-Madrid#Registration", + "type": "teaching_content", + "priority": 2 + }, + { + "title": "\"High-Fidelity Simulations Using Basilisk C\" - Registration", + "content": "For registration details, please contact \n
    \n bubbles@ing.uc3m.es\n \n
    \n
    \n vatsalsy@comphy-lab.org\n \n
    ", + "url": "/teaching/2025-Basilisk101-Madrid#Registration", + "type": "teaching_paragraph", + "priority": 2 + }, + { + "title": "Teaching - Teaching", + "content": "Welcome to the CoMPhy Lab's educational resources. Apart from the university courses, we aim to develop and offer a range of workshops and tutorials on modern computational methods for multiphase flows and high-fidelity simulations.\n\n
    \n
    \n \"Basilisk\n
    \n

    High-Fidelity Simulations Using Basilisk C

    \n
    \n Universidad Carlos III de Madrid\n
    \n
    \n March 10-13, 2025\n
    \n

    \n A comprehensive course on using Basilisk C for simulating multiphase flows, interface tracking, and solving conservation equations. Learn to tackle complex fluid dynamics problems with high-fidelity numerical methods.\n

    \n View Course\n
    \n
    \n
    ", + "url": "/teaching/#Teaching", + "type": "teaching_content", + "priority": 2 + }, + { + "title": "Teaching - Teaching", + "content": "Welcome to the CoMPhy Lab's educational resources. Apart from the university courses, we aim to develop and offer a range of workshops and tutorials on modern computational methods for multiphase flows and high-fidelity simulations.", + "url": "/teaching/#Teaching", + "type": "teaching_paragraph", + "priority": 2 + }, + { + "title": "Teaching - About Our Teaching Philosophy", + "content": "At CoMPhy Lab, we believe in hands-on learning and deep understanding of computational methods. Our courses combine theoretical foundations with practical implementation, allowing students to develop both conceptual understanding and technical skills.\n\nOur teaching approach emphasizes:\n\n- **Think before you compute**: Understanding the underlying physics before implementation\n- **Modular code structure**: Building maintainable and extensible computational tools\n- **Advanced numerical methods**: Mastering state-of-the-art techniques for complex problems\n- **Open science**: Sharing knowledge and tools with the scientific community. Checkout \n\nIf you're interested in hosting a course or workshop with CoMPhy Lab, please [contact us](/join) for collaboration opportunities.", + "url": "/teaching/#About%2BOur%2BTeaching%2BPhilosophy", + "type": "teaching_content", + "priority": 2 + }, + { + "title": "Teaching - About Our Teaching Philosophy", + "content": "At CoMPhy Lab, we believe in hands-on learning and deep understanding of computational methods. Our courses combine theoretical foundations with practical implementation, allowing students to develop both conceptual understanding and technical skills.", + "url": "/teaching/#About%2BOur%2BTeaching%2BPhilosophy", + "type": "teaching_paragraph", + "priority": 2 + }, + { + "title": "Teaching - About Our Teaching Philosophy", + "content": "- **Think before you compute**: Understanding the underlying physics before implementation\n- **Modular code structure**: Building maintainable and extensible computational tools\n- **Advanced numerical methods**: Mastering state-of-the-art techniques for complex problems\n- **Open science**: Sharing knowledge and tools with the scientific community. Checkout ", + "url": "/teaching/#About%2BOur%2BTeaching%2BPhilosophy", + "type": "teaching_paragraph", + "priority": 2 + }, + { + "title": "Teaching - About Our Teaching Philosophy", + "content": "If you're interested in hosting a course or workshop with CoMPhy Lab, please [contact us](/join) for collaboration opportunities.", + "url": "/teaching/#About%2BOur%2BTeaching%2BPhilosophy", + "type": "teaching_paragraph", + "priority": 2 + }, + { + "title": "404! But, there are no dragons either.", + "content": "Seems like you didn't find what you wanted. But don't worry - we've got plenty of exciting projects \n waiting for you to explore!\n Check Out:", + "url": "/404.html#404%2BBut%2Bthere%2Bare%2Bno%2Bdragons%2Beither", + "type": "text", + "links": [], + "priority": 3 + }, + { + "title": "404! But, there are no dragons either.", + "content": "Take Me Home", + "url": "/404.html#404%2BBut%2Bthere%2Bare%2Bno%2Bdragons%2Beither", + "type": "text", + "links": [ + "" + ], + "priority": 3 + }, + { + "title": "404! But, there are no dragons either.", + "content": "© Copyright\n CoMPhy Lab 2025", + "url": "/404.html#404%2BBut%2Bthere%2Bare%2Bno%2Bdragons%2Beither", + "type": "text", + "links": [], + "priority": 3 + }, + { + "title": "About", + "content": "If you are not redirected automatically, please click here.", + "url": "/about.html#About", + "type": "text", + "links": [ + "#about" + ], + "priority": 3 + }, + { + "title": "Contact", + "content": "Redirecting to Join Us page...", + "url": "/contact/index.html#Contact", + "type": "text", + "links": [], + "priority": 3 + }, + { + "title": "Contact", + "content": "If you are not redirected automatically, click here.", + "url": "/contact/index.html#Contact", + "type": "text", + "links": [ + "join" + ], + "priority": 3 + }, + { + "title": "Contact", + "content": "© Copyright\n CoMPhy Lab 2025", + "url": "/contact/index.html#Contact", + "type": "text", + "links": [], + "priority": 3 + }, + { + "title": "Contact", + "content": "Redirecting to Join Us page... If you are not redirected automatically, click here.", + "url": "/contact/index.html#Contact", + "type": "section", + "links": [ + "join" + ], + "priority": 3 + }, + { + "title": "Computational Multiphase \n Physics (CoMPhy) Lab", + "content": "Picture: Worthington jet formed due to bursting bubble.", + "url": "/index.html#Computational%2BMultiphase%2BPhysics%2BCoMPhy%2BLab", + "type": "text", + "links": [ + "research/#7" + ], + "priority": 3 + }, + { + "title": "Computational Multiphase \n Physics (CoMPhy) Lab", + "content": "Featured Research", + "url": "/index.html#Computational%2BMultiphase%2BPhysics%2BCoMPhy%2BLab", + "type": "text", + "links": [ + "#featured" + ], + "priority": 3 + }, + { + "title": "Featured Research", + "content": "© Copyright\n CoMPhy Lab 2025", + "url": "/index.html#Featured%2BResearch", + "type": "text", + "links": [], + "priority": 3 + }, + { + "title": "PhD Positions", + "content": "We are always looking for enthusiastic PhD students interested in:", + "url": "/join/index.html#PhD%2BPositions", + "type": "text", + "links": [], + "priority": 3 + }, + { + "title": "PhD Positions", + "content": "If you are interested, please send your:", + "url": "/join/index.html#PhD%2BPositions", + "type": "text", + "links": [], + "priority": 3 + }, + { + "title": "PhD Positions", + "content": "to:", + "url": "/join/index.html#PhD%2BPositions", + "type": "text", + "links": [], + "priority": 3 + }, + { + "title": "Master's Projects", + "content": "We have several ongoing projects suitable for master's students. Current opportunities are listed at the project page.", + "url": "/join/index.html#Master%27s%2BProjects", + "type": "text", + "links": [], + "priority": 3 + }, + { + "title": "Master's Projects", + "content": "Contact us to discuss these or other potential projects.", + "url": "/join/index.html#Master%27s%2BProjects", + "type": "text", + "links": [], + "priority": 3 + }, + { + "title": "Master's Projects", + "content": "© Copyright\n CoMPhy Lab 2025", + "url": "/join/index.html#Master%27s%2BProjects", + "type": "text", + "links": [], + "priority": 3 + }, + { + "title": "PhD Positions", + "content": "We are always looking for enthusiastic PhD students interested in: High-performance Computing\n Multiphase flows\n Physics-based modeling\n Soft singularities", + "url": "/join/index.html#PhD%2BPositions", + "type": "section", + "links": [], + "priority": 3 + }, + { + "title": "Master's Projects", + "content": "We have several ongoing projects suitable for master's students. Current opportunities are listed at the project page. Contact us to discuss these or other potential projects.", + "url": "/join/index.html#Master%27s%2BProjects", + "type": "section", + "links": [], + "priority": 3 + }, + { + "title": "Demirkır, Ç., Yang, R., Bashkatov, A., Sanjay, V., Lohse, D., & Krug, D. To jump or not to jump: Adhesion and viscous dissipation dictate the detachment of coalescing wall-attached bubbles. Submitted to Phys. Rev. Lett. (2025).", + "content": "", + "url": "/research/index.html#Demirkr%2BYang%2BR%2BBashkatov%2BA%2BSanjay%2BV%2BLohse%2BD%2BKrug%2BD%2BTo%2Bjump%2Bor%2Bnot%2Bto%2Bjump%3A%2BAdhesion%2Band%2Bviscous%2Bdissipation%2Bdictate%2Bthe%2Bdetachment%2Bof%2Bcoalescing%2Bwall-attached%2Bbubbles%2BSubmitted%2Bto%2BPhys%2BRev%2BLett%2B2025", + "type": "paper", + "tags": [ + "Bubbles", + "Coalescence" + ], + "priority": 3 + }, + { + "title": "Dixit, A. K., Oratis, A., Zinelis, K., Lohse, D., & Sanjay, V. Viscoelastic Worthington jets & droplets produced by bursting bubbles. Received positive reviews in J. Fluid Mech. (2025).", + "content": "", + "url": "/research/index.html#Dixit%2BA%2BK%2BOratis%2BA%2BZinelis%2BK%2BLohse%2BD%2BSanjay%2BV%2BViscoelastic%2BWorthington%2Bjets%2Bdroplets%2Bproduced%2Bby%2Bbursting%2Bbubbles%2BReceived%2Bpositive%2Breviews%2Bin%2BJ%2BFluid%2BMech%2B2025", + "type": "paper", + "tags": [ + "Bubbles", + "Non-Newtonian", + "Jets", + "Soft-matter-singularities", + "Drops" + ], + "priority": 3 + }, + { + "title": "Sanjay, V., & Lohse, D. Unifying theory of scaling in drop impact: Forces & maximum spreading diameter. Received positive reviews in Phys. Rev. Lett. (2025).", + "content": "", + "url": "/research/index.html#Sanjay%2BV%2BLohse%2BD%2BUnifying%2Btheory%2Bof%2Bscaling%2Bin%2Bdrop%2Bimpact%3A%2BForces%2Bmaximum%2Bspreading%2Bdiameter%2BReceived%2Bpositive%2Breviews%2Bin%2BPhys%2BRev%2BLett%2B2025", + "type": "paper", + "tags": [ + "Drops", + "Dissipative anamoly", + "Superamphiphobic-surfaces", + "Impact forces" + ], + "priority": 3 + }, + { + "title": "Bashkatov, A., Bürkle, F., Demirkır, Ç., Ding, W., Sanjay, V., Babich, A., Yang, X., Mutschke, G., Czarske, J., Lohse, D., et al. Electrolyte spraying within H₂ bubbles during water electrolysis. Received positive reviews in Nat. Commun. (2025).", + "content": "", + "url": "/research/index.html#Bashkatov%2BA%2BBrkle%2BF%2BDemirkr%2BDing%2BW%2BSanjay%2BV%2BBabich%2BA%2BYang%2BX%2BMutschke%2BG%2BCzarske%2BJ%2BLohse%2BD%2Bet%2Bal%2BElectrolyte%2Bspraying%2Bwithin%2BH%2Bbubbles%2Bduring%2Bwater%2Belectrolysis%2BReceived%2Bpositive%2Breviews%2Bin%2BNat%2BCommun%2B2025", + "type": "paper", + "tags": [ + "Bubbles", + "Jets", + "Coalescence", + "Soft-matter-singularities", + "Drops" + ], + "priority": 3 + }, + { + "title": "[14] Sanjay, V., Zhang, B., Lv, C., & Lohse, D. The role of viscosity on drop impact forces on non-wetting surfaces. J. Fluid Mech., 1004, A6 (2025).", + "content": "", + "url": "/research/index.html#14%2BSanjay%2BV%2BZhang%2BB%2BLv%2BC%2BLohse%2BD%2BThe%2Brole%2Bof%2Bviscosity%2Bon%2Bdrop%2Bimpact%2Bforces%2Bon%2Bnon-wetting%2Bsurfaces%2BJ%2BFluid%2BMech%2B1004%2BA6%2B2025", + "type": "paper", + "tags": [ + "Drops", + "Impact forces", + "Featured" + ], + "priority": 3 + }, + { + "title": "[13] Kayal, L., Sanjay, V., Yewale, N., Kumar, A., & Dasgupta, R. Focusing of concentric free-surface waves. J. Fluid Mech., 1003, A14 (2025).", + "content": "", + "url": "/research/index.html#13%2BKayal%2BL%2BSanjay%2BV%2BYewale%2BN%2BKumar%2BA%2BDasgupta%2BR%2BFocusing%2Bof%2Bconcentric%2Bfree-surface%2Bwaves%2BJ%2BFluid%2BMech%2B1003%2BA14%2B2025", + "type": "paper", + "tags": [ + "Waves", + "Dissipative anamoly" + ], + "priority": 3 + }, + { + "title": "[12] Balasubramanian, A. G., Sanjay, V., Jalaal, M., Vinuesa, R., & Tammisola, O. Bursting bubble in an elastoviscoplastic medium. J. Fluid Mech., 1001, A9 (2024).", + "content": "", + "url": "/research/index.html#12%2BBalasubramanian%2BA%2BG%2BSanjay%2BV%2BJalaal%2BM%2BVinuesa%2BR%2BTammisola%2BO%2BBursting%2Bbubble%2Bin%2Ban%2Belastoviscoplastic%2Bmedium%2BJ%2BFluid%2BMech%2B1001%2BA9%2B2024", + "type": "paper", + "tags": [ + "Bubbles", + "Non-Newtonian", + "Jets", + "Soft-matter-singularities", + "Featured" + ], + "priority": 3 + }, + { + "title": "[11] Sanjay, V., Chantelot, P., & Lohse, D. When does an impacting drop stop bouncing? J. Fluid Mech., 958, A26 (2023).", + "content": "", + "url": "/research/index.html#11%2BSanjay%2BV%2BChantelot%2BP%2BLohse%2BD%2BWhen%2Bdoes%2Ban%2Bimpacting%2Bdrop%2Bstop%2Bbouncing%2BJ%2BFluid%2BMech%2B958%2BA26%2B2023", + "type": "paper", + "tags": [ + "Drops", + "Bouncing", + "Dissipative anamoly" + ], + "priority": 3 + }, + { + "title": "[10] Sanjay, V., Lakshman, S., Chantelot, P., Snoeijer, J. H., & Lohse, D. Drop impact on viscous liquid films. J. Fluid Mech., 958, A25 (2023).", + "content": "", + "url": "/research/index.html#10%2BSanjay%2BV%2BLakshman%2BS%2BChantelot%2BP%2BSnoeijer%2BJ%2BH%2BLohse%2BD%2BDrop%2Bimpact%2Bon%2Bviscous%2Bliquid%2Bfilms%2BJ%2BFluid%2BMech%2B958%2BA25%2B2023", + "type": "paper", + "tags": [ + "Drops", + "Bouncing", + "Superhydrophobic surfaces" + ], + "priority": 3 + }, + { + "title": "➡️ Sanjay, V. Viscous free-surface flows. Ph.D. Thesis, Physics of Fluids, University of Twente (2022).", + "content": "", + "url": "/research/index.html#%2BSanjay%2BV%2BViscous%2Bfree-surface%2Bflows%2BPhD%2BThesis%2BPhysics%2Bof%2BFluids%2BUniversity%2Bof%2BTwente%2B2022", + "type": "paper", + "tags": [ + "Drops", + "Jets", + "Sheets", + "Bubbles", + "Soft-matter-singularities", + "Non-Newtonian" + ], + "priority": 3 + }, + { + "title": "[9] Sanjay, V. Taylor--Culick retractions and the influence of the surroundings. J. Fluid Mech., 948, A14 (2022).", + "content": "", + "url": "/research/index.html#9%2BSanjay%2BV%2BTaylor--Culick%2Bretractions%2Band%2Bthe%2Binfluence%2Bof%2Bthe%2Bsurroundings%2BJ%2BFluid%2BMech%2B948%2BA14%2B2022", + "type": "paper", + "tags": [ + "Sheets", + "Dissipative anamoly", + "Retraction" + ], + "priority": 3 + }, + { + "title": "[8] Zhang, B., Sanjay, V., Shi, S., and Zhao, Y., and Lv, C., and Feng, X.-Q., & Lohse, D. Impact forces of water drops falling on superhydrophobic surfaces. Phys. Rev. Lett., 129(10), 104501 (2022).", + "content": "", + "url": "/research/index.html#8%2BZhang%2BB%2BSanjay%2BV%2BShi%2BS%2Band%2BZhao%2BY%2Band%2BLv%2BC%2Band%2BFeng%2BX-Q%2BLohse%2BD%2BImpact%2Bforces%2Bof%2Bwater%2Bdrops%2Bfalling%2Bon%2Bsuperhydrophobic%2Bsurfaces%2BPhys%2BRev%2BLett%2B12910%2B104501%2B2022", + "type": "paper", + "tags": [ + "Drops", + "Superhydrophobic surfaces", + "Impact forces" + ], + "priority": 3 + }, + { + "title": "[7] Sanjay, V., Lohse, D., & Jalaal, M. Bursting bubble in a viscoplastic medium. J. Fluid Mech., 922, A2 (2021).", + "content": "", + "url": "/research/index.html#7%2BSanjay%2BV%2BLohse%2BD%2BJalaal%2BM%2BBursting%2Bbubble%2Bin%2Ba%2Bviscoplastic%2Bmedium%2BJ%2BFluid%2BMech%2B922%2BA2%2B2021", + "type": "paper", + "tags": [ + "Bubbles", + "Jets", + "Non-Newtonian", + "Soft-matter-singularities" + ], + "priority": 3 + }, + { + "title": "[6] Ramírez-Soto, O., Sanjay, V., Lohse, D., Pham, J. T., & Vollmer, D. Lifting a sessile oil drop from a superamphiphobic surface with an impacting one. Sci. Adv., 6(34), eaba4330 (2020).", + "content": "", + "url": "/research/index.html#6%2BRamrez-Soto%2BO%2BSanjay%2BV%2BLohse%2BD%2BPham%2BJ%2BT%2BVollmer%2BD%2BLifting%2Ba%2Bsessile%2Boil%2Bdrop%2Bfrom%2Ba%2Bsuperamphiphobic%2Bsurface%2Bwith%2Ban%2Bimpacting%2Bone%2BSci%2BAdv%2B634%2Beaba4330%2B2020", + "type": "paper", + "tags": [ + "Drops", + "Superamphiphobic-surfaces", + "Lifting" + ], + "priority": 3 + }, + { + "title": "[5] Jain, A., Sanjay, V., & Das, A. K. Consequences of inclined and dual jet impingement in stagnant liquid and stratified layers. AIChE J., 65(1), 372-384 (2019).", + "content": "", + "url": "/research/index.html#5%2BJain%2BA%2BSanjay%2BV%2BDas%2BA%2BK%2BConsequences%2Bof%2Binclined%2Band%2Bdual%2Bjet%2Bimpingement%2Bin%2Bstagnant%2Bliquid%2Band%2Bstratified%2Blayers%2BAIChE%2BJ%2B651%2B372-384%2B2019", + "type": "paper", + "tags": [ + "Jets", + "Bubbles" + ], + "priority": 3 + }, + { + "title": "[4] Soni, A., Sanjay, V., & Das, A. K. Formation of fluid structures due to jet-jet and jet-sheet interactions. Chem. Eng. Sci., 191, 67-77 (2018).", + "content": "", + "url": "/research/index.html#4%2BSoni%2BA%2BSanjay%2BV%2BDas%2BA%2BK%2BFormation%2Bof%2Bfluid%2Bstructures%2Bdue%2Bto%2Bjet-jet%2Band%2Bjet-sheet%2Binteractions%2BChem%2BEng%2BSci%2B191%2B67-77%2B2018", + "type": "paper", + "tags": [ + "Jets" + ], + "priority": 3 + }, + { + "title": "[3] Sanjay, V., Das, A.K. Numerical assessment of hazard in compartmental fire having steady heat release rate from the source. Build. Simul. 11, 613–624 (2018).", + "content": "", + "url": "/research/index.html#3%2BSanjay%2BV%2BDas%2BAK%2BNumerical%2Bassessment%2Bof%2Bhazard%2Bin%2Bcompartmental%2Bfire%2Bhaving%2Bsteady%2Bheat%2Brelease%2Brate%2Bfrom%2Bthe%2Bsource%2BBuild%2BSimul%2B11%2B613624%2B2018", + "type": "paper", + "tags": [ + "Others", + "Fire", + "Evacuation" + ], + "priority": 3 + }, + { + "title": "[2] Sanjay, V., & Das, A. K. Formation of liquid chain by collision of two laminar jets. Phys. Fluids, 29(11), 112101 (2017).", + "content": "", + "url": "/research/index.html#2%2BSanjay%2BV%2BDas%2BA%2BK%2BFormation%2Bof%2Bliquid%2Bchain%2Bby%2Bcollision%2Bof%2Btwo%2Blaminar%2Bjets%2BPhys%2BFluids%2B2911%2B112101%2B2017", + "type": "paper", + "tags": [ + "Jets", + "Sheets" + ], + "priority": 3 + }, + { + "title": "[1] Sanjay, V., & Das, A. K. On air entrainment in a water pool by impingement of a jet. AIChE J., 63(11), 5169-5181 (2017).", + "content": "", + "url": "/research/index.html#1%2BSanjay%2BV%2BDas%2BA%2BK%2BOn%2Bair%2Bentrainment%2Bin%2Ba%2Bwater%2Bpool%2Bby%2Bimpingement%2Bof%2Ba%2Bjet%2BAIChE%2BJ%2B6311%2B5169-5181%2B2017", + "type": "paper", + "tags": [ + "Jets", + "Bubbles" + ], + "priority": 3 + }, + { + "title": "Sort by topic", + "content": "Click on any tag to filter papers by topic. Each paper can have multiple tags.", + "url": "/research/index.html#Sort%2Bby%2Btopic", + "type": "text", + "links": [], + "priority": 3 + }, + { + "title": "[1] Sanjay, V., & Das, A. K. On air entrainment in a water pool by impingement of a jet. AIChE J., 63(11), 5169-5181 (2017).", + "content": "© Copyright\n CoMPhy Lab 2025", + "url": "/research/index.html#1%2BSanjay%2BV%2BDas%2BA%2BK%2BOn%2Bair%2Bentrainment%2Bin%2Ba%2Bwater%2Bpool%2Bby%2Bimpingement%2Bof%2Ba%2Bjet%2BAIChE%2BJ%2B6311%2B5169-5181%2B2017", + "type": "text", + "links": [], + "priority": 3 + }, + { + "title": "Research", + "content": "Cover of that volume of J. Fluid Mech. As of March/April 2024, this highly cited paper received enough citations to place it in the top 1% of the academic field of Physics based on a highly cited threshold for the field and publication year. Source: Web of Science.\n Editor’s Suggestion of that issue of Phys. Rev. Lett.\n Research Highlight: Castelvecchi, D. The physics of a bouncing droplet’s impact. Nature, 609, 225 (2022).", + "url": "/research/index.html#Research", + "type": "section", + "links": [], + "priority": 3 + }, + { + "title": "2024", + "content": "Cover of that volume of J. Fluid Mech.", + "url": "/research/index.html#2024", + "type": "section", + "links": [], + "priority": 3 + }, + { + "title": "2022", + "content": "As of March/April 2024, this highly cited paper received enough citations to place it in the top 1% of the academic field of Physics based on a highly cited threshold for the field and publication year. Source: Web of Science.\n Editor’s Suggestion of that issue of Phys. Rev. Lett.\n Research Highlight: Castelvecchi, D. The physics of a bouncing droplet’s impact. Nature, 609, 225 (2022).", + "url": "/research/index.html#2022", + "type": "section", + "links": [], + "priority": 3 + }, + { + "title": "High-Fidelity Simulations Using Basilisk C - CoMPhy Lab - Dates", + "content": "March 10-13, 2025", + "url": "/teaching/2025-Basilisk101-Madrid.html", + "type": "teaching_detail", + "priority": 2 + }, + { + "title": "High-Fidelity Simulations Using Basilisk C - CoMPhy Lab - Location", + "content": "Universidad Carlos III de Madrid, Spain", + "url": "/teaching/2025-Basilisk101-Madrid.html", + "type": "teaching_detail", + "priority": 2 + }, + { + "title": "High-Fidelity Simulations Using Basilisk C - CoMPhy Lab - Duration", + "content": "4 days, full-time", + "url": "/teaching/2025-Basilisk101-Madrid.html", + "type": "teaching_detail", + "priority": 2 + }, + { + "title": "High-Fidelity Simulations Using Basilisk C - CoMPhy Lab - Monday: Foundations", + "content": "Think before you compute 10:00-11:30\n Lecture (1a)\n \n \n \n \n Conservation laws and the numerical solution of the Navier–Stokes equations\n \n \n \n \n \n \n 11:45-13:00\n Lecture (1b)\n \n \n \n \n Advection-diffusion, diffusion-reaction, and other transport equations\n Brief intro to Basilisk coding framework First coding steps 15:00-18:00\n Hybrid Session\n \n \n \n \n Implementing basic transport equations in Basilisk C\n Whiteboard + coding", + "url": "/teaching/2025-Basilisk101-Madrid.html#Monday%3A%2BFoundations", + "type": "teaching_section", + "priority": 2 + }, + { + "title": "High-Fidelity Simulations Using Basilisk C - CoMPhy Lab - Tuesday: Advanced Implementation", + "content": "Coding like a pro 10:00-11:15\n Hackathon (1c)\n \n \n \n \n Using headers in Basilisk, modular code structure, problem setup, and compilation\n \n \n \n \n \n \n 11:30-13:00\n Hackathon Continued\n \n \n \n \n Expanding on the morning tasks and code debugging", + "url": "/teaching/2025-Basilisk101-Madrid.html#Tuesday%3A%2BAdvanced%2BImplementation", + "type": "teaching_section", + "priority": 2 + }, + { + "title": "High-Fidelity Simulations Using Basilisk C - CoMPhy Lab - Wednesday: Interface Dynamics", + "content": "Interface tracking methods 10:00-11:30\n Lecture (2a)\n \n \n \n \n Interface tracking methods (VoF, level set, phase-field approaches) and numerical strategies\n \n \n \n \n \n \n 11:45-13:00\n Hackathon (2b)\n \n \n \n \n Hands-on tutorial applying interface-tracking to a simple two-phase problem Seminar 13:30-14:00\n Department seminar (2c)\n \n \n \n \n A note on the thrust of airfoils by José Mnauel Gordillo Non-Newtonian flows 15:00-16:00\n Lecture (3a)\n \n \n \n \n Non-Newtonian flows: viscoplasticity and viscoelasticity\n \n \n \n \n \n \n 16:15-18:00\n Hackathon (3b)\n \n \n \n \n Coding exercises for shear-thinning, viscoplastic, or viscoelastic fluids", + "url": "/teaching/2025-Basilisk101-Madrid.html#Wednesday%3A%2BInterface%2BDynamics", + "type": "teaching_section", + "priority": 2 + }, + { + "title": "High-Fidelity Simulations Using Basilisk C - CoMPhy Lab - Thursday: Special Topics", + "content": "Special topics 10:00-11:30\n Lecture (4a)\n \n \n \n \n Special Topics: multilayer solver, lubrication equation, Marangoni flows, manifold death, and research-oriented examples\n \n \n \n \n \n \n 11:45-13:00\n Hackathon (4b)\n \n \n \n \n Focused tutorials on the special topics introduced in the lecture\n \n \n \n \n \n \n 15:00-16:30\n Lecture (4c)\n \n \n \n \n Open discussion, deeper dives into advanced features, final code reviews, and next steps Prerequisites Basic knowledge of fluid mechanics\n Experience with programming (any language, C preferred)\n Understanding of partial differential equations\n Laptop with ability to compile C code Registration For registration details, please contact bubbles@ing.uc3m.es vatsalsy@comphy-lab.org function copyEmail(button) {\n const textToCopy = button.getAttribute('data-text');\n \n // Create a temporary textarea element to copy from\n const textarea = document.createElement('textarea');\n textarea.value = textToCopy;\n textarea.setAttribute('readonly', '');\n textarea.style.position = 'absolute';\n textarea.style.left = '-9999px';\n document.body.appendChild(textarea);\n \n // Select and copy the text\n textarea.select();\n document.execCommand('copy');\n \n // Remove the temporary element\n document.body.removeChild(textarea);\n \n // Show feedback\n const originalIcon = button.innerHTML;\n button.innerHTML = '';\n button.classList.add('copied');\n \n // Restore original state after a delay\n setTimeout(() => {\n button.innerHTML = originalIcon;\n button.classList.remove('copied');\n }, 2000);\n} Course GitHub Repository", + "url": "/teaching/2025-Basilisk101-Madrid.html#Thursday%3A%2BSpecial%2BTopics", + "type": "teaching_section", + "priority": 2 + }, + { + "title": "High-Fidelity Simulations Using Basilisk C - CoMPhy Lab - Prerequisites", + "content": "Basic knowledge of fluid mechanics\n Experience with programming (any language, C preferred)\n Understanding of partial differential equations\n Laptop with ability to compile C code", + "url": "/teaching/2025-Basilisk101-Madrid.html#Prerequisites", + "type": "teaching_course_info", + "priority": 2 + }, + { + "title": "High-Fidelity Simulations Using Basilisk C - CoMPhy Lab - Course Description", + "content": "This intensive 4-day course provides a comprehensive introduction to high-fidelity simulations using Basilisk C, a powerful computational framework for fluid dynamics. Participants will learn to implement and solve complex fluid mechanics problems with an emphasis on multiphase flows, interface dynamics, and non-Newtonian rheology. The course combines theoretical lectures with extensive hands-on sessions, allowing participants to immediately apply concepts through guided coding exercises. By the end of the course, you’ll be able to develop your own simulations for a variety of fluid dynamics applications.", + "url": "/teaching/2025-Basilisk101-Madrid.html#Course%2BDescription", + "type": "teaching_course_info", + "priority": 2 + }, + { + "title": "High-Fidelity Simulations Using Basilisk C - CoMPhy Lab - Registration", + "content": "For registration details, please contact bubbles@ing.uc3m.es vatsalsy@comphy-lab.org function copyEmail(button) {\n const textToCopy = button.getAttribute('data-text');\n \n // Create a temporary textarea element to copy from\n const textarea = document.createElement('textarea');\n textarea.value = textToCopy;\n textarea.setAttribute('readonly', '');\n textarea.style.position = 'absolute';\n textarea.style.left = '-9999px';\n document.body.appendChild(textarea);\n \n // Select and copy the text\n textarea.select();\n document.execCommand('copy');\n \n // Remove the temporary element\n document.body.removeChild(textarea);\n \n // Show feedback\n const originalIcon = button.innerHTML;\n button.innerHTML = '';\n button.classList.add('copied');\n \n // Restore original state after a delay\n setTimeout(() => {\n button.innerHTML = originalIcon;\n button.classList.remove('copied');\n }, 2000);\n} Course GitHub Repository", + "url": "/teaching/2025-Basilisk101-Madrid.html#Registration", + "type": "teaching_course_info", + "priority": 2 + }, + { + "title": "High-Fidelity Simulations Using Basilisk C - CoMPhy Lab - What will you learn", + "content": "Think before you compute! Understanding the physics before implementation\n Writing the first code in Basilisk C Getting comfortable with the framework\n Solving conservation equations Numerical approaches to fluid dynamics\n Interface tracking methods Capturing multiphase phenomena accurately\n Non-Newtonian flows Modeling complex rheological behaviors", + "url": "/teaching/2025-Basilisk101-Madrid.html#What%2Bwill%2Byou%2Blearn", + "type": "teaching_course_info", + "priority": 2 + }, + { + "title": "Dates", + "content": "March 10-13, 2025", + "url": "/teaching/2025-Basilisk101-Madrid.html#Dates", + "type": "text", + "links": [], + "priority": 3 + }, + { + "title": "Location", + "content": "Universidad Carlos III de Madrid, Spain", + "url": "/teaching/2025-Basilisk101-Madrid.html#Location", + "type": "text", + "links": [], + "priority": 3 + }, + { + "title": "Duration", + "content": "4 days, full-time", + "url": "/teaching/2025-Basilisk101-Madrid.html#Duration", + "type": "text", + "links": [], + "priority": 3 + }, + { + "title": "Course Description", + "content": "This intensive 4-day course provides a comprehensive introduction to high-fidelity simulations using Basilisk C, a powerful computational framework for fluid dynamics. Participants will learn to implement and solve complex fluid mechanics problems with an emphasis on multiphase flows, interface dynamics, and non-Newtonian rheology.", + "url": "/teaching/2025-Basilisk101-Madrid.html#Course%2BDescription", + "type": "text", + "links": [], + "priority": 3 + }, + { + "title": "Course Description", + "content": "The course combines theoretical lectures with extensive hands-on sessions, allowing participants to immediately apply concepts through guided coding exercises. By the end of the course, you’ll be able to develop your own simulations for a variety of fluid dynamics applications.", + "url": "/teaching/2025-Basilisk101-Madrid.html#Course%2BDescription", + "type": "text", + "links": [], + "priority": 3 + }, + { + "title": "Registration", + "content": "For registration details, please contact", + "url": "/teaching/2025-Basilisk101-Madrid.html#Registration", + "type": "text", + "links": [], + "priority": 3 + }, + { + "title": "Registration", + "content": "© Copyright\n CoMPhy Lab 2025", + "url": "/teaching/2025-Basilisk101-Madrid.html#Registration", + "type": "text", + "links": [], + "priority": 3 + }, + { + "title": "High-Fidelity Simulations Using Basilisk C", + "content": "Think before you compute! Understanding the physics before implementation\n Writing the first code in Basilisk C Getting comfortable with the framework\n Solving conservation equations Numerical approaches to fluid dynamics\n Interface tracking methods Capturing multiphase phenomena accurately\n Non-Newtonian flows Modeling complex rheological behaviors This intensive 4-day course provides a comprehensive introduction to high-fidelity simulations using Basilisk C, a powerful computational framework for fluid dynamics. Participants will learn to implement and solve complex fluid mechanics problems with an emphasis on multiphase flows, interface dynamics, and non-Newtonian rheology. The course combines theoretical lectures with extensive hands-on sessions, allowing participants to immediately apply concepts through guided coding exercises. By the end of the course, you’ll be able to develop your own simulations for a variety of fluid dynamics applications. 10:00-11:30\n Lecture (1a)\n \n \n \n \n Conservation laws and the numerical solution of the Navier–Stokes equations\n \n \n \n \n \n \n 11:45-13:00\n Lecture (1b)\n \n \n \n \n Advection-diffusion, diffusion-reaction, and other transport equations\n Brief intro to Basilisk coding framework 15:00-18:00\n Hybrid Session\n \n \n \n \n Implementing basic transport equations in Basilisk C\n Whiteboard + coding 10:00-11:15\n Hackathon (1c)\n \n \n \n \n Using headers in Basilisk, modular code structure, problem setup, and compilation\n \n \n \n \n \n \n 11:30-13:00\n Hackathon Continued\n \n \n \n \n Expanding on the morning tasks and code debugging 10:00-11:30\n Lecture (2a)\n \n \n \n \n Interface tracking methods (VoF, level set, phase-field approaches) and numerical strategies\n \n \n \n \n \n \n 11:45-13:00\n Hackathon (2b)\n \n \n \n \n Hands-on tutorial applying interface-tracking to a simple two-phase problem 13:30-14:00\n Department seminar (2c)\n \n \n \n \n A note on the thrust of airfoils by José Mnauel Gordillo 15:00-16:00\n Lecture (3a)\n \n \n \n \n Non-Newtonian flows: viscoplasticity and viscoelasticity\n \n \n \n \n \n \n 16:15-18:00\n Hackathon (3b)\n \n \n \n \n Coding exercises for shear-thinning, viscoplastic, or viscoelastic fluids 10:00-11:30\n Lecture (4a)\n \n \n \n \n Special Topics: multilayer solver, lubrication equation, Marangoni flows, manifold death, and research-oriented examples\n \n \n \n \n \n \n 11:45-13:00\n Hackathon (4b)\n \n \n \n \n Focused tutorials on the special topics introduced in the lecture\n \n \n \n \n \n \n 15:00-16:30\n Lecture (4c)\n \n \n \n \n Open discussion, deeper dives into advanced features, final code reviews, and next steps Basic knowledge of fluid mechanics\n Experience with programming (any language, C preferred)\n Understanding of partial differential equations\n Laptop with ability to compile C code For registration details, please contact", + "url": "/teaching/2025-Basilisk101-Madrid.html#High-Fidelity%2BSimulations%2BUsing%2BBasilisk%2BC", + "type": "section", + "links": [], + "priority": 3 + }, + { + "title": "What will you learn?", + "content": "Think before you compute! Understanding the physics before implementation\n Writing the first code in Basilisk C Getting comfortable with the framework\n Solving conservation equations Numerical approaches to fluid dynamics\n Interface tracking methods Capturing multiphase phenomena accurately\n Non-Newtonian flows Modeling complex rheological behaviors", + "url": "/teaching/2025-Basilisk101-Madrid.html#What%2Bwill%2Byou%2Blearn", + "type": "section", + "links": [], + "priority": 3 + }, + { + "title": "Course Description", + "content": "This intensive 4-day course provides a comprehensive introduction to high-fidelity simulations using Basilisk C, a powerful computational framework for fluid dynamics. Participants will learn to implement and solve complex fluid mechanics problems with an emphasis on multiphase flows, interface dynamics, and non-Newtonian rheology. The course combines theoretical lectures with extensive hands-on sessions, allowing participants to immediately apply concepts through guided coding exercises. By the end of the course, you’ll be able to develop your own simulations for a variety of fluid dynamics applications.", + "url": "/teaching/2025-Basilisk101-Madrid.html#Course%2BDescription", + "type": "section", + "links": [], + "priority": 3 + }, + { + "title": "Course Schedule", + "content": "10:00-11:30\n Lecture (1a)\n \n \n \n \n Conservation laws and the numerical solution of the Navier–Stokes equations\n \n \n \n \n \n \n 11:45-13:00\n Lecture (1b)\n \n \n \n \n Advection-diffusion, diffusion-reaction, and other transport equations\n Brief intro to Basilisk coding framework 15:00-18:00\n Hybrid Session\n \n \n \n \n Implementing basic transport equations in Basilisk C\n Whiteboard + coding 10:00-11:15\n Hackathon (1c)\n \n \n \n \n Using headers in Basilisk, modular code structure, problem setup, and compilation\n \n \n \n \n \n \n 11:30-13:00\n Hackathon Continued\n \n \n \n \n Expanding on the morning tasks and code debugging 10:00-11:30\n Lecture (2a)\n \n \n \n \n Interface tracking methods (VoF, level set, phase-field approaches) and numerical strategies\n \n \n \n \n \n \n 11:45-13:00\n Hackathon (2b)\n \n \n \n \n Hands-on tutorial applying interface-tracking to a simple two-phase problem 13:30-14:00\n Department seminar (2c)\n \n \n \n \n A note on the thrust of airfoils by José Mnauel Gordillo 15:00-16:00\n Lecture (3a)\n \n \n \n \n Non-Newtonian flows: viscoplasticity and viscoelasticity\n \n \n \n \n \n \n 16:15-18:00\n Hackathon (3b)\n \n \n \n \n Coding exercises for shear-thinning, viscoplastic, or viscoelastic fluids 10:00-11:30\n Lecture (4a)\n \n \n \n \n Special Topics: multilayer solver, lubrication equation, Marangoni flows, manifold death, and research-oriented examples\n \n \n \n \n \n \n 11:45-13:00\n Hackathon (4b)\n \n \n \n \n Focused tutorials on the special topics introduced in the lecture\n \n \n \n \n \n \n 15:00-16:30\n Lecture (4c)\n \n \n \n \n Open discussion, deeper dives into advanced features, final code reviews, and next steps", + "url": "/teaching/2025-Basilisk101-Madrid.html#Course%2BSchedule", + "type": "section", + "links": [], + "priority": 3 + }, + { + "title": "Monday: Foundations", + "content": "10:00-11:30\n Lecture (1a)\n \n \n \n \n Conservation laws and the numerical solution of the Navier–Stokes equations\n \n \n \n \n \n \n 11:45-13:00\n Lecture (1b)\n \n \n \n \n Advection-diffusion, diffusion-reaction, and other transport equations\n Brief intro to Basilisk coding framework 15:00-18:00\n Hybrid Session\n \n \n \n \n Implementing basic transport equations in Basilisk C\n Whiteboard + coding", + "url": "/teaching/2025-Basilisk101-Madrid.html#Monday%3A%2BFoundations", + "type": "section", + "links": [], + "priority": 3 + }, + { + "title": "Tuesday: Advanced Implementation", + "content": "10:00-11:15\n Hackathon (1c)\n \n \n \n \n Using headers in Basilisk, modular code structure, problem setup, and compilation\n \n \n \n \n \n \n 11:30-13:00\n Hackathon Continued\n \n \n \n \n Expanding on the morning tasks and code debugging", + "url": "/teaching/2025-Basilisk101-Madrid.html#Tuesday%3A%2BAdvanced%2BImplementation", + "type": "section", + "links": [], + "priority": 3 + }, + { + "title": "Wednesday: Interface Dynamics", + "content": "10:00-11:30\n Lecture (2a)\n \n \n \n \n Interface tracking methods (VoF, level set, phase-field approaches) and numerical strategies\n \n \n \n \n \n \n 11:45-13:00\n Hackathon (2b)\n \n \n \n \n Hands-on tutorial applying interface-tracking to a simple two-phase problem 13:30-14:00\n Department seminar (2c)\n \n \n \n \n A note on the thrust of airfoils by José Mnauel Gordillo 15:00-16:00\n Lecture (3a)\n \n \n \n \n Non-Newtonian flows: viscoplasticity and viscoelasticity\n \n \n \n \n \n \n 16:15-18:00\n Hackathon (3b)\n \n \n \n \n Coding exercises for shear-thinning, viscoplastic, or viscoelastic fluids", + "url": "/teaching/2025-Basilisk101-Madrid.html#Wednesday%3A%2BInterface%2BDynamics", + "type": "section", + "links": [], + "priority": 3 + }, + { + "title": "Thursday: Special Topics", + "content": "10:00-11:30\n Lecture (4a)\n \n \n \n \n Special Topics: multilayer solver, lubrication equation, Marangoni flows, manifold death, and research-oriented examples\n \n \n \n \n \n \n 11:45-13:00\n Hackathon (4b)\n \n \n \n \n Focused tutorials on the special topics introduced in the lecture\n \n \n \n \n \n \n 15:00-16:30\n Lecture (4c)\n \n \n \n \n Open discussion, deeper dives into advanced features, final code reviews, and next steps", + "url": "/teaching/2025-Basilisk101-Madrid.html#Thursday%3A%2BSpecial%2BTopics", + "type": "section", + "links": [], + "priority": 3 + }, + { + "title": "Prerequisites", + "content": "Basic knowledge of fluid mechanics\n Experience with programming (any language, C preferred)\n Understanding of partial differential equations\n Laptop with ability to compile C code", + "url": "/teaching/2025-Basilisk101-Madrid.html#Prerequisites", + "type": "section", + "links": [], + "priority": 3 + }, + { + "title": "Registration", + "content": "For registration details, please contact", + "url": "/teaching/2025-Basilisk101-Madrid.html#Registration", + "type": "section", + "links": [], + "priority": 3 + }, + { + "title": "Teaching - CoMPhy Lab - High-Fidelity Simulations Using Basilisk C", + "content": "Universidad Carlos III de Madrid March 10-13, 2025 A comprehensive course on using Basilisk C for simulating multiphase flows, interface tracking, and solving conservation equations. Learn to tackle complex fluid dynamics problems with high-fidelity numerical methods. View Course", + "url": "/teaching/index.html#High-Fidelity%2BSimulations%2BUsing%2BBasilisk%2BC", + "type": "teaching_section", + "priority": 2 + }, + { + "title": "High-Fidelity Simulations Using Basilisk C", + "content": "High-Fidelity Simulations Using Basilisk C - Universidad Carlos III de Madrid\n - \n March 10-13, 2025 - A comprehensive course on using Basilisk C for simulating multiphase flows, interface tracking, and solving conservation equations. Learn to tackle complex fluid dynamics problems with high-fidelity numerical methods.", + "url": "/teaching/2025-Basilisk101-Madrid", + "type": "teaching_course", + "priority": 2 + }, + { + "title": "Teaching", + "content": "Welcome to the CoMPhy Lab’s educational resources. Apart from the university courses, we aim to develop and offer a range of workshops and tutorials on modern computational methods for multiphase flows and high-fidelity simulations.", + "url": "/teaching/index.html#Teaching", + "type": "text", + "links": [], + "priority": 3 + }, + { + "title": "High-Fidelity Simulations Using Basilisk C", + "content": "A comprehensive course on using Basilisk C for simulating multiphase flows, interface tracking, and solving conservation equations. Learn to tackle complex fluid dynamics problems with high-fidelity numerical methods.", + "url": "/teaching/index.html#High-Fidelity%2BSimulations%2BUsing%2BBasilisk%2BC", + "type": "text", + "links": [], + "priority": 3 + }, + { + "title": "About Our Teaching Philosophy", + "content": "At CoMPhy Lab, we believe in hands-on learning and deep understanding of computational methods. Our courses combine theoretical foundations with practical implementation, allowing students to develop both conceptual understanding and technical skills.", + "url": "/teaching/index.html#About%2BOur%2BTeaching%2BPhilosophy", + "type": "text", + "links": [], + "priority": 3 + }, + { + "title": "About Our Teaching Philosophy", + "content": "Our teaching approach emphasizes:", + "url": "/teaching/index.html#About%2BOur%2BTeaching%2BPhilosophy", + "type": "text", + "links": [], + "priority": 3 + }, + { + "title": "About Our Teaching Philosophy", + "content": "If you’re interested in hosting a course or workshop with CoMPhy Lab, please contact us for collaboration opportunities.", + "url": "/teaching/index.html#About%2BOur%2BTeaching%2BPhilosophy", + "type": "text", + "links": [ + "join" + ], + "priority": 3 + }, + { + "title": "About Our Teaching Philosophy", + "content": "© Copyright\n CoMPhy Lab 2025", + "url": "/teaching/index.html#About%2BOur%2BTeaching%2BPhilosophy", + "type": "text", + "links": [], + "priority": 3 + }, + { + "title": "Teaching", + "content": "Welcome to the CoMPhy Lab’s educational resources. Apart from the university courses, we aim to develop and offer a range of workshops and tutorials on modern computational methods for multiphase flows and high-fidelity simulations. At CoMPhy Lab, we believe in hands-on learning and deep understanding of computational methods. Our courses combine theoretical foundations with practical implementation, allowing students to develop both conceptual understanding and technical skills. Our teaching approach emphasizes: Think before you compute: Understanding the underlying physics before implementation\n Modular code structure: Building maintainable and extensible computational tools\n Advanced numerical methods: Mastering state-of-the-art techniques for complex problems\n Open science: Sharing knowledge and tools with the scientific community. Checkout If you’re interested in hosting a course or workshop with CoMPhy Lab, please contact us for collaboration opportunities.", + "url": "/teaching/index.html#Teaching", + "type": "section", + "links": [ + "teaching/2025-Basilisk101-Madrid", + "join" + ], + "priority": 3 + }, + { + "title": "High-Fidelity Simulations Using Basilisk C", + "content": "A comprehensive course on using Basilisk C for simulating multiphase flows, interface tracking, and solving conservation equations. Learn to tackle complex fluid dynamics problems with high-fidelity numerical methods.", + "url": "/teaching/index.html#High-Fidelity%2BSimulations%2BUsing%2BBasilisk%2BC", + "type": "section", + "links": [ + "teaching/2025-Basilisk101-Madrid" + ], + "priority": 3 + }, + { + "title": "About Our Teaching Philosophy", + "content": "At CoMPhy Lab, we believe in hands-on learning and deep understanding of computational methods. Our courses combine theoretical foundations with practical implementation, allowing students to develop both conceptual understanding and technical skills. Our teaching approach emphasizes: Think before you compute: Understanding the underlying physics before implementation\n Modular code structure: Building maintainable and extensible computational tools\n Advanced numerical methods: Mastering state-of-the-art techniques for complex problems\n Open science: Sharing knowledge and tools with the scientific community. Checkout If you’re interested in hosting a course or workshop with CoMPhy Lab, please contact us for collaboration opportunities.", + "url": "/teaching/index.html#About%2BOur%2BTeaching%2BPhilosophy", + "type": "section", + "links": [ + "teaching/2025-Basilisk101-Madrid", + "join" + ], + "priority": 3 + }, + { + "title": "Vatsal Sanjay (PI)", + "content": "Postdoc, Phys. Fluids - Univ. Twente / 2022-25\n Ph.D., Phys. Fluids - Univ. Twente / 2018-22\n B.Tech + M.Tech, Two-Phase Flow & Instability Lab, IIT-Roorkee / 2013-18\n Personal Website Research Interest: See here Download CV", + "url": "/team/index.html#Vatsal%2BSanjay%2BPI", + "type": "team_member", + "priority": 2 + }, + { + "title": "Ayush Dixit (Ph.D)", + "content": "Joint with Detlef Lohse Ph.D. Student, Phys. Fluids - Univ. Twente / 2023-now\n B.Tech + M.Tech, Two-Phase Flow & Instability Lab, IIT-Roorkee / 2018-23 Research Interest: Viscoelastic Flows, Bursting Bubbles, Respiratory Drops.", + "url": "/team/index.html#Ayush%2BDixit%2BPhD", + "type": "team_member", + "priority": 2 + }, + { + "title": "Aman Bhargava (Ph.D)", + "content": "Joint with Detlef Lohse Ph.D. Student, Phys. Fluids - Univ. Twente / 2024-now\n M.Sc. Chemical Engineering, Purdue University / 2022-23\n B.Tech. (Hons.) Chemical Engineering, IIT-Bombay / 2018-22 Research Interest: Inertial Contact Line, Drop Retraction.", + "url": "/team/index.html#Aman%2BBhargava%2BPhD", + "type": "team_member", + "priority": 2 + }, + { + "title": "Jnandeep Talukdar (M.Sc.)", + "content": "Joint with Detlef Lohse Ph.D. Student, Phys. Fluids - Univ. Twente / starting May 2025\n M.Sc. Student, Phys. Fluids - Univ. Twente / 2023-25\n B.Tech. Mechanical Engineering, IIT-Patna / 2019-23 Research Interest: Surfactant Dynamics, Dissipative Anomaly, Soft Wetting.", + "url": "/team/index.html#Jnandeep%2BTalukdar%2BMSc", + "type": "team_member", + "priority": 2 + }, + { + "title": "Saumili Jana (M.Sc.)", + "content": "Joint with Detlef Lohse Ph.D. Student, Phys. Fluids - Univ. Twente / starting Jul 2025\n B.Tech.+M.Tech. Student, IIT-Kharagpur / 2020-25\n Research Intern, Phys. Fluids - Univ. Twente / 2024 Research Interest: Soft Impact.", + "url": "/team/index.html#Saumili%2BJana%2BMSc", + "type": "team_member", + "priority": 2 + }, + { + "title": "Floris Hoek (M.Sc.)", + "content": "Joint with Martin van der Hoef and Alvaro Marin M.Sc. Student, Phys. Fluids - Univ. Twente / 2024 Research Interest: Molecular Dynamics Simulations of Evaporation-Driven Colloidal Self-Assembly.", + "url": "/team/index.html#Floris%2BHoek%2BMSc", + "type": "team_member", + "priority": 2 + }, + { + "title": "Xiangyu Zhang (Intern)", + "content": "Joint with Detlef Lohse Guest Researcher, Phys. Fluids - Univ. Twente / 2024\n City University of Hong Kong, China Research Interest: Viscoplastic Drop Impact.", + "url": "/team/index.html#Xiangyu%2BZhang%2BIntern", + "type": "team_member", + "priority": 2 + }, + { + "title": "Detlef Lohse", + "content": "Professor, Phys. Fluids - Univ. Twente Collaboration on: Drop Impact, Viscoelastic Flows, Dissipative Anomaly, Surfactant Dynamics, Electrolysis, Bubbles, and Everything in Between.", + "url": "/team/index.html#Detlef%2BLohse", + "type": "team_member", + "priority": 2 + }, + { + "title": "Jacco Snoeijer", + "content": "Professor, Phys. Fluids - Univ. Twente Collaboration on: Elastic Sheets, Viscoelasticity vs. Elasticity, Spinning Pizza.", + "url": "/team/index.html#Jacco%2BSnoeijer", + "type": "team_member", + "priority": 2 + }, + { + "title": "Dominik Krug", + "content": "Professor, RWTH Aachen University\n Adjunct Professor, Phys. Fluids - Univ. Twente Collaboration on: Electrolysis, Bubble Coalescence, Swimming Bubbles.", + "url": "/team/index.html#Dominik%2BKrug", + "type": "team_member", + "priority": 2 + }, + { + "title": "Maziyar Jalaal (Mazi)", + "content": "Associate Professor, Fluid Lab, Univ. Amsterdam Collaboration on: Plastocapillarity, Viscoplastic Flows.", + "url": "/team/index.html#Maziyar%2BJalaal%2BMazi", + "type": "team_member", + "priority": 2 + }, + { + "title": "Uddalok Sen (Udo)", + "content": "Assistant Professor, PhySM, Wageningen University and Research Collaboration on: Drop Impact, Sheet Retraction.", + "url": "/team/index.html#Uddalok%2BSen%2BUdo", + "type": "team_member", + "priority": 2 + }, + { + "title": "Alvaro Marin", + "content": "Adjunct Professor, Phys. Fluids - Univ. Twente Collaboration on: Colloidal Systems, Evaporation, Shell Formation.", + "url": "/team/index.html#Alvaro%2BMarin", + "type": "team_member", + "priority": 2 + }, + { + "title": "Christian Diddens", + "content": "Group Leader, Phys. Fluids - Univ. Twente\n Developer of pyoomph Collaboration on: Surfactant Dynamics in Free Surface Flows, Dissipative Anomaly, Sliding Drops.", + "url": "/team/index.html#Christian%2BDiddens", + "type": "team_member", + "priority": 2 + }, + { + "title": "Gareth McKinley", + "content": "Professor, MIT Collaboration on: Die-Swelling, Viscoelastic Flows.", + "url": "/team/index.html#Gareth%2BMcKinley", + "type": "team_member", + "priority": 2 + }, + { + "title": "John Kolinski", + "content": "Asst. Professor, EPFL (École Polytechnique Fédérale de Lausanne) Collaboration on: Soft Impact", + "url": "/team/index.html#John%2BKolinski", + "type": "team_member", + "priority": 2 + }, + { + "title": "C. Ricardo Constante-Amores", + "content": "Asst. Professor, Univ. Illinois Urbana-Champaign Collaboration on: Non-Newtonian Flows, Bubble Bursting, Herschel–Bulkley Fluids, Elastic Coating.", + "url": "/team/index.html#C%2BRicardo%2BConstante-Amores", + "type": "team_member", + "priority": 2 + }, + { + "title": "Radu Cimpeanu", + "content": "Assc. Professor, Univ. Warwick Collaboration on: Open-Source Code Development, Non-Coalescence Impacts.", + "url": "/team/index.html#Radu%2BCimpeanu", + "type": "team_member", + "priority": 2 + }, + { + "title": "Jie Feng", + "content": "Asst. Professor, Univ. Illinois Urbana-Champaign Collaboration on: Elastic Coating, Bursting Bubbles.", + "url": "/team/index.html#Jie%2BFeng", + "type": "team_member", + "priority": 2 + }, + { + "title": "Omar Matar", + "content": "Professor, Imperial College London Collaboration on: Surfactant Dynamics, Viscoelastic Drop Impact.", + "url": "/team/index.html#Omar%2BMatar", + "type": "team_member", + "priority": 2 + }, + { + "title": "Ratul Dasgupta", + "content": "Assc. Professor, IIT-Bombay Collaboration on: Waves, Dissipative Anomaly.", + "url": "/team/index.html#Ratul%2BDasgupta", + "type": "team_member", + "priority": 2 + }, + { + "title": "Eric Lauga", + "content": "Professor, Univ. Cambridge Collaboration on: Mycofluidic Transport.", + "url": "/team/index.html#Eric%2BLauga", + "type": "team_member", + "priority": 2 + }, + { + "title": "Saikat Datta", + "content": "Senior Lecturer, Swansea University Collaboration on: Multiscale Simulation, Ice Nucleation and Removal, Hydrogen Storage.", + "url": "/team/index.html#Saikat%2BDatta", + "type": "team_member", + "priority": 2 + }, + { + "title": "Doris Vollmer", + "content": "Apl. Professor, Max Planck Institute for Polymer Research, Mainz, Germany. Collaboration on: Contact Line, Drop Impact, Superhydrophobic Surfaces.", + "url": "/team/index.html#Doris%2BVollmer", + "type": "team_member", + "priority": 2 + }, + { + "title": "Stéphane Zaleski", + "content": "Professor, Sorbonne Universite Collaboration on: Holey Sheets.", + "url": "/team/index.html#Stphane%2BZaleski", + "type": "team_member", + "priority": 2 + }, + { + "title": "Pierre Chantelot", + "content": "Postdoc, Institut Langevin, ESPCI Paris Collaboration on: Drop Impact", + "url": "/team/index.html#Pierre%2BChantelot", + "type": "team_member", + "priority": 2 + }, + { + "title": "Aleksandr Bashkatov", + "content": "Postdoc, RWTH Aachen University Collaboration on: Electrolysis, Bubble Coalescence, Swimming Bubbles.", + "url": "/team/index.html#Aleksandr%2BBashkatov", + "type": "team_member", + "priority": 2 + }, + { + "title": "Vincent Bertin", + "content": "Postdoc, University Aix-Marseille Collaboration on: Elastic Sheets, Spinning Pizza.", + "url": "/team/index.html#Vincent%2BBertin", + "type": "team_member", + "priority": 2 + }, + { + "title": "Alexandros Oratis", + "content": "Postdoc, TU Delft Collaboration on: Electrolysis, Bubble Coalescence, Swimming Bubbles.", + "url": "/team/index.html#Alexandros%2BOratis", + "type": "team_member", + "priority": 2 + }, + { + "title": "Arivazhagan Balasubramanian (Ari)", + "content": "Ph.D. Student, KTH Sweden Collaboration on: Elastoviscoplastic Flows, Bursting Bubbles", + "url": "/team/index.html#Arivazhagan%2BBalasubramanian%2BAri", + "type": "team_member", + "priority": 2 + }, + { + "title": "Konstantinos Zinelis (Costis)", + "content": "Postdoc, MIT Collaboration on: Viscoelastic Flows, Drop Impact.", + "url": "/team/index.html#Konstantinos%2BZinelis%2BCostis", + "type": "team_member", + "priority": 2 + }, + { + "title": "Swen van den Heuvel", + "content": "Now: Ph.D. Student, Phys. Fluids - Univ. Twente\n 2023: Graduated with M.Sc., Univ. Twente\n Thesis: Hydrodynamic forces acting on vertically rising bubbles", + "url": "/team/index.html#Swen%2Bvan%2Bden%2BHeuvel", + "type": "team_member", + "priority": 2 + }, + { + "title": "Niels Kuipers", + "content": "Now: M.Sc. Student, Adv. Technology - Univ. Twente\n 2023: Graduated with B.Sc., Univ. Twente\n Thesis: Partial coalescence of drops on viscous films", + "url": "/team/index.html#Niels%2BKuipers", + "type": "team_member", + "priority": 2 + }, + { + "title": "Tom Appleford", + "content": "Now: Ph.D. Student, Fluid Lab - Univ. Amsterdam\n 2022: Graduated with M.Sc., Univ. Amsterdam\n Thesis: The deformation of a droplet in a viscoplastic simple shear flow", + "url": "/team/index.html#Tom%2BAppleford", + "type": "team_member", + "priority": 2 + }, + { + "title": "Coen Verschuur", + "content": "Now: Ph.D. Student, Phys. Fluids - Univ. Twente\n 2020: Graduated with B.Sc., Univ. Twente\n Thesis: Early time dynamics in immiscible drop impacts", + "url": "/team/index.html#Coen%2BVerschuur", + "type": "team_member", + "priority": 2 + }, + { + "title": "Pim J. Dekker", + "content": "Now: Ph.D. Student, Phys. Fluids - Univ. Twente\n 2019: Graduated with B.Sc., Univ. Twente\n Thesis: Spreading of a drop on a water-air interface", + "url": "/team/index.html#Pim%2BJ%2BDekker", + "type": "team_member", + "priority": 2 + }, + { + "title": "Vatsal Sanjay (PI)", + "content": "Research Interest: See here", + "url": "/team/index.html#Vatsal%2BSanjay%2BPI", + "type": "text", + "links": [], "priority": 3 }, { - "title": "0_ToDo-Blog-public - tags:", - "content": "Each post should highlight the motivation, key findings, and broader implications of the work, along with engaging visuals or videos (where applicable).", - "url": "https://blogs.comphy-lab.org/0_ToDo-Blog-public", - "type": "blog_excerpt", + "title": "Vatsal Sanjay (PI)", + "content": "Download CV", + "url": "/team/index.html#Vatsal%2BSanjay%2BPI", + "type": "text", + "links": [], "priority": 3 }, { - "title": "0_ToDo-Blog-public - tags:", - "content": "Work in Progress \nTo jump or not to jump: Adhesion and viscous dissipation dictate the detachment of coalescing wall-attached bubbles\nLink: arXiv:2501.05532\nViscoelastic Worthington jets & droplets produced by bursting bubbles\nLink: arXiv:2408.05089\nUnifying theory of scaling in drop impact: Forces & maximum spreading diameter\nLink: arXiv:2408.12714\nElectrolyte spraying within H₂ bubbles during water electrolysis\nLink: arXiv:2409.00515\n2025 \nThe role of viscosity on drop impact forces on non-wetting surfaces\nLink: arXiv:2311.03012\nFocusing of concentric free-surface waves\nLink: arXiv:2406.05416\n2024 \nBursting bubble in an elastoviscoplastic medium\nHighlight: Cover of J.", - "url": "https://blogs.comphy-lab.org/0_ToDo-Blog-public", - "type": "blog_excerpt", + "title": "Ayush Dixit (Ph.D)", + "content": "Joint with Detlef Lohse", + "url": "/team/index.html#Ayush%2BDixit%2BPhD", + "type": "text", + "links": [], "priority": 3 }, { - "title": "0_ToDo-Blog-public - tags:", - "content": "Fluid Mech. Link: DOI\n2023 \nDrop impact on viscous liquid films\nLink: DOI\nWhen does an impacting drop stop bouncing? Link: DOI\n2022 \nPh.D.", - "url": "https://blogs.comphy-lab.org/0_ToDo-Blog-public", - "type": "blog_excerpt", + "title": "Ayush Dixit (Ph.D)", + "content": "Research Interest: Viscoelastic Flows, Bursting Bubbles, Respiratory Drops.", + "url": "/team/index.html#Ayush%2BDixit%2BPhD", + "type": "text", + "links": [], "priority": 3 }, { - "title": "0_ToDo-Blog-public - tags:", - "content": "Thesis: Viscous free-surface flows\nLinks: Thesis Info, DOI, move here from old website\nImpact forces of water drops falling on superhydrophobic surfaces\nHighlights: Editor's Suggestion, Research Highlight in Nature\nLink: DOI\n2021 \nBursting bubble in a viscoplastic medium\nLink: DOI\n2020 \nLifting a sessile oil drop from a superamphiphobic surface with an impacting one\nLink: DOI\n2019 \nConsequences of inclined and dual jet impingement in stagnant liquid and stratified layers\nLink: DOI\n2018 \nFormation of fluid structures due to jet-jet and jet-sheet interactions\nLink: DOI\nNumerical assessment of hazard in compartmental fire having steady heat release rate from the source\nLink: DOI\n2017 \nFormation of liquid chain by collision of two laminar jets\nLink: DOI\nOn air entrainment in a water pool by impingement of a jet\nLink: DOI\nBack to main website\nHome, Team, Research, Github", - "url": "https://blogs.comphy-lab.org/0_ToDo-Blog-public", - "type": "blog_excerpt", + "title": "Aman Bhargava (Ph.D)", + "content": "Joint with Detlef Lohse", + "url": "/team/index.html#Aman%2BBhargava%2BPhD", + "type": "text", + "links": [], "priority": 3 }, { - "title": "Arrowheads in elastoinertial turbulence - tags:", - "content": "tags:\n - arrowhead\n - narwhal\n - Coherent\n - Elastoinertial\n - Turbulence\nCoherent structures could explain the pathway to elastoinertial turbulence.", - "url": "https://blogs.comphy-lab.org/Blog/Arrowheads+in+elastoinertial+turbulence", - "type": "blog_excerpt", + "title": "Aman Bhargava (Ph.D)", + "content": "Research Interest: Inertial Contact Line, Drop Retraction.", + "url": "/team/index.html#Aman%2BBhargava%2BPhD", + "type": "text", + "links": [], "priority": 3 }, { - "title": "Arrowheads in elastoinertial turbulence - tags:", - "content": "Arrowhead (one such traveling wave coherent solution of viscoelastic channel flow) structures are unstable in 3D flows\nLack of experimental validation. Numerical artifacts play significant role − presence or lack of polymeric diffusion. Transient appearances may still influence flow dynamics.", - "url": "https://blogs.comphy-lab.org/Blog/Arrowheads+in+elastoinertial+turbulence", - "type": "blog_excerpt", + "title": "Jnandeep Talukdar (M.Sc.)", + "content": "Joint with Detlef Lohse", + "url": "/team/index.html#Jnandeep%2BTalukdar%2BMSc", + "type": "text", + "links": [], "priority": 3 }, { - "title": "Arrowheads in elastoinertial turbulence - tags:", - "content": "Problem Statement \nCentral Issue: The existence and significance of arrowhead structures in elastoinertial turbulence. Importance: Understanding these structures could reveal fundamental mechanisms of polymer-flow interactions and turbulence development. Historical note The two Phys. Rev.", - "url": "https://blogs.comphy-lab.org/Blog/Arrowheads+in+elastoinertial+turbulence", - "type": "blog_excerpt", + "title": "Jnandeep Talukdar (M.Sc.)", + "content": "Research Interest: Surfactant Dynamics, Dissipative Anomaly, Soft Wetting.", + "url": "/team/index.html#Jnandeep%2BTalukdar%2BMSc", + "type": "text", + "links": [], "priority": 3 }, { - "title": "Arrowheads in elastoinertial turbulence - tags:", - "content": "Lett. outline the exact traveling wave solutions − now commonly called arrowheads or narwhals. J. Page, Y. Dubief & R. R. Kerswell, Phys. Rev. Lett., 125:15, 154501 (2020)\nA. Morozov, Phys. Rev.", - "url": "https://blogs.comphy-lab.org/Blog/Arrowheads+in+elastoinertial+turbulence", - "type": "blog_excerpt", + "title": "Saumili Jana (M.Sc.)", + "content": "Joint with Detlef Lohse", + "url": "/team/index.html#Saumili%2BJana%2BMSc", + "type": "text", + "links": [], "priority": 3 }, { - "title": "Arrowheads in elastoinertial turbulence - tags:", - "content": "Lett., 129:1, 017801 (2022)Methodology The research community has employed multiple approaches to study arrowhead structures:Research Methods\n2D and 3D numerical simulations\nLinear stability analysis\nFlow visualization experiments\nPolymer diffusion modeling\nWhat are arrowheads?", - "url": "https://blogs.comphy-lab.org/Blog/Arrowheads+in+elastoinertial+turbulence", - "type": "blog_excerpt", + "title": "Saumili Jana (M.Sc.)", + "content": "Research Interest: Soft Impact.", + "url": "/team/index.html#Saumili%2BJana%2BMSc", + "type": "text", + "links": [], "priority": 3 }, { - "title": "Arrowheads in elastoinertial turbulence - tags:", - "content": "Elastoinertial turbulence (EIT) is a chaotic flow state in dilute polymer solutions that arises when fluid inertia and polymer elasticity jointly drive instabilities.", - "url": "https://blogs.comphy-lab.org/Blog/Arrowheads+in+elastoinertial+turbulence", - "type": "blog_excerpt", + "title": "Floris Hoek (M.Sc.)", + "content": "Joint with Martin van der Hoef and Alvaro Marin", + "url": "/team/index.html#Floris%2BHoek%2BMSc", + "type": "text", + "links": [], "priority": 3 }, { - "title": "Arrowheads in elastoinertial turbulence - tags:", - "content": "In this regime, simulations have revealed distinctive arrowhead-shaped coherent structures (nicknamed “narwhal” structures for their horn-like appearance).", - "url": "https://blogs.comphy-lab.org/Blog/Arrowheads+in+elastoinertial+turbulence", - "type": "blog_excerpt", + "title": "Floris Hoek (M.Sc.)", + "content": "Research Interest: Molecular Dynamics Simulations of Evaporation-Driven Colloidal Self-Assembly.", + "url": "/team/index.html#Floris%2BHoek%2BMSc", + "type": "text", + "links": [], "priority": 3 }, { - "title": "Arrowheads in elastoinertial turbulence - tags:", - "content": "These are highly organized patterns embedded within the turbulence, consisting of a polymer stress distribution coupled with a vortical flow pattern.An arrowhead structure has\nLocalized polymer stretch: a thin sheet of highly stretched polymer (high elastic stress) forming a triangular, arrowhead-like region spanning from the near-wall area to the channel center (symmetric about the mid-plane).", - "url": "https://blogs.comphy-lab.org/Blog/Arrowheads+in+elastoinertial+turbulence", - "type": "blog_excerpt", + "title": "Xiangyu Zhang (Intern)", + "content": "Joint with Detlef Lohse", + "url": "/team/index.html#Xiangyu%2BZhang%2BIntern", + "type": "text", + "links": [], "priority": 3 }, { - "title": "Arrowheads in elastoinertial turbulence - tags:", - "content": "Flanking vortices: a pair of counter-rotating vortices on either side of this polymer sheet. These vortices pull fluid (and polymers) up from the walls toward the center, feeding and elongating the polymer-rich “horn” of the arrowhead.", - "url": "https://blogs.comphy-lab.org/Blog/Arrowheads+in+elastoinertial+turbulence", - "type": "blog_excerpt", + "title": "Xiangyu Zhang (Intern)", + "content": "Research Interest: Viscoplastic Drop Impact.", + "url": "/team/index.html#Xiangyu%2BZhang%2BIntern", + "type": "text", + "links": [], "priority": 3 }, { - "title": "Arrowheads in elastoinertial turbulence - tags:", - "content": "Coherent propagation: the structure travels downstream as a traveling wave, maintaining its shape over time rather than dissipating like a random eddy. Why do arrowheads form? Arrowhead structures emerge from a flow instability driven by polymer elasticity.", - "url": "https://blogs.comphy-lab.org/Blog/Arrowheads+in+elastoinertial+turbulence", - "type": "blog_excerpt", + "title": "We need you!", + "content": "See: Join Us for ongoing projects.", + "url": "/team/index.html#We%2Bneed%2Byou", + "type": "text", + "links": [], "priority": 3 }, { - "title": "Arrowheads in elastoinertial turbulence - tags:", - "content": "At high Weissenberg number (Wi, dimensionless stress relaxation time − high Wi imply strong elastic effects) and moderate Reynolds number (Re), an otherwise laminar channel flow becomes unstable to disturbances at the channel center.", - "url": "https://blogs.comphy-lab.org/Blog/Arrowheads+in+elastoinertial+turbulence", - "type": "blog_excerpt", + "title": "Detlef Lohse", + "content": "Collaboration on: Drop Impact, Viscoelastic Flows, Dissipative Anomaly, Surfactant Dynamics, Electrolysis, Bubbles, and Everything in Between.", + "url": "/team/index.html#Detlef%2BLohse", + "type": "text", + "links": [], "priority": 3 }, { - "title": "Arrowheads in elastoinertial turbulence - tags:", - "content": "This center-mode instability begins as a pattern of vortices and polymer stretch in the middle of the channel, which grows and nonlinearly saturates into the arrowhead form.", - "url": "https://blogs.comphy-lab.org/Blog/Arrowheads+in+elastoinertial+turbulence", - "type": "blog_excerpt", + "title": "Jacco Snoeijer", + "content": "Collaboration on: Elastic Sheets, Viscoelasticity vs. Elasticity, Spinning Pizza.", + "url": "/team/index.html#Jacco%2BSnoeijer", + "type": "text", + "links": [], "priority": 3 }, { - "title": "Arrowheads in elastoinertial turbulence - tags:", - "content": "Above a critical Wi, the flow spontaneously develops arrowhead patterns; at higher Wi, these patterns persist and can dominate as a stable wave.", - "url": "https://blogs.comphy-lab.org/Blog/Arrowheads+in+elastoinertial+turbulence", - "type": "blog_excerpt", + "title": "Dominik Krug", + "content": "Collaboration on: Electrolysis, Bubble Coalescence, Swimming Bubbles.", + "url": "/team/index.html#Dominik%2BKrug", + "type": "text", + "links": [], "priority": 3 }, { - "title": "Arrowheads in elastoinertial turbulence - tags:", - "content": "At sufficiently large Wi, the chaotic EIT can give way to a single steady arrowhead state, indicating that this coherent structure becomes a stable attractor when elasticity dominates.Polymer–flow coupling: The self-sustaining nature of an arrowhead comes from a feedback loop between polymer stretching and fluid motion.", - "url": "https://blogs.comphy-lab.org/Blog/Arrowheads+in+elastoinertial+turbulence", - "type": "blog_excerpt", + "title": "Maziyar Jalaal (Mazi)", + "content": "Collaboration on: Plastocapillarity, Viscoplastic Flows.", + "url": "/team/index.html#Maziyar%2BJalaal%2BMazi", + "type": "text", + "links": [], "priority": 3 }, { - "title": "Arrowheads in elastoinertial turbulence - tags:", - "content": "The polymer’s evolution equation captures this interplay:DCDt=C⋅(∇u)T+(∇u)⋅C−1Wi(C−I)where C is the polymer conformation tensor. In an arrowhead, the vortex pair generates strong shear and extensional flow that continuously amplifies polymer stretch along its arms.", - "url": "https://blogs.comphy-lab.org/Blog/Arrowheads+in+elastoinertial+turbulence", - "type": "blog_excerpt", + "title": "Uddalok Sen (Udo)", + "content": "Collaboration on: Drop Impact, Sheet Retraction.", + "url": "/team/index.html#Uddalok%2BSen%2BUdo", + "type": "text", + "links": [], "priority": 3 }, { - "title": "Arrowheads in elastoinertial turbulence - tags:", - "content": "Because relaxation is slow at high Wi, a persistent high-tension polymer zone forms instead of relaxing away. That stretched-polymer zone exerts elastic stress back on the fluid, reinforcing the vortices that created it.", - "url": "https://blogs.comphy-lab.org/Blog/Arrowheads+in+elastoinertial+turbulence", - "type": "blog_excerpt", + "title": "Alvaro Marin", + "content": "Collaboration on: Colloidal Systems, Evaporation, Shell Formation.", + "url": "/team/index.html#Alvaro%2BMarin", + "type": "text", + "links": [], "priority": 3 }, { - "title": "Arrowheads in elastoinertial turbulence - tags:", - "content": "Thus the vortices and polymer sheet sustain each other: the flow keeps the polymers stretched, and the polymer tension keeps the flow organized in the arrowhead pattern.Different types of arrowheads \nFig. Different realizations of arrowheads. Figure taken from Y. Dubief, J. Page, R. R. Kerswell, V. E.", - "url": "https://blogs.comphy-lab.org/Blog/Arrowheads+in+elastoinertial+turbulence", - "type": "blog_excerpt", + "title": "Christian Diddens", + "content": "Collaboration on: Surfactant Dynamics in Free Surface Flows, Dissipative Anomaly, Sliding Drops.", + "url": "/team/index.html#Christian%2BDiddens", + "type": "text", + "links": [], "priority": 3 }, { - "title": "Arrowheads in elastoinertial turbulence - tags:", - "content": "Terrapon & V. Steinberg, Phys. Rev. Fluids, 7:7, 073301 (2022) Why are arrowheads important? Identifying arrowhead structures can be pivotal for understanding EIT.", - "url": "https://blogs.comphy-lab.org/Blog/Arrowheads+in+elastoinertial+turbulence", - "type": "blog_excerpt", + "title": "Gareth McKinley", + "content": "Collaboration on: Die-Swelling, Viscoelastic Flows.", + "url": "/team/index.html#Gareth%2BMcKinley", + "type": "text", + "links": [], "priority": 3 }, { - "title": "Arrowheads in elastoinertial turbulence - tags:", - "content": "They are the viscoelastic analog of coherent structures in Newtonian turbulence (like near-wall streaks and rolls), providing a clear skeleton for the turbulence. This shows that EIT is not just random; it is built around repeatable polymer–flow patterns.", - "url": "https://blogs.comphy-lab.org/Blog/Arrowheads+in+elastoinertial+turbulence", - "type": "blog_excerpt", + "title": "John Kolinski", + "content": "Collaboration on: Soft Impact", + "url": "/team/index.html#John%2BKolinski", + "type": "text", + "links": [], "priority": 3 }, { - "title": "Arrowheads in elastoinertial turbulence - tags:", - "content": "Arrowhead structures also showcase a new route to sustain turbulence: they show that elastic forces can maintain complex flow structures even at Reynolds numbers where a Newtonian flow would stay laminar.", - "url": "https://blogs.comphy-lab.org/Blog/Arrowheads+in+elastoinertial+turbulence", - "type": "blog_excerpt", + "title": "C. Ricardo Constante-Amores", + "content": "Collaboration on: Non-Newtonian Flows, Bubble Bursting, Herschel–Bulkley Fluids, Elastic Coating.", + "url": "/team/index.html#C%2BRicardo%2BConstante-Amores", + "type": "text", + "links": [], "priority": 3 }, { - "title": "Arrowheads in elastoinertial turbulence - tags:", - "content": "Finally, they offer a bridge between regimes, connecting purely elastic turbulence (Re≈0) with elastoinertial turbulence at higher Re with Wi≫1.Numerical vs. Experimental Evidence: Do Arrowheads Exist in 3D or in Reality?", - "url": "https://blogs.comphy-lab.org/Blog/Arrowheads+in+elastoinertial+turbulence", - "type": "blog_excerpt", + "title": "Radu Cimpeanu", + "content": "Collaboration on: Open-Source Code Development, Non-Coalescence Impacts.", + "url": "/team/index.html#Radu%2BCimpeanu", + "type": "text", + "links": [], "priority": 3 }, { - "title": "Arrowheads in elastoinertial turbulence - tags:", - "content": "A critical question is whether these arrowhead structures are present in 3D flows and experiments, or if they are artifacts of idealized 2D simulations.", - "url": "https://blogs.comphy-lab.org/Blog/Arrowheads+in+elastoinertial+turbulence", - "type": "blog_excerpt", + "title": "Jie Feng", + "content": "Collaboration on: Elastic Coating, Bursting Bubbles.", + "url": "/team/index.html#Jie%2BFeng", + "type": "text", + "links": [], "priority": 3 }, { - "title": "Arrowheads in elastoinertial turbulence - tags:", - "content": "It has been claimed – and largely supported by evidence – that a single arrowhead wave disappears in fully 3D simulations and has never been observed in experiments. Recent studies confirm that in a 3D domain, an arrowhead (narwhal) state is linearly unstable and quickly breaks down into turbulence.", - "url": "https://blogs.comphy-lab.org/Blog/Arrowheads+in+elastoinertial+turbulence", - "type": "blog_excerpt", + "title": "Omar Matar", + "content": "Collaboration on: Surfactant Dynamics, Viscoelastic Drop Impact.", + "url": "/team/index.html#Omar%2BMatar", + "type": "text", + "links": [], "priority": 3 }, { - "title": "Arrowheads in elastoinertial turbulence - tags:", - "content": "Morozov and co-workers performed a linear stability analysis of the 2D travelling-wave solution and found it unstable to spanwise perturbations when embedded in a 3D domain (M. Lellep, M. Linkmann & A. Morozov, Proc. Natl. Acad. Sci., 121:9, e2318851121 (2024)).", - "url": "https://blogs.comphy-lab.org/Blog/Arrowheads+in+elastoinertial+turbulence", - "type": "blog_excerpt", + "title": "Ratul Dasgupta", + "content": "Collaboration on: Waves, Dissipative Anomaly.", + "url": "/team/index.html#Ratul%2BDasgupta", + "type": "text", + "links": [], "priority": 3 }, { - "title": "Arrowheads in elastoinertial turbulence - tags:", - "content": "In other words, “unlike 2D, in 3D the narwhal in a channel appears to become unstable, leading to chaotic flows”. This means that a coherent arrowhead structure cannot persist as a steady state in a wide 3D channel; it inevitably triggers its own demise into disordered motion.", - "url": "https://blogs.comphy-lab.org/Blog/Arrowheads+in+elastoinertial+turbulence", - "type": "blog_excerpt", + "title": "Eric Lauga", + "content": "Collaboration on: Mycofluidic Transport.", + "url": "/team/index.html#Eric%2BLauga", + "type": "text", + "links": [], "priority": 3 }, { - "title": "Arrowheads in elastoinertial turbulence - tags:", - "content": "Indeed, direct 3D simulations of EIT show the flow filled with multiscale, transient structures rather than a single arrowhead.", - "url": "https://blogs.comphy-lab.org/Blog/Arrowheads+in+elastoinertial+turbulence", - "type": "blog_excerpt", + "title": "Saikat Datta", + "content": "Collaboration on: Multiscale Simulation, Ice Nucleation and Removal, Hydrogen Storage.", + "url": "/team/index.html#Saikat%2BDatta", + "type": "text", + "links": [], "priority": 3 }, { - "title": "Arrowheads in elastoinertial turbulence - tags:", - "content": "The characteristic structures of 3D EIT are sheet-like regions of high polymer stretch and streamwise vortices (often aligned in the spanwise direction) – no long-lived arrowhead pattern is evident. Experimentally, too, no arrowhead or narwhal-shaped structures have been directly observed.", - "url": "https://blogs.comphy-lab.org/Blog/Arrowheads+in+elastoinertial+turbulence", - "type": "blog_excerpt", + "title": "Doris Vollmer", + "content": "Collaboration on: Contact Line, Drop Impact, Superhydrophobic Surfaces.", + "url": "/team/index.html#Doris%2BVollmer", + "type": "text", + "links": [], "priority": 3 }, { - "title": "Arrowheads in elastoinertial turbulence - tags:", - "content": "Laboratory studies (using techniques like flow visualization or velocimetry in polymer solutions) report chaotic fluctuations and elastic “sheets” of polymer stretch, but not a stable arrowhead wave.", - "url": "https://blogs.comphy-lab.org/Blog/Arrowheads+in+elastoinertial+turbulence", - "type": "blog_excerpt", + "title": "Stéphane Zaleski", + "content": "Collaboration on: Holey Sheets.", + "url": "/team/index.html#Stphane%2BZaleski", + "type": "text", + "links": [], "priority": 3 }, { - "title": "Arrowheads in elastoinertial turbulence - tags:", - "content": "For example, experimental and high-Reynolds 3D DNS studies show polymers stretched in thin sheets inclined to the flow and emerging vortex tubes, with no mention of a sustained arrowhead structure. Recent research largely supports these claims.", - "url": "https://blogs.comphy-lab.org/Blog/Arrowheads+in+elastoinertial+turbulence", - "type": "blog_excerpt", + "title": "Pierre Chantelot", + "content": "Collaboration on: Drop Impact", + "url": "/team/index.html#Pierre%2BChantelot", + "type": "text", + "links": [], "priority": 3 }, { - "title": "Arrowheads in elastoinertial turbulence - tags:", - "content": "The consensus is that the arrowhead/narwhal is a 2D artifact in the sense that it cannot survive the spanwise degrees of freedom of a real 3D flow.", - "url": "https://blogs.comphy-lab.org/Blog/Arrowheads+in+elastoinertial+turbulence", - "type": "blog_excerpt", + "title": "Aleksandr Bashkatov", + "content": "Collaboration on: Electrolysis, Bubble Coalescence, Swimming Bubbles.", + "url": "/team/index.html#Aleksandr%2BBashkatov", + "type": "text", + "links": [], "priority": 3 }, { - "title": "Arrowheads in elastoinertial turbulence - tags:", - "content": "Additionally, no experimental evidence of arrowheads has emerged, despite the intensive study of EIT in channels and pipes over the past decade – reinforcing the idea that such structures, if they form, are immediately disrupted in real flows.", - "url": "https://blogs.comphy-lab.org/Blog/Arrowheads+in+elastoinertial+turbulence", - "type": "blog_excerpt", + "title": "Vincent Bertin", + "content": "Collaboration on: Elastic Sheets, Spinning Pizza.", + "url": "/team/index.html#Vincent%2BBertin", + "type": "text", + "links": [], "priority": 3 }, { - "title": "Arrowheads in elastoinertial turbulence - tags:", - "content": "However, recent findings do nuance this picture: even though a stable narwhal does not exist in 3D, researchers have noted that transient arrowhead-like patterns can appear fleetingly within a 3D turbulent flow.", - "url": "https://blogs.comphy-lab.org/Blog/Arrowheads+in+elastoinertial+turbulence", - "type": "blog_excerpt", + "title": "Alexandros Oratis", + "content": "Collaboration on: Electrolysis, Bubble Coalescence, Swimming Bubbles.", + "url": "/team/index.html#Alexandros%2BOratis", + "type": "text", + "links": [], "priority": 3 }, { - "title": "Arrowheads in elastoinertial turbulence - tags:", - "content": "For instance, simulations of low-Re viscoelastic turbulence sometimes display instantaneous polymer-stress contours resembling the arrowhead shape (J. R. C. King, R. J. Poole, C. P. Fonte & S. J. Lind, arXiv:2501.09421 (2025)).", - "url": "https://blogs.comphy-lab.org/Blog/Arrowheads+in+elastoinertial+turbulence", - "type": "blog_excerpt", + "title": "Arivazhagan Balasubramanian (Ari)", + "content": "Collaboration on: Elastoviscoplastic Flows, Bursting Bubbles", + "url": "/team/index.html#Arivazhagan%2BBalasubramanian%2BAri", + "type": "text", + "links": [], "priority": 3 }, { - "title": "Arrowheads in elastoinertial turbulence - tags:", - "content": "These transient appearances suggest that the arrowhead solution still exists as an organizing saddle or ephemeral structure in phase space, even if it’s not sustained.", - "url": "https://blogs.comphy-lab.org/Blog/Arrowheads+in+elastoinertial+turbulence", - "type": "blog_excerpt", + "title": "Konstantinos Zinelis (Costis)", + "content": "Collaboration on: Viscoelastic Flows, Drop Impact.", + "url": "/team/index.html#Konstantinos%2BZinelis%2BCostis", + "type": "text", + "links": [], "priority": 3 }, { - "title": "Arrowheads in elastoinertial turbulence - tags:", - "content": "In summary, the latest evidence indicates that arrowhead/narwhal structures do not persist in fully 3D EIT or experiments – they are replaced by more complex chaotic dynamics – but they may manifest momentarily, hinting that they influence the flow as part of its underlying state space (rather than as an observable steady feature).Role of Diffusion, Resolution, and Modeling Choices: Physical Feature or Numerical Artifact?", - "url": "https://blogs.comphy-lab.org/Blog/Arrowheads+in+elastoinertial+turbulence", - "type": "blog_excerpt", + "title": "Team, collaborators, and Conference visits", + "content": "The locations marked on this map meet one of three criteria (in the order of preference): \n 1. Hometown of our team members (including alumni) in orange, \n 2. Base location of our collaborators in green,\n 3. Places where we have presented talks in purple, or\n 4. Places where we have visited for conferences (no talks) in gray.", + "url": "/team/index.html#Team%2Bcollaborators%2Band%2BConference%2Bvisits", + "type": "text", + "links": [], "priority": 3 }, { - "title": "Arrowheads in elastoinertial turbulence - tags:", - "content": "The formation and persistence of arrowhead structures are highly sensitive to modeling choices like polymer diffusion and domain dimensionality, raising the question of whether these structures are genuine physical features or numerical artifacts.", - "url": "https://blogs.comphy-lab.org/Blog/Arrowheads+in+elastoinertial+turbulence", - "type": "blog_excerpt", + "title": "Team, collaborators, and Conference visits", + "content": "© Copyright\n CoMPhy Lab 2025", + "url": "/team/index.html#Team%2Bcollaborators%2Band%2BConference%2Bvisits", + "type": "text", + "links": [], "priority": 3 }, { - "title": "Arrowheads in elastoinertial turbulence - tags:", - "content": "Polymer stress diffusion in particular plays a pivotal role. In simulations of viscoelastic fluids (Oldroyd-B or FENE-P models), a small artificial diffusion term is often added to the polymer constitutive equations for numerical stability.", - "url": "https://blogs.comphy-lab.org/Blog/Arrowheads+in+elastoinertial+turbulence", - "type": "blog_excerpt", + "title": "Vatsal Sanjay (PI)", + "content": "Postdoc, Phys. Fluids - Univ. Twente / 2022-25\n Ph.D., Phys. Fluids - Univ. Twente / 2018-22\n B.Tech + M.Tech, Two-Phase Flow & Instability Lab, IIT-Roorkee / 2013-18\n Personal Website Research Interest: See here Download CV", + "url": "/team/index.html#Vatsal%2BSanjay%2BPI", + "type": "section", + "links": [], "priority": 3 }, { - "title": "Arrowheads in elastoinertial turbulence - tags:", - "content": "Recent work showed that even an infinitesimal polymer diffusion can qualitatively change the flow’s stability characteristics. M. Beneitez, J. Page, Y. Dubief & R. R. Kerswell, J.", - "url": "https://blogs.comphy-lab.org/Blog/Arrowheads+in+elastoinertial+turbulence", - "type": "blog_excerpt", + "title": "Ayush Dixit (Ph.D)", + "content": "Joint with Detlef Lohse Ph.D. Student, Phys. Fluids - Univ. Twente / 2023-now\n B.Tech + M.Tech, Two-Phase Flow & Instability Lab, IIT-Roorkee / 2018-23 Research Interest: Viscoelastic Flows, Bursting Bubbles, Respiratory Drops.", + "url": "/team/index.html#Ayush%2BDixit%2BPhD", + "type": "section", + "links": [], "priority": 3 }, { - "title": "Arrowheads in elastoinertial turbulence - tags:", - "content": "Fluid Mech., 981, A30 (2024) discovered a polymer diffusive instability (PDI): with a nonzero diffusion, the laminar flow becomes linearly unstable, producing a small-scale traveling wave at the wall as the primary bifurcation.", - "url": "https://blogs.comphy-lab.org/Blog/Arrowheads+in+elastoinertial+turbulence", - "type": "blog_excerpt", + "title": "Aman Bhargava (Ph.D)", + "content": "Joint with Detlef Lohse Ph.D. Student, Phys. Fluids - Univ. Twente / 2024-now\n M.Sc. Chemical Engineering, Purdue University / 2022-23\n B.Tech. (Hons.) Chemical Engineering, IIT-Bombay / 2018-22 Research Interest: Inertial Contact Line, Drop Retraction.", + "url": "/team/index.html#Aman%2BBhargava%2BPhD", + "type": "section", + "links": [], "priority": 3 }, { - "title": "Arrowheads in elastoinertial turbulence - tags:", - "content": "Notably, this instability does not exist when polymer diffusion is strictly zero – the base flow would be linearly stable in the absence of diffusion. The implication is that the arrowhead structure in 2D simulations can arise from the PDI – essentially a numerical artifact of including diffusion.", - "url": "https://blogs.comphy-lab.org/Blog/Arrowheads+in+elastoinertial+turbulence", - "type": "blog_excerpt", + "title": "Jnandeep Talukdar (M.Sc.)", + "content": "Joint with Detlef Lohse Ph.D. Student, Phys. Fluids - Univ. Twente / starting May 2025\n M.Sc. Student, Phys. Fluids - Univ. Twente / 2023-25\n B.Tech. Mechanical Engineering, IIT-Patna / 2019-23 Research Interest: Surfactant Dynamics, Dissipative Anomaly, Soft Wetting.", + "url": "/team/index.html#Jnandeep%2BTalukdar%2BMSc", + "type": "section", + "links": [], "priority": 3 }, { - "title": "Arrowheads in elastoinertial turbulence - tags:", - "content": "The diffusive term “triggers” a finite-amplitude arrowhead-like wave that would not naturally appear at that parameter set without diffusion.", - "url": "https://blogs.comphy-lab.org/Blog/Arrowheads+in+elastoinertial+turbulence", - "type": "blog_excerpt", + "title": "Saumili Jana (M.Sc.)", + "content": "Joint with Detlef Lohse Ph.D. Student, Phys. Fluids - Univ. Twente / starting Jul 2025\n B.Tech.+M.Tech. Student, IIT-Kharagpur / 2020-25\n Research Intern, Phys. Fluids - Univ. Twente / 2024 Research Interest: Soft Impact.", + "url": "/team/index.html#Saumili%2BJana%2BMSc", + "type": "section", + "links": [], "priority": 3 }, { - "title": "Arrowheads in elastoinertial turbulence - tags:", - "content": "In reality, molecular diffusion of polymers is extremely small, so such an instability might never dominate the transition; the flow could bypass it via a subcritical route.", - "url": "https://blogs.comphy-lab.org/Blog/Arrowheads+in+elastoinertial+turbulence", - "type": "blog_excerpt", + "title": "Floris Hoek (M.Sc.)", + "content": "Joint with Martin van der Hoef and Alvaro Marin M.Sc. Student, Phys. Fluids - Univ. Twente / 2024 Research Interest: Molecular Dynamics Simulations of Evaporation-Driven Colloidal Self-Assembly.", + "url": "/team/index.html#Floris%2BHoek%2BMSc", + "type": "section", + "links": [], "priority": 3 }, { - "title": "Arrowheads in elastoinertial turbulence - tags:", - "content": "Thus, one must be cautious: the beautiful arrowhead patterns seen in some simulations may owe their existence to an unphysically large diffusion or other numerical regularization.", - "url": "https://blogs.comphy-lab.org/Blog/Arrowheads+in+elastoinertial+turbulence", - "type": "blog_excerpt", + "title": "Xiangyu Zhang (Intern)", + "content": "Joint with Detlef Lohse Guest Researcher, Phys. Fluids - Univ. Twente / 2024\n City University of Hong Kong, China Research Interest: Viscoplastic Drop Impact.", + "url": "/team/index.html#Xiangyu%2BZhang%2BIntern", + "type": "section", + "links": [], "priority": 3 }, { - "title": "Arrowheads in elastoinertial turbulence - tags:", - "content": "In that sense, they are partly a numerical artifact.Numerical resolution and dimensionality further affect arrowhead formation.", - "url": "https://blogs.comphy-lab.org/Blog/Arrowheads+in+elastoinertial+turbulence", - "type": "blog_excerpt", + "title": "We need you!", + "content": "See: Join Us for ongoing projects.", + "url": "/team/index.html#We%2Bneed%2Byou", + "type": "section", + "links": [], "priority": 3 }, { - "title": "Arrowheads in elastoinertial turbulence - tags:", - "content": "Insufficient resolution can introduce excessive numerical diffusion (smoothing out small scales), potentially artificially stabilizing a large-scale structure like the arrowhead.", - "url": "https://blogs.comphy-lab.org/Blog/Arrowheads+in+elastoinertial+turbulence", - "type": "blog_excerpt", + "title": "Detlef Lohse", + "content": "Professor, Phys. Fluids - Univ. Twente Collaboration on: Drop Impact, Viscoelastic Flows, Dissipative Anomaly, Surfactant Dynamics, Electrolysis, Bubbles, and Everything in Between.", + "url": "/team/index.html#Detlef%2BLohse", + "type": "section", + "links": [], "priority": 3 }, { - "title": "Arrowheads in elastoinertial turbulence - tags:", - "content": "Likewise, restricting the simulation to 2D (as was done in the initial arrowhead studies) removes the 3D disturbances that would break up the arrowhead. The 2D assumption is a strong modeling simplification that essentially locks in the coherent structure. As M. Lellep, M. Linkmann & A. Morozov, Proc.", - "url": "https://blogs.comphy-lab.org/Blog/Arrowheads+in+elastoinertial+turbulence", - "type": "blog_excerpt", + "title": "Jacco Snoeijer", + "content": "Professor, Phys. Fluids - Univ. Twente Collaboration on: Elastic Sheets, Viscoelasticity vs. Elasticity, Spinning Pizza.", + "url": "/team/index.html#Jacco%2BSnoeijer", + "type": "section", + "links": [], "priority": 3 }, { - "title": "Arrowheads in elastoinertial turbulence - tags:", - "content": "Natl. Acad. Sci., 121:9, e2318851121 (2024) note, one cannot draw reliable conclusions about EIT dynamics from strictly 2D simulations. In 2D, the narwhal is “benign” and can remain as a steady state, but this is sustained only because the normal 3D instability modes are disallowed.", - "url": "https://blogs.comphy-lab.org/Blog/Arrowheads+in+elastoinertial+turbulence", - "type": "blog_excerpt", + "title": "Dominik Krug", + "content": "Professor, RWTH Aachen University\n Adjunct Professor, Phys. Fluids - Univ. Twente Collaboration on: Electrolysis, Bubble Coalescence, Swimming Bubbles.", + "url": "/team/index.html#Dominik%2BKrug", + "type": "section", + "links": [], "priority": 3 }, { - "title": "Arrowheads in elastoinertial turbulence - tags:", - "content": "In a fully resolved 3D simulation, that arrowhead state sits on a razor’s edge – any slight spanwise perturbation will cause it to oscillate or breakdown. Thus, the arrowhead’s longevity in 2D is not reflective of physical reality; it’s a consequence of an artificially constrained simulation.", - "url": "https://blogs.comphy-lab.org/Blog/Arrowheads+in+elastoinertial+turbulence", - "type": "blog_excerpt", + "title": "Maziyar Jalaal (Mazi)", + "content": "Associate Professor, Fluid Lab, Univ. Amsterdam Collaboration on: Plastocapillarity, Viscoplastic Flows.", + "url": "/team/index.html#Maziyar%2BJalaal%2BMazi", + "type": "section", + "links": [], "priority": 3 }, { - "title": "Arrowheads in elastoinertial turbulence - tags:", - "content": "On the other hand, it’s too strong to dismiss arrowheads entirely as a “mere artifact.” They are mathematically legitimate nonlinear solutions of the viscoelastic flow equations (even if attained under special conditions) and they capture real physics – notably, a mechanism of polymer stretching and feedback.", - "url": "https://blogs.comphy-lab.org/Blog/Arrowheads+in+elastoinertial+turbulence", - "type": "blog_excerpt", + "title": "Uddalok Sen (Udo)", + "content": "Assistant Professor, PhySM, Wageningen University and Research Collaboration on: Drop Impact, Sheet Retraction.", + "url": "/team/index.html#Uddalok%2BSen%2BUdo", + "type": "section", + "links": [], "priority": 3 }, { - "title": "Arrowheads in elastoinertial turbulence - tags:", - "content": "The consensus view is that arrowhead/narwhal structures are embedded but unstable features of the real system. They are “real” solutions, but one that the unconstrained system will only transiently visit. Diffusion and other numerical choices can exaggerate their prominence.", - "url": "https://blogs.comphy-lab.org/Blog/Arrowheads+in+elastoinertial+turbulence", - "type": "blog_excerpt", + "title": "Alvaro Marin", + "content": "Adjunct Professor, Phys. Fluids - Univ. Twente Collaboration on: Colloidal Systems, Evaporation, Shell Formation.", + "url": "/team/index.html#Alvaro%2BMarin", + "type": "section", + "links": [], "priority": 3 }, { - "title": "Arrowheads in elastoinertial turbulence - tags:", - "content": "In summary, high-fidelity simulations indicate that arrowheads do not represent a permanent physical flow pattern; rather, they are a byproduct of low-dimensional or diffusive modeling that nonetheless offer insight into the polymer–flow interactions in EIT.Conclusion Recent research converges on a nuanced view of arrowhead (narwhal) structures in EIT.", - "url": "https://blogs.comphy-lab.org/Blog/Arrowheads+in+elastoinertial+turbulence", - "type": "blog_excerpt", + "title": "Christian Diddens", + "content": "Group Leader, Phys. Fluids - Univ. Twente\n Developer of pyoomph Collaboration on: Surfactant Dynamics in Free Surface Flows, Dissipative Anomaly, Sliding Drops.", + "url": "/team/index.html#Christian%2BDiddens", + "type": "section", + "links": [], "priority": 3 }, { - "title": "Arrowheads in elastoinertial turbulence - tags:", - "content": "They were a landmark discovery as a coherent viscoelastic wave, and they have deepened our insight into polymer–flow interactions. However, both simulations and theory now indicate that they are transient players in fully developed 3D turbulence rather than the primary architects of it.", - "url": "https://blogs.comphy-lab.org/Blog/Arrowheads+in+elastoinertial+turbulence", - "type": "blog_excerpt", + "title": "Gareth McKinley", + "content": "Professor, MIT Collaboration on: Die-Swelling, Viscoelastic Flows.", + "url": "/team/index.html#Gareth%2BMcKinley", + "type": "section", + "links": [], + "priority": 3 + }, + { + "title": "John Kolinski", + "content": "Asst. Professor, EPFL (École Polytechnique Fédérale de Lausanne) Collaboration on: Soft Impact", + "url": "/team/index.html#John%2BKolinski", + "type": "section", + "links": [], + "priority": 3 + }, + { + "title": "C. Ricardo Constante-Amores", + "content": "Asst. Professor, Univ. Illinois Urbana-Champaign Collaboration on: Non-Newtonian Flows, Bubble Bursting, Herschel–Bulkley Fluids, Elastic Coating.", + "url": "/team/index.html#C%2BRicardo%2BConstante-Amores", + "type": "section", + "links": [], + "priority": 3 + }, + { + "title": "Radu Cimpeanu", + "content": "Assc. Professor, Univ. Warwick Collaboration on: Open-Source Code Development, Non-Coalescence Impacts.", + "url": "/team/index.html#Radu%2BCimpeanu", + "type": "section", + "links": [], + "priority": 3 + }, + { + "title": "Jie Feng", + "content": "Asst. Professor, Univ. Illinois Urbana-Champaign Collaboration on: Elastic Coating, Bursting Bubbles.", + "url": "/team/index.html#Jie%2BFeng", + "type": "section", + "links": [], + "priority": 3 + }, + { + "title": "Omar Matar", + "content": "Professor, Imperial College London Collaboration on: Surfactant Dynamics, Viscoelastic Drop Impact.", + "url": "/team/index.html#Omar%2BMatar", + "type": "section", + "links": [], + "priority": 3 + }, + { + "title": "Ratul Dasgupta", + "content": "Assc. Professor, IIT-Bombay Collaboration on: Waves, Dissipative Anomaly.", + "url": "/team/index.html#Ratul%2BDasgupta", + "type": "section", + "links": [], + "priority": 3 + }, + { + "title": "Eric Lauga", + "content": "Professor, Univ. Cambridge Collaboration on: Mycofluidic Transport.", + "url": "/team/index.html#Eric%2BLauga", + "type": "section", + "links": [], + "priority": 3 + }, + { + "title": "Saikat Datta", + "content": "Senior Lecturer, Swansea University Collaboration on: Multiscale Simulation, Ice Nucleation and Removal, Hydrogen Storage.", + "url": "/team/index.html#Saikat%2BDatta", + "type": "section", + "links": [], + "priority": 3 + }, + { + "title": "Doris Vollmer", + "content": "Apl. Professor, Max Planck Institute for Polymer Research, Mainz, Germany. Collaboration on: Contact Line, Drop Impact, Superhydrophobic Surfaces.", + "url": "/team/index.html#Doris%2BVollmer", + "type": "section", + "links": [], + "priority": 3 + }, + { + "title": "Stéphane Zaleski", + "content": "Professor, Sorbonne Universite Collaboration on: Holey Sheets.", + "url": "/team/index.html#Stphane%2BZaleski", + "type": "section", + "links": [], + "priority": 3 + }, + { + "title": "Pierre Chantelot", + "content": "Postdoc, Institut Langevin, ESPCI Paris Collaboration on: Drop Impact", + "url": "/team/index.html#Pierre%2BChantelot", + "type": "section", + "links": [], + "priority": 3 + }, + { + "title": "Aleksandr Bashkatov", + "content": "Postdoc, RWTH Aachen University Collaboration on: Electrolysis, Bubble Coalescence, Swimming Bubbles.", + "url": "/team/index.html#Aleksandr%2BBashkatov", + "type": "section", + "links": [], + "priority": 3 + }, + { + "title": "Vincent Bertin", + "content": "Postdoc, University Aix-Marseille Collaboration on: Elastic Sheets, Spinning Pizza.", + "url": "/team/index.html#Vincent%2BBertin", + "type": "section", + "links": [], + "priority": 3 + }, + { + "title": "Alexandros Oratis", + "content": "Postdoc, TU Delft Collaboration on: Electrolysis, Bubble Coalescence, Swimming Bubbles.", + "url": "/team/index.html#Alexandros%2BOratis", + "type": "section", + "links": [], + "priority": 3 + }, + { + "title": "Arivazhagan Balasubramanian (Ari)", + "content": "Ph.D. Student, KTH Sweden Collaboration on: Elastoviscoplastic Flows, Bursting Bubbles", + "url": "/team/index.html#Arivazhagan%2BBalasubramanian%2BAri", + "type": "section", + "links": [], + "priority": 3 + }, + { + "title": "Konstantinos Zinelis (Costis)", + "content": "Postdoc, MIT Collaboration on: Viscoelastic Flows, Drop Impact.", + "url": "/team/index.html#Konstantinos%2BZinelis%2BCostis", + "type": "section", + "links": [], + "priority": 3 + }, + { + "title": "Milan Sent", + "content": "2025: Graduated with B.Sc., Univ. Twente\n Thesis: Spinning Pizza", + "url": "/team/index.html#Milan%2BSent", + "type": "section", + "links": [], + "priority": 3 + }, + { + "title": "Valentin Rosario", + "content": "2024: Graduated with M.Sc., Univ. Amsterdam\n Thesis: Modelling the Ward–Hunt ice-shelf as viscoelastic solid", + "url": "/team/index.html#Valentin%2BRosario", + "type": "section", + "links": [], + "priority": 3 + }, + { + "title": "Swen van den Heuvel", + "content": "Now: Ph.D. Student, Phys. Fluids - Univ. Twente\n 2023: Graduated with M.Sc., Univ. Twente\n Thesis: Hydrodynamic forces acting on vertically rising bubbles", + "url": "/team/index.html#Swen%2Bvan%2Bden%2BHeuvel", + "type": "section", + "links": [], + "priority": 3 + }, + { + "title": "Niels Kuipers", + "content": "Now: M.Sc. Student, Adv. Technology - Univ. Twente\n 2023: Graduated with B.Sc., Univ. Twente\n Thesis: Partial coalescence of drops on viscous films", + "url": "/team/index.html#Niels%2BKuipers", + "type": "section", + "links": [], + "priority": 3 + }, + { + "title": "C. H. (Luuk) Maurits", + "content": "2023: Graduated with M.Sc., Univ. Twente\n Thesis: When Laplace meets Marangoni", + "url": "/team/index.html#C%2BH%2BLuuk%2BMaurits", + "type": "section", + "links": [], + "priority": 3 + }, + { + "title": "Tom Appleford", + "content": "Now: Ph.D. Student, Fluid Lab - Univ. Amsterdam\n 2022: Graduated with M.Sc., Univ. Amsterdam\n Thesis: The deformation of a droplet in a viscoplastic simple shear flow", + "url": "/team/index.html#Tom%2BAppleford", + "type": "section", + "links": [], + "priority": 3 + }, + { + "title": "Twan Heijink", + "content": "Now: Software IVQA Engineer at Thales\n 2021: Graduated with B.Sc., Saxion Univ.\n Thesis: Standing waves at a fluid-fluid interface with plastocapillarity", + "url": "/team/index.html#Twan%2BHeijink", + "type": "section", + "links": [], + "priority": 3 + }, + { + "title": "Steven Meuleman", + "content": "Now: Mechanical Engineer at VIRO\n 2020: Graduated with B.Sc., Univ. Twente\n Thesis: Simulations of foam generation for a custom axisymmetric core-shell nozzle", + "url": "/team/index.html#Steven%2BMeuleman", + "type": "section", + "links": [], + "priority": 3 + }, + { + "title": "Thijmen Kroeze", + "content": "Now: CFD Engineer, Brink Climate Systems\n 2020: Graduated with B.Sc., Univ. Twente\n Thesis: Singular jet dynamics of drop impacts at high Bond numbers", + "url": "/team/index.html#Thijmen%2BKroeze", + "type": "section", + "links": [], + "priority": 3 + }, + { + "title": "Coen Verschuur", + "content": "Now: Ph.D. Student, Phys. Fluids - Univ. Twente\n 2020: Graduated with B.Sc., Univ. Twente\n Thesis: Early time dynamics in immiscible drop impacts", + "url": "/team/index.html#Coen%2BVerschuur", + "type": "section", + "links": [], + "priority": 3 + }, + { + "title": "Pim J. Dekker", + "content": "Now: Ph.D. Student, Phys. Fluids - Univ. Twente\n 2019: Graduated with B.Sc., Univ. Twente\n Thesis: Spreading of a drop on a water-air interface", + "url": "/team/index.html#Pim%2BJ%2BDekker", + "type": "section", + "links": [], + "priority": 3 + }, + { + "title": "Laurence Bruggink", + "content": "Now: Research Engineer at Alfen\n 2019: Graduated with B.Sc., Univ. Twente\n Thesis: Bursting bubble in a Herschel–Bulkley fluid", + "url": "/team/index.html#Laurence%2BBruggink", + "type": "section", + "links": [], + "priority": 3 + }, + { + "title": "Team, collaborators, and Conference visits", + "content": "The locations marked on this map meet one of three criteria (in the order of preference): \n 1. Hometown of our team members (including alumni) in orange, \n 2. Base location of our collaborators in green,\n 3. Places where we have presented talks in purple, or\n 4. Places where we have visited for conferences (no talks) in gray.", + "url": "/team/index.html#Team%2Bcollaborators%2Band%2BConference%2Bvisits", + "type": "section", + "links": [], "priority": 3 }, { - "title": "Arrowheads in elastoinertial turbulence - tags:", - "content": "In fact, the energy transfer analysis between flow and polymer reveals that both chaotic regimes maintain through the same near-wall mechanism, independent of the weak arrowhead structure.", - "url": "https://blogs.comphy-lab.org/Blog/Arrowheads+in+elastoinertial+turbulence", + "title": "0_ToDo-Blog-public - tags:", + "content": "tags:\n - \"#TODO-master\"Here is a list of things on our to-publish list:\nStokes waves and related topics 📅 2025-03-01 ⏳ 2025-02-05 \nShowcase bubbles, waves: 📅 2025-01-12 \nTo-Do: Blog Posts on Research Papers and Thesis Below is a list of all the papers and the thesis requiring a dedicated blog post.", + "url": "https://blogs.comphy-lab.org/0_ToDo-Blog-public", "type": "blog_excerpt", "priority": 3 }, { - "title": "Arrowheads in elastoinertial turbulence - tags:", - "content": "These findings indicate that the arrowhead represents a passive flow structure, detached from the self-sustaining mechanics of elastic turbulence inertia (EIT) (M. Beneitez, J. Page, Y. Dubief & R. R. Kerswell, J. Fluid Mech., 981, A30 (2024)).", - "url": "https://blogs.comphy-lab.org/Blog/Arrowheads+in+elastoinertial+turbulence", + "title": "0_ToDo-Blog-public - tags:", + "content": "Each post should highlight the motivation, key findings, and broader implications of the work, along with engaging visuals or videos (where applicable).", + "url": "https://blogs.comphy-lab.org/0_ToDo-Blog-public", "type": "blog_excerpt", "priority": 3 }, { - "title": "Arrowheads in elastoinertial turbulence - tags:", - "content": "The community’s focus has shifted to how such structures populate the state space and trigger transitions, rather than expecting them to appear as steady patterns in experiments.", - "url": "https://blogs.comphy-lab.org/Blog/Arrowheads+in+elastoinertial+turbulence", + "title": "0_ToDo-Blog-public - tags:", + "content": "Work in Progress \nTo jump or not to jump: Adhesion and viscous dissipation dictate the detachment of coalescing wall-attached bubbles\nLink: arXiv:2501.05532\nViscoelastic Worthington jets & droplets produced by bursting bubbles\nLink: arXiv:2408.05089\nUnifying theory of scaling in drop impact: Forces & maximum spreading diameter\nLink: arXiv:2408.12714\nElectrolyte spraying within H₂ bubbles during water electrolysis\nLink: arXiv:2409.00515\n2025 \nThe role of viscosity on drop impact forces on non-wetting surfaces\nLink: arXiv:2311.03012\nFocusing of concentric free-surface waves\nLink: arXiv:2406.05416\n2024 \nBursting bubble in an elastoviscoplastic medium\nHighlight: Cover of J.", + "url": "https://blogs.comphy-lab.org/0_ToDo-Blog-public", "type": "blog_excerpt", "priority": 3 }, { - "title": "Arrowheads in elastoinertial turbulence - tags:", - "content": "Ongoing work continues to unravel the multi-stage transition to elastic and elasto-inertial turbulence – with arrowhead/narwhal waves serving as a valuable piece of the puzzle, albeit one with clear limitations.", - "url": "https://blogs.comphy-lab.org/Blog/Arrowheads+in+elastoinertial+turbulence", + "title": "0_ToDo-Blog-public - tags:", + "content": "Fluid Mech. Link: DOI\n2023 \nDrop impact on viscous liquid films\nLink: DOI\nWhen does an impacting drop stop bouncing? Link: DOI\n2022 \nPh.D.", + "url": "https://blogs.comphy-lab.org/0_ToDo-Blog-public", "type": "blog_excerpt", "priority": 3 }, { - "title": "Arrowheads in elastoinertial turbulence - tags:", - "content": "This critical understanding ensures that we appreciate the arrowhead structures for what they are: insightful and pedagogical solutions that illuminate EIT’s mechanics, but ultimately unstable in the wild chaos of real 3D turbulence.Metadata\nAuthor:: Vatsal Sanjay\nDate published:: Feb 24, 2025\nBack to main website\nHome, Team, Research, Github, Blogs\nLinks to this page0_README", - "url": "https://blogs.comphy-lab.org/Blog/Arrowheads+in+elastoinertial+turbulence", + "title": "0_ToDo-Blog-public - tags:", + "content": "Thesis: Viscous free-surface flows\nLinks: Thesis Info, DOI, move here from old website\nImpact forces of water drops falling on superhydrophobic surfaces\nHighlights: Editor's Suggestion, Research Highlight in Nature\nLink: DOI\n2021 \nBursting bubble in a viscoplastic medium\nLink: DOI\n2020 \nLifting a sessile oil drop from a superamphiphobic surface with an impacting one\nLink: DOI\n2019 \nConsequences of inclined and dual jet impingement in stagnant liquid and stratified layers\nLink: DOI\n2018 \nFormation of fluid structures due to jet-jet and jet-sheet interactions\nLink: DOI\nNumerical assessment of hazard in compartmental fire having steady heat release rate from the source\nLink: DOI\n2017 \nFormation of liquid chain by collision of two laminar jets\nLink: DOI\nOn air entrainment in a water pool by impingement of a jet\nLink: DOI\nBack to main website\nHome, Team, Research, Github", + "url": "https://blogs.comphy-lab.org/0_ToDo-Blog-public", "type": "blog_excerpt", "priority": 3 }, diff --git a/scripts/generate_search_db.rb b/scripts/generate_search_db.rb index 128b3c2..12d1615 100644 --- a/scripts/generate_search_db.rb +++ b/scripts/generate_search_db.rb @@ -130,6 +130,79 @@ def generate_anchor(text) end end +# Process teaching content from _teaching directory +Dir.glob(File.join(ROOT_DIR, '_teaching', '*.md')).each do |file| + puts "Processing teaching file #{file}..." + + content = File.read(file) + + # Extract YAML front matter to get permalink + front_matter = {} + if content.start_with?("---\n") + _, yaml_text, content = content.split("---\n", 3) + yaml_text.lines.each do |line| + if line.include?(":") + key, value = line.split(":", 2).map(&:strip) + front_matter[key] = value + end + end + end + + # Determine the URL for this teaching content + url = front_matter['permalink'] || '/teaching/' + + # Get the title from front matter or filename + title = front_matter['title'] || File.basename(file, '.md').gsub(/^\d{4}-/, '').tr('-', ' ') + + # Split content by headers + sections = content.split(/^#+\s+/) + sections.shift # Remove content before first header + + sections.each do |section| + next if section.strip.empty? + + # Extract header and content + lines = section.lines + header = lines.first.strip + content = lines[1..].join.strip + + next if header.empty? || content.empty? + next if content.length < 50 # Skip very short sections + + # Skip navigation-like sections + next if header.match?(/^(navigation|menu|contents|index)$/i) + + # Create entry for the section + entry = { + 'title' => "#{title} - #{header}", + 'content' => content, + 'url' => "#{url}##{generate_anchor(header)}", + 'type' => 'teaching_content', + 'priority' => 2 # Medium-high priority for teaching content + } + search_db << entry + + # Also create entries for individual paragraphs + paragraphs = content.split(/\n\n+/) + paragraphs.each do |para| + para = para.strip + next if para.empty? + next if para.length < 100 # Only include substantial paragraphs + next if para.start_with?('```') # Skip code blocks + next if para.start_with?('<') # Skip HTML + + entry = { + 'title' => "#{title} - #{header}", + 'content' => para, + 'url' => "#{url}##{generate_anchor(header)}", + 'type' => 'teaching_paragraph', + 'priority' => 2 + } + search_db << entry + end + end +end + # Process each HTML file Dir.glob(File.join(ROOT_DIR, '_site', '**', '*.html')) do |file| next if file.include?('/assets/') # Skip asset files @@ -146,6 +219,103 @@ def generate_anchor(text) # Extract page title title = doc.at_css('title')&.text || File.basename(file, '.html').capitalize + # Special handling for teaching pages + if url.include?('/teaching/') + # Process course details + doc.css('.course-details__item').each do |detail| + heading = detail.at_css('h4')&.text.to_s.strip + content = detail.at_css('p')&.text.to_s.strip + + next if heading.empty? || content.empty? + + # Create entry for course detail + entry = { + 'title' => "#{title} - #{heading}", + 'content' => content, + 'url' => url, + 'type' => 'teaching_detail', + 'priority' => 2 # Medium-high priority + } + search_db << entry + end + + # Process course schedule/sections + doc.css('h3').each do |heading| + heading_text = heading.text.strip + next if heading_text.empty? + + # Get content until next h3 + content_nodes = [] + current = heading.next_element + while current && current.name != 'h3' + content_nodes << current.text.strip unless current.text.strip.empty? + current = current.next_element + end + content = content_nodes.join(' ').strip + next if content.empty? + + # Create entry for course section + entry = { + 'title' => "#{title} - #{heading_text}", + 'content' => content, + 'url' => "#{url}##{generate_anchor(heading_text)}", + 'type' => 'teaching_section', + 'priority' => 2 + } + search_db << entry + end + + # Process specific teaching sections like Prerequisites, Course Description, etc. + ['Prerequisites', 'Course Description', 'Registration', 'What will you learn'].each do |section_name| + section = doc.xpath("//h2[contains(text(), '#{section_name}')]").first + next unless section + + # Get content until next h2 + content_nodes = [] + current = section.next_element + while current && current.name != 'h2' + content_nodes << current.text.strip unless current.text.strip.empty? + current = current.next_element + end + content = content_nodes.join(' ').strip + next if content.empty? + + # Create entry for the specific section + entry = { + 'title' => "#{title} - #{section_name}", + 'content' => content, + 'url' => "#{url}##{generate_anchor(section_name)}", + 'type' => 'teaching_course_info', + 'priority' => 2 + } + search_db << entry + end + + # Process course card content on index page + doc.css('.course-card').each do |card| + card_title = card.at_css('.course-card__title')&.text.to_s.strip + card_desc = card.at_css('.course-card__desc')&.text.to_s.strip + card_meta = card.css('.course-card__meta').map(&:text).join(' - ').strip + + next if card_title.empty? && card_desc.empty? + + content = [card_title, card_meta, card_desc].reject(&:empty?).join(' - ') + + # Get link to course page + course_link = card.at_css('.course-card__link')&.[]('href').to_s + + # Create entry for course card + entry = { + 'title' => card_title.empty? ? "Teaching course" : card_title, + 'content' => content, + 'url' => course_link.empty? ? url : course_link, + 'type' => 'teaching_course', + 'priority' => 2 + } + search_db << entry + end + end + # Special handling for team members doc.css('h2').each do |heading| name = heading.text.strip From 4e17799e7baa2fd830400b2734d99da34f04d0f6 Mon Sep 17 00:00:00 2001 From: Vatsal Sanjay Date: Sun, 2 Mar 2025 00:15:07 +0100 Subject: [PATCH 05/19] feat: add command palette to website This commit adds a simple command palette to the website, allowing users to quickly navigate to different pages and perform common actions. The key changes include: - Add a new CSS file `command-palette.css` to style the command palette - Add a script block to the `default.html` layout to initialize the command palette - Add a new button in the header to open the command palette - Define the command data in a new `command-data.js` file, including navigation commands and external links These changes provide a more efficient and intuitive way for users to interact with the website, improving the overall user experience. --- README.md | 31 ++- _layouts/default.html | 254 +++++++++++++++++++++++- assets/css/command-palette.css | 293 +++++++++++++++++++++++++++ assets/js/command-data.js | 308 +++++++++++++++++++++++++++++ assets/js/command-palette-setup.js | 273 +++++++++++++++++++++++++ 5 files changed, 1153 insertions(+), 6 deletions(-) create mode 100644 assets/css/command-palette.css create mode 100644 assets/js/command-data.js create mode 100644 assets/js/command-palette-setup.js diff --git a/README.md b/README.md index f4fb2e9..40e2816 100644 --- a/README.md +++ b/README.md @@ -247,6 +247,35 @@ Search results are prioritized and filtered as follows: 4. Blog Posts from blogs.comphy-lab.org 5. Regular content (headings and paragraphs) +### Command Palette Functionality +The website includes a command palette feature that provides quick access to actions and navigation through keyboard shortcuts: + +- **Keyboard Shortcut**: Access via ⌘/ on Mac, ctrl+/ on Windows, or by clicking the terminal icon in the navigation +- **Navigation Commands**: Quickly navigate to any section of the website +- **External Link Commands**: Direct access to GitHub, Google Scholar, YouTube, and Bluesky +- **Tool Commands**: Search, scroll to top/bottom, and other utility functions +- **Context-Aware Commands**: Additional commands appear based on current page +- **Recent Commands**: Track and display recently used commands +- **Help**: View all available keyboard shortcuts with the "?" command + +Key features: +- Different visual styling from search to avoid confusion (indigo accent color vs blue for search) +- Grouping of commands by section for easy discoverability +- Shortcuts for common tasks (g h = go home, g r = go to research, etc.) +- Comprehensive shortcut help accessible through the "?" command +- Command history that remembers your frequently used commands + +The command palette is built with: +- NinjaKeys library for the palette UI (/assets/js/search/ninja-keys.min.js) +- HotKeys.js for keyboard shortcut handling (/assets/js/search/hotkeys-js/hotkeys.esm.min.js) +- Custom styling and functionality specific to the website + +Files: +- `/assets/js/command-data.js`: Defines all available commands +- `/assets/js/command-palette-setup.js`: Initializes and manages the command palette +- `/assets/css/command-palette.css`: Styling for the command palette +- `/assets/js/shortcut-key.js`: Detects Mac vs Windows for shortcut display + Search behavior and features: - Minimum query length: 2 characters - Keyboard shortcut (⌘K / ctrl+K) opens a command palette style search interface on all pages @@ -255,7 +284,7 @@ Search behavior and features: - NinjaKeys integration provides a modern command palette experience - Search results appear instantly as you type - Results are ranked by relevance and match percentage -in + The search database is automatically generated during the build process by `scripts/generate_search_db.rb`. This script: - Indexes all HTML and markdown content - Identifies and prioritizes team members, teaching content, and research papers diff --git a/_layouts/default.html b/_layouts/default.html index 78d47ab..69622f1 100644 --- a/_layouts/default.html +++ b/_layouts/default.html @@ -56,16 +56,32 @@ + + + + - - - - +
    + +
  • + +
  • @@ -195,10 +276,173 @@
    + + + + + + - + + + + + + + diff --git a/assets/css/command-palette.css b/assets/css/command-palette.css new file mode 100644 index 0000000..dacf113 --- /dev/null +++ b/assets/css/command-palette.css @@ -0,0 +1,293 @@ +/* Custom Command Palette styling */ +.simple-command-palette { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + z-index: 1000; + display: none; +} + +.simple-command-palette-backdrop { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-color: rgba(0, 0, 0, 0.5); + backdrop-filter: blur(4px); +} + +.simple-command-palette-modal { + position: absolute; + top: 20%; + left: 50%; + transform: translateX(-50%); + width: 640px; + max-width: 90vw; + background-color: white; + border-radius: 8px; + box-shadow: 0 4px 20px rgba(0, 0, 0, 0.2); + overflow: hidden; +} + +#command-palette-input { + width: 100%; + padding: 12px 16px; + border: none; + border-bottom: 1px solid #eee; + font-size: 16px; + outline: none; +} + +.command-palette-results { + max-height: 60vh; + overflow-y: auto; +} + +.command-palette-section { + padding: 8px 0; +} + +.command-palette-section-title { + padding: 4px 16px; + font-size: 12px; + font-weight: bold; + color: #6366f1; + text-transform: uppercase; +} + +.command-palette-commands { + margin-bottom: 8px; +} + +.command-palette-command { + display: flex; + align-items: center; + padding: 8px 16px; + cursor: pointer; +} + +.command-palette-command:hover { + background-color: rgba(99, 102, 241, 0.1); +} + +.command-palette-icon { + flex: 0 0 24px; + margin-right: 12px; + color: #6366f1; +} + +.command-palette-title { + flex: 1; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.command-palette-shortcut { + flex: 0 0 auto; + padding: 2px 6px; + border-radius: 4px; + background-color: #f1f1f1; + font-size: 12px; + color: #666; +} + +.command-palette-no-results { + padding: 16px; + text-align: center; + color: #666; +} + +/* Command palette button styling */ +.command-palette-button { + position: relative; + display: inline-flex; + align-items: center; + margin-left: 0.5rem; + z-index: 10; /* Ensure the button is clickable */ +} + +.command-palette-button .btn { + padding: 0.4rem 0.6rem; + font-size: 0.9rem; + border-radius: 0.25rem; + cursor: pointer; + display: inline-flex; + align-items: center; + gap: 0.3rem; + background-color: rgba(255, 255, 255, 0.1); + border: 1px solid #6366f1; + color: #6366f1; + transition: all 0.2s ease-in-out; + position: relative; + z-index: 20; /* Ensure clickability */ +} + +.command-palette-button .btn:hover { + background-color: rgba(99, 102, 241, 0.1); + transform: translateY(-1px); + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); +} + +.command-palette-button .btn:active { + transform: translateY(0); + box-shadow: none; +} + +.command-palette-button .btn .fa-terminal { + font-size: 1.1em; +} + +/* Hide one of the shortcut texts depending on platform */ +.mac-theme-text, .default-theme-text { + display: none; +} + +/* Mobile styling for command palette button */ +@media screen and (max-width: 768px) { + .command-palette-button { + margin: 0.8rem 0; + width: 100%; + } + + .command-palette-button .btn { + width: 100%; + justify-content: center; + padding: 0.6rem; + } + + .simple-command-palette-modal { + width: 95vw; + max-width: 95vw; + } +} + +/* Dark mode styling */ +@media (prefers-color-scheme: dark) { + .simple-command-palette-modal { + background-color: #222; + color: #eee; + } + + #command-palette-input { + background-color: #333; + color: #fff; + border-bottom-color: #444; + } + + .command-palette-section-title { + color: #818cf8; + } + + .command-palette-command:hover { + background-color: rgba(129, 140, 248, 0.1); + } + + .command-palette-icon { + color: #818cf8; + } + + .command-palette-shortcut { + background-color: #444; + color: #ccc; + } + + .command-palette-no-results { + color: #999; + } +} + +/* Custom styles for the shortcut help modal */ +.shortcut-help-overlay { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-color: rgba(0, 0, 0, 0.7); + z-index: 2000; + display: flex; + justify-content: center; + align-items: center; +} + +.shortcut-help-content { + background-color: white; + border-radius: 8px; + padding: 20px; + max-width: 600px; + max-height: 80vh; + overflow: auto; + box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3); +} + +.shortcut-help-content h2 { + margin-top: 0; +} + +.shortcut-help-content table { + width: 100%; + border-collapse: collapse; + margin-bottom: 1rem; +} + +.shortcut-help-content th, +.shortcut-help-content td { + text-align: left; + padding: 8px; + border-bottom: 1px solid #ddd; +} + +.shortcut-help-content kbd { + display: inline-block; + padding: 0.1em 0.6em; + border: 1px solid #ccc; + border-radius: 3px; + background-color: #f7f7f7; + box-shadow: 0 1px 0px rgba(0, 0, 0, 0.2); + font-family: monospace; + font-size: 0.85em; + margin: 0 0.1em; +} + +.shortcut-help-footer { + text-align: center; + margin-top: 20px; +} + +.shortcut-help-footer button { + padding: 8px 16px; + background-color: #6366f1; + color: white; + border: none; + border-radius: 4px; + cursor: pointer; + transition: background-color 0.2s; +} + +.shortcut-help-footer button:hover { + background-color: #4f46e5; +} + +/* Dark mode for shortcut help */ +@media (prefers-color-scheme: dark) { + .shortcut-help-content { + background-color: #222; + color: #eee; + } + + .shortcut-help-content th, + .shortcut-help-content td { + border-bottom: 1px solid #444; + } + + .shortcut-help-content kbd { + background-color: #333; + color: #fff; + border-color: #555; + box-shadow: 0 1px 0px rgba(255, 255, 255, 0.1); + } +} \ No newline at end of file diff --git a/assets/js/command-data.js b/assets/js/command-data.js new file mode 100644 index 0000000..0a08152 --- /dev/null +++ b/assets/js/command-data.js @@ -0,0 +1,308 @@ +// Command data for website command palette +(function() { + console.log('Loading command data'); + + // Define the command data + window.commandData = [ + // Navigation commands + { + id: "home", + title: "Go to Home", + handler: () => { window.location.href = '/'; }, + section: "Navigation", + shortcuts: ["g h"], + icon: '' + }, + { + id: "team", + title: "Go to Team Page", + handler: () => { window.location.href = '/team/'; }, + section: "Navigation", + shortcuts: ["g t"], + icon: '' + }, + { + id: "research", + title: "Go to Research Page", + handler: () => { window.location.href = '/research'; }, + section: "Navigation", + shortcuts: ["g r"], + icon: '' + }, + { + id: "teaching", + title: "Go to Teaching Page", + handler: () => { window.location.href = '/teaching'; }, + section: "Navigation", + shortcuts: ["g e"], + icon: '' + }, + { + id: "join", + title: "Go to Join Us Page", + handler: () => { window.location.href = '/join'; }, + section: "Navigation", + shortcuts: ["g j"], + icon: '' + }, + { + id: "blog", + title: "Go to Blog", + handler: () => { window.location.href = 'https://blogs.comphy-lab.org/'; }, + section: "Navigation", + shortcuts: ["g b"], + icon: '' + }, + { + id: "back", + title: "Go Back", + handler: () => { window.history.back(); }, + section: "Navigation", + shortcuts: ["b"], + icon: '' + }, + { + id: "forward", + title: "Go Forward", + handler: () => { window.history.forward(); }, + section: "Navigation", + shortcuts: ["f"], + icon: '' + }, + + // External links + { + id: "github", + title: "Visit GitHub", + handler: () => { window.open('https://github.com/comphy-lab', '_blank'); }, + section: "External Links", + shortcuts: ["g g"], + icon: '' + }, + { + id: "scholar", + title: "Visit Google Scholar", + handler: () => { window.open('https://scholar.google.com/citations?user=tHb_qZoAAAAJ&hl=en', '_blank'); }, + section: "External Links", + shortcuts: ["g s"], + icon: '' + }, + { + id: "youtube", + title: "Visit YouTube Channel", + handler: () => { window.open('https://www.youtube.com/@CoMPhyLab', '_blank'); }, + section: "External Links", + shortcuts: ["g y"], + icon: '' + }, + { + id: "bluesky", + title: "Visit Bluesky", + handler: () => { window.open('https://bsky.app/profile/comphy-lab.org', '_blank'); }, + section: "External Links", + shortcuts: ["g l"], + icon: '' + }, + + // Tools + { + id: "search", + title: "Search Website", + handler: () => { + // Find and trigger the search function + const searchInput = document.getElementById('searchInput'); + if (searchInput) { + searchInput.focus(); + const searchModal = document.querySelector('ninja-keys#search-modal'); + if (searchModal) searchModal.open(); + } + }, + section: "Tools", + shortcuts: ["s"], + icon: '' + }, + { + id: "top", + title: "Scroll to Top", + handler: () => { window.scrollTo({top: 0, behavior: 'smooth'}); }, + section: "Tools", + shortcuts: ["t t"], + icon: '' + }, + { + id: "bottom", + title: "Scroll to Bottom", + handler: () => { window.scrollTo({top: document.body.scrollHeight, behavior: 'smooth'}); }, + section: "Tools", + shortcuts: ["t b"], + icon: '' + }, + + // Help commands + { + id: "help", + title: "View Keyboard Shortcuts", + handler: () => { window.displayShortcutsHelp(); }, + section: "Help", + shortcuts: ["?"], + icon: '' + }, + { + id: "repository", + title: "View Website Repository", + handler: () => { window.open('https://github.com/comphy-lab/comphy-lab.github.io', '_blank'); }, + section: "Help", + shortcuts: ["h r"], + icon: '' + } + ]; + + console.log('Command data loaded with ' + window.commandData.length + ' commands'); + + // Define the displayShortcutsHelp function globally + window.displayShortcutsHelp = function() { + console.log('Displaying shortcut help'); + // Create a modal to show all available shortcuts + const modal = document.createElement('div'); + modal.style.position = 'fixed'; + modal.style.top = '0'; + modal.style.left = '0'; + modal.style.width = '100%'; + modal.style.height = '100%'; + modal.style.backgroundColor = 'rgba(0, 0, 0, 0.7)'; + modal.style.zIndex = '2000'; + modal.style.display = 'flex'; + modal.style.justifyContent = 'center'; + modal.style.alignItems = 'center'; + + const content = document.createElement('div'); + content.style.backgroundColor = 'white'; + content.style.borderRadius = '8px'; + content.style.padding = '20px'; + content.style.maxWidth = '600px'; + content.style.maxHeight = '80vh'; + content.style.overflow = 'auto'; + content.style.boxShadow = '0 4px 20px rgba(0, 0, 0, 0.3)'; + + // Media query for dark mode + if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) { + content.style.backgroundColor = '#333'; + content.style.color = '#fff'; + } + + // Group commands by section + const sections = {}; + window.commandData.forEach(command => { + if (!sections[command.section]) { + sections[command.section] = []; + } + sections[command.section].push(command); + }); + + let html = '

    Keyboard Shortcuts

    '; + html += '

    Press Ctrl+/ (⌘/ on Mac) to open the command palette

    '; + + // Add each section and its commands + Object.keys(sections).forEach(section => { + html += `

    ${section}

    `; + html += ''; + html += ''; + + sections[section].forEach(command => { + const shortcuts = command.shortcuts ? command.shortcuts.map(s => `${s}`).join(' or ') : ''; + html += ` + + + `; + }); + + html += '
    CommandShortcut
    ${command.icon} ${command.title}${shortcuts}
    '; + }); + + // Add close button + html += '
    '; + + content.innerHTML = html; + modal.appendChild(content); + document.body.appendChild(modal); + + // Add event listener to close + document.getElementById('close-shortcuts-help').addEventListener('click', () => { + document.body.removeChild(modal); + }); + + // Close when clicking outside + modal.addEventListener('click', (e) => { + if (e.target === modal) { + document.body.removeChild(modal); + } + }); + }; + + // Add page-specific command function + window.addContextCommands = function() { + // Get the current path + const currentPath = window.location.pathname; + let contextCommands = []; + + // Research page specific commands + if (currentPath.includes('/research')) { + contextCommands = [ + { + id: "filter-research", + title: "Filter Research by Tag", + handler: () => { + // Focus on the filter input if it exists + const filterInput = document.querySelector('.research-filter-input'); + if (filterInput) filterInput.focus(); + }, + section: "Page Actions", + shortcuts: ["f t"], + icon: '' + } + ]; + } + // Team page specific commands + else if (currentPath.includes('/team')) { + contextCommands = [ + { + id: "email-team", + title: "Contact Team", + handler: () => { window.location.href = '/join'; }, + section: "Page Actions", + shortcuts: ["c t"], + icon: '' + } + ]; + } + // Teaching page specific commands + else if (currentPath.includes('/teaching')) { + contextCommands = [ + { + id: "sort-courses", + title: "Sort Courses by Date", + handler: () => { + // Trigger sorting function if it exists + if (typeof sortCoursesByDate === 'function') { + sortCoursesByDate(); + } + }, + section: "Page Actions", + shortcuts: ["s d"], + icon: '' + } + ]; + } + + // Add context commands if there are any + if (contextCommands.length > 0) { + // Combine context commands with global commands + window.commandData = [...contextCommands, ...window.commandData]; + } + }; + + // Call addContextCommands automatically + document.addEventListener('DOMContentLoaded', function() { + window.addContextCommands(); + }); +})(); \ No newline at end of file diff --git a/assets/js/command-palette-setup.js b/assets/js/command-palette-setup.js new file mode 100644 index 0000000..bc67ce8 --- /dev/null +++ b/assets/js/command-palette-setup.js @@ -0,0 +1,273 @@ +// Initialize the command palette +function initCommandPalette() { + const ninjaKeys = document.querySelector('ninja-keys#command-palette'); + if (!ninjaKeys) { + console.error('Command palette element not found!'); + return; + } + + console.log('Command palette initialized'); + + // Set initial data + if (window.commandData) { + try { + ninjaKeys.data = window.commandData; + console.log('Command data loaded'); + } catch (e) { + console.error('Error setting command data:', e); + } + } else { + console.error('Command data not available'); + } + + // Add theme change listener if you support dark/light modes + const htmlEl = document.querySelector('html'); + if (htmlEl) { + const observer = new MutationObserver(() => { + const darkMode = htmlEl.dataset.theme === 'dark'; + ninjaKeys.setAttribute('theme', darkMode ? 'dark' : 'light'); + }); + observer.observe(htmlEl, { attributes: true }); + + // Set initial theme + const darkMode = htmlEl.dataset.theme === 'dark' || + (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches); + ninjaKeys.setAttribute('theme', darkMode ? 'dark' : 'light'); + } + + // Add context-aware commands based on current page + addContextCommands(); + + // Mark as initialized in case it's not already + if (!window.ninjaKeysInitialized) { + window.ninjaKeysInitialized = true; + ninjaKeys.classList.add('ninja-initialized'); + } +} + +// Function to open the command palette +function openCommandPalette() { + console.log('Opening command palette'); + + // If on mobile and navbar is expanded, collapse it + const navbarToggler = document.querySelector('.s-header__menu-toggle'); + const navbarCollapse = document.querySelector('.s-header__nav'); + if (navbarToggler && navbarCollapse && navbarCollapse.classList.contains('s-header__nav--is-visible')) { + navbarToggler.click(); + } + + // Use the global function if available + if (typeof window.openNinjaKeys === 'function') { + window.openNinjaKeys(); + return; + } + + // Fallback to direct method + const ninjaKeys = document.querySelector('ninja-keys#command-palette'); + if (ninjaKeys) { + if (typeof ninjaKeys.open === 'function') { + console.log('Opening ninja-keys modal'); + try { + ninjaKeys.open(); + } catch (e) { + console.error('Error opening ninja-keys:', e); + } + } else { + console.error('ninja-keys open method is not available'); + // Try to make it work anyway by dispatching a custom event + try { + ninjaKeys.dispatchEvent(new CustomEvent('hotkeys', { detail: { key: '/' } })); + } catch (e) { + console.error('Failed to dispatch hotkeys event:', e); + } + } + } else { + console.error('Command palette element not found when trying to open'); + } +} + +// Add page-specific commands based on the current URL +function addContextCommands() { + // Get the current path + const currentPath = window.location.pathname; + let contextCommands = []; + + // Research page specific commands + if (currentPath.includes('/research')) { + contextCommands = [ + { + id: "filter-research", + title: "Filter Research by Tag", + handler: () => { + // Focus on the filter input if it exists + const filterInput = document.querySelector('.research-filter-input'); + if (filterInput) filterInput.focus(); + }, + section: "Page Actions", + shortcuts: ["f t"], + icon: '' + } + ]; + } + // Team page specific commands + else if (currentPath.includes('/team')) { + contextCommands = [ + { + id: "email-team", + title: "Contact Team", + handler: () => { window.location.href = '/join'; }, + section: "Page Actions", + shortcuts: ["c t"], + icon: '' + } + ]; + } + // Teaching page specific commands + else if (currentPath.includes('/teaching')) { + contextCommands = [ + { + id: "sort-courses", + title: "Sort Courses by Date", + handler: () => { + // Trigger sorting function if it exists + if (typeof sortCoursesByDate === 'function') { + sortCoursesByDate(); + } + }, + section: "Page Actions", + shortcuts: ["s d"], + icon: '' + } + ]; + } + + // Add context commands to ninja-keys if there are any + if (contextCommands.length > 0) { + const ninjaKeys = document.querySelector('ninja-keys#command-palette'); + if (ninjaKeys && window.commandData) { + // Combine context commands with global commands + ninjaKeys.data = [...contextCommands, ...window.commandData]; + } + } +} + +// Recent commands tracking +let recentCommands = []; + +function trackCommandUsage(commandId) { + // Add to recent commands + recentCommands.unshift(commandId); + // Keep only the last 5 commands + recentCommands = recentCommands.slice(0, 5); + // Save to localStorage + localStorage.setItem('recentCommands', JSON.stringify(recentCommands)); + + // Update the command palette data to show recent commands + updateCommandPaletteWithRecent(); +} + +function updateCommandPaletteWithRecent() { + const savedRecent = localStorage.getItem('recentCommands'); + if (savedRecent && window.commandData) { + recentCommands = JSON.parse(savedRecent); + + // Find the existing commands + const recentCommandObjects = recentCommands.map(id => { + return window.commandData.find(cmd => cmd.id === id); + }).filter(Boolean); + + // Only add the section if we have recent commands + if (recentCommandObjects.length > 0) { + // Create a "Recent" section + const recentSection = { + id: "recent-section", + title: "Recent Commands", + section: "Recent", + children: recentCommandObjects.map(cmd => cmd.id) + }; + + // Add to ninja-keys + const ninjaKeys = document.querySelector('ninja-keys#command-palette'); + if (ninjaKeys) { + // Add recent commands at the top + const updatedData = [ + ...recentCommandObjects.map(cmd => ({...cmd, section: "Recent"})), + ...window.commandData + ]; + ninjaKeys.data = updatedData; + } + } + } +} + +// Add direct event listeners for the keyboard shortcut and button +function setupCommandPaletteEvents() { + console.log('Setting up command palette events'); + + // Use a try-catch block to prevent errors from breaking the page + try { + // Register keyboard shortcut without relying on hotkeys library + document.addEventListener('keydown', function(event) { + // For Mac: Command + / + // For Windows/Linux: Ctrl + / + if ((event.metaKey || event.ctrlKey) && event.key === '/') { + event.preventDefault(); + openCommandPalette(); + } + }); + + // Add click event to the command palette button + const commandPaletteBtn = document.getElementById('command-palette-btn'); + if (commandPaletteBtn) { + console.log('Command palette button found'); + commandPaletteBtn.addEventListener('click', function(e) { + e.preventDefault(); + openCommandPalette(); + }); + } else { + console.error('Command palette button not found'); + } + } catch (e) { + console.error('Error setting up command palette events:', e); + } +} + +// Initialize on page load +document.addEventListener('DOMContentLoaded', function() { + console.log('DOM content loaded - initializing command palette'); + + // Wait a bit to ensure custom elements are defined + setTimeout(function() { + // Initialize the command palette + initCommandPalette(); + + // Load recent commands if available + updateCommandPaletteWithRecent(); + + // Setup events + setupCommandPaletteEvents(); + + // Also use the hotkeys library if available + if (typeof hotkeys !== 'undefined') { + console.log('Hotkeys library found, setting up shortcuts'); + hotkeys('ctrl+/,command+/', function(event, handler) { + event.preventDefault(); + openCommandPalette(); + }); + } else { + console.warn('Hotkeys library not found, using fallback keyboard event listener'); + } + + // Define the open method on the ninja-keys element if it doesn't exist + const ninjaKeys = document.querySelector('ninja-keys#command-palette'); + if (ninjaKeys && typeof ninjaKeys.open !== 'function') { + console.warn('Defining open method on ninja-keys element'); + // This is a fallback solution in case the component doesn't load properly + ninjaKeys.open = function() { + this.setAttribute('open', ''); + this.style.display = 'block'; + this.classList.add('ninja-visible'); + }; + } + }, 500); +}); \ No newline at end of file From 004addc03255cf29f21749007c90e43415bc8a8b Mon Sep 17 00:00:00 2001 From: Vatsal Sanjay Date: Sun, 2 Mar 2025 00:35:27 +0100 Subject: [PATCH 06/19] feat(command-palette): add search functionality and excerpt display The changes in this commit add search functionality to the command palette and display excerpts for search results. The main changes are: 1. Fetch and preload the search database on page load for faster searching. 2. Add a new `command-palette-excerpt` class to display a short excerpt for each search result. 3. Implement a new `renderSections` function to handle rendering the command palette sections. 4. Add a search query check and call the `searchDatabaseForCommandPalette` function to fetch and display search results. 5. Hide the excerpt on mobile devices to save space. These changes improve the overall user experience of the command palette by providing a more powerful search feature and additional context for the search results. --- _layouts/default.html | 69 ++++++++++++++++++++++++---- assets/css/command-palette.css | 32 +++++++++++++ assets/js/command-data.js | 84 ++++++++++++++++++++++++++++++++++ 3 files changed, 175 insertions(+), 10 deletions(-) diff --git a/_layouts/default.html b/_layouts/default.html index 69622f1..4334379 100644 --- a/_layouts/default.html +++ b/_layouts/default.html @@ -75,6 +75,22 @@ } } }; + + // Ensure search database is preloaded for faster searching + window.addEventListener('DOMContentLoaded', function() { + // Try to prefetch the search database if it exists + fetch('/assets/js/search_db.json').then(response => { + if (response.ok) { + return response.json(); + } + throw new Error('Search database not found'); + }).then(data => { + console.log('Search database prefetched successfully'); + window.searchData = data; + }).catch(err => { + console.warn('Could not prefetch search database:', err.message); + }); + }); @@ -329,6 +345,39 @@ sections[cmd.section].push(cmd); }); + // If query is at least 3 characters, search the database as well + if (query && query.length >= 3 && typeof window.searchDatabaseForCommandPalette === 'function') { + // We'll use a promise to handle the async search + window.searchDatabaseForCommandPalette(query).then(searchResults => { + if (searchResults && searchResults.length > 0) { + // Add search results to sections + sections['Search Results'] = searchResults; + + // Re-render the UI with search results + renderSections(sections, resultsContainer); + } + }).catch(err => { + console.error('Error searching database:', err); + }); + } + + // Render the sections we have now (this will be called immediately, and again if search results come in) + renderSections(sections, resultsContainer); + + // Show message if no results + if (Object.keys(sections).length === 0) { + const noResults = document.createElement('div'); + noResults.className = 'command-palette-no-results'; + noResults.textContent = 'No commands found'; + resultsContainer.appendChild(noResults); + } + } + + // Helper function to render sections + function renderSections(sections, container) { + // Clear container first + container.innerHTML = ''; + // Create DOM elements for results Object.keys(sections).forEach(section => { const sectionEl = document.createElement('div'); @@ -345,12 +394,20 @@ sections[section].forEach(cmd => { const cmdEl = document.createElement('div'); cmdEl.className = 'command-palette-command'; - cmdEl.innerHTML = ` + + let cmdContent = `
    ${cmd.icon || ''}
    ${cmd.title}
    ${cmd.shortcuts ? `
    ${cmd.shortcuts[0]}
    ` : ''} `; + // Add excerpt for search results if available + if (cmd.excerpt) { + cmdContent += `
    ${cmd.excerpt.substring(0, 120)}${cmd.excerpt.length > 120 ? '...' : ''}
    `; + } + + cmdEl.innerHTML = cmdContent; + cmdEl.addEventListener('click', function(e) { if (typeof cmd.handler === 'function') { document.getElementById('simple-command-palette').style.display = 'none'; @@ -362,16 +419,8 @@ }); sectionEl.appendChild(commandsList); - resultsContainer.appendChild(sectionEl); + container.appendChild(sectionEl); }); - - // Show message if no results - if (filteredCommands.length === 0) { - const noResults = document.createElement('div'); - noResults.className = 'command-palette-no-results'; - noResults.textContent = 'No commands found'; - resultsContainer.appendChild(noResults); - } } // Set up command palette functionality when DOM is loaded diff --git a/assets/css/command-palette.css b/assets/css/command-palette.css index dacf113..3121ad1 100644 --- a/assets/css/command-palette.css +++ b/assets/css/command-palette.css @@ -67,6 +67,7 @@ align-items: center; padding: 8px 16px; cursor: pointer; + flex-wrap: wrap; } .command-palette-command:hover { @@ -95,6 +96,25 @@ color: #666; } +.command-palette-excerpt { + flex-basis: 100%; + margin-top: 4px; + margin-left: 36px; + font-size: 12px; + color: #666; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + max-width: 100%; +} + +/* Style for search results section */ +.command-palette-section:has(.command-palette-section-title:contains("Search Results")) { + border-top: 1px solid #eee; + margin-top: 8px; + padding-top: 8px; +} + .command-palette-no-results { padding: 16px; text-align: center; @@ -163,6 +183,10 @@ width: 95vw; max-width: 95vw; } + + .command-palette-excerpt { + display: none; /* Hide excerpts on mobile to save space */ + } } /* Dark mode styling */ @@ -195,6 +219,14 @@ color: #ccc; } + .command-palette-excerpt { + color: #999; + } + + .command-palette-section:has(.command-palette-section-title:contains("Search Results")) { + border-top-color: #444; + } + .command-palette-no-results { color: #999; } diff --git a/assets/js/command-data.js b/assets/js/command-data.js index 0a08152..20fef1f 100644 --- a/assets/js/command-data.js +++ b/assets/js/command-data.js @@ -239,6 +239,90 @@ }); }; + // Search database integration + window.searchDatabaseForCommandPalette = async function(query) { + // Only perform search if query is at least 3 characters long + if (!query || query.length < 3) { + return []; + } + + console.log('Searching database for:', query); + + try { + // Check if we have a searchIndex already loaded in window + if (!window.searchFuse && window.searchData) { + // If we already have search data but no Fuse object + try { + window.searchFuse = new Fuse(window.searchData, { + keys: ['title', 'content', 'tags', 'categories'], + includeScore: true, + threshold: 0.4 + }); + } catch (e) { + console.error('Error creating Fuse instance:', e); + return []; + } + } else if (!window.searchFuse) { + // Try to fetch search database if it doesn't exist yet + try { + const response = await fetch('/assets/js/search_db.json'); + if (response.ok) { + try { + const searchData = await response.json(); + if (!searchData || !Array.isArray(searchData)) { + console.warn('Search database has invalid format'); + return []; + } + window.searchData = searchData; + window.searchFuse = new Fuse(searchData, { + keys: ['title', 'content', 'tags', 'categories'], + includeScore: true, + threshold: 0.4 + }); + } catch (e) { + console.error('Error parsing search database JSON:', e); + return []; + } + } else { + console.warn(`No search database found (${response.status})`); + return []; + } + } catch (e) { + console.error('Error loading search database:', e); + return []; + } + } + + // Perform the search + if (window.searchFuse) { + try { + const results = window.searchFuse.search(query); + + // Return at most 5 results to avoid cluttering the command palette + return results.slice(0, 5).map(result => ({ + id: `search-result-${result.refIndex}`, + title: result.item.title || 'Untitled', + handler: () => { + if (result.item.url) { + window.location.href = result.item.url; + } + }, + section: "Search Results", + icon: '', + excerpt: result.item.excerpt || (result.item.content && result.item.content.substring(0, 100) + '...') || '' + })); + } catch (e) { + console.error('Error performing search with Fuse:', e); + return []; + } + } + } catch (e) { + console.error('Error searching database:', e); + } + + return []; + }; + // Add page-specific command function window.addContextCommands = function() { // Get the current path From f64e56beb08c69a4febe207c3e61b137ef15b378 Mon Sep 17 00:00:00 2001 From: Vatsal Sanjay Date: Sun, 2 Mar 2025 00:37:27 +0100 Subject: [PATCH 07/19] feat: Improve command palette UI Removes the command palette shortcut display as it is not necessary for the user experience. This simplifies the UI and makes the command palette more focused on the essential information. --- _layouts/default.html | 1 - 1 file changed, 1 deletion(-) diff --git a/_layouts/default.html b/_layouts/default.html index 4334379..5c2208c 100644 --- a/_layouts/default.html +++ b/_layouts/default.html @@ -398,7 +398,6 @@ let cmdContent = `
    ${cmd.icon || ''}
    ${cmd.title}
    - ${cmd.shortcuts ? `
    ${cmd.shortcuts[0]}
    ` : ''} `; // Add excerpt for search results if available From b3400d033f1d842345777138733c50d27c27799d Mon Sep 17 00:00:00 2001 From: Vatsal Sanjay Date: Sun, 2 Mar 2025 00:41:36 +0100 Subject: [PATCH 08/19] feat: add keyboard navigation to command palette This commit adds keyboard navigation functionality to the command palette modal. Users can now use the up and down arrow keys to navigate through the available commands, and press Enter to select the currently highlighted command. Additionally, a footer has been added to the modal, providing instructions for the keyboard shortcuts. The changes include: - Adding event listeners to the document to handle keyboard events - Implementing logic to track the currently selected command and update the UI accordingly - Ensuring the selected command is scrolled into view when navigating - Adding a footer element with keyboard shortcut information These improvements enhance the usability and accessibility of the command palette feature. --- _layouts/default.html | 45 +++++++++++++++++++++++++++-- assets/css/command-palette.css | 53 +++++++++++++++++++++++++++++++++- 2 files changed, 94 insertions(+), 4 deletions(-) diff --git a/_layouts/default.html b/_layouts/default.html index 5c2208c..ab96a44 100644 --- a/_layouts/default.html +++ b/_layouts/default.html @@ -298,6 +298,11 @@
    +
    @@ -443,10 +448,44 @@ if (e.key === 'Escape') { document.getElementById('simple-command-palette').style.display = 'none'; } else if (e.key === 'Enter') { - const firstCommand = document.querySelector('.command-palette-command'); - if (firstCommand) { - firstCommand.click(); + const selectedCommand = document.querySelector('.command-palette-command.selected'); + if (selectedCommand) { + selectedCommand.click(); + } else { + const firstCommand = document.querySelector('.command-palette-command'); + if (firstCommand) { + firstCommand.click(); + } } + } else if (e.key === 'ArrowDown' || e.key === 'ArrowUp') { + e.preventDefault(); + + const commands = Array.from(document.querySelectorAll('.command-palette-command')); + if (commands.length === 0) return; + + const currentSelected = document.querySelector('.command-palette-command.selected'); + let nextIndex = 0; + + if (currentSelected) { + const currentIndex = commands.indexOf(currentSelected); + currentSelected.classList.remove('selected'); + + if (e.key === 'ArrowDown') { + nextIndex = (currentIndex + 1) % commands.length; + } else { + nextIndex = (currentIndex - 1 + commands.length) % commands.length; + } + } else { + nextIndex = e.key === 'ArrowDown' ? 0 : commands.length - 1; + } + + commands[nextIndex].classList.add('selected'); + + // Ensure the selected element is visible in the scroll view + commands[nextIndex].scrollIntoView({ + behavior: 'smooth', + block: 'nearest' + }); } }); } diff --git a/assets/css/command-palette.css b/assets/css/command-palette.css index 3121ad1..83f98f3 100644 --- a/assets/css/command-palette.css +++ b/assets/css/command-palette.css @@ -70,10 +70,15 @@ flex-wrap: wrap; } -.command-palette-command:hover { +.command-palette-command:hover, .command-palette-command.selected { background-color: rgba(99, 102, 241, 0.1); } +.command-palette-command.selected { + outline: 2px solid rgba(99, 102, 241, 0.3); + position: relative; +} + .command-palette-icon { flex: 0 0 24px; margin-right: 12px; @@ -322,4 +327,50 @@ border-color: #555; box-shadow: 0 1px 0px rgba(255, 255, 255, 0.1); } +} + +/* Command palette footer */ +.command-palette-footer { + padding: 8px 16px; + border-top: 1px solid #eee; + display: flex; + justify-content: center; + font-size: 12px; + color: #777; + flex-wrap: wrap; + gap: 16px; +} + +.command-palette-footer-item { + display: flex; + align-items: center; + gap: 4px; +} + +.command-palette-footer kbd { + display: inline-flex; + align-items: center; + justify-content: center; + padding: 2px 6px; + border-radius: 4px; + background-color: #f1f1f1; + border: 1px solid #ddd; + font-family: monospace; + font-size: 11px; + box-shadow: 0 1px 1px rgba(0, 0, 0, 0.1); + min-width: 16px; +} + +/* Dark mode footer styling */ +@media (prefers-color-scheme: dark) { + .command-palette-footer { + border-top-color: #444; + color: #aaa; + } + + .command-palette-footer kbd { + background-color: #444; + border-color: #555; + color: #ddd; + } } \ No newline at end of file From 5681872406bcab287d5c06509723e67ffd26b707 Mon Sep 17 00:00:00 2001 From: Vatsal Sanjay Date: Sun, 2 Mar 2025 07:26:27 +0100 Subject: [PATCH 09/19] feat: Optimize search functionality The changes made in this commit focus on optimizing the search functionality of the website. The main changes are: - Removed the `search.js` script and replaced it with a new `command-data.js` script. This new script likely contains the data and logic for the search functionality. - Removed the `hotkeys-js` library, which was previously used for handling keyboard shortcuts. This library is no longer needed, as the search functionality has been updated. These changes aim to improve the overall search experience for users by optimizing the underlying data and logic. The removal of the `hotkeys-js` library also helps to reduce the overall file size and improve the website's performance. --- README.md | 28 +- _layouts/default.html | 119 ++++---- _layouts/research.html | 10 +- _layouts/teaching.html | 10 +- _layouts/team.html | 10 +- assets/css/command-palette.css | 85 ++++-- assets/css/search-modal.css | 46 --- assets/css/search.css | 217 -------------- assets/js/command-data.js | 8 +- assets/js/command-palette-setup.js | 273 ------------------ assets/js/search-data.js | 111 ------- assets/js/search-setup.js | 33 --- assets/js/search.js | 272 ----------------- .../js/search/hotkeys-js/hotkeys.esm.min.js | 16 - assets/js/search/ninja-keys.min.js | 35 --- assets/js/search/search-ninja.js | 89 ------ 16 files changed, 142 insertions(+), 1220 deletions(-) delete mode 100644 assets/css/search-modal.css delete mode 100644 assets/css/search.css delete mode 100644 assets/js/command-palette-setup.js delete mode 100644 assets/js/search-data.js delete mode 100644 assets/js/search-setup.js delete mode 100644 assets/js/search.js delete mode 100644 assets/js/search/hotkeys-js/hotkeys.esm.min.js delete mode 100644 assets/js/search/ninja-keys.min.js delete mode 100644 assets/js/search/search-ninja.js diff --git a/README.md b/README.md index 40e2816..081b091 100644 --- a/README.md +++ b/README.md @@ -32,18 +32,12 @@ A static website for the Computational Multiphase Physics Laboratory, built with │ │ ├── research.css # Research page styles │ │ ├── teaching.css # Teaching page styles │ │ ├── team.css # Team page styles -│ │ ├── search.css # Search functionality styles -│ │ └── search-modal.css # Cmd+K search modal styles +│ │ └── command-palette.css # Command palette styles (⌘/) │ ├── js # JavaScript files │ │ ├── main.js # Main JavaScript -│ │ ├── search.js # Search functionality -│ │ ├── search-data.js # Cmd+K search data converter -│ │ ├── search-setup.js # Cmd+K search initialization +│ │ ├── command-data.js # Command palette data and functionality │ │ ├── shortcut-key.js # Platform detection for shortcuts -│ │ ├── search_db.json # Generated search database -│ │ └── search # Search libraries -│ │ ├── ninja-keys.min.js # NinjaKeys command palette -│ │ └── hotkeys-js # Keyboard shortcut library +│ │ └── search_db.json # Generated search database (used by command palette) │ ├── favicon # Favicon files │ └── img # Image assets │ └── teaching # Teaching images @@ -257,6 +251,7 @@ The website includes a command palette feature that provides quick access to act - **Context-Aware Commands**: Additional commands appear based on current page - **Recent Commands**: Track and display recently used commands - **Help**: View all available keyboard shortcuts with the "?" command +- **Keyboard Navigation**: Use arrow keys to navigate through commands, Enter to select, and Esc to close Key features: - Different visual styling from search to avoid confusion (indigo accent color vs blue for search) @@ -264,24 +259,25 @@ Key features: - Shortcuts for common tasks (g h = go home, g r = go to research, etc.) - Comprehensive shortcut help accessible through the "?" command - Command history that remembers your frequently used commands +- Full keyboard navigation with arrow keys, Enter, and Escape +- Integrated search functionality that searches the site content The command palette is built with: -- NinjaKeys library for the palette UI (/assets/js/search/ninja-keys.min.js) -- HotKeys.js for keyboard shortcut handling (/assets/js/search/hotkeys-js/hotkeys.esm.min.js) -- Custom styling and functionality specific to the website +- Custom vanilla JavaScript implementation +- Responsive and accessible design +- Integration with the site search database for content discovery +- Complete keyboard navigation support Files: -- `/assets/js/command-data.js`: Defines all available commands -- `/assets/js/command-palette-setup.js`: Initializes and manages the command palette +- `/assets/js/command-data.js`: Defines all available commands and search database integration - `/assets/css/command-palette.css`: Styling for the command palette -- `/assets/js/shortcut-key.js`: Detects Mac vs Windows for shortcut display Search behavior and features: - Minimum query length: 2 characters - Keyboard shortcut (⌘K / ctrl+K) opens a command palette style search interface on all pages - Magnifying glass icon in navigation opens the search interface when clicked - Search input in navigation shows the full "⌘K (search)" text by default -- NinjaKeys integration provides a modern command palette experience +- Custom command palette implementation provides a modern command palette experience - Search results appear instantly as you type - Results are ranked by relevance and match percentage diff --git a/_layouts/default.html b/_layouts/default.html index ab96a44..28ed599 100644 --- a/_layouts/default.html +++ b/_layouts/default.html @@ -54,8 +54,6 @@ - - @@ -76,7 +74,7 @@ } }; - // Ensure search database is preloaded for faster searching + // Ensure search database is preloaded for command palette search functionality window.addEventListener('DOMContentLoaded', function() { // Try to prefetch the search database if it exists fetch('/assets/js/search_db.json').then(response => { @@ -85,10 +83,10 @@ } throw new Error('Search database not found'); }).then(data => { - console.log('Search database prefetched successfully'); + console.log('Search database prefetched for command palette'); window.searchData = data; }).catch(err => { - console.warn('Could not prefetch search database:', err.message); + console.warn('Could not prefetch search database for command palette:', err.message); }); }); @@ -97,7 +95,7 @@ - + - - - - - - - - - + diff --git a/_layouts/research.html b/_layouts/research.html index bbf5035..f051651 100644 --- a/_layouts/research.html +++ b/_layouts/research.html @@ -473,13 +473,7 @@

    Contents

    }); - - - - - - - - + + \ No newline at end of file diff --git a/_layouts/teaching.html b/_layouts/teaching.html index 4d4adb8..076fc02 100644 --- a/_layouts/teaching.html +++ b/_layouts/teaching.html @@ -242,13 +242,7 @@ }); - - - - - - - - + + \ No newline at end of file diff --git a/_layouts/team.html b/_layouts/team.html index dea0b63..a602489 100644 --- a/_layouts/team.html +++ b/_layouts/team.html @@ -357,13 +357,7 @@

    Team, collaborators, and Conference visits

    }); - - - - - - - - + + \ No newline at end of file diff --git a/assets/css/command-palette.css b/assets/css/command-palette.css index 83f98f3..8343aab 100644 --- a/assets/css/command-palette.css +++ b/assets/css/command-palette.css @@ -135,35 +135,55 @@ z-index: 10; /* Ensure the button is clickable */ } -.command-palette-button .btn { - padding: 0.4rem 0.6rem; - font-size: 0.9rem; - border-radius: 0.25rem; - cursor: pointer; - display: inline-flex; +.command-wrapper { + display: flex; align-items: center; - gap: 0.3rem; - background-color: rgba(255, 255, 255, 0.1); - border: 1px solid #6366f1; - color: #6366f1; - transition: all 0.2s ease-in-out; position: relative; - z-index: 20; /* Ensure clickability */ } -.command-palette-button .btn:hover { - background-color: rgba(99, 102, 241, 0.1); - transform: translateY(-1px); - box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); +.command-k-style-btn { + display: flex; + align-items: center; + justify-content: center; + background-color: #f5f5f5; + border: 1px solid #e0e0e0; + border-radius: 6px; + padding: 6px 12px; + font-size: 0.9rem; + color: #333; + cursor: pointer; + transition: all 0.2s ease; + white-space: nowrap; + gap: 6px; +} + +.command-k-style-btn:hover { + background-color: #e9e9e9; + border-color: #d0d0d0; +} + +.command-k-style-btn:focus, +.command-k-style-btn.focused { + outline: 2px solid #6366f1; + outline-offset: 1px; + border-color: #6366f1; +} + +.command-k-style-btn .search-icon { + margin-left: 4px; + font-size: 0.9em; + color: #666; } -.command-palette-button .btn:active { - transform: translateY(0); - box-shadow: none; +.command-k-style-btn .default-theme-text, +.command-k-style-btn .mac-theme-text { + font-weight: medium; + color: #555; } -.command-palette-button .btn .fa-terminal { - font-size: 1.1em; +/* Hide previous button styling */ +.command-palette-button .btn { + display: none; } /* Hide one of the shortcut texts depending on platform */ @@ -178,7 +198,7 @@ width: 100%; } - .command-palette-button .btn { + .command-k-style-btn { width: 100%; justify-content: center; padding: 0.6rem; @@ -235,6 +255,27 @@ .command-palette-no-results { color: #999; } + + /* Dark mode for the command-k-style button */ + .command-k-style-btn { + background-color: #333; + border-color: #444; + color: #eee; + } + + .command-k-style-btn:hover { + background-color: #444; + border-color: #555; + } + + .command-k-style-btn .search-icon { + color: #aaa; + } + + .command-k-style-btn .default-theme-text, + .command-k-style-btn .mac-theme-text { + color: #ccc; + } } /* Custom styles for the shortcut help modal */ diff --git a/assets/css/search-modal.css b/assets/css/search-modal.css deleted file mode 100644 index e15619b..0000000 --- a/assets/css/search-modal.css +++ /dev/null @@ -1,46 +0,0 @@ -/* Ninja Keys styling */ -ninja-keys { - --ninja-width: 640px; - --ninja-backdrop-filter: blur(16px); - --ninja-overflow: hidden; - --ninja-z-index: 1000; - --ninja-modal-shadow: 0 0 10px rgba(0, 0, 0, 0.2); - --ninja-modal-background: rgba(255, 255, 255, 0.8); - --ninja-accent-color: #5b79a8; /* Match your site theme color */ - --ninja-selected-background: rgba(91, 121, 168, 0.1); - --ninja-font-family: inherit; -} - -/* Dark mode styling if needed */ -@media (prefers-color-scheme: dark) { - ninja-keys { - --ninja-modal-background: rgba(30, 30, 30, 0.8); - --ninja-accent-color: #7a9bc9; - --ninja-selected-background: rgba(122, 155, 201, 0.1); - --ninja-text-color: #e1e1e1; - --ninja-secondary-text-color: #a0a0a0; - } -} - -/* Search button styling */ -.search-button .btn { - padding: 0.25rem 0.5rem; - font-size: 0.875rem; - border-radius: 0.2rem; - cursor: pointer; - display: inline-flex; - align-items: center; - gap: 0.5rem; - background-color: transparent; - border: 1px solid #5b79a8; - color: #5b79a8; -} - -.search-button .btn:hover { - background-color: rgba(91, 121, 168, 0.1); -} - -/* Hide one of the shortcut texts depending on platform */ -.mac-theme-text, .default-theme-text { - display: none; -} \ No newline at end of file diff --git a/assets/css/search.css b/assets/css/search.css deleted file mode 100644 index 1d2bbd7..0000000 --- a/assets/css/search.css +++ /dev/null @@ -1,217 +0,0 @@ -/* Search Styles */ -.search-container { - position: relative; - margin-left: 0.1rem; -} - -.search-wrapper { - position: relative; - display: flex; - align-items: center; -} - -#searchInput { - width: 75px; - padding: 0.8rem 1.6rem 0.8rem 0.8rem; - border: none; - border-radius: 5px; - background: rgba(255, 255, 255, 0.9); - font-size: 1.4rem; - transition: all 0.3s ease; - box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); -} - -#searchInput:focus { - width: 150px; - outline: none; - background: rgba(255, 255, 255, 1); - box-shadow: 0 4px 15px rgba(0, 0, 0, 0.15); -} - -.search-icon { - position: absolute; - color: #666; - font-size: 1rem; - margin-left: 0; -} - -.search-button { - background: none; - border: none; - padding: 0; - cursor: pointer; - display: flex; - align-items: center; - justify-content: center; - transition: color 0.2s ease; - width: 24px; - height: 24px; - border-radius: 50%; - position: absolute; - right: 0.4rem; - top: 50%; - transform: translateY(-50%); - background-color: #f2f2f2; -} - -.search-button:hover .search-icon { - color: #333; -} - -.search-button:focus { - outline: none; -} - -.search-button:focus-visible { - outline: 2px solid #007bff; - border-radius: 4px; -} - -#searchResults { - display: none; - position: absolute; - top: 100%; - right: 0; - width: 350px; - max-height: 500px; - overflow-y: auto; - background: white; - border-radius: 5px; - box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15); - z-index: 1000; - margin-top: 0.5rem; - padding: 1rem; -} - -#searchResults:not(:empty) { - display: block; -} - -.search-result { - margin-bottom: 1.5rem; - padding: 1rem; - border-radius: 8px; - background: var(--bg-color); - box-shadow: 0 2px 4px rgba(0,0,0,0.1); -} - -.search-result h3 { - margin: 0 0 0.5rem 0; - font-size: 1.2rem; -} - -.search-result h3 a { - color: var(--primary-color); - text-decoration: none; -} - -.search-result h3 a:hover { - text-decoration: underline; -} - -.result-content { - margin: 0.5rem 0; - font-size: 0.95rem; - line-height: 1.5; -} - -.result-content a { - color: var(--primary-color); - text-decoration: none; -} - -.result-content a:hover { - text-decoration: underline; -} - -.result-content img { - max-width: 100px; - height: auto; - border-radius: 50%; - margin: 0.5rem 0; -} - -.result-type { - display: inline-block; - padding: 0.25rem 0.5rem; - border-radius: 4px; - background: var(--primary-color); - color: white; - font-size: 0.8rem; - margin: 0.5rem 0; -} - -.result-tags { - margin-top: 0.5rem; -} - -.result-tags .tag { - display: inline-block; - padding: 0.2rem 0.5rem; - margin: 0.2rem; - border-radius: 4px; - background: var(--secondary-color); - color: white; - font-size: 0.8rem; -} - -.match-score { - font-size: 0.8rem; - color: var(--text-muted); - margin-top: 0.5rem; -} - -/* Team member specific styles */ -.search-result.team_member { - background: var(--bg-color-alt); - border-left: 4px solid var(--primary-color); -} - -.search-result.team_member .result-content i { - font-size: 1.5em; - margin-right: 0.5rem; - vertical-align: middle; -} - -.search-result.team_member img { - float: left; - margin-right: 1rem; - margin-bottom: 0.5rem; -} - -/* Clear float after team member content */ -.search-result.team_member::after { - content: ""; - display: table; - clear: both; -} - -.search-no-results { - padding: 2rem; - text-align: center; - color: #666; - font-size: 1.4rem; -} - -/* Mobile Responsive Search */ -@media screen and (max-width: 768px) { - .search-container { - width: 100%; - margin: 1rem 0; - } - - #searchInput { - width: 100%; - } - - #searchInput:focus { - width: 100%; - } - - #searchResults { - width: 100%; - max-height: 400px; - left: 0; - right: 0; - } -} diff --git a/assets/js/command-data.js b/assets/js/command-data.js index 20fef1f..7dd6e65 100644 --- a/assets/js/command-data.js +++ b/assets/js/command-data.js @@ -113,8 +113,12 @@ const searchInput = document.getElementById('searchInput'); if (searchInput) { searchInput.focus(); - const searchModal = document.querySelector('ninja-keys#search-modal'); - if (searchModal) searchModal.open(); + const searchModal = document.querySelector('.simple-command-palette'); + if (searchModal) { + searchModal.classList.add('visible'); + const searchModalInput = searchModal.querySelector('input'); + if (searchModalInput) searchModalInput.focus(); + } } }, section: "Tools", diff --git a/assets/js/command-palette-setup.js b/assets/js/command-palette-setup.js deleted file mode 100644 index bc67ce8..0000000 --- a/assets/js/command-palette-setup.js +++ /dev/null @@ -1,273 +0,0 @@ -// Initialize the command palette -function initCommandPalette() { - const ninjaKeys = document.querySelector('ninja-keys#command-palette'); - if (!ninjaKeys) { - console.error('Command palette element not found!'); - return; - } - - console.log('Command palette initialized'); - - // Set initial data - if (window.commandData) { - try { - ninjaKeys.data = window.commandData; - console.log('Command data loaded'); - } catch (e) { - console.error('Error setting command data:', e); - } - } else { - console.error('Command data not available'); - } - - // Add theme change listener if you support dark/light modes - const htmlEl = document.querySelector('html'); - if (htmlEl) { - const observer = new MutationObserver(() => { - const darkMode = htmlEl.dataset.theme === 'dark'; - ninjaKeys.setAttribute('theme', darkMode ? 'dark' : 'light'); - }); - observer.observe(htmlEl, { attributes: true }); - - // Set initial theme - const darkMode = htmlEl.dataset.theme === 'dark' || - (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches); - ninjaKeys.setAttribute('theme', darkMode ? 'dark' : 'light'); - } - - // Add context-aware commands based on current page - addContextCommands(); - - // Mark as initialized in case it's not already - if (!window.ninjaKeysInitialized) { - window.ninjaKeysInitialized = true; - ninjaKeys.classList.add('ninja-initialized'); - } -} - -// Function to open the command palette -function openCommandPalette() { - console.log('Opening command palette'); - - // If on mobile and navbar is expanded, collapse it - const navbarToggler = document.querySelector('.s-header__menu-toggle'); - const navbarCollapse = document.querySelector('.s-header__nav'); - if (navbarToggler && navbarCollapse && navbarCollapse.classList.contains('s-header__nav--is-visible')) { - navbarToggler.click(); - } - - // Use the global function if available - if (typeof window.openNinjaKeys === 'function') { - window.openNinjaKeys(); - return; - } - - // Fallback to direct method - const ninjaKeys = document.querySelector('ninja-keys#command-palette'); - if (ninjaKeys) { - if (typeof ninjaKeys.open === 'function') { - console.log('Opening ninja-keys modal'); - try { - ninjaKeys.open(); - } catch (e) { - console.error('Error opening ninja-keys:', e); - } - } else { - console.error('ninja-keys open method is not available'); - // Try to make it work anyway by dispatching a custom event - try { - ninjaKeys.dispatchEvent(new CustomEvent('hotkeys', { detail: { key: '/' } })); - } catch (e) { - console.error('Failed to dispatch hotkeys event:', e); - } - } - } else { - console.error('Command palette element not found when trying to open'); - } -} - -// Add page-specific commands based on the current URL -function addContextCommands() { - // Get the current path - const currentPath = window.location.pathname; - let contextCommands = []; - - // Research page specific commands - if (currentPath.includes('/research')) { - contextCommands = [ - { - id: "filter-research", - title: "Filter Research by Tag", - handler: () => { - // Focus on the filter input if it exists - const filterInput = document.querySelector('.research-filter-input'); - if (filterInput) filterInput.focus(); - }, - section: "Page Actions", - shortcuts: ["f t"], - icon: '' - } - ]; - } - // Team page specific commands - else if (currentPath.includes('/team')) { - contextCommands = [ - { - id: "email-team", - title: "Contact Team", - handler: () => { window.location.href = '/join'; }, - section: "Page Actions", - shortcuts: ["c t"], - icon: '' - } - ]; - } - // Teaching page specific commands - else if (currentPath.includes('/teaching')) { - contextCommands = [ - { - id: "sort-courses", - title: "Sort Courses by Date", - handler: () => { - // Trigger sorting function if it exists - if (typeof sortCoursesByDate === 'function') { - sortCoursesByDate(); - } - }, - section: "Page Actions", - shortcuts: ["s d"], - icon: '' - } - ]; - } - - // Add context commands to ninja-keys if there are any - if (contextCommands.length > 0) { - const ninjaKeys = document.querySelector('ninja-keys#command-palette'); - if (ninjaKeys && window.commandData) { - // Combine context commands with global commands - ninjaKeys.data = [...contextCommands, ...window.commandData]; - } - } -} - -// Recent commands tracking -let recentCommands = []; - -function trackCommandUsage(commandId) { - // Add to recent commands - recentCommands.unshift(commandId); - // Keep only the last 5 commands - recentCommands = recentCommands.slice(0, 5); - // Save to localStorage - localStorage.setItem('recentCommands', JSON.stringify(recentCommands)); - - // Update the command palette data to show recent commands - updateCommandPaletteWithRecent(); -} - -function updateCommandPaletteWithRecent() { - const savedRecent = localStorage.getItem('recentCommands'); - if (savedRecent && window.commandData) { - recentCommands = JSON.parse(savedRecent); - - // Find the existing commands - const recentCommandObjects = recentCommands.map(id => { - return window.commandData.find(cmd => cmd.id === id); - }).filter(Boolean); - - // Only add the section if we have recent commands - if (recentCommandObjects.length > 0) { - // Create a "Recent" section - const recentSection = { - id: "recent-section", - title: "Recent Commands", - section: "Recent", - children: recentCommandObjects.map(cmd => cmd.id) - }; - - // Add to ninja-keys - const ninjaKeys = document.querySelector('ninja-keys#command-palette'); - if (ninjaKeys) { - // Add recent commands at the top - const updatedData = [ - ...recentCommandObjects.map(cmd => ({...cmd, section: "Recent"})), - ...window.commandData - ]; - ninjaKeys.data = updatedData; - } - } - } -} - -// Add direct event listeners for the keyboard shortcut and button -function setupCommandPaletteEvents() { - console.log('Setting up command palette events'); - - // Use a try-catch block to prevent errors from breaking the page - try { - // Register keyboard shortcut without relying on hotkeys library - document.addEventListener('keydown', function(event) { - // For Mac: Command + / - // For Windows/Linux: Ctrl + / - if ((event.metaKey || event.ctrlKey) && event.key === '/') { - event.preventDefault(); - openCommandPalette(); - } - }); - - // Add click event to the command palette button - const commandPaletteBtn = document.getElementById('command-palette-btn'); - if (commandPaletteBtn) { - console.log('Command palette button found'); - commandPaletteBtn.addEventListener('click', function(e) { - e.preventDefault(); - openCommandPalette(); - }); - } else { - console.error('Command palette button not found'); - } - } catch (e) { - console.error('Error setting up command palette events:', e); - } -} - -// Initialize on page load -document.addEventListener('DOMContentLoaded', function() { - console.log('DOM content loaded - initializing command palette'); - - // Wait a bit to ensure custom elements are defined - setTimeout(function() { - // Initialize the command palette - initCommandPalette(); - - // Load recent commands if available - updateCommandPaletteWithRecent(); - - // Setup events - setupCommandPaletteEvents(); - - // Also use the hotkeys library if available - if (typeof hotkeys !== 'undefined') { - console.log('Hotkeys library found, setting up shortcuts'); - hotkeys('ctrl+/,command+/', function(event, handler) { - event.preventDefault(); - openCommandPalette(); - }); - } else { - console.warn('Hotkeys library not found, using fallback keyboard event listener'); - } - - // Define the open method on the ninja-keys element if it doesn't exist - const ninjaKeys = document.querySelector('ninja-keys#command-palette'); - if (ninjaKeys && typeof ninjaKeys.open !== 'function') { - console.warn('Defining open method on ninja-keys element'); - // This is a fallback solution in case the component doesn't load properly - ninjaKeys.open = function() { - this.setAttribute('open', ''); - this.style.display = 'block'; - this.classList.add('ninja-visible'); - }; - } - }, 500); -}); \ No newline at end of file diff --git a/assets/js/search-data.js b/assets/js/search-data.js deleted file mode 100644 index b6e4563..0000000 --- a/assets/js/search-data.js +++ /dev/null @@ -1,111 +0,0 @@ -// search-data.js - Convert existing search_db.json to NinjaKeys format -document.addEventListener('DOMContentLoaded', function() { - // Initialize empty search data array - window.searchData = []; - - // Add navigation items - window.searchData.push( - { - id: "home", - title: "Home", - handler: () => { window.location.href = '/'; }, - section: "Navigation", - shortcuts: ["h"], - icon: '' - }, - { - id: "team", - title: "Team", - handler: () => { window.location.href = '/team/'; }, - section: "Navigation", - shortcuts: ["t"], - icon: '' - }, - { - id: "research", - title: "Research", - handler: () => { window.location.href = '/research/'; }, - section: "Navigation", - shortcuts: ["r"], - icon: '' - }, - { - id: "teaching", - title: "Teaching", - handler: () => { window.location.href = '/teaching/'; }, - section: "Navigation", - shortcuts: ["e"], - icon: '' - }, - { - id: "join", - title: "Join Us", - handler: () => { window.location.href = '/join/'; }, - section: "Navigation", - icon: '' - }, - { - id: "contact", - title: "Contact", - handler: () => { window.location.href = '/contact/'; }, - section: "Navigation", - icon: '' - } - ); - - // Get the base URL from meta tag if it exists - const baseUrl = document.querySelector('meta[name="base-url"]')?.content || ''; - - // Load existing search database to add content items - fetch(`${baseUrl}/assets/js/search_db.json`) - .then(response => response.json()) - .then(data => { - // Process each search item and convert to NinjaKeys format - data.forEach(item => { - // Create appropriate section based on item type - let section = "Content"; - let icon = ''; - - if (item.type === 'team_member') { - section = "Team Members"; - icon = ''; - } else if (item.type === 'paper') { - section = "Research Papers"; - icon = ''; - } else if (item.type === 'markdown_section' || item.type === 'markdown_text') { - section = "Pages"; - icon = ''; - } - - // Create a subtitle from content (truncate if needed) - let subtitle = ''; - if (item.content) { - // Strip markdown and HTML - const tempDiv = document.createElement('div'); - tempDiv.innerHTML = item.content.replace(/\*\*|__|\*|_|`|\[.*?\]\(.*?\)/g, ''); - subtitle = tempDiv.textContent.substring(0, 60).trim(); - if (item.content.length > 60) subtitle += '...'; - } - - // Add to search data - window.searchData.push({ - id: item.url.replace(/[^\w-]/g, '-'), - title: item.title, - subtitle: subtitle, - handler: () => { window.location.href = item.url; }, - section: section, - keywords: item.tags ? item.tags.join(', ') : '', - icon: icon - }); - }); - - // Update the ninja-keys component with the data - const ninjaKeys = document.querySelector('ninja-keys'); - if (ninjaKeys) { - ninjaKeys.data = window.searchData; - } - }) - .catch(error => { - console.error('Error loading search database:', error); - }); -}); \ No newline at end of file diff --git a/assets/js/search-setup.js b/assets/js/search-setup.js deleted file mode 100644 index 2f0c475..0000000 --- a/assets/js/search-setup.js +++ /dev/null @@ -1,33 +0,0 @@ -// Initialize the search modal -function initSearch() { - const ninjaKeys = document.querySelector('ninja-keys'); - if (!ninjaKeys) return; - - // Data is loaded in search-data.js - - // Add theme change listener if you support dark/light modes - const htmlEl = document.querySelector('html'); - if (htmlEl) { - const observer = new MutationObserver(() => { - const darkMode = htmlEl.dataset.theme === 'dark'; - ninjaKeys.setAttribute('theme', darkMode ? 'dark' : 'light'); - }); - observer.observe(htmlEl, { attributes: true }); - - // Set initial theme based on current site theme or time of day - const prefersDark = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches; - ninjaKeys.setAttribute('theme', prefersDark ? 'dark' : 'light'); - } -} - -// Function to open the search modal -function openSearchModal() { - // Open the ninja-keys modal - const ninjaKeys = document.querySelector('ninja-keys'); - if (ninjaKeys) { - ninjaKeys.open(); - } -} - -// Initialize on page load -document.addEventListener('DOMContentLoaded', initSearch); \ No newline at end of file diff --git a/assets/js/search.js b/assets/js/search.js deleted file mode 100644 index e79fc75..0000000 --- a/assets/js/search.js +++ /dev/null @@ -1,272 +0,0 @@ -// search.js -document.addEventListener('DOMContentLoaded', () => { - const searchInput = document.getElementById('searchInput'); - const searchResults = document.getElementById('searchResults'); - let searchDatabase = null; - - // Get the base URL from meta tag if it exists - const baseUrl = document.querySelector('meta[name="base-url"]')?.content || ''; - - // Load the search database - fetch(`${baseUrl}/assets/js/search_db.json`) - .then(response => response.json()) - .then(data => { - searchDatabase = data; - console.log(`Loaded search database with ${data.length} entries`); - }) - .catch(error => { - console.error('Error loading search database:', error); - }); - - // Calculate match percentage - function calculateMatchPercentage(text, query) { - if (!text) return 0; - text = text.toLowerCase(); - query = query.toLowerCase(); - - // Split into words and remove empty strings - const queryWords = query.split(/\s+/).filter(w => w.length > 0); - const textWords = text.split(/\s+/).filter(w => w.length > 0); - - // Exact phrase match gets highest score - if (text.includes(query)) { - return 100; - } - - // Calculate word matches with strict word boundaries - let matchedWords = 0; - let titleBonus = 0; - let positionPenalty = 0; - - queryWords.forEach((qWord, index) => { - // Check for word boundaries to avoid partial matches - const wordBoundaryRegex = new RegExp(`\\b${qWord}\\b`, 'i'); - const matchFound = textWords.some((tWord, tIndex) => { - if (wordBoundaryRegex.test(tWord)) { - // Give bonus for matches near the start - if (tIndex < 3) { - titleBonus += 15; - } - // Add position-based penalty - positionPenalty += tIndex; - return true; - } - return false; - }); - - if (matchFound) { - matchedWords++; - } - }); - - // Calculate base score - const wordMatch = (matchedWords / queryWords.length) * 100; - - // Apply position penalty (reduces score for matches that appear later in text) - const positionFactor = Math.max(0, 1 - (positionPenalty / (textWords.length * 2))); - - // Combine scores with weights - let finalScore = (wordMatch * 0.6) + (titleBonus * 0.4); - finalScore *= positionFactor; - - // Require at least half of query words to match for any score - if (matchedWords < queryWords.length / 2) { - return 0; - } - - return Math.min(100, Math.round(finalScore)); - } - - // Search function with improved relevance scoring - function searchContent(query) { - if (!searchDatabase || !query.trim()) return []; - - // Minimum query length check - if (query.trim().length < 2) return []; - - const results = searchDatabase - .map(item => { - // Calculate matches in different fields - const titleMatch = calculateMatchPercentage(item.title, query); - const contentMatch = calculateMatchPercentage(item.content, query); - const tagsMatch = item.tags ? calculateMatchPercentage(item.tags.join(' '), query) : 0; - - // Weight different match types - let matchScore = Math.max( - titleMatch * 1.5, // Title matches are more important - contentMatch * 0.8, // Content matches less important - tagsMatch * 1.2 // Tag matches somewhat important - ); - - // Apply type-specific bonuses - if (item.type === 'team_member') { - matchScore *= 1.5; // Boost team member matches - } - - // Apply priority multiplier (1.0 to 1.5) - const priorityMultiplier = item.priority ? (1.5 - (item.priority - 1) * 0.25) : 1.0; - matchScore *= priorityMultiplier; - - return { - ...item, - matchScore: Math.min(100, Math.round(matchScore)) - }; - }) - .filter(item => item.matchScore > 40) // Only show items with good matches - .sort((a, b) => { - // First sort by priority if available - const priorityDiff = (a.priority || 3) - (b.priority || 3); - if (priorityDiff !== 0) return priorityDiff; - - // Then sort by match score - return b.matchScore - a.matchScore; - }) - .slice(0, 5); // Limit to top 5 results - - return results; - } - - // Format search results - function showResults(results, query) { - if (!searchResults) return; - - if (!query.trim()) { - searchResults.innerHTML = ''; - return; - } - - // Helper function to safely render HTML content - function renderContent(content, type) { - // Create a temporary div to safely parse HTML - const div = document.createElement('div'); - - // Handle markdown-style links [text](url) - content = content.replace(/\[([^\]]+)\]\(([^)]+)\)/g, (match, text, url) => { - return `${text}`; - }); - - // Handle markdown-style bold **text** - content = content.replace(/\*\*([^*]+)\*\*/g, '$1'); - - // Handle markdown-style italic *text* - content = content.replace(/\*([^*]+)\*/g, '$1'); - - // Handle HTML tags for icons (common in team member entries) - if (type === 'team_member') { - div.innerHTML = content; - // Keep only allowed tags and attributes - const allowedTags = ['i', 'a', 'strong', 'em', 'img']; - const allowedAttributes = { - 'i': ['class', 'style'], - 'a': ['href', 'target'], - 'img': ['src', 'alt', 'width', 'height', 'loading', 'class'] - }; - - const clean = (node) => { - if (node.nodeType === 3) return; // Text node - if (!allowedTags.includes(node.tagName.toLowerCase())) { - node.replaceWith(document.createTextNode(node.textContent)); - return; - } - const attrs = Array.from(node.attributes); - attrs.forEach(attr => { - if (!allowedAttributes[node.tagName.toLowerCase()]?.includes(attr.name)) { - node.removeAttribute(attr.name); - } - }); - Array.from(node.children).forEach(clean); - }; - - Array.from(div.children).forEach(clean); - return div.innerHTML; - } - - // For other types, escape HTML but render markdown - div.textContent = content; - return div.innerHTML; - } - - const resultsList = results.map(result => { - const title = renderContent(result.title, result.type); - const content = renderContent(result.content, result.type); - - return ` -
    -

    ${title}

    -
    ${content}
    - ${result.type === 'team_member' ? 'Team Member' : ''} - ${result.tags ? `
    ${result.tags.map(tag => `${tag}`).join('')}
    ` : ''} -
    Match: ${result.matchScore}%
    -
    - `; - }).join(''); - - searchResults.innerHTML = resultsList || '

    No results found

    '; - } - - // Search input handler - if (searchInput) { - let debounceTimeout; - - // Function to perform search - function performSearch() { - const query = searchInput.value; - const results = searchContent(query); - showResults(results, query); - } - - // Input event handler with debounce - searchInput.addEventListener('input', (e) => { - clearTimeout(debounceTimeout); - debounceTimeout = setTimeout(performSearch, 300); - }); - - // Handle clicks outside search area - document.addEventListener('click', (e) => { - // Check if click is outside both search input and results - if (!searchInput.contains(e.target) && !searchResults.contains(e.target)) { - searchResults.innerHTML = ''; // Hide results but keep input value - } - }); - - // Handle result selection - searchResults.addEventListener('click', (e) => { - const resultLink = e.target.closest('a'); - if (resultLink) { - // If a result link was clicked, hide the results but keep the input value - setTimeout(() => { - searchResults.innerHTML = ''; - }, 100); // Small delay to ensure the link is followed - } - }); - - // Prevent clicks within search results from closing the dropdown - searchResults.addEventListener('click', (e) => { - e.stopPropagation(); - }); - - // Show results when focusing back on search input - searchInput.addEventListener('focus', () => { - if (searchInput.value.trim()) { - performSearch(); - } - }); - - // Add click handler for search button - const searchButton = document.getElementById('searchButton'); - if (searchButton) { - searchButton.addEventListener('click', () => { - searchInput.focus(); - performSearch(); - }); - } - - // Add keyboard handler for search input - searchInput.addEventListener('keypress', (e) => { - if (e.key === 'Enter') { - clearTimeout(debounceTimeout); - performSearch(); - } - }); - } -}); \ No newline at end of file diff --git a/assets/js/search/hotkeys-js/hotkeys.esm.min.js b/assets/js/search/hotkeys-js/hotkeys.esm.min.js deleted file mode 100644 index 86a9ffe..0000000 --- a/assets/js/search/hotkeys-js/hotkeys.esm.min.js +++ /dev/null @@ -1,16 +0,0 @@ -/** - * Minified by jsDelivr using Terser v5.15.1. - * Original file: /npm/hotkeys-js@3.10.1/dist/hotkeys.esm.js - * - * Do NOT use SRI with dynamically generated files! More information: https://www.jsdelivr.com/using-sri-with-dynamic-files - */ -/**! - * hotkeys-js v3.10.1 - * A simple micro-library for defining and dispatching keyboard shortcuts. It has no dependencies. - * - * Copyright (c) 2022 kenny wong - * http://jaywcjlove.github.io/hotkeys - * Licensed under the MIT license - */ -var isff="undefined"!=typeof navigator&&navigator.userAgent.toLowerCase().indexOf("firefox")>0;function addEvent(e,n,t,o){e.addEventListener?e.addEventListener(n,t,o):e.attachEvent&&e.attachEvent("on".concat(n),(function(){t(window.event)}))}function getMods(e,n){for(var t=n.slice(0,n.length-1),o=0;o=0;)n[t-1]+=",",n.splice(t,1),t=n.lastIndexOf("");return n}function compareArray(e,n){for(var t=e.length>=n.length?e:n,o=e.length>=n.length?n:e,r=!0,i=0;i=0&&_downKeys.splice(t,1),e.key&&"meta"===e.key.toLowerCase()&&_downKeys.splice(0,_downKeys.length),93!==n&&224!==n||(n=91),n in _mods)for(var o in _mods[n]=!1,_modifier)_modifier[o]===n&&(hotkeys[o]=!1)}function unbind(e){if(void 0===e)Object.keys(_handlers).forEach((function(e){return delete _handlers[e]}));else if(Array.isArray(e))e.forEach((function(e){e.key&&eachUnbind(e)}));else if("object"==typeof e)e.key&&eachUnbind(e);else if("string"==typeof e){for(var n=arguments.length,t=new Array(n>1?n-1:0),o=1;o1?getMods(_modifier,n):[];_handlers[d]=_handlers[d].filter((function(e){return!((!o||e.method===o)&&e.scope===t&&compareArray(e.mods,a))}))}}))};function eventHandler(e,n,t,o){var r;if(n.element===o&&(n.scope===t||"all"===n.scope)){for(var i in r=n.mods.length>0,_mods)Object.prototype.hasOwnProperty.call(_mods,i)&&(!_mods[i]&&n.mods.indexOf(+i)>-1||_mods[i]&&-1===n.mods.indexOf(+i))&&(r=!1);(0!==n.mods.length||_mods[16]||_mods[18]||_mods[17]||_mods[91])&&!r&&"*"!==n.shortcut||!1===n.method(e,n)&&(e.preventDefault?e.preventDefault():e.returnValue=!1,e.stopPropagation&&e.stopPropagation(),e.cancelBubble&&(e.cancelBubble=!0))}}function dispatch(e,n){var t=_handlers["*"],o=e.keyCode||e.which||e.charCode;if(hotkeys.filter.call(this,e)){if(93!==o&&224!==o||(o=91),-1===_downKeys.indexOf(o)&&229!==o&&_downKeys.push(o),["ctrlKey","altKey","shiftKey","metaKey"].forEach((function(n){var t=modifierMap[n];e[n]&&-1===_downKeys.indexOf(t)?_downKeys.push(t):!e[n]&&_downKeys.indexOf(t)>-1?_downKeys.splice(_downKeys.indexOf(t),1):"metaKey"===n&&e[n]&&3===_downKeys.length&&(e.ctrlKey||e.shiftKey||e.altKey||(_downKeys=_downKeys.slice(_downKeys.indexOf(t))))})),o in _mods){for(var r in _mods[o]=!0,_modifier)_modifier[r]===o&&(hotkeys[r]=!0);if(!t)return}for(var i in _mods)Object.prototype.hasOwnProperty.call(_mods,i)&&(_mods[i]=e[modifierMap[i]]);e.getModifierState&&(!e.altKey||e.ctrlKey)&&e.getModifierState("AltGraph")&&(-1===_downKeys.indexOf(17)&&_downKeys.push(17),-1===_downKeys.indexOf(18)&&_downKeys.push(18),_mods[17]=!0,_mods[18]=!0);var s=getScope();if(t)for(var d=0;d-1}function hotkeys(e,n,t){_downKeys=[];var o=getKeys(e),r=[],i="all",s=document,d=0,a=!1,c=!0,f="+",l=!1;for(void 0===t&&"function"==typeof n&&(t=n),"[object Object]"===Object.prototype.toString.call(n)&&(n.scope&&(i=n.scope),n.element&&(s=n.element),n.keyup&&(a=n.keyup),void 0!==n.keydown&&(c=n.keydown),void 0!==n.capture&&(l=n.capture),"string"==typeof n.splitKey&&(f=n.splitKey)),"string"==typeof n&&(i=n);d1&&(r=getMods(_modifier,e)),(e="*"===(e=e[e.length-1])?"*":code(e))in _handlers||(_handlers[e]=[]),_handlers[e].push({keyup:a,keydown:c,scope:i,mods:r,shortcut:o[d],method:t,key:o[d],splitKey:f,element:s});void 0!==s&&!isElementBind(s)&&window&&(elementHasBindEvent.push(s),addEvent(s,"keydown",(function(e){dispatch(e,s)}),l),winListendFocus||(winListendFocus=!0,addEvent(window,"focus",(function(){_downKeys=[]}),l)),addEvent(s,"keyup",(function(e){dispatch(e,s),clearModifier(e)}),l))}function trigger(e){var n=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"all";Object.keys(_handlers).forEach((function(t){_handlers[t].filter((function(t){return t.scope===n&&t.shortcut===e})).forEach((function(e){e&&e.method&&e.method()}))}))}var _api={getPressedKeyString:getPressedKeyString,setScope:setScope,getScope:getScope,deleteScope:deleteScope,getPressedKeyCodes:getPressedKeyCodes,isPressed:isPressed,filter:filter,trigger:trigger,unbind:unbind,keyMap:_keyMap,modifier:_modifier,modifierMap:modifierMap};for(var a in _api)Object.prototype.hasOwnProperty.call(_api,a)&&(hotkeys[a]=_api[a]);if("undefined"!=typeof window){var _hotkeys=window.hotkeys;hotkeys.noConflict=function(e){return e&&window.hotkeys===hotkeys&&(window.hotkeys=_hotkeys),hotkeys},window.hotkeys=hotkeys}export{hotkeys as default}; -//# sourceMappingURL=/sm/57fed5bcab1877840f34e6f82efa3e116c9761284e6a7ae04fbf43251971954e.map \ No newline at end of file diff --git a/assets/js/search/ninja-keys.min.js b/assets/js/search/ninja-keys.min.js deleted file mode 100644 index b33ce3a..0000000 --- a/assets/js/search/ninja-keys.min.js +++ /dev/null @@ -1,35 +0,0 @@ -/** - * Minified by jsDelivr using Terser v5.15.1. - * Original file: /npm/ninja-keys@1.2.2/dist/ninja-keys.js - * - * Do NOT use SRI with dynamically generated files! More information: https://www.jsdelivr.com/using-sri-with-dynamic-files - */ -var __decorate=this&&this.__decorate||function(e,t,s,i){var o,a=arguments.length,n=a<3?t:null===i?i=Object.getOwnPropertyDescriptor(t,s):i;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)n=Reflect.decorate(e,t,s,i);else for(var r=e.length-1;r>=0;r--)(o=e[r])&&(n=(a<3?o(n):a>3?o(t,s,n):o(t,s))||n);return a>3&&n&&Object.defineProperty(t,s,n),n};import{LitElement,html}from"lit";import{customElement,property,state}from"lit/decorators.js";import{repeat}from"lit/directives/repeat.js";import{live}from"lit/directives/live.js";import{createRef,ref}from"lit-html/directives/ref.js";import{classMap}from"lit/directives/class-map.js";import hotkeys from"hotkeys-js";import"./ninja-header.js";import"./ninja-action.js";import{footerHtml}from"./ninja-footer.js";import{baseStyles}from"./base-styles.js";let NinjaKeys=class extends LitElement{constructor(){super(...arguments),this.placeholder="Type a command or search...",this.disableHotkeys=!1,this.hideBreadcrumbs=!1,this.openHotkey="cmd+k,ctrl+k",this.navigationUpHotkey="up,shift+tab",this.navigationDownHotkey="down,tab",this.closeHotkey="esc",this.goBackHotkey="backspace",this.selectHotkey="enter",this.hotKeysJoinedView=!1,this.noAutoLoadMdIcons=!1,this.data=[],this.visible=!1,this._bump=!0,this._actionMatches=[],this._search="",this._flatData=[],this._headerRef=createRef()}open(e={}){this._bump=!0,this.visible=!0,this._headerRef.value.focusSearch(),this._actionMatches.length>0&&(this._selected=this._actionMatches[0]),this.setParent(e.parent)}close(){this._bump=!1,this.visible=!1}setParent(e){this._currentRoot=e||void 0,this._selected=void 0,this._search="",this._headerRef.value.setSearch("")}get breadcrumbs(){var e;const t=[];let s=null===(e=this._selected)||void 0===e?void 0:e.parent;if(s)for(t.push(s);s;){const e=this._flatData.find((e=>e.id===s));(null==e?void 0:e.parent)&&t.push(e.parent),s=e?e.parent:void 0}return t.reverse()}connectedCallback(){super.connectedCallback(),this.noAutoLoadMdIcons||document.fonts.load("24px Material Icons","apps").then((()=>{})),this._registerInternalHotkeys()}disconnectedCallback(){super.disconnectedCallback(),this._unregisterInternalHotkeys()}_flattern(e,t){let s=[];return e||(e=[]),e.map((e=>{const i=e.children&&e.children.some((e=>"string"==typeof e)),o={...e,parent:e.parent||t};return i||(o.children&&o.children.length&&(t=e.id,s=[...s,...o.children]),o.children=o.children?o.children.map((e=>e.id)):[]),o})).concat(s.length?this._flattern(s,t):s)}update(e){e.has("data")&&!this.disableHotkeys&&(this._flatData=this._flattern(this.data),this._flatData.filter((e=>!!e.hotkey)).forEach((e=>{hotkeys(e.hotkey,(t=>{t.preventDefault(),e.handler&&e.handler(e)}))}))),super.update(e)}_registerInternalHotkeys(){this.openHotkey&&hotkeys(this.openHotkey,(e=>{e.preventDefault(),this.visible?this.close():this.open()})),this.selectHotkey&&hotkeys(this.selectHotkey,(e=>{this.visible&&(e.preventDefault(),this._actionSelected(this._actionMatches[this._selectedIndex]))})),this.goBackHotkey&&hotkeys(this.goBackHotkey,(e=>{this.visible&&(this._search||(e.preventDefault(),this._goBack()))})),this.navigationDownHotkey&&hotkeys(this.navigationDownHotkey,(e=>{this.visible&&(e.preventDefault(),this._selectedIndex>=this._actionMatches.length-1?this._selected=this._actionMatches[0]:this._selected=this._actionMatches[this._selectedIndex+1])})),this.navigationUpHotkey&&hotkeys(this.navigationUpHotkey,(e=>{this.visible&&(e.preventDefault(),0===this._selectedIndex?this._selected=this._actionMatches[this._actionMatches.length-1]:this._selected=this._actionMatches[this._selectedIndex-1])})),this.closeHotkey&&hotkeys(this.closeHotkey,(()=>{this.visible&&this.close()}))}_unregisterInternalHotkeys(){this.openHotkey&&hotkeys.unbind(this.openHotkey),this.selectHotkey&&hotkeys.unbind(this.selectHotkey),this.goBackHotkey&&hotkeys.unbind(this.goBackHotkey),this.navigationDownHotkey&&hotkeys.unbind(this.navigationDownHotkey),this.navigationUpHotkey&&hotkeys.unbind(this.navigationUpHotkey),this.closeHotkey&&hotkeys.unbind(this.closeHotkey)}_actionFocused(e,t){this._selected=e,t.target.ensureInView()}_onTransitionEnd(){this._bump=!1}_goBack(){const e=this.breadcrumbs.length>1?this.breadcrumbs[this.breadcrumbs.length-2]:void 0;this.setParent(e)}render(){const e={bump:this._bump,"modal-content":!0},t={visible:this.visible,modal:!0},s=this._flatData.filter((e=>{var t;const s=new RegExp(this._search,"gi"),i=e.title.match(s)||(null===(t=e.keywords)||void 0===t?void 0:t.match(s));return(!this._currentRoot&&this._search||e.parent===this._currentRoot)&&i})).reduce(((e,t)=>e.set(t.section,[...e.get(t.section)||[],t])),new Map);this._actionMatches=[...s.values()].flat(),this._actionMatches.length>0&&-1===this._selectedIndex&&(this._selected=this._actionMatches[0]),0===this._actionMatches.length&&(this._selected=void 0);const i=e=>html` ${repeat(e,(e=>e.id),(e=>{var t;return html`this._actionFocused(e,t)} - @actionsSelected=${e=>this._actionSelected(e.detail)} - .action=${e} - >`}))}`,o=[];return s.forEach(((e,t)=>{const s=t?html`
    ${t}
    `:void 0;o.push(html`${s}${i(e)}`)})),html` -
    -
    - this.setParent(e.detail.parent)} - @close=${this.close} - > - - - ${footerHtml} -
    -
    - `}get _selectedIndex(){return this._selected?this._actionMatches.indexOf(this._selected):-1}_actionSelected(e){var t;if(this.dispatchEvent(new CustomEvent("selected",{detail:{search:this._search,action:e},bubbles:!0,composed:!0})),e){if(e.children&&(null===(t=e.children)||void 0===t?void 0:t.length)>0&&(this._currentRoot=e.id,this._search=""),this._headerRef.value.setSearch(""),this._headerRef.value.focusSearch(),e.handler){const t=e.handler(e);(null==t?void 0:t.keepOpen)||this.close()}this._bump=!0}}async _handleInput(e){this._search=e.detail.search,await this.updateComplete,this.dispatchEvent(new CustomEvent("change",{detail:{search:this._search,actions:this._actionMatches},bubbles:!0,composed:!0}))}_overlayClick(e){var t;(null===(t=e.target)||void 0===t?void 0:t.classList.contains("modal"))&&this.close()}};NinjaKeys.styles=[baseStyles],__decorate([property({type:String})],NinjaKeys.prototype,"placeholder",void 0),__decorate([property({type:Boolean})],NinjaKeys.prototype,"disableHotkeys",void 0),__decorate([property({type:Boolean})],NinjaKeys.prototype,"hideBreadcrumbs",void 0),__decorate([property()],NinjaKeys.prototype,"openHotkey",void 0),__decorate([property()],NinjaKeys.prototype,"navigationUpHotkey",void 0),__decorate([property()],NinjaKeys.prototype,"navigationDownHotkey",void 0),__decorate([property()],NinjaKeys.prototype,"closeHotkey",void 0),__decorate([property()],NinjaKeys.prototype,"goBackHotkey",void 0),__decorate([property()],NinjaKeys.prototype,"selectHotkey",void 0),__decorate([property({type:Boolean})],NinjaKeys.prototype,"hotKeysJoinedView",void 0),__decorate([property({type:Boolean})],NinjaKeys.prototype,"noAutoLoadMdIcons",void 0),__decorate([property({type:Array,hasChanged:()=>!0})],NinjaKeys.prototype,"data",void 0),__decorate([state()],NinjaKeys.prototype,"visible",void 0),__decorate([state()],NinjaKeys.prototype,"_bump",void 0),__decorate([state()],NinjaKeys.prototype,"_actionMatches",void 0),__decorate([state()],NinjaKeys.prototype,"_search",void 0),__decorate([state()],NinjaKeys.prototype,"_currentRoot",void 0),__decorate([state()],NinjaKeys.prototype,"_flatData",void 0),__decorate([state()],NinjaKeys.prototype,"breadcrumbs",null),__decorate([state()],NinjaKeys.prototype,"_selected",void 0),NinjaKeys=__decorate([customElement("ninja-keys")],NinjaKeys);export{NinjaKeys}; -//# sourceMappingURL=/sm/acda11afe47968a6322b61500a2789f4191d6f4dc73daf53212b55752de5b10c.map \ No newline at end of file diff --git a/assets/js/search/search-ninja.js b/assets/js/search/search-ninja.js deleted file mode 100644 index 1c88f7c..0000000 --- a/assets/js/search/search-ninja.js +++ /dev/null @@ -1,89 +0,0 @@ -// search-ninja.js - Integration of NinjaKeys with search functionality -document.addEventListener('DOMContentLoaded', () => { - // Create and append the ninja-keys element to the body - const ninjaKeys = document.getElementById('search-modal') || document.createElement('ninja-keys'); - ninjaKeys.id = 'search-modal'; - ninjaKeys.setAttribute('placeholder', '⌘K (search)'); - ninjaKeys.setAttribute('data-info', 'Press ctrl+k to search (⌘+k on Mac)'); - - if (!document.getElementById('search-modal')) { - document.body.appendChild(ninjaKeys); - } - - // Get the base URL from meta tag if it exists - const baseUrl = document.querySelector('meta[name="base-url"]')?.content || ''; - let searchDatabase = null; - - // Load the search database - fetch(`${baseUrl}/assets/js/search_db.json`) - .then(response => response.json()) - .then(data => { - searchDatabase = data; - console.log(`Loaded search database with ${data.length} entries for ninja search`); - - // Once we have the data, update the ninja-keys data - updateNinjaKeysData(searchDatabase); - }) - .catch(error => { - console.error('Error loading search database for ninja search:', error); - }); - - // Function to update ninja-keys data based on search database - function updateNinjaKeysData(database) { - if (!database) return; - - // Create actions for ninja-keys - const actions = database.map(item => { - return { - id: item.url, - title: item.title, - section: item.section || 'Pages', - keywords: item.content, - handler: () => { - window.location.href = item.url; - return { keepOpen: false }; - } - }; - }); - - ninjaKeys.data = actions; - } - - // Add keyboard shortcut listener for cmd+k/ctrl+k - document.addEventListener('keydown', (e) => { - // Check if cmd+k or ctrl+k is pressed - if ((e.metaKey || e.ctrlKey) && e.key === 'k') { - e.preventDefault(); // Prevent default browser behavior - - // Focus the search input if it exists - const searchInput = document.getElementById('searchInput'); - if (searchInput) { - searchInput.focus(); - - // If ninja-keys is available, open it - if (ninjaKeys) { - ninjaKeys.open(); - } - } - } - }); - - // Add click event listener to the search button - const searchButton = document.getElementById('searchButton'); - if (searchButton) { - searchButton.addEventListener('click', (e) => { - e.preventDefault(); - - // Focus the search input if it exists - const searchInput = document.getElementById('searchInput'); - if (searchInput) { - searchInput.focus(); - - // If ninja-keys is available, open it - if (ninjaKeys) { - ninjaKeys.open(); - } - } - }); - } -}); \ No newline at end of file From 9c5fbf3d2a898e4a1d82f01efb9973aff256c796 Mon Sep 17 00:00:00 2001 From: Vatsal Sanjay Date: Sun, 2 Mar 2025 08:39:16 +0100 Subject: [PATCH 10/19] feat(research): Implement modal for filtering research by tag This change introduces a modal that allows users to filter research content by tags. The modal displays all unique tags found on the page, and users can navigate through the tags using keyboard controls (arrow keys, Enter, Esc). When a tag is selected, the modal can be closed, and the research content will be filtered accordingly. --- README.md | 7 +- _layouts/research.html | 363 ++++++++++++++++++++++++++++--- _layouts/teaching.html | 447 +++++++++++++++++++++++++++++++------- _layouts/team.html | 427 ++++++++++++++++++++++++++++++++++-- assets/js/command-data.js | 165 +++++++++++++- 5 files changed, 1282 insertions(+), 127 deletions(-) diff --git a/README.md b/README.md index 081b091..6aa9dce 100644 --- a/README.md +++ b/README.md @@ -249,18 +249,17 @@ The website includes a command palette feature that provides quick access to act - **External Link Commands**: Direct access to GitHub, Google Scholar, YouTube, and Bluesky - **Tool Commands**: Search, scroll to top/bottom, and other utility functions - **Context-Aware Commands**: Additional commands appear based on current page -- **Recent Commands**: Track and display recently used commands -- **Help**: View all available keyboard shortcuts with the "?" command +- **Search Integration**: Search the site content directly from the command palette - **Keyboard Navigation**: Use arrow keys to navigate through commands, Enter to select, and Esc to close Key features: +- Custom implementation with vanilla JavaScript for better control and performance - Different visual styling from search to avoid confusion (indigo accent color vs blue for search) - Grouping of commands by section for easy discoverability - Shortcuts for common tasks (g h = go home, g r = go to research, etc.) -- Comprehensive shortcut help accessible through the "?" command -- Command history that remembers your frequently used commands - Full keyboard navigation with arrow keys, Enter, and Escape - Integrated search functionality that searches the site content +- Footer with keyboard shortcut hints for better usability The command palette is built with: - Custom vanilla JavaScript implementation diff --git a/_layouts/research.html b/_layouts/research.html index f051651..49cb5e2 100644 --- a/_layouts/research.html +++ b/_layouts/research.html @@ -79,10 +79,21 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    +
    + + + + +
    +
    + + + Menu + + + +
    + + +
    +
    +
    + {% capture lazy_img %}
    ' %} + {{ content_with_video }} +
    +
    + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/_layouts/teaching.html b/_layouts/teaching.html index 4c47915..e6cb438 100644 --- a/_layouts/teaching.html +++ b/_layouts/teaching.html @@ -207,7 +207,7 @@
    • -
    • About
    • +
    • About
    • Team
    • Research
    • Teaching
    • diff --git a/_layouts/team.html b/_layouts/team.html index 1a17e35..921c9ab 100644 --- a/_layouts/team.html +++ b/_layouts/team.html @@ -162,7 +162,7 @@
      • -
      • About
      • +
      • About
      • Team
      • Research
      • Teaching
      • @@ -431,11 +431,11 @@

        Team, collaborators, and Conference visits

        -
        +