, which is why the UI half lives in panel.ts at the "page" stage.
A11Y_CSS is effects.css, passed in as a string argument by the integration.
========================================================================== */
try {
var W = window;
var D = document;
var H = D.documentElement;
/* Bail. /admin and /login are operator-only SSR surfaces that run their own
dark-mode system (src/layouts/Layout.astro), and data-a11y-disable is the
documented client escape hatch. On a bail we return WITHOUT defining
window.a11y — that absence is the single signal panel.ts keys on, so it
also covers this file having thrown. */
if (/^\/(admin|login)(\/|$)/.test(W.location.pathname)) return;
if (H.hasAttribute("data-a11y-disable")) return;
var KEY = "a11y:prefs";
var VERSION = 1;
/* Every value is a string enum, never a boolean: costs nothing today and
lets a future third state land without a storage migration.
`always:1` means the attribute is present whatever the value, so clients
test one thing (data-a11y-motion) rather than presence-or-value. */
var DEFS = [
{ k: "motion", attr: "data-a11y-motion", on: "reduce", off: "allow", os: "(prefers-reduced-motion: reduce)", always: 1 },
{ k: "links", attr: "data-a11y-links", on: "underline", off: null },
{ k: "spacing", attr: "data-a11y-spacing", on: "loose", off: null },
{ k: "font", attr: "data-a11y-font", on: "readable", off: null },
{ k: "focus", attr: "data-a11y-focus", on: "strong", off: null },
{ k: "contrast", attr: "data-a11y-contrast", enums: ["standard", "high"], off: null, os: "(prefers-contrast: more)", osValue: "high" },
{ k: "scheme", attr: "data-a11y-scheme", enums: ["light", "dark"], off: null, os: "(prefers-color-scheme: dark)", osValue: "dark" }
];
/* side + min are host-only: stored here, applied by panel.ts. They get no
attribute on because nothing in client CSS should key on them. */
var HOST = { side: ["start", "end"], min: ["yes"] };
/* ---- storage -----------------------------------------------------------
localStorage throws outright in some privacy modes, so every access is
wrapped and falls back to an in-memory object: the panel still works for
the session, it just does not persist. */
var mem = null;
var memOn = false;
function rawRead() {
if (memOn) return mem;
var s;
try { s = W.localStorage.getItem(KEY); } catch (e) { memOn = true; return mem; }
if (s == null) return null;
var o;
try { o = JSON.parse(s); } catch (e) { return null; }
if (!o || typeof o !== "object") return null;
if (Object.prototype.toString.call(o) === "[object Array]") return null;
if (typeof o.v !== "number") return null;
return o;
}
function rawWrite(o) {
if (!memOn) {
try { W.localStorage.setItem(KEY, JSON.stringify(o)); return; } catch (e) { memOn = true; }
}
mem = o;
}
/* Effective preferences. A future version is IGNORED but never deleted, so
rolling the build back loses nothing. A past version would migrate here. */
function prefs() {
var o = rawRead();
if (!o) return {};
if (o.v > VERSION) return {};
return o;
}
/* ---- resolution --------------------------------------------------------
explicit stored value > OS media query > built-in default
Never write a value just because the OS said so. Persisting a detected
value makes "unset" indistinguishable from "chosen" and the site stops
tracking the OS forever — the bug that silently kills OS-following. */
function mq(q) {
try { return W.matchMedia(q); } catch (e) { return null; }
}
function mqOn(q) {
var m = mq(q);
return !!(m && m.matches);
}
function defOf(k) {
for (var i = 0; i < DEFS.length; i++) { if (DEFS[i].k === k) return DEFS[i]; }
return null;
}
function allowed(d, v) {
var list = d.enums;
if (list) {
for (var i = 0; i < list.length; i++) { if (list[i] === v) return true; }
return false;
}
return v === d.on || v === d.off;
}
function hostAllowed(k, v) {
var list = HOST[k];
if (!list) return false;
for (var i = 0; i < list.length; i++) { if (list[i] === v) return true; }
return false;
}
function resolved(k) {
var p = prefs();
var v = p[k];
var d = defOf(k);
if (!d) return hostAllowed(k, v) ? v : null;
if (v != null && allowed(d, v)) return v;
if (d.os && mqOn(d.os)) return d.osValue || d.on;
return d.off;
}
function apply() {
for (var i = 0; i < DEFS.length; i++) {
var d = DEFS[i];
var v = resolved(d.k);
if (v == null) H.removeAttribute(d.attr);
else H.setAttribute(d.attr, v);
}
}
/* ---- effects sheet ----------------------------------------------------
Inserted unconditionally: predictability beats micro-optimisation, and
every selector is gated on a root attribute that fails immediately when
absent, so the universal-selector motion rules cost nothing when motion
resolves to "allow". */
function injectCss() {
if (D.getElementById("a11y-effects")) return;
var s = D.createElement("style");
s.id = "a11y-effects";
s.appendChild(D.createTextNode(A11Y_CSS));
(D.head || H).appendChild(s);
}
function fire(k) {
try {
D.dispatchEvent(new W.CustomEvent("a11y:change", {
detail: {
key: k,
value: k == null ? null : prefs()[k],
resolved: k == null ? null : resolved(k)
}
}));
} catch (e) {}
}
function set(k, v) {
if (v != null) {
var d = defOf(k);
if (d ? !allowed(d, v) : !hostAllowed(k, v)) return;
}
var stored = rawRead();
/* A stored version ahead of ours cannot be safely merged, so the first
explicit change replaces it wholesale. Acceptable: the user is actively
choosing at that moment. Otherwise read-modify-write preserves unknown
keys so a newer build's settings survive a rollback. */
var future = !!(stored && stored.v > VERSION);
var o = future || !stored ? {} : stored;
o.v = VERSION;
if (v == null) delete o[k];
else o[k] = v;
rawWrite(o);
apply();
fire(k);
}
function reset() {
if (!memOn) {
try { W.localStorage.removeItem(KEY); } catch (e) { memOn = true; }
}
mem = null;
apply();
fire(null);
}
apply();
injectCss();
var api = {
version: VERSION,
get: function (k) { return prefs()[k]; },
resolved: resolved,
set: set,
reset: reset,
motionReduced: function () { return resolved("motion") === "reduce"; }
};
/* caps is a LIVE getter and is never read pre-paint. In a production build
the client CSS is a before this script and a classic blocking
script waits on pending stylesheets, so getComputedStyle would resolve —
but under `astro dev` the CSS arrives through Vite's module graph and a
pre-paint read can return empty. That failure would be silent and
byte-identical to a client that never opted in. Nothing pre-paint needs
capabilities: the gated fieldsets live in the lazily-imported panel body. */
try {
Object.defineProperty(api, "caps", {
enumerable: true,
get: function () {
var raw = "";
try {
raw = getComputedStyle(H).getPropertyValue("--a11y-caps") || "";
} catch (e) {}
/* getPropertyValue returns the token stream verbatim, leading space and
literal quotes included, so a naive split yields ["", "\"contrast",
"dark\""] and every capability check silently fails. */
return raw.replace(/["']/g, " ").replace(/^\s+|\s+$/g, "").split(/\s+/)
.filter(function (t) { return !!t; });
}
});
} catch (e) {
api.caps = [];
}
W.a11y = api;
/* While a key is absent the OS query is authoritative and LIVE. Once the
user has chosen, an OS change must not move the site under them. */
for (var i = 0; i < DEFS.length; i++) {
(function (d) {
if (!d.os) return;
var m = mq(d.os);
if (!m) return;
var onChange = function () {
if (prefs()[d.k] != null) return;
apply();
fire(d.k);
};
if (m.addEventListener) m.addEventListener("change", onChange);
else if (m.addListener) m.addListener(onChange);
})(DEFS[i]);
}
try {
W.addEventListener("storage", function (e) {
if (e && e.key && e.key !== KEY) return;
mem = null;
memOn = false;
apply();
fire(null);
});
} catch (e) {}
} catch (e) {}
})("/* ============================================================================\n a11y effects sheet.\n\n Injected pre-paint by boot.js as the LAST node in
.\n ------------------------------------------------------------------------- */\n:root[data-a11y-spacing=\"loose\"]\n :is(main, article, footer, [role=\"main\"], [role=\"contentinfo\"])\n :is(p, li, dd, dt, blockquote, figcaption, summary, td, th):not(nav *) {\n line-height: 1.5 !important;\n letter-spacing: 0.12em !important;\n word-spacing: 0.16em !important;\n overflow-wrap: break-word !important;\n min-width: 0;\n}\n:root[data-a11y-spacing=\"loose\"]\n :is(main, article, footer, [role=\"main\"], [role=\"contentinfo\"])\n :is(p, blockquote, figcaption):not(nav *) { margin-block-end: 2em !important; }\n\n/* ---------- Readable font (prose only) -------------------------------------\n Honest scope: this switches PROSE type to the system stack. The strongest\n real win is a client's condensed display face — on 4bkstorage that is\n --f-display, a \"Google Sans Flex Cond\" family pinned to a 75% width axis,\n used for every heading inside main/footer.\n\n It deliberately does NOT touch letter-spacing (the spacing setting owns\n that) and does NOT touch text-transform: killing uppercase would render the\n brand as \"bk storage\" — misrepresentation, not accessibility. Nav micro-\n labels (.brand-l2 :397, .sheet-title :516) are out of scope by the header\n rule above; that is the price of deleting the header regression, and it is\n the right trade.\n\n `font-stretch: normal` and `font-variation-settings: normal` are NOT set,\n and must not be: a client may pin a variable font's width axis with a\n `font-stretch` DESCRIPTOR inside @font-face rather than a declaration on the\n element (4bkstorage does exactly this to get a condensed display family and a\n normal body family out of one file). A descriptor belongs to the @font-face,\n so replacing font-family drops the pinned width along with it. Resetting\n those properties here would instead fight any client that legitimately sets\n them on elements, for no gain.\n\n CONTRACT NOTE for clients: if you set `font-variation-settings` on ELEMENTS\n rather than as an @font-face descriptor, this override cannot reset it and\n the system stack may inherit meaningless axis values. Pin axes in\n @font-face.\n\n Ligatures: `none` includes `no-contextual`, which breaks Arabic and several\n Indic scripts. Narrowed to the three Latin-disambiguation families only.\n ------------------------------------------------------------------------- */\n:root[data-a11y-font=\"readable\"]\n :is(main, article, footer, [role=\"main\"], [role=\"contentinfo\"]),\n:root[data-a11y-font=\"readable\"]\n :is(main, article, footer, [role=\"main\"], [role=\"contentinfo\"])\n *:not(code, pre, kbd, samp, svg, svg *, iframe, nav, nav *, a11y-prefs, a11y-prefs *) {\n font-family: var(--a11y-readable-stack,\n system-ui, -apple-system, \"Segoe UI\", Roboto, \"Helvetica Neue\", Arial, sans-serif) !important;\n font-variant-ligatures: no-common-ligatures no-discretionary-ligatures\n no-historical-ligatures !important;\n}\n\n/* ---------- Strong focus ---------------------------------------------------\n White halo hugged by a black outline. Verified on 4bkstorage's palette:\n #fff is 13.69:1 on --navy #0c2e54 and 11.43:1 on --deep #0f3a6b; #000 is\n 21:1 on white. So one edge always clears SC 1.4.11's 3:1 whatever it lands\n on. GOV.UK yellow alone would not (#ffdd00 is 1.35:1 on white).\n\n !important is mandatory: .field input:focus{outline:none} (bkstorage.css\n :1489-1495) is (0,2,1) and beats :root[data-a11y-focus] :focus-visible at\n (0,2,0) — it would kill the ring on exactly the form fields where it matters\n most.\n\n NO border-radius declaration. At (0,3,0) it would outrank .btn's 11px\n (:265) and .btn-lg's var(--radius) (:275) and snap every focused button to a\n near-square on focus and back on blur. `outline` follows the element's own\n border shape in every current engine, which is the desired result for free.\n\n offset 2px + a 2px halo (5px total beyond the border box, was 7px) plus\n scroll-margin, because the ring is drawn inside the client's own clipping\n ancestors: .hero (:592), .type (:947), .cta (:1296), and body{overflow-x:\n hidden} (:134).\n ------------------------------------------------------------------------- */\n:root[data-a11y-focus=\"strong\"] :focus-visible {\n outline: 3px solid #000 !important;\n outline-offset: 2px !important;\n box-shadow: 0 0 0 2px #fff !important;\n scroll-margin: 12px;\n}\n@media (forced-colors: active) {\n :root[data-a11y-focus=\"strong\"] :focus-visible {\n outline-color: Highlight !important;\n box-shadow: none !important;\n }\n}\n\n/* ---------- Panel plumbing ------------------------------------------------\n No scroll lock. `:root{overflow:hidden}` was deleted: 4bkstorage's\n body{overflow-x:hidden} (:134) makes viewport-overflow propagation\n engine-dependent, and iOS Safari — the only platform where the bottom-sheet\n layout exists — ignores overflow:hidden for touch scrolling. It was\n unverifiable behaviour that changed the page under the user and contradicted\n the non-modal ruling. overscroll-behavior:contain on the sheet plus\n focusout-closes at <=40rem replaces it. Never write body.style.overflow:\n both clients' Nav.tsx reset it to \"\" unconditionally (4bkstorage :29-32,\n rockin2ind :40-43) and whichever closed last would destroy the other's lock.\n ------------------------------------------------------------------------- */\n@media print { a11y-prefs { display: none !important; } }\n"); Skip to content
Texas Hydro DNT is a tint, ceramic and detail shop in Odessa: one
bay, one standard, and a steady share of Midland vehicles in it. For
commercial and fleet accounts we come the other way instead.
Tint, ceramic coating, paint correction, metal polishing and
windshield replacement are all shop work, and they are shop work for
a reason. Film has to be cut and laid in controlled conditions and
then left to cure. A coating needs the paint corrected first and
needs hours to bond. Glass needs the adhesive to set before the
vehicle is safe to drive.
None of that happens in a driveway, and any shop telling you
otherwise is cutting the part you cannot see. So Midland customers
drop off, and most full day jobs go in first thing.
Mobile work
We come to you.
Concierge mobile service runs to Midland yards for commercial and
fleet accounts: exterior wash, interior clean out, and a walk
around before inspection on a schedule that fits your routes.
Semi tractors and trailers welcome.
Commercial and residential flat glass is the other one that travels:
storefronts, offices and homes get measured and filmed on site.
Midland and Odessa share the same climate and the same
problem: UV that fades paint and cracks dashboards, caliche dust
that is abrasive enough to scratch clear coat every time somebody
wipes a panel dry, and hard water that etches if it sits on hot
paint. Tint, coatings and interior protectants are not cosmetics
here. They are what keeps a vehicle from ageing at double speed.
Work trucks get the same standard
A lot of what comes over from Midland is not a show car. It is a
crew cab that has been earning across the Basin, and the standard
does not change for it: same decontamination, same stages, same
honest read on what will and will not come back. Fleets get the
recurring version of that at their own yard.
Midland questions
Straight answers.
Do you have a shop in Midland?
No, and we would rather say so than let you find out in a parking lot. The bay is in Odessa, about twenty minutes west on I-20, and a good share of the vehicles in it on any given day drove over from Midland. What does come to Midland is the mobile rig, for fleet and commercial accounts.
Do you do mobile service in Midland?
For commercial and fleet accounts, yes: exterior wash, interior clean out and a walk around before inspection, at your yard, on a schedule that fits your routes. Tint, ceramic coating, correction and glass are shop work: they need controlled conditions and cure time, so those come to the bay.
Is it worth driving from Midland for tint?
That is your call, but here is the honest version of the trade: we are an Autobahn authorized dealer, so you are picking from the films actually in the building rather than off a catalogue, and the entry level auto film carries a lifetime warranty. If the shop down the road from you offers the same film at the same tier, the drive is not worth it. Most of the time that is not the conversation people are having when they call.
Can you do commercial or residential glass in Midland?
Yes: storefronts, offices and homes. Flat glass is measured and filmed on site, so Midland is no different from Odessa for that work. Call (432) 250-7554 with the number of windows and the exposure and we'll quote it.
How do I plan the drive over?
Mon to Fri 8am to 6pm, Sat 9am to 6pm, Sun closed. Call ahead and we'll book a slot rather than have you sit; the whole point of scheduling is that each vehicle gets the bay time it needs. For a full day's work most Midland customers drop off in the morning.
Book from Midland.
Call and describe the vehicle. We'll tell you what it needs, what it
doesn't, and whether it's a drop off or a wait, before you make the
drive.