update news & event 1
This commit is contained in:
@ -73,6 +73,7 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<Folder Include="wwwroot\images\3000\NewFolder\" />
|
||||||
<Folder Include="wwwroot\images\wait-section\" />
|
<Folder Include="wwwroot\images\wait-section\" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
|||||||
@ -21,13 +21,41 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="c-12 c-l-8 c-x-9 p-20">
|
<div class="c-12 c-l-8 c-x-9 p-20">
|
||||||
<div class="d-f">
|
<div class="d-f f-c">
|
||||||
<h2 class="mb-4 title underline">Why GSSA Services Are Essential</h2>
|
<p class="sub-title">
|
||||||
|
We Are Air Cargo GSSA
|
||||||
|
</p>
|
||||||
|
<h2 class="title"> If You’ve Ever Heard of A Cargo GSSA Before, This Is How It Works</h2>
|
||||||
|
<img class="w-100" src="~/images/3000/3001/gssa-overview.png" alt="Air Cargo GSSA illustration" />
|
||||||
</div>
|
</div>
|
||||||
<p class="mb-3">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.</p>
|
<div class="r-n-g">
|
||||||
<p class="mb-3">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.</p>
|
<div class="c-12 c-x-6 c-xm-4 p-20">
|
||||||
<div class="d-f">
|
<div class="d-f">
|
||||||
<h2 class="mt-2 mb-5 title underline">Key Benefits of GSSA Services</h2>
|
<h3 class="mb-4 title underline">Being a Cargo GSSA/GSA means</h3>
|
||||||
|
</div>
|
||||||
|
<ul class="order-list mb-4">
|
||||||
|
<li class="d-f a-i-start">
|
||||||
|
<i class="atg atg-radiobutton a-2x"></i>
|
||||||
|
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
|
||||||
|
</li>
|
||||||
|
<li class="d-f a-i-start mt-4">
|
||||||
|
<i class="atg atg-radiobutton a-2x"></i>
|
||||||
|
Represent for Airlines typically work with freight forwarders and handling agents (GHAs) to deliver transportation services to shippers.
|
||||||
|
</li>
|
||||||
|
<li class="d-f a-i-start mt-4">
|
||||||
|
<i class="atg atg-radiobutton a-2x"></i>
|
||||||
|
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
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div class="c-12 c-x-6 c-xm-8 p-20">
|
||||||
|
<div class="con-image">
|
||||||
|
<img class="w-100" src="~/images/3000/3001/gssa-operations-team.png" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="d-f mt-4">
|
||||||
|
<h3 class="mb-5 title underline">Why any Airlines should operate as Cargo GSSAs/GSAs model</h3>
|
||||||
</div>
|
</div>
|
||||||
<div class="cfull">
|
<div class="cfull">
|
||||||
<div class="r-n-g">
|
<div class="r-n-g">
|
||||||
|
|||||||
@ -2129,7 +2129,7 @@ video.bg-video {
|
|||||||
padding: 25px;
|
padding: 25px;
|
||||||
background: var(--text-color-lW3);
|
background: var(--text-color-lW3);
|
||||||
border-radius: var(--radius);
|
border-radius: var(--radius);
|
||||||
margin-bottom: 40px
|
transition: top .3s ease-in-out
|
||||||
}
|
}
|
||||||
.sidebarR .nav-item {
|
.sidebarR .nav-item {
|
||||||
font-size: .98rem;
|
font-size: .98rem;
|
||||||
|
|||||||
BIN
TWA-App/wwwroot/images/3000/3001/gssa-operations-team.png
Normal file
BIN
TWA-App/wwwroot/images/3000/3001/gssa-operations-team.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 676 KiB |
BIN
TWA-App/wwwroot/images/3000/3001/gssa-overview.png
Normal file
BIN
TWA-App/wwwroot/images/3000/3001/gssa-overview.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 5.5 MiB |
@ -1,5 +1,5 @@
|
|||||||
import AMenu from '/js/libs/js-AMenu.js'
|
import AMenu from '/js/libs/js-AMenu.js'
|
||||||
|
import AElementFixed from '/js/libs/js-AElementFixed.js'
|
||||||
function runServices () {
|
function runServices () {
|
||||||
|
|
||||||
const parallaxEls = document.querySelectorAll('.con-page-section .parallax');
|
const parallaxEls = document.querySelectorAll('.con-page-section .parallax');
|
||||||
@ -24,6 +24,12 @@ function runServices () {
|
|||||||
|
|
||||||
window.LoadingServices = function () {
|
window.LoadingServices = function () {
|
||||||
runServices();
|
runServices();
|
||||||
|
const aef = new AElementFixed({
|
||||||
|
ref: '#fHeader',
|
||||||
|
target: '.con-sidebarR',
|
||||||
|
breakpoint: 992,
|
||||||
|
unsnapThreshold: 40
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateParallax(t, parallaxEls) {
|
function updateParallax(t, parallaxEls) {
|
||||||
|
|||||||
264
TWA-App/wwwroot/js/libs/js-AElementFixed.js
Normal file
264
TWA-App/wwwroot/js/libs/js-AElementFixed.js
Normal file
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user