diff --git a/examples/app_hub/assets/continents-meta.json b/examples/app_hub/assets/continents-meta.json new file mode 100644 index 0000000..1391f56 --- /dev/null +++ b/examples/app_hub/assets/continents-meta.json @@ -0,0 +1,50 @@ +{ + "source": "mercator-source.jpg", + "size": [ + 800, + 519 + ], + "components": 8, + "layers": { + "vertical_snapshot": { + "translate": [ + 0.0, + 238.0 + ], + "scale": [ + 1.2408477842003853, + 1.2408477842003853 + ] + }, + "inner": { + "translate": [ + 223.66088631984587, + 238.0 + ], + "scale": [ + 1.2408477842003853, + 1.2408477842003853 + ] + }, + "outer": { + "scale": [ + 1.0, + 1.0 + ] + }, + "pivot": [ + 720.0, + 560.0 + ] + }, + "areas_top10": [ + 78439, + 42423, + 21849, + 5520, + 5428, + 3434, + 546, + 540 + ] +} \ No newline at end of file diff --git a/examples/app_hub/assets/extract_red_polygons.py b/examples/app_hub/assets/extract_red_polygons.py index bf3029e..a75a79e 100644 --- a/examples/app_hub/assets/extract_red_polygons.py +++ b/examples/app_hub/assets/extract_red_polygons.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -"""Extract red land polygons from Mercator reference image -> SVG paths.""" +"""Extract red land polygons from Mercator reference image -> layered SVG paths.""" from __future__ import annotations @@ -14,10 +14,17 @@ from PIL import Image ROOT = Path(__file__).resolve().parent SRC = ROOT / "mercator-source.jpg" OUT_FLAT = ROOT / "hub-logo-continents-mercator-flat.svg" +OUT_GLOBE = ROOT / "hub-logo-continents-mercator-globe.svg" +OUT_GLOBE_SNAP_VERTICAL = ROOT / "hub-logo-continents-mercator-globe.snapshot-vertical.svg" OUT_META = ROOT / "continents-meta.json" -# Source image is 800x519 Mercator-style plate. -W, H = 800, 519 +# Globe framing in 1920x1080 logo scene. +GLOBE_CX = 720.0 +GLOBE_CY = 560.0 +GLOBE_R = 322.0 +# Orthographic center (visible hemisphere). +CENTER_LAT = 18.0 +CENTER_LON = 12.0 def red_mask(rgb: np.ndarray) -> np.ndarray: @@ -26,7 +33,6 @@ def red_mask(rgb: np.ndarray) -> np.ndarray: def dilate(mask: np.ndarray, radius: int = 3) -> np.ndarray: - """Bridge thin white grid lines between red land cells.""" h, w = mask.shape out = mask.copy() ys, xs = np.where(mask) @@ -61,18 +67,15 @@ def label_components(mask: np.ndarray) -> tuple[np.ndarray, int]: def trace_boundary(labels: np.ndarray, label_id: int) -> list[tuple[int, int]]: - """Moore-neighbor contour for one connected component.""" h, w = labels.shape comp = labels == label_id if not comp.any(): return [] - # Start at topmost-leftmost pixel of component. ys, xs = np.where(comp) start = (int(xs[0]), int(ys[0])) x, y = start path = [start] - # Directions: E, SE, S, SW, W, NW, N, NE dirs = [(1, 0), (1, 1), (0, 1), (-1, 1), (-1, 0), (-1, -1), (0, -1), (1, -1)] dir_idx = 0 @@ -123,35 +126,30 @@ def path_d(points: list[tuple[float, float]]) -> str: def mercator_y_to_lat(y: float, h: float) -> float: - # Map top->north, bottom->south; clamp to avoid infinities at poles. t = 1.0 - (y / h) t = min(max(t, 0.02), 0.98) return math.degrees(math.atan(math.sinh(math.pi * (2 * t - 1)))) -def lat_lon_to_globe(lat: float, lon: float, cx: float, cy: float, r: float) -> tuple[float, float]: - """Simple orthographic-ish placement on visible globe disk.""" +def lat_lon_to_globe(lat: float, lon: float) -> tuple[float, float]: lat_r = math.radians(lat) lon_r = math.radians(lon) - # Visible hemisphere centered around lon 20, lat 20 for this logo framing. - lon0 = math.radians(20.0) - lat0 = math.radians(15.0) - # Rotate lon relative to center. + lon0 = math.radians(CENTER_LON) dlon = lon_r - lon0 - x = cx + r * math.cos(lat_r) * math.sin(dlon) - y = cy - r * math.sin(lat_r) + x = GLOBE_CX + GLOBE_R * math.cos(lat_r) * math.sin(dlon) + y = GLOBE_CY - GLOBE_R * math.sin(lat_r) return x, y -def map_to_globe(points: list[tuple[int, int]], w: int, h: int) -> list[tuple[float, float]]: - out: list[tuple[float, float]] = [] - cx, cy, r = 720.0, 560.0, 318.0 +def map_to_globe(points: list[tuple[int, int]], w: int, h: int) -> str: + mapped: list[tuple[float, float]] = [] for x, y in points: lon = (x / w) * 360.0 - 180.0 lat = mercator_y_to_lat(float(y), float(h)) - gx, gy = lat_lon_to_globe(lat, lon, cx, cy, r) - out.append((gx, gy)) - return out + mapped.append(lat_lon_to_globe(lat, lon)) + if len(mapped) < 3: + return "" + return path_d(mapped) def main() -> None: @@ -164,31 +162,34 @@ def main() -> None: components: list[dict] = [] for label_id in range(1, n + 1): area = int((labels == label_id).sum()) - if area < 500: # drop tiny noise / watermark specks + if area < 500: continue boundary = trace_boundary(labels, label_id) if len(boundary) < 20: continue boundary = simplify(boundary, min_dist=3.5) + flat = path_d([(float(x), float(y)) for x, y in boundary]) + globe = map_to_globe(boundary, w, h) + if not flat: + continue ys, xs = np.where(labels == label_id) - bbox = [int(xs.min()), int(ys.min()), int(xs.max()), int(ys.max())] components.append( { "id": label_id, "area": area, - "bbox": bbox, - "flat": path_d([(float(x), float(y)) for x, y in boundary]), - "globe": path_d(map_to_globe(boundary, w, h)), + "bbox": [int(xs.min()), int(ys.min()), int(xs.max()), int(ys.max())], + "flat": flat, + "globe": globe, } ) components.sort(key=lambda c: c["area"], reverse=True) - flat_paths = "\n ".join( + flat_paths = "\n ".join( f'' for c in components ) - globe_paths = "\n ".join( + globe_paths = "\n ".join( f'' for c in components ) @@ -196,7 +197,7 @@ def main() -> None: OUT_FLAT.write_text( f""" - + {flat_paths} @@ -204,17 +205,73 @@ def main() -> None: encoding="utf-8", ) - globe_svg = ROOT.parent.parent / "examples" / "app_hub" / "assets" / "hub-logo-continents-mercator-globe.svg" - globe_svg.write_text( - f""" + # Two-step alignment: + # 1) Vertical fit: Mercator north/south poles match globe top/bottom. + # 2) Horizontal fit: layer centers/pivots match at globe center. + globe_diameter = GLOBE_R * 2 + inner_sy = globe_diameter / h + inner_sx = inner_sy + inner_ty = GLOBE_CY - GLOBE_R + inner_tx_vertical = 0.0 + inner_tx = GLOBE_CX - (w * inner_sx) / 2 + outer_sx = 1.0 + outer_sy = 1.0 + + def globe_svg( + inner_tx_val: float, + inner_ty_val: float, + inner_sx_val: float, + inner_sy_val: float, + outer_sx_val: float, + outer_sy_val: float, + label: str, + ) -> str: + return f""" - + + + + + + + + + + - - {globe_paths} + + + + {flat_paths} + + -""", +""" + + OUT_GLOBE_SNAP_VERTICAL.write_text( + globe_svg( + inner_tx_vertical, + inner_ty, + inner_sx, + inner_sy, + outer_sx, + outer_sy, + "Continents vertical-fit snapshot", + ), + encoding="utf-8", + ) + + OUT_GLOBE.write_text( + globe_svg( + inner_tx, + inner_ty, + inner_sx, + inner_sy, + outer_sx, + outer_sy, + "Continents layered Mercator overlay", + ), encoding="utf-8", ) @@ -224,6 +281,15 @@ def main() -> None: "source": str(SRC.name), "size": [w, h], "components": len(components), + "layers": { + "vertical_snapshot": { + "translate": [inner_tx_vertical, inner_ty], + "scale": [inner_sx, inner_sy], + }, + "inner": {"translate": [inner_tx, inner_ty], "scale": [inner_sx, inner_sy]}, + "outer": {"scale": [outer_sx, outer_sy]}, + "pivot": [GLOBE_CX, GLOBE_CY], + }, "areas_top10": [c["area"] for c in components[:10]], }, indent=2, @@ -233,7 +299,7 @@ def main() -> None: print(f"components={len(components)}") print(f"wrote {OUT_FLAT}") - print(f"wrote {globe_svg}") + print(f"wrote {OUT_GLOBE}") if __name__ == "__main__": diff --git a/examples/app_hub/assets/hub-logo-continents-mercator-flat.svg b/examples/app_hub/assets/hub-logo-continents-mercator-flat.svg index eecc1f6..fc0f862 100644 --- a/examples/app_hub/assets/hub-logo-continents-mercator-flat.svg +++ b/examples/app_hub/assets/hub-logo-continents-mercator-flat.svg @@ -1,13 +1,13 @@ - + - - - - - - - + + + + + + + diff --git a/examples/app_hub/assets/hub-logo-continents-mercator-globe.snapshot-vertical.svg b/examples/app_hub/assets/hub-logo-continents-mercator-globe.snapshot-vertical.svg new file mode 100644 index 0000000..cdacd04 --- /dev/null +++ b/examples/app_hub/assets/hub-logo-continents-mercator-globe.snapshot-vertical.svg @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/app_hub/assets/hub-logo-continents-mercator-globe.svg b/examples/app_hub/assets/hub-logo-continents-mercator-globe.svg index e09b66c..ab6ddf3 100644 --- a/examples/app_hub/assets/hub-logo-continents-mercator-globe.svg +++ b/examples/app_hub/assets/hub-logo-continents-mercator-globe.svg @@ -1,15 +1,28 @@ - + + + + + + + + + + - - - - - - - - - + + + + + + + + + + + + + diff --git a/examples/app_hub/assets/hub-logo-fragment-compass.svg b/examples/app_hub/assets/hub-logo-fragment-compass.svg new file mode 100644 index 0000000..c849047 --- /dev/null +++ b/examples/app_hub/assets/hub-logo-fragment-compass.svg @@ -0,0 +1,14 @@ + + + + + + + + + N + S + W + E + + diff --git a/examples/app_hub/assets/hub-logo-fragment-globe-grid-overlay.svg b/examples/app_hub/assets/hub-logo-fragment-globe-grid-overlay.svg index d380efe..489ddc7 100644 --- a/examples/app_hub/assets/hub-logo-fragment-globe-grid-overlay.svg +++ b/examples/app_hub/assets/hub-logo-fragment-globe-grid-overlay.svg @@ -31,18 +31,4 @@ - - - - - - - - N - S - W - E - - - diff --git a/examples/app_hub/assets/hub-logo-fragment-globe.svg b/examples/app_hub/assets/hub-logo-fragment-globe.svg index 80a644f..d7ce631 100644 --- a/examples/app_hub/assets/hub-logo-fragment-globe.svg +++ b/examples/app_hub/assets/hub-logo-fragment-globe.svg @@ -27,6 +27,14 @@ + + + + + + + + @@ -45,18 +53,6 @@ - - - - - - - - N - S - W - E - - - + + diff --git a/examples/app_hub/assets/hub-thunder.svg b/examples/app_hub/assets/hub-thunder.svg index 8cb8b54..8029789 100644 --- a/examples/app_hub/assets/hub-thunder.svg +++ b/examples/app_hub/assets/hub-thunder.svg @@ -1,22 +1,14 @@ - - - - - - - - - - - - + + + + + + - - - + + + diff --git a/examples/app_hub/assets/hub-widget-resize-arrow.svg b/examples/app_hub/assets/hub-widget-resize-arrow.svg new file mode 100644 index 0000000..1f95c7b --- /dev/null +++ b/examples/app_hub/assets/hub-widget-resize-arrow.svg @@ -0,0 +1,7 @@ + diff --git a/examples/app_hub/assets/overtime-01-alphabet.svg b/examples/app_hub/assets/overtime-01-alphabet.svg new file mode 100644 index 0000000..1bb41c0 --- /dev/null +++ b/examples/app_hub/assets/overtime-01-alphabet.svg @@ -0,0 +1,22 @@ + diff --git a/examples/app_hub/clock-widget.html b/examples/app_hub/clock-widget.html new file mode 100644 index 0000000..e142823 --- /dev/null +++ b/examples/app_hub/clock-widget.html @@ -0,0 +1,482 @@ + + + + + + Clock Widget + + + +
+
+
+
+
+ 00 + : + 00 + 00 +
+
+
+ +
+
+
+
+
MON 01 JAN 2026
+
+ +
+
+ + + + diff --git a/examples/app_hub/hub-logo-layers.css b/examples/app_hub/hub-logo-layers.css index e71bd29..0906b6f 100644 --- a/examples/app_hub/hub-logo-layers.css +++ b/examples/app_hub/hub-logo-layers.css @@ -38,6 +38,26 @@ z-index: 4; } +.hub-logo-layer--compass { + z-index: 5; + left: 54.7%; + top: 75.9%; + width: 11.5%; + height: auto; + inset: auto; + transform: translate(-50%, -50%); + pointer-events: auto; + cursor: pointer; + transition: transform 0.22s ease, filter 0.22s ease; +} + +.hub-logo-layer--compass:hover, +.hub-logo-layer--compass:focus-visible { + transform: translate(-50%, -50%) scale(1.12); + filter: drop-shadow(0 0 16px rgba(186, 228, 255, 0.55)); + outline: none; +} + .hub-logo-layer--tail { z-index: 5; } diff --git a/examples/app_hub/hub.css b/examples/app_hub/hub.css index 17bcd6f..5808db4 100644 --- a/examples/app_hub/hub.css +++ b/examples/app_hub/hub.css @@ -169,70 +169,307 @@ z-index: 1; } -.hub-compass-hit { +.hub-clock-widget { position: fixed; - left: calc(50% + 252px); - top: calc(50% + 226px); - width: 110px; - height: 110px; - z-index: 2; - border-radius: 50%; - border: 2px solid rgba(219, 229, 241, 0.45); - background: - radial-gradient(circle at 35% 35%, rgba(231, 236, 243, 0.35), rgba(10, 14, 20, 0.22) 68%, rgba(10, 14, 20, 0.1) 100%); - box-shadow: - -6px -8px 8px rgba(160, 215, 255, 0.12), - 8px 10px 14px rgba(9, 13, 19, 0.24); - cursor: pointer; - transform: translate(-50%, -50%) scale(1); - transition: transform 0.22s ease, border-width 0.22s ease, box-shadow 0.22s ease; + left: calc(50% + 290px); + top: calc(50% - 300px); + width: min(420px, 42vw); + min-width: 330px; + z-index: 3; + border: 1px solid rgba(255, 255, 255, 0.72); + box-shadow: 0 10px 30px rgba(0, 0, 0, 0.48); + border-radius: 18px; + overflow: hidden; + background: linear-gradient(180deg, rgba(18, 24, 34, 0.92), rgba(13, 18, 28, 0.9)); + backdrop-filter: blur(1.5px); + transition: transform 0.2s ease, box-shadow 0.2s ease, filter 0.2s ease; + cursor: move; + user-select: none; + touch-action: none; + padding: 30px; + box-sizing: border-box; } -.hub-compass-hit::before, -.hub-compass-hit::after { - content: ""; +.hub-clock-widget:hover { + transform: scale(1.035); + box-shadow: 0 16px 42px rgba(0, 0, 0, 0.62), 0 0 26px rgba(155, 205, 255, 0.22); + filter: saturate(1.04); +} + +.cw-row { + width: 100%; + display: grid; + border-top: 1px solid rgba(255, 255, 255, 0.32); +} + +.cw-row:first-child { border-top: 0; } +.cw-row-main { grid-template-columns: 1fr; } +.cw-row-mid { grid-template-columns: repeat(4, 1fr); } +.cw-row-world { grid-template-columns: repeat(3, 1fr); } + +.cw-cell { + border-left: 1px solid rgba(255, 255, 255, 0.32); + min-height: 42px; + padding: 6px; +} + +.cw-cell:first-child { border-left: 0; } + +.cw-main-cell { + min-height: 168px; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + gap: 6px; + padding: 0; + background: linear-gradient(180deg, rgba(30, 40, 28, 0.85), rgba(20, 28, 20, 0.85)); +} + +.cw-line-time { + width: 100%; + display: flex; + justify-content: center; +} + +.cw-line-time .cw-time-main { + font-weight: 800; +} + +.cw-time-main { + display: inline-flex; + align-items: flex-end; + justify-content: center; + gap: 2px; + line-height: 1; + font-weight: 700; + width: 100%; + max-width: 100%; +} + +.ot-lcd-line { + display: inline-flex; + align-items: flex-end; + gap: 4px; +} + +.ot-lcd-bold .ot-glyph-wrap--bold { + transform: scale(1.12, 1.2); +} + +#cw-hm-lcd .ot-glyph { + height: 68px; + width: auto; +} + +.ot-lcd-sec { + margin-left: 8px; +} + +.ot-lcd-sec .ot-glyph-wrap:first-child { + margin-right: 8px; +} + +.ot-lcd-sec .ot-glyph { + height: 32px; + width: auto; +} + +.ot-lcd-tz .ot-glyph { + height: 30px; + width: auto; +} + +.ot-lcd-tz { + margin-left: 14px; +} + +.ot-glyph-wrap { + display: inline-block; + line-height: 0; + transform-origin: bottom center; +} + +.ot-glyph-wrap--bold { + transform: scale(1.08, 1.14); +} + +.ot-glyph { + fill: #d8f6b8; + filter: drop-shadow(0 0 5px rgba(190, 240, 150, 0.38)); +} + +.ot-glyph .ot-grid rect { + fill: rgba(20, 30, 24, 0.85); + filter: drop-shadow(0 0 1.4px rgba(135, 205, 255, 0.5)); +} + +.ot-glyph .ot-active { + fill: #d8f6b8; +} + +.ot-colon-blink { + animation: cwBlink 1.25s linear infinite; +} + +.cw-line-region { + color: #b8c2d4; + font-size: 10px; + letter-spacing: 0.16em; + font-weight: 300; + text-transform: uppercase; + width: 100%; + text-align: center; +} + +.cw-line-date { + color: #c5d0e2; + font-size: 10px; + letter-spacing: 0.12em; + font-weight: 400; + text-transform: uppercase; + width: 100%; + text-align: center; +} + +.cw-row-mid { + display: none; +} + +.cw-world-cell { + min-height: 178px; + display: flex; + flex-direction: column; + align-items: center; + gap: 5px; + padding: 25px 6px; + background: rgba(20, 28, 40, 0.62); +} + +.cw-world-label { + color: #a6b0c2; + font-size: 9.5px; + letter-spacing: 0.12em; + margin-top: 0; + margin-bottom: 8px; +} + +.cw-dial { + width: 95px; + height: 95px; + border-radius: 50%; + position: relative; + border: 2px solid rgba(239, 243, 252, 0.8); + background: radial-gradient(circle at 35% 30%, rgba(255, 255, 255, 0.36), rgba(255, 255, 255, 0) 36%), #edf2fb; + box-shadow: inset -5px -5px 10px rgba(0, 0, 0, 0.14), inset 6px 7px 9px rgba(255, 255, 255, 0.42); +} + +.cw-roman { position: absolute; left: 50%; top: 50%; - transform: translate(-50%, -50%); - border-radius: 1px; - background: rgba(231, 236, 243, 0.82); - transition: width 0.22s ease, height 0.22s ease; + width: 15px; + height: 15px; + margin: -7.5px; + transform: rotate(calc(var(--i) * 30deg)) translateY(-34px) rotate(calc(var(--i) * -30deg)); + font-family: "Times New Roman", serif; + font-size: 9px; + font-weight: 700; + color: #223147; + text-align: center; } -.hub-compass-hit::before { - width: 3px; - height: 46px; +.cw-roman-major { + font-size: 11.5px; + opacity: 1; } -.hub-compass-hit::after { - width: 46px; - height: 3px; +.cw-roman-minor { + font-size: 3.2px; + opacity: 0.25; } -.hub-compass-hit:hover, -.hub-compass-hit:focus-visible { - transform: translate(-50%, -50%) scale(1.25); - border-width: 2.3px; - box-shadow: - -8px -10px 10px rgba(180, 228, 255, 0.22), - 14px 16px 22px rgba(7, 10, 16, 0.34), - 22px 26px 34px rgba(6, 8, 14, 0.42); - outline: none; +.cw-resize-handle { + position: absolute; + width: 32px; + height: 32px; + z-index: 6; + display: grid; + color: rgba(220, 236, 255, 0.9); + filter: drop-shadow(0 0 4px rgba(150, 200, 255, 0.35)); + pointer-events: auto; + background: transparent; } -.hub-compass-hit:hover::before, -.hub-compass-hit:hover::after, -.hub-compass-hit:focus-visible::before, -.hub-compass-hit:focus-visible::after { - width: 52px; - height: 3.45px; +.cw-resize-handle svg { + width: 16px; + height: 16px; + fill: currentColor; + pointer-events: none; } -.hub-compass-hit:hover::before, -.hub-compass-hit:focus-visible::before { - width: 3.45px; - height: 52px; +.cw-resize-sw { + left: 0; + bottom: 0; + cursor: nesw-resize; + place-items: end start; +} + +.cw-resize-nw { + left: 0; + top: 0; + cursor: nwse-resize; + place-items: start start; + transform: scaleY(-1); +} + +.cw-resize-ne { + right: 0; + top: 0; + cursor: nesw-resize; + place-items: start end; + transform: scale(-1, -1); +} + +.cw-resize-se { + right: 0; + bottom: 0; + cursor: nwse-resize; + place-items: end end; + transform: scale(-1, 1); +} + +.cw-hand { + position: absolute; + left: 50%; + top: 50%; + transform-origin: 50% 100%; + transform: translate(-50%, -100%) rotate(0deg); + border-radius: 99px; +} + +.cw-hour { width: 4px; height: 26%; background: #1f2f46; } +.cw-minute { width: 3px; height: 35%; background: #2e4767; } +.cw-second { width: 1.5px; height: 39%; background: #b63b3b; } + +.cw-pin { + position: absolute; + left: 50%; + top: 50%; + width: 7px; + height: 7px; + margin-left: -3.5px; + margin-top: -3.5px; + border-radius: 50%; + background: #101928; + border: 1px solid #f3f6ff; +} + + +@keyframes cwBlink { + 0%, 20% { opacity: 0; } + 20.01%, 60% { opacity: 1; } + 60.01%, 100% { opacity: 0; } } .hub-docs-modal { @@ -252,12 +489,89 @@ left: 50%; top: 50%; transform: translate(-50%, -50%); - width: min(980px, calc(100vw - 40px)); - max-height: calc(100vh - 56px); + width: 90vw; + height: 90vh; + max-width: 90vw; + max-height: 90vh; display: flex; flex-direction: column; } +.hub-docs-layout { + display: grid; + grid-template-columns: minmax(220px, 28%) 1fr; + gap: 0; + flex: 1; + min-height: 0; + border-top: 1px solid var(--border); +} + +.hub-docs-toc { + margin: 0; + border-radius: 0; + border: 0; + border-right: 1px solid var(--border); + overflow: auto; + padding: 10px; +} + +.hub-docs-toc-title { + margin: 0 0 8px; + font-size: 12px; + letter-spacing: 0.08em; + text-transform: uppercase; +} + +.hub-docs-toc-group + .hub-docs-toc-group { + margin-top: 10px; + padding-top: 8px; + border-top: 1px solid var(--border); +} + +.hub-docs-toc-head, +.hub-docs-toc-leaf { + display: block; + width: 100%; + text-align: left; + border: 0; + background: transparent; + color: var(--text); + cursor: pointer; + padding: 8px 10px; + border-radius: 8px; + font: inherit; +} + +.hub-docs-toc-head { + font-weight: 700; + letter-spacing: 0.04em; +} + +.hub-docs-toc-leaf { + margin-left: 8px; + font-weight: 600; + font-size: 13px; +} + +.hub-docs-toc-head:hover, +.hub-docs-toc-leaf:hover, +.hub-docs-toc-head:focus-visible, +.hub-docs-toc-leaf:focus-visible { + background: var(--row-hover); + outline: none; +} + +.hub-docs-panels { + overflow: auto; + padding: 10px 12px 14px; + min-height: 0; +} + +.hub-docs-section .detail-tree-body { + font-size: 14px; + line-height: 1.5; +} + .hub-docs-header { display: flex; align-items: center; @@ -321,6 +635,13 @@ } @media (max-width: 860px) { + .hub-clock-widget { + left: auto; + right: 10px; + top: 10px; + width: min(94vw, 420px); + min-width: 0; + } .hub-cards { grid-template-columns: 1fr; max-width: 420px; diff --git a/examples/app_hub/index.html b/examples/app_hub/index.html index 0070a26..54ff47a 100644 --- a/examples/app_hub/index.html +++ b/examples/app_hub/index.html @@ -11,12 +11,34 @@ })(); - + + - +
+
+
+
+ + + + + +
+
EUROPE/WARSAW
+
WED 27 MAY 2026
+
+
+
+
+
+
+
+
+
+
@@ -35,7 +60,6 @@

Android Cast

- Thunder underline

Project hub