From 99c75d12432f9ef2f3f336e758d18809a8dffa2d Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Sun, 9 Aug 2026 14:51:09 +0530 Subject: [PATCH] generate includes for custom shader demo videos --- docs/_static/custom.css | 59 +++++++++++++++++++++++++++++++++++++++++ docs/_static/custom.js | 54 +++++++++++++++++++++++++++++++++++++ docs/conf.py | 42 +++++++++++++++++++++++++++++ 3 files changed, 155 insertions(+) diff --git a/docs/_static/custom.css b/docs/_static/custom.css index b3a5f12c2..c17c911d4 100644 --- a/docs/_static/custom.css +++ b/docs/_static/custom.css @@ -34,3 +34,62 @@ details > summary { .highlight .w { text-decoration: none } + +/* Custom shader demo cards */ +.shader-demo-card { + transition: transform 0.15s ease, box-shadow 0.15s ease; +} + +.shader-demo-card:hover { + transform: translateY(-2px); +} + +/* Video modal overlay */ +.shader-modal-overlay { + display: none; + position: fixed; + inset: 0; + background: rgba(0, 0, 0, 0.85); + z-index: 10000; + align-items: center; + justify-content: center; +} + +.shader-modal-overlay.active { + display: flex; +} + +body.shader-modal-open { + overflow: hidden; +} + +.shader-modal-container { + position: relative; + max-width: 90vw; + max-height: 90vh; +} + +.shader-modal-close { + position: absolute; + top: -2rem; + right: 0; + background: transparent; + border: none; + color: #fff; + font-size: 2rem; + line-height: 1; + cursor: pointer; + padding: 0; + opacity: 0.8; +} + +.shader-modal-close:hover { + opacity: 1; +} + +.shader-modal-video { + display: block; + max-width: 90vw; + max-height: 85vh; + border-radius: 4px; +} diff --git a/docs/_static/custom.js b/docs/_static/custom.js index 87021c61c..14e68047a 100644 --- a/docs/_static/custom.js +++ b/docs/_static/custom.js @@ -60,5 +60,59 @@ function init_sidebar() { document.addEventListener("DOMContentLoaded", init_sidebar); init_sidebar(); +function init_shader_modals() { + var cards = document.querySelectorAll('.shader-demo-card'); + if (!cards.length) return; + + var overlay = document.createElement('div'); + overlay.id = 'shader-video-modal'; + overlay.className = 'shader-modal-overlay'; + overlay.innerHTML = + '
' + + '' + + '' + + '
'; + document.body.appendChild(overlay); + + var video = overlay.querySelector('.shader-modal-video'); + var close_btn = overlay.querySelector('.shader-modal-close'); + + function open_modal(video_src) { + video.innerHTML = ''; + video.load(); + video.play(); + overlay.classList.add('active'); + document.body.classList.add('shader-modal-open'); + } + + function close_modal() { + overlay.classList.remove('active'); + video.pause(); + video.innerHTML = ''; + document.body.classList.remove('shader-modal-open'); + } + + cards.forEach(function(card) { + var link = card.querySelector('a.sd-stretched-link'); + if (!link) return; + var href = link.getAttribute('href'); + if (!href || href.slice(-5) !== '.webm') return; + link.addEventListener('click', function(e) { + e.preventDefault(); + open_modal(href); + }); + }); + + close_btn.addEventListener('click', close_modal); + overlay.addEventListener('click', function(e) { + if (e.target === overlay) close_modal(); + }); + document.addEventListener('keydown', function(e) { + if (e.key === 'Escape') close_modal(); + }); +} + +document.addEventListener("DOMContentLoaded", init_shader_modals); + }()); diff --git a/docs/conf.py b/docs/conf.py index 10f800a24..5d824e1eb 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -162,6 +162,7 @@ html_theme_options: Dict[str, Any] = { # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ['_static'] +html_extra_path = ['screenshots'] html_favicon = html_logo = '../logo/kitty.svg' html_css_files = ['custom.css', 'timestamps.css'] html_js_files = ['custom.js', 'timestamps.js'] @@ -308,6 +309,46 @@ if you specify a program-to-run you can use the special placeholder # }}} +def write_custom_shader_docs() -> None: # {{{ + import kitty.shaders.custom.demo as demo_module + + category_slug_map = { + 'cursor-trail': 'cursor-trails', + 'background': 'backgrounds', + 'mouse': 'mouse', + } + + by_category: Dict[str, List[str]] = {} + for name, meta in demo_module.metadata.items(): + cat_raw = str(meta.get('category', 'other')) + cat = category_slug_map.get(cat_raw, cat_raw) + by_category.setdefault(cat, []).append(name) + + def shader_title(name: str) -> str: + return ' '.join(w.capitalize() for w in name.replace('-', ' ').split()) + + for category, shader_names in by_category.items(): + lines: List[str] = [] + lines.append('.. grid:: 1 2 2 3') + lines.append(' :gutter: 3') + lines.append('') + for shader_name in shader_names: + title = shader_title(shader_name) + video_url = f'{shader_name}.webm' + lines.append(f' .. grid-item-card:: {title}') + lines.append(f' :link: {video_url}') + lines.append(f' :link-type: url') + lines.append(f' :class-card: shader-demo-card') + lines.append('') + lines.append(f' Preview of the {shader_name} shader effect.') + lines.append('') + with open(f'generated/custom-shaders-{category}', 'w') as f: + f.write('\n'.join(lines)) + + +# }}} + + def write_color_names_table() -> None: # {{{ from kitty.fast_data_types import all_color_names @@ -785,6 +826,7 @@ def setup(app: Any) -> None: write_cli_docs(kn) write_remote_control_protocol_docs() write_color_names_table() + write_custom_shader_docs() write_conf_docs(app, kn) app.connect('source-read', replace_string) app.add_config_value('analytics_id', '', 'env')