feat(ui): upright icon tabs on the right rail, never shrunk or clipped

The rail tabs show one Material Symbols glyph each (Display tune, CCTV
videocam, Street Level streetview, Context travel_explore, Weather cloud,
Recent Imagery satellite_alt) in a fixed 34px square, the size of the Radio
launcher under them. The panel's translated title is the tab's aria-label and
tooltip. The sideways labels, the equal share of a short strip, the end fade
and the column's lane cap are gone: every tab stays whole, and on the shortest
windows the strip runs past the lane instead of clipping. The glyphs join the
icon font subset, with a test that keeps them there. The screenshot is
refreshed.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
German KhokhlovandClaude Opus 5.5 committed 2026-10-29 14:00:54 +04:00
1 parent 33ccdad744
commit 8396fd3dc7
8 files changed
+109 -130

No files matched your search

+10 -11
View File
@@ -1,14 +1,13 @@
- The right rail (Display, CCTV, Street Level, Context, plus Weather and Recent
Imagery while shown) is a vertical tab strip with sideways labels instead of
an accordion of collapsed launcher cards: at most one panel is open in every
desktop HUD layout, and the open tab and its panel share one surface and
outline, like a folder tab. On a short window the rail rises toward the HUD
corner above it, and the tabs then share the room equally rather than
scroll. A first-time visitor on a desktop finds Display open; a stored
choice still wins. The panels' own collapse arrows are gone on desktop: the
tab opens and closes a panel, Escape inside one closes it and returns focus
to its tab, and the strip is a keyboard-navigable tablist. The compact Radio
launcher moves from Context's header to the bottom of the strip on desktop
and always opens the compact player, also while Context is open; the
Imagery while shown) is a vertical strip of icon tabs instead of an accordion
of collapsed launcher cards: at most one panel is open in every desktop HUD
layout, and the open tab and its panel share one surface and outline, like a
folder tab. On a short window the rail rises toward the HUD corner above it
rather than scroll. A first-time visitor on a desktop finds Display open; a
stored choice still wins. The panels' own collapse arrows are gone on desktop:
the tab opens and closes a panel, Escape inside one closes it and returns
focus to its tab, and the strip is a keyboard-navigable tablist. The compact
Radio launcher moves from Context's header to the bottom of the strip on
desktop and always opens the compact player, also while Context is open; the
player's Details button leads to the Radio section. Saved preferences, share
links and the phone sheet shell are unchanged (gekh, #1425).
+10 -9
View File
@@ -1498,8 +1498,8 @@ rule can still give them back.
On desktop the right rail (Display, CCTV, Street Level, Context, plus Weather
and Recent Imagery while shown) is a vertical tab strip
(`src/ui/rightRailTabs.js`, `src/ui/styles/right-rail-tabs.css`): one sideways
tab per panel at the rail's right edge, the open panel beside it. A tab
(`src/ui/rightRailTabs.js`, `src/ui/styles/right-rail-tabs.css`): one upright
icon tab per panel at the rail's right edge, the open panel beside it. A tab
toggles its panel through `setPanelCollapsed` with the explicit flag; the
panels' `collapsed` classes remain the only state, mirrored through a
MutationObserver, and the owner rule above hides the rest. On desktop the
@@ -1509,11 +1509,12 @@ The strip is a vertical `role="tablist"`: Up/Down
wrap, Home/End jump, the open tab (else the first) is the tab stop, Escape
inside a panel returns focus to its tab, and the strip precedes the panels in
the DOM so Tab from the open tab reaches the tool slot and then the panel.
Labels are the panels' own translated header titles. The lane's top follows the
left rail's; a strip taller than the lane lifts the rail toward the HUD corner
above (`src/ui/rightPanelRail.js`), and a lane still shorter is shared equally
between the tabs, so the long labels end up one height and clip; the strip
never scrolls. Under the tabs the strip's
Glyphs come from `RAIL_TAB_ICONS` (Material Symbols, in the `index.html`
subset); a tab's accessible name and tooltip are the panel's translated header
title. The tabs keep their size. The lane's top follows the left rail's; a
strip taller than the lane lifts the rail toward the HUD corner above
(`src/ui/rightPanelRail.js`), and on a lane still shorter the strip runs past
its end; the strip never scrolls or clips. Under the tabs the strip's
column keeps a tool slot (`#right-rail-tools`) that holds the compact Radio
launcher on desktop: `src/ui/radioPanelPlacement.js` moves it there from
Context's header, and back for the phone sheet, so the compact player stays one
@@ -1524,7 +1525,7 @@ plain geometry. Phones hide the strip and keep the sheet shell; Cockpit hides
the rail and the strip with it.
The open tab and its panel are one surface, like a folder tab; the other tabs
are borderless labels. The strip measures the open panel and tab, traces their
are borderless icons. The strip measures the open panel and tab, traces their
joined silhouette as an SVG path (`railJoinPath`; Cyber gets its chamfered
frame) and shapes a join element under the panels with it: glass, outline and
shadow in one piece, so the junction has no seam. The panel and the tab draw
@@ -1533,7 +1534,7 @@ minimum height, so a short panel such as Context still reaches its tab.
A first-time visitor on a desktop viewport finds Display open
(`src/ui/panelPositionControls.js`): with every panel closed nothing shows that
the labels are launchers. Phones keep it closed, a share link decides for
the icons are launchers. Phones keep it closed, a share link decides for
itself, and a stored choice wins either way. When the rail picks its owner, a
panel the visitor or a share link opened outranks one open only by default, so
a stored CCTV choice is not displaced by the Display default.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 387 KiB

After

Width:  |  Height:  |  Size: 404 KiB

+1 -1
View File
@@ -26,7 +26,7 @@
with the sources: src/materialSymbolsSubset.test.mjs fails when a source
names a glyph this list is missing, because a missing glyph does not draw
a box — the ligature never forms and the element renders the literal word. -->
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@20,400,0,0&icon_names=adjust,arrow_drop_down,arrow_forward,arrow_left,arrow_right,bolt,chevron_left,chevron_right,close,close_fullscreen,dark_mode,draw,east,flare,flight,layers,layers_clear,light_mode,local_fire_department,more_horiz,my_location,navigation,normal,on,open_in_full,public,radar,radio,right_panel_close,right_panel_open,rocket_launch,search,settings,skip_next,skip_previous,upload_file,videocam,view_in_ar,visibility,visibility_off" rel="stylesheet" />
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@20,400,0,0&icon_names=adjust,arrow_drop_down,arrow_forward,arrow_left,arrow_right,bolt,chevron_left,chevron_right,close,close_fullscreen,cloud,dark_mode,draw,east,flare,flight,layers,layers_clear,light_mode,local_fire_department,more_horiz,my_location,navigation,normal,on,open_in_full,public,radar,radio,right_panel_close,right_panel_open,rocket_launch,satellite_alt,search,settings,skip_next,skip_previous,streetview,travel_explore,tune,upload_file,videocam,view_in_ar,visibility,visibility_off" rel="stylesheet" />
</head>
<body>
<!-- gev:template scene-chrome -->
+2 -3
View File
@@ -175,8 +175,7 @@ export function layoutRightPanelRail({
.map((node) => [node, node.scrollTop || 0])
.filter(([, top]) => top > 0);
const naturalHeights = new Map();
// The tab strip's column stands beside the panels; the measuring override
// gives it its natural height.
// The tab strip's column stands beside the panels at its natural height.
const column = stack.querySelector?.(':scope > #right-rail-column');
let columnHeight = 0;
// Remove live height allocations through a synchronous CSS override. Keeping
@@ -223,7 +222,7 @@ export function layoutRightPanelRail({
if (!layout) return;
// A lane too short for the strip's column: the rail climbs toward the HUD
// above, as far as the strip needs and the obstacles allow. A lane still
// shorter shrinks the tabs (styles/right-rail-tabs.css).
// shorter leaves the strip running past the lane's end.
const shortfall = columnHeight - (layout.safeBottom - layout.safeTop);
if (shortfall > 0) layout = resolve(alignedTop - shortfall) || layout;
const { safeTop, safeBottom, maxHeight: availableHeight } = layout;
+28 -15
View File
@@ -1,5 +1,5 @@
/**
* Vertical tab strip for the right context rail: one sideways tab per rail
* Vertical tab strip for the right context rail: one icon tab per rail
* panel, the open panel beside the strip, at most one panel open. The panels'
* `collapsed` classes remain the only state; the strip mirrors them and asks
* the owner (setPanelCollapsed) for every change, so persistence, share links
@@ -21,6 +21,19 @@ const RAIL_TABS_ATTRIBUTE = 'data-rail-tabs';
const SVG_NS = 'http://www.w3.org/2000/svg';
/**
* Each rail panel's tab glyph (Material Symbols). index.html subsets the icon
* font, so its `icon_names` must carry every one.
*/
export const RAIL_TAB_ICONS = Object.freeze({
'pp-toggles': 'tune',
'cctv-panel': 'videocam',
'street-level-panel': 'streetview',
'global-context-panel': 'travel_explore',
'weather-panel': 'cloud',
'recent-imagery-panel': 'satellite_alt',
});
/**
* The tab keyboard navigation moves to in a vertical tablist: Down and Up
* wrap, Home and End jump. Any other key keeps the current index.
@@ -177,7 +190,7 @@ export function railJoinPath({
return d.join(' ');
}
/** The tab label: the panel's own header title, already translated. */
/** The tab's name: the panel's own header title, already translated. */
function panelTitle(panel) {
return (
panel
@@ -230,7 +243,7 @@ export function createRightRailTabs({
outline.append(outlinePath);
join.append(shadow, glass, outline);
/** @type {Map<string, {tab: HTMLElement, label: HTMLElement}>} */
/** @type {Map<string, {tab: HTMLElement, icon: HTMLElement}>} */
const entries = new Map();
const observed = new WeakSet();
let destroyed = false;
@@ -244,10 +257,12 @@ export function createRightRailTabs({
tab.dataset.railTab = panel.id;
tab.setAttribute('role', 'tab');
tab.setAttribute('aria-controls', panel.id);
const label = documentRef.createElement('span');
label.className = 'right-rail-tab-label';
tab.append(label);
entry = { tab, label };
const icon = documentRef.createElement('span');
icon.className = 'right-rail-tab-icon material-symbols-outlined';
icon.setAttribute('aria-hidden', 'true');
icon.textContent = RAIL_TAB_ICONS[panel.id] || 'more_horiz';
tab.append(icon);
entry = { tab, icon };
entries.set(panel.id, entry);
return entry;
};
@@ -256,9 +271,8 @@ export function createRightRailTabs({
documentRef.defaultView?.MutationObserver ?? globalThis.MutationObserver;
const observer =
typeof Observer === 'function' ? new Observer(() => sync()) : null;
// Tab heights follow their labels (fonts, locale) and panel heights their
// content and allocation: re-measure the join when the strip or a panel
// changes size.
// Panel heights follow their content and allocation: re-measure the join
// when the strip or a panel changes size.
const Resize = documentRef.defaultView?.ResizeObserver;
const resizer =
typeof Resize === 'function' ? new Resize(() => sync()) : null;
@@ -325,8 +339,7 @@ export function createRightRailTabs({
right: stripLeft + tab.offsetLeft + tab.offsetWidth,
},
corner,
// A tab shrunk by a short strip keeps corners no larger than half of it.
tabRadius: Math.min(tabRadius, (tabBottom - tabTop) / 2),
tabRadius,
fillet,
style: chamfer ? 'chamfer' : 'round',
};
@@ -342,7 +355,7 @@ export function createRightRailTabs({
join.hidden = false;
}
/** Reconcile tabs with the rail: order, visibility, selection, labels. */
/** Reconcile tabs with the rail: order, visibility, selection, names. */
function sync() {
if (destroyed) return;
// The column precedes its panels in the tab order, so Tab from the open
@@ -365,13 +378,13 @@ export function createRightRailTabs({
let cursor = strip.firstElementChild;
let open = null;
for (const panel of panels) {
const { tab, label } = entryFor(panel);
const { tab } = entryFor(panel);
if (tab !== cursor) strip.insertBefore(tab, cursor);
else cursor = cursor.nextElementSibling;
const collapsed = panel.classList.contains('collapsed');
if (!collapsed && !open) open = { tab, panel };
const name = panelTitle(panel);
if (label.textContent !== name) label.textContent = name;
tab.setAttribute('aria-label', name);
tab.setAttribute('aria-selected', String(!collapsed));
tab.tabIndex = panel === focusOwner ? 0 : -1;
tab.title = t(collapsed ? msg('Expand {name}') : msg('Collapse {name}'), {
+40 -44
View File
@@ -2,7 +2,9 @@ import assert from 'node:assert/strict';
import test from 'node:test';
import { fire, makeDocument } from '../testSupport/fakeDom.mjs';
import { readStylesheet } from '../testSupport/readStylesheet.mjs';
import { readFileSync } from 'node:fs';
import {
RAIL_TAB_ICONS,
createRightRailTabs,
nextRailTabIndex,
railJoinPath,
@@ -57,10 +59,10 @@ function fixture() {
const join = rail.querySelector('.right-rail-join');
const tabFor = (id) =>
strip.children.find((tab) => tab.dataset.railTab === id);
const labelOf = (tab) =>
const iconOf = (tab) =>
tab.children.find((child) =>
child.classList.contains('right-rail-tab-label'),
).textContent;
child.classList.contains('right-rail-tab-icon'),
);
return {
doc,
rail,
@@ -75,11 +77,11 @@ function fixture() {
join,
calls,
tabFor,
labelOf,
iconOf,
};
}
test('builds one tab per visible rail panel, in rail order, labelled from the panel header', () => {
test('builds one icon tab per visible rail panel, in rail order, named from the panel header', () => {
const f = fixture();
assert.equal(
f.rail.children[0],
@@ -98,22 +100,39 @@ test('builds one tab per visible rail panel, in rail order, labelled from the pa
assert.deepEqual(
f.strip.children.map((tab) => [
tab.dataset.railTab,
f.labelOf(tab),
f.iconOf(tab).textContent,
tab.getAttribute('aria-label'),
tab.getAttribute('aria-controls'),
]),
[
['pp-toggles', 'DISPLAY', 'pp-toggles'],
['cctv-panel', 'CCTV', 'cctv-panel'],
['global-context-panel', 'CONTEXT', 'global-context-panel'],
['pp-toggles', 'tune', 'DISPLAY', 'pp-toggles'],
['cctv-panel', 'videocam', 'CCTV', 'cctv-panel'],
[
'global-context-panel',
'travel_explore',
'CONTEXT',
'global-context-panel',
],
],
'a hidden panel (Weather off) has no tab',
);
for (const tab of f.strip.children) {
assert.equal(tab.getAttribute('role'), 'tab');
assert.equal(tab.type, 'button');
assert.equal(f.iconOf(tab).getAttribute('aria-hidden'), 'true');
}
});
test('the icon font subset carries every rail tab glyph', () => {
const html = readFileSync(
new URL('../../index.html', import.meta.url),
'utf8',
);
const subset = /icon_names=([a-z0-9_,]+)/.exec(html)[1].split(',');
for (const glyph of Object.values(RAIL_TAB_ICONS))
assert.ok(subset.includes(glyph), glyph);
});
test('mirrors collapsed state into aria-selected, a roving tabindex and titles', () => {
const f = fixture();
assert.deepEqual(
@@ -255,15 +274,6 @@ test('publishes the open tab extent on the rail and keeps a join element under t
glass.style.clipPath,
/^path\('M 16 0 L 314 0 A 16 16 0 0 1 330 16 L 330 78 /,
);
// A short strip shrinks the tab: its corners stay within half its height.
tab.offsetHeight = 12;
f.tabs.sync();
assert.equal(props.get('--rail-tab-bottom'), '98px');
assert.match(
glass.style.clipPath,
/L 366 86 A 6 6 0 0 1 372 92 A 6 6 0 0 1 366 98 L 338 98 /,
'a 12px tab gets 6px corners',
);
f.cctv.classList.add('collapsed');
f.tabs.sync();
assert.deepEqual([...props], [], 'no open panel, no join');
@@ -431,7 +441,7 @@ test('railJoinPath shrinks the fillet and corner to the room left, down to a sha
);
});
test('the stylesheet keeps phones on border-box, folds the strip into narrow desktops, shrinks tabs instead of scrolling and draws the focus ring inside', () => {
test('the stylesheet keeps phones on border-box, folds the strip into narrow desktops, keeps icon tabs square and whole without scrolling', () => {
const css = readStylesheet(new URL('../../style.css', import.meta.url));
assert.match(
css,
@@ -441,38 +451,24 @@ test('the stylesheet keeps phones on border-box, folds the strip into narrow des
css,
/@media \(min-width: 721px\) and \(max-width: 760px\) \{\s*#right-context-rail\[data-rail-tabs\] \{\s*width: calc\(330px - var\(--right-rail-tabs-inset\)\);/,
);
assert.match(css, /#right-rail-tabs \{[^}]*overflow: hidden;/);
// The tabs keep their size: a square icon, never shrunk or rotated.
assert.match(
css,
/\.right-rail-tab \{[^}]*flex: 0 0 auto;[^}]*width: var\(--right-rail-tabs-width\);\s*height: var\(--right-rail-tabs-width\);/,
);
assert.doesNotMatch(
css,
/#right-rail-tabs \{[^}]*(overflow: [^;]*auto|scrollbar-width)/,
/\.right-rail-tab[^{]*\{[^}]*(writing-mode|mask-image)/,
);
// The tabs give way to a short strip and clip their labels at the end.
assert.match(
// The column keeps its natural height: every tab stays whole, nothing
// scrolls or clips; only the strip and the slot take the pointer.
assert.doesNotMatch(
css,
/\.right-rail-tab \{[^}]*flex: 1 1 0%;[^}]*align-items: flex-start;[^}]*min-height: 0;[^}]*max-height: max-content;[^}]*overflow: hidden;[^}]*mask-image: linear-gradient\(to bottom, #000 calc\(100% - 6px\), transparent\);/,
);
// The column stands the lane's full height so the strip's size is definite
// and a zero basis shares it; only the strip and the slot take the pointer.
assert.match(
css,
/#right-rail-column \{[^}]*height: var\(--right-stack-max-height, 70vh\);[^}]*pointer-events: none;/,
/#right-rail-(column|tabs) \{[^}]*(max-height|overflow|scrollbar-width)/,
);
assert.match(css, /#right-rail-column \{[^}]*pointer-events: none;/);
assert.match(css, /#right-rail-tabs \{[^}]*pointer-events: auto;/);
assert.match(css, /#right-rail-tools \{[^}]*pointer-events: auto;/);
assert.doesNotMatch(
css,
/#right-rail-column \{[^}]*max-height: var\(--right-stack-max-height/,
);
// The rail layout pass measures the column's natural height to climb above
// its baseline before the strip has to scroll.
assert.match(
css,
/#right-context-rail\[data-rail-measuring\] > #right-rail-column \{\s*height: auto !important;/,
);
assert.match(
css,
/#right-rail-tabs > \.right-rail-tab:focus-visible \{\s*outline-offset: -3px;/,
);
assert.match(css, /--right-rail-panel-cover: min\(/);
// The column hides on phones, where the Radio launcher is back in Context's
// header; in the slot the launcher's popover opens upward and to the left.
+18 -47
View File
@@ -1,5 +1,5 @@
/* ── Right rail as vertical tabs ───────────────────────────────────────────
A sideways tab strip (rightRailTabs.js) hugs the rail's right edge and the
A strip of icon tabs (rightRailTabs.js) hugs the rail's right edge and the
open panel sits beside it. Panels keep their `collapsed` state; the rail's
exclusive layout (rightPanelRail.js) hides the collapsed ones, so the strip
is the only launcher. The compact Radio launcher, which rides in Context's
@@ -12,7 +12,7 @@
The glass layer blurs and tints the backdrop inside the path, one SVG
strokes the outline and another casts the shadow, so the junction has no
seam and no second blur region. The open panel and the tab draw no surface
of their own; the other tabs are plain labels. `data-rail-tabs` arrives on
of their own; the other tabs are plain icons. `data-rail-tabs` arrives on
the rail with the strip, so a rail without it keeps its plain geometry; the
selectors carry it to outrank the Cyber rail rules that follow this sheet. */
@@ -58,9 +58,9 @@
}
/* The strip's column: the tablist, then a tool slot for the compact Radio
launcher (radioPanelPlacement.js). It stands the lane's full height, which
makes the strip's size definite for the tabs' share (.right-rail-tab); only
the strip and the slot take the pointer. */
launcher (radioPanelPlacement.js). It keeps its natural height, so every
tab stays whole even past a short lane; only the strip and the slot take
the pointer. */
#right-rail-column {
position: absolute;
top: 0;
@@ -70,28 +70,17 @@
flex-direction: column;
box-sizing: border-box;
width: var(--right-rail-tabs-inset);
height: var(--right-stack-max-height, 70vh);
pointer-events: none;
}
/* The layout pass reads the column's natural height, to climb when the lane
is too short for it (rightPanelRail.js). */
#right-context-rail[data-rail-measuring] > #right-rail-column {
height: auto !important;
}
#right-rail-tabs {
position: relative;
display: flex;
flex: 0 1 auto;
flex-direction: column;
gap: 4px;
box-sizing: border-box;
min-height: 0;
/* Room for the open tab's bridge on the left. */
padding-left: var(--right-rail-tabs-gap);
/* Never a scrollbar: a lane too short shrinks the tabs (.right-rail-tab). */
overflow: hidden;
pointer-events: auto;
}
@@ -110,7 +99,7 @@
display: none;
}
/* In the slot the launcher is one more label in the column: no card, the
/* In the slot the launcher is one more icon in the column: no card, the
tab's corner, the accent while the radio plays. Its popover opens to the
left over the panel, bottom-aligned so it stays inside the lane. */
#right-rail-tools .context-radio-dock {
@@ -163,38 +152,22 @@ body:not(.mobile-shell)
display: none;
}
/* The tab's mask covers its own box only: the focus ring draws inside it. */
#right-rail-tabs > .right-rail-tab:focus-visible {
outline-offset: -3px;
}
/* A short strip is shared out equally: the zero basis hands every tab the
same part, a tab's own length caps it, and a short label's leftover goes
to the longer ones; with room each tab is its own length. The basis is
0%, not 0, so the strip still sizes to its labels. The label clips at the
end, faded over the bottom padding. */
/* A square icon the strip's width; the panel's name is the tab's accessible
name and tooltip. */
.right-rail-tab {
position: relative;
display: flex;
flex: 1 1 0%;
align-items: flex-start;
flex: 0 0 auto;
align-items: center;
justify-content: center;
box-sizing: border-box;
width: var(--right-rail-tabs-width);
min-height: 0;
max-height: max-content;
padding: 6px 0;
overflow: hidden;
mask-image: linear-gradient(to bottom, #000 calc(100% - 6px), transparent);
height: var(--right-rail-tabs-width);
padding: 0;
border: 0;
border-radius: var(--right-rail-tab-radius);
background: transparent;
color: var(--text-dim);
font-family: var(--font-mono);
font-size: 9px;
line-height: 1;
letter-spacing: 1.6px;
text-transform: uppercase;
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.7);
cursor: pointer;
transition:
@@ -202,11 +175,9 @@ body:not(.mobile-shell)
color var(--transition-fast);
}
.right-rail-tab-label {
display: block;
writing-mode: vertical-rl;
text-orientation: mixed;
white-space: nowrap;
.right-rail-tab-icon {
color: inherit;
font-size: 19px;
}
.right-rail-tab:not([aria-selected='true']):hover {
@@ -214,8 +185,8 @@ body:not(.mobile-shell)
color: var(--text-primary);
}
/* The open tab is the label and hit area over the join's surface; it spans
the bridge too, with the label kept in the strip's column. */
/* The open tab is the icon and hit area over the join's surface; it spans
the bridge too, with the icon kept in the strip's column. */
.right-rail-tab[aria-selected='true'] {
width: calc(var(--right-rail-tabs-width) + var(--right-rail-tabs-gap));
margin-left: calc(-1 * var(--right-rail-tabs-gap));
@@ -331,7 +302,7 @@ body:not(.mobile-shell)
}
/* Cyber's generic hover/selected button treatment (cyber.css) would paint
the tabs; the open tab stays a label on the shared surface. */
the tabs; the open tab stays an icon on the shared surface. */
:root[data-ui-theme='cyber'] .right-rail-tab:not([aria-selected='true']):hover {
background-color: rgba(226, 235, 236, 0.08);
box-shadow: none;