diff --git a/extension/entrypoints/options/App.tsx b/extension/entrypoints/options/App.tsx
index 536788e..76e9300 100644
--- a/extension/entrypoints/options/App.tsx
+++ b/extension/entrypoints/options/App.tsx
@@ -26,11 +26,11 @@ function AturiMark() {
);
}
-const TABS: { id: TabId; label: string; sub: string }[] = [
- { id: 'defaults', label: 'General', sub: 'Auto-redirect & popup' },
- { id: 'visibility', label: 'Waypoints', sub: 'Hide or show apps' },
- { id: 'custom', label: 'Custom waypoints', sub: 'Add your own sites' },
- { id: 'about', label: 'About', sub: 'Project, credits, feedback' },
+const TABS: { id: TabId; label: string }[] = [
+ { id: 'defaults', label: 'General' },
+ { id: 'visibility', label: 'Waypoints' },
+ { id: 'custom', label: 'Custom' },
+ { id: 'about', label: 'About' },
];
export default function App() {
@@ -58,7 +58,7 @@ export default function App() {
return (
-
+
{tab === 'defaults' && }
diff --git a/extension/entrypoints/options/index.html b/extension/entrypoints/options/index.html
index c343e2e..fb0515b 100644
--- a/extension/entrypoints/options/index.html
+++ b/extension/entrypoints/options/index.html
@@ -3,6 +3,13 @@
+
+
Aturi Settings
diff --git a/extension/entrypoints/options/options.css b/extension/entrypoints/options/options.css
index 2ff7860..0fe5f96 100644
--- a/extension/entrypoints/options/options.css
+++ b/extension/entrypoints/options/options.css
@@ -4,36 +4,40 @@ body {
}
.options-root {
- display: grid;
- /* `minmax(0, 1fr)` lets the content column actually shrink — a plain `1fr`
- uses min-content as its floor, which a long un-breakable URL inside the
- custom-waypoint preview can blow past, forcing horizontal scroll. */
- grid-template-columns: 188px minmax(0, 1fr);
+ display: flex;
+ flex-direction: column;
max-width: 1100px;
margin: 0 auto;
min-height: 100vh;
+ /* Prevent intrinsic min-content of nested grids/long URLs from forcing the
+ whole page to grow horizontally. */
+ min-width: 0;
}
-.options-sidebar {
- border-right: 1px solid var(--border-subtle);
- padding: 28px 0;
- background: var(--bg-secondary);
+.options-header {
position: sticky;
top: 0;
- align-self: start;
- min-height: 100vh;
+ z-index: 5;
+ background: var(--bg-secondary);
+ border-bottom: 1px solid var(--border-subtle);
+ padding: 14px 24px 0;
+ display: flex;
+ flex-direction: column;
+ gap: 10px;
}
.options-brand {
- padding: 0 18px 18px;
- border-bottom: 1px solid var(--border-subtle);
- margin-bottom: 12px;
+ display: flex;
+ align-items: center;
+ gap: 12px;
+ flex-wrap: wrap;
}
.options-brand-title {
font-family: var(--font-serif);
font-weight: 500;
font-size: 20px;
+ line-height: 1;
letter-spacing: -0.02em;
display: inline-flex;
align-items: center;
@@ -46,24 +50,24 @@ body {
}
.options-brand-sub {
- font-size: 11px;
+ font-size: 10px;
+ line-height: 1;
color: var(--text-tertiary);
text-transform: uppercase;
- letter-spacing: 0.1em;
- margin-top: 2px;
+ letter-spacing: 0.14em;
+ /* Nudge down so the small caps optically center with the cap-height of
+ "Aturi" rather than its full ascender-to-descender box. */
+ transform: translateY(1px);
}
.options-nav {
display: flex;
- flex-direction: column;
+ flex-wrap: wrap;
+ gap: 2px;
+ margin: 0 -6px;
}
.options-nav-item {
- display: flex;
- align-items: center;
- justify-content: space-between;
- padding: 10px 18px;
- text-align: left;
background: transparent;
border: none;
color: var(--text-secondary);
@@ -71,30 +75,48 @@ body {
font-family: var(--font-serif);
font-size: 13.5px;
font-weight: 400;
- border-left: 2px solid transparent;
- transition: all 0.2s ease;
- position: relative;
+ padding: 8px 12px 10px;
+ border-bottom: 2px solid transparent;
+ margin-bottom: -1px;
+ transition: color 0.15s ease, border-color 0.15s ease, background 0.15s ease;
+ white-space: nowrap;
}
.options-nav-item:hover {
- background: var(--bg-tertiary);
color: var(--text-primary);
+ background: var(--bg-tertiary);
}
.options-nav-item.active {
- background: var(--bg-tertiary);
- border-left-color: var(--text-accent);
color: var(--text-primary);
+ border-bottom-color: var(--text-accent);
font-weight: 500;
}
.options-content {
- padding: 32px 36px;
+ padding: 28px 28px 40px;
max-width: 880px;
+ width: 100%;
/* Same reason as .options-root: prevent intrinsic min-content of children
from forcing this block to grow beyond its container. */
min-width: 0;
overflow-x: hidden;
+ box-sizing: border-box;
+}
+
+@media (max-width: 560px) {
+ .options-header {
+ padding: 12px 16px 0;
+ }
+
+ .options-content {
+ padding: 22px 16px 32px;
+ }
+
+ .options-nav-item {
+ font-size: 13px;
+ padding: 8px 10px 10px;
+ }
}
.options-h1 {
diff --git a/extension/entrypoints/popup/App.tsx b/extension/entrypoints/popup/App.tsx
index 40444cf..8be8a72 100644
--- a/extension/entrypoints/popup/App.tsx
+++ b/extension/entrypoints/popup/App.tsx
@@ -24,6 +24,24 @@ type PopupState =
| { phase: 'unsupported'; prefs: Prefs; tabId: number | null; isKnownHost: boolean }
| { phase: 'ready'; match: ReverseMatch; prefs: Prefs; tabId: number | null };
+// Open the extension's options page. We prefer browser.runtime.openOptionsPage()
+// because it focuses an existing options tab when one is already open. Some
+// Chromium-derived browsers (notably Arc) do not implement the chrome://extensions
+// surface that hosts embedded options, so the call can silently no-op. If it
+// throws or never resolves, fall back to opening options.html in a new tab.
+async function openOptionsPage() {
+ const fallbackUrl = browser.runtime.getURL('/options.html');
+ try {
+ const result = browser.runtime.openOptionsPage();
+ if (result && typeof (result as Promise).then === 'function') {
+ await (result as Promise);
+ }
+ } catch {
+ await browser.tabs.create({ url: fallbackUrl });
+ }
+ window.close();
+}
+
export default function App() {
const [state, setState] = useState({ phase: 'loading' });
const [pendingId, setPendingId] = useState(null);
@@ -288,7 +306,7 @@ function NoAtmosphereView({
No destinations are visible. Add waypoints to a group in Settings.
-
@@ -334,7 +352,7 @@ function NoAtmosphereView({
browser.runtime.openOptionsPage()}
+ onClick={() => void openOptionsPage()}
>
Settings
@@ -492,7 +510,7 @@ function Ready({ match, prefs, pendingId, copiedId, onOpen, onCopy }: ReadyProps
- browser.runtime.openOptionsPage()}>
+ void openOptionsPage()}>
Settings
@@ -581,7 +599,7 @@ function HeaderSettingsButton() {
browser.runtime.openOptionsPage()}
+ onClick={() => void openOptionsPage()}
title="Open settings"
aria-label="Open extension settings"
>