A Pen by Nicholas Berlette on CodePen.
Last active
December 6, 2024 00:15
-
-
Save nberlette/4c1377a392b57804b86c97b2efc8d438 to your computer and use it in GitHub Desktop.
Window Mangement Web Component Library
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<div class="editor"> | |
<n-window id="window1" class="window"> | |
<n-view slot="views"> | |
<n-tab slot="tabs" part="primary">Explorer</n-tab> | |
<n-tab slot="tabs" part="primary">Search</n-tab> | |
<n-tab slot="tabs" part="primary">Extensions</n-tab> | |
<div slot="content" name="panel-0"> | |
<n-view> | |
<n-tab slot="tabs" active>File 1</n-tab> | |
<n-tab slot="tabs">File 2</n-tab> | |
<div slot="content" name="panel-0">Content for File 1</div> | |
<div slot="content" name="panel-1" hidden>Content for File 2</div> | |
</n-view> | |
</div> | |
<div slot="content" name="panel-1"> | |
<n-view> | |
<n-tab slot="tabs" active>Match 1</n-tab> | |
<n-tab slot="tabs">Match 2</n-tab> | |
<div slot="content" name="panel-0">Search Result 1</div> | |
<div slot="content" name="panel-1" hidden>Search Result 2</div> | |
</n-view> | |
</div> | |
<div slot="content" name="panel-2"> | |
<n-view> | |
<n-tab slot="tabs" active>Installed</n-tab> | |
<n-tab slot="tabs">Disabled</n-tab> | |
<n-tab slot="tabs">Built-in</n-tab> | |
<div slot="content" name="panel-0">Installed Extensions</div> | |
<div slot="content" name="panel-1" hidden>Disabled Extensions</div> | |
<div slot="content" name="panel-2" hidden>Built-in Extensions</div> | |
</n-view> | |
</div> | |
</n-view> | |
</n-window> | |
<n-window id="window2" class="window"> | |
<n-view slot="views"> | |
<n-tab slot="tabs" active>Home</n-tab> | |
<n-tab slot="tabs">Settings</n-tab> | |
<div slot="content" name="panel-0">Home Content</div> | |
<div slot="content" name="panel-1" hidden>Settings Content</div> | |
</n-view> | |
</n-window> | |
</div> | |
<script> | |
document.addEventListener('DOMContentLoaded', () => { | |
// Initialize windows | |
const window1 = document.getElementById('window1'); | |
window1?.setTitle('Explorer') | |
.on('drag', (e) => console.log('Window #1 dragging:', e.detail)) | |
.on('resize', (e) => console.log('Window #1 resizing:', e.detail)) | |
.querySelector('[name="panel-0"] n-view') | |
.addTab('New File', '<p>New File Content</p>') | |
.on('tab-added', (e) => console.log('Tab added:', e.detail)); | |
// chainable API allows for fluent combinations of method calls | |
const window2 = document.getElementById("window2"); | |
window2?.setTitle('Home') | |
.on('maximize', (e) => console.log('Window #2 maximized:', e.detail)) | |
.on('close', () => console.log('Window #2 closed')) | |
.querySelector('n-view') | |
.addTab('About', 'About Content') | |
.on('tab-added', (e) => console.log('Tab added:', e.detail)) | |
.on('tab-activated', (e) => console.log('Activated:', e.detail)); | |
}); | |
</script> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import dedent from "https://esm.sh/[email protected]"; | |
// deno-lint-ignore-file ban-types | |
type strings = string & {}; | |
export interface Template<T = {}> { | |
(context: T): string; | |
} | |
export type UntypedTaggedTemplate = <T extends {}>( | |
tpl: TemplateStringsArray, | |
...values: TemplateValues<T> | |
) => Template<T>; | |
export interface TaggedTemplate<T = {}> { | |
(tpl: TemplateStringsArray, ...values: TemplateValues<T>): Template<T>; | |
} | |
export interface StaticTemplate<T = {}> { | |
(tpl: TemplateStringsArray, ...values: TemplateValues<T>): string; | |
} | |
export type Interpolation<T, U = string> = ( | |
this: T, | |
data: T, | |
index: number, | |
tpl: TemplateStringsArray | |
) => U; | |
export type TemplateValues<T> = ReadonlyArray< | |
keyof T | Interpolation<T, unknown> | strings | |
>; | |
// deno-lint-ignore no-explicit-any | |
const cache = new WeakMap<TemplateStringsArray, Template<any>>(); | |
/** | |
* Tagged template literal function with type-safe interpolated values. | |
* | |
* ### | |
* | |
* Constructs and caches an optimized template function for the given template | |
* string and interpolated expressions, which can be used to render arbitrary | |
* data payloads into the template at runtime. | |
* | |
* ### Caching | |
* | |
* The template function is cached for the given template string and values, so | |
* that the same _template string with the same values_[^1] will always return | |
* the same function instance. This allows for efficient reuse of the template | |
* and its compiled function, and avoids unnecessary recompilation overhead. | |
* | |
* **Note**: no strong references are maintained to cached templates, meaning | |
* the runtime can garbage collect a template if it is no longer accessible. | |
* | |
* [^1]: The template string and values are compared by reference, so "the | |
* same" means quite literally the same object instances, not just | |
* equivalent strings with the same content, so calling `template``foo`` | |
* twice will not take advantage of this caching feature. | |
* | |
* To render the template to a string, call the returned function with the | |
* desired data payload for interpolation, and it will return the template | |
* string with the payload's values injected into the appropriate locations. | |
* | |
* ### Template Expressions and Interpolations | |
* | |
* Expressions that match a key in the context object will be replaced with the | |
* associated value; if the value is a function (or is a key for a method in | |
* the context object), it will be called with the context object as the `this` | |
* value (and as the first argument). | |
* | |
* @param tpl The template string array. | |
* @param values Interpolated values/expressions to inject into the template. | |
* @returns A cached template function that can be passed a payload to render. | |
* @example | |
* ```ts | |
* import { template } from "@nick/tpl"; | |
* | |
* class User { | |
* static readonly greet = template<User>`Hello, ${"name"}!`; | |
* | |
* constructor(public name: string, public age: number) {} | |
* | |
* readonly greet = template<User>`Hello, ${"name"}!`.bind(this); | |
* } | |
* | |
* const user = new User("Alice", 42); | |
* console.log(User.greet(user)); // "Hello, Alice!" | |
* console.log(user.greet()); // "Hello, Alice!" | |
* | |
* user.name = "Bob"; | |
* console.log(User.greet(user)); // "Hello, Bob!" | |
* console.log(user.greet()); // "Hello, Bob!" | |
* ``` | |
*/ | |
export function template<T extends {}>( | |
tpl: TemplateStringsArray, | |
...values: TemplateValues<T> | |
): Template<T> { | |
const cached = cache.get(tpl); | |
if (cached) return cached; | |
const tmp = function template(data) { | |
let str = ""; | |
for (let i = 0; i < tpl.length; i++) { | |
str += tpl[i]; | |
if (i < values.length) { | |
let v = values[i]; | |
if (typeof v === "string" || typeof v === "symbol") { | |
const k = v as keyof T; | |
v = (o) => (k in o ? o[k] : k.toString()); | |
} | |
str += typeof v === "function" ? v.call(data, data, i, tpl) : v; | |
} | |
} | |
return str; | |
} as Template<T>; | |
cache.set(tpl, tmp); | |
return tmp; | |
} | |
const createResultTransformer = ( | |
fn: (s: string) => string | |
): UntypedTaggedTemplate => { | |
return <T extends {}>( | |
tpl: TemplateStringsArray, | |
...values: TemplateValues<T> | |
): Template<T> => (ctx, ...args) => | |
fn(template<T>(tpl, ...values).call(ctx, ctx, ...args)); | |
}; | |
export const trim: UntypedTaggedTemplate = createResultTransformer((s) => | |
s.trim() | |
); | |
export const trimStart: UntypedTaggedTemplate = createResultTransformer((s) => | |
s.trimStart() | |
); | |
export const trimEnd: UntypedTaggedTemplate = createResultTransformer((s) => | |
s.trimEnd() | |
); | |
export const upper: UntypedTaggedTemplate = createResultTransformer((s) => | |
s.toUpperCase() | |
); | |
export const lower: UntypedTaggedTemplate = createResultTransformer((s) => | |
s.toLowerCase() | |
); | |
export const concat = function concatTemplate<T extends {}, U = unknown>( | |
first: Template<T>, | |
second: Template<T | U> | |
): Template<T & U> { | |
return (data: T & U) => first(data) + second(data); | |
}; | |
export const using = function usingTemplateContext<T extends {}>( | |
context: T | |
): StaticTemplate<T> { | |
return (tpl, ...values) => template(tpl, ...values).call(context, context); | |
}; | |
export const defaults = function withDefaultContext<T extends {}>( | |
outer: T | |
): TaggedTemplate<T> { | |
return (tpl, ...values) => (inner) => { | |
const ctx = { ...outer, ...inner }; | |
return template(tpl, ...values).call(ctx, ctx); | |
}; | |
}; | |
template.dedent = function dedentedTemplate<T extends {}>( | |
tpl: TemplateStringsArray, | |
...values: TemplateValues<T> | |
): Template<T> { | |
return (ctx) => dedent.call([template(tpl, ...values).call(ctx, ctx)]); | |
}; | |
template.trim = trim; | |
template.trimStart = trimStart; | |
template.trimEnd = trimEnd; | |
template.upper = upper; | |
template.lower = lower; | |
template.using = using; | |
template.concat = concat; | |
template.defaults = defaults; | |
globalThis.template = template; | |
export const nothing = Symbol("nada"); | |
export type nothing = typeof nothing; | |
template.nothing = nothing; | |
// BaseComponent.ts | |
abstract class BaseComponent extends HTMLElement { | |
static #ids = new WeakMap(); | |
static #cache = new WeakMap(); | |
static styles = ` | |
:host { | |
display: block; | |
box-sizing: border-box; | |
} | |
`; | |
static template = `<slot></slot>`; | |
static tagName = "n-component"; | |
static define(tag = this.tagName, registry = customElements) { | |
if (!registry.get(tag)) registry.define(tag, this); | |
} | |
constructor() { | |
super(); | |
this.attachShadow({ mode: "open" }); | |
if (this.shadowRoot) this.render(); | |
} | |
render() { | |
if (this.shadowRoot) { | |
const css_tpl = (this.constructor as typeof BaseComponent).styles; | |
const css = | |
typeof css_tpl === "function" ? css_tpl.call(this, this) : css_tpl; | |
const html_tpl = (this.constructor as typeof BaseComponent).template; | |
const html = | |
typeof html_tpl === "function" ? html_tpl.call(this, this) : html_tpl; | |
this.shadowRoot.innerHTML = `<style>${css}</style>${html}`; | |
} | |
} | |
/** | |
* Shorthand for querySelector within shadow DOM | |
* @param selector CSS selector string | |
* @returns Element | null | |
*/ | |
$(selector: string): Element | null { | |
return this.shadowRoot?.querySelector(selector) ?? null; | |
} | |
/** | |
* Shorthand for querySelectorAll within shadow DOM | |
* @param selector CSS selector string | |
* @returns NodeListOf<Element> | |
*/ | |
$$(selector: string): Array<Element> { | |
return [...(this.shadowRoot?.querySelectorAll(selector) ?? [])]; | |
} | |
/** | |
* Set multiple attributes at once | |
* @param attributes Object with key-value pairs | |
* @returns this for chaining | |
*/ | |
setAttributes<A extends { [key: string]: string | number | boolean | nothing | null | undefined }>(attributes: A): this { | |
for (const key in attributes) { | |
const value = attributes[key]; | |
if (value === nothing) this.removeAttribute(key); | |
this.setAttribute(key, (value ?? "").toString()); | |
} | |
return this; | |
} | |
/** | |
* Emit a custom event with optional detail | |
* @param eventName Name of the event | |
* @param detail Optional data to pass with the event | |
* @returns this for chaining | |
*/ | |
emit(eventName: string, detail: any = {}): this { | |
this.dispatchEvent( | |
new CustomEvent(eventName, { detail, bubbles: true, composed: true }) | |
); | |
return this; | |
} | |
/** | |
* Add an event listener | |
* @param eventName Name of the event | |
* @param callback Event handler | |
* @returns this for chaining | |
*/ | |
on(eventName: string, callback: EventListenerOrEventListenerObject): this { | |
this.addEventListener(eventName, callback); | |
return this; | |
} | |
/** | |
* Remove an event listener | |
* @param eventName Name of the event | |
* @param callback Event handler to remove | |
* @returns this for chaining | |
*/ | |
off(eventName: string, callback: EventListenerOrEventListenerObject): this { | |
this.removeEventListener(eventName, callback); | |
return this; | |
} | |
getCachedUniqueId(): string { | |
const cache = BaseComponent.#ids; | |
return cache.get(this) ?? cache.set(this, this.generateId()).get(this); | |
} | |
getInstanceCache<K, V>(label: string): Map<K, V> { | |
const cache = | |
BaseComponent.#cache.get(this) ?? | |
BaseComponent.#cache.set(this, new Map()).get(this)!; | |
return cache.get(label) ?? cache.set(label, new Map()).get(label)!; | |
} | |
/** | |
* Generate a unique identifier | |
* @param index Tab index | |
* @returns Unique ID string | |
*/ | |
uniqueId(index: number, prefix?: string): string { | |
const cache = this.getInstanceCache<number, string>("id"); | |
prefix = `${this.getCachedUniqueId()}${prefix ? "-" + prefix : ""}`; | |
return ( | |
cache.get(index) ?? cache.set(index, `${prefix}-${index}`).get(index) | |
); | |
} | |
/** | |
* Generate a random ID | |
* @returns Random ID string | |
*/ | |
generateId(): string { | |
return Math.random().toString(36).substr(2, 9); | |
} | |
} | |
// WindowManager.ts | |
class WindowManager { | |
static singleton = new WindowManager(); | |
#indices = new WeakMap<WindowComponent, number>(); | |
#refs: WeakRef<WindowComponent>[] = []; | |
zIndex = 20; | |
get windows(): WeakRef<WindowComponent>[] { | |
return this.#refs; | |
} | |
*deref(): IterableIterator<WindowComponent> { | |
for (const ref of this.#refs) yield ref.deref(); | |
} | |
/** | |
* Add a window to the manager | |
* @param window WindowComponent instance | |
*/ | |
addWindow(window: WindowComponent): this { | |
const oldIndex = this.windows.findIndex( | |
(r) => r.deref() && r.deref() === window | |
); | |
if (oldIndex === -1) { | |
const newZIndex = this.zIndex + this.windows.length; | |
window.style.setProperty("z-index", `var(--z, ${newZIndex})`); | |
window.style.setProperty("--z", `${newZIndex}`); | |
this.windows.push(new WeakRef(window)); | |
this.updateWindowFocus(window); | |
} | |
return this; | |
} | |
/** | |
* Remove a window from the manager | |
* @param window WindowComponent instance | |
*/ | |
removeWindow(ref: WeakRef<WindowComponent> | WindowComponent): this { | |
const window = ref && "deref" in ref ? ref.deref() : ref; | |
if (!(window instanceof BaseComponent)) { | |
console.error("Invalid window instance."); | |
} else { | |
this.#refs = this.windows.filter( | |
(w) => w.deref() && w.deref() !== window | |
); | |
window.remove(); | |
this.updateBlurredWindows(); | |
this.updateWindowFocus(this.windows.at(-1)?.deref()); | |
} | |
return this; | |
} | |
/** | |
* Bring a window to focus | |
* @param focusedWindow WindowComponent instance | |
*/ | |
updateWindowFocus(focusedWindow?: WindowComponent): void { | |
this.windows.forEach((ref, index, windows) => { | |
const window = ref.deref(); | |
if (!window) return this.windows.splice(index, 1), void 0; | |
if (!(window instanceof BaseComponent)) { | |
console.error("Invalid window component."); | |
} else { | |
let newZIndex = parseInt(getComputedStyle(window).zIndex); | |
if (isNaN(newZIndex)) newZIndex = this.zIndex + index; | |
const oldZIndex = newZIndex; | |
let focused = false; | |
if (focusedWindow != null && window === focusedWindow) { | |
const newIndex = windows.length; | |
windows.splice(index, 1); | |
windows.push(ref); | |
newZIndex = this.zIndex + newIndex; | |
focused = true; | |
window.classList.add("focused"); | |
window.classList.remove("blurred"); | |
window.setAttribute("aria-modal", "true"); | |
window.style.setProperty("--z", (newZIndex = this.zIndex + newIndex)); | |
} else { | |
focused = false; | |
window.removeAttribute("aria-modal"); | |
newZIndex = this.zIndex + index; | |
window.classList.remove("focused"); | |
window.classList.add("blurred"); | |
window.toggleAttribute("aria-modal", focused); | |
window.style.setProperty("--z", newZIndex); | |
} | |
window.emit("focus", { oldZIndex, newZIndex, window, focused }); | |
} | |
}); | |
} | |
/** | |
* Apply blurred effect to all non-focused windows | |
*/ | |
protected updateBlurredWindows(): void { | |
this.#refs = this.#refs.filter((r) => r && r.deref()); | |
this.updateWindowFocus(); | |
} | |
} | |
// TabComponent.ts | |
class TabComponent extends BaseComponent { | |
static readonly tagName = "n-tab"; | |
static styles = dedent` | |
:host { | |
--border-color: transparent; | |
--active-border-color: var(--tab-active-border-color, #007BFF); | |
--hover-background: var(--tab-hover-background, #eee); | |
--active-background: var(--tab-active-background, #fff); | |
--transition-duration: var(--tab-transition-duration, 0.3s); | |
--transition-ease: var(--tab-transition-ease, ease); | |
--padding: var(--tab-padding, 0.625rem 0.9375rem); | |
--font-size: var(--tab-font-size, 0.875rem); | |
--border-width: var(--tab-border-width, 0 0 0.125rem 0); | |
display: inline-block; | |
padding: var(--padding); | |
cursor: pointer; | |
font-size: var(--font-size); | |
border-width: var(--border-width); | |
border-style: solid; | |
border-color: var(--border-color); | |
transition: | |
background var(--transition-duration) var(--transition-ease), | |
border-bottom var(--transition-duration) var(--transition-ease); | |
&[aria-selected="true"] { | |
border-color: var(--active-border-color); | |
background: var(--active-background); | |
} | |
&:hover, &:focus { | |
background: var(--hover-background); | |
outline: none; | |
} | |
}`; | |
static template = `<slot></slot>`; | |
protected _view: TabViewComponent | null = null; | |
protected _panel: TabPanelComponent | null = null; | |
protected _window: WindowComponent | null = null; | |
setPanel(panel: TabPanelComponent | null): this { | |
if (panel == null || panel instanceof TabPanelComponent) { | |
this._panel = panel ?? null; | |
if (this._panel?.["_tab"] !== this) this._panel?.setTab(this); | |
} | |
return this; | |
} | |
setView(view: TabViewComponent | null): this { | |
if (view == null || view instanceof TabViewComponent) { | |
this._view = view ?? null; | |
} | |
return this; | |
} | |
setWindow(window: WindowComponent | null): this { | |
if (window == null || window instanceof WindowComponent) { | |
this._window = window ?? null; | |
} | |
return this; | |
} | |
} | |
// customElements.define(TabComponent.tagName, TabComponent); | |
TabComponent.define(); | |
class TabPanelComponent extends BaseComponent { | |
static readonly tagName = "n-panel"; | |
static styles = dedent` | |
:host {} | |
`; | |
static template = dedent` | |
<slot></slot> | |
`; | |
protected _tab: TabComponent | null = null; | |
protected _view: TabViewComponent | null = null; | |
protected _window: WindowComponent | null = null; | |
setTab(tab: TabComponent | null): this { | |
if (tab == null || tab instanceof TabComponent) { | |
this._tab = tab ?? null; | |
if (this._tab?.["_panel"] !== this) this._tab?.setPanel(this); | |
} | |
return this; | |
} | |
setView(view: TabViewComponent | null): this { | |
if (view == null || view instanceof TabViewComponent) { | |
this._view = view ?? null; | |
} | |
return this; | |
} | |
setWindow(window: WindowComponent | null): this { | |
if (window == null || window instanceof WindowComponent) { | |
this._window = window ?? null; | |
} | |
return this; | |
} | |
} | |
TabPanelComponent.define(); | |
// TabViewComponent.ts | |
class TabViewComponent extends BaseComponent { | |
static readonly tagName = "n-view"; | |
static styles = dedent` | |
:host { | |
--tab-background: #f9f9f9; | |
--tab-hover-background: #eee; | |
--tab-active-border: #007BFF; | |
--tab-active-background: var(--tab-active-background, #fff); | |
--transition-duration: 0.3s; | |
--transition-ease: ease; | |
display: flex; | |
flex-direction: column; | |
height: 100%; | |
} | |
.tab-list { | |
display: flex; | |
background: var(--top-bar-bg-color); | |
border-bottom: 0.0625rem solid #ccc; /* 1px */ | |
} | |
.tab-list button { | |
background: none; | |
border: none; | |
padding: 0.5rem 0.75rem; /* 8px 12px */ | |
cursor: pointer; | |
font-size: 0.875rem; /* 14px */ | |
border-bottom: 0.125rem solid transparent; /* 2px */ | |
transition: background var(--transition-duration) var(--transition-ease), | |
border-bottom var(--transition-duration) var(--transition-ease); | |
} | |
.tab-list button:hover, | |
.tab-list button:focus { | |
background: var(--tab-hover-background); | |
outline: none; | |
} | |
.tab-list button[aria-selected="true"] { | |
border-bottom: 0.125rem solid var(--tab-active-border); | |
background: var(--tab-active-background); | |
} | |
.tab-content { | |
flex: 1; | |
padding: 0.625rem; /* 10px */ | |
overflow: auto; | |
} | |
.tab-panel { | |
display: none; | |
height: 100%; | |
} | |
.tab-panel[active] { | |
display: block; | |
} | |
`; | |
static template = dedent` | |
<div class="tab-list" role="tablist"> | |
<slot name="tabs"></slot> | |
</div> | |
<div class="tab-content"> | |
<slot name="content"></slot> | |
</div> | |
`; | |
protected tabList: HTMLElement | null = null; | |
protected tabContent: HTMLElement | null = null; | |
protected tabSlot: HTMLSlotElement; | |
protected contentSlot: HTMLSlotElement; | |
protected tabs: TabComponent[] = []; | |
protected panels: HTMLElement[] = []; | |
protected activeTabIndex = 0; | |
constructor() { | |
super(); | |
this.tabList = this.$(".tab-list"); | |
this.tabContent = this.$(".tab-content"); | |
this.tabSlot = this.$('slot[name="tabs"]') as HTMLSlotElement; | |
this.contentSlot = this.$( | |
'slot[name="content"],slot:not([name])' | |
) as HTMLSlotElement; | |
} | |
connectedCallback() { | |
this.tabSlot.addEventListener("slotchange", this.handleSlotChange); | |
this.contentSlot.addEventListener("slotchange", this.updatePanels); | |
} | |
disconnectedCallback() { | |
this.tabSlot.removeEventListener("slotchange", this.handleSlotChange); | |
this.contentSlot.removeEventListener("slotchange", this.updatePanels); | |
} | |
attributeChangeCallback( | |
name: string, | |
oldValue: string | null, | |
newValue: string | null | |
) {} | |
// Event listener methods using arrow functions | |
handleSlotChange = () => { | |
this.tabs = Array.from(this.tabSlot.assignedElements()).filter( | |
(el) => el.tagName.toLowerCase() === TabComponent.tagName | |
) as TabComponent[]; | |
this.panels = Array.from(this.contentSlot.assignedElements()).filter((el) => | |
el.hasAttribute("name") | |
) as HTMLElement[]; | |
this.updateTabs(); | |
this.updatePanels(); | |
if (this.tabs.length > 0) this.activateTab(0); | |
}; | |
updateTabs = () => { | |
this.tabs.forEach((tab, index) => { | |
const uniqueId = this.uniqueId(index); | |
const active = index === this.activeTabIndex; | |
tab.setAttributes({ | |
role: "tab", | |
id: `tab-${uniqueId}`, | |
"aria-selected": "false", | |
"aria-controls": `panel-${uniqueId}`, | |
"aria-label": tab.textContent, | |
tabindex: active ? "0" : "-1" | |
}); | |
if (tab._onclick) { | |
tab.off("click", tab._onclick); | |
tab._onclick = null; | |
} | |
tab._onclick = () => this.activateTab(index); | |
tab.on("click", tab._onclick); | |
if (tab._onkeydown) { | |
tab.off("keydown", tab._onkeydown); | |
tab._onkeydown = null; | |
} | |
tab._onkeydown = (e: KeyboardEvent) => this.onKeyDown(e, index); | |
tab.on("keydown", tab._onkeydown); | |
}); | |
}; | |
/** | |
* Update panel references and accessibility attributes | |
*/ | |
updatePanels = () => { | |
this.panels.forEach((panel, index) => { | |
const uniqueId = this.uniqueId(index); | |
panel.setAttribute("role", "tabpanel"); | |
panel.setAttribute("aria-labelledby", `tab-${uniqueId}`); | |
panel.setAttribute("id", `panel-${uniqueId}`); | |
const active = index === this.activeTabIndex; | |
panel.toggleAttribute("active", active); | |
const tab = this.tabs[index]; | |
panel.classList.add("tab-panel"); | |
panel.toggleAttribute("hidden", !active); | |
panel.setAttribute("aria-hidden", !active + ""); | |
tab?.setAttribute("aria-selected", active + ""); | |
if (!active) tab?.removeAttribute("active"); | |
}); | |
}; | |
/** | |
* Activate a tab by index | |
* @param index Tab index | |
* @returns this for chaining | |
*/ | |
activateTab(index: number): this { | |
if (index < 0) index += this.tabs.length; | |
if (!isFinite((index = +index))) index = this.activeTabIndex; | |
this.activeTabIndex = index = Math.max( | |
0, | |
Math.min(this.tabs.length - 1, +index) | |
); | |
this.updateTabs(); | |
this.updatePanels(); | |
this.tabs.forEach((tab, i) => { | |
const selected = i === this.activeTabIndex; | |
// const id = this.uniqueId(i); | |
const panel = this.panels[i]; | |
// panel.hidden = !selected; | |
// panel.toggleAttribute("hidden", !selected); | |
// panel.setAttribute("aria-hidden", !selected + ""); | |
if (selected) { | |
tab.focus(); | |
this.emit("tab-activated", { index, tab, panel }); | |
} | |
}); | |
return this; | |
} | |
/** | |
* Add a new tab with title and content | |
* @param title Title of the tab | |
* @param content HTML string for the content | |
* @returns this for chaining | |
*/ | |
addTab(title: string, content: string): this { | |
const index = this.tabs.length; | |
const uid = this.uniqueId(index); | |
const tab = document.createElement(TabComponent.tagName); | |
const tab_id = `tab-${uid}`; | |
const panel_id = `panel-${uid}`; | |
tab.dataset.panelId = panel_id; | |
tab.dataset.tabId = tab.id = tab_id; | |
tab.dataset.index = index; | |
tab.setAttribute("slot", "tabs"); | |
tab.textContent = title; | |
const panel = document.createElement(TabPanelComponent.tagName); | |
panel.dataset.panelId = panel.id = panel_id; | |
panel.dataset.tabId = tab_id; | |
panel.dataset.index = index; | |
panel.setAttribute("id", panel_id); | |
panel.setAttribute("name", panel_id); | |
panel.setAttribute("aria-labelledby", tab.id); | |
panel.setAttribute("role", "tabpanel"); | |
panel.setAttribute("slot", "content"); | |
panel.innerHTML = content; | |
this.appendChild(tab); | |
this.appendChild(panel); | |
this.emit("tab-added", { title, content, tab, panel }); | |
this.updateTabs(); | |
this.updatePanels(); | |
this.activateTab(index); | |
return this; | |
} | |
/** | |
* Remove a tab by index | |
* @param index Tab index | |
* @returns this for chaining | |
*/ | |
removeTab(index: number): this { | |
if (index < 0 || index >= this.tabs.length) return this; | |
const tab = this.tabs[index]; | |
const panel = this.panels[index]; | |
this.removeChild(tab); | |
this.removeChild(panel); | |
this.emit("tab-removed", { index, tab, panel }); | |
// Activate another tab if necessary | |
if (this.tabs.length > 0) this.activateTab(index - 1); | |
return this; | |
} | |
/** | |
* Handle keyboard navigation | |
* @param event KeyboardEvent | |
* @param index Current tab index | |
*/ | |
onKeyDown = (event: KeyboardEvent, index: number): void => { | |
const key = event.key; | |
if (key === "ArrowRight") { | |
this.activateTab((index + 1) % this.tabs.length); | |
event.preventDefault(); | |
} else if (key === "ArrowLeft") { | |
this.activateTab((index - 1 + this.tabs.length) % this.tabs.length); | |
event.preventDefault(); | |
} | |
}; | |
} | |
// customElements.define(TabViewComponent.tagName, TabViewComponent); | |
TabViewComponent.define(); | |
interface Position { | |
top: number; | |
left: number; | |
width: number; | |
height: number; | |
} | |
// WindowComponent.ts | |
class WindowComponent extends BaseComponent { | |
static readonly tagName = "n-window"; | |
static readonly styles = dedent` | |
:host { | |
color-scheme: light dark; | |
--background-color: white; | |
--border-color: var(--window-border-color, #ccc); | |
--border-radius: var(--window-border-radius, 0); | |
--border-width: var(--window-border-width, 1px); | |
--focused-shadow: var(--window-focused-shadow, 0 4px 20px rgba(0,0,0,0.3)); | |
--blurred-filter: var(--window-blurred-filter, blur(2px)); | |
--transition-duration: var(--window-transition-duration, 0.2s); | |
--transition-ease: var(--window-transition-ease, ease); | |
--title-bar-background: light-dark(#f0f0f0, #111827); | |
--title-bar-padding: var(--window-title-bar-padding, 0.3125rem 0.625rem); | |
--button-size: var(--window-button-size, 0.625rem); | |
--button-bg: var(--window-button-bg, light-dark(#456, #678)); | |
--tab-background: var(--window-tab-background, #f9f9f9); | |
--tab-hover-background: var(--window-tab-hover-background, #eee); | |
--tab-active-border: var(--window-tab-active-border, #007BFF); | |
--tab-active-background: var(--window-tab-active-background, #fff); | |
--tab-transition-duration: var(--window-tab-transition-duration, 0.3s); | |
--tab-transition-ease: var(--window-tab-transition-ease, ease); | |
--min-width: 200px; | |
--min-height: 48px; | |
--width: var(--window-width, 400px); | |
--height: var(--window-height, 400px); | |
--x: var(--window-x, 25vw); | |
--y: var(--window-y, 20vh); | |
--z: var(--window-z, 20); | |
position: absolute !important; | |
display: flex; | |
flex-direction: column; | |
background: var(--background-color); | |
border: 1px solid var(--border-color); | |
box-shadow: var(--focused-shadow); | |
border-radius: var(--border-radius); | |
overflow: hidden; | |
transition: filter var(--transition-duration) var(--transition-ease), box-shadow var(--transition-duration) var(--transition-ease) !important; | |
min-width: var(--min-width) !important; | |
min-height: var(--min-height) !important; | |
max-width: 100vw !important; | |
max-height: 100vh !important; | |
width: var(--width) !important; | |
height: var(--height) !important; | |
top: var(--y) !important; | |
left: var(--x) !important; | |
z-index: var(--z) !important; | |
} | |
:host(.blurred, :blur):not(:focus,.focused) { | |
filter: var(--blurred-filter); | |
} | |
:host(.focused, :focus) { | |
box-shadow: var(--focused-shadow); | |
} | |
.title-bar { | |
display: flex; | |
justify-content: space-between; | |
align-items: center; | |
background: var(--title-bar-background); | |
padding: var(--title-bar-padding); | |
cursor: move; | |
user-select: none; | |
& .title { | |
font-size: 1rem; | |
font-weight: bold; | |
outline: none; | |
margin: 0; | |
padding: 0; | |
} | |
} | |
.window-controls { | |
display: flex; | |
gap: calc(var(--button-size) * 0.4); | |
flex-direction: row; | |
place-items: center; | |
align-items: center; | |
height: 100%; | |
& button { | |
--color: var(--button-bg, light-dark(#456, #678)); | |
border: 2.5px solid var(--color); | |
background: transparent; | |
cursor: pointer; | |
display: inline-block; | |
width: var(--button-size); | |
height: var(--button-size); | |
padding: 0 !important; | |
margin: 0 !important; | |
outline: none; | |
border-radius: 9999px; | |
transition-property: background, color, border-color, box-shadow; | |
transition-duration: 0.3s; | |
transition-delay: 1s; | |
&:hover, &:focus { | |
background: #ddd; | |
outline: none; | |
} | |
&.close { | |
--color: indianred; | |
} | |
&.minimize { | |
--color: goldenrod; | |
} | |
&.maximize { | |
--color: lightseagreen; | |
} | |
.title-bar:hover & { | |
background: var(--color); | |
transition-delay: 0.15s; | |
} | |
} | |
} | |
.content { | |
flex: 1; | |
position: relative; | |
overflow: auto; | |
} | |
:host(.minimized) { | |
--height: 3rem; | |
} | |
[data-resize] { | |
--handle-size: 1rem; | |
--handle-offset: calc(-0.5 * var(--handle-size)); | |
--handle-fullsize: calc(100% - calc(2 * var(--handle-size))); | |
position: absolute; | |
width: var(--handle-size); | |
height: var(--handle-size); | |
background: transparent; | |
cursor: se-resize; | |
z-index: 100; | |
} | |
/* Additional resize handles */ | |
[data-resize="t"] { | |
left: var(--handle-size); | |
top: var(--handle-offset); | |
width: var(--handle-fullsize); | |
cursor: n-resize; | |
} | |
[data-resize="r"] { | |
top: var(--handle-size); | |
right: var(--handle-offset); | |
height: var(--handle-fullsize); | |
cursor: e-resize; | |
} | |
[data-resize="b"] { | |
left: var(--handle-size); | |
bottom: var(--handle-offset); | |
width: var(--handle-fullsize); | |
cursor: s-resize; | |
} | |
[data-resize="l"] { | |
top: var(--handle-size); | |
left: var(--handle-offset); | |
height: var(--handle-fullsize); | |
cursor: w-resize; | |
} | |
[data-resize="tl"] { | |
top: var(--handle-offset); | |
left: var(--handle-offset); | |
cursor: nw-resize; | |
} | |
[data-resize="tr"] { | |
top: var(--handle-offset); | |
right: var(--handle-offset); | |
cursor: ne-resize; | |
} | |
[data-resize="bl"] { | |
bottom: var(--handle-offset); | |
left: var(--handle-offset); | |
cursor: sw-resize; | |
} | |
[data-resize="br"] { | |
bottom: var(--handle-offset); | |
right: var(--handle-offset); | |
cursor: se-resize; | |
} | |
`; | |
static template = dedent` | |
<div part="window"> | |
<header class="top-bar" role="toolbar" aria-label="Window Controls"> | |
<div class="traffic-signal window-controls"> | |
<button class="btn close" aria-label="Close Window" title="Close" part="window-button close"> | |
<span class="sr-only" aria-hidden="true" hidden>Close</span> | |
</button> | |
<button class="btn minimize" aria-label="Minimize Window" title="Minimize" part="window-button minimize"> | |
<span class="sr-only" aria-hidden="true" hidden>Minimize</span> | |
</button> | |
<button class="btn maximize" aria-label="Maximize Window" title="Maximize" part="window-button maximize"> | |
<span class="text-xs sr-only" aria-hidden="true" hidden>Maximize</span> | |
</button> | |
</div> | |
<h2 class="title" tabindex="0" id="window-title"> | |
<slot name="title">Untitled</slot> | |
</h2> | |
</header> | |
<div class="content view-primary activity-bar-left" role="document"> | |
<slot name="views"></slot> | |
</div> | |
<slot name="footer"> | |
<footer class="status-bar group z-50"> | |
<div class="inner pb-0.5 !h-[1.65rem]"> | |
<div class="flex flex-1 flex-shrink-0 !text-[0.7rem] items-center space-x-1.5 place-items-center"> | |
<svg | |
stroke="currentColor" | |
fill="currentColor" | |
stroke-width="0" | |
viewBox="0 0 24 24" | |
class="size-3" | |
> | |
<use href="#i-git-fork" width="100%" height="100%" /> | |
</svg> | |
<span>main*</span> | |
</div> | |
<div class="flex items-center justify-end gap-x-2 opacity-60 group-hover:!opacity-100 transition-opacity duration-300"> | |
<button class="whitespace-pre md:px-2" aria-label="Current Position"> | |
Ln 7, Col 1 | |
</button> | |
<button | |
class="min-w-12 px-2" | |
data-path="editor.settings.tabSize" | |
> | |
Spaces: 2 | |
</button> | |
<button | |
class="min-w-12 flex-shrink-1 flex-grow-0 px-2" | |
data-path="editor.settings.encoding" | |
hidden | |
> | |
UTF-8 | |
</button> | |
<button | |
class="min-w-12 px-2" | |
data-path="editor.settings.language" | |
data-value="ts" | |
data-label="TypeScript" | |
> | |
TypeScript | |
</button> | |
<button | |
class="size-3 cursor-pointer" | |
id="btn-github-copilot-icon" | |
> | |
<svg | |
viewBox="0 0 16 15" | |
fill="currentColor" | |
class="size-3" | |
> | |
<use | |
href="#i-github-copilot" | |
width="100%" | |
height="100%" | |
/> | |
</svg> | |
</button> | |
</div> | |
</div> | |
</footer> | |
</slot> | |
<div data-resize="t" aria-label="Resize (top)"></div> | |
<div data-resize="r" aria-label="Resize (right)"></div> | |
<div data-resize="b" aria-label="Resize (bottom)"></div> | |
<div data-resize="l" aria-label="Resize (left)"></div> | |
<div data-resize="tl" aria-label="Resize (top left)"></div> | |
<div data-resize="tr" aria-label="Resize (top right)"></div> | |
<div data-resize="bl" aria-label="Resize (bottom left)"></div> | |
<div data-resize="br" aria-label="Resize (bottom right)"></div> | |
</div> | |
${WindowComponent.getSpriteSheet()} | |
`; | |
protected windowManager: WindowManager = WindowManager.singleton; | |
protected titleBar: Element | null = null; | |
protected titleElement: Element | null = null; | |
protected controls: { | |
minimize: HTMLButtonElement; | |
maximize: HTMLButtonElement; | |
close: HTMLButtonElement; | |
}; | |
protected content: Element | null = null; | |
protected resizeHandles: NodeListOf<Element>; | |
protected currentResizeHandle: Element | null = null; | |
protected isResizing = false; | |
protected isDragging = false; | |
protected isMaximized = false; | |
protected isMinimized = false; | |
protected startX = 0; | |
protected startY = 0; | |
protected startWidth = 0; | |
protected startHeight = 0; | |
protected dragOffsetX = 0; | |
protected dragOffsetY = 0; | |
protected minWidth = 200; | |
protected minHeight = 150; | |
protected position: Position = { | |
top: Math.random() * 200, | |
left: Math.random() * 300, | |
width: Math.max(this.minWidth, Math.min(Math.random() * 600, 600)), | |
height: Math.max(this.minHeight, Math.min(Math.random() * 500, 500)) | |
}; // rem units | |
protected history: Position[] = []; | |
protected historyLimit = 100; // something reasonable for an undo stack | |
connectedCallback() { | |
this.windowManager = WindowManager.singleton; | |
this.titleBar = this.$(".top-bar"); | |
this.titleElement = this.$(".title"); | |
this.controls = { | |
close: this.$(".close") as HTMLButtonElement, | |
minimize: this.$(".minimize") as HTMLButtonElement, | |
maximize: this.$(".maximize") as HTMLButtonElement | |
}; | |
this.content = this.$(".content"); | |
this.resizeHandles = this.$$("[data-resize]"); | |
// initial positioning | |
this.moveTo(this.position); | |
// Bind event listeners | |
this.titleBar!.addEventListener("pointerdown", this.startDrag); | |
addEventListener("pointermove", this.onDrag); | |
addEventListener("pointerup", this.stopDrag); | |
this.resizeHandles.forEach((handle) => { | |
handle.addEventListener("pointerdown", this.startResize); | |
}); | |
addEventListener("pointermove", this.onResize); | |
addEventListener("pointerup", this.stopResize); | |
this.controls.minimize.addEventListener("click", this.minimize); | |
this.controls.maximize.addEventListener("click", this.maximize); | |
this.controls.close.addEventListener("click", this.close); | |
this.titleBar.addEventListener("dblclick", this.maximize); | |
this.on("pointerdown", this.focusWindow); | |
this.titleElement.addEventListener("keydown", this.onTitleKeyDown); | |
// Prevent text selection during drag/resize | |
this.on("dragstart", (e) => e.preventDefault()); | |
addEventListener("dragstart", (e) => e.preventDefault()); | |
// Add to manager | |
this.windowManager.addWindow(this); | |
} | |
disconnectedCallback() { | |
// Remove event listeners | |
this.titleBar!.removeEventListener("pointerdown", this.startDrag); | |
removeEventListener("pointermove", this.onDrag); | |
removeEventListener("pointerup", this.stopDrag); | |
this.resizeHandles.forEach((handle) => { | |
handle.removeEventListener("pointerdown", this.startResize); | |
}); | |
removeEventListener("pointermove", this.onResize); | |
removeEventListener("pointerup", this.stopResize); | |
this.controls.minimize.removeEventListener("click", this.minimize); | |
this.controls.maximize.removeEventListener("click", this.maximize); | |
this.controls.close.removeEventListener("click", this.close); | |
this.titleBar.removeEventListener("dblclick", this.maximize); | |
this.removeEventListener("pointerdown", this.focusWindow); | |
this.titleElement.removeEventListener("keydown", this.onTitleKeyDown); | |
// remove text selection prevention | |
document.body.style.userSelect = ""; | |
this.windowManager.removeWindow(this); | |
} | |
// Event listener methods using arrow functions to bind 'this' | |
focusWindow = () => { | |
this.windowManager?.updateWindowFocus(this); | |
return this; | |
}; | |
startDrag = (event: PointerEvent) => { | |
if (this.isMaximized || this.isResizing) return; | |
this.isDragging = true; | |
this.dragOffsetX = event.clientX - this.position.left; | |
this.dragOffsetY = event.clientY - this.position.top; | |
this.savePosition(); | |
this.emit("dragstart", { x: this.position.left, y: this.position.top }); | |
// Prevent text selection | |
document.body.style.userSelect = "none"; | |
}; | |
onDrag = (event: PointerEvent) => { | |
if (!this.isDragging || this.isResizing) return; | |
this.position.left = event.clientX - this.dragOffsetX; | |
this.position.top = event.clientY - this.dragOffsetY; | |
this.moveTo(this.position, true); | |
this.emit("drag", { x: this.position.left, y: this.position.top }); | |
}; | |
stopDrag = () => { | |
if (this.isDragging) { | |
this.isDragging = false; | |
this.refreshPosition().savePosition(); | |
this.emit("dragend", { x: this.position.left, y: this.position.top }); | |
// Re-enable text selection | |
document.body.style.userSelect = ""; | |
} | |
}; | |
startResize = (event: PointerEvent) => { | |
if ( | |
this.isResizing || | |
this.isDragging || | |
this.isMaximized || | |
this.isMinimized | |
) | |
return; | |
if ( | |
!this.isResizing && | |
!this.isDragging && | |
!this.isMaximized && | |
!this.isMinimized | |
) { | |
this.isResizing = true; | |
this.currentResizeHandle = event.target as Element; | |
this.startWidth = parseFloat(this.position.width); | |
this.startHeight = parseFloat(this.position.height); | |
this.startX = event.clientX; | |
this.startY = event.clientY; | |
this.savePosition(); | |
this.emit("resize:start", { | |
width: this.position.width, | |
height: this.position.height | |
}); | |
} | |
// Prevent text selection | |
document.body.style.userSelect = "none"; | |
}; | |
onResize = (event: PointerEvent) => { | |
if (this.isResizing && !this.isDragging) { | |
const deltaX = event.clientX - this.startX; | |
const deltaY = event.clientY - this.startY; | |
let width = this.startWidth; | |
let height = this.startHeight; | |
let left = this.position.left; | |
let top = this.position.top; | |
const handle = this.currentResizeHandle; | |
const minWidth = this.minWidth; | |
const minHeight = this.minHeight; | |
if (handle.dataset.resize.includes("r")) { | |
width = Math.max(minWidth, this.startWidth + deltaX); // min 200px | |
} | |
if (handle.dataset.resize.includes("l")) { | |
width = Math.max(minWidth, this.startWidth - deltaX); | |
left = this.position.left; | |
left += width === minWidth ? 0 : deltaX; | |
} | |
if (handle.dataset.resize.includes("b")) { | |
height = Math.max(minHeight, this.startHeight + deltaY); // min 150px | |
} | |
if (handle.dataset.resize.includes("t")) { | |
height = Math.max(minHeight, this.startHeight - deltaY); | |
top = this.position.top; | |
top += height === minHeight ? 0 : deltaY; | |
} | |
const position = { top, left, width, height }; | |
this.moveTo(position, true); | |
this.emit("resize", { ...position }); | |
} | |
}; | |
stopResize = () => { | |
if (this.isResizing) { | |
this.isResizing = false; | |
this.refreshPosition().savePosition(); | |
this.emit("resize:end", { ...this.position }); | |
this.currentResizeHandle = null; | |
// Re-enable text selection | |
document.body.style.userSelect = ""; | |
} | |
}; | |
minimize = () => { | |
if (this.isMinimized) return this.restore(); | |
this.isMinimized = true; | |
this.savePosition(); | |
this.classList.add("minimized"); | |
this.emit("minimize", { minimized: true }); | |
return this; | |
}; | |
maximize = () => { | |
if (this.isMaximized) return this.restore(); | |
this.isMaximized = true; | |
this.savePosition(); | |
this.classList.add("maximized"); | |
const width = window.innerWidth; | |
const height = window.innerHeight; | |
this.animateTo({ top: 0, left: 0, width, height }, { duration: 300 }, true); | |
this.emit("maximize", { maximized: true }); | |
return this; | |
}; | |
restore = () => { | |
if (!this.isMaximized && !this.isMinimized) return this; | |
const previous = this.history.pop(); | |
if (previous) { | |
this.position = { ...previous }; | |
this.animateTo(previous, { duration: 200 }, true); | |
} | |
this.isMaximized = false; | |
this.isMinimized = false; | |
this.classList.remove("maximized"); | |
this.classList.remove("minimized"); | |
this.emit("restore", { | |
maximized: this.isMaximized, | |
minimized: this.isMinimized | |
}); | |
return this; | |
}; | |
toggleMaximize = () => { | |
if (this.isMaximized) { | |
this.restore(); | |
} else { | |
this.maximize(); | |
} | |
return this; | |
}; | |
close = () => { | |
this.windowManager.removeWindow(this); | |
this.emit("close", { closed: true }); | |
return this.remove(); | |
}; | |
onTitleKeyDown = (e: KeyboardEvent) => { | |
if (e.key === "Enter" || e.key === " ") { | |
e.preventDefault(); | |
this.minimize(); | |
} | |
}; | |
moveTo(position: Position, noSave?: boolean) { | |
if (!noSave) this.savePosition(); | |
position ??= this.position; | |
const t = `${position.top}px`; | |
const l = `${position.left}px`; | |
const w = `${position.width}px`; | |
const h = `${position.height}px`; | |
const z = position.zIndex ?? this.style.getPropertyValue("--z"); | |
this.style.setProperty("--x", l); | |
this.style.setProperty("--y", t); | |
this.style.setProperty("--width", w); | |
this.style.setProperty("--height", h); | |
z && this.style.setProperty("--z", z); | |
return this; | |
} | |
animateTo( | |
position?: Position, | |
options?: Parameters<typeof HTMLElement.animate>[1], | |
noSave?: boolean | |
): this { | |
if (!noSave) this.savePosition(); | |
position ??= this.position; | |
const top = `${parseFloat(position.top + "")}px`; | |
const left = `${parseFloat(position.left + "")}px`; | |
const width = `${parseFloat(position.width + "")}px`; | |
const height = `${parseFloat(position.height + "")}px`; | |
this.animate( | |
{ "--x": left, "--y": top, "--width": width, "--height": height }, | |
{ fill: "both", duration: 200, easing: "ease-in", ...options } | |
); | |
} | |
refreshPosition() { | |
const style = getComputedStyle(this); | |
const width = parseFloat(style.width); | |
const height = parseFloat(style.height); | |
const top = parseFloat(style.top); | |
const left = parseFloat(style.left); | |
const zIndex = parseFloat(style.zIndex); | |
this.position = { top, left, width, height, zIndex }; | |
return this; | |
} | |
/** | |
* Save current position to history for restoring | |
*/ | |
savePosition(position?: Position, noSave?: boolean) { | |
if (this.history.length > this.historyLimit) { | |
const history = this.history.slice(-(this.historyLimit - 5)); | |
this.history.length = 0; | |
this.history.push(...history); | |
} | |
position ??= { ...this.position }; | |
this.history.push(position); | |
return this; | |
} | |
/** | |
* Restore the last saved position | |
* @returns {WindowComponent} for chaining | |
*/ | |
restorePosition(index = 1): this { | |
if (this.history.length > 0) { | |
let previous = null; | |
while (index-- > 0) { | |
previous = this.history.pop(); | |
if (previous) { | |
this.animateTo(previous, { duration: 200 }); | |
this.position = { ...previous }; | |
this.emit("position:restored", { position }); | |
} | |
} | |
} | |
return this; | |
} | |
/** | |
* Set the window title | |
* @param title Title string | |
* @returns this for chaining | |
*/ | |
setTitle(title: string): this { | |
if (this.titleElement) this.titleElement.textContent = title; | |
return this; | |
} | |
/** | |
* Add a view to the window | |
* @param view HTMLElement to add as a view | |
* @returns this for chaining | |
*/ | |
addView(view: HTMLElement): this { | |
if (this.content) { | |
this.content!.appendChild(view); | |
this.emit("view:create", { view }); | |
} | |
return this; | |
} | |
/** | |
* Remove a view from the window | |
* @param view HTMLElement to remove | |
* @returns this for chaining | |
*/ | |
removeView(view: HTMLElement): this { | |
if (this.content) { | |
this.content!.removeChild(view); | |
this.emit("view:remove", { view }); | |
} | |
return this; | |
} | |
static getSpriteSheet() { | |
return dedent` | |
<svg xmlns="http://www.w3.org/2000/svg" id="icon-spritesheet" width="0" height="0" aria-hidden="true" class="hidden"><defs><symbol xmlns="http://www.w3.org/2000/svg" id="i-warning" fill="none" viewBox="0 0 24 24"><path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m5.2 16.2 5.08-10.13a1.93 1.93 0 0 1 3.45 0l5.06 10.13a1.93 1.93 0 0 1-1.72 2.8H6.93a1.93 1.93 0 0 1-1.72-2.8ZM12 10v2"/><path stroke="currentColor" d="M12.5 16a.5.5 0 1 1-1 0 .5.5 0 0 1 1 0Z"/></symbol><symbol xmlns="http://www.w3.org/2000/svg" id="i-git-fork" fill="currentColor" stroke="currentColor" stroke-width="0" viewBox="0 0 24 24"><path d="M21 8.22a3.74 3.74 0 0 0-5.96-3.02 3.74 3.74 0 0 0 1.16 6.58 2.99 2.99 0 0 1-2.67 1.67h-2.99a4.46 4.46 0 0 0-2.99 1.17V7.4a3.74 3.74 0 1 0-1.49 0v9.12a3.78 3.78 0 1 0 1.82.1 2.99 2.99 0 0 1 2.66-1.67h3a4.48 4.48 0 0 0 4.22-3.04A3.74 3.74 0 0 0 21 8.22zM4.58 3.74a2.24 2.24 0 1 1 4.48 0 2.24 2.24 0 0 1-4.48 0zm4.48 16.44a2.24 2.24 0 1 1-4.48 0 2.24 2.24 0 0 1 4.48 0zm8.22-9.72a2.24 2.24 0 1 1 0-4.48 2.24 2.24 0 0 1 0 4.48z"/></symbol><symbol xmlns="http://www.w3.org/2000/svg" id="i-github-copilot" fill="currentColor" class="size-3.5" viewBox="0 0 16 15"><path d="M5.5 8.75a.75.75 0 1 1 1.5 0v1.5a.75.75 0 1 1-1.5 0v-1.5Zm5 0a.75.75 0 1 0-1.5 0v1.5a.75.75 0 1 0 1.5 0v-1.5Z"/><path fill-rule="evenodd" d="M5.04.03C6.05-.07 7.37.01 8 .92c.63-.91 1.95-.98 2.96-.89 1.15.12 2.13.51 2.67 1.1.95 1.03 1 3.21.54 4.41.04.2.09.41.12.63C15.14 6.4 16 7.6 16 8.45v1.62c0 .45-.21.86-.58 1.14-2.12 1.57-4.75 2.8-7.42 2.8S2.7 12.77.58 11.2A1.42 1.42 0 0 1 0 10.07V8.45C0 7.6.86 6.4 1.71 6.17c.03-.22.08-.43.12-.63-.46-1.2-.41-3.38.54-4.41A4.29 4.29 0 0 1 5.04.03ZM8 12.5c1.94 0 3.85-.86 5-1.5V6.66c-1.86.72-4 .35-5-1.32-1 1.67-3.14 2.04-5 1.32V11a10.9 10.9 0 0 0 5 1.5Zm-3-7c1.64 0 2-1.3 2-2.48 0-1.1-.19-1.52-1.47-1.52-2.22 0-2.48.75-2.48 2.5 0 1.1.3 1.5 1.95 1.5Zm6 0c-1.64 0-2-1.3-2-2.48 0-1.1.19-1.52 1.47-1.52 2.22 0 2.48.75 2.48 2.5 0 1.1-.3 1.5-1.95 1.5Z" clip-rule="evenodd"/></symbol><symbol xmlns="http://www.w3.org/2000/svg" id="i-left-arrow" fill="none" viewBox="0 0 24 24"><path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m15 7-5 5 5 5"/></symbol><symbol xmlns="http://www.w3.org/2000/svg" id="i-right-arrow" fill="none" viewBox="0 0 24 24"><path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m10 7 5 5-5 5"/></symbol><symbol xmlns="http://www.w3.org/2000/svg" id="i-show-apps" fill="none" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M12.5 6a.5.5 0 1 1-1 0 .5.5 0 0 1 1 0Zm0 6a.5.5 0 1 1-1 0 .5.5 0 0 1 1 0Zm6-6a.5.5 0 1 1-1 0 .5.5 0 0 1 1 0Zm0 6a.5.5 0 1 1-1 0 .5.5 0 0 1 1 0Zm-12-6a.5.5 0 1 1-1 0 .5.5 0 0 1 1 0Zm0 6a.5.5 0 1 1-1 0 .5.5 0 0 1 1 0Zm6 6a.5.5 0 1 1-1 0 .5.5 0 0 1 1 0Zm6 0a.5.5 0 1 1-1 0 .5.5 0 0 1 1 0Zm-12 0a.5.5 0 1 1-1 0 .5.5 0 0 1 1 0Z"/></symbol><symbol xmlns="http://www.w3.org/2000/svg" id="i-explorer" fill="none" viewBox="0 0 24 24"><path stroke="currentColor" d="M8 7H5a1 1 0 0 0-1 1v12a1 1 0 0 0 1 1h10a1 1 0 0 0 1-1v-3m3.7-10.3-3.4-3.4a1 1 0 0 0-.71-.3H9a1 1 0 0 0-1 1v12a1 1 0 0 0 1 1h10a1 1 0 0 0 1-1V7.41a1 1 0 0 0-.3-.7zM7 103a2 2 0 1 0 0-4 2 2 0 0 0 0 4z"/></symbol><symbol xmlns="http://www.w3.org/2000/svg" id="i-search" fill="none" viewBox="0 0 24 24"><path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" d="m3 22 6-6m-2-5a7 7 0 1 0 14 0 7 7 0 0 0-14 0z"/></symbol><symbol xmlns="http://www.w3.org/2000/svg" id="i-vcs" fill="none" viewBox="0 0 24 24"><path stroke="currentColor" d="M6 1a2 2 0 1 0 0 4 2 2 0 0 0 0-4m0 4.5v10m10-6a2 2 0 1 0 0-4 2 2 0 0 0 0 4zm0 0a3 3 0 0 1-3 3H9a3 3 0 0 0-3 3m0 0a2 2 0 1 0 0 4 2 2 0 0 0 0-4z"/></symbol><symbol xmlns="http://www.w3.org/2000/svg" id="i-debug" fill="none" viewBox="0 143 24 24"><path fill="currentColor" d="M11.5 210.03a.75.75 0 0 0-1-1.12l1 1.12zm-8-1.12a.75.75 0 0 0-1 1.12l1-1.12zm6.97 6.15a.75.75 0 1 0 1-1.12l-1 1.12zm-7.93-1.12a.75.75 0 1 0 .99 1.12l-1-1.12zm7.02-.37-.64-.4.64.4zm-5.12 0 .64-.4-.64.4zM3 161.25a.75.75 0 0 0 0 1.5v-1.5zm8 1.5a.75.75 0 0 0 0-1.5v1.5zM8 147l.37-.65a.75.75 0 0 0-1.12.65H8zm14 8 .37.65a.75.75 0 0 0 0-1.3L22 155zm-14.75 0a.75.75 0 0 0 1.5 0h-1.5zm5.38 4.5a.75.75 0 1 0 .74 1.3l-.74-1.3zM7 157.74A2.25 2.25 0 0 1 9.25 160h1.5A3.75 3.75 0 0 0 7 156.25v1.5zm0-1.5A3.75 3.75 0 0 0 3.25 160h1.5A2.25 2.25 0 0 1 7 157.75v-1.5zm2.62 3.3a5.22 5.22 0 0 1-2.62.7v1.5a6.73 6.73 0 0 0 3.38-.9l-.76-1.3zm-.37.45v.2h1.5v-.2h-1.5zm0 .2v1.8h1.5v-1.8h-1.5zm-2.25.05a5.22 5.22 0 0 1-2.62-.7l-.76 1.3a6.73 6.73 0 0 0 3.38.9v-1.5zM4.75 162v-1.8h-1.5v1.8h1.5zm0-1.8v-.2h-1.5v.2h1.5zm5.75-1.29a5.2 5.2 0 0 1-.88.64l.76 1.3a7 7 0 0 0 1.12-.82l-1-1.12zm-6.12.64a5.21 5.21 0 0 1-.88-.64l-1 1.12a7 7 0 0 0 1.12.82l.76-1.3zm4.86 4.7c.45.21.86.49 1.23.81l1-1.12a6.77 6.77 0 0 0-1.6-1.05l-.63 1.36zm.01-2.25c0 .43-.12.83-.33 1.18l1.27.78a3.74 3.74 0 0 0 .56-1.96h-1.5zm-.33 1.18A2.25 2.25 0 0 1 7 164.25v1.5a3.75 3.75 0 0 0 3.2-1.79l-1.28-.78zm-5.4 1.88a5.25 5.25 0 0 1 1.24-.81l-.64-1.36a6.77 6.77 0 0 0-1.58 1.05l.99 1.12zm3.48-.81c-.81 0-1.52-.43-1.92-1.07l-1.28.78a3.75 3.75 0 0 0 3.2 1.79v-1.5zm-1.92-1.07a2.23 2.23 0 0 1-.33-1.18h-1.5c0 .72.2 1.4.56 1.96l1.27-.78zM4 161.25H3v1.5h1v-1.5zm7 0h-1v1.5h1v-1.5zm-3.37-13.6 14 8 .74-1.3-14-8-.74 1.3zM8.75 155v-8h-1.5v8h1.5zm12.88-.65-9 5.14.74 1.3 9-5.14-.74-1.3z"/></symbol><symbol xmlns="http://www.w3.org/2000/svg" id="i-extensions" fill="none" viewBox="0 0 24 24"><path stroke="currentColor" d="M2 15h8m-8 0v7a1 1 0 0 0 1 1h7m-8-8V8a1 1 0 0 1 1-1h6a1 1 0 0 1 1 1v7m0 0v8m0-8h7a1 1 0 0 1 1 1v6a1 1 0 0 1-1 1h-7m4-11h6a1 1 0 0 0 1-1V5a1 1 0 0 0-1-1h-6a1 1 0 0 0-1 1v6a1 1 0 0 0 1 1z"/></symbol><symbol id="i-settings" fill="none" viewBox="0 0 24 24"><path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" d="M10.32 2.32c.43-1.76 2.93-1.76 3.35 0a1.72 1.72 0 0 0 2.58 1.06c1.54-.94 3.3.83 2.37 2.37a1.72 1.72 0 0 0 1.06 2.58c1.76.42 1.76 2.92 0 3.34a1.72 1.72 0 0 0-1.06 2.58c.94 1.54-.83 3.3-2.37 2.37a1.72 1.72 0 0 0-2.58 1.06c-.42 1.76-2.92 1.76-3.34 0a1.72 1.72 0 0 0-2.58-1.06c-1.54.94-3.3-.83-2.37-2.37a1.72 1.72 0 0 0-1.06-2.58c-1.76-.42-1.76-2.92 0-3.34a1.72 1.72 0 0 0 1.06-2.58c-.94-1.54.83-3.3 2.37-2.37 1 .61 2.3.07 2.58-1.06zM15 10a3 3 0 1 1-6 0 3 3 0 0 1 6 0z"/></symbol><symbol id="i-user-profile" fill="none" viewBox="0 0 24 24"><path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" d="M5.12 17.8A13.94 13.94 0 0 1 12 16c2.5 0 4.85.66 6.88 1.8M15 10a3 3 0 1 1-6 0 3 3 0 0 1 6 0zm6 2a9 9 0 1 1-18 0 9 9 0 0 1 18 0z"/></symbol></defs></svg> | |
`; | |
} | |
} | |
// customElements.define(WindowComponent.tagName, WindowComponent); | |
WindowComponent.define(); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@use postcss-nested; | |
@use postcss-color-function; | |
@use postcss-apply; | |
@use postcss-mixins; | |
@use postcss-extend; | |
@use postcss-simple-vars; | |
@use postcss-discard-comments; | |
@use postcss-custom-media; | |
@use postcss-media-minmax; | |
@use postcss-conditionals; | |
@use postcss-for; | |
@use postcss-nested-ancestors; | |
body { | |
margin: 0; | |
padding: 0; | |
height: 100vh; | |
width: 100vw; | |
position: relative; | |
background: #f5f5f5; | |
overflow: hidden; | |
font-family: system-ui, sans-serif; | |
font-size: 1rem; /* 1rem = 16px */ | |
--window-border-color: #6667; | |
--window-border-radius: 0.4rem; | |
--window-focused-shadow: 0 7px 18px -8px rgba(0, 0, 0, 0.33); | |
--window-blurred-filter: blur(2px); | |
--window-title-bar-background: #f0f0f0; | |
--window-tab-background: #f9f9f9; | |
--window-tab-hover-background: #eee; | |
--window-tab-active-border: #007bff; | |
--window-tab-active-background: #fff; | |
--window-tab-transition-duration: 0.15s; | |
--window-tab-transition-ease: ease; | |
} | |
:root { | |
color-scheme: light dark; | |
} | |
:is(html, body, :root > main):not(.light, .dark) { | |
color-scheme: light dark; | |
background: none; | |
background-color: light-dark(#eee, #111420); | |
} | |
.light { | |
color-scheme: light; | |
} | |
.dark { | |
color-scheme: dark; | |
} | |
/* :is(html, body, main):not(.light, .dark) { | |
background-color: light-dark(#9ca3af, #111827) !important; | |
} */ | |
@media (prefers-color-scheme: light) { | |
:not(.dark) { | |
color-scheme: light; | |
--tw-bg-opacity: 1 !important; | |
/* background-color: rgb(241 245 249 / var(--tw-bg-opacity))!important; */ | |
} | |
} | |
@media (prefers-color-scheme: dark) { | |
:not(.light) { | |
color-scheme: dark; | |
--tw-bg-opacity: 1 !important; | |
/* background-color: rgb(241 245 249 / var(--tw-bg-opacity))!important; */ | |
} | |
} | |
/* #region chrome */ | |
.browser { | |
color-scheme: light dark; | |
position: relative; | |
transition-property: width, height; | |
transition-duration: 500ms; | |
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); | |
--tw-bg-opacity: 1; | |
--tw-text-opacity: 1; | |
--tw-border-opacity: 1; | |
--navigator-padding-x: 1rem; | |
--navigator-padding-y: 0.375rem; | |
--navigator-column-gap: 0.5rem; | |
--btn-size: 0.625rem; | |
--btn-column-gap: 0.375rem; | |
--btn-background-color: light-dark(#64748b, #94a3b8); | |
--img-border-style: dashed; | |
--img-border-color: rgb(148 163 184 / 0.5); | |
--img-size: 1rem; | |
--img-padding: 1px; | |
--tab-padding: 3px 0.625rem; | |
--tab-max-width: 10rem; | |
--tab-panel-margin-bottom: 2rem; | |
--tab-panel-padding-bottom: 1rem; | |
--tab-border-color: light-dark(rgb(2 6 23 / 0.075), rgb(2 6 23 / 0.3)); | |
--tab-border-bottom-color: light-dark(rgb(2 6 23 / 0.15), rgb(2 6 23 / 0.35)); | |
--tab-background-color: light-dark( | |
rgb(241 245 249 / var(--tw-bg-opacity)), | |
rgb(51 65 85 / var(--tw-bg-opacity)) | |
); | |
--tab-color: light-dark( | |
rgb(148 163 184 / var(--tw-text-opacity)), | |
rgb(100 116 139 / 0.8) | |
); | |
--top-bar-gradient-from-color: light-dark( | |
#f3f4f6, | |
rgb(30 41 59 / var(--tw-bg-opacity)) | |
); | |
--top-bar-gradient-to-color: light-dark( | |
#f8fafc, | |
rgb(51 65 85 / var(--tw-bg-opacity)) | |
); | |
--tab-panel-border-color: light-dark( | |
rgb(226 232 240 / var(--tw-border-opacity)), | |
rgb(15 23 42 / 0.5) | |
); | |
--tab-panel-background-color: light-dark( | |
rgb(255 255 255 / var(--tw-bg-opacity)), | |
rgb(30 41 59 / var(--tw-bg-opacity)) | |
); | |
--tab-panel-text-color: light-dark( | |
rgb(100 116 139 / var(--tw-text-opacity)), | |
rgb(226 232 240 / var(--tw-text-opacity)) | |
); | |
--content-max-height: 36rem; | |
--window-min-height: 10rem; | |
--window-max-height: 40rem; | |
--window-width: auto; | |
--window-height: auto; | |
--window-min-width: 25rem; | |
--window-max-width: 100%; | |
min-width: var(--window-min-width); | |
max-width: var(--window-max-width); | |
min-height: var(--window-min-height); | |
max-height: var(--window-max-height); | |
&.fullscreen, | |
&.maximized { | |
/* --btn-size: 0.725rem; */ | |
--content-max-height: 100%; | |
--tab-max-width: 15rem; | |
--window-border-radius: 0; | |
--window-height: 100vh !important; | |
--window-min-height: 100vh !important; | |
--window-width: 100vw !important; | |
--window-min-width: 100vw !important; | |
--window-x: 0; | |
--window-y: 0; | |
--window-z: 99999; | |
position: fixed; | |
inset: 0; | |
& .window { | |
/* height: 100%; | |
width: 100%; */ | |
--window-min-height: 100vh !important; | |
--window-border-radius: 0 !important; | |
& .tab-panel-container { | |
z-index: 100000; | |
& [role="tabpanel"] { | |
overscroll-behavior: contain; | |
} | |
} | |
} | |
} | |
& .window { | |
--tw-shadow: 0 20px 25px -5px rgb(0 0 0 / 0.1), | |
0 8px 10px -6px rgb(0 0 0 / 0.1); | |
--tw-shadow-colored: 0 20px 25px -5px var(--tw-shadow-color), | |
0 8px 10px -6px var(--tw-shadow-color); | |
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), | |
var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); | |
& .boundary { | |
--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 | |
var(--tw-ring-offset-width) var(--tw-ring-offset-color); | |
--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 | |
calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color); | |
box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), | |
var(--tw-shadow, 0 0 #0000); | |
--tw-ring-color: rgb(15 23 42 / 0.05); | |
border-radius: var(--window-border-radius, 0); | |
} | |
& .top-bar { | |
--tw-gradient-from-color: var(--top-bar-gradient-from-color); | |
--tw-gradient-to-color: var(--top-bar-gradient-to-color); | |
--tw-gradient-from: var(--tw-gradient-from-color) | |
var(--tw-gradient-from-position, 0%); | |
--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to); | |
--tw-gradient-to: var(--tw-gradient-to-color) | |
var(--tw-gradient-to-position, 100%); | |
background-image: linear-gradient( | |
to bottom, | |
var(--tw-gradient-stops) | |
) !important; | |
--tw-bg-opacity: 0; | |
background-color: light-dark( | |
#f3f4f6, | |
rgb(51 65 85 / var(--tw-bg-opacity)) | |
) !important; | |
& *::selection { | |
background-color: light-dark( | |
rgb(51 65 85 / 0.05), | |
rgb(255 255 255 / 0.05) | |
); | |
} | |
@media (prefers-color-scheme: dark) { | |
& { | |
--tw-bg-opacity: 1; | |
background-image: none !important; | |
} | |
} | |
&:is(.fullscreen &) { | |
border-radius: 0 !important; | |
--window-width: 100vw; | |
--window-min-width: 100%; | |
} | |
& .navigator { | |
display: flex; | |
min-width: 100%; | |
flex-grow: 1; | |
flex-wrap: nowrap; | |
align-items: center; | |
justify-content: space-around; | |
column-gap: var(--navigator-column-gap); | |
padding: var(--navigator-padding-y) var(--navigator-padding-x); | |
/* @apply grid [grid-template-columns:5rem_3fr_0rem] sm:[grid-template-columns:5.5rem_2fr_1rem] lg:[grid-template-columns:9rem_2fr_6rem]; */ | |
& .traffic-signal { | |
position: relative; | |
display: flex; | |
flex-wrap: nowrap; | |
justify-content: flex-start; | |
column-gap: var(--btn-column-gap); | |
padding-right: 0.5rem; | |
& .btn { | |
--color: light-dark(#64748b, #94a3b8); | |
--tw-ring-color: var(--color); | |
--btn-background-color: var(--color); | |
background-color: var(--btn-background-color, var(--color)); | |
width: var(--btn-size); | |
height: var(--btn-size); | |
border-radius: 9999px; | |
--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 | |
var(--tw-ring-offset-width) var(--tw-ring-offset-color); | |
--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 | |
calc(2.875px + var(--tw-ring-offset-width)) var(--tw-ring-color); | |
box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), | |
var(--tw-shadow, 0 0 #0000); | |
--tw-ring-inset: inset; | |
--tw-drop-shadow: drop-shadow(0 1px 1px rgb(0 0 0 / 0.05)); | |
--tw-saturate: saturate(1.3); | |
filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) | |
var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) | |
var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow); | |
transition-property: all; | |
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); | |
transition-duration: 700ms; | |
animation-duration: 700ms; | |
animation-timing-function: cubic-bezier(0, 0, 0.2, 1); | |
will-change: background-color, filter, opacity, box-shadow, --color, | |
--btn-size, --btn-background-color; | |
&.close { | |
--color: light-dark(#e3342f, #ec6a5f); | |
} | |
&:is(.minimize, .disabled) { | |
--tw-contrast: contrast(0) !important; | |
--tw-saturate: saturate(0) !important; | |
filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) | |
var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) | |
var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow) !important; | |
} | |
&.minimize { | |
/* --color: #F4BF75; */ | |
--color: light-dark(#f4bf50, #f4bf75); | |
} | |
@media (min-width: 768px) { | |
&.minimize { | |
--tw-contrast: contrast(1) !important; | |
--tw-saturate: saturate(1.3) !important; | |
} | |
} | |
&.zoom { | |
/* --color: #61C454; */ | |
--color: light-dark(#61c454, #a8cc8c); | |
} | |
} | |
.group:hover & .btn, | |
& .btn:hover { | |
--tw-bg-opacity: 1 !important; | |
--btn-background-color: rgb( | |
229 231 235 / var(--tw-bg-opacity) | |
) !important; | |
} | |
@media (prefers-color-scheme: dark) { | |
& .btn:hover, | |
.group:hover & .btn { | |
--tw-bg-opacity: 1 !important; | |
--btn-background-color: rgb( | |
51 65 85 / var(--tw-bg-opacity) | |
) !important; | |
} | |
} | |
} | |
@media (min-width: 1024px) { | |
& .traffic-signal { | |
left: -0.175rem; | |
top: -0.6rem; | |
column-gap: 7px !important; | |
padding-right: 2.5rem; | |
} | |
} | |
@media (min-width: 1536px) { | |
& .traffic-signal { | |
column-gap: 0.5rem !important; | |
} | |
} | |
& .btn:not(.traffic-signal .btn) { | |
--tw-brightness: brightness(1); | |
--tw-contrast: contrast(1); | |
--tw-drop-shadow: drop-shadow(0 1px 1px rgb(0 0 0 / 0.05)); | |
filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) | |
var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) | |
var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow); | |
transition-property: filter, opacity; | |
transition-duration: 500ms; | |
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); | |
animation-duration: 500ms; | |
animation-timing-function: cubic-bezier(0.4, 0, 0.2, 1); | |
will-change: opacity, filter; | |
& svg { | |
margin-left: 0.5rem; | |
flex: none; | |
--tw-text-opacity: 1; | |
color: rgb(148 163 184 / var(--tw-text-opacity)); | |
} | |
@media (prefers-color-scheme: dark) { | |
& svg { | |
--tw-text-opacity: 1; | |
color: rgb(100 116 139 / var(--tw-text-opacity)); | |
} | |
} | |
&:hover { | |
opacity: 1 !important; | |
--tw-brightness: brightness(0.75); | |
--tw-contrast: contrast(1.5); | |
--tw-drop-shadow: drop-shadow(0 4px 3px rgb(0 0 0 / 0.07)) | |
drop-shadow(0 2px 2px rgb(0 0 0 / 0.06)); | |
} | |
} | |
@media (prefers-color-scheme: dark) { | |
& .btn:not(.traffic-signal .btn):hover { | |
--tw-brightness: brightness(1.25) !important; | |
--tw-contrast: contrast(1.25) !important; | |
} | |
} | |
& .url { | |
position: relative; | |
top: -0.5px; | |
height: 1.25rem; | |
width: 83.333333%; | |
border-collapse: collapse; | |
overflow: hidden; | |
text-overflow: ellipsis; | |
white-space: nowrap; | |
border-radius: 0.75rem; | |
border-style: none; | |
--tw-bg-opacity: 1 !important; | |
background-color: rgb(241 245 249 / var(--tw-bg-opacity)) !important; | |
padding: 13px 0.5rem 13px 0.5rem; | |
outline: 2px solid transparent; | |
outline-offset: 2px; | |
text-align: left; | |
font-size: 0.7rem; | |
font-weight: 500; | |
line-height: 1.25rem; | |
transition-property: all; | |
transition-duration: 300ms; | |
transition-timing-function: cubic-bezier(0.4, 0, 1, 1); | |
animation-duration: 300ms; | |
--tw-text-opacity: 1; | |
color: rgb(100 116 139 / var(--tw-text-opacity)); | |
--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 | |
var(--tw-ring-offset-width) var(--tw-ring-offset-color); | |
--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 | |
calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color); | |
--tw-ring-color: rgb(15 23 42 / 0.05) !important; | |
box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), | |
var(--tw-shadow, 0 0 #0000); | |
&:focus { | |
outline: 2px solid transparent !important; | |
outline-offset: 2px !important; | |
--tw-ring-color: rgb(15 23 42 / 0.4) !important; | |
--tw-text-opacity: 1 !important; | |
color: rgb(15 23 42 / var(--tw-text-opacity)) !important; | |
} | |
} | |
@media (min-width: 640px) { | |
& .url { | |
--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 | |
var(--tw-ring-offset-width) var(--tw-ring-offset-color); | |
--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 | |
calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color); | |
box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), | |
var(--tw-shadow, 0 0 #0000); | |
} | |
} | |
@media not all and (min-width: 768px) { | |
& .url { | |
padding-left: 0.5rem; | |
padding-right: 0.5rem; | |
} | |
} | |
@media not all and (min-width: 640px) { | |
& .url { | |
font-size: 0.7rem; | |
} | |
} | |
@media (min-width: 768px) { | |
& .url { | |
font-size: 0.8rem !important; | |
/* line-height: 1.25rem; */ | |
padding-top: 0.875rem; | |
padding-bottom: 0.875rem; | |
} | |
& .secure::before { | |
top: calc(50% + 0.125rem); | |
} | |
} | |
@media (min-width: 1024px) { | |
& .url { | |
padding-top: 1rem; | |
padding-bottom: 1rem; | |
font-size: 0.85rem !important; | |
line-height: 1.25rem; | |
&:is(.secure &) { | |
padding-left: 2rem !important; | |
} | |
} | |
& .secure::before { | |
top: calc(50% + 0rem); | |
} | |
} | |
@media (min-width: 1280px) { | |
& .url { | |
font-size: 0.9rem !important; | |
line-height: 1.25rem; | |
padding-top: 1.125rem; | |
padding-bottom: 1.125rem; | |
} | |
& .secure::before { | |
top: calc(50% + 0rem); | |
} | |
} | |
@media (prefers-color-scheme: dark) { | |
& .url { | |
--tw-text-opacity: 1; | |
--tw-ring-color: rgb(100 116 139 / 0.2) !important; | |
color: rgb(100 116 139 / var(--tw-text-opacity)); | |
--tw-bg-opacity: 1 !important; | |
background-color: rgb(30 41 59 / var(--tw-bg-opacity)) !important; | |
&:focus { | |
--tw-ring-color: rgb(203 213 225 / 0.2) !important; | |
--tw-text-opacity: 1 !important; | |
color: rgb(203 213 225 / var(--tw-text-opacity)) !important; | |
} | |
} | |
} | |
& .secure { | |
position: relative; | |
& .url { | |
padding-left: 26px !important; | |
} | |
&::before { | |
position: absolute; | |
left: 0.5rem; | |
top: 50%; | |
z-index: 10; | |
margin-left: 1px; | |
width: 0.75rem; | |
height: 0.75rem; | |
--tw-translate-y: -33.333333%; | |
transform: translate(var(--tw-translate-x), var(--tw-translate-y)) | |
rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) | |
skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) | |
scaleY(var(--tw-scale-y)); | |
cursor: pointer; | |
opacity: 0.7; | |
--tw-drop-shadow: drop-shadow(0 1px 1px rgb(0 0 0 / 0.05)); | |
--tw-saturate: saturate(0); | |
filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) | |
var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) | |
var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow); | |
transition-duration: 700ms; | |
transition-timing-function: cubic-bezier(0.4, 0, 1, 1); | |
--tw-content: ""; | |
content: var(--tw-content); | |
animation-duration: 700ms; | |
animation-timing-function: cubic-bezier(0.4, 0, 1, 1); | |
background-image: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20' fill='%2361C454'><path fill-rule='evenodd' d='M5 9V7a5 5 0 0110 0v2a2 2 0 012 2v5a2 2 0 01-2 2H5a2 2 0 01-2-2v-5a2 2 0 012-2zm8-2v2H7V7a3 3 0 016 0z' clip-rule='evenodd'/></svg>"); | |
will-change: opacity, filter; | |
transition-property: opacity, filter !important; | |
} | |
.group:hover &::before { | |
opacity: 1 !important; | |
--tw-drop-shadow: drop-shadow(0 1px 2px rgb(0 0 0 / 0.1)) | |
drop-shadow(0 1px 1px rgb(0 0 0 / 0.06)) !important; | |
filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) | |
var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) | |
var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow) !important; | |
} | |
@media (min-width: 640px) { | |
&::before { | |
left: 0.5rem; | |
top: 48%; | |
margin: 0; | |
} | |
} | |
@media (min-width: 768px) { | |
&::before { | |
top: 50%; | |
--tw-translate-y: -50%; | |
transform: translate(var(--tw-translate-x), var(--tw-translate-y)) | |
rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) | |
skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) | |
scaleY(var(--tw-scale-y)); | |
} | |
} | |
@media (min-width: 1024px) { | |
&::before { | |
left: 0.625rem; | |
margin-top: 1px; | |
width: 0.875rem !important; | |
height: 0.875rem !important; | |
--tw-translate-y: -50% !important; | |
transform: translate(var(--tw-translate-x), var(--tw-translate-y)) | |
rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) | |
skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) | |
scaleY(var(--tw-scale-y)) !important; | |
} | |
} | |
&:has(> .url:hover)::before { | |
opacity: 1; | |
--tw-drop-shadow: drop-shadow(0 1px 1px rgb(0 0 0 / 0.05)); | |
filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) | |
var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) | |
var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow); | |
} | |
&:has(> .url:focus)::before { | |
opacity: 1; | |
--tw-drop-shadow: drop-shadow(0 1px 1px rgb(0 0 0 / 0.05)); | |
--tw-saturate: saturate(0.85); | |
filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) | |
var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) | |
var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow); | |
} | |
@media (prefers-color-scheme: dark) { | |
&:has(> .url:focus)::before { | |
--tw-saturate: saturate(0.75); | |
filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) | |
var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) | |
var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow); | |
} | |
} | |
} | |
} | |
@media (min-width: 768px) { | |
& .navigator { | |
padding-top: 0.55rem !important; | |
padding-bottom: 0.5rem !important; | |
} | |
} | |
@media (min-width: 1024px) { | |
& .navigator { | |
padding-top: 0.75rem !important; | |
padding-bottom: 0.75rem !important; | |
} | |
} | |
& [role="tablist"] { | |
margin-bottom: 0 !important; | |
display: flex; | |
flex: 1 1 auto; | |
flex-grow: 1; | |
flex-shrink: 1; | |
flex-direction: row; | |
flex-wrap: nowrap; | |
overflow-x: auto; | |
overflow-y: hidden; | |
justify-items: stretch; | |
justify-content: stretch; | |
justify-self: stretch; | |
align-items: stretch; | |
align-content: stretch; | |
align-self: stretch; | |
overscroll-behavior: contain; | |
padding-bottom: 0 !important; | |
font-size: 0.65rem; | |
line-height: 1.25rem; | |
position: relative; | |
scroll-padding-right: 3rem; | |
scroll-snap-type: x; | |
--tab-max-width: 12rem; | |
--inner-max-width: calc(var(--tab-width) - 1rem); | |
--radius-l: 0; | |
--radius-r: 0; | |
&::-webkit-scrollbar { | |
width: 0 !important; | |
height: 0 !important; | |
} | |
& [role="tab"] { | |
--tw-bg-opacity: 1; | |
--tw-text-opacity: 1; | |
margin-left: -1px; | |
-webkit-user-select: none; | |
user-select: none; | |
border-top: 1px solid | |
var(--tab-border-top-color, var(--tab-border-color)); | |
border-left: 1px solid | |
var(--tab-border-left-color, var(--tab-border-color)); | |
border-right: 1px solid | |
var(--tab-border-right-color, var(--tab-border-color)); | |
border-bottom: 1px solid | |
var(--tab-border-bottom-color, var(--tab-border-color)); | |
background-color: var(--tab-background-color); | |
} | |
/* &::after { | |
content: ""; | |
position: absolute; | |
inset: 0; | |
width: 100%; | |
height: 100%; | |
display: block; | |
opacity: 1; | |
border-top: 1px solid transparent; | |
border-left: 1px solid var(--tab-border-color); | |
border-bottom: 1px solid var(--tab-border-color); | |
background-color: var(--tab-background-color) !important; | |
z-index: 2; | |
} */ | |
& [role="tab"] { | |
z-index: 5; | |
max-width: var(--tab-max-width); | |
cursor: pointer; | |
padding: var(--tab-padding); | |
color: var(--tab-color); | |
display: flex; | |
flex-grow: 0; | |
align-items: center; | |
justify-content: stretch; | |
justify-self: stretch; | |
border-top-left-radius: var(--radius-l) !important; | |
border-top-right-radius: var(--radius-r) !important; | |
background-color: var(--tab-background-color) !important; | |
border-color: var(--tab-border-color) !important; | |
transition: all 150ms cubic-bezier(0.4, 0, 1, 1), | |
background-color 300ms ease-in; | |
animation-duration: 200ms; | |
animation-timing-function: cubic-bezier(0.4, 0, 1, 1); | |
will-change: border-radius, color, border-color, text-decoration-color, | |
fill, stroke, font-size; | |
&:not([aria-selected="true"]) { | |
--tw-bg-opacity: 1; | |
background-color: light-dark( | |
#f3f4f6, | |
rgb(30 41 59 / var(--tw-bg-opacity)) | |
) !important; | |
} | |
& > :not([hidden]) ~ :not([hidden]) { | |
--tw-space-x-reverse: 0; | |
margin-right: calc(0.25rem * var(--tw-space-x-reverse)); | |
margin-left: calc(0.25rem * calc(1 - var(--tw-space-x-reverse))); | |
} | |
& > .truncate { | |
max-width: 100% !important; | |
overflow-x: hidden; | |
text-overflow: ellipsis; | |
white-space: nowrap; | |
} | |
& :is(img, svg), | |
&:is([data-tab-url], [data-tab-img]):not(:has(img, svg)) | |
> :is(div, span)::before { | |
content: ""; | |
display: inline-flex; | |
width: var(--img-size, 1rem); | |
height: var(--img-size, 1rem); | |
justify-content: center; | |
align-self: center; | |
border-radius: 9999px; | |
border-width: 1px; | |
border-color: var(--img-border-color); | |
border-style: var(--img-border-style) !important; | |
padding: var(--img-padding, 1px); | |
vertical-align: middle; | |
&:is([aria-selected="false"]) { | |
--img-border-style: dashed; | |
--img-padding: 2px; | |
} | |
&:is([aria-selected="true"]) { | |
--img-border-style: solid; | |
--img-padding: 0.666px !important; | |
} | |
} | |
/* https://t1.gstatic.com/faviconV2?client=SOCIAL&type=FAVICON&fallback_opts=TYPE,SIZE,URL&size=128&url=https://jsfiddle.net */ | |
&:is([data-tab-img], [data-tab-url]):not(:has(img, svg)) { | |
& > :is(div, span) { | |
&::before { | |
margin-top: 0; | |
margin-right: 0.5rem; | |
} | |
} | |
} | |
&:has(+ [aria-selected="true"]) { | |
margin-left: -1px; | |
cursor: pointer; | |
--radius-r: 0.25rem; | |
--tab-border-right-color: rgb(2 6 23 / 0.1); | |
} | |
&:is(:hover, :target, [aria-selected="true"]) { | |
--tw-text-opacity: 1 !important; | |
--tab-color: light-dark( | |
rgb(15 23 42 / var(--tw-text-opacity)), | |
rgb(226 232 240 / var(--tw-text-opacity)) | |
) !important; | |
} | |
&[aria-selected="true"] { | |
--img-border-style: solid; | |
/* --tab-max-width: minmax(1fr, fit-content); */ | |
position: relative; | |
margin: 0 1px 0 0; | |
padding-right: 2.5rem !important; | |
font-weight: 500; | |
justify-self: stretch; | |
--tw-gradient-from-color: var(--top-bar-gradient-from-color); | |
--tw-gradient-to-color: var(--top-bar-gradient-to-color); | |
--tw-gradient-from-position: -100%; | |
--tw-gradient-to-position: 100%; | |
--tw-gradient-from: var(--tw-gradient-from-color) | |
var(--tw-gradient-from-position); | |
--tw-gradient-to: var(--tw-gradient-to-color) | |
var(--tw-gradient-to-position); | |
--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to); | |
background-image: linear-gradient( | |
to top, | |
var(--tw-gradient-stops) | |
) !important; | |
/* --tw-bg-opacity: 0; */ | |
/* background-color: light-dark(#f3f4f6, rgb(51 65 85 / var(--tw-bg-opacity))) !important; */ | |
border-top: none; | |
border-left: none; | |
border-right: none; | |
border-bottom: 1px solid var(--tab-border-color) !important; | |
z-index: 10; | |
@media (prefers-color-scheme: dark) { | |
& { | |
& *::selection { | |
background-color: rgb(255 255 255 / 0.05); | |
} | |
} | |
} | |
&:after { | |
content: "𐄂"; | |
position: absolute; | |
right: 0.5rem; | |
top: 50%; | |
z-index: 20; | |
margin-top: 0.2rem; | |
display: block; | |
height: 0.93rem; | |
width: 0.93rem; | |
--tw-translate-y: -66.666667%; | |
transform: translate(var(--tw-translate-x), var(--tw-translate-y)) | |
rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) | |
skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) | |
scaleY(var(--tw-scale-y)); | |
border-radius: 0.25rem; | |
background-color: rgb(226 232 240 / var(--tw-bg-opacity)); | |
--tw-bg-opacity: 0.75 !important; | |
padding: 0.125rem 0.1375rem 0.125rem 0.1125rem; | |
text-align: center; | |
font-size: 0.9rem; | |
line-height: 0.375rem !important; | |
color: rgb(148 163 184 / 1); | |
opacity: 0.75; | |
transition-property: all; | |
transition-duration: 300ms; | |
transition-timing-function: cubic-bezier(0.4, 0, 1, 1); | |
animation-duration: 300ms; | |
animation-timing-function: cubic-bezier(0.4, 0, 1, 1); | |
cursor: not-allowed !important; | |
} | |
@media (min-width: 1024px) { | |
&:after { | |
font-size: 1.1rem; | |
line-height: 1.75rem; | |
} | |
} | |
@media (prefers-color-scheme: dark) { | |
&:after { | |
--tw-bg-opacity: 1; | |
background-color: rgb(30 41 59 / var(--tw-bg-opacity)); | |
color: rgb(203 213 225 / 0.9); | |
} | |
} | |
&:hover:after { | |
--tw-bg-opacity: 1 !important; | |
opacity: 1 !important; | |
@apply drop-shadow-sm; | |
} | |
& + [role="tab"]:not(&) { | |
margin-bottom: 0; | |
margin-right: -1rem; | |
--radius-l: 0.25rem; | |
border-left-color: rgb(2 6 23 / 0.1); | |
padding-left: 0.75rem; | |
padding-right: 2rem; | |
} | |
} | |
&:last-child { | |
border-right-color: var(--tab-border-color) !important; | |
} | |
@media (min-width: 640px) { | |
& { | |
--img-size: 1rem; | |
} | |
&:has(+ [aria-selected="true"]) { | |
--radius-r: 0.375rem; | |
} | |
&:is([aria-selected="true"] + :not([aria-selected="true"])) { | |
--radius-l: 0.375rem !important; | |
} | |
} | |
@media (min-width: 768px) { | |
& { | |
--img-size: 1.125rem; | |
} | |
&:has(+ [aria-selected="true"]) { | |
--radius-r: 0.5rem; | |
} | |
&:is([aria-selected="true"] + :not([aria-selected="true"])) { | |
--radius-l: 0.5rem !important; | |
} | |
} | |
@media (min-width: 1024px) { | |
& { | |
--img-size: 1.25rem; | |
} | |
&:has(+ [aria-selected="true"]) { | |
--radius-r: 0.75rem; | |
} | |
&:is([aria-selected="true"] + :not([aria-selected="true"])) { | |
--radius-l: 0.75rem !important; | |
} | |
} | |
@media (prefers-color-scheme: dark) { | |
& { | |
--img-border-color: rgb(226 232 240 / 0.15); | |
} | |
&:has(+ [aria-selected="true"]) { | |
--tab-border-right-color: rgb(2 6 23 / 0.3) !important; | |
--tab-border-bottom-color: rgb(2 6 23 / 0.3) !important; | |
} | |
&:is([aria-selected="true"] + :not([aria-selected="true"])) { | |
--tab-border-left-color: rgb(2 6 23 / 0.3) !important; | |
} | |
} | |
&:not(&:has(+ &[aria-selected="true"]), &[aria-selected="true"], &:is([aria-selected="true"] | |
+ :not([aria-selected="true"]))) { | |
border-left-width: 1px; | |
border-top-width: 1px; | |
} | |
} | |
@media (min-width: 640px) { | |
& [role="tab"] { | |
max-width: minmax(1fr, 11rem); | |
padding-top: 0.375rem; | |
padding-bottom: 0.375rem; | |
} | |
} | |
@media (min-width: 768px) { | |
& [role="tab"] { | |
max-width: minmax(1fr, 13rem); | |
padding-top: 0.5rem; | |
padding-bottom: 0.5rem; | |
} | |
} | |
@media (min-width: 1024px) { | |
& [role="tab"] { | |
max-width: minmax(1fr, 16rem); | |
padding-top: 0.55rem; | |
padding-bottom: 0.55rem; | |
} | |
} | |
@media (prefers-color-scheme: dark) { | |
[role="tablist"] { | |
&, | |
& :not([aria-selected="true"]), | |
&:after { | |
--tab-border-color: rgb(2 6 23 / 0.3) !important; | |
--tw-bg-opacity: 1; | |
--tab-background-color: rgb( | |
30 41 59 / var(--tw-bg-opacity) | |
) !important; | |
--tw-text-opacity: 0.8; | |
--tab-color: rgb(100 116 139 / var(--tw-text-opacity)); | |
background-image: none !important; | |
} | |
} | |
} | |
} | |
@media (min-width: 640px) { | |
& [role="tablist"] { | |
font-size: 0.7rem; | |
} | |
} | |
@media (min-width: 768px) { | |
& [role="tablist"] { | |
font-size: 0.75rem; | |
line-height: 1rem; | |
} | |
} | |
@media (min-width: 1024px) { | |
& [role="tablist"] { | |
font-size: 0.875rem; | |
line-height: 1.25rem; | |
} | |
} | |
} | |
@media (min-width: 768px) { | |
:is(&:not(.fullscreen), :not(.fullscreen) &) .top-bar { | |
border-top-left-radius: 0.75rem; | |
border-top-right-radius: 0.75rem; | |
} | |
&:not(.fullscreen, .fullscreen &), | |
:is(&:not(.fullscreen), :not(.fullscreen) &) .tab-panel-container { | |
border-bottom-right-radius: 0.75rem; | |
border-bottom-left-radius: 0.75rem; | |
} | |
} | |
& .tab-panel-container { | |
--tw-bg-opacity: 1; | |
--tw-text-opacity: 1; | |
--tw-border-opacity: 1; | |
position: relative; | |
overflow: auto; | |
/* overscroll-behavior: contain; */ | |
border-top-width: 0; | |
border-bottom-width: 0; | |
border-color: var(--tab-panel-border-color); | |
background-color: var(--tab-panel-background-color); | |
color: var(--tab-panel-text-color); | |
padding-bottom: var(--tab-panel-padding-bottom); | |
margin-bottom: var(--tab-panel-margin-bottom); | |
font-size: var(--tab-panel-font-size, 1rem); | |
min-height: 100% !important; | |
.fullscreen & { | |
border-radius: 0 !important; | |
} | |
&:is(.overlap &) { | |
margin-bottom: 0 !important; | |
} | |
& .wrapper { | |
pointer-events: auto; | |
min-height: 100%; | |
width: 100%; | |
:not(.fullscreen) .wrapper { | |
max-height: var(--content-max-height); | |
padding-bottom: 2rem; | |
} | |
& [role="tabpanel"] { | |
padding: 1rem; | |
height: 100% !important; | |
} | |
} | |
} | |
} | |
@media (max-width: 640px) { | |
.tab-panel-container { | |
--tab-panel-font-size: 0.8rem; | |
} | |
} | |
@media (min-width: 640px) { | |
& .window { | |
--window-min-width: 0; | |
--window-max-width: none; | |
--btn-size: 0.675rem !important; | |
--tab-panel-font-size: 0.83rem; | |
} | |
} | |
@media (min-width: 768px) { | |
.window { | |
--window-border-radius: 0.75rem; | |
--btn-size: 0.7rem !important; | |
--tab-panel-font-size: 0.86rem; | |
} | |
} | |
@media (min-width: 1024px) { | |
.window { | |
--btn-size: 0.75rem !important; | |
--tab-panel-font-size: 0.93rem; | |
} | |
} | |
@media (min-width: 1280px) { | |
.window { | |
--btn-size: 0.775rem !important; | |
--tab-panel-font-size: 1rem; | |
} | |
} | |
@media (prefers-color-scheme: dark) { | |
.window { | |
[role="tab"] { | |
--tw-text-opacity: 0.75; | |
} | |
} | |
.tab-panel-container { | |
--tab-panel-border-color: rgb(15 23 42 / 0.5); | |
--tw-bg-opacity: 1; | |
--tab-panel-background-color: rgb(30 41 59 / var(--tw-bg-opacity)); | |
--tw-text-opacity: 1; | |
--tab-panel-text-color: rgb( | |
226 232 240 / var(--tw-text-opacity) | |
) !important; | |
} | |
} | |
} | |
/* #endregion chrome */ | |
/* #region dialog and editor */ | |
:is(.dialog, .editor) { | |
--top-bar-border-color-rgb: 100 116 139; | |
--top-bar-border-opacity: 0.3; | |
--btn-window-size: 0.625rem; | |
--btn-window-border: 2.5px; | |
--btn-window-bg-color-rgb: 71 85 105; | |
--btn-window-bg-color: rgb( | |
var(--btn-window-bg-color-rgb) / var(--tw-bg-opacity, 1) | |
); | |
--tw-border-opacity: 1; | |
--btn-window-border-color: rgb(71 85 105 / var(--tw-border-opacity)); | |
--window-bg-color-rgb: 30 41 59; | |
--window-bg-opacity: 1; | |
--window-bg-color-rgb-dark: 15 23 42; | |
--window-bg-opacity-dark: 0.7; | |
--window-bg-color-light: rgb( | |
var(--window-bg-color-rgb) / | |
var(--window-bg-opacity, var(--tw-bg-opacity, 1)) | |
); | |
--window-bg-color-dark: rgb( | |
var(--window-bg-color-rgb-dark) / | |
var(--window-bg-opacity-dark, var(--tw-bg-opacity, 0.7)) | |
); | |
--window-bg-color: light-dark( | |
var(--window-bg-color-light), | |
var(--window-bg-color-dark) | |
); | |
--window-max-height: 50vh; | |
--window-min-height: 25vh; | |
--window-width: auto; | |
--window-height: auto; | |
--window-title-font-size: 0.7rem; | |
--window-title-line-height: 1rem; | |
&.fullscreen { | |
&, | |
& > .window { | |
--btn-window-size: 0.725rem; | |
--window-min-width: 100%; | |
--window-min-height: 100%; | |
--window-max-width: 100vw; | |
--window-max-height: 100vh; | |
--window-width: 100%; | |
--window-height: 100%; | |
--window-border-radius: 0 !important; | |
margin: 0; | |
} | |
& .window { | |
border-radius: 0 !important; | |
} | |
} | |
& .window { | |
color-scheme: light dark; | |
--tw-shadow-color-rgb: 0 0 0; | |
--tw-shadow-opacity: 0.1; | |
--tw-shadow-color: light-dark( | |
rgb(var(--tw-shadow-color-rgb) / var(--tw-shadow-opacity)), | |
rgb(var(--tw-shadow-color-rgb) / calc(var(--tw-shadow-opacity) * 2)) | |
); | |
--tw-shadow: 0 20px 25px -5px var(--tw-shadow-color), | |
0 8px 10px -6px var(--tw-shadow-color); | |
--tw-shadow-colored: 0 20px 25px -5px var(--tw-shadow-color), | |
0 8px 10px -6px var(--tw-shadow-color); | |
--tw-shadow: 0 20px 25px -5px var(--tw-shadow-color), | |
0 8px 10px -6px var(--tw-shadow-color), | |
0 1.25rem 2rem -1rem var(--tw-shadow-color), | |
0 0.25rem 0.18625rem -0.125rem var(--tw-shadow-color), | |
0 -8px 10px -6px var(--tw-shadow-color), | |
0 -1.25rem 2rem -1rem var(--tw-shadow-color), | |
0 -0.25rem 0.18625rem -0.125rem var(--tw-shadow-color); | |
/* position: relative; */ | |
display: flex; | |
max-height: var(--window-max-height); | |
min-height: var(--window-min-height); | |
/* height: var(--window-height); */ | |
--tw-bg-opacity: 1; | |
background-color: var(--window-bg-color); | |
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), | |
var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); | |
border-radius: var(--window-border-radius, 0); | |
overflow: hidden; | |
&:is(.fullscreen, .editor.fullscreen &) { | |
--window-min-height: 100vh !important; | |
--window-max-height: 100vh !important; | |
--window-min-width: 100vw !important; | |
--window-max-width: 100vw !important; | |
--window-width: 100vw !important; | |
--window-height: 100vh !important; | |
--position: fixed !important; | |
inset: 0 !important; | |
& .tab-panel-container, | |
& .top-bar { | |
--window-border-radius: 0 !important; | |
} | |
} | |
&:is(.editor:not(.fullscreen) &:not(.fullscreen)) { | |
/* --window-height: 30rem !important; */ | |
--window-max-height: 60vh; | |
} | |
& | |
:is(.btn-window, :is(.traffic-signal, .buttons) | |
> .btn:is(.close, .minimize, .maximize, .zoom)) { | |
height: var(--btn-window-size, 0.625rem); | |
width: var(--btn-window-size, 0.625rem); | |
border-radius: 9999px; | |
border-width: var(--btn-window-border, 2.5px); | |
--tw-border-opacity: 1; | |
border-color: var(--btn-window-border-color); | |
--tw-bg-opacity: 0; | |
background-color: var(--btn-window-bg-color); | |
transition-property: color, background-color, border-color, fill, stroke, | |
-webkit-text-decoration-color; | |
transition-property: color, background-color, border-color, | |
text-decoration-color, fill, stroke; | |
transition-property: color, background-color, border-color, | |
text-decoration-color, fill, stroke, -webkit-text-decoration-color; | |
transition-duration: 300ms; | |
transition-timing-function: linear; | |
will-change: background-color; | |
&:hover, | |
&:is(.group:hover > &) { | |
--tw-bg-opacity: 1; | |
background-color: rgb(51 65 85 / var(--tw-bg-opacity)); | |
} | |
} | |
&:is(.editor &) .btn { | |
@apply rounded-full inline-block bg-transparent border-2 !border-current transition-colors duration-500 ease-in; | |
&.close { | |
@apply !text-red-500; | |
} | |
&.minimize { | |
@apply !text-amber-400; | |
} | |
&.maximize, | |
&.zoom { | |
@apply !text-green-400; | |
} | |
&:is(.traffic-signal:hover > &) { | |
@apply !bg-current; | |
} | |
} | |
& .top-bar { | |
flex: none; | |
border-bottom-width: 1px; | |
--tw-border-opacity: 0.3; | |
border-color: rgb( | |
var(--top-bar-border-color-rgb) / var(--tw-border-opacity) | |
); | |
& h2 { | |
--offset: calc(3 * calc(var(--btn-window-size) * 2)); | |
margin-top: 2px; | |
width: calc(100% - 4rem); | |
cursor: default; | |
-webkit-user-select: none; | |
user-select: none; | |
padding: 0; | |
padding-right: 3rem; | |
text-align: center; | |
font-size: var(--window-title-font-size); | |
line-height: var(--window-title-line-height); | |
color: rgb(148 163 184 / 0.8); | |
} | |
@media (min-width: 768px) { | |
& h2 { | |
--window-title-font-size: 0.75rem; | |
--window-title-line-height: 1rem; | |
} | |
} | |
@media (min-width: 1024px) { | |
& h2 { | |
--window-title-font-size: 0.875rem; | |
--window-title-line-height: 1.25rem; | |
} | |
} | |
@media (prefers-color-scheme: dark) { | |
& :is(h2, .status-bar .inner) { | |
--tw-text-opacity: 0.9111; | |
color: rgb(203 213 225 / var(--tw-text-opacity, 1)) !important; | |
} | |
} | |
} | |
& pre { | |
overflow-y: visible; | |
/* overscroll-behavior-y: contain; */ | |
& ::-webkit-scrollbar { | |
width: 4px !important; | |
} | |
& code { | |
overflow-x: auto; | |
overscroll-behavior-x: contain; | |
overflow-y: visible; | |
&::-webkit-scrollbar { | |
width: 0 !important; | |
height: 0 !important; | |
} | |
} | |
} | |
@media (min-width: 640px) { | |
.editor .window { | |
--window-height: 36rem; | |
--window-max-height: none; | |
} | |
} | |
@media (min-width: 1024px) { | |
.editor .window { | |
--window-height: 39rem; | |
} | |
} | |
@media (min-width: 1280px) { | |
.editor .window { | |
--window-height: 46rem; | |
} | |
} | |
} | |
@media (min-width: 768px) { | |
& .window { | |
--window-border-radius: 0.75rem; | |
--window-overflow: hidden; | |
} | |
} | |
@media (prefers-color-scheme: dark) { | |
& .window { | |
background-color: rgb(15 23 42 / 0.7); | |
--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 | |
var(--tw-ring-offset-width) var(--tw-ring-offset-color); | |
--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 | |
calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color); | |
box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), | |
var(--tw-shadow, 0 0 #0000); | |
--tw-ring-inset: inset; | |
--tw-ring-color: rgb(255 255 255 / 0.1); | |
--tw-backdrop-blur: blur(64px); | |
-webkit-backdrop-filter: var(--tw-backdrop-blur) | |
var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) | |
var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) | |
var(--tw-backdrop-invert) var(--tw-backdrop-opacity) | |
var(--tw-backdrop-saturate) var(--tw-backdrop-sepia); | |
backdrop-filter: var(--tw-backdrop-blur) var(--tw-backdrop-brightness) | |
var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) | |
var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) | |
var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) | |
var(--tw-backdrop-sepia); | |
} | |
} | |
} | |
.dialog { | |
& .window { | |
overflow: hidden; | |
& .content { | |
position: relative; | |
max-height: calc(100% - 0px); | |
width: 100%; | |
flex: 1 1 auto; | |
overflow-y: auto; | |
margin-bottom: 1px; | |
border-bottom-left-radius: calc( | |
var(--window-border-radius, 0) + 0.1px | |
) !important; | |
& .line-numbers-tray { | |
min-height: 100% !important; | |
border-bottom-left-radius: calc( | |
var(--window-border-radius, 0) + 0.05rem | |
) !important; | |
} | |
& code { | |
height: 100%; | |
} | |
} | |
} | |
} | |
.editor { | |
--activity-bar-border-color-rgb: 100 116 139; | |
--activity-bar-border-opacity: 0.3; | |
--activity-bar-border-color: rgb( | |
var(--activity-bar-border-color-rgb) / var(--activity-bar-border-opacity) | |
); | |
--activity-bar-padding: 0.5rem 1px 1rem 2px; | |
--status-bar-min-width: 640px; | |
--status-bar-border-opacity: 0.1; | |
--status-bar-border-color-rgb: 203 213 225; | |
--status-bar-border-color: rgb( | |
var(--status-bar-border-color-rgb) / var(--status-bar-border-opacity, 0.1) | |
); | |
--status-bar-background-color: light-dark(#1f2937, #111827); | |
--status-bar-font-size: 0.675rem; | |
--status-bar-line-height: 0.975rem; | |
--status-bar-text-color: #cbd5e1; | |
--status-bar-height: 1.5rem; | |
--status-bar-margin-y: 0.125rem; | |
--status-bar-padding-x: 0.75rem; | |
--status-bar-padding-y: 3px; | |
--status-bar-x-inset: 0.125rem; | |
& .window { | |
/* min-height: 100%; */ | |
& :is(.view, .view-primary) { | |
display: flex; | |
min-height: 0; | |
flex: 1 1 auto; | |
&:is(.justify-right, .activity-bar-right):has(.activity-bar) { | |
flex-direction: row-reverse !important; | |
flex: auto 1 1; | |
--activity-bar-padding: 0.35rem 1px 0.35rem 0; | |
& .activity-bar { | |
border-left-width: 1px !important; | |
border-right-width: 0 !important; | |
} | |
} | |
& .activity-bar { | |
display: none; | |
width: 3rem; | |
flex: none; | |
flex-direction: column; | |
align-items: center; | |
justify-content: space-between; | |
border-right-width: 1px; | |
border-color: var(--activity-bar-border-color); | |
margin: 0.175rem 0; | |
padding: var(--activity-bar-padding); | |
overflow-x: hidden; | |
overflow-y: auto; | |
overscroll-behavior: none contain; | |
--tw-text-opacity: 1; | |
color: rgb(148 163 184 / var(--tw-text-opacity)); | |
z-index: 20; | |
&::-webkit-scrollbar { | |
width: 0 !important; | |
/* height: 0 !important; */ | |
} | |
& :is(.side-icon-tray, .user-controls) { | |
margin: 0.5rem; | |
display: flex; | |
flex-direction: column; | |
align-items: center; | |
justify-content: space-between; | |
row-gap: 1rem; | |
} | |
&.visible { | |
display: flex !important; | |
} | |
& > :not([hidden]) ~ :not([hidden]) { | |
--tw-divide-y-reverse: 0; | |
border-top-width: calc(1px * calc(1 - var(--tw-divide-y-reverse))); | |
border-bottom-width: calc(1px * var(--tw-divide-y-reverse)); | |
border-style: dashed; | |
border-color: rgb(71 85 105 / 0.05); | |
} | |
@media (prefers-color-scheme: dark) { | |
& .side-icon-tray > :not([hidden]) ~ :not([hidden]) { | |
border-color: rgb(100 116 139 / 0.1); | |
} | |
} | |
& :is(.user-controls, .side-icon-tray) > button { | |
--tw-brightness: brightness(1); | |
--tw-contrast: contrast(1); | |
--tw-drop-shadow: drop-shadow(0 1px 1px rgb(0 0 0 / 0.05)); | |
filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) | |
var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) | |
var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow); | |
transition-property: all; | |
transition-duration: 500ms; | |
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); | |
animation-duration: 500ms; | |
animation-timing-function: cubic-bezier(0.4, 0, 0.2, 1); | |
&:hover { | |
opacity: 1 !important; | |
--tw-brightness: brightness(0.75); | |
--tw-contrast: contrast(1.5); | |
--tw-drop-shadow: drop-shadow(0 4px 3px rgb(0 0 0 / 0.07)) | |
drop-shadow(0 2px 2px rgb(0 0 0 / 0.06)); | |
} | |
@media (prefers-color-scheme: dark) { | |
&:hover { | |
--tw-brightness: brightness(1.5) !important; | |
--tw-contrast: contrast(1.25) !important; | |
} | |
& svg { | |
flex: none; | |
} | |
} | |
} | |
} | |
@media (min-width: 768px) { | |
& .activity-bar { | |
display: flex; | |
} | |
} | |
@media (prefers-color-scheme: dark) { | |
& .activity-bar { | |
--tw-text-opacity: 1 !important; | |
color: rgb(100 116 139 / var(--tw-text-opacity)) !important; | |
} | |
} | |
& :is(.view.main, .view-main) { | |
display: flex; | |
min-width: 0; | |
flex: 1 1 auto; | |
flex-direction: column; | |
& :is(.panel.content, .panel-content) { | |
display: flex; | |
min-height: 0; | |
width: 100%; | |
flex: 1 1 auto; | |
} | |
& :is(.panel.bottom, .panel-bottom) { | |
border-top-width: 1px; | |
border-color: rgb(100 116 139 / 0.3); | |
padding: 0.75rem; | |
font-family: OperatorMonoLig, OperatorMono Nerd Font, MonoLisa, | |
DM Mono, Dank Mono, IBM Plex Mono, Menlo, consolas, ui-monospace, | |
Courier, monospace; | |
font-size: 0.65rem; | |
line-height: 1.25rem; | |
--tw-text-opacity: 1; | |
color: rgb(226 232 240 / var(--tw-text-opacity)); | |
& h3 { | |
-webkit-user-select: none; | |
user-select: none; | |
& + * { | |
will-change: height, max-height, opacity; | |
transition: all 0.1s ease-in; | |
} | |
} | |
& > :not([hidden]) ~ :not([hidden]) { | |
--tw-space-y-reverse: 0; | |
margin-top: calc(0.5rem * calc(1 - var(--tw-space-y-reverse))); | |
margin-bottom: calc(0.5rem * var(--tw-space-y-reverse)); | |
} | |
} | |
@media (min-width: 640px) { | |
& :is(.panel.bottom, .panel-bottom) { | |
line-height: 1.5rem; | |
} | |
} | |
@media (min-width: 768px) { | |
& :is(.panel.bottom, .panel-bottom) { | |
font-size: 0.7rem; | |
line-height: 1.75rem; | |
} | |
} | |
@media (min-width: 1024px) { | |
& :is(.panel.bottom, .panel-bottom) { | |
font-size: 0.75rem; | |
line-height: 1rem; | |
} | |
} | |
} | |
} | |
& .status-bar { | |
position: relative; | |
display: none; | |
height: var(--status-bar-height, 1.5rem); | |
width: 100%; | |
& .inner { | |
position: absolute; | |
left: var(--status-bar-x-inset, 0); | |
right: var(--status-bar-x-inset, 0); | |
bottom: 0; | |
margin-top: var(--status-bar-margin-y, 0); | |
margin-bottom: 0; | |
display: flex; | |
height: var(--status-bar-height); | |
width: calc(100% - calc(var(--status-bar-x-inset, 0rem) * 2)); | |
overflow: hidden; | |
border-top-width: 1px; | |
border-bottom-width: 1px; | |
border-color: var(--status-bar-border-color); | |
background-color: var(--status-bar-background-color); | |
padding-left: var(--status-bar-padding-x, 0); | |
padding-right: var(--status-bar-padding-x, 0); | |
padding-top: var(--status-bar-padding-y, 0); | |
font-size: var(--status-bar-font-size, 1rem); | |
line-height: var(--status-bar-line-height, 1rem); | |
color: var(--status-bar-text-color, inherit); | |
& > :nth-child(1) { | |
font-size: 1.1em; | |
} | |
& * { | |
-webkit-user-select: none; | |
user-select: none; | |
} | |
} | |
} | |
@media (min-width: 640px) { | |
& .status-bar { | |
display: block; | |
} | |
} | |
} | |
} | |
/* #endregion dialog and editor */ | |
pre, | |
code, | |
kbd { | |
font-family: "Operator Mono ScreenSmart", "Operator Mono Lig", | |
"OperatorMono Nerd Font", "Mono Lisa", "DM Mono", "Dank Mono", | |
"IBM Plex Mono", "Fira Code", "Roboto Mono", "Lucida Console", Menlo, Monaco, | |
Consolas, ui-monospace, monospace !important; | |
white-space: pre; | |
tab-size: 2; | |
-moz-tab-size: 2; | |
-moz-tab-spaces: 2; | |
} | |
pre:is(.hljs, .code-block) { | |
position: relative; | |
margin: 1px; | |
display: flex; | |
min-height: calc(100% - 5px); | |
font-size: 0.7rem; | |
line-height: 1.3rem !important; | |
border-bottom-left-radius: 0.75rem; | |
} | |
@media (min-width: 768px) { | |
pre:is(.hljs, .code-block) { | |
font-size: 0.75rem; | |
line-height: 1.4rem !important; | |
} | |
} | |
@media (min-width: 1024px) { | |
pre:is(.hljs, .code-block) { | |
font-size: 0.875rem; | |
line-height: 1.5rem !important; | |
} | |
} | |
pre:is(.hljs, .code-block) { | |
--line-numbers-display: none; | |
--line-numbers-tray-display: none; | |
--line-numbers-bg-opacity: 0.8; | |
/* --line-numbers-bg: rgb(3 7 18 / var(--line-numbers-bg-opacity, 0.8)); */ | |
--line-numbers-bg: rgb(12 18 34 / var(--line-numbers-bg-opacity)); | |
--code-padding-left: 0.75rem; | |
--line-numbers-backdrop-blur: 12px; | |
transform: translate3d(0, 0, 0) translatez(0); | |
& code { | |
position: relative; | |
display: block; | |
flex: 1 1 auto; | |
padding: 0.75rem; | |
padding-left: var(--code-padding-left); | |
--tw-text-opacity: 1; | |
color: rgb(248 250 252 / var(--tw-text-opacity)); | |
} | |
&:is(.line-numbers) { | |
&::before { | |
content: ""; | |
position: fixed; | |
top: 0; | |
left: 0; | |
bottom: 0; | |
z-index: 30 !important; | |
margin: 0 0 1px 0; | |
display: var(--line-numbers-tray-display, none); | |
min-height: 100%; | |
height: calc(100% + 10px); | |
padding: 0 0 10px 0; | |
width: 3.25rem; | |
flex: none; | |
-webkit-user-select: none; | |
user-select: none; | |
background-color: var(--line-numbers-bg); | |
text-align: right; | |
font-size: 0.75rem; | |
line-height: 1rem; | |
--tw-text-opacity: 1; | |
color: rgb(255 255 255 / var(--tw-text-opacity)); | |
--tw-backdrop-blur: blur(var(--line-numbers-backdrop-blur, 2px)); | |
-webkit-backdrop-filter: var(--tw-backdrop-blur) | |
var(--tw-backdrop-brightness) var(--tw-backdrop-contrast) | |
var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) | |
var(--tw-backdrop-invert) var(--tw-backdrop-opacity) | |
var(--tw-backdrop-saturate) var(--tw-backdrop-sepia) !important; | |
backdrop-filter: var(--tw-backdrop-blur) var(--tw-backdrop-brightness) | |
var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) | |
var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert) | |
var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) | |
var(--tw-backdrop-sepia) !important; | |
&:not(.editor &) { | |
border-bottom-left-radius: var(--window-border-radius, 0.75rem); | |
} | |
} | |
@media (min-width: 640px) { | |
& { | |
--line-numbers-tray-display: flex; | |
--line-numbers-display: inline-block; | |
& code { | |
--code-padding-left: 4rem; | |
} | |
} | |
} | |
& code { | |
counter-reset: line 1; | |
&.visible { | |
--code-padding-left: 4rem; | |
} | |
& :is(span.token ~ span:not([class])) { | |
& + & { | |
counter-increment: line 1; | |
} | |
&:before { | |
position: fixed; | |
left: 0; | |
z-index: 30 !important; | |
display: var(--line-numbers-display, none); | |
width: 2.7rem; | |
text-align: right; | |
--tw-text-opacity: 1; | |
color: rgb(168 183 204 / var(--tw-text-opacity)); | |
content: counter(line); | |
&.visible { | |
display: inline-block !important; | |
} | |
} | |
} | |
} | |
} | |
} | |
.scrollbar-w-0\.5::-webkit-scrollbar { | |
width: 0.125rem; | |
} | |
.scrollbar-w-1\.5::-webkit-scrollbar { | |
width: 0.375rem; | |
} | |
.scrollbar-w-2\.5::-webkit-scrollbar { | |
width: 0.625rem; | |
} | |
.scrollbar-w-3\.5::-webkit-scrollbar { | |
width: 0.875rem; | |
} | |
/* #region syntax */ | |
.token { | |
&.class-name, | |
&.function, | |
&.selector, | |
&.selector .class, | |
&.selector.class, | |
&.tag { | |
--tw-text-opacity: 1; | |
color: rgb(244 114 182 / var(--tw-text-opacity)); | |
} | |
&.attr-name, | |
&.important, | |
&.keyword, | |
&.module, | |
&.pseudo-class, | |
&.rule { | |
--tw-text-opacity: 1; | |
color: rgb(203 213 225 / var(--tw-text-opacity)); | |
} | |
&.attr-value, | |
&.class, | |
&.string { | |
--tw-text-opacity: 1; | |
color: rgb(125 211 252 / var(--tw-text-opacity)); | |
} | |
&.attr-equals, | |
&.punctuation { | |
--tw-text-opacity: 1; | |
color: rgb(100 116 139 / var(--tw-text-opacity)); | |
} | |
&.attr-value * { | |
--tw-text-opacity: 1; | |
color: rgb(125 211 252 / var(--tw-text-opacity)); | |
} | |
&.attr-value .attr-equals, | |
&.attr-value .attr-equals + .punctuation, | |
&.attr-value > .punctuation:last-child { | |
--tw-text-opacity: 1; | |
color: rgb(100 116 139 / var(--tw-text-opacity)); | |
} | |
&.property { | |
--tw-text-opacity: 1; | |
color: rgb(125 211 252 / var(--tw-text-opacity)); | |
} | |
&.unit { | |
--tw-text-opacity: 1; | |
color: rgb(153 246 228 / var(--tw-text-opacity)); | |
} | |
.language-shell .token:not(.comment), | |
&.atapply .token:not(.rule):not(.important):not(.punctuation) { | |
color: inherit; | |
} | |
.language-css &.function { | |
--tw-text-opacity: 1; | |
color: rgb(153 246 228 / var(--tw-text-opacity)); | |
} | |
&.combinator, | |
&.comment, | |
&.operator { | |
--tw-text-opacity: 1; | |
color: rgb(148 163 184 / var(--tw-text-opacity)); | |
} | |
&.unchanged { | |
display: block; | |
} | |
&.deleted, | |
&.inserted { | |
position: relative; | |
margin-left: -2.25rem; | |
margin-right: -2.25rem; | |
display: block; | |
border-left-width: 4px; | |
padding-left: 2rem; | |
padding-right: 1.25rem; | |
} | |
&.deleted:before, | |
&.inserted:before { | |
position: absolute; | |
top: 0; | |
content: var(--tw-content); | |
left: 1rem; | |
} | |
&.inserted { | |
--tw-border-opacity: 1; | |
border-color: rgb(45 212 191 / var(--tw-border-opacity)); | |
background-color: #2dd4bf26; | |
} | |
&.inserted:before { | |
--tw-text-opacity: 1; | |
color: rgb(45 212 191 / var(--tw-text-opacity)); | |
--tw-content: "+"; | |
content: var(--tw-content); | |
} | |
&.deleted { | |
--tw-border-opacity: 1; | |
border-color: rgb(251 113 133 / var(--tw-border-opacity)); | |
background-color: #f43f5e26; | |
} | |
&.deleted:before { | |
--tw-text-opacity: 1; | |
color: rgb(251 113 133 / var(--tw-text-opacity)); | |
--tw-content: "-"; | |
content: var(--tw-content); | |
} | |
} | |
pre[class^="language-diff-"] { | |
display: flex; | |
padding-left: 2.25rem; | |
padding-right: 2.25rem; | |
& > code { | |
min-width: 100%; | |
flex: none; | |
} | |
} | |
span.code-highlight.bg-code-highlight:has(> span[title*="\AD"]) { | |
margin-left: 1px; | |
margin-right: 1px; | |
background-color: #ec48991a; | |
--tw-text-opacity: 1; | |
color: rgb(244 114 182 / var(--tw-text-opacity)); | |
} | |
.bar-of-progress:after { | |
content: ""; | |
display: block; | |
position: absolute; | |
right: 0; | |
width: 100px; | |
height: 100%; | |
box-shadow: 0 0 10px currentColor, 0 0 5px currentColor; | |
transform: rotate(3deg) translateY(-4px); | |
} | |
/* #endregion syntax */ | |
.hover\:text-slate-500:hover { | |
--tw-text-opacity: 1; | |
color: rgb(100 116 139 / var(--tw-text-opacity)); | |
} | |
.focus\:z-10:focus { | |
z-index: 10; | |
} | |
.focus\:border-blue-300:focus { | |
--tw-border-opacity: 1; | |
border-color: rgb(147 197 253 / var(--tw-border-opacity)); | |
} | |
.focus\:outline-none:focus { | |
outline: 2px solid transparent; | |
outline-offset: 2px; | |
} | |
.group:hover .group-hover\:bg-slate-800\/10 { | |
background-color: rgb(30 41 59 / 0.1); | |
} | |
.group:hover .group-hover\:\!bg-opacity-20 { | |
--tw-bg-opacity: 0.2 !important; | |
} | |
.group:hover .group-hover\:\!stroke-2 { | |
stroke-width: 2 !important; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment