Files
zeekayandHanzo Dev 7d293212cb refactor: web/ is now console/ — bootnode/bootnode is the backend, bootnode/console the frontend
Names the two halves for what they are. The repo already holds both (12407e9 merged
bootnode/bootnode into this history); this makes the frontend directory say so, so the layout
reads the way the owner describes the system: backend and frontend separate, one repo.

git mv throughout — 187 renames, history preserved. Every path reference follows: hanzo.yml
build context and dockerfile, compose.yml, compose.prod.yml, start.sh, LLM.md,
infra/k8s/web-deployments.yaml, infra/cloud/deploy.sh, and the Dockerfile's own COPY paths.
Image and container names (bootnode-web, the -web tag suffix) are deliberately NOT renamed here:
those are a deploy contract the App CR and ingress bind to on port 3001, and breaking them is a
separate, staged change.

Console builds green after the move; the Go tree builds.

Co-authored-by: Hanzo Dev <[email protected]>
2026-07-26 17:47:51 -07:00

100 lines
3.2 KiB
TypeScript

"use client"
import Image from "next/image"
import { useEffect, useState } from "react"
import { useBrand } from "@/components/brand-provider"
import { cn } from "@/lib/utils"
// Re-exported so existing importers of `@/components/brand-logo` keep working;
// the hook itself lives in brand-provider.tsx.
export { useBrand }
const MARK = { small: 18, default: 22, large: 30 } as const
const WORD = { small: "text-base", default: "text-lg", large: "text-2xl" } as const
/**
* The brand lockup: mark plus wordmark, white-labelled by hostname.
*
* lux.cloud's signature move is here — the wordmark retracts a couple of
* seconds after load and returns on hover, so the mark holds the corner and
* the nav gets the width back. It is a width transition on an overflow-hidden
* span, not a mount/unmount, so nothing reflows abruptly and the accessible
* name never disappears.
*
* The mark is the brand's own asset (public/logo/<brand>-icon.svg), not a
* shared placeholder, so bootno.de, lux.cloud and zoo.cloud each render their
* own mark from the one build.
*/
export function BrandLogo({
showText = true,
size = "default",
collapsible = false,
className = "",
}: {
showText?: boolean
size?: "small" | "default" | "large"
/** Retract the wordmark after load, revealing it on hover (header use). */
collapsible?: boolean
className?: string
}) {
const brand = useBrand()
const [collapsed, setCollapsed] = useState(false)
useEffect(() => {
if (!collapsible) return
const t = setTimeout(() => setCollapsed(true), 2500)
return () => clearTimeout(t)
}, [collapsible])
const px = MARK[size]
return (
<span className={cn("group inline-flex items-center gap-2", className)}>
{brand.monoMark ? (
// A single-colour mark is painted in the CURRENT TEXT COLOUR, so it is
// legible in both themes and always matches the wordmark beside it —
// the same thing lux.cloud does with its pyramid. An <img> cannot do
// this: it is an isolated document, so a `fill="currentColor"` mark
// resolves to black and disappears on a black page.
<span
role="img"
aria-label={brand.name}
style={{
width: px,
height: px,
maskImage: `url(${brand.logoIcon})`,
WebkitMaskImage: `url(${brand.logoIcon})`,
maskSize: "contain",
WebkitMaskSize: "contain",
maskRepeat: "no-repeat",
WebkitMaskRepeat: "no-repeat",
maskPosition: "center",
WebkitMaskPosition: "center",
}}
className="shrink-0 bg-current"
/>
) : (
<Image
src={brand.logoIcon}
alt={brand.name}
width={px}
height={px}
style={{ width: px, height: px }}
className="shrink-0"
/>
)}
{showText && (
<span
className={cn(
"inline-block overflow-hidden whitespace-nowrap font-medium tracking-tight transition-[max-width,opacity] duration-300 ease-out group-hover:max-w-40 group-hover:opacity-100",
WORD[size],
collapsed ? "max-w-0 opacity-0" : "max-w-40 opacity-100"
)}
>
{brand.name}
</span>
)}
</span>
)
}