forked from izu/student-web-if-development-kit
feat(tapops): add css modules, js scripts, and viewer page
This commit is contained in:
@@ -0,0 +1,123 @@
|
|||||||
|
/* ===========================================
|
||||||
|
Base / Reset — scoped under .tapops-doc-root
|
||||||
|
Prevents contaminating Tailwind nav/footer.
|
||||||
|
=========================================== */
|
||||||
|
|
||||||
|
.tapops-doc-root *,
|
||||||
|
.tapops-doc-root *::before,
|
||||||
|
.tapops-doc-root *::after {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Font is set on the root only (inherited), NOT on `*`.
|
||||||
|
A universal `.tapops-doc-root *` rule would tie Font Awesome's
|
||||||
|
`.fas` font-family in specificity and—loading later—override it,
|
||||||
|
blanking every icon. Inheritance loses to FA's explicit rule, so
|
||||||
|
icons render while normal text still gets Roboto. */
|
||||||
|
.tapops-doc-root {
|
||||||
|
font-family: 'Roboto', sans-serif;
|
||||||
|
color: var(--text-dark);
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Form controls don't inherit font-family by default — opt them in.
|
||||||
|
These elements never hold Font Awesome glyphs, so this is icon-safe. */
|
||||||
|
.tapops-doc-root input,
|
||||||
|
.tapops-doc-root select,
|
||||||
|
.tapops-doc-root textarea,
|
||||||
|
.tapops-doc-root button {
|
||||||
|
font-family: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tapops-doc-root a {
|
||||||
|
text-decoration: none;
|
||||||
|
color: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tapops-doc-root img {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- Shared Section Styles --- */
|
||||||
|
|
||||||
|
.tapops-doc-root .section-header {
|
||||||
|
margin-bottom: 50px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tapops-doc-root .section-tag {
|
||||||
|
font-size: 0.75rem;
|
||||||
|
font-weight: 800;
|
||||||
|
letter-spacing: 2px;
|
||||||
|
background-color: var(--secondary-gold);
|
||||||
|
color: var(--primary-blue);
|
||||||
|
display: inline-block;
|
||||||
|
padding: 6px 14px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tapops-doc-root .section-title {
|
||||||
|
font-size: 2.5rem;
|
||||||
|
font-weight: 800;
|
||||||
|
color: var(--primary-blue);
|
||||||
|
letter-spacing: -1px;
|
||||||
|
line-height: 1.15;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- Shared Button Styles --- */
|
||||||
|
|
||||||
|
.tapops-doc-root .btn-join,
|
||||||
|
.tapops-doc-root .btn-outline {
|
||||||
|
padding: 17px 30px;
|
||||||
|
font-weight: 800;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tapops-doc-root .btn-join {
|
||||||
|
background-color: var(--secondary-gold);
|
||||||
|
color: var(--primary-blue);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tapops-doc-root .btn-outline {
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.18);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tapops-doc-root .btn-join:hover,
|
||||||
|
.tapops-doc-root .btn-outline:hover {
|
||||||
|
transform: translateY(-2px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tapops-doc-root .btn-join:hover {
|
||||||
|
background-color: #fff;
|
||||||
|
box-shadow: 0 15px 30px rgba(0, 0, 0, 0.25);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tapops-doc-root .btn-outline:hover {
|
||||||
|
border-color: var(--secondary-gold);
|
||||||
|
color: var(--secondary-gold);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.tapops-doc-root .btn-join,
|
||||||
|
.tapops-doc-root .btn-outline {
|
||||||
|
padding: 14px 24px;
|
||||||
|
font-size: 0.82rem;
|
||||||
|
width: 100%;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 480px) {
|
||||||
|
.tapops-doc-root .btn-join,
|
||||||
|
.tapops-doc-root .btn-outline {
|
||||||
|
padding: 12px 20px;
|
||||||
|
font-size: 0.78rem;
|
||||||
|
letter-spacing: 0.5px;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,845 @@
|
|||||||
|
/* ===========================================
|
||||||
|
Document Hub & Dashboard Layout
|
||||||
|
=========================================== */
|
||||||
|
|
||||||
|
.documents-section {
|
||||||
|
padding: 120px 5%;
|
||||||
|
background: hsl(0, 0%, 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.repo-dashboard-container {
|
||||||
|
max-width: 1200px;
|
||||||
|
margin: 0 auto;
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 280px 1fr;
|
||||||
|
gap: 40px;
|
||||||
|
align-items: start;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- Sidebar Styling --- */
|
||||||
|
|
||||||
|
.repo-sidebar {
|
||||||
|
position: sticky;
|
||||||
|
top: 150px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Mobile category dropdown wrapper (hidden on desktop) */
|
||||||
|
.mobile-category-control-group {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-title {
|
||||||
|
font-size: 0.75rem;
|
||||||
|
letter-spacing: 2px;
|
||||||
|
font-weight: 800;
|
||||||
|
color: #94a3b8;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
.document-filter-wrapper {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
margin-bottom: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Desktop: show buttons, hide dropdown */
|
||||||
|
.filter-btn-group {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-select-group {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.doc-filter-btn {
|
||||||
|
text-align: left;
|
||||||
|
padding: 14px 18px;
|
||||||
|
background: white;
|
||||||
|
border: 1px solid #e2e8f0;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
font-weight: 700;
|
||||||
|
color: var(--primary-blue);
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.doc-filter-btn.active {
|
||||||
|
background: var(--primary-blue);
|
||||||
|
color: white;
|
||||||
|
border-color: var(--primary-blue);
|
||||||
|
box-shadow: 0 4px 12px rgba(0, 49, 80, 0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
.doc-filter-btn:hover:not(.active) {
|
||||||
|
border-color: var(--secondary-gold);
|
||||||
|
background: #fffdf5;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- Custom UI Dropdown --- */
|
||||||
|
.custom-dropdown {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-dropdown-trigger {
|
||||||
|
width: 100%;
|
||||||
|
padding: 14px 18px;
|
||||||
|
background: white;
|
||||||
|
border: 1px solid #e2e8f0;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-family: inherit;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
font-weight: 700;
|
||||||
|
color: var(--primary-blue);
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
transition: all 0.25s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-dropdown-trigger:hover {
|
||||||
|
border-color: var(--secondary-gold);
|
||||||
|
background: #fffdf5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-dropdown-trigger:focus-visible {
|
||||||
|
outline: none;
|
||||||
|
border-color: var(--primary-blue);
|
||||||
|
box-shadow: 0 0 0 3px rgba(0, 49, 80, 0.08);
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-dropdown-trigger .trigger-arrow {
|
||||||
|
font-size: 0.8rem;
|
||||||
|
transition: transform 0.25s ease;
|
||||||
|
color: var(--primary-blue);
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-dropdown.active .custom-dropdown-trigger .trigger-arrow {
|
||||||
|
transform: rotate(180deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-dropdown.active .custom-dropdown-trigger {
|
||||||
|
border-color: var(--primary-blue);
|
||||||
|
box-shadow: 0 0 0 3px rgba(0, 49, 80, 0.08);
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-dropdown-menu {
|
||||||
|
position: absolute;
|
||||||
|
top: calc(100% + 6px);
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
background: white;
|
||||||
|
border: 1px solid #e2e8f0;
|
||||||
|
border-radius: 6px;
|
||||||
|
box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.1), 0 8px 10px -6px rgba(0, 0, 0, 0.1);
|
||||||
|
z-index: 100;
|
||||||
|
opacity: 0;
|
||||||
|
visibility: hidden;
|
||||||
|
transform: translateY(-8px);
|
||||||
|
transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
overflow: hidden;
|
||||||
|
max-height: 300px;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-dropdown.active .custom-dropdown-menu {
|
||||||
|
opacity: 1;
|
||||||
|
visibility: visible;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-dropdown-item {
|
||||||
|
padding: 12px 18px;
|
||||||
|
font-size: 0.88rem;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #475569;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.15s ease;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-dropdown-item:hover {
|
||||||
|
background: #f1f5f9;
|
||||||
|
color: var(--primary-blue);
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-dropdown-item.active {
|
||||||
|
background: #f0f7ff;
|
||||||
|
color: var(--primary-blue);
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Tick mark for selected item */
|
||||||
|
.custom-dropdown-item.active::after {
|
||||||
|
content: "\f00c";
|
||||||
|
font-family: "Font Awesome 5 Free";
|
||||||
|
font-weight: 900;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
color: var(--primary-blue);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- Filter Skeleton Loading --- */
|
||||||
|
|
||||||
|
.filter-skeleton {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-skeleton-item {
|
||||||
|
height: 48px;
|
||||||
|
border-radius: 4px;
|
||||||
|
background: linear-gradient(90deg, #f1f5f9 25%, #e2e8f0 50%, #f1f5f9 75%);
|
||||||
|
background-size: 200% 100%;
|
||||||
|
animation: skeleton-shimmer 1.5s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-skeleton-item.short {
|
||||||
|
width: 70%;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes skeleton-shimmer {
|
||||||
|
0% { background-position: 200% 0; }
|
||||||
|
100% { background-position: -200% 0; }
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-info-box {
|
||||||
|
background: #f1f5f9;
|
||||||
|
padding: 20px;
|
||||||
|
border-radius: 4px;
|
||||||
|
display: flex;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-info-box i {
|
||||||
|
color: var(--primary-blue);
|
||||||
|
margin-top: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-info-box p {
|
||||||
|
font-size: 0.82rem;
|
||||||
|
color: #64748b;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- Content Area & Search --- */
|
||||||
|
|
||||||
|
.repo-main-content {
|
||||||
|
min-height: 800px;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.repo-content-header {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 30px;
|
||||||
|
margin-bottom: 50px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-title-area .section-title {
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-subtitle {
|
||||||
|
font-size: 0.95rem;
|
||||||
|
color: #64748b;
|
||||||
|
font-weight: 400;
|
||||||
|
}
|
||||||
|
|
||||||
|
.repo-controls {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: flex-end;
|
||||||
|
gap: 24px;
|
||||||
|
background: white;
|
||||||
|
padding: 24px;
|
||||||
|
border-radius: 8px;
|
||||||
|
border: 1px solid #e2e8f0;
|
||||||
|
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-container {
|
||||||
|
flex: 1;
|
||||||
|
max-width: 500px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-input-group {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-icon {
|
||||||
|
position: absolute;
|
||||||
|
left: 18px;
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
color: #94a3b8;
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
#doc-search {
|
||||||
|
width: 100%;
|
||||||
|
padding: 16px 16px 16px 50px;
|
||||||
|
background: white;
|
||||||
|
border: 1px solid #e2e8f0;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 0.95rem;
|
||||||
|
color: var(--primary-blue);
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
#doc-search:focus {
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- Filter Controls --- */
|
||||||
|
|
||||||
|
.filter-controls {
|
||||||
|
display: flex;
|
||||||
|
gap: 16px;
|
||||||
|
align-items: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
|
.control-group {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.date-range-group {
|
||||||
|
min-width: 320px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.control-label {
|
||||||
|
font-size: 0.7rem;
|
||||||
|
font-weight: 800;
|
||||||
|
color: #94a3b8;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select-wrapper {
|
||||||
|
position: relative;
|
||||||
|
min-width: 140px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#sort-filter-wrapper {
|
||||||
|
position: relative;
|
||||||
|
min-width: 140px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select-icon {
|
||||||
|
position: absolute;
|
||||||
|
left: 14px;
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
color: var(--primary-blue);
|
||||||
|
font-size: 0.8rem;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- Date Range Filter --- */
|
||||||
|
.date-range-inputs {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
background: #f8fafc;
|
||||||
|
border: 1px solid #e2e8f0;
|
||||||
|
padding: 4px 12px;
|
||||||
|
border-radius: 4px;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.date-range-inputs:focus-within {
|
||||||
|
background: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.date-range-inputs .input-wrapper {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.date-range-inputs input[type="date"] {
|
||||||
|
border: none;
|
||||||
|
background: transparent;
|
||||||
|
padding: 8px 0;
|
||||||
|
font-family: inherit;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
font-weight: 700;
|
||||||
|
color: var(--primary-blue);
|
||||||
|
cursor: pointer;
|
||||||
|
min-width: 120px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.date-range-inputs input[type="date"]:focus {
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.date-range-inputs input[type="date"]::-webkit-calendar-picker-indicator {
|
||||||
|
cursor: pointer;
|
||||||
|
opacity: 0.6;
|
||||||
|
transition: opacity 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.date-range-inputs input[type="date"]::-webkit-calendar-picker-indicator:hover {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.range-divider {
|
||||||
|
font-size: 0.7rem;
|
||||||
|
font-weight: 800;
|
||||||
|
color: #94a3b8;
|
||||||
|
text-transform: uppercase;
|
||||||
|
padding: 0 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-select,
|
||||||
|
select#sort-filter {
|
||||||
|
width: 100%;
|
||||||
|
padding: 12px 12px 12px 36px;
|
||||||
|
background: #f8fafc;
|
||||||
|
border: 1px solid #e2e8f0;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
font-weight: 700;
|
||||||
|
color: var(--primary-blue);
|
||||||
|
cursor: pointer;
|
||||||
|
appearance: none;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-select:hover,
|
||||||
|
select#sort-filter:hover {
|
||||||
|
border-color: var(--secondary-gold);
|
||||||
|
background: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-select:focus,
|
||||||
|
select#sort-filter:focus {
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- Document Cards Grid --- */
|
||||||
|
|
||||||
|
.document-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
|
||||||
|
gap: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.document-card {
|
||||||
|
background: white;
|
||||||
|
border: 1px solid #e2e8f0;
|
||||||
|
padding: 30px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
transition: all 0.3s cubic-bezier(0.165, 0.84, 0.44, 1);
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.document-card:hover {
|
||||||
|
border-color: var(--secondary-gold);
|
||||||
|
box-shadow: 0 20px 40px rgba(0, 49, 80, 0.05);
|
||||||
|
transform: translateY(-5px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.doc-type-icon {
|
||||||
|
font-size: 2.2rem;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.doc-type-pdf {
|
||||||
|
color: #ef4444;
|
||||||
|
}
|
||||||
|
|
||||||
|
.doc-type-docx {
|
||||||
|
color: #3b82f6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.doc-title {
|
||||||
|
font-size: 1.15rem;
|
||||||
|
font-weight: 800;
|
||||||
|
color: var(--primary-blue);
|
||||||
|
margin-bottom: 12px;
|
||||||
|
line-height: 1.4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.doc-meta {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
margin-bottom: 25px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.doc-badge {
|
||||||
|
font-size: 0.65rem;
|
||||||
|
font-weight: 800;
|
||||||
|
padding: 4px 10px;
|
||||||
|
background: #f1f5f9;
|
||||||
|
color: #64748b;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
.doc-badge.date {
|
||||||
|
background: hsl(200, 95%, 95%);
|
||||||
|
color: hsl(200, 95%, 30%);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- Tag Badges --- */
|
||||||
|
|
||||||
|
.doc-tags {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.doc-tags-icon {
|
||||||
|
font-size: 0.7rem;
|
||||||
|
color: #94a3b8;
|
||||||
|
margin-right: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.doc-tag {
|
||||||
|
font-size: 0.65rem;
|
||||||
|
font-weight: 700;
|
||||||
|
padding: 3px 10px;
|
||||||
|
background: hsl(43, 100%, 95%);
|
||||||
|
color: hsl(43, 80%, 35%);
|
||||||
|
border: 1px solid hsl(43, 80%, 85%);
|
||||||
|
border-radius: 20px;
|
||||||
|
text-transform: lowercase;
|
||||||
|
letter-spacing: 0.3px;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.doc-tag:hover {
|
||||||
|
background: hsl(43, 80%, 88%);
|
||||||
|
border-color: hsl(43, 80%, 70%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.doc-actions {
|
||||||
|
margin-top: auto;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-action-primary,
|
||||||
|
.btn-action-outline {
|
||||||
|
padding: 12px;
|
||||||
|
text-align: center;
|
||||||
|
text-decoration: none;
|
||||||
|
font-weight: 800;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
letter-spacing: 0.5px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 8px;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
border-radius: 4px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
a.btn-action-primary {
|
||||||
|
background: var(--primary-blue);
|
||||||
|
color: white;
|
||||||
|
border: 1px solid var(--primary-blue);
|
||||||
|
}
|
||||||
|
|
||||||
|
a.btn-action-primary:hover {
|
||||||
|
background: var(--secondary-gold);
|
||||||
|
border-color: var(--secondary-gold);
|
||||||
|
color: var(--primary-blue);
|
||||||
|
}
|
||||||
|
|
||||||
|
a.btn-action-outline {
|
||||||
|
background: transparent;
|
||||||
|
color: var(--primary-blue);
|
||||||
|
border: 1px solid #e2e8f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
a.btn-action-outline:hover {
|
||||||
|
border-color: var(--primary-blue);
|
||||||
|
background: #f8fafc;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===========================================
|
||||||
|
Responsive — Tablet (≤1024px)
|
||||||
|
=========================================== */
|
||||||
|
|
||||||
|
@media (max-width: 1024px) {
|
||||||
|
.repo-dashboard-container {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
.repo-sidebar {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-category-control-group {
|
||||||
|
display: flex !important;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Mobile: show dropdown */
|
||||||
|
.filter-select-group {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Skeleton single bar on mobile */
|
||||||
|
.filter-skeleton {
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-skeleton-item {
|
||||||
|
height: 48px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-skeleton-item.short {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-skeleton-item:not(.short) ~ .filter-skeleton-item:not(.short) {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.repo-controls {
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: stretch;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-container {
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-controls {
|
||||||
|
justify-content: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.control-group {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===========================================
|
||||||
|
Responsive — Tablet / Large Phone (≤768px)
|
||||||
|
=========================================== */
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.documents-section {
|
||||||
|
padding: 80px 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.repo-content-header {
|
||||||
|
gap: 20px;
|
||||||
|
margin-bottom: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.repo-controls {
|
||||||
|
padding: 16px;
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#doc-search {
|
||||||
|
padding: 14px 14px 14px 44px;
|
||||||
|
font-size: 0.88rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-controls {
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.control-group {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.date-range-group {
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.date-range-inputs {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr auto 1fr;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
border: none;
|
||||||
|
background: transparent;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.date-range-inputs .input-wrapper {
|
||||||
|
background: #f8fafc;
|
||||||
|
border: 1px solid #e2e8f0;
|
||||||
|
padding: 10px 14px;
|
||||||
|
border-radius: 4px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
width: 100%;
|
||||||
|
box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.date-range-inputs .input-wrapper:focus-within {
|
||||||
|
background: white;
|
||||||
|
border-color: var(--primary-blue);
|
||||||
|
box-shadow: 0 0 0 3px rgba(0, 49, 80, 0.08);
|
||||||
|
}
|
||||||
|
|
||||||
|
.date-range-inputs input[type="date"] {
|
||||||
|
width: 100%;
|
||||||
|
border: none;
|
||||||
|
background: transparent;
|
||||||
|
padding: 0;
|
||||||
|
font-family: inherit;
|
||||||
|
font-size: 0.82rem;
|
||||||
|
font-weight: 700;
|
||||||
|
color: var(--primary-blue);
|
||||||
|
min-width: 0;
|
||||||
|
flex: 1;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.range-divider {
|
||||||
|
font-size: 0.75rem;
|
||||||
|
font-weight: 800;
|
||||||
|
color: #94a3b8;
|
||||||
|
padding: 0 4px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select-wrapper,
|
||||||
|
#sort-filter-wrapper {
|
||||||
|
min-width: 0;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Cards: 2-column when space permits, else single */
|
||||||
|
.document-grid {
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.document-card {
|
||||||
|
padding: 22px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.document-card:hover {
|
||||||
|
transform: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.doc-type-icon {
|
||||||
|
font-size: 1.8rem;
|
||||||
|
margin-bottom: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.doc-title {
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.doc-meta {
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.doc-tags {
|
||||||
|
margin-bottom: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.repo-main-content {
|
||||||
|
min-height: 400px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===========================================
|
||||||
|
Responsive — Phone (≤480px)
|
||||||
|
=========================================== */
|
||||||
|
|
||||||
|
@media (max-width: 480px) {
|
||||||
|
.documents-section {
|
||||||
|
padding: 50px 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-dropdown-trigger {
|
||||||
|
padding: 12px 14px;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Single column cards */
|
||||||
|
.document-grid {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.document-card {
|
||||||
|
padding: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.doc-title {
|
||||||
|
font-size: 0.95rem;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.doc-type-icon {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.doc-meta {
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.doc-tags {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.doc-tag {
|
||||||
|
font-size: 0.6rem;
|
||||||
|
padding: 2px 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-action-primary,
|
||||||
|
.btn-action-outline {
|
||||||
|
padding: 10px;
|
||||||
|
font-size: 0.7rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.doc-actions {
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.repo-controls {
|
||||||
|
padding: 12px;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#doc-search {
|
||||||
|
padding: 12px 12px 12px 40px;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-icon {
|
||||||
|
left: 14px;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.repo-content-header {
|
||||||
|
gap: 16px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,276 @@
|
|||||||
|
/* ===========================================
|
||||||
|
Hero Section (Centered & Immersive) — Tapops
|
||||||
|
=========================================== */
|
||||||
|
|
||||||
|
.research-hero.immersive-hero {
|
||||||
|
background: radial-gradient(circle at center, #003150 0%, #001e35 100%);
|
||||||
|
color: white;
|
||||||
|
padding: 160px 5% 180px;
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
text-align: center;
|
||||||
|
padding-bottom: 200px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.research-hero::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
width: 800px;
|
||||||
|
height: 800px;
|
||||||
|
left: 50%;
|
||||||
|
top: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
|
||||||
|
background: conic-gradient(from 180deg at 50% 50%,
|
||||||
|
transparent 0deg,
|
||||||
|
rgba(254, 180, 1, 0.15) 180deg,
|
||||||
|
transparent 360deg);
|
||||||
|
|
||||||
|
-webkit-mask: radial-gradient(farthest-side, transparent calc(100% - 2px), #fff 0);
|
||||||
|
mask: radial-gradient(farthest-side, transparent calc(100% - 2px), #fff 0);
|
||||||
|
|
||||||
|
border-radius: 50%;
|
||||||
|
pointer-events: none;
|
||||||
|
z-index: 1;
|
||||||
|
animation: rotate-orbit 30s linear infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes rotate-orbit {
|
||||||
|
from {
|
||||||
|
transform: translate(-50%, -50%) rotate(0deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
to {
|
||||||
|
transform: translate(-50%, -50%) rotate(360deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-container-centered {
|
||||||
|
max-width: 900px;
|
||||||
|
position: relative;
|
||||||
|
z-index: 2;
|
||||||
|
width: 100%;
|
||||||
|
padding: 0 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-kicker {
|
||||||
|
font-size: 0.85rem;
|
||||||
|
letter-spacing: 4px;
|
||||||
|
font-weight: 800;
|
||||||
|
color: var(--secondary-gold);
|
||||||
|
text-transform: uppercase;
|
||||||
|
margin-bottom: 24px;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-title {
|
||||||
|
font-size: 4.8rem;
|
||||||
|
font-weight: 800;
|
||||||
|
line-height: 1.05;
|
||||||
|
letter-spacing: -3px;
|
||||||
|
margin-bottom: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-tagline {
|
||||||
|
font-size: 1.25rem;
|
||||||
|
line-height: 1.6;
|
||||||
|
opacity: 0.85;
|
||||||
|
margin-bottom: 45px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-actions {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- Floating Insight Bar --- */
|
||||||
|
|
||||||
|
.hero-floating-stat-bar {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 50px;
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
background: rgba(255, 255, 255, 0.03);
|
||||||
|
backdrop-filter: blur(20px);
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||||
|
padding: 25px 60px;
|
||||||
|
border-radius: 4px;
|
||||||
|
z-index: 3;
|
||||||
|
gap: 40px;
|
||||||
|
box-shadow: 0 30px 60px rgba(0, 0, 0, 0.3);
|
||||||
|
max-width: calc(100% - 32px);
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-bar-item {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-bar-value {
|
||||||
|
font-size: 2.2rem;
|
||||||
|
font-weight: 800;
|
||||||
|
color: var(--secondary-gold);
|
||||||
|
line-height: 1.1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-bar-label {
|
||||||
|
font-size: 0.7rem;
|
||||||
|
letter-spacing: 1.5px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
font-weight: 700;
|
||||||
|
opacity: 0.6;
|
||||||
|
margin-top: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-bar-divider {
|
||||||
|
width: 1px;
|
||||||
|
height: 40px;
|
||||||
|
background: rgba(255, 255, 255, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===========================================
|
||||||
|
Hero Responsive
|
||||||
|
=========================================== */
|
||||||
|
|
||||||
|
@media (max-width: 992px) {
|
||||||
|
.research-hero.immersive-hero {
|
||||||
|
padding: 120px 5% 180px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-title {
|
||||||
|
font-size: 3.5rem;
|
||||||
|
letter-spacing: -2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-tagline {
|
||||||
|
font-size: 1.1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-floating-stat-bar {
|
||||||
|
padding: 20px 32px;
|
||||||
|
gap: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-bar-value {
|
||||||
|
font-size: 1.6rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.research-hero.immersive-hero {
|
||||||
|
padding: 100px 16px 130px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-kicker {
|
||||||
|
font-size: 0.68rem;
|
||||||
|
letter-spacing: 2.5px;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-title {
|
||||||
|
font-size: 2.2rem;
|
||||||
|
letter-spacing: -1px;
|
||||||
|
margin-bottom: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-tagline {
|
||||||
|
font-size: 0.9rem;
|
||||||
|
line-height: 1.5;
|
||||||
|
margin-bottom: 28px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-actions {
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: stretch;
|
||||||
|
gap: 12px;
|
||||||
|
width: 100%;
|
||||||
|
max-width: 320px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-floating-stat-bar {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 320px;
|
||||||
|
padding: 18px 24px;
|
||||||
|
gap: 20px;
|
||||||
|
bottom: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-bar-value {
|
||||||
|
font-size: 1.3rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-bar-label {
|
||||||
|
font-size: 0.6rem;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-bar-divider {
|
||||||
|
height: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.research-hero::before {
|
||||||
|
width: 400px;
|
||||||
|
height: 400px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 480px) {
|
||||||
|
.research-hero.immersive-hero {
|
||||||
|
padding: 80px 12px 125px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-kicker {
|
||||||
|
font-size: 0.6rem;
|
||||||
|
letter-spacing: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-title {
|
||||||
|
font-size: 1.8rem;
|
||||||
|
letter-spacing: -0.5px;
|
||||||
|
margin-bottom: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-tagline {
|
||||||
|
font-size: 0.82rem;
|
||||||
|
line-height: 1.45;
|
||||||
|
margin-bottom: 22px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-actions {
|
||||||
|
max-width: 280px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-floating-stat-bar {
|
||||||
|
flex-direction: row;
|
||||||
|
bottom: 20px;
|
||||||
|
gap: 16px;
|
||||||
|
padding: 14px 20px;
|
||||||
|
width: 100%;
|
||||||
|
max-width: 280px;
|
||||||
|
justify-content: space-evenly;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-bar-divider {
|
||||||
|
width: 1px;
|
||||||
|
height: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-bar-value {
|
||||||
|
font-size: 1.2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.research-hero::before {
|
||||||
|
width: 280px;
|
||||||
|
height: 280px;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,286 @@
|
|||||||
|
/* ===========================================
|
||||||
|
Navigation & Mega Menu Styles
|
||||||
|
Extracted from inline <style> in index.html
|
||||||
|
=========================================== */
|
||||||
|
|
||||||
|
/* ===== MOBILE NAV STABILITY FIX =====
|
||||||
|
Prevents logo bar from collapsing/shifting on Android when
|
||||||
|
address bar hides/shows and triggers viewport reflow.
|
||||||
|
===================================== */
|
||||||
|
#nav-wrapper {
|
||||||
|
position: fixed !important;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
z-index: 50;
|
||||||
|
backface-visibility: hidden;
|
||||||
|
-webkit-backface-visibility: hidden;
|
||||||
|
will-change: box-shadow;
|
||||||
|
}
|
||||||
|
|
||||||
|
#nav-wrapper>div:first-child {
|
||||||
|
transform: translateZ(0);
|
||||||
|
-webkit-transform: translateZ(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 1023px) {
|
||||||
|
#nav-wrapper>div:first-child {
|
||||||
|
min-height: 0 !important;
|
||||||
|
max-height: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#pengumuman-bar {
|
||||||
|
z-index: 40;
|
||||||
|
}
|
||||||
|
|
||||||
|
#pengumuman-bar.is-stuck {
|
||||||
|
position: fixed;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
z-index: 40;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-reduced-motion: reduce) {
|
||||||
|
|
||||||
|
*,
|
||||||
|
*::before,
|
||||||
|
*::after {
|
||||||
|
animation-duration: 0.01ms !important;
|
||||||
|
animation-iteration-count: 1 !important;
|
||||||
|
transition-duration: 0.01ms !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Skip Link Styling */
|
||||||
|
.skip-link {
|
||||||
|
position: absolute;
|
||||||
|
top: -40px;
|
||||||
|
left: 0;
|
||||||
|
background: #003150;
|
||||||
|
color: white;
|
||||||
|
padding: 8px 16px;
|
||||||
|
z-index: 100;
|
||||||
|
transition: top 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.skip-link:focus {
|
||||||
|
top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Focus visible for better keyboard navigation */
|
||||||
|
:focus-visible {
|
||||||
|
outline: 3px solid #feb401;
|
||||||
|
outline-offset: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Mega Menu Styles */
|
||||||
|
.hero-pagination .swiper-pagination-bullet {
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: rgba(255, 255, 255, 0.4);
|
||||||
|
display: inline-block;
|
||||||
|
margin: 0 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero-pagination .swiper-pagination-bullet-active {
|
||||||
|
background: rgba(255, 255, 255, 1);
|
||||||
|
width: 24px;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mega-panel {
|
||||||
|
opacity: 0;
|
||||||
|
visibility: hidden;
|
||||||
|
transition: opacity 0.2s ease, visibility 0.2s ease;
|
||||||
|
position: absolute;
|
||||||
|
top: 100%;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mega-panel.active {
|
||||||
|
opacity: 1;
|
||||||
|
visibility: visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Overlay transition */
|
||||||
|
#mega-overlay {
|
||||||
|
opacity: 0;
|
||||||
|
visibility: hidden;
|
||||||
|
transition: opacity 0.2s ease, visibility 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
#mega-overlay.active {
|
||||||
|
opacity: 1;
|
||||||
|
visibility: visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mega-link-card {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: 1rem;
|
||||||
|
padding: 1rem;
|
||||||
|
border-radius: 0.75rem;
|
||||||
|
border: 1px solid #e2e8f0;
|
||||||
|
background: white;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mega-link-card:hover {
|
||||||
|
border-color: #3b82f6;
|
||||||
|
box-shadow: 0 4px 12px rgba(59, 130, 246, 0.15);
|
||||||
|
transform: translateY(-2px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.mega-link-icon {
|
||||||
|
width: 2.5rem;
|
||||||
|
height: 2.5rem;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
flex-shrink: 0;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-menu-btn.active {
|
||||||
|
background: rgba(255, 255, 255, 0.9);
|
||||||
|
color: #1e40af;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-menu-btn:hover {
|
||||||
|
background: rgba(255, 255, 255, 0.8);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Mega menu positioning - no gap */
|
||||||
|
#main-nav {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
#mega-menu-container {
|
||||||
|
position: absolute;
|
||||||
|
top: 100%;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
#mega-menu-container .mega-panel {
|
||||||
|
pointer-events: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Keep pointer bridge under nav to prevent hover loss */
|
||||||
|
.nav-menu-item {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-menu-item::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
bottom: -12px;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
height: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===== Luco Chatbot Animations ===== */
|
||||||
|
@keyframes slideInRight {
|
||||||
|
from {
|
||||||
|
transform: translateX(100px);
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
to {
|
||||||
|
transform: translateX(0);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fadeInUp {
|
||||||
|
from {
|
||||||
|
transform: translateY(10px);
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
to {
|
||||||
|
transform: translateY(0);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes lucoPulse {
|
||||||
|
|
||||||
|
0%,
|
||||||
|
100% {
|
||||||
|
transform: scale(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
50% {
|
||||||
|
transform: scale(1.05);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#luco-container {
|
||||||
|
animation: slideInRight 0.5s ease-out forwards;
|
||||||
|
}
|
||||||
|
|
||||||
|
#luco-bubble {
|
||||||
|
animation: fadeInUp 0.3s ease-out forwards;
|
||||||
|
width: min(18rem, calc(100vw - 2rem));
|
||||||
|
}
|
||||||
|
|
||||||
|
#luco-btn {
|
||||||
|
animation: lucoPulse 2s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
#luco-btn:hover {
|
||||||
|
animation: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Typing indicator */
|
||||||
|
.typing-indicator span {
|
||||||
|
display: inline-block;
|
||||||
|
animation: bounce 1.4s infinite ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.typing-indicator span:nth-child(1) {
|
||||||
|
animation-delay: 0s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.typing-indicator span:nth-child(2) {
|
||||||
|
animation-delay: 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.typing-indicator span:nth-child(3) {
|
||||||
|
animation-delay: 0.4s;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes bounce {
|
||||||
|
|
||||||
|
0%,
|
||||||
|
80%,
|
||||||
|
100% {
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
40% {
|
||||||
|
transform: translateY(-6px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Luco speech bubble arrow */
|
||||||
|
#luco-bubble .bubble-arrow {
|
||||||
|
position: absolute;
|
||||||
|
right: 24px;
|
||||||
|
bottom: -6px;
|
||||||
|
width: 12px;
|
||||||
|
height: 12px;
|
||||||
|
background: white;
|
||||||
|
transform: rotate(45deg);
|
||||||
|
border-right: 1px solid #e2e8f0;
|
||||||
|
border-bottom: 1px solid #e2e8f0;
|
||||||
|
}
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
/* ===========================================
|
||||||
|
Responsive Breakpoints — Document Sections
|
||||||
|
=========================================== */
|
||||||
|
|
||||||
|
/* --- Tablet (≤768px) --- */
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.tapops-doc-root .section-title {
|
||||||
|
font-size: 1.8rem;
|
||||||
|
letter-spacing: -0.5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tapops-doc-root .section-tag {
|
||||||
|
font-size: 0.68rem;
|
||||||
|
letter-spacing: 2px;
|
||||||
|
padding: 5px 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tapops-doc-root .section-header {
|
||||||
|
margin-bottom: 35px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- Phone (≤480px) --- */
|
||||||
|
|
||||||
|
@media (max-width: 480px) {
|
||||||
|
.tapops-doc-root .section-title {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
letter-spacing: -0.5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tapops-doc-root .section-tag {
|
||||||
|
font-size: 0.6rem;
|
||||||
|
letter-spacing: 1.5px;
|
||||||
|
padding: 4px 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tapops-doc-root .section-subtitle {
|
||||||
|
font-size: 0.82rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tapops-doc-root .section-header {
|
||||||
|
margin-bottom: 28px;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,429 @@
|
|||||||
|
/* ===========================================
|
||||||
|
Procedures (Modern Process Flow)
|
||||||
|
=========================================== */
|
||||||
|
|
||||||
|
.journey-section {
|
||||||
|
padding: 120px 5%;
|
||||||
|
background: #ffffff;
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.journey-container {
|
||||||
|
max-width: 1200px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.process-flow-wrapper {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 40px;
|
||||||
|
margin-top: 80px;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.process-step {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
text-align: center;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.step-visual {
|
||||||
|
position: relative;
|
||||||
|
margin-bottom: 30px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.step-icon-box {
|
||||||
|
width: 80px;
|
||||||
|
height: 80px;
|
||||||
|
background: white;
|
||||||
|
border: 1px solid #e2e8f0;
|
||||||
|
border-radius: 50%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: 1.8rem;
|
||||||
|
color: var(--primary-blue);
|
||||||
|
box-shadow: 0 10px 25px rgba(0, 49, 80, 0.05);
|
||||||
|
transition: all 0.3s cubic-bezier(0.165, 0.84, 0.44, 1);
|
||||||
|
z-index: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.process-step:hover .step-icon-box {
|
||||||
|
border-color: var(--secondary-gold);
|
||||||
|
color: var(--secondary-gold);
|
||||||
|
transform: translateY(-5px);
|
||||||
|
box-shadow: 0 15px 35px rgba(254, 180, 1, 0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
.step-number-badge {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
right: calc(50% - 38px);
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
background: var(--secondary-gold);
|
||||||
|
color: var(--primary-blue);
|
||||||
|
border-radius: 50%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: 0.7rem;
|
||||||
|
font-weight: 800;
|
||||||
|
z-index: 3;
|
||||||
|
border: 2px solid white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.step-connector {
|
||||||
|
position: absolute;
|
||||||
|
left: calc(50% + 50px);
|
||||||
|
top: 50%;
|
||||||
|
width: calc(100% - 100px);
|
||||||
|
height: 2px;
|
||||||
|
background: repeating-linear-gradient(to right, #e2e8f0, #e2e8f0 5px, transparent 5px, transparent 10px);
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.step-content h3 {
|
||||||
|
font-size: 1.25rem;
|
||||||
|
color: var(--primary-blue);
|
||||||
|
font-weight: 800;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
letter-spacing: -0.3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.step-content p {
|
||||||
|
font-size: 0.95rem;
|
||||||
|
color: #64748b;
|
||||||
|
line-height: 1.7;
|
||||||
|
max-width: 280px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 1024px) {
|
||||||
|
.process-flow-wrapper {
|
||||||
|
gap: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.step-connector {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.process-flow-wrapper {
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 60px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.process-step {
|
||||||
|
flex-direction: row;
|
||||||
|
text-align: left;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.step-visual {
|
||||||
|
width: 80px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.step-number-badge {
|
||||||
|
right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.step-content p {
|
||||||
|
margin: 0;
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* ===========================================
|
||||||
|
Help Center (Interaction Cards)
|
||||||
|
=========================================== */
|
||||||
|
|
||||||
|
.help-center-section {
|
||||||
|
padding: 100px 5%;
|
||||||
|
background: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.help-container {
|
||||||
|
max-width: 1100px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.help-header-centered {
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 60px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.help-cards-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(2, 1fr);
|
||||||
|
gap: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.interaction-card {
|
||||||
|
position: relative;
|
||||||
|
border-radius: 4px;
|
||||||
|
overflow: hidden;
|
||||||
|
padding: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.help-official {
|
||||||
|
background: linear-gradient(135deg, var(--primary-blue) 0%, #001e35 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.help-urgent {
|
||||||
|
background: linear-gradient(135deg, var(--secondary-gold) 0%, #ffd54f 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-inner {
|
||||||
|
background: white;
|
||||||
|
padding: 50px;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
text-align: center;
|
||||||
|
border-radius: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.help-official .card-inner {
|
||||||
|
border: 1px solid rgba(0, 49, 80, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-icon {
|
||||||
|
width: 80px;
|
||||||
|
height: 80px;
|
||||||
|
background: #f8fafc;
|
||||||
|
border-radius: 50%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: 2rem;
|
||||||
|
margin-bottom: 30px;
|
||||||
|
color: var(--primary-blue);
|
||||||
|
}
|
||||||
|
|
||||||
|
.help-urgent .card-icon {
|
||||||
|
background: rgba(254, 180, 1, 0.1);
|
||||||
|
color: var(--secondary-gold);
|
||||||
|
}
|
||||||
|
|
||||||
|
.interaction-card h3 {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
color: var(--primary-blue);
|
||||||
|
margin-bottom: 15px;
|
||||||
|
font-weight: 800;
|
||||||
|
}
|
||||||
|
|
||||||
|
.interaction-card p {
|
||||||
|
font-size: 1rem;
|
||||||
|
color: #64748b;
|
||||||
|
line-height: 1.6;
|
||||||
|
margin-bottom: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-checklist {
|
||||||
|
list-style: none;
|
||||||
|
margin-bottom: 40px;
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-checklist li {
|
||||||
|
font-size: 0.85rem;
|
||||||
|
font-weight: 700;
|
||||||
|
color: var(--primary-blue);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-checklist li i {
|
||||||
|
color: #10b981;
|
||||||
|
}
|
||||||
|
|
||||||
|
a.btn-interaction,
|
||||||
|
a.btn-interaction-alt {
|
||||||
|
width: 100%;
|
||||||
|
padding: 18px;
|
||||||
|
font-weight: 800;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 2px;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
text-decoration: none;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
a.btn-interaction {
|
||||||
|
background: var(--primary-blue);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
a.btn-interaction-alt {
|
||||||
|
background: var(--secondary-gold);
|
||||||
|
color: var(--primary-blue);
|
||||||
|
}
|
||||||
|
|
||||||
|
a.btn-interaction:hover {
|
||||||
|
background: #0f172a;
|
||||||
|
transform: translateY(-2px);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
a.btn-interaction-alt:hover {
|
||||||
|
background: #facc15;
|
||||||
|
transform: translateY(-2px);
|
||||||
|
color: var(--primary-blue);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.journey-section {
|
||||||
|
padding: 80px 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.help-center-section {
|
||||||
|
padding: 80px 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.help-cards-grid {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-inner {
|
||||||
|
padding: 35px 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.interaction-card h3 {
|
||||||
|
font-size: 1.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.interaction-card p {
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-icon {
|
||||||
|
width: 64px;
|
||||||
|
height: 64px;
|
||||||
|
font-size: 1.6rem;
|
||||||
|
margin-bottom: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
a.btn-interaction,
|
||||||
|
a.btn-interaction-alt {
|
||||||
|
padding: 14px;
|
||||||
|
font-size: 0.78rem;
|
||||||
|
letter-spacing: 1.5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.process-flow-wrapper {
|
||||||
|
margin-top: 50px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.step-content h3 {
|
||||||
|
font-size: 1.1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.step-content p {
|
||||||
|
font-size: 0.88rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 480px) {
|
||||||
|
.journey-section {
|
||||||
|
padding: 50px 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.help-center-section {
|
||||||
|
padding: 50px 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.help-header-centered {
|
||||||
|
margin-bottom: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-inner {
|
||||||
|
padding: 28px 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-icon {
|
||||||
|
width: 56px;
|
||||||
|
height: 56px;
|
||||||
|
font-size: 1.4rem;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.interaction-card h3 {
|
||||||
|
font-size: 1.1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.interaction-card p {
|
||||||
|
font-size: 0.85rem;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-checklist {
|
||||||
|
margin-bottom: 28px;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-checklist li {
|
||||||
|
font-size: 0.78rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
a.btn-interaction,
|
||||||
|
a.btn-interaction-alt {
|
||||||
|
padding: 12px;
|
||||||
|
font-size: 0.72rem;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.process-flow-wrapper {
|
||||||
|
margin-top: 35px;
|
||||||
|
gap: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.process-step {
|
||||||
|
gap: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.step-icon-box {
|
||||||
|
width: 60px;
|
||||||
|
height: 60px;
|
||||||
|
font-size: 1.4rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.step-content h3 {
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.step-content p {
|
||||||
|
font-size: 0.82rem;
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.step-number-badge {
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
font-size: 0.6rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
/* ===========================================
|
||||||
|
Design Tokens — TAPOPS (Informatika UNTAN)
|
||||||
|
Overrides edo variable names with Tapops palette.
|
||||||
|
All document section CSS files inherit from here.
|
||||||
|
=========================================== */
|
||||||
|
|
||||||
|
:root {
|
||||||
|
--primary-blue: #003150; /* Tapops navy (was #1b3652) */
|
||||||
|
--secondary-gold: #feb401; /* Tapops yellow (was #f7b738) */
|
||||||
|
--text-dark: #1e293b; /* slate-800 (was #222) */
|
||||||
|
--text-light: #fff;
|
||||||
|
--bg-light: #f8fafc; /* slate-50 (was #f4f6f9) */
|
||||||
|
--border-color: #e2e8f0; /* slate-200 (was #eaeaea) */
|
||||||
|
--shadow: 0 18px 35px rgba(0, 49, 80, 0.08);
|
||||||
|
}
|
||||||
@@ -0,0 +1,234 @@
|
|||||||
|
/* ===========================================
|
||||||
|
Viewer Page — PDF iframe + DOCX rendered preview
|
||||||
|
=========================================== */
|
||||||
|
|
||||||
|
.viewer-body {
|
||||||
|
margin: 0;
|
||||||
|
background: var(--bg-light);
|
||||||
|
font-family: 'Roboto', sans-serif;
|
||||||
|
min-height: 100vh;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- Header --- */
|
||||||
|
|
||||||
|
.viewer-header {
|
||||||
|
background: var(--primary-blue);
|
||||||
|
color: var(--text-light);
|
||||||
|
padding: 16px 32px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
gap: 24px;
|
||||||
|
box-shadow: var(--shadow);
|
||||||
|
flex-wrap: wrap;
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
.viewer-header-left {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 20px;
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.viewer-title-group {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 4px;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.viewer-title {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 1.1rem;
|
||||||
|
font-weight: 700;
|
||||||
|
color: var(--text-light);
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
.viewer-type-badge {
|
||||||
|
display: inline-block;
|
||||||
|
width: fit-content;
|
||||||
|
padding: 2px 10px;
|
||||||
|
background: var(--secondary-gold);
|
||||||
|
color: var(--primary-blue);
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 0.7rem;
|
||||||
|
font-weight: 800;
|
||||||
|
letter-spacing: 0.5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- Buttons --- */
|
||||||
|
|
||||||
|
.btn-back,
|
||||||
|
.btn-download,
|
||||||
|
.btn-back-mini {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 10px 18px;
|
||||||
|
border-radius: 6px;
|
||||||
|
font-weight: 700;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
text-decoration: none;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-back {
|
||||||
|
background: rgba(255, 255, 255, 0.1);
|
||||||
|
color: var(--text-light);
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.25);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-back:hover {
|
||||||
|
background: rgba(255, 255, 255, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-download {
|
||||||
|
background: var(--secondary-gold);
|
||||||
|
color: var(--primary-blue);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-download:hover {
|
||||||
|
transform: translateY(-1px);
|
||||||
|
box-shadow: 0 6px 16px rgba(254, 180, 1, 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-back-mini {
|
||||||
|
background: var(--primary-blue);
|
||||||
|
color: var(--text-light);
|
||||||
|
margin-top: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-back-mini:hover {
|
||||||
|
background: #001e35;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- Content area --- */
|
||||||
|
|
||||||
|
.viewer-content {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
background: #525659;
|
||||||
|
min-height: calc(100vh - 80px);
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.viewer-pdf-frame {
|
||||||
|
flex: 1;
|
||||||
|
width: 100%;
|
||||||
|
border: none;
|
||||||
|
min-height: calc(100vh - 80px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.viewer-docx-content {
|
||||||
|
background: white;
|
||||||
|
margin: 24px auto;
|
||||||
|
padding: 60px 80px;
|
||||||
|
max-width: 900px;
|
||||||
|
width: calc(100% - 48px);
|
||||||
|
box-sizing: border-box;
|
||||||
|
box-shadow: var(--shadow);
|
||||||
|
border-radius: 4px;
|
||||||
|
overflow-x: auto;
|
||||||
|
color: var(--text-dark);
|
||||||
|
line-height: 1.6;
|
||||||
|
font-family: 'Roboto', sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* docx-preview injects its own structure; override its defaults gently. */
|
||||||
|
.viewer-docx-content .docx-wrapper {
|
||||||
|
background: transparent !important;
|
||||||
|
padding: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.viewer-docx-content .docx {
|
||||||
|
box-shadow: none !important;
|
||||||
|
margin: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- States --- */
|
||||||
|
|
||||||
|
.viewer-loading,
|
||||||
|
.viewer-error {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 16px;
|
||||||
|
color: var(--text-light);
|
||||||
|
font-size: 1rem;
|
||||||
|
font-weight: 600;
|
||||||
|
padding: 60px 20px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.viewer-loading i {
|
||||||
|
font-size: 3rem;
|
||||||
|
color: var(--secondary-gold);
|
||||||
|
}
|
||||||
|
|
||||||
|
.viewer-error i {
|
||||||
|
font-size: 3rem;
|
||||||
|
color: #ef4444;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- Download Progress Bar --- */
|
||||||
|
|
||||||
|
.viewer-progress-bar {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 3px;
|
||||||
|
background: rgba(255, 255, 255, 0.1);
|
||||||
|
z-index: 5;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.viewer-progress-fill {
|
||||||
|
height: 100%;
|
||||||
|
width: 0%;
|
||||||
|
background: linear-gradient(90deg, var(--secondary-gold), #ffd54f);
|
||||||
|
border-radius: 0 2px 2px 0;
|
||||||
|
transition: width 0.2s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- Responsive --- */
|
||||||
|
|
||||||
|
@media (max-width: 640px) {
|
||||||
|
.viewer-header {
|
||||||
|
padding: 12px 16px;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.viewer-header-left {
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.viewer-title {
|
||||||
|
font-size: 0.95rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.viewer-docx-content {
|
||||||
|
padding: 24px;
|
||||||
|
margin: 12px;
|
||||||
|
width: calc(100% - 24px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-back,
|
||||||
|
.btn-download {
|
||||||
|
padding: 8px 14px;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
+219
-300
@@ -1,4 +1,4 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="id" class="scroll-smooth">
|
<html lang="id" class="scroll-smooth">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
@@ -8,290 +8,18 @@
|
|||||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||||
<link href="https://fonts.googleapis.com/css2?family=Roboto+Condensed:ital,wght@0,100..900;1,100..900&family=Roboto:ital,wght@0,100;0,300;0,400;0,500;0,700;0,900;1,100;1,300;1,400;1,500;1,700;1,900&display=swap" rel="stylesheet">
|
<link href="https://fonts.googleapis.com/css2?family=Roboto+Condensed:ital,wght@0,100..900;1,100..900&family=Roboto:ital,wght@0,100;0,300;0,400;0,500;0,700;0,900;1,100;1,300;1,400;1,500;1,700;1,900&display=swap" rel="stylesheet">
|
||||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"
|
||||||
<style>
|
integrity="sha384-iw3OoTErCYJJB9mCa8LNS2hbsQ7M3C0EpIsO/H5+EGAkPGc6rk+V8i04oW/K5xq0"
|
||||||
/* ===== MOBILE NAV STABILITY FIX =====
|
crossorigin="anonymous">
|
||||||
Prevents logo bar from collapsing/shifting on Android when
|
<!-- Navigation styles (extracted from inline) -->
|
||||||
address bar hides/shows and triggers viewport reflow.
|
<link rel="stylesheet" href="css/nav.css">
|
||||||
===================================== */
|
<!-- Document section styles -->
|
||||||
#nav-wrapper {
|
<link rel="stylesheet" href="css/variables.css">
|
||||||
position: fixed !important;
|
<link rel="stylesheet" href="css/base-scoped.css">
|
||||||
top: 0;
|
<link rel="stylesheet" href="css/hero.css">
|
||||||
left: 0;
|
<link rel="stylesheet" href="css/documents.css">
|
||||||
right: 0;
|
<link rel="stylesheet" href="css/sections.css">
|
||||||
z-index: 50;
|
<link rel="stylesheet" href="css/responsive.css">
|
||||||
backface-visibility: hidden;
|
|
||||||
-webkit-backface-visibility: hidden;
|
|
||||||
will-change: box-shadow;
|
|
||||||
}
|
|
||||||
|
|
||||||
#nav-wrapper>div:first-child {
|
|
||||||
transform: translateZ(0);
|
|
||||||
-webkit-transform: translateZ(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 1023px) {
|
|
||||||
#nav-wrapper>div:first-child {
|
|
||||||
min-height: 0 !important;
|
|
||||||
max-height: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#pengumuman-bar {
|
|
||||||
z-index: 40;
|
|
||||||
}
|
|
||||||
|
|
||||||
#pengumuman-bar.is-stuck {
|
|
||||||
position: fixed;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
z-index: 40;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (prefers-reduced-motion: reduce) {
|
|
||||||
|
|
||||||
*,
|
|
||||||
*::before,
|
|
||||||
*::after {
|
|
||||||
animation-duration: 0.01ms !important;
|
|
||||||
animation-iteration-count: 1 !important;
|
|
||||||
transition-duration: 0.01ms !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Skip Link Styling */
|
|
||||||
.skip-link {
|
|
||||||
position: absolute;
|
|
||||||
top: -40px;
|
|
||||||
left: 0;
|
|
||||||
background: #003150;
|
|
||||||
color: white;
|
|
||||||
padding: 8px 16px;
|
|
||||||
z-index: 100;
|
|
||||||
transition: top 0.3s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.skip-link:focus {
|
|
||||||
top: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Focus visible for better keyboard navigation */
|
|
||||||
:focus-visible {
|
|
||||||
outline: 3px solid #feb401;
|
|
||||||
outline-offset: 2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Mega Menu Styles */
|
|
||||||
.hero-pagination .swiper-pagination-bullet {
|
|
||||||
width: 8px;
|
|
||||||
height: 8px;
|
|
||||||
border-radius: 50%;
|
|
||||||
background: rgba(255, 255, 255, 0.4);
|
|
||||||
display: inline-block;
|
|
||||||
margin: 0 4px;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: all 0.3s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hero-pagination .swiper-pagination-bullet-active {
|
|
||||||
background: rgba(255, 255, 255, 1);
|
|
||||||
width: 24px;
|
|
||||||
border-radius: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mega-panel {
|
|
||||||
opacity: 0;
|
|
||||||
visibility: hidden;
|
|
||||||
transition: opacity 0.2s ease, visibility 0.2s ease;
|
|
||||||
position: absolute;
|
|
||||||
top: 100%;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mega-panel.active {
|
|
||||||
opacity: 1;
|
|
||||||
visibility: visible;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Overlay transition */
|
|
||||||
#mega-overlay {
|
|
||||||
opacity: 0;
|
|
||||||
visibility: hidden;
|
|
||||||
transition: opacity 0.2s ease, visibility 0.2s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
#mega-overlay.active {
|
|
||||||
opacity: 1;
|
|
||||||
visibility: visible;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mega-link-card {
|
|
||||||
display: flex;
|
|
||||||
align-items: flex-start;
|
|
||||||
gap: 1rem;
|
|
||||||
padding: 1rem;
|
|
||||||
border-radius: 0.75rem;
|
|
||||||
border: 1px solid #e2e8f0;
|
|
||||||
background: white;
|
|
||||||
transition: all 0.2s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mega-link-card:hover {
|
|
||||||
border-color: #3b82f6;
|
|
||||||
box-shadow: 0 4px 12px rgba(59, 130, 246, 0.15);
|
|
||||||
transform: translateY(-2px);
|
|
||||||
}
|
|
||||||
|
|
||||||
.mega-link-icon {
|
|
||||||
width: 2.5rem;
|
|
||||||
height: 2.5rem;
|
|
||||||
border-radius: 0.5rem;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
flex-shrink: 0;
|
|
||||||
transition: all 0.2s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav-menu-btn.active {
|
|
||||||
background: rgba(255, 255, 255, 0.9);
|
|
||||||
color: #1e40af;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav-menu-btn:hover {
|
|
||||||
background: rgba(255, 255, 255, 0.8);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Mega menu positioning - no gap */
|
|
||||||
#main-nav {
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
#mega-menu-container {
|
|
||||||
position: absolute;
|
|
||||||
top: 100%;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
#mega-menu-container .mega-panel {
|
|
||||||
pointer-events: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Keep pointer bridge under nav to prevent hover loss */
|
|
||||||
.nav-menu-item {
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav-menu-item::after {
|
|
||||||
content: '';
|
|
||||||
position: absolute;
|
|
||||||
bottom: -12px;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
height: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ===== Luco Chatbot Animations ===== */
|
|
||||||
@keyframes slideInRight {
|
|
||||||
from {
|
|
||||||
transform: translateX(100px);
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
to {
|
|
||||||
transform: translateX(0);
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes fadeInUp {
|
|
||||||
from {
|
|
||||||
transform: translateY(10px);
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
to {
|
|
||||||
transform: translateY(0);
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes lucoPulse {
|
|
||||||
|
|
||||||
0%,
|
|
||||||
100% {
|
|
||||||
transform: scale(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
50% {
|
|
||||||
transform: scale(1.05);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#luco-container {
|
|
||||||
animation: slideInRight 0.5s ease-out forwards;
|
|
||||||
}
|
|
||||||
|
|
||||||
#luco-bubble {
|
|
||||||
animation: fadeInUp 0.3s ease-out forwards;
|
|
||||||
width: min(18rem, calc(100vw - 2rem));
|
|
||||||
}
|
|
||||||
|
|
||||||
#luco-btn {
|
|
||||||
animation: lucoPulse 2s ease-in-out infinite;
|
|
||||||
}
|
|
||||||
|
|
||||||
#luco-btn:hover {
|
|
||||||
animation: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Typing indicator */
|
|
||||||
.typing-indicator span {
|
|
||||||
display: inline-block;
|
|
||||||
animation: bounce 1.4s infinite ease-in-out;
|
|
||||||
}
|
|
||||||
|
|
||||||
.typing-indicator span:nth-child(1) {
|
|
||||||
animation-delay: 0s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.typing-indicator span:nth-child(2) {
|
|
||||||
animation-delay: 0.2s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.typing-indicator span:nth-child(3) {
|
|
||||||
animation-delay: 0.4s;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes bounce {
|
|
||||||
|
|
||||||
0%,
|
|
||||||
80%,
|
|
||||||
100% {
|
|
||||||
transform: translateY(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
40% {
|
|
||||||
transform: translateY(-6px);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Luco speech bubble arrow */
|
|
||||||
#luco-bubble .bubble-arrow {
|
|
||||||
position: absolute;
|
|
||||||
right: 24px;
|
|
||||||
bottom: -6px;
|
|
||||||
width: 12px;
|
|
||||||
height: 12px;
|
|
||||||
background: white;
|
|
||||||
transform: rotate(45deg);
|
|
||||||
border-right: 1px solid #e2e8f0;
|
|
||||||
border-bottom: 1px solid #e2e8f0;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
</head>
|
||||||
<body class="text-slate-800 antialiased bg-white selection:bg-untan-yellow selection:text-black">
|
<body class="text-slate-800 antialiased bg-white selection:bg-untan-yellow selection:text-black">
|
||||||
|
|
||||||
@@ -345,7 +73,7 @@
|
|||||||
<button
|
<button
|
||||||
class="w-8 h-8 flex items-center justify-center rounded-sm hover:bg-white/10 transition-colors"
|
class="w-8 h-8 flex items-center justify-center rounded-sm hover:bg-white/10 transition-colors"
|
||||||
aria-label="Pengaturan aksesibilitas"
|
aria-label="Pengaturan aksesibilitas"
|
||||||
onclick="const p=document.getElementById('access-panel'); if (p) { p.classList.toggle('hidden'); p.classList.toggle('flex'); }">
|
onclick="var p=document.getElementById('access-panel'); if (p) { p.classList.toggle('hidden'); p.classList.toggle('flex'); }">
|
||||||
<i class="fas fa-universal-access" aria-hidden="true"></i>
|
<i class="fas fa-universal-access" aria-hidden="true"></i>
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
@@ -1013,7 +741,7 @@
|
|||||||
<button
|
<button
|
||||||
class="w-8 h-8 rounded-sm bg-white/10 hover:bg-white/20 flex items-center justify-center text-white transition-colors"
|
class="w-8 h-8 rounded-sm bg-white/10 hover:bg-white/20 flex items-center justify-center text-white transition-colors"
|
||||||
aria-label="Pengaturan aksesibilitas"
|
aria-label="Pengaturan aksesibilitas"
|
||||||
onclick="const p=document.getElementById('access-panel'); if (p) { p.classList.toggle('hidden'); p.classList.toggle('flex'); }">
|
onclick="var p=document.getElementById('access-panel'); if (p) { p.classList.toggle('hidden'); p.classList.toggle('flex'); }">
|
||||||
<i class="fas fa-universal-access" aria-hidden="true"></i>
|
<i class="fas fa-universal-access" aria-hidden="true"></i>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -1139,21 +867,210 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<main id="main-content" class="pt-[88px] lg:pt-[160px] min-h-screen bg-white">
|
<main id="main-content" class="tapops-doc-root pt-[72px] lg:pt-[160px]">
|
||||||
<div class="container mx-auto px-4 lg:px-8 py-12">
|
|
||||||
<div class="mb-8 pb-8 border-b border-slate-200">
|
<!-- ═══════════════════════════════════════
|
||||||
<span class="bg-untan-yellow text-slate-900 text-[10px] font-bold uppercase tracking-widest px-2 py-0.5 inline-block mb-3">Tugas Halaman Statis</span>
|
HERO
|
||||||
<h1 class="text-3xl lg:text-4xl font-bold text-slate-900 mb-2">Berkas Penting</h1>
|
═══════════════════════════════════════ -->
|
||||||
<p class="text-slate-500 text-sm">Kelompok 09 — TAPOPS · Informatika UNTAN 2026</p>
|
<section class="research-hero immersive-hero">
|
||||||
</div>
|
<div class="hero-container-centered">
|
||||||
<div id="assignment-body" class="prose prose-slate max-w-none">
|
<span class="hero-kicker" data-tp-i18n="hero_kicker">Digital Administrative Hub</span>
|
||||||
<!-- ================= START: BAGIAN YANG DIEDIT KELOMPOK ================= -->
|
<h2 class="hero-title" data-tp-i18n="hero_title">Administrasi Akademik Digital</h2>
|
||||||
<p>Tulis konten halaman kelompok di sini.</p>
|
<p class="hero-tagline" data-tp-i18n="hero_tagline">
|
||||||
<h3>Subjudul</h3>
|
Satu pintu menuju semua dokumen penting. Akses terpusat menuju repositori dokumen akademik Program Studi
|
||||||
<p>Gunakan paragraf, list, tabel, gambar, dan elemen konten lainnya.</p>
|
Informatika Universitas Tanjungpura untuk mahasiswa dan staf.
|
||||||
<!-- ================= END: BAGIAN YANG DIEDIT KELOMPOK ================= -->
|
</p>
|
||||||
|
<div class="hero-actions">
|
||||||
|
<a href="#repository" class="btn-join"><span data-tp-i18n="hero_btn_search">Cari Dokumen</span> <i class="fas fa-search"></i></a>
|
||||||
|
<a href="#procedures" class="btn-outline"><span data-tp-i18n="hero_btn_flow">ALUR PENGAJUAN</span> <i class="fas fa-chevron-right"></i></a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="hero-floating-stat-bar">
|
||||||
|
<div class="stat-bar-item">
|
||||||
|
<span class="stat-bar-value" id="stat-active-value">—</span>
|
||||||
|
<span class="stat-bar-label" data-tp-i18n="stat_active">Dokumen Aktif</span>
|
||||||
|
</div>
|
||||||
|
<div class="stat-bar-divider"></div>
|
||||||
|
<div class="stat-bar-item">
|
||||||
|
<span class="stat-bar-value" id="stat-updates-value">—</span>
|
||||||
|
<span class="stat-bar-label" data-tp-i18n="stat_updates">Pembaruan Terbaru</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- ═══════════════════════════════════════
|
||||||
|
DOCUMENT REPOSITORY
|
||||||
|
═══════════════════════════════════════ -->
|
||||||
|
<section id="repository" class="documents-section">
|
||||||
|
<div class="repo-dashboard-container">
|
||||||
|
<!-- Sidebar Filter -->
|
||||||
|
<aside class="repo-sidebar">
|
||||||
|
<div class="sidebar-sticky">
|
||||||
|
<h3 class="sidebar-title" data-tp-i18n="sidebar_category">KATEGORI BERKAS</h3>
|
||||||
|
<div class="document-filter-wrapper" id="tag-filter-wrapper">
|
||||||
|
<!-- Filter buttons are built dynamically from API tags by main.js -->
|
||||||
|
<div class="filter-skeleton">
|
||||||
|
<div class="filter-skeleton-item"></div>
|
||||||
|
<div class="filter-skeleton-item short"></div>
|
||||||
|
<div class="filter-skeleton-item"></div>
|
||||||
|
<div class="filter-skeleton-item short"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="sidebar-info-box">
|
||||||
|
<i class="fas fa-info-circle"></i>
|
||||||
|
<p data-tp-i18n="sidebar_info">Pastikan mengunduh versi terbaru untuk menjamin validitas berkas pengajuan.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</aside>
|
||||||
|
|
||||||
|
<!-- Main Document Area (div, not main — only one <main> per page) -->
|
||||||
|
<div class="repo-main-content">
|
||||||
|
<div class="repo-content-header">
|
||||||
|
<div class="header-title-area">
|
||||||
|
<h2 class="section-title" data-tp-i18n="repo_title">Eksplorasi Dokumen Prodi</h2>
|
||||||
|
<p class="section-subtitle" data-tp-i18n="repo_subtitle">Temukan berkas administrasi dan akademik terbaru Program Studi
|
||||||
|
Informatika.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="repo-controls">
|
||||||
|
<div class="search-container">
|
||||||
|
<div class="search-input-group">
|
||||||
|
<i class="fas fa-search search-icon"></i>
|
||||||
|
<input type="text" id="doc-search" data-tp-i18n-placeholder="search_ph" placeholder="Cari nama berkas atau kata kunci...">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="filter-controls">
|
||||||
|
<div class="control-group date-range-group">
|
||||||
|
<span class="control-label" data-tp-i18n="label_period">Periode Unggahan</span>
|
||||||
|
<div class="date-range-inputs">
|
||||||
|
<div class="input-wrapper">
|
||||||
|
<input type="date" id="date-start" placeholder="Mulai">
|
||||||
|
</div>
|
||||||
|
<span class="range-divider" data-tp-i18n="range_divider">s/d</span>
|
||||||
|
<div class="input-wrapper">
|
||||||
|
<input type="date" id="date-end" placeholder="Selesai">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="control-group">
|
||||||
|
<span class="control-label" data-tp-i18n="label_sort">Urutkan</span>
|
||||||
|
<div id="sort-filter-wrapper"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="control-group mobile-category-control-group">
|
||||||
|
<span class="control-label" data-tp-i18n="sidebar_category">Kategori Berkas</span>
|
||||||
|
<div id="mobile-tag-filter-wrapper"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="document-grid" id="document-grid">
|
||||||
|
<div class="repo-loading" style="grid-column: 1 / -1; text-align: center; padding: 60px 20px; color: var(--primary-blue); font-weight: 700; font-size: 1.1rem; display: flex; flex-direction: column; align-items: center; gap: 15px;">
|
||||||
|
<i class="fas fa-circle-notch fa-spin" style="font-size: 2.5rem; color: var(--secondary-gold);"></i>
|
||||||
|
<span data-tp-i18n="loading">Menghubungkan ke repositori berkas...</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- ═══════════════════════════════════════
|
||||||
|
PROCEDURES
|
||||||
|
═══════════════════════════════════════ -->
|
||||||
|
<section id="procedures" class="journey-section">
|
||||||
|
<div class="journey-container">
|
||||||
|
<div class="section-header centered">
|
||||||
|
<h2 class="section-title" data-tp-i18n="proc_title">Tata Cara Pengajuan Berkas</h2>
|
||||||
|
<span class="section-tag" data-tp-i18n="proc_tag">Alur Pengajuan</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="process-flow-wrapper">
|
||||||
|
<!-- Step 01 -->
|
||||||
|
<div class="process-step">
|
||||||
|
<div class="step-visual">
|
||||||
|
<div class="step-icon-box"><i class="fas fa-file-download"></i></div>
|
||||||
|
<div class="step-number-badge">01</div>
|
||||||
|
<div class="step-connector"></div>
|
||||||
|
</div>
|
||||||
|
<div class="step-content">
|
||||||
|
<h3 data-tp-i18n="proc_s1_h">Unduh Formulir</h3>
|
||||||
|
<p data-tp-i18n="proc_s1_p">Pilih kategori pengajuan Anda, lalu unduh formulir versi terbaru. Pastikan kode revisi pada formulir sesuai dengan yang tertera di halaman ini.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Step 02 -->
|
||||||
|
<div class="process-step">
|
||||||
|
<div class="step-visual">
|
||||||
|
<div class="step-icon-box"><i class="fas fa-edit"></i></div>
|
||||||
|
<div class="step-number-badge">02</div>
|
||||||
|
<div class="step-connector"></div>
|
||||||
|
</div>
|
||||||
|
<div class="step-content">
|
||||||
|
<h3 data-tp-i18n="proc_s2_h">Lengkapi Formulir</h3>
|
||||||
|
<p data-tp-i18n="proc_s2_p">Isi setiap kolom dengan lengkap dan teliti. Sertakan seluruh dokumen pendukung yang diminta sebelum pengiriman.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Step 03 -->
|
||||||
|
<div class="process-step">
|
||||||
|
<div class="step-visual">
|
||||||
|
<div class="step-icon-box"><i class="fas fa-paper-plane"></i></div>
|
||||||
|
<div class="step-number-badge">03</div>
|
||||||
|
</div>
|
||||||
|
<div class="step-content">
|
||||||
|
<h3 data-tp-i18n="proc_s3_h">Kirim Berkas</h3>
|
||||||
|
<p data-tp-i18n="proc_s3_p">Kirimkan formulir beserta dokumen pendukung melalui email resmi atau WhatsApp admin prodi. Berkas yang lengkap akan langsung diproses oleh admin prodi.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- ═══════════════════════════════════════
|
||||||
|
HELP CENTER
|
||||||
|
═══════════════════════════════════════ -->
|
||||||
|
<section id="help" class="help-center-section">
|
||||||
|
<div class="help-container">
|
||||||
|
<div class="help-header-centered">
|
||||||
|
<h2 class="section-title" data-tp-i18n="help_title">Butuh Bantuan Lebih Lanjut?</h2>
|
||||||
|
<p data-tp-i18n="help_desc">Tim admin kami siap membantu Anda melalui kanal komunikasi resmi di bawah ini.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="help-cards-grid">
|
||||||
|
<div class="interaction-card help-official">
|
||||||
|
<div class="card-inner">
|
||||||
|
<div class="card-icon"><i class="fas fa-envelope-open-text"></i></div>
|
||||||
|
<h3 data-tp-i18n="help_email_title">Korespondensi Resmi (Email)</h3>
|
||||||
|
<p data-tp-i18n="help_email_desc">Gunakan untuk permohonan form khusus non-publik atau administrasi formal lainnya.</p>
|
||||||
|
<ul class="card-checklist">
|
||||||
|
<li><i class="fas fa-check"></i> <span data-tp-i18n="help_email_c1">Respon Maks. 24 Jam</span></li>
|
||||||
|
<li><i class="fas fa-check"></i> <span data-tp-i18n="help_email_c2">Pelayanan Formal</span></li>
|
||||||
|
</ul>
|
||||||
|
<a href="mailto:admin@informatika.untan.ac.id?subject=Layanan%20Administrasi%20%26%20CS%20Informatika%20UNTAN&body=Halo%20Admin%20Prodi%20Informatika%20UNTAN%2C%0A%0ASaya%20ingin%20mengajukan%20permohonan%20%2F%20melaporkan%20kendala%20terkait%20administrasi%20akademik.%20Berikut%20detail%20informasi%20saya%3A%0A%0ANama%3A%20%5BNama%20Lengkap%20Anda%5D%0ANIM%3A%20%5BNIM%20Anda%5D%0AKategori%20Layanan%3A%20%5BTata%20Cara%20Pengajuan%20%2F%20Kendala%20Teknis%20%26%20CS%5D%0A%0ADetail%20Masalah%20%2F%20Pengajuan%3A%0A%5BTuliskan%20pertanyaan%2C%20detail%20pengajuan%2C%20atau%20kendala%20Anda%20di%20sini...%5D%0A%0ATerima%20kasih." class="btn-interaction"><span data-tp-i18n="help_email_btn">HUBUNGI ADMIN</span> <i class="fas fa-paper-plane"></i></a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="interaction-card help-urgent">
|
||||||
|
<div class="card-inner">
|
||||||
|
<div class="card-icon"><i class="fab fa-whatsapp"></i></div>
|
||||||
|
<h3 data-tp-i18n="help_wa_title">Quick Support (WhatsApp)</h3>
|
||||||
|
<p data-tp-i18n="help_wa_desc">Konsultasi cepat alur birokrasi atau pelaporan kendala akses pada berkas repositori.</p>
|
||||||
|
<ul class="card-checklist">
|
||||||
|
<li><i class="fas fa-check"></i> <span data-tp-i18n="help_wa_c1">Respon Instan Jam Kerja</span></li>
|
||||||
|
<li><i class="fas fa-check"></i> <span data-tp-i18n="help_wa_c2">Konsultasi Teknis</span></li>
|
||||||
|
</ul>
|
||||||
|
<a href="https://wa.me/6285171655161?text=Halo%20Admin%20Prodi%20Informatika%20UNTAN%2C%0A%0ASaya%20ingin%20mengajukan%20permohonan%20%2F%20melaporkan%20kendala%20terkait%20administrasi%20akademik.%20Berikut%20detail%20informasi%20saya%3A%0A%0ANama%3A%20%5BNama%20Lengkap%20Anda%5D%0ANIM%3A%20%5BNIM%20Anda%5D%0AKategori%20Layanan%3A%20%5BTata%20Cara%20Pengajuan%20%2F%20Kendala%20Teknis%20%26%20CS%5D%0A%0ADetail%20Masalah%20%2F%20Pengajuan%3A%0A%5BTuliskan%20pertanyaan%2C%20detail%20pengajuan%2C%20atau%20kendala%20Anda%20di%20sini...%5D%0A%0ATerima%20kasih." class="btn-interaction-alt"><span data-tp-i18n="help_wa_btn">CHAT WHATSAPP</span> <i class="fab fa-whatsapp"></i></a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
<footer class="bg-untan-navy text-white pt-16 relative overflow-hidden mt-0">
|
<footer class="bg-untan-navy text-white pt-16 relative overflow-hidden mt-0">
|
||||||
@@ -1334,5 +1251,7 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script src="../../assets/global/localization.js"></script>
|
<script src="../../assets/global/localization.js"></script>
|
||||||
|
<script src="js/i18n-tapops.js"></script>
|
||||||
|
<script src="js/main.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
@@ -0,0 +1,182 @@
|
|||||||
|
/**
|
||||||
|
* Tapops-local i18n — bilingual ID/EN for the document-repository sections.
|
||||||
|
*
|
||||||
|
* The shared assets/global/localization.js owns the nav/footer translations
|
||||||
|
* and exposes window.getLang() plus a `languageChanged` event. Its internal
|
||||||
|
* `translations` object is not extendable from here, so this layer keeps its
|
||||||
|
* own dictionary under a separate `data-tp-i18n*` attribute namespace. The
|
||||||
|
* global applyLang() ignores those attributes, and this script ignores the
|
||||||
|
* global `data-i18n*` ones — no collisions.
|
||||||
|
*
|
||||||
|
* Exposes window.applyTp() and window.tpT(key) so main.js can translate the
|
||||||
|
* dynamically-rendered document cards and state messages too.
|
||||||
|
*/
|
||||||
|
(function () {
|
||||||
|
const dict = {
|
||||||
|
id: {
|
||||||
|
// Hero
|
||||||
|
hero_kicker: "Digital Administrative Hub",
|
||||||
|
hero_title: "Administrasi Akademik Digital",
|
||||||
|
hero_tagline: "Satu pintu menuju semua dokumen penting. Akses terpusat menuju repositori dokumen akademik Program Studi Informatika Universitas Tanjungpura untuk mahasiswa dan staf.",
|
||||||
|
hero_btn_search: "Cari Dokumen",
|
||||||
|
hero_btn_flow: "ALUR PENGAJUAN",
|
||||||
|
stat_active: "Dokumen Aktif",
|
||||||
|
stat_updates: "Pembaruan Terbaru",
|
||||||
|
|
||||||
|
// Repository — sidebar
|
||||||
|
sidebar_category: "KATEGORI BERKAS",
|
||||||
|
filter_all: "Semua Berkas",
|
||||||
|
filter_skripsi: "Panduan Skripsi",
|
||||||
|
filter_akademik: "Kalender Akademik",
|
||||||
|
filter_praktik: "Kerja Praktik",
|
||||||
|
filter_umum: "Formulir Umum",
|
||||||
|
sidebar_info: "Pastikan mengunduh versi terbaru untuk menjamin validitas berkas pengajuan.",
|
||||||
|
|
||||||
|
// Repository — controls
|
||||||
|
repo_title: "Eksplorasi Dokumen Prodi",
|
||||||
|
repo_subtitle: "Temukan berkas administrasi dan akademik terbaru Program Studi Informatika.",
|
||||||
|
search_ph: "Cari nama berkas atau kata kunci...",
|
||||||
|
label_period: "Periode Unggahan",
|
||||||
|
range_divider: "s/d",
|
||||||
|
label_sort: "Urutkan",
|
||||||
|
sort_newest: "Terbaru",
|
||||||
|
sort_oldest: "Terlama",
|
||||||
|
sort_az: "Nama A-Z",
|
||||||
|
loading: "Menghubungkan ke repositori berkas...",
|
||||||
|
|
||||||
|
// Repository — dynamic (cards + states, used by main.js)
|
||||||
|
card_open: "Buka Berkas",
|
||||||
|
card_download: "Unduh Berkas",
|
||||||
|
state_empty: "Belum ada dokumen yang tersedia di repositori.",
|
||||||
|
state_error: "Gagal memuat berkas. Silakan periksa koneksi internet Anda atau coba lagi nanti.",
|
||||||
|
|
||||||
|
// Procedures
|
||||||
|
proc_title: "Tata Cara Pengajuan Berkas",
|
||||||
|
proc_tag: "Alur Pengajuan",
|
||||||
|
proc_s1_h: "Unduh Formulir",
|
||||||
|
proc_s1_p: "Pilih kategori pengajuan Anda, lalu unduh formulir versi terbaru. Pastikan kode revisi pada formulir sesuai dengan yang tertera di halaman ini.",
|
||||||
|
proc_s2_h: "Lengkapi Formulir",
|
||||||
|
proc_s2_p: "Isi setiap kolom dengan lengkap dan teliti. Sertakan seluruh dokumen pendukung yang diminta sebelum pengiriman.",
|
||||||
|
proc_s3_h: "Kirim Berkas",
|
||||||
|
proc_s3_p: "Kirimkan formulir beserta dokumen pendukung melalui email resmi atau WhatsApp admin prodi. Berkas yang lengkap akan langsung diproses oleh admin prodi.",
|
||||||
|
|
||||||
|
// Help Center
|
||||||
|
help_title: "Butuh Bantuan Lebih Lanjut?",
|
||||||
|
help_desc: "Tim admin kami siap membantu Anda melalui kanal komunikasi resmi di bawah ini.",
|
||||||
|
help_email_title: "Korespondensi Resmi (Email)",
|
||||||
|
help_email_desc: "Gunakan untuk permohonan form khusus non-publik atau administrasi formal lainnya.",
|
||||||
|
help_email_c1: "Respon Maks. 24 Jam",
|
||||||
|
help_email_c2: "Pelayanan Formal",
|
||||||
|
help_email_btn: "HUBUNGI ADMIN",
|
||||||
|
help_wa_title: "Quick Support (WhatsApp)",
|
||||||
|
help_wa_desc: "Konsultasi cepat alur birokrasi atau pelaporan kendala akses pada berkas repositori.",
|
||||||
|
help_wa_c1: "Respon Instan Jam Kerja",
|
||||||
|
help_wa_c2: "Konsultasi Teknis",
|
||||||
|
help_wa_btn: "CHAT WHATSAPP",
|
||||||
|
},
|
||||||
|
en: {
|
||||||
|
// Hero
|
||||||
|
hero_kicker: "Digital Administrative Hub",
|
||||||
|
hero_title: "Digital Academic Administration",
|
||||||
|
hero_tagline: "One gateway to every important document. A centralized access point to the academic document repository of the Informatics Department, Universitas Tanjungpura, for students and staff.",
|
||||||
|
hero_btn_search: "Search Documents",
|
||||||
|
hero_btn_flow: "SUBMISSION FLOW",
|
||||||
|
stat_active: "Active Documents",
|
||||||
|
stat_updates: "Recent Updates",
|
||||||
|
|
||||||
|
// Repository — sidebar
|
||||||
|
sidebar_category: "DOCUMENT CATEGORY",
|
||||||
|
filter_all: "All Files",
|
||||||
|
filter_skripsi: "Thesis Guides",
|
||||||
|
filter_akademik: "Academic Calendar",
|
||||||
|
filter_praktik: "Internship",
|
||||||
|
filter_umum: "General Forms",
|
||||||
|
sidebar_info: "Always download the latest version to ensure your submission files remain valid.",
|
||||||
|
|
||||||
|
// Repository — controls
|
||||||
|
repo_title: "Explore Department Documents",
|
||||||
|
repo_subtitle: "Find the latest administrative and academic files of the Informatics Department.",
|
||||||
|
search_ph: "Search file name or keyword...",
|
||||||
|
label_period: "Upload Period",
|
||||||
|
range_divider: "to",
|
||||||
|
label_sort: "Sort by",
|
||||||
|
sort_newest: "Newest",
|
||||||
|
sort_oldest: "Oldest",
|
||||||
|
sort_az: "Name A-Z",
|
||||||
|
loading: "Connecting to the file repository...",
|
||||||
|
|
||||||
|
// Repository — dynamic (cards + states, used by main.js)
|
||||||
|
card_open: "Open File",
|
||||||
|
card_download: "Download File",
|
||||||
|
state_empty: "No documents are available in the repository yet.",
|
||||||
|
state_error: "Failed to load files. Please check your internet connection or try again later.",
|
||||||
|
|
||||||
|
// Procedures
|
||||||
|
proc_title: "Document Submission Procedure",
|
||||||
|
proc_tag: "Submission Flow",
|
||||||
|
proc_s1_h: "Download the Form",
|
||||||
|
proc_s1_p: "Choose your submission category, then download the latest version of the form. Make sure the revision code on the form matches the one shown on this page.",
|
||||||
|
proc_s2_h: "Complete the Form",
|
||||||
|
proc_s2_p: "Fill in every field completely and carefully. Include all required supporting documents before submitting.",
|
||||||
|
proc_s3_h: "Submit the Document",
|
||||||
|
proc_s3_p: "Send the form along with the supporting documents via the official email or the department admin's WhatsApp. Complete submissions are processed immediately by the department admin.",
|
||||||
|
|
||||||
|
// Help Center
|
||||||
|
help_title: "Need Further Assistance?",
|
||||||
|
help_desc: "Our admin team is ready to help you through the official communication channels below.",
|
||||||
|
help_email_title: "Official Correspondence (Email)",
|
||||||
|
help_email_desc: "Use this for requests of non-public special forms or other formal administration.",
|
||||||
|
help_email_c1: "Response within 24 Hours",
|
||||||
|
help_email_c2: "Formal Service",
|
||||||
|
help_email_btn: "CONTACT ADMIN",
|
||||||
|
help_wa_title: "Quick Support (WhatsApp)",
|
||||||
|
help_wa_desc: "Quick consultation on bureaucratic flow or reporting access issues with repository files.",
|
||||||
|
help_wa_c1: "Instant Response in Work Hours",
|
||||||
|
help_wa_c2: "Technical Consultation",
|
||||||
|
help_wa_btn: "CHAT ON WHATSAPP",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
function currentLang() {
|
||||||
|
if (typeof window.getLang === 'function') {
|
||||||
|
const l = window.getLang();
|
||||||
|
if (l === 'id' || l === 'en') return l;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const saved = localStorage.getItem('if_untan_lang');
|
||||||
|
if (saved === 'id' || saved === 'en') return saved;
|
||||||
|
} catch (e) { /* localStorage unavailable */ }
|
||||||
|
const nav = (navigator.language || 'id').toLowerCase();
|
||||||
|
return nav.startsWith('en') ? 'en' : 'id';
|
||||||
|
}
|
||||||
|
|
||||||
|
function tpT(key) {
|
||||||
|
const lang = currentLang();
|
||||||
|
return (dict[lang] && dict[lang][key]) || dict.id[key] || null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyTp(root) {
|
||||||
|
const scope = root || document;
|
||||||
|
|
||||||
|
scope.querySelectorAll('[data-tp-i18n]').forEach(el => {
|
||||||
|
const val = tpT(el.getAttribute('data-tp-i18n'));
|
||||||
|
if (val != null) el.textContent = val;
|
||||||
|
});
|
||||||
|
|
||||||
|
scope.querySelectorAll('[data-tp-i18n-placeholder]').forEach(el => {
|
||||||
|
const val = tpT(el.getAttribute('data-tp-i18n-placeholder'));
|
||||||
|
if (val != null) el.placeholder = val;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Expose for main.js (dynamic cards/states).
|
||||||
|
window.tpT = tpT;
|
||||||
|
window.applyTp = applyTp;
|
||||||
|
|
||||||
|
// Re-translate when the shared toggle switches language.
|
||||||
|
document.addEventListener('languageChanged', () => applyTp());
|
||||||
|
|
||||||
|
// Initial pass (runs after localization.js's own DOMContentLoaded handler,
|
||||||
|
// so getLang() already reflects the resolved language).
|
||||||
|
document.addEventListener('DOMContentLoaded', () => applyTp());
|
||||||
|
})();
|
||||||
@@ -0,0 +1,775 @@
|
|||||||
|
/**
|
||||||
|
* Main JavaScript — Informatika UNTAN 2026
|
||||||
|
* OOP modules:
|
||||||
|
* DirectusClient — API base + URL builders + fetch
|
||||||
|
* FileDownloader — blob-based force-download with fallback
|
||||||
|
* DocumentFormatter — pure formatting / mapping / escaping utilities
|
||||||
|
* DocumentCardView — renders a single card and binds its actions
|
||||||
|
* DocumentRepository — controller for the repository section
|
||||||
|
* CounterAnimation — scroll-triggered number animation
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* ===========================================
|
||||||
|
DirectusClient
|
||||||
|
=========================================== */
|
||||||
|
class DirectusClient {
|
||||||
|
static API_BASE = 'https://api.ifuntanhub.dev';
|
||||||
|
|
||||||
|
static itemsUrl(collection) {
|
||||||
|
return `${this.API_BASE}/items/${collection}?limit=-1`;
|
||||||
|
}
|
||||||
|
|
||||||
|
static assetUrl(uuid) {
|
||||||
|
return `${this.API_BASE}/assets/${uuid}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
static viewerUrl(item) {
|
||||||
|
const type = DocumentFormatter.fileTypeFromName(item.nama_berkas);
|
||||||
|
const params = new URLSearchParams({
|
||||||
|
id: item.file,
|
||||||
|
name: item.nama_berkas,
|
||||||
|
type,
|
||||||
|
});
|
||||||
|
return `viewer.html?${params.toString()}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
static async fetchPublished(collection) {
|
||||||
|
const response = await fetch(this.itemsUrl(collection));
|
||||||
|
if (!response.ok) throw new Error(`Gagal memuat koleksi ${collection}.`);
|
||||||
|
const body = await response.json();
|
||||||
|
const items = body.data || [];
|
||||||
|
return items.filter(item => item.status === 'published');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===========================================
|
||||||
|
FileDownloader
|
||||||
|
=========================================== */
|
||||||
|
class FileDownloader {
|
||||||
|
static async download(url, filename) {
|
||||||
|
try {
|
||||||
|
const response = await fetch(url);
|
||||||
|
if (!response.ok) throw new Error('Gagal mengunduh berkas.');
|
||||||
|
const blob = await response.blob();
|
||||||
|
const blobUrl = URL.createObjectURL(blob);
|
||||||
|
this._click(blobUrl, filename);
|
||||||
|
URL.revokeObjectURL(blobUrl);
|
||||||
|
} catch (err) {
|
||||||
|
console.warn('Unduh via Blob gagal, beralih ke parameter Directus:', err);
|
||||||
|
const fallbackUrl = url.includes('?') ? `${url}&download` : `${url}?download`;
|
||||||
|
this._click(fallbackUrl, filename);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static _click(href, filename) {
|
||||||
|
const a = document.createElement('a');
|
||||||
|
a.style.display = 'none';
|
||||||
|
a.href = href;
|
||||||
|
a.download = filename;
|
||||||
|
document.body.appendChild(a);
|
||||||
|
a.click();
|
||||||
|
document.body.removeChild(a);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===========================================
|
||||||
|
DocumentFormatter
|
||||||
|
=========================================== */
|
||||||
|
class DocumentFormatter {
|
||||||
|
static MONTHS_ID = ['Jan', 'Feb', 'Mar', 'Apr', 'Mei', 'Jun', 'Jul', 'Agu', 'Sep', 'Okt', 'Nov', 'Des'];
|
||||||
|
|
||||||
|
static formatDate(iso) {
|
||||||
|
if (!iso) return '';
|
||||||
|
const date = new Date(iso);
|
||||||
|
if (Number.isNaN(date.getTime())) return '';
|
||||||
|
const day = String(date.getDate()).padStart(2, '0');
|
||||||
|
const month = this.MONTHS_ID[date.getMonth()];
|
||||||
|
return `${day} ${month} ${date.getFullYear()}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Map a document's parsed tags to one of the fixed UI categories.
|
||||||
|
* Uses the tags array for precise matching instead of raw string search.
|
||||||
|
*/
|
||||||
|
static CATEGORY_RULES = [
|
||||||
|
{ key: 'skripsi', match: ['skripsi', 'tugas akhir', 'ta'] },
|
||||||
|
{ key: 'akademik', match: ['akademik', 'kalender', 'kaldik'] },
|
||||||
|
{ key: 'praktik', match: ['praktik', 'praktek', 'kerja praktik', 'kp'] },
|
||||||
|
];
|
||||||
|
|
||||||
|
static categoryFromTags(tags) {
|
||||||
|
if (!tags || tags.length === 0) return 'umum';
|
||||||
|
const lower = tags.map(t => t.toLowerCase());
|
||||||
|
for (const rule of this.CATEGORY_RULES) {
|
||||||
|
if (rule.match.some(m => lower.includes(m))) return rule.key;
|
||||||
|
}
|
||||||
|
return 'umum';
|
||||||
|
}
|
||||||
|
|
||||||
|
static fileTypeFromName(name) {
|
||||||
|
const lower = (name || '').toLowerCase();
|
||||||
|
if (lower.endsWith('.docx') || lower.endsWith('.doc')) return 'docx';
|
||||||
|
return 'pdf';
|
||||||
|
}
|
||||||
|
|
||||||
|
static downloadFilename(name, type) {
|
||||||
|
const ext = `.${type}`;
|
||||||
|
return name.toLowerCase().endsWith(ext) ? name : `${name}${ext}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Escape API-supplied strings before splicing into innerHTML templates.
|
||||||
|
static escapeHtml(str) {
|
||||||
|
if (str == null) return '';
|
||||||
|
return String(str)
|
||||||
|
.replace(/&/g, '&')
|
||||||
|
.replace(/</g, '<')
|
||||||
|
.replace(/>/g, '>')
|
||||||
|
.replace(/"/g, '"')
|
||||||
|
.replace(/'/g, ''');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Escape values destined for HTML attribute slots (href, etc.).
|
||||||
|
static escapeAttr(str) {
|
||||||
|
return this.escapeHtml(str);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse the `tag` field from the API.
|
||||||
|
* Handles two formats:
|
||||||
|
* 1. Comma-separated string: "akademik,panduan,2024"
|
||||||
|
* 2. JSON array string: '["panduan","proposal"]'
|
||||||
|
* Returns an array of trimmed, non-empty tag strings.
|
||||||
|
*/
|
||||||
|
static parseTags(raw) {
|
||||||
|
if (!raw) return [];
|
||||||
|
const str = String(raw).trim();
|
||||||
|
if (!str) return [];
|
||||||
|
|
||||||
|
// Attempt JSON parse first (handles '["a","b"]' format).
|
||||||
|
if (str.startsWith('[')) {
|
||||||
|
try {
|
||||||
|
const arr = JSON.parse(str);
|
||||||
|
if (Array.isArray(arr)) {
|
||||||
|
return arr.map(t => String(t).trim()).filter(Boolean);
|
||||||
|
}
|
||||||
|
} catch { /* fall through to comma-split */ }
|
||||||
|
}
|
||||||
|
|
||||||
|
// Comma-separated fallback.
|
||||||
|
return str.split(',').map(t => t.trim()).filter(Boolean);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===========================================
|
||||||
|
DocumentCardView
|
||||||
|
=========================================== */
|
||||||
|
class DocumentCardView {
|
||||||
|
constructor(item) {
|
||||||
|
this.item = item;
|
||||||
|
this.title = item.nama_berkas;
|
||||||
|
this.tags = DocumentFormatter.parseTags(item.tag);
|
||||||
|
this.category = DocumentFormatter.categoryFromTags(this.tags);
|
||||||
|
this.fileType = DocumentFormatter.fileTypeFromName(this.title);
|
||||||
|
this.assetUrl = DirectusClient.assetUrl(item.file);
|
||||||
|
this.viewerUrl = DirectusClient.viewerUrl(item);
|
||||||
|
this.downloadName = DocumentFormatter.downloadFilename(this.title, this.fileType);
|
||||||
|
this.dateIso = item.tanggal_upload || '';
|
||||||
|
this.dateKey = this.dateIso ? this.dateIso.substring(0, 10) : '';
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const card = document.createElement('article');
|
||||||
|
card.className = 'document-card';
|
||||||
|
card.dataset.date = this.dateKey;
|
||||||
|
|
||||||
|
const typeUpper = this.fileType.toUpperCase();
|
||||||
|
const typeClass = this.fileType === 'pdf' ? 'doc-type-pdf' : 'doc-type-docx';
|
||||||
|
const iconClass = this.fileType === 'pdf' ? 'fas fa-file-pdf' : 'fas fa-file-word';
|
||||||
|
const formattedDate = DocumentFormatter.formatDate(this.dateIso);
|
||||||
|
|
||||||
|
const safeTitle = DocumentFormatter.escapeHtml(this.title);
|
||||||
|
const safeViewerUrl = DocumentFormatter.escapeAttr(this.viewerUrl);
|
||||||
|
const safeAssetUrl = DocumentFormatter.escapeAttr(this.assetUrl);
|
||||||
|
|
||||||
|
// Build tag badges HTML.
|
||||||
|
const tagBadgesHtml = this.tags.length
|
||||||
|
? `<div class="doc-tags"><i class="fas fa-tags doc-tags-icon"></i>${this.tags.map(t => `<span class="doc-tag">${DocumentFormatter.escapeHtml(t)}</span>`).join('')}</div>`
|
||||||
|
: '';
|
||||||
|
|
||||||
|
card.innerHTML = `
|
||||||
|
<div class="doc-type-icon ${typeClass}"><i class="${iconClass}"></i></div>
|
||||||
|
<h3 class="doc-title">${safeTitle}</h3>
|
||||||
|
<div class="doc-meta">
|
||||||
|
<span class="doc-badge">${typeUpper}</span>
|
||||||
|
<span class="doc-badge date"><i class="far fa-calendar-alt"></i> ${formattedDate}</span>
|
||||||
|
</div>
|
||||||
|
${tagBadgesHtml}
|
||||||
|
<div class="doc-actions">
|
||||||
|
<a href="${safeViewerUrl}" class="btn-action-primary" target="_blank" rel="noopener"><span data-tp-i18n="card_open">Buka Berkas</span> <i class="fas fa-external-link-alt"></i></a>
|
||||||
|
<a href="${safeAssetUrl}" class="btn-action-outline"><span data-tp-i18n="card_download">Unduh Berkas</span> <i class="fas fa-download"></i></a>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
card.querySelector('.btn-action-outline').addEventListener('click', (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
FileDownloader.download(this.assetUrl, this.downloadName);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Prefetch the asset when the user hovers over the card so the
|
||||||
|
// browser cache already has the file by the time the viewer loads.
|
||||||
|
let prefetched = false;
|
||||||
|
card.addEventListener('mouseenter', () => {
|
||||||
|
if (prefetched) return;
|
||||||
|
prefetched = true;
|
||||||
|
const link = document.createElement('link');
|
||||||
|
link.rel = 'prefetch';
|
||||||
|
link.as = 'fetch';
|
||||||
|
link.crossOrigin = 'anonymous';
|
||||||
|
link.href = this.assetUrl;
|
||||||
|
document.head.appendChild(link);
|
||||||
|
}, { once: true });
|
||||||
|
|
||||||
|
return card;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===========================================
|
||||||
|
CustomDropdown
|
||||||
|
=========================================== */
|
||||||
|
class CustomDropdown {
|
||||||
|
constructor({ container, options, defaultValue, onChange, prefixIcon }) {
|
||||||
|
this.container = container;
|
||||||
|
this.options = options; // Array of { value, text, i18nKey }
|
||||||
|
this.onChange = onChange;
|
||||||
|
this.currentValue = defaultValue || options[0]?.value;
|
||||||
|
this.prefixIcon = prefixIcon; // optional class name like 'fas fa-sort'
|
||||||
|
this.isOpen = false;
|
||||||
|
|
||||||
|
this.elements = {};
|
||||||
|
this._build();
|
||||||
|
this._bindEvents();
|
||||||
|
}
|
||||||
|
|
||||||
|
_build() {
|
||||||
|
this.container.innerHTML = '';
|
||||||
|
this.container.classList.add('custom-dropdown');
|
||||||
|
|
||||||
|
// Trigger Button
|
||||||
|
const trigger = document.createElement('button');
|
||||||
|
trigger.className = 'custom-dropdown-trigger';
|
||||||
|
trigger.setAttribute('aria-haspopup', 'listbox');
|
||||||
|
trigger.setAttribute('aria-expanded', 'false');
|
||||||
|
trigger.type = 'button';
|
||||||
|
|
||||||
|
const triggerLeft = document.createElement('div');
|
||||||
|
triggerLeft.className = 'trigger-left-content';
|
||||||
|
triggerLeft.style.display = 'flex';
|
||||||
|
triggerLeft.style.alignItems = 'center';
|
||||||
|
triggerLeft.style.gap = '10px';
|
||||||
|
|
||||||
|
if (this.prefixIcon) {
|
||||||
|
const icon = document.createElement('i');
|
||||||
|
icon.className = this.prefixIcon;
|
||||||
|
triggerLeft.appendChild(icon);
|
||||||
|
}
|
||||||
|
|
||||||
|
const labelSpan = document.createElement('span');
|
||||||
|
labelSpan.className = 'trigger-label';
|
||||||
|
|
||||||
|
// Find default text
|
||||||
|
const defaultOpt = this.options.find(opt => opt.value === this.currentValue) || this.options[0];
|
||||||
|
labelSpan.textContent = defaultOpt ? defaultOpt.text : '';
|
||||||
|
if (defaultOpt?.i18nKey) {
|
||||||
|
labelSpan.setAttribute('data-tp-i18n', defaultOpt.i18nKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
triggerLeft.appendChild(labelSpan);
|
||||||
|
trigger.appendChild(triggerLeft);
|
||||||
|
|
||||||
|
const arrowIcon = document.createElement('i');
|
||||||
|
arrowIcon.className = 'fas fa-chevron-down trigger-arrow';
|
||||||
|
|
||||||
|
trigger.appendChild(arrowIcon);
|
||||||
|
this.container.appendChild(trigger);
|
||||||
|
|
||||||
|
// Menu wrapper
|
||||||
|
const menu = document.createElement('div');
|
||||||
|
menu.className = 'custom-dropdown-menu';
|
||||||
|
menu.setAttribute('role', 'listbox');
|
||||||
|
|
||||||
|
// Populate options
|
||||||
|
this.options.forEach(opt => {
|
||||||
|
const item = document.createElement('div');
|
||||||
|
item.className = 'custom-dropdown-item' + (opt.value === this.currentValue ? ' active' : '');
|
||||||
|
item.setAttribute('role', 'option');
|
||||||
|
item.setAttribute('data-value', opt.value);
|
||||||
|
|
||||||
|
const itemText = document.createElement('span');
|
||||||
|
itemText.textContent = opt.text;
|
||||||
|
if (opt.i18nKey) {
|
||||||
|
itemText.setAttribute('data-tp-i18n', opt.i18nKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
item.appendChild(itemText);
|
||||||
|
menu.appendChild(item);
|
||||||
|
});
|
||||||
|
|
||||||
|
this.container.appendChild(menu);
|
||||||
|
|
||||||
|
this.elements = { trigger, labelSpan, menu };
|
||||||
|
}
|
||||||
|
|
||||||
|
_bindEvents() {
|
||||||
|
// Toggle dropdown on trigger click
|
||||||
|
this.elements.trigger.addEventListener('click', (e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
this.toggle();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Handle item selection
|
||||||
|
this.elements.menu.addEventListener('click', (e) => {
|
||||||
|
const item = e.target.closest('.custom-dropdown-item');
|
||||||
|
if (!item) return;
|
||||||
|
|
||||||
|
const value = item.getAttribute('data-value');
|
||||||
|
this.select(value);
|
||||||
|
this.close();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Close when clicking outside
|
||||||
|
document.addEventListener('click', () => {
|
||||||
|
this.close();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Close on escape key
|
||||||
|
document.addEventListener('keydown', (e) => {
|
||||||
|
if (e.key === 'Escape') this.close();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
toggle() {
|
||||||
|
if (this.isOpen) {
|
||||||
|
this.close();
|
||||||
|
} else {
|
||||||
|
this.open();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
open() {
|
||||||
|
// Close other custom dropdowns
|
||||||
|
document.querySelectorAll('.custom-dropdown.active').forEach(dropdown => {
|
||||||
|
if (dropdown !== this.container) {
|
||||||
|
dropdown.classList.remove('active');
|
||||||
|
dropdown.querySelector('.custom-dropdown-trigger')?.setAttribute('aria-expanded', 'false');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.isOpen = true;
|
||||||
|
this.container.classList.add('active');
|
||||||
|
this.elements.trigger.setAttribute('aria-expanded', 'true');
|
||||||
|
}
|
||||||
|
|
||||||
|
close() {
|
||||||
|
this.isOpen = false;
|
||||||
|
this.container.classList.remove('active');
|
||||||
|
this.elements.trigger.setAttribute('aria-expanded', 'false');
|
||||||
|
}
|
||||||
|
|
||||||
|
select(value) {
|
||||||
|
if (value === this.currentValue) return;
|
||||||
|
|
||||||
|
this.currentValue = value;
|
||||||
|
|
||||||
|
// Update active class in menu items
|
||||||
|
this.elements.menu.querySelectorAll('.custom-dropdown-item').forEach(item => {
|
||||||
|
const isCurrent = item.getAttribute('data-value') === value;
|
||||||
|
item.classList.toggle('active', isCurrent);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Update trigger label
|
||||||
|
const selectedOpt = this.options.find(opt => opt.value === value);
|
||||||
|
if (selectedOpt) {
|
||||||
|
this.elements.labelSpan.textContent = selectedOpt.text;
|
||||||
|
if (selectedOpt.i18nKey) {
|
||||||
|
this.elements.labelSpan.setAttribute('data-tp-i18n', selectedOpt.i18nKey);
|
||||||
|
} else {
|
||||||
|
this.elements.labelSpan.removeAttribute('data-tp-i18n');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Trigger callback
|
||||||
|
if (typeof this.onChange === 'function') {
|
||||||
|
this.onChange(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setValue(value) {
|
||||||
|
this.select(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
getValue() {
|
||||||
|
return this.currentValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===========================================
|
||||||
|
DocumentRepository
|
||||||
|
=========================================== */
|
||||||
|
class DocumentRepository {
|
||||||
|
constructor() {
|
||||||
|
this.gridContainer = document.getElementById('document-grid');
|
||||||
|
this.filterWrapper = document.getElementById('tag-filter-wrapper');
|
||||||
|
this.mobileFilterWrapper = document.getElementById('mobile-tag-filter-wrapper');
|
||||||
|
this.searchInput = document.getElementById('doc-search');
|
||||||
|
this.dateStartInput = document.getElementById('date-start');
|
||||||
|
this.dateEndInput = document.getElementById('date-end');
|
||||||
|
this.sortFilterWrapper = document.getElementById('sort-filter-wrapper');
|
||||||
|
|
||||||
|
// Hero stat values (filled from real API data after fetch).
|
||||||
|
this.statActiveEl = document.getElementById('stat-active-value');
|
||||||
|
this.statUpdatesEl = document.getElementById('stat-updates-value');
|
||||||
|
|
||||||
|
this.cards = [];
|
||||||
|
this.state = {
|
||||||
|
filter: 'all', // 'all' or a specific tag string
|
||||||
|
search: '',
|
||||||
|
startDate: '',
|
||||||
|
endDate: '',
|
||||||
|
sort: 'newest',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async init() {
|
||||||
|
if (!this.gridContainer) return;
|
||||||
|
this._bindControls();
|
||||||
|
try {
|
||||||
|
const items = await DirectusClient.fetchPublished('berkas');
|
||||||
|
this._renderCards(items);
|
||||||
|
this._buildCategoryFilters(items);
|
||||||
|
this._renderStats(items);
|
||||||
|
this._applySort();
|
||||||
|
this._applyFilters();
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
this._renderError();
|
||||||
|
this._renderStatsError();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Days within which an upload counts as a "recent update".
|
||||||
|
static RECENT_DAYS = 30;
|
||||||
|
|
||||||
|
// Populate the hero stats from real data:
|
||||||
|
// Dokumen Aktif → number of published documents
|
||||||
|
// Pembaruan Terbaru → uploads within the last RECENT_DAYS days
|
||||||
|
_renderStats(items) {
|
||||||
|
const active = items.length;
|
||||||
|
|
||||||
|
const windowMs = DocumentRepository.RECENT_DAYS * 24 * 60 * 60 * 1000;
|
||||||
|
const now = Date.now();
|
||||||
|
const recent = items.filter(item => {
|
||||||
|
if (!item.tanggal_upload) return false;
|
||||||
|
const t = new Date(item.tanggal_upload).getTime();
|
||||||
|
return !Number.isNaN(t) && now - t <= windowMs;
|
||||||
|
}).length;
|
||||||
|
|
||||||
|
CounterAnimation.animateValue(this.statActiveEl, active);
|
||||||
|
CounterAnimation.animateValue(this.statUpdatesEl, recent);
|
||||||
|
}
|
||||||
|
|
||||||
|
_renderStatsError() {
|
||||||
|
if (this.statActiveEl) this.statActiveEl.textContent = '—';
|
||||||
|
if (this.statUpdatesEl) this.statUpdatesEl.textContent = '—';
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Fixed category definitions for the sidebar filter.
|
||||||
|
* Each has a key, an i18n key for bilingual labels, and a fallback label.
|
||||||
|
*/
|
||||||
|
static CATEGORIES = [
|
||||||
|
{ key: 'all', i18n: 'filter_all', label: 'Semua Berkas' },
|
||||||
|
{ key: 'skripsi', i18n: 'filter_skripsi', label: 'Panduan Skripsi' },
|
||||||
|
{ key: 'akademik', i18n: 'filter_akademik', label: 'Kalender Akademik' },
|
||||||
|
{ key: 'praktik', i18n: 'filter_praktik', label: 'Kerja Praktik' },
|
||||||
|
{ key: 'umum', i18n: 'filter_umum', label: 'Formulir Umum' },
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build category filter controls:
|
||||||
|
* – Desktop: sidebar buttons (hidden on mobile via CSS)
|
||||||
|
* – Mobile: a <select> dropdown (hidden on desktop via CSS)
|
||||||
|
* Both stay in sync through a shared _setFilter() helper.
|
||||||
|
*/
|
||||||
|
_buildCategoryFilters(items) {
|
||||||
|
if (!this.filterWrapper) return;
|
||||||
|
|
||||||
|
// Clear skeleton placeholder.
|
||||||
|
this.filterWrapper.innerHTML = '';
|
||||||
|
|
||||||
|
// ── Desktop buttons ──
|
||||||
|
const btnGroup = document.createElement('div');
|
||||||
|
btnGroup.className = 'filter-btn-group';
|
||||||
|
|
||||||
|
DocumentRepository.CATEGORIES.forEach(({ key, i18n, label }) => {
|
||||||
|
const isActive = key === 'all';
|
||||||
|
const btn = document.createElement('button');
|
||||||
|
btn.className = 'doc-filter-btn' + (isActive ? ' active' : '');
|
||||||
|
btn.dataset.filter = key;
|
||||||
|
btn.textContent = (window.tpT?.(i18n)) || label;
|
||||||
|
btn.setAttribute('data-tp-i18n', i18n);
|
||||||
|
|
||||||
|
btn.addEventListener('click', () => this._setFilter(key));
|
||||||
|
btnGroup.appendChild(btn);
|
||||||
|
});
|
||||||
|
|
||||||
|
this.filterWrapper.appendChild(btnGroup);
|
||||||
|
|
||||||
|
// ── Mobile Custom UI Dropdown ──
|
||||||
|
if (this.mobileFilterWrapper) {
|
||||||
|
const dropdownOptions = DocumentRepository.CATEGORIES.map(({ key, i18n, label }) => ({
|
||||||
|
value: key,
|
||||||
|
i18nKey: i18n,
|
||||||
|
text: (window.tpT?.(i18n)) || label
|
||||||
|
}));
|
||||||
|
|
||||||
|
this._mobileDropdown = new CustomDropdown({
|
||||||
|
container: this.mobileFilterWrapper,
|
||||||
|
options: dropdownOptions,
|
||||||
|
defaultValue: this.state.filter,
|
||||||
|
onChange: (value) => this._setFilter(value)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store refs for syncing.
|
||||||
|
this._filterBtnGroup = btnGroup;
|
||||||
|
|
||||||
|
// Re-translate if i18n is active.
|
||||||
|
window.applyTp?.(this.filterWrapper);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shared filter setter — keeps buttons + dropdown in sync.
|
||||||
|
*/
|
||||||
|
_setFilter(key) {
|
||||||
|
this.state.filter = key;
|
||||||
|
|
||||||
|
// Sync buttons.
|
||||||
|
if (this._filterBtnGroup) {
|
||||||
|
this._filterBtnGroup.querySelectorAll('.doc-filter-btn')
|
||||||
|
.forEach(b => b.classList.toggle('active', b.dataset.filter === key));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sync custom dropdown.
|
||||||
|
if (this._mobileDropdown && this._mobileDropdown.getValue() !== key) {
|
||||||
|
this._mobileDropdown.setValue(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
this._applyFilters();
|
||||||
|
}
|
||||||
|
|
||||||
|
_bindControls() {
|
||||||
|
// Tag filter buttons are built dynamically after API fetch
|
||||||
|
// (see _buildTagFilters). Only bind search/date/sort here.
|
||||||
|
|
||||||
|
this.searchInput?.addEventListener('input', (e) => {
|
||||||
|
this.state.search = e.target.value.toLowerCase().trim();
|
||||||
|
this._applyFilters();
|
||||||
|
});
|
||||||
|
|
||||||
|
this.dateStartInput?.addEventListener('change', (e) => {
|
||||||
|
this.state.startDate = e.target.value;
|
||||||
|
this._applyFilters();
|
||||||
|
});
|
||||||
|
|
||||||
|
this.dateEndInput?.addEventListener('change', (e) => {
|
||||||
|
this.state.endDate = e.target.value;
|
||||||
|
this._applyFilters();
|
||||||
|
});
|
||||||
|
|
||||||
|
if (this.sortFilterWrapper) {
|
||||||
|
const sortOptions = [
|
||||||
|
{ value: 'newest', i18nKey: 'sort_newest', text: (window.tpT?.('sort_newest')) || 'Terbaru' },
|
||||||
|
{ value: 'oldest', i18nKey: 'sort_oldest', text: (window.tpT?.('sort_oldest')) || 'Terlama' },
|
||||||
|
{ value: 'az', i18nKey: 'sort_az', text: (window.tpT?.('sort_az')) || 'Nama A-Z' }
|
||||||
|
];
|
||||||
|
|
||||||
|
this._sortDropdown = new CustomDropdown({
|
||||||
|
container: this.sortFilterWrapper,
|
||||||
|
options: sortOptions,
|
||||||
|
defaultValue: this.state.sort,
|
||||||
|
prefixIcon: 'fas fa-sort-amount-down',
|
||||||
|
onChange: (value) => {
|
||||||
|
this.state.sort = value;
|
||||||
|
this._applySort();
|
||||||
|
this._applyFilters();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
window.applyTp?.(this.sortFilterWrapper);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_renderCards(items) {
|
||||||
|
this.gridContainer.innerHTML = '';
|
||||||
|
|
||||||
|
if (items.length === 0) {
|
||||||
|
this._renderState('repo-empty', 'fas fa-folder-open', '#cbd5e1', '#64748b',
|
||||||
|
'state_empty', 'Belum ada dokumen yang tersedia di repositori.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.cards = items.map(item => {
|
||||||
|
const view = new DocumentCardView(item);
|
||||||
|
const element = view.render();
|
||||||
|
this.gridContainer.appendChild(element);
|
||||||
|
return { view, element };
|
||||||
|
});
|
||||||
|
|
||||||
|
// Translate the freshly-rendered card labels (Buka/Unduh Berkas).
|
||||||
|
window.applyTp?.(this.gridContainer);
|
||||||
|
}
|
||||||
|
|
||||||
|
_renderError() {
|
||||||
|
this._renderState('repo-error', 'fas fa-exclamation-triangle', '#f87171', '#ef4444',
|
||||||
|
'state_error', 'Gagal memuat berkas. Silakan periksa koneksi internet Anda atau coba lagi nanti.');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build the empty/error placeholder via safe DOM APIs (no innerHTML).
|
||||||
|
// `i18nKey` lets the Tapops i18n layer re-translate the message on toggle.
|
||||||
|
_renderState(className, iconClass, iconColor, textColor, i18nKey, message) {
|
||||||
|
this.cards = [];
|
||||||
|
this.gridContainer.innerHTML = '';
|
||||||
|
|
||||||
|
const wrapper = document.createElement('div');
|
||||||
|
wrapper.className = className;
|
||||||
|
Object.assign(wrapper.style, {
|
||||||
|
gridColumn: '1 / -1',
|
||||||
|
textAlign: 'center',
|
||||||
|
padding: '60px 20px',
|
||||||
|
color: textColor,
|
||||||
|
fontWeight: '700',
|
||||||
|
fontSize: '1rem',
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: '15px',
|
||||||
|
});
|
||||||
|
|
||||||
|
const icon = document.createElement('i');
|
||||||
|
icon.className = iconClass;
|
||||||
|
Object.assign(icon.style, { fontSize: '3rem', color: iconColor });
|
||||||
|
|
||||||
|
const span = document.createElement('span');
|
||||||
|
if (i18nKey) span.setAttribute('data-tp-i18n', i18nKey);
|
||||||
|
span.textContent = (window.tpT?.(i18nKey)) || message;
|
||||||
|
|
||||||
|
wrapper.appendChild(icon);
|
||||||
|
wrapper.appendChild(span);
|
||||||
|
this.gridContainer.appendChild(wrapper);
|
||||||
|
}
|
||||||
|
|
||||||
|
_applyFilters() {
|
||||||
|
this.cards.forEach(({ view, element }) => {
|
||||||
|
if (this._cardMatches(view)) {
|
||||||
|
element.style.display = 'flex';
|
||||||
|
setTimeout(() => {
|
||||||
|
element.style.opacity = '1';
|
||||||
|
element.style.transform = 'translateY(0)';
|
||||||
|
}, 10);
|
||||||
|
} else {
|
||||||
|
element.style.display = 'none';
|
||||||
|
element.style.opacity = '0';
|
||||||
|
element.style.transform = 'translateY(10px)';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
_cardMatches(view) {
|
||||||
|
const { filter, search } = this.state;
|
||||||
|
const matchesCategory = filter === 'all' || view.category === filter;
|
||||||
|
const matchesTitle = view.title.toLowerCase().includes(search);
|
||||||
|
const matchesTags = search
|
||||||
|
? view.tags.some(t => t.toLowerCase().includes(search))
|
||||||
|
: false;
|
||||||
|
const matchesSearch = matchesTitle || matchesTags;
|
||||||
|
return matchesCategory && matchesSearch && this._dateMatches(view.dateKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
_dateMatches(dateKey) {
|
||||||
|
const { startDate, endDate } = this.state;
|
||||||
|
if (!dateKey) return !startDate && !endDate;
|
||||||
|
const cardDate = new Date(dateKey);
|
||||||
|
if (startDate && cardDate < new Date(startDate)) return false;
|
||||||
|
if (endDate) {
|
||||||
|
const end = new Date(endDate);
|
||||||
|
end.setHours(23, 59, 59, 999);
|
||||||
|
if (cardDate > end) return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
_applySort() {
|
||||||
|
const { sort } = this.state;
|
||||||
|
this.cards.sort((a, b) => {
|
||||||
|
if (sort === 'newest') return b.view.dateKey.localeCompare(a.view.dateKey);
|
||||||
|
if (sort === 'oldest') return a.view.dateKey.localeCompare(b.view.dateKey);
|
||||||
|
if (sort === 'az') return a.view.title.toLowerCase().localeCompare(b.view.title.toLowerCase());
|
||||||
|
return 0;
|
||||||
|
});
|
||||||
|
this.cards.forEach(({ element }) => this.gridContainer.appendChild(element));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===========================================
|
||||||
|
CounterAnimation
|
||||||
|
=========================================== */
|
||||||
|
class CounterAnimation {
|
||||||
|
static DURATION_MS = 2000;
|
||||||
|
|
||||||
|
init() {
|
||||||
|
const counters = document.querySelectorAll('.counter-value[data-target]');
|
||||||
|
if (counters.length === 0) return;
|
||||||
|
|
||||||
|
if ('IntersectionObserver' in window) {
|
||||||
|
const observer = new IntersectionObserver((entries) => {
|
||||||
|
entries.forEach(entry => {
|
||||||
|
if (entry.isIntersecting) {
|
||||||
|
this._animate(entry.target);
|
||||||
|
observer.unobserve(entry.target);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, { threshold: 0.2 });
|
||||||
|
counters.forEach(c => observer.observe(c));
|
||||||
|
} else {
|
||||||
|
counters.forEach(c => this._animate(c));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_animate(el) {
|
||||||
|
const target = Number.parseInt((el.dataset.target || '0').replace(/[^0-9]/g, ''), 10);
|
||||||
|
if (Number.isNaN(target)) return;
|
||||||
|
CounterAnimation.animateValue(el, target, el.dataset.suffix || '');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reusable easing tween from 0 → target. Callable for stats whose
|
||||||
|
// value is only known after an async fetch (no data-target needed).
|
||||||
|
static animateValue(el, target, suffix = '') {
|
||||||
|
if (!el || Number.isNaN(target)) return;
|
||||||
|
const startTime = performance.now();
|
||||||
|
|
||||||
|
const tick = (now) => {
|
||||||
|
const progress = Math.min((now - startTime) / CounterAnimation.DURATION_MS, 1);
|
||||||
|
const eased = 1 - Math.pow(1 - progress, 3);
|
||||||
|
el.textContent = Math.floor(eased * target) + suffix;
|
||||||
|
if (progress < 1) requestAnimationFrame(tick);
|
||||||
|
else el.textContent = target + suffix;
|
||||||
|
};
|
||||||
|
requestAnimationFrame(tick);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===========================================
|
||||||
|
App entry
|
||||||
|
=========================================== */
|
||||||
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
new DocumentRepository().init();
|
||||||
|
});
|
||||||
@@ -0,0 +1,220 @@
|
|||||||
|
/**
|
||||||
|
* Viewer page — renders a single Directus asset in-browser.
|
||||||
|
* PDF → native <iframe>
|
||||||
|
* DOCX → docx-preview library (renders document XML to HTML in-page)
|
||||||
|
*
|
||||||
|
* URL params: ?id={uuid}&name={filename}&type={pdf|docx}
|
||||||
|
*/
|
||||||
|
|
||||||
|
class ViewerApi {
|
||||||
|
static API_BASE = 'https://api.ifuntanhub.dev';
|
||||||
|
|
||||||
|
static assetUrl(uuid) {
|
||||||
|
return `${this.API_BASE}/assets/${uuid}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
static async fetchBlob(url, onProgress) {
|
||||||
|
const response = await fetch(url);
|
||||||
|
if (!response.ok) throw new Error(`Gagal mengambil berkas (HTTP ${response.status}).`);
|
||||||
|
|
||||||
|
// If the response supports streaming and we have a progress callback,
|
||||||
|
// pipe through a progress-tracking reader.
|
||||||
|
const contentLength = response.headers.get('Content-Length');
|
||||||
|
if (onProgress && contentLength && response.body) {
|
||||||
|
const total = parseInt(contentLength, 10);
|
||||||
|
const reader = response.body.getReader();
|
||||||
|
const chunks = [];
|
||||||
|
let received = 0;
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
const { done, value } = await reader.read();
|
||||||
|
if (done) break;
|
||||||
|
chunks.push(value);
|
||||||
|
received += value.length;
|
||||||
|
onProgress(received / total);
|
||||||
|
}
|
||||||
|
return new Blob(chunks);
|
||||||
|
}
|
||||||
|
|
||||||
|
return response.blob();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class FileViewer {
|
||||||
|
constructor() {
|
||||||
|
const params = new URLSearchParams(window.location.search);
|
||||||
|
this.id = params.get('id');
|
||||||
|
this.name = params.get('name') || 'Dokumen';
|
||||||
|
this.type = (params.get('type') || 'pdf').toLowerCase();
|
||||||
|
|
||||||
|
this.titleEl = document.getElementById('viewer-title');
|
||||||
|
this.typeBadgeEl = document.getElementById('viewer-type');
|
||||||
|
this.downloadEl = document.getElementById('viewer-download');
|
||||||
|
this.contentEl = document.getElementById('viewer-content');
|
||||||
|
|
||||||
|
this.assetUrl = this.id ? ViewerApi.assetUrl(this.id) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
async init() {
|
||||||
|
if (!this.id) {
|
||||||
|
this._renderError('Parameter berkas tidak ditemukan di URL.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
document.title = `${this.name} — Pratinjau Dokumen`;
|
||||||
|
this.titleEl.textContent = this.name;
|
||||||
|
this.typeBadgeEl.textContent = this.type.toUpperCase();
|
||||||
|
this._setupDownload();
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (this.type === 'docx') {
|
||||||
|
await this._renderDocx();
|
||||||
|
} else {
|
||||||
|
this._renderPdf();
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
this._renderError('Gagal memuat pratinjau berkas. Silakan unduh berkas untuk membuka secara lokal.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_setupDownload() {
|
||||||
|
const ext = `.${this.type}`;
|
||||||
|
const filename = this.name.toLowerCase().endsWith(ext) ? this.name : `${this.name}${ext}`;
|
||||||
|
|
||||||
|
// Use blob-based download so the browser always triggers a save
|
||||||
|
// dialog rather than navigating to the file (cross-origin `download`
|
||||||
|
// attribute is ignored by browsers).
|
||||||
|
this.downloadEl.removeAttribute('href');
|
||||||
|
this.downloadEl.style.cursor = 'pointer';
|
||||||
|
this.downloadEl.addEventListener('click', async (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
// Visual feedback while downloading.
|
||||||
|
const origHtml = this.downloadEl.innerHTML;
|
||||||
|
this.downloadEl.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Mengunduh…';
|
||||||
|
this.downloadEl.style.pointerEvents = 'none';
|
||||||
|
try {
|
||||||
|
const blob = await ViewerApi.fetchBlob(this.assetUrl);
|
||||||
|
const blobUrl = URL.createObjectURL(blob);
|
||||||
|
const a = document.createElement('a');
|
||||||
|
a.style.display = 'none';
|
||||||
|
a.href = blobUrl;
|
||||||
|
a.download = filename;
|
||||||
|
document.body.appendChild(a);
|
||||||
|
a.click();
|
||||||
|
document.body.removeChild(a);
|
||||||
|
URL.revokeObjectURL(blobUrl);
|
||||||
|
} catch (err) {
|
||||||
|
console.warn('Blob download failed, falling back:', err);
|
||||||
|
// Fallback: open asset URL with ?download param
|
||||||
|
window.open(`${this.assetUrl}?download`, '_blank');
|
||||||
|
} finally {
|
||||||
|
this.downloadEl.innerHTML = origHtml;
|
||||||
|
this.downloadEl.style.pointerEvents = '';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
_renderPdf() {
|
||||||
|
const iframe = document.createElement('iframe');
|
||||||
|
iframe.className = 'viewer-pdf-frame';
|
||||||
|
iframe.title = this.name;
|
||||||
|
iframe.src = this.assetUrl;
|
||||||
|
this.contentEl.replaceChildren(iframe);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lazy-load a script by URL. Returns a promise that resolves on load.
|
||||||
|
*/
|
||||||
|
static _loadScript(src) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const s = document.createElement('script');
|
||||||
|
s.src = src;
|
||||||
|
s.onload = resolve;
|
||||||
|
s.onerror = () => reject(new Error(`Gagal memuat skrip: ${src}`));
|
||||||
|
document.head.appendChild(s);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async _renderDocx() {
|
||||||
|
// Show progress bar immediately.
|
||||||
|
const progressBar = this._showProgress();
|
||||||
|
|
||||||
|
// Load DOCX libraries and fetch the blob in parallel.
|
||||||
|
const [blob] = await Promise.all([
|
||||||
|
ViewerApi.fetchBlob(this.assetUrl, (ratio) => {
|
||||||
|
progressBar.update(ratio);
|
||||||
|
}),
|
||||||
|
this._loadDocxLibraries(),
|
||||||
|
]);
|
||||||
|
progressBar.finish();
|
||||||
|
|
||||||
|
if (typeof docx === 'undefined' || typeof docx.renderAsync !== 'function') {
|
||||||
|
throw new Error('Library docx-preview belum dimuat.');
|
||||||
|
}
|
||||||
|
|
||||||
|
const container = document.createElement('div');
|
||||||
|
container.className = 'viewer-docx-content';
|
||||||
|
this.contentEl.replaceChildren(container);
|
||||||
|
|
||||||
|
await docx.renderAsync(blob, container, null, {
|
||||||
|
inWrapper: true,
|
||||||
|
ignoreWidth: false,
|
||||||
|
ignoreHeight: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async _loadDocxLibraries() {
|
||||||
|
if (typeof docx !== 'undefined') return; // already loaded
|
||||||
|
await FileViewer._loadScript('https://unpkg.com/jszip@3.10.1/dist/jszip.min.js');
|
||||||
|
await FileViewer._loadScript('https://unpkg.com/docx-preview@0.3.5/dist/docx-preview.js');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show a thin progress bar at the top of the content area.
|
||||||
|
* Returns { update(ratio), finish() }.
|
||||||
|
*/
|
||||||
|
_showProgress() {
|
||||||
|
const bar = document.createElement('div');
|
||||||
|
bar.className = 'viewer-progress-bar';
|
||||||
|
const fill = document.createElement('div');
|
||||||
|
fill.className = 'viewer-progress-fill';
|
||||||
|
bar.appendChild(fill);
|
||||||
|
|
||||||
|
// Insert above the loading spinner.
|
||||||
|
this.contentEl.prepend(bar);
|
||||||
|
|
||||||
|
return {
|
||||||
|
update(ratio) {
|
||||||
|
fill.style.width = `${Math.min(Math.round(ratio * 100), 100)}%`;
|
||||||
|
},
|
||||||
|
finish() {
|
||||||
|
fill.style.width = '100%';
|
||||||
|
setTimeout(() => bar.remove(), 300);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
_renderError(message) {
|
||||||
|
const wrapper = document.createElement('div');
|
||||||
|
wrapper.className = 'viewer-error';
|
||||||
|
|
||||||
|
const icon = document.createElement('i');
|
||||||
|
icon.className = 'fas fa-exclamation-triangle';
|
||||||
|
|
||||||
|
const span = document.createElement('span');
|
||||||
|
span.textContent = message;
|
||||||
|
|
||||||
|
const back = document.createElement('a');
|
||||||
|
back.href = 'index.html#repository';
|
||||||
|
back.className = 'btn-back-mini';
|
||||||
|
back.textContent = 'Kembali ke Repositori';
|
||||||
|
|
||||||
|
wrapper.append(icon, span, back);
|
||||||
|
this.contentEl.replaceChildren(wrapper);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
new FileViewer().init();
|
||||||
|
});
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="id">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Pratinjau Dokumen — TAPOPS | Informatika UNTAN</title>
|
||||||
|
|
||||||
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||||
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||||
|
<link rel="preconnect" href="https://api.ifuntanhub.dev" crossorigin>
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Roboto:ital,wght@0,300;0,400;0,700;0,900;1,300;1,400;1,700&display=swap" rel="stylesheet">
|
||||||
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"
|
||||||
|
integrity="sha384-iw3OoTErCYJJB9mCa8LNS2hbsQ7M3C0EpIsO/H5+EGAkPGc6rk+V8i04oW/K5xq0"
|
||||||
|
crossorigin="anonymous">
|
||||||
|
|
||||||
|
<link rel="stylesheet" href="css/variables.css">
|
||||||
|
<link rel="stylesheet" href="css/viewer.css">
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Early asset prefetch: start downloading the file immediately while the
|
||||||
|
rest of the page (CSS, fonts, scripts) loads in parallel. This shaves
|
||||||
|
off the entire page-load time from the perceived wait.
|
||||||
|
-->
|
||||||
|
<script>
|
||||||
|
(function() {
|
||||||
|
var params = new URLSearchParams(window.location.search);
|
||||||
|
var id = params.get('id');
|
||||||
|
if (id) {
|
||||||
|
var link = document.createElement('link');
|
||||||
|
link.rel = 'prefetch';
|
||||||
|
link.as = 'fetch';
|
||||||
|
link.crossOrigin = 'anonymous';
|
||||||
|
link.href = 'https://api.ifuntanhub.dev/assets/' + id;
|
||||||
|
document.head.appendChild(link);
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body class="viewer-body">
|
||||||
|
<header class="viewer-header">
|
||||||
|
<div class="viewer-header-left">
|
||||||
|
<a href="index.html#repository" class="btn-back">
|
||||||
|
<i class="fas fa-arrow-left"></i> Kembali ke Repositori
|
||||||
|
</a>
|
||||||
|
<div class="viewer-title-group">
|
||||||
|
<h1 id="viewer-title" class="viewer-title">Memuat dokumen…</h1>
|
||||||
|
<span id="viewer-type" class="viewer-type-badge"></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="viewer-header-right">
|
||||||
|
<a id="viewer-download" class="btn-download" href="#" download>
|
||||||
|
<i class="fas fa-download"></i> Unduh Berkas
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<main id="viewer-content" class="viewer-content">
|
||||||
|
<div class="viewer-loading">
|
||||||
|
<i class="fas fa-circle-notch fa-spin"></i>
|
||||||
|
<span>Memuat pratinjau berkas…</span>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
DOCX libraries are now lazy-loaded by viewer.js ONLY when type=docx.
|
||||||
|
PDFs no longer wait for ~350KB of unused scripts to download.
|
||||||
|
-->
|
||||||
|
<script src="js/viewer.js"></script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
Generated
-1
@@ -917,7 +917,6 @@
|
|||||||
"integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
|
"integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12"
|
"node": ">=12"
|
||||||
},
|
},
|
||||||
|
|||||||
Generated
+654
@@ -0,0 +1,654 @@
|
|||||||
|
lockfileVersion: '9.0'
|
||||||
|
|
||||||
|
settings:
|
||||||
|
autoInstallPeers: true
|
||||||
|
excludeLinksFromLockfile: false
|
||||||
|
|
||||||
|
importers:
|
||||||
|
|
||||||
|
.:
|
||||||
|
devDependencies:
|
||||||
|
vite:
|
||||||
|
specifier: ^7.1.0
|
||||||
|
version: 7.3.3
|
||||||
|
|
||||||
|
packages:
|
||||||
|
|
||||||
|
'@esbuild/aix-ppc64@0.27.7':
|
||||||
|
resolution: {integrity: sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [ppc64]
|
||||||
|
os: [aix]
|
||||||
|
|
||||||
|
'@esbuild/android-arm64@0.27.7':
|
||||||
|
resolution: {integrity: sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [arm64]
|
||||||
|
os: [android]
|
||||||
|
|
||||||
|
'@esbuild/android-arm@0.27.7':
|
||||||
|
resolution: {integrity: sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [arm]
|
||||||
|
os: [android]
|
||||||
|
|
||||||
|
'@esbuild/android-x64@0.27.7':
|
||||||
|
resolution: {integrity: sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [x64]
|
||||||
|
os: [android]
|
||||||
|
|
||||||
|
'@esbuild/darwin-arm64@0.27.7':
|
||||||
|
resolution: {integrity: sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [arm64]
|
||||||
|
os: [darwin]
|
||||||
|
|
||||||
|
'@esbuild/darwin-x64@0.27.7':
|
||||||
|
resolution: {integrity: sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [x64]
|
||||||
|
os: [darwin]
|
||||||
|
|
||||||
|
'@esbuild/freebsd-arm64@0.27.7':
|
||||||
|
resolution: {integrity: sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [arm64]
|
||||||
|
os: [freebsd]
|
||||||
|
|
||||||
|
'@esbuild/freebsd-x64@0.27.7':
|
||||||
|
resolution: {integrity: sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [x64]
|
||||||
|
os: [freebsd]
|
||||||
|
|
||||||
|
'@esbuild/linux-arm64@0.27.7':
|
||||||
|
resolution: {integrity: sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [arm64]
|
||||||
|
os: [linux]
|
||||||
|
|
||||||
|
'@esbuild/linux-arm@0.27.7':
|
||||||
|
resolution: {integrity: sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [arm]
|
||||||
|
os: [linux]
|
||||||
|
|
||||||
|
'@esbuild/linux-ia32@0.27.7':
|
||||||
|
resolution: {integrity: sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [ia32]
|
||||||
|
os: [linux]
|
||||||
|
|
||||||
|
'@esbuild/linux-loong64@0.27.7':
|
||||||
|
resolution: {integrity: sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [loong64]
|
||||||
|
os: [linux]
|
||||||
|
|
||||||
|
'@esbuild/linux-mips64el@0.27.7':
|
||||||
|
resolution: {integrity: sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [mips64el]
|
||||||
|
os: [linux]
|
||||||
|
|
||||||
|
'@esbuild/linux-ppc64@0.27.7':
|
||||||
|
resolution: {integrity: sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [ppc64]
|
||||||
|
os: [linux]
|
||||||
|
|
||||||
|
'@esbuild/linux-riscv64@0.27.7':
|
||||||
|
resolution: {integrity: sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [riscv64]
|
||||||
|
os: [linux]
|
||||||
|
|
||||||
|
'@esbuild/linux-s390x@0.27.7':
|
||||||
|
resolution: {integrity: sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [s390x]
|
||||||
|
os: [linux]
|
||||||
|
|
||||||
|
'@esbuild/linux-x64@0.27.7':
|
||||||
|
resolution: {integrity: sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [x64]
|
||||||
|
os: [linux]
|
||||||
|
|
||||||
|
'@esbuild/netbsd-arm64@0.27.7':
|
||||||
|
resolution: {integrity: sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [arm64]
|
||||||
|
os: [netbsd]
|
||||||
|
|
||||||
|
'@esbuild/netbsd-x64@0.27.7':
|
||||||
|
resolution: {integrity: sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [x64]
|
||||||
|
os: [netbsd]
|
||||||
|
|
||||||
|
'@esbuild/openbsd-arm64@0.27.7':
|
||||||
|
resolution: {integrity: sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [arm64]
|
||||||
|
os: [openbsd]
|
||||||
|
|
||||||
|
'@esbuild/openbsd-x64@0.27.7':
|
||||||
|
resolution: {integrity: sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [x64]
|
||||||
|
os: [openbsd]
|
||||||
|
|
||||||
|
'@esbuild/openharmony-arm64@0.27.7':
|
||||||
|
resolution: {integrity: sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [arm64]
|
||||||
|
os: [openharmony]
|
||||||
|
|
||||||
|
'@esbuild/sunos-x64@0.27.7':
|
||||||
|
resolution: {integrity: sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [x64]
|
||||||
|
os: [sunos]
|
||||||
|
|
||||||
|
'@esbuild/win32-arm64@0.27.7':
|
||||||
|
resolution: {integrity: sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [arm64]
|
||||||
|
os: [win32]
|
||||||
|
|
||||||
|
'@esbuild/win32-ia32@0.27.7':
|
||||||
|
resolution: {integrity: sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [ia32]
|
||||||
|
os: [win32]
|
||||||
|
|
||||||
|
'@esbuild/win32-x64@0.27.7':
|
||||||
|
resolution: {integrity: sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
cpu: [x64]
|
||||||
|
os: [win32]
|
||||||
|
|
||||||
|
'@rollup/rollup-android-arm-eabi@4.60.4':
|
||||||
|
resolution: {integrity: sha512-F5QXMSiFebS9hKZj02XhWLLnRpJ3B3AROP0tWbFBSj+6kCbg5m9j5JoHKd4mmSVy5mS/IMQloYgYxCuJC0fxEQ==}
|
||||||
|
cpu: [arm]
|
||||||
|
os: [android]
|
||||||
|
|
||||||
|
'@rollup/rollup-android-arm64@4.60.4':
|
||||||
|
resolution: {integrity: sha512-GxxTKApUpzRhof7poWvCJHRF51C67u1R7D6DiluBE8wKU1u5GWE8t+v81JvJYtbawoBFX1hLv5Ei4eVjkWokaw==}
|
||||||
|
cpu: [arm64]
|
||||||
|
os: [android]
|
||||||
|
|
||||||
|
'@rollup/rollup-darwin-arm64@4.60.4':
|
||||||
|
resolution: {integrity: sha512-tua0TaJxMOB1R0V0RS1jFZ/RpURFDJIOR2A6jWwQeawuFyS4gBW+rntLRaQd0EQ4bd6Vp44Z2rXW+YYDBsj6IA==}
|
||||||
|
cpu: [arm64]
|
||||||
|
os: [darwin]
|
||||||
|
|
||||||
|
'@rollup/rollup-darwin-x64@4.60.4':
|
||||||
|
resolution: {integrity: sha512-CSKq7MsP+5PFIcydhAiR1K0UhEI1A2jWXVKHPCBZ151yOutENwvnPocgVHkivu2kviURtCEB6zUQw0vs8RrhMg==}
|
||||||
|
cpu: [x64]
|
||||||
|
os: [darwin]
|
||||||
|
|
||||||
|
'@rollup/rollup-freebsd-arm64@4.60.4':
|
||||||
|
resolution: {integrity: sha512-+O8OkVdyvXMtJEciu2wS/pzm1IxntEEQx3z5TAVy4l32G0etZn+RsA48ARRrFm6Ri8fvqPQfgrvNxSjKAbnd3g==}
|
||||||
|
cpu: [arm64]
|
||||||
|
os: [freebsd]
|
||||||
|
|
||||||
|
'@rollup/rollup-freebsd-x64@4.60.4':
|
||||||
|
resolution: {integrity: sha512-Iw3oMskH3AfNuhU0MSN7vNbdi4me/NiYo2azqPz/Le16zHSa+3RRmliCMWWQmh4lcndccU40xcJuTYJZxNo/lw==}
|
||||||
|
cpu: [x64]
|
||||||
|
os: [freebsd]
|
||||||
|
|
||||||
|
'@rollup/rollup-linux-arm-gnueabihf@4.60.4':
|
||||||
|
resolution: {integrity: sha512-EIPRXTVQpHyF8WOo219AD2yEltPehLTcTMz2fn6JsatLYSzQf00hj3rulF+yauOlF9/FtM2WpkT/hJh/KJFGhA==}
|
||||||
|
cpu: [arm]
|
||||||
|
os: [linux]
|
||||||
|
libc: [glibc]
|
||||||
|
|
||||||
|
'@rollup/rollup-linux-arm-musleabihf@4.60.4':
|
||||||
|
resolution: {integrity: sha512-J3Yh9PzzF1Ovah2At+lHiGQdsYgArxBbXv/zHfSyaiFQEqvNv7DcW98pCrmdjCZBrqBiKrKKe2V+aaSGWuBe/w==}
|
||||||
|
cpu: [arm]
|
||||||
|
os: [linux]
|
||||||
|
libc: [musl]
|
||||||
|
|
||||||
|
'@rollup/rollup-linux-arm64-gnu@4.60.4':
|
||||||
|
resolution: {integrity: sha512-BFDEZMYfUvLn37ONE1yMBojPxnMlTFsdyNoqncT0qFq1mAfllL+ATMMJd8TeuVMiX84s1KbcxcZbXInmcO2mRg==}
|
||||||
|
cpu: [arm64]
|
||||||
|
os: [linux]
|
||||||
|
libc: [glibc]
|
||||||
|
|
||||||
|
'@rollup/rollup-linux-arm64-musl@4.60.4':
|
||||||
|
resolution: {integrity: sha512-pc9EYOSlOgdQ2uPl1o9PF6/kLSgaUosia7gOuS8mB69IxJvlclko1MECXysjs5ryez1/5zjYqx3+xYU0TU6R1A==}
|
||||||
|
cpu: [arm64]
|
||||||
|
os: [linux]
|
||||||
|
libc: [musl]
|
||||||
|
|
||||||
|
'@rollup/rollup-linux-loong64-gnu@4.60.4':
|
||||||
|
resolution: {integrity: sha512-NxnomyxYerDh5n4iLrNa+sH+Z+U4BMEE46V2PgQ/hoB909i8gV1M5wPojWg9fk1jWpO3IQnOs20K4wyZuFLEFQ==}
|
||||||
|
cpu: [loong64]
|
||||||
|
os: [linux]
|
||||||
|
libc: [glibc]
|
||||||
|
|
||||||
|
'@rollup/rollup-linux-loong64-musl@4.60.4':
|
||||||
|
resolution: {integrity: sha512-nbJnQ8a3z1mtmrwImCYhc6BGpThAyYVRQxw9uKSKG4wR6aAYno9sVjJ0zaZcW9BPJX1GbrDPf+SvdWjgTuDmnw==}
|
||||||
|
cpu: [loong64]
|
||||||
|
os: [linux]
|
||||||
|
libc: [musl]
|
||||||
|
|
||||||
|
'@rollup/rollup-linux-ppc64-gnu@4.60.4':
|
||||||
|
resolution: {integrity: sha512-2EU6acNrQLd8tYvo/LXW535wupT3m6fo7HKo6lr7ktQoItxTyOL1ZCR/GfGCuXl2vR+zmfI6eRXkSemafv+iVg==}
|
||||||
|
cpu: [ppc64]
|
||||||
|
os: [linux]
|
||||||
|
libc: [glibc]
|
||||||
|
|
||||||
|
'@rollup/rollup-linux-ppc64-musl@4.60.4':
|
||||||
|
resolution: {integrity: sha512-WeBtoMuaMxiiIrO2IYP3xs6GMWkJP2C0EoT8beTLkUPmzV1i/UcOSVw1d5r9KBODtHKilG5yFxsGRnBbK3wJ4A==}
|
||||||
|
cpu: [ppc64]
|
||||||
|
os: [linux]
|
||||||
|
libc: [musl]
|
||||||
|
|
||||||
|
'@rollup/rollup-linux-riscv64-gnu@4.60.4':
|
||||||
|
resolution: {integrity: sha512-FJHFfqpKUI3A10WrWKiFbBZ7yVbGT4q4B5o1qKFFojqpaYoh9LrQgqWCmmcxQzVSXYtyB5bzkXrYzlHTs21MYA==}
|
||||||
|
cpu: [riscv64]
|
||||||
|
os: [linux]
|
||||||
|
libc: [glibc]
|
||||||
|
|
||||||
|
'@rollup/rollup-linux-riscv64-musl@4.60.4':
|
||||||
|
resolution: {integrity: sha512-mcEl6CUT5IAUmQf1m9FYSmVqCJlpQ8r8eyftFUHG8i9OhY7BkBXSUdnLH5DOf0wCOjcP9v/QO93zpmF1SptCCw==}
|
||||||
|
cpu: [riscv64]
|
||||||
|
os: [linux]
|
||||||
|
libc: [musl]
|
||||||
|
|
||||||
|
'@rollup/rollup-linux-s390x-gnu@4.60.4':
|
||||||
|
resolution: {integrity: sha512-ynt3JxVd2w2buzoKDWIyiV1pJW93xlQic1THVLXilz429oijRpSHivZAgp65KBu+cMcgf1eVVjdnTLvPxgCuoQ==}
|
||||||
|
cpu: [s390x]
|
||||||
|
os: [linux]
|
||||||
|
libc: [glibc]
|
||||||
|
|
||||||
|
'@rollup/rollup-linux-x64-gnu@4.60.4':
|
||||||
|
resolution: {integrity: sha512-Boiz5+MsaROEWDf+GGEwF8VMHGhlUoQMtIPjOgA5fv4osupqTVnJteQNKJwUcnUog2G55jYXH7KZFFiJe0TEzQ==}
|
||||||
|
cpu: [x64]
|
||||||
|
os: [linux]
|
||||||
|
libc: [glibc]
|
||||||
|
|
||||||
|
'@rollup/rollup-linux-x64-musl@4.60.4':
|
||||||
|
resolution: {integrity: sha512-+qfSY27qIrFfI/Hom04KYFw3GKZSGU4lXus51wsb5EuySfFlWRwjkKWoE9emgRw/ukoT4Udsj4W/+xxG8VbPKg==}
|
||||||
|
cpu: [x64]
|
||||||
|
os: [linux]
|
||||||
|
libc: [musl]
|
||||||
|
|
||||||
|
'@rollup/rollup-openbsd-x64@4.60.4':
|
||||||
|
resolution: {integrity: sha512-VpTfOPHgVXEBeeR8hZ2O0F3aSso+JDWqTWmTmzcQKted54IAdUVbxE+j/MVxUsKa8L20HJhv3vUezVPoquqWjA==}
|
||||||
|
cpu: [x64]
|
||||||
|
os: [openbsd]
|
||||||
|
|
||||||
|
'@rollup/rollup-openharmony-arm64@4.60.4':
|
||||||
|
resolution: {integrity: sha512-IPOsh5aRYuLv/nkU51X10Bf75Bsf6+gZdx1X+QP5QM6lIJFHHqbHLG0uJn/hWthzo13UAc2umiUorqZy3axoZg==}
|
||||||
|
cpu: [arm64]
|
||||||
|
os: [openharmony]
|
||||||
|
|
||||||
|
'@rollup/rollup-win32-arm64-msvc@4.60.4':
|
||||||
|
resolution: {integrity: sha512-4QzE9E81OohJ/HKzHhsqU+zcYYojVOXlFMs1DdyMT6qXl/niOH7AVElmmEdUNHHS/oRkc++d5k6Vy85zFs0DEw==}
|
||||||
|
cpu: [arm64]
|
||||||
|
os: [win32]
|
||||||
|
|
||||||
|
'@rollup/rollup-win32-ia32-msvc@4.60.4':
|
||||||
|
resolution: {integrity: sha512-zTPgT1YuHHcd+Tmx7h8aml0FWFVelV5N54oHow9SLj+GfoDy/huQ+UV396N/C7KpMDMiPspRktzM1/0r1usYEA==}
|
||||||
|
cpu: [ia32]
|
||||||
|
os: [win32]
|
||||||
|
|
||||||
|
'@rollup/rollup-win32-x64-gnu@4.60.4':
|
||||||
|
resolution: {integrity: sha512-DRS4G7mi9lJxqEDezIkKCaUIKCrLUUDCUaCsTPCi/rtqaC6D/jjwslMQyiDU50Ka0JKpeXeRBFBAXwArY52vBw==}
|
||||||
|
cpu: [x64]
|
||||||
|
os: [win32]
|
||||||
|
|
||||||
|
'@rollup/rollup-win32-x64-msvc@4.60.4':
|
||||||
|
resolution: {integrity: sha512-QVTUovf40zgTqlFVrKA1uXMVvU2QWEFWfAH8Wdc48IxLvrJMQVMBRjuQyUpzZCDkakImib9eVazbWlC6ksWtJw==}
|
||||||
|
cpu: [x64]
|
||||||
|
os: [win32]
|
||||||
|
|
||||||
|
'@types/estree@1.0.8':
|
||||||
|
resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==}
|
||||||
|
|
||||||
|
esbuild@0.27.7:
|
||||||
|
resolution: {integrity: sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
hasBin: true
|
||||||
|
|
||||||
|
fdir@6.5.0:
|
||||||
|
resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==}
|
||||||
|
engines: {node: '>=12.0.0'}
|
||||||
|
peerDependencies:
|
||||||
|
picomatch: ^3 || ^4
|
||||||
|
peerDependenciesMeta:
|
||||||
|
picomatch:
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
fsevents@2.3.3:
|
||||||
|
resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
|
||||||
|
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
|
||||||
|
os: [darwin]
|
||||||
|
|
||||||
|
nanoid@3.3.12:
|
||||||
|
resolution: {integrity: sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==}
|
||||||
|
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
|
||||||
|
hasBin: true
|
||||||
|
|
||||||
|
picocolors@1.1.1:
|
||||||
|
resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
|
||||||
|
|
||||||
|
picomatch@4.0.4:
|
||||||
|
resolution: {integrity: sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==}
|
||||||
|
engines: {node: '>=12'}
|
||||||
|
|
||||||
|
postcss@8.5.15:
|
||||||
|
resolution: {integrity: sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==}
|
||||||
|
engines: {node: ^10 || ^12 || >=14}
|
||||||
|
|
||||||
|
rollup@4.60.4:
|
||||||
|
resolution: {integrity: sha512-WHeFSbZYsPu3+bLoNRUuAO+wavNlocOPf3wSHTP7hcFKVnJeWsYlCDbr3mTS14FCizf9ccIxXA8sGL8zKeQN3g==}
|
||||||
|
engines: {node: '>=18.0.0', npm: '>=8.0.0'}
|
||||||
|
hasBin: true
|
||||||
|
|
||||||
|
source-map-js@1.2.1:
|
||||||
|
resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
|
||||||
|
engines: {node: '>=0.10.0'}
|
||||||
|
|
||||||
|
tinyglobby@0.2.16:
|
||||||
|
resolution: {integrity: sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==}
|
||||||
|
engines: {node: '>=12.0.0'}
|
||||||
|
|
||||||
|
vite@7.3.3:
|
||||||
|
resolution: {integrity: sha512-/4XH147Ui7OGTjg3HbdWe5arnZQSbfuRzdr9Ec7TQi5I7R+ir0Rlc9GIvD4v0XZurELqA035KVXJXpR61xhiTA==}
|
||||||
|
engines: {node: ^20.19.0 || >=22.12.0}
|
||||||
|
hasBin: true
|
||||||
|
peerDependencies:
|
||||||
|
'@types/node': ^20.19.0 || >=22.12.0
|
||||||
|
jiti: '>=1.21.0'
|
||||||
|
less: ^4.0.0
|
||||||
|
lightningcss: ^1.21.0
|
||||||
|
sass: ^1.70.0
|
||||||
|
sass-embedded: ^1.70.0
|
||||||
|
stylus: '>=0.54.8'
|
||||||
|
sugarss: ^5.0.0
|
||||||
|
terser: ^5.16.0
|
||||||
|
tsx: ^4.8.1
|
||||||
|
yaml: ^2.4.2
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@types/node':
|
||||||
|
optional: true
|
||||||
|
jiti:
|
||||||
|
optional: true
|
||||||
|
less:
|
||||||
|
optional: true
|
||||||
|
lightningcss:
|
||||||
|
optional: true
|
||||||
|
sass:
|
||||||
|
optional: true
|
||||||
|
sass-embedded:
|
||||||
|
optional: true
|
||||||
|
stylus:
|
||||||
|
optional: true
|
||||||
|
sugarss:
|
||||||
|
optional: true
|
||||||
|
terser:
|
||||||
|
optional: true
|
||||||
|
tsx:
|
||||||
|
optional: true
|
||||||
|
yaml:
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
snapshots:
|
||||||
|
|
||||||
|
'@esbuild/aix-ppc64@0.27.7':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/android-arm64@0.27.7':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/android-arm@0.27.7':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/android-x64@0.27.7':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/darwin-arm64@0.27.7':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/darwin-x64@0.27.7':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/freebsd-arm64@0.27.7':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/freebsd-x64@0.27.7':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/linux-arm64@0.27.7':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/linux-arm@0.27.7':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/linux-ia32@0.27.7':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/linux-loong64@0.27.7':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/linux-mips64el@0.27.7':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/linux-ppc64@0.27.7':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/linux-riscv64@0.27.7':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/linux-s390x@0.27.7':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/linux-x64@0.27.7':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/netbsd-arm64@0.27.7':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/netbsd-x64@0.27.7':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/openbsd-arm64@0.27.7':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/openbsd-x64@0.27.7':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/openharmony-arm64@0.27.7':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/sunos-x64@0.27.7':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/win32-arm64@0.27.7':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/win32-ia32@0.27.7':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@esbuild/win32-x64@0.27.7':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@rollup/rollup-android-arm-eabi@4.60.4':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@rollup/rollup-android-arm64@4.60.4':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@rollup/rollup-darwin-arm64@4.60.4':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@rollup/rollup-darwin-x64@4.60.4':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@rollup/rollup-freebsd-arm64@4.60.4':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@rollup/rollup-freebsd-x64@4.60.4':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@rollup/rollup-linux-arm-gnueabihf@4.60.4':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@rollup/rollup-linux-arm-musleabihf@4.60.4':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@rollup/rollup-linux-arm64-gnu@4.60.4':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@rollup/rollup-linux-arm64-musl@4.60.4':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@rollup/rollup-linux-loong64-gnu@4.60.4':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@rollup/rollup-linux-loong64-musl@4.60.4':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@rollup/rollup-linux-ppc64-gnu@4.60.4':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@rollup/rollup-linux-ppc64-musl@4.60.4':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@rollup/rollup-linux-riscv64-gnu@4.60.4':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@rollup/rollup-linux-riscv64-musl@4.60.4':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@rollup/rollup-linux-s390x-gnu@4.60.4':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@rollup/rollup-linux-x64-gnu@4.60.4':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@rollup/rollup-linux-x64-musl@4.60.4':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@rollup/rollup-openbsd-x64@4.60.4':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@rollup/rollup-openharmony-arm64@4.60.4':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@rollup/rollup-win32-arm64-msvc@4.60.4':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@rollup/rollup-win32-ia32-msvc@4.60.4':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@rollup/rollup-win32-x64-gnu@4.60.4':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@rollup/rollup-win32-x64-msvc@4.60.4':
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@types/estree@1.0.8': {}
|
||||||
|
|
||||||
|
esbuild@0.27.7:
|
||||||
|
optionalDependencies:
|
||||||
|
'@esbuild/aix-ppc64': 0.27.7
|
||||||
|
'@esbuild/android-arm': 0.27.7
|
||||||
|
'@esbuild/android-arm64': 0.27.7
|
||||||
|
'@esbuild/android-x64': 0.27.7
|
||||||
|
'@esbuild/darwin-arm64': 0.27.7
|
||||||
|
'@esbuild/darwin-x64': 0.27.7
|
||||||
|
'@esbuild/freebsd-arm64': 0.27.7
|
||||||
|
'@esbuild/freebsd-x64': 0.27.7
|
||||||
|
'@esbuild/linux-arm': 0.27.7
|
||||||
|
'@esbuild/linux-arm64': 0.27.7
|
||||||
|
'@esbuild/linux-ia32': 0.27.7
|
||||||
|
'@esbuild/linux-loong64': 0.27.7
|
||||||
|
'@esbuild/linux-mips64el': 0.27.7
|
||||||
|
'@esbuild/linux-ppc64': 0.27.7
|
||||||
|
'@esbuild/linux-riscv64': 0.27.7
|
||||||
|
'@esbuild/linux-s390x': 0.27.7
|
||||||
|
'@esbuild/linux-x64': 0.27.7
|
||||||
|
'@esbuild/netbsd-arm64': 0.27.7
|
||||||
|
'@esbuild/netbsd-x64': 0.27.7
|
||||||
|
'@esbuild/openbsd-arm64': 0.27.7
|
||||||
|
'@esbuild/openbsd-x64': 0.27.7
|
||||||
|
'@esbuild/openharmony-arm64': 0.27.7
|
||||||
|
'@esbuild/sunos-x64': 0.27.7
|
||||||
|
'@esbuild/win32-arm64': 0.27.7
|
||||||
|
'@esbuild/win32-ia32': 0.27.7
|
||||||
|
'@esbuild/win32-x64': 0.27.7
|
||||||
|
|
||||||
|
fdir@6.5.0(picomatch@4.0.4):
|
||||||
|
optionalDependencies:
|
||||||
|
picomatch: 4.0.4
|
||||||
|
|
||||||
|
fsevents@2.3.3:
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
nanoid@3.3.12: {}
|
||||||
|
|
||||||
|
picocolors@1.1.1: {}
|
||||||
|
|
||||||
|
picomatch@4.0.4: {}
|
||||||
|
|
||||||
|
postcss@8.5.15:
|
||||||
|
dependencies:
|
||||||
|
nanoid: 3.3.12
|
||||||
|
picocolors: 1.1.1
|
||||||
|
source-map-js: 1.2.1
|
||||||
|
|
||||||
|
rollup@4.60.4:
|
||||||
|
dependencies:
|
||||||
|
'@types/estree': 1.0.8
|
||||||
|
optionalDependencies:
|
||||||
|
'@rollup/rollup-android-arm-eabi': 4.60.4
|
||||||
|
'@rollup/rollup-android-arm64': 4.60.4
|
||||||
|
'@rollup/rollup-darwin-arm64': 4.60.4
|
||||||
|
'@rollup/rollup-darwin-x64': 4.60.4
|
||||||
|
'@rollup/rollup-freebsd-arm64': 4.60.4
|
||||||
|
'@rollup/rollup-freebsd-x64': 4.60.4
|
||||||
|
'@rollup/rollup-linux-arm-gnueabihf': 4.60.4
|
||||||
|
'@rollup/rollup-linux-arm-musleabihf': 4.60.4
|
||||||
|
'@rollup/rollup-linux-arm64-gnu': 4.60.4
|
||||||
|
'@rollup/rollup-linux-arm64-musl': 4.60.4
|
||||||
|
'@rollup/rollup-linux-loong64-gnu': 4.60.4
|
||||||
|
'@rollup/rollup-linux-loong64-musl': 4.60.4
|
||||||
|
'@rollup/rollup-linux-ppc64-gnu': 4.60.4
|
||||||
|
'@rollup/rollup-linux-ppc64-musl': 4.60.4
|
||||||
|
'@rollup/rollup-linux-riscv64-gnu': 4.60.4
|
||||||
|
'@rollup/rollup-linux-riscv64-musl': 4.60.4
|
||||||
|
'@rollup/rollup-linux-s390x-gnu': 4.60.4
|
||||||
|
'@rollup/rollup-linux-x64-gnu': 4.60.4
|
||||||
|
'@rollup/rollup-linux-x64-musl': 4.60.4
|
||||||
|
'@rollup/rollup-openbsd-x64': 4.60.4
|
||||||
|
'@rollup/rollup-openharmony-arm64': 4.60.4
|
||||||
|
'@rollup/rollup-win32-arm64-msvc': 4.60.4
|
||||||
|
'@rollup/rollup-win32-ia32-msvc': 4.60.4
|
||||||
|
'@rollup/rollup-win32-x64-gnu': 4.60.4
|
||||||
|
'@rollup/rollup-win32-x64-msvc': 4.60.4
|
||||||
|
fsevents: 2.3.3
|
||||||
|
|
||||||
|
source-map-js@1.2.1: {}
|
||||||
|
|
||||||
|
tinyglobby@0.2.16:
|
||||||
|
dependencies:
|
||||||
|
fdir: 6.5.0(picomatch@4.0.4)
|
||||||
|
picomatch: 4.0.4
|
||||||
|
|
||||||
|
vite@7.3.3:
|
||||||
|
dependencies:
|
||||||
|
esbuild: 0.27.7
|
||||||
|
fdir: 6.5.0(picomatch@4.0.4)
|
||||||
|
picomatch: 4.0.4
|
||||||
|
postcss: 8.5.15
|
||||||
|
rollup: 4.60.4
|
||||||
|
tinyglobby: 0.2.16
|
||||||
|
optionalDependencies:
|
||||||
|
fsevents: 2.3.3
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
allowBuilds:
|
||||||
|
esbuild: set this to true or false
|
||||||
Reference in New Issue
Block a user