diff --git a/TWA-App/TWA-App.csproj b/TWA-App/TWA-App.csproj index 15015cf..1911cb7 100644 --- a/TWA-App/TWA-App.csproj +++ b/TWA-App/TWA-App.csproj @@ -73,6 +73,7 @@ + diff --git a/TWA-App/Views/Services/Index.cshtml b/TWA-App/Views/Services/Index.cshtml index cd8f704..6a97d43 100644 --- a/TWA-App/Views/Services/Index.cshtml +++ b/TWA-App/Views/Services/Index.cshtml @@ -1,86 +1,114 @@ 
-
- Background -
-
-
-

@ViewData["Title"]

-
-
+
+ Background +
+
+
+

@ViewData["Title"]

+
+
-
-
-
-

Catagories

-
- @Html.Raw(await ServicesModel.GetSidebar(2)) -
-
-
-
-
-

Why GSSA Services Are Essential

-
-

In today’s competitive air cargo market, GSSA services are essential for airlines and brokers looking to optimize their operations and maximize revenue. More than 26% of global air cargo is managed through GSSAs, and this market share continues to grow as more airlines leverage these services to expand their presence in new regions​.

-

By working with GSSA, airlines and brokers gain access to comprehensive services such as cargo sales, market analysis, capacity management and compliance with local regulations. This approach allows them to capture new business opportunities and reduce operational costs​.

-
-

Key Benefits of GSSA Services

-
-
-
-
-
- -
-

Cost Reduction

-

- Using a GSSA eliminates the need for airlines to establish local offices or hire dedicated sales teams in each region. This reduces overhead costs while ensuring a strong market presence -

-
-
-
-
-
- -
-

Local Expertise and Market Knowledge

-

+

+
+
+

Catagories

+
+ @Html.Raw(await ServicesModel.GetSidebar(2)) +
+
+
+
+
+

+ We Are Air Cargo GSSA +

+

If You’ve Ever Heard of A Cargo GSSA Before, This Is How It Works

+ Air Cargo GSSA illustration +
+
+
+
+

Being a Cargo GSSA/GSA means

+
+
    +
  • + + General Sales & Service Agents (GSSAs)/General Sales Agents (GSAs) represent airlines commercializing air freight capacity and supervise complex local cargo operations and administration and financial services for a assigned specific territory or region +
  • +
  • + + Represent for Airlines typically work with freight forwarders and handling agents (GHAs) to deliver transportation services to shippers. +
  • +
  • + + A extremely helpful contract with minimum cost, resources and top efficiency for Airlines in looking to expand into new markets or streamline costs in an existing location +
  • +
+
+
+
+ +
+
+
+
+

Why any Airlines should operate as Cargo GSSAs/GSAs model

+
+
+
+
+
+ +
+

Cost Reduction

+

+ Using a GSSA eliminates the need for airlines to establish local offices or hire dedicated sales teams in each region. This reduces overhead costs while ensuring a strong market presence +

+
+
+
+
+
+ +
+

Local Expertise and Market Knowledge

+

GSSAs possess in-depth knowledge of local markets and maintain strong relationships with key stakeholders, enabling them to maximize revenue for airlines by tapping into regional opportunities​

-
-
-
-
-
- -
-

Flexibility and Innovation

-

- GSSAs provide flexible solutions that adapt to changing market conditions. From digital booking systems to real-time data analytics, GSSAs help airlines and brokers optimize routes, reduce costs and ensure efficient cargo handling -

-
-
-
-
-
-
-

- Our GSSA Services at TWA -

-
-

- Air Cargo Green Capabilities offers a range of specialized GSSA services to help airlines and brokers streamline their cargo operations and maximize revenue: -

-
    -
  • GSSA Counseling: Air Cargo Green Capabilities provides airlines with innovative solutions to optimize costs and improve operational efficiency. By implementing eco-friendly approaches, we help airlines reduce environmental impact and capture a larger share in premium cargo segments, such as pharmaceuticals and oversized goods.
  • -
  • GSSA Pharma: Focusing on the safe transport of pharmaceutical goods, GSSA Pharma offers specialized risk assessment and logistics solutions to ensure the integrity of sensitive shipments. Our collaboration with major global organizations like UNICEF and the Red Cross enables us to support humanitarian projects while expanding the reach of pharma logistics.
  • -
  • GSSA for Airlines: We help airlines improve their cargo operations by maximizing the use of high-demand cargo segments like pharma, e-commerce, and oversized goods. Air Cargo Green Capabilities assists airlines with capacity management and long-term planning, ensuring they meet growing market demands while optimizing revenue.
  • -
  • GSSA for Charter Brokers: This service supports charter brokers in expanding their market reach and improving operational flexibility. We help brokers discover new charter opportunities and manage the logistics of complex operations, ensuring they can meet the needs of their clients efficiently and profitably.
+
+
+
+
+
+ +
+

Flexibility and Innovation

+

+ GSSAs provide flexible solutions that adapt to changing market conditions. From digital booking systems to real-time data analytics, GSSAs help airlines and brokers optimize routes, reduce costs and ensure efficient cargo handling +

+
+
+
+
+
+
+

+ Our GSSA Services at TWA +

+
+

+ Air Cargo Green Capabilities offers a range of specialized GSSA services to help airlines and brokers streamline their cargo operations and maximize revenue: +

+
    +
  • GSSA Counseling: Air Cargo Green Capabilities provides airlines with innovative solutions to optimize costs and improve operational efficiency. By implementing eco-friendly approaches, we help airlines reduce environmental impact and capture a larger share in premium cargo segments, such as pharmaceuticals and oversized goods.
  • +
  • GSSA Pharma: Focusing on the safe transport of pharmaceutical goods, GSSA Pharma offers specialized risk assessment and logistics solutions to ensure the integrity of sensitive shipments. Our collaboration with major global organizations like UNICEF and the Red Cross enables us to support humanitarian projects while expanding the reach of pharma logistics.
  • +
  • GSSA for Airlines: We help airlines improve their cargo operations by maximizing the use of high-demand cargo segments like pharma, e-commerce, and oversized goods. Air Cargo Green Capabilities assists airlines with capacity management and long-term planning, ensuring they meet growing market demands while optimizing revenue.
  • +
  • GSSA for Charter Brokers: This service supports charter brokers in expanding their market reach and improving operational flexibility. We help brokers discover new charter opportunities and manage the logistics of complex operations, ensuring they can meet the needs of their clients efficiently and profitably.
-
-
+
+
@@ -88,6 +116,6 @@ GSSAs possess in-depth knowledge of local markets and maintain strong relationsh @Html.Raw(ViewBag.Content) @section jsLib { - - + + } \ No newline at end of file diff --git a/TWA-App/wwwroot/css/site.css b/TWA-App/wwwroot/css/site.css index 4465400..bed4ae5 100644 --- a/TWA-App/wwwroot/css/site.css +++ b/TWA-App/wwwroot/css/site.css @@ -2129,7 +2129,7 @@ video.bg-video { padding: 25px; background: var(--text-color-lW3); border-radius: var(--radius); - margin-bottom: 40px + transition: top .3s ease-in-out } .sidebarR .nav-item { font-size: .98rem; diff --git a/TWA-App/wwwroot/images/3000/3001/gssa-operations-team.png b/TWA-App/wwwroot/images/3000/3001/gssa-operations-team.png new file mode 100644 index 0000000..ea1de83 Binary files /dev/null and b/TWA-App/wwwroot/images/3000/3001/gssa-operations-team.png differ diff --git a/TWA-App/wwwroot/images/3000/3001/gssa-overview.png b/TWA-App/wwwroot/images/3000/3001/gssa-overview.png new file mode 100644 index 0000000..de0ce94 Binary files /dev/null and b/TWA-App/wwwroot/images/3000/3001/gssa-overview.png differ diff --git a/TWA-App/wwwroot/js/js-page/3000.js b/TWA-App/wwwroot/js/js-page/3000.js index 5ff4dfb..e72f391 100644 --- a/TWA-App/wwwroot/js/js-page/3000.js +++ b/TWA-App/wwwroot/js/js-page/3000.js @@ -1,5 +1,5 @@ import AMenu from '/js/libs/js-AMenu.js' - +import AElementFixed from '/js/libs/js-AElementFixed.js' function runServices () { const parallaxEls = document.querySelectorAll('.con-page-section .parallax'); @@ -24,6 +24,12 @@ function runServices () { window.LoadingServices = function () { runServices(); + const aef = new AElementFixed({ + ref: '#fHeader', + target: '.con-sidebarR', + breakpoint: 992, + unsnapThreshold: 40 + }); } function updateParallax(t, parallaxEls) { diff --git a/TWA-App/wwwroot/js/libs/js-AElementFixed.js b/TWA-App/wwwroot/js/libs/js-AElementFixed.js new file mode 100644 index 0000000..9376146 --- /dev/null +++ b/TWA-App/wwwroot/js/libs/js-AElementFixed.js @@ -0,0 +1,264 @@ +// AElementFixed.snap-up-near-ref.js +// - Snap xuống (portal lên body) khi PROBE ra khỏi top viewport (như trước) +// - Khi lướt LÊN lại: nếu TARGET (đang ở trong CHA) tiến gần REF (±snapUpProximity quanh ref.bottom+unsnapThreshold) → SNAP ABSOLUTE +// - Unsnap khi PROBE vào lại viewport và |ref.top - target.top| > unsnapThreshold (giữ logic cũ) +// - Khi đang snap: nếu đáy TARGET chạm/vượt đáy CHA → trả về CHA & pin bottom:0 +export default class AElementFixed extends window.AObject { + constructor(opts = {}) { + super(); + this.o = Object.assign({ + ref: null, // Element | selector + target: null, // Element | selector + breakpoint: 992, // < => mobile: unsnap + unsnapThreshold: 40, // px: ngưỡng unsnap và offset top khi snap + enableTargetBottomSnap: true, // snap nếu target ra đáy viewport (khi đang ở CHA) + gutterLeft: 20, // px: left khi pin bottom về CHA + snapUpProximity: 24 // px: khoảng "gần" để snap khi lướt lên lại + }, opts); + + this.ref = typeof this.o.ref === 'string' ? document.querySelector(this.o.ref) : this.o.ref; + this.el = typeof this.o.target === 'string' ? document.querySelector(this.o.target) : this.o.target; + if (!this.ref || !this.el) throw new Error('[AElementFixed] Missing ref/target'); + + // state + this.snapped = false; + this._isPortaled = false; + this._hostParent = null; // CHA ban đầu + this._loopId = null; // rAF: loop khi probe trong viewport (check snap-up & unsnap) + this._snapLoopId = null; // rAF: monitor chạm đáy CHA khi đang snap + this._needIntroPose = true;// hiệu ứng intro khi snap + this._snapWidth = null; // lock width (px) khi portal + + this._bind(); + this._initObservers(); + this._onResize(); + } + + update(opts = {}) { + Object.assign(this.o, opts); + this._stopInViewLoop(); + this._stopSnapMonitor(); + this._teardownObservers(); + this._initObservers(); + this._onResize(); + } + + destroy() { + this._stopInViewLoop(); + this._stopSnapMonitor(); + this._teardownObservers(); + this._unsnapAbsolute(); + window.removeEventListener('resize', this._onResize); + } + + // ===== private ===== + _bind() { + this._onResize = this._onResize.bind(this); + window.addEventListener('resize', this._onResize); + } + + _onResize() { + const desktop = window.innerWidth >= this.o.breakpoint; + if (!desktop) { + this._stopInViewLoop(); + this._stopSnapMonitor(); + this._unsnapAbsolute(); + } + } + + _initObservers() { + // PROBE: đặt TRƯỚC target trong CHA để giữ vị trí flow + this.probe = document.createElement('div'); + this.probe.setAttribute('data-aef-probe', ''); + Object.assign(this.probe.style, { width: '1px', height: '1px', pointerEvents: 'none', opacity: 0 }); + this.el.parentElement.insertBefore(this.probe, this.el); + + // PROBE với root = viewport + this.ioProbeVP = new IntersectionObserver((entries) => { + for (const e of entries) { + // PROBE rời viewport phía TRÊN => SNAP ngay + if (!e.isIntersecting && e.boundingClientRect.top < 0) { + if (!this.snapped) this._snapAbsolute(); + this._stopInViewLoop(); + continue; + } + // PROBE vào viewport => bật loop để: + // - nếu đang snap: xét UNSNAP + // - nếu không snap: xét SNAP-UP khi target gần ref + if (e.isIntersecting) { + this._needIntroPose = true; + this._startInViewLoop(); + } + } + }, { root: null, threshold: 0 }); + this.ioProbeVP.observe(this.probe); + + // (Tuỳ chọn) TARGET với root = viewport: out đáy viewport (khi ở CHA) → snap + if (this.o.enableTargetBottomSnap) { + this.ioTargetVP = new IntersectionObserver((entries) => { + for (const e of entries) { + if (this.snapped) continue; + // Bắt "đáy chạm đáy viewport" (vẫn intersect) để mượt hơn: + const vpBottom = e.rootBounds ? e.rootBounds.bottom : document.documentElement.clientHeight; + const EPS = 0.75; + if (e.isIntersecting) { + console.log(e.target.style.bottom); + if (e.target.style.bottom === '0px') { + this._startInViewLoop(); + + } + } + } + }, { root: null, threshold: [0, 1] }); + this.ioTargetVP.observe(this.el); + } + } + + _teardownObservers() { + if (this.ioProbeVP) { this.ioProbeVP.disconnect(); this.ioProbeVP = null; } + if (this.ioTargetVP) { this.ioTargetVP.disconnect(); this.ioTargetVP = null; } + if (this.probe && this.probe.parentNode) this.probe.parentNode.removeChild(this.probe); + this.probe = null; + } + + // ===== Loop khi PROBE đang trong viewport: xét SNAP-UP & UNSNAP ===== + _startInViewLoop() { + if (this._loopId) return; + const tick = () => { + const rr = this.ref.getBoundingClientRect(); + const tr = this.el.getBoundingClientRect(); + const anchor = rr.bottom + this.o.unsnapThreshold; // mốc bám khi snap + + if (this.snapped) { + // UNSNAP: nếu lệch khỏi mốc quá ngưỡng (tránh dính khi user kéo mạnh) + const dy = Math.abs(tr.top - rr.top); + if (dy > this.o.unsnapThreshold) { + this._unsnapAbsolute(); // trả về CHA, giữ width + this._stopInViewLoop(); + return; + } + } else { + // SNAP-UP khi lướt lên: target gần ref (quanh anchor) + const delta = Math.abs(tr.top - anchor); + if (delta <= this.o.snapUpProximity) { + this._snapAbsolute(); + // sau snap, tiếp tục monitor chạm đáy CHA; loop này tự dừng khi ioProbe báo ra viewport + this._stopInViewLoop(); + return; + } + } + this._loopId = requestAnimationFrame(tick); + }; + this._loopId = requestAnimationFrame(tick); + } + _stopInViewLoop() { + if (this._loopId) { cancelAnimationFrame(this._loopId); this._loopId = null; } + } + + // ===== Snap monitor: đang snap → nếu đáy target chạm/vượt đáy CHA => pin bottom:0 ===== + _startSnapMonitor() { + if (this._snapLoopId) return; + const tickSnap = () => { + if (!this.snapped || !this._isPortaled || !this._hostParent) { + this._stopSnapMonitor(); + return; + } + const pr = this._hostParent.getBoundingClientRect(); + const er = this.el.getBoundingClientRect(); + if (er.bottom >= pr.bottom) { + this._pinToHostBottom(); + this._stopSnapMonitor(); + return; + } + this._snapLoopId = requestAnimationFrame(tickSnap); + }; + this._snapLoopId = requestAnimationFrame(tickSnap); + } + _stopSnapMonitor() { + if (this._snapLoopId) { cancelAnimationFrame(this._snapLoopId); this._snapLoopId = null; } + } + + // ===== helpers ===== + _portalToBody() { + if (this._isPortaled) return; + this._hostParent = this.el.parentNode; + + // lock width trước khi portal để không nhảy layout + this._snapWidth = this.el.offsetWidth; + if (this._snapWidth && Number.isFinite(this._snapWidth)) { + this.el.style.width = this._snapWidth + 'px'; + } + + document.body.appendChild(this.el); + this._isPortaled = true; + } + + // Trả về CHA: append vào host (đứng sau mọi thứ; nếu probe ở cuối thì tự sau probe) + _restoreToHostBeforeProbe() { + if (!this._isPortaled) return; + if (this._hostParent) this._hostParent.appendChild(this.el); + else document.body.appendChild(this.el); // fallback hiếm + this._isPortaled = false; + // GIỮ style.width để giữ bề ngang như yêu cầu + } + + // Pin về CHA bottom:0 (absolute trong CHA), giữ width + _pinToHostBottom() { + this._restoreToHostBeforeProbe(); + + const host = this._hostParent || this.el.parentNode; + if (host && getComputedStyle(host).position === 'static') { + host.style.position = 'relative'; + } + + this.el.style.position = 'absolute'; + this.el.style.left = this.o.gutterLeft + 'px'; + this.el.style.top = ''; + this.el.style.bottom = '0px'; + + this.snapped = false; + } + + // ===== Snap / Unsnap ===== + _snapAbsolute() { + if (this.snapped) return; + this.snapped = true; + + // đo trước khi portal để giữ LEFT & intro pose + const tRectBefore = this.el.getBoundingClientRect(); + const rRect = this.ref.getBoundingClientRect(); + + const pageLeft = Math.round(tRectBefore.left); + const pageTopNow = Math.round(tRectBefore.top + this.o.unsnapThreshold); + const pageTopFinal = Math.round(rRect.bottom + this.o.unsnapThreshold); + + // portal lên body (đồng thời lock width) + this._portalToBody(); + + // intro pose → frame kế tiếp về top final + this.el.style.position = 'absolute'; + this.el.style.left = pageLeft + 'px'; + this.el.style.top = (this._needIntroPose ? pageTopNow : pageTopFinal) + 'px'; + this.el.style.bottom = ''; + + if (this._needIntroPose) { + requestAnimationFrame(() => { this.el.style.top = pageTopFinal + 'px'; }); + this._needIntroPose = false; + } + + this._startSnapMonitor(); + } + + _unsnapAbsolute() { + if (!this.snapped) return; + this.snapped = false; + + // không xoá width → giữ nguyên bề ngang + this.el.style.position = ''; + this.el.style.left = ''; + this.el.style.top = ''; + this.el.style.bottom = ''; + + this._restoreToHostBeforeProbe(); + } +}