@@ -0,0 +1,163 @@
< script lang = "ts" >
import PendingForm from '$lib/components/PendingForm.svelte' ;
import SegmentedChoice from '$lib/components/SegmentedChoice.svelte' ;
let { data , form } = $props ();
const binaryOptions = [
{ value : '1' , label : 'ENABLED' },
{ value : '0' , label : 'DISABLED' }
];
const launchOptions = [
{ value : '1' , label : 'COMING SOON' },
{ value : '0' , label : 'PUBLIC LAUNCH' }
];
const sectionDefinitions = [
{ key : 'news' , label : 'NEWS' , description : 'Field reports, updates, and community reporting.' },
{ key : 'database' , label : 'DATABASE' , description : 'Public game-data registries and tactical lookup.' },
{ key : 'builds' , label : 'BUILDS' , description : 'Published loadout discovery and shared build views.' },
{ key : 'guides' , label : 'GUIDES' , description : 'Long-form field manuals and build walkthroughs.' },
{ key : 'builder' , label : 'BUILDER' , description : 'Loadout creation, calculation, and account saves.' }
] as const ;
const settingValue = ( key : string ) => Number ( data . settings [ key ]) ? '1' : '0' ;
let comingSoonEnabled = $state ( settingValue ( 'coming_soon_enabled' ));
let accountCreationEnabled = $state ( settingValue ( 'account_creation_enabled' ));
let sectionValues = $state < Record < string , string >>( Object . fromEntries (
sectionDefinitions . map (( section ) => [ section . key , settingValue ( ` ${ section . key } _enabled` )])
));
let reviewing = $state ( false );
let synchronizedAt = $state ( '' );
let handledResultToken = $state ( '' );
const synchronize = () => {
comingSoonEnabled = settingValue ( 'coming_soon_enabled' );
accountCreationEnabled = settingValue ( 'account_creation_enabled' );
sectionValues = Object . fromEntries ( sectionDefinitions . map (( section ) => [
section . key ,
settingValue ( ` ${ section . key } _enabled` )
]));
};
$effect (() => {
const updatedAt = String ( data . settings . updated_at );
if ( updatedAt === synchronizedAt ) return ;
synchronizedAt = updatedAt ;
synchronize ();
});
$effect (() => {
if ( ! form ? . siteSettingsSaved || ! form . resultToken || form . resultToken === handledResultToken ) return ;
handledResultToken = form . resultToken ;
reviewing = false ;
});
const currentValue = ( key : string ) => Number ( data . settings [ key ]) ? 'ENABLED' : 'DISABLED' ;
const proposedValue = ( value : string ) => value === '1' ? 'ENABLED' : 'DISABLED' ;
const changes = $derived . by (() => {
const rows = [
{
label : 'LAUNCH GATE' ,
before : Number ( data . settings . coming_soon_enabled ) ? 'COMING SOON' : 'PUBLIC LAUNCH' ,
after : comingSoonEnabled === '1' ? 'COMING SOON' : 'PUBLIC LAUNCH'
},
{
label : 'ACCOUNT CREATION' ,
before : currentValue ( 'account_creation_enabled' ),
after : proposedValue ( accountCreationEnabled )
},
... sectionDefinitions . map (( section ) => ({
label : section.label ,
before : currentValue ( ` ${ section . key } _enabled` ),
after : proposedValue ( sectionValues [ section . key ] ?? '0' )
}))
];
return rows . filter (( row ) => row . before !== row . after );
});
const date = ( value : unknown ) => value
? new Intl . DateTimeFormat ( undefined , { dateStyle : 'medium' , timeStyle : 'short' }). format ( new Date ( String ( value )))
: 'INITIAL CONFIGURATION' ;
</ script >
< svelte:head >< title > Site controls — ISAC</ title ></ svelte:head >
< div class = "module-toolbar site-control-toolbar" >
< div >
< p class = "eyebrow" > SYSTEM CONFIGURATION / RELEASE CONTROL</ p >
< h2 > SITE CONTROLS</ h2 >
< p > Coordinate public launch state, account registration, and access to major SHD.build sections.</ p >
</ div >
< div class = "site-control-state" >
< span > NETWORK STATE</ span >
< strong class:restricted = { comingSoonEnabled === '1' } > { comingSoonEnabled === '1' ? 'COMING SOON' : 'PUBLIC' } </strong >
< small > LAST UPDATED { date ( data . settings . updated_at )} </ small >
</ div >
</ div >
< div class = "site-control-grid" >
< section class = "site-control-panel launch-control" >
< header >< div >< span > 01 / LAUNCH GATE</ span >< h3 > PUBLIC NETWORK STATE</ h3 ></ div >< b class:restricted = { comingSoonEnabled === '1' } > { comingSoonEnabled === '1' ? 'GATED' : 'LIVE' } </b ></ header >
< p > The Coming Soon gate takes priority over individual sections for signed-out visitors. Signed-in agents bypass the launch gate, but section availability still applies.</ p >
< SegmentedChoice legend = "LAUNCH STATE" name = "launchStateDraft" options = { launchOptions } bind:value= { comingSoonEnabled } / >
</ section >
< section class = "site-control-panel account-control" >
< header >< div >< span > 02 / IDENTITY INTAKE</ span >< h3 > ACCOUNT CREATION</ h3 ></ div >< b class:restricted = { accountCreationEnabled === '0' } > { accountCreationEnabled === '1' ? 'OPEN' : 'CLOSED' } </b ></ header >
< p > Controls creation of new accounts across supported registration methods. Existing agents retain their configured sign-in methods.</ p >
< SegmentedChoice legend = "REGISTRATION STATE" name = "accountCreationDraft" options = { binaryOptions } bind:value= { accountCreationEnabled } / >
</ section >
< section class = "site-control-panel public-sections" >
< header >< div >< span > 03 / PUBLIC SECTIONS</ span >< h3 > SECTION AVAILABILITY</ h3 ></ div >< b > { sectionDefinitions . filter (( section ) => sectionValues [ section . key ] === '1' ). length } / { sectionDefinitions . length } ONLINE</ b ></ header >
< p > Section controls apply after the Launch Gate. Disabled sections disappear from navigation and reject direct access and mutations for every agent.</ p >
< div class = "section-control-list" >
{ #each sectionDefinitions as section }
< article >
< div >< strong > { section . label } </ strong >< small > { section . description } </ small ></ div >
< SegmentedChoice legend = { ` ${ section . label } STATE` } name= { ` ${ section . key } StateDraft` } options = { binaryOptions } bind:value= { sectionValues [ section . key ]} / >
</ article >
{ /each }
</ div >
</ section >
</ div >
{ #if form ? . siteSettingsError } < p class = "error" > { form . siteSettingsError } </ p > { /if }
< div class = "site-control-actions" >
< div >< span > CHANGE SET</ span >< b > { changes . length ? `$ { changes . length } PENDING ` : 'SYNCHRONIZED' } </ b >< small > UPDATED BY { data . settings . updated_by_name ?? 'SYSTEM BOOTSTRAP' } </ small ></ div >
< div class = "site-control-action-buttons" >
< a class = "secondary" href = "/coming-soon" > PREVIEW PAGE ↗</ a >
< button class = "primary" disabled = { ! changes . length } onclick= {() => reviewing = true } > REVIEW CHANGES →</ button >
</ div >
</ div >
{ #if reviewing }
< div class = "isac-modal-backdrop" role = "presentation" >
< div class = "isac-confirm-modal site-control-review" role = "dialog" aria-modal = "true" aria-labelledby = "site-control-review-title" >
< div class = "editor-title" >
< div >< p class = "eyebrow" > CONFIRM SITE ACCESS PROFILE</ p >< h3 id = "site-control-review-title" > { changes . length } OPERATIONAL CHANGE{ changes . length === 1 ? '' : 'S' } </ h3 ></ div >
< button type = "button" aria-label = "Close site control review" onclick = {() => reviewing = false } > × </button>
</ div >
< p > These settings change which public SHD.build services agents can reach. Review the full change set before deployment.</ p >
< div class = "site-control-change-list" >
{ #each changes as change }
< div >< strong > { change . label } </ strong >< span > { change . before } </ span >< b > →</ b >< span class:restricted = { change . after === 'DISABLED' || change . after === 'COMING SOON' } > { change . after } </span ></ div >
{ /each }
</ div >
< PendingForm action = "?/save" successMessage = "Site access settings updated." >
{ # snippet children ( pending )}
< input type = "hidden" name = "confirmation" value = "apply-site-access-settings" />
< input type = "hidden" name = "comingSoonEnabled" value = { comingSoonEnabled } / >
< input type = "hidden" name = "accountCreationEnabled" value = { accountCreationEnabled } / >
{ #each sectionDefinitions as section } < input type = "hidden" name = { ` ${ section . key } Enabled` } value= { sectionValues [ section . key ]} / > { /each }
< div class = "form-actions" >
< button type = "button" class = "secondary" onclick = {() => reviewing = false } disabled= { pending } > CANCEL</ button >
< button class = "primary" disabled = { pending || ! changes . length } aria-busy= { pending } > { pending ? 'APPLYING PROFILE…' : 'CONFIRM SITE CONTROLS →' } </ button >
</ div >
{ /snippet }
</ PendingForm >
</ div >
</ div >
{ /if }
< style >
. site-control-toolbar { align-items : stretch }. site-control-state { display : grid ; align-content : center ; min-width : 230 px ; padding : 10 px 14 px ; border : 1 px solid var ( -- shd - border ); border-left : 2 px solid var ( -- shd - orange ); background : var ( -- shd - surface )}. site-control-state span , . site-control-state small , . site-control-panel header span , . site-control-actions span , . site-control-actions small { color : var ( -- shd - text - muted ); font-size : var ( -- shd - type - telemetry ); font-weight : 900 ; letter-spacing : .8 px }. site-control-state strong { color : var ( -- shd - success ); font-size : 21 px }. site-control-state strong . restricted , . site-control-panel b . restricted , . site-control-change-list span . restricted { color : var ( -- shd - danger )}. site-control-grid { display : grid ; grid-template-columns : repeat ( 2 , minmax ( 0 , 1 fr )); gap : 8 px }. site-control-panel { display : grid ; align-content : start ; gap : 10 px ; padding : 12 px ; border : 1 px solid var ( -- shd - border ); background : var ( -- shd - surface )}. site-control-panel > header { display : flex ; justify-content : space-between ; gap : 12 px ; align-items : start ; padding-bottom : 9 px ; border-bottom : 1 px solid var ( -- shd - border )}. site-control-panel h3 , . site-control-panel p { margin : 0 }. site-control-panel > p { color : var ( -- shd - text - muted ); font-size : var ( -- shd - type - supporting )}. site-control-panel header b { color : var ( -- shd - success ); font-size : var ( -- shd - type - supporting ); letter-spacing : .8 px }. public-sections { grid-column : 1 /- 1 }. section-control-list { display : grid ; grid-template-columns : repeat ( 2 , minmax ( 0 , 1 fr )); gap : 7 px }. section-control-list article { display : grid ; grid-template-columns : minmax ( 0 , 1 fr ) minmax ( 220 px , .8 fr ); gap : 10 px ; align-items : center ; padding : 9 px ; border : 1 px solid var ( -- shd - border ); background : #0d1011 }. section-control-list article > div { display : grid ; gap : 2 px }. section-control-list small { color : var ( -- shd - text - muted )}. site-control-actions { display : flex ; justify-content : space-between ; align-items : center ; gap : 12 px ; margin-top : 9 px ; padding : 10 px ; border : 1 px solid var ( -- shd - border ); background : var ( -- shd - surface )}. site-control-actions > div : first-child { display : grid }. site-control-action-buttons { display : flex ; gap : 7 px }. site-control-action-buttons > a { display : flex ; align-items : center }. site-control-actions b { color : var ( -- shd - orange )}. site-control-review > p { margin : 0 ; padding : 12 px ; color : var ( -- shd - text - muted )}. site-control-change-list { display : grid ; gap : 4 px ; padding : 0 12 px 12 px }. site-control-change-list > div { display : grid ; grid-template-columns : minmax ( 140 px , 1 fr ) minmax ( 110 px , .7 fr ) 28 px minmax ( 110 px , .7 fr ); gap : 7 px ; align-items : center ; padding : 8 px ; border : 1 px solid var ( -- shd - border ); background : #0d1011 }. site-control-change-list > div > b { text-align : center ; color : var ( -- shd - orange )}. site-control-change-list span : last-child { font-weight : 900 }. site-control-review . form-actions { padding : 12 px ; border-top : 1 px solid var ( -- shd - border )}
@ media ( max-width : 900px ) {. site-control-grid { grid-template-columns : 1 fr }. public-sections { grid-column : auto }. section-control-list { grid-template-columns : 1 fr }}
@ media ( max-width : 650px ) {. site-control-state { min-width : 0 }. section-control-list article { grid-template-columns : 1 fr }. site-control-actions { display : grid }. site-control-action-buttons { display : grid ; grid-template-columns : 1 fr }. site-control-action-buttons >* { width : 100 % ; justify-content : center }. site-control-change-list > div { grid-template-columns : 1 fr auto 1 fr }. site-control-change-list strong { grid-column : 1 /- 1 }. site-control-change-list span : last-child { text-align : right }}
</ style >